def __init__(self, path: Path): self.steamdir = path try: with self.app_info_path.open('rb') as app_info_file: try: self.app_info = appinfo.load(app_info_file) self.app_info_available = True except ValueError: #This will be thrown by steamfiles.appinfo if the appinfo.vdf structure is different than expected, which apparently has happened in earlier versions of it, so I should probably be prepared for that self.app_info = None self.app_info_available = False except FileNotFoundError: self.app_info = None self.app_info_available = False try: with self.config_path.open('rt', encoding='utf-8') as config_file: self.config = acf.load(config_file) self.config_available = True except FileNotFoundError: self.config = None self.config_available = False try: with self.localization_path.open('rt', encoding='utf8') as localization_file: self.localization = acf.load(localization_file) self.localization_available = True except FileNotFoundError: self.localization = None self.localization_available = False
def get_csgo_path(steamapps_folder): # Get every SteamLibrary folder with open(steamapps_folder + '\\libraryfolders.vdf') as infile: libraryfolders = acf.load(infile) folders = [steamapps_folder] i = 1 while True: try: print('Found steamapps folder %s' % (libraryfolders['LibraryFolders'][str(i)] + '\\steamapps')) folders.append(libraryfolders['LibraryFolders'][str(i)] + '\\steamapps') except KeyError: break i = i + 1 # Find the one CS:GO is in for folder in folders: try: print('Opening appmanifest %s...' % (folder + '\\appmanifest_730.acf')) with open(folder + '\\appmanifest_730.acf') as infile: appmanifest = acf.load(infile) print('Valid installdir found: %s' % (folder + "\\common\\" + appmanifest["AppState"]["installdir"])) return folder + "\\common\\" + appmanifest["AppState"]["installdir"] except FileNotFoundError: continue print('CS:GO not found :/')
def _check_steam_for_update(self, app_id: str, branch: str): manifest_file = get_server_path( ["steamapps", f"appmanifest_{app_id}.acf"]) if not os.path.isfile(manifest_file): self.logger.debug("No local manifet") return True manifest = None with open(manifest_file, "r") as f: manifest = acf.load(f) stdout = self.run_command( (f"{self.config.steamcmd_path} +app_info_update 1 " f"+app_info_print {app_id} +quit"), redirect_output=True, ) index = stdout.find(f'"{app_id}"') app_info = acf.loads(stdout[index:]) try: current_buildid = app_info[app_id]["depots"]["branches"][branch][ "buildid"] except KeyError: self.logger.debug("Failed to parse remote manifest") return True self.logger.debug(f"current: {manifest['AppState']['buildid']}") self.logger.debug(f"latest: {current_buildid}") return manifest["AppState"]["buildid"] != current_buildid
def find_arma_workshop_dir(): # Check if Arma is installed in the default library first # If not, we'll need to scan the other library folders for it if path.exists(path.join(steam_install_path, arma_manifest_suffix)): return path.join(steam_install_path, arma_workshop_suffix) # Make sure the libraryfolders.vdf manifest is in the usual Steam install location if not path.exists(path.join(steam_install_path, steam_library_manifest_suffix)): raise FileNotFoundError('Unable to find Steam library folder metadata') # Load libraryfolders.vdf and pull out each of the library folder paths from it # Library paths are indexed under numeric keys that are parsed as strings, so we'll just drop the two other keys # and iterate over the remaining ones which will be the library paths with open(path.join(steam_install_path, steam_library_manifest_suffix), 'r') as file: lib_path_data = acf.load(file) lib_folder_paths = [] for key in lib_path_data['LibraryFolders'].keys() - ['TimeNextStatsReport', 'ContentStatsID']: lib_folder_paths.append(path.join(lib_path_data['LibraryFolders'][key])) # Look in each of the library paths for the Arma 3 app manifest file for lib_folder_path in lib_folder_paths: if path.exists(path.join(lib_folder_path, arma_manifest_suffix)): return path.join(lib_folder_path, arma_workshop_suffix) return None
def launch(): if not request.args.get("launcher") or not request.args.get("game_identifier"): return "404" launcher = request.args.get("launcher") game_identifier = request.args.get("game_identifier") if request.args.get("launcher") == "STEAM": _acf = acf.load(open(settings.STEAM_LIBRARY_PATH + f"appmanifest_{request.args.get('game_identifier')}.acf")) _path = settings.STEAM_LIBRARY_GAMES_PATH + _acf["AppState"]["installdir"] + "/" for _file in os.listdir(_path): if _file[0] != "." and _file.split(".")[-1]: path = _path + _file subprocess.Popen(["open", settings.STEAM_APPLICATION_PATH], stdout=subprocess.PIPE, stderr=subprocess.PIPE) time.sleep(30) elif request.args.get("launcher") == "ORIGIN": print(settings.ORIGIN_LIBRARY_PATH) print(game_identifier) path = settings.ORIGIN_LIBRARY_PATH + game_identifier + ".app" subprocess.Popen(["open", path], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() return "200"
def test_load_dump(acf_data): with open(test_file_name, 'rt') as in_file: out_file = io.StringIO() loaded = acf.load(in_file) acf.dump(sort_dict(loaded), out_file) # Rewind to the beginning out_file.seek(0) assert out_file.read() == acf_data
def get_steam_games(): _steam_games = [] for _file in os.listdir(settings.STEAM_LIBRARY_PATH): if _file[0] != "." and _file.split(".")[-1] == "acf": _acf = acf.load(open(settings.STEAM_LIBRARY_PATH + _file)) _steam_games.append( (_acf["AppState"]["name"], _acf["AppState"]["appid"])) return _steam_games
def iter_steam_library_folders(self) -> Iterator[Path]: with self.steam_library_list_path.open('rt', encoding='utf-8') as steam_library_list_file: steam_library_list = acf.load(steam_library_list_file) library_folders = steam_library_list.get('libraryfolders') if library_folders: #Should always happen unless the format of this file changes for k, v in library_folders.items(): if k.isnumeric(): yield Path(v['path']) yield self.steamdir
def autolocateSpacehaven(self): self.gamePath = None self.jarPath = None self.modPath = None # Open previous location if known try: with open("previous_spacehaven_path.txt", 'r') as f: location = f.read() if os.path.exists(location): self.locateSpacehaven(location) return except FileNotFoundError: ui.log.log( "Unable to get last space haven location. Autolocating again.") # Steam based locator (Windows) try: registry_path = "SOFTWARE\\WOW6432Node\\Valve\\Steam" if ( platform.architecture()[0] == "64bit") else "SOFTWARE\\Valve\\Steam" steam_path = winreg.QueryValueEx( winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registry_path), "InstallPath")[0] library_folders = acf.load(open(steam_path + "\\steamapps\\libraryfolders.vdf"), wrapper=OrderedDict) locations = [ steam_path + "\\steamapps\\common\\SpaceHaven\\spacehaven.exe" ] for key, value in library_folders["LibraryFolders"].items(): if str.isnumeric(key): locations.append( value + "\\steamapps\\common\\SpaceHaven\\spacehaven.exe") for location in locations: if os.path.exists(location): self.locateSpacehaven(location) return except FileNotFoundError: ui.log.log( "Unable to locate Steam registry keys, aborting Steam autolocator" ) for location in POSSIBLE_SPACEHAVEN_LOCATIONS: try: location = os.path.abspath(location) if os.path.exists(location): self.locateSpacehaven(location) return except: pass ui.log.log( "Unable to autolocate installation. User will need to pick manually." )
def test_load_dump_with_wrapper(acf_data): with open(test_file_name, 'rt') as in_file: out_file = io.StringIO() loaded = acf.load(in_file, wrapper=OrderedDict) acf.dump(loaded, out_file) # Rewind to the beginning out_file.seek(0) assert isinstance(loaded, OrderedDict) assert out_file.read() == acf_data
def getSteamIDs(filePath=GAME_DIR_STEAM): #'D:\\SteamLibrary\\steamapps\\' listOfFiles = os.listdir(filePath) pattern = "*.acf" for entry in listOfFiles: if fnmatch.fnmatch(entry, pattern): with open(filePath + entry) as acfFile: STEAM_DATA = acf.load(acfFile, wrapper=OrderedDict) gameName = nested_lookup('name', STEAM_DATA) gameAppID = nested_lookup('appid', STEAM_DATA) gameAdd( gameName[0], gameAppID[0], GameStore.STEAM ) #gameName and gameAppID are lists with 1 element only
def get_installed_build_id(self): appmanifest_path = os.path.join( self.thrall.config.get_server_root(), 'steamapps/appmanifest_%s.acf' % settings.CONAN_SERVER_APP_ID) if not os.path.exists(appmanifest_path): return self.NO_INSTALLED_VERSION with open(appmanifest_path, 'r') as f: data = acf.load(f) return data['AppState']['buildid']
def get_manifest_updated_times(self): if not os.path.exists(self.steamcmd_mod_manifest_path): self.logger.debug('Doesnt exist: %s' % self.steamcmd_mod_manifest_path) return {} with open(self.steamcmd_mod_manifest_path, 'r') as f: data = acf.load(f) mods_updated = {} for workshop_id in data['AppWorkshop']['WorkshopItemsInstalled']: mod = data['AppWorkshop']['WorkshopItemsInstalled'][workshop_id] mods_updated[workshop_id] = int(mod['timeupdated']) return mods_updated
def iter_steam_installed_appids() -> Iterator[tuple[Path, int, Mapping[str, Any]]]: for library_folder in steam_installation.iter_steam_library_folders(): for acf_file_path in library_folder.joinpath('steamapps').glob('*.acf'): #Technically I could try and parse it without steamfiles, but that would be irresponsible, so I shouldn't do that with acf_file_path.open('rt', encoding='utf-8') as acf_file: app_manifest = acf.load(acf_file) app_state = app_manifest.get('AppState') if not app_state: #Should only happen if .acf is junk (or format changes dramatically), there's no other keys than AppState if main_config.debug: print('This should not happen', acf_file_path, 'is invalid or format is weird and new and spooky, has no AppState') continue appid_str = app_state.get('appid') if appid_str is None: #Yeah we need that if main_config.debug: print(acf_file_path, app_state.get('name'), 'has no appid which is weird') continue try: appid = int(appid_str) except ValueError: if main_config.debug: print('Skipping', acf_file_path, app_state.get('name'), appid_str, 'as appid is not numeric which is weird') continue try: state_flags = StateFlags(int(app_state.get('StateFlags'))) if not state_flags: continue except ValueError: if main_config.debug: print('Skipping', app_state.get('name'), appid, 'as StateFlags are invalid', app_state.get('StateFlags')) continue #Is StageFlags.AppRunning actually not what it means? Seems that an app that is running doesn't have its StateFlags changed and 64 is instead used for full versions installed where demos are the only version owned, etc #Anyway, we're going to check for it this way last_owner = app_state.get('LastOwner') if last_owner == '0': if main_config.debug: print('Skipping', app_state.get('name'), appid, 'as nobody actually owns it') continue #Only yield fully installed games if (state_flags & StateFlags.FullyInstalled) == 0: if main_config.debug: print('Skipping', app_state.get('name'), appid, 'as it is not actually installed (StateFlags =', state_flags, ')') continue yield library_folder, appid, app_state
def __init__(self, steam_path=""): if os.name != "posix": try: key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Valve\\Steams") except FileNotFoundError: try: key = winreg.OpenKey( winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\Wow6432Node\\Valve\\Steam") except FileNotFoundError as e: print("gSynch can't find your Steam installation") raise e self.steam_path = winreg.QueryValueEx(key, "InstallPath")[0] else: whereis = subprocess.run(['whereis', 'steam'], stdout=subprocess.PIPE) self.steam_path = whereis.stdout.decode('utf-8')[ 7:] # we get rid of "steam: " (which is 7 char long) self.games_path = [self.steam_path + "\\steamapps"] # Check if there is other library directories (see "libraryfolders.vdf analysis.txt") try: with open(self.steam_path + "\\libraryfolders.vdf") as foldersfile: data = acf.load(foldersfile) except OSError as e: print( "We can't figure out where the f**k is your libraryfolders.vdf file !" f"Full error :\n {e}") sys.exit(-1) # this is a fatal error except TypeError or ValueError as e: print( f"An error occurred while trying to parse {self.steam_path}\\libraryfolders.vdf. Please open an Issue " f"at https://github.com/Gabyfle/gSynch with your libraryfolders.vdf file !" ) sys.exit(-1) # this is a fatal error finally: if foldersfile is not None: foldersfile.close() # Now check if the libraryfolders.vdf file is correct, and if he follow "libraryfolders.vdf analysis.txt" (if # not, abort) if not data["LibraryFolders"]: print( f"An error occurred while trying to use data from your libraryfolders.vdf file. Please report this " f"to https://github.com/Gabyfle/gSynch in the Issue section, attaching your libraryfolders.vdf file." ) sys.exit(-1) # this is a fatal error else: for key in data["LibraryFolders"]: if key.isdigit( ): # if the key is a digit, it's a path to a library folder self.games_path.append(data["LibraryFolders"][key])
def _load_games(self): acf_path = "{}/steamapps".format(self.config["path"]) files = os.listdir(acf_path) acf_files = list(filter(lambda f: f.split(".")[-1] == "acf", files)) for f in acf_files: with open("{}/{}".format(acf_path, f), "r") as file: fcontent = acf.load(file) self.games_dict.update({fcontent["AppState"]["name"]: Game(name=fcontent["AppState"]["name"], appid=fcontent["AppState"]["appid"], img_src=self.config["img_src"] .format(fcontent["AppState"]["appid"]), extra_info=fcontent["AppState"]) })
def get_install_path(self, app_id): """ Get the installation path of the application "app_id" Args: app_id: Game's steam id Returns: str: path to the game """ data = dict # contains parsed data from appmanifest_id.acf files for path in self.games_path: for file in os.listdir(path): if os.path.isfile( (os.path.join(path, file) )): # make sure this is a file and not a directory if app_id in file: # this file contains the application id of the requested app if os.path.splitext( file )[1] == ".acf": # make sure this is a manifest file try: with open(os.path.join(path, file)) as acf_file: data = acf.load(acf_file) except OSError as e: print( f"A problem occurred while trying to open {os.path.join(path, file)}. Make sure " f"gSynch has the permission to read this path!" ) sys.exit(-1) # this is a fatal error except TypeError or ValueError as e: print( f"An error occurred while trying to parse {os.path.join(path, file)}. Please open " f"an Issue " f"at https://github.com/Gabyfle/gSynch with {os.path.join(path, file)} attached!" ) sys.exit(-1) # this is a fatal error finally: if acf_file is not None: acf_file.close() if data is not None and data["installDir"] is not None: directory = data["installDir"] else: raise NotFound( f"Sorry, but gSynch can't find the app : {app_id}... Are you sure it's installed ?" ) return directory
def getData(f, m): arquivo = open(f, m) return acf.load(arquivo)
def workshop_download( self, allow_run: bool, force: bool, stop: bool, restart: bool, *args, **kwargs, ) -> int: """ downloads Steam workshop items """ was_running = False if not force: needs_update = self._check_steam_for_update( str(self.config.workshop_id), "public") if not needs_update: self.logger.success( f"{self.config.workshop_id} is already on latest version") self._start_servers(restart, was_running) return STATUS_SUCCESS if not allow_run: was_running = self.is_running(check_all=True) if was_running: if not (restart or stop): self.logger.warning( f"at least once instance of {self.config.app_id} " "is still running") return STATUS_PARTIAL_FAIL self._stop_servers(was_running, reason="Updates found for workshop app") status = self.invoke( self.install, app_id=self.config.workshop_id, allow_run=True, force=force, ) if not status == STATUS_SUCCESS: return status if len(self.config.workshop_items) == 0: self.logger.warning("\nno workshop items selected for install") return STATUS_PARTIAL_FAIL mods_to_update = [] manifest_file = get_server_path([ "steamapps", "workshop", f"appworkshop_{self.config.workshop_id}.acf", ], ) if not force and os.path.isfile(manifest_file): manifest = None with open(manifest_file, "r") as f: manifest = acf.load(f) self.logger.info("checking for updates for workshop items...") with click.progressbar(self.config.workshop_items) as bar: for workshop_item in bar: workshop_item = str(workshop_item) if (workshop_item not in manifest["AppWorkshop"] ["WorkshopItemsInstalled"]): mods_to_update.append(workshop_item) continue last_update_time = int( manifest["AppWorkshop"]["WorkshopItemsInstalled"] [workshop_item]["timeupdated"]) try: latest_metadata = self._get_published_file( workshop_item) except requests.HTTPError: self.logger.error( "\ncould not query Steam for updates") return STATUS_FAILED newest_update_time = int( latest_metadata["response"]["publishedfiledetails"][0] ["time_updated"]) if last_update_time < newest_update_time: mods_to_update.append(workshop_item) else: mods_to_update = self.config.workshop_items if len(mods_to_update) == 0: self.logger.success("all workshop items already up to date") self._start_servers(restart, was_running) return STATUS_SUCCESS self.logger.info("downloading workshop items...") with click.progressbar(mods_to_update) as bar: for workshop_item in bar: try: self.run_command( (f"{self.config.steamcmd_path} " f"{self._steam_login()} +force_install_dir " f"{self.config.server_path} " "+workshop_download_item " f"{self.config.workshop_id} {workshop_item} +quit")) except CalledProcessError: self.logger.error("\nfailed to validate workshop items") return STATUS_FAILED self.logger.success("\nvalidated workshop items") self._start_servers(restart, was_running) return STATUS_SUCCESS
def workshop_download( self, allow_run: bool, force: bool, stop: bool, restart: bool, *args, **kwargs, ) -> int: """ downloads and installs ARK mods """ status = self.invoke( super().workshop_download, allow_run=True, force=force, stop=stop, restart=False, ) self.logger.debug("super status: {}".format(status)) if status == STATUS_SUCCESS: mod_path = get_server_path(["ShooterGame", "Content", "Mods"]) base_src_dir = get_server_path([ "steamapps", "workshop", "content", str(self.config.workshop_id), ]) mods_to_update = [] manifest_file = get_server_path([ "steamapps", "workshop", f"appworkshop_{self.config.workshop_id}.acf", ]) if not force and os.path.isfile(manifest_file): manifest = None with open(manifest_file, "r") as f: manifest = acf.load(f) for workshop_item in self.config.workshop_items: workshop_item = str(workshop_item) mod_dir = os.path.join(mod_path, str(workshop_item)) mod_file = os.path.join(mod_path, "{}.mod".format(workshop_item)) if not os.path.isfile(mod_file) or ( workshop_item not in manifest["AppWorkshop"] ["WorkshopItemsInstalled"]): mods_to_update.append(workshop_item) continue last_update_time = int( manifest["AppWorkshop"]["WorkshopItemsInstalled"] [workshop_item]["timeupdated"]) last_extract_time = os.path.getctime(mod_file) if last_update_time > last_extract_time: mods_to_update.append(workshop_item) else: mods_to_update = self.config.workshop_items mods_to_update = self.str_mods(mods_to_update) if len(mods_to_update) == 0: was_running = self.is_running("@any") # automatically check for any servers shutdown by install if not was_running: self._start_servers(restart, was_running) return STATUS_SUCCESS self.logger.info( f"{len(mods_to_update)} mod(s) need to be extracted: " f"{','.join(mods_to_update)}") was_running = self.is_running("@any") if was_running: if not (restart or stop): self.logger.warning( (f"at least once instance of {self.config.app_id}" " is still running")) return STATUS_PARTIAL_FAIL self._stop_servers( was_running, reason=(f"Updates found for {len(mods_to_update)} " f"mod(s): {','.join(mods_to_update)}"), ) self.logger.info("extracting mods...") with click.progressbar(mods_to_update) as bar: for workshop_item in bar: src_dir = os.path.join(base_src_dir, str(workshop_item)) branch_dir = os.path.join( src_dir, "{}NoEditor".format(self.config.workshop_branch), ) mod_dir = os.path.join(mod_path, str(workshop_item)) mod_file = os.path.join(mod_path, "{}.mod".format(workshop_item)) if not os.path.isdir(src_dir): self.logger.error( "could not find workshop item: {}".format( self.config.workshop_id)) return STATUS_FAILED elif os.path.isdir(branch_dir): src_dir = branch_dir if os.path.isdir(mod_dir): self.logger.debug( "removing old mod_dir of {}...".format( workshop_item)) shutil.rmtree(mod_dir) if os.path.isfile(mod_file): self.logger.debug( "removing old mod_file of {}...".format( workshop_item)) os.remove(mod_file) self.logger.debug("copying {}...".format(workshop_item)) shutil.copytree(src_dir, mod_dir) if not self._create_mod_file(mod_dir, mod_file, workshop_item): self.logger.error( "could not create .mod file for {}".format( workshop_item)) return STATUS_FAILED if not self._extract_files(mod_dir): return STATUS_FAILED self.logger.success("workshop items successfully installed") if status == STATUS_SUCCESS: self._start_servers(restart, was_running) return status