diff --git a/rf2/setup_server.py b/rf2/setup_server.py index 34d806e..00c23cf 100644 --- a/rf2/setup_server.py +++ b/rf2/setup_server.py @@ -1,7 +1,11 @@ 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 @@ -24,8 +28,8 @@ import zipfile STEAM_CMD_URL = "https://steamcdn-a.akamaihd.net/client/installer/steamcmd.zip" -SERVER_APP_ID = 400300 -GAME_APP_ID = 365960 +SERVER_APP_ID = "400300" +GAME_APP_ID = "365960" # 917387157 - Silverstone 2012 # 1515650133 - McLaren MP4/13 @@ -98,7 +102,7 @@ def download_server(steamcmd_path: str) -> bool: "+login", "anonymous", "+app_update", - "400300", + SERVER_APP_ID, "validate", "+quit" ] @@ -107,53 +111,175 @@ def download_server(steamcmd_path: str) -> bool: return True def read_steam_credentials(credentials_file: str) -> tuple: - return (False, {}) + 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: - return (False, []) + print("Reading Steam Workshop content file...") -def download_workshop(server_base_path: str, workshop_content: list) -> bool: - return False + out_content = [] -def move_workshop(server_base_path: str, workshop_content: list) -> bool: - return False + with open(content_file, "rt") as content: + content_ids = content.readlines() + for content_id in content_ids: + if content_id.strip() == "": + break -def install_workshop(server_base_path: str, workshop_content: list) -> bool: - return False + if not content_id.isnumeric(): + print(f"Error: Invalid content ID '{content_id}', all IDs should be numeric!") + return (False, []) -def uninstall_workshop(server_base_path: str, workshop_content: list) -> bool: - return False + out_content.append(content_id) -def open_mas2(server_base_path: str) -> bool: + 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 - return False + print("Opening MAS2 tool...") + mas2_path = f"{server_path}/Support/Tools/MAS2_x64.exe" -def start_server(server_base_path: str) -> bool: - return False + subprocess.run( + [ + mas2_path + ] + ) -def server_keys(server_base_path: str) -> bool: - # download + install basic content here + 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_base_path}/UserData/ServerKeys.bin") - print("\t3 - Copy this file to your main rFactor 2 installation which owns the paid DLC you are installing") + 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("\t4 - Start a single player race (any content I think)") - print("\t5 - There should not be a ServerUnlock.bin file in the folder you copied ServerKeys.bin to") + 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"\t6 - Copy ServerUnlock.bin back to {server_base_path}/UserData/ServerUnlock.bin") + print(f"\t7 - Copy ServerUnlock.bin back to {server_path}/UserData/ServerUnlock.bin") - # start server here - dont think this can be scripted? + if not start_server(): + return False - input("\nPress Enter once you have completed those steps...") + input("\nPress Enter once you have completed those steps: ") return True -def setup_server(server_name: str, server_path: str, content_file: str, credentials_file: str) -> bool: - server_base_path = f"{server_path}/{server_name}" +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, server_path): + if not create_server_dir(server_base_path, parent_path): return False if not download_steamcmd(steamcmd_path): @@ -165,7 +291,31 @@ def setup_server(server_name: str, server_path: str, content_file: str, credenti if not download_server(steamcmd_path): return False - if not server_keys(server_base_path): + 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 @@ -176,7 +326,7 @@ if __name__ == "__main__": 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") + parser.add_argument("credentials_file", type=str, help="Path to file containing Steam username and password in format 'username password'") args = parser.parse_args()