First Commit

This commit is contained in:
Carl Manzi 2021-01-02 23:30:39 -05:00
commit 8ffa4dacbd
16 changed files with 1383 additions and 0 deletions

View file

@ -0,0 +1,10 @@
[Unit]
Description=t2bouncer (restarts t2server at configured time)
Wants=t2bouncer.timer
[Service]
Type=oneshot
ExecStart=/usr/local/bin/t2bouncer
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,10 @@
[Unit]
Description=t2bouncer timer
BindsTo=t2server.service
[Timer]
Unit=t2bouncer.service
OnCalendar=*:00:00
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,36 @@
[Unit]
Description=Tribes 2 Dedicated Server
Requires=network.target
After=network.target
Wants=t2bouncer.timer
Before=t2bouncer.timer
[Service]
Type=simple
Environment=TERM=xterm-256color
Environment=PYTHONUNBUFFERED=1
User=t2server
CPUAffinity=0
ExecStart=/usr/local/bin/t2server
ExecStop=/usr/bin/wineserver -k
Restart=on-failure
RestartSec=15s
TimeoutStopSec=60s
WorkingDirectory=/opt/t2server/GameData
LogsDirectory=t2server
# Below settings help lock down the service for security
ProtectSystem=full
ProtectHome=true
SystemCallFilter=@system-service
NoNewPrivileges=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target

44
etc/t2server/config.yaml Normal file
View file

@ -0,0 +1,44 @@
---
## ServerPrefs indicates the server config file in /etc/t2server/serverprefs
## to use. This is case-sensitive and must match the filename exactly.
ServerPrefs: Classic_CTF.cs
## Tribes 2 servers tend to get unstable after a couple weeks of being online.
## Here you can specify a day and hour to automatically bounce the server.
## Set RestartTime to the hour of the day, 0-23, to cycle the server
## (eg. 4=4:00am, 16=4:00pm) or False to disable. Set RestartDay to the
## three-letter abbreviation of the day on which the server should be restarted
## (Sun, Mon, Tue, Wed, Thu, Fri, or Sat). This will be ignored if
## RestartTime = False.
RestartTime: False
RestartDay: Mon
## Set Mod to the directory name of the mod to be loaded, or 'base' for
## vanilla (but why?). This is case-sensitive and must match the subdirectory
## name exactly.
Mod: Classic
## Set Public to False to host a LAN-only game, or to True to host a public
## game which will be registered with the TribesNext master.
Public: False
## Tribes 2 servers try to detect descrepencies between their own IP and the IP
## that the client believes it's connecting to as possible man-in-the-middle
## attacks, however this often interfers with connections when the server is
## NATed or multi-homed. Enable OverrideMITM to effectively disable this
## detection. This setting has no effect if Public = False.
OverrideMITM: True
## Configure a custom map rotation list. The standard Mission Types are
## "Bounty", "CnH" (Capture and Hold), "CTF" (Capture the Flag), "DM"
## (Deathmatch), "DnD" (Defend and Destroy), "Hunters", "Rabbit", "Siege",
## "TeamHunters", and "TeamRabbit". Your server will always launch with the
## MissionType and Map specified in your serverprefs file ($Host::MissionType
## and $Host::Map), so make sure MissionType matches $Host::MissionType and
## the first map in MapList matches $Host::Map. If you're running a mod that
## handles map rotation, set these to 'False'.
## Example:
## MissionType: CTF
## MapList: ["Katabatic", "Minotaur", "Tombstone"]
MissionType: False
MapList: False

View file

@ -0,0 +1,52 @@
$Host::AdminPassword = "changethis";
$Host::allowAdminPlayerVotes = "0";
$Host::AllowMapScript = 1;
$Host::BanTime = 1800;
$Host::BotCount = 7;
$Host::BotsEnabled = "0";
$Host::ClassicSuperAdminPassword = "changethis";
$Host::CRCTextures = 0;
$Host::FloodProtectionEnabled = 1;
$Host::GameName = "Tribes 2 Classic CTF Server";
$Host::HiVisibility = "1";
$Host::holoName1 = "Storm";
$Host::holoName2 = "Inferno";
$Host::holoName3 = "Starwolf";
$Host::holoName4 = "DSword";
$Host::holoName5 = "BloodEagle";
$Host::holoName6 = "Harbinger";
$Host::Info = "This is a Tribes 2 Classic Server.";
$Host::KickBanTime = 300;
$Host::Map = "Minotaur";
$Host::MarkDnDObjectives = 1;
$Host::MaxMessageLen = 120;
$Host::MaxPlayers = "64";
$Host::MissionType = "CTF";
$Host::NoSmurfs = 0;
$Host::PlayerRespawnTimeout = "60";
$Host::Port = "28000";
$Host::PureServer = 0;
$Host::TeamDamageOn = "1";
$Host::TeamName0 = "Unassigned";
$Host::TeamName1 = "Storm";
$Host::TeamName2 = "Inferno";
$Host::TeamName3 = "Starwolf";
$Host::TeamName4 = "Diamond Sword";
$Host::TeamName5 = "Blood Eagle";
$Host::TeamName6 = "Phoenix";
$Host::TeamSkin0 = "blank";
$Host::TeamSkin1 = "base";
$Host::TeamSkin2 = "baseb";
$Host::TeamSkin3 = "swolf";
$Host::TeamSkin4 = "dsword";
$Host::TeamSkin5 = "beagle";
$Host::TeamSkin6 = "cotp";
$Host::TimeLimit = "200";
$Host::TN::beat = 3;
$Host::TN::echo = 1;
$Host::TournamentMode = "0";
$Host::UseHighPerformanceCounter = 0;
$Host::VotePassPercent = "60";
$Host::VoteSpread = 20;
$Host::VoteTime = "30";
$Host::warmupTime = "20";

304
setup Executable file
View file

