diff --git a/README.md b/README.md index a78770a..5cff6f8 100644 --- a/README.md +++ b/README.md @@ -106,9 +106,10 @@ py -m plugin_helper py -m plugin_helper --config plugin-helper.windows.toml --profile windows installed --instance 1.44.1 ``` -On native Windows, `bootstrap` runs `IPA.exe -n` directly instead of through -Proton. `bootstrap-check` accepts a recorded native bootstrap without requiring -`Logs/_latest.log` when `IPA.exe -n` completed successfully. +On native Windows, `bootstrap` runs `IPA.exe "Beat Saber.exe" -n` directly +instead of through Proton. `bootstrap-check` accepts a recorded native bootstrap +without requiring `Logs/_latest.log` when `IPA.exe "Beat Saber.exe" -n` +completed successfully. When no config file is present on Windows, defaults are `~/BSManager/BSInstances` and `%LOCALAPPDATA%/plugin-helper`. @@ -212,10 +213,10 @@ custom content or non-obvious user choices rather than pure cache data. arguments such as `--no-yeet fpfc` can make the game fail command-line parsing after BSIPA and plugins have already loaded. - BSIPA is managed as a first-class bootstrap phase. The `bootstrap` command - applies the locked `bsipa` root archive, runs `IPA.exe -n` (natively on - Windows or through Proton on Linux), and records every bootstrap-relevant - file under root `IPA.exe*`, `winhttp.dll`, `Libs/`, and `IPA/`, including - backups created during patching. + applies the locked `bsipa` root archive, runs `IPA.exe "Beat Saber.exe" -n` + (natively on Windows or through Proton on Linux), and records every + bootstrap-relevant file under root `IPA.exe*`, `winhttp.dll`, `Libs/`, and + `IPA/`, including backups created during patching. - If an instance version lock includes `bsipa`, ordinary plugin plans require a recorded bootstrap state plus a `Logs/_latest.log` that shows BSIPA startup. Use `bootstrap-check` before planning a batch when you want a quick gate. diff --git a/docs/notes/windows-compatibility.md b/docs/notes/windows-compatibility.md index cef71a3..4363ab0 100644 --- a/docs/notes/windows-compatibility.md +++ b/docs/notes/windows-compatibility.md @@ -48,13 +48,13 @@ Windows instances. ### Done - Add native Windows bootstrap support. - - `bootstrap` auto-detects Windows and runs `IPA.exe -n` directly. + - `bootstrap` auto-detects Windows and runs `IPA.exe "Beat Saber.exe" -n` directly. - `--native` forces native mode; `--proton` remains for Linux/Proton installs. - Timeout cleanup uses `process.terminate()` / `process.kill()` on Windows instead of POSIX process groups. - Bootstrap state records `bootstrapMode: "native"` or `"proton"`. - `bootstrap-check` accepts recorded native Windows bootstrap state without - `Logs/_latest.log` when `IPA.exe -n` completed successfully + `Logs/_latest.log` when `IPA.exe "Beat Saber.exe" -n` completed successfully (`ipaExitCode == 0`, not timed out). - Add Windows-aware default paths when no config is present: `~/BSManager/BSInstances` and `%LOCALAPPDATA%/plugin-helper`. diff --git a/registry/plugins/beatleader.toml b/registry/plugins/beatleader.toml index fffdc03..38d4cc3 100644 --- a/registry/plugins/beatleader.toml +++ b/registry/plugins/beatleader.toml @@ -10,5 +10,6 @@ dependencies = [ { id = "songcore", constraint = ">=3.16.0" }, { id = "sirautil", constraint = ">=3.3.1" }, { id = "system-io-compression", constraint = ">=4.6.0" }, + { id = "system-io-compression-filesystem", constraint = ">=4.7.3056" }, { id = "bsipa", constraint = ">=4.3.7" }, ] diff --git a/src/plugin_helper/bootstrap.py b/src/plugin_helper/bootstrap.py index ba16430..a8d74e7 100644 --- a/src/plugin_helper/bootstrap.py +++ b/src/plugin_helper/bootstrap.py @@ -140,11 +140,12 @@ def build_bootstrap_command( proton: Path | None = None, native: bool | None = None, ) -> list[str]: + beat_saber_exe = ipa.with_name("Beat Saber.exe") use_native = is_windows() if native is None else native if use_native: - return [str(ipa), "-n"] + return [str(ipa), str(beat_saber_exe), "-n"] proton_path = proton or _default_proton() - return [str(proton_path), "run", str(ipa), "-n"] + return [str(proton_path), "run", str(ipa), str(beat_saber_exe), "-n"] def _terminate_process(process: subprocess.Popen[str]) -> None: @@ -249,15 +250,18 @@ def run_bootstrap( ipa = instance_path / "IPA.exe" if not ipa.is_file(): raise FileNotFoundError(f"BSIPA archive did not install IPA.exe: {ipa}") + beat_saber_exe = instance_path / "Beat Saber.exe" + if not beat_saber_exe.is_file(): + raise FileNotFoundError(f"Beat Saber executable not found: {beat_saber_exe}") command = build_bootstrap_command(ipa, proton=proton, native=use_native) if use_native: - tell(f"Running IPA.exe -n natively; timeout {ipa_timeout_seconds}s") + tell(f"Running IPA.exe \"Beat Saber.exe\" -n natively; timeout {ipa_timeout_seconds}s") else: proton_path = proton or _default_proton() if not proton_path.is_file(): raise FileNotFoundError(f"Proton executable not found: {proton_path}") - tell(f"Running IPA.exe -n through Proton; timeout {ipa_timeout_seconds}s") + tell(f"Running IPA.exe \"Beat Saber.exe\" -n through Proton; timeout {ipa_timeout_seconds}s") completed = _run_ipa( command=command, @@ -265,7 +269,7 @@ def run_bootstrap( timeout_seconds=ipa_timeout_seconds, native=use_native, ) - tell("Scanning bootstrap files after IPA.exe -n") + tell("Scanning bootstrap files after IPA.exe \"Beat Saber.exe\" -n") after = scan_bootstrap_files(instance_path) delta = _files_delta(before, after) state: dict[str, Any] = { diff --git a/tests/test_plugin_helper.py b/tests/test_plugin_helper.py index 9117a93..a0cb670 100644 --- a/tests/test_plugin_helper.py +++ b/tests/test_plugin_helper.py @@ -319,14 +319,17 @@ state_dir = "config-state" def test_build_bootstrap_command_native(self) -> None: ipa = Path("C:/Games/Beat Saber/IPA.exe") - self.assertEqual(build_bootstrap_command(ipa, native=True), [str(ipa), "-n"]) + self.assertEqual( + build_bootstrap_command(ipa, native=True), + [str(ipa), str(ipa.with_name("Beat Saber.exe")), "-n"], + ) def test_build_bootstrap_command_proton(self) -> None: ipa = Path("/tmp/1.44.1/IPA.exe") proton = Path("/tmp/proton") self.assertEqual( build_bootstrap_command(ipa, proton=proton, native=False), - [str(proton), "run", str(ipa), "-n"], + [str(proton), "run", str(ipa), str(ipa.with_name("Beat Saber.exe")), "-n"], ) def test_profile_config_resolves_windows_paths(self) -> None: