Skip to content

Commit

Permalink
updated ufunction stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
WistfulHopes committed Feb 17, 2024
1 parent c389342 commit c173fad
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 56 deletions.
91 changes: 51 additions & 40 deletions CSharpLoader/include/DotNetLibrary.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,12 @@ using namespace RC::Unreal;
#define CSHARPLOADER_API __declspec(dllexport)
namespace RC::DotNetLibrary
{
enum struct CallbackType : int32_t
{
ActorOverlapDelegate,
ActorHitDelegate,
ActorCursorDelegate,
ActorKeyDelegate,
ComponentOverlapDelegate,
ComponentHitDelegate,
ComponentCursorDelegate,
ComponentKeyDelegate,
CharacterLandedDelegate
};

enum struct ArgumentType : int32_t
{
None,
Single,
Integer,
Pointer,
Callback
};

enum struct CommandType : int32_t
Expand Down Expand Up @@ -102,25 +88,12 @@ namespace RC::DotNetLibrary
static inline std::unordered_map<int32_t, PropertyType> m_property_type_map;
};

struct Callback
{
void** Parameters;
CallbackType Type;

FORCEINLINE Callback(void** Parameters, CallbackType Type)
{
this->Parameters = Parameters;
this->Type = Type;
}
};

struct Argument
{
union {
float Single;
uint32_t Integer;
void* Pointer;
Callback Callback;
};
ArgumentType Type;

Expand All @@ -141,12 +114,6 @@ namespace RC::DotNetLibrary
this->Pointer = Value;
this->Type = !Value ? ArgumentType::None : ArgumentType::Pointer;
}

FORCEINLINE Argument(DotNetLibrary::Callback Value)
{
this->Callback = Value;
this->Type = ArgumentType::Callback;
}
};

struct Command
Expand Down Expand Up @@ -202,9 +169,8 @@ namespace RC::DotNetLibrary
}
};

static_assert(sizeof(Callback) == 16, "Invalid size of the [Callback] structure");
static_assert(sizeof(Argument) == 24, "Invalid size of the [Argument] structure");
static_assert(sizeof(Command) == 40, "Invalid size of the [Command] structure");
static_assert(sizeof(Argument) == 16, "Invalid size of the [Argument] structure");
static_assert(sizeof(Command) == 32, "Invalid size of the [Command] structure");

static void* (*ManagedCommand)(Command);

Expand Down Expand Up @@ -309,19 +275,56 @@ namespace RC::DotNetLibrary
public:
static void Log(LogLevel::LogLevel Level, const char* Message);
};

using UFunctionCallback = void(*)(UObject* pThis, void* parms, void* out_parms, void* return_val);

struct CSharpUnrealScriptFunctionData
{
Unreal::CallbackId pre_callback_id;
Unreal::CallbackId post_callback_id;
Unreal::UFunction* unreal_function;
UFunctionCallback callback_ref;
UFunctionCallback post_callback_ref;

bool has_return_value{};
// Will be non-nullptr if the UFunction has a return value
Unreal::FProperty* return_property{};
};
struct CSharpCallbackData
{
struct RegistryIndex
{
UFunctionCallback callback_ref;
Unreal::CallbackId callback_id;
};
Unreal::UClass* instance_of_class;
std::vector<RegistryIndex> registry_indexes;
};

static std::vector<std::unique_ptr<CSharpUnrealScriptFunctionData>> g_hooked_script_function_data{};
static inline std::unordered_map<int32_t, int32_t> m_generic_hook_id_to_native_hook_id{};
static inline int32_t m_last_generic_hook_id{};
static inline std::unordered_map<StringType, CSharpCallbackData> m_script_hook_callbacks{};

struct CallbackIds
{
CallbackId pre_id;
CallbackId post_id;
};

class CSHARPLOADER_API Hooking
{
private:
static void CSharpUnrealScriptFunctionHookPre(Unreal::UnrealScriptFunctionCallableContext context, void* custom_data);
static void CSharpUnrealScriptFunctionHookPost(Unreal::UnrealScriptFunctionCallableContext context, void* custom_data);
public:
static intptr_t SigScan(const char* Signature);
static PLH::x64Detour* Hook(uint64_t fnAddress, uint64_t fnCallback, uint64_t* userTrampVar);
static CallbackId HookUFunctionPre(UFunction* function, const UnrealScriptFunctionCallable& PreCallback, void* CustomData);
static CallbackId HookUFunctionPost(UFunction* function, const UnrealScriptFunctionCallable& PostCallback, void* CustomData);
static CallbackIds HookUFunction(UFunction* function, UFunctionCallback pre_callback, UFunctionCallback post_callback);
static void Unhook(PLH::x64Detour* Hook);
static bool UnhookUFunction(UFunction* function, CallbackId CallbackId);
static void UnhookUFunction(UFunction* function, CallbackIds callback_ids);
};


class CSHARPLOADER_API Object
{
public:
Expand Down Expand Up @@ -400,5 +403,13 @@ namespace RC::DotNetLibrary
static void EditValueAt(UEnum* Enum, int Index, int64 Value);
static void RemoveFromNamesAt(UEnum* Enum, int Index, int Count = 1, bool AllowShrinking = true);
};

