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
32 changes: 20 additions & 12 deletions .github/workflows/compile_macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
name: MelonLoader.macOS.${{ matrix.arch }}.CI.Release
path: Output/Release/${{ matrix.rid }}
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
tags:
- '*.*.*'

permissions:
contents: write

jobs:
fix_tag_version:
name: Fix Tag Version
Expand Down Expand Up @@ -36,11 +39,13 @@ jobs:
upload:
name: Upload
needs: [ build, fix_tag_version ]
permissions:
contents: write
uses: ./.github/workflows/upload_all.yml
with:
TAG: ${{ github.ref_name }}
RELEASE_NAME: ${{ github.ref_name }} Open-Beta
VERSION: ${{ needs.fix_tag_version.outputs.clean_version }}
PACKAGE_VERSION: ${{ needs.fix_tag_version.outputs.clean_version }}
PACKAGE_NAME: MelonLoader
secrets: inherit
secrets: inherit
4 changes: 3 additions & 1 deletion .github/workflows/upload_all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ on:
jobs:
upload_github:
name: GitHub
permissions:
contents: write
uses: ./.github/workflows/upload_github.yml
with:
TAG: ${{ inputs.TAG }}
Expand All @@ -37,4 +39,4 @@ jobs:
with:
VERSION: ${{ inputs.PACKAGE_VERSION }}
PACKAGE_NAME: ${{ inputs.PACKAGE_NAME }}
secrets: inherit
secrets: inherit
18 changes: 16 additions & 2 deletions .github/workflows/upload_github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ jobs:
upload_github:
name: Upload GitHub Release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6

Expand Down Expand Up @@ -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: |
Expand All @@ -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
7 changes: 5 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
<IsPackable>false</IsPackable>
<Configurations>Debug;Release</Configurations>

<Platforms>x86;x64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64</RuntimeIdentifiers>
<Platforms>x86;x64;arm64</Platforms>
<RuntimeIdentifiers>win-x86;win-x64;linux-x64;osx-x64;osx-arm64</RuntimeIdentifiers>

<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
Expand Down Expand Up @@ -109,6 +109,9 @@
<PropertyGroup Condition="'$(RuntimeIdentifier.EndsWith(`-x64`))' == 'true'">
<DefineConstants>$(DefineConstants);X64</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier.EndsWith(`-arm64`))' == 'true'">
<DefineConstants>$(DefineConstants);ARM64</DefineConstants>
</PropertyGroup>

<PropertyGroup>
<MLOutDir>$(SolutionDir)Output/$(Configuration)/$(RuntimeIdentifier)</MLOutDir>
Expand Down
Binary file added MelonLoader.Bootstrap/Deps/osx-arm64/libdobby.a
Binary file not shown.
Binary file added MelonLoader.Bootstrap/Deps/osx-arm64/libplthook.a
Binary file not shown.
Binary file modified MelonLoader.Bootstrap/Deps/osx-x64/libplthook.a
Binary file not shown.
14 changes: 14 additions & 0 deletions MelonLoader.Bootstrap/MelonLoader.Bootstrap.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@
triggers MelonLoader on macOS, because nothing in the managed code references it. -->
<ItemGroup Condition="'$(RuntimeIdentifier.StartsWith(`osx-`))' == 'true'">
<LinkerArg Include="-Wl,-u,_SetRlimitHook" />
<!-- Keep the dlsym interpose + its registration/real-dlsym exports. -->
<LinkerArg Include="-Wl,-u,_DlsymHook" />
<LinkerArg Include="-Wl,-u,_MLRegisterDlsymHook" />
<LinkerArg Include="-Wl,-u,_MLRealDlsym" />
<LinkerArg Include="-Wl,-u,_MLMacOSJitCopy" />
</ItemGroup>

<!-- Ship the launch wrapper alongside the dylib so end users have a ready-made
Expand Down Expand Up @@ -110,6 +115,15 @@
<WriteLinesToFile File="$(ExportsFile)" Lines="$([System.IO.File]::ReadAllText($(ExportsFile)).Replace('V1.0',''))" Overwrite="true" />
</Target>

<!-- The exports file is regenerated from the managed UnmanagedCallersOnly
exports during IlcCompile, so native (C++) exports must be appended here,
after regeneration but before the native link. _MLRegisterDlsymHook is the
osxentry.cpp setter the managed dlsym interpose uses. -->
<Target Name="AddNativeOSXExports" AfterTargets="Replace" BeforeTargets="LinkNative"
Condition="'$(RuntimeIdentifier.StartsWith(`osx-`))' == 'true'">
<WriteLinesToFile File="$(ExportsFile)" Lines="_MLRegisterDlsymHook;_MLRealDlsym;_MLMacOSJitCopy" Overwrite="false" />
</Target>

