Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,65 @@
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": true
}
},
{
"name": "ios-simulator-debug",
"displayName": "iOS Simulator Debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "iOS",
"CMAKE_OSX_SYSROOT": "iphonesimulator",
"CMAKE_OSX_ARCHITECTURES": "arm64",
"CMAKE_BUILD_TYPE": "Debug",
"CMAKE_TOOLCHAIN_FILE": {
"value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"type": "FILEPATH"
},
"VCPKG_TARGET_TRIPLET": {
"value": "arm64-ios-simulator",
"type": "STRING"
}
},
"environment": {
"VCPKG_ROOT": "${sourceDir}/thirdparty/vcpkg"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
},
{
"name": "ios-device-release",
"displayName": "iOS Device Release",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"installDir": "${sourceDir}/out/install/${presetName}",
"cacheVariables": {
"CMAKE_SYSTEM_NAME": "iOS",
"CMAKE_OSX_SYSROOT": "iphoneos",
"CMAKE_OSX_ARCHITECTURES": "arm64",
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_INTERPROCEDURAL_OPTIMIZATION": true,
"CMAKE_TOOLCHAIN_FILE": {
"value": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
"type": "FILEPATH"
},
"VCPKG_TARGET_TRIPLET": {
"value": "arm64-ios",
"type": "STRING"
}
},
"environment": {
"VCPKG_ROOT": "${sourceDir}/thirdparty/vcpkg"
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
}
}
]
}
27 changes: 23 additions & 4 deletions UnleashedRecomp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ elseif (CMAKE_SYSTEM_NAME MATCHES "Linux")
"os/linux/user_linux.cpp"
"os/linux/version_linux.cpp"
)
elseif (APPLE)
set(UNLEASHED_RECOMP_OS_CXX_SOURCES
"os/macos/logger_macos.cpp"
elseif (APPLE)
set(UNLEASHED_RECOMP_OS_CXX_SOURCES
"os/macos/logger_macos.cpp"
"os/macos/media_macos.cpp"
"os/macos/process_macos.cpp"
"os/macos/user_macos.cpp"
Expand Down Expand Up @@ -292,7 +292,26 @@ if (WIN32)
if (${CMAKE_BUILD_TYPE} MATCHES "Release")
target_link_options(UnleashedRecomp PRIVATE "/SUBSYSTEM:WINDOWS" "/ENTRY:mainCRTStartup")
endif()
elseif (APPLE)
elseif (CMAKE_SYSTEM_NAME STREQUAL "iOS")
CreateVersionString(
VERSION_TXT ${VERSION_TXT}
OUTPUT_VAR IOS_BUNDLE_VERSION
)

add_executable(UnleashedRecomp MACOSX_BUNDLE
${UNLEASHED_RECOMP_CXX_SOURCES}
)
set_target_properties(UnleashedRecomp PROPERTIES
OUTPUT_NAME "Unleashed Recompiled"
MACOSX_BUNDLE_GUI_IDENTIFIER hedge-dev.UnleashedRecomp
MACOSX_BUNDLE_BUNDLE_NAME "Unleashed Recompiled"
MACOSX_BUNDLE_BUNDLE_VERSION ${IOS_BUNDLE_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${IOS_BUNDLE_VERSION}
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "hedge-dev.UnleashedRecomp"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
)
elseif (APPLE)
# Create version number for app bundle.
CreateVersionString(
VERSION_TXT ${VERSION_TXT}
Expand Down
12 changes: 9 additions & 3 deletions thirdparty/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ set(SDL2MIXER_OPUS OFF)
set(SDL2MIXER_VORBIS "VORBISFILE")
set(SDL2MIXER_WAVPACK OFF)

if (CMAKE_SYSTEM_NAME MATCHES "Linux")
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
set(SDL_VULKAN_ENABLED ON CACHE BOOL "")
endif()

Expand All @@ -24,12 +24,18 @@ if (WIN32)
endif()

add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/msdf-atlas-gen")
add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/nativefiledialog-extended")
if (CMAKE_SYSTEM_NAME STREQUAL "iOS")
add_library(nfd STATIC "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/nfd_ios_stub.c")
add_library(nfd::nfd ALIAS nfd)
target_include_directories(nfd PUBLIC "${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/nativefiledialog-extended/src/include")
else()
add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/nativefiledialog-extended")
endif()
add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/o1heap")
add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/SDL")
add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/SDL_mixer")
add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/plume")

if (APPLE)
if (APPLE AND NOT CMAKE_SYSTEM_NAME STREQUAL "iOS")
add_subdirectory("${UNLEASHED_RECOMP_THIRDPARTY_ROOT}/MoltenVK")
endif()
42 changes: 42 additions & 0 deletions thirdparty/nfd_ios_stub.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <nfd.h>
#include <stdlib.h>

static const char* g_nfd_ios_error = "Native file dialogs are not implemented for iOS yet.";

nfdresult_t NFD_Init(void) { return NFD_OKAY; }
void NFD_Quit(void) {}
const char* NFD_GetError(void) { return g_nfd_ios_error; }

void NFD_FreePathN(nfdnchar_t* filePath) { free(filePath); }
void NFD_FreePathU8(nfdu8char_t* filePath) { free(filePath); }

nfdresult_t NFD_OpenDialogN(nfdnchar_t** outPath, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, const nfdnchar_t* defaultPath) { (void)outPath; (void)filterList; (void)filterCount; (void)defaultPath; return NFD_ERROR; }
nfdresult_t NFD_OpenDialogU8(nfdu8char_t** outPath, const nfdu8filteritem_t* filterList, nfdfiltersize_t filterCount, const nfdu8char_t* defaultPath) { (void)outPath; (void)filterList; (void)filterCount; (void)defaultPath; return NFD_ERROR; }
nfdresult_t NFD_OpenDialogN_With_Impl(nfdversion_t version, nfdnchar_t** outPath, const nfdopendialognargs_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; }
nfdresult_t NFD_OpenDialogU8_With_Impl(nfdversion_t version, nfdu8char_t** outPath, const nfdopendialogu8args_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; }
nfdresult_t NFD_OpenDialogMultipleN(const nfdpathset_t** outPaths, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, const nfdnchar_t* defaultPath) { (void)outPaths; (void)filterList; (void)filterCount; (void)defaultPath; return NFD_ERROR; }
nfdresult_t NFD_OpenDialogMultipleU8(const nfdpathset_t** outPaths, const nfdu8filteritem_t* filterList, nfdfiltersize_t filterCount, const nfdu8char_t* defaultPath) { (void)outPaths; (void)filterList; (void)filterCount; (void)defaultPath; return NFD_ERROR; }
nfdresult_t NFD_OpenDialogMultipleN_With_Impl(nfdversion_t version, const nfdpathset_t** outPaths, const nfdopendialognargs_t* args) { (void)version; (void)outPaths; (void)args; return NFD_ERROR; }
nfdresult_t NFD_OpenDialogMultipleU8_With_Impl(nfdversion_t version, const nfdpathset_t** outPaths, const nfdopendialogu8args_t* args) { (void)version; (void)outPaths; (void)args; return NFD_ERROR; }
nfdresult_t NFD_SaveDialogN(nfdnchar_t** outPath, const nfdnfilteritem_t* filterList, nfdfiltersize_t filterCount, const nfdnchar_t* defaultPath, const nfdnchar_t* defaultName) { (void)outPath; (void)filterList; (void)filterCount; (void)defaultPath; (void)defaultName; return NFD_ERROR; }
nfdresult_t NFD_SaveDialogU8(nfdu8char_t** outPath, const nfdu8filteritem_t* filterList, nfdfiltersize_t filterCount, const nfdu8char_t* defaultPath, const nfdu8char_t* defaultName) { (void)outPath; (void)filterList; (void)filterCount; (void)defaultPath; (void)defaultName; return NFD_ERROR; }
nfdresult_t NFD_SaveDialogN_With_Impl(nfdversion_t version, nfdnchar_t** outPath, const nfdsavedialognargs_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; }
nfdresult_t NFD_SaveDialogU8_With_Impl(nfdversion_t version, nfdu8char_t** outPath, const nfdsavedialogu8args_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; }
nfdresult_t NFD_PickFolderN(nfdnchar_t** outPath, const nfdnchar_t* defaultPath) { (void)outPath; (void)defaultPath; return NFD_ERROR; }
nfdresult_t NFD_PickFolderU8(nfdu8char_t** outPath, const nfdu8char_t* defaultPath) { (void)outPath; (void)defaultPath; return NFD_ERROR; }
nfdresult_t NFD_PickFolderN_With_Impl(nfdversion_t version, nfdnchar_t** outPath, const nfdpickfoldernargs_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; }
nfdresult_t NFD_PickFolderU8_With_Impl(nfdversion_t version, nfdu8char_t** outPath, const nfdpickfolderu8args_t* args) { (void)version; (void)outPath; (void)args; return NFD_ERROR; }
nfdresult_t NFD_PickFolderMultipleN(const nfdpathset_t** outPaths, const nfdnchar_t* defaultPath) { (void)outPaths; (void)defaultPath; return NFD_ERROR; }
nfdresult_t NFD_PickFolderMultipleU8(const nfdpathset_t** outPaths, const nfdu8char_t* defaultPath) { (void)outPaths; (void)defaultPath; return NFD_ERROR; }
nfdresult_t NFD_PickFolderMultipleN_With_Impl(nfdversion_t version, const nfdpathset_t** outPaths, const nfdpickfoldernargs_t* args) { (void)version; (void)outPaths; (void)args; return NFD_ERROR; }
nfdresult_t NFD_PickFolderMultipleU8_With_Impl(nfdversion_t version, const nfdpathset_t** outPaths, const nfdpickfolderu8args_t* args) { (void)version; (void)outPaths; (void)args; return NFD_ERROR; }
nfdresult_t NFD_PathSet_GetCount(const nfdpathset_t* pathSet, nfdpathsetsize_t* count) { (void)pathSet; if (count) *count = 0; return NFD_OKAY; }
nfdresult_t NFD_PathSet_GetPathN(const nfdpathset_t* pathSet, nfdpathsetsize_t index, nfdnchar_t** outPath) { (void)pathSet; (void)index; (void)outPath; return NFD_ERROR; }
nfdresult_t NFD_PathSet_GetPathU8(const nfdpathset_t* pathSet, nfdpathsetsize_t index, nfdu8char_t** outPath) { (void)pathSet; (void)index; (void)outPath; return NFD_ERROR; }
void NFD_PathSet_FreePathN(const nfdnchar_t* filePath) { free((void*)filePath); }
void NFD_PathSet_FreePathU8(const nfdu8char_t* filePath) { free((void*)filePath); }
void NFD_PathSet_Free(const nfdpathset_t* pathSet) { (void)pathSet; }
void NFD_PathSet_FreeEnum(nfdpathsetenum_t* enumerator) { (void)enumerator; }
nfdresult_t NFD_PathSet_GetEnum(const nfdpathset_t* pathSet, nfdpathsetenum_t** outEnumerator) { (void)pathSet; (void)outEnumerator; return NFD_ERROR; }
nfdresult_t NFD_PathSet_EnumNextN(nfdpathsetenum_t* enumerator, nfdnchar_t** outPath) { (void)enumerator; (void)outPath; return NFD_ERROR; }
nfdresult_t NFD_PathSet_EnumNextU8(nfdpathsetenum_t* enumerator, nfdu8char_t** outPath) { (void)enumerator; (void)outPath; return NFD_ERROR; }
23 changes: 23 additions & 0 deletions tools/apply_ios_submodule_patches.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
PLUME_DIR="$ROOT/thirdparty/plume"
PATCH_FILE="$ROOT/tools/patches/plume-ios-sdl-vulkan.patch"

if [[ ! -d "$PLUME_DIR/.git" && ! -f "$PLUME_DIR/.git" ]]; then
printf 'Missing Plume submodule. Run: git submodule update --init --recursive\n' >&2
exit 1
fi

if git -C "$PLUME_DIR" apply --check "$PATCH_FILE" >/dev/null 2>&1; then
git -C "$PLUME_DIR" apply "$PATCH_FILE"
printf 'Applied Plume iOS patch.\n'
else
if git -C "$PLUME_DIR" apply --reverse --check "$PATCH_FILE" >/dev/null 2>&1; then
printf 'Plume iOS patch already applied.\n'
else
printf 'Plume iOS patch cannot be applied cleanly. Check thirdparty/plume for local changes.\n' >&2
exit 1
fi
fi
70 changes: 70 additions & 0 deletions tools/package_ios_ipa.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
BUILD_DIR="$ROOT/out/build/ios-device-release"
IPA_DIR="$ROOT/out/ipa"
PAYLOAD_DIR="$IPA_DIR/Payload"
APP_PATH="$BUILD_DIR/Unleashed Recompiled.app"
IPA_PATH="$IPA_DIR/UnleashedRecompiled.ipa"
DEFAULT_SIGNING_IDENTITY="Apple Development: aroblesalago@gmail.com (MK28YRUCCG)"

"$ROOT/tools/apply_ios_submodule_patches.sh"

missing=0
for file in \
"$ROOT/UnleashedRecompLib/private/default.xex" \
"$ROOT/UnleashedRecompLib/private/default.xexp" \
"$ROOT/UnleashedRecompLib/private/shader.ar"; do
if [[ ! -f "$file" ]]; then
printf 'Missing required file: %s\n' "$file" >&2
missing=1
fi
done

if [[ "$missing" -ne 0 ]]; then
printf '\nAdd these files from your own compatible Sonic Unleashed Xbox 360 dump before building an IPA.\n' >&2
exit 1
fi

cmake --preset ios-device-release
cmake --build "$BUILD_DIR" --target UnleashedRecomp -j "${JOBS:-8}"

if [[ ! -d "$APP_PATH" ]]; then
printf 'Expected app bundle was not produced: %s\n' "$APP_PATH" >&2
exit 1
fi

rm -rf "$IPA_DIR"
mkdir -p "$PAYLOAD_DIR"
cp -R "$APP_PATH" "$PAYLOAD_DIR/"

if [[ -z "${CODESIGN_IDENTITY:-}" ]] && security find-identity -v -p codesigning | grep -q "$DEFAULT_SIGNING_IDENTITY"; then
CODESIGN_IDENTITY="$DEFAULT_SIGNING_IDENTITY"
fi

if [[ -n "${MOBILEPROVISION:-}" && ! -f "${MOBILEPROVISION:-}" ]]; then
printf 'Provisioning profile not found: %s\n' "$MOBILEPROVISION" >&2
exit 1
fi

if [[ -n "${CODESIGN_IDENTITY:-}" ]]; then
if [[ -z "${MOBILEPROVISION:-}" ]]; then
printf 'CODESIGN_IDENTITY is set, but MOBILEPROVISION is not. Producing an unsigned IPA for sideloading tools that sign on import.\n' >&2
else
cp "$MOBILEPROVISION" "$PAYLOAD_DIR/Unleashed Recompiled.app/embedded.mobileprovision"
fi
fi

if [[ -n "${CODESIGN_IDENTITY:-}" && -n "${MOBILEPROVISION:-}" ]]; then
codesign --force --sign "$CODESIGN_IDENTITY" \
${ENTITLEMENTS:+--entitlements "$ENTITLEMENTS"} \
"$PAYLOAD_DIR/Unleashed Recompiled.app"
fi

(
cd "$IPA_DIR"
zip -qry "$IPA_PATH" Payload
)

printf '%s\n' "$IPA_PATH"
72 changes: 72 additions & 0 deletions tools/patches/plume-ios-sdl-vulkan.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6a7645a..bf336b9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,10 +8,16 @@ if(APPLE)
endif()

string(COMPARE EQUAL ${CMAKE_SYSTEM_NAME} "Linux" IS_LINUX)
+string(COMPARE EQUAL ${CMAKE_SYSTEM_NAME} "iOS" IS_IOS)
+if(IS_LINUX OR IS_IOS)
+ set(IS_SDL_VULKAN_PLATFORM ON)
+else()
+ set(IS_SDL_VULKAN_PLATFORM OFF)
+endif()

# Project options
include(CMakeDependentOption)
-cmake_dependent_option(SDL_VULKAN_ENABLED "Enable SDL Vulkan integration" OFF IS_LINUX OFF)
+cmake_dependent_option(SDL_VULKAN_ENABLED "Enable SDL Vulkan integration" OFF IS_SDL_VULKAN_PLATFORM OFF)
cmake_dependent_option(D3D12_AGILITY_SDK_ENABLED "Enable D3D12 Agility SDK" OFF WIN32 OFF)
option(PLUME_BUILD_EXAMPLES "Build example applications" OFF)

diff --git a/plume_vulkan.cpp b/plume_vulkan.cpp
index 9103ca8..02c3591 100644
--- a/plume_vulkan.cpp
+++ b/plume_vulkan.cpp
@@ -2112,6 +2112,12 @@ namespace plume {
fprintf(stderr, "vkCreateXlibSurfaceKHR failed with error code 0x%X.\n", res);
return;
}
+# elif defined(__APPLE__) && defined(SDL_VULKAN_ENABLED)
+ VulkanInterface *renderInterface = commandQueue->device->renderInterface;
+ if (!SDL_Vulkan_CreateSurface(renderWindow, renderInterface->instance, &surface)) {
+ fprintf(stderr, "SDL_Vulkan_CreateSurface failed: %s.\n", SDL_GetError());
+ return;
+ }
# elif defined(__APPLE__)
assert(renderWindow.window != 0);
assert(renderWindow.view != 0);
@@ -2443,7 +2449,7 @@ namespace plume {
// The attributes width and height members do not include the border.
dstWidth = attributes.width;
dstHeight = attributes.height;
-# elif defined(__APPLE__)
+# elif defined(__APPLE__) && !defined(SDL_VULKAN_ENABLED)
CocoaWindowAttributes attributes;
windowWrapper->getWindowAttributes(&attributes);
dstWidth = attributes.width;
diff --git a/plume_vulkan.h b/plume_vulkan.h
index 73022bb..9d89adf 100644
--- a/plume_vulkan.h
+++ b/plume_vulkan.h
@@ -22,8 +22,10 @@
#define VK_USE_PLATFORM_XLIB_KHR
#elif defined(__APPLE__)
#define VK_USE_PLATFORM_METAL_EXT
+#ifndef SDL_VULKAN_ENABLED
#include "plume_apple.h"
#endif
+#endif

// For VK_KHR_portability_subset
#define VK_ENABLE_BETA_EXTENSIONS
@@ -226,7 +228,7 @@ namespace plume {
VulkanCommandQueue *commandQueue = nullptr;
VkSurfaceKHR surface = VK_NULL_HANDLE;
RenderWindow renderWindow = {};
-#if defined(__APPLE__)
+#if defined(__APPLE__) && !defined(SDL_VULKAN_ENABLED)
std::unique_ptr<CocoaWindow> windowWrapper;
#endif
uint32_t textureCount = 0;