Failed attempt to not open IPA.exe window

This commit is contained in:
pleb
2026-07-05 15:44:04 -07:00
parent ea1e2ad61e
commit 545d407116
5 changed files with 25 additions and 16 deletions
+8 -7
View File
@@ -106,9 +106,10 @@ py -m plugin_helper
py -m plugin_helper --config plugin-helper.windows.toml --profile windows installed --instance 1.44.1 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 On native Windows, `bootstrap` runs `IPA.exe "Beat Saber.exe" -n` directly
Proton. `bootstrap-check` accepts a recorded native bootstrap without requiring instead of through Proton. `bootstrap-check` accepts a recorded native bootstrap
`Logs/_latest.log` when `IPA.exe -n` completed successfully. without requiring `Logs/_latest.log` when `IPA.exe "Beat Saber.exe" -n`
completed successfully.
When no config file is present on Windows, defaults are When no config file is present on Windows, defaults are
`~/BSManager/BSInstances` and `%LOCALAPPDATA%/plugin-helper`. `~/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 arguments such as `--no-yeet fpfc` can make the game fail command-line
parsing after BSIPA and plugins have already loaded. parsing after BSIPA and plugins have already loaded.
- BSIPA is managed as a first-class bootstrap phase. The `bootstrap` command - BSIPA is managed as a first-class bootstrap phase. The `bootstrap` command
applies the locked `bsipa` root archive, runs `IPA.exe -n` (natively on applies the locked `bsipa` root archive, runs `IPA.exe "Beat Saber.exe" -n`
Windows or through Proton on Linux), and records every bootstrap-relevant (natively on Windows or through Proton on Linux), and records every
file under root `IPA.exe*`, `winhttp.dll`, `Libs/`, and `IPA/`, including bootstrap-relevant file under root `IPA.exe*`, `winhttp.dll`, `Libs/`, and
backups created during patching. `IPA/`, including backups created during patching.
- If an instance version lock includes `bsipa`, ordinary plugin plans require a - If an instance version lock includes `bsipa`, ordinary plugin plans require a
recorded bootstrap state plus a `Logs/_latest.log` that shows BSIPA startup. 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. Use `bootstrap-check` before planning a batch when you want a quick gate.
+2 -2
View File
@@ -48,13 +48,13 @@ Windows instances.
### Done ### Done
- Add native Windows bootstrap support. - 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. - `--native` forces native mode; `--proton` remains for Linux/Proton installs.
- Timeout cleanup uses `process.terminate()` / `process.kill()` on Windows - Timeout cleanup uses `process.terminate()` / `process.kill()` on Windows
instead of POSIX process groups. instead of POSIX process groups.
- Bootstrap state records `bootstrapMode: "native"` or `"proton"`. - Bootstrap state records `bootstrapMode: "native"` or `"proton"`.
- `bootstrap-check` accepts recorded native Windows bootstrap state without - `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). (`ipaExitCode == 0`, not timed out).
- Add Windows-aware default paths when no config is present: - Add Windows-aware default paths when no config is present:
`~/BSManager/BSInstances` and `%LOCALAPPDATA%/plugin-helper`. `~/BSManager/BSInstances` and `%LOCALAPPDATA%/plugin-helper`.
+1
View File
@@ -10,5 +10,6 @@ dependencies = [
{ id = "songcore", constraint = ">=3.16.0" }, { id = "songcore", constraint = ">=3.16.0" },
{ id = "sirautil", constraint = ">=3.3.1" }, { id = "sirautil", constraint = ">=3.3.1" },
{ id = "system-io-compression", constraint = ">=4.6.0" }, { id = "system-io-compression", constraint = ">=4.6.0" },
{ id = "system-io-compression-filesystem", constraint = ">=4.7.3056" },
{ id = "bsipa", constraint = ">=4.3.7" }, { id = "bsipa", constraint = ">=4.3.7" },
] ]
+9 -5
View File
@@ -140,11 +140,12 @@ def build_bootstrap_command(
proton: Path | None = None, proton: Path | None = None,
native: bool | None = None, native: bool | None = None,
) -> list[str]: ) -> list[str]:
beat_saber_exe = ipa.with_name("Beat Saber.exe")
use_native = is_windows() if native is None else native use_native = is_windows() if native is None else native
if use_native: if use_native:
return [str(ipa), "-n"] return [str(ipa), str(beat_saber_exe), "-n"]
proton_path = proton or _default_proton() 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: def _terminate_process(process: subprocess.Popen[str]) -> None:
@@ -249,15 +250,18 @@ def run_bootstrap(
ipa = instance_path / "IPA.exe" ipa = instance_path / "IPA.exe"
if not ipa.is_file(): if not ipa.is_file():
raise FileNotFoundError(f"BSIPA archive did not install IPA.exe: {ipa}") 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) command = build_bootstrap_command(ipa, proton=proton, native=use_native)
if 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: else:
proton_path = proton or _default_proton() proton_path = proton or _default_proton()
if not proton_path.is_file(): if not proton_path.is_file():
raise FileNotFoundError(f"Proton executable not found: {proton_path}") 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( completed = _run_ipa(
command=command, command=command,
@@ -265,7 +269,7 @@ def run_bootstrap(
timeout_seconds=ipa_timeout_seconds, timeout_seconds=ipa_timeout_seconds,
native=use_native, 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) after = scan_bootstrap_files(instance_path)
delta = _files_delta(before, after) delta = _files_delta(before, after)
state: dict[str, Any] = { state: dict[str, Any] = {
+5 -2
View File
@@ -319,14 +319,17 @@ state_dir = "config-state"
def test_build_bootstrap_command_native(self) -> None: def test_build_bootstrap_command_native(self) -> None:
ipa = Path("C:/Games/Beat Saber/IPA.exe") 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: def test_build_bootstrap_command_proton(self) -> None:
ipa = Path("/tmp/1.44.1/IPA.exe") ipa = Path("/tmp/1.44.1/IPA.exe")
proton = Path("/tmp/proton") proton = Path("/tmp/proton")
self.assertEqual( self.assertEqual(
build_bootstrap_command(ipa, proton=proton, native=False), 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: def test_profile_config_resolves_windows_paths(self) -> None: