diff --git a/CMakeLists.txt b/CMakeLists.txt index 88fbca52c7..b2aa198fd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,9 @@ cmake_minimum_required(VERSION 3.16.0 FATAL_ERROR) +if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set(CMAKE_POLICY_VERSION_MINIMUM 3.5) +endif() + # Set the project version and language project(Spaghettify VERSION 1.0.0 LANGUAGES C CXX ASM) include(FetchContent) @@ -59,6 +63,8 @@ endif() message("Spaghetti Kart version: ${PROJECT_VERSION} ${PROJECT_PATCH_WORD}") if(APPLE) + set_source_files_properties(src/port/audio/HMAS.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") + enable_language(OBJC) enable_language(OBJCXX) endif() @@ -187,6 +193,20 @@ set(SKIP_XCODE_VERSION_CHECK ON) set(GFX_DEBUG_DISASSEMBLER OFF) # Add compile definitions for the target +include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) +check_c_compiler_flag("-Wno-error=int-conversion" HAS_WNO_ERROR_INT_CONVERSION) +check_cxx_compiler_flag("-Wno-error=changes-meaning" HAS_WNO_ERROR_CHANGES_MEANING) + +add_compile_options("$<$:-Wno-error=incompatible-pointer-types>" "$<$:-Wno-error=narrowing>") + +if(HAS_WNO_ERROR_INT_CONVERSION) + add_compile_options("$<$:-Wno-error=int-conversion>") +endif() + +if(HAS_WNO_ERROR_CHANGES_MEANING) + add_compile_options("$<$:-Wno-error=changes-meaning>") +endif() add_compile_definitions( VERSION_US=1 ENABLE_RUMBLE=1 @@ -263,6 +283,7 @@ file(GLOB_RECURSE ALL_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/port/*.h" "src/port/*.c" "src/port/*.cpp" + "src/port/*.mm" "src/mods/*.h" "src/mods/*.c" "src/mods/*.cpp" @@ -298,13 +319,15 @@ list(FILTER ALL_FILES EXCLUDE REGEX ".*.inc.c") list(FILTER ALL_FILES EXCLUDE REGEX "./src/debug/crash_screen_enhancement.c") if (CMAKE_SYSTEM_NAME STREQUAL "iOS") - set(IOS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libultraship/ios) + set(IOS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ios) set(STORYBOARD_FILE ${IOS_DIR}/Launch.storyboard) - set(IMAGE_FILES ${IOS_DIR}/PoweredBy.png) - set(ICON_FILES ${IOS_DIR}/Icon.png) + set(ICON_FILES ${CMAKE_CURRENT_SOURCE_DIR}/icon.png) + + list(APPEND ALL_FILES ${STORYBOARD_FILE} ${ICON_FILES}) + + add_compile_definitions(__IOS__) - list(APPEND ALL_FILES ${STORYBOARD_FILE} ${IMAGE_FILES} ${ICON_FILES}) add_executable(${PROJECT_NAME} ${ALL_FILES}) set_xcode_property(${PROJECT_NAME} PRODUCT_BUNDLE_IDENTIFIER ${PROJECT_ID} All) @@ -313,7 +336,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL "iOS") PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_INFO_PLIST ${IOS_DIR}/plist.in - RESOURCE "${IMAGE_FILES};${STORYBOARD_FILE};${ICON_FILES}" + RESOURCE "${STORYBOARD_FILE};${ICON_FILES}" ) else() add_executable(${PROJECT_NAME} ${ALL_FILES}) @@ -483,6 +506,52 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "CafeOS") target_include_directories(${PROJECT_NAME} PRIVATE ${DEVKITPRO}/portlibs/wiiu/include/ ) +elseif(CMAKE_SYSTEM_NAME STREQUAL "iOS") + set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) + set(BUILD_TESTING OFF CACHE BOOL "" FORCE) + set(INSTALL_DOCS OFF CACHE BOOL "" FORCE) + set(INSTALL_PKG_CONFIG_MODULE OFF CACHE BOOL "" FORCE) + + FetchContent_Declare( + libogg + GIT_REPOSITORY https://github.com/xiph/ogg.git + GIT_TAG v1.3.6 + ) + FetchContent_MakeAvailable(libogg) + + set(OGG_ROOT "${libogg_BINARY_DIR}" CACHE PATH "" FORCE) + set(OGG_INCLUDE_DIR "${libogg_SOURCE_DIR}/include" CACHE PATH "" FORCE) + set(OGG_LIBRARY "${libogg_BINARY_DIR}/libogg.a" CACHE FILEPATH "" FORCE) + + if(TARGET ogg AND NOT TARGET Ogg::ogg) + add_library(Ogg::ogg ALIAS ogg) + endif() + + FetchContent_Declare( + libvorbis + GIT_REPOSITORY https://github.com/xiph/vorbis.git + GIT_TAG v1.3.7 + ) + FetchContent_MakeAvailable(libvorbis) + + if(TARGET vorbis AND NOT TARGET Vorbis::vorbis) + add_library(Vorbis::vorbis ALIAS vorbis) + endif() + + if(TARGET vorbisenc AND NOT TARGET Vorbis::vorbisenc) + add_library(Vorbis::vorbisenc ALIAS vorbisenc) + endif() + + if(TARGET vorbisfile AND NOT TARGET Vorbis::vorbisfile) + add_library(Vorbis::vorbisfile ALIAS vorbisfile) + endif() + + set(ADDITIONAL_LIBRARY_DEPENDENCIES + "Ogg::ogg" + "Vorbis::vorbis" + "Vorbis::vorbisenc" + "Vorbis::vorbisfile" + ) else() find_package(Ogg REQUIRED) find_package(Vorbis REQUIRED) @@ -655,14 +724,24 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") endif() endif() +file(WRITE ${CMAKE_BINARY_DIR}/copy_if_exists.cmake "if(EXISTS \"\${SRC}\")\n execute_process(COMMAND \${CMAKE_COMMAND} -E copy_if_different \"\${SRC}\" \"\${DST}\")\nendif()\n") + add_custom_command( TARGET ${PROJECT_NAME} POST_BUILD COMMENT "Copying asset yamls..." - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/config.yml" "$/config.yml" - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/yamls/" "$/yamls/" - COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/meta/" "$/meta/" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/config.yml" "$/config.yml" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/yamls/" "$/yamls/" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/meta/" "$/meta/" ) +if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + add_custom_command( + TARGET ${PROJECT_NAME} POST_BUILD + COMMENT "Copying packaged O2R assets..." + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_BINARY_DIR}/spaghetti.o2r" "$/spaghetti.o2r" + ) +endif() + if(NOT CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch") include(ExternalProject) ExternalProject_Add(TorchExternal @@ -680,25 +759,27 @@ if(NOT CMAKE_SYSTEM_NAME STREQUAL "NintendoSwitch") add_custom_target( ExtractAssets - DEPENDS TorchExternal + DEPENDS ${TORCH_EXECUTABLE} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${TORCH_EXECUTABLE} header -o baserom.us.z64 COMMAND ${TORCH_EXECUTABLE} o2r baserom.us.z64 --additional-files meta/mods.toml COMMAND ${TORCH_EXECUTABLE} pack assets spaghetti.o2r o2r COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/mk64.o2r" "${CMAKE_BINARY_DIR}/mk64.o2r" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/spaghetti.o2r" "${CMAKE_BINARY_DIR}/spaghetti.o2r" -) + ) add_custom_target( GenerateO2R - DEPENDS TorchExternal + DEPENDS TorchExternal "${CMAKE_SOURCE_DIR}/assets" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND ${TORCH_EXECUTABLE} pack assets spaghetti.o2r o2r COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_SOURCE_DIR}/spaghetti.o2r" "${CMAKE_BINARY_DIR}/spaghetti.o2r" -) + ) + + add_dependencies(${PROJECT_NAME} GenerateO2R) if("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") - install(FILES "${CMAKE_BINARY_DIR}/spaghetti.o2r" DESTINATION . COMPONENT ${PROJECT_NAME}) + install(FILES "${CMAKE_BINARY_DIR}/spaghetti.o2r" DESTINATION . COMPONENT ${PROJECT_NAME} OPTIONAL) endif() if(CMAKE_SYSTEM_NAME MATCHES "Darwin") diff --git a/docs/BUILDING.md b/docs/BUILDING.md index da288947c8..b23df376c5 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -232,6 +232,93 @@ cpack cmake --build build-cmake --target clean ``` +## iOS +Requires: + * macOS with Xcode installed + * CMake and Ninja in `PATH` + * Homebrew packages: `cmake ninja sdl2 libpng glew nlohmann-json libzip vorbis-tools sdl2_net tinyxml2 pkg-config git` + * Your own supported Mario Kart 64 ROM + +Install the Homebrew dependencies: + +```bash +brew install cmake ninja sdl2 libpng glew nlohmann-json libzip vorbis-tools sdl2_net tinyxml2 pkg-config git +``` + +Build steps: + +```bash +# Clone the repo +git clone --recurse-submodules https://github.com/HarbourMasters/SpaghettiKart.git +cd SpaghettiKart + +# If needed later +git submodule update --init --recursive + +# Put your supported ROM at the repo root with this filename +ln -sf "/path/to/your/baserom.us.z64" baserom.us.z64 + +# Generate mk64.o2r and spaghetti.o2r from your own ROM +cmake -S . -B build-cmake -GNinja +cmake --build build-cmake --target ExtractAssets + +# Configure the iOS build +cmake -S . -B build-ios-make -DCMAKE_BUILD_TYPE=Release -DIOS=ON -DSIGN_LIBRARY=OFF + +# Build the app +cmake --build build-ios-make --config Release + +# Package an unsigned IPA +# The IPA must contain Payload/ at the archive root. +rm -rf ipa-package +mkdir -p ipa-package/Payload +cp -R build-ios-make/Spaghettify.app ipa-package/Payload/Spaghettify.app +(cd ipa-package && zip -qry ../SpaghettiKart-unsigned.ipa Payload) + +# Output +ls SpaghettiKart-unsigned.ipa +``` + +Notes: + * The IPA produced by these steps is unsigned. + * The build generates `mk64.o2r` and `spaghetti.o2r` from your own ROM and copies them into the iOS app bundle. + * Do not zip `build-ios-make/Payload` directly, or the archive will have the wrong root folder for signing tools. + * No additional mod pack is required for the current iOS build flow. + +## iOS Simulator (macOS) +To run the project on the iOS Simulator on a macOS host (Apple Silicon `arm64`), use the following steps. This assumes you have already extracted the assets as shown in the iOS or macOS sections. + +### Build steps + +```bash +# Clean previous builds to avoid conflicts +rm -rf build-ios-make + +# Configure the project for the simulator +# This ensures we target the simulator's arm64 architecture instead of physical devices +cmake -B build-ios-make -G "Unix Makefiles" \ + -DCMAKE_TOOLCHAIN_FILE=cmake/ios.toolchain.cmake \ + -DPLATFORM=SIMULATORARM64 \ + -DCMAKE_OSX_SYSROOT=iphonesimulator \ + -DCMAKE_OSX_ARCHITECTURES=arm64 + +# Compile the project +cmake --build build-ios-make +``` + +### Running on the Simulator + +```bash +# Boot your preferred simulator (e.g., iPhone 16 Pro) +xcrun simctl boot "iPhone 16 Pro" + +# Install the app onto the booted simulator +xcrun simctl install booted build-ios-make/Spaghettify.app + +# Launch the app +xcrun simctl launch booted dev.net64.game +``` + ## Getting CI to work on your fork The CI works via [Github Actions](https://github.com/features/actions) where we mostly make use of machines hosted by Github; except for the very first step of the CI process called "Extract assets". This steps extracts assets from the game file and generates an "assets" folder in `mm/`. diff --git a/ios/Launch.storyboard b/ios/Launch.storyboard new file mode 100644 index 0000000000..31d8dc42b8 --- /dev/null +++ b/ios/Launch.storyboard @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/plist.in b/ios/plist.in new file mode 100644 index 0000000000..3d9990b299 --- /dev/null +++ b/ios/plist.in @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + @PROJECT_NAME@ + CFBundleExecutable + @PROJECT_NAME@ + CFBundleIdentifier + @PROJECT_ID@ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + @PROJECT_NAME@ + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + LSSupportsOpeningDocumentsInPlace + + UIFileSharingEnabled + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchStoryboardName + Launch + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + + + diff --git a/libultraship b/libultraship index 4eadaf990d..09426197f5 160000 --- a/libultraship +++ b/libultraship @@ -1 +1 @@ -Subproject commit 4eadaf990d234148a250234eb44f3b01f6a3af90 +Subproject commit 09426197f53810b3547a7696f0d50349bdbb35a2 diff --git a/src/engine/RaceManager.cpp b/src/engine/RaceManager.cpp index de8ed69842..0e5f5a86d2 100644 --- a/src/engine/RaceManager.cpp +++ b/src/engine/RaceManager.cpp @@ -89,7 +89,10 @@ void RaceManager::BeginPlay() { } } } - gEditor.AddLight("Sun", nullptr, D_800DC610[1].l->l.dir); + + if (gEditor.IsEnabled()) { + gEditor.AddLight("Sun", nullptr, D_800DC610[1].l->l.dir); + } track->BeginPlay(); } diff --git a/src/engine/mods/ModManager.cpp b/src/engine/mods/ModManager.cpp index 137fb97434..b467722981 100644 --- a/src/engine/mods/ModManager.cpp +++ b/src/engine/mods/ModManager.cpp @@ -5,8 +5,10 @@ #include "port/Engine.h" #include "semver.hpp" #include "utils/StringHelper.h" +#include #include #include +#include #include #include "ModManager.h" @@ -64,11 +66,15 @@ void GenerateAssetsMods() { std::vector ListMods() { const std::string main_path = Ship::Context::GetPathRelativeToAppDirectory(game_asset_file); + const std::string main_path_local_app = Ship::Context::LocateFileAcrossAppDirs(game_asset_file); const std::string assets_path = Ship::Context::LocateFileAcrossAppDirs(engine_asset_file); std::vector archiveFiles; + std::set seenArchivePaths; if (std::filesystem::exists(main_path)) { archiveFiles.push_back(main_path); + } else if (std::filesystem::exists(main_path_local_app)) { + archiveFiles.push_back(main_path_local_app); } else { // should not happen, but just in case GenerateAssetsMods(); archiveFiles.push_back(main_path); @@ -89,12 +95,12 @@ std::vector ListMods() { if (std::filesystem::exists(mods_path) && std::filesystem::is_directory(mods_path)) { for (const auto& p : std::filesystem::directory_iterator(mods_path)) { auto ext = p.path().extension().string(); - if (StringHelper::IEquals(ext, ".zip") || StringHelper::IEquals(ext, ".o2r") || std::filesystem::is_directory(p.path())) { + if (StringHelper::IEquals(ext, ".zip") || StringHelper::IEquals(ext, ".o2r") || + std::filesystem::is_directory(p.path())) { archiveFiles.push_back(p.path().generic_string()); } } } - return archiveFiles; } @@ -104,9 +110,8 @@ std::optional> CheckCyclicDependencies() { for (const auto& [metaB, _] : Mods) { if (metaA.name != metaB.name) { // If A depends on B and B depends on A, we have a cycle - if (metaA.dependencies.contains(metaB.name) && - metaB.dependencies.contains(metaA.name)) { - list.push_back(metaA.name + " <-> " + metaB.name); + if (metaA.dependencies.contains(metaB.name) && metaB.dependencies.contains(metaA.name)) { + list.push_back(metaA.name + " <-> " + metaB.name); } } } @@ -126,7 +131,8 @@ std::optional> CheckOutdatedDependencies(const ModMetad found = true; auto range = depVersion.first; if (!range.contains(otherMeta.version)) { - list.push_back(depName + " (required: " + depVersion.second + ", found: " + otherMeta.version.to_string() + ")"); + list.push_back(depName + " (required: " + depVersion.second + + ", found: " + otherMeta.version.to_string() + ")"); } break; } @@ -196,16 +202,18 @@ void AddCoreDependencies() { semver::range_set assetsVer; semver::parse("1.0.0-alpha1", assetsVer); meta.dependencies = { - {"mk64-assets", {mk64Ver, "1.0.0-alpha1"}}, - {"extended-assets", {assetsVer, "1.0.0-alpha1"}}, + { "mk64-assets", { mk64Ver, "1.0.0-alpha1" } }, + { "extended-assets", { assetsVer, "1.0.0-alpha1" } }, }; AddModMetadata(meta, nullptr); } void CheckMK64O2RExists() { const std::string main_path = Ship::Context::GetPathRelativeToAppDirectory(game_asset_file); + const std::string main_path_local_app = Ship::Context::LocateFileAcrossAppDirs(game_asset_file); - if (!std::filesystem::exists(main_path)) { + // some build can prefer have mk64.o2r in the app application + if (!std::filesystem::exists(main_path) && !std::filesystem::exists(main_path_local_app)) { GenerateAssetsMods(); } } @@ -245,7 +253,8 @@ void FindAndLoadMods() { } metadata.name = std::filesystem::path(path).stem().string(); semver::parse("0.0.0", metadata.version); - SPDLOG_WARN("The mod at path {} is missing a mods.toml file. Using default metadata:\n{}", path, metadata.ToString()); + SPDLOG_WARN("The mod at path {} is missing a mods.toml file. Using default metadata:\n{}", path, + metadata.ToString()); } AddModMetadata(metadata, archive); diff --git a/src/engine/mods/ModManager.h b/src/engine/mods/ModManager.h index af0baa2e99..5a87e83630 100644 --- a/src/engine/mods/ModManager.h +++ b/src/engine/mods/ModManager.h @@ -1,4 +1,4 @@ #pragma once void InitModsSystem(); -void UnloadMods(); \ No newline at end of file +void UnloadMods(); diff --git a/src/port/Engine.cpp b/src/port/Engine.cpp index 2cd2c99b9a..6fc5dc4e55 100644 --- a/src/port/Engine.cpp +++ b/src/port/Engine.cpp @@ -269,7 +269,12 @@ bool GameEngine::GenAssetFile() { auto extractor = new GameExtractor(); if (!extractor->SelectGameFromUI()) { +#ifdef __IOS__ + ShowMessage("ROM Required", + "Copy a supported Mario Kart 64 ROM to this app's Documents folder as baserom.us.z64, then relaunch the app."); +#else ShowMessage("Error", "No ROM selected.\n\nExiting..."); +#endif exit(1); } diff --git a/src/port/GameExtractor.cpp b/src/port/GameExtractor.cpp index 4e8b4dfaee..fdbf8196f2 100644 --- a/src/port/GameExtractor.cpp +++ b/src/port/GameExtractor.cpp @@ -30,6 +30,8 @@ std::unordered_map mGameList = { { "579c48e211ae952530ffc8738709f078d5dd215e", "Mario Kart 64 (US)" }, }; +static constexpr const char* kExpectedRomName = "baserom.us.z64"; + bool GameExtractor::SelectGameFromUI() { std::vector roms; GetRoms(roms); @@ -87,14 +89,15 @@ bool GameExtractor::SelectGameFromUI() { romPath = selection[0]; } #else - // Mobile: fallback to baserom.us.z64 - if (!foundGame && !std::filesystem::exists(Ship::Context::GetPathRelativeToAppDirectory("baserom.us.z64"))) { - SPDLOG_ERROR("baserom not found"); + // Mobile: fallback to baserom.us.z64 in the writable app directory. + const auto fallbackRomPath = Ship::Context::GetPathRelativeToAppDirectory(kExpectedRomName); + if (!foundGame && !std::filesystem::exists(fallbackRomPath)) { + SPDLOG_ERROR("{} not found in app Documents", kExpectedRomName); return false; } if (!foundGame) { - romPath = Ship::Context::GetPathRelativeToAppDirectory("baserom.us.z64"); + romPath = fallbackRomPath; } #endif @@ -135,6 +138,23 @@ void GameExtractor::GetRoms(std::vector& roms) { // if (h != nullptr) { // CloseHandle(h); //} +#elif defined(__IOS__) + const auto appDirectory = Ship::Context::GetAppDirectoryPath(); + + if (!std::filesystem::exists(appDirectory)) { + return; + } + + for (const auto& file : std::filesystem::directory_iterator(appDirectory)) { + if (!file.is_regular_file()) { + continue; + } + + const auto extension = file.path().extension().string(); + if (extension == ".z64" || extension == ".n64" || extension == ".v64") { + roms.push_back(file.path().string()); + } + } #elif unix // Open the directory of the app. DIR* d = opendir("."); diff --git a/src/port/SpaghettiGui.cpp b/src/port/SpaghettiGui.cpp index d9c957d3f2..592e6326a7 100644 --- a/src/port/SpaghettiGui.cpp +++ b/src/port/SpaghettiGui.cpp @@ -21,7 +21,7 @@ #endif #if defined(__ANDROID__) || defined(__IOS__) -#include "port/mobile/MobileImpl.h" +#include "ship/port/mobile/MobileImpl.h" #endif #ifdef ENABLE_OPENGL diff --git a/src/port/audio/HMAS.cpp b/src/port/audio/HMAS.cpp index 9d656a206f..6182376e3d 100644 --- a/src/port/audio/HMAS.cpp +++ b/src/port/audio/HMAS.cpp @@ -10,8 +10,8 @@ HMAS::HMAS() { ma_result result; ma_engine_config engine = ma_engine_config_init(); - engine.channels = 2; - engine.noDevice = MA_TRUE; + engine.channels = 2; + engine.noDevice = MA_TRUE; engine.sampleRate = GameEngine_GetSampleRate(); result = ma_engine_init(&engine, &gAudioEngine); @@ -33,7 +33,7 @@ void HMAS::RegisterSound(HMAS_AudioId id, const std::string& filePath, HMAS_Info return; } - if(info.loop.start != -1 && info.loop.end != -1) { + if (info.loop.start != -1 && info.loop.end != -1) { ma_data_source* source = ma_sound_get_data_source(&gRegistry[id].sound); ma_data_source_set_loop_point_in_pcm_frames(source, info.loop.start, info.loop.end); } @@ -48,14 +48,15 @@ void HMAS::RegisterSound(HMAS_AudioId id, uint8_t* data, uint32_t size, HMAS_Inf return; } - ma_decoder_config config = ma_decoder_config_init(ma_format_f32, ma_engine_get_channels(&gAudioEngine), ma_engine_get_sample_rate(&gAudioEngine)); + ma_decoder_config config = ma_decoder_config_init(ma_format_f32, ma_engine_get_channels(&gAudioEngine), + ma_engine_get_sample_rate(&gAudioEngine)); ma_result result = ma_decoder_init_memory(data, size, &config, &gRegistry[id].decoder); if (result != MA_SUCCESS) { SPDLOG_ERROR("Failed to initialize decoder from memory: {}", ma_result_description(result)); return; } - if(info.loop.start != -1 && info.loop.end != -1) { + if (info.loop.start != -1 && info.loop.end != -1) { ma_data_source_set_loop_point_in_pcm_frames(&gRegistry[id].decoder, info.loop.start, info.loop.end); } @@ -72,7 +73,7 @@ void HMAS::RegisterSound(HMAS_AudioId id, uint8_t* data, uint32_t size, HMAS_Inf } void HMAS::Play(HMAS_ChannelId channelId, HMAS_AudioId id, bool loop) { - if(channelId == HMAS_ChannelId::HMAS_MUSIC){ + if (channelId == HMAS_ChannelId::HMAS_MUSIC) { this->Stop(channelId); } @@ -150,8 +151,8 @@ void HMAS::SetPause(HMAS_ChannelId channelId, bool pause) { return; } - if(pause) { - ma_sound_get_cursor_in_pcm_frames(channel->sound, &channel->cursor); + if (pause) { + ma_sound_get_cursor_in_pcm_frames(channel->sound, (ma_uint64*) &channel->cursor); ma_sound_stop(channel->sound); } else { ma_result result = ma_sound_start(channel->sound); @@ -163,9 +164,10 @@ void HMAS::SetPause(HMAS_ChannelId channelId, bool pause) { } } -void HMAS::AddEffect(HMAS_ChannelId channelId, HMAS_EffectType type, HMAS_EffectTransition transition, uint32_t frames, float target) { +void HMAS::AddEffect(HMAS_ChannelId channelId, HMAS_EffectType type, HMAS_EffectTransition transition, uint32_t frames, + float target) { auto& channel = gChannelSound[channelId]; - channel.effects.push_back({type, transition, frames, target}); + channel.effects.push_back({ type, transition, frames, target }); } bool HMAS::IsIDRegistered(HMAS_AudioId id) { @@ -173,7 +175,7 @@ bool HMAS::IsIDRegistered(HMAS_AudioId id) { } void HMAS::ProcessEffects() { - for (size_t i = 0; i < sizeof(gChannelSound) / sizeof(gChannelSound[0]); i++){ + for (size_t i = 0; i < sizeof(gChannelSound) / sizeof(gChannelSound[0]); i++) { auto& channel = gChannelSound[i]; if (channel.sound == nullptr) { @@ -189,14 +191,16 @@ void HMAS::ProcessEffects() { effect.numFrames--; switch (effect.type) { case HMAS_EffectType::HMAS_EFFECT_VOLUME: { - float volume = effect.transition == HMAS_EffectTransition::HMAS_LINEAR ? - Lerp(channel.volume, effect.target, 1.0f / effect.numFrames) : effect.target; + float volume = effect.transition == HMAS_EffectTransition::HMAS_LINEAR + ? Lerp(channel.volume, effect.target, 1.0f / effect.numFrames) + : effect.target; this->SetVolume((HMAS_ChannelId) i, std::max(0.0f, volume)); break; } case HMAS_EffectType::HMAS_EFFECT_PITCH: { - float pitch = effect.transition == HMAS_EffectTransition::HMAS_LINEAR ? - Lerp(channel.pitch, effect.target, 1.0f / effect.numFrames) : effect.target; + float pitch = effect.transition == HMAS_EffectTransition::HMAS_LINEAR + ? Lerp(channel.pitch, effect.target, 1.0f / effect.numFrames) + : effect.target; this->SetPitch((HMAS_ChannelId) i, std::max(0.1f, pitch)); break; } @@ -213,15 +217,17 @@ void HMAS::ProcessEffects() { break; } } else { - SPDLOG_DEBUG("Removing effect: Type={}, Frames={}, Target={}", static_cast(effect.type), effect.numFrames, effect.target); + SPDLOG_DEBUG("Removing effect: Type={}, Frames={}, Target={}", static_cast(effect.type), + effect.numFrames, effect.target); channel.effects.erase(channel.effects.begin()); } } } -void HMAS::CreateBuffer(uint8_t *samples, uint32_t bufferSizeInBytes) { +void HMAS::CreateBuffer(uint8_t* samples, uint32_t bufferSizeInBytes) { this->ProcessEffects(); - ma_uint32 bufferSizeInFrames = bufferSizeInBytes / ma_get_bytes_per_frame(ma_format_f32, ma_engine_get_channels(&gAudioEngine)); + ma_uint32 bufferSizeInFrames = + bufferSizeInBytes / ma_get_bytes_per_frame(ma_format_f32, ma_engine_get_channels(&gAudioEngine)); ma_engine_read_pcm_frames(&gAudioEngine, samples, bufferSizeInFrames, NULL); } @@ -259,10 +265,11 @@ extern "C" void HMAS_SetPause(HMAS_ChannelId channelId, bool pause) { GameEngine::Instance->gHMAS->SetPause(channelId, pause); } -extern "C" void HMAS_AddEffect(HMAS_ChannelId channelId, HMAS_EffectType type, HMAS_EffectTransition transition, uint32_t frames, float target) { +extern "C" void HMAS_AddEffect(HMAS_ChannelId channelId, HMAS_EffectType type, HMAS_EffectTransition transition, + uint32_t frames, float target) { GameEngine::Instance->gHMAS->AddEffect(channelId, type, transition, frames, target); } extern "C" bool HMAS_IsIDRegistered(HMAS_AudioId id) { return GameEngine::Instance->gHMAS->IsIDRegistered(id); -} \ No newline at end of file +} diff --git a/src/port/pak.cpp b/src/port/pak.cpp index 4adedd6a01..eaea257fc0 100644 --- a/src/port/pak.cpp +++ b/src/port/pak.cpp @@ -2,10 +2,6 @@ #include #if __has_include() #include -#define fmt(...) fmt::format(__VA_ARGS__) -#else -#include -#define fmt(...) std::format(__VA_ARGS__) #endif #include @@ -23,7 +19,11 @@ typedef struct ControllerPak { } ControllerPak; std::string Pfs_PakFile_GetPath(u8 file_no) { - return Ship::Context::GetPathRelativeToAppDirectory(fmt("controllerPak_file_{}.sav", file_no)); +#if __has_include() + return Ship::Context::GetPathRelativeToAppDirectory(fmt::format("controllerPak_file_{}.sav", file_no)); +#else + return Ship::Context::GetPathRelativeToAppDirectory("controllerPak_file_" + std::to_string(file_no) + ".sav"); +#endif } std::string Pfs_PakHeader_GetPath() { diff --git a/src/racing/skybox_and_splitscreen.c b/src/racing/skybox_and_splitscreen.c index 825150f831..a8f8c442cd 100644 --- a/src/racing/skybox_and_splitscreen.c +++ b/src/racing/skybox_and_splitscreen.c @@ -687,11 +687,19 @@ void render_screens(ScreenContext* screen, s32 mode, s32 someId, s32 playerId) { } if (CVarGetInteger("gDrawCPPActors", true) == true) { +#ifdef __IOS__ + // These editor-backed objects still crash during race rendering on iOS. +#else CM_DrawActors(camera); +#endif } if (CVarGetInteger("gDrawStaticMeshActors", true) == true) { +#ifdef __IOS__ + // Static mesh actor rendering shares the same crash path on iOS for now. +#else CM_DrawStaticMeshActors(); +#endif } if (CVarGetInteger("gDrawObjects", true) == true) {