diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-seed-redists b/tools/linuxfs/overlay/usr/local/bin/droiddeck-seed-redists index e6a8ecb4d..5b314d71b 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-seed-redists +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-seed-redists @@ -139,6 +139,51 @@ unseed() { return 1 } +# The ledger lines whose values the registry still holds, in both views where we wrote both. A Proton +# update replaces its default_pfx system.reg but not our ledger beside it, and new prefixes copy both. +present() { awk -F'\t' ' + NR == FNR { + path = $1 + if (path != "vcrt") { + sub(/^HKEY_LOCAL_MACHINE\\\\/, "", path) + if (path == $1) + next + } + n++ + line[n] = $0 + need[n] = 0 + if (path == "vcrt") { + for (a = split("x86 x64 arm64", arch, " "); a > 0; a--) { + runtime = "microsoft\\\\visualstudio\\\\14.0\\\\vc\\\\runtimes\\\\" arch[a] + key[n, ++need[n]] = "[software\\\\" runtime "]" SUBSEP "installed" + key[n, ++need[n]] = "[software\\\\wow6432node\\\\" runtime "]" SUBSEP "installed" + } + } else { + both = substr(path, 1, 10) == "Software\\\\" + path = tolower(path) + key[n, ++need[n]] = "[" path "]" SUBSEP tolower($2) + if (both) + key[n, ++need[n]] = "[software\\\\wow6432node\\\\" substr(path, 11) "]" SUBSEP tolower($2) + } + for (j = 1; j <= need[n]; j++) wanted[key[n, j]] = 1 + next + } + /^\[/ { section = tolower(substr($0, 1, index($0, "]"))) } + /^"/ { + value = substr($0, 2) + value = tolower(substr(value, 1, index(value, "\"=") - 1)) + if ((section SUBSEP value) in wanted) + seen[section SUBSEP value] = 1 + } + END { + for (i = 1; i <= n; i++) { + ok = 1 + for (j = 1; j <= need[i]; j++) if (!(key[i, j] in seen)) ok = 0 + if (ok) print line[i] + } + } +' - "$1"; } + for reg in "$steam_root"/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ /mnt/droiddeck-sd/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ /root/Games/*/steamapps/common/Proton*/files/share/default_pfx*/system.reg \ @@ -166,6 +211,17 @@ for reg in "$steam_root"/steamapps/common/Proton*/files/share/default_pfx*/syste printf '%s\n' "${kept[@]}" >"$seeded" echo "== took Vulkan runtime markers out of $reg" fi + if [ ${#had[@]} -gt 0 ]; then + declare -A held=() + while IFS= read -r line; do [[ $line == *$'\t'* ]] && held[$line]=1; done < <(printf '%s\n' "${!had[@]}" | present "$reg") + if [ ${#held[@]} -lt ${#had[@]} ]; then + echo "== $((${#had[@]} - ${#held[@]})) recorded redist markers are gone from $reg" + had=() + for line in "${!held[@]}"; do had[$line]=1; done + printf '%s\n' "${!had[@]}" >"$seeded" + fi + unset held + fi missing=() for line in "${want[@]}"; do [ -n "${had[$line]:-}" ] || missing+=("$line"); done [ ${#missing[@]} -eq 0 ] && continue diff --git a/tools/tests/test_seed_redists.py b/tools/tests/test_seed_redists.py index 248e3f75a..e23672318 100644 --- a/tools/tests/test_seed_redists.py +++ b/tools/tests/test_seed_redists.py @@ -100,6 +100,53 @@ class SeedRedistsTest(unittest.TestCase): self.run_seed() self.assertIn('"VCRedist2022"=dword:00000001', self.reg.read_text()) + def test_markers_a_replaced_registry_lost_are_seeded_again(self): + self.game() + stock = self.reg.read_text() + self.run_seed() + ledger = (self.reg.parent / ".droiddeck-redists").read_text() + self.assertIn("VCRedist2022", ledger) + self.reg.write_text(stock) + self.run_seed() + reg = self.reg.read_text() + self.assertEqual(reg.count('"VCRedist2022"=dword:00000001'), 2, "both views again") + self.assertIn("[Software\\\\Microsoft\\\\VisualStudio\\\\14.0\\\\VC\\\\Runtimes\\\\x86]", reg) + self.assertEqual(sorted((self.reg.parent / ".droiddeck-redists").read_text().splitlines()), sorted(ledger.splitlines())) + self.run_seed() + self.assertEqual(reg, self.reg.read_text()) + + def test_a_marker_kept_in_only_one_view_is_seeded_again(self): + self.game() + self.run_seed() + wow = "[Software\\\\Wow6432Node\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\vcredist\\\\2022]" + blocks = self.reg.read_text().split("\n\n") + self.reg.write_text("\n\n".join(b for b in blocks if not (b.startswith(wow) and '"VCRedist2022"' in b))) + self.assertEqual(self.reg.read_text().count('"VCRedist2022"'), 1) + self.run_seed() + reg = self.reg.read_text() + self.assertEqual(reg.count('"VCRedist2022"'), 3, "the one left plus both views again") + self.assertTrue(any(b.startswith(wow) and '"VCRedist2022"' in b for b in reg.split("\n\n"))) + + def test_a_vc_runtime_another_architecture_lost_is_seeded_again(self): + self.run_seed() + arm64 = "[Software\\\\Wow6432Node\\\\Microsoft\\\\VisualStudio\\\\14.0\\\\VC\\\\Runtimes\\\\arm64]" + blocks = self.reg.read_text().split("\n\n") + self.reg.write_text("\n\n".join(b for b in blocks if not b.startswith(arm64))) + self.assertNotIn(arm64, self.reg.read_text()) + self.run_seed() + reg = self.reg.read_text() + self.assertIn(arm64, reg) + self.assertEqual(reg.count("[Software\\\\Microsoft\\\\VisualStudio\\\\14.0\\\\VC\\\\Runtimes\\\\x86]"), 2) + + def test_a_marker_written_in_one_view_only_is_kept_as_it_is(self): + self.game(script=GAME_SCRIPT.replace("Software\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\vcredist\\\\2022", "SOFTWARE\\\\AGEIA Technologies").replace("VCRedist2022", "PhysX Version")) + self.run_seed() + once = self.reg.read_text() + self.assertEqual(once.count('"PhysX Version"=dword:00000001'), 1) + result = self.run_seed() + self.assertEqual(once, self.reg.read_text()) + self.assertNotIn("gone", result.stdout) + def test_an_evaluator_script_nested_deeper_is_read(self): script = self.tmp / "legacycompat/evaluatorscript_409710.vdf" script.parent.mkdir(parents=True)