Files
python-scripts/rf2/setup_server.py
2022-02-03 18:57:59 +00:00

335 lines
9.9 KiB
Python

import argparse
from gettext import install
from http import server
from logging import exception
import os
from posixpath import split
import requests
import shutil
import subprocess
import zipfile
# Download / extract SteamCMD
# Download game server
# Download workshop - config for this
# Move workshop files to game server folder
# Can MAS2 etc be scripted?
# Also need to deal with keys
# Run server with some basic content (have default car / track to download)
# Once the server closes, ServerKeys.bin (or whatever its called) should be there
# Optionally copy this somewhere?
# Possible args:
# Server name
# Path to install
# Car list
# Track list
STEAM_CMD_URL = "https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip"
SERVER_APP_ID = "400300"
GAME_APP_ID = "365960"
# 917387157 - Silverstone 2012
# 1515650133 - McLaren MP4/13
SERVER_SETUP_CONTENT = ["917387157", "1515650133"]
def create_server_dir(server_base_path: str, server_path: str) -> bool:
if not os.path.exists(server_path):
response = input(f"'{server_path}' does not exist. Create? y/N: ")
if response.upper == "Y":
try:
os.makedirs(server_path)
except Exception as e:
print(f"\nError: Could not create '{server_path}' - {e}")
return False
else:
return False
if os.path.exists(server_base_path):
print(f"\nError: '{server_base_path}' already exists!")
return False
try:
os.mkdir(server_base_path)
except Exception as e:
print(f"\nError: Could not create '{server_base_path}' - {e}")
return True
def download_steamcmd(steamcmd_path: str) -> bool:
# Make dir for steamcmd
print(f"Creating directory for SteamCMD at '{steamcmd_path}'...")
if os.path.exists(steamcmd_path):
print(f"\nError: '{steamcmd_path}' already exists!")
return False
try:
os.mkdir(steamcmd_path)
except Exception as e:
print(f"\nError: Could not create '{steamcmd_path}' - {e}")
return False
print("Downloading SteamCMD...")
print(f"\t{STEAM_CMD_URL}")
response = requests.get(STEAM_CMD_URL)
print("Writing downloaded file...")
with open(f"{steamcmd_path}/steamcmd.zip", "wb") as steamcmd_file:
steamcmd_file.write(response.content)
return True
def extract_steamcmd(steamcmd_path: str) -> bool:
print("Extracting SteamCMD zip...")
with zipfile.ZipFile(f"{steamcmd_path}/steamcmd.zip", "r") as steamcmd_zip:
steamcmd_zip.extractall(steamcmd_path)
# Remove steamcmd.zip folder
print("Removing downloaded zip...")
os.remove(f"{steamcmd_path}/steamcmd.zip")
return True
def download_server(steamcmd_path: str) -> bool:
print("Downloading rFactor 2 server...")
subprocess.run(
[
f"{steamcmd_path}/steamcmd.exe",
"+login",
"anonymous",
"+app_update",
SERVER_APP_ID,
"validate",
"+quit"
]
)
return True
def read_steam_credentials(credentials_file: str) -> tuple:
print("Reading Steam credentials file...")
with open(credentials_file, "rt") as creds:
credentials = creds.readline()
split_creds = credentials.split(" ", 1)
if len(split_creds) != 2:
print("Error: Could not parse username and password!")
return (False, [])
return (True, split_creds)
def read_workshop(content_file: str) -> tuple:
print("Reading Steam Workshop content file...")
out_content = []
with open(content_file, "rt") as content:
content_ids = content.readlines()
for content_id in content_ids:
stripped_id = content_id.strip()
if stripped_id == "":
break
if not stripped_id.isnumeric():
print(f"Error: Invalid content ID '{stripped_id}', all IDs should be numeric!")
return (False, [])
if stripped_id in SERVER_SETUP_CONTENT:
continue
out_content.append(stripped_id)
print(f"Found {len(out_content)} content IDs")
return (True, out_content)
def download_workshop(steamcmd_path: str, workshop_content: list, credentials: list) -> bool:
args = [
f"{steamcmd_path}/steamcmd.exe",
"+login",
credentials[0],
credentials[1]
]
for content_id in workshop_content:
args.append("+workshop_download_item")
args.append(GAME_APP_ID)
args.append(content_id)
args.append("validate")
args.append("+quit")
subprocess.run(args)
return True
def move_workshop(server_path: str, steamcmd_path: str, workshop_content: list) -> tuple:
print("Moving workshop files...")
workshop_path = f"{steamcmd_path}/steamapps/workshop/content/{GAME_APP_ID}"
packages_path = f"{server_path}/Packages"
moved_files = []
for content_id in workshop_content:
content_path = f"{workshop_path}/{content_id}"
if not os.path.exists(content_path):
print(f"Error: '{content_path}' does not exist!")
return (False, [])
files = os.listdir(content_path)
for file in files:
if os.path.splitext(file)[1] == ".rfcmp":
file_path = f"{content_path}/{file}"
print(f"Moving '{file}'...")
shutil.move(file_path, f"{packages_path}/{file}")
moved_files.append(file)
return (True, moved_files)
def install_workshop(server_path: str, workshop_files: list) -> bool:
print("Installing workshop files...")
mod_mgr_path = f"{server_path}/Bin64/ModMgr.exe"
working_dir = server_path
packages_dir = f"{server_path}/Packages"
install_dir = f"{server_path}/Installed"
args = [
mod_mgr_path,
f"-c{working_dir}",
f"-p{packages_dir}",
f"-d{install_dir}"
]
for file in workshop_files:
args.append(f"-i{file}")
subprocess.run(args)
return True
def uninstall_workshop(server_path: str, workshop_content: list) -> bool:
print("Can't uninstall yet!")
return True
def open_mas2(server_path: str) -> bool:
# MIGHT be able to create MAS files using the ModMgr it seems
print("Opening MAS2 tool...")
mas2_path = f"{server_path}/Support/Tools/MAS2_x64.exe"
subprocess.run(
[
mas2_path
]
)
return True
def start_server(server_path: str) -> bool:
print("Starting server...")
subprocess.run(
[
f"{server_path}/Bin64/rFactor2 Dedicated.exe"
]
)
return True
def server_keys(server_path: str, steamcmd_path: str, credentials: list) -> bool:
print("Downloading setup content so a server can be created...")
if not download_workshop(steamcmd_path, SERVER_SETUP_CONTENT, credentials):
return False
result, workshop_files = move_workshop(server_path, steamcmd_path, SERVER_SETUP_CONTENT)
if not result:
return False
if not install_workshop(server_path, workshop_files):
return False
if not open_mas2(server_path):
return False
print("Starting server to get server keys file")
print("\t1 - Create a server using basic content")
print(f"\t2 - Once the server is running, ServerKeys.bin should exist at {server_path}/UserData/ServerKeys.bin")
print("\t3 - Close the server")
print("\t4 - Copy this file to your main rFactor 2 installation which owns the paid DLC you are installing")
print("\t\t<steam_games_dir>\\steamapps\\common\\rFactor 2\\UserData\\ServerKeys.bin")
print("\t5 - Start a single player race (any content I think)")
print("\t6 - There should not be a ServerUnlock.bin file in the folder you copied ServerKeys.bin to")
print("\t\t<steam_games_dir>\\steamapps\\common\\rFactor 2\\UserData\\ServerUnlock.bin")
print(f"\t7 - Copy ServerUnlock.bin back to {server_path}/UserData/ServerUnlock.bin")
if not start_server(server_path):
return False
input("\nPress Enter once you have completed those steps: ")
return True
def setup_server(server_name: str, parent_path: str, content_file: str, credentials_file: str) -> bool:
server_base_path = f"{parent_path}/{server_name}"
steamcmd_path = f"{server_base_path}/steamcmd"
server_path = f"{steamcmd_path}/steamapps/common/rFactor 2 Dedicated Server"
if not create_server_dir(server_base_path, parent_path):
return False
if not download_steamcmd(steamcmd_path):
return False
if not extract_steamcmd(steamcmd_path):
return False
if not download_server(steamcmd_path):
return False
result, credentials = read_steam_credentials(credentials_file)
if not result:
return False
result, content = read_workshop(content_file)
if not result:
return False
if not server_keys(server_path, steamcmd_path, credentials):
return False
if not download_workshop(steamcmd_path, content, credentials):
return False
result, workshop_files = move_workshop(server_path, steamcmd_path, content)
if not result:
return False
if not install_workshop(server_path, workshop_files):
return False
if not open_mas2(server_path):
return False
if not start_server(server_path):
return False
return True
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Set up rFactor 2 server :)")
parser.add_argument("name", type=str, help="Friendly name for the server")
parser.add_argument("path", type=str, help="Path to download the server to")
parser.add_argument("content_file", type=str, help="Path to a list of workshop IDs to download and install onto the server")
parser.add_argument("credentials_file", type=str, help="Path to file containing Steam username and password in format 'username password'")
args = parser.parse_args()
setup_server(args.name, os.path.abspath(args.path), args.content_file, args.credentials_file)