Skip to content

Instantly share code, notes, and snippets.

@Slaynash
Last active December 19, 2023 23:08
Show Gist options
  • Save Slaynash/753b552a05d3168c72c60e98116cfca9 to your computer and use it in GitHub Desktop.
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.
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));
}
}
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.");
}
}
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*)
}
}
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);
}
}
@DubyaDude
Copy link

OwO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment