• src/doors/syncconquer/CMakeLists.txt PROVENANCE.md src/doors/syncconqu

    From Rob Swindell (on Windows 11)@VERT to Git commit to main/sbbs/master on Fri Jul 10 03:28:50 2026
    https://gitlab.synchro.net/main/sbbs/-/commit/37de0f10359177f53b344452
    Modified Files:
    src/doors/syncconquer/CMakeLists.txt PROVENANCE.md src/doors/syncconquer/door/door_io.c src/doors/syncconquer/vanilla/common/utfargs.h src/doors/syncconquer/vanilla/redalert/startup.cpp
    Log Message:
    syncalert: fix four Windows-only defects that broke the door end to end

    The door's first-ever Windows build surfaced four bugs, each invisible on
    *nix. Two are in our code, two in the vendored engine's
    Windows-without-SDL path -- which upstream never compiles, because its
    Windows build always defines SDL_BUILD. All four date from f5578ea57f (foam-43-clip), where the door landed and was only ever built for *nix.

    1. setvbuf(stderr, NULL, _IOLBF, 0) fast-failed the process before it
    could log a thing: MSVC's CRT requires 2 <= size <= INT_MAX for
    _IOLBF/_IOFBF, so size 0 is an invalid parameter and, with no handler
    installed, __fastfail()s (c0000409, FAST_FAIL_INVALID_ARG, straight
    through ucrtbase!_invoke_watson). glibc ignores the size. Launching
    the door dumped the caller back to the BBS with a zero-byte node log.
    Use _IONBF on Windows: valid with size 0, and it flushes every write
    -- the crash-durability the comment promises, which _IOLBF would not
    have delivered there anyway (that CRT silently treats it as _IOFBF).
    syncduke fixed the identical fast-fail in 705a840fe9 (panel-11-mind);
    syncconquer had copied the pre-fix idiom.

    2. Winsock was only ever initialized from door_early_write(), which runs
    on failure paths (the missing-assets splash). syncduke gets away with
    the same placement because its constructor calls its splash
    unconditionally. Here, a healthy launch never called WSAStartup() and
    died on its first recv() with WSANOTINITIALISED (10093), which the
    pump reads as a client hangup -- again dropping the user back to the
    BBS. Hoist it into an idempotent door_wsa_ensure() called from the
    pre-main constructor. xpdev's sockwrap does not self-init Winsock.

    3. redalert/startup.cpp's `UtfArgs args(argc, argv)` replaces ArgV with a
    fresh CommandLineToArgvU(GetCommandLineW()) parse, discarding the argv
    door_sanitize_argv() rewrote in the constructor. So -NOMOVIES never
    reached the engine (the intro and mission FMV played, with music but
    no dialog audio -- vqaaudio_null.cpp stubs the VQA player's audio
    hooks), and the door's own -s<fd>/-home tokens reached
    Parse_Command_Line(), whose strstr() substring scan over every token
    can mis-set an engine option from an unrelated path. utfargs.h now
    honors SYNCALERT_KEEP_CRT_ARGV, defined for VanillaRA only.
    UNICODE/_UNICODE stay on, so common/'s UTF-8 path handling is
    untouched; only the argv re-derivation is skipped.

    4. "Exit Game" hung forever. main() sets ReadyToQuit = 1, posts
    WM_DESTROY to MainWindow, then spins on Keyboard->Check() until
    winstub.cpp's window procedure advances it. The headless door has no
    window (MainWindow is NULL) and no message pump, so nothing ever did:
    the process stayed alive burning CPU in door_io_pump()'s recv(), the
    terminal kept looping its music, and no frames were drawn.
    Emergency_Exit()'s ReadyToQuit == 3 spin would hang the same way. Both
    are now gated on !NEW_VIDEO_BUILD, which upstream sets for exactly the
    backends (SDL1/SDL2) that have no legacy WndProc -- a no-op for every
    upstream configuration.

    Also close the door's own console window on Windows. Synchronet spawns a
    native door with CREATE_NEW_CONSOLE, so a console pops up on the BBS
    machine each session; the door draws to the client's terminal and logs to data/syncalert/syncalert_n<node>.log, so it is pure noise. Detach only
    when we own the console exclusively (GetConsoleProcessList() == 1) -- a developer running the door from a shell shares that shell's console and
    must not lose stdout -- and only with a door socket. FreeConsole()
    invalidates all three std streams even when stderr already points at a
    file, so it runs BEFORE the node log is opened and re-points every stream
    at NUL (the engine still printf()s on some paths).

    Vendored patches 10 and 11 recorded in PROVENANCE.md.

    Verified on Windows (VS2022, Win32/Release) and by Rob against the live
    BBS: the door launches, plays with audio, shows no movies, and exits
    cleanly. The engine-argv fix confirmed under cdb (argv[1] "-s999" -> "-NOMOVIES"; bNoMovies 00 -> 01 across Parse_Command_Line); the console
    detach confirmed against a CREATE_NEW_CONSOLE / CREATE_NO_WINDOW
    launcher, with the node log intact in both. Not compiled by GCC/Clang.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net