Compare commits

...

21 Commits

Author SHA1 Message Date
dcvz 65083d2863 Add macOS Support 2024-10-07 23:26:32 +02:00
Mr-Wiseguy b9ea76847f Disable identical code folding to prevent mods from conflicting with themselves due to merged functions 2024-10-07 01:35:56 -04:00
LittleCube 7bcb984347
add exports and events for moon crash save and owl save (#487)
* add exports and events for moon crash save and owl save

* fix recomp_on_owl_save event, add exports and callbacks for save loading

* add more flexible owl events

* add init event

* fix init event to not be terrible

* rename a couple events

* use deletes instead of resets

* use better names, add better annotations

* use full signature for event annotations
2024-10-04 00:01:23 -04:00
Mr-Wiseguy 4767424aee Fix CI building the wrong target in the N64Recomp repo 2024-09-28 19:01:03 -04:00
Mr-Wiseguy beeef1c7ef Fix commit hash for N64Recomp 2024-09-28 18:23:19 -04:00
Mr-Wiseguy 26607e541f Update runtime for custom mod content types and add RT64 texture pack support 2024-09-28 16:50:58 -04:00
Mr-Wiseguy 806d9bc848 Update runtime to add DLL search path, enable strict mode in patch recompilation 2024-09-27 00:57:49 -04:00
Mr-Wiseguy 1e792299c8 Update runtime after merge 2024-09-12 19:24:04 -04:00
Mr-Wiseguy 64793ef337 Update runtime for populating missing runtime pointers in offline compiled mods 2024-09-12 00:55:35 -04:00
Mr-Wiseguy 49517ff978 Update runtime to fix reference symbols 2024-09-12 00:10:29 -04:00
Mr-Wiseguy 9b6296871b Update runtime after merge 2024-09-09 23:23:57 -04:00
Mr-Wiseguy 50536858c8 Update N64ModernRuntime for mod mips32 relocs and function lookups 2024-09-09 02:02:47 -04:00
Mr-Wiseguy 99dc216c75 Update runtime for mod loading on posix systems, enable all mods temporarily 2024-09-08 22:45:37 -04:00
Mr-Wiseguy c2d7a47512 Update runtime and print mod authors and dependencies 2024-09-04 02:29:01 -04:00
Mr-Wiseguy 25e6ca6717 Update runtime to have mod list functionality 2024-09-03 00:04:17 -04:00
Mr-Wiseguy cffda249a7 Update runtime for new mod manifest schema 2024-09-02 23:46:38 -04:00
Mr-Wiseguy f4e8884de6 Parse version number from a string and provide it to the runtime 2024-09-02 20:04:25 -04:00
Mr-Wiseguy a9bb035c0b Update N64Recomp commit for CI 2024-09-02 12:04:14 -04:00
Mr-Wiseguy 5a8de31e82 Update modern runtime for mod support, add some exports and events in the patches 2024-09-02 12:02:03 -04:00
Mr-Wiseguy d4a8cd351e Removed shader cache 2024-09-02 12:02:02 -04:00
Mr-Wiseguy d68a8063c4 Updated RT64 and enabled RT64 extended RDRAM mode 2024-09-02 12:02:02 -04:00
31 changed files with 741 additions and 110 deletions

33
.github/macos/Info.plist.in vendored Normal file
View File

@ -0,0 +1,33 @@
<?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>LSEnvironment</key>
<dict>
<key>MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS</key>
<string>1</string>
<key>MVK_CONFIG_USE_METAL_PRIVATE_API</key>
<string>1</string>
<key>MVK_CONFIG_RESUME_LOST_DEVICE</key>
<string>1</string>
</dict>
</dict>
</plist>

8
.github/macos/MoltenVK_icd.json vendored Normal file
View File

@ -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
}
}

11
.github/macos/fixup_bundle.cmake vendored Normal file
View File

@ -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}")

14
.github/macos/macports.yaml vendored Normal file
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

View File

@ -9,11 +9,19 @@ on:
N64RECOMP_COMMIT:
type: string
required: false
default: 'f8d439aeee6048b7365d1cb3bcd2578ec27a0288'
default: 'd33d38161798167929b114c2b0fd445f9670e10a'
DXC_CHECKSUM:
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
@ -64,7 +72,7 @@ jobs:
# Build N64Recomp & RSPRecomp
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
cmake --build cmake-build --config Release --target N64Recomp -j $(nproc)
cmake --build cmake-build --config Release --target N64RecompCLI -j $(nproc)
cmake --build cmake-build --config Release --target RSPRecomp -j $(nproc)
# Copy N64Recomp & RSPRecomp to root directory
@ -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
@ -160,7 +168,7 @@ jobs:
# Build N64Recomp & RSPRecomp
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build
cmake --build cmake-build --config Release --target N64Recomp -j $(nproc)
cmake --build cmake-build --config Release --target N64RecompCLI -j $(nproc)
cmake --build cmake-build --config Release --target RSPRecomp -j $(nproc)
# Copy N64Recomp & RSPRecomp to root directory
@ -234,7 +242,7 @@ jobs:
$cpuCores = (Get-CimInstance -ClassName Win32_Processor).NumberOfLogicalProcessors
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 $cpuCores
cmake --build cmake-build --config Release --target N64RecompCLI -j $cpuCores
cmake --build cmake-build --config Release --target RSPRecomp -j $cpuCores
# Copy N64Recomp & RSPRecomp to root directory
@ -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

