Skip to content

Commit 3dccaaa

Browse files
committed
.NET: Add support for adding callbacks via attributes
1 parent 105ad2d commit 3dccaaa

File tree

14 files changed

+407
-160
lines changed

14 files changed

+407
-160
lines changed

csharp-api/CMakeLists.txt

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ set_target_properties(REFCoreDeps PROPERTIES VS_PACKAGE_REFERENCES "${REFRAMEWOR
174174
set(csharp-api_SOURCES
175175
"REFrameworkNET/API.cpp"
176176
"REFrameworkNET/AssemblyInfo.cpp"
177+
"REFrameworkNET/Attributes/Callback.cpp"
177178
"REFrameworkNET/Attributes/Method.cpp"
178179
"REFrameworkNET/Attributes/MethodHook.cpp"
179180
"REFrameworkNET/Attributes/Plugin.cpp"
@@ -192,6 +193,7 @@ set(csharp-api_SOURCES
192193
"REFrameworkNET/UnifiedObject.cpp"
193194
"REFrameworkNET/VM.cpp"
194195
"REFrameworkNET/API.hpp"
196+
"REFrameworkNET/Attributes/Callback.hpp"
195197
"REFrameworkNET/Attributes/Method.hpp"
196198
"REFrameworkNET/Attributes/MethodHook.hpp"
197199
"REFrameworkNET/Attributes/Plugin.hpp"
@@ -490,3 +492,75 @@ set_target_properties(CSharpAPITestRE2 PROPERTIES
490492
set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application")
491493

492494
endif()
495+
# Target: ObjectExplorer
496+
if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) # build-csharp-test-re2
497+
set(ObjectExplorer_SOURCES
498+
"test/Test/ObjectExplorer.cs"
499+
cmake.toml
500+
)
501+
502+
add_library(ObjectExplorer SHARED)
503+
504+
target_sources(ObjectExplorer PRIVATE ${ObjectExplorer_SOURCES})
505+
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${ObjectExplorer_SOURCES})
506+
507+
target_link_libraries(ObjectExplorer PUBLIC
508+
csharp-api
509+
)
510+
511+
set_target_properties(ObjectExplorer PROPERTIES
512+
RUNTIME_OUTPUT_DIRECTORY_RELEASE
513+
"${CMAKE_BINARY_DIR}/bin/"
514+
RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO
515+
"${CMAKE_BINARY_DIR}/bin"
516+
RUNTIME_OUTPUT_DIRECTORY_DEBUG
517+
"${CMAKE_BINARY_DIR}/bin"
518+
LIBRARY_OUTPUT_DIRECTORY_RELEASE
519+
"${CMAKE_BINARY_DIR}/lib"
520+
LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO
521+
"${CMAKE_BINARY_DIR}/lib"
522+
LIBRARY_OUTPUT_DIRECTORY_DEBUG
523+
"${CMAKE_BINARY_DIR}/lib"
524+
DOTNET_SDK
525+
Microsoft.NET.Sdk
526+
DOTNET_TARGET_FRAMEWORK
527+
net8.0-windows
528+
VS_CONFIGURATION_TYPE
529+
ClassLibrary
530+
)
531+
532+
set(CMKR_TARGET ObjectExplorer)
533+
set_target_properties(ObjectExplorer PROPERTIES
534+
VS_DOTNET_REFERENCE_REFramework.NET
535+
"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}"
536+
)
537+
538+
set_target_properties(ObjectExplorer PROPERTIES
539+
VS_DOTNET_REFERENCE_REFramework.NET._mscorlib
540+
"${re2_mscorlibdll}"
541+
)
542+
543+
set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib")
544+
545+
set_target_properties(ObjectExplorer PROPERTIES
546+
VS_DOTNET_REFERENCE_REFramework.NET._System
547+
"${re2_systemdll}"
548+
)
549+
550+
set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System")
551+
552+
set_target_properties(ObjectExplorer PROPERTIES
553+
VS_DOTNET_REFERENCE_REFramework.NET.System.Core
554+
"${re2_systemcoredll}"
555+
)
556+
557+
set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core")
558+
559+
set_target_properties(ObjectExplorer PROPERTIES
560+
VS_DOTNET_REFERENCE_REFramework.NET.viacore
561+
"${re2_viacoredll}"
562+
)
563+
564+
set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore")
565+
566+
endif()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "Callback.hpp"
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
5+
#include "../Callbacks.hpp"
6+
7+
namespace REFrameworkNET {
8+
public enum class CallbackType : uint8_t {
9+
Pre,
10+
Post
11+
};
12+
}
13+
14+
namespace REFrameworkNET::Attributes {
15+
[System::AttributeUsage(System::AttributeTargets::Method)]
16+
public ref class CallbackAttribute : public System::Attribute {
17+
public:
18+
CallbackAttribute(System::Type^ declaringType, CallbackType type) {
19+
if (declaringType == nullptr) {
20+
throw gcnew System::ArgumentNullException("declaringType");
21+
}
22+
23+
if (!REFrameworkNET::ICallback::typeid->IsAssignableFrom(declaringType)) {
24+
throw gcnew System::ArgumentException("Type does not implement ICallback");
25+
}
26+
27+
const auto wantedFlags = System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Static;
28+
29+
// Double check that the type has Pre and Post events
30+
auto preEvent = declaringType->GetField("PreImplementation", wantedFlags);
31+
auto postEvent = declaringType->GetField("PostImplementation", wantedFlags);
32+
33+
if (preEvent == nullptr || postEvent == nullptr) {
34+
throw gcnew System::ArgumentException("Callback type does not have Pre and Post events");
35+
}
36+
37+
m_declaringType = declaringType;
38+
}
39+
40+
property bool Valid {
41+
public:
42+
bool get() {
43+
return m_declaringType != nullptr;
44+
}
45+
}
46+
47+
void Install(System::Reflection::MethodInfo^ destination) {
48+
if (!Valid) {
49+
throw gcnew System::InvalidOperationException("Callback is not valid");
50+
}
51+
52+
if (!destination->IsStatic) {
53+
throw gcnew System::ArgumentException("Destination method must be static");
54+
}
55+
56+
const auto wantedFlags = System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Static;
57+
58+
auto preEvent = m_declaringType->GetField("PreImplementation", wantedFlags);
59+
auto postEvent = m_declaringType->GetField("PostImplementation", wantedFlags);
60+
61+
if (preEvent == nullptr || postEvent == nullptr) {
62+
throw gcnew System::ArgumentException("Callback type does not have Pre and Post fields");
63+
}
64+
65+
auto preDelegate = (BaseCallback::Delegate^)preEvent->GetValue(nullptr);
66+
auto postDelegate = (BaseCallback::Delegate^)postEvent->GetValue(nullptr);
67+
68+
if (m_callbackType == CallbackType::Pre) {
69+
auto del = (BaseCallback::Delegate^)System::Delegate::CreateDelegate(BaseCallback::Delegate::typeid, destination);
70+
71+
if (preDelegate == nullptr) {
72+
preDelegate = del;
73+
} else {
74+
preDelegate = preDelegate + del;
75+
}
76+
77+
preEvent->SetValue(nullptr, preDelegate);
78+
} else {
79+
auto del = (BaseCallback::Delegate^)System::Delegate::CreateDelegate(BaseCallback::Delegate::typeid, destination);
80+
81+
if (postDelegate == nullptr) {
82+
postDelegate = del;
83+
} else {
84+
postDelegate = postDelegate + del;
85+
}
86+
87+
postEvent->SetValue(nullptr, postDelegate);
88+
}
89+
}
90+
91+
private:
92+
System::Type^ m_declaringType{nullptr};
93+
CallbackType m_callbackType;
94+
};
95+
}

csharp-api/REFrameworkNET/Attributes/Method.hpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,26 @@
11
#pragma once
22

3+
#include <cstdint>
4+
35
#include "../TDB.hpp"
46

57
namespace REFrameworkNET::Attributes {
68
/// <summary>Attribute to mark a reference assembly method for easier lookup.</summary>
79
[System::AttributeUsage(System::AttributeTargets::Method)]
8-
public ref class Method : public System::Attribute {
10+
public ref class MethodAttribute : public System::Attribute {
911
private:
10-
static System::Collections::Concurrent::ConcurrentDictionary<System::Reflection::MethodInfo^, Method^>^ cache
11-
= gcnew System::Collections::Concurrent::ConcurrentDictionary<System::Reflection::MethodInfo^, Method^>(System::Environment::ProcessorCount * 2, 8192);
12+
static System::Collections::Concurrent::ConcurrentDictionary<System::Reflection::MethodInfo^, MethodAttribute^>^ cache
13+
= gcnew System::Collections::Concurrent::ConcurrentDictionary<System::Reflection::MethodInfo^, MethodAttribute^>(System::Environment::ProcessorCount * 2, 8192);
1214

1315
public:
14-
static Method^ GetCachedAttribute(System::Reflection::MethodInfo^ target) {
15-
Method^ attr = nullptr;
16+
static MethodAttribute^ GetCachedAttribute(System::Reflection::MethodInfo^ target) {
17+
MethodAttribute^ attr = nullptr;
1618

1719
if (cache->TryGetValue(target, attr)) {
1820
return attr;
1921
}
2022

21-
if (attr = (REFrameworkNET::Attributes::Method^)System::Attribute::GetCustomAttribute(target, REFrameworkNET::Attributes::Method::typeid); attr != nullptr) {
23+
if (attr = (MethodAttribute^)System::Attribute::GetCustomAttribute(target, MethodAttribute::typeid); attr != nullptr) {
2224
cache->TryAdd(target, attr);
2325

2426
#ifdef REFRAMEWORK_VERBOSE
@@ -32,7 +34,7 @@ namespace REFrameworkNET::Attributes {
3234
}
3335

3436
public:
35-
Method(uint32_t methodIndex) {
37+
MethodAttribute(uint32_t methodIndex) {
3638
method = REFrameworkNET::TDB::Get()->GetMethod(methodIndex);
3739

3840
if (method != nullptr && method->IsVirtual()) {

csharp-api/REFrameworkNET/Attributes/MethodHook.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ namespace REFrameworkNET::Attributes {
8383
}
8484

8585
protected:
86-
REFrameworkNET::TypeDefinition^ m_declaringType;
87-
REFrameworkNET::Method^ m_method;
86+
REFrameworkNET::TypeDefinition^ m_declaringType{nullptr};
87+
REFrameworkNET::Method^ m_method{nullptr};
8888
MethodHookType m_hookType;
8989
bool m_skipJmp{ false };
9090
};

csharp-api/REFrameworkNET/Callbacks.hpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@ public ref class BaseCallback {
3434
delegate void Delegate();
3535
};
3636

37+
/// <summary>Internal interface used to prove that a class is a callback belonging to REFrameworkNET</summary>
38+
interface class ICallback {
39+
40+
};
41+
3742
#define GENERATE_POCKET_CLASS(EVENT_NAME) \
38-
public ref class EVENT_NAME { \
43+
public ref class EVENT_NAME : public ICallback { \
3944
public: \
4045
static event BaseCallback::Delegate^ Pre { \
4146
void add(BaseCallback::Delegate^ value) { \

csharp-api/REFrameworkNET/ManagedObject.hpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject
5353
return nullptr;
5454
}
5555

56+
/// <summary>
57+
/// Retrieves a managed object from the pool or creates a new one if it doesn't exist.
58+
/// </summary>
59+
/// <param name="addr">The address of the managed object.</param>
60+
/// <returns>The managed object.</returns>
5661
static T^ Get(uintptr_t addr) {
5762
if (addr == 0) {
5863
return nullptr;
@@ -145,11 +150,19 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject
145150
static void CleanupKnownCaches();
146151

147152
public:
153+
/// <summary>
154+
/// Retrieves a managed object from the pool or creates a new one if it doesn't exist.
155+
/// </summary>
156+
/// <returns>The managed object.</returns>
148157
template <class T = ManagedObject>
149158
static T^ Get(reframework::API::ManagedObject* obj) {
150159
return Cache<T>::Get((uintptr_t)obj);
151160
}
152161

162+
/// <summary>
163+
/// Retrieves a managed object from the pool or creates a new one if it doesn't exist.
164+
/// </summary>
165+
/// <returns>The managed object.</returns>
153166
template <class T = ManagedObject>
154167
static T^ Get(::REFrameworkManagedObjectHandle handle) {
155168
return Cache<T>::Get((uintptr_t)handle);

csharp-api/REFrameworkNET/PluginManager.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include "Attributes/Plugin.hpp"
55
#include "Attributes/MethodHook.hpp"
6+
#include "Attributes/Callback.hpp"
67
#include "MethodHook.hpp"
78
#include "SystemString.hpp"
89
#include "NativePool.hpp"
@@ -314,12 +315,35 @@ namespace REFrameworkNET {
314315
} else {
315316
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName);
316317
}
318+
continue;
317319
} catch(System::Exception^ e) {
318320
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": " + e->Message);
321+
continue;
319322
} catch(const std::exception& e) {
320323
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": " + gcnew System::String(e.what()));
324+
continue;
321325
} catch(...) {
322326
REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": Unknown exception caught");
327+
continue;
328+
}
329+
330+
// Callback attribute(s)
331+
attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::CallbackAttribute::typeid, true);
332+
333+
if (attributes->Length > 0) try {
334+
REFrameworkNET::API::LogInfo("Found Callback in " + method->Name + " in " + type->FullName);
335+
auto callbackAttr = (REFrameworkNET::Attributes::CallbackAttribute^)attributes[0];
336+
callbackAttr->Install(method);
337+
continue;
338+
} catch(System::Exception^ e) {
339+
REFrameworkNET::API::LogError("Failed to install Callback in " + method->Name + " in " + type->FullName + ": " + e->Message);
340+
continue;
341+
} catch(const std::exception& e) {
342+
REFrameworkNET::API::LogError("Failed to install Callback in " + method->Name + " in " + type->FullName + ": " + gcnew System::String(e.what()));
343+
continue;
344+
} catch(...) {
345+
REFrameworkNET::API::LogError("Failed to install Callback in " + method->Name + " in " + type->FullName + ": Unknown exception caught");
346+
continue;
323347
}
324348
}
325349
}

csharp-api/REFrameworkNET/Proxy.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public
146146
protected:
147147
virtual Object^ Invoke(Reflection::MethodInfo^ targetMethod, array<Object^>^ args) override {
148148
// Get the REFrameworkNET::Attributes::Method attribute from the method
149-
auto methodAttribute = REFrameworkNET::Attributes::Method::GetCachedAttribute(targetMethod);
149+
auto methodAttribute = REFrameworkNET::Attributes::MethodAttribute::GetCachedAttribute(targetMethod);
150150

151151
Object^ result = nullptr;
152152
auto iobject = static_cast<REFrameworkNET::IObject^>(Instance);

csharp-api/cmake.toml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,49 @@ VS_DOTNET_REFERENCE_REFramework.NET.application
296296
297297
set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application")
298298
299+
"""
300+
301+
# Even though this targets RE2 it is not game specific
302+
# It just uses the System/via DLLs which are game agnostic
303+
[target.ObjectExplorer]
304+
condition = "build-csharp-test-re2"
305+
type = "CSharpSharedTarget"
306+
sources = ["test/Test/ObjectExplorer.cs"]
307+
link-libraries = [
308+
"csharp-api"
309+
]
310+
311+
cmake-after = """
312+
set_target_properties(ObjectExplorer PROPERTIES
313+
VS_DOTNET_REFERENCE_REFramework.NET
314+
"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}"
315+
)
316+
317+
set_target_properties(ObjectExplorer PROPERTIES
318+
VS_DOTNET_REFERENCE_REFramework.NET._mscorlib
319+
"${re2_mscorlibdll}"
320+
)
321+
322+
set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib")
323+
324+
set_target_properties(ObjectExplorer PROPERTIES
325+
VS_DOTNET_REFERENCE_REFramework.NET._System
326+
"${re2_systemdll}"
327+
)
328+
329+
set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System")
330+
331+
set_target_properties(ObjectExplorer PROPERTIES
332+
VS_DOTNET_REFERENCE_REFramework.NET.System.Core
333+
"${re2_systemcoredll}"
334+
)
335+
336+
set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core")
337+
338+
set_target_properties(ObjectExplorer PROPERTIES
339+
VS_DOTNET_REFERENCE_REFramework.NET.viacore
340+
"${re2_viacoredll}"
341+
)
342+
343+
set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore")
299344
"""

0 commit comments

Comments
 (0)