Skip to content

Instantly share code, notes, and snippets.

@nosoop nosoop/clone_gameinfo.py
Last active Apr 27, 2019

Embed
What would you like to do?
ctrl+c, ctrl+v srcds/tf2 but smol
#!/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}"
@nosoop

This comment has been minimized.

Copy link
Owner Author

nosoop commented Feb 26, 2018

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.

@nosoop

This comment has been minimized.

Copy link
Owner Author

nosoop commented Feb 27, 2018

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.

@nosoop

This comment has been minimized.

Copy link
Owner Author

nosoop commented Jul 11, 2018

Added a Python version of the wrapper script, which is able to detect plugins that should be automatically loaded in.
Requires vdf.

@nosoop

This comment has been minimized.

Copy link
Owner Author

nosoop commented Feb 9, 2019

Installation instructions

  1. cd yourself into an empty directory where all the files will go (e.g., /home/me/tf-servers)
  2. Run install-tf-multi.sh, will install TF2 game files and SteamCMD into core and core/steam
  3. mkdir and cd into a new "server instance" directory, run clone_gameinfo.sh ../core/tf in the new directory to generate the rewritten gameinfo and symlink the necessary files
  4. (Optional) Copy srcds_wrapper.py or srcds_wrapper.sh into the directory; intended as a srcds_run wrapper that automatically handles loading Valve server plugins (e.g., MetaMod:Source)
  5. (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.
  6. Run srcds_run (or srcds_wrapper) as you normally would

For new server instances cd into the directory created in step 1 then start from step 3

@nosoop

This comment has been minimized.

Copy link
Owner Author

nosoop commented Mar 5, 2019

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.