@ -0,0 +1,304 @@
#!/usr/bin/env -S python3 -B
from os import system, unlink, symlink, makedirs, geteuid, getcwd, chmod, rename
from os.path import isfile, isdir
from time import time
from shutil import copyfile, rmtree
from glob import iglob
from pwd import getpwnam as getuser
from t2support import *
if geteuid() != 0:
bail(f"This script must be run with sudo or as root.\n")
pwd = getcwd()
installer_mirror_list = [
"https://www.the-construct.net/downloads/tribes2/tribes2gsi.exe",
"http://spinfusor.ch/tribes2/setup/tribes2_gsi.exe",
"https://adamantis.keybase.pub/Abandonware/Tribes2/tribes2gsi.exe?dl=1",
"http://xfer1.the-construct.net/tribes2/tribes2gsi.exe",
"http://dl.rawr32.net/tribes2gsi.exe",
"https://files.playt2.com/Install/tribes2gsi.exe",
"https://gamestand.net/dl/tribes-2/?ind=1527034109041&filename=tribes2_gsi.exe&wpdmdl=165&refresh=5fb884009d78b1605927936",
"http://www.tribes2stats.com/files/tribes2_gsi.exe"
]
tnpatch_mirror_list = [
"http://www.tribesnext.com/files/TribesNext_rc2a.exe",
"https://keybase.pub/adamantis/Abandonware/Tribes2/TribesNext_rc2a.exe",
"https://files.playt2.com/Install/TribesNext_rc2a.exe",
"https://gamestand.net/dl/tribes-2/?ind=1527034087554&filename=TribesNext_rc2a.exe&wpdmdl=165&refresh=5fb884009d73d1605927936",
"http://www.tribes2stats.com/files/patches/TribesNext_rc2a.exe",
"http://files.nastyhobbit.org/t2-installer/TribesNext_rc2a.exe",
"http://www.the-flet.com/dynamix/t2/TribesNext_rc2a.exe",
"http://cdn.net-load.com/TribesNext_rc2a.exe",
"https://starsiege.pw/_tribes2/TribesNext_rc2a.exe"
]
installer_checksum = "93460541ddd3bdff9b30829ba04f2186"
tnpatch_checksum = "3bec757215cd29b37d85b567edf8d693"
def md5sum(filename):
""" Return the md5 checksum of the given file """
with open(filename, "rb") as file:
file_hash = md5()
chunk = file.read(8192)
while chunk:
file_hash.update(chunk)
chunk = file.read(8192)
return file_hash.hexdigest()
def download_file(url, filename):
""" Download url with progress meter and save to filename """
req = get(url, stream=True)
with open(filename, 'wb') as outfile:
pbar = tqdm(total=int(req.headers['Content-Length']), unit="B", unit_scale=True, unit_divisor=1024, position=0, desc=filename.split("/")[-1])
for chunk in req.iter_content(chunk_size=1024*1024):
if chunk:
pbar.update(len(chunk))
outfile.write(chunk)
pbar.close()
return filename
if __name__ == "__main__":
action=menu(["~~[C]ontinue","[Q]uit"],header="This script will install Tribes 2 for use as a dedicated server.")
if action == "Q": bail()
# Check if user exists
try:
user_info = getuser(user)
except KeyError:
user_info = False
# Create or repurpose user
if user_info:
if user_info.pw_dir == install_dir:
pwarn(f"User '{user}' exists and will be reused.")
else:
bail(f"ERROR: User '{user}' already exists and may belong to another person or process.")
else:
pinfo(f"Creating {user} user and {install_dir}.")
system(f"useradd -md {install_dir} {user}")
if not user_info: user_info = getuser(user)
# Create log_dir
pinfo(f"Creating {log_dir}.")
makedirs(log_dir, mode=0o777, exist_ok=True)
chmod(log_dir, 0o777)
# Create .wine dir
pinfo(f"Creating {install_dir}/.wine defaults.")
system(f"su - {user} -c'wineboot -i > /dev/null 2>&1'")
# Map wine I: drive to pwd T: drive to install_dir and L: to log_dir
pinfo(f"Mapping I: in wine for {user}.")
try:
symlink(f"{pwd}/winbin", f"{install_dir}/.wine/dosdevices/i:")
except FileExistsError:
pass
pinfo(f"Mapping L: in wine for {user}.")
try:
symlink(log_dir, f"{install_dir}/.wine/dosdevices/l:")
except FileExistsError:
pass
pinfo(f"Mapping T: in wine for {user}.")
try:
symlink(install_parent, f"{install_dir}/.wine/dosdevices/t:")
except FileExistsError:
pass
# Check for needed exe/zip/dll files in winbin dir
needed_files=[]
if isfile(f"{pwd}/winbin/tribes2gsi.exe"):
pinfo("tribes2gsi.exe found.")
installer_exe = f"{pwd}/winbin/tribes2gsi.exe"
elif isfile(f"{pwd}/winbin/tribes2_gsi.exe"):
pinfo("tribes2_gsi.exe found.")
installer_exe = f"{pwd}/winbin/tribes2_gsi.exe"
else:
pwarn("Tribes 2 installer not found.")
needed_files.append("tribes2_gsi.exe")
installer_exe = False
if isfile(f"{pwd}/winbin/TribesNext_rc2a.exe"):
pinfo("TribesNext_rc2a.exe found.")
tnpatch_exe = f"{pwd}/winbin/TribesNext_rc2a.exe"
else:
pwarn("Tribes Next patch not found.")
needed_files.append("TribesNext_rc2a.exe")
tnpatch_exe = False
ruby_dll = f"{pwd}/winbin/msvcrt-ruby191.dll"
instwrap_exe = f"{pwd}/winbin/install_wrapper.exe"
# Download files if needed
if not installer_exe or not tnpatch_exe:
action=menu(["~~[D]ownload automatically","[Q]uit"],header="One or more needed files were not found. Download automatically or quit so they can be manually placed in the 'winbin' subdirectory?")
if needed_files == 2:
needed_files=f"{needed_files[0]} and {needed_files[1]}"
if action=="Q": bail(f"Manually place {needed_files} in the 'winbin' subdirectory then rerun setup.")
if not installer_exe:
for url in installer_mirror_list:
try:
pinfo(f"\nDownloading from {url.split('/')[2]}...")
installer_exe = download_file(url, f"{pwd}/winbin/tribes2_gsi.exe")
if md5sum(installer_exe) == installer_checksum:
pinfo("Checksum validation passed.")
break
else:
perror("Checksum validation failed. Trying next mirror.")
except KeyError:
perror("Download error. Trying next mirror.")
continue
if not installer_exe:
bail("ERROR: Tribes 2 installer could not be downloaded.")
if not tnpatch_exe:
for url in tnpatch_mirror_list:
try:
pinfo(f"\nDownloading from {url.split('/')[2]}...")
tnpatch_exe = download_file(url, f"{pwd}/winbin/TribesNext_rc2a.exe")
if md5sum(tnpatch_exe) == tnpatch_checksum:
pinfo("Checksum validation passed.")
break
else:
perror("Checksum validation failed. Trying next mirror." )
except KeyError:
perror("Download error. Trying next mirror.")
continue
if not tnpatch_exe:
bail("ERROR: Tribes Next patch could not be downloaded.")
# Present SLAs before beginning install
sla = None
while not sla:
sla=menu(["[V]iew Tribes 2 and TribesNext License Agreements", "[A]ccept License Agreements", "[Q]uit"], header="Please take a moment to review and accept the Tribes 2 and TribeNext License Agreements before beginning automated install.")
if sla == "V":
print(color.DY)
system(f"/usr/bin/less {pwd}/sla/tribes2.txt")
print(color.DP)
system(f"/usr/bin/less {pwd}/sla/tribesnext.txt")
sla = None
elif sla == "A":
break
elif sla == "Q":
bail("You must accept the License Agreements to install.")
# Ensure sufficient permissions on winbin and its contents
chmod(f"{pwd}/winbin", 0o777)
chmod(installer_exe, 0o777)
chmod(tnpatch_exe, 0o777)
chmod(instwrap_exe, 0o777)
chmod(ruby_dll, 0o777)
# Execute install wrapper
pinfo(f"\nInstalling Tribes 2 and the TribesNext patch in wine. Please wait...")
chowner(install_dir, user)
system(f"su - {user} -c'xvfb-run -as " + '"-fbdir /var/tmp"' + " wine I:/install_wrapper.exe > /dev/null 2>&1'")
# Rudamentary check to see if T2 install succeeded
if not isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): bail(f"ERROR: Tribes 2 installation appears to have failed. Check {log_dir}/install_wrapper.log")
# Rudamentary check to see if TN install succeeded
if not isfile(f"{install_dir}/GameData/TN_Uninstall.exe"): bail(f"ERROR: Tribes Next installation appears to have failed. Check {log_dir}/install_wrapper.log")
# Replace msvcrt-ruby190.dll with msvcrt-ruby191.dll
pinfo("Updating msvcrt-ruby190.dll to msvcrt-ruby191.dll.\n")
copyfile(ruby_dll,f"{install_dir}/GameData/msvcrt-ruby191.dll")
unlink(f"{install_dir}/GameData/msvcrt-ruby190.dll")
symlink(f"{install_dir}/GameData/msvcrt-ruby191.dll", f"{install_dir}/GameData/msvcrt-ruby190.dll")
# Install addons
for addon in iglob(f"{pwd}/addons/*"):
if addon.endswith(".zip"):
pinfo(f"Unpacking {addon} into {install_dir}/GameData.")
system(f"unzip -qqd {install_dir}/GameData {addon}")
elif addon.endswith((".tar",".tgz",".tar.gz",".txz",".tar.xz",".tbz",".tar.bz")):
pinfo(f"Unpacking {addon} into {install_dir}/GameData.")
system(f"tar -C {install_dir}/GameData -xf {addon}")
elif addon.endswith(".vl2"):
pinfo(f"Copying {addon} to {install_dir}/GameData/base.")
copyfile(addon,f"{install_dir}/GameData/base/{addon.split('/')[-1]}")
elif addon.endswith("readme.txt"):
pass
else:
pwarn(f"Ignoring {addon}.")
# Copy t2server and t2bouncer to /usr/local/bin/
pinfo("Installing t2server script.")
copyfile(f"{pwd}/usr/local/bin/t2server",f"{bin_dir}/t2server")
pinfo("Installing t2bouncer script.")
copyfile(f"{pwd}/usr/local/bin/t2bouncer",f"{bin_dir}/t2bouncer")
# Set owner/group on install_dir
chowner(install_dir, user)
# Clean up temp dir and some unneeded files
pinfo("A little housekeeping...")
if isfile(f"{install_dir}/Tribes 2 Online.lnk"): unlink(f"{install_dir}/Tribes 2 Online.lnk")
if isfile(f"{install_dir}/Tribes 2 Solo & LAN.lnk"): unlink(f"{install_dir}/Tribes 2 Solo & LAN.lnk")
if isfile(f"{install_dir}/UNWISE.EXE"): unlink(f"{install_dir}/UNWISE.EXE")
if isfile(f"{install_dir}/Readme.txt"): unlink(f"{install_dir}/Readme.txt")
if isfile(f"{install_dir}/GameData/Classic_LAN.bat"): unlink(f"{install_dir}/GameData/Classic_LAN.bat")
if isfile(f"{install_dir}/GameData/Classic_dedicated_server.bat"): unlink(f"{install_dir}/GameData/Classic_dedicated_server.bat")
if isfile(f"{install_dir}/GameData/Classic_online.bat"): unlink(f"{install_dir}/GameData/Classic_online.bat")
if isfile(f"{install_dir}/GameData/base/EULA.txt"): unlink(f"{install_dir}/GameData/base/EULA.txt")
if isfile(f"{install_dir}/GameData/base/UKEULA.txt"): unlink(f"{install_dir}/GameData/base/UKEULA.txt")
if isdir(f"{install_dir}/Manual"): rmtree(f"{install_dir}/Manual")
if isdir(f"{install_dir}/.wine/drive_c/users/t2server/Temp"): rmtree(f"{install_dir}/.wine/drive_c/users/t2server/Temp")
if isfile(f"{install_dir}/t2csri_eula.txt"): unlink(f"{install_dir}/t2csri_eula.txt")
if isfile(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt"): unlink(f"{install_dir}/Inside\ Team\ Rabbit\ 2.txt")
if isfile(f"{install_dir}/UpdatePatch.txt"): unlink(f"{install_dir}/UpdatePatch.txt")
if isfile(f"{install_dir}/Classic/Classic_readme.txt"): unlink(f"{install_dir}/Classic/Classic_readme.txt")
if isfile(f"{install_dir}/Classic_technical.txt"): unlink(f"{install_dir}/Classic_technical.txt")
# Create config directory and files
pinfo(f"\nCreating {etc_dir}, default config, and installing prefs files.")
makedirs(f"{etc_dir}/serverprefs", mode=0o775, exist_ok=True)
if isfile(f"{etc_dir}/config.yaml"):
timestamp = int(time())
rename(f"{etc_dir}/config.yaml",f"{etc_dir}/config.yaml.{timestamp}")
pwarn(f"Existing {etc_dir}/config.yaml renamed to {etc_dir}/config.yaml.{timestamp}. Be sure to compare with and update the new config.yaml file.")
pinfo(f"Writing default {etc_dir}/config.yaml.")
copyfile(f"{pwd}/etc/t2server/config.yaml", f"{etc_dir}/config.yaml")
for pfile in iglob(f"{pwd}/etc/t2server/serverprefs/*"):
pinfo(f"Copying {pfile} to {etc_dir}/serverprefs.")
copyfile(pfile,f"{etc_dir}/serverprefs/{pfile.split('/')[-1]}")
# Create systemd units
pinfo("\nCreating systemd units:")
pinfo("- t2server service")
copyfile(f"{pwd}/etc/systemd/system/t2server.service",f"{unit_dir}/t2server.service")
pinfo("- t2bouncer service")
copyfile(f"{pwd}/etc/systemd/system/t2bouncer.service",f"{unit_dir}/t2bouncer.service")
pinfo("- t2bouncer timer")
copyfile(f"{pwd}/etc/systemd/system/t2bouncer.timer",f"{unit_dir}/t2bouncer.timer")
system("systemctl daemon-reload")
# Install utility scripts
pinfo("\nInstalling utilities:")
pinfo("- t2bouncer")
copyfile(f"{pwd}/usr/local/bin/t2fixer",f"{bin_dir}/t2fixer")
pinfo("- t2remove")
copyfile(f"{pwd}/usr/local/bin/t2remove",f"{bin_dir}/t2remove")
pinfo("- t2help")
copyfile(f"{pwd}/usr/local/bin/t2help",f"{bin_dir}/t2help")
# Install python module
copyfile(f"{pwd}/usr/local/bin/t2support.py",f"{bin_dir}/t2support.py")
chmod(f"{bin_dir}/t2fixer",0o777)
system(f"{bin_dir}/t2fixer")
# Show help
system(f"{bin_dir}/t2help")
menu(['~~[E]xit'],header="You can run 't2help' at any time to view the info above again.")
print(f"{color.X}\n")

196
sla/tribes2.txt Normal file
View file

@ -0,0 +1,196 @@
_______ __ __ _______
| |.----.|__|| |--..-----..-----. | |
|.| | || _|| || _ || -__||__ --| |___| |
`-|. |-'|__| |__||_____||_____||_____| / ___/
|: | |: 1 \
--------------- |::.| ------- License Agreement -------- |::.. . | -------------
`---' `-------'
YOU SHOULD CAREFULLY READ THE FOLLOWING END USER LICENSE AGREEMENT BEFORE
INSTALLING THIS SOFTWARE PROGRAM. BY INSTALLING OR OTHERWISE USING THE SOFTWARE
PROGRAM, YOU AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT
AGREE TO THE TERMS OF THIS AGREEMENT, PROMPTLY RETURN THE UNUSED SOFTWARE
PROGRAM TO THE PLACE OF PURCHASE OR CONTACT SIERRA ON-LINE, INC. CUSTOMER
SERVICE AT (425) 746-5771 FOR A FULL REFUND OF THE PURCHASE PRICE WITHIN 30 DAYS
OF THE ORIGINAL PURCHASE.
This software program (the "Program"), any printed materials, any on-line or
electronic documentation, and any and all copies and derivative works of such
software program (including materials created with a so called level editor, if
included) and materials are the copyrighted work of Sierra On-Line, Inc., a
division of Havas Interactive, Inc. and/or its wholly owned subsidiaries, or its
suppliers. All rights reserved, except as expressly stated herein. All use of
the Program is governed by the terms of this End User License Agreement provided
below ("License Agreement"). The Program is solely for use by end users
according to the terms of the License Agreement. Any use, reproduction or
redistribution of the Program not in accordance with the terms of the License
Agreement is expressly prohibited.
END USER LICENSE AGREEMENT
1. Limited Use License. Sierra On-Line, Inc. ("Sierra ") hereby grants, and by
installing the Program you thereby accept, a limited, non-exclusive license and
right to install and use one (1) copy of the Program for your use on either a
home, business or portable computer. In addition, the Program has a multi-player
capability that allows users to utilize the Program over the Internet via
Sierra's online game network Sierra.com. Use of the Program over Sierra.com is
subject to your acceptance of Sierra.com's Terms of Use Agreement. Sierra
On-Line, Inc. reserves the right to update, modify or change the Sierra.com
Terms of Use Agreement at any time. The Program may also contain a Level Editor
(the "Editor") that allows you to create custom levels or other materials for
your personal use in connection with the Program ("New Materials"). All use of
the Editor or any New Materials is subject to this License Agreement. The
Program is licensed, not sold. Your license confers no title or ownership in the
Program.
2. Ownership. All title, ownership rights and intellectual property rights in
and to the Program and any and all copies thereof (including but not limited to
any titles, computer code, themes, objects, characters, character names,
stories, dialog, catch phrases, locations, concepts, artwork, animations,
sounds, musical compositions, audio-visual effects, methods of operation, moral
rights, any related documentation, and "applets" incorporated into the Program)
are owned by Sierra On-Line, Inc. or its licensors. The Program is protected by
the copyright laws of the United States, international copyright treaties and
conventions and other laws. All rights are reserved. The Program contains
certain licensed materials and Sierra 's licensors may protect their rights in
the event of any violation of this Agreement.
3. Responsibilities of End User.
A. Subject to the Grant of License hereinabove, you may not, in whole or in
part, copy, photocopy, reproduce, translate, reverse engineer, derive source
code, modify, disassemble, decompile, create derivative works based on the
Program, or remove any proprietary notices or labels on the Program without
the prior consent, in writing, of Sierra.
B. The Program is licensed to you as a single product. Its component parts
may not be separated for use on more than one computer.
C. You are entitled to use the Program for your own use, but you are not
entitled to:
(i) sell, grant a security interest in or transfer reproductions of the
Program to other parties in any way, nor to rent, lease or license the
Program to others without the prior written consent of Sierra.
(ii) exploit the Program or any of its parts for any commercial purpose
including, but not limited to, use at a cyber café, computer gaming center
or any other location-based site. Sierra may offer a separate Site License
Agreement to permit you to make the Program available for commercial use;
contact Sierra for details;
(iii) host or provide matchmaking services for the Program or emulate or
redirect the communication protocols used by Sierra in the network feature
of the Program, through protocol emulation, tunneling, modifying or adding
components to the Program, use of a utility program or any other
techniques now known or hereafter developed, for any purpose including,
but not limited to network play over the Internet, network play utilizing
commercial or non-commercial gaming networks or as part of content
aggregation networks without the prior written consent of Sierra ;
(iv) create or maintain, under any circumstance, more than one
simultaneous connection to Sierra.com. All such connections to Sierra.com,
whether created by the Program or by other tools and utilities, may only
be made through methods and means expressly approved by Sierra On-Line,
Inc. Under no circumstances may you connect, or create tools that allow
you to connect to Sierra.com's private binary interface or interfaces
other than those explicitly provided by Sierra On-Line, Inc. for public
use.
4. Program Transfer. You may permanently transfer all of your rights under this
License Agreement, provided the recipient agrees to the terms of this License
Agreement and you agree to remove the Program from your home or portable
computer.
5. Termination. This License Agreement is effective until terminated. You may
terminate the License Agreement at any time by destroying the Program. Sierra
may, at its discretion, terminate this License Agreement in the event that you
fail to comply with the terms and conditions contained herein. In such event,
you must immediately destroy the Program.
6. Export Controls. The Program may not be re-exported, downloaded or otherwise
exported into (or to a national or resident of) any country to which the U.S.
has embargoed goods, or to anyone on the U.S. Treasury Department's list of
Specially Designated Nationals or the U.S. Commerce Department's Table of Denial
Orders. By installing the Program, you are agreeing to the foregoing and you are
representing and warranting that you are not located in, under the control of,
or a national or resident of any such country or on any such list.
7. Limited Warranty. Sierra expressly disclaims any warranty for the Program,
Editor and Manual(s). The Program, Editor and Manual(s) are provided "as is"
without warranty of any kind, either express or implied, including, without
limitation, the implied warranties of merchantability, fitness for a particular
purpose, or noninfringement. The entire risk arising out of use or performance
of the Program and Manual(s) remains with the User, however Sierra warrants up
to and including 90 days from the date of your purchase of the Program that the
media containing the Program shall be free from defects in material and
workmanship. In the event that the media proves to be defective during that time
period, and upon presentation to Sierra of proof of purchase of the defective
Program, Sierra will at its option 1) correct any defect, 2) provide you with a
product of equal or lesser value, or 3) refund your money. Some states do not
allow the exclusion or limitation of implied warranties or liability for
incidental damages, so the above limitations may not apply to you.
8. Limitation of Liability. NEITHER SIERRA, HAVAS INTERACTIVE, INC., ITS PARENT,
SUBSIDIARIES OR AFFILIATES SHALL BE LIABLE IN ANY WAY FOR LOSS OR DAMAGE OF ANY
KIND RESULTING FROM THE USE OF THE PROGRAM OR USE OF SIERRA ON-LINE, INC.'S
ONLINE GAME NETWORK, SIERRA.COM INCLUDING, BUT NOT LIMITED TO, LOSS OF
GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
COMMERCIAL DAMAGES OR LOSSES. SIERRA FURTHER DISCLAIMS ALL WARRANTIES WITH
REGARD TO YEAR 2000 COMPLIANCE OF THE SOFTWARE. SPECIFICALLY, SIERRA MAKES NO
WARRANTIES THAT THE PERFORMANCE OR FUNCTIONALITY OF THE PROGRAM WILL NOT BE
AFFECTED BY DATES PRIOR TO, DURING OR AFTER THE YEAR 2000, OR THAT THE PROGRAM
WILL BE CAPABLE OF CORRECTLY PROCESSING, PROVIDING, AND/OR RECEIVING DATE
INFORMATION WITHIN AND BETWEEN CENTURIES, INCLUDING THE PROPER EXCHANGE OF DATE
INFORMATION BETWEEN PRODUCTS OR APPLICATIONS. ANY WARRANTY AGAINST INFRINGEMENT
THAT MAY BE PROVIDED IN SECTION 2-312(3) OF THE UNIFORM COMMERCIAL CODE AND/OR
IN ANY OTHER COMPARABLE STATE STATUTE IS EXPRESSLY DISCLAIMED. FURTHER, Sierra
On-Line, Inc. SHALL NOT BE LIABLE IN ANY WAY FOR THE LOSS OR DAMAGE TO PLAYER
CHARACTERS, ACCOUNTS, STATISTICS OR USER PROFILE INFORMATION STORED ON
SIERRA.COM. I UNDERSTAND AND ACKNOWLEDGE THAT SIERRA ON-LINE, INC. CANNOT AND
WILL NOT BE RESPONSIBLE FOR ANY INTURUPTIONS OF SERVICE ON SIERRA.COM INCLUDING,
BUT NOT LIMITED TO ISP DISRUPTIONS, SOFTWARE OR HARDWARE FAILURES OR ANY OTHER
EVENT WHICH MAY RESULT IN A LOSS OF DATA OR DISRUPTION OF SERVICE. Some states
do not allow the exclusion or limitation of incidental or consequential damages,
or allow limitations on how long an implied warranty lasts, so the above
limitations may not apply.
9. Equitable Remedies. You hereby agree that Sierra would be irreparably damaged
if the terms of this License Agreement were not specifically enforced, and
therefore you agree that Sierra shall be entitled, without bond, other security,
or proof of damages, to appropriate equitable remedies with respect to breaches
of this License Agreement, in addition to such other remedies as Sierra may
otherwise have available to it under applicable laws. In the event any
litigation is brought by either party in connection with this License Agreement,
the prevailing party in such litigation shall be entitled to recover from the
other party all the costs, attorneys' fees and other expenses incurred by such
prevailing party in the litigation.
10. Limitations on License. Nothing in this License Agreement shall preclude
you from making or authorizing the making of another copy or adaptation of the
Program provided, however, that (1) such new copy or adaptation is created as an
essential step in your utilization of the Program in accordance with the terms
of this License Agreement and for NO OTHER PURPOSE; or (2) such new copy or
adaptation is for archival purposes ONLY and all archival copies are destroyed
in the event of your Transfer of the Program, the Termination of this Agreement
or other circumstances under which your continued use of the Program ceases to
be rightful.
11. Miscellaneous. This License Agreement shall be deemed to have been made and
executed in the State of California and any dispute arising hereunder shall be
resolved in accordance with the law of California. You agree that any claim
asserted in any legal proceeding by one of the parties against the other shall
be commenced and maintained in any state or federal court located in the State
of California, County of Los Angeles, having subject matter jurisdiction with
respect to the dispute between the parties. This License Agreement may be
amended, altered or modified only by an instrument in writing, specifying such
amendment, alteration or modification, executed by both parties. In the event
that any provision of this License Agreement shall be held by a court or other
tribunal of competent jurisdiction to be unenforceable, such provision will be
enforced to the maximum extent permissible and the remaining portions of this
License Agreement shall remain in full force and effect. This License Agreement
constitutes and contains the entire agreement between the parties with respect
to the subject matter hereof and supersedes any prior oral or written
agreements.
I hereby acknowledge that I have read and understand the foregoing License
Agreement and agree that the action of installing the Program is an
acknowledgment of my agreement to be bound by the terms and conditions of the
License Agreement contained herein. I also acknowledge and agree that this
License Agreement is the complete and exclusive statement of the agreement
between Sierra and I and that the License Agreement supersedes any prior or
contemporaneous agreement, either oral or written, and any other communications
between Sierra and myself.

