Skip to content

Commit 2b71bae

Browse files
committed
use libraryfolders.vdf for installed games detection
- Replace custom vdf_parse with bundled vdf library - Use libraryfolders.vdf instead of deprecated registry.vdf - Add cross-platform Steam path detection (Linux, macOS, Windows) - Prioritize user-configured steam-path setting - Properly close Windows registry handles
1 parent 83f9b80 commit 2b71bae

2 files changed

Lines changed: 118 additions & 63 deletions

File tree

resources/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def installed_games():
6464
'If this problem persists please contact support.')
6565
return
6666

67-
installed_appids = registry.get_installed_steam_apps(os.path.join(__addon__.getSetting('steam-path'), 'registry.vdf'))
67+
installed_appids = registry.get_installed_steam_apps(__addon__.getSetting('steam-path'))
6868

6969
# filter out any applications not listed as installed
7070
steam_installed_games = filter(lambda app_entry: str(app_entry['appid']) in installed_appids, steam_games_details)

resources/registry.py

Lines changed: 117 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,155 @@
1-
'''
2-
get registry values for steam games
3-
'''
1+
"""
2+
Get installed Steam games across platforms.
3+
4+
Supports:
5+
- Linux: ~/.steam, ~/.local/share/Steam
6+
- macOS: ~/Library/Application Support/Steam
7+
- Windows: Registry + Program Files
8+
"""
49

510
import os
6-
import xbmc
7-
import io
8-
from .util import *
11+
import sys
12+
13+
from .util import log, show_error
14+
15+
# Add bundled libraries to path
16+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'lib'))
17+
import vdf
918

1019
if os.name == 'nt':
1120
import winreg
1221

1322