<Target Name="PublishAfterBuild" AfterTargets="Build" Condition="'$(SkipPublishAfterBuild)' != 'true'">
<MSBuild Projects="$(MSBuildProjectFile)"
Targets="Publish"
Expand Down
59 changes: 55 additions & 4 deletions MelonLoader.Bootstrap/ModuleSymbolRedirect.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using MelonLoader.Bootstrap.RuntimeHandlers.Il2Cpp;
using MelonLoader.Bootstrap.RuntimeHandlers.Mono;
using MelonLoader.Bootstrap.Utils;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace MelonLoader.Bootstrap
Expand All @@ -24,7 +25,14 @@ internal static void Attach()

IntPtr detourPtr = Marshal.GetFunctionPointerForDelegate(DetourDelegate);

#if LINUX || OSX
#if OSX
// plthook cannot patch UnityPlayer.dylib's GOT on modern macOS
// (LC_DYLD_CHAINED_FIXUPS) -- it reports "no such function" and the
// mono hook never installs. Interpose dlsym in native code instead
// (osxentry.cpp), the same DYLD interpose mechanism used to trigger
// Init via setrlimit.
RegisterDlsymHook(Marshal.GetFunctionPointerForDelegate(DlsymDetourDelegate));
#elif LINUX
PltHook.InstallHooks
([
("dlsym", detourPtr)
Expand All @@ -50,8 +58,12 @@ private static nint GetSymbol(nint handle, nint symbolName)

#if WINDOWS
return GetProcAddress(handle, symbolName);
#elif LINUX || OSX
#elif LINUX
return dlsym(handle, symbolName);
#elif OSX
// Use the real (un-interposed) dlsym so MelonLoader resolves the
// genuine runtime exports, not its own dlsym detours.
return RealDlsym(handle, symbolName);
#else
return nint.Zero;
#endif
Expand Down Expand Up @@ -84,15 +96,54 @@ private static nint SymbolDetour(nint handle, nint symbol)
return redirect.detourPtr;
}

#if OSX
// macOS interpose detour. osxentry.cpp resolves the real address and
// passes it in (calling dlsym from managed code re-enters the interpose
// and recurses), so this only decides whether to redirect.
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate nint DlsymDetourFn(nint handle, nint symbol, nint real);
private static readonly DlsymDetourFn DlsymDetourDelegate = DlsymDetour;

private static nint DlsymDetour(nint handle, nint symbol, nint real)
{
string? symbolName = Marshal.PtrToStringAnsi(symbol);
if (string.IsNullOrEmpty(symbolName))
return real;

if (!MonoHandler.SymbolRedirects.TryGetValue(symbolName, out var redirect)
&& !Il2CppHandler.SymbolRedirects.TryGetValue(symbolName, out redirect))
return real;

if (!_runtimeInitialised)
{
_runtimeInitialised = true;
MelonDebug.Log("Initializing Runtime");
redirect.InitMethod(handle);
if (!LoaderConfig.Current.Loader.CapturePlayerLogs)
ConsoleHandler.ResetHandles();
}

MelonDebug.Log($"Redirecting {symbolName}");
return redirect.detourPtr;
}
#endif

#if WINDOWS
[DllImport("kernel32")]
private static extern nint GetProcAddress(nint handle, nint symbol);
#elif LINUX
[DllImport("libdl.so.2")]
private static extern IntPtr dlsym(nint handle, nint symbol);
#elif OSX
[DllImport("libSystem.B.dylib")]
private static extern IntPtr dlsym(nint handle, nint symbol);
// Native (osxentry.cpp): RealDlsym is the un-interposed dlsym used by
// GetSymbol; RegisterDlsymHook installs the managed interpose target.
[LibraryImport("*", EntryPoint = "MLRealDlsym")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
private static partial nint RealDlsym(nint handle, nint symbol);

[LibraryImport("*", EntryPoint = "MLRegisterDlsymHook")]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
private static partial void RegisterDlsymHook(nint detour);
#endif
}
}
66 changes: 66 additions & 0 deletions MelonLoader.Bootstrap/OSXEntry/melonloader-launch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,70 @@ set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BOOTSTRAP_PATH="$SCRIPT_DIR/MelonLoader.Bootstrap.dylib"

shell_quote() {
printf "'%s'" "$(printf "%s" "$1" | sed "s/'/'\\\\''/g")"
}

open_melonloader_terminal() {
if [ "${MELONLOADER_NO_TERMINAL:-0}" = "1" ]; then
return
fi

if ! command -v open >/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" <<EOF
#!/bin/bash
LOG_PATH=$quoted_log_path
TITLE=$quoted_title
GAME_PID=$$

printf '\\033]0;%s\\007' "\$TITLE"
clear
echo "\$TITLE"
echo "Waiting for MelonLoader log..."
echo

while [ ! -f "\$LOG_PATH" ] && kill -0 "\$GAME_PID" 2>/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)
Expand Down Expand Up @@ -78,4 +142,6 @@ else
export DYLD_INSERT_LIBRARIES="$BOOTSTRAP_PATH"
fi

open_melonloader_terminal

exec "$@"
Loading