104
sla/tribesnext.txt Normal file
View file

@ -0,0 +1,104 @@
_______ __ __ _______ _______ ___ ___ _______
|_ _|.----.|__|| |--..-----..-----.| | || ___|| | ||_ _|
| | | _|| || _ || -__||__ --|| || ___||- -| | |
|___| |__| |__||_____||_____||_____||__|____||_______||___|___| |___|
----------------------------- License Agreement --------------------------------
YOU SHOULD CAREFULLY READ THE FOLLOWING LICENSE AGREEMENT BEFORE INSTALLING THIS
SOFTWARE PROGRAM. BY INSTALLING OR OTHERWISE USING THE SOFTWARE PROGRAM, YOU
AGREE TO BE BOUND BY THE TERMS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO THE
TERMS OF THIS AGREEMENT, PROMPTLY DELETE THE UNUSED SOFTWARE PROGRAM.
Definitions:
1. "This software program" is defined as the integrated, machine readable,
executable code, and application tools included as part of the TribesNext
match making service.
2. "Associated intepreted script code" is defined as the human readable,
executable code, and all intermediate representations included as part of or
generated during utilization of the TribesNext match making service.
This software program (the “Program”), associated interpreted script code, any
on-line or electronic documentation, and any and all copies and derivative works
of such software program are the copyrighted work of the TribesNext team, All
rights reserved, except as expressly stated herein. All use of the Program is
governed by the terms of this License Agreement provided below. The Program is
solely for use by end users according to the terms of the License Agreement. Any
use, reproduction or redistribution of the Program not in accordance with the
terms of the License Agreement is expressly prohibited.
1. Ownership. All title, ownership rights and intellectual property rights in
and to the Program and any and all copies thereof (including but not limited
to any titles, computer code, objects, concepts, artwork, methods of
operation, moral rights, any related documentation, and “applets”
incorporated into the Program) are owned by the TribesNext team or its
licensors. The Program is protected by the copyright laws of the United
States, international copyright treaties and conventions and other laws. All
rights are reserved. The Program contains certain licensed materials and
TribesNext's licensors may protect their rights in the event of any violation
of this Agreement.
2. Termination. This License Agreement is effective until terminated. You may
terminate the License Agreement at any time by destroying the Program.
TribesNext may, at its discretion, terminate this License Agreement in the
event that you fail to comply with the terms and conditions contained herein.
In such event, you must immediately destroy the Program.
3. Export Controls. The Program contains strong cryptography, thus the
Program may not be re-exported, downloaded or otherwise exported into (or to
a national or resident of) any country to which the U.S. has embargoed goods,
or to anyone on the U.S. Treasury Departments list of Specially Designated
Nationals or the U.S. Commerce Departments Table of Denial Orders. By
installing the Program, you are agreeing to the foregoing and you are
representing and warranting that you are not located in, under the control
of, or a national or resident of any such country or on any such list.
4. Limited Warranty. TribesNext expressly disclaims any warranty for the
Program, components and any assorted documentation. The Program, components
and any assorted documentation are provided "as is" without warranty of any
kind, either express or implied, including, without limitation, the implied
warranties of merchantability, fitness for a particular purpose, or
noninfringement. The entire risk arising out of use or performance of the
Program, components and any assorted documentation remains with the User.
Some states do not allow the exclusion or limitation of implied warranties or
liability for incidental damages, so the above limitations may not apply to
you.
5. Limitation of Liability. NEITHER TRIBESNEXT, SUBSIDIARIES OR AFFILIATES
SHALL BE LIABLE IN ANY WAY FOR LOSS OR DAMAGE OF ANY KIND RESULTING FROM THE
USE OF THE PROGRAM OR USE OF ASSOCIATED ONLINE SERVICES INCLUDING, BUT NOT
LIMITED TO, LOSS OF GOODWILL, WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION,
OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR LOSSES. TRIBESNEXT FURTHER
DISCLAIMS ALL WARRANTIES WITH REGARD TO YEAR 2000 COMPLIANCE OF THE SOFTWARE.
SPECIFICALLY, TRIBESNEXT MAKES NO WARRANTIES THAT THE PERFORMANCE OR
FUNCTIONALITY OF THE PROGRAM WILL NOT BE AFFECTED BY DATES PRIOR TO, DURING
OR AFTER THE YEAR 2000, OR THAT THE PROGRAM WILL BE CAPABLE OF CORRECTLY
PROCESSING, PROVIDING, AND/OR RECEIVING DATE INFORMATION WITHIN AND BETWEEN
CENTURIES, INCLUDING THE PROPER EXCHANGE OF DATE INFORMATION BETWEEN PRODUCTS
OR APPLICATIONS. ANY WARRANTY AGAINST INFRINGEMENT THAT MAY BE PROVIDED IN
SECTION 2-312(3) OF THE UNIFORM COMMERCIAL CODE AND/OR IN ANY OTHER
COMPARABLE STATE STATUTE IS EXPRESSLY DISCLAIMED. FURTHER, TRIBESNEXT SHALL
NOT BE LIABLE IN ANY WAY FOR THE LOSS OR DAMAGE TO PLAYER CHARACTERS,
ACCOUNTS, STATISTICS OR USER PROFILE INFORMATION STORED ON TribesNext.com. I
UNDERSTAND AND ACKNOWLEDGE THAT TRIBESNEXT. CANNOT AND WILL NOT BE
RESPONSIBLE FOR ANY INTURUPTIONS OF SERVICE ON TRIBESNEXT.COM INCLUDING, BUT
NOT LIMITED TO ISP DISRUPTIONS, SOFTWARE OR HARDWARE FAILURES OR ANY OTHER
EVENT WHICH MAY RESULT IN A LOSS OF DATA OR DISRUPTION OF SERVICE. Some
states do not allow the exclusion or limitation of incidental or
consequential damages, or allow limitations on how long an implied warranty
lasts, so the above limitations may not apply.
6. Equitable Remedies. You hereby agree that TribesNext would be irreparably
damaged if the terms of this License Agreement were not specifically
enforced, and therefore you agree that TribesNext shall be entitled, without
bond, other security, or proof of damages, to appropriate equitable remedies
with respect to breaches of this License Agreement, in addition to such other
remedies as TribesNext may otherwise have available to it under applicable
laws. In the event any litigation is brought by either party in connection
with this License Agreement, the prevailing party in such litigation shall be
entitled to recover from the other party all the costs, attorneys fees and
other expenses incurred by such prevailing party in the litigation.
I hereby acknowledge that I have read and understand the foregoing License
Agreement and agree that the action of installing the Program is an
acknowledgment of my agreement to be bound by the terms and conditions of the
License Agreement contained herein. I also acknowledge and agree that this
License Agreement is the complete and exclusive statement of the agreement
between TribesNext and I and that the License Agreement supersedes any prior or
contemporaneous agreement, either oral or written, and any other communications
between TribesNext and myself

