Skip to content

build(macos): native Apple Silicon arm64 build#216

Open
blackden wants to merge 6 commits into
wolfetplayer:mainfrom
blackden:macos-arm64
Open

build(macos): native Apple Silicon arm64 build#216
blackden wants to merge 6 commits into
wolfetplayer:mainfrom
blackden:macos-arm64

Conversation

@blackden

@blackden blackden commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Three minimal patches that let RealRTCW build and run natively on
Apple Silicon (M-series, macOS 11+). The bundled make-macosx-ub.sh
in this repo still targets gcc-4.0 and the 10.5/10.6 SDKs, so there
was no working path from a current macOS install — this PR provides
one with make ARCH=arm64 USE_INTERNAL_LIBS=0.

  • Makefile: add ffmpeg via pkg-config to the darwin block.
    Upstream only wires libavcodec/libavformat/libavutil/libswscale/ libswresample flags into the Linux block, so on macOS cl_cin.c
    fails with 'libavcodec/avcodec.h' file not found even with ffmpeg
    installed.

  • Makefile: replace the darwin SDL block. The bundled
    code/libs/macosx/libSDL2-2.0.0.dylib predates the SDL3 API
    migration, so -framework SDL2 no longer resolves symbols like
    SDL_PutAudioStreamData, SDL_UpdateGamepads,
    SDL_SetAudioStreamGain. Now linked against Homebrew SDL3 via
    pkg-config sdl3.

  • code/game/g_main.c: fix #include "../../steam/steam.h"
    "../steam/steam.h". Every other file in code/game/ already
    uses the correct relative path; this is a stray typo that happens
    to compile under the Windows toolchain but not on a stricter
    clang.

Also adds HOWTO-Build-macOS.md with the full recipe and
scripts/mac/install-rtcw-steamcmd.sh, a small helper that uses
steamcmd (Windows depot) to fetch the RTCW data files and symlink
them into the engine's homepath. No engine, renderer, or platform
code is touched.

USE_INTERNAL_LIBS=0 is load-bearing because clang 21 (the stock
CLT compiler on recent macOS releases) rejects the vendored
zlib-1.2.11 and freetype-2.9 under C23 strictness. This matches
the approach the MacSourcePorts iortcw fork already uses for the
shipped iowolfsp.app.

Test plan

  • brew install pkgconf sdl3 ffmpeg freetype jpeg opus opusfile libogg libvorbis
  • make ARCH=arm64 USE_INTERNAL_LIBS=0 succeeds on macOS 26.5.1 / Apple Silicon
  • RealRTCW.arm64 is Mach-O arm64; qagame/cgame/ui.sp.arm64.dylib likewise
  • Engine launches via SDL3 cocoa driver, opens fullscreen window, initialises Apple GL 2.1 / Metal renderer
  • FS_Startup loads pak0.pk3, sp_pak1..4.pk3, and the RealRTCW mod paks
  • realrtcwdefault.cfg is read; no Sys_Error
  • Main menu interactive; New Game starts the RealRTCW campaign and is playable

Not yet covered (would be a follow-up): Universal 2 (Intel slice),
renderer2 build on macOS, code signing / notarisation,
.dmg-bundled release.

🤖 Generated with Claude Code

blackden and others added 6 commits June 5, 2026 18:39
Three minimal patches to get RealRTCW building and launching natively
on macOS Tahoe (clang 21 / Apple Silicon):

* Makefile: add ffmpeg pkg-config to the darwin section. Upstream only
  wires libavcodec/libavformat/libavutil/libswscale/libswresample into
  the Linux block; on macOS cl_cin.c failed with "libavcodec/avcodec.h
  file not found" because pkg-config silently returned empty.

* Makefile: replace the darwin SDL block. The bundled
  code/libs/macosx/libSDL2-2.0.0.dylib is stale (predates the upstream
  SDL3 API migration), and -framework SDL2 fails to resolve
  SDL_PutAudioStreamData, SDL_UpdateGamepads, SDL_SetAudioStreamGain,
  etc. Now linked against Homebrew SDL3 via pkg-config (sdl3).

* code/game/g_main.c: fix include path "../../steam/steam.h" ->
  "../steam/steam.h". All other 8 files in code/game/ already use the
  correct relative path; this was a stray typo that happened to compile
  under upstream's Windows toolchain but not on a stricter macOS clang.

Recipe (macOS arm64):
  brew install pkgconf sdl3 ffmpeg opusfile freetype jpeg opus libogg libvorbis
  make ARCH=arm64 USE_INTERNAL_LIBS=0

