mirror of
https://github.com/Octops/quake-kube.git
synced 2026-04-07 18:30:33 +00:00
Initial commit
This commit is contained in:
221
internal/quake/server/config.go
Normal file
221
internal/quake/server/config.go
Normal file
@ -0,0 +1,221 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type GameType int
|
||||
|
||||
const (
|
||||
FreeForAll GameType = 0
|
||||
Tournament GameType = 1
|
||||
SinglePlayer GameType = 2
|
||||
TeamDeathmatch GameType = 3
|
||||
CaptureTheFlag GameType = 4
|
||||
)
|
||||
|
||||
func (gt GameType) String() string {
|
||||
switch gt {
|
||||
case FreeForAll:
|
||||
return "FreeForAll"
|
||||
case Tournament:
|
||||
return "Tournament"
|
||||
case SinglePlayer:
|
||||
return "SinglePlayer"
|
||||
case TeamDeathmatch:
|
||||
return "TeamDeathmatch"
|
||||
case CaptureTheFlag:
|
||||
return "CaptureTheFlag"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (gt *GameType) UnmarshalText(data []byte) error {
|
||||
switch string(data) {
|
||||
case "FreeForAll", "FFA":
|
||||
*gt = FreeForAll
|
||||
case "Tournament":
|
||||
*gt = Tournament
|
||||
case "SinglePlayer":
|
||||
*gt = SinglePlayer
|
||||
case "TeamDeathmatch":
|
||||
*gt = TeamDeathmatch
|
||||
case "CaptureTheFlag", "CTF":
|
||||
*gt = CaptureTheFlag
|
||||
default:
|
||||
return errors.Errorf("unknown GameType: %s", data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
FragLimit int `name:"fraglimit"`
|
||||
TimeLimit time.Duration `name:"timelimit"`
|
||||
|
||||
GameConfig
|
||||
ServerConfig
|
||||
}
|
||||
|
||||
type GameConfig struct {
|
||||
ForceRespawn bool `name:"g_forcerespawn"`
|
||||
GameType GameType `name:"g_gametype"`
|
||||
Inactivity time.Duration `name:"g_inactivity"`
|
||||
Log string `name:"g_log"`
|
||||
MOTD string `name:"g_motd"`
|
||||
QuadFactor int `name:"g_quadfactor"`
|
||||
WeaponRespawn int `name:"g_weaponrespawn"`
|
||||
}
|
||||
|
||||
type FileServerConfig struct {
|
||||
// allows people to base mods upon mods syntax to follow
|
||||
BaseGame string `name:"fs_basegame"`
|
||||
// set base path root C:\Program Files\Quake III Arena for files to be
|
||||
// downloaded from this path may change for TC's and MOD's
|
||||
BasePath string `name:"fs_basepath"`
|
||||
// toggle if files can be copied from servers or if client will download
|
||||
CopyFiles bool `name:"fs_copyfiles"`
|
||||
// possibly enables file server debug mode for download/uploads or
|
||||
// something
|
||||
Debug bool `name:"fs_debug"`
|
||||
// set gamedir set the game folder/dir default is baseq3
|
||||
Game string `name:"fs_game"`
|
||||
// possibly for TC's and MODS the default is the path to quake3.exe
|
||||
HomePath string `name:"fs_homepath"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
AllowDownload bool `name:"sv_allowDownload"`
|
||||
DownloadURL string `name:"sv_dlURL"`
|
||||
Hostname string `name:"sv_hostname"`
|
||||
MaxClients int `name:"sv_maxclients"`
|
||||
Password string `name:"rconpassword"`
|
||||
}
|
||||
|
||||
func (c *Config) Marshal() ([]byte, error) {
|
||||
return writeStruct(reflect.Indirect(reflect.ValueOf(c)))
|
||||
}
|
||||
|
||||
func writeStruct(v reflect.Value) ([]byte, error) {
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil, errors.Errorf("expected struct, received %T", v.Kind())
|
||||
}
|
||||
var b bytes.Buffer
|
||||
for i := 0; i < v.Type().NumField(); i++ {
|
||||
fv := v.Field(i)
|
||||
switch fv.Kind() {
|
||||
case reflect.Struct:
|
||||
data, err := writeStruct(fv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.Write(data)
|
||||
default:
|
||||
tv, ok := v.Type().Field(i).Tag.Lookup("name")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
s := toString(v.Type().Field(i).Name, fv)
|
||||
switch tv {
|
||||
case "sv_dlURL":
|
||||
if s != "" {
|
||||
b.WriteString(fmt.Sprintf("sets %s %s\n", tv, s))
|
||||
}
|
||||
default:
|
||||
b.WriteString(fmt.Sprintf("seta %s %s\n", tv, strconv.Quote(s)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
func toString(name string, v reflect.Value) string {
|
||||
switch val := v.Interface().(type) {
|
||||
case string:
|
||||
return val
|
||||
case int:
|
||||
return strconv.Itoa(val)
|
||||
case time.Duration:
|
||||
switch name {
|
||||
case "TimeLimit":
|
||||
return fmt.Sprintf("%d", int(val.Minutes()))
|
||||
default:
|
||||
return fmt.Sprintf("%d", int(val.Seconds()))
|
||||
}
|
||||
case bool:
|
||||
if val {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
case GameType:
|
||||
return fmt.Sprintf("%d", val)
|
||||
default:
|
||||
panic(fmt.Errorf("received unknown type %T", v.Interface()))
|
||||
}
|
||||
}
|
||||
|
||||
func Default() *Config {
|
||||
return &Config{
|
||||
TimeLimit: 15 * time.Minute,
|
||||
FragLimit: 25,
|
||||
GameConfig: GameConfig{
|
||||
Log: "",
|
||||
MOTD: "Welcome to Critical Stack",
|
||||
QuadFactor: 3,
|
||||
GameType: FreeForAll,
|
||||
WeaponRespawn: 3,
|
||||
Inactivity: 10 * time.Minute,
|
||||
ForceRespawn: false,
|
||||
},
|
||||
ServerConfig: ServerConfig{
|
||||
MaxClients: 12,
|
||||
Hostname: "quakekube",
|
||||
Password: "changeme",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Maps []Map
|
||||
|
||||
func (maps Maps) Marshal() ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
for i, m := range maps {
|
||||
cmds := []string{
|
||||
fmt.Sprintf("g_gametype %d", m.Type),
|
||||
}
|
||||
if m.Type == CaptureTheFlag && m.CaptureLimit != 0 {
|
||||
cmds = append(cmds, fmt.Sprintf("capturelimit %d", m.CaptureLimit))
|
||||
}
|
||||
if m.FragLimit != 0 {
|
||||
cmds = append(cmds, fmt.Sprintf("fraglimit %d", m.FragLimit))
|
||||
}
|
||||
if m.TimeLimit != 0 {
|
||||
cmds = append(cmds, fmt.Sprintf("timelimit %d", int(m.TimeLimit.Minutes())))
|
||||
}
|
||||
cmds = append(cmds, fmt.Sprintf("map %s", m.Name))
|
||||
nextmap := "d0"
|
||||
if i < len(maps)-1 {
|
||||
nextmap = fmt.Sprintf("d%d", i+1)
|
||||
}
|
||||
cmds = append(cmds, fmt.Sprintf("set nextmap vstr %s", nextmap))
|
||||
b.WriteString(fmt.Sprintf("set d%d \"seta %s\"\n", i, strings.Join(cmds, " ; ")))
|
||||
}
|
||||
b.WriteString("vstr d0")
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
type Map struct {
|
||||
Name string `json:"name"`
|
||||
Type GameType `json:"type"`
|
||||
|
||||
CaptureLimit int `json:"captureLimit"`
|
||||
FragLimit int `json:"fragLimit"`
|
||||
TimeLimit time.Duration `json:"timeLimit"`
|
||||
}
|
||||
80
internal/quake/server/config_test.go
Normal file
80
internal/quake/server/config_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
const expectedConfig = `seta fraglimit "25"
|
||||
seta timelimit "15"
|
||||
seta g_forcerespawn "0"
|
||||
seta g_gametype "0"
|
||||
seta g_inactivity "600"
|
||||
seta g_log ""
|
||||
seta g_motd "Welcome to Critical Stack"
|
||||
seta g_quadfactor "3"
|
||||
seta g_weaponrespawn "3"
|
||||
seta sv_allowDownload "0"
|
||||
seta sv_hostname "quakekube"
|
||||
seta sv_maxclients "12"
|
||||
seta rconpassword "changeme"
|
||||
`
|
||||
|
||||
func TestConfigMarshal(t *testing.T) {
|
||||
c := Default()
|
||||
|
||||
data, err := c.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Printf("data = %s\n", data)
|
||||
if diff := cmp.Diff(string(data), expectedConfig); diff != "" {
|
||||
t.Fatalf(diff)
|
||||
}
|
||||
}
|
||||
|
||||
const mapConfig = `- name: q3dm7
|
||||
type: FreeForAll
|
||||
- name: q3dm17
|
||||
type: FreeForAll
|
||||
- name: q3wctf1
|
||||
type: CaptureTheFlag
|
||||
captureLimit: 8
|
||||
- name: q3tourney2
|
||||
type: Tournament
|
||||
- name: q3wctf3
|
||||
type: CaptureTheFlag
|
||||
captureLimit: 8
|
||||
- name: ztn3tourney1
|
||||
type: Tournament
|
||||
`
|
||||
|
||||
const expectedMapConfig = `set d0 "seta g_gametype 0 ; map q3dm7 ; set nextmap vstr d1"
|
||||
set d1 "seta g_gametype 0 ; map q3dm17 ; set nextmap vstr d2"
|
||||
set d2 "seta g_gametype 4 ; capturelimit 8 ; map q3wctf1 ; set nextmap vstr d3"
|
||||
set d3 "seta g_gametype 1 ; map q3tourney2 ; set nextmap vstr d4"
|
||||
set d4 "seta g_gametype 4 ; capturelimit 8 ; map q3wctf3 ; set nextmap vstr d5"
|
||||
set d5 "seta g_gametype 1 ; map ztn3tourney1 ; set nextmap vstr d0"
|
||||
vstr d0`
|
||||
|
||||
func TestMapRead(t *testing.T) {
|
||||
var maps Maps
|
||||
if err := yaml.Unmarshal([]byte(mapConfig), &maps); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, m := range maps {
|
||||
fmt.Printf("m = %+v\n", m)
|
||||
}
|
||||
data, err := maps.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(string(data), expectedMapConfig); diff != "" {
|
||||
t.Fatalf(diff)
|
||||
}
|
||||
}
|
||||
178
internal/quake/server/eula.go
Normal file
178
internal/quake/server/eula.go
Normal file
@ -0,0 +1,178 @@
|
||||
package server
|
||||
|
||||
const Q3DemoEULA = `LIMITED USE SOFTWARE LICENSE AGREEMENT
|
||||
|
||||
This Limited Use Software License Agreement (the "Agreement") is a legal
|
||||
agreement between you, the end-user, and Id Software, Inc. ("ID"). BY
|
||||
CONTINUING THE INSTALLATION OF THIS GAME DEMO PROGRAM ENTITLED QUAKE III:
|
||||
ARENA (THE "SOFTWARE"), BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING
|
||||
OR COPYING THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE, COMPUTER RAM OR
|
||||
OTHER STORAGE, YOU ARE AGREEING TO BE BOUND BY THE TERMS OF THIS
|
||||
AGREEMENT.
|
||||
|
||||
1. Grant of License. Subject to the terms and provisions of this
|
||||
Agreement, ID grants to you the non-exclusive and limited right to use the
|
||||
Software only in executable or object code form. The term "Software"
|
||||
includes all elements of the Software, including, without limitation, data
|
||||
files and screen displays. You are not receiving any ownership or
|
||||
proprietary right, title or interest in or to the Software or the
|
||||
copyright, trademarks, or other rights related thereto. For purposes of
|
||||
this section, "use" means loading the Software into RAM and/or onto
|
||||
computer hard drive, as well as installation of the Software on a hard
|
||||
disk or other storage device and means the uses permitted in section 3.
|
||||
hereinbelow. You agree that the Software will not be shipped,
|
||||
transferred or exported into any country in violation of the U.S. Export
|
||||
Administration Act (or any other law governing such matters) by you or
|
||||
anyone at your direction and that you will not utilize and will not
|
||||
authorize anyone to utilize, in any other manner, the Software in
|
||||
violation of any applicable law. The Software may not be downloaded
|
||||
or otherwise exported or exported into (or to a national or resident
|
||||
of) any country to which the U.S. has embargoed goods or to anyone
|
||||
or into any country who/which are prohibited, by applicable law, from
|
||||
receiving such property.
|
||||
|
||||
2. Prohibitions. You, either directly or indirectly, shall not do
|
||||
any of the following acts:
|
||||
|
||||
a. rent the Software;
|
||||
|
||||
b. sell the Software;
|
||||
|
||||
c. lease or lend the Software;
|
||||
|
||||
d. offer the Software on a "pay-per-play" basis;
|
||||
|
||||
e. distribute the Software (except as permitted by section 3.
|
||||
hereinbelow);
|
||||
|
||||
f. in any other manner and through any medium whatsoever
|
||||
commercially exploit the Software or use the Software for any commercial
|
||||
purpose;
|
||||
|
||||
g. disassemble, reverse engineer, decompile, modify or alter the
|
||||
Software including, without limitation, creating or developing extra or
|
||||
add-on levels for the Software;
|
||||
|
||||
h. translate the Software;
|
||||
|
||||
i. reproduce or copy the Software (except as permitted by section
|
||||
3. hereinbelow);
|
||||
|
||||
j. publicly display the Software;
|
||||
|
||||
k. prepare or develop derivative works based upon the Software; or
|
||||
|
||||
l. remove or alter any legal notices or other markings or
|
||||
legends, such as trademark and copyright notices, affixed on or within
|
||||
the Software.
|
||||
|
||||
3. Permitted Distribution and Copying. So long as this Agreement
|
||||
accompanies each copy you make of the Software, and so long as you fully
|
||||
comply, at all times, with this Agreement, ID grants to you the
|
||||
non-exclusive and limited right to copy the Software and to distribute
|
||||
such copies of the Software free of charge for non-commercial purposes
|
||||
which shall include the free of charge distribution of copies of the
|
||||
Software as mounted on the covers of magazines; provided, however, you
|
||||
shall not copy or distribute the Software in any infringing manner or
|
||||
in any manner which violates any law or third party right and you shall
|
||||
not distribute the Software together with any material which is
|
||||
infringing, libelous, defamatory, obscene, false, misleading, or
|
||||
otherwise illegal or unlawful. You agree to label conspicuously as
|
||||
"SHAREWARE" or "DEMO" each CD or other non-electronic copy of the
|
||||
Software that you make and distribute. ID reserves all rights not
|
||||
granted in this Agreement. You shall not commercially distribute the
|
||||
Software unless you first enter into a separate contract with ID, a
|
||||
copy of which you may request, but which ID may decline to execute.
|
||||
For more information visit www.quake3arena.com.
|
||||
|
||||
4. Intellectual Property Rights. The Software and all copyrights,
|
||||
trademarks and all other conceivable intellectual property rights related
|
||||
to the Software are owned by ID and are protected by United States
|
||||
copyright laws, international treaty provisions and all applicable law,
|
||||
such as the Lanham Act. You must treat the Software like any other
|
||||
copyrighted material, as required by 17 U.S.C., §101 et seq. and other
|
||||
applicable law. You agree to use your best efforts to see that any user
|
||||
of the Software licensed hereunder complies with this Agreement. You
|
||||
agree that you are receiving a copy of the Software by license only
|
||||
and not by sale and that the "first sale" doctrine of 17 U.S.C. §109
|
||||
does not apply to your receipt or use of the Software.
|
||||
|
||||
5. NO WARRANTIES. ID DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS OR
|
||||
IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE WITH RESPECT TO THE
|
||||
SOFTWARE. ID DOES NOT WARRANT THAT THE OPERATION OF THE SOFTWARE WILL BE
|
||||
UNINTERRUPTED OR ERROR FREE OR THAT THE SOFTWARE WILL MEET YOUR SPECIFIC
|
||||
REQUIREMENTS. ADDITIONAL STATEMENTS SUCH AS PRESENTATIONS, WHETHER ORAL
|
||||
OR WRITTEN, DO NOT CONSTITUTE WARRANTIES BY ID AND SHOULD NOT BE RELIED
|
||||
UPON. THIS SECTION 5. SHALL SURVIVE CANCELLATION OR TERMINATION OF THIS
|
||||
AGREEMENT.
|
||||
|
||||
6. Governing Law, Venue, Indemnity and Liability Limitation. This
|
||||
Agreement shall be construed in accordance with and governed by the
|
||||
applicable laws of the State of Texas and applicable United States federal
|
||||
law. Copyright and other proprietary matters will be governed by United
|
||||
States laws and international treaties. Exclusive venue for all
|
||||
litigation regarding this Agreement shall be in Dallas County, Texas
|
||||
and you agree to submit to the jurisdiction of the courts in Dallas,
|
||||
Texas for any such litigation. You agree to indemnify, defend and hold
|
||||
harmless ID and ID's officers, employees, directors, agents, licensees
|
||||
(excluding you), successors and assigns from and against all losses,
|
||||
lawsuits, damages, causes of action and claims relating to and/or
|
||||
arising from your breach of this Agreement. You agree that your
|
||||
unauthorized use of the Software, or any part thereof, may immediately
|
||||
and irreparably damage ID such that ID could not be adequately
|
||||
compensated solely by a monetary award and that at ID's option ID shall
|
||||
be entitled to an injunctive order, in addition to all other available
|
||||
remedies including a monetary award, appropriately restraining and/or
|
||||
prohibiting such unauthorized use without the necessity of ID posting
|
||||
bond or other security. IN ANY CASE, ID AND ID'S OFFICERS, EMPLOYEES,
|
||||
DIRECTORS, AGENTS, LICENSEES, SUBLICENSEES, SUCCESSORS AND ASSIGNS
|
||||
SHALL NOT BE LIABLE FOR LOSS OF DATA, LOSS OF PROFITS, LOST SAVINGS,
|
||||
SPECIAL, INCIDENTAL, CONSEQUENTIAL, INDIRECT, PUNITIVE OR OTHER SIMILAR
|
||||
DAMAGES ARISING FROM ANY ALLEGED CLAIM FOR BREACH OF WARRANTY, BREACH
|
||||
OF CONTRACT, NEGLIGENCE, STRICT PRODUCT LIABILITY, OR OTHER LEGAL
|
||||
THEORY EVEN IF ID OR ITS AGENT HAVE BEEN ADVISED OF THE POSSIBILITY
|
||||
OF SUCH DAMAGES OR EVEN IF SUCH DAMAGES ARE FORESEEABLE, OR LIABLE
|
||||
FOR ANY CLAIM BY ANY OTHER PARTY. Some jurisdictions do not allow
|
||||
the exclusion or limitation of incidental or consequential damages,
|
||||
so the above limitation or exclusion may not apply to you. This
|
||||
Section 6. shall survive cancellation or termination of this Agreement.
|
||||
|
||||
7. U.S. Government Restricted Rights. To the extent applicable,
|
||||
the United States Government shall only have those rights to use the
|
||||
Software as expressly stated and expressly limited and restricted in
|
||||
this Agreement, as provided in 48 C.F.R. §§ 227.7201 through 227.7204,
|
||||
inclusive.
|
||||
|
||||
8. General Provisions. Neither this Agreement nor any part or
|
||||
portion hereof shall be assigned or sublicensed by you. ID may assign its
|
||||
rights under this Agreement in ID's sole discretion. Should any provision
|
||||
of this Agreement be held to be void, invalid, unenforceable or illegal by
|
||||
a court of competent jurisdiction, the validity and enforceability of the
|
||||
other provisions shall not be affected thereby. If any provision is
|
||||
determined to be unenforceable by a court of competent jurisdiction, you
|
||||
agree to a modification of such provision to provide for enforcement of
|
||||
the provision's intent, to the extent permitted by applicable law.
|
||||
Failure of ID to enforce any provision of this Agreement shall not
|
||||
constitute or be construed as a waiver of such provision or of the right
|
||||
to enforce such provision. Immediately upon your failure to comply with
|
||||
or breach of any term or provision of this Agreement, THIS AGREEMENT
|
||||
AND YOUR LICENSE SHALL AUTOMATICALLY TERMINATE, WITHOUT NOTICE, AND ID
|
||||
MAY PURSUE ALL RELIEF AND REMEDIES AGAINST YOU WHICH ARE AVAILABLE UNDER
|
||||
APPLICABLE LAW AND/OR THIS AGREEMENT. In the event this Agreement is
|
||||
terminated, you shall have no right to use the Software, in any manner,
|
||||
and you shall immediately destroy all copies of the Software in your
|
||||
possession, custody or control.
|
||||
|
||||
YOU ACKNOWLEDGE THAT YOU HAVE READ THIS AGREEMENT, YOU UNDERSTAND THIS
|
||||
AGREEMENT, AND UNDERSTAND THAT BY CONTINUING THE INSTALLATION OF THE
|
||||
SOFTWARE, BY LOADING OR RUNNING THE SOFTWARE, OR BY PLACING OR COPYING
|
||||
THE SOFTWARE ONTO YOUR COMPUTER HARD DRIVE OR RAM, YOU AGREE TO BE BOUND
|
||||
BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. YOU FURTHER AGREE THAT,
|
||||
EXCEPT FOR WRITTEN SEPARATE AGREEMENTS BETWEEN ID AND YOU, THIS
|
||||
AGREEMENT IS A COMPLETE AND EXCLUSIVE STATEMENT OF THE RIGHTS AND
|
||||
LIABILITIES OF THE PARTIES HERETO. THIS AGREEMENT SUPERSEDES ALL PRIOR
|
||||
ORAL AGREEMENTS, PROPOSALS OR UNDERSTANDINGS, AND ANY OTHER
|
||||
COMMUNICATIONS BETWEEN ID AND YOU RELATING TO THE SUBJECT MATTER OF
|
||||
THIS AGREEMENT.
|
||||
`
|
||||
18
internal/quake/server/server.go
Normal file
18
internal/quake/server/server.go
Normal file
@ -0,0 +1,18 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func Start(ctx context.Context, dir string) error {
|
||||
cmd := exec.CommandContext(ctx, "ioq3ded", "+set", "dedicated", "1", "+exec", "server.cfg", "+exec", "maps.cfg")
|
||||
cmd.Dir = dir
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
return cmd.Wait()
|
||||
}
|
||||
Reference in New Issue
Block a user