Skip to content

Fix Windows launch crash since v0.5: noexcept LocateFile (issue #58)#118

Merged
JRickey merged 1 commit intomainfrom
agent/issue-58-locate-noexcept
May 3, 2026
Merged

Fix Windows launch crash since v0.5: noexcept LocateFile (issue #58)#118
JRickey merged 1 commit intomainfrom
agent/issue-58-locate-noexcept

Conversation

@JRickey
Copy link
Copy Markdown
Owner

@JRickey JRickey commented May 3, 2026

Summary

  • Replace Ship::Context::LocateFileAcrossAppDirs at the three port call sites with a port-side PortLocateFile that uses the noexcept std::filesystem::exists(p, ec) overload — fixes immediate-launch crash on Windows reproduced across Win10 19042 (Immediately Crashing on v0.5 (Windows) #58, Girtana1), Win10 22H2 (Immediately Crashing on v0.5 (Windows) #58 comment, DanKorell), and Win11 (today's report).
  • Decode MSVC C++ throws (0xE06D7363) in the vectored handler so future bugs of this shape print a readable type name + what() instead of an opaque exception code.

Diagnosis

User crash logs (Win11, today) show a first-chance 0xC0000374 (STATUS_HEAP_CORRUPTION) immediately after the SSB64: locating f3d.o2r ... checkpoint, with the second-chance arriving as a 0xE06D7363 MSVC C++ throw. The only code between the checkpoint and the crash is Ship::Context::LocateFileAcrossAppDirs("f3d.o2r"), which uses the throwing overload of std::filesystem::exists.

Under NON_PORTABLE on Windows the first probed path is <%APPDATA%>\BattleShip\ (backslash-terminated from SDL_GetPrefPath) joined with a literal "/" — yielding mixed separators like C:\Users\u\AppData\Roaming\BattleShip\/f3d.o2r. On at least three different Windows builds, GetFileAttributesExW returns an error severe enough to throw filesystem_error, the unwind trips heap-corruption detection, and the process fast-fails before any user-level catch can run.

The fix:

  1. PortLocateFile (new helper) uses exists(p, ec)false is the right answer for any "does this file exist?" probing failure. It also probes the bundle directory via ssb64::RealAppBundlePath() (real exe dir on Windows portable-zip) instead of Ship::Context::GetAppBundlePath(), which under NON_PORTABLE returns the literal CMAKE_INSTALL_PREFIX ("BattleShip") and is wrong for the portable distribution. The same defensive shape is applied to the inline std::filesystem::exists on the BattleShip.o2r post-extract probe.

  2. Vectored handler now decodes the MSVC C++ throw ABI for code 0xE06D7363 — primary CatchableTypestd::type_info::name() and, for std::exception derivatives, what(). Logs cap at 8 per process. Still returns EXCEPTION_CONTINUE_SEARCH so SEH catch handlers run normally; this is purely diagnostic. what() decode is wrapped in __try/__except so the handler never triggers a secondary fault.

Backstory

This commit (42ad2d6) was authored on May 1 against #58 but was never opened as a PR — issue #58 was closed when the diagnostic-logging commit (edf1440) shipped in v0.5.2-beta, but the actual fix never landed. v0.5/0.6/0.7/0.7.1 all carry the same crash.

Test plan

  • macOS Debug build green (cherry-picked onto main, all 688 TUs compile + link).
  • Windows CI artifact boots without 0xC0000374.
  • Reporters confirm the launch crash is gone (Girtana1, DanKorell, today's reporter).
  • If the fix doesn't fully resolve it, the new C++ throw decoder in the vectored handler should log a precise next-step diagnosis on the next crash.

🤖 Generated with Claude Code

Win10 19042 crashed at launch with STATUS_HEAP_CORRUPTION the first
time PortInit called Ship::Context::LocateFileAcrossAppDirs (immediately
after "ControlDeck OK"). The v0.5.2-beta diagnostic log narrowed the
fault to a heap-manager exception fired during a C++ exception unwind,
which our vectored handler had been silently dropping under the
0xE06D7363 suppression — so the *triggering* throw was invisible.

Two fixes:

1. PortLocateFile (replaces Ship::Context::LocateFileAcrossAppDirs at
   the three call sites in port.cpp). LUS's version uses the throwing
   std::filesystem::exists(p) overload, which raises filesystem_error
   if the underlying GetFileAttributesExW returns anything beyond an
   ENOENT-equivalent. Under NON_PORTABLE on Windows the first probed
   path is "<%APPDATA%>\\BattleShip\\/<basename>" — SDL_GetPrefPath
   returns a backslash-terminated path, joined with a literal "/"
   yielding mixed separators that on at least Win10 20H2 are rejected
   with an error severe enough to throw. The unwind from the throw then
   trips the heap-corruption detector before any user-level catch can
   run, fast-failing the process. Switch to the noexcept exists(p, ec)
   overload — false is the right answer for any "does this file exist?"
   probing failure. Also probe via ssb64::RealAppBundlePath() instead
   of Ship::Context::GetAppBundlePath(), which under NON_PORTABLE
   returns the literal "BattleShip" (CMAKE_INSTALL_PREFIX) — wrong for
   the portable-zip distro the user has unzipped to anywhere.

   Same defensive shape applied to the in-line std::filesystem::exists
   on the BattleShip.o2r post-extract probe.

2. Vectored handler now decodes the MSVC C++ throw ABI for code
   0xE06D7363 — primary CatchableType -> std::type_info name and, for
   std::exception derivatives, what(). Logs to ssb64.log capped at 8
   per process (in case a hot path throws). Still returns
   EXCEPTION_CONTINUE_SEARCH so SEH catch handlers run normally; this
   is purely diagnostic. Cap is by InterlockedIncrement on a static
   LONG so multi-threaded throws stay bounded.

   what() decode is wrapped in __try/__except so we never trigger a
   secondary fault from inside a vectored handler.

The libultraship-side fix (mirror the noexcept overload in
Context::LocateFileAcrossAppDirs itself) is the right long-term
home for #1, but the libultraship fork is out of this session's
push scope; folding it into port.cpp avoids the race for now.
@JRickey JRickey merged commit 9056e53 into main May 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants