Skip to content

Commit

Permalink
.NET: Add support for hooking via attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Apr 24, 2024
1 parent 014e33d commit 105ad2d
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 4 deletions.
2 changes: 2 additions & 0 deletions csharp-api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ set(csharp-api_SOURCES
"REFrameworkNET/API.cpp"
"REFrameworkNET/AssemblyInfo.cpp"
"REFrameworkNET/Attributes/Method.cpp"
"REFrameworkNET/Attributes/MethodHook.cpp"
"REFrameworkNET/Attributes/Plugin.cpp"
"REFrameworkNET/Callbacks.cpp"
"REFrameworkNET/ManagedObject.cpp"
Expand All @@ -192,6 +193,7 @@ set(csharp-api_SOURCES
"REFrameworkNET/VM.cpp"
"REFrameworkNET/API.hpp"
"REFrameworkNET/Attributes/Method.hpp"
"REFrameworkNET/Attributes/MethodHook.hpp"
"REFrameworkNET/Attributes/Plugin.hpp"
"REFrameworkNET/Callbacks.hpp"
"REFrameworkNET/Field.hpp"
Expand Down
1 change: 1 addition & 0 deletions csharp-api/REFrameworkNET/Attributes/MethodHook.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "MethodHook.hpp"
91 changes: 91 additions & 0 deletions csharp-api/REFrameworkNET/Attributes/MethodHook.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#pragma once

#include <cstdint>

#include "../TDB.hpp"
#include "../Method.hpp"
#include "../MethodHook.hpp"

namespace REFrameworkNET {
ref class TypeDefinition;
ref class Method;

public enum class MethodHookType : uint8_t {
Pre,
Post
};
}

namespace REFrameworkNET::Attributes {
/// <summary>Attribute to mark a method as a hook.</summary>
[System::AttributeUsage(System::AttributeTargets::Method)]
public ref class MethodHookAttribute : public System::Attribute {
internal:
void BaseConstructor(System::Type^ declaringType, System::String^ methodSignature, MethodHookType type) {
if (declaringType == nullptr) {
throw gcnew System::ArgumentNullException("declaringType");
}

// new static readonly TypeDefinition REFType = TDB.Get().FindType("app.Collision.HitController.DamageInfo");
auto refTypeField = declaringType->GetField("REFType", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public);

if (refTypeField == nullptr) {
throw gcnew System::ArgumentException("Type does not have a REFrameworkNET::TypeDefinition field");
}

m_declaringType = (TypeDefinition^)refTypeField->GetValue(nullptr);

if (m_declaringType == nullptr) {
throw gcnew System::ArgumentException("Type does not have a REFrameworkNET::TypeDefinition field");
}

m_method = m_declaringType != nullptr ? m_declaringType->GetMethod(methodSignature) : nullptr;
m_hookType = type;
}

public:
MethodHookAttribute(System::Type^ declaringType, System::String^ methodSignature, MethodHookType type) {
BaseConstructor(declaringType, methodSignature, type);
}

MethodHookAttribute(System::Type^ declaringType, System::String^ methodSignature, MethodHookType type, bool skipJmp) {
BaseConstructor(declaringType, methodSignature, type);
m_skipJmp = skipJmp;
}

property bool Valid {
public:
bool get() {
return m_declaringType != nullptr && m_method != nullptr;
}
}

bool Install(System::Reflection::MethodInfo^ destination) {
if (!Valid) {
throw gcnew System::ArgumentException("Invalid method hook");
}

if (!destination->IsStatic) {
throw gcnew System::ArgumentException("Destination method must be static");
}

auto hook = REFrameworkNET::MethodHook::Create(m_method, m_skipJmp);

if (m_hookType == MethodHookType::Pre) {
auto del = System::Delegate::CreateDelegate(REFrameworkNET::MethodHook::PreHookDelegate::typeid, destination);
hook->AddPre((REFrameworkNET::MethodHook::PreHookDelegate^)del);
} else if (m_hookType == MethodHookType::Post) {
auto del = System::Delegate::CreateDelegate(REFrameworkNET::MethodHook::PostHookDelegate::typeid, destination);
hook->AddPost((REFrameworkNET::MethodHook::PostHookDelegate^)del);
}

return true;
}

protected:
REFrameworkNET::TypeDefinition^ m_declaringType;
REFrameworkNET::Method^ m_method;
MethodHookType m_hookType;
bool m_skipJmp{ false };
};
}
5 changes: 5 additions & 0 deletions csharp-api/REFrameworkNET/MethodHook.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public ref class MethodHook
s_hooked_methods_lock->EnterWriteLock();

try {
// Try to get it again in case another thread added it
if (s_hooked_methods->TryGetValue(method, wrapper)) {
return wrapper;
}

wrapper = gcnew MethodHook(method, ignore_jmp);
s_hooked_methods->Add(method, wrapper);
return wrapper;
Expand Down
34 changes: 33 additions & 1 deletion csharp-api/REFrameworkNET/PluginManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <filesystem>

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

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

if (attributes->Length > 0) {
if (attributes->Length > 0) try {
REFrameworkNET::API::LogInfo("Found PluginEntryPoint in " + method->Name + " in " + type->FullName);
method->Invoke(nullptr, nullptr);
ever_found = true;
continue;
} catch(System::Exception^ e) {
REFrameworkNET::API::LogError("Failed to invoke PluginEntryPoint in " + method->Name + " in " + type->FullName + ": " + e->Message);
continue;
} catch(const std::exception& e) {
REFrameworkNET::API::LogError("Failed to invoke PluginEntryPoint in " + method->Name + " in " + type->FullName + ": " + gcnew System::String(e.what()));
continue;
} catch(...) {
REFrameworkNET::API::LogError("Failed to invoke PluginEntryPoint in " + method->Name + " in " + type->FullName + ": Unknown exception caught");
continue;
}

// MethodHook attribute(s)
attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::MethodHookAttribute::typeid, true);

if (attributes->Length > 0) try {
REFrameworkNET::API::LogInfo("Found MethodHook in " + method->Name + " in " + type->FullName);
auto hookAttr = (REFrameworkNET::Attributes::MethodHookAttribute^)attributes[0];

if (hookAttr->Install(method)) {
REFrameworkNET::API::LogInfo("Installed MethodHook in " + method->Name + " in " + type->FullName);
} else {
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName);
}
} catch(System::Exception^ e) {
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": " + e->Message);
} catch(const std::exception& e) {
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": " + gcnew System::String(e.what()));
} catch(...) {
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": Unknown exception caught");
}
}
}
Expand Down
9 changes: 6 additions & 3 deletions csharp-api/test/Test/TestRE2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,8 @@ public static void Bench(System.Action action) {

rwl.ExitWriteLock();
}


[REFrameworkNET.Attributes.MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Pre, false)]
static PreHookResult Pre(System.Span<ulong> args) {
var hitController = ManagedObject.ToManagedObject(args[1]).As<app.Collision.HitController>();

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

[REFrameworkNET.Attributes.MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Post, false)]
static void Post(ref ulong retval) {

}

public static void InstallHook() {
app.Collision.HitController.REFType
/*app.Collision.HitController.REFType
.GetMethod("update")
.AddHook(false)
.AddPre(Pre)
.AddPost(Post);
.AddPost(Post);*/
}
}

0 comments on commit 105ad2d

Please sign in to comment.