From 3c43135d12e4f35c742611374ada4afb69b7e206 Mon Sep 17 00:00:00 2001 From: Emmanuel AYME Date: Sun, 22 Feb 2026 11:30:19 +0100 Subject: [PATCH] Add Unity Mono tools --- libs/Unity/MonoLoader.cpp | 143 ++++++++++++++++++++++++++++++++++++++ libs/Unity/MonoLoader.hpp | 116 +++++++++++++++++++++++++++++++ 2 files changed, 259 insertions(+) create mode 100644 libs/Unity/MonoLoader.cpp create mode 100644 libs/Unity/MonoLoader.hpp diff --git a/libs/Unity/MonoLoader.cpp b/libs/Unity/MonoLoader.cpp new file mode 100644 index 0000000..415b4f4 --- /dev/null +++ b/libs/Unity/MonoLoader.cpp @@ -0,0 +1,143 @@ +#include "MonoLoader.hpp" +#include +#include + +HMODULE MonoLoader::WaitForMono(const char* monoName) { + HMODULE mono = nullptr; + while (!mono) { + mono = GetModuleHandleA(monoName); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + } + return mono; +} + +bool MonoLoader::Initialize(std::shared_ptr log, const char* monoName) { + if (log) logger = log; + + HMODULE mono = WaitForMono(monoName); + if (!mono) { + logger->error("Mono module not found."); + return false; + } + logger->info("Mono DLL found at {:p}", (void*)mono); + + g_mono_get_root_domain = (MonoDomain * (*)())GetProcAddress(mono, "mono_get_root_domain"); + g_mono_thread_attach = (void(*)(MonoDomain*))GetProcAddress(mono, "mono_thread_attach"); + g_mono_domain_assembly_open = (MonoAssembly * (*)(MonoDomain*, const char*))GetProcAddress(mono, "mono_domain_assembly_open"); + g_mono_assembly_get_image = (MonoImage * (*)(MonoAssembly*))GetProcAddress(mono, "mono_assembly_get_image"); + g_mono_class_from_name = (MonoClass * (*)(MonoImage*, const char*, const char*))GetProcAddress(mono, "mono_class_from_name"); + g_mono_class_get_method_from_name = (MonoMethod * (*)(MonoClass*, const char*, int))GetProcAddress(mono, "mono_class_get_method_from_name"); + g_mono_compile_method = (void* (*)(MonoMethod*))GetProcAddress(mono, "mono_compile_method"); + mono_assembly_foreach = (void(*)(void(*)(MonoAssembly*, void*), void*))GetProcAddress(mono, "mono_assembly_foreach"); + mono_assembly_get_name = (mono_assembly_get_name_t)GetProcAddress(mono, "mono_assembly_get_name"); + mono_assembly_name_get_name = (mono_assembly_name_get_name_t)GetProcAddress(mono, "mono_assembly_name_get_name"); + mono_class_get_methods = (mono_class_get_methods_t)GetProcAddress(mono, "mono_class_get_methods"); + mono_method_get_name = (mono_method_get_name_t)GetProcAddress(mono, "mono_method_get_name"); + mono_method_signature = (mono_method_signature_t)GetProcAddress(mono, "mono_method_signature"); + mono_signature_get_param_count = (mono_signature_get_param_count_t)GetProcAddress(mono, "mono_signature_get_param_count"); + + if (!g_mono_get_root_domain || !g_mono_thread_attach || !g_mono_domain_assembly_open || + !g_mono_assembly_get_image || !g_mono_class_from_name || !g_mono_class_get_method_from_name || + !g_mono_compile_method || !mono_assembly_foreach || !mono_assembly_get_name || !mono_assembly_name_get_name || + !mono_class_get_methods || !mono_method_get_name || !mono_method_signature || !mono_signature_get_param_count) { + logger->error("Failed to resolve one or more Mono exports."); + return false; + } + + g_monoDomain = g_mono_get_root_domain(); + if (!g_monoDomain) { + logger->error("Mono root domain is null."); + return false; + } + + g_mono_thread_attach(g_monoDomain); + logger->info("Attached to Mono domain {:p}", (void*)g_monoDomain); + return true; +} + +struct FindAssemblyData { + MonoLoader* loader; + const std::string* targetName; + MonoAssembly* result = nullptr; +}; + +void MonoLoader::FindAssemblyCallback(MonoAssembly* assembly, void* userData) { + auto data = reinterpret_cast(userData); + if (!assembly || !data || !data->targetName) return; + + const void* nameStruct = data->loader->mono_assembly_get_name(assembly); + const char* nameCStr = data->loader->mono_assembly_name_get_name(nameStruct); + + if (nameCStr && *data->targetName == nameCStr) { + data->result = assembly; + } +} + +MonoAssembly* MonoLoader::FindAssembly(const std::string& name) { + if (!mono_assembly_foreach || !mono_assembly_get_name || !mono_assembly_name_get_name) { + logger->error("Mono exports not resolved, cannot enumerate assemblies."); + return nullptr; + } + + FindAssemblyData data; + data.loader = this; + data.targetName = &name; + data.result = nullptr; + + mono_assembly_foreach(MonoLoader::FindAssemblyCallback, &data); + return data.result; +} + +MonoClass* MonoLoader::FindClass(const std::string& assemblyName, const std::string& namespaceName, const std::string& className) { + MonoAssembly* assembly = FindAssembly(assemblyName); + if (!assembly) return nullptr; + + MonoImage* image = g_mono_assembly_get_image(assembly); + if (!image) return nullptr; + + return g_mono_class_from_name(image, namespaceName.c_str(), className.c_str()); +} + +uint8_t* MonoLoader::GetCompiledMethod(MonoClass* monoClass, const std::string& methodName, int paramCount) { + if (!monoClass) return nullptr; + MonoMethod* method = g_mono_class_get_method_from_name(monoClass, methodName.c_str(), paramCount); + if (!method) return nullptr; + return (uint8_t*)g_mono_compile_method(method); +} + +void MonoLoader::DumpMethods(MonoClass* monoClass, std::shared_ptr logger) { + if (!monoClass) { + logger->error("DumpMethods: monoClass is null."); + return; + } + + if (!g_monoDomain) { + logger->error("DumpMethods: Mono domain is null."); + return; + } + + g_mono_thread_attach(g_monoDomain); + + if (!mono_class_get_methods || !mono_method_get_name || !mono_method_signature || !mono_signature_get_param_count) { + logger->error("DumpMethods: Required Mono exports not resolved."); + return; + } + + logger->info("---- Dumping methods ----"); + + void* iter = nullptr; + MonoMethod* method = nullptr; + + while ((method = mono_class_get_methods(monoClass, &iter)) != nullptr) { + const char* name = mono_method_get_name(method); + + MonoMethodSignature* sig = mono_method_signature(method); + uint32_t paramCount = 0; + + if (sig) paramCount = mono_signature_get_param_count(sig); + + logger->info("Method: {} | Params: {}", name ? name : "null", paramCount); + } + + logger->info("-------------------------"); +} \ No newline at end of file diff --git a/libs/Unity/MonoLoader.hpp b/libs/Unity/MonoLoader.hpp new file mode 100644 index 0000000..cf7f123 --- /dev/null +++ b/libs/Unity/MonoLoader.hpp @@ -0,0 +1,116 @@ +#pragma once +#include +#include + +// Forward declarations Mono +struct MonoDomain; +struct MonoAssembly; +struct MonoImage; +struct MonoClass; +struct MonoMethod; +struct MonoMethodSignature; + +// Typedefs pour les exports Mono +using mono_assembly_foreach_t = void(*)(void(*)(MonoAssembly*, void*), void*); +using mono_assembly_get_name_t = const void* (*)(MonoAssembly*); +using mono_assembly_name_get_name_t = const char* (*)(const void*); +using mono_class_get_methods_t = MonoMethod * (*)(MonoClass*, void**); +using mono_method_get_name_t = const char* (*)(MonoMethod*); +using mono_method_signature_t = MonoMethodSignature * (*)(MonoMethod*); +using mono_signature_get_param_count_t = uint32_t(*)(MonoMethodSignature*); + +class MonoLoader { +public: + MonoLoader() = default; + ~MonoLoader() = default; + + /** + * @brief Callback used during Mono assembly enumeration. + * + * This function is passed to `mono_assembly_foreach` and checks each loaded assembly + * to find the one matching a specific name (provided via userData). + * Can be used to initialize pointers to target assemblies. + * + * @param assembly Pointer to the current assembly. + * @param userData User data passed to `mono_assembly_foreach`, typically a structure + * containing the target name and a location to store the result. + */ + bool Initialize(std::shared_ptr log = nullptr, const char* monoName = "mono-2.0-bdwgc.dll"); + MonoDomain* GetDomain() const { return g_monoDomain; } + + /** + * @brief Finds a Mono assembly by its name. + * + * This function iterates over all loaded assemblies in the Mono domain and + * returns a pointer to the assembly matching the given name. + * + * @param name Name of the assembly to search for. + * @return MonoAssembly* Pointer to the found assembly, or nullptr if not found. + */ + MonoAssembly* FindAssembly(const std::string& name); + + /** + * @brief Finds a class in a loaded assembly by namespace and name. + * @param assemblyName Name of the assembly (ex: "UnityEngine.CoreModule"). + * @param namespaceName Namespace of the class (ex: "UnityEngine"). + * @param className Name of the class (ex: "Screen"). + * @return Pointer to the MonoClass, or nullptr if not found. + */ + MonoClass* FindClass(const std::string& assemblyName, const std::string& namespaceName, const std::string& className); + + /** + * @brief Finds a method in a MonoClass and compiles it. + * @param monoClass The MonoClass to search in. + * @param methodName The method name (ex: "get_width"). + * @param paramCount Number of parameters. + * @return Compiled method address (uint8_t*), or nullptr if not found. + */ + uint8_t* GetCompiledMethod(MonoClass* monoClass, const std::string& methodName, int paramCount); + + void DumpMethods(MonoClass* monoClass, std::shared_ptr log = nullptr); + + // Exports + mono_assembly_foreach_t mono_assembly_foreach = nullptr; + mono_assembly_get_name_t mono_assembly_get_name = nullptr; + mono_assembly_name_get_name_t mono_assembly_name_get_name = nullptr; + MonoDomain* (*g_mono_get_root_domain)() = nullptr; + void (*g_mono_thread_attach)(MonoDomain*) = nullptr; + MonoAssembly* (*g_mono_domain_assembly_open)(MonoDomain*, const char*) = nullptr; + MonoImage* (*g_mono_assembly_get_image)(MonoAssembly*) = nullptr; + MonoClass* (*g_mono_class_from_name)(MonoImage*, const char*, const char*) = nullptr; + MonoMethod* (*g_mono_class_get_method_from_name)(MonoClass*, const char*, int) = nullptr; + void* (*g_mono_compile_method)(MonoMethod*) = nullptr; + mono_class_get_methods_t mono_class_get_methods = nullptr; + mono_method_get_name_t mono_method_get_name = nullptr; + mono_method_signature_t mono_method_signature = nullptr; + mono_signature_get_param_count_t mono_signature_get_param_count = nullptr; + + +private: + MonoDomain* g_monoDomain = nullptr; + std::shared_ptr logger; + + /** + * @brief Waits for the Mono module to be loaded in the process and returns its handle. + * + * This function loops every 200 ms until the "mono-2.0-bdwgc.dll" module + * is present in the current process. Useful to ensure that Mono is initialized + * before resolving its exports. + * + * @return HMODULE Handle of the Mono module, or nullptr if not found (practically never). + */ + HMODULE WaitForMono(const char* monoName); + + /** + * @brief Callback used during Mono assembly enumeration. + * + * This function is passed to `mono_assembly_foreach` and checks each loaded assembly + * to find the one matching a specific name (provided via userData). + * Can be used to initialize pointers to target assemblies. + * + * @param assembly Pointer to the current assembly. + * @param userData User data passed to `mono_assembly_foreach`, typically a structure + * containing the target name and a location to store the result. + */ + static void FindAssemblyCallback(MonoAssembly* assembly, void* userData); +}; \ No newline at end of file