diff --git a/include/zelda_support.h b/include/zelda_support.h index 4e138a7..9751e9a 100644 --- a/include/zelda_support.h +++ b/include/zelda_support.h @@ -12,7 +12,8 @@ namespace zelda64 { // Apple specific methods that usually require Objective-C. Implemented in support_apple.mm. #ifdef __APPLE__ void dispatch_on_ui_thread(std::function func); - const char* get_bundle_resource_directory(); + std::filesystem::path get_bundle_resource_directory(); + std::filesystem::path get_bundle_directory(); #endif } diff --git a/src/game/config.cpp b/src/game/config.cpp index 3681b7b..7f6c614 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -2,6 +2,7 @@ #include "recomp_input.h" #include "zelda_sound.h" #include "zelda_render.h" +#include "zelda_support.h" #include "ultramodern/config.hpp" #include "librecomp/files.hpp" #include @@ -136,6 +137,14 @@ std::filesystem::path zelda64::get_app_folder_path() { return std::filesystem::current_path(); } +#if defined(__APPLE__) + // Check for portable file in the directory containing the app bundle. + const auto app_bundle_path = zelda64::get_bundle_directory().parent_path(); + if (std::filesystem::exists(app_bundle_path / "portable.txt")) { + return app_bundle_path; + } +#endif + std::filesystem::path recomp_dir{}; #if defined(_WIN32) diff --git a/src/main/support.cpp b/src/main/support.cpp index 3bad3fd..dee5f5d 100644 --- a/src/main/support.cpp +++ b/src/main/support.cpp @@ -25,9 +25,7 @@ namespace zelda64 { std::filesystem::path get_asset_path(const char* asset) { std::filesystem::path base_path = ""; #if defined(__APPLE__) - const char* resource_dir = get_bundle_resource_directory(); - base_path = resource_dir; - free((void*)resource_dir); + base_path = get_bundle_resource_directory(); #endif return base_path / "assets" / asset; diff --git a/src/main/support_apple.mm b/src/main/support_apple.mm index 67b56f8..f033131 100644 --- a/src/main/support_apple.mm +++ b/src/main/support_apple.mm @@ -1,4 +1,5 @@ #include "zelda_support.h" +#include #import #import #import @@ -12,9 +13,41 @@ namespace zelda64 { }); } - const char* get_bundle_resource_directory() { + std::filesystem::path get_bundle_resource_directory() { NSString *bundlePath = [[NSBundle mainBundle] resourcePath]; - return strdup([bundlePath UTF8String]); + return std::filesystem::path([bundlePath UTF8String]); + } + + std::filesystem::path get_bundle_directory() { + NSURL *bundleUrl = [[NSBundle mainBundle] bundleURL]; + + // The OS may relocate the app elsewhere for security reasons if, for example, + // it was downloaded and opened from the Downloads folder. In this case, we need + // to untranslocate the path to find out where the actual app bundle is. + if (void* securityHandle = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY)) { + using IsTranslocatedURLFunc = Boolean (*)(CFURLRef path, bool* isTranslocated, + CFErrorRef* __nullable error); + using CreateOriginalPathForURLFunc = CFURLRef __nullable (*)(CFURLRef translocatedPath, + CFErrorRef* __nullable error); + + const auto IsTranslocatedURL = reinterpret_cast( + dlsym(securityHandle, "SecTranslocateIsTranslocatedURL")); + const auto CreateOriginalPathForURL = reinterpret_cast( + dlsym(securityHandle, "SecTranslocateCreateOriginalPathForURL")); + + bool translocated = false; + if (IsTranslocatedURL && CreateOriginalPathForURL && + IsTranslocatedURL((__bridge CFURLRef) bundleUrl, &translocated, nullptr) && translocated) { + CFURLRef untranslocated = CreateOriginalPathForURL((__bridge CFURLRef) bundleUrl, nullptr); + if (untranslocated) { + bundleUrl = (NSURL*) untranslocated; + } + } + + dlclose(securityHandle); + } + + return std::filesystem::path([bundleUrl fileSystemRepresentation]); } }