Skip to content

Commit 105ad2d

Browse files
committed
.NET: Add support for hooking via attributes
1 parent 014e33d commit 105ad2d

File tree

6 files changed

+138
-4
lines changed

6 files changed

+138
-4
lines changed

csharp-api/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ set(csharp-api_SOURCES
175175
"REFrameworkNET/API.cpp"
176176
"REFrameworkNET/AssemblyInfo.cpp"
177177
"REFrameworkNET/Attributes/Method.cpp"
178+
"REFrameworkNET/Attributes/MethodHook.cpp"
178179
"REFrameworkNET/Attributes/Plugin.cpp"
179180
"REFrameworkNET/Callbacks.cpp"
180181
"REFrameworkNET/ManagedObject.cpp"
@@ -192,6 +193,7 @@ set(csharp-api_SOURCES
192193
"REFrameworkNET/VM.cpp"
193194
"REFrameworkNET/API.hpp"
194195
"REFrameworkNET/Attributes/Method.hpp"
196+
"REFrameworkNET/Attributes/MethodHook.hpp"
195197
"REFrameworkNET/Attributes/Plugin.hpp"
196198
"REFrameworkNET/Callbacks.hpp"
197199
"REFrameworkNET/Field.hpp"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "MethodHook.hpp"
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
5+
#include "../TDB.hpp"
6+
#include "../Method.hpp"
7+
#include "../MethodHook.hpp"
8+
9+
namespace REFrameworkNET {
10+
ref class TypeDefinition;
11+
ref class Method;
12+
13+
public enum class MethodHookType : uint8_t {
14+
Pre,
15+
Post
16+
};
17+
}
18+
19+
namespace REFrameworkNET::Attributes {
20+
/// <summary>Attribute to mark a method as a hook.</summary>
21+
[System::AttributeUsage(System::AttributeTargets::Method)]
22+
public ref class MethodHookAttribute : public System::Attribute {
23+
internal:
24+
void BaseConstructor(System::Type^ declaringType, System::String^ methodSignature, MethodHookType type) {
25+
if (declaringType == nullptr) {
26+
throw gcnew System::ArgumentNullException("declaringType");
27+
}
28+
29+
// new static readonly TypeDefinition REFType = TDB.Get().FindType("app.Collision.HitController.DamageInfo");
30+
auto refTypeField = declaringType->GetField("REFType", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public);
31+
32+
if (refTypeField == nullptr) {
33+
throw gcnew System::ArgumentException("Type does not have a REFrameworkNET::TypeDefinition field");
34+
}
35+
36+
m_declaringType = (TypeDefinition^)refTypeField->GetValue(nullptr);
37+
38+
if (m_declaringType == nullptr) {
39+
throw gcnew System::ArgumentException("Type does not have a REFrameworkNET::TypeDefinition field");
40+
}
41+
42+
m_method = m_declaringType != nullptr ? m_declaringType->GetMethod(methodSignature) : nullptr;
43+
m_hookType = type;
44+
}
45+
46+
public:
47+
MethodHookAttribute(System::Type^ declaringType, System::String^ methodSignature, MethodHookType type) {
48+
BaseConstructor(declaringType, methodSignature, type);
49+
}
50+
51+
MethodHookAttribute(System::Type^ declaringType, System::String^ methodSignature, MethodHookType type, bool skipJmp) {
52+
BaseConstructor(declaringType, methodSignature, type);
53+
m_skipJmp = skipJmp;
54+
}
55+
56+
property bool Valid {
57+
public:
58+
bool get() {
59+
return m_declaringType != nullptr && m_method != nullptr;
60+
}
61+
}
62+
63+
bool Install(System::Reflection::MethodInfo^ destination) {
64+
if (!Valid) {
65+
throw gcnew System::ArgumentException("Invalid method hook");
66+
}
67+
68+
if (!destination->IsStatic) {
69+
throw gcnew System::ArgumentException("Destination method must be static");
70+
}
71+
72+
auto hook = REFrameworkNET::MethodHook::Create(m_method, m_skipJmp);
73+
74+
if (m_hookType == MethodHookType::Pre) {
75+
auto del = System::Delegate::CreateDelegate(REFrameworkNET::MethodHook::PreHookDelegate::typeid, destination);
76+
hook->AddPre((REFrameworkNET::MethodHook::PreHookDelegate^)del);
77+
} else if (m_hookType == MethodHookType::Post) {
78+
auto del = System::Delegate::CreateDelegate(REFrameworkNET::MethodHook::PostHookDelegate::typeid, destination);
79+
hook->AddPost((REFrameworkNET::MethodHook::PostHookDelegate^)del);
80+
}
81+
82+
return true;
83+
}
84+
85+
protected:
86+
REFrameworkNET::TypeDefinition^ m_declaringType;
87+
REFrameworkNET::Method^ m_method;
88+
MethodHookType m_hookType;
89+
bool m_skipJmp{ false };
90+
};
91+
}

csharp-api/REFrameworkNET/MethodHook.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ public ref class MethodHook
3636
s_hooked_methods_lock->EnterWriteLock();
3737

3838
try {
39+
// Try to get it again in case another thread added it
40+
if (s_hooked_methods->TryGetValue(method, wrapper)) {
41+
return wrapper;
42+
}
43+
3944
wrapper = gcnew MethodHook(method, ignore_jmp);
4045
s_hooked_methods->Add(method, wrapper);
4146
return wrapper;

csharp-api/REFrameworkNET/PluginManager.cpp

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <filesystem>
33

44
#include "Attributes/Plugin.hpp"
5+
#include "Attributes/MethodHook.hpp"
56
#include "MethodHook.hpp"
67
#include "SystemString.hpp"
78
#include "NativePool.hpp"
@@ -282,12 +283,43 @@ namespace REFrameworkNET {
282283
array<System::Reflection::MethodInfo^>^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic);
283284

284285
for each (System::Reflection::MethodInfo^ method in methods) {
286+
// EntryPoint attribute
285287
array<Object^>^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginEntryPoint::typeid, true);
286288

287-
if (attributes->Length > 0) {
289+
if (attributes->Length > 0) try {
288290
REFrameworkNET::API::LogInfo("Found PluginEntryPoint in " + method->Name + " in " + type->FullName);
289291
method->Invoke(nullptr, nullptr);
290292
ever_found = true;
293+
continue;
294+
} catch(System::Exception^ e) {
295+
REFrameworkNET::API::LogError("Failed to invoke PluginEntryPoint in " + method->Name + " in " + type->FullName + ": " + e->Message);
296+
continue;
297+
} catch(const std::exception& e) {
298+
REFrameworkNET::API::LogError("Failed to invoke PluginEntryPoint in " + method->Name + " in " + type->FullName + ": " + gcnew System::String(e.what()));
299+
continue;
300+
} catch(...) {
301+
REFrameworkNET::API::LogError("Failed to invoke PluginEntryPoint in " + method->Name + " in " + type->FullName + ": Unknown exception caught");
302+
continue;
303+
}
304+
305+
// MethodHook attribute(s)
306+
attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::MethodHookAttribute::typeid, true);
307+
308+
if (attributes->Length > 0) try {
309+
REFrameworkNET::API::LogInfo("Found MethodHook in " + method->Name + " in " + type->FullName);
310+
auto hookAttr = (REFrameworkNET::Attributes::MethodHookAttribute^)attributes[0];
311+
312+
if (hookAttr->Install(method)) {
313+
REFrameworkNET::API::LogInfo("Installed MethodHook in " + method->Name + " in " + type->FullName);
314+
} else {
315+
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName);
316+
}
317+
} catch(System::Exception^ e) {
318+
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": " + e->Message);
319+
} catch(const std::exception& e) {
320+
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": " + gcnew System::String(e.what()));
321+
} catch(...) {
322+
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": Unknown exception caught");
291323
}
292324
}
293325
}

csharp-api/test/Test/TestRE2.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ public static void Bench(System.Action action) {
253253

254254
rwl.ExitWriteLock();
255255
}
256-
256+
257+
[REFrameworkNET.Attributes.MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Pre, false)]
257258
static PreHookResult Pre(System.Span<ulong> args) {
258259
var hitController = ManagedObject.ToManagedObject(args[1]).As<app.Collision.HitController>();
259260

@@ -269,14 +270,16 @@ static PreHookResult Pre(System.Span<ulong> args) {
269270
return PreHookResult.Continue;
270271
}
271272

273+
[REFrameworkNET.Attributes.MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Post, false)]
272274
static void Post(ref ulong retval) {
275+
273276
}
274277

275278
public static void InstallHook() {
276-
app.Collision.HitController.REFType
279+
/*app.Collision.HitController.REFType
277280
.GetMethod("update")
278281
.AddHook(false)
279282
.AddPre(Pre)
280-
.AddPost(Post);
283+
.AddPost(Post);*/
281284
}
282285
}

0 commit comments

Comments
 (0)