Merge contributed renamed-shortcut fix from PR #8

This commit is contained in:
ClassicWasTaken committed 2026-10-06 00:22:21 -07:00
commit 0aba1aa2b5
2 files changed
+52 -5

No files matched your search

+9 -5
View File
@@ -312,7 +312,7 @@ def update_shortcut(data: bytes | None, executable: str, directory: str) -> tupl
def remove_shortcut(data: bytes, executable: str) -> tuple[bytes, list[int]]:
"""Remove only exact native title/path matches; keep all foreign fields."""
"""Remove only entries that launch the native executable; keep all foreign fields."""
root = loads(data)
shortcuts = root.get("shortcuts")
if shortcuts is None or shortcuts.kind != 0:
@@ -322,13 +322,17 @@ def remove_shortcut(data: bytes, executable: str) -> tuple[bytes, list[int]]:
if entry.kind != 0:
raise ValueError("Unexpected Steam shortcut record; no changes were made.")
fields = entry.value
title = fields.get("AppName", fields.get("appname"))
path = fields.get("Exe", fields.get("exe"))
if (title is not None and title.kind == 1 and title.value == NAME
and path is not None and path.kind == 1 and path.value in (executable, f'"{executable}"')):
# Ownership is the native executable, not the display title: Steam
# lets users rename library entries, and the live client path treats
# an entry pointing at the native executable as owned as well. Match
# the path exactly, bare or quoted once, like the live client does.
# A malformed app ID leaves the entry in place instead of aborting
# the removal of the valid ones.
if path is not None and path.kind == 1 and path.value in (executable, f'"{executable}"'):
appid = fields.get("appid")
if appid is None or appid.kind != 2 or not 2 ** 31 <= appid.value < 2 ** 32:
raise ValueError("The native Halo shortcut has an invalid app ID; no changes were made.")
continue
matches.append(index)
appids.append(appid.value)
for index in matches:
+43
View File
@@ -124,6 +124,49 @@ def test_uninstall_keeps_saves_removes_all_exact_native_entries_and_managed_art(
assert (saved / "save/profile.sav").read_bytes() == b"campaign progress"
def test_uninstall_removes_a_native_entry_the_user_renamed(installation):
remote, steam, configs, appids, _ = installation
config = configs[0]
executable = str(remote.GAME / "halo")
root = steam.loads((config / "shortcuts.vdf").read_bytes())
for entry in root["shortcuts"].value.values():
path = entry.value.get("Exe")
if path is not None and path.value in (executable, f'"{executable}"'):
entry.value["AppName"] = steam.Value(1, "My favorite Halo")
(config / "shortcuts.vdf").write_bytes(steam.dumps(root))
result = remote.uninstall(RUN_ID, keep_saves=True)
assert result["uninstalled"] and result["steam"]["status"] == "removed"
assert result["steam"]["accounts"] == ["10", "20"]
remaining = steam.loads((config / "shortcuts.vdf").read_bytes())["shortcuts"].value
assert set(remaining) == {"0", "2"}
assert "My favorite Halo" not in {entry.value["AppName"].value for entry in remaining.values()}
assert result["steam"]["appids"] == sorted(set(appids))
assert not (config / "grid" / f"{appids[0]}p.jpg").exists()
assert not (config / "grid" / f"{appids[0]}.png").exists()
assert (configs[1] / "grid" / f"{appids[1]}.png").read_bytes() == b"custom user tile"
assert (configs[0] / "grid/10p.jpg").read_bytes() == b"other game's art"
backups = list(config.glob("shortcuts.vdf.halo-frame-uninstall-*.bak"))
assert len(backups) == 1 and b"My favorite Halo" in backups[0].read_bytes()
def test_uninstall_skips_native_entries_with_malformed_ids_and_odd_quoting(installation):
remote, steam, configs, _, _ = installation
config = configs[0]
executable = str(remote.GAME / "halo")
root = steam.loads((config / "shortcuts.vdf").read_bytes())
root["shortcuts"].value["3"] = steam.Value(0, OrderedDict([
("appid", steam.Value(1, "not-a-number")), ("AppName", steam.Value(1, "Broken id")),
("Exe", steam.Value(1, executable))]))
root["shortcuts"].value["4"] = steam.Value(0, OrderedDict([
("appid", steam.Value(2, 0xF2233445)), ("AppName", steam.Value(1, "Odd quotes")),
("Exe", steam.Value(1, executable + '"'))]))
(config / "shortcuts.vdf").write_bytes(steam.dumps(root))
result = remote.uninstall(RUN_ID, keep_saves=True)
assert result["uninstalled"] and result["steam"]["status"] == "removed"
remaining = steam.loads((config / "shortcuts.vdf").read_bytes())["shortcuts"].value
assert set(remaining) == {"0", "2", "3", "4"}
def test_uninstall_without_keep_saves_deletes_contained_saves_only(installation):
remote, _, _, _, _ = installation
outside = remote.HOME / "external-saves"