1
t2support.py Symbolic link
View file

@ -0,0 +1 @@
usr/local/bin/t2support.py

31
usr/local/bin/t2bouncer Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env -S python3 -B
import yaml
from datetime import datetime
from os import system
from t2support import *
# Set default configuration
config_defaults = {
'RestartTime' : False,
'RestartDay' : 'Mon',
}
# Read configuration from config.yaml
with open(f'{etc_dir}/config.yaml', 'r') as f:
loaded_config = yaml.full_load(f)
# Merge config_defaults and loaded_config, with loaded_config taking precedence where there are conflicts.
# This ensures there are no undefined values in the case of a user removing one from config.yaml.
config = {**config_defaults, **loaded_config}
now=datetime.now()
if not config['RestartTime']:
print("RestartTime is disabled.")
elif config['RestartTime'] > 0 and config['RestartTime'] < 25:
if config['RestartTime'] == int(now.strftime('%H')) and config['RestartDay'][:3].upper() == now.strftime('%a').upper():
print("RestartTime and RestartDay match current time and day. Restarting t2server.service.")
system("/usr/bin/systemctl try-restart t2server.service")
else:
print("RestartTime and RestartDay do not match current time and day.")

103
usr/local/bin/t2fixer Executable file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env -S python3 -B
from os import geteuid, chmod, walk
from os.path import isdir, islink, join
from shutil import chown
from sys import exit as bail
from t2support import *
if geteuid() != 0:
bail("This script must be run with sudo or as root.\n")
def setperm(file):
if islink(file):
pass
elif isdir(file):
log.write(f"Setting mode 775 on directory {file}")
try:
chmod(file,0o775)
log.write(f"\n")
except:
pwarn(f"Failed to set permissions on {file}")
log.write(f"... FAILED!\n")
pass
elif file.endswith("t2support.py"):
log.write(f"Setting mode 664 on file {file}")
try:
chmod(file,0o664)
log.write(f"\n")
except:
pwarn(f"Failed to set permissions on {file}")
log.write(f"... FAILED!\n")
pass
elif file.startswith(bin_dir):
log.write(f"Setting mode 775 on script {file}")
try:
chmod(file,0o775)
log.write(f"\n")
except:
pwarn(f"Failed to set permissions on {file}")
log.write(f"... FAILED!\n")
pass
elif file.endswith(".exe"):
log.write(f"Setting mode 775 on exe {file}")
try:
chmod(file,0o775)
log.write(f"\n")
except:
pwarn(f"Failed to set permissions on {file}")
log.write(f"... FAILED!\n")
pass
else:
log.write(f"Setting mode 664 on file {file}")
try:
chmod(file,0o664)
log.write(f"\n")
except:
pwarn(f"Failed to set permissions on {file}")
log.write(f"... FAILED!\n")
pass
if islink(file):
pass
else:
try:
log.write(f"Setting owner/group on {file}")
chown(file, "t2server", "t2server")
log.write(f"\n")
except:
pwarn(f"Failed to set owner/group on {file}")
log.write(f"... FAILED!\n")
pass
with open(f'{log_dir}/t2fixer.log', 'w') as log:
setperm(install_dir)
setperm(etc_dir)
for r, d, f in walk(f"{install_dir}/GameData"):
for file in d:
setperm(join(r, file))
for file in f:
setperm(join(r, file))
for r, d, f in walk(etc_dir):
for file in d:
setperm(join(r, file))
for file in f:
setperm(join(r, file))
for r, d, f in walk(log_dir):
for file in d:
setperm(join(r, file))
for file in f:
setperm(join(r, file))
setperm(f"{unit_dir}/t2server.service")
setperm(f"{unit_dir}/t2bouncer.service")
setperm(f"{unit_dir}/t2bouncer.timer")
setperm(f"{bin_dir}/t2server")
setperm(f"{bin_dir}/t2bouncer")
setperm(f"{bin_dir}/t2remove")
setperm(f"{bin_dir}/t2fixer")
setperm(f"{bin_dir}/t2help")
setperm(f"{bin_dir}/t2support.py")

