From 382a9f5a78d9c3a1718fcec242155cc2bc6509a1 Mon Sep 17 00:00:00 2001 From: ragnar Date: Fri, 5 Jun 2026 18:39:13 +0300 Subject: [PATCH 1/6] build(macos): wire up native arm64 build on Apple Silicon 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) --- Makefile | 18 +++++++++++++++--- code/game/g_main.c | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 1dd8cee48..e2c855562 100644 --- a/Makefile +++ b/Makefile @@ -505,6 +505,12 @@ ifeq ($(PLATFORM),darwin) RENDERER_LIBS= OPTIMIZEVM = -O3 + # ffmpeg via Homebrew pkg-config (upstream Makefile only wires this up for Linux) + FFMPEG_CFLAGS := $(shell $(PKG_CONFIG) --silence-errors --cflags libavcodec libavformat libavutil libswscale libswresample) + FFMPEG_LIBS := $(shell $(PKG_CONFIG) --silence-errors --libs libavcodec libavformat libavutil libswscale libswresample) + BASE_CFLAGS += $(FFMPEG_CFLAGS) + LIBS += $(FFMPEG_LIBS) + # Default minimum Mac OS X version ifeq ($(MACOSX_VERSION_MIN),) MACOSX_VERSION_MIN=10.5 @@ -637,9 +643,15 @@ ifeq ($(PLATFORM),darwin) RENDERER_LIBS += $(LIBSDIR)/macosx/libSDL2-2.0.0.dylib CLIENT_EXTRA_FILES += $(LIBSDIR)/macosx/libSDL2-2.0.0.dylib else - BASE_CFLAGS += -I/Library/Frameworks/SDL2.framework/Headers - CLIENT_LIBS += -framework SDL2 - RENDERER_LIBS += -framework SDL2 + # macOS: link SDL3 from Homebrew (RealRTCW upstream migrated to SDL3 API; + # the bundled libSDL2-2.0.0.dylib in code/libs/macosx is stale and predates + # the migration, so the old -framework SDL2 fallback no longer satisfies + # symbols like SDL_PutAudioStreamData, SDL_UpdateGamepads, etc.). + SDL_CFLAGS ?= $(shell $(PKG_CONFIG) --silence-errors --cflags sdl3) + SDL_LIBS ?= $(shell $(PKG_CONFIG) --silence-errors --libs sdl3) + BASE_CFLAGS += $(SDL_CFLAGS) + CLIENT_LIBS += $(SDL_LIBS) + RENDERER_LIBS += $(SDL_LIBS) endif OPTIMIZE = $(OPTIMIZEVM) -ffast-math diff --git a/code/game/g_main.c b/code/game/g_main.c index d8e4481c6..bf10d4f38 100644 --- a/code/game/g_main.c +++ b/code/game/g_main.c @@ -31,7 +31,7 @@ If you have questions concerning this license or the applicable additional terms #include "g_local.h" #include "g_survival.h" -#include "../../steam/steam.h" +#include "../steam/steam.h" level_locals_t level; From 82d24523a8f685e580c6a01ddeeb5974e274bd79 Mon Sep 17 00:00:00 2001 From: ragnar Date: Fri, 5 Jun 2026 19:24:04 +0300 Subject: [PATCH 2/6] docs(macos): add HOWTO-Build-macOS.md and steamcmd helper 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) --- HOWTO-Build-macOS.md | 149 +++++++++++++++++++++++++++ scripts/mac/install-rtcw-steamcmd.sh | 82 +++++++++++++++ 2 files changed, 231 insertions(+) create mode 100644 HOWTO-Build-macOS.md create mode 100755 scripts/mac/install-rtcw-steamcmd.sh diff --git a/HOWTO-Build-macOS.md b/HOWTO-Build-macOS.md new file mode 100644 index 000000000..da2c0a0ab --- /dev/null +++ b/HOWTO-Build-macOS.md @@ -0,0 +1,149 @@ +# Build RealRTCW natively on macOS (Apple Silicon) + +A native arm64 build of RealRTCW for modern macOS. Tested on macOS 26.5 +(Tahoe) on M1 / M2 / M3; should also work on later Apple Silicon and on +recent Intel Macs (the same Homebrew toolchain handles both arches — only +the `ARCH=` flag changes). + +There is no Apple-Silicon build of RealRTCW on ModDB or the upstream +repository: the bundled `make-macosx-ub.sh` targets gcc-4.0 and the +10.5/10.6 SDKs and has not been updated for Apple Silicon. This file +documents the minimal set of changes needed to get a clean native build +on a current macOS install. + +## 1. Prerequisites + +Xcode is not required. The build only needs the Command Line Tools and a +handful of Homebrew libraries. + +```bash +xcode-select --install # if you don't already have CLT installed + +brew install pkgconf sdl3 ffmpeg freetype jpeg opus opusfile libogg libvorbis +``` + +(`pkgconf` provides the `pkg-config` binary the Makefile calls. `sdl3` is +required because RealRTCW upstream migrated from SDL2 to SDL3 — the +bundled `code/libs/macosx/libSDL2-2.0.0.dylib` predates that migration +and no longer satisfies symbols like `SDL_PutAudioStreamData` or +`SDL_UpdateGamepads`.) + +## 2. Build + +From the repo root: + +```bash +make ARCH=arm64 USE_INTERNAL_LIBS=0 +``` + +`USE_INTERNAL_LIBS=0` tells the Makefile to link against Homebrew's +zlib / freetype / jpeg / opus / ogg / vorbis instead of the in-tree +vendored copies. The vendored `zlib-1.2.11` and `freetype-2.9` were +written before C23 and no longer compile cleanly under clang 21 (the +stock CLT compiler in late-2025 macOS releases). This is also what +MacSourcePorts use for their iortcw build. + +For an Intel Mac, replace `ARCH=arm64` with `ARCH=x86_64`. + +The build deposits these artefacts under +`build/release-darwin--nosteam/`: + +* `RealRTCW.arm64` — main executable +* `renderer_sp_opengl1_arm64.dylib` — OpenGL 1 renderer (Apple's GL on + Apple Silicon is GL 2.1 backed by Metal, which is plenty for this + renderer) +* `main/qagame.sp.arm64.dylib` +* `main/cgame.sp.arm64.dylib` +* `main/ui.sp.arm64.dylib` + +You will see a few link-time warnings of the form: + +``` +ld: warning: building for macOS-11.0, but linking with dylib + '/opt/homebrew/opt/ffmpeg/lib/libavcodec.62.dylib' + which was built for newer version 26.0 +``` + +These are cosmetic — Homebrew ffmpeg is compiled against a newer minimum +SDK than RealRTCW's `-mmacosx-version-min=11.0`. Linker still resolves +all symbols. + +## 3. Game data + +RealRTCW needs both the original RTCW pak files **and** the RealRTCW mod +data (pak files, `realrtcwdefault.cfg`, cinematic videos). Both are +distributed via Steam: + +| Steam AppID | Title | Required for | +|-------------|-------|--------------| +| 9010 | Return to Castle Wolfenstein | `pak0.pk3`, `sp_pak1.pk3`, etc. | +| 1379630 | RealRTCW | mod paks, `realrtcwdefault.cfg`, `video/` | + +Both depots ship only Windows binaries on Steam; we force the Windows +platform on steamcmd. The .dll / .exe files inside those depots aren't +needed — we only consume their data files. + +You can use `scripts/mac/install-rtcw-steamcmd.sh` to fetch RTCW and +symlink its paks into the homepath the native build looks at. Then run +the same `steamcmd` for AppID 1379630 to fetch RealRTCW's mod data and +symlink the rest: + +```bash +brew install --cask steamcmd + +scripts/mac/install-rtcw-steamcmd.sh +# steamcmd will prompt for password + Steam Guard the first time. + +# Now grab the RealRTCW mod data (free Steam game; requires you to own RTCW). +mkdir -p ~/Games/RealRTCW-data +steamcmd +@sSteamCmdForcePlatformType windows \ + +force_install_dir ~/Games/RealRTCW-data \ + +login \ + +app_update 1379630 validate \ + +quit + +# Symlink RealRTCW mod paks, configs, and cinematic videos: +SRC=~/Games/RealRTCW-data/Main +DST="$HOME/Library/Application Support/RealRTCW/main" +mkdir -p "$DST" +for f in "$SRC"/*.pk3 "$SRC"/*.cfg; do ln -sfh "$f" "$DST/$(basename "$f")"; done +ln -sfh "$SRC/video" "$DST/video" +``` + +After this, `~/Library/Application Support/RealRTCW/main/` should +contain `pak0.pk3` + `sp_pak1..4.pk3` (vanilla RTCW), all +`z_realrtcw_*.pk3` (mod content), `realrtcwdefault.cfg`, `autoexec.cfg`, +and `video/` (cinematics). + +## 4. Run + +```bash +./build/release-darwin-arm64-nosteam/RealRTCW.arm64 +``` + +The engine opens a fullscreen SDL3/Cocoa window at native desktop +resolution, initialises Apple's GL-on-Metal driver, and drops you into +the RealRTCW main menu. + +## What the macOS patches actually do + +Three minimal changes against upstream: + +1. **`Makefile`** — wire `ffmpeg` into the `darwin` block via + `pkg-config`. Upstream only adds `libavcodec/libavformat/libavutil/ + libswscale/libswresample` flags in the Linux block, so on macOS + `cl_cin.c` failed with `'libavcodec/avcodec.h' file not found` even + with ffmpeg installed. + +2. **`Makefile`** — replace the darwin SDL block. The stale `-framework + SDL2` (and bundled `libSDL2-2.0.0.dylib`) cannot satisfy upstream's + new SDL3 audio/gamepad API calls. Now links Homebrew SDL3 via + `pkg-config sdl3`. + +3. **`code/game/g_main.c`** — fix a stray + `#include "../../steam/steam.h"` (every other file in `code/game/` + correctly uses `"../steam/steam.h"`). This is an upstream typo + that happens to compile under the maintainer's Windows toolchain. + +That is the complete patch set. No engine code, no platform layer, no +renderer code was changed. diff --git a/scripts/mac/install-rtcw-steamcmd.sh b/scripts/mac/install-rtcw-steamcmd.sh new file mode 100755 index 000000000..d4f842c67 --- /dev/null +++ b/scripts/mac/install-rtcw-steamcmd.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +# Install Return to Castle Wolfenstein on macOS via steamcmd, then link its +# pak files into RealRTCW's homepath so the native arm64 build can find them. +# +# RTCW on Steam ships only a Windows depot; we force the Windows platform on +# steamcmd to download it. We only need the pak0.pk3 / sp_pak*.pk3 data files, +# which are content-only and platform-agnostic. +set -euo pipefail + +STEAM_LOGIN="${STEAM_LOGIN:-${1:-}}" +INSTALL_DIR="${INSTALL_DIR:-${2:-$HOME/Games/RTCW}}" +APPID="${APPID:-9010}" # 9010 = Return to Castle Wolfenstein + +# Where the native macOS RealRTCW binary looks for `main/*.pk3`. See FS_Startup +# output: ~/Library/Application Support/RealRTCW/main is checked first. +HOMEPATH_MAIN="${HOMEPATH_MAIN:-$HOME/Library/Application Support/RealRTCW/main}" + +usage() { + cat <&2 +Usage: STEAM_LOGIN= [INSTALL_DIR=] $0 + or: $0 [] + +Environment variables: + STEAM_LOGIN Your Steam account name (you must own RTCW, appid 9010). + INSTALL_DIR Where steamcmd installs RTCW. Default: \$HOME/Games/RTCW. + HOMEPATH_MAIN Where to symlink the .pk3 files for RealRTCW to find them. + Default: \$HOME/Library/Application Support/RealRTCW/main. + +Notes: + * Steam Guard: steamcmd will prompt for the code on first login; + subsequent runs reuse the saved credentials. + * +@sSteamCmdForcePlatformType windows is mandatory and must come + before +login - RTCW has no native macOS depot on Steam. +USAGE +} + +if [[ -z "$STEAM_LOGIN" ]]; then + usage + exit 1 +fi + +if ! command -v steamcmd >/dev/null 2>&1; then + echo "ERROR: steamcmd not found in PATH." >&2 + echo " Install with: brew install --cask steamcmd" >&2 + exit 1 +fi + +mkdir -p "$INSTALL_DIR" + +echo "==> Installing appid $APPID into $INSTALL_DIR as Steam user '$STEAM_LOGIN'" + +steamcmd \ + +@sSteamCmdForcePlatformType windows \ + +force_install_dir "$INSTALL_DIR" \ + +login "$STEAM_LOGIN" \ + +app_update "$APPID" validate \ + +quit + +# RTCW's Steam depot lays out paks under /Main/ (capital M). +MAIN_SRC="$INSTALL_DIR/Main" +if [[ ! -d "$MAIN_SRC" ]]; then + echo "==> Install finished but $MAIN_SRC was not found. Layout may have changed." >&2 + exit 1 +fi + +if [[ ! -f "$MAIN_SRC/pak0.pk3" ]]; then + echo "==> $MAIN_SRC exists but pak0.pk3 is missing. Likely a partial install." >&2 + exit 1 +fi + +echo "==> Linking $MAIN_SRC/*.pk3 -> $HOMEPATH_MAIN/" +mkdir -p "$HOMEPATH_MAIN" +for pk3 in "$MAIN_SRC"/*.pk3; do + ln -sfh "$pk3" "$HOMEPATH_MAIN/$(basename "$pk3")" +done + +echo +echo "==> Done. RealRTCW will find these on next launch:" +ls -l "$HOMEPATH_MAIN" | sed 's/^/ /' +echo +echo "==> Run:" +echo " ./build/release-darwin-arm64-nosteam/RealRTCW.arm64" From 0221e87e18360f2c304e654c36eec3aeed9c38cf Mon Sep 17 00:00:00 2001 From: ragnar Date: Sat, 6 Jun 2026 02:27:43 +0300 Subject: [PATCH 3/6] fix(cgame): use COM_BitCheck in CG_CheckAmmo for high-enum weapons 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). --- code/cgame/cg_playerstate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/cgame/cg_playerstate.c b/code/cgame/cg_playerstate.c index 1c5d9a293..858af7d34 100644 --- a/code/cgame/cg_playerstate.c +++ b/code/cgame/cg_playerstate.c @@ -59,7 +59,7 @@ void CG_CheckAmmo( void ) { // first weap now WP_LUGER for ( i = WP_KNIFE ; i < WP_NUM_WEAPONS ; i++ ) { - if ( !( weapons[0] & ( 1 << i ) ) ) { + if ( !COM_BitCheck( weapons, i ) ) { continue; } if ( cg.snap->ps.ammo[BG_FindAmmoForWeapon( i )] < 0 ) { From c5558c7b07dd81aa3aeddb4ec1709cc445f97b30 Mon Sep 17 00:00:00 2001 From: ragnar Date: Sat, 6 Jun 2026 02:29:03 +0300 Subject: [PATCH 4/6] fix(ui): restore item->text after savegame-info paint to avoid stale 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). --- code/ui/ui_shared.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/code/ui/ui_shared.c b/code/ui/ui_shared.c index 354cf5260..c4b965f62 100644 --- a/code/ui/ui_shared.c +++ b/code/ui/ui_shared.c @@ -3445,27 +3445,34 @@ void Item_Text_Paint( itemDef_t *item ) { const char *textPtr; int height, width; vec4_t color; + const char *savedText = NULL; + qboolean savedTextValid = qfalse; //----(SA) added if ( item->textSavegameInfo ) { DC->getCVarString( "ui_savegameInfo", infostring, sizeof( infostring ) ); // grab the string the client set + // infostring is on this function's stack; stash the original + // item->text so the temporary override does not leak past return + // and produce a dangling pointer for the next frame. + savedText = item->text; + savedTextValid = qtrue; item->text = &infostring[0]; } //----(SA) end if ( item->window.flags & WINDOW_WRAPPED ) { Item_Text_Wrapped_Paint( item ); - return; + goto cleanup; } if ( item->window.flags & WINDOW_AUTOWRAPPED ) { Item_Text_AutoWrapped_Paint( item ); - return; + goto cleanup; } if ( item->text == NULL ) { if ( item->cvar == NULL ) { - return; + goto cleanup; } else { DC->getCVarString( item->cvar, text, sizeof( text ) ); textPtr = text; @@ -3478,7 +3485,7 @@ void Item_Text_Paint( itemDef_t *item ) { Item_SetTextExtents( item, &width, &height, textPtr ); if ( *textPtr == '\0' ) { - return; + goto cleanup; } @@ -3514,6 +3521,11 @@ void Item_Text_Paint( itemDef_t *item ) { // } DC->drawText( item->textRect.x, item->textRect.y, item->font, item->textscale, color, textPtr, 0, 0, item->textStyle ); + +cleanup: + if ( savedTextValid ) { + item->text = savedText; + } } From 31624c632961f4c79499c83b6d1878d322b214eb Mon Sep 17 00:00:00 2001 From: ragnar Date: Sun, 7 Jun 2026 01:56:19 +0300 Subject: [PATCH 5/6] chore(macos): add playtest.sh launcher with ASAN log capture 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 --- .gitignore | 3 ++ scripts/mac/playtest.sh | 112 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100755 scripts/mac/playtest.sh diff --git a/.gitignore b/.gitignore index ee529704a..a4b9fec53 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,6 @@ profile # vscode .vscode/ /.vs + +# Claude Code session/agent state (local only) +.claude/ diff --git a/scripts/mac/playtest.sh b/scripts/mac/playtest.sh new file mode 100755 index 000000000..9377a5f6b --- /dev/null +++ b/scripts/mac/playtest.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash +# RealRTCW playtest launcher with ASAN/UBSAN capture. +# +# Запускает текущий arm64 билд, ловит любые ASAN/UBSAN reports в /tmp, +# и в конце выводит чеклист, что проверять для 4 фиксов на этой ветке. +# +# Если билд собран с -fsanitize (наличие Makefile.local с -fsanitize=...), +# инструментация активна — играй спокойно, при любом UAF/UB будет вопль +# в stderr ИЛИ в /tmp/rtcw-asan..log. +# +# Если хочешь обычный (быстрый, без оверхеда) билд: +# rm Makefile.local +# make ARCH=arm64 USE_INTERNAL_LIBS=0 USE_OPENAL=0 -j8 +# +# Если хочешь снова ASAN — Makefile.local восстанавливается одной строкой: +# echo "BASE_CFLAGS += -fsanitize=address,undefined -fno-omit-frame-pointer -g -O1\nLDFLAGS += -fsanitize=address,undefined" > Makefile.local +# make clean && make ARCH=arm64 USE_INTERNAL_LIBS=0 USE_OPENAL=0 -j8 + +set -uo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +BIN="$REPO_ROOT/build/release-darwin-arm64-nosteam/RealRTCW.arm64" +ASAN_LOG_PREFIX="/tmp/rtcw-asan" +UBSAN_LOG_PREFIX="/tmp/rtcw-ubsan" + +if [ ! -x "$BIN" ]; then + echo "ERROR: бинарник не найден: $BIN" + echo " собери его: cd $REPO_ROOT && make ARCH=arm64 USE_INTERNAL_LIBS=0 USE_OPENAL=0 -j8" + exit 1 +fi + +# Чистим прошлые логи sanitizer'а — чтобы видеть свежие. +rm -f "${ASAN_LOG_PREFIX}".* "${UBSAN_LOG_PREFIX}".* 2>/dev/null + +# Определяем, есть ли ASAN в бинаре, чтобы предупредить +if otool -L "$BIN" 2>/dev/null | grep -q asan; then + echo "═══ RealRTCW ASAN/UBSAN build ═══" + echo "Sanitizer активен — будет медленнее обычного, но любой UAF/UB будет пойман." +else + echo "═══ RealRTCW release build (без sanitizer'а) ═══" +fi +echo "Лог-префиксы: ${ASAN_LOG_PREFIX}..log ${UBSAN_LOG_PREFIX}..log" +echo "Бинарник: $BIN" +echo "" + +export ASAN_OPTIONS="abort_on_error=0:detect_leaks=0:halt_on_error=0:log_path=${ASAN_LOG_PREFIX}" +export UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=0:log_path=${UBSAN_LOG_PREFIX}" + +# cd в каталог с бинарником, чтобы он нашёл renderer .dylib рядом. +cd "$(dirname "$BIN")" +# stdin → /dev/null: иначе Q3-движок включает встроенную TTY-консоль и +# крадёт клавиатуру у SDL-окна (символы дублируются в терминал). +"$BIN" "$@" < /dev/null +GAME_EXIT=$? + +echo "" +echo "═══ Игра вышла с кодом $GAME_EXIT ═══" + +ASAN_HITS=$(ls "${ASAN_LOG_PREFIX}".* 2>/dev/null | wc -l | tr -d ' ') +UBSAN_HITS=$(ls "${UBSAN_LOG_PREFIX}".* 2>/dev/null | wc -l | tr -d ' ') + +if [ "$ASAN_HITS" -gt 0 ] || [ "$UBSAN_HITS" -gt 0 ]; then + echo "" + echo "⚠ Sanitizer что-то нашёл:" + for f in ${ASAN_LOG_PREFIX}.* ${UBSAN_LOG_PREFIX}.*; do + [ -f "$f" ] || continue + echo "─── $f ───" + head -40 "$f" + echo "(см. полностью: cat $f)" + echo "" + done +else + echo "✓ Никаких ASAN/UBSAN репортов. Sanitizer молчал — это хорошо." +fi + +cat <<'EOF' + +═══ ЧЕКЛИСТ — что проверить для 4 фиксов ═══ + +A1 — low-ammo warning для поздних оружий (cg_playerstate.c) + • Новая игра / любой сохраняшка + • В консоли: \cheats 1; \giveall + • Возьми MP44 / MG42M / BAR / WP_VENOM / WP_PANZERFAUST + (любое оружие с enum >= 32 — см. bg_public.h:528-599) + • Постреляй до низкого боезапаса + • ОЖИДАЕМО: должен прозвучать warning beep + HUD ammo flash + • БЫЛО ДО ФИКСА: тишина — этот код пути для оружия >=32 был сломан + +A2 — savegame UAF (ui_shared.c) + • Запусти кампанию, дойди до места, сделай Quick Save (F5) + • ESC → Load Game + • Наведи курсор на сохраняшку + • ОЖИДАЕМО: справа корректная инфа — date, level, playtime + • БЫЛО ДО ФИКСА: каждый кадр после первого item->text dangling — мог + показываться garbage / crash на ASAN-сборке + +A3 — Com_StringContains size_t wrap (common.c) + • В игре, в консоли: \find этооченьдлиннаяподстрокачтобывсёполомать + • ОЖИДАЕМО: ничего не находит, не крашит, не подвисает + • БЫЛО ДО ФИКСА: на arm64 (64→32-bit truncation) — UB, потенциально + garbage iter / OOB read + +A4 — snd_mix NULL chunk wrap (snd_mix.c) + • Самый сложный для ручной проверки. + • В консоли: \s_useOpenAL 0; \snd_restart + (откатываемся на legacy SDL mixer — путь, где жил баг) + • Поиграй 60+ секунд, послушай звуки + • ОЖИДАЕМО: звук работает корректно, не крашит на длинных сэмплах + • БЫЛО ДО ФИКСА: NULL deref на границе chunk'а в SetVoiceAmplitudeFrom16 / + S_PaintChannelFrom16_scalar / S_PaintChannelFromMuLaw + +EOF From 3062d0e0fbd78af5bf45353eb90ebfdcf4c4434b Mon Sep 17 00:00:00 2001 From: ragnar Date: Sun, 7 Jun 2026 02:21:46 +0300 Subject: [PATCH 6/6] build(darwin): use Homebrew openal-soft via pkg-config for USE_OPENAL=1 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 --- Makefile | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index e2c855562..17b018105 100644 --- a/Makefile +++ b/Makefile @@ -601,12 +601,24 @@ ifeq ($(PLATFORM),darwin) BASE_CFLAGS += -fno-strict-aliasing -fno-common -pipe ifeq ($(USE_OPENAL),1) + # Modern macOS no longer ships OpenAL.framework headers in the SDK. + # Use Homebrew's openal-soft. It's a keg-only formula (Apple's frozen + # OpenAL.framework dylib still lives in /System, so brew refuses to + # symlink the replacement into /opt/homebrew); naked pkg-config won't + # find it. Probe brew for the prefix and re-run pkg-config with the + # keg's pkgconfig dir on PKG_CONFIG_PATH when OPENAL_CFLAGS is empty. + ifeq ($(strip $(OPENAL_CFLAGS)),) + BREW_OPENAL_PREFIX := $(shell brew --prefix openal-soft 2>/dev/null) + ifneq ($(BREW_OPENAL_PREFIX),) + OPENAL_CFLAGS := $(shell PKG_CONFIG_PATH=$(BREW_OPENAL_PREFIX)/lib/pkgconfig $(PKG_CONFIG) --silence-errors --cflags openal) + OPENAL_LIBS := $(shell PKG_CONFIG_PATH=$(BREW_OPENAL_PREFIX)/lib/pkgconfig $(PKG_CONFIG) --silence-errors --libs openal) + endif + endif ifneq ($(USE_LOCAL_HEADERS),1) - CLIENT_CFLAGS += -I/System/Library/Frameworks/OpenAL.framework/Headers + CLIENT_CFLAGS += $(OPENAL_CFLAGS) endif ifneq ($(USE_OPENAL_DLOPEN),1) ifneq ($(USE_INTERNAL_LIBS),1) - CLIENT_CFLAGS += $(OPENAL_CFLAGS) CLIENT_LIBS += $(THREAD_LIBS) $(OPENAL_LIBS) CLIENT_EXTRA_FILES += $(LIBSDIR)/macosx/libopenal.dylib else