diff --git a/.github/workflows/compile_macos.yml b/.github/workflows/compile_macos.yml index e4d7e848e..67509d7e1 100644 --- a/.github/workflows/compile_macos.yml +++ b/.github/workflows/compile_macos.yml @@ -10,8 +10,16 @@ on: jobs: compile_macos: - name: Build MacOS Binaries + name: Build MacOS ${{ matrix.arch }} Binaries runs-on: macos-latest + strategy: + fail-fast: false + matrix: + include: + - arch: x64 + rid: osx-x64 + - arch: arm64 + rid: osx-arm64 steps: - uses: actions/checkout@v6 - name: Setup dotnet @@ -20,24 +28,24 @@ jobs: dotnet-version: 9.0.x - name: Restore Dependencies - run: dotnet restore + run: dotnet restore -p:Platform="${{ matrix.arch }}" -p:ForceRID="${{ matrix.rid }}" - - name: Build Debug for osx-x64 + - name: Build Debug for ${{ matrix.rid }} if: inputs.SKIP_DEBUG != true - run: dotnet build --no-restore -c Debug -p:Platform="x64" -p:Version="${{ inputs.VERSION }}" + run: dotnet build --no-restore -c Debug -p:Platform="${{ matrix.arch }}" -p:ForceRID="${{ matrix.rid }}" -p:Version="${{ inputs.VERSION }}" - - name: Upload Debug for osx-x64 + - name: Upload Debug for ${{ matrix.rid }} if: inputs.SKIP_DEBUG != true uses: actions/upload-artifact@v7 with: - name: MelonLoader.macOS.x64.CI.Debug - path: Output/Debug/osx-x64 + name: MelonLoader.macOS.${{ matrix.arch }}.CI.Debug + path: Output/Debug/${{ matrix.rid }} - - name: Build Release for osx-x64 - run: dotnet build --no-restore -c Release -p:Platform="x64" -p:Version="${{ inputs.VERSION }}" + - name: Build Release for ${{ matrix.rid }} + run: dotnet build --no-restore -c Release -p:Platform="${{ matrix.arch }}" -p:ForceRID="${{ matrix.rid }}" -p:Version="${{ inputs.VERSION }}" - - name: Upload Release for osx-x64 + - name: Upload Release for ${{ matrix.rid }} uses: actions/upload-artifact@v7 with: - name: MelonLoader.macOS.x64.CI.Release - path: Output/Release/osx-x64 \ No newline at end of file + name: MelonLoader.macOS.${{ matrix.arch }}.CI.Release + path: Output/Release/${{ matrix.rid }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec4a8625a..b87b0509f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,9 @@ on: tags: - '*.*.*' +permissions: + contents: write + jobs: fix_tag_version: name: Fix Tag Version @@ -36,6 +39,8 @@ jobs: upload: name: Upload needs: [ build, fix_tag_version ] + permissions: + contents: write uses: ./.github/workflows/upload_all.yml with: TAG: ${{ github.ref_name }} @@ -43,4 +48,4 @@ jobs: VERSION: ${{ needs.fix_tag_version.outputs.clean_version }} PACKAGE_VERSION: ${{ needs.fix_tag_version.outputs.clean_version }} PACKAGE_NAME: MelonLoader - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/.github/workflows/upload_all.yml b/.github/workflows/upload_all.yml index ca731e4d6..9c29c9892 100644 --- a/.github/workflows/upload_all.yml +++ b/.github/workflows/upload_all.yml @@ -20,6 +20,8 @@ on: jobs: upload_github: name: GitHub + permissions: + contents: write uses: ./.github/workflows/upload_github.yml with: TAG: ${{ inputs.TAG }} @@ -37,4 +39,4 @@ jobs: with: VERSION: ${{ inputs.PACKAGE_VERSION }} PACKAGE_NAME: ${{ inputs.PACKAGE_NAME }} - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/.github/workflows/upload_github.yml b/.github/workflows/upload_github.yml index 7a24d72fc..f5fcc4d4d 100644 --- a/.github/workflows/upload_github.yml +++ b/.github/workflows/upload_github.yml @@ -12,6 +12,8 @@ jobs: upload_github: name: Upload GitHub Release runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: actions/checkout@v6 @@ -54,6 +56,16 @@ jobs: run: | cd compiled_release/macos_x64 zip -r ../MelonLoader.macOS.x64.zip ./ + + - name: Download MacOS arm64 Artifact + uses: actions/download-artifact@v8 + with: + name: MelonLoader.macOS.arm64.CI.Release + path: ./compiled_release/macos_arm64 + - name: Package MacOS arm64 into Zip Archive + run: | + cd compiled_release/macos_arm64 + zip -r ../MelonLoader.macOS.arm64.zip ./ - name: Download MelonLoader Installer run: | @@ -65,13 +77,15 @@ jobs: uses: softprops/action-gh-release@v3 with: body_path: RELEASE-NOTES.md - tag: ${{ inputs.TAG }} - release_name: ${{ inputs.RELEASE_NAME }} + tag_name: ${{ inputs.TAG }} + name: ${{ inputs.RELEASE_NAME }} + overwrite_files: true files: | compiled_release/MelonLoader.x86.zip compiled_release/MelonLoader.x64.zip compiled_release/MelonLoader.Linux.x64.zip compiled_release/MelonLoader.macOS.x64.zip + compiled_release/MelonLoader.macOS.arm64.zip compiled_release/MelonLoader.Installer.exe compiled_release/MelonLoader.Installer.Linux compiled_release/MelonLoader.Installer.MacOS.dmg diff --git a/Directory.Build.props b/Directory.Build.props index 68aa6f4b4..04bb8f05b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,8 +27,8 @@ false Debug;Release - x86;x64 - win-x86;win-x64;linux-x64;osx-x64 + x86;x64;arm64 + win-x86;win-x64;linux-x64;osx-x64;osx-arm64 false false @@ -109,6 +109,9 @@ $(DefineConstants);X64 + + $(DefineConstants);ARM64 + $(SolutionDir)Output/$(Configuration)/$(RuntimeIdentifier) diff --git a/MelonLoader.Bootstrap/Deps/osx-arm64/libdobby.a b/MelonLoader.Bootstrap/Deps/osx-arm64/libdobby.a new file mode 100644 index 000000000..4f6999e1b Binary files /dev/null and b/MelonLoader.Bootstrap/Deps/osx-arm64/libdobby.a differ diff --git a/MelonLoader.Bootstrap/Deps/osx-arm64/libplthook.a b/MelonLoader.Bootstrap/Deps/osx-arm64/libplthook.a new file mode 100644 index 000000000..d46385b0f Binary files /dev/null and b/MelonLoader.Bootstrap/Deps/osx-arm64/libplthook.a differ diff --git a/MelonLoader.Bootstrap/Deps/osx-x64/libplthook.a b/MelonLoader.Bootstrap/Deps/osx-x64/libplthook.a index 9240b35c5..91ba75d5f 100644 Binary files a/MelonLoader.Bootstrap/Deps/osx-x64/libplthook.a and b/MelonLoader.Bootstrap/Deps/osx-x64/libplthook.a differ diff --git a/MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj b/MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj index 779890cae..ea9545dd3 100644 --- a/MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj +++ b/MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj @@ -45,6 +45,11 @@ triggers MelonLoader on macOS, because nothing in the managed code references it. --> + + + + + + + + + /dev/null 2>&1; then + return + fi + + local log_path="$SCRIPT_DIR/MelonLoader/Latest.log" + local title="MelonLoader - $(basename "$APP" .app)" + local watcher_base watcher_script + watcher_base="$(mktemp "${TMPDIR:-/tmp}/melonloader-log-tail.XXXXXX")" || return + watcher_script="$watcher_base.command" + mv "$watcher_base" "$watcher_script" || { + rm -f "$watcher_base" + return + } + + local quoted_log_path quoted_title + quoted_log_path="$(shell_quote "$log_path")" + quoted_title="$(shell_quote "$title")" + + cat > "$watcher_script" </dev/null; do + sleep 0.2 +done + +if [ -f "\$LOG_PATH" ]; then + tail -n +1 -F "\$LOG_PATH" & + TAIL_PID=\$! + while kill -0 "\$GAME_PID" 2>/dev/null; do + sleep 1 + done + kill "\$TAIL_PID" 2>/dev/null || true + wait "\$TAIL_PID" 2>/dev/null || true +else + echo "MelonLoader log was not created." +fi + +rm -f "\$0" +echo +echo "Game exited. You can close this window." +EOF + + chmod +x "$watcher_script" + open -a Terminal "$watcher_script" >/dev/null 2>&1 || true +} + # Find the one .app bundle next to this script. shopt -s nullglob APPS=("$SCRIPT_DIR"/*.app) @@ -78,4 +142,6 @@ else export DYLD_INSERT_LIBRARIES="$BOOTSTRAP_PATH" fi +open_melonloader_terminal + exec "$@" diff --git a/MelonLoader.Bootstrap/OSXEntry/osxentry.cpp b/MelonLoader.Bootstrap/OSXEntry/osxentry.cpp index 99dc0b150..8239e76e3 100644 --- a/MelonLoader.Bootstrap/OSXEntry/osxentry.cpp +++ b/MelonLoader.Bootstrap/OSXEntry/osxentry.cpp @@ -1,9 +1,17 @@ #include +#include +#include +#include +#include extern "C" { void Init(); int SetRlimitHook(int resource, rlimit* rlp); + void MLRegisterDlsymHook(void* detour); + void* MLRealDlsym(void* handle, const char* symbol); + int MLMacOSJitCopy(void* dst, const void* src, size_t len); + void* DlsymHook(void* handle, const char* symbol); } #define DYLD_INTERPOSE(_replacement,_replacee) \ @@ -18,3 +26,63 @@ int SetRlimitHook(int resource, rlimit* rlp) } DYLD_INTERPOSE(SetRlimitHook, setrlimit) + +// Detour registered by the managed bootstrap (ModuleSymbolRedirect.Attach). +// Receives the symbol AND its already-resolved real address, and returns either +// MelonLoader's hook (for the runtime entry points) or the real address. +typedef void* (*DlsymDetourFn)(void* handle, const char* symbol, void* real); +static DlsymDetourFn g_dlsymDetour = nullptr; + +void MLRegisterDlsymHook(void* detour) +{ + g_dlsymDetour = (DlsymDetourFn)detour; +} + +// The real dlsym, exposed to managed code. dyld does not interpose the image +// that declares the interpose, so this call resolves the genuine export. The +// managed side MUST use this (not its own dlsym P/Invoke, whose GOT slot IS +// interposed) so MelonLoader resolves the real mono/il2cpp exports rather than +// its own detours -- otherwise the detours call themselves and recurse. +void* MLRealDlsym(void* handle, const char* symbol) +{ + return dlsym(handle, symbol); +} + +int MLMacOSJitCopy(void* dst, const void* src, size_t len) +{ +#if defined(__APPLE__) && defined(__arm64__) + if (!pthread_jit_write_protect_supported_np()) + return 0; + if (len == 0) + return 1; + if (dst == nullptr || src == nullptr) + return 0; + + pthread_jit_write_protect_np(0); + memcpy(dst, src, len); + sys_icache_invalidate(dst, len); + pthread_jit_write_protect_np(1); + return 1; +#else + return 0; +#endif +} + +// The game's engine (UnityPlayer.dylib) resolves the mono/il2cpp entry points +// via dlsym, so we interpose dlsym to return MelonLoader's detours for them. +// plthook cannot patch UnityPlayer.dylib's GOT on modern macOS +// (LC_DYLD_CHAINED_FIXUPS); DYLD interpose is the mechanism that reliably works +// here (it is what triggers Init via setrlimit). Only runtime-entry symbols +// (mono_/il2cpp_) are handed to managed code -- routing every dlsym in the +// process (e.g. AppKit's during startup) through a managed callback is needless +// and unsafe. +void* DlsymHook(void* handle, const char* symbol) +{ + void* real = dlsym(handle, symbol); + if (g_dlsymDetour != nullptr && symbol != nullptr && + (strncmp(symbol, "mono_", 5) == 0 || strncmp(symbol, "il2cpp_", 7) == 0)) + return g_dlsymDetour(handle, symbol, real); + return real; +} + +DYLD_INTERPOSE(DlsymHook, dlsym) diff --git a/MelonLoader.Bootstrap/osxexports.def b/MelonLoader.Bootstrap/osxexports.def index 8eaf59df6..39860ab74 100644 --- a/MelonLoader.Bootstrap/osxexports.def +++ b/MelonLoader.Bootstrap/osxexports.def @@ -10,3 +10,6 @@ _MonoGetDomainPtr _MonoGetRuntimeHandle _IsConsoleOpen _GetLoaderConfig +_MLRegisterDlsymHook +_MLRealDlsym +_MLMacOSJitCopy diff --git a/MelonLoader.sln b/MelonLoader.sln index 31574dbbf..c457cd876 100644 --- a/MelonLoader.sln +++ b/MelonLoader.sln @@ -70,118 +70,174 @@ Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 Debug|x64 = Debug|x64 + Debug|arm64 = Debug|arm64 Release|x86 = Release|x86 Release|x64 = Release|x64 + Release|arm64 = Release|arm64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Debug|x86.ActiveCfg = Debug|x86 {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Debug|x86.Build.0 = Debug|x86 {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Debug|x64.ActiveCfg = Debug|x64 + {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Debug|arm64.ActiveCfg = Debug|arm64 {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Debug|x64.Build.0 = Debug|x64 + {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Debug|arm64.Build.0 = Debug|arm64 {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Release|x86.ActiveCfg = Release|x86 {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Release|x86.Build.0 = Release|x86 {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Release|x64.ActiveCfg = Release|x64 + {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Release|arm64.ActiveCfg = Release|arm64 {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Release|x64.Build.0 = Release|x64 + {10CC7C0B-E7BC-43CC-BCE1-B0181337CCF5}.Release|arm64.Build.0 = Release|arm64 {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Debug|x64.ActiveCfg = Debug|x64 + {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Debug|arm64.ActiveCfg = Debug|arm64 {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Debug|x64.Build.0 = Debug|x64 + {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Debug|arm64.Build.0 = Debug|arm64 {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Debug|x86.ActiveCfg = Debug|x86 {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Debug|x86.Build.0 = Debug|x86 {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Release|x64.ActiveCfg = Release|x64 + {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Release|arm64.ActiveCfg = Release|arm64 {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Release|x64.Build.0 = Release|x64 + {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Release|arm64.Build.0 = Release|arm64 {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Release|x86.ActiveCfg = Release|x86 {3AF58371-2E0A-4256-B3A3-C100DB07E599}.Release|x86.Build.0 = Release|x86 {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Debug|x64.ActiveCfg = Debug|x64 + {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Debug|arm64.ActiveCfg = Debug|arm64 {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Debug|x64.Build.0 = Debug|x64 + {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Debug|arm64.Build.0 = Debug|arm64 {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Debug|x86.ActiveCfg = Debug|x86 {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Debug|x86.Build.0 = Debug|x86 {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Release|x64.ActiveCfg = Release|x64 + {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Release|arm64.ActiveCfg = Release|arm64 {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Release|x64.Build.0 = Release|x64 + {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Release|arm64.Build.0 = Release|arm64 {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Release|x86.ActiveCfg = Release|x86 {4BEAF9B5-F780-414B-8F2E-59DA4A91BE50}.Release|x86.Build.0 = Release|x86 {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Debug|x64.ActiveCfg = Debug|x64 + {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Debug|arm64.ActiveCfg = Debug|arm64 {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Debug|x64.Build.0 = Debug|x64 + {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Debug|arm64.Build.0 = Debug|arm64 {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Debug|x86.ActiveCfg = Debug|x86 {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Debug|x86.Build.0 = Debug|x86 {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Release|x64.ActiveCfg = Release|x64 + {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Release|arm64.ActiveCfg = Release|arm64 {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Release|x64.Build.0 = Release|x64 + {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Release|arm64.Build.0 = Release|arm64 {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Release|x86.ActiveCfg = Release|x86 {A6452A3F-4BD6-497A-97DA-24F7DF97B234}.Release|x86.Build.0 = Release|x86 {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Debug|x64.ActiveCfg = Debug|x64 + {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Debug|arm64.ActiveCfg = Debug|arm64 {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Debug|x64.Build.0 = Debug|x64 + {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Debug|arm64.Build.0 = Debug|arm64 {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Debug|x86.ActiveCfg = Debug|x86 {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Debug|x86.Build.0 = Debug|x86 {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Release|x64.ActiveCfg = Release|x64 + {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Release|arm64.ActiveCfg = Release|arm64 {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Release|x64.Build.0 = Release|x64 + {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Release|arm64.Build.0 = Release|arm64 {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Release|x86.ActiveCfg = Release|x86 {4FDDBEC0-9C20-456E-8906-77E9CD39C3CA}.Release|x86.Build.0 = Release|x86 {F9700790-414B-431B-9F9C-1D9210FAD682}.Debug|x64.ActiveCfg = Debug|x64 + {F9700790-414B-431B-9F9C-1D9210FAD682}.Debug|arm64.ActiveCfg = Debug|arm64 {F9700790-414B-431B-9F9C-1D9210FAD682}.Debug|x64.Build.0 = Debug|x64 + {F9700790-414B-431B-9F9C-1D9210FAD682}.Debug|arm64.Build.0 = Debug|arm64 {F9700790-414B-431B-9F9C-1D9210FAD682}.Debug|x86.ActiveCfg = Debug|x86 {F9700790-414B-431B-9F9C-1D9210FAD682}.Debug|x86.Build.0 = Debug|x86 {F9700790-414B-431B-9F9C-1D9210FAD682}.Release|x64.ActiveCfg = Release|x64 + {F9700790-414B-431B-9F9C-1D9210FAD682}.Release|arm64.ActiveCfg = Release|arm64 {F9700790-414B-431B-9F9C-1D9210FAD682}.Release|x64.Build.0 = Release|x64 + {F9700790-414B-431B-9F9C-1D9210FAD682}.Release|arm64.Build.0 = Release|arm64 {F9700790-414B-431B-9F9C-1D9210FAD682}.Release|x86.ActiveCfg = Release|x86 {F9700790-414B-431B-9F9C-1D9210FAD682}.Release|x86.Build.0 = Release|x86 {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Debug|x64.ActiveCfg = Debug|x64 + {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Debug|arm64.ActiveCfg = Debug|arm64 {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Debug|x64.Build.0 = Debug|x64 + {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Debug|arm64.Build.0 = Debug|arm64 {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Debug|x86.ActiveCfg = Debug|x86 {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Debug|x86.Build.0 = Debug|x86 {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Release|x64.ActiveCfg = Release|x64 + {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Release|arm64.ActiveCfg = Release|arm64 {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Release|x64.Build.0 = Release|x64 + {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Release|arm64.Build.0 = Release|arm64 {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Release|x86.ActiveCfg = Release|x86 {60D688E3-ECE0-43A6-9922-9D1D3B8C07CC}.Release|x86.Build.0 = Release|x86 {762D7545-6F6B-441A-B040-49CC31A1713B}.Debug|x64.ActiveCfg = Debug|x64 + {762D7545-6F6B-441A-B040-49CC31A1713B}.Debug|arm64.ActiveCfg = Debug|arm64 {762D7545-6F6B-441A-B040-49CC31A1713B}.Debug|x86.ActiveCfg = Debug|x86 {762D7545-6F6B-441A-B040-49CC31A1713B}.Release|x64.ActiveCfg = Release|x64 + {762D7545-6F6B-441A-B040-49CC31A1713B}.Release|arm64.ActiveCfg = Release|arm64 {762D7545-6F6B-441A-B040-49CC31A1713B}.Release|x86.ActiveCfg = Release|x86 {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Debug|x64.ActiveCfg = Debug|x64 + {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Debug|arm64.ActiveCfg = Debug|arm64 {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Debug|x64.Build.0 = Debug|x64 + {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Debug|arm64.Build.0 = Debug|arm64 {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Debug|x86.ActiveCfg = Debug|x86 {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Debug|x86.Build.0 = Debug|x86 {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Release|x64.ActiveCfg = Release|x64 + {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Release|arm64.ActiveCfg = Release|arm64 {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Release|x64.Build.0 = Release|x64 + {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Release|arm64.Build.0 = Release|arm64 {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Release|x86.ActiveCfg = Release|x86 {542EC51D-E480-4802-B5AA-96EEC05AF19C}.Release|x86.Build.0 = Release|x86 {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Debug|x64.ActiveCfg = Debug|x64 + {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Debug|arm64.ActiveCfg = Debug|arm64 {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Debug|x64.Build.0 = Debug|x64 + {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Debug|arm64.Build.0 = Debug|arm64 {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Debug|x86.ActiveCfg = Debug|x86 {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Debug|x86.Build.0 = Debug|x86 {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Release|x64.ActiveCfg = Release|x64 + {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Release|arm64.ActiveCfg = Release|arm64 {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Release|x64.Build.0 = Release|x64 + {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Release|arm64.Build.0 = Release|arm64 {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Release|x86.ActiveCfg = Release|x86 {15CFF766-420B-47EF-9E4A-E73D35A4AB44}.Release|x86.Build.0 = Release|x86 {1DB3679C-DCCA-492D-A725-75604A379C7A}.Debug|x64.ActiveCfg = Debug|x64 + {1DB3679C-DCCA-492D-A725-75604A379C7A}.Debug|arm64.ActiveCfg = Debug|arm64 {1DB3679C-DCCA-492D-A725-75604A379C7A}.Debug|x64.Build.0 = Debug|x64 + {1DB3679C-DCCA-492D-A725-75604A379C7A}.Debug|arm64.Build.0 = Debug|arm64 {1DB3679C-DCCA-492D-A725-75604A379C7A}.Debug|x86.ActiveCfg = Debug|x86 {1DB3679C-DCCA-492D-A725-75604A379C7A}.Debug|x86.Build.0 = Debug|x86 {1DB3679C-DCCA-492D-A725-75604A379C7A}.Release|x64.ActiveCfg = Release|x64 + {1DB3679C-DCCA-492D-A725-75604A379C7A}.Release|arm64.ActiveCfg = Release|arm64 {1DB3679C-DCCA-492D-A725-75604A379C7A}.Release|x64.Build.0 = Release|x64 + {1DB3679C-DCCA-492D-A725-75604A379C7A}.Release|arm64.Build.0 = Release|arm64 {1DB3679C-DCCA-492D-A725-75604A379C7A}.Release|x86.ActiveCfg = Release|x86 {1DB3679C-DCCA-492D-A725-75604A379C7A}.Release|x86.Build.0 = Release|x86 {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Debug|x64.ActiveCfg = Debug|x64 + {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Debug|arm64.ActiveCfg = Debug|arm64 {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Debug|x64.Build.0 = Debug|x64 + {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Debug|arm64.Build.0 = Debug|arm64 {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Debug|x86.ActiveCfg = Debug|x86 {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Debug|x86.Build.0 = Debug|x86 {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Release|x64.ActiveCfg = Release|x64 + {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Release|arm64.ActiveCfg = Release|arm64 {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Release|x64.Build.0 = Release|x64 + {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Release|arm64.Build.0 = Release|arm64 {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Release|x86.ActiveCfg = Release|x86 {5C270941-AAA4-4FF4-870E-BDE170C5407C}.Release|x86.Build.0 = Release|x86 {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Debug|x64.ActiveCfg = Debug|x64 + {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Debug|arm64.ActiveCfg = Debug|arm64 {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Debug|x64.Build.0 = Debug|x64 + {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Debug|arm64.Build.0 = Debug|arm64 {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Debug|x86.ActiveCfg = Debug|x86 {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Debug|x86.Build.0 = Debug|x86 {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Release|x64.ActiveCfg = Release|x64 + {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Release|arm64.ActiveCfg = Release|arm64 {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Release|x64.Build.0 = Release|x64 + {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Release|arm64.Build.0 = Release|arm64 {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Release|x86.ActiveCfg = Release|x86 {8D271ABF-209A-4AF9-8C62-EF92806B7DC1}.Release|x86.Build.0 = Release|x86 {90832409-6D54-445D-9310-DCE81F0BB991}.Debug|x86.ActiveCfg = Debug|x86 {90832409-6D54-445D-9310-DCE81F0BB991}.Debug|x86.Build.0 = Debug|x86 {90832409-6D54-445D-9310-DCE81F0BB991}.Debug|x64.ActiveCfg = Debug|x64 + {90832409-6D54-445D-9310-DCE81F0BB991}.Debug|arm64.ActiveCfg = Debug|arm64 {90832409-6D54-445D-9310-DCE81F0BB991}.Release|x86.ActiveCfg = Release|x86 {90832409-6D54-445D-9310-DCE81F0BB991}.Release|x86.Build.0 = Release|x86 {90832409-6D54-445D-9310-DCE81F0BB991}.Release|x64.ActiveCfg = Release|x64 + {90832409-6D54-445D-9310-DCE81F0BB991}.Release|arm64.ActiveCfg = Release|arm64 {90832409-6D54-445D-9310-DCE81F0BB991}.Release|x64.Build.0 = Release|x64 + {90832409-6D54-445D-9310-DCE81F0BB991}.Release|arm64.Build.0 = Release|arm64 {90832409-6D54-445D-9310-DCE81F0BB991}.Debug|x64.Build.0 = Debug|x64 + {90832409-6D54-445D-9310-DCE81F0BB991}.Debug|arm64.Build.0 = Debug|arm64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/MelonLoader/Core.cs b/MelonLoader/Core.cs index 536776136..b567ded54 100644 --- a/MelonLoader/Core.cs +++ b/MelonLoader/Core.cs @@ -59,7 +59,12 @@ internal static int Initialize() // Since MonoMod's PlatformHelper (used by DetourHelper.Native) runs Process.Start to determine ARM/x86 // platform, this causes the unpatched TermInfoReader to kick in before it can be patched and fixed when // installing the XTermFix below. To work around this, we can force the platform directly +#if OSX && ARM64 + DetourHelper.Native = new Fixes.MonoMod.MacOSArm64NativeDetourPlatform( + new DetourNativeARMPlatform()); +#else DetourHelper.Native = new DetourNativeMonoPosixPlatform(new DetourNativeX86Platform()); +#endif #endif HarmonyInstance = new HarmonyLib.Harmony(Properties.BuildInfo.Name); diff --git a/MelonLoader/Fixes/MonoMod/MacOSArm64NativeDetourPlatform.cs b/MelonLoader/Fixes/MonoMod/MacOSArm64NativeDetourPlatform.cs new file mode 100644 index 000000000..5f90f43cd --- /dev/null +++ b/MelonLoader/Fixes/MonoMod/MacOSArm64NativeDetourPlatform.cs @@ -0,0 +1,208 @@ +using System; +using System.Runtime.InteropServices; +using MonoMod.RuntimeDetour; + +namespace MelonLoader.Fixes.MonoMod; + +internal sealed class MacOSArm64NativeDetourPlatform : IDetourNativePlatform +{ + private readonly IDetourNativePlatform _inner; + private static bool? _nativeJitCopySupported; + + public MacOSArm64NativeDetourPlatform(IDetourNativePlatform inner) + { + _inner = inner; + } + + public NativeDetourData Create(IntPtr from, IntPtr to, byte? type = null) + { + return _inner.Create(from, to, type); + } + + public void Free(NativeDetourData detour) + { + _inner.Free(detour); + } + + public void Apply(NativeDetourData detour) + { + if (TryApplyWithNativeCopy(detour)) + return; + + _inner.Apply(detour); + } + + public void Copy(IntPtr src, IntPtr dst, byte type) + { + if (TryCopyWithNativeCopy(src, dst, type)) + return; + + _inner.Copy(src, dst, type); + } + + public void MakeWritable(IntPtr src, uint size) + { + if (NativeJitCopySupported) + return; + + _inner.MakeWritable(src, size); + } + + public void MakeExecutable(IntPtr src, uint size) + { + if (NativeJitCopySupported) + { + TryFlushICache(src, size); + return; + } + + _inner.MakeExecutable(src, size); + } + + public void MakeReadWriteExecutable(IntPtr src, uint size) + { + if (NativeJitCopySupported) + { + TryFlushICache(src, size); + return; + } + + _inner.MakeReadWriteExecutable(src, size); + } + + public void FlushICache(IntPtr src, uint size) + { + if (TryFlushICache(src, size)) + return; + + _inner.FlushICache(src, size); + } + + public IntPtr MemAlloc(uint size) + { + return _inner.MemAlloc(size); + } + + public void MemFree(IntPtr ptr) + { + _inner.MemFree(ptr); + } + + private bool TryApplyWithNativeCopy(NativeDetourData detour) + { + if (detour.Size == 0 || detour.Extra != IntPtr.Zero || !NativeJitCopySupported) + return false; + + IntPtr copy = Marshal.AllocHGlobal((int)detour.Size); + try + { + NativeDetourData copyDetour = detour; + copyDetour.Method = copy; + _inner.Apply(copyDetour); + + return TryNativeJitCopy(detour.Method, copy, detour.Size); + } + finally + { + Marshal.FreeHGlobal(copy); + } + } + + private bool TryCopyWithNativeCopy(IntPtr src, IntPtr dst, byte type) + { + uint size = GetDetourSize(type); + if (size == 0) + return false; + + return TryNativeJitCopy(dst, src, size); + } + + private static bool TryFlushICache(IntPtr src, uint size) + { + try + { + sys_icache_invalidate(src, (UIntPtr)size); + return true; + } + catch + { + return false; + } + } + + private static uint GetDetourSize(byte type) + { + switch (type) + { + case 0: + return 5; + case 1: + return 6; + case 2: + return 14; + case 3: + return 6; + case 4: + return 16; + default: + return 0; + } + } + + private static bool TryNativeJitCopy(IntPtr dst, IntPtr src, uint size) + { + if (!NativeJitCopySupported) + return false; + + try + { + return CallNativeJitCopy(dst, src, (UIntPtr)size) != 0; + } + catch + { + _nativeJitCopySupported = false; + return false; + } + } + + private static bool NativeJitCopySupported + { + get + { + if (_nativeJitCopySupported.HasValue) + return _nativeJitCopySupported.Value; + + try + { + _nativeJitCopySupported = CallNativeJitCopy(IntPtr.Zero, IntPtr.Zero, UIntPtr.Zero) != 0; + } + catch + { + _nativeJitCopySupported = false; + } + + return _nativeJitCopySupported.Value; + } + } + + private static int CallNativeJitCopy(IntPtr dst, IntPtr src, UIntPtr size) + { + try + { + return MLMacOSJitCopyBootstrap(dst, src, size); + } + catch + { + return MLMacOSJitCopyInternal(dst, src, size); + } + } + + [DllImport("MelonLoader.Bootstrap.dylib", EntryPoint = "MLMacOSJitCopy", CallingConvention = CallingConvention.Cdecl)] + private static extern int MLMacOSJitCopyBootstrap(IntPtr dst, IntPtr src, UIntPtr size); + + [DllImport("__Internal", EntryPoint = "MLMacOSJitCopy", CallingConvention = CallingConvention.Cdecl)] + private static extern int MLMacOSJitCopyInternal(IntPtr dst, IntPtr src, UIntPtr size); + + [DllImport("libSystem.B.dylib", CallingConvention = CallingConvention.Cdecl)] + private static extern void sys_icache_invalidate(IntPtr start, UIntPtr len); +} diff --git a/MelonLoader/MelonUtils.cs b/MelonLoader/MelonUtils.cs index e036d81cf..50ce208e9 100644 --- a/MelonLoader/MelonUtils.cs +++ b/MelonLoader/MelonUtils.cs @@ -85,6 +85,17 @@ public static bool IsOldMono() { #if OSX string frameworksPath = Path.Combine(MelonEnvironment.GameExecutablePath, "Contents/Frameworks"); + if (!Directory.Exists(frameworksPath)) + { + string[] apps = Directory.Exists(MelonEnvironment.MelonBaseDirectory) + ? Directory.GetDirectories(MelonEnvironment.MelonBaseDirectory, "*.app", SearchOption.TopDirectoryOnly) + : new string[0]; + if (apps.Length == 1) + frameworksPath = Path.Combine(apps[0], "Contents/Frameworks"); + } + if (!Directory.Exists(frameworksPath)) + return false; + var libs = Directory.GetFiles(frameworksPath, "*.dylib", SearchOption.AllDirectories); return libs.Select(Path.GetFileName).Contains("libmono.0.dylib"); #else diff --git a/MelonLoader/Utils/MelonEnvironment.cs b/MelonLoader/Utils/MelonEnvironment.cs index d5e0f50be..4021018c6 100644 --- a/MelonLoader/Utils/MelonEnvironment.cs +++ b/MelonLoader/Utils/MelonEnvironment.cs @@ -19,7 +19,7 @@ public static class MelonEnvironment public static string GameExecutablePath { get; } = #if OSX - MelonUtils.GetPathAncestor(Process.GetCurrentProcess()!.MainModule!.FileName, 3); + GetOSXGameExecutablePath(); #else Process.GetCurrentProcess().MainModule.FileName; #endif @@ -52,6 +52,38 @@ public static class MelonEnvironment public static string MelonManagedDirectory { get; } = Path.Combine(DependenciesDirectory, "Mono"); public static string Il2CppAssembliesDirectory { get; } = Path.Combine(MelonLoaderDirectory, "Il2CppAssemblies"); +#if OSX + private static string GetOSXGameExecutablePath() + { + string path = Process.GetCurrentProcess()!.MainModule!.FileName; + string current = Directory.Exists(path) ? path : Path.GetDirectoryName(path); + + while (!string.IsNullOrEmpty(current)) + { + if (current.EndsWith(".app")) + return current; + current = Path.GetDirectoryName(current); + } + + try + { + string baseDirectory = MelonBaseDirectory; + if (!string.IsNullOrEmpty(baseDirectory) && Directory.Exists(baseDirectory)) + { + string[] apps = Directory.GetDirectories(baseDirectory, "*.app", SearchOption.TopDirectoryOnly); + if (apps.Length == 1) + return apps[0]; + } + } + catch + { + // Fall through to the legacy ancestor fallback below. + } + + return MelonUtils.GetPathAncestor(path, 3); + } +#endif + internal static void PrintEnvironment() { //These must not be changed, lum needs them @@ -63,4 +95,4 @@ internal static void PrintEnvironment() MelonLogger.MsgDirect($"Runtime Type: {OurRuntimeName}"); } } -} \ No newline at end of file +}