Outputs RealRTCW.arm64 plus arm64 .dylib game modules.
Vendored zlib 1.2.11 and freetype 2.9 break under clang 21 C23 strictness,
so they are bypassed via USE_INTERNAL_LIBS=0 in favour of system zlib and
Homebrew freetype - matching the MacSourcePorts/iortcw approach.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
HOWTO-Build-macOS.md walks through the full path from a stock macOS
26.x / Apple Silicon machine to a running native arm64 binary:

  brew install pkgconf sdl3 ffmpeg freetype jpeg opus opusfile libogg libvorbis
  make ARCH=arm64 USE_INTERNAL_LIBS=0
  ./build/release-darwin-arm64-nosteam/RealRTCW.arm64

It also documents what the three macOS patches (ffmpeg pkg-config,
SDL3 link, g_main.c include typo) are actually doing, so the diff is
intelligible to anyone reviewing or upstreaming it.

scripts/mac/install-rtcw-steamcmd.sh fetches Return to Castle
Wolfenstein (AppID 9010) via steamcmd against the Windows depot,
since RTCW ships no macOS depot on Steam, then symlinks its .pk3
files into ~/Library/Application Support/RealRTCW/main (the homepath
that FS_Startup checks first). Pair it with a regular
'steamcmd +app_update 1379630' for the RealRTCW mod data itself.

Tested end-to-end on macOS 26.5.1 / M-series: SDL3 + Cocoa, Apple GL
2.1-on-Metal renderer, RealRTCW 5.4 main menu and gameplay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CG_CheckAmmo iterated weapon enums up to WP_NUM_WEAPONS (57 in
RealRTCW) but tested membership with `weapons[0] & (1 << i)`. Two
independent bugs in that one line:

* For i >= 31, `1 << i` is signed-int undefined behaviour on a 32-bit
  int (and trapped by UBSan).
* Even where the shift accidentally produces the "right" value,
  weapons[0] only holds slots 0-31. Slots 32+ live in weapons[1], so
  every weapon with enum >= 32 (WP_VENOM and onwards: MP44, FG42, BAR,
  M97, Auto-5, Browning, MG42M, all explosives, all alt-fire variants,
  monster attacks) was silently excluded from the low-ammo warning
  check.

Replaced with the engine-provided helper COM_BitCheck (q_shared.c:188),
which already handles the multi-int bitfield correctly and is used at
dozens of other call sites in cgame/, game/, ai_cast/ etc. for the same
weapons[] array.

Found by cppcheck (shiftTooManyBits + integerOverflow at cg_playerstate.c:62).
…stack pointer

Item_Text_Paint() reads ui_savegameInfo cvar into a local stack buffer
and writes its address into item->text:

    char infostring[SAVE_INFOSTRING_LENGTH];
    ...
    item->text = &infostring[0];

The itemDef_t lives in the long-lived UI string arena, but infostring
dies the moment Item_Text_Paint returns. From that point until the next
frame's call rewrites item->text, the pointer is dangling. The very
next paint frame's Item_SetTextExtents() reads item->text before the
textSavegameInfo branch re-runs, so the read of indeterminate stack
contents happens every frame the savegame menu stays open.

Stash the original "savegameinfo" literal in a local on entry, replace
the four early returns (WINDOW_WRAPPED, WINDOW_AUTOWRAPPED, text+cvar
both NULL, empty textPtr) plus the natural fall-through with a single
goto cleanup that puts item->text back. savedTextValid guards against
mutating item->text when the savegame branch never fired.

Found by cppcheck (autoVariables at ui_shared.c:3453).
scripts/mac/playtest.sh wraps the arm64 release binary and:
  * redirects stdin to /dev/null so the Q3 engine's built-in TTY
    console doesn't steal keyboard input from the SDL window,
  * sets ASAN_OPTIONS / UBSAN_OPTIONS log paths under /tmp,
  * auto-detects sanitizer builds via otool -L,
  * prints the A1-A4 manual-verification checklist on exit.

Local dev convenience only; not wired into the build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The darwin block hardcoded -I/System/Library/Frameworks/OpenAL.framework/Headers,
but Apple removed those headers from the macOS SDK years ago, so
USE_OPENAL=1 hasn't built on any modern Xcode toolchain.

Replace the dead framework include with the pkg-config-populated
\$(OPENAL_CFLAGS). Brew's openal-soft is keg-only (it conflicts with
Apple's frozen OpenAL.framework dylib in /System), so naked
pkg-config can't find openal.pc; the new darwin-block probe asks
brew for the prefix and re-runs pkg-config with the keg's
pkgconfig dir on PKG_CONFIG_PATH. End result: \`make USE_OPENAL=1\`
Just Works on any standard Homebrew-equipped Mac with
\`brew install openal-soft pkgconf\`.

The existing -framework OpenAL fallback under USE_INTERNAL_LIBS=1
is preserved (it links the dylib, but headers still come from
brew via \$(OPENAL_CFLAGS) since the SDK headers are gone).

Tested: make ARCH=arm64 USE_INTERNAL_LIBS=0 USE_OPENAL=1 -j8 on
macOS 26 / Apple M1 with brew openal-soft 1.25.2, no env override.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant