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