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/{SERVER_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, "-c", working_dir, "-p", packages_dir, "-d", install_dir ] for file in workshop_files: args.append(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/rFactor 2 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\\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\\steamapps\\common\\rFactor 2\\UserData\\ServerUnlock.bin") print(f"\t7 - Copy ServerUnlock.bin back to {server_path}/UserData/ServerUnlock.bin") if not start_server(): 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(): 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)