class AstroLauncher(): """ Starts a new instance of the Server Launcher""" @dataclasses.dataclass class LauncherConfig(): DisableAutoUpdate: bool = False UpdateOnServerRestart: bool = True HideServerConsoleWindow: bool = False HideLauncherConsoleWindow: bool = False ServerStatusFrequency: float = 2 PlayfabAPIFrequency: float = 2 DisableBackupRetention: bool = False BackupRetentionPeriodHours: float = 72 BackupRetentionFolderLocation: str = r"Astro\Saved\Backup\LauncherBackups" EnableAutoRestart: bool = False AutoRestartEveryHours: float = 24 AutoRestartSyncTimestamp: str = "00:00" DisableNetworkCheck: bool = False OverwritePublicIP: bool = False ShowServerFPSInConsole: bool = True DisableWebServer: bool = False WebServerPort: int = 5000 WebServerPasswordHash: str = "" EnableWebServerSSL: bool = False SSLPort: int = 443 SSLCertFile: str = "" SSLKeyFile: str = "" CPUAffinity: str = "" def __post_init__(self): # pylint: disable=no-member hasError = False for field, data in self.__dataclass_fields__.items(): try: self.__dict__[field] = data.type(self.__dict__[field]) except ValueError: hasError = True AstroLogging.logPrint( f"INI error: {field} must be of type {data.type.__name__}", "critical") if hasError: AstroLogging.logPrint("Fix your launcher config file!", "critical") sys.exit() class SaveHandler(FileSystemEventHandler): def __init__(self, launcher): self.launcher = launcher self.astroPath = self.launcher.astroPath self.moveToPath = self.launcher.launcherConfig.BackupRetentionFolderLocation super().__init__() def on_created(self, event): # print(event) # time.sleep(1) try: time.sleep(0.5) dirName = os.path.dirname(event.src_path) fileNames = [ os.path.join(dirName, f) for f in os.listdir(dirName) if os.path.isfile(os.path.join(dirName, f)) ] # print(fileNames) fileName = sorted(fileNames, key=os.path.getmtime, reverse=True)[0] AstroLogging.logPrint( f"Server saved. {os.path.basename(fileName)}") except: pass # self.launcher.saveObserver.stop() class BackupHandler(FileSystemEventHandler): def __init__(self, launcher): self.launcher = launcher self.astroPath = self.launcher.astroPath self.moveToPath = self.launcher.launcherConfig.BackupRetentionFolderLocation self.retentionPeriodHours = self.launcher.launcherConfig.BackupRetentionPeriodHours self.pendingFiles = [] super().__init__() def handle_files(self): #print(f"first: {self.pendingFiles}") time.sleep(2) #print(f"second: {self.pendingFiles}") path = os.path.join(self.astroPath, self.moveToPath) try: if not os.path.exists(path): os.makedirs(path) except Exception as e: AstroLogging.logPrint(e, "error") now = time.time() try: for f in os.listdir(path): fpath = os.path.join(path, f) if os.stat(fpath).st_mtime < ( now - (self.retentionPeriodHours * 60 * 60)): os.remove(fpath) except Exception as e: AstroLogging.logPrint(e, "error") AstroLogging.logPrint("Copying backup(s) to retention folder.") # time.sleep(1) try: dirName = os.path.dirname(self.pendingFiles[0]) fileNames = [ os.path.join(dirName, f) for f in os.listdir(dirName) if os.path.isfile(os.path.join(dirName, f)) ] for cFile in fileNames: # AstroLogging.logPrint(newFile, "debug") # print(cFile) shutil.copy2(cFile, path) # AstroLogging.logPrint(copiedFile, "debug") except FileNotFoundError as e: AstroLogging.logPrint(e, "error") except Exception as e: AstroLogging.logPrint(e, "error") self.launcher.backupObserver.stop() self.launcher.backup_retention() def on_modified(self, event): # print(event) # AstroLogging.logPrint("File in save directory changed") try: self.pendingFiles.append(event.src_path) if len(self.pendingFiles) == 1: t = Thread(target=self.handle_files, args=()) t.daemon = True t.start() except: pass def __init__(self, astroPath, launcherINI="Launcher.ini", disable_auto_update=None): AstroLogging.setup_logging() # check if path specified if astroPath is not None: if os.path.exists(os.path.join(astroPath, "AstroServer.exe")): self.astroPath = astroPath else: AstroLogging.logPrint( "Specified path does not contain the server executable! (AstroServer.exe)", "critical") time.sleep(5) return # check if executable in current directory elif os.path.exists(os.path.join(os.getcwd(), "AstroServer.exe")): self.astroPath = os.getcwd() # fallback to automatic detection (experimental, do NOT rely on it) else: try: autoPath = AstroAPI.getInstallPath() if os.path.exists(os.path.join(autoPath, "AstroServer.exe")): self.astroPath = autoPath except: AstroLogging.logPrint( "Unable to find server executable anywhere! (AstroServer.exe)", "critical") time.sleep(5) return AstroLogging.setup_loggingPath(self.astroPath) self.launcherINI = launcherINI self.launcherConfig = self.LauncherConfig() self.launcherPath = os.getcwd() self.refresh_launcher_config() if disable_auto_update is not None: self.launcherConfig.DisableAutoUpdate = disable_auto_update self.version = "v1.6.0" AstroLogging.logPrint( f"AstroLauncher - Unofficial Dedicated Server Launcher {self.version}" ) AstroLogging.logPrint( "If you encounter any bugs please open a new issue at:") AstroLogging.logPrint( "https://github.com/ricky-davis/AstroLauncher/issues") AstroLogging.logPrint( "To safely stop the launcher and server press CTRL+C") self.latestURL = "https://github.com/ricky-davis/AstroLauncher/releases/latest" bName = os.path.basename(sys.executable) if sys.argv[0] == os.path.splitext(bName)[0]: self.isExecutable = True else: self.isExecutable = os.path.samefile(sys.executable, sys.argv[0]) self.headers = AstroAPI.base_headers self.DaemonProcess = None self.saveObserver = None self.backupObserver = None self.hasUpdate = False self.affinity = self.launcherConfig.CPUAffinity try: if self.affinity != "": affinityList = [ int(x.strip()) for x in self.affinity.split(',') ] p = psutil.Process() p.cpu_affinity(affinityList) except ValueError as e: AstroLogging.logPrint(f"CPU Affinity Error: {e}", "critical") AstroLogging.logPrint( "Please correct this in your launcher config", "critical") return self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) self.check_for_update() AstroLogging.logPrint("Starting a new session") if not self.launcherConfig.DisableNetworkCheck: AstroLogging.logPrint("Checking the network configuration..") self.check_network_config() self.save_reporting() if not self.launcherConfig.DisableBackupRetention: self.backup_retention() AstroLogging.logPrint("Backup retention started") # setup queue for data exchange if not self.launcherConfig.DisableWebServer: # start http server self.webServer = self.start_WebServer() # AstroLogging.logPrint( # f"HTTP Server started at 127.0.0.1:{self.launcherConfig.WebServerPort}") if self.launcherConfig.HideLauncherConsoleWindow: # hide window AstroLogging.logPrint( "HideLauncherConsoleWindow enabled, Hiding window in 5 seconds..." ) time.sleep(5) # pylint: disable=redefined-outer-name kernel32 = ctypes.WinDLL('kernel32') user32 = ctypes.WinDLL('user32') hWnd = kernel32.GetConsoleWindow() user32.ShowWindow(hWnd, 0) self.start_server(firstLaunch=True) def save_reporting(self): if self.saveObserver: if not self.saveObserver.is_alive(): self.saveObserver = None self.save_reporting() else: self.saveObserver = Observer() saveGamePath = r"Astro\Saved\SaveGames" watchPath = os.path.join(self.astroPath, saveGamePath) try: if not os.path.exists(watchPath): os.makedirs(watchPath) except Exception as e: AstroLogging.logPrint(e) self.saveObserver.schedule(self.SaveHandler(self), watchPath) self.saveObserver.start() def backup_retention(self): if self.backupObserver: if not self.backupObserver.is_alive(): self.backupObserver = None self.backup_retention() else: self.backupObserver = Observer() backupSaveGamePath = r"Astro\Saved\Backup\SaveGames" watchPath = os.path.join(self.astroPath, backupSaveGamePath) try: if not os.path.exists(watchPath): os.makedirs(watchPath) except Exception as e: AstroLogging.logPrint(e) self.backupObserver.daemon = True self.backupObserver.schedule(self.BackupHandler(self), watchPath) self.backupObserver.start() def refresh_launcher_config(self, lcfg=None): field_names = set(f.name for f in dataclasses.fields(self.LauncherConfig)) cleaned_config = { k: v for k, v in self.get_launcher_config(lcfg).items() if k in field_names } self.launcherConfig = dataclasses.replace(self.launcherConfig, **cleaned_config) config = MultiConfig() config.read_dict({"AstroLauncher": cleaned_config}) with open(self.launcherINI, 'w') as configfile: config.write(configfile) def overwrite_launcher_config(self, ovrDict): ovrConfig = {"AstroLauncher": ovrDict} MultiConfig().overwrite_with(self.launcherINI, ovrConfig) def get_launcher_config(self, lfcg=None): if not lfcg: lfcg = self.LauncherConfig() baseConfig = {"AstroLauncher": dataclasses.asdict(lfcg)} config = MultiConfig().baseline(self.launcherINI, baseConfig) # print(settings) settings = config.getdict()['AstroLauncher'] return settings def check_for_update(self, serverStart=False): try: url = "https://api.github.com/repos/ricky-davis/AstroLauncher/releases/latest" data = ((requests.get(url)).json()) latestVersion = data['tag_name'] if latestVersion != self.version: self.hasUpdate = latestVersion AstroLogging.logPrint( f"UPDATE: There is a newer version of the launcher out! {latestVersion}" ) AstroLogging.logPrint(f"Download it at {self.latestURL}") aupdate = not self.launcherConfig.DisableAutoUpdate if not self.launcherConfig.UpdateOnServerRestart and serverStart: return if self.isExecutable and aupdate: self.autoupdate(data) except: pass def autoupdate(self, data): x = data downloadFolder = os.path.dirname(sys.executable) for fileObj in x['assets']: downloadURL = fileObj['browser_download_url'] fileName = (os.path.splitext(fileObj['name'])[0]) downloadPath = os.path.join(downloadFolder, fileName) downloadCMD = [ "powershell", '-executionpolicy', 'bypass', '-command', 'Write-Host "Downloading latest AstroLauncher.exe..";', 'wait-process', str(os.getpid()), ';', '[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;', "$ProgressPreference = 'SilentlyContinue';", 'Invoke-WebRequest', f"'{downloadURL}'", "-OutFile", f"'{downloadPath + '_new.exe'}'", ';', "Move-Item", "-path", f"'{downloadPath + '_new.exe'}'", "-destination", f"'{downloadPath + '.exe'}'", "-Force;", 'Write-Host "Download complete!";', 'Start-Process', f"'{downloadPath + '.exe'}'" ] # print(' '.join(downloadCMD)) subprocess.Popen(downloadCMD, shell=True, creationflags=subprocess.DETACHED_PROCESS) time.sleep(2) self.DedicatedServer.kill_server("Auto-Update") def start_server(self, firstLaunch=False): """ Starts the Dedicated Server process and waits for it to be registered """ if firstLaunch: atexit.register(self.DedicatedServer.kill_server, reason="Launcher shutting down", save=True) else: self.check_for_update(serverStart=True) self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) self.DedicatedServer.status = "starting" self.DedicatedServer.busy = False self.headers['X-Authorization'] = AstroAPI.generate_XAUTH( self.DedicatedServer.settings.ServerGuid) oldLobbyIDs = self.DedicatedServer.deregister_all_server() AstroLogging.logPrint("Starting Server process...") if self.launcherConfig.EnableAutoRestart: AstroLogging.logPrint( f"Next restart is at {self.DedicatedServer.nextRestartTime}") # time.sleep(5) startTime = time.time() self.DedicatedServer.start() self.DaemonProcess = AstroDaemon.launch( executable=self.isExecutable, consolePID=self.DedicatedServer.process.pid) # Wait for server to finish registering... while not self.DedicatedServer.registered: try: serverData = (AstroAPI.get_server( self.DedicatedServer.ipPortCombo, self.headers)) serverData = serverData['data']['Games'] lobbyIDs = [x['LobbyID'] for x in serverData] if len(set(lobbyIDs) - set(oldLobbyIDs)) == 0: time.sleep(self.launcherConfig.PlayfabAPIFrequency) else: now = time.time() if now - startTime > 15: self.DedicatedServer.registered = True del oldLobbyIDs self.DedicatedServer.LobbyID = serverData[0]['LobbyID'] if self.DedicatedServer.process.poll() is not None: AstroLogging.logPrint( "Server was forcefully closed before registration. Exiting...." ) return False except KeyboardInterrupt: self.DedicatedServer.kill_server("Launcher shutting down") except: AstroLogging.logPrint( "Failed to check server. Probably hit rate limit. Backing off and trying again..." ) self.launcherConfig.PlayfabAPIFrequency += 1 time.sleep(self.launcherConfig.PlayfabAPIFrequency) doneTime = time.time() elapsed = doneTime - startTime AstroLogging.logPrint( f"Server ready! Took {round(elapsed,2)} seconds to register." ) # {self.DedicatedServer.LobbyID} self.DedicatedServer.status = "ready" self.DedicatedServer.server_loop() def check_network_config(self): networkCorrect = ValidateSettings.test_network( self.DedicatedServer.settings.PublicIP, int(self.DedicatedServer.settings.Port), False) if networkCorrect: AstroLogging.logPrint("Server network configuration good!") else: AstroLogging.logPrint( "I can't seem to validate your network settings..", "warning") AstroLogging.logPrint( f"Make sure to Port Forward ({self.DedicatedServer.settings.Port} UDP) and enable NAT Loopback", "warning") AstroLogging.logPrint("If nobody can connect, Port Forward.", "warning") AstroLogging.logPrint( "If others are able to connect, but you aren't, enable NAT Loopback.", "warning") rconNetworkCorrect = not (ValidateSettings.test_network( self.DedicatedServer.settings.PublicIP, int(self.DedicatedServer.settings.ConsolePort), True)) if rconNetworkCorrect: AstroLogging.logPrint("Remote Console network configuration good!") else: AstroLogging.logPrint( f"SECURITY ALERT: Your console port ({self.DedicatedServer.settings.ConsolePort}) is Port Forwarded!", "warning") AstroLogging.logPrint( "SECURITY ALERT: This allows anybody to control your server.", "warning") AstroLogging.logPrint( "SECURITY ALERT: Disable this ASAP to prevent issues.", "warning") time.sleep(5) def start_WebServer(self): ws = AstroWebServer.WebServer(self) def start_WebServerThread(): if sys.version_info.minor > 7: asyncio.set_event_loop_policy( asyncio.WindowsSelectorEventLoopPolicy()) asyncio.set_event_loop(asyncio.new_event_loop()) ws.run() t = Thread(target=start_WebServerThread, args=()) t.daemon = True t.start() return ws
def start_server(self, firstLaunch=False): """ Starts the Dedicated Server process and waits for it to be registered """ if firstLaunch: atexit.register(self.DedicatedServer.kill_server, reason="Launcher shutting down", save=True) else: self.check_for_update(serverStart=True) self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) self.DedicatedServer.status = "starting" self.DedicatedServer.busy = False self.headers['X-Authorization'] = AstroAPI.generate_XAUTH( self.DedicatedServer.settings.ServerGuid) oldLobbyIDs = self.DedicatedServer.deregister_all_server() AstroLogging.logPrint("Starting Server process...") if self.launcherConfig.EnableAutoRestart: AstroLogging.logPrint( f"Next restart is at {self.DedicatedServer.nextRestartTime}") # time.sleep(5) startTime = time.time() self.DedicatedServer.start() self.DaemonProcess = AstroDaemon.launch( executable=self.isExecutable, consolePID=self.DedicatedServer.process.pid) # Wait for server to finish registering... while not self.DedicatedServer.registered: try: serverData = (AstroAPI.get_server( self.DedicatedServer.ipPortCombo, self.headers)) serverData = serverData['data']['Games'] lobbyIDs = [x['LobbyID'] for x in serverData] if len(set(lobbyIDs) - set(oldLobbyIDs)) == 0: time.sleep(self.launcherConfig.PlayfabAPIFrequency) else: now = time.time() if now - startTime > 15: self.DedicatedServer.registered = True del oldLobbyIDs self.DedicatedServer.LobbyID = serverData[0]['LobbyID'] if self.DedicatedServer.process.poll() is not None: AstroLogging.logPrint( "Server was forcefully closed before registration. Exiting...." ) return False except KeyboardInterrupt: self.DedicatedServer.kill_server("Launcher shutting down") except: AstroLogging.logPrint( "Failed to check server. Probably hit rate limit. Backing off and trying again..." ) self.launcherConfig.PlayfabAPIFrequency += 1 time.sleep(self.launcherConfig.PlayfabAPIFrequency) doneTime = time.time() elapsed = doneTime - startTime AstroLogging.logPrint( f"Server ready! Took {round(elapsed,2)} seconds to register." ) # {self.DedicatedServer.LobbyID} self.DedicatedServer.status = "ready" self.DedicatedServer.server_loop()
def __init__(self, astroPath, launcherINI="Launcher.ini", disable_auto_update=None): AstroLogging.setup_logging() # check if path specified if astroPath is not None: if os.path.exists(os.path.join(astroPath, "AstroServer.exe")): self.astroPath = astroPath else: AstroLogging.logPrint( "Specified path does not contain the server executable! (AstroServer.exe)", "critical") time.sleep(5) return # check if executable in current directory elif os.path.exists(os.path.join(os.getcwd(), "AstroServer.exe")): self.astroPath = os.getcwd() # fallback to automatic detection (experimental, do NOT rely on it) else: try: autoPath = AstroAPI.getInstallPath() if os.path.exists(os.path.join(autoPath, "AstroServer.exe")): self.astroPath = autoPath except: AstroLogging.logPrint( "Unable to find server executable anywhere! (AstroServer.exe)", "critical") time.sleep(5) return AstroLogging.setup_loggingPath(self.astroPath) self.launcherINI = launcherINI self.launcherConfig = self.LauncherConfig() self.launcherPath = os.getcwd() self.refresh_launcher_config() if disable_auto_update is not None: self.launcherConfig.DisableAutoUpdate = disable_auto_update self.version = "v1.6.0" AstroLogging.logPrint( f"AstroLauncher - Unofficial Dedicated Server Launcher {self.version}" ) AstroLogging.logPrint( "If you encounter any bugs please open a new issue at:") AstroLogging.logPrint( "https://github.com/ricky-davis/AstroLauncher/issues") AstroLogging.logPrint( "To safely stop the launcher and server press CTRL+C") self.latestURL = "https://github.com/ricky-davis/AstroLauncher/releases/latest" bName = os.path.basename(sys.executable) if sys.argv[0] == os.path.splitext(bName)[0]: self.isExecutable = True else: self.isExecutable = os.path.samefile(sys.executable, sys.argv[0]) self.headers = AstroAPI.base_headers self.DaemonProcess = None self.saveObserver = None self.backupObserver = None self.hasUpdate = False self.affinity = self.launcherConfig.CPUAffinity try: if self.affinity != "": affinityList = [ int(x.strip()) for x in self.affinity.split(',') ] p = psutil.Process() p.cpu_affinity(affinityList) except ValueError as e: AstroLogging.logPrint(f"CPU Affinity Error: {e}", "critical") AstroLogging.logPrint( "Please correct this in your launcher config", "critical") return self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) self.check_for_update() AstroLogging.logPrint("Starting a new session") if not self.launcherConfig.DisableNetworkCheck: AstroLogging.logPrint("Checking the network configuration..") self.check_network_config() self.save_reporting() if not self.launcherConfig.DisableBackupRetention: self.backup_retention() AstroLogging.logPrint("Backup retention started") # setup queue for data exchange if not self.launcherConfig.DisableWebServer: # start http server self.webServer = self.start_WebServer() # AstroLogging.logPrint( # f"HTTP Server started at 127.0.0.1:{self.launcherConfig.WebServerPort}") if self.launcherConfig.HideLauncherConsoleWindow: # hide window AstroLogging.logPrint( "HideLauncherConsoleWindow enabled, Hiding window in 5 seconds..." ) time.sleep(5) # pylint: disable=redefined-outer-name kernel32 = ctypes.WinDLL('kernel32') user32 = ctypes.WinDLL('user32') hWnd = kernel32.GetConsoleWindow() user32.ShowWindow(hWnd, 0) self.start_server(firstLaunch=True)
def start_server(self, firstLaunch=False): """ Starts the Dedicated Server process and waits for it to be registered """ if firstLaunch: atexit.register(self.DedicatedServer.kill_server, reason="Launcher shutting down via exit", save=True) signal.signal(signal.SIGINT, self.signal_handler) else: self.check_for_update(serverStart=True) self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) self.DedicatedServer.status = "starting" self.DedicatedServer.busy = False gxAuth = None while gxAuth is None: try: gxAuth = AstroAPI.generate_XAUTH( self.DedicatedServer.settings.ServerGuid) except: AstroLogging.logPrint( "Unable to generate XAuth token... Are you connected to the internet?", msgType="warning") time.sleep(5) self.headers['X-Authorization'] = gxAuth oldLobbyIDs = self.DedicatedServer.deregister_all_server() AstroLogging.logPrint("Starting Server process...") if self.launcherConfig.EnableAutoRestart: AstroLogging.logPrint( f"Next restart is at {self.DedicatedServer.nextRestartTime}") # time.sleep(5) startTime = time.time() try: self.DedicatedServer.start() except: AstroLogging.logPrint("Unable to launch AstroServer.exe", "critical") return False reachableProcess = None pcounter = 40 while not reachableProcess: try: reachableProcess = not bool( self.DedicatedServer.process.poll()) pcounter -= 1 time.sleep(0.25) except: pcounter -= 2 time.sleep(0.5) if pcounter <= 0: AstroLogging.logPrint( "Unable to start Server Process after 10 seconds!", "critical") return False AstroLogging.logPrint("Server started! Getting ready....", ovrDWHL=True) try: self.DaemonProcess = AstroDaemon.launch( executable=self.isExecutable, consolePID=self.DedicatedServer.process.pid) except: AstroLogging.logPrint("Unable to start watcher daemon", "warning") return False # Wait for server to finish registering... serverData = None oPFF = self.launcherConfig.PlayfabAPIFrequency while not self.DedicatedServer.registered: AstroLogging.logPrint("Waiting for server to register...", "debug") try: serverData = (AstroAPI.get_server( self.DedicatedServer.ipPortCombo, self.headers)) serverData = serverData['data']['Games'] lobbyIDs = [x['LobbyID'] for x in serverData] if len(set(lobbyIDs) - set(oldLobbyIDs)) == 0: time.sleep(self.launcherConfig.PlayfabAPIFrequency) else: now = time.time() if now - startTime > 15: serverData = serverData[0] self.DedicatedServer.registered = True oldLobbyIDs = None self.DedicatedServer.LobbyID = serverData['LobbyID'] if self.DedicatedServer.process.poll() is not None: AstroLogging.logPrint( "Server was forcefully closed before registration. Exiting...." ) return False except KeyboardInterrupt: self.DedicatedServer.kill_server( "Launcher shutting down via KeyboardInterrupt") except: AstroLogging.logPrint( "Failed to check server. Probably hit rate limit. Backing off and trying again..." ) if self.launcherConfig.PlayfabAPIFrequency < 30: self.launcherConfig.PlayfabAPIFrequency += 1 time.sleep(self.launcherConfig.PlayfabAPIFrequency) self.launcherConfig.PlayfabAPIFrequency = oPFF self.DedicatedServer.serverData = serverData doneTime = time.time() elapsed = doneTime - startTime AstroLogging.logPrint( f"Server ready! Took {round(elapsed,2)} seconds to register.", ovrDWHL=True) # {self.DedicatedServer.LobbyID} self.DedicatedServer.status = "ready" self.DedicatedServer.server_loop()
class AstroLauncher(): """ Starts a new instance of the Server Launcher""" @dataclasses.dataclass class LauncherConfig(): DisableAutoUpdate: bool = False UpdateOnServerRestart: bool = True HideServerConsoleWindow: bool = False HideLauncherConsoleWindow: bool = False ServerStatusFrequency: float = 2 PlayfabAPIFrequency: float = 2 HeartBeatFailRestartServer: int = 8 DisableBackupRetention: bool = False BackupRetentionPeriodHours: float = 72 BackupRetentionFolderLocation: str = r"Astro\Saved\Backup\LauncherBackups" EnableAutoRestart: bool = False AutoRestartEveryHours: float = 24 AutoRestartSyncTimestamp: str = "00:00" DisableNetworkCheck: bool = False OverwritePublicIP: bool = False ShowServerFPSInConsole: bool = True AdminAutoConfigureFirewall: bool = True LogRetentionDays: int = 7 DiscordWebHookURL: str = "" DiscordWebHookLevel: str = "cmd" RODataURL: str = secrets.token_hex(16) DisableWebServer: bool = False WebServerPort: int = 5000 WebServerPasswordHash: str = "" WebServerBaseURL: str = "/" EnableWebServerSSL: bool = False SSLPort: int = 443 SSLCertFile: str = "" SSLKeyFile: str = "" CPUAffinity: str = "" def __post_init__(self): # pylint: disable=no-member hasError = False for field, data in self.__dataclass_fields__.items(): try: self.__dict__[field] = data.type(self.__dict__[field]) except ValueError: hasError = True AstroLogging.logPrint( f"INI error: {field} must be of type {data.type.__name__}", "critical") if hasError: AstroLogging.logPrint("Fix your launcher config file!", "critical") sys.exit() class SaveHandler(FileSystemEventHandler): def __init__(self, launcher): self.launcher = launcher self.astroPath = self.launcher.astroPath self.moveToPath = self.launcher.launcherConfig.BackupRetentionFolderLocation super().__init__() def on_created(self, event): # print(event) # time.sleep(1) try: time.sleep(0.5) dirName = os.path.dirname(event.src_path) fileNames = [ os.path.join(dirName, f) for f in os.listdir(dirName) if os.path.isfile(os.path.join(dirName, f)) ] # print(fileNames) fileName = sorted(fileNames, key=os.path.getmtime, reverse=True)[0] AstroLogging.logPrint( f"Server saved. {os.path.basename(fileName)}", dwet="s") except: pass # self.launcher.saveObserver.stop() class BackupHandler(FileSystemEventHandler): def __init__(self, launcher): self.launcher = launcher self.astroPath = self.launcher.astroPath self.moveToPath = self.launcher.launcherConfig.BackupRetentionFolderLocation self.retentionPeriodHours = self.launcher.launcherConfig.BackupRetentionPeriodHours self.pendingFiles = [] super().__init__() def handle_files(self): # print(f"first: {self.pendingFiles}") time.sleep(2) # print(f"second: {self.pendingFiles}") # AstroLogging.logPrint("DEBUG: INSIDE THREAD") path = os.path.join(self.astroPath, self.moveToPath) try: if not os.path.exists(path): os.makedirs(path) except Exception as e: AstroLogging.logPrint(e, "error") now = time.time() try: for f in os.listdir(path): fpath = os.path.join(path, f) if os.stat(fpath).st_mtime < ( now - (self.retentionPeriodHours * 60 * 60)): os.remove(fpath) except Exception as e: AstroLogging.logPrint(e, "error") AstroLogging.logPrint("Copying backup(s) to retention folder.", dwet="b") # time.sleep(1) try: dirName = os.path.dirname(self.pendingFiles[0]) fileNames = [ os.path.join(dirName, f) for f in os.listdir(dirName) if os.path.isfile(os.path.join(dirName, f)) ] for cFile in fileNames: # AstroLogging.logPrint(newFile, "debug") # print(cFile) shutil.copy2(cFile, path) # AstroLogging.logPrint(copiedFile, "debug") except FileNotFoundError as e: AstroLogging.logPrint(e, "error") except Exception as e: AstroLogging.logPrint(e, "error") self.launcher.backupObserver.stop() self.launcher.backup_retention() def on_deleted(self, event): # AstroLogging.logPrint(event) # AstroLogging.logPrint("File in save directory changed") # AstroLogging.logPrint("DEBUG: File modified.. Starting thread") try: self.pendingFiles.append(event.src_path) if len(self.pendingFiles) == 1: t = Thread(target=self.handle_files, args=()) t.daemon = True t.start() except: pass def __init__(self, astroPath, launcherINI="Launcher.ini", disable_auto_update=None): AstroLogging.setup_logging() # check if path specified if astroPath is not None: if os.path.exists(os.path.join(astroPath, "AstroServer.exe")): self.astroPath = astroPath else: AstroLogging.logPrint( "Specified path does not contain the server executable! (AstroServer.exe)", "critical") time.sleep(5) return # check if executable in current directory elif os.path.exists(os.path.join(os.getcwd(), "AstroServer.exe")): self.astroPath = os.getcwd() # fallback to automatic detection (experimental, do NOT rely on it) else: try: autoPath = AstroAPI.getInstallPath() if os.path.exists(os.path.join(autoPath, "AstroServer.exe")): self.astroPath = autoPath except: AstroLogging.logPrint( "Unable to find server executable anywhere! (AstroServer.exe)", "critical") time.sleep(5) return # AstroRequests.checkProxies() self.launcherINI = launcherINI self.launcherConfig = self.LauncherConfig() self.launcherPath = os.getcwd() self.refresh_launcher_config() AstroLogging.discordWebhookURL = self.launcherConfig.DiscordWebHookURL dwhl = self.launcherConfig.DiscordWebHookLevel.lower() dwhl = dwhl if dwhl in ("all", "cmd", "chat") else "cmd" AstroLogging.discordWebhookLevel = dwhl self.start_WebHookLoop() AstroLogging.setup_loggingPath( astroPath=self.astroPath, logRetention=int(self.launcherConfig.LogRetentionDays)) if disable_auto_update is not None: self.launcherConfig.DisableAutoUpdate = disable_auto_update self.version = "v1.7.6" colsize = os.get_terminal_size().columns if colsize >= 77: vText = "Version " + self.version[1:] # pylint: disable=anomalous-backslash-in-string print( " __________________________________________________________________________\n" + "| _ _ _ _ |\n" + "| /_\\ ___| |_ _ _ ___ | | __ _ _ _ _ _ __ | |_ ___ _ _ |\n" + "| / _ \\ (_-<| _|| '_|/ _ \\ | |__ / _` || || || ' \\ / _|| ' \\ / -_)| '_| |\n" + "| /_/ \\_\\/__/ \\__||_| \\___/ |____|\\__,_| \\_,_||_||_|\\__||_||_|\\___||_| |\n" + "| |\n" + "|" + vText.center(74) + "|\n" + "|__________________________________________________________________________|" ) AstroLogging.logPrint( f"AstroLauncher - Unofficial Dedicated Server Launcher {self.version}" ) AstroLogging.logPrint( "If you encounter any bugs please open a new issue at:") AstroLogging.logPrint( "https://github.com/ricky-davis/AstroLauncher/issues") AstroLogging.logPrint( "To safely stop the launcher and server press CTRL+C") # AstroRequests.checkProxies() self.latestURL = "https://github.com/ricky-davis/AstroLauncher/releases/latest" bName = os.path.basename(sys.executable) if sys.argv[0] == os.path.splitext(bName)[0]: self.isExecutable = True else: self.isExecutable = os.path.samefile(sys.executable, sys.argv[0]) self.headers = AstroAPI.base_headers self.DaemonProcess = None self.saveObserver = None self.backupObserver = None self.hasUpdate = False self.is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 self.affinity = self.launcherConfig.CPUAffinity try: if self.affinity != "": affinityList = [ int(x.strip()) for x in self.affinity.split(',') ] p = psutil.Process() p.cpu_affinity(affinityList) except ValueError as e: AstroLogging.logPrint(f"CPU Affinity Error: {e}", "critical") AstroLogging.logPrint( "Please correct this in your launcher config", "critical") return self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) self.check_for_update() AstroLogging.logPrint("Starting a new session") self.validate_playfab_certs() self.check_ports_free() if self.launcherConfig.AdminAutoConfigureFirewall: self.configure_firewall() if not self.launcherConfig.DisableNetworkCheck: AstroLogging.logPrint("Checking the network configuration..") self.check_network_config() self.save_reporting() if not self.launcherConfig.DisableBackupRetention: self.backup_retention() AstroLogging.logPrint("Backup retention started") # setup queue for data exchange self.webServer = None if not self.launcherConfig.DisableWebServer: # start http server self.webServer = self.start_WebServer() self.start_InfoLoop() # AstroLogging.logPrint( # f"HTTP Server started at 127.0.0.1:{self.launcherConfig.WebServerPort}") if self.launcherConfig.HideLauncherConsoleWindow: # hide window AstroLogging.logPrint( "HideLauncherConsoleWindow enabled, Hiding window in 5 seconds..." ) time.sleep(5) # pylint: disable=redefined-outer-name kernel32 = ctypes.WinDLL('kernel32') user32 = ctypes.WinDLL('user32') hWnd = kernel32.GetConsoleWindow() user32.ShowWindow(hWnd, 0) self.start_server(firstLaunch=True) def save_reporting(self): if self.saveObserver: if not self.saveObserver.is_alive(): self.saveObserver = None self.save_reporting() else: self.saveObserver = Observer() saveGamePath = r"Astro\Saved\SaveGames" watchPath = os.path.join(self.astroPath, saveGamePath) try: if not os.path.exists(watchPath): os.makedirs(watchPath) except Exception as e: AstroLogging.logPrint(e) self.saveObserver.schedule(self.SaveHandler(self), watchPath) self.saveObserver.start() def backup_retention(self): if self.backupObserver: if not self.backupObserver.is_alive(): self.backupObserver = None self.backup_retention() else: self.backupObserver = Observer() backupSaveGamePath = r"Astro\Saved\Backup\SaveGames" watchPath = os.path.join(self.astroPath, backupSaveGamePath) try: if not os.path.exists(watchPath): os.makedirs(watchPath) except Exception as e: AstroLogging.logPrint(e) self.backupObserver.daemon = True self.backupObserver.schedule(self.BackupHandler(self), watchPath) self.backupObserver.start() def refresh_launcher_config(self, lcfg=None): field_names = set(f.name for f in dataclasses.fields(self.LauncherConfig)) cleaned_config = { k: v for k, v in self.get_launcher_config(lcfg).items() if k in field_names } self.launcherConfig = dataclasses.replace(self.launcherConfig, **cleaned_config) config = MultiConfig() config.read_dict({"AstroLauncher": cleaned_config}) with open(self.launcherINI, 'w') as configfile: config.write(configfile) def overwrite_launcher_config(self, ovrDict): ovrConfig = {"AstroLauncher": ovrDict} MultiConfig().overwrite_with(self.launcherINI, ovrConfig) def get_launcher_config(self, lfcg=None): if not lfcg: lfcg = self.LauncherConfig() baseConfig = {"AstroLauncher": dataclasses.asdict(lfcg)} config = MultiConfig().baseline(self.launcherINI, baseConfig) # print(settings) settings = config.getdict()['AstroLauncher'] return settings def validate_playfab_certs(self): AstroLogging.logPrint("Attempting to validate Playfab Certs") playfabRequestCommand = [ "powershell", '-executionpolicy', 'bypass', '-command', 'Invoke-WebRequest -uri https://5ea1.playfabapi.com/ -UseBasicParsing' ] with open(os.devnull, 'w') as tempf: proc = subprocess.Popen(playfabRequestCommand, stdout=tempf, stderr=tempf) proc.communicate() def check_for_update(self, serverStart=False): try: url = "https://api.github.com/repos/ricky-davis/AstroLauncher/releases/latest" data = ((AstroRequests.get(url)).json()) latestVersion = data['tag_name'] if latestVersion != self.version: self.hasUpdate = latestVersion AstroLogging.logPrint( f"UPDATE: There is a newer version of the launcher out! {latestVersion}" ) AstroLogging.logPrint(f"Download it at {self.latestURL}") aupdate = not self.launcherConfig.DisableAutoUpdate if not self.launcherConfig.UpdateOnServerRestart and serverStart: return if self.isExecutable and aupdate: self.autoupdate(data) except: AstroLogging.logPrint("Could not determine if new update exists.", msgType="debug") def autoupdate(self, data): x = data downloadFolder = os.path.dirname(sys.executable) for fileObj in x['assets']: downloadURL = fileObj['browser_download_url'] fileName = (os.path.splitext(fileObj['name'])[0]) downloadPath = os.path.join(downloadFolder, fileName) downloadCMD = [ "powershell", '-executionpolicy', 'bypass', '-command', 'Write-Host "Downloading latest AstroLauncher.exe..";', 'wait-process', str(os.getpid()), ';', '[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;', "$ProgressPreference = 'SilentlyContinue';", 'Invoke-WebRequest', f"'{downloadURL}'", "-OutFile", f"'{downloadPath + '_new.exe'}'", ';', "Move-Item", "-path", f"'{downloadPath + '_new.exe'}'", "-destination", f"'{downloadPath + '.exe'}'", "-Force;", 'Write-Host "Download complete!";', 'Start-Process', f"'{downloadPath + '.exe'}'" ] # print(' '.join(downloadCMD)) subprocess.Popen(downloadCMD, shell=True, creationflags=subprocess.DETACHED_PROCESS) time.sleep(2) self.DedicatedServer.kill_server("Auto-Update") # pylint: disable=unused-argument def signal_handler(self, sig, frame): self.DedicatedServer.kill_server( reason="Launcher shutting down via signal", save=True) def start_server(self, firstLaunch=False): """ Starts the Dedicated Server process and waits for it to be registered """ if firstLaunch: atexit.register(self.DedicatedServer.kill_server, reason="Launcher shutting down via exit", save=True) signal.signal(signal.SIGINT, self.signal_handler) else: self.check_for_update(serverStart=True) self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) self.DedicatedServer.status = "starting" self.DedicatedServer.busy = False gxAuth = None while gxAuth is None: try: gxAuth = AstroAPI.generate_XAUTH( self.DedicatedServer.settings.ServerGuid) except: AstroLogging.logPrint( "Unable to generate XAuth token... Are you connected to the internet?", msgType="warning") time.sleep(5) self.headers['X-Authorization'] = gxAuth oldLobbyIDs = self.DedicatedServer.deregister_all_server() AstroLogging.logPrint("Starting Server process...") if self.launcherConfig.EnableAutoRestart: AstroLogging.logPrint( f"Next restart is at {self.DedicatedServer.nextRestartTime}") # time.sleep(5) startTime = time.time() try: self.DedicatedServer.start() except: AstroLogging.logPrint("Unable to launch AstroServer.exe", "critical") return False reachableProcess = None pcounter = 40 while not reachableProcess: try: reachableProcess = not bool( self.DedicatedServer.process.poll()) pcounter -= 1 time.sleep(0.25) except: pcounter -= 2 time.sleep(0.5) if pcounter <= 0: AstroLogging.logPrint( "Unable to start Server Process after 10 seconds!", "critical") return False AstroLogging.logPrint("Server started! Getting ready....", ovrDWHL=True) try: self.DaemonProcess = AstroDaemon.launch( executable=self.isExecutable, consolePID=self.DedicatedServer.process.pid) except: AstroLogging.logPrint("Unable to start watcher daemon", "warning") return False # Wait for server to finish registering... serverData = None oPFF = self.launcherConfig.PlayfabAPIFrequency while not self.DedicatedServer.registered: AstroLogging.logPrint("Waiting for server to register...", "debug") try: serverData = (AstroAPI.get_server( self.DedicatedServer.ipPortCombo, self.headers)) serverData = serverData['data']['Games'] lobbyIDs = [x['LobbyID'] for x in serverData] if len(set(lobbyIDs) - set(oldLobbyIDs)) == 0: time.sleep(self.launcherConfig.PlayfabAPIFrequency) else: now = time.time() if now - startTime > 15: serverData = serverData[0] self.DedicatedServer.registered = True oldLobbyIDs = None self.DedicatedServer.LobbyID = serverData['LobbyID'] if self.DedicatedServer.process.poll() is not None: AstroLogging.logPrint( "Server was forcefully closed before registration. Exiting...." ) return False except KeyboardInterrupt: self.DedicatedServer.kill_server( "Launcher shutting down via KeyboardInterrupt") except: AstroLogging.logPrint( "Failed to check server. Probably hit rate limit. Backing off and trying again..." ) if self.launcherConfig.PlayfabAPIFrequency < 30: self.launcherConfig.PlayfabAPIFrequency += 1 time.sleep(self.launcherConfig.PlayfabAPIFrequency) self.launcherConfig.PlayfabAPIFrequency = oPFF self.DedicatedServer.serverData = serverData doneTime = time.time() elapsed = doneTime - startTime AstroLogging.logPrint( f"Server ready! Took {round(elapsed,2)} seconds to register.", ovrDWHL=True) # {self.DedicatedServer.LobbyID} self.DedicatedServer.status = "ready" self.DedicatedServer.server_loop() def check_ports_free(self): serverPort = False sp = int(self.DedicatedServer.settings.Port) consolePort = False cp = int(self.DedicatedServer.settings.ConsolePort) webPort = False wp = int(self.launcherConfig.WebServerPort) def is_port_in_use(port, tcp=True): lc = psutil.net_connections('inet') lc = [ x for x in lc if x.type == (socket.SOCK_STREAM if tcp else socket.SOCK_DGRAM) and x.laddr[1] == port ] return len(lc) > 0 serverPort = bool(is_port_in_use(sp, False)) consolePort = bool(is_port_in_use(cp)) if not self.launcherConfig.DisableWebServer: webPort = bool(is_port_in_use(wp)) if serverPort: AstroLogging.logPrint( f"A process is already using your Server Port ( {sp} UDP )", "critical") if consolePort: AstroLogging.logPrint( f"A process is already using your Console Port ( {cp} TCP )", "critical") if webPort: AstroLogging.logPrint( f"A process is already using your Web Port ( {wp} TCP )", "critical") if serverPort or consolePort or webPort: self.kill_launcher() def configure_firewall(self): if not self.launcherConfig.AdminAutoConfigureFirewall: return ALRule = None ALWRule = None ASRule = None launcherEXEPath = None isFirewallEnabled = None with os.popen( 'netsh advfirewall show currentprofile | findstr /L "State" | findstr /L "ON"' ) as fwCheck: isFirewallEnabled = fwCheck.read() if isFirewallEnabled: serverExePath = os.path.join( self.astroPath, 'astro\\binaries\\win64\\astroserver-win64-shipping.exe') ASRule = os.popen( f'netsh advfirewall firewall show rule name=astroserver-win64-shipping.exe verbose | findstr /L "{serverExePath}"' ).read() if self.isExecutable: launcherEXEPath = os.path.join(os.getcwd(), sys.argv[0]) ALRule = os.popen( f'netsh advfirewall firewall show rule name=astrolauncher.exe verbose | findstr /L "{launcherEXEPath}"' ).read() if not self.launcherConfig.DisableWebServer: ALWRule = os.popen( f'netsh advfirewall firewall show rule name=AstroLauncherWeb | findstr /L "{self.launcherConfig.WebServerPort}"' ).read() if not self.is_admin: if (not ASRule)\ or (self.isExecutable and not ALRule)\ or (not self.launcherConfig.DisableWebServer and self.isExecutable and not ALWRule): AstroLogging.logPrint( "Could not find firewall settings! Please relaunch as Administrator.", "warning") else: newRules = False if not ASRule: newRules = True subprocess.call( f'netsh advfirewall firewall delete rule name=astroserver-win64-shipping.exe dir=in program="{serverExePath}"' + f'& netsh advfirewall firewall add rule name=astroserver-win64-shipping.exe dir=in action=allow program="{serverExePath}"', shell=True, stdout=DEVNULL, stderr=DEVNULL) if self.isExecutable: if not ALRule: newRules = True subprocess.call( f'netsh advfirewall firewall delete rule name=astrolauncher.exe dir=in program="{launcherEXEPath}"' + f'& netsh advfirewall firewall add rule name=astrolauncher.exe dir=in action=allow program="{launcherEXEPath}"', shell=True, stdout=DEVNULL, stderr=DEVNULL) if not self.launcherConfig.DisableWebServer and not ALWRule: newRules = True subprocess.call( f'netsh advfirewall firewall delete rule name=AstroLauncherWeb dir=in protocol=TCP localport={self.launcherConfig.WebServerPort}' + f'& netsh advfirewall firewall add rule name=AstroLauncherWeb dir=in action=allow protocol=TCP localport={self.launcherConfig.WebServerPort}', shell=True, stdout=DEVNULL, stderr=DEVNULL) if newRules: AstroLogging.logPrint("Setting custom firewall rules...") def check_network_config(self): networkCorrect = ValidateSettings.test_network( self.DedicatedServer.settings.PublicIP, int(self.DedicatedServer.settings.Port), False) if networkCorrect: AstroLogging.logPrint("Server network configuration good!") else: AstroLogging.logPrint( "I can't seem to validate your network settings..", "warning") AstroLogging.logPrint( f"Make sure to Port Forward ({self.DedicatedServer.settings.Port} UDP), enable NAT Loopback...", "warning") AstroLogging.logPrint( f"And allow port {self.DedicatedServer.settings.Port} UDP inbound in your firewall", "warning") AstroLogging.logPrint( "If nobody can connect, Port Forward or fix your firewall.", "warning") AstroLogging.logPrint( "If others are able to connect, but you aren't, enable NAT Loopback.", "warning") rconNetworkCorrect = not (ValidateSettings.test_network( self.DedicatedServer.settings.PublicIP, int(self.DedicatedServer.settings.ConsolePort), True)) if rconNetworkCorrect: AstroLogging.logPrint("Remote Console network configuration good!") else: AstroLogging.logPrint( f"SECURITY ALERT: Your console port ({self.DedicatedServer.settings.ConsolePort}) is Port Forwarded!", "warning") AstroLogging.logPrint( "SECURITY ALERT: This allows anybody to control your server.", "warning") AstroLogging.logPrint( "SECURITY ALERT: Disable this ASAP to prevent issues.", "warning") time.sleep(5) def start_WebServer(self): ws = AstroWebServer.WebServer(self) def start_WebServerThread(): if sys.version_info.minor > 7: asyncio.set_event_loop_policy( asyncio.WindowsSelectorEventLoopPolicy()) asyncio.set_event_loop(asyncio.new_event_loop()) ws.run() t = Thread(target=start_WebServerThread, args=()) t.daemon = True t.start() return ws def autoUpdateLoop(self): while True: time.sleep(1) self.webServer.iterWebSocketConnections() def start_InfoLoop(self): def start_InfoLoopThread(self): if sys.version_info.minor > 7: asyncio.set_event_loop_policy( asyncio.WindowsSelectorEventLoopPolicy()) asyncio.set_event_loop(asyncio.new_event_loop()) self.autoUpdateLoop() t = Thread(target=start_InfoLoopThread, args=(self, )) t.daemon = True t.start() def kill_launcher(self): time.sleep(5) try: for child in psutil.Process(os.getpid()).children(): child.kill() except: pass # Kill current process try: os.kill(os.getpid(), 9) except: pass def start_WebHookLoop(self): t = Thread(target=AstroLogging.sendDiscordReqLoop, args=()) t.daemon = True t.start()
def __init__(self, astroPath, launcherINI="Launcher.ini", disable_auto_update=None): AstroLogging.setup_logging() # check if path specified if astroPath is not None: if os.path.exists(os.path.join(astroPath, "AstroServer.exe")): self.astroPath = astroPath else: AstroLogging.logPrint( "Specified path does not contain the server executable! (AstroServer.exe)", "critical") time.sleep(5) return # check if executable in current directory elif os.path.exists(os.path.join(os.getcwd(), "AstroServer.exe")): self.astroPath = os.getcwd() # fallback to automatic detection (experimental, do NOT rely on it) else: try: autoPath = AstroAPI.getInstallPath() if os.path.exists(os.path.join(autoPath, "AstroServer.exe")): self.astroPath = autoPath except: AstroLogging.logPrint( "Unable to find server executable anywhere! (AstroServer.exe)", "critical") time.sleep(5) return # AstroRequests.checkProxies() self.launcherINI = launcherINI self.launcherConfig = self.LauncherConfig() self.launcherPath = os.getcwd() self.refresh_launcher_config() AstroLogging.discordWebhookURL = self.launcherConfig.DiscordWebHookURL dwhl = self.launcherConfig.DiscordWebHookLevel.lower() dwhl = dwhl if dwhl in ("all", "cmd", "chat") else "cmd" AstroLogging.discordWebhookLevel = dwhl self.start_WebHookLoop() AstroLogging.setup_loggingPath( astroPath=self.astroPath, logRetention=int(self.launcherConfig.LogRetentionDays)) if disable_auto_update is not None: self.launcherConfig.DisableAutoUpdate = disable_auto_update self.version = "v1.7.6" colsize = os.get_terminal_size().columns if colsize >= 77: vText = "Version " + self.version[1:] # pylint: disable=anomalous-backslash-in-string print( " __________________________________________________________________________\n" + "| _ _ _ _ |\n" + "| /_\\ ___| |_ _ _ ___ | | __ _ _ _ _ _ __ | |_ ___ _ _ |\n" + "| / _ \\ (_-<| _|| '_|/ _ \\ | |__ / _` || || || ' \\ / _|| ' \\ / -_)| '_| |\n" + "| /_/ \\_\\/__/ \\__||_| \\___/ |____|\\__,_| \\_,_||_||_|\\__||_||_|\\___||_| |\n" + "| |\n" + "|" + vText.center(74) + "|\n" + "|__________________________________________________________________________|" ) AstroLogging.logPrint( f"AstroLauncher - Unofficial Dedicated Server Launcher {self.version}" ) AstroLogging.logPrint( "If you encounter any bugs please open a new issue at:") AstroLogging.logPrint( "https://github.com/ricky-davis/AstroLauncher/issues") AstroLogging.logPrint( "To safely stop the launcher and server press CTRL+C") # AstroRequests.checkProxies() self.latestURL = "https://github.com/ricky-davis/AstroLauncher/releases/latest" bName = os.path.basename(sys.executable) if sys.argv[0] == os.path.splitext(bName)[0]: self.isExecutable = True else: self.isExecutable = os.path.samefile(sys.executable, sys.argv[0]) self.headers = AstroAPI.base_headers self.DaemonProcess = None self.saveObserver = None self.backupObserver = None self.hasUpdate = False self.is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 self.affinity = self.launcherConfig.CPUAffinity try: if self.affinity != "": affinityList = [ int(x.strip()) for x in self.affinity.split(',') ] p = psutil.Process() p.cpu_affinity(affinityList) except ValueError as e: AstroLogging.logPrint(f"CPU Affinity Error: {e}", "critical") AstroLogging.logPrint( "Please correct this in your launcher config", "critical") return self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) self.check_for_update() AstroLogging.logPrint("Starting a new session") self.validate_playfab_certs() self.check_ports_free() if self.launcherConfig.AdminAutoConfigureFirewall: self.configure_firewall() if not self.launcherConfig.DisableNetworkCheck: AstroLogging.logPrint("Checking the network configuration..") self.check_network_config() self.save_reporting() if not self.launcherConfig.DisableBackupRetention: self.backup_retention() AstroLogging.logPrint("Backup retention started") # setup queue for data exchange self.webServer = None if not self.launcherConfig.DisableWebServer: # start http server self.webServer = self.start_WebServer() self.start_InfoLoop() # AstroLogging.logPrint( # f"HTTP Server started at 127.0.0.1:{self.launcherConfig.WebServerPort}") if self.launcherConfig.HideLauncherConsoleWindow: # hide window AstroLogging.logPrint( "HideLauncherConsoleWindow enabled, Hiding window in 5 seconds..." ) time.sleep(5) # pylint: disable=redefined-outer-name kernel32 = ctypes.WinDLL('kernel32') user32 = ctypes.WinDLL('user32') hWnd = kernel32.GetConsoleWindow() user32.ShowWindow(hWnd, 0) self.start_server(firstLaunch=True)
def __init__(self, astroPath, launcherINI="Launcher.ini", disable_auto_update=None): # check if path specified if astroPath is not None: if os.path.exists(os.path.join(astroPath, "AstroServer.exe")): self.astroPath = astroPath else: print("Specified path does not contain the server executable") time.sleep(5) # check if executable in current directory elif os.path.exists(os.path.join(os.getcwd(), "AstroServer.exe")): self.astroPath = os.getcwd() # fallback to automatic detection (experimental, do NOT rely on it) else: try: autoPath = AstroAPI.getInstallPath() if os.path.exists(os.path.join(autoPath, "AstroServer.exe")): self.astroPath = autoPath except: AstroLogging.logPrint("Unable to find AstroServer.exe!", "critical") return AstroLogging.setup_logging(self.astroPath) self.launcherINI = launcherINI self.launcherConfig = self.LauncherConfig() self.launcherPath = os.getcwd() self.refresh_launcher_config() if disable_auto_update is not None: self.launcherConfig.DisableAutoUpdate = disable_auto_update self.version = "v1.4.6" self.latestURL = "https://github.com/ricky-davis/AstroLauncher/releases/latest" self.isExecutable = os.path.samefile(sys.executable, sys.argv[0]) self.headers = AstroAPI.base_headers self.DaemonProcess = None self.saveObserver = None self.backupObserver = None self.DSServerStats = None self.DedicatedServer = AstroDedicatedServer(self.astroPath, self) AstroLogging.logPrint( f"AstroLauncher - Unofficial Dedicated Server Launcher {self.version}" ) AstroLogging.logPrint( "If you encounter any bugs please open a new issue at:") AstroLogging.logPrint( "https://github.com/ricky-davis/AstroLauncher/issues") AstroLogging.logPrint( "To safely stop the launcher and server press CTRL+C") self.check_for_update() AstroLogging.logPrint("Starting a new session") if not self.launcherConfig.DisableNetworkCheck: AstroLogging.logPrint("Checking the network configuration..") self.check_network_config() self.headers['X-Authorization'] = AstroAPI.generate_XAUTH( self.DedicatedServer.settings.ServerGuid) self.save_reporting() if not self.launcherConfig.DisableBackupRetention: self.backup_retention() AstroLogging.logPrint("Backup retention started") # setup queue for data exchange if not self.launcherConfig.DisableWebServer: # start http server self.webServer = self.start_WebServer() AstroLogging.logPrint( f"HTTP Server started at 127.0.0.1:{self.launcherConfig.WebServerPort}" ) atexit.register(self.DedicatedServer.kill_server, reason="Launcher shutting down", save=True) self.start_server()