Skip to content

[macOS] Bundle app artifacts with runtime data discovery#2090

Open
TmLev wants to merge 3 commits into
OpenXRay:devfrom
TmLev:tmlev/1780-macos-app-bundle-v2
Open

[macOS] Bundle app artifacts with runtime data discovery#2090
TmLev wants to merge 3 commits into
OpenXRay:devfrom
TmLev:tmlev/1780-macos-app-bundle-v2

Conversation

@TmLev
Copy link
Copy Markdown
Contributor

@TmLev TmLev commented May 1, 2026

Description

This adds macOS .app bundle packaging for OpenXRay and CI artifact generation for Release macOS builds.

The bundle includes the engine executable, bundled dynamic libraries, open-source engine resources from res/, and a small startup resolver for proprietary game data. On first launch, the app tries to discover existing game data locations, offers the user a native macOS folder selection flow when needed, and prepares the expected runtime layout under Application Support using symlinks.

Should resolve #1780.
Supersedes #2050.

What changed

  • Added misc/macos/make_app_bundle.sh to create:
    • OpenXRay.app
    • openxray-<config>-<arch>.app.zip
    • openxray-<config>-<arch>.dmg
  • Bundles non-system dylibs with dylibbundler.
  • Fixes duplicate LC_RPATH entries and ad-hoc signs the app bundle.
  • Adds a DMG layout with OpenXRay.app and an Applications symlink.
  • Adds macOS CI packaging for Release builds and uploads app bundle artifacts.
  • Adds macOS startup gamedata discovery/selection.
  • Supports common proprietary data locations, including:
    • SDL/Application Support path
    • $HOME/.local/share/GSC Game World/...
    • common Steam locations
    • common GOG/Applications locations
    • app-neighbor layout
  • Lets users choose another gamedata directory even when autodiscovery finds candidates.
  • Persists the selected gamedata path for later launches.
  • Skips the resolver when -fsltx is provided.

Runtime fixes found during validation

While testing the packaged app, two startup issues were found and fixed.
More on this down below.

Validation

Tested locally with:

cmake -S . -B build-local
cmake --build build-local --target xrEngine --parallel 4
cmake --build build-local --target xrRender_GL xrGame --parallel 4
bash -n misc/macos/make_app_bundle.sh
OPENXRAY_SKIP_DMG=1 misc/macos/make_app_bundle.sh arm64 Release
misc/macos/make_app_bundle.sh arm64 Release
codesign --verify --deep --strict --verbose=2 build/artifacts/OpenXRay.app

Also manually verified that the packaged app launches, reaches the menu, starts a new game, plays/skips the intro, and enters gameplay.

macOS Gatekeeper note

The generated .app is ad-hoc signed so bundled libraries and modified install names/rpaths validate locally, but it is not Developer ID signed or notarized.

Because CI artifacts are downloaded from the browser and keep the com.apple.quarantine attribute, macOS Gatekeeper may block the app with:

Apple could not verify “OpenXRay.app” is free of malware that may harm your Mac or compromise your privacy.

For now, testers can remove quarantine manually:

xattr -d com.apple.quarantine openxray-Release-arm64.dmg
open openxray-Release-arm64.dmg

or after copying the app:

cp -R "/Volumes/OpenXRay Release arm64/OpenXRay.app" /Applications/
xattr -dr com.apple.quarantine /Applications/OpenXRay.app
open /Applications/OpenXRay.app

Developer ID signing and notarization are intentionally left for a follow-up because they require Apple Developer credentials and CI secrets.

Suggested next steps on signing:

  1. Merge this PR as the unsigned/ad-hoc signed CI artifact baseline.
  2. Open a follow-up issue/PR for Developer ID signing and notarization.
  3. Decide where signing credentials should live: OpenXRay org Apple Developer account, maintainer-owned account, or release-only manual notarization.
  4. Add CI support for signing/notarization only when secrets are present, keeping PR builds ad-hoc signed.
  5. Optionally add a short README or release note for macOS users explaining the quarantine workaround until notarization exists.

Let me know what you think is best here.

Notes on xrRenderGL and xrGame changes

This PR also contains two runtime fixes found while validating the packaged macOS app:

src/Layers/xrRenderGL/glSH_Texture.cpp
src/xrGame/alife_object.cpp

Issue 1: new game appeared to hang during loading

After the app bundle could launch, show title videos, and reach the main menu, starting a new game did not reliably enter gameplay. It looked like the game was stuck on the loading screen.

Debugging showed the process was not blocked in the bundle/data-path setup. Sampling the process pointed at a stack protector failure around CSE_ALifeObject::spawn_supplies, which runs during ALife object creation while loading a level.

The affected code parsed spawn/loadout config strings with _GetItem(...) into fixed-size local buffers:

string64 tmp;
string128 tmp;
string64 buf;

Those buffers are too small for some real gamedata values. _GetItem writes the parsed token into the provided output buffer, so long spawn options can overwrite the stack. On macOS Release builds this surfaced as __stack_chk_fail, which from the user side looked like loading never completed.

The fix changes those temporary buffers to xr_string in the three parsing sites:

xr_string tmp;
xr_string buf;

This keeps the parsing behavior the same but removes the arbitrary stack-buffer limit. It is intentionally narrow: no spawn logic, probabilities, addon handling, ammo selection, or ALife behavior was changed.

Issue 2: intro cutscene had audio but rendered solid green

After the loading issue was fixed, starting a new game reached the intro sequence, but the video frame was solid green while audio played correctly. Pressing Escape skipped the video and gameplay worked, so the failure was isolated to video texture setup/update rather than game state or level loading.

The log showed:

Invalid video stream: 0x502

0x502 is GL_INVALID_OPERATION. The error was observed in the OpenGL Theora/OGM texture path in CTexture::Load().

The previous OGM path allocated immutable storage with:

glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, _w, _h);

Then video frames are uploaded later with:

glTexSubImage2D(..., GL_BGRA, GL_UNSIGNED_BYTE, ...);

For this use case the texture is dynamic single-level video storage, updated through a PBO each frame. Immutable storage is not needed here, and on macOS OpenGL this path was producing an invalid video texture state. There was also a second problem: the code checked glGetError() at the end of video texture creation, so a stale pending GL error from earlier texture work could falsely invalidate the video stream.

The fix does two things in the OGM path only:

  1. Clears pending GL errors immediately before the video texture allocation block.
  2. Uses mutable single-level texture allocation with explicit level bounds:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _w, _h, 0, GL_BGRA, GL_UNSIGNED_BYTE, nullptr);

This matches the later frame upload format and avoids requiring mip levels. The texture still stores as GL_RGBA8; GL_BGRA is only the external upload format. The Windows AVI path was left unchanged.

Why these issue fixes are in this PR

These fixes are not packaging mechanics, but they are required for the packaged macOS app to be practically usable – without them the user can launch the bundle, but new-game startup either fails during loading or shows a broken intro video.

Copy link
Copy Markdown
Member

@Xottab-DUTY Xottab-DUTY left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

[Feature Request] [macOS] Improved support for app bundles on Mac

2 participants