-
Notifications
You must be signed in to change notification settings - Fork 54
/
steam_utils.py
187 lines (141 loc) · 4.99 KB
/
steam_utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# Code greatly inspired by https://github.com/LostDragonist/steam-library-setup-tool
import sys
import winreg
from pathlib import Path
from typing import TypedDict, cast
import vdf # pyright: ignore[reportMissingTypeStubs]
class SteamGame:
def __init__(self, appid: str, installdir: str):
self.appid = appid
self.installdir = installdir
def __repr__(self):
return str(self)
def __str__(self):
return "{} ({})".format(self.appid, self.installdir)
class _AppState(TypedDict):
appid: str
installdir: str
class _AppManifest(TypedDict):
AppState: _AppState
class _LibraryFolder(TypedDict):
path: str
class _LibraryFolders(TypedDict, total=False):
libraryfolders: dict[str, _LibraryFolder]
LibraryFolders: dict[str, str]
class LibraryFolder:
def __init__(self, path: Path):
self.path = path
self.games: list[SteamGame] = []
for filepath in path.joinpath("steamapps").glob("appmanifest_*.acf"):
try:
with open(filepath, "r", encoding="utf-8") as fp:
info = cast(
_AppManifest,
vdf.load(fp), # pyright: ignore[reportUnknownMemberType]
)
app_state = info["AppState"]
except KeyError:
print(
f'Unable to read application state from "{filepath}"',
file=sys.stderr,
)
continue
except Exception as e:
print(f'Unable to parse file "{filepath}": {e}', file=sys.stderr)
continue
try:
app_id = app_state["appid"]
install_dir = app_state["installdir"]
self.games.append(SteamGame(app_id, install_dir))
except KeyError:
print(
f"Unable to read application ID or installation folder "
f'from "{filepath}"',
file=sys.stderr,
)
continue
def __repr__(self):
return str(self)
def __str__(self):
return "LibraryFolder at {}: {}".format(self.path, self.games)
def parse_library_info(library_vdf_path: Path) -> list[LibraryFolder]:
"""
Read library folders from the main library file.
Args:
library_vdf_path: The main library file (from the Steam installation
folder).
Returns:
A list of LibraryFolder, for each library found.
"""
with open(library_vdf_path, "r", encoding="utf-8") as f:
info = cast(
_LibraryFolders,
vdf.load(f), # pyright: ignore[reportUnknownMemberType]
)
info_folders: dict[str, str] | dict[str, _LibraryFolder]
if "libraryfolders" in info:
# new format
info_folders = info["libraryfolders"]
elif "LibraryFolders" in info:
# old format
info_folders = info["LibraryFolders"]
else:
raise ValueError(f'Unknown file format from "{library_vdf_path}"')
library_folders: list[LibraryFolder] = []
for key, value in info_folders.items():
# only keys that are integer values contains library folder
try:
int(key)
except ValueError:
continue
if isinstance(value, str):
path = value
else:
path = value["path"]
try:
library_folders.append(LibraryFolder(Path(path)))
except Exception as e:
print(
'Failed to read steam library from "{}", {}'.format(path, repr(e)),
file=sys.stderr,
)
return library_folders
def find_steam_path() -> Path | None:
"""
Retrieve the Steam path, if available.
Returns:
The Steam path, or None if Steam is not installed.
"""
try:
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Valve\\Steam") as key:
value = winreg.QueryValueEx(key, "SteamExe")
return Path(value[0].replace("/", "\\")).parent
except FileNotFoundError:
return None
def find_games() -> dict[str, Path]:
"""
Find the list of Steam games installed.
Returns:
A mapping from Steam game ID to install locations for available
Steam games.
"""
steam_path = find_steam_path()
if not steam_path:
return {}
library_vdf_path = steam_path.joinpath("steamapps", "libraryfolders.vdf")
try:
library_folders = parse_library_info(library_vdf_path)
library_folders.append(LibraryFolder(steam_path))
except FileNotFoundError:
return {}
games: dict[str, Path] = {}
for library in library_folders:
for game in library.games:
games[game.appid] = Path(library.path).joinpath(
"steamapps", "common", game.installdir
)
return games
if __name__ == "__main__":
games = find_games()
for k, v in games.items():
print("Found game with id {} at {}.".format(k, v))