Last active
December 19, 2023 23:08
-
-
Save Slaynash/753b552a05d3168c72c60e98116cfca9 to your computer and use it in GitHub Desktop.
SendMessageAny Hook for Unity 2018.4.23f1. Imports.Hook require MelonLoader, or a similar library supporting detour hooking.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Diagnostics; | |
using System.Text; | |
public static class CppUtils | |
{ | |
public static unsafe IntPtr ByReference(this IntPtr pointer) => (IntPtr)(&pointer); | |
public static unsafe IntPtr ByValue(this IntPtr pointer) => *(IntPtr*)(pointer); | |
public static unsafe string CharArrayPtrToString(this IntPtr ptr) | |
{ | |
byte* text = *(byte**)ptr; | |
int length = 0; | |
while (text[length] != 0) | |
++length; | |
return Encoding.UTF8.GetString(text, length); | |
} | |
internal static unsafe IntPtr Sigscan(IntPtr module, string signature) | |
{ | |
int moduleSize = 0; | |
foreach (ProcessModule module_ in Process.GetCurrentProcess().Modules) | |
{ | |
if (module_.ModuleName == "UnityPlayer.dll") | |
{ | |
moduleSize = module_.ModuleMemorySize; | |
break; | |
} | |
} | |
string signatureSpaceless = signature.Replace(" ", ""); | |
int signatureLength = signatureSpaceless.Length / 2; | |
byte[] signatureBytes = new byte[signatureLength]; | |
bool[] signatureNullBytes = new bool[signatureLength]; | |
for (int i = 0; i < signatureLength; ++i) | |
{ | |
if (signatureSpaceless[i * 2] == '?') | |
signatureNullBytes[i] = true; | |
else | |
signatureBytes[i] = (byte)((GetHexVal(signatureSpaceless[i * 2]) << 4) + (GetHexVal(signatureSpaceless[(i * 2) + 1]))); | |
} | |
long index = module.ToInt64(); | |
long maxIndex = index + moduleSize; | |
long tmpAddress = 0; | |
int processed = 0; | |
while (index < maxIndex) | |
{ | |
if (signatureNullBytes[processed] || *(byte*)index == signatureBytes[processed]) | |
{ | |
if (processed == 0) | |
tmpAddress = index; | |
++processed; | |
if (processed == signatureLength) | |
return (IntPtr)tmpAddress; | |
} | |
else | |
{ | |
processed = 0; | |
} | |
++index; | |
} | |
return IntPtr.Zero; | |
} | |
// source: https://stackoverflow.com/a/9995303 | |
private static int GetHexVal(char hex) | |
{ | |
int val = (int)hex; | |
return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
class Example | |
{ | |
static void Main(string[] args) | |
{ | |
SendMessageAnyHandler.Setup(); | |
SendMessageAnyHandler.RegisterMessageHandler("OnTransformChildrenChanged", OnChildrenChanged); | |
} | |
private static void OnChildrenChanged(UnityPlayerGameObject gameObject, IntPtr messageData) | |
{ | |
// Replace with `MelonLoader.MelonModLogger.Log` if you are using MelonLoader | |
UnityEngine.Debug.Log("Children of GameObject '" + gameObject.name + "' have changed."); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
public class UnityPlayerGameObject | |
{ | |
private static readonly int offset_components = 0x30; | |
private static readonly int offset_componentCount = 0x40; | |
private static readonly int offset_name = 0x60; | |
/* | |
// Object | |
public IntPtr type; | |
public uint unknown_x8; | |
public uint unknown_xC; | |
public IntPtr unknown_x10; | |
public IntPtr gchandle; | |
public IntPtr unknown_x20; | |
public IntPtr unknown_x28; | |
// GameObject | |
public IntPtr components; | |
public uint unknown_x38; | |
public long componentCount; | |
public IntPtr unknown_x48; | |
public uint unknown_x50; // not set | |
public uint unknown_x54; | |
public IntPtr unknown_x58; // not set | |
public string name; | |
public IntPtr unknown_x68; | |
public IntPtr unknown_x70; | |
public IntPtr unknown_x78; // set but idk | |
*/ | |
private IntPtr ptr; | |
public IntPtr Pointer => ptr; | |
public UnityPlayerGameObject(IntPtr ptr) | |
{ | |
this.ptr = ptr; | |
} | |
public unsafe string name => CppUtils.CharArrayPtrToString(ptr + offset_name); | |
/* | |
public Transform transform | |
{ | |
get | |
{ | |
IntPtr componentPtr = GetComponent(0); | |
if (componentPtr == IntPtr.Zero) | |
return null; | |
return new Transform(componentPtr); | |
} | |
} | |
*/ | |
private unsafe long componentCount => *(long*)(ptr + offset_componentCount); | |
private unsafe IntPtr GetComponent(int index) | |
{ | |
if (index > componentCount) | |
return IntPtr.Zero; | |
return *(IntPtr*)(*(IntPtr*)(ptr + offset_components) + index * 0x10 + 0x8); // array of (Undefined8, Component*) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Reflection; | |
using System.Runtime.InteropServices; | |
internal static class SendMessageAnyHandler | |
{ | |
private delegate void SendMessageAny(/* GameObject* */ IntPtr this_, /* MessageIdentifier* */ IntPtr param_1, /* MessageData* */ IntPtr param_2); | |
private static SendMessageAny originalSendMessageAny; | |
private static Dictionary<string, List<Action<UnityPlayerGameObject, IntPtr>>> messageHandlers = new Dictionary<string, List<Action<UnityPlayerGameObject, IntPtr>>>(); | |
private static Dictionary<IntPtr, string> messageIdentifiers = new Dictionary<IntPtr, string>(); | |
#region Externs | |
[DllImport("GameAssembly", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)] | |
public extern static IntPtr il2cpp_resolve_icall(string name); | |
[DllImport("kernel32.dll")] | |
public static extern IntPtr GetModuleHandle(string lpModuleName); | |
#endregion | |
#region Utils | |
internal static IntPtr GetFunctionPointerFromMethod(string methodName) => | |
typeof(SendMessageAnyHandler).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Static).MethodHandle.GetFunctionPointer(); | |
public static string AsHexString(this IntPtr ptr) => | |
string.Format("{0:X}", ptr.ToInt64()); | |
// You might want to replace these methods with MelonLoader.MelonModLogger.Log (or LogError) if you are using MelonLoader | |
public static void Log(string s) => UnityEngine.Debug.Log(s); | |
public static void LogError(string s) => UnityEngine.Debug.LogError(s); | |
#endregion | |
public static void RegisterMessageHandler(string message, Action<UnityPlayerGameObject, IntPtr> messageHandler) | |
{ | |
if (!messageHandlers.TryGetValue(message, out List<Action<UnityPlayerGameObject, IntPtr>> handlers)) | |
messageHandlers[message] = handlers = new List<Action<UnityPlayerGameObject, IntPtr>>(); | |
handlers.Add(messageHandler); | |
} | |
// We need to call this first! (and only once is better) | |
public static void Setup() | |
{ | |
IntPtr unityPlayer = GetModuleHandle("UnityPlayer.dll"); | |
Log(" > UnityPlayer.dll: @" + unityPlayer.AsHexString()); | |
// This is the sigscan for Unity 2018.4.23f1. It may break with other unity version! | |
IntPtr sendMessageAny = CppUtils.Sigscan(unityPlayer, "40 55 57 41 54 41 57 48 83 ec ?? 4d 8b e0 4c 8b fa 48 8b e9 e8 ?? ?? ?? ?? c1 e8 02 33 ff a8 01 74"); | |
if (sendMessageAny == IntPtr.Zero) | |
{ | |
LogError("Failed to locate the function SendMessageAny in UnityEngine.dll. Please report this error to Slaynash#2879"); | |
return; | |
} | |
else | |
Log(" > SendMessageAny found at @" + sendMessageAny.AsHexString()); | |
IntPtr sendMessageAnyPtr = sendMessageAny.ByReference(); | |
Imports.Hook(sendMessageAnyPtr, GetFunctionPointerFromMethod("SendMessageAnyHandler")); // Imports.Hook is a hook using Detour (Included with MelonLoader) | |
Log(" > Hooked SendMessageAny"); | |
originalSendMessageAny = Marshal.GetDelegateForFunctionPointer<SendMessageAny>(sendMessageAnyPtr.ByValue()); | |
} | |
private static void SendMessageAnyDetour(/*GameObject**/ IntPtr this_, /*MessageIdentifier**/ IntPtr param_1, /*MessageData**/ IntPtr param_2) | |
{ | |
// We never want the program to crash here, else it will crash the whole game (or do weird things) | |
try | |
{ | |
// Cache string equivalent, because we don't want to process the string each time it's called | |
if (!messageIdentifiers.TryGetValue(param_1, out string message)) | |
messageIdentifiers[param_1] = message = CppUtils.CharArrayPtrToString(param_1); | |
if (messageHandlers.TryGetValue(message, out List<Action<UnityPlayerGameObject, IntPtr>> handlers) && handlers.Count > 0) | |
{ | |
UnityPlayerGameObject gameObject = new UnityPlayerGameObject(this_); | |
foreach (Action<UnityPlayerGameObject, IntPtr> handler in handlers) | |
{ | |
try | |
{ | |
handler(gameObject, param_2); | |
} | |
catch (Exception e) | |
{ | |
LogError("An error occured while handling Unity's message '" + message + "':\n" + e.ToString()); | |
} | |
} | |
} | |
} | |
catch (Exception e) | |
{ | |
LogError(e.ToString()); | |
} | |
originalSendMessageAny(this_, param_1, param_2); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
OwO