-
-
Save nosoop/688303af4ddb7d193eaf644c6b6bb8f3 to your computer and use it in GitHub Desktop.
#!/usr/bin/python3 | |
''' | |
gameinfo cloner for TF2 | |
takes an existing TF2 dedicated server installation and an empty directory and links / copies / | |
modifies files in the latter to create a runnable server | |
''' | |
import os | |
import pathlib | |
import platform | |
import re | |
import sys | |
import shutil | |
ENGINE_PATH = pathlib.Path('..', 'core') | |
GAME_PATH = ENGINE_PATH / 'tf' | |
DEST_ENGINE_PATH = pathlib.Path('.') | |
DEST_GAME_PATH = DEST_ENGINE_PATH / 'tf' | |
DEST_GAME_PATH.mkdir(exist_ok = True) | |
# support absolute paths on linux, windows must use relative paths | |
# might want to use relative for both, but linux generally avoids spaces in file paths anyways | |
if platform.system() == 'Linux': | |
ENGINE_PATH = ENGINE_PATH.resolve() | |
GAME_PATH = GAME_PATH.resolve() | |
DEST_ENGINE_PATH = DEST_ENGINE_PATH.resolve() | |
DEST_GAME_PATH = DEST_GAME_PATH.resolve() | |
# process gameinfo.txt file, rewriting relative paths to point to our existing files | |
with (GAME_PATH / 'gameinfo.txt').open('rt') as input, (DEST_GAME_PATH / 'gameinfo.txt').open('wt') as output: | |
for line in filter(lambda s: not (s.strip().startswith('//') or len(s.strip()) == 0), input): | |
line = re.sub('tf/(.*\.vpk|bin)', r'{gamedir}/\1'.format(gamedir = GAME_PATH.as_posix()), line) | |
line = re.sub('game\+game_write.*$', r'game\t{gamedir}\n\t\t\tgame_write\t\ttf'.format(gamedir = GAME_PATH.as_posix()), line) | |
line = re.sub('\|all_source_engine_paths\|', r'{sedir}/'.format(sedir = ENGINE_PATH.as_posix()), line) | |
output.write(line) | |
# list of required files / directories | |
ENGINE_LINKS = [ "bin" ] | |
GAME_LINKS = [ "steam.inf" ] | |
GAME_COPIES = [ "scripts" ] | |
if platform.system() == 'Linux': | |
shutil.copy2(ENGINE_PATH / 'srcds_run', 'srcds_run') | |
ENGINE_LINKS.append("srcds_linux") | |
elif platform.system() == 'Windows': | |
shutil.copy2(ENGINE_PATH / 'srcds.exe', 'srcds.exe') | |
for link in filter(lambda l: not (DEST_ENGINE_PATH / l).exists(), ENGINE_LINKS): | |
os.symlink(ENGINE_PATH / link, DEST_ENGINE_PATH / link) | |
for link in filter(lambda l: not (DEST_GAME_PATH / l).exists(), GAME_LINKS): | |
# get a proper relative path on windows (os.symlink src is relative to target) | |
link_rel = pathlib.Path(os.path.relpath((GAME_PATH / link).resolve(), DEST_GAME_PATH)) | |
os.symlink(link_rel, DEST_GAME_PATH / link) | |
for file in filter(lambda f: not (DEST_GAME_PATH / f).exists(), GAME_COPIES): | |
shutil.copytree(GAME_PATH / file, DEST_GAME_PATH / file) |
#!/bin/bash | |
# cd into destination directory before running | |
# /path/to/clone_gameinfo.sh "/path/to/installed/tf" | |
TF_PATH=`realpath $1` | |
SOURCE_ENGINE_PATH=`realpath $TF_PATH/..` | |
process_gameinfo() { | |
# rewrite gameinfo, replace relative bin and .vpk paths | |
# also strips comments | |
sed -E "s:tf/(.*\.vpk|bin):${TF_PATH}/\1:" \ | |
| sed -E "s:game\+game_write.*$:game\t${TF_PATH}\n\t\t\tgame_write\t\ttf:" \ | |
| sed "s:|all_source_engine_paths|:${SOURCE_ENGINE_PATH}/:" \ | |
| grep -v '\t*//' | |
} | |
# cat "$1/gameinfo.txt" | process_gameinfo "$1" | |
# exit | |
# list of required directories | |
GAME_LINKS=("scripts") | |
ENGINE_LINKS=("bin" "srcds_linux") | |
if [ ! -d "tf" ]; then | |
mkdir tf | |
fi | |
cp "${SOURCE_ENGINE_PATH}/srcds_run" . | |
for link in "${ENGINE_LINKS[@]}"; do | |
ln -nsf "${SOURCE_ENGINE_PATH}/$link" "$link" | |
done | |
for link in "${GAME_LINKS[@]}"; do | |
# copy instead of symlink, server may update the file themselves | |
cp -r "${TF_PATH}/$link" "tf" | |
done | |
cat "${TF_PATH}/gameinfo.txt" | process_gameinfo > tf/gameinfo.txt |
#!/bin/bash | |
# script to set up a gameinfo-cloning TF2 server | |
# cd into your base directory | |
mkdir -p core/steam | |
pushd core | |
# install steam and install game above | |
wget -qO- "https://steamcdn-a.akamaihd.net/client/installer/steamcmd_linux.tar.gz" | tar xzf - -C steam | |
steam/steamcmd.sh +login anonymous +force_install_dir ../ +app_update 232250 +quit | |
popd |
#!/usr/bin/python3 | |
import os, vdf, subprocess, sys, signal | |
# insert default command line args here, or pass the args to the script | |
base_opts = [ ] | |
GAME_DIR = 'tf' | |
if __name__ == '__main__': | |
src_dir = os.path.dirname(os.path.realpath(__file__)) | |
os.chdir(src_dir) | |
plugins = [] | |
# add plugins from vdf files | |
for f in filter(lambda f: f.path.endswith('.vdf'), os.scandir(os.path.join(GAME_DIR, 'addons'))): | |
with open(f.path) as vdf_file: | |
data = vdf.load(vdf_file) | |
plugin_file = data.get('Plugin', {}).get('file', None) | |
if plugin_file: | |
plugins.append(plugin_file) | |
cmd = [ './srcds_run' ] | |
cmd.extend(base_opts) | |
cmd.extend(sys.argv[1:]) | |
for plugin in plugins: | |
print('Loading detected server plugin', plugin) | |
cmd.extend(['+plugin_load', os.path.join(src_dir, GAME_DIR, plugin)]) | |
# prevent sigint from ruining our day | |
s = signal.signal(signal.SIGINT, signal.SIG_IGN) | |
subprocess.call(cmd) | |
signal.signal(signal.SIGINT, s) |
#!/bin/sh | |
# Wrapper script to load addons. | |
cd "$(dirname "$0")" || return | |
if [ -f "$PWD/tf/addons/metamod.vdf" ]; then | |
echo "MetaMod:Source detected, loading plugin." | |
# TODO you'd want to read the actual locations from the VDF entries | |
LOAD_MMS="+plugin_load $(realpath .)/tf/addons/metamod/bin/server" | |
fi | |
./srcds_run "$@" "${LOAD_MMS}" |
Added a wrapper script; copy that to your cloned server's "root" directory and use that in place of srcds_run
.
A more robust solution would extract the paths from tf/addons/*.vdf
and adding those, but that's left as an exercise for those that care.
Added a Python version of the wrapper script, which is able to detect plugins that should be automatically loaded in.
Requires vdf
.
Installation instructions
cd
yourself into an empty directory where all the files will go (e.g.,/home/me/tf-servers
)- Run
install-tf-multi.sh
, will install TF2 game files and SteamCMD intocore
andcore/steam
mkdir
andcd
into a new "server instance" directory, runclone_gameinfo.sh ../core/tf
in the new directory to generate the rewritten gameinfo and symlink the necessary files- (Optional) Copy
srcds_wrapper.py
orsrcds_wrapper.sh
into the directory; intended as asrcds_run
wrapper that automatically handles loading Valve server plugins (e.g., MetaMod:Source) - (Optional) Install MM:S, SM, etc., they should work as expected. Source.Python needs the server bin symlinked, so do that if you're installing it as well.
- Run
srcds_run
(orsrcds_wrapper
) as you normally would
For new server instances cd
into the directory created in step 1 then start from step 3
Ported the clone_gameinfo
shell script to Python with support for Windows systems (run as Administrator to get the symlinks working).
Requires Python 3.6+, as we use path-like objects in file operations.
Same installation method as before, though at the moment the Python script takes no arguments and assumes the parent SRCDS installation is ../core
.
Note that clean server installs will not be able to load MetaMod:Source by default.
Add the following to some startup configuration file (must be run before a map loads in), or append it to your
./srcds_run
command line as an engine command; MM:S assumed to be installed in the default directory:plugin_load "${TF_PATH}/addons/metamod/bin/server";
Replace
${TF_PATH}
with the full path to your TF directory, obviously.