mirror of
https://github.com/greenseeker/t2server.git
synced 2026-07-14 07:54:33 +00:00
First Commit
This commit is contained in:
commit
8a9f6b6510
17 changed files with 1407 additions and 0 deletions
24
addons/readme.txt
Normal file
24
addons/readme.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
This directory can hold addons that you want automatically added to your server.
|
||||||
|
|
||||||
|
Any .vl2 files placed here will be copied into the /opt/t2server/GameData/base
|
||||||
|
directory.
|
||||||
|
|
||||||
|
Any .zip, .tar, .tgz, or .tar.gz files placed here will be extracted into
|
||||||
|
/opt/t2server/GameData. This is intended for deploying mods. The setup script
|
||||||
|
will not create a directory, so be sure your archive contains the parent
|
||||||
|
directory for the mod. For example:
|
||||||
|
# tar -tf Version2.tar
|
||||||
|
Version2/
|
||||||
|
Version2/V2_Features.txt
|
||||||
|
Version2/prefs/
|
||||||
|
Version2/prefs/banlist.cs
|
||||||
|
Version2/prefs/11.29.2020Connect.log
|
||||||
|
Version2/prefs/ServerPrefs.cs
|
||||||
|
Version2/prefs/SctfPrefs.cs.dso
|
||||||
|
Version2/prefs/SctfPrefs.cs
|
||||||
|
Version2/prefs/ClientPrefs.cs
|
||||||
|
Version2/Version2.txt
|
||||||
|
...
|
||||||
|
|
||||||
|
If your mod archive doesn't start with a directory, it will just make a mess of
|
||||||
|
GameData when extracted. There are no safety checks in the script.
|
||||||
10
etc/systemd/system/t2bouncer.service
Normal file
10
etc/systemd/system/t2bouncer.service
Normal 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
|
||||||
10
etc/systemd/system/t2bouncer.timer
Normal file
10
etc/systemd/system/t2bouncer.timer
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
[Unit]
|
||||||
|
Description=t2bouncer timer
|
||||||
|
BindsTo=t2server.service
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
Unit=t2bouncer.service
|
||||||
|
OnCalendar=*:00:00
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
36
etc/systemd/system/t2server.service
Normal file
36
etc/systemd/system/t2server.service
Normal 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
44
etc/t2server/config.yaml
Normal 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
|
||||||
52
etc/t2server/serverprefs/Classic_CTF.cs
Normal file
52
etc/t2server/serverprefs/Classic_CTF.cs
Normal 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
304
setup
Executable 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
196
sla/tribes2.txt
Normal 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
104
sla/tribesnext.txt
Normal 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 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.
|
||||||
|
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
1
t2support.py
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
usr/local/bin/t2support.py
|
||||||
31
usr/local/bin/t2bouncer
Executable file
31
usr/local/bin/t2bouncer
Executable 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
103
usr/local/bin/t2fixer
Executable 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
22
usr/local/bin/t2help
Executable 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
64
usr/local/bin/t2remove
Executable 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
192
usr/local/bin/t2server
Executable 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
132
usr/local/bin/t2support.py
Normal 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.")
|
||||||
82
winbin/install_wrapper.au3
Normal file
82
winbin/install_wrapper.au3
Normal 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 ---")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue