Skip to content

Commit

Permalink
Fix crash with older app manifests
Browse files Browse the repository at this point in the history
Older app manifest files store the name of the application in the
`userconfig/name` field rather than just `name`.

Fixes #103
  • Loading branch information
Matoking committed May 30, 2021
1 parent d2b4ba0 commit ae43d27
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Fixed
- Custom Proton installations now use Steam Runtime installations when applicable
- Fix crash caused by older Steam app installations using a different app manifest structure

## [1.5.1] - 2021-05-10
### Fixed
Expand Down
7 changes: 6 additions & 1 deletion src/protontricks/steam.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,12 @@ def from_appmanifest(cls, path, steam_lib_paths):
# Use case-insensitive field names to deal with these.
app_state = lower_dict(app_state)
appid = int(app_state["appid"])
name = app_state["name"]

try:
name = app_state["name"]
except KeyError:
# Older app installations also use `userconfig/name`
name = app_state["userconfig"]["name"]

# Proton prefix may exist on a different library
prefix_path = find_appid_proton_prefix(
Expand Down
11 changes: 9 additions & 2 deletions src/protontricks/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,19 @@ def is_flatpak_sandbox():

def lower_dict(d):
"""
Return a copy of the dictionary with all keys converted to lowercase.
Return a copy of the dictionary with all keys recursively converted to
lowercase.
This is mainly used when dealing with Steam VDF files, as those tend to
have either CamelCase or lowercase keys depending on the version.
"""
return {k.lower(): v for k, v in d.items()}
def _lower_value(value):
if not isinstance(value, dict):
return value

return {k.lower(): _lower_value(v) for k, v in value.items()}

return {k.lower(): _lower_value(v) for k, v in d.items()}


def get_legacy_runtime_library_paths(legacy_steam_runtime_path, proton_app):
Expand Down
29 changes: 29 additions & 0 deletions tests/test_steam.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pathlib import Path

import pytest
import vdf

from protontricks.steam import (SteamApp, find_appid_proton_prefix,
find_steam_path, find_steam_proton_app,
Expand Down Expand Up @@ -102,6 +103,34 @@ def test_steam_app_proton_dist_path(self, default_proton):
shutil.rmtree(str(default_proton.install_path / "files"))
assert default_proton.proton_dist_path is None

def test_steam_app_userconfig_name(self, steam_app_factory):
"""
Try creating a SteamApp from an older version of the app manifest
which contains the application name in a different field
See GitHub issue #103 for details
"""
steam_app = steam_app_factory(name="Fake game", appid=10)

appmanifest_path = \
Path(steam_app.install_path).parent.parent / "appmanifest_10.acf"
data = vdf.loads(appmanifest_path.read_text())

# Older installations store the name in `userconfig/name` instead
del data["AppState"]["name"]
data["AppState"]["userconfig"] = {
"name": "Fake game"
}

appmanifest_path.write_text(vdf.dumps(data))

app = SteamApp.from_appmanifest(
path=appmanifest_path,
steam_lib_paths=[]
)

assert app.name == "Fake game"


class TestFindSteamProtonApp:
def test_find_steam_specific_app_proton(
Expand Down
30 changes: 29 additions & 1 deletion tests/test_util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from pathlib import Path

from protontricks.util import create_wine_bin_dir, run_command
from protontricks.util import create_wine_bin_dir, run_command, lower_dict


def get_files_in_dir(d):
Expand Down Expand Up @@ -123,3 +123,31 @@ def test_unknown_steam_runtime_detected(
)
assert warning.getMessage() == \
"Current Steam Runtime not recognized by Protontricks."


class TestLowerDict:
def test_lower_nested_dict(self):
"""
Turn all keys in a nested dictionary to lowercase using `lower_dict`
"""
before = {
"AppState": {
"Name": "Blah",
"appid": 123450,
"userconfig": {
"Language": "English"
}
}
}

after = {
"appstate": {
"name": "Blah",
"appid": 123450,
"userconfig": {
"language": "English"
}
}
}

assert lower_dict(before) == after

0 comments on commit ae43d27

Please sign in to comment.