22
usr/local/bin/t2help Executable file
View file

@ -0,0 +1,22 @@
#!/usr/bin/env -S python3 -B
from t2support import color
print(f"\n{color.DC}The follow commands can be used to manage your Tribes 2 server:")
print(f"""\n{color.BW}systemctl <action> t2server{color.DC}: The t2server service can be managed with the
standard Linux systemctl command.""")
print(f"""\n{color.BW}t2fixer{color.DC}: This command resets the owner and permissions on all files associated
with t2server. It's recommended to run this command any time you make changes
or add files (such as mods or map packs) to your server to ensure they can be
properly read and accessed. In the future, this command may also resolve other
common issues.""")
print(f"""\n{color.BW}t2remove{color.DC}: This command uninstalls t2server and removes most associated files.
Configuration and serverprefs files will be left alone.""")
print(f"\n{color.DC}Tribes 2 is installed in {color.BW}/opt/t2server{color.DC}.")
print(f"{color.DC}The {color.BW}/etc/t2server/config.yaml{color.DC} file holds basic service configuration values.")
print(f"{color.DC}The {color.BW}/etc/t2server/serverprefs{color.DC} directory holds your T2 serverprefs .cs files.")
print(color.X)

64
usr/local/bin/t2remove Executable file
View file

@ -0,0 +1,64 @@
#!/usr/bin/env -S python3 -B
from os import system, unlink, geteuid
from sys import argv
from t2support import *
if geteuid() != 0:
bail(f"This script must be run with sudo or as root.\n")
if menu(["[Y]es, remove Tribes 2", "~~[N]o, exit t2remove"],header=f"This script will remove Tribes 2, service files, and utilities. {etc_dir} will be left as it is.") == "N": bail()
system("/usr/bin/systemctl stop t2server.service")
system("/usr/sbin/userdel -fr t2server > /dev/null 2>&1")
system("/usr/sbin/groupdel t2server > /dev/null 2>&1")
systemd_delete_failed=0
try:
unlink(f"{unit_dir}/t2bouncer.service")
except:
pwarn(f"Failed to delete {unit_dir}/t2bouncer.service")
systemd_delete_failed+=1
try:
unlink(f"{unit_dir}/t2bouncer.timer")
except:
pwarn(f"Failed to delete {unit_dir}/t2bouncer.timer")
systemd_delete_failed+=1
try:
unlink(f"{unit_dir}/t2server.service")
except:
pwarn(f"Failed to delete {unit_dir}/t2server.service")
systemd_delete_failed+=1
if systemd_delete_failed > 0:
pinfo("After manually removing the files above, run 'systemctl daemon-reload' to refresh systemd.")
else:
system("/usr/bin/systemctl daemon-reload")
try:
unlink(f"{bin_dir}/t2bouncer")
except:
pwarn(f"Failed to delete {bin_dir}/t2bouncer")
try:
unlink(f"{bin_dir}/t2fixer")
except:
pwarn(f"Failed to delete {bin_dir}/t2fixer")
try:
unlink(f"{bin_dir}/t2server")
except:
pwarn(f"Failed to delete {bin_dir}/t2server")
try:
unlink(f"{bin_dir}/t2support.py")
except:
pwarn(f"Failed to delete {bin_dir}/t2support.py")
try:
if argv[0].startswith(bin_dir): unlink(argv[0])
except:
pwarn(f"Failed to delete {argv[0]}")

