Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ DEPS := $(OBJS:.o=.d)
tests-list overlay-shim overlay-shim-install overlay-shim-tests \
overlay-shim-bench iconmaker icon \
appinfo helpers-install ntdll-resolve bridge runcheck compatcheck envpasscheck \
compatsvc-check prefixstate-check defaults-check compatpage-check defaults-drift shortcutcheck scriptcheck envcheck buildcheck settingscheck routecheck bridgecheck gamedrivecheck launchercheck launch-shell activatecheck FORCE
compatsvc-check prefixstate-check defaults-check compatpage-check defaults-drift shortcutcheck scriptcheck envcheck buildcheck settingscheck routecheck bridgecheck gamedrivecheck launchercheck launchperfcheck launch-shell activatecheck FORCE

APP_PAYLOAD := app/Sources/NotProtonApp/Resources/payload

Expand Down Expand Up @@ -144,6 +144,9 @@ launchercheck:
@if [ ! -f dylib/tests/launchercheck.sh ]; then $(call SKIP,launchercheck,dylib/tests/launchercheck.sh); exit 0; fi; \
sh dylib/tests/launchercheck.sh dylib/feats/compat_run.sh

launchperfcheck:
python3 dylib/tests/launchperfcheck.py dylib/feats/compat_run.sh

.PHONY: mincheck
mincheck:
@sh dylib/tests/mincheck.sh "$(APP_BUNDLE)" "$(MIN_VER)" "$(GLASS_SDK)"
Expand Down
11 changes: 11 additions & 0 deletions docs/launch-performance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Launch preparation and Dock startup

The launcher checks completed Steam common-redist scripts and existing prefix settings before starting Windows helpers. Unsupported scripts, missing completion markers, changed settings, or failed Python checks follow the normal preparation path. The optional checks use the system Python 3 in isolated mode.

A completed install-script check starts Wine and SamSs in the background while Steam hands off to the game. It uses a command to trigger automatic Wine initialization once; explicitly running `wineboot --init` at this point starts a second service manager and can make SamSs initialization fail. Prefix lock descriptors are closed before starting persistent Wine processes. The generated macOS launcher replaces its shell with Wine using `exec`.

Correct font links are preserved. Bridge and launcher metadata caches are reused only while their dependencies and launch configuration match. Changed files invalidate the caches. LaunchServices registration is repeated when the installed bundle changes. The normal game-exit teardown still stops the prefix server.

`TIMING` records in `notproton-run.log` identify preparation stages and macOS bundle invocation. In local Subnautica 2 tests, bundle invocation to the main game process dropped from roughly four seconds to about one to two seconds. One normal relaunch reached the main game process in 9.98 seconds overall. These observations exclude loading the menu or a playable world and are not performance guarantees.

Run `make launchperfcheck settingscheck launchercheck bridgecheck routecheck gamedrivecheck envcheck buildcheck seedcheck` to exercise the fast paths and existing launcher behavior without launching Steam or Wine.
321 changes: 317 additions & 4 deletions dylib/feats/compat_run.sh

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion dylib/tests/bridgecheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ esac
block=$(printf '%s\n' "$block" | sed '$d')
legacy=$(sed -n '/^install_legacycompat() {/,/^}/p' "$SRC")
[ -n "$legacy" ] || { echo "FAIL: install_legacycompat not found in $SRC"; exit 1; }
body='same_volume() { [ -z "$cross" ]; }
body='np_cache() { return 1; }
np_timing() { :; }
same_volume() { [ -z "$cross" ]; }
volume_clones() { :; }
show_alert() { printf "alert: %s\n" "$1" >> "$log"; }
'"$block
fi
fi"

work=$(mktemp -d)
Expand Down
2 changes: 1 addition & 1 deletion dylib/tests/launchercheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -e
SRC="${1:-$(dirname "$0")/../feats/compat_run.sh}"
[ -f "$SRC" ] || { echo "launchercheck: $SRC not present, skipped"; exit 0; }

