Skip to content

Commit

Permalink
.NET: Initial working method hooking
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Mar 25, 2024
1 parent 448fd28 commit 5ae4b40
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 9 deletions.
2 changes: 2 additions & 0 deletions csharp-api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ set(csharp-api_SOURCES
"REFrameworkNET/Callbacks.cpp"
"REFrameworkNET/ManagedObject.cpp"
"REFrameworkNET/Method.cpp"
"REFrameworkNET/MethodHookWrapper.cpp"
"REFrameworkNET/NativeObject.cpp"
"REFrameworkNET/Plugin.cpp"
"REFrameworkNET/PluginManager.cpp"
Expand All @@ -119,6 +120,7 @@ set(csharp-api_SOURCES
"REFrameworkNET/ManagedObject.hpp"
"REFrameworkNET/ManagedSingleton.hpp"
"REFrameworkNET/Method.hpp"
"REFrameworkNET/MethodHookWrapper.hpp"
"REFrameworkNET/MethodParameter.hpp"
"REFrameworkNET/NativeObject.hpp"
"REFrameworkNET/Plugin.hpp"
Expand Down
5 changes: 5 additions & 0 deletions csharp-api/REFrameworkNET/Method.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "ManagedObject.hpp"
#include "NativeObject.hpp"

#include "MethodHookWrapper.hpp"
#include "Method.hpp"
#include "Field.hpp"

Expand All @@ -11,6 +12,10 @@
#include "Utility.hpp"

namespace REFrameworkNET {
MethodHookWrapper^ Method::AddHook(bool ignore_jmp) {
return MethodHookWrapper::Create(this, ignore_jmp);
}

REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array<System::Object^>^ args) {
if (obj == nullptr && !this->IsStatic()) {
System::String^ declaringName = this->GetDeclaringType() != nullptr ? this->GetDeclaringType()->GetFullName() : gcnew System::String("UnknownType");
Expand Down
26 changes: 17 additions & 9 deletions csharp-api/REFrameworkNET/Method.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,28 @@
#include "MethodParameter.hpp"

namespace REFrameworkNET {
ref class MethodHookWrapper;

public enum class PreHookResult : int32_t {
Continue = 0,
Skip = 1,
};

public ref class Method : public System::IEquatable<Method^>
{
public:
Method(reframework::API::Method* method) : m_method(method) {}

void* GetRaw() {
return m_method;
}

REFrameworkNET::InvokeRet^ Invoke(System::Object^ obj, array<System::Object^>^ args);
bool HandleInvokeMember_Internal(System::Object^ obj, System::String^ methodName, array<System::Object^>^ args, System::Object^% result);

/*Void* GetFunctionRaw() {
void* GetFunctionPtr() {
return m_method->get_function_raw();
}*/
}

System::String^ GetName() {
return gcnew System::String(m_method->get_name());
Expand Down Expand Up @@ -144,14 +155,11 @@ public ref class Method : public System::IEquatable<Method^>
}
}

// hmm...
/*UInt32 AddHook(pre_fn, post_fn, Boolean ignore_jmp) {
return m_method->add_hook(pre_fn, post_fn, ignore_jmp);
}*/
// More palatable C# versions
delegate PreHookResult REFPreHookDelegate(System::Collections::Generic::List<System::Object^>^ args);
delegate void REFPostHookDelegate();

void RemoveHook(uint32_t hook_id) {
m_method->remove_hook(hook_id);
}
MethodHookWrapper^ AddHook(bool ignore_jmp);

public:
virtual bool Equals(System::Object^ other) override {
Expand Down
47 changes: 47 additions & 0 deletions csharp-api/REFrameworkNET/MethodHookWrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "./API.hpp"
#include "MethodHookWrapper.hpp"

using namespace System::Runtime::InteropServices;
using namespace System::Collections::Generic;

namespace REFrameworkNET {
void MethodHookWrapper::InstallHooks(bool ignore_jmp)
{
if (m_hooks_installed) {
return;
}

REFrameworkNET::API::LogInfo("Creating .NET hook for method: " + m_method->GetName());

m_hooks_installed = true;

reframework::API::Method* raw = (reframework::API::Method*)m_method->GetRaw();

IntPtr preHookPtr = Marshal::GetFunctionPointerForDelegate(m_preHookLambda);
IntPtr postHookPtr = Marshal::GetFunctionPointerForDelegate(m_postHookLambda);
m_hook_id = raw->add_hook((REFPreHookFn)preHookPtr.ToPointer(), (REFPostHookFn)postHookPtr.ToPointer(), ignore_jmp);
}

void MethodHookWrapper::UninstallHooks() {
if (!m_hooks_installed) {
return;
}

reframework::API::Method* raw = (reframework::API::Method*)m_method->GetRaw();

m_hooks_installed = false;
raw->remove_hook(m_hook_id);
}

int32_t MethodHookWrapper::OnPreStart_Raw(int argc, void** argv, REFrameworkTypeDefinitionHandle* arg_tys, unsigned long long ret_addr)
{
OnPreStart(gcnew List<System::Object^>()); // todo: pass the arguments
System::Console::WriteLine("Hello from" + m_method->GetName() + " pre-hook!");
return 0; // Or another appropriate value
}

void MethodHookWrapper::OnPostStart_Raw(void** ret_val, REFrameworkTypeDefinitionHandle ret_ty, unsigned long long ret_addr) {
//OnPostStart(/* arguments */);
System::Console::WriteLine("Hello from" + m_method->GetName() + " post-hook!");
}
}
78 changes: 78 additions & 0 deletions csharp-api/REFrameworkNET/MethodHookWrapper.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
#pragma once

#include <cstdint>

#include "./API.hpp"
#include "./Method.hpp"

namespace REFrameworkNET {
// Wrapper class we create when we create an initial hook
// Additional hook calls on the same method will return the instance we already made
// and additional callbacks will be appended to the existing events
public ref class MethodHookWrapper
{
public:
// Public factory method to create a new hook
static MethodHookWrapper^ Create(Method^ method, bool ignore_jmp)
{
if (s_hooked_methods->ContainsKey(method)) {
return s_hooked_methods[method];
}

auto wrapper = gcnew MethodHookWrapper(method, ignore_jmp);
s_hooked_methods->Add(method, wrapper);
return wrapper;
}

MethodHookWrapper^ AddPre(Method::REFPreHookDelegate^ callback)
{
OnPreStart += callback;
return this;
}

MethodHookWrapper^ AddPost(Method::REFPostHookDelegate^ callback)
{
OnPostStart += callback;
return this;
}

private:
event Method::REFPreHookDelegate^ OnPreStart;
event Method::REFPostHookDelegate^ OnPostStart;


// This is never meant to publicly be called
MethodHookWrapper(Method^ method, bool ignore_jmp)
{
m_method = method;
m_preHookLambda = gcnew REFPreHookDelegateRaw(this, &MethodHookWrapper::OnPreStart_Raw);
m_postHookLambda = gcnew REFPostHookDelegateRaw(this, &MethodHookWrapper::OnPostStart_Raw);
InstallHooks(ignore_jmp);
}

~MethodHookWrapper()
{
if (m_hooks_installed) {
UninstallHooks();
}
}

static System::Collections::Generic::Dictionary<Method^, MethodHookWrapper^>^ s_hooked_methods = gcnew System::Collections::Generic::Dictionary<Method^, MethodHookWrapper^>();

delegate int32_t REFPreHookDelegateRaw(int argc, void** argv, REFrameworkTypeDefinitionHandle* arg_tys, unsigned long long ret_addr);
delegate void REFPostHookDelegateRaw(void** ret_val, REFrameworkTypeDefinitionHandle ret_ty, unsigned long long ret_addr);

void InstallHooks(bool ignore_jmp);
void UninstallHooks();

int32_t OnPreStart_Raw(int argc, void** argv, REFrameworkTypeDefinitionHandle* arg_tys, unsigned long long ret_addr);
void OnPostStart_Raw(void** ret_val, REFrameworkTypeDefinitionHandle ret_ty, unsigned long long ret_addr);

Method^ m_method{};
uint32_t m_hook_id{};
bool m_hooks_installed{false};

REFPreHookDelegateRaw^ m_preHookLambda{};
REFPostHookDelegateRaw^ m_postHookLambda{};
};
}
17 changes: 17 additions & 0 deletions csharp-api/test/Test/Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,24 @@ public class NativeProxy<T> : Proxy<T, REFrameworkNET.NativeObject> {
}

public class DangerousFunctions {
public static REFrameworkNET.PreHookResult isInsidePreHook(System.Collections.Generic.List<object> args) {
Console.WriteLine("Inside pre hook (From C#)");
REFrameworkNET.API.LogInfo("isInsidePreHook");
return REFrameworkNET.PreHookResult.Continue;
}

public static void isInsidePostHook() {
Console.WriteLine("Inside post hook (From C#)");
}

public static void Entry() {
var tdb = REFrameworkNET.API.GetTDB();
tdb.GetType("app.CameraManager")?.
GetMethod("isInside")?.
AddHook(false).
AddPre(isInsidePreHook).
AddPost(isInsidePostHook);

// These via.SceneManager and via.Scene are
// loaded from an external reference assembly
// the classes are all interfaces that correspond to real in-game classes
Expand Down

0 comments on commit 5ae4b40

Please sign in to comment.