Skip to content

Commit 55011ff

Browse files
jessicaboggiaclaude
andcommitted
Fix silent-exit when frozen + show MessageBox on startup errors
The bundled gui.exe was exiting on startup because main() checked for cli.py next to gui.py — that file isn't shipped in the bundle (we ship xeno-cli.exe instead). With console=False, the stderr message was invisible, so the exe appeared to "just not run." * Replace the cli.py existence check with CLI_TARGET which resolves to xeno-cli.exe in frozen mode and cli.py from source. * Add a _fatal() helper that pops a Windows MessageBox in addition to printing — so any future startup failure (missing CLI sibling, port bind failure, unhandled exception) shows the user a real error instead of nothing. * Wrap main() in a try/except that routes unhandled exceptions through the same MessageBox path with a traceback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 6833374 commit 55011ff

2 files changed

Lines changed: 60 additions & 8 deletions

File tree

__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
"""Xenosaga III Python Extractor."""
2-
__version__ = "0.2.2"
2+
__version__ = "0.2.3"

gui.py

Lines changed: 59 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,34 @@
4040

4141
# When frozen by PyInstaller, sys.executable is gui.exe — it can't run cli.py.
4242
# We ship a sibling CLI binary built from the same spec; invoke it directly.
43-
if getattr(sys, "frozen", False):
43+
FROZEN = bool(getattr(sys, "frozen", False))
44+
if FROZEN:
4445
_cli_name = "xeno-cli.exe" if os.name == "nt" else "xeno-cli"
4546
_cli_exe = Path(sys.executable).resolve().parent / _cli_name
46-
CLI_ARGV: list[str] = [str(_cli_exe)] if _cli_exe.exists() else [PY, str(CLI)]
47+
CLI_ARGV: list[str] = [str(_cli_exe)]
48+
CLI_TARGET = _cli_exe
4749
else:
4850
CLI_ARGV = [PY, str(CLI)]
51+
CLI_TARGET = CLI
52+
53+
54+
def _fatal(title: str, body: str) -> None:
55+
"""Surface a startup error the user can actually see.
56+
57+
PyInstaller windowed builds have no console — print() goes to the
58+
void. On Windows we pop a MessageBox so the user sees *something*
59+
instead of an exe that "just doesn't run"."""
60+
msg = f"{title}\n\n{body}"
61+
print(f"[gui] FATAL: {msg}", file=sys.stderr)
62+
if os.name == "nt":
63+
try:
64+
import ctypes
65+
ctypes.windll.user32.MessageBoxW(
66+
0, body, f"Xenosaga III Extractor — {title}", 0x10
67+
)
68+
except Exception:
69+
pass
70+
sys.exit(2)
4971

5072

5173
# ---------------------------------------------------------------------------
@@ -1508,14 +1530,34 @@ def _free_port() -> int:
15081530

15091531

15101532
def main():
1511-
if not CLI.exists():
1512-
print(f"[gui] cli.py not found next to gui.py at {CLI}; aborting.", file=sys.stderr)
1513-
sys.exit(2)
1533+
if not CLI_TARGET.exists():
1534+
if FROZEN:
1535+
_fatal(
1536+
"CLI binary missing",
1537+
f"Expected {CLI_TARGET.name} next to gui.exe.\n\n"
1538+
"Re-extract the release zip — running gui.exe straight from "
1539+
"the zip preview will fail because Windows only extracts the "
1540+
"one file you click. Right-click the zip → Extract All…",
1541+
)
1542+
else:
1543+
_fatal(
1544+
"cli.py missing",
1545+
f"Expected cli.py next to gui.py at {CLI_TARGET}.",
1546+
)
15141547

15151548
port = int(os.environ.get("PORT") or _free_port())
15161549
url = f"http://localhost:{port}/"
15171550

1518-
server = ThreadingHTTPServer(("127.0.0.1", port), Handler)
1551+
try:
1552+
server = ThreadingHTTPServer(("127.0.0.1", port), Handler)
1553+
except OSError as exc:
1554+
_fatal(
1555+
"Could not start local server",
1556+
f"Failed to bind to 127.0.0.1:{port}{exc}.\n\n"
1557+
"Most likely something else is already using that port, or "
1558+
"Windows Firewall is blocking loopback for unsigned binaries.",
1559+
)
1560+
15191561
print(f"[gui] Xenosaga III Extractor GUI")
15201562
print(f"[gui] Open: {url}")
15211563
print(f"[gui] Press Ctrl+C to stop.")
@@ -1532,4 +1574,14 @@ def main():
15321574

15331575

15341576
if __name__ == "__main__":
1535-
main()
1577+
try:
1578+
main()
1579+
except SystemExit:
1580+
raise
1581+
except BaseException as exc:
1582+
import traceback
1583+
_fatal(
1584+
"Unexpected error during startup",
1585+
f"{type(exc).__name__}: {exc}\n\n"
1586+
f"Traceback:\n{traceback.format_exc()}",
1587+
)

0 commit comments

Comments
 (0)