funcs=$(sed -n '/^bundle_listing() {$/,/^install_bundle$/p' "$SRC" | sed '$d')
funcs=$(sed -n '/^bundle_listing() {$/,/^install_bundle$/p' "$SRC" | sed '$d; /^np_timing /d')
name_lines=$(sed -n '/^bundle_name=\$(printf/,/^\[ -n "\$bundle_name" \] || bundle_name=/p' "$SRC")
launcher_body=$(sed -n '/^cat > "\$loader_macos\/launcher" <<LAUNCHER$/,/^LAUNCHER$/p' "$SRC" | sed 1d)
loader_line=$(grep -A1 '^if \[ -x "\$loader_macos/wine" \]; then$' "$SRC" | sed -n 2p)
Expand Down
103 changes: 103 additions & 0 deletions dylib/tests/launchperfcheck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
"""Exercise launcher fast paths without Wine, Steam, or a real prefix."""
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import tempfile
import unittest

SOURCE = Path(sys.argv.pop(1) if len(sys.argv) > 1 else Path(__file__).parents[1] / 'feats/compat_run.sh').read_text()


def embedded(marker):
return re.search(r"<<'" + marker + r"'\n(.*?)\n" + marker, SOURCE, re.S)[1]


class LaunchFastPaths(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)

def run_code(self, marker, *args):
return subprocess.run([sys.executable, '-I', '-c', embedded(marker), *map(str, args)], capture_output=True, text=True)

def install_fixture(self, completed=True, action='run process'):
script = self.root / 'script.vdf'
script.write_text('"evaluatorscript" { "0" { "appid" "228980" "mode" "1" '
'"compat_installscript" { "' + action + '" { "dxsetup" { '
'"hasrunkey" "HKEY_LOCAL_MACHINE\\\\Software\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\DirectX\\\\Jun2010" '
'"process 1" "%INSTALLDIR%\\\\_CommonRedist\\\\DirectX\\\\Jun2010\\\\DXSETUP.exe" '
'"command 1" "/silent" "nocleanup" "1" } } } } }')
(self.root / 'system.reg').write_text('[Software\\\\Wow6432Node\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\DirectX\\\\Jun2010]\n'
'"dxsetup"=dword:0000000' + ('1' if completed else '0') + '\n')
return script

def test_completed_install_check(self):
self.assertEqual(self.run_code('NP_COMPLETED_REDIST', self.install_fixture(), self.root).returncode, 0)

def vcredist_2013_fixture(self, x64_complete=True):
script = self.install_fixture()
text = script.read_text().replace('DirectX\\\\Jun2010', 'vcredist\\\\2013')
text = text.replace('dxsetup', 'x86 12.0.30501').replace('DXSETUP.exe', 'vcredist_x86.exe')
# Two architectures must both be complete before the helper is skipped.
text = text.replace('"nocleanup" "1"', '"nocleanup" "1" } "x64 12.0.30501" { '
'"hasrunkey" "HKEY_LOCAL_MACHINE\\\\Software\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\vcredist\\\\2013" '
'"process 1" "%INSTALLDIR%\\\\_CommonRedist\\\\vcredist\\\\2013\\\\vcredist_x64.exe" '
'"nocleanup" "1" "requirement_os" { "Is64BitWindows" "1" }')
script.write_text(text)
(self.root / 'system.reg').write_text('[Software\\\\Wow6432Node\\\\Valve\\\\Steam\\\\Apps\\\\CommonRedist\\\\vcredist\\\\2013]\n'
'"x86 12.0.30501"=dword:00000001\n'
'"x64 12.0.30501"=dword:0000000' + ('1' if x64_complete else '0') + '\n')
return script

def test_completed_vcredist_2013_both_architectures(self):
script = self.vcredist_2013_fixture()
self.assertEqual(self.run_code('NP_COMPLETED_REDIST', script, self.root).returncode, 0)

def test_incomplete_vcredist_2013_requires_evaluator(self):
script = self.vcredist_2013_fixture(x64_complete=False)
self.assertNotEqual(self.run_code('NP_COMPLETED_REDIST', script, self.root).returncode, 0)

def test_incomplete_install_requires_evaluator(self):
self.assertNotEqual(self.run_code('NP_COMPLETED_REDIST', self.install_fixture(False), self.root).returncode, 0)

def test_unknown_action_requires_evaluator(self):
self.assertNotEqual(self.run_code('NP_COMPLETED_REDIST', self.install_fixture(action='registry'), self.root).returncode, 0)

def test_malformed_script_requires_evaluator(self):
script = self.install_fixture()
script.write_text('broken')
self.assertNotEqual(self.run_code('NP_COMPLETED_REDIST', script, self.root).returncode, 0)

def test_cache_dependency_changes(self):
cache = self.root / 'cache.json'
dependency = self.root / 'bridge.dll'
dependency.write_text('original')
args = (cache, 'environment', 'WINELOADER=/runner/wine', dependency)
self.assertEqual(self.run_code('NP_METADATA_CACHE', 'write', *args).returncode, 0)
self.assertEqual(self.run_code('NP_METADATA_CACHE', 'read', *args).returncode, 0)
dependency.write_text('changed')
self.assertNotEqual(self.run_code('NP_METADATA_CACHE', 'read', *args).returncode, 0)
dependency.unlink()
self.assertNotEqual(self.run_code('NP_METADATA_CACHE', 'read', *args).returncode, 0)

def test_bundle_detects_symlink_change(self):
bundle = self.root / 'Game.app'
bundle.mkdir()
link = bundle / 'library.dylib'
link.symlink_to('/one')
before = self.run_code('NP_BUNDLE_CHECK', bundle)
link.unlink()
link.symlink_to('/two')
after = self.run_code('NP_BUNDLE_CHECK', bundle)
self.assertEqual(before.returncode, 0)
self.assertEqual(after.returncode, 0)
self.assertNotEqual(before.stdout, after.stdout)


if __name__ == '__main__':
unittest.main()
4 changes: 2 additions & 2 deletions dylib/tests/routecheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ route() {
case_dir=$(mktemp -d "$work/case.XXXXXX")
: > "$case_dir/calls"
: > "$case_dir/log"
env WINELOADER="$work/wine" STEAM_COMPAT_INSTALL_PATH=/g \
env WINELOADER="$work/wine" WINESERVER=/usr/bin/true STEAM_COMPAT_INSTALL_PATH=/g \
FAKE_STATUS="${FAKE_STATUS:-0}" FAKE_CALLS="$case_dir/calls" \
log="$case_dir/log" body="$body" verb="$verb" \
sh -ec 'eval "$body"; echo "fell through foreground=$foreground"' \
sh -ec 'np_timing() { :; }; eval "$body"; echo "fell through foreground=$foreground"' \
"$verb" "$@" > "$case_dir/out" 2>&1 || true
}
calls() { cat "$case_dir/calls"; }
Expand Down
2 changes: 1 addition & 1 deletion dylib/tests/settingscheck.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ wrapper=$(sed -n '/^without_lock_fds() {$/,/^}$/p' "$SRC")
[ -n "$wrapper" ] || { echo "FAIL: without_lock_fds not found in $SRC"; exit 1; }
helpers=""
for helper in controller_ids ids_without hidraw_lines write_owned_controllers plan_hidden_controllers \
plan_disable_hidraw winebus_lines plan_logpixels video_lines; do
plan_disable_hidraw winebus_lines plan_logpixels video_lines settings_already_applied; do
part=$(sed -n "/^$helper() {\$/,/^}\$/p" "$SRC")
[ -n "$part" ] || { echo "FAIL: $helper not found in $SRC"; exit 1; }
helpers="$helpers
Expand Down