mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-12 03:21:08 +00:00
Run verified legacy PhysX prerequisite without its wizard
This commit is contained in:
1 parent
a9cd982ec8
commit
9e2d7ede3b
2 files changed
+100
-8
No files matched your search
@@ -144,8 +144,8 @@ bl_game_cpus() {
|
||||
}
|
||||
"""
|
||||
|
||||
# Prepare Steam's transient evaluator input before Wine reads it. Only the old x86 IExpress
|
||||
# redistributables are changed: their extracted MSI is run normally, without the stuck wrapper.
|
||||
# Prepare Steam's transient evaluator input before Wine reads it. Recognized legacy VC bundles
|
||||
# use their actual MSI; a verified legacy PhysX bundle uses its supported quiet argument.
|
||||
BL_INSTALLSCRIPT_SETUP = """
|
||||
bl_installscript() {
|
||||
[ "${verb:-}" = run ] || return 0
|
||||
@@ -528,6 +528,22 @@ def redist_msi(installer, cache_root):
|
||||
return None
|
||||
|
||||
|
||||
# Mirror's Edge bundles Wise's PhysX 8.10.17 bootstrapper without arguments. /s is forwarded to
|
||||
# msiexec and still opens its license wizard; /qn installs the actual runtime silently. Match the
|
||||
# exact tested bundle, rather than guessing switches for other PhysX installer generations.
|
||||
QUIET_PHYSX_SHA256 = '32d27a5e64e099adc4d3d2c87e5702f237c3d1292406f166059837c4c827e22f'
|
||||
|
||||
|
||||
def quiet_physx(installer):
|
||||
if os.path.getsize(installer) > 64 * 1024 * 1024 or not os.path.isfile(installer):
|
||||
return False
|
||||
digest = hashlib.sha256()
|
||||
with open(installer, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(65536), b''):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest() == QUIET_PHYSX_SHA256
|
||||
|
||||
|
||||
def prepare_installscript(steam_root, script):
|
||||
# The input is Steam's temporary evaluator file, not a game's installed metadata. Restrict
|
||||
# resolution to that one directory so a Windows path or a traversal cannot rewrite other files.
|
||||
@@ -561,17 +577,18 @@ def prepare_installscript(steam_root, script):
|
||||
continue
|
||||
fields = dict(direct_entries(tokens, process))
|
||||
exe_index, args_index = fields.get('process 1'), fields.get('command 1')
|
||||
if exe_index is None or args_index is None:
|
||||
if exe_index is None:
|
||||
continue
|
||||
match = re.fullmatch(r'%INSTALLDIR%[\\/]+(.+)',
|
||||
vdf_value(tokens[exe_index]), re.IGNORECASE)
|
||||
args = vdf_value(tokens[args_index]).split()
|
||||
if not match or not args or any(arg.lower() not in
|
||||
('/q:a', '/q', '/qn', '/quiet', '/norestart') for arg in args):
|
||||
args = vdf_value(tokens[args_index]).split() if args_index is not None else []
|
||||
if not match:
|
||||
continue
|
||||
relative = re.split(r'[\\/]+', match[1])
|
||||
if any(part in ('', '.', '..') for part in relative) or not re.fullmatch(
|
||||
r'vcredist_x86[^\\/]*\.exe', relative[-1], re.IGNORECASE):
|
||||
physx = relative[-1].lower() == 'physx_systemsoftware.exe' and not args
|
||||
vc = re.fullmatch(r'vcredist_x86[^\\/]*\.exe', relative[-1], re.IGNORECASE)
|
||||
if any(part in ('', '.', '..') for part in relative) or not (physx or (vc and args and
|
||||
all(arg.lower() in ('/q:a', '/q', '/qn', '/quiet', '/norestart') for arg in args))):
|
||||
continue
|
||||
try:
|
||||
installer = install_dir
|
||||
@@ -583,6 +600,18 @@ def prepare_installscript(steam_root, script):
|
||||
raise ValueError('ambiguous or missing redistributable path')
|
||||
part = matches[0]
|
||||
installer = os.path.join(installer, part)
|
||||
if physx:
|
||||
if not quiet_physx(installer):
|
||||
continue
|
||||
if args_index is None:
|
||||
end = block_end(tokens, process)
|
||||
tokens[end:end] = [vdf_quote('command 1'), vdf_quote('/qn')]
|
||||
else:
|
||||
tokens[args_index] = vdf_quote('/qn')
|
||||
changed = True
|
||||
print('bannerlator-steam-compat: running verified PhysX 8.10.17 quietly',
|
||||
file=sys.stderr)
|
||||
continue
|
||||
msi = redist_msi(installer,
|
||||
os.path.join(steam_root, 'legacycompat', '.droiddeck-redists'))
|
||||
except (OSError, ValueError, subprocess.SubprocessError) as e:
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import runpy
|
||||
import tempfile
|
||||
import unittest
|
||||
@@ -114,6 +115,68 @@ class SteamRedistTest(unittest.TestCase):
|
||||
self.assertTrue(Path(result).is_file())
|
||||
self.assertFalse((Path(self.tmp.name) / 'outside').exists())
|
||||
|
||||
def physx_fixture(self, command=None):
|
||||
payload = b'verified Wise PhysX bundle fixture'
|
||||
directory = self.installer_dir / 'PhysX'
|
||||
directory.mkdir(exist_ok=True)
|
||||
installer = directory / 'PhysX_SystemSoftware.exe'
|
||||
installer.write_bytes(payload)
|
||||
script = '''"evaluatorscript" { "0" {
|
||||
"install_path" "%s" "compat_installscript" { "run process" {
|
||||
"PhysX Version" {
|
||||
"process 1" "%%INSTALLDIR%%\\\\physx\\\\PHYSX_SYSTEMSOFTWARE.EXE"
|
||||
%s
|
||||
"HasRunKey" "HKEY_LOCAL_MACHINE\\\\SOFTWARE\\\\AGEIA Technologies"
|
||||
"MinimumHasRunValue" "81017" "NoCleanUp" "1"
|
||||
}
|
||||
"Other step" { "process 1" "%%INSTALLDIR%%\\\\other.exe" }
|
||||
} }
|
||||
} }''' % (self.installer_dir, '' if command is None else
|
||||
'"command 1" "%s"' % command)
|
||||
self.path.write_text(script)
|
||||
return installer, hashlib.sha256(payload).hexdigest()
|
||||
|
||||
def test_verified_physx_runs_quietly_and_preserves_steam_completion_checks(self):
|
||||
for command in (None, ''):
|
||||
installer, digest = self.physx_fixture(command)
|
||||
with patch.dict(PREPARE.__globals__, QUIET_PHYSX_SHA256=digest):
|
||||
self.assertTrue(self.prepare())
|
||||
tokens = COMPAT['tokenize'](self.path.read_text())
|
||||
decoded = [COMPAT['vdf_value'](t) for t in tokens if t.startswith('"')]
|
||||
self.assertIn('/qn', decoded)
|
||||
self.assertIn(r'%INSTALLDIR%\physx\PHYSX_SYSTEMSOFTWARE.EXE', decoded)
|
||||
self.assertIn('81017', decoded)
|
||||
self.assertIn(r'HKEY_LOCAL_MACHINE\SOFTWARE\AGEIA Technologies', decoded)
|
||||
self.assertIn('Other step', decoded)
|
||||
before = self.path.read_bytes()
|
||||
self.assertFalse(self.prepare())
|
||||
self.assertEqual(before, self.path.read_bytes())
|
||||
|
||||
def test_physx_other_versions_and_existing_commands_are_untouched(self):
|
||||
installer, digest = self.physx_fixture()
|
||||
before = self.path.read_bytes()
|
||||
self.assertFalse(self.prepare()) # Fixture is not the real bundle's hash.
|
||||
self.assertEqual(before, self.path.read_bytes())
|
||||
for command in ('/uninstall', '/custom', '/s', '/qn'):
|
||||
self.physx_fixture(command)
|
||||
before = self.path.read_bytes()
|
||||
with patch.dict(PREPARE.__globals__, QUIET_PHYSX_SHA256=digest):
|
||||
self.assertFalse(self.prepare())
|
||||
self.assertEqual(before, self.path.read_bytes())
|
||||
|
||||
def test_physx_missing_or_traversing_installer_keeps_original(self):
|
||||
installer, digest = self.physx_fixture()
|
||||
installer.unlink()
|
||||
before = self.path.read_bytes()
|
||||
self.assertFalse(self.prepare())
|
||||
self.assertEqual(before, self.path.read_bytes())
|
||||
self.physx_fixture()
|
||||
self.path.write_text(self.path.read_text().replace('physx\\\\', '..\\\\'))
|
||||
before = self.path.read_bytes()
|
||||
with patch.dict(PREPARE.__globals__, QUIET_PHYSX_SHA256=digest):
|
||||
self.assertFalse(self.prepare())
|
||||
self.assertEqual(before, self.path.read_bytes())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user