Skip to content

Commit

Permalink
Detect game specific compatibility tool mappings
Browse files Browse the repository at this point in the history
The Steam Play manifest in `appinfo.vdf` contains game specific
compatibility tool mappings. For example, at the time of writing
Cyberpunk 2077 is set to use Proton Experimental by default to enable
DualSense support.

Update Protontricks to support detect these compatibility tool mappings,
as otherwise Protontricks might use a wrong Proton version or fail to
select a Proton version entirely.

Fixes #267
  • Loading branch information
Matoking committed Dec 6, 2023
1 parent 53f3794 commit 96f253d
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Fixed
- Fix Protontricks not recognizing supported Steam Runtime installation due to changed name
- Fix Protontricks not recognizing default Proton installation for games with different Proton preselected by Valve testing

## [1.10.5] - 2023-09-05
### Fixed
Expand Down
26 changes: 24 additions & 2 deletions src/protontricks/steam.py
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,16 @@ def _get_tool_app(compat_tool_name, steam_apps, steam_play_manifest):
# App ID was most likely not provided
app_section = None

try:
manifest_app_compat_section = next(
entry for mapping_appid, entry in
steam_play_manifest["appinfo"]["extended"]["app_mappings"].items()
if int(mapping_appid) == appid
)
except StopIteration:
# App doesn't have a default compatibility tool mapping
manifest_app_compat_section = None

# ToolMapping seems to be used in older Steam beta releases
try:
tool_mapping = (
Expand Down Expand Up @@ -679,7 +689,19 @@ def _get_tool_app(compat_tool_name, steam_apps, steam_play_manifest):
)
potential_names.append(recommended_runtime)

# Global user settings have the 3rd highest priority
# Game specific default compatibility tool mapping in Steam Play 2.0
# manifest has the 3rd highest priority
if manifest_app_compat_section:
if "tool" in manifest_app_compat_section:
tool = manifest_app_compat_section["tool"]
logger.info(
"App has default compatibility tool mapping in the Steam Play "
"manifest: %s",
tool
)
potential_names.append(tool)

# Global user settings have the 4th highest priority
if compat_tool_mapping.get("0", {}).get("name"):
tool_name = compat_tool_mapping["0"]["name"]
logger.info(
Expand All @@ -689,7 +711,7 @@ def _get_tool_app(compat_tool_name, steam_apps, steam_play_manifest):
)
potential_names.append(tool_name)

# Legacy user settings (ToolMapping) have the 4th highest priority
# Legacy user settings (ToolMapping) have the 5th highest priority
if tool_mapping.get(str(appid), {}).get("name", {}):
tool_name = tool_mapping[str(appid)]["name"]
logger.info(
Expand Down
31 changes: 30 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,34 @@ def func(proton_app, compat_tool_name, aliases=None):
return func


@pytest.fixture(scope="function")
def appinfo_app_mapping_factory(appinfo_factory, steam_dir):
"""
Factory function to add Steam app specific compat tool app mappings
to the appinfo.vdf binary file
"""
def func(steam_app, compat_tool_name):
manifest_appinfo = next(
section["appinfo"] for section
in iter_appinfo_sections(steam_dir / "appcache" / "appinfo.vdf")
if section["appinfo"]["appid"] == 891390
)

manifest_appinfo["extended"]["app_mappings"][str(steam_app.appid)] = {
"appid": steam_app.appid,
"tool": compat_tool_name
}

# Update the appinfo.vdf with the compat tools that have been
# added so far.
appinfo_factory(
appid=891390, # Steam Play 2.0 Manifests app ID,
appinfo=manifest_appinfo
)

return func


@pytest.fixture(scope="function", autouse=True)
def appinfo_factory(steam_dir):
"""
Expand All @@ -364,7 +392,8 @@ def appinfo_factory(steam_dir):
"appinfo": {
"appid": 891390,
"extended": {
"compat_tools": {}
"compat_tools": {},
"app_mappings": {}
}
}
}
Expand Down
45 changes: 43 additions & 2 deletions tests/test_steam.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,6 @@ def test_find_legacy_tool_mapping_global(
)
assert proton_app.name == "Proton B"



@pytest.mark.usefixtures("steam_deck", "info_logging")
def test_find_steam_deck_profile(
self, steam_app_factory, proton_factory, appinfo_factory,
Expand Down Expand Up @@ -374,6 +372,49 @@ def test_find_steam_default_proton(
if "Using stable version of Proton" in record.message
)

@pytest.mark.usefixtures("info_logging")
def test_find_steam_steamplay_manifest_app_mapping(
self, steam_app_factory, steam_dir, proton_factory,
appinfo_app_mapping_factory, steam_config_path, caplog):
"""
Ensure the function returns the Proton version defined in the
Steam Play manifest if one is defined
"""
steam_app = steam_app_factory(name="Fake game", appid=10)
proton_app = proton_factory(
name="Test Proton", appid=123450, compat_tool_name="test-proton"
)

appinfo_app_mapping_factory(
steam_app=steam_app, compat_tool_name="test-proton"
)

steam_config_path.write_text(
vdf.dumps({
"InstallConfigStore": {
"Software": {
"Valve": {
"Steam": {}
}
}
}
})
)

proton_app = find_steam_compat_tool_app(
steam_path=steam_dir,
steam_apps=[steam_app, proton_app],
appid=10
)

assert proton_app.appid == 123450

assert any(
record for record in caplog.records
if "App has default compatibility tool mapping in the Steam Play"
in record.message
)


class TestFindLibraryPaths:
@pytest.mark.parametrize(
Expand Down

0 comments on commit 96f253d

Please sign in to comment.