14-
# https://github.com/lutris/lutris/blob/master/lutris/util/steam.py
15-
def vdf_parse(steam_config_file, config):
16-
"""Parse a Steam config file and return the contents as a dict with lowercase keys.
17-
The motivation behind returning lowercase keys is that the case is not consistent between environments it seems.
23+
def get_default_steam_paths():
1824
"""
19-
line = " "
20-
while line:
25+
Returns a list of possible Steam installation paths for the current platform.
26+
Used as fallback when steam-path setting is not configured.
27+
"""
28+
paths = []
29+
30+
if sys.platform == 'darwin':
31+
# macOS
32+
paths.append(os.path.expanduser('~/Library/Application Support/Steam'))
33+
elif os.name == 'nt':
34+
# Windows - try to get from registry first
2135
try:
22-
line = steam_config_file.readline()
23-
except UnicodeDecodeError:
24-
log("Error while reading Steam VDF file {}. Returning {}".format(steam_config_file, config), xbmc.LOGERROR)
25-
return config
26-
if not line or line.strip() == "}":
27-
return config
28-
while not line.strip().endswith("\""):
29-
nextline = steam_config_file.readline()
30-
if not nextline:
31-
break
32-
line = line[:-1] + nextline
33-
34-
line_elements = line.strip().split("\"")
35-
if len(line_elements) == 3:
36-
key = line_elements[1].lower()
37-
steam_config_file.readline() # skip '{'
38-
config[key] = vdf_parse(steam_config_file, {})
39-
else:
40-
try:
41-
config[line_elements[1].lower()] = line_elements[3]
42-
except IndexError:
43-
log('Malformed config file: {}'.format(line), xbmc.LOGERROR)
44-
return config
36+
key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Valve\Steam')
37+
steam_path, _ = winreg.QueryValueEx(key, 'SteamPath')
38+
winreg.CloseKey(key)
39+
if steam_path:
40+
paths.append(steam_path)
41+
except WindowsError:
42+
pass
43+
# Fallback paths
44+
paths.extend([
45+
os.path.expandvars(r'%ProgramFiles(x86)%\Steam'),
46+
os.path.expandvars(r'%ProgramFiles%\Steam'),
47+
])
48+
else:
49+
# Linux and other Unix-like systems
50+
paths.extend([
51+
os.path.expanduser('~/.steam/steam'),
52+
os.path.expanduser('~/.steam'),
53+
os.path.expanduser('~/.local/share/Steam'),
54+
])
55+
56+
return paths
57+
58+
59+
def find_libraryfolders_vdf(steam_path):
60+
"""
61+
Finds the libraryfolders.vdf file given a Steam path.
62+
Returns the path if found, None otherwise.
63+
"""
64+
# Handle symlinks (common on Linux where ~/.steam/steam -> ~/.local/share/Steam)
65+
if os.path.islink(steam_path):
66+
steam_path = os.path.realpath(steam_path)
67+
68+
possible_locations = [
69+
os.path.join(steam_path, 'steamapps', 'libraryfolders.vdf'),
70+
os.path.join(steam_path, 'steam', 'steamapps', 'libraryfolders.vdf'),
71+
os.path.join(steam_path, 'Steam', 'steamapps', 'libraryfolders.vdf'),
72+
]
73+
74+
for path in possible_locations:
75+
if os.path.isfile(path):
76+
return path
77+
78+
return None
4579

4680

4781
def is_installed_win(app_id):
4882
"""
49-
Gets whether an app with the given app id is installed, on Windows
83+
Gets whether an app with the given app id is installed, on Windows.
5084
:param app_id: app_id to check
5185
:return: True if the app is installed, false otherwise
5286
"""
5387
try:
54-
app = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Valve\\Steam\\Apps\\" + app_id)
55-
print(winreg.QueryInfoKey(app)[1])
88+
app = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Valve\Steam\Apps\{}'.format(app_id))
5689
for i in range(winreg.QueryInfoKey(app)[1]):
57-
name, value, type = winreg.EnumValue(app, i)
58-
if name == "Installed":
90+
name, value, _ = winreg.EnumValue(app, i)
91+
if name == 'Installed':
92+
winreg.CloseKey(app)
5993
return value == 1
60-
94+
winreg.CloseKey(app)
6195
except WindowsError:
6296
pass
63-
# Sometimes the key "Installed" does not exist, and we get out of the loop without returning anything,so we return False at the end of the function
6497
return False
6598

6699

67-
def get_installed_steam_apps(registry_path):
100+
def get_installed_steam_apps(steam_path):
68101
"""
69-
Obtains the steam games/apps installed on the computer.
70-
:param registry_path: Path to the registry.vdf file
71-
:return: an array of appids that are installed.
102+
Obtains the Steam games/apps installed on the computer.
103+
:param steam_path: Path to the Steam folder (from settings, or auto-detected)
104+
:return: a list of appids that are installed.
72105
"""
73106
installed_apps = []
74107

108+
# Build list of paths to try: user setting first, then defaults
109+
paths_to_try = []
110+
if steam_path and os.path.isdir(steam_path):
111+
paths_to_try.append(steam_path)
112+
paths_to_try.extend(get_default_steam_paths())
113+
114+
# Windows: Try registry first
75115
if os.name == 'nt':
76116
try:
77-
apps = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Valve\\Steam\\Apps")
78-
print(winreg.QueryInfoKey(apps)[0])
79-
for i in range(winreg.QueryInfoKey(apps)[0]):
117+
apps = winreg.OpenKey(winreg.HKEY_CURRENT_USER, r'Software\Valve\Steam\Apps')
118+
num_apps = winreg.QueryInfoKey(apps)[0]
119+
log("Found {} apps in Windows registry".format(num_apps))
120+
for i in range(num_apps):
80121
app_id = winreg.EnumKey(apps, i)
81122
if is_installed_win(app_id):
82123
installed_apps.append(app_id)
124+
winreg.CloseKey(apps)
125+
return installed_apps
126+
except WindowsError:
127+
log("Windows registry method failed, falling back to libraryfolders.vdf")
83128

84-
except WindowsError as e:
85-
show_error(e, "Error while reading Windows registry")
86-
pass
87-
else:
88-
with io.open(registry_path, 'r', encoding="utf-8") as file:
129+
# Try libraryfolders.vdf method (all platforms)
130+
for path in paths_to_try:
131+
libraryfolders_path = find_libraryfolders_vdf(path)
132+
if libraryfolders_path:
89133
try:
90-
vdf = vdf_parse(file, {})
91-
apps = vdf['registry']['hkcu']['software']['valve']['steam']['apps']
134+
with open(libraryfolders_path, 'r', encoding='utf-8') as f:
135+
data = vdf.load(f)
136+
137+
libraryfolders = data.get('libraryfolders', {})
138+
139+
# Each library folder entry (0, 1, 2, etc.) contains an 'apps' dict
140+
for folder_id, folder_info in libraryfolders.items():
141+
if isinstance(folder_info, dict) and 'apps' in folder_info:
142+
installed_apps.extend(folder_info['apps'].keys())
143+
144+
log("Found {} installed apps via {}".format(len(installed_apps), libraryfolders_path))
145+
return installed_apps
92146

93-
# apparently case of 'installed' differs depending on ... ?
94-
# We create a list of the apps that have a "installed" key equal to "1".
95-
installed_apps = [appid for (appid, information) in apps.items() if (information.get('installed', '0') == '1')]
96-
except KeyError as e:
97-
show_error(e, "Error finding the values from registry.vdf")
98-
pass
147+
except (SyntaxError, IOError, KeyError) as e:
148+
log("Error reading {}: {}".format(libraryfolders_path, e))
149+
continue
99150

151+
show_error(
152+
FileNotFoundError("libraryfolders.vdf not found"),
153+
"Could not find Steam library folders. Please check your Steam path setting."
154+
)
100155
return installed_apps

0 commit comments

Comments
 (0)