Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
1057e44
Phase 5 Sprint 1: macOS CMake configuration with all dependencies
fbraz3 Feb 24, 2026
85e8371
Phase 5 Sprint 2: Platform-specific code fixes for macOS (4 days, 8 f…
fbraz3 Feb 24, 2026
31ff810
Phase 5 Sprint 2 finalization: Fix GLI/GLM, malloc.h, d3dx8 stub
fbraz3 Feb 24, 2026
78a636b
audit: Fix platform guards - use __APPLE__ not _WIN32 for macOS fixes
fbraz3 Feb 24, 2026
5685186
fix(macos): malloc.h guards, thread THREAD_ID, matrix3d D3D headers
fbraz3 Feb 24, 2026
7049acb
build: macOS port fixes - ccache, FREETYPE, file.h sync
fbraz3 Feb 24, 2026
b1f69b1
macOS port (Phase 5): fix compiler and linker errors, z_generals buil…
fbraz3 Feb 24, 2026
ff3d631
macOS port (Phase 5): add VS Code tasks and deploy/run scripts
fbraz3 Feb 24, 2026
c711409
macOS port (Phase 5): build DXVK 2.6 natively — libdxvk_d3d8.dylib
fbraz3 Feb 24, 2026
bddb771
macOS port: DXVK meson setup must pass -arch to Clang
fbraz3 Feb 24, 2026
d2cf01a
docs: Session 62 lesson — Meson Rosetta produces x86_64 on Apple Silicon
fbraz3 Feb 24, 2026
bcb1e2c
macOS port: deploy libdxvk_d3d9 alongside d3d8 (required dependency)
fbraz3 Feb 24, 2026
f8671ee
macOS DXVK Patch 6: getExePath() using _NSGetExecutablePath
fbraz3 Feb 24, 2026
95c7911
macOS DXVK Patch 7: Vulkan library loading + deploy Vulkan SDK libs
fbraz3 Feb 25, 2026
03d6c72
docs: Session 63 - DXVK Patch 7 Vulkan loader macOS
fbraz3 Feb 25, 2026
63dd042
macOS DXVK Patch 8: VK_KHR_portability_enumeration for MoltenVK
fbraz3 Feb 25, 2026
6f4e73d
docs: Session 63 cont. - DXVK Patch 8 MoltenVK portability enumeration
fbraz3 Feb 25, 2026
99b46db
macOS DXVK Patch 9: VK_KHR_portability_subset + core feature masking
fbraz3 Feb 25, 2026
91a72ea
docs: Session 63 - DXVK Patch 9 portability_subset device creation fix
fbraz3 Feb 25, 2026
0ef460b
docs: macOS build reproducibility - build script + updated instructions
fbraz3 Feb 25, 2026
14a8407
fix(dxvk-macos): Patch 10 - do not request robustness2 features on macOS
fbraz3 Feb 25, 2026
5b77219
docs: update MACOS_BUILD_INSTRUCTIONS for Patch 10 (robustness2)
fbraz3 Feb 25, 2026
01ab202
fix(macos): Session 67 - Architecture mismatch RESOLVED, game initial…
fbraz3 Feb 26, 2026
88db214
Merge branch 'main' of github.com:fbraz3/GeneralsX into macos-build
fbraz3 Feb 27, 2026
649b8f6
docs: Update macOS documentation to reflect Phase 1 completion and ac…
fbraz3 Feb 27, 2026
ef0bce8
@build: Add GitHub Actions macOS CI workflow with workflow_dispatch s…
fbraz3 Feb 27, 2026
bd4b3a2
fix: GitHub Actions macOS workflow — Apply 9 critical improvements
fbraz3 Feb 28, 2026
5f0a8d6
docs: Add before/after comparison for macOS workflow improvements
fbraz3 Feb 28, 2026
008f066
fix: Correct binary names in build-macos.yml workflow verification step
fbraz3 Feb 28, 2026
f12b71d
fix: GitHub Actions macOS workflow — Deploy bundle instead of bare bi…
fbraz3 Feb 28, 2026
bab3722
refactor: Move run.sh wrapper to scripts/run-bundled-game.sh asset (m…
fbraz3 Feb 28, 2026
26c19c6
Merge branch 'main' of github.com:fbraz3/GeneralsX into macos-build
fbraz3 Mar 2, 2026
97b5fad
build(macos): add bundle-macos-zh.sh to package GeneralsXZH + dylibs …
fbraz3 Mar 2, 2026
e640dd8
fix(linux): restore DXVK include paths and d3dx8 static build for Linux
fbraz3 Mar 3, 2026
62e65ae
bugfix: fix macOS CompatLib build -- windows_base.h not found on clea…
fbraz3 Mar 3, 2026
af555a5
Initial plan
Copilot Mar 3, 2026
bf54706
fix: address PR review comments on macOS build
Copilot Mar 3, 2026
964ec4f
Merge pull request #4 from fbraz3/copilot/sub-pr-3
fbraz3 Mar 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
- **Platform strategy**: Cross-platform port targeting Linux, macOS, and Windows under a single codebase: SDL3 (windowing/input) + DXVK (DX8 to Vulkan graphics) + OpenAL (audio) + 64-bit. Legacy VC6 + win32 builds remain as upstream baseline. Isolate platform code to `Core/GameEngineDevice/` and `Core/Libraries/Source/Platform/`.
- **Key entry points**: Game launchers in `GeneralsMD/Code/Main/WinMain.cpp` and `Generals/Code/Main/WinMain.cpp`. Renderer device setup in `Core/GameEngineDevice/Source/` (DX8 now; DXVK path follows fighter19 reference under `references/fighter19-dxvk-port/GeneralsMD/Code/GameEngineDevice/`).
- **Critical convention**: Every user-facing code change needs `// GeneralsX @keyword author DD/MM/YYYY Description` above it. Keywords: @bugfix/@feature/@performance/@refactor/@tweak/@build.
- **Build presets**: Legacy: `cmake --preset vc6` or `win32`. Cross-platform: `linux64-deploy` (primary), `macos-deploy` (TBD), `windows64-deploy` (TBD). Linux via Docker: `./scripts/docker-configure-linux.sh linux64-deploy` then `./scripts/docker-build-linux-zh.sh linux64-deploy`. MinGW cross: `./scripts/docker-build-mingw-zh.sh mingw-w64-i686`.
- **Build presets**: Legacy: `cmake --preset vc6` or `win32`. Cross-platform: `linux64-deploy` (primary Linux), `macos-vulkan` (macOS ARM64 Apple Silicon -- active), `windows64-deploy` (TBD). Linux via Docker: `./scripts/docker-configure-linux.sh linux64-deploy` then `./scripts/docker-build-linux-zh.sh linux64-deploy`. macOS native: `./scripts/build-macos-zh.sh`. MinGW cross: `./scripts/docker-build-mingw-zh.sh mingw-w64-i686`.
- **Testing hotspots**: Replay compatibility uses VC6 optimized builds with `RTS_BUILD_OPTION_DEBUG=OFF` and replays in `GeneralsReplays/`; run via `generalszh.exe -jobs 4 -headless -replay subfolder/*.rep`. Keep determinism—avoid logic changes when touching rendering/audio paths.
- **Platform isolation rules**: No platform-specific code inside gameplay (GameLogic). Use compile guards and device/platform layers. Keep DX8/Miles path working for VC6; add DXVK/OpenAL behind feature flags. SDL3 is the unified platform layer — no native POSIX, Win32, or Cocoa calls in game code.
- **Reference guides**: DXVK patterns in `references/fighter19-dxvk-port/` (CMake presets, SDL3 hooks, device wrappers). OpenAL/Miles mapping ideas in `references/jmarshall-win64-modern/Code/Audio/` (Generals-only, adapt carefully for Zero Hour).
Expand Down
58 changes: 44 additions & 14 deletions .github/instructions/generalsx.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ We follow a **multi-phase research-first approach** with clear acceptance criter
- Native ELF binaries
- DXVK + SDL3 + OpenAL
- **`linux64-testing`** -- Linux debug variant
- **`macos-deploy`** -- macOS x86_64/arm64 -- **TBD**
- **`macos-vulkan`** -- macOS ARM64 (Apple Silicon) -- **ACTIVE**
- Native Mach-O binaries
- DXVK + MoltenVK + SDL3 + OpenAL
- macOS 15.0+ minimum; Universal binary (arm64 + x86_64) planned
- **`windows64-deploy`** -- Windows x86_64 (MSVC or MinGW) -- **TBD**
- **`mingw-w64-i686`** -- MinGW cross-compile (32-bit Windows .exe from Linux/macOS)

Expand Down Expand Up @@ -525,30 +528,57 @@ mkdir -p logs && gdb -batch -ex "run -win" -ex "bt full" -ex "thread apply all b

## macOS

**Branch**: TBD
**Status**: Not yet started
**Branch**: `main` (active development alongside Linux)
**Status**: Active development -- Phase 1 (Graphics/DXVK) complete on ARM64 Apple Silicon

### Architecture Plan
### Architecture
- SDL3 for windowing/input (same as Linux)
- DXVK + MoltenVK for DirectX 8 to Vulkan to Metal translation
- DXVK + MoltenVK for DirectX 8 to Vulkan to Metal translation (DX8 → Vulkan → Metal chain)
- OpenAL for audio (same as Linux)
- Universal binary target: x86_64 + arm64 (Apple Silicon)
- Current target: **ARM64 (Apple Silicon)** -- macOS 15.0+
- Universal binary (arm64 + x86_64) planned for future

### Key Considerations
- MoltenVK translates Vulkan to Metal; DXVK sits on top of it (DX8 to Vulkan to Metal chain)
- Code signing and notarization requirements for distribution
- DXVK is built via Meson as an ExternalProject -- must pass `-arch arm64` explicitly via `cmake/meson-arm64-native.ini` to avoid Rosetta2 confusing the host arch
- The full DXVK patch series (~13 patches) is applied automatically by `cmake/dxvk-macos-patches.py` -- do not apply manually
- Vulkan SDK **must** be installed from LunarG (not Homebrew) -- it provides the MoltenVK ICD JSON required to route Vulkan calls to Metal
- Code signing and notarization requirements for distribution (future)
- macOS-specific SDL3 quirks (Retina scaling, fullscreen spaces, etc.)
- Homebrew or vcpkg for dependency management

### Build Presets (Planned)
- **`macos-deploy`** -- Universal binary (x86_64 + arm64), Release
- **`macos-testing`** -- Debug variant
### Build Presets
- **`macos-vulkan`** -- macOS ARM64, RelWithDebInfo -- **PRIMARY MACOS TARGET**
- Native Mach-O binaries (`GeneralsXZH`)
- DXVK + MoltenVK + SDL3 + OpenAL + FFmpeg (video TBD)

### TBD Items
- [ ] MoltenVK integration tested with DXVK
- [ ] CMake preset created and tested
### Build Workflow
```bash
# Prerequisites (once): brew install cmake ninja meson
# + LunarG Vulkan SDK: https://vulkan.lunarg.com/sdk/home#mac
./scripts/build-macos-zh.sh # configure + build
./scripts/deploy-macos-zh.sh # copy to ~/GeneralsX/GeneralsMD/
./scripts/run-macos-zh.sh -win # launch windowed
```

### macOS-Specific Notes
- **Rosetta2 + Meson adversarial**: Use `cmake/meson-arm64-native.ini` to force `-arch arm64` in DXVK sub-build
- **DXVK patches are scripted**: `cmake/dxvk-macos-patches.py` applies all 13 MoltenVK/ARM64 compatibility patches automatically at configure time
- **SDL3 from source**: Fetched via CMake FetchContent -- no system package needed
- **Vulkan SDK path**: `~/VulkanSDK/<version>/macOS/` -- must contain `libvulkan.dylib` and `libMoltenVK.dylib`
- **No Cocoa/Metal calls in game code**: All platform access goes through SDL3 + DXVK layers

### Current Progress (as of February 2026)
- Phase 0 (Planning): Complete
- Phase 1 (Graphics/DXVK): Complete -- game renders and runs on ARM64 Apple Silicon
- Phase 2 (Audio/OpenAL): In progress (CMake flag `SAGE_USE_OPENAL=ON` set; full integration pending)
- Phase 3 (Video/FFmpeg): CMake flag `RTS_BUILD_OPTION_FFMPEG=ON` set; playback integration not started

### Remaining Items
- [ ] Audio (OpenAL) fully wired and tested
- [ ] Video (FFmpeg) playback integrated
- [ ] App bundle (.app) packaging
- [ ] Code signing workflow
- [ ] Universal binary (arm64 + x86_64)
- [ ] CI/CD pipeline for macOS builds

---
Expand Down
231 changes: 231 additions & 0 deletions .github/workflows/build-macos.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
name: Build macOS

permissions:
contents: read
pull-requests: read

on:
workflow_call:
inputs:
game:
required: true
type: string
description: "Game to build (GeneralsMD, Generals)"
preset:
required: false
type: string
default: "macos-vulkan"
description: "CMake preset for macOS"
workflow_dispatch:
inputs:
game:
type: choice
description: "Game to build"
options:
- GeneralsMD
- Generals
default: GeneralsMD
preset:
type: choice
description: "CMake preset for macOS"
options:
- macos-vulkan
default: macos-vulkan

jobs:
build:
name: ${{ inputs.game }}@${{ inputs.preset }}
runs-on: macos-latest
timeout-minutes: 120

steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Cache brew packages
id: cache-brew
uses: actions/cache@v4
with:
path: ~/Library/Caches/Homebrew
key: brew-macos-${{ runner.os }}-${{ hashFiles('.github/workflows/build-macos.yml') }}
restore-keys: |
brew-macos-${{ runner.os }}-

- name: Install Dependencies
run: |
echo "Installing macOS build dependencies..."
brew install cmake ninja meson
cmake --version
ninja --version
echo "✅ Build tools installed"

- name: Install Vulkan SDK
run: |
echo "Installing Vulkan SDK (required for DXVK + MoltenVK)..."

# Fallback 1: Direct download with retry
VULKAN_URL="https://sdk.lunarg.com/download/latest/mac/vulkan-sdk.dmg"
VULKAN_DMG="/tmp/vulkan-sdk.dmg"

if [ ! -d "$HOME/VulkanSDK" ]; then
for attempt in 1 2 3; do
echo "Download attempt $attempt..."
if curl -L --max-time 60 -o "$VULKAN_DMG" "$VULKAN_URL"; then
break
fi
if [ $attempt -lt 3 ]; then sleep 10; fi
done

if [ -f "$VULKAN_DMG" ]; then
echo "Mounting DMG..."
hdiutil attach "$VULKAN_DMG" -mountpoint /tmp/vulkan-mount
mkdir -p "$HOME/VulkanSDK"
cp -r /tmp/vulkan-mount/VulkanSDK/*macOS* "$HOME/VulkanSDK" || true
hdiutil detach /tmp/vulkan-mount
rm -f "$VULKAN_DMG"
else
echo "⚠️ Vulkan SDK download failed; build may fail if not using system Vulkan"
fi
fi

# Set environment variables for downstream steps
export VULKAN_SDK="$(find $HOME/VulkanSDK -name 'macOS*' -type d | head -1)"
if [ -z "$VULKAN_SDK" ] || [ ! -d "$VULKAN_SDK" ]; then
echo "⚠️ Vulkan SDK not found in expected location"
VULKAN_SDK="$HOME/VulkanSDK"
fi

echo "VULKAN_SDK=$VULKAN_SDK" >> $GITHUB_ENV
echo "✅ Vulkan SDK path: $VULKAN_SDK"

# Verify MoltenVK presence
if [ -f "$VULKAN_SDK/lib/libMoltenVK.dylib" ]; then
echo "✅ MoltenVK found at $VULKAN_SDK/lib/libMoltenVK.dylib"
else
echo "⚠️ MoltenVK not found; graphics may fail"
fi

- name: Configure CMake (macOS)
env:
VULKAN_SDK: ${{ env.VULKAN_SDK }}
run: |
mkdir -p logs
echo "VULKAN_SDK=$VULKAN_SDK"
cmake --preset ${{ inputs.preset }} 2>&1 | tee logs/configure_macos.log
CONFIG_STATUS=${PIPESTATUS[0]}
if [ $CONFIG_STATUS -eq 0 ]; then echo "✅ CMake configuration complete"; else echo "❌ CMake configuration failed"; exit 1; fi

- name: Build ${{ inputs.game }}
run: |
mkdir -p logs
NPROC=$(sysctl -n hw.ncpu)
echo "Building with $NPROC parallel jobs..."

if [ "${{ inputs.game }}" = "GeneralsMD" ]; then
cmake --build build/${{ inputs.preset }} --target z_generals -j $NPROC 2>&1 | tee logs/build_zh_macos.log
BUILD_STATUS=${PIPESTATUS[0]}
LOG_FILE="logs/build_zh_macos.log"
else
cmake --build build/${{ inputs.preset }} --target g_generals -j $NPROC 2>&1 | tee logs/build_generals_macos.log
BUILD_STATUS=${PIPESTATUS[0]}
LOG_FILE="logs/build_generals_macos.log"
fi

if [ $BUILD_STATUS -eq 0 ]; then
echo "✅ Build completed successfully"
else
echo "❌ Build failed; see $LOG_FILE for details"
tail -100 "$LOG_FILE"
exit 1
fi

- name: Verify Build Artifacts
run: |
if [ "${{ inputs.game }}" = "GeneralsMD" ]; then
BINARY="build/${{ inputs.preset }}/GeneralsMD/GeneralsXZH"
DISPLAY_NAME="GeneralsXZH"
GAME_DIR="GeneralsMD"
else
BINARY="build/${{ inputs.preset }}/Generals/GeneralsX"
DISPLAY_NAME="Generals"
GAME_DIR="Generals"
fi

if [ -f "$BINARY" ]; then
echo "✅ Binary built: $DISPLAY_NAME"
file "$BINARY"
ls -lh "$BINARY"

# Verify it's a valid Mach-O binary
if file "$BINARY" | grep -q "Mach-O"; then
echo "✅ Valid Mach-O binary"
else
echo "❌ File exists but is not a valid Mach-O binary"
exit 1
fi
else
echo "❌ Binary NOT found: $BINARY"
echo "Checking build directory:"
ls -la build/${{ inputs.preset }}/GeneralsMD/ 2>/dev/null || ls -la build/${{ inputs.preset }}/Generals/ 2>/dev/null || echo "Build directories not found"
exit 1
fi

- name: Deploy Bundle (Binary + Libraries + Wrapper)
if: success()
run: |
if [ "${{ inputs.game }}" = "GeneralsMD" ]; then
BINARY="build/${{ inputs.preset }}/GeneralsMD/GeneralsXZH"
GAME_DIR="GeneralsMD"
else
BINARY="build/${{ inputs.preset }}/Generals/GeneralsX"
GAME_DIR="Generals"
fi

RUNTIME_DIR="/tmp/GeneralsX-${{ inputs.game }}-macos-deploy"
mkdir -p "${RUNTIME_DIR}"

echo "Creating deployment bundle at: ${RUNTIME_DIR}"
echo " Copying executable..."
cp -v "${BINARY}" "${RUNTIME_DIR}/$(basename ${BINARY})"
chmod +x "${RUNTIME_DIR}/$(basename ${BINARY})"

echo " Copying framework libraries..."
mkdir -p "${RUNTIME_DIR}/lib"

# DXVK dylibs
find "build/${{ inputs.preset }}/_deps" -name "*.dylib" -o -name "*.dylib.so" 2>/dev/null | while read f; do
[ -f "$f" ] && cp -v "$f" "${RUNTIME_DIR}/lib/" || true
done

# MoltenVK dylib from Vulkan SDK
if [ -f "${{ env.VULKAN_SDK }}/lib/libMoltenVK.dylib" ]; then
echo " Copying MoltenVK..."
cp -v "${{ env.VULKAN_SDK }}/lib/libMoltenVK.dylib" "${RUNTIME_DIR}/lib/" || true
fi

echo " Copying run.sh wrapper from scripts..."
cp -v "scripts/run-bundled-game.sh" "${RUNTIME_DIR}/run.sh"
chmod +x "${RUNTIME_DIR}/run.sh"

echo "✅ Bundle ready at: ${RUNTIME_DIR}"
ls -lh "${RUNTIME_DIR}/"
ls -lh "${RUNTIME_DIR}/lib/" 2>/dev/null || echo "(no libs directory)"

- name: Upload Build Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: build-logs-macos-${{ inputs.game }}
path: logs/
retention-days: 7

- name: Upload Deployment Bundle
if: success()
uses: actions/upload-artifact@v4
with:
name: bundle-macos-${{ inputs.game }}-${{ inputs.preset }}
path: /tmp/GeneralsX-${{ inputs.game }}-macos-deploy/
retention-days: 7
if-no-files-found: warn
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,24 @@ jobs:
preset: ${{ matrix.preset }}
secrets: inherit

build-generalsmd-macos:
name: Build GeneralsMD@macOS
needs: detect-changes
if: ${{ github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.generalsmd == 'true' || needs.detect-changes.outputs.shared == 'true' }}
uses: ./.github/workflows/build-macos.yml
with:
game: "GeneralsMD"
preset: "macos-vulkan"
secrets: inherit

build-generals-macos:
name: Build Generals@macOS
needs: detect-changes
if: ${{ github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.generals == 'true' || needs.detect-changes.outputs.shared == 'true' }}
uses: ./.github/workflows/build-macos.yml
with:
game: "Generals"
preset: "macos-vulkan"
build-generalsmd-linux:
name: Build GeneralsMD@Linux
needs: detect-changes
Expand Down
Loading