View File

@ -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,21 +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)
# Generate mm_shader_cache.c from the MM shader cache if it exists
if (EXISTS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin)
set(HAS_MM_SHADER_CACHE TRUE)
target_compile_definitions(Zelda64Recompiled PRIVATE HAS_MM_SHADER_CACHE)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.h
COMMAND file_to_c ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin mm_shader_cache_bytes ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.h
DEPENDS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin
)
endif()
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
@ -174,8 +194,8 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp
)
if (HAS_MM_SHADER_CACHE)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c)
if (APPLE)
list(APPEND SOURCES ${CMAKE_SOURCE_DIR}/src/main/support_apple.mm)
endif()
target_include_directories(Zelda64Recompiled PRIVATE
@ -210,6 +230,11 @@ else()
)
endif()
if (MSVC)
# Disable identical code folding, since this breaks mod function patching as multiple functions can get merged into one.
target_link_options(Zelda64Recompiled PRIVATE /OPT:NOICF)
endif()
if (WIN32)
include(FetchContent)
@ -252,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} $<TARGET_BUNDLE_DIR:Zelda64Recompiled>/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 $<TARGET_BUNDLE_DIR:Zelda64Recompiled>/Contents/Resources/vulkan/icd.d
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/.github/macOS/MoltenVK_icd.json $<TARGET_BUNDLE_DIR:Zelda64Recompiled>/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 $<TARGET_BUNDLE_DIR:Zelda64Recompiled>/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/" $<TARGET_BUNDLE_DIR:Zelda64Recompiled>/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=$<TARGET_FILE:Zelda64Recompiled> 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 - $<TARGET_BUNDLE_DIR:Zelda64Recompiled>
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)

View File

@ -58,7 +58,7 @@
</div>
</div>
<div class="bottom-left">
<label>{{version_number}}</label>
<label>v{{version_number}}</label>
</div>
</div>
</div>

View File