class CSHARPLOADER_API Function : public Struct
{
static int GetParmsSize(UFunction* Func);
static int GetOffsetOfParam(UFunction* Func, const char* Name);
static int GetSizeOfParam(UFunction* Func, const char* Name);
static int GetReturnValueOffset(UFunction* Func);
};
}
}
165 changes: 149 additions & 16 deletions CSharpLoader/src/DotNetLibrary.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <polyhook2/Detour/x64Detour.hpp>
#include "DotNetLibrary.hpp"

#include <stack>
#include <UnrealDef.hpp>
#include <Unreal/UScriptStruct.hpp>
#include <File/Macros.hpp>
Expand All @@ -8,6 +10,8 @@
#include <Helpers/Casting.hpp>
#include <SigScanner/SinglePassSigScanner.hpp>

#include "ExceptionHandling.hpp"

namespace RC::DotNetLibrary
{
auto Runtime::log(LogLevel::LogLevel Level, const char* Message)
Expand Down Expand Up @@ -129,12 +133,44 @@ namespace RC::DotNetLibrary
{
if (Shared::Events[ProgramStart]) ManagedCommand(Command(Shared::Events[ProgramStart]));
}

static auto script_hook([[maybe_unused]] Unreal::UObject* Context, Unreal::FFrame& Stack, [[maybe_unused]] void* RESULT_DECL) -> void
{
auto execute_hook = [&](std::unordered_map<StringType, Framework::CSharpCallbackData>& callback_container, bool precise_name_match) {
if (callback_container.empty())
{
return;
}
if (auto it = callback_container.find(precise_name_match ? Stack.Node()->GetFullName() : Stack.Node()->GetName()); it != callback_container.end())
{
const auto& callback_data = it->second;
for (const auto& [callback, index] : callback_data.registry_indexes)
{
callback(Context, Stack.Locals(), Stack.OutParms(), RESULT_DECL);
}
}
};

TRY([&] {
execute_hook(Framework::m_script_hook_callbacks, true);
});
}

auto Runtime::fire_unreal_init() -> void
{
if (Shared::Events[UnrealInit]) ManagedCommand(Command(Shared::Events[UnrealInit]));

for (const auto callback : unreal_init_callbacks) callback();
if (Unreal::UObject::ProcessLocalScriptFunctionInternal.is_ready() && Unreal::Version::IsAtLeast(4, 22))
{
Output::send(STR("Enabling custom events\n"));
Unreal::Hook::RegisterProcessLocalScriptFunctionPostCallback(script_hook);
}
else if (Unreal::UObject::ProcessInternalInternal.is_ready() && Unreal::Version::IsBelow(4, 22))
{
Output::send(STR("Enabling custom events\n"));
Unreal::Hook::RegisterProcessInternalPostCallback(script_hook);
}
}

auto Runtime::fire_update() -> void
Expand All @@ -146,19 +182,37 @@ namespace RC::DotNetLibrary

namespace Framework
{
#define CLR_GET_PROPERTY_VALUE(PropertyType, Type, Object, Name, Value) \
PropertyType* prop = static_cast<PropertyType*>(Object->GetPropertyByNameInChain(to_wstring(Name).c_str())); \
#define CLR_GET_PROPERTY_VALUE(PropertyType, Type, Object, Name, Value) \
PropertyType* prop = static_cast<PropertyType*>(Object->GetPropertyByNameInChain(to_wstring(Name).c_str())); \
if (!prop) return false; \
\
*Value = *prop->ContainerPtrToValuePtr<Type>(Object); \
*Value = *prop->ContainerPtrToValuePtr<Type>(Object); \
return true;

#define CLR_SET_PROPERTY_VALUE(PropertyType, Type, Object, Name, Value) \
PropertyType* prop = static_cast<PropertyType*>(Object->GetPropertyByNameInChain(to_wstring(Name).c_str())); \
#define CLR_SET_PROPERTY_VALUE(PropertyType, Type, Object, Name, Value) \
PropertyType* prop = static_cast<PropertyType*>(Object->GetPropertyByNameInChain(to_wstring(Name).c_str())); \
if (!prop) return false; \
\
*prop->ContainerPtrToValuePtr<Type>(Object) = Value; \
return true;
*prop->ContainerPtrToValuePtr<Type>(Object) = Value; \
return true;

void Hooking::CSharpUnrealScriptFunctionHookPre(UnrealScriptFunctionCallableContext context, void* custom_data)
{
auto& csharp_data = *static_cast<CSharpUnrealScriptFunctionData*>(custom_data);

if (csharp_data.callback_ref == nullptr) return;

csharp_data.callback_ref(context.Context, context.TheStack.Locals(), context.TheStack.OutParms(), nullptr);
}

void Hooking::CSharpUnrealScriptFunctionHookPost(UnrealScriptFunctionCallableContext context, void* custom_data)
{
auto& csharp_data = *static_cast<CSharpUnrealScriptFunctionData*>(custom_data);

if (csharp_data.post_callback_ref == nullptr) return;

csharp_data.post_callback_ref(context.Context, context.TheStack.Locals(), context.TheStack.OutParms(), context.RESULT_DECL);
}

intptr_t Hooking::SigScan(const char* Signature)
{
Expand Down Expand Up @@ -188,24 +242,74 @@ namespace RC::DotNetLibrary
return hook;
}

CallbackId Hooking::HookUFunctionPre(UFunction* function, const UnrealScriptFunctionCallable& PreCallback, void* CustomData)
CallbackIds Hooking::HookUFunction(UFunction* function, UFunctionCallback pre_callback, UFunctionCallback post_callback)
{
return function->RegisterPreHook(PreCallback, CustomData);
}
int32_t generic_pre_id{};
int32_t generic_post_id{};

CallbackId Hooking::HookUFunctionPost(UFunction* function, const UnrealScriptFunctionCallable& PostCallback, void* CustomData)
{
return function->RegisterPostHook(PostCallback, CustomData);
const auto func_ptr = function->GetFunc();

if (func_ptr && func_ptr != Unreal::UObject::ProcessInternalInternal.get_function_address() &&
function->HasAnyFunctionFlags(FUNC_Native))
{
const auto& custom_data = g_hooked_script_function_data.emplace_back(std::make_unique<CSharpUnrealScriptFunctionData>(
CSharpUnrealScriptFunctionData{0, 0, function, pre_callback, post_callback}));
CallbackId pre_id;
CallbackId post_id;
if (pre_callback)
pre_id = function->RegisterPreHook(&CSharpUnrealScriptFunctionHookPre, custom_data.get());
else
pre_id = 0;
if (post_callback)
post_id = function->RegisterPostHook(&CSharpUnrealScriptFunctionHookPost, custom_data.get());
else
post_id = 0;
custom_data->pre_callback_id = pre_id;
custom_data->post_callback_id = post_id;
m_generic_hook_id_to_native_hook_id.emplace(++m_last_generic_hook_id, pre_id);
generic_pre_id = m_last_generic_hook_id;
m_generic_hook_id_to_native_hook_id.emplace(++m_last_generic_hook_id, post_id);
generic_post_id = m_last_generic_hook_id;
Output::send<LogLevel::Verbose>(STR("[RegisterHook] Registered native hook ({}, {}) for {}\n"),
generic_pre_id,
generic_post_id,
function->GetFullName());
}
else if (func_ptr && func_ptr == Unreal::UObject::ProcessInternalInternal.get_function_address() &&
!function->HasAnyFunctionFlags(FUNC_Native))
{
++m_last_generic_hook_id;
auto [callback_data, _] = m_script_hook_callbacks.emplace(function->GetFullName(), CSharpCallbackData{nullptr, {}});
callback_data->second.registry_indexes.emplace_back(CSharpCallbackData::RegistryIndex{pre_callback, m_last_generic_hook_id});

generic_pre_id = m_last_generic_hook_id;
generic_post_id = m_last_generic_hook_id;
Output::send<LogLevel::Verbose>(STR("[RegisterHook] Registered script hook ({}, {}) for {}\n"),
generic_pre_id,
generic_post_id,
function->GetFullName());
}

return CallbackIds { generic_pre_id, generic_post_id };
}

void Hooking::Unhook(PLH::x64Detour* Hook)
{
if (Hook) Hook->unHook();
}

bool Hooking::UnhookUFunction(UFunction* function, CallbackId CallbackId)
void Hooking::UnhookUFunction(UFunction* function, CallbackIds callback_ids)
{
return function->UnregisterHook(CallbackId);
if (const auto callback_data_it = m_script_hook_callbacks.find(function->GetFullName());
callback_data_it != m_script_hook_callbacks.end())
{
m_script_hook_callbacks.erase(callback_data_it);
}
else
{
function->UnregisterHook(callback_ids.pre_id);
function->UnregisterHook(callback_ids.post_id);
}
}

void Debug::Log(LogLevel::LogLevel Level, const char* Message)
Expand Down Expand Up @@ -627,5 +731,34 @@ namespace RC::DotNetLibrary
{
Enum->RemoveFromNamesAt(Index, Count, AllowShrinking);
}

int Function::GetParmsSize(UFunction* Func)
{
return Func->GetParmsSize();
}

int Function::GetOffsetOfParam(UFunction* Func, const char* Name)
{
const auto prop = Func->FindProperty(FName(to_wstring(Name)));
if (!prop)
throw std::runtime_error{std::format("Property not found: '{}'", Name)};
return prop->GetOffset_Internal();
}

int Function::GetSizeOfParam(UFunction* Func, const char* Name)
{
const auto prop = Func->FindProperty(FName(to_wstring(Name)));
if (!prop)
throw std::runtime_error{std::format("Property not found: '{}'", Name)};
return prop->GetSize();
}

int Function::GetReturnValueOffset(UFunction* Func)
{
const auto returnProp = Func->GetReturnProperty();
if (!returnProp)
throw std::runtime_error{std::format("ReturnProperty is null for '{}'", to_string(Func->GetFullName()))};
return returnProp->GetOffset_Internal();
}
}
}
}

0 comments on commit c173fad

Please sign in to comment.