mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
* fix: redist markers a replaced registry lost are seeded again droiddeck-seed-redists recorded what it seeded in .droiddeck-redists and trusted that record from then on. A Proton update replaces its default_pfx system.reg but leaves our record beside it, and every new prefix copies both, so new games got a record of markers their registry never had: Steam then ran their install scripts for real. Batman: Arkham City's AMD Dual-Core Optimizer installer hangs there, leaving the game on "Running install script" on every launch. Each recorded marker is now looked up in the registry, in both views where both were written, and seeded again when it is gone; a record of markers nobody wants any more is rewritten without them. * Check every VC runtime architecture before keeping the vcrt marker vcrt writes Installed for x86, x64 and arm64 in both registry views, but present() only looked at x86. If only an x64 or arm64 value was gone the runtime would not be seeded again. Require all six values and test the loss of the arm64 Wow6432Node one.
204 lines
9.1 KiB
Python
204 lines
9.1 KiB
Python
import os
|
|
import shutil
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
SCRIPT = Path(__file__).resolve().parents[1] / "linuxfs/overlay/usr/local/bin/droiddeck-seed-redists"
|
|
|
|
GAME_SCRIPT = r'''"InstallScript"
|
|
{
|
|
"Run Process"
|
|
{
|
|
"VCRedist2022"
|
|
{
|
|
"HasRunKey" "HKEY_LOCAL_MACHINE\\Software\\Valve\\Steam\\Apps\\CommonRedist\\vcredist\\2022"
|
|
"process 1" "%INSTALLDIR%\\_CommonRedist\\vcredist\\2022\\VC_redist.x64.exe"
|
|
"command 1" "/q /norestart"
|
|
"NoCleanUp" "1"
|
|
}
|
|
}
|
|
}
|
|
'''
|
|
|
|
VULKAN_SCRIPT = r'''"InstallScript"
|
|
{
|
|
"Run Process"
|
|
{
|
|
"Vulkan111080"
|
|
{
|
|
"HasRunKey" "HKEY_LOCAL_MACHINE\\Software\\Valve\\Steam\\Apps\\1174180"
|
|
"process 1" "%INSTALLDIR%\\Redistributables\\VulkanRT-1.1.108.0-Installer.exe"
|
|
"command 1" "/S"
|
|
"NoCleanUp" "0"
|
|
}
|
|
"RockstarLauncher"
|
|
{
|
|
"HasRunKey" "HKEY_LOCAL_MACHINE\\Software\\Rockstar Games\\Steam\\Launcher"
|
|
"process 1" "%INSTALLDIR%\\Redistributables\\Rockstar-Games-Launcher.exe"
|
|
"command 1" "/s /t"
|
|
"NoCleanUp" "0"
|
|
}
|
|
}
|
|
}
|
|
'''
|
|
|
|
|
|
class SeedRedistsTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = Path(tempfile.mkdtemp())
|
|
self.addCleanup(shutil.rmtree, self.tmp, True)
|
|
self.steam = self.tmp / "Steam"
|
|
self.reg = self.steam / "steamapps/compatdata/1234/pfx/system.reg"
|
|
self.reg.parent.mkdir(parents=True)
|
|
self.reg.write_text("WINE REGISTRY Version 2\n")
|
|
|
|
def game(self, name="Some Game", script=GAME_SCRIPT, library=None):
|
|
root = Path(library or self.steam)
|
|
path = root / "steamapps/common" / name / "_CommonRedist/vcredist/2022/installscript.vdf"
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(script)
|
|
manifest = root / "steamapps" / ("appmanifest_%d.acf" % (abs(hash(name)) % 100000))
|
|
manifest.write_text('"AppState" { "installdir" "%s" }' % name)
|
|
return path
|
|
|
|
def run_seed(self, libraries=""):
|
|
env = dict(os.environ, DROIDDECK_LIBRARIES=libraries)
|
|
return subprocess.run(["bash", str(SCRIPT), str(self.steam)], env=env, capture_output=True, text=True, check=True)
|
|
|
|
def test_a_games_own_install_script_is_marked_as_run(self):
|
|
self.game()
|
|
self.run_seed()
|
|
reg = self.reg.read_text()
|
|
self.assertIn("[Software\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\vcredist\\\\2022]", reg)
|
|
self.assertIn("[Software\\\\Wow6432Node\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\vcredist\\\\2022]", reg)
|
|
self.assertIn('"VCRedist2022"=dword:00000001', reg)
|
|
|
|
def test_games_in_other_libraries_are_read(self):
|
|
sd = self.tmp / "sd"
|
|
self.game(library=sd)
|
|
self.run_seed(str(sd))
|
|
self.assertIn('"VCRedist2022"=dword:00000001', self.reg.read_text())
|
|
|
|
def test_the_vc_runtime_is_recorded_for_every_architecture_and_view(self):
|
|
self.run_seed()
|
|
reg = self.reg.read_text()
|
|
for view in ("Software", "Software\\\\Wow6432Node"):
|
|
for arch in ("x86", "x64", "arm64"):
|
|
self.assertIn("[%s\\\\Microsoft\\\\VisualStudio\\\\14.0\\\\VC\\\\Runtimes\\\\%s]" % (view, arch), reg)
|
|
self.assertIn('"Installed"=dword:00000001', reg)
|
|
self.assertIn('"Major"=dword:0000000e', reg)
|
|
self.assertIn('"Version"="v14.51.36247.00"', reg)
|
|
|
|
def test_a_seeded_prefix_is_left_alone_until_a_game_changes(self):
|
|
self.run_seed()
|
|
once = self.reg.read_text()
|
|
self.run_seed()
|
|
self.assertEqual(once, self.reg.read_text())
|
|
self.game()
|
|
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)
|
|
script.write_text('"evaluatorscript"\n{\n\t"1"\n' + "\n".join("\t" + line for line in GAME_SCRIPT.replace("VCRedist2022", "x64 14.99.1.0").splitlines()) + "\n}\n")
|
|
env = dict(os.environ, DROIDDECK_LIBRARIES="", DROIDDECK_SEED_SCRIPTS=str(script))
|
|
subprocess.run(["bash", str(SCRIPT), str(self.steam)], env=env, capture_output=True, text=True, check=True)
|
|
self.assertIn('"x64 14.99.1.0"=dword:00000001', self.reg.read_text())
|
|
|
|
def test_the_prefix_date_is_kept(self):
|
|
os.utime(self.reg, (1_000_000_000, 1_000_000_000))
|
|
self.run_seed()
|
|
self.assertEqual(int(self.reg.stat().st_mtime), 1_000_000_000)
|
|
|
|
def rdr2(self):
|
|
path = self.steam / "steamapps/common/Red Dead Redemption 2/installscript.vdf"
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(VULKAN_SCRIPT)
|
|
(self.steam / "steamapps/appmanifest_1174180.acf").write_text('"AppState" { "installdir" "Red Dead Redemption 2" }')
|
|
|
|
def test_a_vulkan_runtime_installer_is_left_to_run(self):
|
|
self.rdr2()
|
|
self.run_seed()
|
|
reg = self.reg.read_text()
|
|
self.assertNotIn("Vulkan111080", reg)
|
|
self.assertIn('"RockstarLauncher"=dword:00000001', reg)
|
|
self.assertNotIn("Vulkan111080", (self.reg.parent / ".droiddeck-redists").read_text())
|
|
|
|
def test_a_vulkan_marker_an_earlier_seeding_wrote_comes_out_once(self):
|
|
self.rdr2()
|
|
marker = "HKEY_LOCAL_MACHINE\\\\Software\\\\Valve\\\\Steam\\\\Apps\\\\1174180\tVulkan111080"
|
|
self.reg.write_text(
|
|
"WINE REGISTRY Version 2\n"
|
|
"\n[Software\\\\Valve\\\\Steam\\\\Apps\\\\1174180] 1791729070\n#time=1dd598d29161f72\n"
|
|
'"Other"=dword:00000001\n"Vulkan111080"=dword:00000001\n'
|
|
"\n[Software\\\\Wow6432Node\\\\Valve\\\\Steam\\\\Apps\\\\1174180] 1791729070\n"
|
|
'"Vulkan111080"=dword:00000001\n'
|
|
"\n[Software\\\\Valve\\\\Steam\\\\Apps\\\\22380] 1791311644\n"
|
|
'"Vulkan111080"=dword:00000001\n')
|
|
(self.reg.parent / ".droiddeck-redists").write_text(marker + "\n")
|
|
os.utime(self.reg, (1_000_000_000, 1_000_000_000))
|
|
self.run_seed()
|
|
reg = self.reg.read_text()
|
|
self.assertEqual(reg.count('"Vulkan111080"'), 1, "only the other app's value stays")
|
|
self.assertIn('"Other"=dword:00000001', reg)
|
|
self.assertNotIn("Vulkan111080", (self.reg.parent / ".droiddeck-redists").read_text())
|
|
self.assertEqual(int(self.reg.stat().st_mtime), 1_000_000_000)
|
|
with self.reg.open("a") as out:
|
|
out.write('\n[Software\\\\Valve\\\\Steam\\\\Apps\\\\1174180] 1791730000\n"Vulkan111080"=dword:00000001\n')
|
|
self.run_seed()
|
|
self.assertEqual(self.reg.read_text().count('"Vulkan111080"'), 2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|