@ -120,7 +120,8 @@ namespace recomp {
std::vector<InputField> apply_menu;
};
constexpr const std::vector<InputField>& get_default_mapping_for_input(const DefaultN64Mappings& defaults, const GameInput input) {
inline const std::vector<InputField>& get_default_mapping_for_input(const DefaultN64Mappings& defaults, const GameInput input) {
static const std::vector<InputField> empty_input_field{};
switch (input) {
case GameInput::A: return defaults.a;
case GameInput::B: return defaults.b;
@ -143,7 +144,7 @@ namespace recomp {
case GameInput::TOGGLE_MENU: return defaults.toggle_menu;
case GameInput::ACCEPT_MENU: return defaults.accept_menu;
case GameInput::APPLY_MENU: return defaults.apply_menu;
default: return std::vector<InputField>();
default: return empty_input_field;
}
}

View File

@ -1,8 +1,12 @@
#ifndef __ZELDA_RENDER_H__
#define __ZELDA_RENDER_H__
#include <unordered_set>
#include <filesystem>
#include "common/rt64_user_configuration.h"
#include "ultramodern/renderer_context.hpp"
#include "librecomp/mods.hpp"
namespace RT64 {
struct Application;
@ -29,6 +33,7 @@ namespace zelda64 {
protected:
std::unique_ptr<RT64::Application> app;
std::unordered_set<std::filesystem::path> enabled_texture_packs;
};
std::unique_ptr<ultramodern::renderer::RendererContext> create_render_context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode);
@ -36,6 +41,9 @@ namespace zelda64 {
RT64::UserConfiguration::Antialiasing RT64MaxMSAA();
bool RT64SamplePositionsSupported();
bool RT64HighPrecisionFBEnabled();
void enable_texture_pack(const recomp::mods::ModHandle& mod);
void disable_texture_pack(const recomp::mods::ModHandle& mod);
}
}

14
include/zelda_support.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef __ZELDA_SUPPORT_H__
#define __ZELDA_SUPPORT_H__
#include <functional>
namespace zelda64 {
void dispatch_on_main_thread(std::function<void()> func);
#ifdef __APPLE__
const char* get_bundle_resource_directory();
#endif
}
#endif

@ -1 +1 @@
Subproject commit 0a538553330ea5fdb1673708704bb92a854241b9
Subproject commit a4f5ee10c8303474d9028468e84845b192e9297d

@ -1 +1 @@
Subproject commit 88c618c1f8d8089f94e78537e49e0d77798fc0be
Subproject commit c648adae38ed8d0342c02ccf3a6063e99d746752

View File

@ -14,3 +14,7 @@ data_reference_syms_files = [ "Zelda64RecompSyms/mm.us.rev1.datasyms.toml", "Zel
output_binary_path = "patches/patches.bin"
# Do not emit warnings for unpaired LO16 values, as clang produces many of them.
unpaired_lo16_warnings = false
# Allow exporting functions and events for mods to use.
allow_exports = true
# # Enable strict patch mode, validates that patched symbols exist and that non-patch functions aren't symbols.
strict_patch_mode = true

View File

@ -67,10 +67,10 @@ RECOMP_PATCH void KaleidoSetup_Update(PlayState* play) {
void Sram_SyncWriteToFlash(SramContext* sramCtx, s32 curPage, s32 numPages);
void autosave_reset_timer();
void autosave_reset_timer_slow();
void recomp_reset_autosave_timer();
void recomp_reset_autosave_timer_slow();
void do_autosave(PlayState* play) {
RECOMP_EXPORT void recomp_do_autosave(PlayState* play) {
// Transfer the scene flags into the cycle flags.
Play_SaveCycleSceneFlags(&play->state);
// Transfer the cycle flags into the save buffer. Logic copied from func_8014546C.
@ -99,13 +99,22 @@ void do_autosave(PlayState* play) {
gSaveContext.save.isOwlSave = false;
}
// @recomp Do not clear the save if the save was an autosave.
bool loading_deletes_owl_save = true;
// @recomp_export void recomp_set_loading_deletes_owl_save(bool new_val): Set whether loading an owl save should also delete it.
RECOMP_EXPORT void recomp_set_loading_deletes_owl_save(bool new_val)
{
loading_deletes_owl_save = new_val;
}
// @recomp Do not clear the save if the save was an autosave, or if mods have disabled save deletion.
RECOMP_PATCH void func_80147314(SramContext* sramCtx, s32 fileNum) {
s32 save_type = gSaveContext.save.isOwlSave;
gSaveContext.save.isOwlSave = false;
// @recomp Prevent owl save/autosave deletion if autosaving is enabled.
if (!recomp_autosave_enabled()) {
// @recomp Prevent owl save/autosave deletion if autosaving is enabled, and...
// @recomp_use_export_var loading_deletes_owl_save: Prevent owl save deletion if mods disable it.
if (!recomp_autosave_enabled() && loading_deletes_owl_save) {
gSaveContext.save.saveInfo.playerData.newf[0] = '\0';
gSaveContext.save.saveInfo.playerData.newf[1] = '\0';
gSaveContext.save.saveInfo.playerData.newf[2] = '\0';
@ -176,7 +185,7 @@ RECOMP_PATCH void func_8014546C(SramContext* sramCtx) {
// @recomp Delete the owl save.
delete_owl_save(sramCtx, gSaveContext.fileNum);
// @recomp Reset the autosave timer.
autosave_reset_timer();
recomp_reset_autosave_timer();
for (i = 0; i < ARRAY_COUNT(gSaveContext.cycleSceneFlags); i++) {
gSaveContext.save.saveInfo.permanentSceneFlags[i].chest = gSaveContext.cycleSceneFlags[i].chest;
gSaveContext.save.saveInfo.permanentSceneFlags[i].switch0 = gSaveContext.cycleSceneFlags[i].switch0;
@ -344,11 +353,11 @@ void draw_autosave_icon(PlayState* play) {
CLOSE_DISPS(play->state.gfxCtx);
}
void show_autosave_icon() {
RECOMP_EXPORT void recomp_show_autosave_icon() {
autosave_icon_counter = AUTOSAVE_ICON_TOTAL_FRAMES;
}
u32 recomp_autosave_interval() {
RECOMP_EXPORT u32 recomp_autosave_interval() {
return 2 * 60 * 1000;
}
@ -367,12 +376,12 @@ bool reached_final_three_hours() {
return false;
}
void autosave_reset_timer() {
RECOMP_EXPORT void recomp_reset_autosave_timer() {
last_autosave_time = osGetTime();
extra_autosave_delay_milliseconds = 0;
}
void autosave_reset_timer_slow() {
RECOMP_EXPORT void recomp_reset_autosave_timer_slow() {
// Set the most recent autosave time in the future to give extra time before an autosave triggers.
last_autosave_time = osGetTime();
extra_autosave_delay_milliseconds = 2 * 60 * 1000;
@ -425,20 +434,20 @@ void autosave_post_play_update(PlayState* play) {
frames_since_autosave_ready >= MIN_FRAMES_SINCE_READY &&
time_now - last_autosave_time > (OS_USEC_TO_CYCLES(1000 * (recomp_autosave_interval() + extra_autosave_delay_milliseconds)))
) {
do_autosave(play);
show_autosave_icon();
autosave_reset_timer();
recomp_do_autosave(play);
recomp_show_autosave_icon();
recomp_reset_autosave_timer();
}
}
else {
// Update the last autosave time to the current time to prevent autosaving immediately if autosaves are turned back on.
autosave_reset_timer();
recomp_reset_autosave_timer();
}
gCanPause = false;
}
void autosave_init() {
autosave_reset_timer_slow();
recomp_reset_autosave_timer_slow();
Lib_MemCpy(&prev_save_ctx, &gSaveContext, offsetof(SaveContext, fileNum));
}
@ -522,6 +531,9 @@ s32 spawn_entrance_from_autosave_entrance(s16 autosave_entrance) {
}
}
RECOMP_DECLARE_EVENT(recomp_on_load_save(FileSelectState* fileSelect, SramContext* sramCtx));
RECOMP_DECLARE_EVENT(recomp_after_load_save(FileSelectState* fileSelect, SramContext* sramCtx));
// @recomp Patched to change the entrance for autosaves and initialize autosaves.
RECOMP_PATCH void Sram_OpenSave(FileSelectState* fileSelect, SramContext* sramCtx) {
s32 i;
@ -530,6 +542,9 @@ RECOMP_PATCH void Sram_OpenSave(FileSelectState* fileSelect, SramContext* sramCt
s32 pad1;
s32 fileNum;
// @recomp_event recomp_on_load_save(FileSelectState* fileSelect, SramContext* sramCtx): A save-file was just chosen.
recomp_on_load_save(fileSelect, sramCtx);
if (gSaveContext.flashSaveAvailable) {
bzero(sramCtx->saveBuf, SAVE_BUFFER_SIZE);
@ -653,15 +668,34 @@ RECOMP_PATCH void Sram_OpenSave(FileSelectState* fileSelect, SramContext* sramCt
// @recomp Initialize the autosave state tracking.
autosave_init();
// @recomp_event recomp_after_load_save(FileSelectState* fileSelect, SramContext* sramCtx): The save has finished loading.
recomp_after_load_save(fileSelect, sramCtx);
}
bool moon_crash_resets_save = true;
// @recomp_export void recomp_set_moon_crash_resets_save(bool new_val): Set whether a moon crash should revert the player's save data.
RECOMP_EXPORT void recomp_set_moon_crash_resets_save(bool new_val)
{
moon_crash_resets_save = new_val;
}
extern s32 Actor_ProcessTalkRequest(Actor* actor, GameState* gameState);
RECOMP_DECLARE_EVENT(recomp_on_moon_crash(SramContext* sramCtx));
RECOMP_DECLARE_EVENT(recomp_after_moon_crash(SramContext* sramCtx));
// @recomp Reset the autosave timer when the moon crashes.
RECOMP_PATCH void Sram_ResetSaveFromMoonCrash(SramContext* sramCtx) {
s32 i;
s32 cutsceneIndex = gSaveContext.save.cutsceneIndex;
// @recomp_event recomp_on_moon_crash(SramContext* sramCtx): A moon crash has just been triggered.
recomp_on_moon_crash(sramCtx);
if (moon_crash_resets_save)
{
bzero(sramCtx->saveBuf, SAVE_BUFFER_SIZE);
if (SysFlashrom_ReadData(sramCtx->saveBuf, gFlashSaveStartPages[gSaveContext.fileNum * 2],
@ -676,6 +710,7 @@ RECOMP_PATCH void Sram_ResetSaveFromMoonCrash(SramContext* sramCtx) {
Lib_MemCpy(&gSaveContext, sramCtx->saveBuf, sizeof(Save));
}
gSaveContext.save.cutsceneIndex = cutsceneIndex;
}
for (i = 0; i < ARRAY_COUNT(gSaveContext.eventInf); i++) {
gSaveContext.eventInf[i] = 0;
@ -705,25 +740,56 @@ RECOMP_PATCH void Sram_ResetSaveFromMoonCrash(SramContext* sramCtx) {
gSaveContext.jinxTimer = 0;
// @recomp Use the slow autosave timer to give the player extra time to respond to the moon crashing to decide if they want to reload their autosave.
autosave_reset_timer_slow();
recomp_reset_autosave_timer_slow();
// @recomp_event recomp_after_moon_crash(SramContext* sramCtx): The effects of moon crash have been written.
recomp_after_moon_crash(sramCtx);
}
// @recomp If autosave is enabled, skip the part of the owl statue dialog that talks about the file being deleted on load, since it's not true.
bool owls_save_and_quit = true;
// @recomp_export void recomp_set_owls_save_and_quit(bool new_val): Set if owls should use their code to save and quit. If false is passed, owl saves now do nothing.
RECOMP_EXPORT void recomp_set_owls_save_and_quit(bool new_val)
{
owls_save_and_quit = new_val;
}
RECOMP_DECLARE_EVENT(recomp_on_owl_update(ObjWarpstone* this, PlayState* play));
RECOMP_DECLARE_EVENT(recomp_on_owl_save(ObjWarpstone* this, PlayState* play));
RECOMP_DECLARE_EVENT(recomp_after_owl_save(ObjWarpstone* this, PlayState* play));
// @recomp If autosave is enabled or owl save deletion is disabled, skip the part of the owl statue dialog that talks about the file being deleted on load, since it's not true.
RECOMP_PATCH void ObjWarpstone_Update(Actor* thisx, PlayState* play) {
ObjWarpstone* this = (ObjWarpstone*)thisx;
s32 pad;
// @recomp_event recomp_on_owl_update(ObjWarpstone* this, PlayState* play): Allow mods to handle owl update frames.
recomp_on_owl_update(this, play);
if (this->isTalking) {
if (Actor_TextboxIsClosing(&this->dyna.actor, play)) {
this->isTalking = false;
} else if ((Message_GetState(&play->msgCtx) == TEXT_STATE_CHOICE) && Message_ShouldAdvance(play)) {
if (play->msgCtx.choiceIndex != 0) {
// @recomp_event recomp_on_owl_save(ObjWarpstone* this, PlayState* play): The player chose to save from an owl statue.
recomp_on_owl_save(this, play);
Audio_PlaySfx_MessageDecide();
// @recomp_use_export_var owls_save_and_quit: Only use normal owl save if quit flag is set.
if (owls_save_and_quit) {
play->msgCtx.msgMode = MSGMODE_OWL_SAVE_0;
} else {
Message_CloseTextbox(play);
}
play->msgCtx.unk120D6 = 0;
play->msgCtx.unk120D4 = 0;
gSaveContext.save.owlWarpId = OBJ_WARPSTONE_GET_OWL_WARP_ID(&this->dyna.actor);
// @recomp_event recomp_after_owl_save(ObjWarpstone* this, PlayState* play): Owl save is finished.
recomp_after_owl_save(this, play);
} else {
Message_CloseTextbox(play);
}
@ -734,8 +800,8 @@ RECOMP_PATCH void ObjWarpstone_Update(Actor* thisx, PlayState* play) {
Actor_OfferTalkNearColChkInfoCylinder(&this->dyna.actor, play);
}
// @recomp Skip the text talking about the save being deleted on load, if autosave is enabled.
if (recomp_autosave_enabled()) {
// @recomp_use_export_var loading_deletes_owl_save: Skip the text talking about the save being deleted on load, if autosave is enabled or if owl save deletion is disabled.
if (recomp_autosave_enabled() || !loading_deletes_owl_save) {
if (this->isTalking && play->msgCtx.currentTextId == 0xC01 && play->msgCtx.msgBufPos == 269) {
play->msgCtx.msgBufPos = 530;
}

View File

@ -4,6 +4,11 @@
#define RECOMP_EXPORT __attribute__((section(".recomp_export")))
#define RECOMP_PATCH __attribute__((section(".recomp_patch")))
#define RECOMP_FORCE_PATCH __attribute__((section(".recomp_force_patch")))
#define RECOMP_DECLARE_EVENT(func) \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wunused-parameter\"") \
__attribute__((noinline, weak, used, section(".recomp_event"))) void func {} \
_Pragma("GCC diagnostic pop")
// TODO fix renaming symbols in patch recompilation
#define osCreateMesgQueue osCreateMesgQueue_recomp

View File

@ -1,5 +1,5 @@
RAMBASE = 0x80801000; /* Used to hold any new symbols */
EXTRA_RAM_SIZE = 0x01000000; /* Amount of extra ram allocated by recomp */
PATCH_RAM_END = 0x81000000; /* Amount of extra ram allocated by recomp */
MEMORY {
extram : ORIGIN = RAMBASE, LENGTH = 64M
@ -12,10 +12,11 @@ SECTIONS {
.text : { *(.text*) } >extram AT >rom
.recomp_patch : { *(.recomp_patch*) *(.recomp_force_patch*) } >extram AT >rom
.recomp_export : { *(.recomp_export*) } >extram AT >rom
.recomp_event : { *(.recomp_event*) } >extram AT >rom
.rodata : { *(.rodata*) } >extram AT >rom
.data : { *(.data*) } >extram AT >rom
.bss (NOLOAD) : { *(.bss*) *(COMMON) } >extram
ASSERT(. < RAMBASE + EXTRA_RAM_SIZE, "Maxed out recomp extra ram")
ASSERT(. <= PATCH_RAM_END, "Maxed out recomp extra ram")
.reloc 0 : { *(.reloc*) }
.symtab 0 : { *(.symtab) }

View File

@ -4,6 +4,10 @@
extern Input D_801F6C18;
RECOMP_DECLARE_EVENT(recomp_on_play_main(PlayState* play));
RECOMP_DECLARE_EVENT(recomp_on_play_update(PlayState* play));
RECOMP_DECLARE_EVENT(recomp_after_play_update(PlayState* play));
void controls_play_update(PlayState* play) {
gSaveContext.options.zTargetSetting = recomp_get_targeting_mode();
}
@ -13,6 +17,9 @@ RECOMP_PATCH void Play_Main(GameState* thisx) {
static Input* prevInput = NULL;
PlayState* this = (PlayState*)thisx;
// @recomp_event recomp_on_play_main(PlayState* play): Allow mods to execute code every frame.
recomp_on_play_main(this);
// @recomp
debug_play_update(this);
controls_play_update(this);
@ -32,7 +39,15 @@ RECOMP_PATCH void Play_Main(GameState* thisx) {
this->state.gfxCtx = NULL;
}
camera_pre_play_update(this);
// @recomp_event recomp_on_play_update(PlayState* play): Play_Update is about to be called.
recomp_on_play_update(this);
Play_Update(this);
// @recomp_event recomp_after_play_update(PlayState* play): Play_Update was called.
recomp_after_play_update(this);
camera_post_play_update(this);
analog_cam_post_play_update(this);
autosave_post_play_update(this);

View File

@ -6,7 +6,7 @@ void* proutPrintf(void* dst, const char* fmt, size_t size) {
return (void*)1;
}
int recomp_printf(const char* fmt, ...) {
RECOMP_EXPORT int recomp_printf(const char* fmt, ...) {
va_list args;
va_start(args, fmt);

View File

@ -7,6 +7,8 @@ void Main_InitMemory(void);
void Main_InitScreen(void);
RECOMP_DECLARE_EVENT(recomp_on_init());
// @recomp Patched to load the code segment in the recomp runtime.
RECOMP_PATCH void Main_Init(void) {
DmaRequest dmaReq;
@ -14,6 +16,9 @@ RECOMP_PATCH void Main_Init(void) {
OSMesg msg[1];
size_t prevSize;
// @recomp_event recomp_on_init(): Allow mods to initialize themselves once.
recomp_on_init();
osCreateMesgQueue(&mq, msg, ARRAY_COUNT(msg));
prevSize = gDmaMgrDmaBuffSize;

View File

@ -1266,7 +1266,7 @@ extern Gfx ovl_En_Tanron1_DL_001888[];
extern Gfx ovl_En_Tanron1_DL_001900[];
// @recomp Patched to interpolate the moths that circle torches.
void func_80BB5AAC(EnTanron1* this, PlayState* play) {
RECOMP_PATCH void func_80BB5AAC(EnTanron1* this, PlayState* play) {
EnTanron1Struct* ptrBase = &this->unk_160[0];
s16 i;
u8 flag = 0;

View File

@ -56,9 +56,10 @@ RECOMP_PATCH void Graph_SetNextGfxPool(GraphicsContext* gfxCtx) {
gSPEndDisplayList(&gGfxMasterDL->disps[4]);
gSPBranchList(&gGfxMasterDL->debugDisp[0], bigger_pool->debugBuffer);
// @recomp Enable RT64 extended GBI mode and set the current framerate
// @recomp Enable RT64 extended GBI mode and extended rdram.
OPEN_DISPS(gfxCtx);
gEXEnable(POLY_OPA_DISP++);
gEXSetRDRAMExtended(POLY_OPA_DISP++, 1);
CLOSE_DISPS(gfxCtx);
}

View File

@ -13,13 +13,14 @@
#elif defined(__linux__)
#include <unistd.h>
#include <pwd.h>
#elif defined(__APPLE__)
#include "common/rt64_apple.h"
#endif
constexpr std::u8string_view general_filename = u8"general.json";
constexpr std::u8string_view graphics_filename = u8"graphics.json";
constexpr std::u8string_view controls_filename = u8"controls.json";
constexpr std::u8string_view sound_filename = u8"sound.json";
constexpr std::u8string_view program_id = u8"Zelda64Recompiled";
constexpr auto res_default = ultramodern::renderer::Resolution::Auto;
constexpr auto hr_default = ultramodern::renderer::HUDRatioMode::Clamp16x9;
@ -146,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")};
}
@ -155,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) {

View File

@ -25,8 +25,10 @@
#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"
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
@ -36,12 +38,14 @@
#include "../../lib/rt64/src/contrib/stb/stb_image.h"
const std::string version_string = "1.2.0-dev";
template<typename... Ts>
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() {
@ -115,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
@ -143,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
@ -327,6 +339,7 @@ std::vector<recomp::GameEntry> supported_games = {
.rom_hash = 0xEF18B4A9E2386169ULL,
.internal_name = "ZELDA MAJORA'S MASK",
.game_id = u8"mm.n64.us.1.0",
.mod_game_id = "mm",
.is_enabled = true,
.entrypoint_address = get_entrypoint_address(),
.entrypoint = recomp_entrypoint,
@ -524,7 +537,23 @@ void release_preload(PreloadContext& context) {
#endif
void enable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
(void)context;
zelda64::renderer::enable_texture_pack(mod);
}
void disable_texture_pack(recomp::mods::ModContext& context, const recomp::mods::ModHandle& mod) {
(void)context;
zelda64::renderer::disable_texture_pack(mod);
}
int main(int argc, char** argv) {
recomp::Version project_version{};
if (!recomp::Version::from_string(version_string, project_version)) {
ultramodern::error_handling::message_box(("Invalid version string: " + version_string).c_str());
return EXIT_FAILURE;
}
// Map this executable into memory and lock it, which should keep it in physical memory. This ensures
// that there are no stutters from the OS having to load new pages of the executable whenever a new code page is run.
PreloadContext preload_context;
@ -563,11 +592,18 @@ 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());
}
recomp::register_config_path(zelda64::get_app_folder_path());
// Register supported games and patches
for (const auto& game : supported_games) {
recomp::register_game(game);
@ -575,8 +611,6 @@ int main(int argc, char** argv) {
zelda64::register_overlays();
zelda64::register_patches();
recomp::register_config_path(zelda64::get_app_folder_path());
zelda64::load_config();
recomp::rsp::callbacks_t rsp_callbacks{
@ -619,7 +653,48 @@ int main(int argc, char** argv) {
.get_game_thread_name = zelda64::get_game_thread_name,
};
// Register the texture pack content type with rt64.json as its content file.
recomp::mods::ModContentType texture_pack_content_type{
.content_filename = "rt64.json",
.allow_runtime_toggle = true,
.on_enabled = enable_texture_pack,
.on_disabled = disable_texture_pack,
};
auto texture_pack_content_type_id = recomp::mods::register_mod_content_type(texture_pack_content_type);
// Register the .rtz texture pack file format with the previous content type as its only allowed content type.
recomp::mods::register_mod_container_type("rtz", std::vector{ texture_pack_content_type_id }, false);
recomp::mods::scan_mods();
printf("Found mods:\n");
for (const auto& mod : recomp::mods::get_mod_details("mm")) {
printf(" %s(%s)\n", mod.mod_id.c_str(), mod.version.to_string().c_str());
if (!mod.authors.empty()) {
printf(" Authors: %s", mod.authors[0].c_str());
for (size_t author_index = 1; author_index < mod.authors.size(); author_index++) {
const std::string& author = mod.authors[author_index];
printf(", %s", author.c_str());
}
printf("\n");
printf(" Runtime toggleable: %d\n", mod.runtime_toggleable);
}
if (!mod.dependencies.empty()) {
printf(" Dependencies: %s:%s", mod.dependencies[0].mod_id.c_str(), mod.dependencies[0].version.to_string().c_str());
for (size_t dep_index = 1; dep_index < mod.dependencies.size(); dep_index++) {
const recomp::mods::Dependency& dep = mod.dependencies[dep_index];
printf(", %s:%s", dep.mod_id.c_str(), dep.version.to_string().c_str());
}
printf("\n");
}
// TODO load all mods as a temporary solution to not having a UI yet.
recomp::mods::enable_mod(mod.mod_id, true);
}
printf("\n");
recomp::start(
64 * 1024 * 1024, // 64MB to have plenty of room for loading mods
project_version,
{},
rsp_callbacks,
renderer_callbacks,

View File

@ -7,4 +7,6 @@
void zelda64::register_patches() {
recomp::overlays::register_patches(mm_patches_bin, sizeof(mm_patches_bin), section_table, ARRLEN(section_table));
recomp::overlays::register_base_exports(export_table);
recomp::overlays::register_base_events(event_names);
}

View File

@ -1,5 +1,6 @@
#include <memory>
#include <cstring>
#include <variant>
#define HLSL_CPU
#include "hle/rt64_application.h"
@ -10,6 +11,13 @@
#include "zelda_render.h"
#include "recomp_ui.h"
#include "concurrentqueue.h"
// Helper class for variant visiting.
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None;
static bool sample_positions_supported = false;
@ -18,6 +26,18 @@ static bool high_precision_fb_enabled = false;
static uint8_t DMEM[0x1000];
static uint8_t IMEM[0x1000];
struct TexturePackEnableAction {
std::filesystem::path path;
};
struct TexturePackDisableAction {
std::filesystem::path path;
};
using TexturePackAction = std::variant<TexturePackEnableAction, TexturePackDisableAction>;
static moodycamel::ConcurrentQueue<TexturePackAction> texture_pack_action_queue;
unsigned int MI_INTR_REG = 0;
unsigned int DPC_START_REG = 0;
@ -187,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;
@ -286,6 +306,32 @@ zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::rendere
zelda64::renderer::RT64Context::~RT64Context() = default;
void zelda64::renderer::RT64Context::send_dl(const OSTask* task) {
bool packs_disabled = false;
TexturePackAction cur_action;
while (texture_pack_action_queue.try_dequeue(cur_action)) {
std::visit(overloaded{
[&](TexturePackDisableAction& to_disable) {
enabled_texture_packs.erase(to_disable.path);
packs_disabled = true;
},
[&](TexturePackEnableAction& to_enable) {
enabled_texture_packs.insert(to_enable.path);
// Load the pack now if no packs have been disabled.
if (!packs_disabled) {
app->textureCache->loadReplacementDirectory(to_enable.path);
}
}
}, cur_action);
}
// If any packs were disabled, unload all packs and load all the active ones.
if (packs_disabled) {
app->textureCache->clearReplacementDirectories();
for (const std::filesystem::path& cur_pack_path : enabled_texture_packs) {
app->textureCache->loadReplacementDirectory(cur_pack_path);
}
}
app->state->rsp->reset();
app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true);
app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true);
@ -376,3 +422,11 @@ bool zelda64::renderer::RT64SamplePositionsSupported() {
bool zelda64::renderer::RT64HighPrecisionFBEnabled() {
return high_precision_fb_enabled;
}
void zelda64::renderer::enable_texture_pack(const recomp::mods::ModHandle& mod) {
texture_pack_action_queue.enqueue(TexturePackEnableAction{mod.manifest.mod_root_path});
}
void zelda64::renderer::disable_texture_pack(const recomp::mods::ModHandle& mod) {
texture_pack_action_queue.enqueue(TexturePackDisableAction{mod.manifest.mod_root_path});
}

10
src/main/support.cpp Normal file
View File

@ -0,0 +1,10 @@
#ifndef __APPLE__
#include "zelda_support.h"
#include <functional>
void zelda64::dispatch_on_main_thread(std::function<void()> func) {
func();
}
#endif

13
src/main/support_apple.mm Normal file
View File

@ -0,0 +1,13 @@
#include "zelda_support.h"
#import <Foundation/Foundation.h>
void zelda64::dispatch_on_main_thread(std::function<void()> func) {
dispatch_async(dispatch_get_main_queue(), ^{
func();
});
}
const char* zelda64::get_bundle_resource_directory() {
NSString *bundlePath = [[NSBundle mainBundle] resourcePath];
return strdup([bundlePath UTF8String]);
}

View File

@ -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<uint64_t>(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) {

View File

@ -1,12 +1,13 @@
#include "recomp_ui.h"
#include "zelda_config.h"
#include "zelda_support.h"
#include "librecomp/game.hpp"
#include "ultramodern/ultramodern.hpp"
#include "RmlUi/Core.h"
#include "nfd.h"
#include <filesystem>
std::string version_number = "v1.1.1";
static std::string version_string;
Rml::DataModelHandle model_handle;
bool mm_rom_valid = false;
@ -15,6 +16,7 @@ extern std::vector<recomp::GameEntry> supported_games;
void select_rom() {
nfdnchar_t* native_path = nullptr;
zelda64::dispatch_on_main_thread([&native_path] {
nfdresult_t result = NFD_OpenDialogN(&native_path, nullptr, 0, nullptr);
if (result == NFD_OKAY) {
@ -50,6 +52,7 @@ void select_rom() {
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 {
@ -103,7 +111,9 @@ public:
Rml::DataModelConstructor constructor = context->CreateDataModel("launcher_model");
constructor.Bind("mm_rom_valid", &mm_rom_valid);
constructor.Bind("version_number", &version_number);
version_string = recomp::get_project_version().to_string();
constructor.Bind("version_number", &version_string);
model_handle = constructor.GetModelHandle();
}

View File

@ -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
}
}
@ -1287,6 +1292,12 @@ void draw_hook(RT64::RenderCommandList* command_list, RT64::RenderFramebuffer* s
static recompui::Menu prev_menu = recompui::Menu::None;
recompui::Menu cur_menu = open_menu.load();
// Return to the launcher if no menu is open and the game isn't started.
if (cur_menu == recompui::Menu::None && !ultramodern::is_game_started()) {
cur_menu = recompui::Menu::Launcher;
recompui::set_current_menu(cur_menu);
}
if (reload_sheets) {
ui_context->rml.load_documents();
prev_menu = recompui::Menu::None;
@ -1500,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);
}

View File

@ -5,6 +5,7 @@ entrypoint = 0x80080000
# Paths are relative to the location of this config file.
output_func_path = "RecompiledFuncs"
relocatable_sections_path = "overlays.us.rev1.txt"
# elf_path = "mm.us.rev1.rom_uncompressed.elf"
symbols_file_path = "Zelda64RecompSyms/mm.us.rev1.syms.toml"
rom_file_path = "mm.us.rev1.rom_uncompressed.z64"