Add macOS Support

This commit is contained in:
David Chavez
2025-02-25 16:54:25 +01:00
parent 91db87632c
commit 855ee68466
21 changed files with 594 additions and 65 deletions
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleExecutable</key>
<string>Zelda64Recompiled</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>LSMinimumSystemVersion</key>
<string>11</string>
<key>GCSupportsGameMode</key>
<true/>
</dict>
</plist>
+87
View File
@@ -0,0 +1,87 @@
# Define the path to the entitlements file
set(ENTITLEMENTS_FILE ${CMAKE_SOURCE_DIR}/.github/macos/entitlements.plist)
# Set bundle properties
set_target_properties(Zelda64Recompiled PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME "Zelda64Recompiled"
MACOSX_BUNDLE_GUI_IDENTIFIER "com.github.zelda64recompiled"
MACOSX_BUNDLE_BUNDLE_VERSION "1.0"
MACOSX_BUNDLE_SHORT_VERSION_STRING "1.0"
MACOSX_BUNDLE_ICON_FILE "AppIcon.icns"
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_BINARY_DIR}/Info.plist
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "-"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${ENTITLEMENTS_FILE}
)
# Create icon files for macOS bundle
set(ICON_SOURCE ${CMAKE_SOURCE_DIR}/icons/512.png)
set(ICONSET_DIR ${CMAKE_BINARY_DIR}/AppIcon.iconset)
set(ICNS_FILE ${CMAKE_BINARY_DIR}/resources/AppIcon.icns)
# Create iconset directory and add PNG file
add_custom_command(
OUTPUT ${ICONSET_DIR}
COMMAND ${CMAKE_COMMAND} -E make_directory ${ICONSET_DIR}
COMMAND ${CMAKE_COMMAND} -E copy ${ICON_SOURCE} ${ICONSET_DIR}/icon_512x512.png
COMMAND ${CMAKE_COMMAND} -E copy ${ICON_SOURCE} ${ICONSET_DIR}/icon_512x512@2x.png
COMMAND touch ${ICONSET_DIR}
COMMENT "Creating iconset directory and copying PNG file"
)
# Convert iconset to icns
add_custom_command(
OUTPUT ${ICNS_FILE}
DEPENDS ${ICONSET_DIR}
COMMAND iconutil -c icns ${ICONSET_DIR} -o ${ICNS_FILE}
COMMENT "Converting iconset to icns"
)
# Custom target to ensure icns creation
add_custom_target(create_icns ALL DEPENDS ${ICNS_FILE})
# Set source file properties for the resulting icns file
set_source_files_properties(${ICNS_FILE} PROPERTIES
MACOSX_PACKAGE_LOCATION "Resources"
)
# Add the icns file to the executable target
target_sources(Zelda64Recompiled PRIVATE ${ICNS_FILE})
# Ensure Zelda64Recompiled depends on create_icns
add_dependencies(Zelda64Recompiled create_icns)
# Configure Info.plist
configure_file(${CMAKE_SOURCE_DIR}/.github/macos/Info.plist.in ${CMAKE_BINARY_DIR}/Info.plist @ONLY)
# Install the app bundle
install(TARGETS Zelda64Recompiled BUNDLE DESTINATION .)
# Ensure the entitlements file exists
if(NOT EXISTS ${ENTITLEMENTS_FILE})
message(FATAL_ERROR "Entitlements file not found at ${ENTITLEMENTS_FILE}")
endif()
# Post-build steps for macOS bundle
add_custom_command(TARGET Zelda64Recompiled POST_BUILD
# Copy and fix frameworks first
COMMAND ${CMAKE_COMMAND} -D CMAKE_BUILD_TYPE=$<CONFIG> -D CMAKE_GENERATOR=${CMAKE_GENERATOR} -P ${CMAKE_SOURCE_DIR}/.github/macos/fixup_bundle.cmake
# Copy all resources
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets ${CMAKE_BINARY_DIR}/temp_assets
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/temp_assets/scss
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_BINARY_DIR}/temp_assets $<TARGET_BUNDLE_DIR:Zelda64Recompiled>/Contents/Resources/assets
COMMAND ${CMAKE_COMMAND} -E remove_directory ${CMAKE_BINARY_DIR}/temp_assets
# Copy controller database
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt $<TARGET_BUNDLE_DIR:Zelda64Recompiled>/Contents/Resources/
# Set RPATH
COMMAND install_name_tool -add_rpath "@executable_path/../Frameworks/" $<TARGET_BUNDLE_DIR:Zelda64Recompiled>/Contents/MacOS/Zelda64Recompiled
# Sign the bundle
COMMAND codesign --verbose=4 --options=runtime --no-strict --sign - --entitlements ${ENTITLEMENTS_FILE} --deep --force $<TARGET_BUNDLE_DIR:Zelda64Recompiled>
COMMENT "Performing post-build steps for macOS bundle"
VERBATIM
)
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-executable-page-protection</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
+44
View File
@@ -0,0 +1,44 @@
include(BundleUtilities)
# Check for pkgx installation
find_program(PKGX_EXECUTABLE pkgx)
# Xcode generator puts the build type in the build directory
set(BUILD_PREFIX "")
if (CMAKE_GENERATOR STREQUAL "Xcode")
set(BUILD_PREFIX "${CMAKE_BUILD_TYPE}/")
endif()
# Use generator expressions to get the absolute path to the bundle
set(APPS "${BUILD_PREFIX}Zelda64Recompiled.app/Contents/MacOS/Zelda64Recompiled")
# Set up framework search paths
set(DIRS "${BUILD_PREFIX}Zelda64Recompiled.app/Contents/Frameworks")
# Detect if we're using pkgx
if(PKGX_EXECUTABLE)
message(STATUS "pkgx detected, adding pkgx directories to framework search path")
list(APPEND DIRS "$ENV{HOME}/.pkgx/")
endif()
# Convert all paths to absolute paths
file(REAL_PATH ${APPS} APPS)
set(RESOLVED_DIRS "")
foreach(DIR IN LISTS DIRS)
# Handle home directory expansion
string(REPLACE "~" "$ENV{HOME}" DIR "${DIR}")
# Convert to absolute path, but don't fail if directory doesn't exist
if(EXISTS "${DIR}")
file(REAL_PATH "${DIR}" RESOLVED_DIR)
list(APPEND RESOLVED_DIRS "${RESOLVED_DIR}")
endif()
endforeach()
# Debug output
message(STATUS "Bundle fixup paths:")
message(STATUS " App: ${APPS}")
message(STATUS " Search dirs: ${RESOLVED_DIRS}")
# Fix up the bundle
fixup_bundle("${APPS}" "" "${RESOLVED_DIRS}")
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/python3
"""
Custom ld64 wrapper for macOS
This script wraps the standard macOS linker (/usr/bin/ld) to modify executable memory
protection flags in the resulting Mach-O binary. It works in three stages:
1. First, it passes through all arguments to the regular macOS linker to create the binary
2. Then, it parses command line arguments to identify output file and segment protection flags
3. Finally, it modifies the output binary's Mach-O headers to ensure segments (particularly __TEXT)
have the maximum protection flags (rwx) we specify, even if the default macOS linker would restrict them
This is necessary because macOS restricts writable+executable memory by default,
but certain applications need this capability for dynamic code generation or JIT compilation.
Usage: Same as the standard ld64 linker, with the added benefit that -segprot options
will have their max_prot values properly preserved in the output binary.
"""
import sys
import subprocess
from itertools import takewhile
from macholib import MachO, ptypes
def parse_rwx(text):
return ('r' in text and 1) | ('w' in text and 2) | ('x' in text and 4)
def apply_maxprots(path, maxprots):
mach = MachO.MachO(path)
header = mach.headers[0]
offset = ptypes.sizeof(header.mach_header)
for cload, ccmd, cdata in header.commands:
if not hasattr(ccmd, 'segname'):
break
if hasattr(ccmd.segname, 'to_str'):
segname = ccmd.segname.to_str().decode('utf-8').strip('\0')
else:
segname = ccmd.segname.decode('utf-8').strip('\0')
if segname in maxprots and ccmd.maxprot != maxprots[segname]:
fields = list(takewhile(lambda field: field[0] != 'maxprot', cload._fields_ + ccmd._fields_))
index = offset + sum(ptypes.sizeof(typ) for _, typ in fields)
with open(path, 'r+b') as fh:
fh.seek(index)
fh.write(bytes([maxprots[segname]]))
offset += cload.cmdsize
try:
subprocess.check_call(['/usr/bin/ld'] + sys.argv[1:])
except subprocess.CalledProcessError as ex:
sys.exit(ex.returncode)
output_file = 'a.out'
segprots = {'__TEXT': parse_rwx('rwx')} # maxprot = rwx
i = 1
while i < len(sys.argv):
if sys.argv[i] == '-o' and i + 1 < len(sys.argv):
output_file = sys.argv[i + 1]
i += 2
elif sys.argv[i] == '-segprot' and i + 3 < len(sys.argv):
segment = sys.argv[i + 1]
maxprot = sys.argv[i + 2]
segprots[segment] = parse_rwx(maxprot)
i += 4
else:
i += 1
apply_maxprots(output_file, segprots)
+14
View File
@@ -0,0 +1,14 @@
version: '2.9.3'
prefix: '/opt/local'
variants:
select:
- aqua
- metal
deselect: x11
ports:
- name: clang-18
- name: llvm-18
- name: libsdl2
select: universal
- name: freetype
select: universal
+74 -3
View File
@@ -78,16 +78,16 @@ jobs:
- name: Hotpatch DXC into RT64's contrib
run: |
# check if dxc was updated before we replace it, to detect changes
echo ${{ inputs.DXC_CHECKSUM }} ./lib/rt64/src/contrib/dxc/bin/x64/dxc | sha256sum --status -c -
echo ${{ inputs.DXC_CHECKSUM }} ./lib/rt64/src/contrib/dxc/bin/x64/dxc-linux | sha256sum --status -c -
cp -v /usr/local/lib/libdxcompiler.so ./lib/rt64/src/contrib/dxc/lib/x64/libdxcompiler.so
cp -v /usr/local/bin/dxc ./lib/rt64/src/contrib/dxc/bin/x64/dxc
cp -v /usr/local/bin/dxc ./lib/rt64/src/contrib/dxc/bin/x64/dxc-linux
- name: Build ZeldaRecomp
run: |-
# enable ccache
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang++-17 -DCMAKE_C_COMPILER=clang-17 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build -DPATCHES_C_COMPILER=clang-17 -DPATCHES_LD=ld.lld-17 -DPATCHES_OBJCOPY=llvm-objcopy-17
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang++-17 -DCMAKE_C_COMPILER=clang-17 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build -DPATCHES_C_COMPILER=clang-17 -DPATCHES_LD=ld.lld-17
cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j $(nproc)
- name: Prepare Archive
run: |
@@ -283,3 +283,74 @@ jobs:
name: Zelda64Recompiled-PDB-${{ matrix.type }}
path: |
Zelda64Recompiled.pdb
build-macos:
runs-on: blaze/macos-14
strategy:
matrix:
type: [ Debug, Release ]
name: macos (x64, arm64, ${{ matrix.type }})
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.ref }}
submodules: recursive
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2
with:
key: ${{ runner.os }}-z64re-ccache-${{ matrix.type }}
- name: Homebrew Setup
run: |
brew install ninja
brew uninstall --ignore-dependencies libpng freetype
- name: MacPorts Setup
uses: melusina-org/setup-macports@v1
id: 'macports'
with:
parameters: '.github/macos/macports.yaml'
- name: Prepare Build
run: |-
git clone ${{ secrets.ZRE_REPO_WITH_PAT }}
./zre/process.sh
cp ./zre/mm_shader_cache.bin ./shadercache/
- name: Build N64Recomp & RSPRecomp
run: |
git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource
cd N64RecompSource
git checkout ${{ inputs.N64RECOMP_COMMIT }}
git submodule update --init --recursive
# enable ccache
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
# Build N64Recomp & RSPRecomp
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
cmake --build cmake-build --config Release --target N64Recomp -j $(sysctl -n hw.ncpu)
cmake --build cmake-build --config Release --target RSPRecomp -j $(sysctl -n hw.ncpu)
# Copy N64Recomp & RSPRecomp to root directory
cp cmake-build/N64Recomp ..
cp cmake-build/RSPRecomp ..
- name: Run N64Recomp & RSPRecomp
run: |
./N64Recomp us.rev1.toml
./RSPRecomp aspMain.us.rev1.toml
./RSPRecomp njpgdspMain.us.rev1.toml
- name: Build ZeldaRecomp
run: |-
# enable ccache
export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH"
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build \
-DPATCHES_LD=/opt/local/bin/ld.lld-mp-18 -DCMAKE_AR=/opt/local/bin/llvm-ar-mp-18 -DPATCHES_C_COMPILER=/opt/local/bin/clang-mp-18 \
-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j $(sysctl -n hw.ncpu)
- name: Prepare Archive
run: |
mv cmake-build/Zelda64Recompiled.app Zelda64Recompiled.app
zip -r -y Zelda64Recompiled.zip Zelda64Recompiled.app
- name: Archive Zelda64Recomp
uses: actions/upload-artifact@v4
with:
name: Zelda64Recompiled-${{ runner.os }}-${{ matrix.type }}
path: Zelda64Recompiled.zip