diff --git a/.github/macos/Info.plist.in b/.github/macos/Info.plist.in
new file mode 100644
index 0000000..44a1a14
--- /dev/null
+++ b/.github/macos/Info.plist.in
@@ -0,0 +1,33 @@
+
+
+
+
+ CFBundleName
+ ${MACOSX_BUNDLE_BUNDLE_NAME}
+ CFBundleIdentifier
+ ${MACOSX_BUNDLE_GUI_IDENTIFIER}
+ CFBundleVersion
+ ${MACOSX_BUNDLE_BUNDLE_VERSION}
+ CFBundleShortVersionString
+ ${MACOSX_BUNDLE_SHORT_VERSION_STRING}
+ CFBundleExecutable
+ Zelda64Recompiled
+ CFBundleIconFile
+ ${MACOSX_BUNDLE_ICON_FILE}
+ LSApplicationCategoryType
+ public.app-category.games
+ CFBundlePackageType
+ APPL
+ LSMinimumSystemVersion
+ 11
+ LSEnvironment
+
+ MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS
+ 1
+ MVK_CONFIG_USE_METAL_PRIVATE_API
+ 1
+ MVK_CONFIG_RESUME_LOST_DEVICE
+ 1
+
+
+
diff --git a/.github/macos/MoltenVK_icd.json b/.github/macos/MoltenVK_icd.json
new file mode 100644
index 0000000..ffcdbd9
--- /dev/null
+++ b/.github/macos/MoltenVK_icd.json
@@ -0,0 +1,8 @@
+{
+ "file_format_version": "1.0.0",
+ "ICD": {
+ "library_path": "../../../Frameworks/libMoltenVK.dylib",
+ "api_version": "1.2.0",
+ "is_portability_driver": true
+ }
+}
\ No newline at end of file
diff --git a/.github/macos/fixup_bundle.cmake b/.github/macos/fixup_bundle.cmake
new file mode 100644
index 0000000..080806f
--- /dev/null
+++ b/.github/macos/fixup_bundle.cmake
@@ -0,0 +1,11 @@
+include(BundleUtilities)
+
+# Use generator expressions to get the absolute path to the bundle and frameworks
+set(APPS "Zelda64Recompiled.app/Contents/MacOS/Zelda64Recompiled")
+set(DIRS "Zelda64Recompiled.app/Contents/Frameworks")
+
+# The fixup_bundle command needs an absolute path
+file(REAL_PATH ${APPS} APPS)
+file(REAL_PATH ${DIRS} DIRS)
+
+fixup_bundle("${APPS}" "" "${DIRS}")
\ No newline at end of file
diff --git a/.github/macos/macports.yaml b/.github/macos/macports.yaml
new file mode 100644
index 0000000..a51ccec
--- /dev/null
+++ b/.github/macos/macports.yaml
@@ -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
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 442d031..6dad566 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -14,6 +14,14 @@ on:
type: string
required: false
default: '4e6f4e52989aca69739880b40b9f988357f15d10ca03284377b81f1502463ff5'
+ VULKAN_SDK_VERSION:
+ type: string
+ required: false
+ default: '1.3.290.0'
+ MOLTENVK_COMMIT:
+ type: string
+ required: false
+ default: '3b9e335fe8fc8a72cf6099acbd54128889445c0a'
secrets:
ZRE_REPO_WITH_PAT:
required: true
@@ -95,7 +103,7 @@ jobs:
rm -rf assets/scss
tar -czf Zelda64Recompiled.tar.gz Zelda64Recompiled assets/ gamecontrollerdb.txt
- name: Archive Zelda64Recomp
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: Zelda64Recompiled-${{ runner.os }}-X64-${{ matrix.type }}
path: Zelda64Recompiled.tar.gz
@@ -103,7 +111,7 @@ jobs:
run: |-
./.github/linux/appimage.sh
- name: Zelda64Recomp AppImage
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: Zelda64Recompiled-AppImage-X64-${{ matrix.type }}
path: Zelda64Recompiled-*.AppImage
@@ -276,3 +284,117 @@ jobs:
SDL2.dll
assets/
gamecontrollerdb.txt
+ 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: Cache Vulkan SDK
+ id: cache-vulkan-sdk
+ uses: actions/cache@v3
+ with:
+ path: /Users/runner/VulkanSDK/${{ inputs.VULKAN_SDK_VERSION }}
+ key: ${{ runner.os }}-${{ matrix.type }}-vulkan-sdk-${{ inputs.VULKAN_SDK_VERSION }}
+ - name: Install Vulkan SDK
+ if: steps.cache-vulkan-sdk.outputs.cache-hit != 'true'
+ run: |
+ wget https://sdk.lunarg.com/sdk/download/${{ inputs.VULKAN_SDK_VERSION }}/mac/vulkansdk-macos-${{ inputs.VULKAN_SDK_VERSION }}.dmg
+ hdiutil attach ./vulkansdk-macos-${{ inputs.VULKAN_SDK_VERSION }}.dmg
+ sudo /Volumes/vulkansdk-macos-${{ inputs.VULKAN_SDK_VERSION }}/InstallVulkan.app/Contents/MacOS/InstallVulkan --root ~/VulkanSDK/${{ inputs.VULKAN_SDK_VERSION }} --accept-licenses --default-answer --confirm-command install
+ hdiutil detach /Volumes/vulkansdk-macos-${{ inputs.VULKAN_SDK_VERSION }}
+ - name: Checkout MoltenVK
+ run: |
+ git clone https://github.com/KhronosGroup/MoltenVK.git
+ cd MoltenVK
+ git checkout ${{ inputs.MOLTENVK_COMMIT }}
+ - name: Cache MoltenVK Dependencies
+ id: cache-mvk-dependencies
+ uses: actions/cache@v3
+ with:
+ path: |
+ MoltenVK/External/build
+ !MoltenVK/External/build/Intermediates
+ key: ${{ runner.os }}-${{ matrix.type }}-${{ hashFiles('MoltenVK/fetchDependencies','MoltenVK/ExternalRevisions/**','MoltenVK/ExternalDependencies.xcodeproj/**','MoltenVK/Scripts/**') }}
+ - name: Fetch MVK Dependencies (Use Built Cache)
+ if: steps.cache-mvk-dependencies.outputs.cache-hit == 'true'
+ run: |
+ cd MoltenVK
+ ./fetchDependencies -v --none
+ - name: Fetch MVK Dependencies
+ if: steps.cache-mvk-dependencies.outputs.cache-hit != 'true'
+ run: |
+ cd MoltenVK
+ ./fetchDependencies -v --macos
+ - name: Build MoltenVK
+ run: |
+ cd MoltenVK
+ make macos
+ sudo cp Package/Latest/MoltenVK/dylib/macOS/libMoltenVK.dylib ~/VulkanSDK/${{ inputs.VULKAN_SDK_VERSION }}/macOS/lib/libMoltenVK.dylib
+ - 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 -DPATCHES_OBJCOPY=/opt/local/bin/llvm-objcopy-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)
+ env:
+ VULKAN_SDK: /Users/runner/VulkanSDK/${{ inputs.VULKAN_SDK_VERSION }}/macOS
+ - 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
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0b900bc..3de60a9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,10 @@
cmake_minimum_required(VERSION 3.20)
-project(Zelda64Recompiled)
+
+if (APPLE) # has to be set before the first project() or enable_language()
+ set(CMAKE_OSX_DEPLOYMENT_TARGET "11.0" CACHE STRING "Minimum OS X deployment version")
+endif()
+
+project(Zelda64Recompiled LANGUAGES C CXX)
set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@@ -15,6 +20,30 @@ if (WIN32)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_DISABLE_CONSTEXPR_MUTEX_CONSTRUCTOR")
endif()
+if (APPLE)
+ enable_language(OBJC OBJCXX)
+
+ # Check if VULKAN_SDK environment variable is set
+ if(NOT DEFINED ENV{VULKAN_SDK})
+ message(FATAL_ERROR "VULKAN_SDK environment variable is not set.")
+ else()
+ set(VULKAN_SDK $ENV{VULKAN_SDK})
+ endif()
+
+ # Define paths to Vulkan loader and MoltenVK libraries
+ set(VULKAN_LOADER_PATH "${VULKAN_SDK}/lib/libvulkan.dylib")
+ set(MOLTENVK_PATH "${VULKAN_SDK}/lib/libMoltenVK.dylib")
+
+ # Ensure the Vulkan loader and MoltenVK libraries exist
+ if(NOT EXISTS "${VULKAN_LOADER_PATH}")
+ message(FATAL_ERROR "Vulkan loader not found at ${VULKAN_LOADER_PATH}")
+ endif()
+
+ if(NOT EXISTS "${MOLTENVK_PATH}")
+ message(FATAL_ERROR "MoltenVK not found at ${MOLTENVK_PATH}")
+ endif()
+endif()
+
# Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24:
if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
cmake_policy(SET CMP0135 NEW)
@@ -135,11 +164,12 @@ add_custom_target(DownloadGameControllerDB
DEPENDS ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt)
# Main executable
-add_executable(Zelda64Recompiled)
+add_executable(Zelda64Recompiled MACOSX_BUNDLE)
add_dependencies(Zelda64Recompiled DownloadGameControllerDB)
set (SOURCES
${CMAKE_SOURCE_DIR}/src/main/main.cpp
+ ${CMAKE_SOURCE_DIR}/src/main/support.cpp
${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp
${CMAKE_SOURCE_DIR}/src/main/register_patches.cpp
${CMAKE_SOURCE_DIR}/src/main/rt64_render_context.cpp
@@ -164,6 +194,10 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp
)
+if (APPLE)
+ list(APPEND SOURCES ${CMAKE_SOURCE_DIR}/src/main/support_apple.mm)
+endif()
+
target_include_directories(Zelda64Recompiled PRIVATE
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/lib/concurrentqueue
@@ -243,6 +277,114 @@ if (WIN32)
target_sources(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/icons/app.rc)
endif()
+if (APPLE)
+ find_package(SDL2 REQUIRED)
+ target_include_directories(Zelda64Recompiled PRIVATE ${SDL2_INCLUDE_DIRS})
+
+ set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
+ set(THREADS_PREFER_PTHREAD_FLAG TRUE)
+ find_package(Threads REQUIRED)
+
+ target_link_libraries(Zelda64Recompiled PRIVATE ${CMAKE_DL_LIBS} Threads::Threads)
+
+ # 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
+ )
+
+ 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 .)
+
+ # Copy required frameworks to bundle
+ target_link_libraries(Zelda64Recompiled PRIVATE ${MOLTENVK_PATH} ${VULKAN_LOADER_PATH})
+ add_custom_command(TARGET Zelda64Recompiled POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/.github/macos/fixup_bundle.cmake
+ )
+
+ # Copy assets folder to the MacOS folder of the app bundle
+ set(TEMP_ASSETS_DIR "${CMAKE_BINARY_DIR}/temp_assets")
+ add_custom_command(TARGET Zelda64Recompiled POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/assets ${TEMP_ASSETS_DIR}
+ COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEMP_ASSETS_DIR}/scss
+ COMMAND ${CMAKE_COMMAND} -E copy_directory ${TEMP_ASSETS_DIR} $/Contents/Resources/assets
+ COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEMP_ASSETS_DIR}
+ )
+
+ # Copy ICD files to macOS Resources folder
+ add_custom_command(TARGET Zelda64Recompiled POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E make_directory $/Contents/Resources/vulkan/icd.d
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/.github/macOS/MoltenVK_icd.json $/Contents/Resources/vulkan/icd.d/
+ )
+
+ # Copy controller db file to macOS Resources folder
+ add_custom_command(TARGET Zelda64Recompiled POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt $/Contents/Resources/
+ )
+
+ # Use install_name_tool to set the RPATH after the build
+ add_custom_command(TARGET Zelda64Recompiled POST_BUILD
+ COMMAND install_name_tool -add_rpath "@executable_path/../Frameworks/" $/Contents/MacOS/Zelda64Recompiled
+ )
+
+ # Apply JIT compilation workaround
+ add_custom_command(TARGET Zelda64Recompiled POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E echo "Applying JIT compilation workaround"
+ COMMAND /bin/bash -c "printf '\\x07' | dd of=$ bs=1 seek=160 count=1 conv=notrunc"
+ VERBATIM
+ )
+
+ # Code sign the app bundle with ad-hoc identity
+ add_custom_command(TARGET Zelda64Recompiled POST_BUILD
+ COMMAND codesign --deep --force --sign - $
+ COMMENT "Code signing the app bundle with ad-hoc identity"
+ )
+endif()
+
if (CMAKE_SYSTEM_NAME MATCHES "Linux")
find_package(SDL2 REQUIRED)
find_package(X11 REQUIRED)
diff --git a/include/zelda_support.h b/include/zelda_support.h
new file mode 100644
index 0000000..42f74c2
--- /dev/null
+++ b/include/zelda_support.h
@@ -0,0 +1,14 @@
+#ifndef __ZELDA_SUPPORT_H__
+#define __ZELDA_SUPPORT_H__
+
+#include
+
+namespace zelda64 {
+ void dispatch_on_main_thread(std::function func);
+
+#ifdef __APPLE__
+ const char* get_bundle_resource_directory();
+#endif
+}
+
+#endif
diff --git a/lib/rt64 b/lib/rt64
index 88c618c..c648ada 160000
--- a/lib/rt64
+++ b/lib/rt64
@@ -1 +1 @@
-Subproject commit 88c618c1f8d8089f94e78537e49e0d77798fc0be
+Subproject commit c648adae38ed8d0342c02ccf3a6063e99d746752
diff --git a/src/game/config.cpp b/src/game/config.cpp
index ab24e0a..2a1fdd4 100644
--- a/src/game/config.cpp
+++ b/src/game/config.cpp
@@ -13,6 +13,8 @@
#elif defined(__linux__)
#include
#include
+#elif defined(__APPLE__)
+#include "common/rt64_apple.h"
#endif
constexpr std::u8string_view general_filename = u8"general.json";
@@ -145,8 +147,8 @@ std::filesystem::path zelda64::get_app_folder_path() {
}
CoTaskMemFree(known_path);
-#elif defined(__linux__)
- // check for APP_FOLDER_PATH env var used by AppImage
+#elif defined(__linux__) || defined(__APPLE__)
+ // check for APP_FOLDER_PATH env var
if (getenv("APP_FOLDER_PATH") != nullptr) {
return std::filesystem::path{getenv("APP_FOLDER_PATH")};
}
@@ -154,7 +156,11 @@ std::filesystem::path zelda64::get_app_folder_path() {
const char *homedir;
if ((homedir = getenv("HOME")) == nullptr) {
+ #if defined(__linux__)
homedir = getpwuid(getuid())->pw_dir;
+ #elif defined(__APPLE__)
+ homedir = GetHomeDirectory();
+ #endif
}
if (homedir != nullptr) {
diff --git a/src/main/main.cpp b/src/main/main.cpp
index cbeb63a..de6f550 100644
--- a/src/main/main.cpp
+++ b/src/main/main.cpp
@@ -25,6 +25,7 @@
#include "zelda_config.h"
#include "zelda_sound.h"
#include "zelda_render.h"
+#include "zelda_support.h"
#include "ovl_patches.hpp"
#include "librecomp/game.hpp"
#include "librecomp/mods.hpp"
@@ -44,7 +45,7 @@ void exit_error(const char* str, Ts ...args) {
// TODO pop up an error
((void)fprintf(stderr, str, args), ...);
assert(false);
- std::quick_exit(EXIT_FAILURE);
+ ULTRAMODERN_QUICK_EXIT();
}
ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() {
@@ -118,7 +119,12 @@ bool SetImageAsIcon(const char* filename, SDL_Window* window)
SDL_Window* window;
ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) {
- window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, SDL_WINDOW_RESIZABLE );
+ uint32_t window_flags = SDL_WINDOW_RESIZABLE;
+#ifdef __APPLE__
+ window_flags |= SDL_WINDOW_METAL;
+#endif
+
+ window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, window_flags );
#if defined(__linux__)
SetImageAsIcon("icons/512.png",window);
if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux
@@ -146,6 +152,9 @@ ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::
}
return ultramodern::renderer::WindowHandle{ wmInfo.info.x11.display, wmInfo.info.x11.window };
+#elif defined(__APPLE__)
+ SDL_MetalView view = SDL_Metal_CreateView(window);
+ return ultramodern::renderer::WindowHandle{ wmInfo.info.cocoa.window, SDL_Metal_GetLayer(view) };
#else
static_assert(false && "Unimplemented");
#endif
@@ -583,8 +592,13 @@ int main(int argc, char** argv) {
SDL_InitSubSystem(SDL_INIT_AUDIO);
reset_audio(48000);
- // Source controller mappings file
- if (SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt") < 0) {
+#if defined(__APPLE__)
+ const char* resource_directory = zelda64::get_bundle_resource_directory();
+ std::string mapping_file_path = std::string(resource_directory) + "gamecontrollerdb.txt";
+#else
+ std::string mapping_file_path = "gamecontrollerdb.txt";
+#endif
+ if (SDL_GameControllerAddMappingsFromFile(mapping_file_path.c_str()) < 0) {
fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError());
}
diff --git a/src/main/rt64_render_context.cpp b/src/main/rt64_render_context.cpp
index 8d5c735..016cddf 100644
--- a/src/main/rt64_render_context.cpp
+++ b/src/main/rt64_render_context.cpp
@@ -207,7 +207,7 @@ zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::rendere
appCore.window.window = window_handle.window;
#elif defined(__APPLE__)
appCore.window.window = window_handle.window;
- appCore.window.view = window_handle.view;
+ appCore.window.layer = window_handle.view;
#endif
appCore.checkInterrupts = dummy_check_interrupts;
diff --git a/src/main/support.cpp b/src/main/support.cpp
new file mode 100644
index 0000000..4c7631d
--- /dev/null
+++ b/src/main/support.cpp
@@ -0,0 +1,10 @@
+#ifndef __APPLE__
+
+#include "zelda_support.h"
+#include
+
+void zelda64::dispatch_on_main_thread(std::function func) {
+ func();
+}
+
+#endif
\ No newline at end of file
diff --git a/src/main/support_apple.mm b/src/main/support_apple.mm
new file mode 100644
index 0000000..ceba22c
--- /dev/null
+++ b/src/main/support_apple.mm
@@ -0,0 +1,13 @@
+#include "zelda_support.h"
+#import
+
+void zelda64::dispatch_on_main_thread(std::function func) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ func();
+ });
+}
+
+const char* zelda64::get_bundle_resource_directory() {
+ NSString *bundlePath = [[NSBundle mainBundle] resourcePath];
+ return strdup([bundlePath UTF8String]);
+}
diff --git a/src/ui/ui_config.cpp b/src/ui/ui_config.cpp
index 12bb20e..18158cd 100644
--- a/src/ui/ui_config.cpp
+++ b/src/ui/ui_config.cpp
@@ -2,6 +2,7 @@
#include "recomp_input.h"
#include "zelda_sound.h"
#include "zelda_config.h"
+#include "zelda_support.h"
#include "zelda_debug.h"
#include "zelda_render.h"
#include "promptfont.h"
@@ -519,6 +520,11 @@ public:
}
Rml::ElementDocument* load_document(Rml::Context* context) override {
+#if defined(__APPLE__)
+ const Rml::String asset = "/assets/config_menu.rml";
+ return context->LoadDocument(zelda64::get_bundle_resource_directory() + asset);
+#endif
+
return context->LoadDocument("assets/config_menu.rml");
}
void register_events(recompui::UiEventListenerInstancer& listener) override {
@@ -725,7 +731,7 @@ public:
throw std::runtime_error("Failed to make RmlUi data model for the controls config menu");
}
- constructor.BindFunc("input_count", [](Rml::Variant& out) { out = recomp::get_num_inputs(); } );
+ constructor.BindFunc("input_count", [](Rml::Variant& out) { out = static_cast(recomp::get_num_inputs()); } );
constructor.BindFunc("input_device_is_keyboard", [](Rml::Variant& out) { out = cur_device == recomp::InputDevice::Keyboard; } );
constructor.RegisterTransformFunc("get_input_name", [](const Rml::VariantList& inputs) {
diff --git a/src/ui/ui_launcher.cpp b/src/ui/ui_launcher.cpp
index f699eb7..c876674 100644
--- a/src/ui/ui_launcher.cpp
+++ b/src/ui/ui_launcher.cpp
@@ -1,5 +1,6 @@
#include "recomp_ui.h"
#include "zelda_config.h"
+#include "zelda_support.h"
#include "librecomp/game.hpp"
#include "ultramodern/ultramodern.hpp"
#include "RmlUi/Core.h"
@@ -15,41 +16,43 @@ extern std::vector supported_games;
void select_rom() {
nfdnchar_t* native_path = nullptr;
- nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
+ zelda64::dispatch_on_main_thread([&native_path] {
+ nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
- if (result == NFD_OKAY) {
- std::filesystem::path path{native_path};
+ if (result == NFD_OKAY) {
+ std::filesystem::path path{native_path};
- NFD_FreePathN(native_path);
- native_path = nullptr;
+ NFD_FreePathN(native_path);
+ native_path = nullptr;
- recomp::RomValidationError rom_error = recomp::select_rom(path, supported_games[0].game_id);
- switch (rom_error) {
- case recomp::RomValidationError::Good:
- mm_rom_valid = true;
- model_handle.DirtyVariable("mm_rom_valid");
- break;
- case recomp::RomValidationError::FailedToOpen:
- recompui::message_box("Failed to open ROM file.");
- break;
- case recomp::RomValidationError::NotARom:
- recompui::message_box("This is not a valid ROM file.");
- break;
- case recomp::RomValidationError::IncorrectRom:
- recompui::message_box("This ROM is not the correct game.");
- break;
- case recomp::RomValidationError::NotYet:
- recompui::message_box("This game isn't supported yet.");
- break;
- case recomp::RomValidationError::IncorrectVersion:
- recompui::message_box(
- "This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
- break;
- case recomp::RomValidationError::OtherError:
- recompui::message_box("An unknown error has occurred.");
- break;
+ recomp::RomValidationError rom_error = recomp::select_rom(path, supported_games[0].game_id);
+ switch (rom_error) {
+ case recomp::RomValidationError::Good:
+ mm_rom_valid = true;
+ model_handle.DirtyVariable("mm_rom_valid");
+ break;
+ case recomp::RomValidationError::FailedToOpen:
+ recompui::message_box("Failed to open ROM file.");
+ break;
+ case recomp::RomValidationError::NotARom:
+ recompui::message_box("This is not a valid ROM file.");
+ break;
+ case recomp::RomValidationError::IncorrectRom:
+ recompui::message_box("This ROM is not the correct game.");
+ break;
+ case recomp::RomValidationError::NotYet:
+ recompui::message_box("This game isn't supported yet.");
+ break;
+ case recomp::RomValidationError::IncorrectVersion:
+ recompui::message_box(
+ "This ROM is the correct game, but the wrong version.\nThis project requires the NTSC-U N64 version of the game.");
+ break;
+ case recomp::RomValidationError::OtherError:
+ recompui::message_box("An unknown error has occurred.");
+ break;
+ }
}
- }
+ });
}
class LauncherMenu : public recompui::MenuController {
@@ -61,6 +64,11 @@ public:
}
Rml::ElementDocument* load_document(Rml::Context* context) override {
+#if defined(__APPLE__)
+ const Rml::String asset = "/assets/launcher.rml";
+ return context->LoadDocument(zelda64::get_bundle_resource_directory() + asset);
+#endif
+
return context->LoadDocument("assets/launcher.rml");
}
void register_events(recompui::UiEventListenerInstancer& listener) override {
diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp
index a234d7c..a93d7ad 100644
--- a/src/ui/ui_renderer.cpp
+++ b/src/ui/ui_renderer.cpp
@@ -16,6 +16,7 @@
#include "librecomp/game.hpp"
#include "zelda_config.h"
#include "ui_rml_hacks.hpp"
+#include "zelda_support.h"
#include "concurrentqueue.h"
@@ -1144,8 +1145,6 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
Rml::Debugger::Initialise(ui_context->rml.context);
{
- const Rml::String directory = "assets/";
-
struct FontFace {
const char* filename;
bool fallback_face;
@@ -1162,7 +1161,13 @@ void init_hook(RT64::RenderInterface* interface, RT64::RenderDevice* device) {
};
for (const FontFace& face : font_faces) {
+ #if defined(__APPLE__)
+ const Rml::String directory = "/assets/";
+ Rml::LoadFontFace(zelda64::get_bundle_resource_directory() + directory + face.filename, face.fallback_face);
+ #else
+ const Rml::String directory = "assets/";
Rml::LoadFontFace(directory + face.filename, face.fallback_face);
+ #endif
}
}
@@ -1506,6 +1511,9 @@ recompui::Menu recompui::get_current_menu() {
}
void recompui::message_box(const char* msg) {
- SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, zelda64::program_name.data(), msg, nullptr);
+ std::string message(msg);
+ zelda64::dispatch_on_main_thread([message] {
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, zelda64::program_name.data(), message.c_str(), nullptr);
+ });
printf("[ERROR] %s\n", msg);
}