Run verified legacy PhysX prerequisite without its wizard

This commit is contained in:
xXJsonDeruloXx committed 2026-10-01 19:28:18 -04:00
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:
+63
View File
@@ -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()