Skip to content

Commit

Permalink
Detect app icon location
Browse files Browse the repository at this point in the history
Previously we could assume the app icon path for Steam apps. Starting
with newer Steam client, this is no longer the case and the icon path
might be located under either of the two paths:

* `<steam_path>/appcache/librarycache/<appid>/<40 hex chars>.jpg` (new)
* `<steam_path/appcache/librarycache/<appid>_icon.jpg`

This also means we have to perform additional IO for each Steam app to
detect the icon location; this is done in `SteamApp.from_appmanifest`.
  • Loading branch information
Matoking committed Dec 21, 2024
1 parent 09e9a1f commit eb9343e
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]
### Fixed
- Fix missing app icons for games installed using newer Steam client

## [1.12.0] - 2024-09-16
### Added
- `--cwd-app` flag to set working directory to the game's installation directory
Expand Down
28 changes: 26 additions & 2 deletions src/protontricks/steam.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import os
import re
import string
import struct
import zlib
Expand Down Expand Up @@ -263,8 +264,31 @@ def from_appmanifest(cls, path, steam_lib_paths, steam_path=None):

# If Steam path was provided, also populate the icon path
if steam_path:
icon_path = \
steam_path / "appcache" / "librarycache" / f"{appid}_icon.jpg"
# The icon path might be located in one of the two locations:
# 1. `<steam_path>/appcache/librarycache/<appid>/<40 hex chars>.jpg
# 2. `<steam_path>/appcache/librarycache/<appid>_icon.jpg`
#
# There doesn't appear to be any other way to determine which is
# used other than by checking. This incurs some I/O for each app.
icon_path = None
library_cache_path = steam_path / "appcache" / "librarycache"
try:
# Try 1st location. This appears to be the newest, so this
# should hopefully be the first match, at least on newer Steam
# installations.
app_lib_cache_path = library_cache_path / str(appid)

icon_path = next(
path for path in app_lib_cache_path.iterdir()
if re.match(r"[a-f0-9]{40}\.jpg", path.name)
)
except (StopIteration, FileNotFoundError):
# Try 2nd location
icon_path = library_cache_path / f"{appid}_icon.jpg"

if not icon_path.is_file():
# No icon was found
icon_path = None

# Check if the app requires another app. This is the case with
# newer versions of Proton, which use Steam Runtimes installed as
Expand Down
37 changes: 28 additions & 9 deletions tests/test_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from protontricks.gui import (prompt_filesystem_access,
select_steam_app_with_gui,
select_steam_installation)
from protontricks.steam import SteamApp


@pytest.fixture(scope="function")
Expand Down Expand Up @@ -96,16 +97,33 @@ def test_select_game_icons(
Select a game using the GUI. Ensure that icons are used in the dialog
whenever available.
"""
steam_apps = [
steam_app_factory(name="Fake game 1", appid=10),
steam_app_factory(name="Fake game 2", appid=20),
steam_app_factory(name="Fake game 3", appid=30),
]
steam_app_factory(name="Fake game 1", appid=10)
steam_app_factory(name="Fake game 2", appid=20)
steam_app_factory(name="Fake game 3", appid=30)

# Create icons for game 1 and 3
for appid in (10, 30):
Image.new("RGB", (32, 32)).save(
steam_dir / "appcache" / "librarycache" / f"{appid}_icon.jpg")
# Old location for 10
Image.new("RGB", (32, 32)).save(
steam_dir / "appcache" / "librarycache" / "10_icon.jpg"
)

# New location for 30
(steam_dir / "appcache" / "librarycache" / "30").mkdir()
Image.new("RGB", (32, 32)).save(
steam_dir / "appcache" / "librarycache" / "30"
/ "ffffffffffffffffffffffffffffffffffffffff.jpg"
)

# Read Steam apps using `SteamApp.from_appmanifest` to ensure
# icon paths are detected correctly
steam_apps = [
SteamApp.from_appmanifest(
steam_dir / "steamapps" / f"appmanifest_{appid}.acf",
steam_path=steam_dir,
steam_lib_paths=[steam_dir]
)
for appid in (10, 20, 30)
]

gui_provider.mock_stdout = "Fake game 2: 20"
select_steam_app_with_gui(steam_apps=steam_apps, steam_path=steam_dir)
Expand All @@ -114,7 +132,8 @@ def test_select_game_icons(

assert b"librarycache/10_icon.jpg\nFake game 1" in input_
assert b"icon_placeholder.png\nFake game 2" in input_
assert b"librarycache/30_icon.jpg\nFake game 3" in input_
assert b"librarycache/30/ffffffffffffffffffffffffffffffffffffffff.jpg\nFake game 3" \
in input_

def test_select_game_icons_ensure_resize(
self, gui_provider, steam_app_factory, steam_dir, home_dir):
Expand Down
2 changes: 0 additions & 2 deletions tests/test_steam.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,6 @@ def test_steam_app_from_appmanifest_and_steam_path(

assert steam_app.name == "Fake game"
assert steam_app.appid == 10
assert steam_app.icon_path \
== steam_dir / "appcache" / "librarycache" / "10_icon.jpg"

@pytest.mark.parametrize(
"content",
Expand Down

0 comments on commit eb9343e

Please sign in to comment.