# CLI app to find Steam game paths with Compatdata from rich import print from prompt_toolkit import prompt from rich.console import Console from rich.columns import Columns from rich.panel import Panel import src.vdfparser as vd from src.prompt_helper import PromptHelper # Commandline options support import argparse # basic attempt at making program faster import datetime from src.timing import timeit from cachier import cachier cachier = cachier(stale_after=datetime.timedelta(days=1), cache_dir='/tmp/.cache') class TableItem: def __init__(self, name, data='', colour='white', type='desc'): self.name = name self.data = data self.type = type self.colour = colour def generate_link(self): if self.type == 'file': return f"[{self.colour}][link=file://{self.data}]file[/link][/{self.colour}]" elif self.type == 'dir': return f"[{self.colour}][link=file://{self.data}]dir[/link][/{self.colour}]" else: return f"[{self.colour}]{self.data}[/{self.colour}]" def __str__(self): return f"\n[white]{self.name}[/white]: [{self.colour}]"+self.generate_link()+"" class SteamGamePathTool: # arg mode means user input will be skipped and take all args from command line def __init__(self, steamPath=vd.fetch_steam_path(), arg_mode_enabled=None, game_name=None, game_id=None, all=False): self.steam_path = steamPath self.steam_vdf_path = vd.fetch_steam_vdf() # arg support self.arg_mode_enabled = arg_mode_enabled self.game_name = game_name self.game_id = game_id self.all = all if self.steam_vdf_path is None: print("Steam VDF file not found") # Add user input to attempt to find self.steam_vdf_path = prompt("Enter the path to the Steam VDF file: ", default=self.steam_path) # Fetch steam's vdf file and convert to json dict self.steam_vdf = vd.parse_vdf(self.steam_vdf_path) # Possible other locations where steamlibrary could be found self.steam_library_locations = vd.find_extra_locations(self.steam_vdf) games = vd.fetchall_vdfs(self.steam_vdf) self.games = self.sort_games(games) self.prompter = PromptHelper(games) for library in self.steam_library_locations: print(f"[+] Found Steam library at: {library}") if arg_mode_enabled: self.arg_mode() return while True: self.prompt_user() try: input("Press Enter to continue...") except KeyboardInterrupt: print("\nExiting...") break def arg_mode(self): if self.all: console = Console() game_rend = [Panel(self.get_game_content(game), expand=True) for game in self.games] console.print(Columns(game_rend)) return if self.game_id: game = self.prompter.find_game_num(self.game_id) elif self.game_name: game = self.prompter.find_game_str(self.game_name) else: return console = Console() data = self.get_game_content(game) if data: console.print(Panel(data, expand=True)) else: console.print("No game found") @timeit @cachier def prepare_all_games(self): game_rend = [Panel(self.get_game_content(game), expand=True) for game in self.games] return game_rend def print_all_games(self): game_rend = self.prepare_all_games() console = Console() console.print(Columns(game_rend)) def prompt_user(self): game = self.prompter.prompt_game(text="Input (game name | appid | all | q/quit): ") if game is None: return elif game == 'fetch_all_games': self.print_all_games() else: console = Console() console.print(Panel(self.get_game_content(game), expand=True)) def sort_games(self, games): return sorted(games, key=lambda x: x['name']) @cachier def get_game_content(self, game): """ Takes a game dictionary and returns a string that formats game information into a string renderable by the console in the rich library. :param game: A dictionary containing information about a Steam game :return: string formatted for table using rich library """ # Spaces must be replaced with %20 otherwise they won't link properly try: string = f"""[b]{game['name']}[/b]""" string+=str(TableItem(name="Game ID", data=f"{game['appid']}", colour="blue",type="desc")) string+=str(TableItem(name="Game acf", data=f"{game['acf_path'].replace(' ', '%20')}", colour="green",type="file")) string+=str(TableItem(name="Game Path", data=f"{game['true_path'].replace(' ', '%20')}", colour="blue",type="dir")) if game['workshop_path']: string+=str(TableItem(name="Game Workshop Path", data=f"{game['workshop_path'].replace(' ', '%20')}", colour="blue", type="dir")) if game["compatdata_path"]: string+=str(TableItem(name="Game Compatdata Path",data=f"{game['compatdata_path'].replace(' ', '%20')}",colour="blue",type="dir")) except TypeError: return None return string if __name__ == "__main__": print("""Welcome to S-Path A tool to print the locations of steam games, their respective gameID, workshop path, etc.""") parser = argparse.ArgumentParser() parser.add_argument('--name', help='Game name to search for') parser.add_argument('--id', help='Game ID to search for') # parser.add_argument('--help', help='Display help message') parser.add_argument('--steam-path', help='Steam path to use') parser.add_argument('--all', help='Displays all games installed', action='store_true') args = parser.parse_args() if args.name or args.id or args.all: steam_path_tool = SteamGamePathTool(game_id=args.id, game_name=args.name, arg_mode_enabled=True, all=args.all) else: steam_path_tool = SteamGamePathTool()