192
usr/local/bin/t2server Executable file
View file

@ -0,0 +1,192 @@
#!/usr/bin/env -S python3 -B
import yaml
from os import unlink, symlink, chdir, getppid, makedirs
from os.path import isfile, islink, isdir, getmtime
from re import match
from glob import iglob
from subprocess import run, PIPE
from time import sleep
from requests import get
from threading import Thread
from t2support import *
winecmd=["/usr/bin/wineconsole", "--backend=curses"]
argbase=["Tribes2.exe","-dedicated"]
basecmd=winecmd+argbase
parent=getppid()
def dso_cleanup():
"""
This function finds all .dso files (compiled .cs scripts) and deletes them
if they are older than their associated .cs script so that Tribes 2 will
recompile them at startup.
"""
for dso_file in iglob(f"{install_dir}/**/*.dso", recursive=True):
cs_file=dso_file[:-4]
if isfile(cs_file):
cs_mtime=getmtime(cs_file)
dso_mtime=getmtime(dso_file)
if cs_mtime > dso_mtime:
print(f"Deleting {dso_file} so it can be rebuilt.")
unlink(dso_file)
def build_args(basecmd,config):
"""
This function assembles the command line to launch the server based on the
config.yaml file.
"""
server_command=basecmd
if config['Public']:
server_command.append("-online")
else:
server_command.append("-nologin")
if config['Mod'] != "base":
server_command.extend(["-mod", config['Mod']])
if config['ServerPrefs']:
server_command.extend(["-serverprefs","prefs\\ServerPrefs.cs"])
return server_command
def server_files(config):
"""
This function creates a symlink to the specified /etc/t2server/serverprefs
file in the GameData/<mod>/prefs directory where Tribes 2 expects to find it.
If MapList and MissionType are also configured, this funtion writes
GameData/base/prefs/missions.txt with the appropriate values for the
GameData/base/scripts/autoexec/missioncycle.cs script. If either MapList or
MissionType is False, an empty missions.txt is written.
"""
if config['ServerPrefs']:
if isfile(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs") or islink(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs"):
print(f"Deleting {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
unlink(f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
print(f"Linking {install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs -> {etc_dir}/serverprefs/{config['ServerPrefs']}")
makedirs(f"{install_dir}/GameData/{config['Mod']}/prefs", mode=0o755, exist_ok=True)
symlink(f"{etc_dir}/serverprefs/{config['ServerPrefs']}", f"{install_dir}/GameData/{config['Mod']}/prefs/ServerPrefs.cs")
if config["MapList"] and config["MissionType"]:
print(f"Writing {install_dir}/GameData/base/prefs/missions.txt")
makedirs(f"{install_dir}/GameData/base/prefs", mode=0o755, exist_ok=True)
with open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w') as mlist:
for mission in config["MapList"]:
mlist.write(f"{config['MissionType']} {mission}\n")
else:
if isfile(f"{install_dir}/GameData/base/prefs/missions.txt"):
print(f"Purging {install_dir}/GameData/base/prefs/missions.txt")
# missions.txt needs to exist or the missioncycle.cs script will hang, so overwrite with an empty file
open(f"{install_dir}/GameData/base/prefs/missions.txt", 'w').close()
def runaway_control():
"""
When run in the background, wine will spawn a 'wineconsole --use-event=52'
process that will consume all available CPU. This function finds that
process and uses cpulimit to keep it under control.
"""
for x in range(20):
sleep(15)
print("Checking for runaway wineconsole process...")
runaway_pid=run(["/usr/bin/pgrep","-f","wineconsole --use-event=52"],stdout=PIPE).stdout
if runaway_pid:
runaway_pid=str(int(runaway_pid))
print(f"Limiting runaway wineconsole process: {runaway_pid}")
run(["/usr/bin/cpulimit","-bp",runaway_pid,"-l2"])
break
def master_heartbeat():
"""
A public Tribes 2 server should send a regular heartbeat to the TribexNext
master server so that it appears in the list, however this seems
inconsistent, so this function takes over that responsibility.
"""
print("Starting TribesNext heartbeat thread...")
while True:
get('http://master.tribesnext.com/add/28000')
sleep(240)
def is_valid_ip(ip):
"""Check if an ip looks like a valid IP address."""
return bool(match(r"^(\d{1,3}\.){3}\d{1,3}$", ip))
def override_mitm():
"""
Tribes 2 servers try to detect descrepencies between their own IP and the IP
that the client believes it's connecting to as possible man-in-the-middle
attacks, however this often interfers with connections when the server is
NATed or multi-homed. This function gets the public-facing IP of the host
and writes an autoexec script to effectively disable this detection.
"""
for ip_service in ["http://api.ipify.org","http://ifconfig.me","http://ipinfo.io/ip"]:
r=get(ip_service)
if r.status_code == 200 and is_valid_ip(r.text):
print(f"Got public IP address {r.text}")
break
if r.status_code != 200: bail("Could not get this server's public IP address.")
print(f"Overriding Man-in-the-Middle attack detection.")
with open(f'{install_dir}/GameData/base/scripts/autoexec/noMITM.cs', 'w') as nomitm_script:
nomitm_script.write(f'$IPv4::InetAddress = "{r.text}";\n')
if __name__ == "__main__":
# If run interactively, warn user that they probably don't want to do this unless troubleshooting.
if parent == 1:
print(f"Started by init")
else:
interactive_run=menu(['[Y]es, run t2server interactively.',"~~[N]o, abort."],header="Running t2server directly can be helpful for troubleshooting but generally it's best to manage your server with 'systemctl'. Do you still want to run t2server?",footer="Choose [N] and run 't2help' if you're unsure of what to do.")
if interactive_run == 'N': bail()
chdir(f"{install_dir}/GameData")
# Set default configuration
config_defaults = {
'ServerPrefs' : 'Classic_CTF.cs',
'Mod' : 'Classic',
'Public' : False,
'OverrideMITM': True,
'MissionType' : 'CTF',
'MapList' : False
}
# Read configuration from config.yaml
with open(f'{etc_dir}/config.yaml', 'r') as f:
loaded_config = yaml.full_load(f)
# Merge config_defaults and loaded_config, with loaded_config taking precedence where there are conflicts.
# This ensures there are no undefined values in the case of a user removing one from config.yaml.
config = {**config_defaults, **loaded_config}
print(config)
# Validate the mod directory and serverprefs file
if not isdir(f"{install_dir}/GameData/{config['Mod']}"):
bail(f"Invalid Mod directory: {config['Mod']}")
if not isfile(f"{etc_dir}/serverprefs/{config['ServerPrefs']}"):
bail(f"Invalid ServerPrefs file: {config['ServerPrefs']}")
# Delete any pre-existing noMITM script/dso. It will be recreated below, if needed.
if isfile(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs")
if isfile(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs.dso"): unlink(f"{install_dir}/GameData/base/scripts/autoexec/noMITM.cs.dso")
# Create serverprefs symlink and missions.txt (if appropriate), clean out stale dso files, then assemble the command line arguments to launch the server
server_files(config)
dso_cleanup()
server_command=build_args(basecmd,config)
# If this is a public server, start a hearbeat thread. Also write the MITM override file if configured.
if config['Public']:
print("Starting heartbeat...")
if config['OverrideMITM']: override_mitm()
heartbeat=Thread(target=master_heartbeat)
heartbeat.daemon=True
heartbeat.start()
# Cap the CPU of the runaway wineconsole process
wcpid_limit=Thread(target=runaway_control)
wcpid_limit.start()
# Open the console log file if running as service and start the Tribes 2 server
print(f"Starting Tribes 2 server: " + " ".join(server_command))
if parent == 1:
with open(f"{log_dir}/console.log", 'w') as consolelog:
run(server_command,stdout=consolelog)
else:
run(server_command)

132
usr/local/bin/t2support.py Normal file
View file

@ -0,0 +1,132 @@
from tqdm import tqdm
from re import search
from requests import get
from hashlib import md5
from os import walk
from os.path import join, islink
from shutil import chown
from sys import exit
from textwrap import wrap
from itertools import chain
user = "t2server"
install_parent = "/opt"
install_dir = f"{install_parent}/t2server"
etc_dir = "/etc/t2server"
log_dir = "/var/log/t2server"
unit_dir = "/etc/systemd/system"
bin_dir = "/usr/local/bin"
class color:
X = '\033[m' # Reset
DR = '\033[31m' # Dark Red
DG = '\033[32m' # Dark Green
DY = '\033[33m' # Dark Yellow (Brown)
DU = '\033[34m' # Dark Blue
DP = '\033[35m' # Dark Purple
DC = '\033[36m' # Dark Cyan
DW = '\033[37m' # Dark White (light grey)
BK = '\033[90m' # Bright Black (dark grey)
BR = '\033[91m' # Bright Red
BG = '\033[92m' # Bright Green
BY = '\033[93m' # Bright Yellow
BU = '\033[94m' # Bright Blue
BP = '\033[95m' # Bright Purple
BC = '\033[96m' # Bright Cyan
BW = '\033[97m' # Bright White
class box:
TR = u'\u2510'
TL = u'\u250c'
BR = u'\u2518'
BL = u'\u2514'
H = u'\u2500'
V = u'\u2502'
def menu(option_list,header="",footer=""):
"""
This function takes a list of options and make a BBS-style menu out of them.
Each item should have a unique alphanum in square brackets (eg. "[Q]uit" or "[1] Quit")
that represents the letter the user will type to select that option. Additionally, one
option may start with ~~ to indicate that it is the default option. This option will be
selected if the user presses Enter without typing a letter. ~~ will not be displayed on
screen. The function returns the selected letter in uppercase.
"""
default=None
keys=[]
line_list=list(option_list)
if header:
header_list = wrap(header,width=76)
line_list.extend(header_list)
else:
header_list = None
if footer:
footer_list = wrap(footer,width=76)
line_list.extend(footer_list)
else:
footer_list = None
longest = max(list(chain((len(ele) for ele in line_list),[40])))
print(f"{color.BG}{box.TL}{box.H}{''.ljust(longest,box.H)}{box.H}{box.TR}{color.X}")
print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}")
if header_list:
for line in header_list:
print(f"{color.BG}{box.V} {color.DG}{line.ljust(longest)} {box.V}{color.X}")
print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}")
for option in option_list:
try:
key=search(r'\[([0-9a-zA-Z])\]', option).group(1)
except AttributeError:
pass
if option.startswith("~~"):
default = str(key)
keys.append(key.upper())
option=option[2:]
else:
keys.append(str(key).lower())
option=option.ljust(longest)
option=option.replace("[", color.BG + "[" + color.BW)
option=option.replace("]", color.BG + "]" + color.DG)
option=color.DG + option + color.X
print(f"{color.BG}{box.V} {option.ljust(longest)} {color.DG}{box.V}{color.X}")
if footer_list:
print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}")
for line in footer_list:
print(f"{color.BG}{box.V} {color.DG}{line.ljust(longest)} {box.V}{color.X}")
print(f"{color.BG}{box.V} {''.ljust(longest)} {color.DG}{box.V}{color.X}")
print(f"{color.BG}{box.BL}{color.DG}{box.H}{''.ljust(longest,box.H)}{box.H}{box.BR}{color.X}")
menu_choice=" "
if default:
prompt=f"{color.DG}Choice {color.BK}({color.BG}{default}{color.BK}){color.DG}: {color.X}"
else:
prompt=f"{color.DG}Choice: {color.X}"
while menu_choice.upper() not in [x.upper() for x in keys]:
menu_choice = str(input(prompt))
if default and menu_choice == "": menu_choice = default
return menu_choice.upper()
def chowner(path, owner):
""" Recursively chown path with owner as both user and group """
for dirpath, ignore, filenames in walk(path):
chown(dirpath, owner, owner)
for filename in filenames:
if not islink(join(dirpath, filename)):
chown(join(dirpath, filename), owner, owner)
def pinfo(message):
print(color.BU + message + color.X)
def pwarn(message):
print(color.BY + message + color.X)
def perror(message):
print(color.BR + message + color.X)
def bail(message=""):
exit(color.BR + message + color.X)
if __name__ == "__main__":
bail("This file contains only shared functions and variables for the other scripts and is not meant to be run interactively.")

View file

@ -0,0 +1,82 @@
; Define a logging function
Func WriteLog($ErrorMessage)
FileWriteLine("L:\install_wrapper.log", @MON & "/" & @MDAY & " " & @HOUR & ":" & @MIN & ":" & @SEC & " " & $ErrorMessage)
EndFunc
WriteLog("--- Starting script ---")
; Run the Tribes 2 installer
WriteLog("Launching tribes2_gsi.exe: " & Run("I:\tribes2_gsi.exe"))
; SLA
WriteLog("Wait for SLA window: " & WinWait("Software License Agreement", "Please read through the entire license agreement", 60))
WriteLog("Click [I Agree]: " & ControlClick("Software License Agreement", "Please read through the entire license agreement", 4)) ; click I Agree
; Welcome window
WriteLog("Wait for Welcome window: " & WinWait("Tribes 2", "Welcome to the Tribes 2 Installation!", 60))
WriteLog("Click [Skip]: " & ControlClick("Tribes 2", "Welcome to the Tribes 2 Installation!", 4)) ; click "Skip >"
; Tribes Vengeance preorder
WriteLog("Wait for Tribes Vengeance Preorder window: " & WinWait("Tribes: Vengeance", "Click here to Pre-order Tribes: Vengeance Now!", 60))
WriteLog("Click [Next]: " & ControlClick("Tribes: Vengeance", "Click here to Pre-order Tribes: Vengeance Now!", 5)) ; click "Next >"
; Welcome 2
WriteLog("Wait for Welcome: " & WinWait("Welcome", "Welcome to the Tribes 2 Setup program", 60))
WriteLog("Click [Next]: " & ControlClick("Welcome", "Welcome to the Tribes 2 Setup program", 3)) ; click "Next >"
; Credits
WriteLog("Wait for Credits window: " & WinWait("Credits", "It's not often that the community of a game", 60))
WriteLog("Click [Next]: " & ControlClick("Credits", "It's not often that the community of a game", 3)) ; click "Next >"
; Choose Destination Location
WriteLog("Wait for Choose Destination window: " & WinWait("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 60))
WriteLog("Click [Browse]: " & ControlClick("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 9)) ; click "Browse"
WriteLog("Wait for Browse window: " & WinWait("Select Destination Directory", "", 60))
WriteLog("Type 'T:\t2server': " & ControlSend("Select Destination Directory", "", 3, "T:\t2server")) ; update the destination path
WriteLog("Click [OK]: " & ControlClick("Select Destination Directory", "", 7)) ; click OK
WriteLog("Click [Yes] to use existing directory: " & ControlClick("Install", "The directory T:\t2server already exists", 1)) ; T:\t2server already exists, install anyway
WriteLog("Click [Next]: " & ControlClick("Choose Destination Location", "Setup will install Tribes 2 in the following folder.", 3)) ; click "Next >"
; Select Program Manager Group
WriteLog("Wait for Start Menu Group window: " & WinWait("Select Program Manager Group", "Enter the name of the Program Manager group to add Tribes 2 icons to:", 60))
WriteLog("Click [Next]: " & ControlClick("Select Program Manager Group", "Enter the name of the Program Manager group to add Tribes 2 icons to:", 3)) ; click "Next >"
; Start Installation
WriteLog("Wait for Start Installation window: " & WinWait("Start Installation", "You are now ready to install Tribes 2.", 60))
WriteLog("Click [Next]: " & ControlClick("Start Installation", "You are now ready to install Tribes 2.", 3)) ; click "Next >"
; Register
WriteLog("Wait for Register window: " & WinWait("Register", "You can register Tribes 2 on the World Wide Web.", 60))
WriteLog("Uncheck 'Register Tribes 2 Now': " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 9)) ; uncheck "Register Tribes 2 Now"
WriteLog("Click [Next]: " & ControlClick("Register", "You can register Tribes 2 on the World Wide Web.", 3)) ; click "Next >"
; DirectX 8a
WriteLog("Wait for DX8 Install window: " & WinWait("DirectX 8a", "DirectX 8a Install", 60))
WriteLog("Uncheck 'Install DirectX 8a': " & ControlClick("DirectX 8a", "DirectX 8a Install", 9)) ; uncheck "Install DirectX 8a"
WriteLog("Click [Next]: " & ControlClick("DirectX 8a", "DirectX 8a Install", 3)) ; click "Next >"
; Installation Complete
WriteLog("Wait for Installation Complete window: " & WinWait("Installation Complete", "Tribes 2 has been successfully installed.", 60))
WriteLog("Uncheck 'Open the Tribes 2 Readme': " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 9)) ; uncheck "Open the Tribes 2 Readme"
WriteLog("Uncheck 'Launch Tribes 2': " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 10)) ; uncheck "Launch Tribes 2"
WriteLog("Click [Next]: " & ControlClick("Installation Complete", "Tribes 2 has been successfully installed.", 3)) ; click "Next >"
; Wait up to a minute for the installer to complete and exit
WriteLog("Wait for tribes2_gsi.exe to exit: " & ProcessWaitClose("tribes2_gsi.exe", 60))
; Run the TribesNext Patch
WriteLog("Launching TribesNExt_rc2a.exe: " & Run("I:\TribesNext_rc2a.exe"))
; SLA
WriteLog("Wait for License Agreement window: " & WinWait("TribesNext Patcher: License Agreement", "Please review the license agreement before installing", 60))
WriteLog("Click [I Agree]: " & ControlClick("TribesNext Patcher: License Agreement", "Please review the license agreement before installing", 1)) ; click "I Agree"
; Installation Folder
WriteLog("Wait for Completed window: " & WinWait("TribesNext Patcher: Installation Folder", "Setup will apply the TribesNext multiplayer patch", 60))
WriteLog("Click [Apply Patch]: " & ControlClick("TribesNext Patcher: Installation Folder", "Setup will apply the TribesNext multiplayer patch", 1)) ; click "Apply Patch"
; Completed
WriteLog("Wait for SLA window: " & WinWait("TribesNext Patcher: Completed", "Completed", 60))
WriteLog("Click [Close]: " & ControlClick("TribesNext Patcher: Completed", "Completed", 1)) ; click "Close"
WriteLog("--- Script Complete ---")