From 2776767c62da7451d674aa45c7c0cbb66a4b7e63 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 10:58:58 -0700 Subject: [PATCH 001/207] Initial rough work on a C#/.NET API --- .gitignore | 4 +- CMakeLists.txt | 63 + cmake.toml | 26 + csharp-api/Plugin.cpp | 166 +++ csharp-api/Plugin.hpp | 1 + csharp-api/REFrameworkAPI.cpp | 25 + csharp-api/REFrameworkAPI.hpp | 23 + csharp-api/Wrappers/ApiWrapper.cs | 57 + csharp-api/test/Test/ApiWrapper.cs | 51 + csharp-api/test/Test/ManagedObjectWrapper.cs | 58 + csharp-api/test/Test/TDBWrapper.cs | 82 ++ csharp-api/test/Test/Test.cs | 165 +++ csharp-api/test/Test/Test.csproj | 35 + csharp-api/test/Test/Test.sln | 31 + csharp-api/test/Test/TypeDefinitionWrapper.cs | 187 +++ include/reframework/API.h | 4 + include/reframework/API.hpp | 1094 ++++++++++------- src/mods/PluginLoader.cpp | 1 + 18 files changed, 1641 insertions(+), 432 deletions(-) create mode 100644 csharp-api/Plugin.cpp create mode 100644 csharp-api/Plugin.hpp create mode 100644 csharp-api/REFrameworkAPI.cpp create mode 100644 csharp-api/REFrameworkAPI.hpp create mode 100644 csharp-api/Wrappers/ApiWrapper.cs create mode 100644 csharp-api/test/Test/ApiWrapper.cs create mode 100644 csharp-api/test/Test/ManagedObjectWrapper.cs create mode 100644 csharp-api/test/Test/TDBWrapper.cs create mode 100644 csharp-api/test/Test/Test.cs create mode 100644 csharp-api/test/Test/Test.csproj create mode 100644 csharp-api/test/Test/Test.sln create mode 100644 csharp-api/test/Test/TypeDefinitionWrapper.cs diff --git a/.gitignore b/.gitignore index 743ef9a33..8a8086b7e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ src/sdk/generated cmake-build* .idea/ build_vs2019_devmode_DMC5.bat -src/CommitHash.autogenerated \ No newline at end of file +src/CommitHash.autogenerated +csharp-api/test/Test/bin +csharp-api/test/Test/obj diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f44cbcfe..e16224c43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13105,3 +13105,66 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework unset(CMKR_SOURCES) endif() +# Target csharp-api +if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework + set(CMKR_TARGET csharp-api) + set(csharp-api_SOURCES "") + + list(APPEND csharp-api_SOURCES + "csharp-api/Plugin.cpp" + "csharp-api/REFrameworkAPI.cpp" + ) + + list(APPEND csharp-api_SOURCES + cmake.toml + ) + + set(CMKR_SOURCES ${csharp-api_SOURCES}) + add_library(csharp-api SHARED) + + if(csharp-api_SOURCES) + target_sources(csharp-api PRIVATE ${csharp-api_SOURCES}) + endif() + + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${csharp-api_SOURCES}) + + target_compile_features(csharp-api PUBLIC + cxx_std_20 + ) + + target_compile_options(csharp-api PUBLIC + "/EHa" + "/MD" + "/clr:netcore" + ) + + target_include_directories(csharp-api PUBLIC + "include/" + ) + + + set_target_properties(csharp-api PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" + DOTNET_TARGET_FRAMEWORK + net8.0 + DOTNET_TARGET_FRAMEWORK_VERSION + v8.0 + ) + + # Custom command to compile the Test.cs file + add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND dotnet build ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/Test --configuration Release -o ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ --framework net8.0 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/ + COMMENT "Building C# test project" + ) + + unset(CMKR_TARGET) + unset(CMKR_SOURCES) +endif() + diff --git a/cmake.toml b/cmake.toml index a307e4712..9f46d5dc2 100644 --- a/cmake.toml +++ b/cmake.toml @@ -378,3 +378,29 @@ link-libraries = [ [target.weapon_stay_big_plugin] type = "plugin" sources = ["examples/weapon_stay_big_plugin/weapon_stay_big.cpp"] + +[target.csharp-api] +type = "shared" +include-directories = ["include/"] +sources = ["csharp-api/**.cpp", "csharp-api/**.c"] +compile-features = ["cxx_std_20"] +compile-options = ["/EHa", "/MD", "/clr:netcore"] +condition = "build-framework" +link-libraries = [ +] +cmake-after = """ +# Custom command to compile the Test.cs file +add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND dotnet build ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/Test --configuration Release -o ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ --framework net8.0 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/ + COMMENT "Building C# test project" +) +""" + +[target.csharp-api.properties] +RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" +RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" +LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" +DOTNET_TARGET_FRAMEWORK = "net8.0" +DOTNET_TARGET_FRAMEWORK_VERSION = "v8.0" \ No newline at end of file diff --git a/csharp-api/Plugin.cpp b/csharp-api/Plugin.cpp new file mode 100644 index 000000000..bbd6f0e54 --- /dev/null +++ b/csharp-api/Plugin.cpp @@ -0,0 +1,166 @@ +extern "C" { + #include +} + +#include +#include +#include + +#include +#include + +#include "REFrameworkAPI.hpp" + +#include "Plugin.hpp" + +using namespace reframework; +using namespace System; +using namespace System::Runtime; + +void on_pre_begin_rendering() { + +} + +void on_post_end_rendering() { + +} + + +namespace REFManagedInternal { +public ref class API { +public: + static REFramework::API^ instance; +}; +} + +ref class CSharpAPIImpl { +public: + static bool ManagedImpl(uintptr_t param_raw) { + auto self = System::Reflection::Assembly::GetExecutingAssembly(); + + // Look for any DLLs in the "managed" directory, load them, then call a function in them (REFrameworkPlugin.Main) + // This is useful for loading C# plugins + // Create the REFramework::API class first though (managed) + REFManagedInternal::API::instance = gcnew REFramework::API(param_raw); + + std::filesystem::create_directories(std::filesystem::current_path() / "reframework" / "plugins" / "managed"); + + String^ managedDir = gcnew String((std::filesystem::current_path() / "reframework" / "plugins" / "managed").wstring().c_str()); + + bool ever_found = false; + auto files = System::IO::Directory::GetFiles(managedDir, "*.dll"); + + if (files->Length == 0) { + //API::get()->log_error("No DLLs found in %s", managedDir); + return false; + } + + for each (String^ file in files) { + Console::WriteLine(file); + System::Reflection::Assembly^ assem = System::Reflection::Assembly::LoadFrom(file); + + if (assem == nullptr) { + //API::get()->log_error("Failed to load assembly from %s", file); + Console::WriteLine("Failed to load assembly from " + file); + continue; + } + + // Iterate through all types in the assembly + for each (Type^ type in assem->GetTypes()) { + // Attempt to find the Main method with the expected signature in each type + System::Reflection::MethodInfo^ mainMethod = type->GetMethod( + "Main", + System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, + gcnew array{REFramework::API::typeid}); + + if (mainMethod != nullptr) { + Console::WriteLine("Found Main method in " + file); + //API::get()->log_info("Found Main method in %s", file); + // If found, invoke the method + + array^ args = gcnew array{REFManagedInternal::API::instance}; + mainMethod->Invoke(nullptr, args); + ever_found = true; + break; // Optional: break if you only expect one Main method per assembly + } + } + } + + if (!ever_found) { + //API::get()->log_error("No Main method found in any DLLs in %s", managedDir); + Console::WriteLine("No Main method found in any DLLs in " + managedDir); + } + + return true; + } + +private: +}; + +bool managed_impl(const REFrameworkPluginInitializeParam* param) try { + // Write to console using C++/CLI + System::String^ str = "Hello from C++/CLI!"; + System::Console::WriteLine(str); + + const auto functions = param->functions; + functions->on_pre_application_entry("BeginRendering", on_pre_begin_rendering); // Look at via.ModuleEntry or the wiki for valid names here + functions->on_post_application_entry("EndRendering", on_post_end_rendering); + + functions->log_error("%s %s", "Hello", "error"); + functions->log_warn("%s %s", "Hello", "warning"); + functions->log_info("%s %s", "Hello", "info"); + + // Make sure plugins that are loaded can find a reference to the current assembly + // Even though "this" is loaded currently, its not in the right context to be found + // So we have to load ourselves again, and call CSharpAPIImpl.ManagedImpl via a dynamic lookup + auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); + + // Get CSharpAPIImpl type + auto type = self->GetType("CSharpAPIImpl"); + + // Get ManagedImpl method + auto method = type->GetMethod("ManagedImpl", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); + + // Invoke ManagedImpl method + method->Invoke(nullptr, gcnew array{reinterpret_cast(param)}); + + return true; +} catch (System::Reflection::ReflectionTypeLoadException^ ex) { + auto loaderExceptions = ex->LoaderExceptions; + for each (Exception^ innerEx in loaderExceptions) { + //API::get()->log_error("Loader exception caught: %s", msclr::interop::marshal_as(innerEx->Message).c_str()); + System::Console::WriteLine(innerEx->Message); + } + // Optionally log the main exception as well + //API::get()->log_error("ReflectionTypeLoadException caught: %s", msclr::interop::marshal_as(ex->Message).c_str()); + System::Console::WriteLine(ex->Message); + return false; +} catch (System::Exception^ e) { + //API::get()->log_error("Exception caught: %s", msclr::interop::marshal_as(e->Message).c_str()); + System::Console::WriteLine(e->Message); + return false; +} catch (const std::exception& e) { + //API::get()->log_error("Exception caught: %s", e.what()); + System::Console::WriteLine(gcnew System::String(e.what())); + return false; +} catch (...) { + //API::get()->log_error("Unknown exception caught"); + System::Console::WriteLine("Unknown exception caught"); + return false; +} + +extern "C" __declspec(dllexport) bool reframework_plugin_initialize(const REFrameworkPluginInitializeParam* param) { + // Create a console + AllocConsole(); + + return managed_impl(param); +} + +extern "C" __declspec(dllexport) void reframework_plugin_required_version(REFrameworkPluginVersion* version) { + version->major = REFRAMEWORK_PLUGIN_VERSION_MAJOR; + version->minor = REFRAMEWORK_PLUGIN_VERSION_MINOR; + version->patch = REFRAMEWORK_PLUGIN_VERSION_PATCH; + + // Optionally, specify a specific game name that this plugin is compatible with. + //version->game_name = "MHRISE"; +} diff --git a/csharp-api/Plugin.hpp b/csharp-api/Plugin.hpp new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/csharp-api/Plugin.hpp @@ -0,0 +1 @@ +#pragma once diff --git a/csharp-api/REFrameworkAPI.cpp b/csharp-api/REFrameworkAPI.cpp new file mode 100644 index 000000000..a3baa2573 --- /dev/null +++ b/csharp-api/REFrameworkAPI.cpp @@ -0,0 +1,25 @@ +#include + +#include "REFrameworkAPI.hpp" + + +REFramework::API::API(uintptr_t param) + : m_api{ reframework::API::initialize(param) } +{ + Console::WriteLine("Constructor called."); +} + + +REFramework::API::~API() +{ + Console::WriteLine("Destructor called."); +} + +void REFramework::API::Test() { + Console::WriteLine("Test called."); + m_api->log_info("Hello from C++/CLI!"); +} + +reframework::API^ REFramework::API::Get() { + return m_api; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkAPI.hpp b/csharp-api/REFrameworkAPI.hpp new file mode 100644 index 000000000..1076ba95d --- /dev/null +++ b/csharp-api/REFrameworkAPI.hpp @@ -0,0 +1,23 @@ +#pragma once + +namespace reframework { +ref class API; +} + +using namespace System; + +namespace REFramework { +public ref class API +{ +public: + API(uintptr_t param); + ~API(); + + void Test(); + + reframework::API^ Get(); + +protected: + reframework::API^ m_api; +}; +} \ No newline at end of file diff --git a/csharp-api/Wrappers/ApiWrapper.cs b/csharp-api/Wrappers/ApiWrapper.cs new file mode 100644 index 000000000..70e80ffa3 --- /dev/null +++ b/csharp-api/Wrappers/ApiWrapper.cs @@ -0,0 +1,57 @@ +using System; +using reframework; + +public class APIWrapper +{ + private readonly reframework.API _original; + + public APIWrapper(API original) + { + _original = original; + } + + public API.REFrameworkPluginInitializeParam* Param() + { + return _original.param(); + } + + public API.REFrameworkSDKData* Sdk() + { + return _original.sdk(); + } + + public API.TDB Tdb() + { + return _original.tdb(); + } + + public API.ResourceManager ResourceManager() + { + return _original.resource_manager(); + } + + public API.REFramework_ Reframework() + { + return _original.reframework(); + } + + public API.VMContext GetVmContext() + { + return _original.get_vm_context(); + } + + public ManagedObject Typeof(String name) + { + return _original.Invoke("typeof", name); + } + + public ManagedObject GetManagedSingleton(String name) + { + return _original.get_managed_singleton(name); + } + + public Void* GetNativeSingleton(String name) + { + return _original.get_native_singleton(name); + } +} diff --git a/csharp-api/test/Test/ApiWrapper.cs b/csharp-api/test/Test/ApiWrapper.cs new file mode 100644 index 000000000..94bbcb78b --- /dev/null +++ b/csharp-api/test/Test/ApiWrapper.cs @@ -0,0 +1,51 @@ +using System; +using reframework; + +public class APIWrapper +{ + private readonly reframework.API _original; + + public APIWrapper(API original) + { + _original = original; + } + + public TDBWrapper GetTDB() + { + return new TDBWrapper(_original.tdb()); + } + + public API.ResourceManager GetResourceManager() + { + return _original.resource_manager(); + } + + public API.REFramework_ Get() + { + return _original.reframework(); + } + + public API.VMContext GetVMContext() + { + return _original.get_vm_context(); + } + + public API.ManagedObject TypeOf(String name) + { + return (API.ManagedObject)_original.GetType().InvokeMember("typeof", System.Reflection.BindingFlags.Public, null, _original, new object[]{ _original, name }); + } + + public API.ManagedObject GetManagedSingleton(String name) + { + return _original.get_managed_singleton(name); + } + + public List GetManagedSingletons() { + return _original.get_managed_singletons(); + } + + public System.UIntPtr GetNativeSingleton(String name) + { + return (System.UIntPtr)_original.get_native_singleton(name); + } +} diff --git a/csharp-api/test/Test/ManagedObjectWrapper.cs b/csharp-api/test/Test/ManagedObjectWrapper.cs new file mode 100644 index 000000000..d631c3fde --- /dev/null +++ b/csharp-api/test/Test/ManagedObjectWrapper.cs @@ -0,0 +1,58 @@ +using System; +using static reframework.API; +public class ManagedObjectWrapper { + private readonly ManagedObject _original; + + public ManagedObjectWrapper(ManagedObject original) { + _original = original; + _original.add_ref(); + } + + ~ManagedObjectWrapper() { + _original.release(); + } + + public void AddRef() { + _original.add_ref(); + } + + public void Release() { + _original.release(); + } + + public TypeDefinitionWrapper GetTypeDefinition() { + return new TypeDefinitionWrapper(_original.get_type_definition()); + } + + public Boolean IsManagedObject() { + return _original.is_managed_object(); + } + + public UInt32 GetRefCount() { + return _original.get_ref_count(); + } + + public UInt32 GetVmObjType() { + return _original.get_vm_obj_type(); + } + + public TypeInfo GetTypeInfo() { + return _original.get_type_info(); + } + + /*public Void* GetReflectionProperties() { + return _original.get_reflection_properties(); + }*/ + + /*public ReflectionProperty GetReflectionPropertyDescriptor(basic_string_view> name) { + return _original.get_reflection_property_descriptor(name); + } + + public ReflectionMethod GetReflectionMethodDescriptor(basic_string_view> name) { + return _original.get_reflection_method_descriptor(name); + }*/ + + public DotNetInvokeRet Invoke(String methodName, object[] args) { + return _original.invoke(methodName, args); + } +} diff --git a/csharp-api/test/Test/TDBWrapper.cs b/csharp-api/test/Test/TDBWrapper.cs new file mode 100644 index 000000000..2a11a4a96 --- /dev/null +++ b/csharp-api/test/Test/TDBWrapper.cs @@ -0,0 +1,82 @@ +using System; + +using static reframework.API; +public class TDBWrapper { + private readonly TDB _original; + + public TDBWrapper(TDB original) { + _original = original; + } + + public UInt32 GetNumTypes() { + return _original.get_num_types(); + } + + public UInt32 GetNumMethods() { + return _original.get_num_methods(); + } + + public UInt32 GetNumFields() { + return _original.get_num_fields(); + } + + public UInt32 GetNumProperties() { + return _original.get_num_properties(); + } + + public UInt32 GetStringsSize() { + return _original.get_strings_size(); + } + + public UInt32 GetRawDataSize() { + return _original.get_raw_data_size(); + } + + /*public SByte* GetStringDatabase() { + return _original.get_string_database(); + } + + public Byte* GetRawDatabase() { + return _original.get_raw_database(); + }*/ + + public Span GetRawData() { + return _original.get_raw_data(); + } + + public String GetString(UInt32 index) { + return _original.get_string(index); + } + + public TypeDefinitionWrapper GetType(UInt32 index) { + return new TypeDefinitionWrapper(_original.get_type(index)); + } + + public TypeDefinitionWrapper FindType(String name) { + return new TypeDefinitionWrapper(_original.find_type(name)); + } + + public TypeDefinitionWrapper FindTypeByFqn(UInt32 fqn) { + return new TypeDefinitionWrapper(_original.find_type_by_fqn(fqn)); + } + + public Method GetMethod(UInt32 index) { + return _original.get_method(index); + } + + public Method FindMethod(String type_name, String name) { + return _original.find_method(type_name, name); + } + + public Field GetField(UInt32 index) { + return _original.get_field(index); + } + + public Field FindField(String type_name, String name) { + return _original.find_field(type_name, name); + } + + public Property GetProperty(UInt32 index) { + return _original.get_property(index); + } +} diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs new file mode 100644 index 000000000..2c4c61236 --- /dev/null +++ b/csharp-api/test/Test/Test.cs @@ -0,0 +1,165 @@ +// Import REFramework::API + +using System; +using System.Reflection; + +using System.Text; +using System.IO; + +public static class ApiWrapperGenerator +{ + public static void GenerateWrapper(Type type, string outputPath) + { + var sb = new StringBuilder(); + sb.AppendLine("using System;"); + sb.AppendFormat("public class {0}Wrapper\n", type.Name); + sb.AppendLine("{"); + sb.AppendFormat(" private readonly {0} _original;\n\n", type.Name); + sb.AppendFormat(" public {0}Wrapper({0} original)\n", type.Name); + sb.AppendLine(" {"); + sb.AppendLine(" _original = original;"); + sb.AppendLine(" }\n"); + + foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + { + var pascalCaseName = ConvertToPascalCase(method.Name); + var parameters = method.GetParameters(); + + sb.AppendFormat(" public {0} {1}({2})\n", + method.ReturnType.Name, pascalCaseName, GetParameterDeclaration(parameters)); + sb.AppendLine(" {"); + sb.AppendFormat(" {0}_original.{1}({2});\n", + method.ReturnType.Name == "void" ? "" : "return ", + method.Name, + GetParameterInvocation(parameters)); + sb.AppendLine(" }\n"); + } + + sb.AppendLine("}"); + + File.WriteAllText(outputPath, sb.ToString()); + } + + private static string ConvertToPascalCase(string snakeCaseName) + { + // Split the snake_case string into words + string[] words = snakeCaseName.Split('_'); + + // Capitalize the first letter of each word + for (int i = 0; i < words.Length; i++) + { + if (words[i].Length > 0) + { + words[i] = char.ToUpper(words[i][0]) + words[i].Substring(1); + } + } + + // Join the words to form the PascalCase string + string pascalCaseName = string.Join("", words); + + return pascalCaseName; + } + + private static string GetParameterDeclaration(ParameterInfo[] parameters) + { + var sb = new StringBuilder(); + for (int i = 0; i < parameters.Length; i++) + { + sb.AppendFormat("{0} {1}", parameters[i].ParameterType.Name, parameters[i].Name); + if (i < parameters.Length - 1) + { + sb.Append(", "); + } + } + return sb.ToString(); + } + + private static string GetParameterInvocation(ParameterInfo[] parameters) + { + var sb = new StringBuilder(); + for (int i = 0; i < parameters.Length; i++) + { + sb.Append(parameters[i].Name); + if (i < parameters.Length - 1) + { + sb.Append(", "); + } + } + return sb.ToString(); + } +} + +class REFrameworkPlugin { + public static void Main(REFramework.API api_) { + Console.WriteLine("Testing REFrameworkAPI..."); + + // Convert api.Get() type to pass to GenerateWrapper + var currentDir = Directory.GetCurrentDirectory(); + var targetType = typeof(reframework.API.Method); + var outputPath = Path.Combine(currentDir, "MethodWrapper.cs"); + + ApiWrapperGenerator.GenerateWrapper(targetType, outputPath); + + // Open in explorer + System.Diagnostics.Process.Start("explorer.exe", currentDir); + + var api = new APIWrapper(api_.Get()); + var tdb = api.GetTDB(); + + Console.WriteLine(tdb.GetNumTypes().ToString() + " types"); + + /*var typesSorted = new System.Collections.Generic.List(); + + for (uint i = 0; i < tdb.GetNumTypes(); i++) { + var t = tdb.GetType(i); + if (t == null) { + continue; + } + + typesSorted.Add(t.GetFullName()); + } + + typesSorted.Sort();*/ + + /*var singletons = api.GetManagedSingletons(); + + foreach (var singletonDesc in singletons) { + var singleton = new ManagedObjectWrapper(singletonDesc.instance); + + Console.WriteLine(singleton.GetTypeDefinition().GetFullName()); + + // Log all methods + var td = singleton.GetTypeDefinition(); + var methods = td.GetMethods(); + + foreach (var method in methods) { + Console.WriteLine(" " + method.get_name()); + } + + var fields = td.GetFields(); + + foreach (var field in fields) { + Console.WriteLine(" " + field.get_name()); + } + }*/ + + var sceneManager = api.GetNativeSingleton("via.SceneManager"); + Console.WriteLine("sceneManager: " + sceneManager); + var sceneManager_t = tdb.FindType("via.SceneManager"); + Console.WriteLine("sceneManager_t: " + sceneManager_t); + var get_CurrentScene = sceneManager_t.FindMethod("get_CurrentScene"); + Console.WriteLine("get_CurrentScene: " + get_CurrentScene); + var scene = get_CurrentScene.invoke(sceneManager, new object[]{}).Ptr; + + Console.WriteLine("scene: " + scene); + + if (scene != null) { + var scene_t = tdb.FindType("via.Scene"); + var set_TimeScale = scene_t.FindMethod("set_TimeScale"); + + Console.WriteLine("set_TimeScale: " + set_TimeScale); + + set_TimeScale.invoke(scene, new object[]{ 0.1f }); + } + } +}; \ No newline at end of file diff --git a/csharp-api/test/Test/Test.csproj b/csharp-api/test/Test/Test.csproj new file mode 100644 index 000000000..8f48dff9e --- /dev/null +++ b/csharp-api/test/Test/Test.csproj @@ -0,0 +1,35 @@ + + + + Library + net8.0 + enable + AnyCPU;x64 + x64 + + + + True + + + + True + + + + True + + + + True + + + + + ..\..\..\build\bin\csharp-api\csharp-api.dll + False + False + + + + diff --git a/csharp-api/test/Test/Test.sln b/csharp-api/test/Test/Test.sln new file mode 100644 index 000000000..dc9447815 --- /dev/null +++ b/csharp-api/test/Test/Test.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34330.188 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test.csproj", "{F1E90371-FCC8-45C3-8133-339A559CD37A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F1E90371-FCC8-45C3-8133-339A559CD37A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1E90371-FCC8-45C3-8133-339A559CD37A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1E90371-FCC8-45C3-8133-339A559CD37A}.Debug|x64.ActiveCfg = Debug|x64 + {F1E90371-FCC8-45C3-8133-339A559CD37A}.Debug|x64.Build.0 = Debug|x64 + {F1E90371-FCC8-45C3-8133-339A559CD37A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1E90371-FCC8-45C3-8133-339A559CD37A}.Release|Any CPU.Build.0 = Release|Any CPU + {F1E90371-FCC8-45C3-8133-339A559CD37A}.Release|x64.ActiveCfg = Release|x64 + {F1E90371-FCC8-45C3-8133-339A559CD37A}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E9C2E7FC-0E99-4A61-99A3-1D337164B31E} + EndGlobalSection +EndGlobal diff --git a/csharp-api/test/Test/TypeDefinitionWrapper.cs b/csharp-api/test/Test/TypeDefinitionWrapper.cs new file mode 100644 index 000000000..e0463eae1 --- /dev/null +++ b/csharp-api/test/Test/TypeDefinitionWrapper.cs @@ -0,0 +1,187 @@ +using System; +using reframework; + +public class TypeDefinitionWrapper +{ + private readonly reframework.API.TypeDefinition _original; + + public TypeDefinitionWrapper(API.TypeDefinition original) + { + _original = original; + } + + + public UInt32 GetIndex() + { + return _original.get_index(); + } + + public UInt32 GetSize() + { + return _original.get_size(); + } + + public UInt32 GetValuetypeSize() + { + return _original.get_valuetype_size(); + } + + public UInt32 GetFqn() + { + return _original.get_fqn(); + } + + public String GetName() + { + return _original.get_name(); + } + + public String GetNamespace() + { + return _original.get_namespace(); + } + + public String GetFullName() + { + return _original.get_full_name(); + } + + public Boolean HasFieldptrOffset() + { + return _original.has_fieldptr_offset(); + } + + public Int32 GetFieldptrOffset() + { + return _original.get_fieldptr_offset(); + } + + public UInt32 GetNumMethods() + { + return _original.get_num_methods(); + } + + public UInt32 GetNumFields() + { + return _original.get_num_fields(); + } + + public UInt32 GetNumProperties() + { + return _original.get_num_properties(); + } + + public Boolean IsDerivedFrom(String other) + { + return _original.is_derived_from(other); + } + + public Boolean IsDerivedFrom(reframework.API.TypeDefinition other) + { + return _original.is_derived_from(other); + } + + public Boolean IsDerivedFrom(TypeDefinitionWrapper other) { + return _original.is_derived_from(other._original); + } + + public Boolean IsValuetype() + { + return _original.is_valuetype(); + } + + public Boolean IsEnum() + { + return _original.is_enum(); + } + + public Boolean IsByRef() + { + return _original.is_by_ref(); + } + + public Boolean IsPointer() + { + return _original.is_pointer(); + } + + public Boolean IsPrimitive() + { + return _original.is_primitive(); + } + + public UInt32 GetVmObjType() + { + return _original.get_vm_obj_type(); + } + + public API.Method FindMethod(String name) + { + return _original.find_method(name); + } + + public API.Field FindField(String name) + { + return _original.find_field(name); + } + + public API.Property FindProperty(String name) + { + return _original.find_property(name); + } + + public List GetMethods() + { + return _original.get_methods(); + } + + public List GetFields() + { + return _original.get_fields(); + } + + public List GetProperties() + { + return _original.get_properties(); + } + + /*public Void* GetInstance() + { + return _original.get_instance(); + } + + public Void* CreateInstanceDeprecated() + { + return _original.create_instance_deprecated(); + }*/ + + public API.ManagedObject CreateInstance(Int32 flags) + { + return _original.create_instance(flags); + } + + public TypeDefinitionWrapper GetParentType() + { + return new TypeDefinitionWrapper(_original.get_parent_type()); + } + + public TypeDefinitionWrapper GetDeclaringType() + { + return new TypeDefinitionWrapper(_original.get_declaring_type()); + } + + public TypeDefinitionWrapper GetUnderlyingType() + { + return new TypeDefinitionWrapper(_original.get_underlying_type()); + } + + public API.TypeInfo GetTypeInfo() + { + return _original.get_type_info(); + } + + public API.ManagedObject GetRuntimeType() + { + return _original.get_runtime_type(); + } +} diff --git a/include/reframework/API.h b/include/reframework/API.h index eddf62e20..3da47aea3 100644 --- a/include/reframework/API.h +++ b/include/reframework/API.h @@ -98,8 +98,12 @@ typedef struct { } REFrameworkRendererData; /* strong typedefs */ +#ifdef __cplusplus_cli +#define DECLARE_REFRAMEWORK_HANDLE(name) using name = void* +#else #define DECLARE_REFRAMEWORK_HANDLE(name) struct name##__ { int unused; }; \ typedef struct name##__ *name +#endif DECLARE_REFRAMEWORK_HANDLE(REFrameworkTypeDefinitionHandle); DECLARE_REFRAMEWORK_HANDLE(REFrameworkMethodHandle); diff --git a/include/reframework/API.hpp b/include/reframework/API.hpp index adc73ab71..6be780982 100644 --- a/include/reframework/API.hpp +++ b/include/reframework/API.hpp @@ -1,5 +1,14 @@ #pragma once +#ifdef __cplusplus_cli +#include +#include +//#include +#define REF_CONTAINER_NAMESPACE cliext +#else +#define REF_CONTAINER_NAMESPACE std +#endif + extern "C" { #include "API.h" } @@ -32,11 +41,63 @@ struct InvokeRet { }; #pragma pack(pop) + + +#ifdef __cplusplus_cli +#define CPP_MEMBER_CONST +#define CPP_POINTER ^ +#define REF_MAKE_POINTER(Tx, x) gcnew Tx(x) +#define CPP_CONTAINER_RET ^ +#define CPP_VECTOR(Tx) System::Collections::Generic::List^ + +// I had to resort to this because was having issues with template types causing compiler crashes +#define PUBLIC_POINTER_CONTAINER(X, Tx) ref struct X { public: X(Tx ptr) : m_impl{gcnew System::UIntPtr((void*)ptr)} { } operator Tx() { return (Tx)m_impl->ToPointer(); } void* ptr() { return m_impl->ToPointer(); } private: System::UIntPtr^ m_impl; public: + + +ref class API; + +public ref class API { +#else +#define CPP_MEMBER_CONST const +#define CPP_POINTER * +#define REF_MAKE_POINTER(Tx, x) ((Tx*)x) +#define CPP_CONTAINER_RET +#define CPP_VECTOR(Tx) std::vector + +class API; + class API { +#endif private: + const ::REFrameworkPluginInitializeParam* m_param; + const REFrameworkSDKData* m_sdk; + +#ifndef __cplusplus_cli + std::recursive_mutex m_lua_mtx{}; +#endif + +#ifdef __cplusplus_cli + static API^ s_instance{}; +#else static inline std::unique_ptr s_instance{}; +#endif public: +#ifdef __cplusplus_cli + ref struct TDB; + ref struct TypeDefinition; + ref struct Method; + ref struct Field; + ref struct Property; + ref struct ManagedObject; + ref struct ResourceManager; + ref struct Resource; + ref struct TypeInfo; + ref struct VMContext; + ref struct ReflectionProperty; + ref struct ReflectionMethod; + ref struct REFramework_; +#else struct TDB; struct TypeDefinition; struct Method; @@ -49,7 +110,10 @@ class API { struct VMContext; struct ReflectionProperty; struct ReflectionMethod; + struct REFramework_; +#endif +#ifndef __cplusplus_cli struct LuaLock { LuaLock() { API::s_instance->lock_lua(); @@ -59,24 +123,22 @@ class API { API::s_instance->unlock_lua(); } }; +#endif public: // ALWAYS call initialize first in reframework_plugin_initialize - static auto& initialize(const REFrameworkPluginInitializeParam* param) { - if (param == nullptr) { - throw std::runtime_error("param is null"); - } - - if (s_instance != nullptr) { - throw std::runtime_error("API already initialized"); - } - - s_instance = std::make_unique(param); - return s_instance; - } +#ifndef __cplusplus_cli + static API& initialize(const REFrameworkPluginInitializeParam* param); +#else + static API^ initialize(uintptr_t param); +#endif // only call this AFTER calling initialize +#ifndef __cplusplus_cli static auto& get() { +#else + static auto get() { +#endif if (s_instance == nullptr) { throw std::runtime_error("API not initialized"); } @@ -85,39 +147,45 @@ class API { } public: - API(const REFrameworkPluginInitializeParam* param) +#ifdef __cplusplus_cli + API(uintptr_t param) + : m_param{reinterpret_cast(param)}, + m_sdk{m_param->sdk} + { + } +#else + API(const REFrameworkPluginInitializeParam* param) : m_param{param}, m_sdk{param->sdk} { } +#endif virtual ~API() { } - inline const auto param() const { + inline const REFrameworkPluginInitializeParam* param() CPP_MEMBER_CONST { return m_param; } - inline const REFrameworkSDKData* sdk() const { + inline const REFrameworkSDKData* sdk() CPP_MEMBER_CONST { return m_sdk; } - inline const auto tdb() const { - static const auto fn = sdk()->functions->get_tdb; - return (TDB*)fn(); + inline TDB CPP_POINTER tdb() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(TDB, sdk()->functions->get_tdb()); } - inline const auto resource_manager() const { - static const auto fn = sdk()->functions->get_resource_manager; - return (ResourceManager*)fn(); + inline ResourceManager CPP_POINTER resource_manager() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(ResourceManager, sdk()->functions->get_resource_manager()); } - inline const auto reframework() const { - static const auto fn = param()->functions; - return (REFramework*)fn; + inline REFramework_ CPP_POINTER reframework() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(REFramework_, param()->functions); } +#ifndef __cplusplus_cli void lock_lua() { m_lua_mtx.lock(); m_param->functions->lock_lua(); @@ -127,40 +195,78 @@ class API { m_param->functions->unlock_lua(); m_lua_mtx.unlock(); } +#endif template void log_error(const char* format, Args... args) { m_param->functions->log_error(format, args...); } template void log_warn(const char* format, Args... args) { m_param->functions->log_warn(format, args...); } template void log_info(const char* format, Args... args) { m_param->functions->log_info(format, args...); } - API::VMContext* get_vm_context() const { - static const auto fn = sdk()->functions->get_vm_context; - return (API::VMContext*)fn(); + API::VMContext CPP_POINTER get_vm_context() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::VMContext, sdk()->functions->get_vm_context()); + } + + API::ManagedObject CPP_POINTER typeof(const char* name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::ManagedObject, sdk()->functions->typeof_(name)); + } + +#ifdef __cplusplus_cli + API::ManagedObject CPP_POINTER typeof(System::String^ name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::ManagedObject, sdk()->functions->typeof_(msclr::interop::marshal_as(name).c_str())); + } +#endif + + + API::ManagedObject CPP_POINTER get_managed_singleton(std::string_view name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::ManagedObject, sdk()->functions->get_managed_singleton(name.data())); } - API::ManagedObject* typeof(const char* name) const { - static const auto fn = sdk()->functions->typeof_; - return (API::ManagedObject*)fn(name); +#ifdef __cplusplus_cli + API::ManagedObject CPP_POINTER get_managed_singleton(System::String^ name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::ManagedObject, sdk()->functions->get_managed_singleton(msclr::interop::marshal_as(name).c_str())); } +#endif - API::ManagedObject* get_managed_singleton(std::string_view name) const { - static const auto fn = sdk()->functions->get_managed_singleton; - return (API::ManagedObject*)fn(name.data()); + void* get_native_singleton(std::string_view name) CPP_MEMBER_CONST { + return sdk()->functions->get_native_singleton(name.data()); } - void* get_native_singleton(std::string_view name) const { - static const auto fn = sdk()->functions->get_native_singleton; - return fn(name.data()); +#ifdef __cplusplus_cli + uintptr_t get_native_singleton(System::String^ name) { + return uintptr_t(sdk()->functions->get_native_singleton(msclr::interop::marshal_as(name).c_str())); } +#endif - std::vector get_managed_singletons() const { - static const auto fn = sdk()->functions->get_managed_singletons; +#ifdef __cplusplus_cli + ref struct DotNetInvokeRet { + array^ Bytes; + uint8_t Byte; + uint16_t Word; + uint32_t DWord; + float F; + uint64_t QWord; + double D; + System::Object^ Ptr; + bool ExceptionThrown; + }; +#endif + +#ifdef __cplusplus_cli + ref struct DotNetManagedSingleton { + API::ManagedObject^ instance; + API::TypeDefinition^ type; + API::TypeInfo^ type_info; + }; + System::Collections::Generic::List^ get_managed_singletons() CPP_MEMBER_CONST { +#else + std::vector get_managed_singletons() CPP_MEMBER_CONST { +#endif std::vector out{}; out.resize(512); uint32_t count{}; - auto result = fn(&out[0], out.size() * sizeof(REFrameworkManagedSingleton), &count); + auto result = sdk()->functions->get_managed_singletons(&out[0], out.size() * sizeof(REFrameworkManagedSingleton), &count); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -173,18 +279,36 @@ class API { #endif out.resize(count); + +#ifdef __cplusplus_cli + System::Collections::Generic::List^ out2 = gcnew System::Collections::Generic::List(); + + for (auto& s : out) { + DotNetManagedSingleton^ managed = gcnew DotNetManagedSingleton(); + managed->instance = REF_MAKE_POINTER(API::ManagedObject, s.instance); + managed->type = REF_MAKE_POINTER(API::TypeDefinition, s.t); + managed->type_info = REF_MAKE_POINTER(API::TypeInfo, s.type_info); + + out2->Add(managed); + } + + return out2; +#else return out; +#endif } + +#ifdef __cplusplus_cli + +#endif - std::vector get_native_singletons() const { - static const auto fn = sdk()->functions->get_native_singletons; - + std::vector get_native_singletons() CPP_MEMBER_CONST { std::vector out{}; out.resize(512); uint32_t count{}; - auto result = fn(&out[0], out.size() * sizeof(REFrameworkNativeSingleton), &count); + auto result = sdk()->functions->get_native_singletons(&out[0], out.size() * sizeof(REFrameworkNativeSingleton), &count); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -201,321 +325,448 @@ class API { } public: - struct TDB { - operator ::REFrameworkTDBHandle() const { - return (::REFrameworkTDBHandle)this; +#ifdef __cplusplus_cli + + + +#else + template + struct PointerContainer { + public: + PointerContainer(T ptr) {}; + + operator T() const { + return (T)this; + } + + void* ptr() const { + return (void*)this; } + }; +#endif + + //#define PUBLIC_POINTER_CONTAINER(Tx) : public PointerContainer + //#define POINTER_CONTAINER_CONSTRUCTOR(WrapperType, Tx) WrapperType(Tx ptr) : PointerContainer(ptr) {} - uint32_t get_num_types() const { - static const auto fn = API::s_instance->sdk()->tdb->get_num_types; - return fn(*this); + PUBLIC_POINTER_CONTAINER(TDB, ::REFrameworkTDBHandle) + uint32_t get_num_types() CPP_MEMBER_CONST { + return API::s_instance->sdk()->tdb->get_num_types(*this); } - uint32_t get_num_methods() const { - static const auto fn = API::s_instance->sdk()->tdb->get_num_methods; - return fn(*this); + uint32_t get_num_methods() CPP_MEMBER_CONST { + return API::s_instance->sdk()->tdb->get_num_methods(*this); } - uint32_t get_num_fields() const { - static const auto fn = API::s_instance->sdk()->tdb->get_num_fields; - return fn(*this); + uint32_t get_num_fields() CPP_MEMBER_CONST { + return API::s_instance->sdk()->tdb->get_num_fields(*this); } - uint32_t get_num_properties() const { - static const auto fn = API::s_instance->sdk()->tdb->get_num_properties; - return fn(*this); + uint32_t get_num_properties() CPP_MEMBER_CONST { + return API::s_instance->sdk()->tdb->get_num_properties(*this); } - uint32_t get_strings_size() const { - static const auto fn = API::s_instance->sdk()->tdb->get_strings_size; - return fn(*this); + uint32_t get_strings_size() CPP_MEMBER_CONST { + return API::s_instance->sdk()->tdb->get_strings_size(*this); } - uint32_t get_raw_data_size() const { - static const auto fn = API::s_instance->sdk()->tdb->get_raw_data_size; - return fn(*this); + uint32_t get_raw_data_size() CPP_MEMBER_CONST { + return API::s_instance->sdk()->tdb->get_raw_data_size(*this); } - const char* get_string_database() const { - static const auto fn = API::s_instance->sdk()->tdb->get_string_database; - return fn(*this); + const char* get_string_database() CPP_MEMBER_CONST { + return API::s_instance->sdk()->tdb->get_string_database(*this); } - uint8_t* get_raw_database() const { - static const auto fn = API::s_instance->sdk()->tdb->get_raw_database; - return (uint8_t*)fn(*this); +#ifdef __cplusplus_cli + // Use Span instead + System::Span get_raw_data() { + return System::Span((uint8_t*)API::s_instance->sdk()->tdb->get_raw_database(*this), get_raw_data_size()); + } + + System::String^ get_string(uint32_t index) { + if (index >= get_strings_size()) { + return System::String::Empty; + } + + return gcnew System::String(API::s_instance->sdk()->tdb->get_string_database(*this) + index); } +#endif - API::TypeDefinition* get_type(uint32_t index) const { - static const auto fn = API::s_instance->sdk()->tdb->get_type; - return (API::TypeDefinition*)fn(*this, index); + uint8_t* get_raw_database() CPP_MEMBER_CONST { + return (uint8_t*)API::s_instance->sdk()->tdb->get_raw_database(*this); } - API::TypeDefinition* find_type(std::string_view name) const { - static const auto fn = API::s_instance->sdk()->tdb->find_type; - return (API::TypeDefinition*)fn(*this, name.data()); + API::TypeDefinition CPP_POINTER get_type(uint32_t index) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->tdb->get_type(*this, index)); } - API::TypeDefinition* find_type_by_fqn(uint32_t fqn) const { - static const auto fn = API::s_instance->sdk()->tdb->find_type_by_fqn; - return (API::TypeDefinition*)fn(*this, fqn); + API::TypeDefinition CPP_POINTER find_type(std::string_view name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->tdb->find_type(*this, name.data())); } - API::Method* get_method(uint32_t index) const { - static const auto fn = API::s_instance->sdk()->tdb->get_method; - return (API::Method*)fn(*this, index); +#ifdef __cplusplus_cli + API::TypeDefinition CPP_POINTER find_type(System::String^ name) { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->tdb->find_type(*this, msclr::interop::marshal_as(name).c_str())); } +#endif - API::Method* find_method(std::string_view type_name, std::string_view name) const { - static const auto fn = API::s_instance->sdk()->tdb->find_method; - return (API::Method*)fn(*this, type_name.data(), name.data()); + API::TypeDefinition CPP_POINTER find_type_by_fqn(uint32_t fqn) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->tdb->find_type_by_fqn(*this, fqn)); } - API::Field* get_field(uint32_t index) const { - static const auto fn = API::s_instance->sdk()->tdb->get_field; - return (API::Field*)fn(*this, index); + API::Method CPP_POINTER get_method(uint32_t index) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->tdb->get_method(*this, index)); } - API::Field* find_field(std::string_view type_name, std::string_view name) const { - static const auto fn = API::s_instance->sdk()->tdb->find_field; - return (API::Field*)fn(*this, type_name.data(), name.data()); + API::Method CPP_POINTER find_method(std::string_view type_name, std::string_view name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->tdb->find_method(*this, type_name.data(), name.data())); } - API::Property* get_property(uint32_t index) const { - static const auto fn = API::s_instance->sdk()->tdb->get_property; - return (API::Property*)fn(*this, index); +#ifdef __cplusplus_cli + API::Method CPP_POINTER find_method(System::String^ type_name, System::String^ name) { + return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->tdb->find_method(*this, msclr::interop::marshal_as(type_name).c_str(), msclr::interop::marshal_as(name).c_str())); } - }; +#endif + + API::Field CPP_POINTER get_field(uint32_t index) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->tdb->get_field(*this, index)); + } + + API::Field CPP_POINTER find_field(std::string_view type_name, std::string_view name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->tdb->find_field(*this, type_name.data(), name.data())); + } + +#ifdef __cplusplus_cli + API::Field CPP_POINTER find_field(System::String^ type_name, System::String^ name) { + return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->tdb->find_field(*this, msclr::interop::marshal_as(type_name).c_str(), msclr::interop::marshal_as(name).c_str())); + } +#endif - struct REFramework { - operator ::REFrameworkHandle() { - return (::REFrameworkHandle)this; + API::Property CPP_POINTER get_property(uint32_t index) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::Property, API::s_instance->sdk()->tdb->get_property(*this, index)); } + }; - bool is_drawing_ui() const { - static const auto fn = API::s_instance->param()->functions->is_drawing_ui; - return fn(); + PUBLIC_POINTER_CONTAINER(REFramework_, const ::REFrameworkPluginFunctions*) + bool is_drawing_ui() CPP_MEMBER_CONST { + return API::s_instance->param()->functions->is_drawing_ui(); } }; - struct TypeDefinition { - operator ::REFrameworkTypeDefinitionHandle() const { - return (::REFrameworkTypeDefinitionHandle)this; + PUBLIC_POINTER_CONTAINER(TypeDefinition, ::REFrameworkTypeDefinitionHandle) + uint32_t get_index() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_index(*this); } - uint32_t get_index() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_index; - return fn(*this); + uint32_t get_size() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_size(*this); } - uint32_t get_size() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_size; - return fn(*this); + uint32_t get_valuetype_size() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_valuetype_size(*this); } - uint32_t get_valuetype_size() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_valuetype_size; - return fn(*this); + uint32_t get_fqn() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_fqn(*this); } - uint32_t get_fqn() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_fqn; - return fn(*this); +#ifdef __cplusplus_cli + System::String^ get_name() { + return gcnew System::String(API::s_instance->sdk()->type_definition->get_name(*this)); } - const char* get_name() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_name; - return fn(*this); + System::String^ get_namespace() { + return gcnew System::String(API::s_instance->sdk()->type_definition->get_namespace(*this)); + } +#else + const char* get_name() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_name(*this); } - const char* get_namespace() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_namespace; - return fn(*this); + const char* get_namespace() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_namespace(*this); } +#endif - std::string get_full_name() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_full_name; +#ifdef __cplusplus_cli + System::String^ get_full_name() { +#else + std::string get_full_name() CPP_MEMBER_CONST { +#endif std::string buffer{}; buffer.resize(512); uint32_t real_size{0}; - auto result = fn(*this, &buffer[0], buffer.size(), &real_size); + + const auto sdk = API::s_instance->sdk(); + auto result = sdk->type_definition->get_full_name(*this, &buffer[0], buffer.size(), &real_size); if (result != REFRAMEWORK_ERROR_NONE) { return ""; } buffer.resize(real_size); + +#ifdef __cplusplus_cli + return gcnew System::String(buffer.c_str()); +#else return buffer; +#endif } - bool has_fieldptr_offset() const { - static const auto fn = API::s_instance->sdk()->type_definition->has_fieldptr_offset; - return fn(*this); + bool has_fieldptr_offset() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->has_fieldptr_offset(*this); } - int32_t get_fieldptr_offset() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_fieldptr_offset; - return fn(*this); + int32_t get_fieldptr_offset() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_fieldptr_offset(*this); } - uint32_t get_num_methods() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_num_methods; - return fn(*this); + uint32_t get_num_methods() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_num_methods(*this); } - uint32_t get_num_fields() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_num_fields; - return fn(*this); + uint32_t get_num_fields() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_num_fields(*this); } - uint32_t get_num_properties() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_num_properties; - return fn(*this); + uint32_t get_num_properties() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_num_properties(*this); } - bool is_derived_from(API::TypeDefinition* other) { - static const auto fn = API::s_instance->sdk()->type_definition->is_derived_from; - return fn(*this, *other); + bool is_derived_from(API::TypeDefinition CPP_POINTER other) { + return API::s_instance->sdk()->type_definition->is_derived_from(*this, *other); } bool is_derived_from(std::string_view other) { - static const auto fn = API::s_instance->sdk()->type_definition->is_derived_from_by_name; - return fn(*this, other.data()); + return API::s_instance->sdk()->type_definition->is_derived_from_by_name(*this, other.data()); + } + +#ifdef __cplusplus_cli + bool is_derived_from(System::String^ other) { + return API::s_instance->sdk()->type_definition->is_derived_from_by_name(*this, msclr::interop::marshal_as(other).c_str()); + } +#endif + + bool is_valuetype() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->is_valuetype(*this); } - bool is_valuetype() const { - static const auto fn = API::s_instance->sdk()->type_definition->is_valuetype; - return fn(*this); + bool is_enum() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->is_enum(*this); } - bool is_enum() const { - static const auto fn = API::s_instance->sdk()->type_definition->is_enum; - return fn(*this); + bool is_by_ref() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->is_by_ref(*this); } - bool is_by_ref() const { - static const auto fn = API::s_instance->sdk()->type_definition->is_by_ref; - return fn(*this); + bool is_pointer() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->is_pointer(*this); } - bool is_pointer() const { - static const auto fn = API::s_instance->sdk()->type_definition->is_pointer; - return fn(*this); + bool is_primitive() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->is_primitive(*this); } - bool is_primitive() const { - static const auto fn = API::s_instance->sdk()->type_definition->is_primitive; - return fn(*this); + ::REFrameworkVMObjType get_vm_obj_type() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_vm_obj_type(*this); } - ::REFrameworkVMObjType get_vm_obj_type() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_vm_obj_type; - return fn(*this); + API::Method CPP_POINTER find_method(std::string_view name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->type_definition->find_method(*this, name.data())); } - API::Method* find_method(std::string_view name) const { - static const auto fn = API::s_instance->sdk()->type_definition->find_method; - return (API::Method*)fn(*this, name.data()); + API::Field CPP_POINTER find_field(std::string_view name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->type_definition->find_field(*this, name.data())); } - API::Field* find_field(std::string_view name) const { - static const auto fn = API::s_instance->sdk()->type_definition->find_field; - return (API::Field*)fn(*this, name.data()); + API::Property CPP_POINTER find_property(std::string_view name) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::Property, API::s_instance->sdk()->type_definition->find_property(*this, name.data())); } - API::Property* find_property(std::string_view name) const { - static const auto fn = API::s_instance->sdk()->type_definition->find_property; - return (API::Property*)fn(*this, name.data()); +#ifdef __cplusplus_cli + API::Method CPP_POINTER find_method(System::String^ name) { + return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->type_definition->find_method(*this, msclr::interop::marshal_as(name).c_str())); } - std::vector get_methods() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_methods; + API::Field CPP_POINTER find_field(System::String^ name) { + return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->type_definition->find_field(*this, msclr::interop::marshal_as(name).c_str())); + } + + API::Property CPP_POINTER find_property(System::String^ name) { + return REF_MAKE_POINTER(API::Property, API::s_instance->sdk()->type_definition->find_property(*this, msclr::interop::marshal_as(name).c_str())); + } +#endif - std::vector methods; + CPP_VECTOR(API::Method CPP_POINTER) get_methods() CPP_MEMBER_CONST { + std::vector methods; methods.resize(get_num_methods()); - auto result = fn(*this, (REFrameworkMethodHandle*)&methods[0], methods.size() * sizeof(API::Method*), nullptr); + auto result = API::s_instance->sdk()->type_definition->get_methods(*this, (::REFrameworkMethodHandle*)methods.data(), methods.size() * sizeof(void*), nullptr); if (result != REFRAMEWORK_ERROR_NONE) { return {}; } - return methods; - } + #ifdef __cplusplus_cli + //cliext::vector out = gcnew cliext::vector(); + System::Collections::Generic::List^ out = gcnew System::Collections::Generic::List(); + + for (auto m : methods) { + //out.push_back(REF_MAKE_POINTER(API::Method, m)); + out->Add(REF_MAKE_POINTER(API::Method, (::REFrameworkMethodHandle)m)); + } - std::vector get_fields() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_fields; + return out; + #else + return *(std::vector*)&methods; + #endif + } - std::vector fields; + CPP_VECTOR(API::Field CPP_POINTER) get_fields() CPP_MEMBER_CONST { + std::vector fields; fields.resize(get_num_fields()); - auto result = fn(*this, (REFrameworkFieldHandle*)&fields[0], fields.size() * sizeof(API::Field*), nullptr); + auto result = API::s_instance->sdk()->type_definition->get_fields(*this, (REFrameworkFieldHandle*)&fields[0], fields.size() * sizeof(void*), nullptr); if (result != REFRAMEWORK_ERROR_NONE) { return {}; } - return fields; + #ifdef __cplusplus_cli + //cliext::::vector out = gcnew cliext::vector(); + System::Collections::Generic::List^ out = gcnew System::Collections::Generic::List(); + + for (auto f : fields) { + out->Add(REF_MAKE_POINTER(API::Field, (::REFrameworkFieldHandle)f)); + } + + return out; + #else + return *(std::vector*)&fields; + #endif } - std::vector get_properties() const { + CPP_VECTOR(API::Property CPP_POINTER) get_properties() CPP_MEMBER_CONST { throw std::runtime_error("Not implemented"); +#ifdef __cplusplus_cli + return nullptr; +#else return {}; +#endif } - void* get_instance() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_instance; - return fn(*this); + void* get_instance() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->get_instance(*this); } - void* create_instance_deprecated() const { - static const auto fn = API::s_instance->sdk()->type_definition->create_instance_deprecated; - return fn(*this); + void* create_instance_deprecated() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_definition->create_instance_deprecated(*this); } - API::ManagedObject* create_instance(int flags = 0) const { - static const auto fn = API::s_instance->sdk()->type_definition->create_instance; - return (API::ManagedObject*)fn(*this, flags); + API::ManagedObject CPP_POINTER create_instance(int flags) CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::ManagedObject, API::s_instance->sdk()->type_definition->create_instance(*this, flags)); } - API::TypeDefinition* get_parent_type() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_parent_type; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_parent_type() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->type_definition->get_parent_type(*this)); } - API::TypeDefinition* get_declaring_type() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_declaring_type; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_declaring_type() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->type_definition->get_declaring_type(*this)); } - API::TypeDefinition* get_underlying_type() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_underlying_type; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_underlying_type() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->type_definition->get_underlying_type(*this)); } - API::TypeInfo* get_type_info() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_type_info; - return (API::TypeInfo*)fn(*this); + API::TypeInfo CPP_POINTER get_type_info() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeInfo, API::s_instance->sdk()->type_definition->get_type_info(*this)); } - API::ManagedObject* get_runtime_type() const { - static const auto fn = API::s_instance->sdk()->type_definition->get_runtime_type; - return (API::ManagedObject*)fn(*this); + API::ManagedObject CPP_POINTER get_runtime_type() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::ManagedObject, API::s_instance->sdk()->type_definition->get_runtime_type(*this)); } }; - struct Method { - operator ::REFrameworkMethodHandle() const { - return (::REFrameworkMethodHandle)this; - } + PUBLIC_POINTER_CONTAINER(Method, ::REFrameworkMethodHandle) + // .NET version +#ifdef __cplusplus_cli + DotNetInvokeRet^ invoke(System::Object^ obj, array^ args) { + std::vector args2{}; + args2.resize(args->Length); + + for (int i = 0; i < args->Length; ++i) { + //args2[i] = args[i]->ptr(); + const auto t = args[i]->GetType(); + + if (t == System::Byte::typeid) { + uint8_t v = System::Convert::ToByte(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::UInt16::typeid) { + uint16_t v = System::Convert::ToUInt16(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::UInt32::typeid) { + uint32_t v = System::Convert::ToUInt32(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::Single::typeid) { + float v = System::Convert::ToSingle(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::UInt64::typeid) { + uint64_t v = System::Convert::ToUInt64(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::Double::typeid) { + double v = System::Convert::ToDouble(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::IntPtr::typeid) { + args2[i] = (void*)(uint64_t)System::Convert::ToInt64(args[i]); + } else { + //args2[i] = args[i]->ptr(); + } + } - reframework::InvokeRet invoke(API::ManagedObject* obj, const std::vector& args) { - static const auto fn = API::s_instance->sdk()->method->invoke; + void* obj_ptr = nullptr; + + if (obj != nullptr) { + const auto obj_t = obj->GetType(); + if (obj_t == System::IntPtr::typeid || obj_t == System::UIntPtr::typeid) { + obj_ptr = (void*)(uint64_t)System::Convert::ToInt64(obj); + } else if (obj_t == API::ManagedObject::typeid) { + obj_ptr = ((API::ManagedObject^)obj)->ptr(); + } else { + //obj_ptr = obj->ptr(); + } + } + + reframework::InvokeRet result; + auto succeed = API::s_instance->sdk()->method->invoke(*this, obj_ptr, (void**)&args2[0], args2.size() * sizeof(void*), &result, sizeof(result)); + + DotNetInvokeRet^ out = gcnew DotNetInvokeRet(); + out->ExceptionThrown = result.exception_thrown; + + if (result.exception_thrown) { + return out; + } + + out->Bytes = gcnew array(result.bytes.size()); + System::Runtime::InteropServices::Marshal::Copy(System::IntPtr((void*)result.bytes.data()), out->Bytes, 0, result.bytes.size()); + + out->Byte = result.byte; + out->Word = result.word; + out->DWord = result.dword; + out->F = result.f; + out->QWord = result.qword; + out->D = result.d; + out->Ptr = gcnew System::IntPtr(result.ptr); + + return out; + } +#else + reframework::InvokeRet invoke(API::ManagedObject CPP_POINTER obj, const std::vector& args) { reframework::InvokeRet out{}; - auto result = fn(*this, obj, (void**)&args[0], args.size() * sizeof(void*), &out, sizeof(out)); + auto result = API::s_instance->sdk()->method->invoke(*this, obj->ptr(), (void**)&args[0], args.size() * sizeof(void*), &out, sizeof(out)); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -525,6 +776,7 @@ class API { return out; } +#endif reframework::InvokeRet invoke(API::ManagedObject* obj, const std::span& args) { static const auto fn = API::s_instance->sdk()->method->invoke; @@ -542,49 +794,47 @@ class API { } template - T get_function() const { - static const auto fn = API::s_instance->sdk()->method->get_function; - return (T)fn(*this); + T get_function() CPP_MEMBER_CONST { + return (T)API::s_instance->sdk()->method->get_function(*this); } - void* get_function_raw() const { - static const auto fn = API::s_instance->sdk()->method->get_function; - return fn(*this); + void* get_function_raw() CPP_MEMBER_CONST { + return API::s_instance->sdk()->method->get_function(*this); } // e.g. call(sdk->get_vm_context(), obj, args...); template - Ret call(Args... args) const { + Ret call(Args... args) CPP_MEMBER_CONST { return get_function()(args...); } - const char* get_name() const { - static const auto fn = API::s_instance->sdk()->method->get_name; +#ifdef __cplusplus_cli + System::String^ get_name() { + return gcnew System::String(API::s_instance->sdk()->method->get_name(*this)); + } +#else + const char* get_name() CPP_MEMBER_CONST { return API::s_instance->sdk()->method->get_name(*this); } +#endif - API::TypeDefinition* get_declaring_type() const { - static const auto fn = API::s_instance->sdk()->method->get_declaring_type; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_declaring_type() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->method->get_declaring_type(*this)); } - API::TypeDefinition* get_return_type() const { - static const auto fn = API::s_instance->sdk()->method->get_return_type; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_return_type() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->method->get_return_type(*this)); } - uint32_t get_num_params() const { - static const auto fn = API::s_instance->sdk()->method->get_num_params; - return fn(*this); + uint32_t get_num_params() CPP_MEMBER_CONST { + return API::s_instance->sdk()->method->get_num_params(*this); } - std::vector get_params() const { - static const auto fn = API::s_instance->sdk()->method->get_params; - + /*CPP_VECTOR(REFrameworkMethodParameter CPP_CONTAINER_RET) get_params() CPP_MEMBER_CONST { std::vector params; params.resize(get_num_params()); - auto result = fn(*this, (REFrameworkMethodParameter*)¶ms[0], params.size() * sizeof(REFrameworkMethodParameter), nullptr); + auto result = API::s_instance->sdk()->method->get_params(*this, (REFrameworkMethodParameter*)¶ms[0], params.size() * sizeof(REFrameworkMethodParameter), nullptr); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -596,174 +846,150 @@ class API { } #endif +#ifdef __cplusplus_cli + //cliext::vector out = gcnew cliext::vector(); + System::Collections::Generic::List^ out = gcnew System::Collections::Generic::List(); + + for (auto p : params) { + //out->Add(gcnew REFrameworkMethodParameter(p)); + } + + return out; +else return params; - } +#endif + }*/ - uint32_t get_index() const { - static const auto fn = API::s_instance->sdk()->method->get_index; - return fn(*this); + uint32_t get_index() CPP_MEMBER_CONST { + return API::s_instance->sdk()->method->get_index(*this); } - int get_virtual_index() const { - static const auto fn = API::s_instance->sdk()->method->get_virtual_index; - return fn(*this); + int get_virtual_index() CPP_MEMBER_CONST { + return API::s_instance->sdk()->method->get_virtual_index(*this); } - bool is_static() const { - static const auto fn = API::s_instance->sdk()->method->is_static; - return fn(*this); + bool is_static() CPP_MEMBER_CONST { + return API::s_instance->sdk()->method->is_static(*this); } - uint16_t get_flags() const { - static const auto fn = API::s_instance->sdk()->method->get_flags; - return fn(*this); + uint16_t get_flags() CPP_MEMBER_CONST { + return API::s_instance->sdk()->method->get_flags(*this); } - uint16_t get_impl_flags() const { - static const auto fn = API::s_instance->sdk()->method->get_impl_flags; - return fn(*this); + uint16_t get_impl_flags() CPP_MEMBER_CONST { + return API::s_instance->sdk()->method->get_impl_flags(*this); } - uint32_t get_invoke_id() const { - static const auto fn = API::s_instance->sdk()->method->get_invoke_id; - return fn(*this); + uint32_t get_invoke_id() CPP_MEMBER_CONST { + return API::s_instance->sdk()->method->get_invoke_id(*this); } - unsigned int add_hook(REFPreHookFn pre_fn, REFPostHookFn post_fn, bool ignore_jmp) const { - static const auto fn = API::s_instance->sdk()->functions->add_hook; - return fn(*this, pre_fn, post_fn, ignore_jmp); + unsigned int add_hook(REFPreHookFn pre_fn, REFPostHookFn post_fn, bool ignore_jmp) CPP_MEMBER_CONST { + return API::s_instance->sdk()->functions->add_hook(*this, pre_fn, post_fn, ignore_jmp); } - void remove_hook(unsigned int hook_id) const { - - static const auto fn = API::s_instance->sdk()->functions->remove_hook; - fn(*this, hook_id); + void remove_hook(unsigned int hook_id) CPP_MEMBER_CONST { + API::s_instance->sdk()->functions->remove_hook(*this, hook_id); } }; - struct Field { - operator ::REFrameworkFieldHandle() const { - return (::REFrameworkFieldHandle)this; + PUBLIC_POINTER_CONTAINER(Field, ::REFrameworkFieldHandle) + #ifdef __cplusplus_cli + System::String^ get_name() { + return gcnew System::String(API::s_instance->sdk()->field->get_name(*this)); } - - const char* get_name() const { - static const auto fn = API::s_instance->sdk()->field->get_name; - return fn(*this); + #else + const char* get_name() CPP_MEMBER_CONST { + return API::s_instance->sdk()->field->get_name(*this); } + #endif - API::TypeDefinition* get_declaring_type() const { - static const auto fn = API::s_instance->sdk()->field->get_declaring_type; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_declaring_type() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->field->get_declaring_type(*this)); } - API::TypeDefinition* get_type() const { - static const auto fn = API::s_instance->sdk()->field->get_type; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_type() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->field->get_type(*this)); } - uint32_t get_offset_from_base() const { - static const auto fn = API::s_instance->sdk()->field->get_offset_from_base; - return fn(*this); + uint32_t get_offset_from_base() CPP_MEMBER_CONST { + return API::s_instance->sdk()->field->get_offset_from_base(*this); } - uint32_t get_offset_from_fieldptr() const { - static const auto fn = API::s_instance->sdk()->field->get_offset_from_fieldptr; - return fn(*this); + uint32_t get_offset_from_fieldptr() CPP_MEMBER_CONST { + return API::s_instance->sdk()->field->get_offset_from_fieldptr(*this); } - uint32_t get_flags() const { - static const auto fn = API::s_instance->sdk()->field->get_flags; - return fn(*this); + uint32_t get_flags() CPP_MEMBER_CONST { + return API::s_instance->sdk()->field->get_flags(*this); } - bool is_static() const { - static const auto fn = API::s_instance->sdk()->field->is_static; - return fn(*this); + bool is_static() CPP_MEMBER_CONST { + return API::s_instance->sdk()->field->is_static(*this); } - bool is_literal() const { - static const auto fn = API::s_instance->sdk()->field->is_literal; - return fn(*this); + bool is_literal() CPP_MEMBER_CONST { + return API::s_instance->sdk()->field->is_literal(*this); } - void* get_init_data() const { - static const auto fn = API::s_instance->sdk()->field->get_init_data; - return fn(*this); + void* get_init_data() CPP_MEMBER_CONST { + return API::s_instance->sdk()->field->get_init_data(*this); } - void* get_data_raw(void* obj, bool is_value_type = false) const { - static const auto fn = API::s_instance->sdk()->field->get_data_raw; - return fn(*this, obj, is_value_type); + void* get_data_raw(void* obj, bool is_value_type) CPP_MEMBER_CONST { + return API::s_instance->sdk()->field->get_data_raw(*this, obj, is_value_type); } - template T& get_data(void* object = nullptr, bool is_value_type = false) const { return *(T*)get_data_raw(object, is_value_type); } + template T& get_data(void* object, bool is_value_type) CPP_MEMBER_CONST { return *(T*)get_data_raw(object); } }; - struct Property { - operator ::REFrameworkPropertyHandle() const { - return (::REFrameworkPropertyHandle)this; - } - + PUBLIC_POINTER_CONTAINER(Property, ::REFrameworkPropertyHandle) // TODO: Implement }; - struct ManagedObject { - operator ::REFrameworkManagedObjectHandle() const { - return (::REFrameworkManagedObjectHandle)this; - } - + PUBLIC_POINTER_CONTAINER(ManagedObject, ::REFrameworkManagedObjectHandle) void add_ref() { - static const auto fn = API::s_instance->sdk()->managed_object->add_ref; - fn(*this); + API::s_instance->sdk()->managed_object->add_ref(*this); } void release() { - static const auto fn = API::s_instance->sdk()->managed_object->release; - fn(*this); + API::s_instance->sdk()->managed_object->release(*this); } - API::TypeDefinition* get_type_definition() const { - static const auto fn = API::s_instance->sdk()->managed_object->get_type_definition; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_type_definition() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->managed_object->get_type_definition(*this)); } - bool is_managed_object() const { - static const auto fn = API::s_instance->sdk()->managed_object->is_managed_object; - return fn(*this); + bool is_managed_object() CPP_MEMBER_CONST { + return API::s_instance->sdk()->managed_object->is_managed_object(this->ptr()); } - uint32_t get_ref_count() const { - static const auto fn = API::s_instance->sdk()->managed_object->get_ref_count; - return fn(*this); + uint32_t get_ref_count() CPP_MEMBER_CONST { + return API::s_instance->sdk()->managed_object->get_ref_count(*this); } - uint32_t get_vm_obj_type() const { - static const auto fn = API::s_instance->sdk()->managed_object->get_vm_obj_type; - return fn(*this); + uint32_t get_vm_obj_type() CPP_MEMBER_CONST { + return API::s_instance->sdk()->managed_object->get_vm_obj_type(*this); } - API::TypeInfo* get_type_info() const { - static const auto fn = API::s_instance->sdk()->managed_object->get_type_info; - return (API::TypeInfo*)fn(*this); + API::TypeInfo CPP_POINTER get_type_info() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeInfo, API::s_instance->sdk()->managed_object->get_type_info(*this)); } - void* get_reflection_properties() const { - static const auto fn = API::s_instance->sdk()->managed_object->get_reflection_properties; - return fn(*this); + void* get_reflection_properties() CPP_MEMBER_CONST { + return API::s_instance->sdk()->managed_object->get_reflection_properties(*this); } - API::ReflectionProperty* get_reflection_property_descriptor(std::string_view name) { - static const auto fn = API::s_instance->sdk()->managed_object->get_reflection_property_descriptor; - return (API::ReflectionProperty*)fn(*this, name.data()); + API::ReflectionProperty CPP_POINTER get_reflection_property_descriptor(std::string_view name) { + return REF_MAKE_POINTER(API::ReflectionProperty, API::s_instance->sdk()->managed_object->get_reflection_property_descriptor(*this, name.data())); } - API::ReflectionMethod* get_reflection_method_descriptor(std::string_view name) { - static const auto fn = API::s_instance->sdk()->managed_object->get_reflection_method_descriptor; - return (API::ReflectionMethod*)fn(*this, name.data()); + API::ReflectionMethod CPP_POINTER get_reflection_method_descriptor(std::string_view name) { + return REF_MAKE_POINTER(API::ReflectionMethod, API::s_instance->sdk()->managed_object->get_reflection_method_descriptor(*this, name.data())); } template - Ret call(std::string_view method_name, Args... args) const { + Ret call(std::string_view method_name, Args... args) CPP_MEMBER_CONST { auto t = get_type_definition(); if (t == nullptr) { @@ -779,6 +1005,24 @@ class API { return m->get_function()(args...); } +#ifdef __cplusplus_cli + // .NET version + DotNetInvokeRet^ invoke(System::String^ method_name, array^ args) { + auto t = get_type_definition(); + + if (t == nullptr) { + return nullptr; + } + + auto m = t->find_method(method_name); + + if (m == nullptr) { + return nullptr; + } + + return m->invoke(this, args); + } +#else reframework::InvokeRet invoke(std::string_view method_name, const std::vector& args) { auto t = get_type_definition(); @@ -794,9 +1038,10 @@ class API { return m->invoke(this, args); } +#endif template - T* get_field(std::string_view name, bool is_value_type = false) const { + T* get_field(std::string_view name, bool is_value_type) CPP_MEMBER_CONST { auto t = get_type_definition(); if (t == nullptr) { @@ -813,155 +1058,142 @@ class API { } }; - struct ResourceManager { - operator ::REFrameworkResourceManagerHandle() const { - return (::REFrameworkResourceManagerHandle)this; - } - - API::Resource* create_resource(std::string_view type_name, std::string_view name) { - static const auto fn = API::s_instance->sdk()->resource_manager->create_resource; - return (API::Resource*)fn(*this, type_name.data(), name.data()); + PUBLIC_POINTER_CONTAINER(ResourceManager, REFrameworkResourceManagerHandle) + API::Resource CPP_POINTER create_resource(std::string_view type_name, std::string_view name) { + return REF_MAKE_POINTER(API::Resource, API::s_instance->sdk()->resource_manager->create_resource(*this, type_name.data(), name.data())); } }; - struct Resource { - operator ::REFrameworkResourceHandle() const { - return (::REFrameworkResourceHandle)this; - } - + PUBLIC_POINTER_CONTAINER(Resource, ::REFrameworkResourceHandle) void add_ref() { - static const auto fn = API::s_instance->sdk()->resource->add_ref; - fn(*this); + API::s_instance->sdk()->resource->add_ref(*this); } void release() { - static const auto fn = API::s_instance->sdk()->resource->release; - fn(*this); + API::s_instance->sdk()->resource->release(*this); } }; - struct TypeInfo { - operator ::REFrameworkTypeInfoHandle() const { - return (::REFrameworkTypeInfoHandle)this; + PUBLIC_POINTER_CONTAINER(TypeInfo, ::REFrameworkTypeInfoHandle) + #ifdef __cplusplus_cli + System::String^ get_name() { + return gcnew System::String(API::s_instance->sdk()->type_info->get_name(*this)); } - - const char* get_name() const { - static const auto fn = API::s_instance->sdk()->type_info->get_name; - return fn(*this); + #else + const char* get_name() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_info->get_name(*this); } + #endif - API::TypeDefinition* get_type_definition() const { - static const auto fn = API::s_instance->sdk()->type_info->get_type_definition; - return (API::TypeDefinition*)fn(*this); + API::TypeDefinition CPP_POINTER get_type_definition() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->type_info->get_type_definition(*this)); } - bool is_clr_type() const { - static const auto fn = API::s_instance->sdk()->type_info->is_clr_type; - return fn(*this); + bool is_clr_type() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_info->is_clr_type(*this); } - bool is_singleton() const { - static const auto fn = API::s_instance->sdk()->type_info->is_singleton; - return fn(*this); + bool is_singleton() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_info->is_singleton(*this); } - void* get_singleton_instance() const { - static const auto fn = API::s_instance->sdk()->type_info->get_singleton_instance; - return fn(*this); + void* get_singleton_instance() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_info->get_singleton_instance(*this); } - void* get_reflection_properties() const { - static const auto fn = API::s_instance->sdk()->type_info->get_reflection_properties; - return fn(*this); + void* get_reflection_properties() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_info->get_reflection_properties(*this); } - API::ReflectionProperty* get_reflection_property_descriptor(std::string_view name) { - static const auto fn = API::s_instance->sdk()->type_info->get_reflection_property_descriptor; - return (API::ReflectionProperty*)API::s_instance->sdk()->type_info->get_reflection_property_descriptor(*this, name.data()); + API::ReflectionProperty CPP_POINTER get_reflection_property_descriptor(std::string_view name) { + return REF_MAKE_POINTER(API::ReflectionProperty, API::s_instance->sdk()->type_info->get_reflection_property_descriptor(*this, name.data())); } - API::ReflectionMethod* get_reflection_method_descriptor(std::string_view name) { - static const auto fn = API::s_instance->sdk()->type_info->get_reflection_method_descriptor; - return (API::ReflectionMethod*)fn(*this, name.data()); + API::ReflectionMethod CPP_POINTER get_reflection_method_descriptor(std::string_view name) { + return REF_MAKE_POINTER(API::ReflectionMethod, API::s_instance->sdk()->type_info->get_reflection_method_descriptor(*this, name.data())); } - void* get_deserializer_fn() const { - static const auto fn = API::s_instance->sdk()->type_info->get_deserializer_fn; - return fn(*this); + void* get_deserializer_fn() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_info->get_deserializer_fn(*this); } - API::TypeInfo* get_parent() const { - static const auto fn = API::s_instance->sdk()->type_info->get_parent; - return (API::TypeInfo*)fn(*this); + API::TypeInfo CPP_POINTER get_parent() CPP_MEMBER_CONST { + return REF_MAKE_POINTER(API::TypeInfo, API::s_instance->sdk()->type_info->get_parent(*this)); } - uint32_t get_crc() const { - static const auto fn = API::s_instance->sdk()->type_info->get_crc; - return fn(*this); + uint32_t get_crc() CPP_MEMBER_CONST { + return API::s_instance->sdk()->type_info->get_crc(*this); } }; - struct VMContext { - operator ::REFrameworkVMContextHandle() const { - return (::REFrameworkVMContextHandle)this; - } - - bool has_exception() const { - static const auto fn = API::s_instance->sdk()->vm_context->has_exception; - return fn(*this); + PUBLIC_POINTER_CONTAINER(VMContext, ::REFrameworkVMContextHandle) + bool has_exception() CPP_MEMBER_CONST { + return API::s_instance->sdk()->vm_context->has_exception(*this); } void unhandled_exception() { - static const auto fn = API::s_instance->sdk()->vm_context->unhandled_exception; - fn(*this); + API::s_instance->sdk()->vm_context->unhandled_exception(*this); } void local_frame_gc() { - static const auto fn = API::s_instance->sdk()->vm_context->local_frame_gc; - fn(*this); + API::s_instance->sdk()->vm_context->local_frame_gc(*this); } void cleanup_after_exception(int32_t old_ref_count) { - static const auto fn = API::s_instance->sdk()->vm_context->cleanup_after_exception; - fn(*this, old_ref_count); + API::s_instance->sdk()->vm_context->cleanup_after_exception(*this, old_ref_count); } }; - struct ReflectionMethod { - operator ::REFrameworkReflectionMethodHandle() const { - return (::REFrameworkReflectionMethodHandle)this; - } - - ::REFrameworkInvokeMethod get_function() const { - static const auto fn = API::s_instance->sdk()->reflection_method->get_function; - return fn(*this); + PUBLIC_POINTER_CONTAINER(ReflectionMethod, ::REFrameworkReflectionMethodHandle) + ::REFrameworkInvokeMethod get_function() CPP_MEMBER_CONST { + return API::s_instance->sdk()->reflection_method->get_function(*this); } }; - struct ReflectionProperty { - operator ::REFrameworkReflectionPropertyHandle() const { - return (::REFrameworkReflectionPropertyHandle)this; - } - - ::REFrameworkReflectionPropertyMethod get_getter() const { - static const auto fn = API::s_instance->sdk()->reflection_property->get_getter; - return fn(*this); + PUBLIC_POINTER_CONTAINER(ReflectionProperty, ::REFrameworkReflectionPropertyHandle) + ::REFrameworkReflectionPropertyMethod get_getter() CPP_MEMBER_CONST { + return API::s_instance->sdk()->reflection_property->get_getter(*this); } - bool is_static() const { - static const auto fn = API::s_instance->sdk()->reflection_property->is_static; - return fn(*this); + bool is_static() CPP_MEMBER_CONST { + return API::s_instance->sdk()->reflection_property->is_static(*this); } - uint32_t get_size() const { - static const auto fn = API::s_instance->sdk()->reflection_property->get_size; - return fn(*this); + uint32_t get_size() CPP_MEMBER_CONST { + return API::s_instance->sdk()->reflection_property->get_size(*this); } }; private: - const REFrameworkPluginInitializeParam* m_param; - const REFrameworkSDKData* m_sdk; - std::recursive_mutex m_lua_mtx{}; }; } + +// ALWAYS call initialize first in reframework_plugin_initialize +#ifndef __cplusplus_cli +reframework::API& reframework::API::initialize(const REFrameworkPluginInitializeParam* param) { +#else +reframework::API^ reframework::API::initialize(uintptr_t param) { +#endif +#ifndef __cplusplus_cli + if (param == nullptr) { + throw std::runtime_error("param is null"); + } + + if (s_instance != nullptr) { + return s_instance; + } + + s_instance = std::make_unique(param); +#else + if (param == 0) { + throw std::runtime_error("param is null"); + } + + if (s_instance != nullptr) { + return s_instance; + } + + s_instance = gcnew reframework::API(param); +#endif + return s_instance; +} diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index cb35315cd..fab02759e 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -617,6 +617,7 @@ void PluginLoader::early_init() try { spdlog::info("[PluginLoader] Loading plugins..."); + LoadLibraryA("ijwhost.dll"); // Load all dlls in the plugins directory. for (auto&& entry : fs::directory_iterator{plugin_path}) { auto&& path = entry.path(); From 4441c9dbc9f27f6e602a21161aac47c7a6047d0d Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 15:38:42 -0700 Subject: [PATCH 002/207] Fix API.hpp failing to compile in native code --- include/reframework/API.hpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/include/reframework/API.hpp b/include/reframework/API.hpp index 6be780982..29d78cdfb 100644 --- a/include/reframework/API.hpp +++ b/include/reframework/API.hpp @@ -1,6 +1,7 @@ #pragma once #ifdef __cplusplus_cli +#pragma managed #include #include //#include @@ -128,16 +129,16 @@ class API { public: // ALWAYS call initialize first in reframework_plugin_initialize #ifndef __cplusplus_cli - static API& initialize(const REFrameworkPluginInitializeParam* param); + static inline std::unique_ptr& initialize(const REFrameworkPluginInitializeParam* param); #else - static API^ initialize(uintptr_t param); + static inline API^ initialize(uintptr_t param); #endif // only call this AFTER calling initialize #ifndef __cplusplus_cli - static auto& get() { + static inline auto& get() { #else - static auto get() { + static inline auto get() { #endif if (s_instance == nullptr) { throw std::runtime_error("API not initialized"); @@ -343,6 +344,12 @@ class API { return (void*)this; } }; + + #define POINTER_CONTAINER_CONSTRUCTOR(WrapperType, Tx) WrapperType(Tx ptr) : PointerContainer(ptr) {} + #define PUBLIC_POINTER_CONTAINER(X, Tx) struct X : public PointerContainer { \ + public:\ + X(Tx ptr) : PointerContainer(ptr) {} + #endif //#define PUBLIC_POINTER_CONTAINER(Tx) : public PointerContainer @@ -1170,9 +1177,9 @@ else // ALWAYS call initialize first in reframework_plugin_initialize #ifndef __cplusplus_cli -reframework::API& reframework::API::initialize(const REFrameworkPluginInitializeParam* param) { +inline std::unique_ptr& reframework::API::initialize(const REFrameworkPluginInitializeParam* param) { #else -reframework::API^ reframework::API::initialize(uintptr_t param) { +inline reframework::API^ reframework::API::initialize(uintptr_t param) { #endif #ifndef __cplusplus_cli if (param == nullptr) { From c04cbaa3259693cc150b576824bebf1ef39a4ebc Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 16:46:54 -0700 Subject: [PATCH 003/207] .NET: Code cleanup --- CMakeLists.txt | 3 +- csharp-api/Plugin.cpp | 142 +----------------- csharp-api/REFrameworkAPI.cpp | 25 --- csharp-api/REFrameworkNET/API.cpp | 21 +++ .../API.hpp} | 5 +- csharp-api/REFrameworkNET/Callbacks.hpp | 10 ++ csharp-api/REFrameworkNET/PluginManager.cpp | 131 ++++++++++++++++ csharp-api/REFrameworkNET/PluginManager.hpp | 34 +++++ csharp-api/test/Test/Test.cs | 6 +- 9 files changed, 208 insertions(+), 169 deletions(-) delete mode 100644 csharp-api/REFrameworkAPI.cpp create mode 100644 csharp-api/REFrameworkNET/API.cpp rename csharp-api/{REFrameworkAPI.hpp => REFrameworkNET/API.hpp} (83%) create mode 100644 csharp-api/REFrameworkNET/Callbacks.hpp create mode 100644 csharp-api/REFrameworkNET/PluginManager.cpp create mode 100644 csharp-api/REFrameworkNET/PluginManager.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e16224c43..0b8e3fd53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13112,7 +13112,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework list(APPEND csharp-api_SOURCES "csharp-api/Plugin.cpp" - "csharp-api/REFrameworkAPI.cpp" + "csharp-api/REFrameworkNET/API.cpp" + "csharp-api/REFrameworkNET/PluginManager.cpp" ) list(APPEND csharp-api_SOURCES diff --git a/csharp-api/Plugin.cpp b/csharp-api/Plugin.cpp index bbd6f0e54..72467d403 100644 --- a/csharp-api/Plugin.cpp +++ b/csharp-api/Plugin.cpp @@ -1,3 +1,5 @@ +#pragma managed + extern "C" { #include } @@ -9,7 +11,8 @@ extern "C" { #include #include -#include "REFrameworkAPI.hpp" +#include "REFrameworkNET/API.hpp" +#include "REFrameworkNET/PluginManager.hpp" #include "Plugin.hpp" @@ -17,150 +20,15 @@ using namespace reframework; using namespace System; using namespace System::Runtime; -void on_pre_begin_rendering() { - -} - -void on_post_end_rendering() { - -} - - -namespace REFManagedInternal { -public ref class API { -public: - static REFramework::API^ instance; -}; -} - -ref class CSharpAPIImpl { -public: - static bool ManagedImpl(uintptr_t param_raw) { - auto self = System::Reflection::Assembly::GetExecutingAssembly(); - - // Look for any DLLs in the "managed" directory, load them, then call a function in them (REFrameworkPlugin.Main) - // This is useful for loading C# plugins - // Create the REFramework::API class first though (managed) - REFManagedInternal::API::instance = gcnew REFramework::API(param_raw); - - std::filesystem::create_directories(std::filesystem::current_path() / "reframework" / "plugins" / "managed"); - - String^ managedDir = gcnew String((std::filesystem::current_path() / "reframework" / "plugins" / "managed").wstring().c_str()); - - bool ever_found = false; - auto files = System::IO::Directory::GetFiles(managedDir, "*.dll"); - - if (files->Length == 0) { - //API::get()->log_error("No DLLs found in %s", managedDir); - return false; - } - - for each (String^ file in files) { - Console::WriteLine(file); - System::Reflection::Assembly^ assem = System::Reflection::Assembly::LoadFrom(file); - - if (assem == nullptr) { - //API::get()->log_error("Failed to load assembly from %s", file); - Console::WriteLine("Failed to load assembly from " + file); - continue; - } - - // Iterate through all types in the assembly - for each (Type^ type in assem->GetTypes()) { - // Attempt to find the Main method with the expected signature in each type - System::Reflection::MethodInfo^ mainMethod = type->GetMethod( - "Main", - System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, - gcnew array{REFramework::API::typeid}); - - if (mainMethod != nullptr) { - Console::WriteLine("Found Main method in " + file); - //API::get()->log_info("Found Main method in %s", file); - // If found, invoke the method - - array^ args = gcnew array{REFManagedInternal::API::instance}; - mainMethod->Invoke(nullptr, args); - ever_found = true; - break; // Optional: break if you only expect one Main method per assembly - } - } - } - - if (!ever_found) { - //API::get()->log_error("No Main method found in any DLLs in %s", managedDir); - Console::WriteLine("No Main method found in any DLLs in " + managedDir); - } - - return true; - } - -private: -}; - -bool managed_impl(const REFrameworkPluginInitializeParam* param) try { - // Write to console using C++/CLI - System::String^ str = "Hello from C++/CLI!"; - System::Console::WriteLine(str); - - const auto functions = param->functions; - functions->on_pre_application_entry("BeginRendering", on_pre_begin_rendering); // Look at via.ModuleEntry or the wiki for valid names here - functions->on_post_application_entry("EndRendering", on_post_end_rendering); - - functions->log_error("%s %s", "Hello", "error"); - functions->log_warn("%s %s", "Hello", "warning"); - functions->log_info("%s %s", "Hello", "info"); - - // Make sure plugins that are loaded can find a reference to the current assembly - // Even though "this" is loaded currently, its not in the right context to be found - // So we have to load ourselves again, and call CSharpAPIImpl.ManagedImpl via a dynamic lookup - auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); - - // Get CSharpAPIImpl type - auto type = self->GetType("CSharpAPIImpl"); - - // Get ManagedImpl method - auto method = type->GetMethod("ManagedImpl", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); - - // Invoke ManagedImpl method - method->Invoke(nullptr, gcnew array{reinterpret_cast(param)}); - - return true; -} catch (System::Reflection::ReflectionTypeLoadException^ ex) { - auto loaderExceptions = ex->LoaderExceptions; - for each (Exception^ innerEx in loaderExceptions) { - //API::get()->log_error("Loader exception caught: %s", msclr::interop::marshal_as(innerEx->Message).c_str()); - System::Console::WriteLine(innerEx->Message); - } - // Optionally log the main exception as well - //API::get()->log_error("ReflectionTypeLoadException caught: %s", msclr::interop::marshal_as(ex->Message).c_str()); - System::Console::WriteLine(ex->Message); - return false; -} catch (System::Exception^ e) { - //API::get()->log_error("Exception caught: %s", msclr::interop::marshal_as(e->Message).c_str()); - System::Console::WriteLine(e->Message); - return false; -} catch (const std::exception& e) { - //API::get()->log_error("Exception caught: %s", e.what()); - System::Console::WriteLine(gcnew System::String(e.what())); - return false; -} catch (...) { - //API::get()->log_error("Unknown exception caught"); - System::Console::WriteLine("Unknown exception caught"); - return false; -} - extern "C" __declspec(dllexport) bool reframework_plugin_initialize(const REFrameworkPluginInitializeParam* param) { // Create a console AllocConsole(); - return managed_impl(param); + return REFrameworkNET::PluginManager::Entry(param); } extern "C" __declspec(dllexport) void reframework_plugin_required_version(REFrameworkPluginVersion* version) { version->major = REFRAMEWORK_PLUGIN_VERSION_MAJOR; version->minor = REFRAMEWORK_PLUGIN_VERSION_MINOR; version->patch = REFRAMEWORK_PLUGIN_VERSION_PATCH; - - // Optionally, specify a specific game name that this plugin is compatible with. - //version->game_name = "MHRISE"; } diff --git a/csharp-api/REFrameworkAPI.cpp b/csharp-api/REFrameworkAPI.cpp deleted file mode 100644 index a3baa2573..000000000 --- a/csharp-api/REFrameworkAPI.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include - -#include "REFrameworkAPI.hpp" - - -REFramework::API::API(uintptr_t param) - : m_api{ reframework::API::initialize(param) } -{ - Console::WriteLine("Constructor called."); -} - - -REFramework::API::~API() -{ - Console::WriteLine("Destructor called."); -} - -void REFramework::API::Test() { - Console::WriteLine("Test called."); - m_api->log_info("Hello from C++/CLI!"); -} - -reframework::API^ REFramework::API::Get() { - return m_api; -} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/API.cpp b/csharp-api/REFrameworkNET/API.cpp new file mode 100644 index 000000000..427033be4 --- /dev/null +++ b/csharp-api/REFrameworkNET/API.cpp @@ -0,0 +1,21 @@ +#pragma managed + +#include + +#include "./API.hpp" + + +REFrameworkNET::API::API(uintptr_t param) + : m_api{ reframework::API::initialize(param) } +{ + Console::WriteLine("REFrameworkNET.API Constructor called."); +} + +REFrameworkNET::API::~API() +{ + Console::WriteLine("REFrameworkNET.API Destructor called."); +} + +reframework::API^ REFrameworkNET::API::Get() { + return m_api; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkAPI.hpp b/csharp-api/REFrameworkNET/API.hpp similarity index 83% rename from csharp-api/REFrameworkAPI.hpp rename to csharp-api/REFrameworkNET/API.hpp index 1076ba95d..0a5c6283c 100644 --- a/csharp-api/REFrameworkAPI.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -1,4 +1,5 @@ #pragma once +#pragma managed namespace reframework { ref class API; @@ -6,15 +7,13 @@ ref class API; using namespace System; -namespace REFramework { +namespace REFrameworkNET { public ref class API { public: API(uintptr_t param); ~API(); - void Test(); - reframework::API^ Get(); protected: diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp new file mode 100644 index 000000000..6c709f80f --- /dev/null +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -0,0 +1,10 @@ +#pragma once +#pragma managed + +#include "API.hpp" + +namespace REFrameworkNET { +public ref class Callbacks { + +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp new file mode 100644 index 000000000..454127974 --- /dev/null +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -0,0 +1,131 @@ +#include +#include + +#include "PluginManager.hpp" + +namespace REFrameworkNET { + // Executed initially when we get loaded via LoadLibrary + // which is not in the correct context to actually load the managed plugins + bool PluginManager::LoadPlugins_FromDefaultContext(const REFrameworkPluginInitializeParam* param) try { + if (s_initialized) { + return true; + } + + PluginManager::s_initialized = true; + + // Write to console using C++/CLI + System::String^ str = "Hello from C++/CLI!"; + System::Console::WriteLine(str); + + // Make sure plugins that are loaded can find a reference to the current assembly + // Even though "this" is loaded currently, its not in the right context to be found + // So we have to load ourselves again, and call CSharpAPIImpl.ManagedImpl via a dynamic lookup + auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); + + if (self == nullptr) { + System::Console::WriteLine("Failed to load self"); + return false; + } + + // Get CSharpAPIImpl type + auto type = self->GetType("REFrameworkNET.PluginManager"); + + if (type == nullptr) { + System::Console::WriteLine("Failed to get type REFrameworkNET.PluginManager"); + return false; + } + + // Get LoadPlugins method + auto method = type->GetMethod("LoadPlugins", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::NonPublic); + + if (method == nullptr) { + System::Console::WriteLine("Failed to get method ManagedImplPartTwo"); + return false; + } + + // Invoke LoadPlugins method + method->Invoke(nullptr, gcnew array{reinterpret_cast(param)}); + + return true; + } catch (System::Reflection::ReflectionTypeLoadException^ ex) { + auto loaderExceptions = ex->LoaderExceptions; + for each (Exception^ innerEx in loaderExceptions) { + System::Console::WriteLine(innerEx->Message); + } + + System::Console::WriteLine(ex->Message); + return false; + } catch (System::Exception^ e) { + System::Console::WriteLine(e->Message); + return false; + } catch (const std::exception& e) { + System::Console::WriteLine(gcnew System::String(e.what())); + return false; + } catch (...) { + System::Console::WriteLine("Unknown exception caught"); + return false; + } + + // meant to be executed in the correct context + // after loading "ourselves" via System::Reflection::Assembly::LoadFrom + bool PluginManager::LoadPlugins(uintptr_t param_raw) { + if (PluginManager::s_initialized) { + return true; + } + + PluginManager::s_initialized = true; + + auto self = System::Reflection::Assembly::GetExecutingAssembly(); + + // Look for any DLLs in the "managed" directory, load them, then call a function in them (REFrameworkPlugin.Main) + // This is useful for loading C# plugins + // Create the REFramework::API class first though (managed) + PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); + + const auto managed_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed"; + std::filesystem::create_directories(managed_path); + + String^ managed_dir = gcnew String(managed_path.wstring().c_str()); + + bool ever_found = false; + auto files = System::IO::Directory::GetFiles(managed_dir, "*.dll"); + + if (files->Length == 0) { + //API::get()->log_error("No DLLs found in %s", managedDir); + return false; + } + + for each (String^ file in files) { + Console::WriteLine(file); + System::Reflection::Assembly^ assem = System::Reflection::Assembly::LoadFrom(file); + + if (assem == nullptr) { + Console::WriteLine("Failed to load assembly from " + file); + continue; + } + + // Iterate through all types in the assembly + for each (Type^ type in assem->GetTypes()) { + // Attempt to find the Main method with the expected signature in each type + System::Reflection::MethodInfo^ mainMethod = type->GetMethod( + "Main", + System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, + gcnew array{REFrameworkNET::API::typeid}); + + if (mainMethod != nullptr) { + Console::WriteLine("Found Main method in " + file); + + array^ args = gcnew array{PluginManager::s_api_instance}; + mainMethod->Invoke(nullptr, args); + ever_found = true; + } + } + } + + if (!ever_found) { + Console::WriteLine("No Main method found in any DLLs in " + managed_dir); + } + + return true; + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp new file mode 100644 index 000000000..0765118ff --- /dev/null +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -0,0 +1,34 @@ +#pragma once +#pragma managed + +extern "C" { + #include +} + +#include + +#include "./API.hpp" + +namespace REFrameworkNET { +private ref class PluginManager +{ +private: + // To stop these funcs from getting called more than once + static bool s_initialized = false; + static REFrameworkNET::API^ s_api_instance{}; + +public: + static bool Entry(const REFrameworkPluginInitializeParam* param) { + return LoadPlugins_FromDefaultContext(param); + } + +private: + // Executed initially when we get loaded via LoadLibrary + // which is not in the correct context to actually load the managed plugins + static bool LoadPlugins_FromDefaultContext(const REFrameworkPluginInitializeParam* param); + + // meant to be executed in the correct context + // after loading "ourselves" via System::Reflection::Assembly::LoadFrom + static bool LoadPlugins(uintptr_t param_raw); +}; +} \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 2c4c61236..99db8c020 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -90,7 +90,7 @@ private static string GetParameterInvocation(ParameterInfo[] parameters) } class REFrameworkPlugin { - public static void Main(REFramework.API api_) { + public static void Main(REFrameworkNET.API api_) { Console.WriteLine("Testing REFrameworkAPI..."); // Convert api.Get() type to pass to GenerateWrapper @@ -121,7 +121,7 @@ public static void Main(REFramework.API api_) { typesSorted.Sort();*/ - /*var singletons = api.GetManagedSingletons(); + var singletons = api.GetManagedSingletons(); foreach (var singletonDesc in singletons) { var singleton = new ManagedObjectWrapper(singletonDesc.instance); @@ -141,7 +141,7 @@ public static void Main(REFramework.API api_) { foreach (var field in fields) { Console.WriteLine(" " + field.get_name()); } - }*/ + } var sceneManager = api.GetNativeSingleton("via.SceneManager"); Console.WriteLine("sceneManager: " + sceneManager); From 96cfbe935cd139188cb6017a50a244643ed64645 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 17:57:22 -0700 Subject: [PATCH 004/207] .NET: Additional method helpers/code cleanup --- csharp-api/test/Test/ManagedObjectWrapper.cs | 2 +- csharp-api/test/Test/MethodWrapper.cs | 71 +++++++++++++++++++ csharp-api/test/Test/Test.cs | 13 ++-- csharp-api/test/Test/TypeDefinitionWrapper.cs | 14 ++-- include/reframework/API.hpp | 68 +++++++++++------- 5 files changed, 132 insertions(+), 36 deletions(-) create mode 100644 csharp-api/test/Test/MethodWrapper.cs diff --git a/csharp-api/test/Test/ManagedObjectWrapper.cs b/csharp-api/test/Test/ManagedObjectWrapper.cs index d631c3fde..1b4c5facf 100644 --- a/csharp-api/test/Test/ManagedObjectWrapper.cs +++ b/csharp-api/test/Test/ManagedObjectWrapper.cs @@ -52,7 +52,7 @@ public ReflectionMethod GetReflectionMethodDescriptor(basic_string_view GetParameters() { + return _original.get_params(); + } + + public UInt32 GetIndex() { + return _original.get_index(); + } + + public Int32 GetVirtualIndex() { + return _original.get_virtual_index(); + } + + public Boolean IsStatic() { + return _original.is_static(); + } + + public UInt16 GetFlags() { + return _original.get_flags(); + } + + public UInt16 GetImplFlags() { + return _original.get_impl_flags(); + } + + public UInt32 GetInvokeID() { + return _original.get_invoke_id(); + } + + // hmm... + /*public UInt32 AddHook(pre_fn, post_fn, Boolean ignore_jmp) { + return _original.add_hook(pre_fn, post_fn, ignore_jmp); + }*/ + + public void RemoveHook(UInt32 hook_id) { + _original.remove_hook(hook_id); + } +} diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 99db8c020..5caf1f06a 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -124,7 +124,7 @@ public static void Main(REFrameworkNET.API api_) { var singletons = api.GetManagedSingletons(); foreach (var singletonDesc in singletons) { - var singleton = new ManagedObjectWrapper(singletonDesc.instance); + var singleton = new ManagedObjectWrapper(singletonDesc.Instance); Console.WriteLine(singleton.GetTypeDefinition().GetFullName()); @@ -133,7 +133,12 @@ public static void Main(REFrameworkNET.API api_) { var methods = td.GetMethods(); foreach (var method in methods) { - Console.WriteLine(" " + method.get_name()); + string postfix = ""; + foreach (var param in method.GetParameters()) { + postfix += new TypeDefinitionWrapper(param.Type).GetFullName() + " " + param.Name + ", "; + } + + Console.WriteLine(" " + method.GetName() + " " + postfix); } var fields = td.GetFields(); @@ -149,7 +154,7 @@ public static void Main(REFrameworkNET.API api_) { Console.WriteLine("sceneManager_t: " + sceneManager_t); var get_CurrentScene = sceneManager_t.FindMethod("get_CurrentScene"); Console.WriteLine("get_CurrentScene: " + get_CurrentScene); - var scene = get_CurrentScene.invoke(sceneManager, new object[]{}).Ptr; + var scene = get_CurrentScene.Invoke(sceneManager, new object[]{}).Ptr; Console.WriteLine("scene: " + scene); @@ -159,7 +164,7 @@ public static void Main(REFrameworkNET.API api_) { Console.WriteLine("set_TimeScale: " + set_TimeScale); - set_TimeScale.invoke(scene, new object[]{ 0.1f }); + set_TimeScale.Invoke(scene, new object[]{ 0.1f }); } } }; \ No newline at end of file diff --git a/csharp-api/test/Test/TypeDefinitionWrapper.cs b/csharp-api/test/Test/TypeDefinitionWrapper.cs index e0463eae1..01bbe7447 100644 --- a/csharp-api/test/Test/TypeDefinitionWrapper.cs +++ b/csharp-api/test/Test/TypeDefinitionWrapper.cs @@ -115,9 +115,9 @@ public UInt32 GetVmObjType() return _original.get_vm_obj_type(); } - public API.Method FindMethod(String name) + public MethodWrapper FindMethod(String name) { - return _original.find_method(name); + return new MethodWrapper(_original.find_method(name)); } public API.Field FindField(String name) @@ -130,9 +130,15 @@ public API.Property FindProperty(String name) return _original.find_property(name); } - public List GetMethods() + public List GetMethods() { - return _original.get_methods(); + var methods = _original.get_methods(); + var result = new List(); + foreach (var method in methods) { + result.Add(new MethodWrapper(method)); + } + + return result; } public List GetFields() diff --git a/include/reframework/API.hpp b/include/reframework/API.hpp index 29d78cdfb..0d61c4956 100644 --- a/include/reframework/API.hpp +++ b/include/reframework/API.hpp @@ -42,6 +42,19 @@ struct InvokeRet { }; #pragma pack(pop) +#ifdef __cplusplus_cli +public ref struct DotNetInvokeRet { + property array^ Bytes; + property uint8_t Byte; + property uint16_t Word; + property uint32_t DWord; + property float F; + property uint64_t QWord; + property double D; + property System::Object^ Ptr; + property bool ExceptionThrown; +}; +#endif #ifdef __cplusplus_cli @@ -237,25 +250,11 @@ class API { } #endif -#ifdef __cplusplus_cli - ref struct DotNetInvokeRet { - array^ Bytes; - uint8_t Byte; - uint16_t Word; - uint32_t DWord; - float F; - uint64_t QWord; - double D; - System::Object^ Ptr; - bool ExceptionThrown; - }; -#endif - #ifdef __cplusplus_cli ref struct DotNetManagedSingleton { - API::ManagedObject^ instance; - API::TypeDefinition^ type; - API::TypeInfo^ type_info; + property API::ManagedObject^ Instance; + property API::TypeDefinition^ Type; + property API::TypeInfo^ TypeInfo; }; System::Collections::Generic::List^ get_managed_singletons() CPP_MEMBER_CONST { @@ -286,9 +285,9 @@ class API { for (auto& s : out) { DotNetManagedSingleton^ managed = gcnew DotNetManagedSingleton(); - managed->instance = REF_MAKE_POINTER(API::ManagedObject, s.instance); - managed->type = REF_MAKE_POINTER(API::TypeDefinition, s.t); - managed->type_info = REF_MAKE_POINTER(API::TypeInfo, s.type_info); + managed->Instance = REF_MAKE_POINTER(API::ManagedObject, s.instance); + managed->Type = REF_MAKE_POINTER(API::TypeDefinition, s.t); + managed->TypeInfo = REF_MAKE_POINTER(API::TypeInfo, s.type_info); out2->Add(managed); } @@ -837,7 +836,23 @@ class API { return API::s_instance->sdk()->method->get_num_params(*this); } - /*CPP_VECTOR(REFrameworkMethodParameter CPP_CONTAINER_RET) get_params() CPP_MEMBER_CONST { +#ifdef __cplusplus_cli + ref struct DotNetMethodParameter { + DotNetMethodParameter(const REFrameworkMethodParameter& p) { + Name = gcnew System::String(p.name); + Type = gcnew API::TypeDefinition(p.t); + } + + property System::String^ Name; + property API::TypeDefinition^ Type; + }; +#endif + +#ifdef __cplusplus_cli + System::Collections::Generic::List^ get_params() { +#else + std::vector get_params() CPP_MEMBER_CONST { +#endif std::vector params; params.resize(get_num_params()); @@ -854,18 +869,17 @@ class API { #endif #ifdef __cplusplus_cli - //cliext::vector out = gcnew cliext::vector(); - System::Collections::Generic::List^ out = gcnew System::Collections::Generic::List(); + System::Collections::Generic::List^ out = gcnew System::Collections::Generic::List(); - for (auto p : params) { - //out->Add(gcnew REFrameworkMethodParameter(p)); + for (auto& p : params) { + out->Add(gcnew DotNetMethodParameter(p)); } return out; -else +#else return params; #endif - }*/ + } uint32_t get_index() CPP_MEMBER_CONST { return API::s_instance->sdk()->method->get_index(*this); From 81bd3d7456e176ac253d25fde5591df9e6da5f6f Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 20:14:30 -0700 Subject: [PATCH 005/207] Revert C++/CLI API.hpp changes entirely --- include/reframework/API.hpp | 845 ++++++++++-------------------------- 1 file changed, 235 insertions(+), 610 deletions(-) diff --git a/include/reframework/API.hpp b/include/reframework/API.hpp index 0d61c4956..7ec43f6b1 100644 --- a/include/reframework/API.hpp +++ b/include/reframework/API.hpp @@ -1,15 +1,5 @@ #pragma once -#ifdef __cplusplus_cli -#pragma managed -#include -#include -//#include -#define REF_CONTAINER_NAMESPACE cliext -#else -#define REF_CONTAINER_NAMESPACE std -#endif - extern "C" { #include "API.h" } @@ -42,76 +32,11 @@ struct InvokeRet { }; #pragma pack(pop) -#ifdef __cplusplus_cli -public ref struct DotNetInvokeRet { - property array^ Bytes; - property uint8_t Byte; - property uint16_t Word; - property uint32_t DWord; - property float F; - property uint64_t QWord; - property double D; - property System::Object^ Ptr; - property bool ExceptionThrown; -}; -#endif - - -#ifdef __cplusplus_cli -#define CPP_MEMBER_CONST -#define CPP_POINTER ^ -#define REF_MAKE_POINTER(Tx, x) gcnew Tx(x) -#define CPP_CONTAINER_RET ^ -#define CPP_VECTOR(Tx) System::Collections::Generic::List^ - -// I had to resort to this because was having issues with template types causing compiler crashes -#define PUBLIC_POINTER_CONTAINER(X, Tx) ref struct X { public: X(Tx ptr) : m_impl{gcnew System::UIntPtr((void*)ptr)} { } operator Tx() { return (Tx)m_impl->ToPointer(); } void* ptr() { return m_impl->ToPointer(); } private: System::UIntPtr^ m_impl; public: - - -ref class API; - -public ref class API { -#else -#define CPP_MEMBER_CONST const -#define CPP_POINTER * -#define REF_MAKE_POINTER(Tx, x) ((Tx*)x) -#define CPP_CONTAINER_RET -#define CPP_VECTOR(Tx) std::vector - -class API; - class API { -#endif private: - const ::REFrameworkPluginInitializeParam* m_param; - const REFrameworkSDKData* m_sdk; - -#ifndef __cplusplus_cli - std::recursive_mutex m_lua_mtx{}; -#endif - -#ifdef __cplusplus_cli - static API^ s_instance{}; -#else static inline std::unique_ptr s_instance{}; -#endif public: -#ifdef __cplusplus_cli - ref struct TDB; - ref struct TypeDefinition; - ref struct Method; - ref struct Field; - ref struct Property; - ref struct ManagedObject; - ref struct ResourceManager; - ref struct Resource; - ref struct TypeInfo; - ref struct VMContext; - ref struct ReflectionProperty; - ref struct ReflectionMethod; - ref struct REFramework_; -#else struct TDB; struct TypeDefinition; struct Method; @@ -124,10 +49,7 @@ class API { struct VMContext; struct ReflectionProperty; struct ReflectionMethod; - struct REFramework_; -#endif -#ifndef __cplusplus_cli struct LuaLock { LuaLock() { API::s_instance->lock_lua(); @@ -137,22 +59,24 @@ class API { API::s_instance->unlock_lua(); } }; -#endif public: // ALWAYS call initialize first in reframework_plugin_initialize -#ifndef __cplusplus_cli - static inline std::unique_ptr& initialize(const REFrameworkPluginInitializeParam* param); -#else - static inline API^ initialize(uintptr_t param); -#endif + static auto& initialize(const REFrameworkPluginInitializeParam* param) { + if (param == nullptr) { + throw std::runtime_error("param is null"); + } + + if (s_instance != nullptr) { + throw std::runtime_error("API already initialized"); + } + + s_instance = std::make_unique(param); + return s_instance; + } // only call this AFTER calling initialize -#ifndef __cplusplus_cli - static inline auto& get() { -#else - static inline auto get() { -#endif + static auto& get() { if (s_instance == nullptr) { throw std::runtime_error("API not initialized"); } @@ -161,45 +85,36 @@ class API { } public: -#ifdef __cplusplus_cli - API(uintptr_t param) - : m_param{reinterpret_cast(param)}, - m_sdk{m_param->sdk} - { - } -#else - API(const REFrameworkPluginInitializeParam* param) + API(const REFrameworkPluginInitializeParam* param) : m_param{param}, m_sdk{param->sdk} { } -#endif virtual ~API() { } - inline const REFrameworkPluginInitializeParam* param() CPP_MEMBER_CONST { + inline const auto param() const { return m_param; } - inline const REFrameworkSDKData* sdk() CPP_MEMBER_CONST { + inline const REFrameworkSDKData* sdk() const { return m_sdk; } - inline TDB CPP_POINTER tdb() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(TDB, sdk()->functions->get_tdb()); + inline const auto tdb() const { + return (TDB*)sdk()->functions->get_tdb(); } - inline ResourceManager CPP_POINTER resource_manager() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(ResourceManager, sdk()->functions->get_resource_manager()); + inline const auto resource_manager() const { + return (ResourceManager*)sdk()->functions->get_resource_manager(); } - inline REFramework_ CPP_POINTER reframework() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(REFramework_, param()->functions); + inline const auto reframework() const { + return (REFramework*)param()->functions; } -#ifndef __cplusplus_cli void lock_lua() { m_lua_mtx.lock(); m_param->functions->lock_lua(); @@ -209,58 +124,28 @@ class API { m_param->functions->unlock_lua(); m_lua_mtx.unlock(); } -#endif template void log_error(const char* format, Args... args) { m_param->functions->log_error(format, args...); } template void log_warn(const char* format, Args... args) { m_param->functions->log_warn(format, args...); } template void log_info(const char* format, Args... args) { m_param->functions->log_info(format, args...); } - API::VMContext CPP_POINTER get_vm_context() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::VMContext, sdk()->functions->get_vm_context()); - } - - API::ManagedObject CPP_POINTER typeof(const char* name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::ManagedObject, sdk()->functions->typeof_(name)); - } - -#ifdef __cplusplus_cli - API::ManagedObject CPP_POINTER typeof(System::String^ name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::ManagedObject, sdk()->functions->typeof_(msclr::interop::marshal_as(name).c_str())); + API::VMContext* get_vm_context() const { + return (API::VMContext*)sdk()->functions->get_vm_context(); } -#endif - - API::ManagedObject CPP_POINTER get_managed_singleton(std::string_view name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::ManagedObject, sdk()->functions->get_managed_singleton(name.data())); + API::ManagedObject* typeof(const char* name) const { + return (API::ManagedObject*)sdk()->functions->typeof_(name); } -#ifdef __cplusplus_cli - API::ManagedObject CPP_POINTER get_managed_singleton(System::String^ name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::ManagedObject, sdk()->functions->get_managed_singleton(msclr::interop::marshal_as(name).c_str())); + API::ManagedObject* get_managed_singleton(std::string_view name) const { + return (API::ManagedObject*)sdk()->functions->get_managed_singleton(name.data()); } -#endif - void* get_native_singleton(std::string_view name) CPP_MEMBER_CONST { + void* get_native_singleton(std::string_view name) const { return sdk()->functions->get_native_singleton(name.data()); } -#ifdef __cplusplus_cli - uintptr_t get_native_singleton(System::String^ name) { - return uintptr_t(sdk()->functions->get_native_singleton(msclr::interop::marshal_as(name).c_str())); - } -#endif - -#ifdef __cplusplus_cli - ref struct DotNetManagedSingleton { - property API::ManagedObject^ Instance; - property API::TypeDefinition^ Type; - property API::TypeInfo^ TypeInfo; - }; - - System::Collections::Generic::List^ get_managed_singletons() CPP_MEMBER_CONST { -#else - std::vector get_managed_singletons() CPP_MEMBER_CONST { -#endif + std::vector get_managed_singletons() const { std::vector out{}; out.resize(512); @@ -279,30 +164,10 @@ class API { #endif out.resize(count); - -#ifdef __cplusplus_cli - System::Collections::Generic::List^ out2 = gcnew System::Collections::Generic::List(); - - for (auto& s : out) { - DotNetManagedSingleton^ managed = gcnew DotNetManagedSingleton(); - managed->Instance = REF_MAKE_POINTER(API::ManagedObject, s.instance); - managed->Type = REF_MAKE_POINTER(API::TypeDefinition, s.t); - managed->TypeInfo = REF_MAKE_POINTER(API::TypeInfo, s.type_info); - - out2->Add(managed); - } - - return out2; -#else return out; -#endif } - -#ifdef __cplusplus_cli - -#endif - std::vector get_native_singletons() CPP_MEMBER_CONST { + std::vector get_native_singletons() const { std::vector out{}; out.resize(512); @@ -325,181 +190,116 @@ class API { } public: -#ifdef __cplusplus_cli - - - -#else - template - struct PointerContainer { - public: - PointerContainer(T ptr) {}; - - operator T() const { - return (T)this; + struct TDB { + operator ::REFrameworkTDBHandle() const { + return (::REFrameworkTDBHandle)this; } - void* ptr() const { - return (void*)this; - } - }; - - #define POINTER_CONTAINER_CONSTRUCTOR(WrapperType, Tx) WrapperType(Tx ptr) : PointerContainer(ptr) {} - #define PUBLIC_POINTER_CONTAINER(X, Tx) struct X : public PointerContainer { \ - public:\ - X(Tx ptr) : PointerContainer(ptr) {} - -#endif - - //#define PUBLIC_POINTER_CONTAINER(Tx) : public PointerContainer - //#define POINTER_CONTAINER_CONSTRUCTOR(WrapperType, Tx) WrapperType(Tx ptr) : PointerContainer(ptr) {} - - PUBLIC_POINTER_CONTAINER(TDB, ::REFrameworkTDBHandle) - uint32_t get_num_types() CPP_MEMBER_CONST { + uint32_t get_num_types() const { return API::s_instance->sdk()->tdb->get_num_types(*this); } - uint32_t get_num_methods() CPP_MEMBER_CONST { + uint32_t get_num_methods() const { return API::s_instance->sdk()->tdb->get_num_methods(*this); } - uint32_t get_num_fields() CPP_MEMBER_CONST { + uint32_t get_num_fields() const { return API::s_instance->sdk()->tdb->get_num_fields(*this); } - uint32_t get_num_properties() CPP_MEMBER_CONST { + uint32_t get_num_properties() const { return API::s_instance->sdk()->tdb->get_num_properties(*this); } - uint32_t get_strings_size() CPP_MEMBER_CONST { + uint32_t get_strings_size() const { return API::s_instance->sdk()->tdb->get_strings_size(*this); } - uint32_t get_raw_data_size() CPP_MEMBER_CONST { + uint32_t get_raw_data_size() const { return API::s_instance->sdk()->tdb->get_raw_data_size(*this); } - const char* get_string_database() CPP_MEMBER_CONST { + const char* get_string_database() const { return API::s_instance->sdk()->tdb->get_string_database(*this); } -#ifdef __cplusplus_cli - // Use Span instead - System::Span get_raw_data() { - return System::Span((uint8_t*)API::s_instance->sdk()->tdb->get_raw_database(*this), get_raw_data_size()); - } - - System::String^ get_string(uint32_t index) { - if (index >= get_strings_size()) { - return System::String::Empty; - } - - return gcnew System::String(API::s_instance->sdk()->tdb->get_string_database(*this) + index); - } -#endif - - uint8_t* get_raw_database() CPP_MEMBER_CONST { + uint8_t* get_raw_database() const { return (uint8_t*)API::s_instance->sdk()->tdb->get_raw_database(*this); } - API::TypeDefinition CPP_POINTER get_type(uint32_t index) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->tdb->get_type(*this, index)); - } - - API::TypeDefinition CPP_POINTER find_type(std::string_view name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->tdb->find_type(*this, name.data())); + API::TypeDefinition* get_type(uint32_t index) const { + return (API::TypeDefinition*)API::s_instance->sdk()->tdb->get_type(*this, index); } -#ifdef __cplusplus_cli - API::TypeDefinition CPP_POINTER find_type(System::String^ name) { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->tdb->find_type(*this, msclr::interop::marshal_as(name).c_str())); + API::TypeDefinition* find_type(std::string_view name) const { + return (API::TypeDefinition*)API::s_instance->sdk()->tdb->find_type(*this, name.data()); } -#endif - API::TypeDefinition CPP_POINTER find_type_by_fqn(uint32_t fqn) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->tdb->find_type_by_fqn(*this, fqn)); + API::TypeDefinition* find_type_by_fqn(uint32_t fqn) const { + return (API::TypeDefinition*)API::s_instance->sdk()->tdb->find_type_by_fqn(*this, fqn); } - API::Method CPP_POINTER get_method(uint32_t index) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->tdb->get_method(*this, index)); + API::Method* get_method(uint32_t index) const { + return (API::Method*)API::s_instance->sdk()->tdb->get_method(*this, index); } - API::Method CPP_POINTER find_method(std::string_view type_name, std::string_view name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->tdb->find_method(*this, type_name.data(), name.data())); + API::Method* find_method(std::string_view type_name, std::string_view name) const { + return (API::Method*)API::s_instance->sdk()->tdb->find_method(*this, type_name.data(), name.data()); } -#ifdef __cplusplus_cli - API::Method CPP_POINTER find_method(System::String^ type_name, System::String^ name) { - return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->tdb->find_method(*this, msclr::interop::marshal_as(type_name).c_str(), msclr::interop::marshal_as(name).c_str())); + API::Field* get_field(uint32_t index) const { + return (API::Field*)API::s_instance->sdk()->tdb->get_field(*this, index); } -#endif - API::Field CPP_POINTER get_field(uint32_t index) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->tdb->get_field(*this, index)); + API::Field* find_field(std::string_view type_name, std::string_view name) const { + return (API::Field*)API::s_instance->sdk()->tdb->find_field(*this, type_name.data(), name.data()); } - API::Field CPP_POINTER find_field(std::string_view type_name, std::string_view name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->tdb->find_field(*this, type_name.data(), name.data())); + API::Property* get_property(uint32_t index) const { + return (API::Property*)API::s_instance->sdk()->tdb->get_property(*this, index); } + }; -#ifdef __cplusplus_cli - API::Field CPP_POINTER find_field(System::String^ type_name, System::String^ name) { - return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->tdb->find_field(*this, msclr::interop::marshal_as(type_name).c_str(), msclr::interop::marshal_as(name).c_str())); + struct REFramework { + operator ::REFrameworkHandle() { + return (::REFrameworkHandle)this; } -#endif - API::Property CPP_POINTER get_property(uint32_t index) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::Property, API::s_instance->sdk()->tdb->get_property(*this, index)); + bool is_drawing_ui() const { + return API::s_instance->param()->functions->is_drawing_ui(); } }; - PUBLIC_POINTER_CONTAINER(REFramework_, const ::REFrameworkPluginFunctions*) - bool is_drawing_ui() CPP_MEMBER_CONST { - return API::s_instance->param()->functions->is_drawing_ui(); + struct TypeDefinition { + operator ::REFrameworkTypeDefinitionHandle() const { + return (::REFrameworkTypeDefinitionHandle)this; } - }; - PUBLIC_POINTER_CONTAINER(TypeDefinition, ::REFrameworkTypeDefinitionHandle) - uint32_t get_index() CPP_MEMBER_CONST { + uint32_t get_index() const { return API::s_instance->sdk()->type_definition->get_index(*this); } - uint32_t get_size() CPP_MEMBER_CONST { + uint32_t get_size() const { return API::s_instance->sdk()->type_definition->get_size(*this); } - uint32_t get_valuetype_size() CPP_MEMBER_CONST { + uint32_t get_valuetype_size() const { return API::s_instance->sdk()->type_definition->get_valuetype_size(*this); } - uint32_t get_fqn() CPP_MEMBER_CONST { + uint32_t get_fqn() const { return API::s_instance->sdk()->type_definition->get_fqn(*this); } -#ifdef __cplusplus_cli - System::String^ get_name() { - return gcnew System::String(API::s_instance->sdk()->type_definition->get_name(*this)); - } - - System::String^ get_namespace() { - return gcnew System::String(API::s_instance->sdk()->type_definition->get_namespace(*this)); - } -#else - const char* get_name() CPP_MEMBER_CONST { + const char* get_name() const { return API::s_instance->sdk()->type_definition->get_name(*this); } - const char* get_namespace() CPP_MEMBER_CONST { + const char* get_namespace() const { return API::s_instance->sdk()->type_definition->get_namespace(*this); } -#endif - -#ifdef __cplusplus_cli - System::String^ get_full_name() { -#else - std::string get_full_name() CPP_MEMBER_CONST { -#endif + std::string get_full_name() const { std::string buffer{}; buffer.resize(512); @@ -513,35 +313,30 @@ class API { } buffer.resize(real_size); - -#ifdef __cplusplus_cli - return gcnew System::String(buffer.c_str()); -#else return buffer; -#endif } - bool has_fieldptr_offset() CPP_MEMBER_CONST { + bool has_fieldptr_offset() const { return API::s_instance->sdk()->type_definition->has_fieldptr_offset(*this); } - int32_t get_fieldptr_offset() CPP_MEMBER_CONST { + int32_t get_fieldptr_offset() const { return API::s_instance->sdk()->type_definition->get_fieldptr_offset(*this); } - uint32_t get_num_methods() CPP_MEMBER_CONST { + uint32_t get_num_methods() const { return API::s_instance->sdk()->type_definition->get_num_methods(*this); } - uint32_t get_num_fields() CPP_MEMBER_CONST { + uint32_t get_num_fields() const { return API::s_instance->sdk()->type_definition->get_num_fields(*this); } - uint32_t get_num_properties() CPP_MEMBER_CONST { + uint32_t get_num_properties() const { return API::s_instance->sdk()->type_definition->get_num_properties(*this); } - bool is_derived_from(API::TypeDefinition CPP_POINTER other) { + bool is_derived_from(API::TypeDefinition* other) { return API::s_instance->sdk()->type_definition->is_derived_from(*this, *other); } @@ -549,230 +344,115 @@ class API { return API::s_instance->sdk()->type_definition->is_derived_from_by_name(*this, other.data()); } -#ifdef __cplusplus_cli - bool is_derived_from(System::String^ other) { - return API::s_instance->sdk()->type_definition->is_derived_from_by_name(*this, msclr::interop::marshal_as(other).c_str()); - } -#endif - - bool is_valuetype() CPP_MEMBER_CONST { + bool is_valuetype() const { return API::s_instance->sdk()->type_definition->is_valuetype(*this); } - bool is_enum() CPP_MEMBER_CONST { + bool is_enum() const { return API::s_instance->sdk()->type_definition->is_enum(*this); } - bool is_by_ref() CPP_MEMBER_CONST { + bool is_by_ref() const { return API::s_instance->sdk()->type_definition->is_by_ref(*this); } - bool is_pointer() CPP_MEMBER_CONST { + bool is_pointer() const { return API::s_instance->sdk()->type_definition->is_pointer(*this); } - bool is_primitive() CPP_MEMBER_CONST { + bool is_primitive() const { return API::s_instance->sdk()->type_definition->is_primitive(*this); } - ::REFrameworkVMObjType get_vm_obj_type() CPP_MEMBER_CONST { + ::REFrameworkVMObjType get_vm_obj_type() const { return API::s_instance->sdk()->type_definition->get_vm_obj_type(*this); } - API::Method CPP_POINTER find_method(std::string_view name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->type_definition->find_method(*this, name.data())); + API::Method* find_method(std::string_view name) const { + return (API::Method*)API::s_instance->sdk()->type_definition->find_method(*this, name.data()); } - API::Field CPP_POINTER find_field(std::string_view name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->type_definition->find_field(*this, name.data())); + API::Field* find_field(std::string_view name) const { + return (API::Field*)API::s_instance->sdk()->type_definition->find_field(*this, name.data()); } - API::Property CPP_POINTER find_property(std::string_view name) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::Property, API::s_instance->sdk()->type_definition->find_property(*this, name.data())); + API::Property* find_property(std::string_view name) const { + return (API::Property*)API::s_instance->sdk()->type_definition->find_property(*this, name.data()); } -#ifdef __cplusplus_cli - API::Method CPP_POINTER find_method(System::String^ name) { - return REF_MAKE_POINTER(API::Method, API::s_instance->sdk()->type_definition->find_method(*this, msclr::interop::marshal_as(name).c_str())); - } - - API::Field CPP_POINTER find_field(System::String^ name) { - return REF_MAKE_POINTER(API::Field, API::s_instance->sdk()->type_definition->find_field(*this, msclr::interop::marshal_as(name).c_str())); - } - - API::Property CPP_POINTER find_property(System::String^ name) { - return REF_MAKE_POINTER(API::Property, API::s_instance->sdk()->type_definition->find_property(*this, msclr::interop::marshal_as(name).c_str())); - } -#endif - - CPP_VECTOR(API::Method CPP_POINTER) get_methods() CPP_MEMBER_CONST { - std::vector methods; + std::vector get_methods() const { + std::vector methods; methods.resize(get_num_methods()); - auto result = API::s_instance->sdk()->type_definition->get_methods(*this, (::REFrameworkMethodHandle*)methods.data(), methods.size() * sizeof(void*), nullptr); + auto result = API::s_instance->sdk()->type_definition->get_methods(*this, (REFrameworkMethodHandle*)&methods[0], methods.size() * sizeof(API::Method*), nullptr); if (result != REFRAMEWORK_ERROR_NONE) { return {}; } - #ifdef __cplusplus_cli - //cliext::vector out = gcnew cliext::vector(); - System::Collections::Generic::List^ out = gcnew System::Collections::Generic::List(); - - for (auto m : methods) { - //out.push_back(REF_MAKE_POINTER(API::Method, m)); - out->Add(REF_MAKE_POINTER(API::Method, (::REFrameworkMethodHandle)m)); - } - - return out; - #else - return *(std::vector*)&methods; - #endif + return methods; } - CPP_VECTOR(API::Field CPP_POINTER) get_fields() CPP_MEMBER_CONST { - std::vector fields; + std::vector get_fields() const { + std::vector fields; fields.resize(get_num_fields()); - auto result = API::s_instance->sdk()->type_definition->get_fields(*this, (REFrameworkFieldHandle*)&fields[0], fields.size() * sizeof(void*), nullptr); + auto result = API::s_instance->sdk()->type_definition->get_fields(*this, (REFrameworkFieldHandle*)&fields[0], fields.size() * sizeof(API::Field*), nullptr); if (result != REFRAMEWORK_ERROR_NONE) { return {}; } - #ifdef __cplusplus_cli - //cliext::::vector out = gcnew cliext::vector(); - System::Collections::Generic::List^ out = gcnew System::Collections::Generic::List(); - - for (auto f : fields) { - out->Add(REF_MAKE_POINTER(API::Field, (::REFrameworkFieldHandle)f)); - } - - return out; - #else - return *(std::vector*)&fields; - #endif + return fields; } - CPP_VECTOR(API::Property CPP_POINTER) get_properties() CPP_MEMBER_CONST { + std::vector get_properties() const { throw std::runtime_error("Not implemented"); -#ifdef __cplusplus_cli - return nullptr; -#else return {}; -#endif } - void* get_instance() CPP_MEMBER_CONST { + void* get_instance() const { return API::s_instance->sdk()->type_definition->get_instance(*this); } - void* create_instance_deprecated() CPP_MEMBER_CONST { + void* create_instance_deprecated() const { return API::s_instance->sdk()->type_definition->create_instance_deprecated(*this); } - API::ManagedObject CPP_POINTER create_instance(int flags) CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::ManagedObject, API::s_instance->sdk()->type_definition->create_instance(*this, flags)); + API::ManagedObject* create_instance(int flags = 0) const { + return (API::ManagedObject*)API::s_instance->sdk()->type_definition->create_instance(*this, flags); } - API::TypeDefinition CPP_POINTER get_parent_type() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->type_definition->get_parent_type(*this)); + API::TypeDefinition* get_parent_type() const { + return (API::TypeDefinition*)API::s_instance->sdk()->type_definition->get_parent_type(*this); } - API::TypeDefinition CPP_POINTER get_declaring_type() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->type_definition->get_declaring_type(*this)); + API::TypeDefinition* get_declaring_type() const { + return (API::TypeDefinition*)API::s_instance->sdk()->type_definition->get_declaring_type(*this); } - API::TypeDefinition CPP_POINTER get_underlying_type() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->type_definition->get_underlying_type(*this)); + API::TypeDefinition* get_underlying_type() const { + return (API::TypeDefinition*)API::s_instance->sdk()->type_definition->get_underlying_type(*this); } - API::TypeInfo CPP_POINTER get_type_info() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeInfo, API::s_instance->sdk()->type_definition->get_type_info(*this)); + API::TypeInfo* get_type_info() const { + return (API::TypeInfo*)API::s_instance->sdk()->type_definition->get_type_info(*this); } - API::ManagedObject CPP_POINTER get_runtime_type() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::ManagedObject, API::s_instance->sdk()->type_definition->get_runtime_type(*this)); + API::ManagedObject* get_runtime_type() const { + return (API::ManagedObject*)API::s_instance->sdk()->type_definition->get_runtime_type(*this); } }; - PUBLIC_POINTER_CONTAINER(Method, ::REFrameworkMethodHandle) - // .NET version -#ifdef __cplusplus_cli - DotNetInvokeRet^ invoke(System::Object^ obj, array^ args) { - std::vector args2{}; - args2.resize(args->Length); - - for (int i = 0; i < args->Length; ++i) { - //args2[i] = args[i]->ptr(); - const auto t = args[i]->GetType(); - - if (t == System::Byte::typeid) { - uint8_t v = System::Convert::ToByte(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::UInt16::typeid) { - uint16_t v = System::Convert::ToUInt16(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::UInt32::typeid) { - uint32_t v = System::Convert::ToUInt32(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::Single::typeid) { - float v = System::Convert::ToSingle(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::UInt64::typeid) { - uint64_t v = System::Convert::ToUInt64(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::Double::typeid) { - double v = System::Convert::ToDouble(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::IntPtr::typeid) { - args2[i] = (void*)(uint64_t)System::Convert::ToInt64(args[i]); - } else { - //args2[i] = args[i]->ptr(); - } - } - - void* obj_ptr = nullptr; - - if (obj != nullptr) { - const auto obj_t = obj->GetType(); - if (obj_t == System::IntPtr::typeid || obj_t == System::UIntPtr::typeid) { - obj_ptr = (void*)(uint64_t)System::Convert::ToInt64(obj); - } else if (obj_t == API::ManagedObject::typeid) { - obj_ptr = ((API::ManagedObject^)obj)->ptr(); - } else { - //obj_ptr = obj->ptr(); - } - } - - reframework::InvokeRet result; - auto succeed = API::s_instance->sdk()->method->invoke(*this, obj_ptr, (void**)&args2[0], args2.size() * sizeof(void*), &result, sizeof(result)); - - DotNetInvokeRet^ out = gcnew DotNetInvokeRet(); - out->ExceptionThrown = result.exception_thrown; - - if (result.exception_thrown) { - return out; - } - - out->Bytes = gcnew array(result.bytes.size()); - System::Runtime::InteropServices::Marshal::Copy(System::IntPtr((void*)result.bytes.data()), out->Bytes, 0, result.bytes.size()); - - out->Byte = result.byte; - out->Word = result.word; - out->DWord = result.dword; - out->F = result.f; - out->QWord = result.qword; - out->D = result.d; - out->Ptr = gcnew System::IntPtr(result.ptr); - - return out; + struct Method { + operator ::REFrameworkMethodHandle() const { + return (::REFrameworkMethodHandle)this; } -#else - reframework::InvokeRet invoke(API::ManagedObject CPP_POINTER obj, const std::vector& args) { + + reframework::InvokeRet invoke(API::ManagedObject* obj, const std::vector& args) { reframework::InvokeRet out{}; - auto result = API::s_instance->sdk()->method->invoke(*this, obj->ptr(), (void**)&args[0], args.size() * sizeof(void*), &out, sizeof(out)); + auto result = API::s_instance->sdk()->method->invoke(*this, obj, (void**)&args[0], args.size() * sizeof(void*), &out, sizeof(out)); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -782,7 +462,6 @@ class API { return out; } -#endif reframework::InvokeRet invoke(API::ManagedObject* obj, const std::span& args) { static const auto fn = API::s_instance->sdk()->method->invoke; @@ -800,59 +479,37 @@ class API { } template - T get_function() CPP_MEMBER_CONST { + T get_function() const { return (T)API::s_instance->sdk()->method->get_function(*this); } - void* get_function_raw() CPP_MEMBER_CONST { + void* get_function_raw() const { return API::s_instance->sdk()->method->get_function(*this); } // e.g. call(sdk->get_vm_context(), obj, args...); template - Ret call(Args... args) CPP_MEMBER_CONST { + Ret call(Args... args) const { return get_function()(args...); } -#ifdef __cplusplus_cli - System::String^ get_name() { - return gcnew System::String(API::s_instance->sdk()->method->get_name(*this)); - } -#else - const char* get_name() CPP_MEMBER_CONST { + const char* get_name() const { return API::s_instance->sdk()->method->get_name(*this); } -#endif - API::TypeDefinition CPP_POINTER get_declaring_type() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->method->get_declaring_type(*this)); + API::TypeDefinition* get_declaring_type() const { + return (API::TypeDefinition*)API::s_instance->sdk()->method->get_declaring_type(*this); } - API::TypeDefinition CPP_POINTER get_return_type() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->method->get_return_type(*this)); + API::TypeDefinition* get_return_type() const { + return (API::TypeDefinition*)API::s_instance->sdk()->method->get_return_type(*this); } - uint32_t get_num_params() CPP_MEMBER_CONST { + uint32_t get_num_params() const { return API::s_instance->sdk()->method->get_num_params(*this); } -#ifdef __cplusplus_cli - ref struct DotNetMethodParameter { - DotNetMethodParameter(const REFrameworkMethodParameter& p) { - Name = gcnew System::String(p.name); - Type = gcnew API::TypeDefinition(p.t); - } - - property System::String^ Name; - property API::TypeDefinition^ Type; - }; -#endif - -#ifdef __cplusplus_cli - System::Collections::Generic::List^ get_params() { -#else - std::vector get_params() CPP_MEMBER_CONST { -#endif + std::vector get_params() const { std::vector params; params.resize(get_num_params()); @@ -868,107 +525,103 @@ class API { } #endif -#ifdef __cplusplus_cli - System::Collections::Generic::List^ out = gcnew System::Collections::Generic::List(); - - for (auto& p : params) { - out->Add(gcnew DotNetMethodParameter(p)); - } - - return out; -#else return params; -#endif } - uint32_t get_index() CPP_MEMBER_CONST { + uint32_t get_index() const { return API::s_instance->sdk()->method->get_index(*this); } - int get_virtual_index() CPP_MEMBER_CONST { + int get_virtual_index() const { return API::s_instance->sdk()->method->get_virtual_index(*this); } - bool is_static() CPP_MEMBER_CONST { + bool is_static() const { return API::s_instance->sdk()->method->is_static(*this); } - uint16_t get_flags() CPP_MEMBER_CONST { + uint16_t get_flags() const { return API::s_instance->sdk()->method->get_flags(*this); } - uint16_t get_impl_flags() CPP_MEMBER_CONST { + uint16_t get_impl_flags() const { return API::s_instance->sdk()->method->get_impl_flags(*this); } - uint32_t get_invoke_id() CPP_MEMBER_CONST { + uint32_t get_invoke_id() const { return API::s_instance->sdk()->method->get_invoke_id(*this); } - unsigned int add_hook(REFPreHookFn pre_fn, REFPostHookFn post_fn, bool ignore_jmp) CPP_MEMBER_CONST { + unsigned int add_hook(REFPreHookFn pre_fn, REFPostHookFn post_fn, bool ignore_jmp) const { return API::s_instance->sdk()->functions->add_hook(*this, pre_fn, post_fn, ignore_jmp); } - void remove_hook(unsigned int hook_id) CPP_MEMBER_CONST { + void remove_hook(unsigned int hook_id) const { API::s_instance->sdk()->functions->remove_hook(*this, hook_id); } }; - PUBLIC_POINTER_CONTAINER(Field, ::REFrameworkFieldHandle) - #ifdef __cplusplus_cli - System::String^ get_name() { - return gcnew System::String(API::s_instance->sdk()->field->get_name(*this)); + struct Field { + operator ::REFrameworkFieldHandle() const { + return (::REFrameworkFieldHandle)this; } - #else - const char* get_name() CPP_MEMBER_CONST { + + const char* get_name() const { return API::s_instance->sdk()->field->get_name(*this); } - #endif - API::TypeDefinition CPP_POINTER get_declaring_type() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->field->get_declaring_type(*this)); + API::TypeDefinition* get_declaring_type() const { + return (API::TypeDefinition*)API::s_instance->sdk()->field->get_declaring_type(*this); } - API::TypeDefinition CPP_POINTER get_type() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->field->get_type(*this)); + API::TypeDefinition* get_type() const { + return (API::TypeDefinition*)API::s_instance->sdk()->field->get_type(*this); } - uint32_t get_offset_from_base() CPP_MEMBER_CONST { + uint32_t get_offset_from_base() const { return API::s_instance->sdk()->field->get_offset_from_base(*this); } - uint32_t get_offset_from_fieldptr() CPP_MEMBER_CONST { + uint32_t get_offset_from_fieldptr() const { return API::s_instance->sdk()->field->get_offset_from_fieldptr(*this); } - uint32_t get_flags() CPP_MEMBER_CONST { + uint32_t get_flags() const { return API::s_instance->sdk()->field->get_flags(*this); } - bool is_static() CPP_MEMBER_CONST { + bool is_static() const { return API::s_instance->sdk()->field->is_static(*this); } - bool is_literal() CPP_MEMBER_CONST { + bool is_literal() const { return API::s_instance->sdk()->field->is_literal(*this); } - void* get_init_data() CPP_MEMBER_CONST { + void* get_init_data() const { return API::s_instance->sdk()->field->get_init_data(*this); } - void* get_data_raw(void* obj, bool is_value_type) CPP_MEMBER_CONST { + void* get_data_raw(void* obj, bool is_value_type = false) const { return API::s_instance->sdk()->field->get_data_raw(*this, obj, is_value_type); } - template T& get_data(void* object, bool is_value_type) CPP_MEMBER_CONST { return *(T*)get_data_raw(object); } + template T& get_data(void* object = nullptr, bool is_value_type = false) const { return *(T*)get_data_raw(object); } }; - PUBLIC_POINTER_CONTAINER(Property, ::REFrameworkPropertyHandle) + struct Property { + operator ::REFrameworkPropertyHandle() const { + return (::REFrameworkPropertyHandle)this; + } + // TODO: Implement }; - PUBLIC_POINTER_CONTAINER(ManagedObject, ::REFrameworkManagedObjectHandle) + struct ManagedObject { + operator ::REFrameworkManagedObjectHandle() const { + return (::REFrameworkManagedObjectHandle)this; + } + void add_ref() { API::s_instance->sdk()->managed_object->add_ref(*this); } @@ -977,40 +630,40 @@ class API { API::s_instance->sdk()->managed_object->release(*this); } - API::TypeDefinition CPP_POINTER get_type_definition() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->managed_object->get_type_definition(*this)); + API::TypeDefinition* get_type_definition() const { + return (API::TypeDefinition*)API::s_instance->sdk()->managed_object->get_type_definition(*this); } - bool is_managed_object() CPP_MEMBER_CONST { - return API::s_instance->sdk()->managed_object->is_managed_object(this->ptr()); + bool is_managed_object() const { + return API::s_instance->sdk()->managed_object->is_managed_object(*this); } - uint32_t get_ref_count() CPP_MEMBER_CONST { + uint32_t get_ref_count() const { return API::s_instance->sdk()->managed_object->get_ref_count(*this); } - uint32_t get_vm_obj_type() CPP_MEMBER_CONST { + uint32_t get_vm_obj_type() const { return API::s_instance->sdk()->managed_object->get_vm_obj_type(*this); } - API::TypeInfo CPP_POINTER get_type_info() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeInfo, API::s_instance->sdk()->managed_object->get_type_info(*this)); + API::TypeInfo* get_type_info() const { + return (API::TypeInfo*)API::s_instance->sdk()->managed_object->get_type_info(*this); } - void* get_reflection_properties() CPP_MEMBER_CONST { + void* get_reflection_properties() const { return API::s_instance->sdk()->managed_object->get_reflection_properties(*this); } - API::ReflectionProperty CPP_POINTER get_reflection_property_descriptor(std::string_view name) { - return REF_MAKE_POINTER(API::ReflectionProperty, API::s_instance->sdk()->managed_object->get_reflection_property_descriptor(*this, name.data())); + API::ReflectionProperty* get_reflection_property_descriptor(std::string_view name) { + return (API::ReflectionProperty*)API::s_instance->sdk()->managed_object->get_reflection_property_descriptor(*this, name.data()); } - API::ReflectionMethod CPP_POINTER get_reflection_method_descriptor(std::string_view name) { - return REF_MAKE_POINTER(API::ReflectionMethod, API::s_instance->sdk()->managed_object->get_reflection_method_descriptor(*this, name.data())); + API::ReflectionMethod* get_reflection_method_descriptor(std::string_view name) { + return (API::ReflectionMethod*)API::s_instance->sdk()->managed_object->get_reflection_method_descriptor(*this, name.data()); } template - Ret call(std::string_view method_name, Args... args) CPP_MEMBER_CONST { + Ret call(std::string_view method_name, Args... args) const { auto t = get_type_definition(); if (t == nullptr) { @@ -1026,24 +679,6 @@ class API { return m->get_function()(args...); } -#ifdef __cplusplus_cli - // .NET version - DotNetInvokeRet^ invoke(System::String^ method_name, array^ args) { - auto t = get_type_definition(); - - if (t == nullptr) { - return nullptr; - } - - auto m = t->find_method(method_name); - - if (m == nullptr) { - return nullptr; - } - - return m->invoke(this, args); - } -#else reframework::InvokeRet invoke(std::string_view method_name, const std::vector& args) { auto t = get_type_definition(); @@ -1059,10 +694,9 @@ class API { return m->invoke(this, args); } -#endif template - T* get_field(std::string_view name, bool is_value_type) CPP_MEMBER_CONST { + T* get_field(std::string_view name, bool is_value_type = false) const { auto t = get_type_definition(); if (t == nullptr) { @@ -1079,13 +713,21 @@ class API { } }; - PUBLIC_POINTER_CONTAINER(ResourceManager, REFrameworkResourceManagerHandle) - API::Resource CPP_POINTER create_resource(std::string_view type_name, std::string_view name) { - return REF_MAKE_POINTER(API::Resource, API::s_instance->sdk()->resource_manager->create_resource(*this, type_name.data(), name.data())); + struct ResourceManager { + operator ::REFrameworkResourceManagerHandle() const { + return (::REFrameworkResourceManagerHandle)this; + } + + API::Resource* create_resource(std::string_view type_name, std::string_view name) { + return (API::Resource*)API::s_instance->sdk()->resource_manager->create_resource(*this, type_name.data(), name.data()); } }; - PUBLIC_POINTER_CONTAINER(Resource, ::REFrameworkResourceHandle) + struct Resource { + operator ::REFrameworkResourceHandle() const { + return (::REFrameworkResourceHandle)this; + } + void add_ref() { API::s_instance->sdk()->resource->add_ref(*this); } @@ -1095,60 +737,62 @@ class API { } }; - PUBLIC_POINTER_CONTAINER(TypeInfo, ::REFrameworkTypeInfoHandle) - #ifdef __cplusplus_cli - System::String^ get_name() { - return gcnew System::String(API::s_instance->sdk()->type_info->get_name(*this)); + struct TypeInfo { + operator ::REFrameworkTypeInfoHandle() const { + return (::REFrameworkTypeInfoHandle)this; } - #else - const char* get_name() CPP_MEMBER_CONST { + + const char* get_name() const { return API::s_instance->sdk()->type_info->get_name(*this); } - #endif - API::TypeDefinition CPP_POINTER get_type_definition() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeDefinition, API::s_instance->sdk()->type_info->get_type_definition(*this)); + API::TypeDefinition* get_type_definition() const { + return (API::TypeDefinition*)API::s_instance->sdk()->type_info->get_type_definition(*this); } - bool is_clr_type() CPP_MEMBER_CONST { + bool is_clr_type() const { return API::s_instance->sdk()->type_info->is_clr_type(*this); } - bool is_singleton() CPP_MEMBER_CONST { + bool is_singleton() const { return API::s_instance->sdk()->type_info->is_singleton(*this); } - void* get_singleton_instance() CPP_MEMBER_CONST { + void* get_singleton_instance() const { return API::s_instance->sdk()->type_info->get_singleton_instance(*this); } - void* get_reflection_properties() CPP_MEMBER_CONST { + void* get_reflection_properties() const { return API::s_instance->sdk()->type_info->get_reflection_properties(*this); } - API::ReflectionProperty CPP_POINTER get_reflection_property_descriptor(std::string_view name) { - return REF_MAKE_POINTER(API::ReflectionProperty, API::s_instance->sdk()->type_info->get_reflection_property_descriptor(*this, name.data())); + API::ReflectionProperty* get_reflection_property_descriptor(std::string_view name) { + return (API::ReflectionProperty*)API::s_instance->sdk()->type_info->get_reflection_property_descriptor(*this, name.data()); } - API::ReflectionMethod CPP_POINTER get_reflection_method_descriptor(std::string_view name) { - return REF_MAKE_POINTER(API::ReflectionMethod, API::s_instance->sdk()->type_info->get_reflection_method_descriptor(*this, name.data())); + API::ReflectionMethod* get_reflection_method_descriptor(std::string_view name) { + return (API::ReflectionMethod*)API::s_instance->sdk()->type_info->get_reflection_method_descriptor(*this, name.data()); } - void* get_deserializer_fn() CPP_MEMBER_CONST { + void* get_deserializer_fn() const { return API::s_instance->sdk()->type_info->get_deserializer_fn(*this); } - API::TypeInfo CPP_POINTER get_parent() CPP_MEMBER_CONST { - return REF_MAKE_POINTER(API::TypeInfo, API::s_instance->sdk()->type_info->get_parent(*this)); + API::TypeInfo* get_parent() const { + return (API::TypeInfo*)API::s_instance->sdk()->type_info->get_parent(*this); } - uint32_t get_crc() CPP_MEMBER_CONST { + uint32_t get_crc() const { return API::s_instance->sdk()->type_info->get_crc(*this); } }; - PUBLIC_POINTER_CONTAINER(VMContext, ::REFrameworkVMContextHandle) - bool has_exception() CPP_MEMBER_CONST { + struct VMContext { + operator ::REFrameworkVMContextHandle() const { + return (::REFrameworkVMContextHandle)this; + } + + bool has_exception() const { return API::s_instance->sdk()->vm_context->has_exception(*this); } @@ -1165,56 +809,37 @@ class API { } }; - PUBLIC_POINTER_CONTAINER(ReflectionMethod, ::REFrameworkReflectionMethodHandle) - ::REFrameworkInvokeMethod get_function() CPP_MEMBER_CONST { + struct ReflectionMethod { + operator ::REFrameworkReflectionMethodHandle() const { + return (::REFrameworkReflectionMethodHandle)this; + } + + ::REFrameworkInvokeMethod get_function() const { return API::s_instance->sdk()->reflection_method->get_function(*this); } }; - PUBLIC_POINTER_CONTAINER(ReflectionProperty, ::REFrameworkReflectionPropertyHandle) - ::REFrameworkReflectionPropertyMethod get_getter() CPP_MEMBER_CONST { + struct ReflectionProperty { + operator ::REFrameworkReflectionPropertyHandle() const { + return (::REFrameworkReflectionPropertyHandle)this; + } + + ::REFrameworkReflectionPropertyMethod get_getter() const { return API::s_instance->sdk()->reflection_property->get_getter(*this); } - bool is_static() CPP_MEMBER_CONST { + bool is_static() const { return API::s_instance->sdk()->reflection_property->is_static(*this); } - uint32_t get_size() CPP_MEMBER_CONST { + uint32_t get_size() const { return API::s_instance->sdk()->reflection_property->get_size(*this); } }; private: + const REFrameworkPluginInitializeParam* m_param; + const REFrameworkSDKData* m_sdk; + std::recursive_mutex m_lua_mtx{}; }; } - -// ALWAYS call initialize first in reframework_plugin_initialize -#ifndef __cplusplus_cli -inline std::unique_ptr& reframework::API::initialize(const REFrameworkPluginInitializeParam* param) { -#else -inline reframework::API^ reframework::API::initialize(uintptr_t param) { -#endif -#ifndef __cplusplus_cli - if (param == nullptr) { - throw std::runtime_error("param is null"); - } - - if (s_instance != nullptr) { - return s_instance; - } - - s_instance = std::make_unique(param); -#else - if (param == 0) { - throw std::runtime_error("param is null"); - } - - if (s_instance != nullptr) { - return s_instance; - } - - s_instance = gcnew reframework::API(param); -#endif - return s_instance; -} From e6d5ed1dfff1bd26808d60be1a61b63113f0e9e4 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 20:47:26 -0700 Subject: [PATCH 006/207] Revert API.h C++/CLI change --- include/reframework/API.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/reframework/API.h b/include/reframework/API.h index 3da47aea3..eddf62e20 100644 --- a/include/reframework/API.h +++ b/include/reframework/API.h @@ -98,12 +98,8 @@ typedef struct { } REFrameworkRendererData; /* strong typedefs */ -#ifdef __cplusplus_cli -#define DECLARE_REFRAMEWORK_HANDLE(name) using name = void* -#else #define DECLARE_REFRAMEWORK_HANDLE(name) struct name##__ { int unused; }; \ typedef struct name##__ *name -#endif DECLARE_REFRAMEWORK_HANDLE(REFrameworkTypeDefinitionHandle); DECLARE_REFRAMEWORK_HANDLE(REFrameworkMethodHandle); From d20bc676ab7b9c833afeb16cb2534c7ee8334c4d Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 20:48:40 -0700 Subject: [PATCH 007/207] .NET: Remove C# wrappers --- csharp-api/Wrappers/ApiWrapper.cs | 57 ------ csharp-api/test/Test/ApiWrapper.cs | 51 ----- csharp-api/test/Test/ManagedObjectWrapper.cs | 58 ------ csharp-api/test/Test/MethodWrapper.cs | 71 ------- csharp-api/test/Test/TDBWrapper.cs | 82 -------- csharp-api/test/Test/TypeDefinitionWrapper.cs | 193 ------------------ 6 files changed, 512 deletions(-) delete mode 100644 csharp-api/Wrappers/ApiWrapper.cs delete mode 100644 csharp-api/test/Test/ApiWrapper.cs delete mode 100644 csharp-api/test/Test/ManagedObjectWrapper.cs delete mode 100644 csharp-api/test/Test/MethodWrapper.cs delete mode 100644 csharp-api/test/Test/TDBWrapper.cs delete mode 100644 csharp-api/test/Test/TypeDefinitionWrapper.cs diff --git a/csharp-api/Wrappers/ApiWrapper.cs b/csharp-api/Wrappers/ApiWrapper.cs deleted file mode 100644 index 70e80ffa3..000000000 --- a/csharp-api/Wrappers/ApiWrapper.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using reframework; - -public class APIWrapper -{ - private readonly reframework.API _original; - - public APIWrapper(API original) - { - _original = original; - } - - public API.REFrameworkPluginInitializeParam* Param() - { - return _original.param(); - } - - public API.REFrameworkSDKData* Sdk() - { - return _original.sdk(); - } - - public API.TDB Tdb() - { - return _original.tdb(); - } - - public API.ResourceManager ResourceManager() - { - return _original.resource_manager(); - } - - public API.REFramework_ Reframework() - { - return _original.reframework(); - } - - public API.VMContext GetVmContext() - { - return _original.get_vm_context(); - } - - public ManagedObject Typeof(String name) - { - return _original.Invoke("typeof", name); - } - - public ManagedObject GetManagedSingleton(String name) - { - return _original.get_managed_singleton(name); - } - - public Void* GetNativeSingleton(String name) - { - return _original.get_native_singleton(name); - } -} diff --git a/csharp-api/test/Test/ApiWrapper.cs b/csharp-api/test/Test/ApiWrapper.cs deleted file mode 100644 index 94bbcb78b..000000000 --- a/csharp-api/test/Test/ApiWrapper.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using reframework; - -public class APIWrapper -{ - private readonly reframework.API _original; - - public APIWrapper(API original) - { - _original = original; - } - - public TDBWrapper GetTDB() - { - return new TDBWrapper(_original.tdb()); - } - - public API.ResourceManager GetResourceManager() - { - return _original.resource_manager(); - } - - public API.REFramework_ Get() - { - return _original.reframework(); - } - - public API.VMContext GetVMContext() - { - return _original.get_vm_context(); - } - - public API.ManagedObject TypeOf(String name) - { - return (API.ManagedObject)_original.GetType().InvokeMember("typeof", System.Reflection.BindingFlags.Public, null, _original, new object[]{ _original, name }); - } - - public API.ManagedObject GetManagedSingleton(String name) - { - return _original.get_managed_singleton(name); - } - - public List GetManagedSingletons() { - return _original.get_managed_singletons(); - } - - public System.UIntPtr GetNativeSingleton(String name) - { - return (System.UIntPtr)_original.get_native_singleton(name); - } -} diff --git a/csharp-api/test/Test/ManagedObjectWrapper.cs b/csharp-api/test/Test/ManagedObjectWrapper.cs deleted file mode 100644 index 1b4c5facf..000000000 --- a/csharp-api/test/Test/ManagedObjectWrapper.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using static reframework.API; -public class ManagedObjectWrapper { - private readonly ManagedObject _original; - - public ManagedObjectWrapper(ManagedObject original) { - _original = original; - _original.add_ref(); - } - - ~ManagedObjectWrapper() { - _original.release(); - } - - public void AddRef() { - _original.add_ref(); - } - - public void Release() { - _original.release(); - } - - public TypeDefinitionWrapper GetTypeDefinition() { - return new TypeDefinitionWrapper(_original.get_type_definition()); - } - - public Boolean IsManagedObject() { - return _original.is_managed_object(); - } - - public UInt32 GetRefCount() { - return _original.get_ref_count(); - } - - public UInt32 GetVmObjType() { - return _original.get_vm_obj_type(); - } - - public TypeInfo GetTypeInfo() { - return _original.get_type_info(); - } - - /*public Void* GetReflectionProperties() { - return _original.get_reflection_properties(); - }*/ - - /*public ReflectionProperty GetReflectionPropertyDescriptor(basic_string_view> name) { - return _original.get_reflection_property_descriptor(name); - } - - public ReflectionMethod GetReflectionMethodDescriptor(basic_string_view> name) { - return _original.get_reflection_method_descriptor(name); - }*/ - - public reframework.DotNetInvokeRet Invoke(String methodName, object[] args) { - return _original.invoke(methodName, args); - } -} diff --git a/csharp-api/test/Test/MethodWrapper.cs b/csharp-api/test/Test/MethodWrapper.cs deleted file mode 100644 index 65f0e8018..000000000 --- a/csharp-api/test/Test/MethodWrapper.cs +++ /dev/null @@ -1,71 +0,0 @@ -using reframework; -using System; -using static reframework.API; -public class MethodWrapper { - private readonly Method _original; - - public MethodWrapper(Method original) { - _original = original; - } - - public DotNetInvokeRet Invoke(Object obj, Object[] args) { - return _original.invoke(obj, args); - } - - /*public Void* GetFunctionRaw() { - return _original.get_function_raw(); - }*/ - - public string GetName() { - return _original.get_name(); - } - - public TypeDefinitionWrapper GetDeclaringType() { - return new TypeDefinitionWrapper(_original.get_declaring_type()); - } - - public TypeDefinitionWrapper GetReturnType() { - return new TypeDefinitionWrapper(_original.get_return_type()); - } - - public UInt32 GetNumParams() { - return _original.get_num_params(); - } - - public List GetParameters() { - return _original.get_params(); - } - - public UInt32 GetIndex() { - return _original.get_index(); - } - - public Int32 GetVirtualIndex() { - return _original.get_virtual_index(); - } - - public Boolean IsStatic() { - return _original.is_static(); - } - - public UInt16 GetFlags() { - return _original.get_flags(); - } - - public UInt16 GetImplFlags() { - return _original.get_impl_flags(); - } - - public UInt32 GetInvokeID() { - return _original.get_invoke_id(); - } - - // hmm... - /*public UInt32 AddHook(pre_fn, post_fn, Boolean ignore_jmp) { - return _original.add_hook(pre_fn, post_fn, ignore_jmp); - }*/ - - public void RemoveHook(UInt32 hook_id) { - _original.remove_hook(hook_id); - } -} diff --git a/csharp-api/test/Test/TDBWrapper.cs b/csharp-api/test/Test/TDBWrapper.cs deleted file mode 100644 index 2a11a4a96..000000000 --- a/csharp-api/test/Test/TDBWrapper.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; - -using static reframework.API; -public class TDBWrapper { - private readonly TDB _original; - - public TDBWrapper(TDB original) { - _original = original; - } - - public UInt32 GetNumTypes() { - return _original.get_num_types(); - } - - public UInt32 GetNumMethods() { - return _original.get_num_methods(); - } - - public UInt32 GetNumFields() { - return _original.get_num_fields(); - } - - public UInt32 GetNumProperties() { - return _original.get_num_properties(); - } - - public UInt32 GetStringsSize() { - return _original.get_strings_size(); - } - - public UInt32 GetRawDataSize() { - return _original.get_raw_data_size(); - } - - /*public SByte* GetStringDatabase() { - return _original.get_string_database(); - } - - public Byte* GetRawDatabase() { - return _original.get_raw_database(); - }*/ - - public Span GetRawData() { - return _original.get_raw_data(); - } - - public String GetString(UInt32 index) { - return _original.get_string(index); - } - - public TypeDefinitionWrapper GetType(UInt32 index) { - return new TypeDefinitionWrapper(_original.get_type(index)); - } - - public TypeDefinitionWrapper FindType(String name) { - return new TypeDefinitionWrapper(_original.find_type(name)); - } - - public TypeDefinitionWrapper FindTypeByFqn(UInt32 fqn) { - return new TypeDefinitionWrapper(_original.find_type_by_fqn(fqn)); - } - - public Method GetMethod(UInt32 index) { - return _original.get_method(index); - } - - public Method FindMethod(String type_name, String name) { - return _original.find_method(type_name, name); - } - - public Field GetField(UInt32 index) { - return _original.get_field(index); - } - - public Field FindField(String type_name, String name) { - return _original.find_field(type_name, name); - } - - public Property GetProperty(UInt32 index) { - return _original.get_property(index); - } -} diff --git a/csharp-api/test/Test/TypeDefinitionWrapper.cs b/csharp-api/test/Test/TypeDefinitionWrapper.cs deleted file mode 100644 index 01bbe7447..000000000 --- a/csharp-api/test/Test/TypeDefinitionWrapper.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System; -using reframework; - -public class TypeDefinitionWrapper -{ - private readonly reframework.API.TypeDefinition _original; - - public TypeDefinitionWrapper(API.TypeDefinition original) - { - _original = original; - } - - - public UInt32 GetIndex() - { - return _original.get_index(); - } - - public UInt32 GetSize() - { - return _original.get_size(); - } - - public UInt32 GetValuetypeSize() - { - return _original.get_valuetype_size(); - } - - public UInt32 GetFqn() - { - return _original.get_fqn(); - } - - public String GetName() - { - return _original.get_name(); - } - - public String GetNamespace() - { - return _original.get_namespace(); - } - - public String GetFullName() - { - return _original.get_full_name(); - } - - public Boolean HasFieldptrOffset() - { - return _original.has_fieldptr_offset(); - } - - public Int32 GetFieldptrOffset() - { - return _original.get_fieldptr_offset(); - } - - public UInt32 GetNumMethods() - { - return _original.get_num_methods(); - } - - public UInt32 GetNumFields() - { - return _original.get_num_fields(); - } - - public UInt32 GetNumProperties() - { - return _original.get_num_properties(); - } - - public Boolean IsDerivedFrom(String other) - { - return _original.is_derived_from(other); - } - - public Boolean IsDerivedFrom(reframework.API.TypeDefinition other) - { - return _original.is_derived_from(other); - } - - public Boolean IsDerivedFrom(TypeDefinitionWrapper other) { - return _original.is_derived_from(other._original); - } - - public Boolean IsValuetype() - { - return _original.is_valuetype(); - } - - public Boolean IsEnum() - { - return _original.is_enum(); - } - - public Boolean IsByRef() - { - return _original.is_by_ref(); - } - - public Boolean IsPointer() - { - return _original.is_pointer(); - } - - public Boolean IsPrimitive() - { - return _original.is_primitive(); - } - - public UInt32 GetVmObjType() - { - return _original.get_vm_obj_type(); - } - - public MethodWrapper FindMethod(String name) - { - return new MethodWrapper(_original.find_method(name)); - } - - public API.Field FindField(String name) - { - return _original.find_field(name); - } - - public API.Property FindProperty(String name) - { - return _original.find_property(name); - } - - public List GetMethods() - { - var methods = _original.get_methods(); - var result = new List(); - foreach (var method in methods) { - result.Add(new MethodWrapper(method)); - } - - return result; - } - - public List GetFields() - { - return _original.get_fields(); - } - - public List GetProperties() - { - return _original.get_properties(); - } - - /*public Void* GetInstance() - { - return _original.get_instance(); - } - - public Void* CreateInstanceDeprecated() - { - return _original.create_instance_deprecated(); - }*/ - - public API.ManagedObject CreateInstance(Int32 flags) - { - return _original.create_instance(flags); - } - - public TypeDefinitionWrapper GetParentType() - { - return new TypeDefinitionWrapper(_original.get_parent_type()); - } - - public TypeDefinitionWrapper GetDeclaringType() - { - return new TypeDefinitionWrapper(_original.get_declaring_type()); - } - - public TypeDefinitionWrapper GetUnderlyingType() - { - return new TypeDefinitionWrapper(_original.get_underlying_type()); - } - - public API.TypeInfo GetTypeInfo() - { - return _original.get_type_info(); - } - - public API.ManagedObject GetRuntimeType() - { - return _original.get_runtime_type(); - } -} From b08b8080e8347c2a5e6afb570e2979469a8ccfc9 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 20:49:46 -0700 Subject: [PATCH 008/207] .NET: Implement API wrappers on the C++/CLI side --- CMakeLists.txt | 4 + csharp-api/REFrameworkNET/API.cpp | 12 +- csharp-api/REFrameworkNET/API.hpp | 48 ++++- csharp-api/REFrameworkNET/Field.hpp | 75 +++++++ csharp-api/REFrameworkNET/InvokeRet.hpp | 35 ++++ csharp-api/REFrameworkNET/ManagedObject.cpp | 15 ++ csharp-api/REFrameworkNET/ManagedObject.hpp | 46 +++++ .../REFrameworkNET/ManagedSingleton.hpp | 24 +++ csharp-api/REFrameworkNET/Method.cpp | 57 ++++++ csharp-api/REFrameworkNET/Method.hpp | 97 +++++++++ csharp-api/REFrameworkNET/MethodParameter.hpp | 15 ++ csharp-api/REFrameworkNET/Property.hpp | 15 ++ csharp-api/REFrameworkNET/TDB.cpp | 1 + csharp-api/REFrameworkNET/TDB.hpp | 97 +++++++++ csharp-api/REFrameworkNET/TypeDefinition.cpp | 111 +++++++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 188 ++++++++++++++++++ csharp-api/REFrameworkNET/TypeInfo.hpp | 16 ++ csharp-api/test/Test/Test.cs | 42 ++-- 18 files changed, 874 insertions(+), 24 deletions(-) create mode 100644 csharp-api/REFrameworkNET/Field.hpp create mode 100644 csharp-api/REFrameworkNET/InvokeRet.hpp create mode 100644 csharp-api/REFrameworkNET/ManagedObject.cpp create mode 100644 csharp-api/REFrameworkNET/ManagedObject.hpp create mode 100644 csharp-api/REFrameworkNET/ManagedSingleton.hpp create mode 100644 csharp-api/REFrameworkNET/Method.cpp create mode 100644 csharp-api/REFrameworkNET/Method.hpp create mode 100644 csharp-api/REFrameworkNET/MethodParameter.hpp create mode 100644 csharp-api/REFrameworkNET/Property.hpp create mode 100644 csharp-api/REFrameworkNET/TDB.cpp create mode 100644 csharp-api/REFrameworkNET/TDB.hpp create mode 100644 csharp-api/REFrameworkNET/TypeDefinition.cpp create mode 100644 csharp-api/REFrameworkNET/TypeDefinition.hpp create mode 100644 csharp-api/REFrameworkNET/TypeInfo.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b8e3fd53..4e353b6d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13113,7 +13113,11 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework list(APPEND csharp-api_SOURCES "csharp-api/Plugin.cpp" "csharp-api/REFrameworkNET/API.cpp" + "csharp-api/REFrameworkNET/ManagedObject.cpp" + "csharp-api/REFrameworkNET/Method.cpp" "csharp-api/REFrameworkNET/PluginManager.cpp" + "csharp-api/REFrameworkNET/TDB.cpp" + "csharp-api/REFrameworkNET/TypeDefinition.cpp" ) list(APPEND csharp-api_SOURCES diff --git a/csharp-api/REFrameworkNET/API.cpp b/csharp-api/REFrameworkNET/API.cpp index 427033be4..505faa58f 100644 --- a/csharp-api/REFrameworkNET/API.cpp +++ b/csharp-api/REFrameworkNET/API.cpp @@ -5,17 +5,19 @@ #include "./API.hpp" +REFrameworkNET::API::API(const REFrameworkPluginInitializeParam* param) +{ + Console::WriteLine("REFrameworkNET.API Constructor called."); + s_api = reframework::API::initialize(param).get(); +} + REFrameworkNET::API::API(uintptr_t param) - : m_api{ reframework::API::initialize(param) } { Console::WriteLine("REFrameworkNET.API Constructor called."); + s_api = reframework::API::initialize((const REFrameworkPluginInitializeParam*)param).get(); } REFrameworkNET::API::~API() { Console::WriteLine("REFrameworkNET.API Destructor called."); -} - -reframework::API^ REFrameworkNET::API::Get() { - return m_api; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index 0a5c6283c..39e362675 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -1,8 +1,18 @@ #pragma once + +#include + #pragma managed +#include + +#include "TypeInfo.hpp" +#include "ManagedObject.hpp" +#include "TDB.hpp" +#include "ManagedSingleton.hpp" + namespace reframework { -ref class API; +class API; } using namespace System; @@ -11,12 +21,44 @@ namespace REFrameworkNET { public ref class API { public: + API(const REFrameworkPluginInitializeParam* param); API(uintptr_t param); ~API(); - reframework::API^ Get(); + inline REFrameworkNET::TDB^ GetTDB() { + return gcnew REFrameworkNET::TDB(s_api->tdb()); + } + + inline REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name) { + auto result = s_api->get_managed_singleton(msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + return gcnew REFrameworkNET::ManagedObject(result); + } + + inline System::Collections::Generic::List^ GetManagedSingletons() { + auto singletons = s_api->get_managed_singletons(); + auto result = gcnew System::Collections::Generic::List(); + + for (auto& singleton : singletons) { + if (singleton.instance == nullptr) { + continue; + } + + result->Add(gcnew REFrameworkNET::ManagedSingleton( + gcnew REFrameworkNET::ManagedObject(singleton.instance), + gcnew REFrameworkNET::TypeDefinition(singleton.t), + gcnew REFrameworkNET::TypeInfo(singleton.type_info) + )); + } + + return result; + } protected: - reframework::API^ m_api; + static reframework::API* s_api; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp new file mode 100644 index 000000000..57e5a22de --- /dev/null +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include + +#pragma managed + +namespace REFrameworkNET { +ref class TypeDefinition; + +public ref class Field { +public: + Field(const reframework::API::Field* field) : m_field(field) {} + + System::String^ GetName() { + return gcnew System::String(m_field->get_name()); + } + + TypeDefinition^ GetDeclaringType() { + auto t = m_field->get_declaring_type(); + + if (t == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(t); + } + + TypeDefinition^ GetType() { + auto t = m_field->get_type(); + + if (t == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(t); + } + + uint32_t GetOffsetFromBase() { + return m_field->get_offset_from_base(); + } + + uint32_t GetOffsetFromFieldPtr() { + return m_field->get_offset_from_fieldptr(); + } + + uint32_t GetFlags() { + return m_field->get_flags(); + } + + bool IsStatic() { + return m_field->is_static(); + } + + bool IsLiteral() { + return m_field->is_literal(); + } + + uintptr_t GetInitDataPtr() { + return (uintptr_t)m_field->get_init_data(); + } + + uintptr_t GetDataRaw(uintptr_t obj, bool isValueType) { + return (uintptr_t)m_field->get_data_raw((void*)obj, isValueType); + } + + // I have no idea if this will work correctly + template + T GetData(uintptr_t obj, bool isValueType) { + return m_field->get_data((void*)obj, isValueType); + } + +private: + const reframework::API::Field* m_field; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/InvokeRet.hpp b/csharp-api/REFrameworkNET/InvokeRet.hpp new file mode 100644 index 000000000..8bb83b6e5 --- /dev/null +++ b/csharp-api/REFrameworkNET/InvokeRet.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#pragma managed + +namespace REFrameworkNET { +public ref struct InvokeRet { + InvokeRet(const reframework::InvokeRet& ret) { + Bytes = gcnew array(ret.bytes.size()); + for (size_t i = 0; i < ret.bytes.size(); i++) { + Bytes[i] = ret.bytes[i]; + } + Byte = ret.byte; + Word = ret.word; + DWord = ret.dword; + F = ret.f; + QWord = ret.qword; + D = ret.d; + Ptr = gcnew System::UIntPtr(ret.ptr); + ExceptionThrown = ret.exception_thrown; + } + + // TODO: improve this? Does .NET have unions? + property array^ Bytes; + property uint8_t Byte; + property uint16_t Word; + property uint32_t DWord; + property float F; + property uint64_t QWord; + property double D; + property System::Object^ Ptr; + property bool ExceptionThrown; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp new file mode 100644 index 000000000..0905dcb5c --- /dev/null +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -0,0 +1,15 @@ +#include "TypeDefinition.hpp" + +#include "ManagedObject.hpp" + +namespace REFrameworkNET { + TypeDefinition^ ManagedObject::GetTypeDefinition() { + auto result = m_object->get_type_definition(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp new file mode 100644 index 000000000..d4745d808 --- /dev/null +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include + +#pragma managed + +namespace REFrameworkNET { +ref class TypeDefinition; + +public ref class ManagedObject { +public: + ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj) { + AddRef(); + } + ManagedObject(::REFrameworkManagedObjectHandle handle) : m_object(reinterpret_cast(handle)) { + AddRef(); + } + + ~ManagedObject() { + if (m_object != nullptr) { + Release(); + } + } + + void AddRef() { + m_object->add_ref(); + } + + void Release() { + m_object->release(); + } + + void* Ptr() { + return (void*)m_object; + } + + uintptr_t GetAddress() { + return (uintptr_t)m_object; + } + + TypeDefinition^ GetTypeDefinition(); + +private: + reframework::API::ManagedObject* m_object; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedSingleton.hpp b/csharp-api/REFrameworkNET/ManagedSingleton.hpp new file mode 100644 index 000000000..d218fb93f --- /dev/null +++ b/csharp-api/REFrameworkNET/ManagedSingleton.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +#pragma managed + +namespace REFrameworkNET { +ref class ManagedObject; +ref class TypeDefinition; +ref class TypeInfo; + +public ref struct ManagedSingleton { + ManagedSingleton(ManagedObject^ instance, TypeDefinition^ type, TypeInfo^ typeInfo) + { + Instance = instance; + Type = type; + TypeInfo = typeInfo; + } + + property ManagedObject^ Instance; + property TypeDefinition^ Type; + property TypeInfo^ TypeInfo; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp new file mode 100644 index 000000000..b7a56a612 --- /dev/null +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -0,0 +1,57 @@ +#include "ManagedObject.hpp" + +#include "Method.hpp" + +namespace REFrameworkNET { +REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { + // We need to convert the managed objects to 8 byte representations + std::vector args2{}; + args2.resize(args->Length); + + for (int i = 0; i < args->Length; ++i) { + //args2[i] = args[i]->ptr(); + const auto t = args[i]->GetType(); + + if (t == System::Byte::typeid) { + uint8_t v = System::Convert::ToByte(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::UInt16::typeid) { + uint16_t v = System::Convert::ToUInt16(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::UInt32::typeid) { + uint32_t v = System::Convert::ToUInt32(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::Single::typeid) { + float v = System::Convert::ToSingle(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::UInt64::typeid) { + uint64_t v = System::Convert::ToUInt64(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::Double::typeid) { + double v = System::Convert::ToDouble(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::IntPtr::typeid) { + args2[i] = (void*)(uint64_t)System::Convert::ToInt64(args[i]); + } else { + //args2[i] = args[i]->ptr(); + } + } + + void* obj_ptr = nullptr; + + if (obj != nullptr) { + const auto obj_t = obj->GetType(); + if (obj_t == System::IntPtr::typeid || obj_t == System::UIntPtr::typeid) { + obj_ptr = (void*)(uint64_t)System::Convert::ToInt64(obj); + } else if (obj_t == REFrameworkNET::ManagedObject::typeid) { + obj_ptr = safe_cast(obj)->Ptr(); + } else { + //obj_ptr = obj->ptr(); + } + } + + const auto native_result = m_method->invoke((reframework::API::ManagedObject*)obj_ptr, args2); + + return gcnew REFrameworkNET::InvokeRet(native_result); +} +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp new file mode 100644 index 000000000..5749eef36 --- /dev/null +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include + +#pragma managed + +#include "InvokeRet.hpp" +#include "MethodParameter.hpp" + +namespace REFrameworkNET { +public ref class Method { +public: + Method(reframework::API::Method* method) : m_method(method) {} + + REFrameworkNET::InvokeRet^ Invoke(System::Object^ obj, array^ args); + + /*Void* GetFunctionRaw() { + return m_method->get_function_raw(); + }*/ + + System::String^ GetName() { + return gcnew System::String(m_method->get_name()); + } + + TypeDefinition^ GetDeclaringType() { + auto result = m_method->get_declaring_type(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); + } + + TypeDefinition^ GetReturnType() { + auto result = m_method->get_return_type(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); + } + + uint32_t GetNumParams() { + return m_method->get_num_params(); + } + + System::Collections::Generic::List^ GetParameters() { + const auto params = m_method->get_params(); + + auto ret = gcnew System::Collections::Generic::List(); + + for (auto& p : params) { + ret->Add(gcnew REFrameworkNET::MethodParameter(p)); + } + + return ret; + } + + uint32_t GetIndex() { + return m_method->get_index(); + } + + int32_t GetVirtualIndex() { + return m_method->get_virtual_index(); + } + + bool IsStatic() { + return m_method->is_static(); + } + + uint16_t GetFlags() { + return m_method->get_flags(); + } + + uint16_t GetImplFlags() { + return m_method->get_impl_flags(); + } + + uint32_t GetInvokeID() { + return m_method->get_invoke_id(); + } + + // hmm... + /*UInt32 AddHook(pre_fn, post_fn, Boolean ignore_jmp) { + return m_method->add_hook(pre_fn, post_fn, ignore_jmp); + }*/ + + void RemoveHook(uint32_t hook_id) { + m_method->remove_hook(hook_id); + } + +private: + reframework::API::Method* m_method; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/MethodParameter.hpp b/csharp-api/REFrameworkNET/MethodParameter.hpp new file mode 100644 index 000000000..7b39ae5d8 --- /dev/null +++ b/csharp-api/REFrameworkNET/MethodParameter.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "TypeDefinition.hpp" + +namespace REFrameworkNET { +public ref struct MethodParameter { + MethodParameter(const REFrameworkMethodParameter& p) { + Name = gcnew System::String(p.name); + Type = gcnew REFrameworkNET::TypeDefinition(p.t); + } + + property System::String^ Name; + property REFrameworkNET::TypeDefinition^ Type; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Property.hpp b/csharp-api/REFrameworkNET/Property.hpp new file mode 100644 index 000000000..89a400d28 --- /dev/null +++ b/csharp-api/REFrameworkNET/Property.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#pragma managed + +namespace REFrameworkNET { +public ref class Property { +public: + Property(const reframework::API::Property* prop) : m_prop(prop) {} + +private: + const reframework::API::Property* m_prop; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TDB.cpp b/csharp-api/REFrameworkNET/TDB.cpp new file mode 100644 index 000000000..42f30d771 --- /dev/null +++ b/csharp-api/REFrameworkNET/TDB.cpp @@ -0,0 +1 @@ +#include "TDB.hpp" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp new file mode 100644 index 000000000..dbbec6127 --- /dev/null +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include + +#pragma managed + +#include +#include "TypeDefinition.hpp" +#include "Method.hpp" +#include "Field.hpp" +#include "Property.hpp" + +namespace REFrameworkNET { +public ref class TDB { +public: + TDB(reframework::API::TDB* tdb) : m_tdb(tdb) {} + + uintptr_t GetAddress() { + return (uintptr_t)m_tdb; + } + + uint32_t GetNumTypes() { + return m_tdb->get_num_types(); + } + + uint32_t GetNumMethods() { + return m_tdb->get_num_methods(); + } + + uint32_t GetNumFields() { + return m_tdb->get_num_fields(); + } + + uint32_t GetNumProperties() { + return m_tdb->get_num_properties(); + } + + uint32_t GetStringsSize() { + return m_tdb->get_strings_size(); + } + + uint32_t GetRawDataSize() { + return m_tdb->get_raw_data_size(); + } + + /*public SByte* GetStringDatabase() { + return m_tdb->get_string_database(); + } + + public Byte* GetRawDatabase() { + return m_tdb->get_raw_database(); + }*/ + + System::Span^ GetRawData() { + return gcnew System::Span(m_tdb->get_raw_database(), m_tdb->get_raw_data_size()); + } + + System::String^ GetString(uint32_t index) { + return gcnew System::String(m_tdb->get_string_database() + index); + } + + TypeDefinition^ GetType(uint32_t index) { + return gcnew TypeDefinition(m_tdb->get_type(index)); + } + + TypeDefinition^ FindType(System::String^ name) { + return gcnew TypeDefinition(m_tdb->find_type(msclr::interop::marshal_as(name))); + } + + TypeDefinition^ FindTypeByFqn(uint32_t fqn) { + return gcnew TypeDefinition(m_tdb->find_type_by_fqn(fqn)); + } + + Method^ GetMethod(uint32_t index) { + return gcnew Method(m_tdb->get_method(index)); + } + + Method^ FindMethod(System::String^ type_name, System::String^ name) { + return gcnew Method(m_tdb->find_method(msclr::interop::marshal_as(type_name), msclr::interop::marshal_as(name))); + } + + Field^ GetField(uint32_t index) { + return gcnew Field(m_tdb->get_field(index)); + } + + Field^ FindField(System::String^ type_name, System::String^ name) { + return gcnew Field(m_tdb->find_field(msclr::interop::marshal_as(type_name), msclr::interop::marshal_as(name))); + } + + Property^ GetProperty(uint32_t index) { + return gcnew Property(m_tdb->get_property(index)); + } + +private: + reframework::API::TDB* m_tdb; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp new file mode 100644 index 000000000..ffadb165a --- /dev/null +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -0,0 +1,111 @@ +#include "Method.hpp" +#include "Field.hpp" +#include "Property.hpp" +#include "ManagedObject.hpp" + +#include "TypeDefinition.hpp" + +namespace REFrameworkNET { + REFrameworkNET::Method^ TypeDefinition::FindMethod(System::String^ name) + { + auto result = m_type->find_method(msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + return gcnew REFrameworkNET::Method(result); + } + + REFrameworkNET::Field^ TypeDefinition::FindField(System::String^ name) + { + auto result = m_type->find_field(msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + return gcnew Field(result); + } + + REFrameworkNET::Property^ TypeDefinition::FindProperty(System::String^ name) + { + auto result = m_type->find_property(msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + return gcnew Property(result); + } + + System::Collections::Generic::List^ TypeDefinition::GetMethods() + { + auto methods = m_type->get_methods(); + auto result = gcnew System::Collections::Generic::List(); + + for (auto& method : methods) { + if (method == nullptr) { + continue; + } + + result->Add(gcnew Method(method)); + } + + return result; + } + + System::Collections::Generic::List^ TypeDefinition::GetFields() + { + auto fields = m_type->get_fields(); + auto result = gcnew System::Collections::Generic::List(); + + for (auto& field : fields) { + if (field == nullptr) { + continue; + } + + result->Add(gcnew Field(field)); + } + + return result; + } + + System::Collections::Generic::List^ TypeDefinition::GetProperties() + { + auto properties = m_type->get_properties(); + auto result = gcnew System::Collections::Generic::List(); + + for (auto& property : properties) { + if (property == nullptr) { + continue; + } + + result->Add(gcnew Property(property)); + } + + return result; + } + + ManagedObject^ TypeDefinition::CreateInstance(int32_t flags) + { + auto result = m_type->create_instance(flags); + + if (result == nullptr) { + return nullptr; + } + + return gcnew ManagedObject(result); + } + + ManagedObject^ TypeDefinition::GetRuntimeType() + { + auto result = m_type->get_runtime_type(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew ManagedObject(result); + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp new file mode 100644 index 000000000..93207ffdb --- /dev/null +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -0,0 +1,188 @@ +#pragma once + +#include + +#pragma managed + +#include + +namespace REFrameworkNET { +ref class ManagedObject; +ref class Method; +ref class Field; +ref class Property; + +public ref class TypeDefinition +{ +public: + TypeDefinition(reframework::API::TypeDefinition* td) : m_type(td) {} + TypeDefinition(::REFrameworkTypeDefinitionHandle handle) : m_type(reinterpret_cast(handle)) {} + + operator reframework::API::TypeDefinition*() { + return (reframework::API::TypeDefinition*)m_type; + } + + uint32_t GetIndex() + { + return m_type->get_index(); + } + + uint32_t GetSize() + { + return m_type->get_size(); + } + + uint32_t GetValuetypeSize() + { + return m_type->get_valuetype_size(); + } + + uint32_t GetFqn() + { + return m_type->get_fqn(); + } + + System::String^ GetName() + { + return gcnew System::String(m_type->get_name()); + } + + System::String^ GetNamespace() + { + return gcnew System::String(m_type->get_namespace()); + } + + System::String^ GetFullName() + { + return gcnew System::String(m_type->get_full_name().c_str()); + } + + bool HasFieldPtrOffset() + { + return m_type->has_fieldptr_offset(); + } + + int32_t GetFieldPtrOffset() + { + return m_type->get_fieldptr_offset(); + } + + uint32_t GetNumMethods() + { + return m_type->get_num_methods(); + } + + uint32_t GetNumFields() + { + return m_type->get_num_fields(); + } + + uint32_t GetNumProperties() + { + return m_type->get_num_properties(); + } + + bool IsDerivedFrom(System::String^ other) + { + return m_type->is_derived_from(msclr::interop::marshal_as(other).c_str()); + } + + bool IsDerivedFrom(TypeDefinition^ other) + { + return m_type->is_derived_from(other); + } + + bool IsValueType() + { + return m_type->is_valuetype(); + } + + bool IsEnum() + { + return m_type->is_enum(); + } + + bool IsByRef() + { + return m_type->is_by_ref(); + } + + bool IsPointer() + { + return m_type->is_pointer(); + } + + bool IsPrimitive() + { + return m_type->is_primitive(); + } + + uint32_t GetVmObjType() + { + return m_type->get_vm_obj_type(); + } + + REFrameworkNET::Method^ FindMethod(System::String^ name); + Field^ FindField(System::String^ name); + Property^ FindProperty(System::String^ name); + + System::Collections::Generic::List^ GetMethods(); + System::Collections::Generic::List^ GetFields(); + System::Collections::Generic::List^ GetProperties(); + + ManagedObject^ CreateInstance(int32_t flags); + + TypeDefinition^ GetParentType() + { + auto result = m_type->get_parent_type(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); + } + + TypeDefinition^ GetDeclaringType() + { + auto result = m_type->get_declaring_type(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); + } + + TypeDefinition^ GetUnderlyingType() + { + auto result = m_type->get_underlying_type(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); + } + + /*API.TypeInfo GetTypeInfo() + { + return m_type->get_type_info(); + }*/ + + ManagedObject^ GetRuntimeType(); + + /*Void* GetInstance() + { + return m_type->get_instance(); + } + + Void* CreateInstanceDeprecated() + { + return m_type->create_instance_deprecated(); + }*/ + +private: + reframework::API::TypeDefinition* m_type; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeInfo.hpp b/csharp-api/REFrameworkNET/TypeInfo.hpp new file mode 100644 index 000000000..165418661 --- /dev/null +++ b/csharp-api/REFrameworkNET/TypeInfo.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include + +#pragma managed + +namespace REFrameworkNET { +public ref class TypeInfo { +public: + TypeInfo(reframework::API::TypeInfo* typeInfo) : m_typeInfo(typeInfo) {} + TypeInfo(::REFrameworkTypeInfoHandle handle) : m_typeInfo(reinterpret_cast(handle)) {} + +private: + reframework::API::TypeInfo* m_typeInfo; +}; +}; \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 5caf1f06a..e92665083 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -90,41 +90,51 @@ private static string GetParameterInvocation(ParameterInfo[] parameters) } class REFrameworkPlugin { - public static void Main(REFrameworkNET.API api_) { + public static void Main(REFrameworkNET.API api) { Console.WriteLine("Testing REFrameworkAPI..."); // Convert api.Get() type to pass to GenerateWrapper - var currentDir = Directory.GetCurrentDirectory(); + /*var currentDir = Directory.GetCurrentDirectory(); var targetType = typeof(reframework.API.Method); var outputPath = Path.Combine(currentDir, "MethodWrapper.cs"); ApiWrapperGenerator.GenerateWrapper(targetType, outputPath); // Open in explorer - System.Diagnostics.Process.Start("explorer.exe", currentDir); + System.Diagnostics.Process.Start("explorer.exe", currentDir);*/ - var api = new APIWrapper(api_.Get()); var tdb = api.GetTDB(); Console.WriteLine(tdb.GetNumTypes().ToString() + " types"); - /*var typesSorted = new System.Collections.Generic.List(); + for (uint i = 0; i < 50; i++) { + var type = tdb.GetType(i); + Console.WriteLine(type.GetFullName()); - for (uint i = 0; i < tdb.GetNumTypes(); i++) { - var t = tdb.GetType(i); - if (t == null) { - continue; + var methods = type.GetMethods(); + + foreach (var method in methods) { + var returnT = method.GetReturnType(); + var returnTName = returnT != null ? returnT.GetFullName() : "null"; + + Console.WriteLine(" " + returnTName + " " + method.GetName()); } - typesSorted.Add(t.GetFullName()); + var fields = type.GetFields(); + + foreach (var field in fields) { + var t = field.GetType(); + string tName = t != null ? t.GetFullName() : "null"; + Console.WriteLine(" " + tName + " " + field.GetName() + " @ " + "0x" + field.GetOffsetFromBase().ToString("X")); + } } - typesSorted.Sort();*/ + Console.WriteLine("Done with types"); var singletons = api.GetManagedSingletons(); foreach (var singletonDesc in singletons) { - var singleton = new ManagedObjectWrapper(singletonDesc.Instance); + var singleton = singletonDesc.Instance; Console.WriteLine(singleton.GetTypeDefinition().GetFullName()); @@ -135,7 +145,7 @@ public static void Main(REFrameworkNET.API api_) { foreach (var method in methods) { string postfix = ""; foreach (var param in method.GetParameters()) { - postfix += new TypeDefinitionWrapper(param.Type).GetFullName() + " " + param.Name + ", "; + postfix += param.Type.GetFullName() + " " + param.Name + ", "; } Console.WriteLine(" " + method.GetName() + " " + postfix); @@ -144,11 +154,11 @@ public static void Main(REFrameworkNET.API api_) { var fields = td.GetFields(); foreach (var field in fields) { - Console.WriteLine(" " + field.get_name()); + Console.WriteLine(" " + field.GetName()); } } - var sceneManager = api.GetNativeSingleton("via.SceneManager"); + /*var sceneManager = api.GetNativeSingleton("via.SceneManager"); Console.WriteLine("sceneManager: " + sceneManager); var sceneManager_t = tdb.FindType("via.SceneManager"); Console.WriteLine("sceneManager_t: " + sceneManager_t); @@ -165,6 +175,6 @@ public static void Main(REFrameworkNET.API api_) { Console.WriteLine("set_TimeScale: " + set_TimeScale); set_TimeScale.Invoke(scene, new object[]{ 0.1f }); - } + }*/ } }; \ No newline at end of file From 2518a2b5223113607d24dcde061132dcfb00dca1 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 20:57:50 -0700 Subject: [PATCH 009/207] .NET: Various sanity checks --- csharp-api/REFrameworkNET/TDB.hpp | 60 +++++++++++++++++--- csharp-api/REFrameworkNET/TypeDefinition.cpp | 12 ++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 7 +-- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp index dbbec6127..2fa501663 100644 --- a/csharp-api/REFrameworkNET/TDB.hpp +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -56,19 +56,41 @@ public ref class TDB { } System::String^ GetString(uint32_t index) { + if (index >= m_tdb->get_strings_size()) { + return nullptr; + } + return gcnew System::String(m_tdb->get_string_database() + index); } TypeDefinition^ GetType(uint32_t index) { - return gcnew TypeDefinition(m_tdb->get_type(index)); + auto result = m_tdb->get_type(index); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); } TypeDefinition^ FindType(System::String^ name) { - return gcnew TypeDefinition(m_tdb->find_type(msclr::interop::marshal_as(name))); + auto result = m_tdb->find_type(msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); } TypeDefinition^ FindTypeByFqn(uint32_t fqn) { - return gcnew TypeDefinition(m_tdb->find_type_by_fqn(fqn)); + auto result = m_tdb->find_type_by_fqn(fqn); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeDefinition(result); } Method^ GetMethod(uint32_t index) { @@ -76,19 +98,43 @@ public ref class TDB { } Method^ FindMethod(System::String^ type_name, System::String^ name) { - return gcnew Method(m_tdb->find_method(msclr::interop::marshal_as(type_name), msclr::interop::marshal_as(name))); + auto result = m_tdb->find_method(msclr::interop::marshal_as(type_name), msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + return gcnew Method(result); } Field^ GetField(uint32_t index) { - return gcnew Field(m_tdb->get_field(index)); + auto result = m_tdb->get_field(index); + + if (result == nullptr) { + return nullptr; + } + + return gcnew Field(result); } Field^ FindField(System::String^ type_name, System::String^ name) { - return gcnew Field(m_tdb->find_field(msclr::interop::marshal_as(type_name), msclr::interop::marshal_as(name))); + auto result = m_tdb->find_field(msclr::interop::marshal_as(type_name), msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + return gcnew Field(result); } Property^ GetProperty(uint32_t index) { - return gcnew Property(m_tdb->get_property(index)); + auto result = m_tdb->get_property(index); + + if (result == nullptr) { + return nullptr; + } + + return gcnew Property(result); } private: diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index ffadb165a..b3630ce5a 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -1,3 +1,4 @@ +#include "TypeInfo.hpp" #include "Method.hpp" #include "Field.hpp" #include "Property.hpp" @@ -98,6 +99,17 @@ namespace REFrameworkNET { return gcnew ManagedObject(result); } + REFrameworkNET::TypeInfo^ TypeDefinition::GetTypeInfo() + { + auto result = m_type->get_type_info(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew REFrameworkNET::TypeInfo(result); + } + ManagedObject^ TypeDefinition::GetRuntimeType() { auto result = m_type->get_runtime_type(); diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 93207ffdb..d69d2dfb6 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -11,6 +11,7 @@ ref class ManagedObject; ref class Method; ref class Field; ref class Property; +ref class TypeInfo; public ref class TypeDefinition { @@ -165,11 +166,7 @@ public ref class TypeDefinition return gcnew TypeDefinition(result); } - /*API.TypeInfo GetTypeInfo() - { - return m_type->get_type_info(); - }*/ - + REFrameworkNET::TypeInfo^ GetTypeInfo(); ManagedObject^ GetRuntimeType(); /*Void* GetInstance() From 389a7008427a11e367a5ef55d31f7ab80f505136 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 17 Mar 2024 23:57:47 -0700 Subject: [PATCH 010/207] .NET: Add callbacks --- CMakeLists.txt | 1 + csharp-api/REFrameworkNET/API.cpp | 13 +- csharp-api/REFrameworkNET/API.hpp | 8 + csharp-api/REFrameworkNET/Callbacks.cpp | 43 +++ csharp-api/REFrameworkNET/Callbacks.hpp | 422 +++++++++++++++++++++++- csharp-api/test/Test/Test.cs | 22 ++ 6 files changed, 504 insertions(+), 5 deletions(-) create mode 100644 csharp-api/REFrameworkNET/Callbacks.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4e353b6d5..7a1df0277 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13113,6 +13113,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework list(APPEND csharp-api_SOURCES "csharp-api/Plugin.cpp" "csharp-api/REFrameworkNET/API.cpp" + "csharp-api/REFrameworkNET/Callbacks.cpp" "csharp-api/REFrameworkNET/ManagedObject.cpp" "csharp-api/REFrameworkNET/Method.cpp" "csharp-api/REFrameworkNET/PluginManager.cpp" diff --git a/csharp-api/REFrameworkNET/API.cpp b/csharp-api/REFrameworkNET/API.cpp index 505faa58f..fb0200d46 100644 --- a/csharp-api/REFrameworkNET/API.cpp +++ b/csharp-api/REFrameworkNET/API.cpp @@ -7,14 +7,19 @@ REFrameworkNET::API::API(const REFrameworkPluginInitializeParam* param) { - Console::WriteLine("REFrameworkNET.API Constructor called."); - s_api = reframework::API::initialize(param).get(); + Init_Internal(param); } REFrameworkNET::API::API(uintptr_t param) { - Console::WriteLine("REFrameworkNET.API Constructor called."); - s_api = reframework::API::initialize((const REFrameworkPluginInitializeParam*)param).get(); + Init_Internal(reinterpret_cast(param)); +} + +void REFrameworkNET::API::Init_Internal(const REFrameworkPluginInitializeParam* param) +{ + Console::WriteLine("REFrameworkNET.API Init_Internal called."); + s_api = reframework::API::initialize(param).get(); + Callbacks::Impl::Setup(this); } REFrameworkNET::API::~API() diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index 39e362675..d5fd72a82 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -11,6 +11,8 @@ #include "TDB.hpp" #include "ManagedSingleton.hpp" +#include "Callbacks.hpp" + namespace reframework { class API; } @@ -58,7 +60,13 @@ public ref class API return result; } + reframework::API* GetNativeImplementation() { + return s_api; + } + protected: + void Init_Internal(const REFrameworkPluginInitializeParam* param); + static reframework::API* s_api; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Callbacks.cpp b/csharp-api/REFrameworkNET/Callbacks.cpp new file mode 100644 index 000000000..4bcf17a62 --- /dev/null +++ b/csharp-api/REFrameworkNET/Callbacks.cpp @@ -0,0 +1,43 @@ +#include "API.hpp" + +#include "Callbacks.hpp" + +using namespace System; + +namespace REFrameworkNET { +namespace Callbacks { +void Impl::Setup(REFrameworkNET::API^ api) { + Console::WriteLine("REFrameworkNET.Callbacks SetupCallbacks called."); + + reframework::API* nativeApi = api->GetNativeImplementation(); + System::Reflection::Assembly^ assembly = System::Reflection::Assembly::GetExecutingAssembly(); + array^ types = assembly->GetTypes(); + + // Look for REFrameworkNET.Callbacks.* classes + for each (Type^ type in types) { + if (type->Namespace != "REFrameworkNET.Callbacks") { + continue; + } + + if (!type->IsClass) { + continue; + } + + if (type->GetField("FUNCTION_PRE_CALLBACK_ADDRESS") == nullptr) { + continue; + } + + if (type->GetField("FUNCTION_POST_CALLBACK_ADDRESS") == nullptr) { + continue; + } + + auto eventName = type->Name; + auto eventNameStr = msclr::interop::marshal_as(eventName); + auto eventHandlerPre = (uintptr_t)type->GetField("FUNCTION_PRE_CALLBACK_ADDRESS")->GetValue(nullptr); + auto eventHandlerPost = (uintptr_t)type->GetField("FUNCTION_POST_CALLBACK_ADDRESS")->GetValue(nullptr); + nativeApi->param()->functions->on_pre_application_entry(eventNameStr.c_str(), (REFOnPreApplicationEntryCb)eventHandlerPre); + nativeApi->param()->functions->on_post_application_entry(eventNameStr.c_str(), (REFOnPostApplicationEntryCb)eventHandlerPost); + } +} +} +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp index 6c709f80f..5845c816b 100644 --- a/csharp-api/REFrameworkNET/Callbacks.hpp +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -3,8 +3,428 @@ #include "API.hpp" +using namespace System::Collections::Generic; + namespace REFrameworkNET { -public ref class Callbacks { +ref class API; + +#define GENERATE_POCKET_CLASS(EVENT_NAME) \ +static void EVENT_NAME##PreHandler_Internal(); \ +static void EVENT_NAME##PostHandler_Internal(); \ +public ref class EVENT_NAME { \ +public: \ + delegate void Delegate(); \ + static event Delegate^ Pre; \ + static event Delegate^ Post; \ + static void TriggerPre() { \ + Pre(); \ + } \ + static void TriggerPost() { \ + Post(); \ + } \ + static System::Reflection::MethodInfo^ TriggerPreMethod = nullptr;\ + static System::Reflection::MethodInfo^ TriggerPostMethod = nullptr;\ + static uintptr_t FUNCTION_PRE_CALLBACK_ADDRESS = (uintptr_t)&EVENT_NAME##PreHandler_Internal; \ + static uintptr_t FUNCTION_POST_CALLBACK_ADDRESS = (uintptr_t)&EVENT_NAME##PostHandler_Internal; \ +}; \ +void EVENT_NAME##PreHandler_Internal() { \ + if (Callbacks::##EVENT_NAME::TriggerPreMethod == nullptr) { \ + auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); \ + auto type = self->GetType("REFrameworkNET.Callbacks." #EVENT_NAME); \ + Callbacks::##EVENT_NAME::TriggerPreMethod = type->GetMethod("TriggerPre", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); \ + } \ + Callbacks::##EVENT_NAME::TriggerPreMethod->Invoke(nullptr, nullptr); \ +} \ +void EVENT_NAME##PostHandler_Internal() { \ + if (Callbacks::##EVENT_NAME::TriggerPostMethod == nullptr) { \ + auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); \ + auto type = self->GetType("REFrameworkNET.Callbacks." #EVENT_NAME); \ + Callbacks::##EVENT_NAME::TriggerPostMethod = type->GetMethod("TriggerPost", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); \ + } \ + Callbacks::##EVENT_NAME::TriggerPostMethod->Invoke(nullptr, nullptr); \ +} + +namespace Callbacks { + GENERATE_POCKET_CLASS(Initialize) + GENERATE_POCKET_CLASS(InitializeLog) + GENERATE_POCKET_CLASS(InitializeGameCore) + GENERATE_POCKET_CLASS(InitializeStorage) + GENERATE_POCKET_CLASS(InitializeResourceManager) + GENERATE_POCKET_CLASS(InitializeScene) + GENERATE_POCKET_CLASS(InitializeRemoteHost) + GENERATE_POCKET_CLASS(InitializeVM) + GENERATE_POCKET_CLASS(InitializeSystemService) + GENERATE_POCKET_CLASS(InitializeHardwareService) + GENERATE_POCKET_CLASS(InitializePushNotificationService) + GENERATE_POCKET_CLASS(InitializeDialog) + GENERATE_POCKET_CLASS(InitializeShareService) + GENERATE_POCKET_CLASS(InitializeUserService) + GENERATE_POCKET_CLASS(InitializeUDS) + GENERATE_POCKET_CLASS(InitializeModalDialogService) + GENERATE_POCKET_CLASS(InitializeGlobalUserData) + GENERATE_POCKET_CLASS(InitializeSteam) + GENERATE_POCKET_CLASS(InitializeWeGame) + GENERATE_POCKET_CLASS(InitializeXCloud) + GENERATE_POCKET_CLASS(InitializeRebe) + GENERATE_POCKET_CLASS(InitializeBcat) + GENERATE_POCKET_CLASS(InitializeEffectMemorySettings) + GENERATE_POCKET_CLASS(InitializeRenderer) + GENERATE_POCKET_CLASS(InitializeVR) + GENERATE_POCKET_CLASS(InitializeSpeedTree) + GENERATE_POCKET_CLASS(InitializeHID) + GENERATE_POCKET_CLASS(InitializeEffect) + GENERATE_POCKET_CLASS(InitializeGeometry) + GENERATE_POCKET_CLASS(InitializeLandscape) + GENERATE_POCKET_CLASS(InitializeHoudini) + GENERATE_POCKET_CLASS(InitializeSound) + GENERATE_POCKET_CLASS(InitializeWwiselib) + GENERATE_POCKET_CLASS(InitializeSimpleWwise) + GENERATE_POCKET_CLASS(InitializeWwise) + GENERATE_POCKET_CLASS(InitializeAudioRender) + GENERATE_POCKET_CLASS(InitializeGUI) + GENERATE_POCKET_CLASS(InitializeSpine) + GENERATE_POCKET_CLASS(InitializeMotion) + GENERATE_POCKET_CLASS(InitializeBehaviorTree) + GENERATE_POCKET_CLASS(InitializeAutoPlay) + GENERATE_POCKET_CLASS(InitializeScenario) + GENERATE_POCKET_CLASS(InitializeOctree) + GENERATE_POCKET_CLASS(InitializeAreaMap) + GENERATE_POCKET_CLASS(InitializeFSM) + GENERATE_POCKET_CLASS(InitializeNavigation) + GENERATE_POCKET_CLASS(InitializePointGraph) + GENERATE_POCKET_CLASS(InitializeFluidFlock) + GENERATE_POCKET_CLASS(InitializeTimeline) + GENERATE_POCKET_CLASS(InitializePhysics) + GENERATE_POCKET_CLASS(InitializeDynamics) + GENERATE_POCKET_CLASS(InitializeHavok) + GENERATE_POCKET_CLASS(InitializeBake) + GENERATE_POCKET_CLASS(InitializeNetwork) + GENERATE_POCKET_CLASS(InitializePuppet) + GENERATE_POCKET_CLASS(InitializeVoiceChat) + GENERATE_POCKET_CLASS(InitializeVivoxlib) + GENERATE_POCKET_CLASS(InitializeStore) + GENERATE_POCKET_CLASS(InitializeBrowser) + GENERATE_POCKET_CLASS(InitializeDevelopSystem) + GENERATE_POCKET_CLASS(InitializeBehavior) + GENERATE_POCKET_CLASS(InitializeMovie) + GENERATE_POCKET_CLASS(InitializeMame) + GENERATE_POCKET_CLASS(InitializeSkuService) + GENERATE_POCKET_CLASS(InitializeTelemetry) + GENERATE_POCKET_CLASS(InitializeHansoft) + GENERATE_POCKET_CLASS(InitializeNNFC) + GENERATE_POCKET_CLASS(InitializeMixer) + GENERATE_POCKET_CLASS(InitializeThreadPool) + GENERATE_POCKET_CLASS(Setup) + GENERATE_POCKET_CLASS(SetupJobScheduler) + GENERATE_POCKET_CLASS(SetupResourceManager) + GENERATE_POCKET_CLASS(SetupStorage) + GENERATE_POCKET_CLASS(SetupGlobalUserData) + GENERATE_POCKET_CLASS(SetupScene) + GENERATE_POCKET_CLASS(SetupDevelopSystem) + GENERATE_POCKET_CLASS(SetupUserService) + GENERATE_POCKET_CLASS(SetupSystemService) + GENERATE_POCKET_CLASS(SetupHardwareService) + GENERATE_POCKET_CLASS(SetupPushNotificationService) + GENERATE_POCKET_CLASS(SetupShareService) + GENERATE_POCKET_CLASS(SetupModalDialogService) + GENERATE_POCKET_CLASS(SetupVM) + GENERATE_POCKET_CLASS(SetupHID) + GENERATE_POCKET_CLASS(SetupRenderer) + GENERATE_POCKET_CLASS(SetupEffect) + GENERATE_POCKET_CLASS(SetupGeometry) + GENERATE_POCKET_CLASS(SetupLandscape) + GENERATE_POCKET_CLASS(SetupHoudini) + GENERATE_POCKET_CLASS(SetupSound) + GENERATE_POCKET_CLASS(SetupWwiselib) + GENERATE_POCKET_CLASS(SetupSimpleWwise) + GENERATE_POCKET_CLASS(SetupWwise) + GENERATE_POCKET_CLASS(SetupAudioRender) + GENERATE_POCKET_CLASS(SetupMotion) + GENERATE_POCKET_CLASS(SetupNavigation) + GENERATE_POCKET_CLASS(SetupPointGraph) + GENERATE_POCKET_CLASS(SetupPhysics) + GENERATE_POCKET_CLASS(SetupDynamics) + GENERATE_POCKET_CLASS(SetupHavok) + GENERATE_POCKET_CLASS(SetupMovie) + GENERATE_POCKET_CLASS(SetupMame) + GENERATE_POCKET_CLASS(SetupNetwork) + GENERATE_POCKET_CLASS(SetupPuppet) + GENERATE_POCKET_CLASS(SetupStore) + GENERATE_POCKET_CLASS(SetupBrowser) + GENERATE_POCKET_CLASS(SetupVoiceChat) + GENERATE_POCKET_CLASS(SetupVivoxlib) + GENERATE_POCKET_CLASS(SetupSkuService) + GENERATE_POCKET_CLASS(SetupTelemetry) + GENERATE_POCKET_CLASS(SetupHansoft) + GENERATE_POCKET_CLASS(StartApp) + GENERATE_POCKET_CLASS(SetupOctree) + GENERATE_POCKET_CLASS(SetupAreaMap) + GENERATE_POCKET_CLASS(SetupBehaviorTree) + GENERATE_POCKET_CLASS(SetupFSM) + GENERATE_POCKET_CLASS(SetupGUI) + GENERATE_POCKET_CLASS(SetupSpine) + GENERATE_POCKET_CLASS(SetupSpeedTree) + GENERATE_POCKET_CLASS(SetupNNFC) + GENERATE_POCKET_CLASS(Start) + GENERATE_POCKET_CLASS(StartStorage) + GENERATE_POCKET_CLASS(StartResourceManager) + GENERATE_POCKET_CLASS(StartGlobalUserData) + GENERATE_POCKET_CLASS(StartPhysics) + GENERATE_POCKET_CLASS(StartDynamics) + GENERATE_POCKET_CLASS(StartGUI) + GENERATE_POCKET_CLASS(StartTimeline) + GENERATE_POCKET_CLASS(StartOctree) + GENERATE_POCKET_CLASS(StartAreaMap) + GENERATE_POCKET_CLASS(StartBehaviorTree) + GENERATE_POCKET_CLASS(StartFSM) + GENERATE_POCKET_CLASS(StartSound) + GENERATE_POCKET_CLASS(StartWwise) + GENERATE_POCKET_CLASS(StartAudioRender) + GENERATE_POCKET_CLASS(StartScene) + GENERATE_POCKET_CLASS(StartRebe) + GENERATE_POCKET_CLASS(StartNetwork) + GENERATE_POCKET_CLASS(Update) + GENERATE_POCKET_CLASS(UpdateDialog) + GENERATE_POCKET_CLASS(UpdateRemoteHost) + GENERATE_POCKET_CLASS(UpdateStorage) + GENERATE_POCKET_CLASS(UpdateScene) + GENERATE_POCKET_CLASS(UpdateDevelopSystem) + GENERATE_POCKET_CLASS(UpdateWidget) + GENERATE_POCKET_CLASS(UpdateAutoPlay) + GENERATE_POCKET_CLASS(UpdateScenario) + GENERATE_POCKET_CLASS(UpdateCapture) + GENERATE_POCKET_CLASS(BeginFrameRendering) + GENERATE_POCKET_CLASS(UpdateVR) + GENERATE_POCKET_CLASS(UpdateHID) + GENERATE_POCKET_CLASS(UpdateMotionFrame) + GENERATE_POCKET_CLASS(BeginDynamics) + GENERATE_POCKET_CLASS(PreupdateGUI) + GENERATE_POCKET_CLASS(BeginHavok) + GENERATE_POCKET_CLASS(UpdateAIMap) + GENERATE_POCKET_CLASS(CreatePreupdateGroupFSM) + GENERATE_POCKET_CLASS(CreatePreupdateGroupBehaviorTree) + GENERATE_POCKET_CLASS(UpdateGlobalUserData) + GENERATE_POCKET_CLASS(UpdateUDS) + GENERATE_POCKET_CLASS(UpdateUserService) + GENERATE_POCKET_CLASS(UpdateSystemService) + GENERATE_POCKET_CLASS(UpdateHardwareService) + GENERATE_POCKET_CLASS(UpdatePushNotificationService) + GENERATE_POCKET_CLASS(UpdateShareService) + GENERATE_POCKET_CLASS(UpdateSteam) + GENERATE_POCKET_CLASS(UpdateWeGame) + GENERATE_POCKET_CLASS(UpdateBcat) + GENERATE_POCKET_CLASS(UpdateXCloud) + GENERATE_POCKET_CLASS(UpdateRebe) + GENERATE_POCKET_CLASS(UpdateNNFC) + GENERATE_POCKET_CLASS(BeginPhysics) + GENERATE_POCKET_CLASS(BeginUpdatePrimitive) + GENERATE_POCKET_CLASS(BeginUpdatePrimitiveGUI) + GENERATE_POCKET_CLASS(BeginUpdateSpineDraw) + GENERATE_POCKET_CLASS(UpdatePuppet) + GENERATE_POCKET_CLASS(UpdateGUI) + GENERATE_POCKET_CLASS(PreupdateBehavior) + GENERATE_POCKET_CLASS(PreupdateBehaviorTree) + GENERATE_POCKET_CLASS(PreupdateFSM) + GENERATE_POCKET_CLASS(PreupdateTimeline) + GENERATE_POCKET_CLASS(UpdateBehavior) + GENERATE_POCKET_CLASS(CreateUpdateGroupBehaviorTree) + GENERATE_POCKET_CLASS(CreateNavigationChain) + GENERATE_POCKET_CLASS(CreateUpdateGroupFSM) + GENERATE_POCKET_CLASS(UpdateTimeline) + GENERATE_POCKET_CLASS(PreUpdateAreaMap) + GENERATE_POCKET_CLASS(UpdateOctree) + GENERATE_POCKET_CLASS(UpdateAreaMap) + GENERATE_POCKET_CLASS(UpdateBehaviorTree) + GENERATE_POCKET_CLASS(UpdateTimelineFsm2) + GENERATE_POCKET_CLASS(UpdateNavigationPrev) + GENERATE_POCKET_CLASS(UpdateFSM) + GENERATE_POCKET_CLASS(UpdateMotion) + GENERATE_POCKET_CLASS(UpdateSpine) + GENERATE_POCKET_CLASS(EffectCollisionLimit) + GENERATE_POCKET_CLASS(UpdatePhysicsAfterUpdatePhase) + GENERATE_POCKET_CLASS(UpdateGeometry) + GENERATE_POCKET_CLASS(UpdateLandscape) + GENERATE_POCKET_CLASS(UpdateHoudini) + GENERATE_POCKET_CLASS(UpdatePhysicsCharacterController) + GENERATE_POCKET_CLASS(BeginUpdateHavok2) + GENERATE_POCKET_CLASS(UpdateDynamics) + GENERATE_POCKET_CLASS(UpdateNavigation) + GENERATE_POCKET_CLASS(UpdatePointGraph) + GENERATE_POCKET_CLASS(UpdateFluidFlock) + GENERATE_POCKET_CLASS(UpdateConstraintsBegin) + GENERATE_POCKET_CLASS(LateUpdateBehavior) + GENERATE_POCKET_CLASS(EditUpdateBehavior) + GENERATE_POCKET_CLASS(LateUpdateSpine) + GENERATE_POCKET_CLASS(BeginUpdateHavok) + GENERATE_POCKET_CLASS(BeginUpdateEffect) + GENERATE_POCKET_CLASS(UpdateConstraintsEnd) + GENERATE_POCKET_CLASS(UpdatePhysicsAfterLateUpdatePhase) + GENERATE_POCKET_CLASS(PrerenderGUI) + GENERATE_POCKET_CLASS(PrepareRendering) + GENERATE_POCKET_CLASS(UpdateSound) + GENERATE_POCKET_CLASS(UpdateWwiselib) + GENERATE_POCKET_CLASS(UpdateSimpleWwise) + GENERATE_POCKET_CLASS(UpdateWwise) + GENERATE_POCKET_CLASS(UpdateAudioRender) + GENERATE_POCKET_CLASS(CreateSelectorGroupFSM) + GENERATE_POCKET_CLASS(UpdateNetwork) + GENERATE_POCKET_CLASS(UpdateHavok) + GENERATE_POCKET_CLASS(EndUpdateHavok) + GENERATE_POCKET_CLASS(UpdateFSMSelector) + GENERATE_POCKET_CLASS(UpdateBehaviorTreeSelector) + GENERATE_POCKET_CLASS(BeforeLockSceneRendering) + GENERATE_POCKET_CLASS(EndUpdateHavok2) + GENERATE_POCKET_CLASS(UpdateJointExpression) + GENERATE_POCKET_CLASS(UpdateBehaviorTreeSelectorLegacy) + GENERATE_POCKET_CLASS(UpdateEffect) + GENERATE_POCKET_CLASS(EndUpdateEffect) + GENERATE_POCKET_CLASS(UpdateWidgetDynamics) + GENERATE_POCKET_CLASS(LockScene) + GENERATE_POCKET_CLASS(WaitRendering) + GENERATE_POCKET_CLASS(EndDynamics) + GENERATE_POCKET_CLASS(EndPhysics) + GENERATE_POCKET_CLASS(BeginRendering) + GENERATE_POCKET_CLASS(UpdateSpeedTree) + GENERATE_POCKET_CLASS(RenderDynamics) + GENERATE_POCKET_CLASS(RenderGUI) + GENERATE_POCKET_CLASS(RenderGeometry) + GENERATE_POCKET_CLASS(RenderLandscape) + GENERATE_POCKET_CLASS(RenderHoudini) + GENERATE_POCKET_CLASS(UpdatePrimitiveGUI) + GENERATE_POCKET_CLASS(UpdatePrimitive) + GENERATE_POCKET_CLASS(UpdateSpineDraw) + GENERATE_POCKET_CLASS(EndUpdatePrimitive) + GENERATE_POCKET_CLASS(EndUpdatePrimitiveGUI) + GENERATE_POCKET_CLASS(EndUpdateSpineDraw) + GENERATE_POCKET_CLASS(GUIPostPrimitiveRender) + GENERATE_POCKET_CLASS(ShapeRenderer) + GENERATE_POCKET_CLASS(UpdateMovie) + GENERATE_POCKET_CLASS(UpdateMame) + GENERATE_POCKET_CLASS(UpdateTelemetry) + GENERATE_POCKET_CLASS(UpdateHansoft) + GENERATE_POCKET_CLASS(DrawWidget) + GENERATE_POCKET_CLASS(DevelopRenderer) + GENERATE_POCKET_CLASS(EndRendering) + GENERATE_POCKET_CLASS(UpdateStore) + GENERATE_POCKET_CLASS(UpdateBrowser) + GENERATE_POCKET_CLASS(UpdateVoiceChat) + GENERATE_POCKET_CLASS(UpdateVivoxlib) + GENERATE_POCKET_CLASS(UnlockScene) + GENERATE_POCKET_CLASS(UpdateVM) + GENERATE_POCKET_CLASS(StepVisualDebugger) + GENERATE_POCKET_CLASS(WaitForVblank) + GENERATE_POCKET_CLASS(Terminate) + GENERATE_POCKET_CLASS(TerminateScene) + GENERATE_POCKET_CLASS(TerminateRemoteHost) + GENERATE_POCKET_CLASS(TerminateHansoft) + GENERATE_POCKET_CLASS(TerminateTelemetry) + GENERATE_POCKET_CLASS(TerminateMame) + GENERATE_POCKET_CLASS(TerminateMovie) + GENERATE_POCKET_CLASS(TerminateSound) + GENERATE_POCKET_CLASS(TerminateSimpleWwise) + GENERATE_POCKET_CLASS(TerminateWwise) + GENERATE_POCKET_CLASS(TerminateWwiselib) + GENERATE_POCKET_CLASS(TerminateAudioRender) + GENERATE_POCKET_CLASS(TerminateVoiceChat) + GENERATE_POCKET_CLASS(TerminateVivoxlib) + GENERATE_POCKET_CLASS(TerminatePuppet) + GENERATE_POCKET_CLASS(TerminateNetwork) + GENERATE_POCKET_CLASS(TerminateStore) + GENERATE_POCKET_CLASS(TerminateBrowser) + GENERATE_POCKET_CLASS(TerminateSpine) + GENERATE_POCKET_CLASS(TerminateGUI) + GENERATE_POCKET_CLASS(TerminateAreaMap) + GENERATE_POCKET_CLASS(TerminateOctree) + GENERATE_POCKET_CLASS(TerminateFluidFlock) + GENERATE_POCKET_CLASS(TerminateBehaviorTree) + GENERATE_POCKET_CLASS(TerminateFSM) + GENERATE_POCKET_CLASS(TerminateNavigation) + GENERATE_POCKET_CLASS(TerminatePointGraph) + GENERATE_POCKET_CLASS(TerminateEffect) + GENERATE_POCKET_CLASS(TerminateGeometry) + GENERATE_POCKET_CLASS(TerminateLandscape) + GENERATE_POCKET_CLASS(TerminateHoudini) + GENERATE_POCKET_CLASS(TerminateRenderer) + GENERATE_POCKET_CLASS(TerminateHID) + GENERATE_POCKET_CLASS(TerminateDynamics) + GENERATE_POCKET_CLASS(TerminatePhysics) + GENERATE_POCKET_CLASS(TerminateResourceManager) + GENERATE_POCKET_CLASS(TerminateHavok) + GENERATE_POCKET_CLASS(TerminateModalDialogService) + GENERATE_POCKET_CLASS(TerminateShareService) + GENERATE_POCKET_CLASS(TerminateGlobalUserData) + GENERATE_POCKET_CLASS(TerminateStorage) + GENERATE_POCKET_CLASS(TerminateVM) + GENERATE_POCKET_CLASS(TerminateJobScheduler) + GENERATE_POCKET_CLASS(Finalize) + GENERATE_POCKET_CLASS(FinalizeThreadPool) + GENERATE_POCKET_CLASS(FinalizeHansoft) + GENERATE_POCKET_CLASS(FinalizeTelemetry) + GENERATE_POCKET_CLASS(FinalizeMame) + GENERATE_POCKET_CLASS(FinalizeMovie) + GENERATE_POCKET_CLASS(FinalizeBehavior) + GENERATE_POCKET_CLASS(FinalizeDevelopSystem) + GENERATE_POCKET_CLASS(FinalizeTimeline) + GENERATE_POCKET_CLASS(FinalizePuppet) + GENERATE_POCKET_CLASS(FinalizeNetwork) + GENERATE_POCKET_CLASS(FinalizeStore) + GENERATE_POCKET_CLASS(FinalizeBrowser) + GENERATE_POCKET_CLASS(finalizeAutoPlay) + GENERATE_POCKET_CLASS(finalizeScenario) + GENERATE_POCKET_CLASS(FinalizeBehaviorTree) + GENERATE_POCKET_CLASS(FinalizeFSM) + GENERATE_POCKET_CLASS(FinalizeNavigation) + GENERATE_POCKET_CLASS(FinalizePointGraph) + GENERATE_POCKET_CLASS(FinalizeAreaMap) + GENERATE_POCKET_CLASS(FinalizeOctree) + GENERATE_POCKET_CLASS(FinalizeFluidFlock) + GENERATE_POCKET_CLASS(FinalizeMotion) + GENERATE_POCKET_CLASS(FinalizeDynamics) + GENERATE_POCKET_CLASS(FinalizePhysics) + GENERATE_POCKET_CLASS(FinalizeHavok) + GENERATE_POCKET_CLASS(FinalizeBake) + GENERATE_POCKET_CLASS(FinalizeSpine) + GENERATE_POCKET_CLASS(FinalizeGUI) + GENERATE_POCKET_CLASS(FinalizeSound) + GENERATE_POCKET_CLASS(FinalizeWwiselib) + GENERATE_POCKET_CLASS(FinalizeSimpleWwise) + GENERATE_POCKET_CLASS(FinalizeWwise) + GENERATE_POCKET_CLASS(FinalizeAudioRender) + GENERATE_POCKET_CLASS(FinalizeEffect) + GENERATE_POCKET_CLASS(FinalizeGeometry) + GENERATE_POCKET_CLASS(FinalizeSpeedTree) + GENERATE_POCKET_CLASS(FinalizeLandscape) + GENERATE_POCKET_CLASS(FinalizeHoudini) + GENERATE_POCKET_CLASS(FinalizeRenderer) + GENERATE_POCKET_CLASS(FinalizeHID) + GENERATE_POCKET_CLASS(FinalizeVR) + GENERATE_POCKET_CLASS(FinalizeBcat) + GENERATE_POCKET_CLASS(FinalizeRebe) + GENERATE_POCKET_CLASS(FinalizeXCloud) + GENERATE_POCKET_CLASS(FinalizeSteam) + GENERATE_POCKET_CLASS(FinalizeWeGame) + GENERATE_POCKET_CLASS(FinalizeNNFC) + GENERATE_POCKET_CLASS(FinalizeGlobalUserData) + GENERATE_POCKET_CLASS(FinalizeModalDialogService) + GENERATE_POCKET_CLASS(FinalizeSkuService) + GENERATE_POCKET_CLASS(FinalizeUDS) + GENERATE_POCKET_CLASS(FinalizeUserService) + GENERATE_POCKET_CLASS(FinalizeShareService) + GENERATE_POCKET_CLASS(FinalizeSystemService) + GENERATE_POCKET_CLASS(FinalizeHardwareService) + GENERATE_POCKET_CLASS(FinalizePushNotificationService) + GENERATE_POCKET_CLASS(FinalizeScene) + GENERATE_POCKET_CLASS(FinalizeVM) + GENERATE_POCKET_CLASS(FinalizeResourceManager) + GENERATE_POCKET_CLASS(FinalizeRemoteHost) + GENERATE_POCKET_CLASS(FinalizeStorage) + GENERATE_POCKET_CLASS(FinalizeDialog) + GENERATE_POCKET_CLASS(FinalizeMixer) + GENERATE_POCKET_CLASS(FinalizeGameCore); + public ref class Impl { + public: + static void Setup(REFrameworkNET::API^ api); + }; }; } \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index e92665083..0488b8c79 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -90,9 +90,31 @@ private static string GetParameterInvocation(ParameterInfo[] parameters) } class REFrameworkPlugin { + // Measure time between pre and post + // get time + static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + public static void Main(REFrameworkNET.API api) { Console.WriteLine("Testing REFrameworkAPI..."); + REFrameworkNET.Callbacks.BeginRendering.Pre += () => { + Console.WriteLine("BeginRendering pre"); + sw.Start(); + }; + REFrameworkNET.Callbacks.BeginRendering.Post += () => { + Console.WriteLine("BeginRendering post"); + sw.Stop(); + Console.WriteLine("BeginRendering took " + sw.ElapsedMilliseconds + "ms"); + sw.Reset(); + }; + REFrameworkNET.Callbacks.EndRendering.Post += () => { + Console.WriteLine("EndRendering"); + }; + + REFrameworkNET.Callbacks.FinalizeRenderer.Pre += () => { + Console.WriteLine("Finalizing!!!!"); + }; + // Convert api.Get() type to pass to GenerateWrapper /*var currentDir = Directory.GetCurrentDirectory(); var targetType = typeof(reframework.API.Method); From 1f7bb4000fd7940c2a5991f08a0624ed333e6915 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 08:00:04 -0700 Subject: [PATCH 011/207] .NET: Continuing adding more bindings... --- CMakeLists.txt | 3 +- cmake.toml | 3 +- csharp-api/REFrameworkNET/API.hpp | 7 +++-- csharp-api/REFrameworkNET/ManagedObject.cpp | 29 ++++++++++++++++++++ csharp-api/REFrameworkNET/ManagedObject.hpp | 23 ++++++++++++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 14 ++++++++++ csharp-api/test/Test/Test.cs | 7 +++++ 7 files changed, 81 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a1df0277..6acde4d31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13141,7 +13141,6 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework target_compile_options(csharp-api PUBLIC "/EHa" "/MD" - "/clr:netcore" ) target_include_directories(csharp-api PUBLIC @@ -13156,6 +13155,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" + COMMON_LANGUAGE_RUNTIME + netcore DOTNET_TARGET_FRAMEWORK net8.0 DOTNET_TARGET_FRAMEWORK_VERSION diff --git a/cmake.toml b/cmake.toml index 9f46d5dc2..068953f32 100644 --- a/cmake.toml +++ b/cmake.toml @@ -384,7 +384,7 @@ type = "shared" include-directories = ["include/"] sources = ["csharp-api/**.cpp", "csharp-api/**.c"] compile-features = ["cxx_std_20"] -compile-options = ["/EHa", "/MD", "/clr:netcore"] +compile-options = ["/EHa", "/MD"] condition = "build-framework" link-libraries = [ ] @@ -402,5 +402,6 @@ add_custom_command( RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" +COMMON_LANGUAGE_RUNTIME = "netcore" DOTNET_TARGET_FRAMEWORK = "net8.0" DOTNET_TARGET_FRAMEWORK_VERSION = "v8.0" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index d5fd72a82..91dbd45db 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -27,11 +27,11 @@ public ref class API API(uintptr_t param); ~API(); - inline REFrameworkNET::TDB^ GetTDB() { + REFrameworkNET::TDB^ GetTDB() { return gcnew REFrameworkNET::TDB(s_api->tdb()); } - inline REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name) { + REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name) { auto result = s_api->get_managed_singleton(msclr::interop::marshal_as(name)); if (result == nullptr) { @@ -41,8 +41,9 @@ public ref class API return gcnew REFrameworkNET::ManagedObject(result); } - inline System::Collections::Generic::List^ GetManagedSingletons() { + System::Collections::Generic::List^ GetManagedSingletons() { auto singletons = s_api->get_managed_singletons(); + auto result = gcnew System::Collections::Generic::List(); for (auto& singleton : singletons) { diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 0905dcb5c..9d1e9d580 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -1,4 +1,7 @@ #include "TypeDefinition.hpp" +#include "TypeInfo.hpp" +#include "InvokeRet.hpp" +#include "Method.hpp" #include "ManagedObject.hpp" @@ -12,4 +15,30 @@ namespace REFrameworkNET { return gcnew TypeDefinition(result); } + + TypeInfo^ ManagedObject::GetTypeInfo() { + auto result = m_object->get_type_info(); + + if (result == nullptr) { + return nullptr; + } + + return gcnew TypeInfo(result); + } + + REFrameworkNET::InvokeRet^ ManagedObject::Invoke(System::String^ methodName, array^ args) { + // Get method + auto t = this->GetTypeDefinition(); + if (t == nullptr) { + return nullptr; + } + + auto m = t->GetMethod(methodName); + + if (m == nullptr) { + return nullptr; + } + + return m->Invoke(this, args); + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index d4745d808..27d7467de 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -6,6 +6,8 @@ namespace REFrameworkNET { ref class TypeDefinition; +ref class TypeInfo; +ref class InvokeRet; public ref class ManagedObject { public: @@ -38,7 +40,28 @@ public ref class ManagedObject { return (uintptr_t)m_object; } + static bool IsManagedObject(uintptr_t ptr) { + static auto fn = reframework::API::get()->param()->sdk->managed_object->is_managed_object; + return fn((void*)ptr); + } + TypeDefinition^ GetTypeDefinition(); + TypeInfo^ GetTypeInfo(); + + REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); + + // TODO methods: + /*public Void* GetReflectionProperties() { + return _original.get_reflection_properties(); + }*/ + + /*public ReflectionProperty GetReflectionPropertyDescriptor(basic_string_view> name) { + return _original.get_reflection_property_descriptor(name); + } + + public ReflectionMethod GetReflectionMethodDescriptor(basic_string_view> name) { + return _original.get_reflection_method_descriptor(name); + }*/ private: reframework::API::ManagedObject* m_object; diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index d69d2dfb6..72d24440f 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -127,6 +127,20 @@ public ref class TypeDefinition Field^ FindField(System::String^ name); Property^ FindProperty(System::String^ name); + // Get versions + // The find versions just line up with the Lua API, the Get versions look more like C# + REFrameworkNET::Method^ GetMethod(System::String^ name) { + return FindMethod(name); + } + + Field^ GetField(System::String^ name) { + return FindField(name); + } + + Property^ GetProperty(System::String^ name) { + return FindProperty(name); + } + System::Collections::Generic::List^ GetMethods(); System::Collections::Generic::List^ GetFields(); System::Collections::Generic::List^ GetProperties(); diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 0488b8c79..9c92524a7 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -115,6 +115,10 @@ public static void Main(REFrameworkNET.API api) { Console.WriteLine("Finalizing!!!!"); }; + REFrameworkNET.Callbacks.PrepareRendering.Post += () => { + Console.WriteLine("PrepareRendering"); + }; + // Convert api.Get() type to pass to GenerateWrapper /*var currentDir = Directory.GetCurrentDirectory(); var targetType = typeof(reframework.API.Method); @@ -159,6 +163,9 @@ public static void Main(REFrameworkNET.API api) { var singleton = singletonDesc.Instance; Console.WriteLine(singleton.GetTypeDefinition().GetFullName()); + var isManagedObject = REFrameworkNET.ManagedObject.IsManagedObject(singleton.GetAddress()); + + Console.WriteLine(" Is managed object: " + isManagedObject.ToString()); // Log all methods var td = singleton.GetTypeDefinition(); From 5062d6aba100fad8b673c02b7bc90ae174dfa83e Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 09:14:21 -0700 Subject: [PATCH 012/207] .NET: Add NativeObject --- CMakeLists.txt | 1 + csharp-api/REFrameworkNET/API.hpp | 45 ++++++++++++++++++++-- csharp-api/REFrameworkNET/NativeObject.cpp | 21 ++++++++++ csharp-api/REFrameworkNET/NativeObject.hpp | 45 ++++++++++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 csharp-api/REFrameworkNET/NativeObject.cpp create mode 100644 csharp-api/REFrameworkNET/NativeObject.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6acde4d31..d68eab245 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13116,6 +13116,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "csharp-api/REFrameworkNET/Callbacks.cpp" "csharp-api/REFrameworkNET/ManagedObject.cpp" "csharp-api/REFrameworkNET/Method.cpp" + "csharp-api/REFrameworkNET/NativeObject.cpp" "csharp-api/REFrameworkNET/PluginManager.cpp" "csharp-api/REFrameworkNET/TDB.cpp" "csharp-api/REFrameworkNET/TypeDefinition.cpp" diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index 91dbd45db..a38f8d003 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -10,6 +10,7 @@ #include "ManagedObject.hpp" #include "TDB.hpp" #include "ManagedSingleton.hpp" +#include "NativeObject.hpp" #include "Callbacks.hpp" @@ -27,11 +28,19 @@ public ref class API API(uintptr_t param); ~API(); - REFrameworkNET::TDB^ GetTDB() { + static REFrameworkNET::TDB^ GetTDB() { + if (s_api == nullptr) { + throw gcnew System::InvalidOperationException("API is not initialized."); + } + return gcnew REFrameworkNET::TDB(s_api->tdb()); } - REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name) { + static REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name) { + if (s_api == nullptr) { + throw gcnew System::InvalidOperationException("API is not initialized."); + } + auto result = s_api->get_managed_singleton(msclr::interop::marshal_as(name)); if (result == nullptr) { @@ -41,7 +50,11 @@ public ref class API return gcnew REFrameworkNET::ManagedObject(result); } - System::Collections::Generic::List^ GetManagedSingletons() { + static System::Collections::Generic::List^ GetManagedSingletons() { + if (s_api == nullptr) { + throw gcnew System::InvalidOperationException("API is not initialized."); + } + auto singletons = s_api->get_managed_singletons(); auto result = gcnew System::Collections::Generic::List(); @@ -61,7 +74,31 @@ public ref class API return result; } - reframework::API* GetNativeImplementation() { + static NativeObject^ GetNativeSingleton(System::String^ name) { + if (s_api == nullptr) { + throw gcnew System::InvalidOperationException("API is not initialized."); + } + + auto result = s_api->get_native_singleton(msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + auto t = GetTDB()->GetType(name); + + if (t == nullptr) { + return nullptr; + } + + return gcnew NativeObject(result, t); + } + + static reframework::API* GetNativeImplementation() { + if (s_api == nullptr) { + throw gcnew System::InvalidOperationException("API is not initialized."); + } + return s_api; } diff --git a/csharp-api/REFrameworkNET/NativeObject.cpp b/csharp-api/REFrameworkNET/NativeObject.cpp new file mode 100644 index 000000000..12216c605 --- /dev/null +++ b/csharp-api/REFrameworkNET/NativeObject.cpp @@ -0,0 +1,21 @@ +#include "InvokeRet.hpp" +#include "Method.hpp" +#include "NativeObject.hpp" + +namespace REFrameworkNET { +InvokeRet^ NativeObject::Invoke(System::String^ methodName, array^ args) { + auto t = this->GetTypeDefinition(); + + if (t == nullptr) { + return nullptr; + } + + auto m = t->GetMethod(methodName); + + if (m == nullptr) { + return nullptr; + } + + return m->Invoke(this, args); +} +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp new file mode 100644 index 000000000..f4f11123f --- /dev/null +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#include "TypeDefinition.hpp" + +namespace REFrameworkNET { +ref class InvokeRet; + +// Native objects are objects that are NOT managed objects +// However, they still have reflection information associated with them +// So this intends to be the "ManagedObject" class for native objects +// So we can easily interact with them in C# +public ref class NativeObject { +public: + NativeObject(uintptr_t obj, TypeDefinition^ t){ + m_object = (void*)obj; + m_type = t; + } + + NativeObject(void* obj, TypeDefinition^ t){ + m_object = obj; + m_type = t; + } + + TypeDefinition^ GetTypeDefinition() { + return m_type; + } + + void* Ptr() { + return m_object; + } + + uintptr_t GetAddress() { + return (uintptr_t)m_object; + } + + InvokeRet^ Invoke(System::String^ methodName, array^ args); + + +private: + void* m_object{}; + TypeDefinition^ m_type{}; +}; +} \ No newline at end of file From a72b3383b3a647cc7bbd206fe8096a7224cce3e0 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 09:14:38 -0700 Subject: [PATCH 013/207] .NET: Get->Find wrappers --- csharp-api/REFrameworkNET/TDB.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp index 2fa501663..379d806de 100644 --- a/csharp-api/REFrameworkNET/TDB.hpp +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -83,6 +83,10 @@ public ref class TDB { return gcnew TypeDefinition(result); } + TypeDefinition^ GetType(System::String^ name) { + return FindType(name); + } + TypeDefinition^ FindTypeByFqn(uint32_t fqn) { auto result = m_tdb->find_type_by_fqn(fqn); @@ -107,6 +111,10 @@ public ref class TDB { return gcnew Method(result); } + Method^ GetMethod(System::String^ type_name, System::String^ name) { + return FindMethod(type_name, name); + } + Field^ GetField(uint32_t index) { auto result = m_tdb->get_field(index); @@ -127,6 +135,10 @@ public ref class TDB { return gcnew Field(result); } + Field^ GetField(System::String^ type_name, System::String^ name) { + return FindField(type_name, name); + } + Property^ GetProperty(uint32_t index) { auto result = m_tdb->get_property(index); From 9b36c192881c16445274a92e6f87d8ea4ba4c296 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 09:15:21 -0700 Subject: [PATCH 014/207] .NET: Fix some method invocations not working correctly --- csharp-api/REFrameworkNET/Method.cpp | 50 +++++++++++++++++++++++----- csharp-api/test/Test/Test.cs | 25 +++++++------- 2 files changed, 54 insertions(+), 21 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index b7a56a612..e22acbc0f 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -1,4 +1,7 @@ +#include + #include "ManagedObject.hpp" +#include "NativeObject.hpp" #include "Method.hpp" @@ -8,11 +11,24 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array args2{}; args2.resize(args->Length); - for (int i = 0; i < args->Length; ++i) { + for (int i = 0; i < args->Length; ++i) try { + if (args[i] == nullptr) { + args2[i] = nullptr; + continue; + } + //args2[i] = args[i]->ptr(); const auto t = args[i]->GetType(); - if (t == System::Byte::typeid) { + if (t == REFrameworkNET::ManagedObject::typeid) { + args2[i] = safe_cast(args[i])->Ptr(); + } else if (t == System::Boolean::typeid) { + bool v = System::Convert::ToBoolean(args[i]); + args2[i] = (void*)(intptr_t)v; + } else if (t == System::Int32::typeid) { + int32_t v = System::Convert::ToInt32(args[i]); + args2[i] = (void*)(intptr_t)v; + } else if (t == System::Byte::typeid) { uint8_t v = System::Convert::ToByte(args[i]); args2[i] = (void*)(uint64_t)v; } else if (t == System::UInt16::typeid) { @@ -22,32 +38,48 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, arrayptr(); + args2[i] = nullptr; + System::Console::WriteLine("Unknown type passed to method invocation @ arg " + i); } + } catch (System::Exception^ e) { + System::Console::WriteLine("Error converting argument " + i + ": " + e->Message); } void* obj_ptr = nullptr; - if (obj != nullptr) { + if (obj != nullptr) try { const auto obj_t = obj->GetType(); - if (obj_t == System::IntPtr::typeid || obj_t == System::UIntPtr::typeid) { - obj_ptr = (void*)(uint64_t)System::Convert::ToInt64(obj); + + if (obj_t == System::IntPtr::typeid) { + obj_ptr = (void*)(intptr_t)safe_cast(obj).ToPointer(); + } else if (obj_t == System::UIntPtr::typeid) { + obj_ptr = (void*)(uintptr_t)safe_cast(obj).ToUInt64(); } else if (obj_t == REFrameworkNET::ManagedObject::typeid) { obj_ptr = safe_cast(obj)->Ptr(); + } else if (obj_t == REFrameworkNET::NativeObject::typeid) { + obj_ptr = safe_cast(obj)->Ptr(); } else { - //obj_ptr = obj->ptr(); + System::Console::WriteLine("Unknown type passed to method invocation @ obj"); } + } catch (System::Exception^ e) { + System::Console::WriteLine("Error converting object: " + e->Message); } const auto native_result = m_method->invoke((reframework::API::ManagedObject*)obj_ptr, args2); diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 9c92524a7..53572c7aa 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -98,25 +98,26 @@ public static void Main(REFrameworkNET.API api) { Console.WriteLine("Testing REFrameworkAPI..."); REFrameworkNET.Callbacks.BeginRendering.Pre += () => { - Console.WriteLine("BeginRendering pre"); sw.Start(); }; REFrameworkNET.Callbacks.BeginRendering.Post += () => { - Console.WriteLine("BeginRendering post"); sw.Stop(); - Console.WriteLine("BeginRendering took " + sw.ElapsedMilliseconds + "ms"); + + if (sw.ElapsedMilliseconds >= 6) { + Console.WriteLine("BeginRendering took " + sw.ElapsedMilliseconds + "ms"); + } + sw.Reset(); }; + REFrameworkNET.Callbacks.EndRendering.Post += () => { - Console.WriteLine("EndRendering"); }; REFrameworkNET.Callbacks.FinalizeRenderer.Pre += () => { - Console.WriteLine("Finalizing!!!!"); + Console.WriteLine("Finalizing Renderer"); }; REFrameworkNET.Callbacks.PrepareRendering.Post += () => { - Console.WriteLine("PrepareRendering"); }; // Convert api.Get() type to pass to GenerateWrapper @@ -129,7 +130,7 @@ public static void Main(REFrameworkNET.API api) { // Open in explorer System.Diagnostics.Process.Start("explorer.exe", currentDir);*/ - var tdb = api.GetTDB(); + var tdb = REFrameworkNET.API.GetTDB(); Console.WriteLine(tdb.GetNumTypes().ToString() + " types"); @@ -157,7 +158,7 @@ public static void Main(REFrameworkNET.API api) { Console.WriteLine("Done with types"); - var singletons = api.GetManagedSingletons(); + var singletons = REFrameworkNET.API.GetManagedSingletons(); foreach (var singletonDesc in singletons) { var singleton = singletonDesc.Instance; @@ -187,13 +188,13 @@ public static void Main(REFrameworkNET.API api) { } } - /*var sceneManager = api.GetNativeSingleton("via.SceneManager"); + var sceneManager = REFrameworkNET.API.GetNativeSingleton("via.SceneManager"); Console.WriteLine("sceneManager: " + sceneManager); var sceneManager_t = tdb.FindType("via.SceneManager"); Console.WriteLine("sceneManager_t: " + sceneManager_t); var get_CurrentScene = sceneManager_t.FindMethod("get_CurrentScene"); Console.WriteLine("get_CurrentScene: " + get_CurrentScene); - var scene = get_CurrentScene.Invoke(sceneManager, new object[]{}).Ptr; + var scene = get_CurrentScene.Invoke(sceneManager, []).Ptr; Console.WriteLine("scene: " + scene); @@ -203,7 +204,7 @@ public static void Main(REFrameworkNET.API api) { Console.WriteLine("set_TimeScale: " + set_TimeScale); - set_TimeScale.Invoke(scene, new object[]{ 0.1f }); - }*/ + set_TimeScale.Invoke(scene, new object[]{0.1f}); + } } }; \ No newline at end of file From c2f89b78e85fcc545fd253e3dcd1affe28934c6a Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 14:49:38 -0700 Subject: [PATCH 015/207] .NET: Logging API --- csharp-api/REFrameworkNET/API.cpp | 1 + csharp-api/REFrameworkNET/API.hpp | 68 ++++++++++++++++++++++++++++--- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/csharp-api/REFrameworkNET/API.cpp b/csharp-api/REFrameworkNET/API.cpp index fb0200d46..55e4d01bd 100644 --- a/csharp-api/REFrameworkNET/API.cpp +++ b/csharp-api/REFrameworkNET/API.cpp @@ -20,6 +20,7 @@ void REFrameworkNET::API::Init_Internal(const REFrameworkPluginInitializeParam* Console::WriteLine("REFrameworkNET.API Init_Internal called."); s_api = reframework::API::initialize(param).get(); Callbacks::Impl::Setup(this); + Console::WriteLine("REFrameworkNET.API Init_Internal finished."); } REFrameworkNET::API::~API() diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index a38f8d003..40626e937 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -21,6 +21,12 @@ class API; using namespace System; namespace REFrameworkNET { +public enum LogLevel { + Info = 0, + Warning = 1, + Error = 2, +}; + public ref class API { public: @@ -28,9 +34,14 @@ public ref class API API(uintptr_t param); ~API(); + ref class APINotInitializedException : public System::InvalidOperationException { + public: + APINotInitializedException() : System::InvalidOperationException("API is not initialized.") {} + }; + static REFrameworkNET::TDB^ GetTDB() { if (s_api == nullptr) { - throw gcnew System::InvalidOperationException("API is not initialized."); + throw gcnew APINotInitializedException(); } return gcnew REFrameworkNET::TDB(s_api->tdb()); @@ -38,7 +49,7 @@ public ref class API static REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name) { if (s_api == nullptr) { - throw gcnew System::InvalidOperationException("API is not initialized."); + throw gcnew APINotInitializedException(); } auto result = s_api->get_managed_singleton(msclr::interop::marshal_as(name)); @@ -52,7 +63,7 @@ public ref class API static System::Collections::Generic::List^ GetManagedSingletons() { if (s_api == nullptr) { - throw gcnew System::InvalidOperationException("API is not initialized."); + throw gcnew APINotInitializedException(); } auto singletons = s_api->get_managed_singletons(); @@ -76,7 +87,7 @@ public ref class API static NativeObject^ GetNativeSingleton(System::String^ name) { if (s_api == nullptr) { - throw gcnew System::InvalidOperationException("API is not initialized."); + throw gcnew APINotInitializedException(); } auto result = s_api->get_native_singleton(msclr::interop::marshal_as(name)); @@ -85,7 +96,7 @@ public ref class API return nullptr; } - auto t = GetTDB()->GetType(name); + auto t = REFrameworkNET::API::GetTDB()->GetType(name); if (t == nullptr) { return nullptr; @@ -96,12 +107,57 @@ public ref class API static reframework::API* GetNativeImplementation() { if (s_api == nullptr) { - throw gcnew System::InvalidOperationException("API is not initialized."); + throw gcnew APINotInitializedException(); } return s_api; } + static LogLevel LogLevel{LogLevel::Info}; + static bool LogToConsole{true}; + + static void LogError(System::String^ message) { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + if (LogLevel <= LogLevel::Error) { + s_api->log_error(msclr::interop::marshal_as(message).c_str()); + + if (LogToConsole) { + System::Console::WriteLine(message); + } + } + } + + static void LogWarning(System::String^ message) { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + if (LogLevel <= LogLevel::Warning) { + s_api->log_warn(msclr::interop::marshal_as(message).c_str()); + + if (LogToConsole) { + System::Console::WriteLine(message); + } + } + } + + static void LogInfo(System::String^ message) { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + if (LogLevel <= LogLevel::Info) { + s_api->log_info(msclr::interop::marshal_as(message).c_str()); + + if (LogToConsole) { + System::Console::WriteLine(message); + } + } + } + protected: void Init_Internal(const REFrameworkPluginInitializeParam* param); From d14dc95f323475af126cd1bb3b80063af7f66061 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 14:51:34 -0700 Subject: [PATCH 016/207] .NET: Add runtime compiler for .cs files --- CMakeLists.txt | 84 ++++++++++- cmake.toml | 46 +++++- csharp-api/Compiler.cs | 113 ++++++++++++++ csharp-api/Intermediary.cs | 17 +++ csharp-api/REFrameworkNET/PluginManager.cpp | 158 +++++++++++++++++++- csharp-api/REFrameworkNET/PluginManager.hpp | 1 + csharp-api/test/Test/Test.cs | 31 ++-- 7 files changed, 424 insertions(+), 26 deletions(-) create mode 100644 csharp-api/Compiler.cs create mode 100644 csharp-api/Intermediary.cs diff --git a/CMakeLists.txt b/CMakeLists.txt index d68eab245..1ed63d521 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,12 @@ option(REF_BUILD_DD2_SDK OFF) option(REF_BUILD_FRAMEWORK "Enable building the full REFramework" ON) option(REF_BUILD_DEPENDENCIES "Enable building dependencies" ON) -project(reframework) +project(reframework + LANGUAGES + CXX + C + CSharp +) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") @@ -72,6 +77,18 @@ endif() set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +set(NUGET_PACKAGES_DIR "$ENV{NUGET_PACKAGES}") + +# If NUGET_PACKAGES_DIR is not set, fall back to the default location +if(NOT NUGET_PACKAGES_DIR) + if(WIN32) + set(DEFAULT_NUGET_PATH "$ENV{USERPROFILE}/.nuget/packages") + else() + set(DEFAULT_NUGET_PATH "$ENV{HOME}/.nuget/packages") + endif() + set(NUGET_PACKAGES_DIR ${DEFAULT_NUGET_PATH}) +endif() + include(FetchContent) message(STATUS "Fetching asmjit (2a706fd2ba355808cada31ac1eed8ce28caa6b37)...") @@ -13105,6 +13122,52 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework unset(CMKR_SOURCES) endif() +# Target csharp-api-intermediary +if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework + set(CMKR_TARGET csharp-api-intermediary) + set(csharp-api-intermediary_SOURCES "") + + list(APPEND csharp-api-intermediary_SOURCES + "csharp-api/Intermediary.cs" + "csharp-api/Compiler.cs" + ) + + list(APPEND csharp-api-intermediary_SOURCES + cmake.toml + ) + + set(CMKR_SOURCES ${csharp-api-intermediary_SOURCES}) + add_library(csharp-api-intermediary SHARED) + + if(csharp-api-intermediary_SOURCES) + target_sources(csharp-api-intermediary PRIVATE ${csharp-api-intermediary_SOURCES}) + endif() + + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${csharp-api-intermediary_SOURCES}) + + set_target_properties(csharp-api-intermediary PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0 + VS_CONFIGURATION_TYPE + ClassLibrary + VS_PACKAGE_REFERENCES + Microsoft.CodeAnalysis_4.9.2 + CMAKE_CSharp_FLAGS + "/langversion:latest /platform:x64" + ) + + unset(CMKR_TARGET) + unset(CMKR_SOURCES) +endif() + # Target csharp-api if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework set(CMKR_TARGET csharp-api) @@ -13148,6 +13211,9 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework "include/" ) + target_link_libraries(csharp-api PUBLIC + csharp-api-intermediary + ) set_target_properties(csharp-api PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE @@ -13160,8 +13226,8 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework netcore DOTNET_TARGET_FRAMEWORK net8.0 - DOTNET_TARGET_FRAMEWORK_VERSION - v8.0 + VS_GLOBAL_EnableManagedPackageReferenceSupport + true ) # Custom command to compile the Test.cs file @@ -13171,6 +13237,18 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/ COMMENT "Building C# test project" ) + +# Copy nuget packages to the output directory + add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ + COMMENT "Copying nuget packages" + ) + add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ + COMMENT "Copying nuget packages" + ) unset(CMKR_TARGET) unset(CMKR_SOURCES) diff --git a/cmake.toml b/cmake.toml index 068953f32..933ef9ac4 100644 --- a/cmake.toml +++ b/cmake.toml @@ -4,6 +4,7 @@ # > cmake --build build --config Release [project] name = "reframework" +languages = ["CXX", "C", "CSharp"] cmake-after = """ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") @@ -30,6 +31,18 @@ if ("${CMAKE_BUILD_TYPE}" MATCHES "Release") endif() set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +set(NUGET_PACKAGES_DIR "$ENV{NUGET_PACKAGES}") + +# If NUGET_PACKAGES_DIR is not set, fall back to the default location +if(NOT NUGET_PACKAGES_DIR) + if(WIN32) + set(DEFAULT_NUGET_PATH "$ENV{USERPROFILE}/.nuget/packages") + else() + set(DEFAULT_NUGET_PATH "$ENV{HOME}/.nuget/packages") + endif() + set(NUGET_PACKAGES_DIR ${DEFAULT_NUGET_PATH}) +endif() """ [options] @@ -379,6 +392,22 @@ link-libraries = [ type = "plugin" sources = ["examples/weapon_stay_big_plugin/weapon_stay_big.cpp"] +# Commenting this out instead of removing it to keep it as an example +[target.csharp-api-intermediary] +type = "shared" +condition = "build-framework" +sources = ["csharp-api/Intermediary.cs", "csharp-api/Compiler.cs"] + +[target.csharp-api-intermediary.properties] +RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" +RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" +LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" +DOTNET_SDK = "Microsoft.NET.Sdk" +DOTNET_TARGET_FRAMEWORK = "net8.0" +VS_CONFIGURATION_TYPE = "ClassLibrary" +VS_PACKAGE_REFERENCES = "Microsoft.CodeAnalysis_4.9.2" +CMAKE_CSharp_FLAGS = "/langversion:latest /platform:x64" + [target.csharp-api] type = "shared" include-directories = ["include/"] @@ -387,6 +416,7 @@ compile-features = ["cxx_std_20"] compile-options = ["/EHa", "/MD"] condition = "build-framework" link-libraries = [ + "csharp-api-intermediary" ] cmake-after = """ # Custom command to compile the Test.cs file @@ -396,6 +426,18 @@ add_custom_command( WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/ COMMENT "Building C# test project" ) + +# Copy nuget packages to the output directory +add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ + COMMENT "Copying nuget packages" +) +add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ + COMMENT "Copying nuget packages" +) """ [target.csharp-api.properties] @@ -404,4 +446,6 @@ RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" COMMON_LANGUAGE_RUNTIME = "netcore" DOTNET_TARGET_FRAMEWORK = "net8.0" -DOTNET_TARGET_FRAMEWORK_VERSION = "v8.0" \ No newline at end of file +# DOTNET_TARGET_FRAMEWORK_VERSION = "net8.0" +VS_GLOBAL_EnableManagedPackageReferenceSupport = "true" +# VS_PACKAGE_REFERENCES = "Microsoft.CodeAnalysis_4.9.2" \ No newline at end of file diff --git a/csharp-api/Compiler.cs b/csharp-api/Compiler.cs new file mode 100644 index 000000000..afe1dc750 --- /dev/null +++ b/csharp-api/Compiler.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Reflection.Metadata; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; + +namespace DynamicRun.Builder +{ + public class Compiler + { + static public byte[] Compile(string filepath, string fromLocation) + { + var sourceCode = File.ReadAllText(filepath); + + using (var peStream = new MemoryStream()) + { + var result = GenerateCode(sourceCode, filepath, fromLocation).Emit(peStream); + + if (!result.Success) + { + Console.WriteLine("Compilation done with error."); + + var failures = result.Diagnostics.Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error); + + foreach (var diagnostic in failures) + { + Console.Error.WriteLine("{0}: {1}", diagnostic.Id, diagnostic.GetMessage()); + } + + return null; + } + + Console.WriteLine("Compilation done without any error."); + + peStream.Seek(0, SeekOrigin.Begin); + + return peStream.ToArray(); + } + } + + private static CSharpCompilation GenerateCode(string sourceCode, string filePath, string fromLocation) + { + var codeString = SourceText.From(sourceCode); + var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); + string assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); + // get all DLLs in that directory + var dlls = Directory.GetFiles(assemblyPath, "*.dll"); + + var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options); + + var referencesStr = new System.Collections.Generic.SortedSet + { + typeof(object).Assembly.Location, + typeof(Console).Assembly.Location, + typeof(System.Linq.Enumerable).Assembly.Location, + typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location, + typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location, + fromLocation, + }; + + // Add all the DLLs to the references + foreach (var dll in dlls) + { + referencesStr.Add(dll); + } + + referencesStr.RemoveWhere(r => + { + try + { + using var fs = new FileStream(r, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var peReader = new System.Reflection.PortableExecutable.PEReader(fs); + + MetadataReader mr = peReader.GetMetadataReader(); + + if (!mr.IsAssembly) + { + Console.WriteLine("Removed reference: " + r); + return true; + } + } + catch (Exception) + { + Console.WriteLine("Error adding reference: " + r); + //Console.WriteLine(e); + return true; + } + + return false; + }); + + var references = referencesStr.Select(r => MetadataReference.CreateFromFile(r)).ToArray(); + + foreach (var reference in references) + { + Console.WriteLine(reference.Display); + } + + // Get only the filename from the path + var dllName = Path.GetFileNameWithoutExtension(filePath) + ".dll"; + + return CSharpCompilation.Create(dllName, + new[] { parsedSyntaxTree }, + references: references, + options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, + optimizationLevel: OptimizationLevel.Release, + assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default)); + } + } +} \ No newline at end of file diff --git a/csharp-api/Intermediary.cs b/csharp-api/Intermediary.cs new file mode 100644 index 000000000..beb3a8d68 --- /dev/null +++ b/csharp-api/Intermediary.cs @@ -0,0 +1,17 @@ +// Intermediary class for the C# API that holds our NuGet package. +using System; +//using System.CodeDom; +using Microsoft.CodeAnalysis.CSharp; + +class Intermediary +{ + // Does nothing. + public static void Main() + { + Console.WriteLine("Hello, World!"); + + // CodeDom test + //CSharpCodeProvider codeProvider = new CSharpCodeProvider(); + //Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider(); + } +} diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 454127974..be0c0450d 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -1,8 +1,16 @@ #include #include - #include "PluginManager.hpp" +using namespace System; +using namespace System::IO; +using namespace System::Reflection; +using namespace System::Collections::Generic; +using namespace Microsoft::CodeAnalysis; +using namespace Microsoft::CodeAnalysis::CSharp; +using namespace Microsoft::CodeAnalysis::Text; +using namespace msclr::interop; + namespace REFrameworkNET { // Executed initially when we get loaded via LoadLibrary // which is not in the correct context to actually load the managed plugins @@ -12,10 +20,7 @@ namespace REFrameworkNET { } PluginManager::s_initialized = true; - - // Write to console using C++/CLI - System::String^ str = "Hello from C++/CLI!"; - System::Console::WriteLine(str); + System::Console::WriteLine("Attempting to load plugins from initial context"); // Make sure plugins that are loaded can find a reference to the current assembly // Even though "this" is loaded currently, its not in the right context to be found @@ -44,6 +49,7 @@ namespace REFrameworkNET { } // Invoke LoadPlugins method + System::Console::WriteLine("Invoking LoadPlugins..."); method->Invoke(nullptr, gcnew array{reinterpret_cast(param)}); return true; @@ -57,6 +63,14 @@ namespace REFrameworkNET { return false; } catch (System::Exception^ e) { System::Console::WriteLine(e->Message); + + // log stack + auto ex = e; + while (ex != nullptr) { + System::Console::WriteLine(ex->StackTrace); + ex = ex->InnerException; + } + return false; } catch (const std::exception& e) { System::Console::WriteLine(gcnew System::String(e.what())); @@ -68,7 +82,9 @@ namespace REFrameworkNET { // meant to be executed in the correct context // after loading "ourselves" via System::Reflection::Assembly::LoadFrom - bool PluginManager::LoadPlugins(uintptr_t param_raw) { + bool PluginManager::LoadPlugins(uintptr_t param_raw) try { + System::Console::WriteLine("LoadPlugins called"); + if (PluginManager::s_initialized) { return true; } @@ -80,7 +96,11 @@ namespace REFrameworkNET { // Look for any DLLs in the "managed" directory, load them, then call a function in them (REFrameworkPlugin.Main) // This is useful for loading C# plugins // Create the REFramework::API class first though (managed) - PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); + if (PluginManager::s_api_instance == nullptr) { + PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); + } + + LoadPlugins_FromSourceCode(param_raw); const auto managed_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed"; std::filesystem::create_directories(managed_path); @@ -127,5 +147,129 @@ namespace REFrameworkNET { } return true; + } catch(System::Exception^ e) { + System::Console::WriteLine(e->Message); + // log stack + auto ex = e; + while (ex != nullptr) { + System::Console::WriteLine(ex->StackTrace); + ex = ex->InnerException; + } + + return false; + } catch(const std::exception& e) { + System::Console::WriteLine(gcnew System::String(e.what())); + return false; + } catch(...) { + System::Console::WriteLine("Unknown exception caught"); + return false; + } + + bool PluginManager::LoadPlugins_FromSourceCode(uintptr_t param_raw) try { + if (PluginManager::s_api_instance == nullptr) { + PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); + } + + System::Console::WriteLine("Test"); + REFrameworkNET::API::LogInfo("Attempting to load plugins from source code..."); + + const auto plugins_path = std::filesystem::current_path() / "reframework" / "plugins"; + const auto cs_files_path = plugins_path / "source"; + std::filesystem::create_directories(cs_files_path); + + String^ cs_files_dir = gcnew String(cs_files_path.wstring().c_str()); + + bool ever_found = false; + + auto files = System::IO::Directory::GetFiles(cs_files_dir, "*.cs"); + + if (files->Length == 0) { + //API::get()->log_error("No C# files found in %s", csFilesDir); + return false; + } + + auto intermediary = System::Reflection::Assembly::LoadFrom(gcnew String((plugins_path / "csharp-api-intermediary.dll").wstring().c_str())); + + if (intermediary == nullptr) { + REFrameworkNET::API::LogError("Failed to load intermediary assembly, cannot compile C# files"); + return false; + } + + auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); + + for each (String^ file in files) { + Console::WriteLine(file); + + // Compile the C# file, and then call a function in it (REFrameworkPlugin.Main) + // This is useful for loading C# plugins that don't want to be compiled into a DLL + //auto bytecode = DynamicRun::Builder::Compiler::Compile(file, self->Location); + // Dynamically look for DynamicRun.Builder.Compiler.Compile + auto type = intermediary->GetType("DynamicRun.Builder.Compiler"); + if (type == nullptr) { + REFrameworkNET::API::LogError("Failed to get type DynamicRun.Builder.Compiler"); + continue; + } + auto method = type->GetMethod("Compile", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); + + if (method == nullptr) { + REFrameworkNET::API::LogError("Failed to get method DynamicRun.Builder.Compiler.Compile"); + continue; + } + + auto bytecode = (array^)method->Invoke(nullptr, gcnew array{file, self->Location}); + + if (bytecode == nullptr) { + REFrameworkNET::API::LogError("Failed to compile " + file); + continue; + } + + auto assem = System::Reflection::Assembly::Load(bytecode); + + if (assem == nullptr) { + REFrameworkNET::API::LogError("Failed to load assembly from " + file); + continue; + } + + REFrameworkNET::API::LogInfo("Compiled " + file); + + // Look for the Main method in the compiled assembly + for each (Type^ type in assem->GetTypes()) { + System::Reflection::MethodInfo^ mainMethod = type->GetMethod( + "Main", + System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, + gcnew array{REFrameworkNET::API::typeid}); + + if (mainMethod != nullptr) { + Console::WriteLine("Found Main method in " + file); + + array^ args = gcnew array{PluginManager::s_api_instance}; + mainMethod->Invoke(nullptr, args); + ever_found = true; + } + } + } + + if (!ever_found) { + Console::WriteLine("No C# files compiled in " + cs_files_dir); + } + + return true; + } catch(System::Exception^ e) { + REFrameworkNET::API::LogError(e->Message); + + // log stack + auto ex = e; + while (ex != nullptr) { + REFrameworkNET::API::LogError(ex->StackTrace); + ex = ex->InnerException; + } + + return false; + } catch(const std::exception& e) { + REFrameworkNET::API::LogError(gcnew System::String(e.what())); + return false; + } catch(...) { + REFrameworkNET::API::LogError("Unknown exception caught while compiling C# files"); + return false; } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 0765118ff..17b211870 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -30,5 +30,6 @@ private ref class PluginManager // meant to be executed in the correct context // after loading "ourselves" via System::Reflection::Assembly::LoadFrom static bool LoadPlugins(uintptr_t param_raw); + static bool LoadPlugins_FromSourceCode(uintptr_t param_raw); }; } \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 53572c7aa..67c9ca6b6 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -93,9 +93,10 @@ class REFrameworkPlugin { // Measure time between pre and post // get time static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); public static void Main(REFrameworkNET.API api) { - Console.WriteLine("Testing REFrameworkAPI..."); + REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); REFrameworkNET.Callbacks.BeginRendering.Pre += () => { sw.Start(); @@ -111,6 +112,14 @@ public static void Main(REFrameworkNET.API api) { }; REFrameworkNET.Callbacks.EndRendering.Post += () => { + if (!sw2.IsRunning) { + sw2.Start(); + } + + if (sw2.ElapsedMilliseconds >= 5000) { + sw2.Restart(); + Console.WriteLine("EndRendering"); + } }; REFrameworkNET.Callbacks.FinalizeRenderer.Pre += () => { @@ -132,11 +141,11 @@ public static void Main(REFrameworkNET.API api) { var tdb = REFrameworkNET.API.GetTDB(); - Console.WriteLine(tdb.GetNumTypes().ToString() + " types"); + REFrameworkNET.API.LogInfo(tdb.GetNumTypes().ToString() + " types"); for (uint i = 0; i < 50; i++) { var type = tdb.GetType(i); - Console.WriteLine(type.GetFullName()); + REFrameworkNET.API.LogInfo(type.GetFullName()); var methods = type.GetMethods(); @@ -144,7 +153,7 @@ public static void Main(REFrameworkNET.API api) { var returnT = method.GetReturnType(); var returnTName = returnT != null ? returnT.GetFullName() : "null"; - Console.WriteLine(" " + returnTName + " " + method.GetName()); + REFrameworkNET.API.LogInfo(" " + returnTName + " " + method.GetName()); } var fields = type.GetFields(); @@ -152,11 +161,11 @@ public static void Main(REFrameworkNET.API api) { foreach (var field in fields) { var t = field.GetType(); string tName = t != null ? t.GetFullName() : "null"; - Console.WriteLine(" " + tName + " " + field.GetName() + " @ " + "0x" + field.GetOffsetFromBase().ToString("X")); + REFrameworkNET.API.LogInfo(" " + tName + " " + field.GetName() + " @ " + "0x" + field.GetOffsetFromBase().ToString("X")); } } - Console.WriteLine("Done with types"); + REFrameworkNET.API.LogInfo("Done with types"); var singletons = REFrameworkNET.API.GetManagedSingletons(); @@ -189,22 +198,14 @@ public static void Main(REFrameworkNET.API api) { } var sceneManager = REFrameworkNET.API.GetNativeSingleton("via.SceneManager"); - Console.WriteLine("sceneManager: " + sceneManager); var sceneManager_t = tdb.FindType("via.SceneManager"); - Console.WriteLine("sceneManager_t: " + sceneManager_t); var get_CurrentScene = sceneManager_t.FindMethod("get_CurrentScene"); - Console.WriteLine("get_CurrentScene: " + get_CurrentScene); var scene = get_CurrentScene.Invoke(sceneManager, []).Ptr; - Console.WriteLine("scene: " + scene); - if (scene != null) { var scene_t = tdb.FindType("via.Scene"); var set_TimeScale = scene_t.FindMethod("set_TimeScale"); - - Console.WriteLine("set_TimeScale: " + set_TimeScale); - - set_TimeScale.Invoke(scene, new object[]{0.1f}); + set_TimeScale.Invoke(scene, [0.1f]); } } }; \ No newline at end of file From 3681e43c0deb21f082b3859e91d767f76466abc2 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 15:46:21 -0700 Subject: [PATCH 017/207] .NET: Rename some stuff for more idiomatic names --- CMakeLists.txt | 29 +++++++++++---------- cmake.toml | 10 +++---- csharp-api/{ => Compiler}/Compiler.cs | 2 +- csharp-api/Intermediary.cs | 17 ------------ csharp-api/REFrameworkNET/PluginManager.cpp | 13 +++------ csharp-api/test/Test/Test.csproj | 2 +- 6 files changed, 25 insertions(+), 48 deletions(-) rename csharp-api/{ => Compiler}/Compiler.cs (99%) delete mode 100644 csharp-api/Intermediary.cs diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ed63d521..ab4e5fdfa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13122,30 +13122,29 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework unset(CMKR_SOURCES) endif() -# Target csharp-api-intermediary +# Target REFCSharpCompiler if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework - set(CMKR_TARGET csharp-api-intermediary) - set(csharp-api-intermediary_SOURCES "") + set(CMKR_TARGET REFCSharpCompiler) + set(REFCSharpCompiler_SOURCES "") - list(APPEND csharp-api-intermediary_SOURCES - "csharp-api/Intermediary.cs" - "csharp-api/Compiler.cs" + list(APPEND REFCSharpCompiler_SOURCES + "csharp-api/Compiler/Compiler.cs" ) - list(APPEND csharp-api-intermediary_SOURCES + list(APPEND REFCSharpCompiler_SOURCES cmake.toml ) - set(CMKR_SOURCES ${csharp-api-intermediary_SOURCES}) - add_library(csharp-api-intermediary SHARED) + set(CMKR_SOURCES ${REFCSharpCompiler_SOURCES}) + add_library(REFCSharpCompiler SHARED) - if(csharp-api-intermediary_SOURCES) - target_sources(csharp-api-intermediary PRIVATE ${csharp-api-intermediary_SOURCES}) + if(REFCSharpCompiler_SOURCES) + target_sources(REFCSharpCompiler PRIVATE ${REFCSharpCompiler_SOURCES}) endif() - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${csharp-api-intermediary_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${REFCSharpCompiler_SOURCES}) - set_target_properties(csharp-api-intermediary PROPERTIES + set_target_properties(REFCSharpCompiler PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -13212,10 +13211,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework ) target_link_libraries(csharp-api PUBLIC - csharp-api-intermediary + REFCSharpCompiler ) set_target_properties(csharp-api PROPERTIES + OUTPUT_NAME + REFramework.NET RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO diff --git a/cmake.toml b/cmake.toml index 933ef9ac4..01116a009 100644 --- a/cmake.toml +++ b/cmake.toml @@ -392,13 +392,12 @@ link-libraries = [ type = "plugin" sources = ["examples/weapon_stay_big_plugin/weapon_stay_big.cpp"] -# Commenting this out instead of removing it to keep it as an example -[target.csharp-api-intermediary] +[target.REFCSharpCompiler] type = "shared" condition = "build-framework" -sources = ["csharp-api/Intermediary.cs", "csharp-api/Compiler.cs"] +sources = ["csharp-api/Compiler/Compiler.cs"] -[target.csharp-api-intermediary.properties] +[target.REFCSharpCompiler.properties] RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" @@ -416,7 +415,7 @@ compile-features = ["cxx_std_20"] compile-options = ["/EHa", "/MD"] condition = "build-framework" link-libraries = [ - "csharp-api-intermediary" + "REFCSharpCompiler" ] cmake-after = """ # Custom command to compile the Test.cs file @@ -441,6 +440,7 @@ add_custom_command( """ [target.csharp-api.properties] +OUTPUT_NAME = "REFramework.NET" RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" diff --git a/csharp-api/Compiler.cs b/csharp-api/Compiler/Compiler.cs similarity index 99% rename from csharp-api/Compiler.cs rename to csharp-api/Compiler/Compiler.cs index afe1dc750..7674095e2 100644 --- a/csharp-api/Compiler.cs +++ b/csharp-api/Compiler/Compiler.cs @@ -7,7 +7,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; -namespace DynamicRun.Builder +namespace REFrameworkNET { public class Compiler { diff --git a/csharp-api/Intermediary.cs b/csharp-api/Intermediary.cs deleted file mode 100644 index beb3a8d68..000000000 --- a/csharp-api/Intermediary.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Intermediary class for the C# API that holds our NuGet package. -using System; -//using System.CodeDom; -using Microsoft.CodeAnalysis.CSharp; - -class Intermediary -{ - // Does nothing. - public static void Main() - { - Console.WriteLine("Hello, World!"); - - // CodeDom test - //CSharpCodeProvider codeProvider = new CSharpCodeProvider(); - //Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider(); - } -} diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index be0c0450d..1f8b2fd29 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -188,13 +188,6 @@ namespace REFrameworkNET { return false; } - auto intermediary = System::Reflection::Assembly::LoadFrom(gcnew String((plugins_path / "csharp-api-intermediary.dll").wstring().c_str())); - - if (intermediary == nullptr) { - REFrameworkNET::API::LogError("Failed to load intermediary assembly, cannot compile C# files"); - return false; - } - auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); for each (String^ file in files) { @@ -202,9 +195,9 @@ namespace REFrameworkNET { // Compile the C# file, and then call a function in it (REFrameworkPlugin.Main) // This is useful for loading C# plugins that don't want to be compiled into a DLL - //auto bytecode = DynamicRun::Builder::Compiler::Compile(file, self->Location); + auto bytecode = REFrameworkNET::Compiler::Compile(file, self->Location); // Dynamically look for DynamicRun.Builder.Compiler.Compile - auto type = intermediary->GetType("DynamicRun.Builder.Compiler"); + /*auto type = intermediary->GetType("DynamicRun.Builder.Compiler"); if (type == nullptr) { REFrameworkNET::API::LogError("Failed to get type DynamicRun.Builder.Compiler"); continue; @@ -216,7 +209,7 @@ namespace REFrameworkNET { continue; } - auto bytecode = (array^)method->Invoke(nullptr, gcnew array{file, self->Location}); + auto bytecode = (array^)method->Invoke(nullptr, gcnew array{file, self->Location});*/ if (bytecode == nullptr) { REFrameworkNET::API::LogError("Failed to compile " + file); diff --git a/csharp-api/test/Test/Test.csproj b/csharp-api/test/Test/Test.csproj index 8f48dff9e..60e1f649d 100644 --- a/csharp-api/test/Test/Test.csproj +++ b/csharp-api/test/Test/Test.csproj @@ -26,7 +26,7 @@ - ..\..\..\build\bin\csharp-api\csharp-api.dll + ..\..\..\build\bin\csharp-api\REFramework.NET.dll False False From 2576b26b0a28ce95551b2fdb3414228c194ded15 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 15:55:10 -0700 Subject: [PATCH 018/207] .NET: Don't fail outright if compiler failed before loading DLLs --- csharp-api/REFrameworkNET/PluginManager.cpp | 28 +++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 1f8b2fd29..25c82fda4 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -100,7 +100,25 @@ namespace REFrameworkNET { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); } - LoadPlugins_FromSourceCode(param_raw); + // Try-catch because the user might not have the compiler + // dependencies in the plugins directory + try { + LoadPlugins_FromSourceCode(param_raw); + } catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Could not load plugins from source code: " + e->Message); + + auto ex = e; + while (ex != nullptr) { + REFrameworkNET::API::LogError(ex->StackTrace); + ex = ex->InnerException; + } + } catch (const std::exception& e) { + REFrameworkNET::API::LogError("Could not load plugins from source code: " + gcnew System::String(e.what())); + } catch (...) { + REFrameworkNET::API::LogError("Could not load plugins from source code: Unknown exception caught"); + } + + System::Console::WriteLine("Continue with managed plugins..."); const auto managed_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed"; std::filesystem::create_directories(managed_path); @@ -111,7 +129,7 @@ namespace REFrameworkNET { auto files = System::IO::Directory::GetFiles(managed_dir, "*.dll"); if (files->Length == 0) { - //API::get()->log_error("No DLLs found in %s", managedDir); + REFrameworkNET::API::LogInfo("No DLLs found in " + managed_dir); return false; } @@ -120,7 +138,7 @@ namespace REFrameworkNET { System::Reflection::Assembly^ assem = System::Reflection::Assembly::LoadFrom(file); if (assem == nullptr) { - Console::WriteLine("Failed to load assembly from " + file); + REFrameworkNET::API::LogError("Failed to load assembly from " + file); continue; } @@ -133,7 +151,7 @@ namespace REFrameworkNET { gcnew array{REFrameworkNET::API::typeid}); if (mainMethod != nullptr) { - Console::WriteLine("Found Main method in " + file); + REFrameworkNET::API::LogInfo("Found Main method in " + file); array^ args = gcnew array{PluginManager::s_api_instance}; mainMethod->Invoke(nullptr, args); @@ -143,7 +161,7 @@ namespace REFrameworkNET { } if (!ever_found) { - Console::WriteLine("No Main method found in any DLLs in " + managed_dir); + REFrameworkNET::API::LogInfo("No Main method found in any DLLs in " + managed_dir); } return true; From edb9df78e894203cc6e9ab3dc7c35e12cd12c1d7 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 17:19:50 -0700 Subject: [PATCH 019/207] .NET: Clean up project layout a bit --- CMakeLists.txt | 65 ++++++++++++++++---- cmake.toml | 43 ++++++++----- csharp-api/test/Test/Test.cs | 100 +------------------------------ csharp-api/test/Test/Test.csproj | 35 ----------- csharp-api/test/Test/Test.sln | 31 ---------- 5 files changed, 84 insertions(+), 190 deletions(-) delete mode 100644 csharp-api/test/Test/Test.csproj delete mode 100644 csharp-api/test/Test/Test.sln diff --git a/CMakeLists.txt b/CMakeLists.txt index ab4e5fdfa..c409c64ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13157,12 +13157,12 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework net8.0 VS_CONFIGURATION_TYPE ClassLibrary - VS_PACKAGE_REFERENCES - Microsoft.CodeAnalysis_4.9.2 CMAKE_CSharp_FLAGS "/langversion:latest /platform:x64" ) + set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.CodeAnalysis_4.9.2") + unset(CMKR_TARGET) unset(CMKR_SOURCES) endif() @@ -13231,15 +13231,7 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework true ) - # Custom command to compile the Test.cs file - add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND dotnet build ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/Test --configuration Release -o ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ --framework net8.0 - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/ - COMMENT "Building C# test project" - ) - -# Copy nuget packages to the output directory + # Copy nuget packages to the output directory add_custom_command( TARGET csharp-api POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ @@ -13250,6 +13242,57 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ COMMENT "Copying nuget packages" ) + set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/REFramework.NET.dll") + + unset(CMKR_TARGET) + unset(CMKR_SOURCES) +endif() + +# Target CSharpAPITest +if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework + set(CMKR_TARGET CSharpAPITest) + set(CSharpAPITest_SOURCES "") + + list(APPEND CSharpAPITest_SOURCES + "csharp-api/test/Test/Test.cs" + ) + + list(APPEND CSharpAPITest_SOURCES + cmake.toml + ) + + set(CMKR_SOURCES ${CSharpAPITest_SOURCES}) + add_library(CSharpAPITest SHARED) + + if(CSharpAPITest_SOURCES) + target_sources(CSharpAPITest PRIVATE ${CSharpAPITest_SOURCES}) + endif() + + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITest_SOURCES}) + + + set_target_properties(CSharpAPITest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0 + VS_CONFIGURATION_TYPE + ClassLibrary + CMAKE_CSharp_FLAGS + "/langversion:latest /platform:x64" + ) + + set_target_properties(CSharpAPITest PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET + "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" + ) + set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET") unset(CMKR_TARGET) unset(CMKR_SOURCES) diff --git a/cmake.toml b/cmake.toml index 01116a009..328d17bba 100644 --- a/cmake.toml +++ b/cmake.toml @@ -392,21 +392,28 @@ link-libraries = [ type = "plugin" sources = ["examples/weapon_stay_big_plugin/weapon_stay_big.cpp"] -[target.REFCSharpCompiler] +[template.CSharpSharedTarget] type = "shared" -condition = "build-framework" -sources = ["csharp-api/Compiler/Compiler.cs"] -[target.REFCSharpCompiler.properties] +[template.CSharpSharedTarget.properties] RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" DOTNET_SDK = "Microsoft.NET.Sdk" DOTNET_TARGET_FRAMEWORK = "net8.0" VS_CONFIGURATION_TYPE = "ClassLibrary" -VS_PACKAGE_REFERENCES = "Microsoft.CodeAnalysis_4.9.2" CMAKE_CSharp_FLAGS = "/langversion:latest /platform:x64" +[target.REFCSharpCompiler] +type = "CSharpSharedTarget" +condition = "build-framework" +sources = ["csharp-api/Compiler/Compiler.cs"] + +# Not using .properties here because it overrides the template properties for whatever reason +cmake-after = """ +set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.CodeAnalysis_4.9.2") +""" + [target.csharp-api] type = "shared" include-directories = ["include/"] @@ -418,14 +425,6 @@ link-libraries = [ "REFCSharpCompiler" ] cmake-after = """ -# Custom command to compile the Test.cs file -add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND dotnet build ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/Test --configuration Release -o ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ --framework net8.0 - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/csharp-api/test/ - COMMENT "Building C# test project" -) - # Copy nuget packages to the output directory add_custom_command( TARGET csharp-api POST_BUILD @@ -437,6 +436,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ COMMENT "Copying nuget packages" ) +set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/REFramework.NET.dll") """ [target.csharp-api.properties] @@ -448,4 +448,19 @@ COMMON_LANGUAGE_RUNTIME = "netcore" DOTNET_TARGET_FRAMEWORK = "net8.0" # DOTNET_TARGET_FRAMEWORK_VERSION = "net8.0" VS_GLOBAL_EnableManagedPackageReferenceSupport = "true" -# VS_PACKAGE_REFERENCES = "Microsoft.CodeAnalysis_4.9.2" \ No newline at end of file +# VS_PACKAGE_REFERENCES = "Microsoft.CodeAnalysis_4.9.2" + +[target.CSharpAPITest] +type = "CSharpSharedTarget" +condition = "build-framework" +sources = ["csharp-api/test/Test/**.cs"] +link-libraries = [ +] + +cmake-after = """ +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET +"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" +) +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET") +""" \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 67c9ca6b6..d30db8579 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -1,99 +1,11 @@ // Import REFramework::API - using System; -using System.Reflection; - -using System.Text; -using System.IO; - -public static class ApiWrapperGenerator -{ - public static void GenerateWrapper(Type type, string outputPath) - { - var sb = new StringBuilder(); - sb.AppendLine("using System;"); - sb.AppendFormat("public class {0}Wrapper\n", type.Name); - sb.AppendLine("{"); - sb.AppendFormat(" private readonly {0} _original;\n\n", type.Name); - sb.AppendFormat(" public {0}Wrapper({0} original)\n", type.Name); - sb.AppendLine(" {"); - sb.AppendLine(" _original = original;"); - sb.AppendLine(" }\n"); - - foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) - { - var pascalCaseName = ConvertToPascalCase(method.Name); - var parameters = method.GetParameters(); - - sb.AppendFormat(" public {0} {1}({2})\n", - method.ReturnType.Name, pascalCaseName, GetParameterDeclaration(parameters)); - sb.AppendLine(" {"); - sb.AppendFormat(" {0}_original.{1}({2});\n", - method.ReturnType.Name == "void" ? "" : "return ", - method.Name, - GetParameterInvocation(parameters)); - sb.AppendLine(" }\n"); - } - - sb.AppendLine("}"); - - File.WriteAllText(outputPath, sb.ToString()); - } - - private static string ConvertToPascalCase(string snakeCaseName) - { - // Split the snake_case string into words - string[] words = snakeCaseName.Split('_'); - - // Capitalize the first letter of each word - for (int i = 0; i < words.Length; i++) - { - if (words[i].Length > 0) - { - words[i] = char.ToUpper(words[i][0]) + words[i].Substring(1); - } - } - - // Join the words to form the PascalCase string - string pascalCaseName = string.Join("", words); - - return pascalCaseName; - } - - private static string GetParameterDeclaration(ParameterInfo[] parameters) - { - var sb = new StringBuilder(); - for (int i = 0; i < parameters.Length; i++) - { - sb.AppendFormat("{0} {1}", parameters[i].ParameterType.Name, parameters[i].Name); - if (i < parameters.Length - 1) - { - sb.Append(", "); - } - } - return sb.ToString(); - } - - private static string GetParameterInvocation(ParameterInfo[] parameters) - { - var sb = new StringBuilder(); - for (int i = 0; i < parameters.Length; i++) - { - sb.Append(parameters[i].Name); - if (i < parameters.Length - 1) - { - sb.Append(", "); - } - } - return sb.ToString(); - } -} class REFrameworkPlugin { // Measure time between pre and post // get time static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); - static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); + static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); public static void Main(REFrameworkNET.API api) { REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); @@ -129,16 +41,6 @@ public static void Main(REFrameworkNET.API api) { REFrameworkNET.Callbacks.PrepareRendering.Post += () => { }; - // Convert api.Get() type to pass to GenerateWrapper - /*var currentDir = Directory.GetCurrentDirectory(); - var targetType = typeof(reframework.API.Method); - var outputPath = Path.Combine(currentDir, "MethodWrapper.cs"); - - ApiWrapperGenerator.GenerateWrapper(targetType, outputPath); - - // Open in explorer - System.Diagnostics.Process.Start("explorer.exe", currentDir);*/ - var tdb = REFrameworkNET.API.GetTDB(); REFrameworkNET.API.LogInfo(tdb.GetNumTypes().ToString() + " types"); diff --git a/csharp-api/test/Test/Test.csproj b/csharp-api/test/Test/Test.csproj deleted file mode 100644 index 60e1f649d..000000000 --- a/csharp-api/test/Test/Test.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - Library - net8.0 - enable - AnyCPU;x64 - x64 - - - - True - - - - True - - - - True - - - - True - - - - - ..\..\..\build\bin\csharp-api\REFramework.NET.dll - False - False - - - - diff --git a/csharp-api/test/Test/Test.sln b/csharp-api/test/Test/Test.sln deleted file mode 100644 index dc9447815..000000000 --- a/csharp-api/test/Test/Test.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34330.188 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test.csproj", "{F1E90371-FCC8-45C3-8133-339A559CD37A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F1E90371-FCC8-45C3-8133-339A559CD37A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F1E90371-FCC8-45C3-8133-339A559CD37A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F1E90371-FCC8-45C3-8133-339A559CD37A}.Debug|x64.ActiveCfg = Debug|x64 - {F1E90371-FCC8-45C3-8133-339A559CD37A}.Debug|x64.Build.0 = Debug|x64 - {F1E90371-FCC8-45C3-8133-339A559CD37A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F1E90371-FCC8-45C3-8133-339A559CD37A}.Release|Any CPU.Build.0 = Release|Any CPU - {F1E90371-FCC8-45C3-8133-339A559CD37A}.Release|x64.ActiveCfg = Release|x64 - {F1E90371-FCC8-45C3-8133-339A559CD37A}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E9C2E7FC-0E99-4A61-99A3-1D337164B31E} - EndGlobalSection -EndGlobal From ad42ddb230399ce9b1180cdf4bbbed0f94a6a91c Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 17:27:40 -0700 Subject: [PATCH 020/207] .NET: Fix API not getting compiled before test --- CMakeLists.txt | 3 +++ cmake.toml | 1 + 2 files changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c409c64ca..1ac4238b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13270,6 +13270,9 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITest_SOURCES}) + target_link_libraries(CSharpAPITest PUBLIC + csharp-api + ) set_target_properties(CSharpAPITest PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE diff --git a/cmake.toml b/cmake.toml index 328d17bba..bc2cd90e4 100644 --- a/cmake.toml +++ b/cmake.toml @@ -455,6 +455,7 @@ type = "CSharpSharedTarget" condition = "build-framework" sources = ["csharp-api/test/Test/**.cs"] link-libraries = [ + "csharp-api" ] cmake-after = """ From 80a716e157241f4edc4b136c8ae200f91bdab43a Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 18:27:20 -0700 Subject: [PATCH 021/207] .NET: Add TryInvokeMember for NativeObject and ManagedObject --- csharp-api/REFrameworkNET/ManagedObject.cpp | 25 ++++ csharp-api/REFrameworkNET/ManagedObject.hpp | 3 +- csharp-api/REFrameworkNET/Method.cpp | 129 ++++++++++++-------- csharp-api/REFrameworkNET/Method.hpp | 1 + csharp-api/REFrameworkNET/NativeObject.cpp | 19 +++ csharp-api/REFrameworkNET/NativeObject.hpp | 4 +- csharp-api/REFrameworkNET/PluginManager.cpp | 3 - csharp-api/test/Test/Test.cs | 21 ++-- 8 files changed, 145 insertions(+), 60 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 9d1e9d580..2fd730317 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -5,6 +5,8 @@ #include "ManagedObject.hpp" +#include "API.hpp" + namespace REFrameworkNET { TypeDefinition^ ManagedObject::GetTypeDefinition() { auto result = m_object->get_type_definition(); @@ -41,4 +43,27 @@ namespace REFrameworkNET { return m->Invoke(this, args); } + + bool ManagedObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) + { + auto methodName = binder->Name; + auto t = this->GetTypeDefinition(); + + if (t == nullptr) { + return false; + } + + auto method = t->FindMethod(methodName); + + if (method != nullptr) + { + // Re-used with ManagedObject::TryInvokeMember + return method->HandleInvokeMember_Internal(this, binder, args, result); + } + + REFrameworkNET::API::LogInfo("Method not found: " + methodName); + + result = nullptr; + return false; + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 27d7467de..59c5fb8d9 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -9,7 +9,7 @@ ref class TypeDefinition; ref class TypeInfo; ref class InvokeRet; -public ref class ManagedObject { +public ref class ManagedObject : public System::Dynamic::DynamicObject { public: ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj) { AddRef(); @@ -49,6 +49,7 @@ public ref class ManagedObject { TypeInfo^ GetTypeInfo(); REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); + virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; // TODO methods: /*public Void* GetReflectionProperties() { diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index e22acbc0f..c0ea4d4d9 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -8,58 +8,62 @@ namespace REFrameworkNET { REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { // We need to convert the managed objects to 8 byte representations + std::vector args2{}; - args2.resize(args->Length); - for (int i = 0; i < args->Length; ++i) try { - if (args[i] == nullptr) { - args2[i] = nullptr; - continue; - } + if (args != nullptr && args->Length > 0) { + args2.resize(args->Length); - //args2[i] = args[i]->ptr(); - const auto t = args[i]->GetType(); - - if (t == REFrameworkNET::ManagedObject::typeid) { - args2[i] = safe_cast(args[i])->Ptr(); - } else if (t == System::Boolean::typeid) { - bool v = System::Convert::ToBoolean(args[i]); - args2[i] = (void*)(intptr_t)v; - } else if (t == System::Int32::typeid) { - int32_t v = System::Convert::ToInt32(args[i]); - args2[i] = (void*)(intptr_t)v; - } else if (t == System::Byte::typeid) { - uint8_t v = System::Convert::ToByte(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::UInt16::typeid) { - uint16_t v = System::Convert::ToUInt16(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::UInt32::typeid) { - uint32_t v = System::Convert::ToUInt32(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::Single::typeid) { - // this might seem counterintuitive, converting a float to a double - // but the invoke wrappers ALWAYS expect a double, so we need to do this - // even when they take a System.Single, the wrappers take in a double and convert it to a float - float v = System::Convert::ToSingle(args[i]); - auto d = (double)v; - auto n = *(int64_t*)&d; - args2[i] = (void*)(uint64_t)n; - } else if (t == System::UInt64::typeid) { - uint64_t v = System::Convert::ToUInt64(args[i]); - args2[i] = (void*)(uint64_t)v; - } else if (t == System::Double::typeid) { - double v = System::Convert::ToDouble(args[i]); - auto n = *(int64_t*)&v; - args2[i] = (void*)(uint64_t)n; - } else if (t == System::IntPtr::typeid) { - args2[i] = (void*)(uint64_t)System::Convert::ToInt64(args[i]); - } else { - args2[i] = nullptr; - System::Console::WriteLine("Unknown type passed to method invocation @ arg " + i); + for (int i = 0; i < args->Length; ++i) try { + if (args[i] == nullptr) { + args2[i] = nullptr; + continue; + } + + //args2[i] = args[i]->ptr(); + const auto t = args[i]->GetType(); + + if (t == REFrameworkNET::ManagedObject::typeid) { + args2[i] = safe_cast(args[i])->Ptr(); + } else if (t == System::Boolean::typeid) { + bool v = System::Convert::ToBoolean(args[i]); + args2[i] = (void*)(intptr_t)v; + } else if (t == System::Int32::typeid) { + int32_t v = System::Convert::ToInt32(args[i]); + args2[i] = (void*)(intptr_t)v; + } else if (t == System::Byte::typeid) { + uint8_t v = System::Convert::ToByte(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::UInt16::typeid) { + uint16_t v = System::Convert::ToUInt16(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::UInt32::typeid) { + uint32_t v = System::Convert::ToUInt32(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::Single::typeid) { + // this might seem counterintuitive, converting a float to a double + // but the invoke wrappers ALWAYS expect a double, so we need to do this + // even when they take a System.Single, the wrappers take in a double and convert it to a float + float v = System::Convert::ToSingle(args[i]); + auto d = (double)v; + auto n = *(int64_t*)&d; + args2[i] = (void*)(uint64_t)n; + } else if (t == System::UInt64::typeid) { + uint64_t v = System::Convert::ToUInt64(args[i]); + args2[i] = (void*)(uint64_t)v; + } else if (t == System::Double::typeid) { + double v = System::Convert::ToDouble(args[i]); + auto n = *(int64_t*)&v; + args2[i] = (void*)(uint64_t)n; + } else if (t == System::IntPtr::typeid) { + args2[i] = (void*)(uint64_t)System::Convert::ToInt64(args[i]); + } else { + args2[i] = nullptr; + System::Console::WriteLine("Unknown type passed to method invocation @ arg " + i); + } + } catch (System::Exception^ e) { + System::Console::WriteLine("Error converting argument " + i + ": " + e->Message); } - } catch (System::Exception^ e) { - System::Console::WriteLine("Error converting argument " + i + ": " + e->Message); } void* obj_ptr = nullptr; @@ -86,4 +90,33 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args, System::Object^% result) { + auto methodName = binder->Name; + auto tempResult = this->Invoke(obj, args); + + if (tempResult != nullptr && tempResult->QWord != 0) { + // Check if the result is a managed object + if (REFrameworkNET::ManagedObject::IsManagedObject((uintptr_t)tempResult->QWord)) { + result = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); + } else { + // Check the return type of the method and return it as a NativeObject if possible + auto returnType = this->GetReturnType(); + + if (returnType == nullptr) { + result = tempResult; + return true; + } + + if (!returnType->IsValueType()) { + result = gcnew REFrameworkNET::NativeObject((uintptr_t)tempResult->QWord, returnType); + return true; + } + + result = tempResult; + } + } + + return true; +} } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 5749eef36..80634c79d 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -13,6 +13,7 @@ public ref class Method { Method(reframework::API::Method* method) : m_method(method) {} REFrameworkNET::InvokeRet^ Invoke(System::Object^ obj, array^ args); + bool HandleInvokeMember_Internal(System::Object^ obj, System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result); /*Void* GetFunctionRaw() { return m_method->get_function_raw(); diff --git a/csharp-api/REFrameworkNET/NativeObject.cpp b/csharp-api/REFrameworkNET/NativeObject.cpp index 12216c605..f6930c9ec 100644 --- a/csharp-api/REFrameworkNET/NativeObject.cpp +++ b/csharp-api/REFrameworkNET/NativeObject.cpp @@ -2,6 +2,8 @@ #include "Method.hpp" #include "NativeObject.hpp" +#include "API.hpp" + namespace REFrameworkNET { InvokeRet^ NativeObject::Invoke(System::String^ methodName, array^ args) { auto t = this->GetTypeDefinition(); @@ -18,4 +20,21 @@ InvokeRet^ NativeObject::Invoke(System::String^ methodName, arrayInvoke(this, args); } + +bool NativeObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) +{ + auto methodName = binder->Name; + auto method = m_type->FindMethod(methodName); + + if (method != nullptr) + { + // Re-used with ManagedObject::TryInvokeMember + return method->HandleInvokeMember_Internal(this, binder, args, result); + } + + REFrameworkNET::API::LogInfo("Method not found: " + methodName); + + result = nullptr; + return false; +} } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index f4f11123f..46379c477 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -11,7 +11,8 @@ ref class InvokeRet; // However, they still have reflection information associated with them // So this intends to be the "ManagedObject" class for native objects // So we can easily interact with them in C# -public ref class NativeObject { +public ref class NativeObject : public System::Dynamic::DynamicObject +{ public: NativeObject(uintptr_t obj, TypeDefinition^ t){ m_object = (void*)obj; @@ -37,6 +38,7 @@ public ref class NativeObject { InvokeRet^ Invoke(System::String^ methodName, array^ args); + virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; private: void* m_object{}; diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 25c82fda4..4f6509649 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -6,9 +6,6 @@ using namespace System; using namespace System::IO; using namespace System::Reflection; using namespace System::Collections::Generic; -using namespace Microsoft::CodeAnalysis; -using namespace Microsoft::CodeAnalysis::CSharp; -using namespace Microsoft::CodeAnalysis::Text; using namespace msclr::interop; namespace REFrameworkNET { diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index d30db8579..edac6266e 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -7,6 +7,11 @@ class REFrameworkPlugin { static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); + class Scene { + public void set_TimeScale(float timeScale) { + + } + }; public static void Main(REFrameworkNET.API api) { REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); @@ -99,15 +104,17 @@ public static void Main(REFrameworkNET.API api) { } } - var sceneManager = REFrameworkNET.API.GetNativeSingleton("via.SceneManager"); - var sceneManager_t = tdb.FindType("via.SceneManager"); - var get_CurrentScene = sceneManager_t.FindMethod("get_CurrentScene"); - var scene = get_CurrentScene.Invoke(sceneManager, []).Ptr; + dynamic sceneManager = REFrameworkNET.API.GetNativeSingleton("via.SceneManager"); + dynamic scene = sceneManager.get_CurrentScene(); if (scene != null) { - var scene_t = tdb.FindType("via.Scene"); - var set_TimeScale = scene_t.FindMethod("set_TimeScale"); - set_TimeScale.Invoke(scene, [0.1f]); + var name = (scene as REFrameworkNET.ManagedObject).GetTypeDefinition().GetFullName(); + REFrameworkNET.API.LogInfo("Scene type: " + name); + REFrameworkNET.API.LogInfo("Scene: " + scene.ToString()); + + scene.set_TimeScale(0.1f); + } else { + REFrameworkNET.API.LogInfo("Scene is null"); } } }; \ No newline at end of file From b3dcb645ffe3ab393ebe32714c53ede91e9d8a90 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 21:58:15 -0700 Subject: [PATCH 022/207] .NET: Implement TryGetMember, equality, various improvements --- csharp-api/REFrameworkNET/Field.hpp | 2 +- csharp-api/REFrameworkNET/InvokeRet.hpp | 8 +- csharp-api/REFrameworkNET/ManagedObject.cpp | 167 +++++++++++++++++++ csharp-api/REFrameworkNET/ManagedObject.hpp | 51 +++++- csharp-api/REFrameworkNET/Method.cpp | 59 +++++-- csharp-api/REFrameworkNET/PluginManager.cpp | 10 +- csharp-api/REFrameworkNET/TypeDefinition.hpp | 15 +- csharp-api/REFrameworkNET/Utility.hpp | 21 +++ csharp-api/test/Test/Test.cs | 45 +++-- include/reframework/API.hpp | 2 +- 10 files changed, 342 insertions(+), 38 deletions(-) create mode 100644 csharp-api/REFrameworkNET/Utility.hpp diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp index 57e5a22de..642a440a2 100644 --- a/csharp-api/REFrameworkNET/Field.hpp +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -65,7 +65,7 @@ public ref class Field { // I have no idea if this will work correctly template - T GetData(uintptr_t obj, bool isValueType) { + T& GetData(uintptr_t obj, bool isValueType) { return m_field->get_data((void*)obj, isValueType); } diff --git a/csharp-api/REFrameworkNET/InvokeRet.hpp b/csharp-api/REFrameworkNET/InvokeRet.hpp index 8bb83b6e5..82fb40fb5 100644 --- a/csharp-api/REFrameworkNET/InvokeRet.hpp +++ b/csharp-api/REFrameworkNET/InvokeRet.hpp @@ -14,9 +14,9 @@ public ref struct InvokeRet { Byte = ret.byte; Word = ret.word; DWord = ret.dword; - F = ret.f; + Float = ret.f; QWord = ret.qword; - D = ret.d; + Double = ret.d; Ptr = gcnew System::UIntPtr(ret.ptr); ExceptionThrown = ret.exception_thrown; } @@ -26,9 +26,9 @@ public ref struct InvokeRet { property uint8_t Byte; property uint16_t Word; property uint32_t DWord; - property float F; + property float Float; property uint64_t QWord; - property double D; + property double Double; property System::Object^ Ptr; property bool ExceptionThrown; }; diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 2fd730317..b6416cc17 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -1,3 +1,5 @@ +#include + #include "TypeDefinition.hpp" #include "TypeInfo.hpp" #include "InvokeRet.hpp" @@ -7,6 +9,8 @@ #include "API.hpp" +#include "Utility.hpp" + namespace REFrameworkNET { TypeDefinition^ ManagedObject::GetTypeDefinition() { auto result = m_object->get_type_definition(); @@ -66,4 +70,167 @@ namespace REFrameworkNET { result = nullptr; return false; } + + bool ManagedObject::TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) + { + auto memberName = binder->Name; + auto t = this->GetTypeDefinition(); + + if (t == nullptr) { + return false; + } + + auto field = t->FindField(memberName); + + if (field != nullptr) + { + const auto field_type = field->GetType(); + + if (field_type == nullptr) { + return false; + } + + const auto raw_ft = (reframework::API::TypeDefinition*)field_type; + const uintptr_t addr = field->IsStatic() ? 0 : this->GetAddress(); + const auto vm_obj_type = field_type->GetVMObjType(); + + #define MAKE_TYPE_HANDLER(X, Y) \ + case ##X##_fnv: \ + result = gcnew Y(field->GetData<##Y##>(addr, field_type->IsValueType())); \ + break; + + switch (REFrameworkNET::hash(raw_ft->get_full_name())) { + MAKE_TYPE_HANDLER("System.Boolean", bool) + MAKE_TYPE_HANDLER("System.Byte", uint8_t) + MAKE_TYPE_HANDLER("System.SByte", int8_t) + MAKE_TYPE_HANDLER("System.Int16", int16_t) + MAKE_TYPE_HANDLER("System.UInt16", uint16_t) + MAKE_TYPE_HANDLER("System.Int32", int32_t) + MAKE_TYPE_HANDLER("System.UInt32", uint32_t) + MAKE_TYPE_HANDLER("System.Int64", int64_t) + MAKE_TYPE_HANDLER("System.UInt64", uint64_t) + MAKE_TYPE_HANDLER("System.Single", float) + MAKE_TYPE_HANDLER("System.Double", double) + MAKE_TYPE_HANDLER("System.Char", wchar_t) + MAKE_TYPE_HANDLER("System.IntPtr", intptr_t) + MAKE_TYPE_HANDLER("System.UIntPtr", uintptr_t) + case "System.String"_fnv: + { + if (field->IsLiteral()) { + result = gcnew System::String((const char*)field->GetInitDataPtr()); + break; + } + + // TODO: Check if this half of it works + auto strObject = field->GetData(addr, field_type->IsValueType()); + + if (strObject == nullptr) { + result = nullptr; + break; + } + + const auto offset = field_type->IsValueType() ? field_type->GetField("_firstChar")->GetOffsetFromFieldPtr() : field_type->GetField("_firstChar")->GetOffsetFromBase(); + + wchar_t* chars = (wchar_t*)((uintptr_t)strObject + offset); + result = gcnew System::String(chars); + break; + } + default: + if (vm_obj_type > VMObjType::NULL_ && vm_obj_type < VMObjType::ValType) { + switch (vm_obj_type) { + case VMObjType::Array: + //return sol::make_object(l, *(::sdk::SystemArray**)data); + result = nullptr; + break; // TODO: Implement array + default: { + //const auto td = utility::re_managed_object::get_type_definition(*(::REManagedObject**)data); + auto& obj = field->GetData(addr, field_type->IsValueType()); + + if (obj == nullptr) { + result = nullptr; + break; + } + + auto td = gcnew REFrameworkNET::TypeDefinition(obj->get_type_definition()); + + // another fallback incase the method returns an object which is an array + if (td != nullptr && td->GetVMObjType() == VMObjType::Array) { + //return sol::make_object(l, *(::sdk::SystemArray**)data); + result = nullptr; + break; + } + + result = gcnew ManagedObject(obj); + break; + } + } + } else { + switch (field_type->GetSize()) { + case 8: + result = gcnew System::UInt64(field->GetData(addr, field_type->IsValueType())); + break; + case 4: + result = gcnew System::UInt32(field->GetData(addr, field_type->IsValueType())); + break; + case 2: + result = gcnew System::UInt16(field->GetData(addr, field_type->IsValueType())); + break; + case 1: + result = gcnew System::Byte(field->GetData(addr, field_type->IsValueType())); + break; + default: + result = nullptr; + break; + } + + break; + } + }; + + return true; + } + + /*auto property = t->FindProperty(memberName); + + if (property != nullptr) + { + result = property->GetValue(this); + return true; + }*/ + + REFrameworkNET::API::LogInfo("Member not found: " + memberName); + + result = nullptr; + return false; + } + + bool ManagedObject::TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) + { + /*auto memberName = binder->Name; + auto t = this->GetTypeDefinition(); + + if (t == nullptr) { + return false; + } + + auto field = t->FindField(memberName); + + if (field != nullptr) + { + field->SetValue(this, value); + return true; + } + + auto property = t->FindProperty(memberName); + + if (property != nullptr) + { + property->SetValue(this, value); + return true; + } + + REFrameworkNET::API::LogInfo("Member not found: " + memberName);*/ + + return false; + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 59c5fb8d9..4f79cdba0 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -8,8 +8,10 @@ namespace REFrameworkNET { ref class TypeDefinition; ref class TypeInfo; ref class InvokeRet; +ref class ManagedObject; -public ref class ManagedObject : public System::Dynamic::DynamicObject { +public ref class ManagedObject : public System::Dynamic::DynamicObject, public System::IEquatable +{ public: ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj) { AddRef(); @@ -40,6 +42,51 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject { return (uintptr_t)m_object; } + virtual bool Equals(System::Object^ other) override { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + if (other->GetType() != ManagedObject::typeid) { + return false; + } + + return Ptr() == safe_cast(other)->Ptr(); + } + + // Override equality operator + virtual bool Equals(ManagedObject^ other) { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + return Ptr() == other->Ptr(); + } + + static bool operator ==(ManagedObject^ left, ManagedObject^ right) { + if (System::Object::ReferenceEquals(left, right)) { + return true; + } + + if (System::Object::ReferenceEquals(left, nullptr) || System::Object::ReferenceEquals(right, nullptr)) { + return false; + } + + return left->Ptr() == right->Ptr(); + } + + static bool operator !=(ManagedObject^ left, ManagedObject^ right) { + return !(left == right); + } + static bool IsManagedObject(uintptr_t ptr) { static auto fn = reframework::API::get()->param()->sdk->managed_object->is_managed_object; return fn((void*)ptr); @@ -50,6 +97,8 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject { REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; + virtual bool TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) override; + virtual bool TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) override; // TODO methods: /*public Void* GetReflectionProperties() { diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index c0ea4d4d9..2ec93b18c 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -5,6 +5,8 @@ #include "Method.hpp" +#include "Utility.hpp" + namespace REFrameworkNET { REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { // We need to convert the managed objects to 8 byte representations @@ -96,24 +98,57 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::Dynamic::I auto tempResult = this->Invoke(obj, args); if (tempResult != nullptr && tempResult->QWord != 0) { - // Check if the result is a managed object - if (REFrameworkNET::ManagedObject::IsManagedObject((uintptr_t)tempResult->QWord)) { - result = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); - } else { - // Check the return type of the method and return it as a NativeObject if possible - auto returnType = this->GetReturnType(); + auto returnType = this->GetReturnType(); - if (returnType == nullptr) { - result = tempResult; - return true; - } + if (returnType == nullptr) { + result = tempResult; + return true; + } + + // Check the return type of the method and return it as a NativeObject if possible + if (!returnType->IsValueType()) { + if (returnType->GetVMObjType() == VMObjType::Object) { + if (tempResult->QWord == 0) { + result = nullptr; + return true; + } - if (!returnType->IsValueType()) { - result = gcnew REFrameworkNET::NativeObject((uintptr_t)tempResult->QWord, returnType); + result = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); return true; } + // TODO: other managed types + result = gcnew REFrameworkNET::NativeObject((uintptr_t)tempResult->QWord, returnType); + return true; + } + + const auto raw_rt = (reframework::API::TypeDefinition*)returnType; + + #define CONCAT_X_C(X, DOT, C) X ## DOT ## C + + #define MAKE_TYPE_HANDLER_2(X, C, Y, Z) \ + case CONCAT_X_C(#X, ".", #C)_fnv: \ + result = gcnew X::C((Y)tempResult->Z); \ + break; + + switch (REFrameworkNET::hash(raw_rt->get_full_name().c_str())) { + MAKE_TYPE_HANDLER_2(System, Boolean, bool, Byte) + MAKE_TYPE_HANDLER_2(System, Byte, uint8_t, Byte) + MAKE_TYPE_HANDLER_2(System, UInt16, uint16_t, Word) + MAKE_TYPE_HANDLER_2(System, UInt32, uint32_t, DWord) + MAKE_TYPE_HANDLER_2(System, UInt64, uint64_t, QWord) + MAKE_TYPE_HANDLER_2(System, SByte, int8_t, Byte) + MAKE_TYPE_HANDLER_2(System, Int16, int16_t, Word) + MAKE_TYPE_HANDLER_2(System, Int32, int32_t, DWord) + MAKE_TYPE_HANDLER_2(System, Int64, int64_t, QWord) + // Because invoke wrappers returning a single actually return a double + // for consistency purposes + MAKE_TYPE_HANDLER_2(System, Single, double, Double) + MAKE_TYPE_HANDLER_2(System, Double, double, Double) + + default: result = tempResult; + break; } } diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 4f6509649..b8d007c9e 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -120,7 +120,7 @@ namespace REFrameworkNET { const auto managed_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed"; std::filesystem::create_directories(managed_path); - String^ managed_dir = gcnew String(managed_path.wstring().c_str()); + System::String^ managed_dir = gcnew System::String(managed_path.wstring().c_str()); bool ever_found = false; auto files = System::IO::Directory::GetFiles(managed_dir, "*.dll"); @@ -130,7 +130,7 @@ namespace REFrameworkNET { return false; } - for each (String^ file in files) { + for each (System::String^ file in files) { Console::WriteLine(file); System::Reflection::Assembly^ assem = System::Reflection::Assembly::LoadFrom(file); @@ -192,7 +192,7 @@ namespace REFrameworkNET { const auto cs_files_path = plugins_path / "source"; std::filesystem::create_directories(cs_files_path); - String^ cs_files_dir = gcnew String(cs_files_path.wstring().c_str()); + System::String^ cs_files_dir = gcnew System::String(cs_files_path.wstring().c_str()); bool ever_found = false; @@ -205,8 +205,8 @@ namespace REFrameworkNET { auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); - for each (String^ file in files) { - Console::WriteLine(file); + for each (System::String^ file in files) { + System::Console::WriteLine(file); // Compile the C# file, and then call a function in it (REFrameworkPlugin.Main) // This is useful for loading C# plugins that don't want to be compiled into a DLL diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 72d24440f..1d12454b3 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -13,6 +13,15 @@ ref class Field; ref class Property; ref class TypeInfo; +public enum VMObjType { + NULL_ = 0, + Object = 1, + Array = 2, + String = 3, + Delegate = 4, + ValType = 5, +}; + public ref class TypeDefinition { public: @@ -33,7 +42,7 @@ public ref class TypeDefinition return m_type->get_size(); } - uint32_t GetValuetypeSize() + uint32_t GetValueTypeSize() { return m_type->get_valuetype_size(); } @@ -118,9 +127,9 @@ public ref class TypeDefinition return m_type->is_primitive(); } - uint32_t GetVmObjType() + VMObjType GetVMObjType() { - return m_type->get_vm_obj_type(); + return (VMObjType)m_type->get_vm_obj_type(); } REFrameworkNET::Method^ FindMethod(System::String^ name); diff --git a/csharp-api/REFrameworkNET/Utility.hpp b/csharp-api/REFrameworkNET/Utility.hpp new file mode 100644 index 000000000..bffbe576f --- /dev/null +++ b/csharp-api/REFrameworkNET/Utility.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +namespace REFrameworkNET { + static constexpr auto hash(std::string_view data) { + size_t result = 0xcbf29ce484222325; + + for (char c : data) { + result ^= c; + result *= (size_t)1099511628211; + } + + return result; + } + + consteval auto operator "" _fnv(const char* s, size_t) { + return hash(s); + } +} \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index edac6266e..1c7bc883f 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -105,16 +105,39 @@ public static void Main(REFrameworkNET.API api) { } dynamic sceneManager = REFrameworkNET.API.GetNativeSingleton("via.SceneManager"); - dynamic scene = sceneManager.get_CurrentScene(); - - if (scene != null) { - var name = (scene as REFrameworkNET.ManagedObject).GetTypeDefinition().GetFullName(); - REFrameworkNET.API.LogInfo("Scene type: " + name); - REFrameworkNET.API.LogInfo("Scene: " + scene.ToString()); - - scene.set_TimeScale(0.1f); - } else { - REFrameworkNET.API.LogInfo("Scene is null"); - } + dynamic scene = sceneManager?.get_CurrentScene(); + + // Testing autocomplete for the concrete ManagedObject + REFrameworkNET.API.LogInfo("Scene: " + scene.ToString() + ": " + scene?.GetTypeDefinition()?.GetFullName()?.ToString()); + + // Testing dynamic invocation + float? currentTimescale = scene?.get_TimeScale(); + scene?.set_TimeScale(0.1f); + + REFrameworkNET.API.LogInfo("Previous timescale: " + currentTimescale.ToString()); + REFrameworkNET.API.LogInfo("Current timescale: " + scene?.get_TimeScale().ToString()); + + dynamic optionManager = REFrameworkNET.API.GetManagedSingleton("app.OptionManager"); + + ulong optionManagerAddress = optionManager != null ? (optionManager as REFrameworkNET.ManagedObject).GetAddress() : 0; + bool? hasAnySave = optionManager?._HasAnySave; + + REFrameworkNET.API.LogInfo("OptionManager: " + optionManager.ToString() + " @ " + optionManagerAddress.ToString("X")); + REFrameworkNET.API.LogInfo("HasAnySave: " + hasAnySave.ToString()); + + dynamic guiManager = REFrameworkNET.API.GetManagedSingleton("app.GuiManager"); + + ulong guiManagerAddress = guiManager != null ? (guiManager as REFrameworkNET.ManagedObject).GetAddress() : 0; + dynamic fadeOwnerCmn = guiManager?.FadeOwnerCmn; + dynamic optionData = guiManager?.OptionData; + dynamic optionDataFromGet = guiManager?.getOptionData(); + bool? isDispSubtitle = optionData?._IsDispSubtitle; + + REFrameworkNET.API.LogInfo("GuiManager: " + guiManager.ToString() + " @ " + guiManagerAddress.ToString("X")); + REFrameworkNET.API.LogInfo(" FadeOwnerCmn: " + fadeOwnerCmn.ToString()); + REFrameworkNET.API.LogInfo(" OptionData: " + optionData.ToString() + ": " + optionData?.GetTypeDefinition()?.GetFullName()?.ToString()); + REFrameworkNET.API.LogInfo(" OptionDataFromGet: " + optionDataFromGet.ToString() + ": " + optionDataFromGet?.GetTypeDefinition()?.GetFullName()?.ToString()); + REFrameworkNET.API.LogInfo(" OptionDataFromGet same: " + (optionData?.Equals(optionDataFromGet)).ToString() + (" {0} vs {1}", optionData?.GetAddress().ToString("X"), optionDataFromGet?.GetAddress().ToString("X"))); + REFrameworkNET.API.LogInfo(" IsDispSubtitle: " + isDispSubtitle.ToString()); } }; \ No newline at end of file diff --git a/include/reframework/API.hpp b/include/reframework/API.hpp index 7ec43f6b1..90b6c2552 100644 --- a/include/reframework/API.hpp +++ b/include/reframework/API.hpp @@ -606,7 +606,7 @@ class API { return API::s_instance->sdk()->field->get_data_raw(*this, obj, is_value_type); } - template T& get_data(void* object = nullptr, bool is_value_type = false) const { return *(T*)get_data_raw(object); } + template T& get_data(void* object = nullptr, bool is_value_type = false) const { return *(T*)get_data_raw(object, is_value_type); } }; struct Property { From c200c0035637ba7c228b5617331e940dd7625bfd Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 18 Mar 2024 22:11:43 -0700 Subject: [PATCH 023/207] .NET: Implement a subset of TrySetMember --- csharp-api/REFrameworkNET/ManagedObject.cpp | 44 ++++++++++++++++----- csharp-api/test/Test/Test.cs | 6 +++ 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index b6416cc17..abd6c7498 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -206,7 +206,7 @@ namespace REFrameworkNET { bool ManagedObject::TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) { - /*auto memberName = binder->Name; + auto memberName = binder->Name; auto t = this->GetTypeDefinition(); if (t == nullptr) { @@ -217,20 +217,44 @@ namespace REFrameworkNET { if (field != nullptr) { - field->SetValue(this, value); - return true; - } + const auto field_type = field->GetType(); - auto property = t->FindProperty(memberName); + if (field_type == nullptr) { + return false; + } + + const auto raw_ft = (reframework::API::TypeDefinition*)field_type; + const uintptr_t addr = field->IsStatic() ? 0 : this->GetAddress(); + const auto vm_obj_type = field_type->GetVMObjType(); + + #define MAKE_TYPE_HANDLER_SET(X, Y) \ + case ##X##_fnv: \ + field->GetData<##Y##>(addr, field_type->IsValueType()) = (Y)value; \ + break; + + switch (REFrameworkNET::hash(raw_ft->get_full_name())) { + MAKE_TYPE_HANDLER_SET("System.Boolean", bool) + MAKE_TYPE_HANDLER_SET("System.Byte", uint8_t) + MAKE_TYPE_HANDLER_SET("System.SByte", int8_t) + MAKE_TYPE_HANDLER_SET("System.Int16", int16_t) + MAKE_TYPE_HANDLER_SET("System.UInt16", uint16_t) + MAKE_TYPE_HANDLER_SET("System.Int32", int32_t) + MAKE_TYPE_HANDLER_SET("System.UInt32", uint32_t) + MAKE_TYPE_HANDLER_SET("System.Int64", int64_t) + MAKE_TYPE_HANDLER_SET("System.UInt64", uint64_t) + MAKE_TYPE_HANDLER_SET("System.Single", float) + MAKE_TYPE_HANDLER_SET("System.Double", double) + MAKE_TYPE_HANDLER_SET("System.Char", wchar_t) + MAKE_TYPE_HANDLER_SET("System.IntPtr", intptr_t) + MAKE_TYPE_HANDLER_SET("System.UIntPtr", uintptr_t) + + default: + break; + } - if (property != nullptr) - { - property->SetValue(this, value); return true; } - REFrameworkNET::API::LogInfo("Member not found: " + memberName);*/ - return false; } } \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 1c7bc883f..ec54b8741 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -138,6 +138,12 @@ public static void Main(REFrameworkNET.API api) { REFrameworkNET.API.LogInfo(" OptionData: " + optionData.ToString() + ": " + optionData?.GetTypeDefinition()?.GetFullName()?.ToString()); REFrameworkNET.API.LogInfo(" OptionDataFromGet: " + optionDataFromGet.ToString() + ": " + optionDataFromGet?.GetTypeDefinition()?.GetFullName()?.ToString()); REFrameworkNET.API.LogInfo(" OptionDataFromGet same: " + (optionData?.Equals(optionDataFromGet)).ToString() + (" {0} vs {1}", optionData?.GetAddress().ToString("X"), optionDataFromGet?.GetAddress().ToString("X"))); + REFrameworkNET.API.LogInfo(" IsDispSubtitle: " + isDispSubtitle.ToString()); + + if (optionData != null) { + optionData._IsDispSubtitle = !isDispSubtitle; + REFrameworkNET.API.LogInfo(" IsDispSubtitle: " + optionData?._IsDispSubtitle.ToString()); + } } }; \ No newline at end of file From d61179c2302dc4a8e11fc5166f732894224a7f1e Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 19 Mar 2024 03:32:19 -0700 Subject: [PATCH 024/207] .NET: Add System.String method return support --- csharp-api/REFrameworkNET/Method.cpp | 18 +++++++++++++++++- csharp-api/test/Test/Test.cs | 15 ++++++++++----- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 2ec93b18c..7e7e5e31c 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -4,6 +4,7 @@ #include "NativeObject.hpp" #include "Method.hpp" +#include "Field.hpp" #include "Utility.hpp" @@ -117,6 +118,22 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::Dynamic::I return true; } + if (returnType->GetVMObjType() == VMObjType::String) { + if (tempResult->QWord == 0) { + result = nullptr; + return true; + } + + // Maybe don't create the GC version and just use the native one? + auto strObject = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); + auto strType = strObject->GetTypeDefinition(); + const auto offset = strType->GetField("_firstChar")->GetOffsetFromBase(); + + wchar_t* chars = (wchar_t*)((uintptr_t)strObject->Ptr() + offset); + result = gcnew System::String(chars); + return true; + } + // TODO: other managed types result = gcnew REFrameworkNET::NativeObject((uintptr_t)tempResult->QWord, returnType); return true; @@ -145,7 +162,6 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::Dynamic::I // for consistency purposes MAKE_TYPE_HANDLER_2(System, Single, double, Double) MAKE_TYPE_HANDLER_2(System, Double, double, Double) - default: result = tempResult; break; diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index ec54b8741..d010f235b 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -7,11 +7,6 @@ class REFrameworkPlugin { static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); - class Scene { - public void set_TimeScale(float timeScale) { - - } - }; public static void Main(REFrameworkNET.API api) { REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); @@ -145,5 +140,15 @@ public static void Main(REFrameworkNET.API api) { optionData._IsDispSubtitle = !isDispSubtitle; REFrameworkNET.API.LogInfo(" IsDispSubtitle: " + optionData?._IsDispSubtitle.ToString()); } + + dynamic test = (guiManager as REFrameworkNET.ManagedObject)?.GetTypeDefinition()?.GetRuntimeType(); + + if (test != null) { + REFrameworkNET.API.LogInfo("GuiManager runtime type: " + test.ToString()); + + // This is basically a System.Type, so lets get the assembly location + REFrameworkNET.API.LogInfo("GuiManager runtime type assembly: " + test.get_Assembly()); + REFrameworkNET.API.LogInfo("GuiManager runtime type assembly name: " + test.get_Assembly().get_Location()); + } } }; \ No newline at end of file From e0ea665e743498de48e6a51fca1f47f48b52ad77 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 20 Mar 2024 02:51:53 -0700 Subject: [PATCH 025/207] ObjectExplorer: Add declaring_type to dump --- src/mods/tools/ObjectExplorer.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mods/tools/ObjectExplorer.cpp b/src/mods/tools/ObjectExplorer.cpp index f8ff5c162..458e5700b 100644 --- a/src/mods/tools/ObjectExplorer.cpp +++ b/src/mods/tools/ObjectExplorer.cpp @@ -1081,6 +1081,10 @@ void ObjectExplorer::generate_sdk() { type_entry["is_generic_type"] = t.is_generic_type(); type_entry["is_generic_type_definition"] = t.is_generic_type_definition(); + if (auto declaring_type = t.get_declaring_type(); declaring_type != nullptr) { + type_entry["declaring_type"] = declaring_type->get_full_name(); + } + if (auto gtd = t.get_generic_type_definition(); gtd != nullptr) { type_entry["generic_type_definition"] = gtd->get_full_name(); } From 7912c192464f334401eaf9b77da78572e7a7c8ee Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 20 Mar 2024 12:23:01 -0700 Subject: [PATCH 026/207] .NET: Add HandleInvokeMember_Internal for later --- csharp-api/REFrameworkNET/ManagedObject.cpp | 12 ++++++--- csharp-api/REFrameworkNET/ManagedObject.hpp | 2 ++ csharp-api/REFrameworkNET/Method.cpp | 4 +-- csharp-api/REFrameworkNET/Method.hpp | 2 +- csharp-api/REFrameworkNET/NativeObject.cpp | 27 ++++++++++++--------- csharp-api/REFrameworkNET/NativeObject.hpp | 1 + 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index abd6c7498..a8be63a1a 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -48,9 +48,7 @@ namespace REFrameworkNET { return m->Invoke(this, args); } - bool ManagedObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) - { - auto methodName = binder->Name; + bool ManagedObject::HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result) { auto t = this->GetTypeDefinition(); if (t == nullptr) { @@ -62,7 +60,7 @@ namespace REFrameworkNET { if (method != nullptr) { // Re-used with ManagedObject::TryInvokeMember - return method->HandleInvokeMember_Internal(this, binder, args, result); + return method->HandleInvokeMember_Internal(this, methodName, args, result); } REFrameworkNET::API::LogInfo("Method not found: " + methodName); @@ -71,6 +69,12 @@ namespace REFrameworkNET { return false; } + bool ManagedObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) + { + auto methodName = binder->Name; + return HandleInvokeMember_Internal(methodName, args, result); + } + bool ManagedObject::TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) { auto memberName = binder->Name; diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 4f79cdba0..e865ee29c 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -95,6 +95,8 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S TypeDefinition^ GetTypeDefinition(); TypeInfo^ GetTypeInfo(); + bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; virtual bool TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) override; diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 7e7e5e31c..5f9a8c2ae 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -94,8 +94,8 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args, System::Object^% result) { - auto methodName = binder->Name; +bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::String^ methodName, array^ args, System::Object^% result) { + //auto methodName = binder->Name; auto tempResult = this->Invoke(obj, args); if (tempResult != nullptr && tempResult->QWord != 0) { diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 80634c79d..1c6fd993c 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -13,7 +13,7 @@ public ref class Method { Method(reframework::API::Method* method) : m_method(method) {} REFrameworkNET::InvokeRet^ Invoke(System::Object^ obj, array^ args); - bool HandleInvokeMember_Internal(System::Object^ obj, System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result); + bool HandleInvokeMember_Internal(System::Object^ obj, System::String^ methodName, array^ args, System::Object^% result); /*Void* GetFunctionRaw() { return m_method->get_function_raw(); diff --git a/csharp-api/REFrameworkNET/NativeObject.cpp b/csharp-api/REFrameworkNET/NativeObject.cpp index f6930c9ec..e86672fe9 100644 --- a/csharp-api/REFrameworkNET/NativeObject.cpp +++ b/csharp-api/REFrameworkNET/NativeObject.cpp @@ -21,20 +21,25 @@ InvokeRet^ NativeObject::Invoke(System::String^ methodName, arrayInvoke(this, args); } -bool NativeObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) -{ - auto methodName = binder->Name; - auto method = m_type->FindMethod(methodName); +bool NativeObject::HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result) { + auto t = this->GetTypeDefinition(); + + if (t == nullptr) { + return false; + } - if (method != nullptr) - { - // Re-used with ManagedObject::TryInvokeMember - return method->HandleInvokeMember_Internal(this, binder, args, result); + auto m = t->GetMethod(methodName); + + if (m == nullptr) { + REFrameworkNET::API::LogInfo("Method not found: " + methodName); + return false; } - REFrameworkNET::API::LogInfo("Method not found: " + methodName); + return m->HandleInvokeMember_Internal(this, methodName, args, result); +} - result = nullptr; - return false; +bool NativeObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) +{ + return HandleInvokeMember_Internal(binder->Name, args, result); } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index 46379c477..33fbb46bf 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -38,6 +38,7 @@ public ref class NativeObject : public System::Dynamic::DynamicObject InvokeRet^ Invoke(System::String^ methodName, array^ args); + bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; private: From 5f13e12135bed497661c534bd773d8781c6bd4a4 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 20 Mar 2024 12:25:46 -0700 Subject: [PATCH 027/207] .NET: Load deps from managed/dependencies folder and pass to compiler --- csharp-api/Compiler/Compiler.cs | 16 +++++-- csharp-api/REFrameworkNET/PluginManager.cpp | 52 +++++++++++++++++++-- csharp-api/REFrameworkNET/PluginManager.hpp | 3 +- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/csharp-api/Compiler/Compiler.cs b/csharp-api/Compiler/Compiler.cs index 7674095e2..75307c6a5 100644 --- a/csharp-api/Compiler/Compiler.cs +++ b/csharp-api/Compiler/Compiler.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; +using System.Reflection; using System.Reflection.Metadata; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -11,13 +13,13 @@ namespace REFrameworkNET { public class Compiler { - static public byte[] Compile(string filepath, string fromLocation) + static public byte[] Compile(string filepath, Assembly executingAssembly, List deps) { var sourceCode = File.ReadAllText(filepath); using (var peStream = new MemoryStream()) { - var result = GenerateCode(sourceCode, filepath, fromLocation).Emit(peStream); + var result = GenerateCode(sourceCode, filepath, executingAssembly, deps).Emit(peStream); if (!result.Success) { @@ -41,7 +43,7 @@ static public byte[] Compile(string filepath, string fromLocation) } } - private static CSharpCompilation GenerateCode(string sourceCode, string filePath, string fromLocation) + private static CSharpCompilation GenerateCode(string sourceCode, string filePath, Assembly executingAssembly, List deps) { var codeString = SourceText.From(sourceCode); var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); @@ -58,9 +60,15 @@ private static CSharpCompilation GenerateCode(string sourceCode, string filePath typeof(System.Linq.Enumerable).Assembly.Location, typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location, typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location, - fromLocation, + executingAssembly.Location }; + // Add all the dependencies to the references + foreach (var dep in deps) + { + referencesStr.Add(dep.Location); + } + // Add all the DLLs to the references foreach (var dll in dlls) { diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index b8d007c9e..8495352e0 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -77,6 +77,50 @@ namespace REFrameworkNET { return false; } + System::Collections::Generic::List^ PluginManager::LoadDependencies() { + REFrameworkNET::API::LogInfo("Loading managed dependencies..."); + + const auto dependencies_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies"; + + std::filesystem::create_directories(dependencies_path); + + auto files = System::IO::Directory::GetFiles(gcnew System::String(dependencies_path.wstring().c_str()), "*.dll"); + + auto dependencies_dir = gcnew System::String(dependencies_path.wstring().c_str()); + auto assemblies = gcnew System::Collections::Generic::List(); + + if (files->Length == 0) { + REFrameworkNET::API::LogInfo("No dependencies found in " + dependencies_dir); + return assemblies; + } + + REFrameworkNET::API::LogInfo("Loading dependencies from " + dependencies_dir + "..."); + + for each (System::String^ file in files) { + try { + REFrameworkNET::API::LogInfo("Loading dependency " + file + "..."); + if (auto assem = System::Reflection::Assembly::LoadFrom(file); assem != nullptr) { + assemblies->Add(assem); + REFrameworkNET::API::LogInfo("Loaded " + file); + } + } catch(System::Exception^ e) { + REFrameworkNET::API::LogInfo(e->Message); + // log stack + auto ex = e; + while (ex != nullptr) { + REFrameworkNET::API::LogInfo(ex->StackTrace); + ex = ex->InnerException; + } + } catch(const std::exception& e) { + REFrameworkNET::API::LogInfo(gcnew System::String(e.what())); + } catch(...) { + REFrameworkNET::API::LogInfo("Unknown exception caught while loading dependency " + file); + } + } + + return assemblies; + } + // meant to be executed in the correct context // after loading "ourselves" via System::Reflection::Assembly::LoadFrom bool PluginManager::LoadPlugins(uintptr_t param_raw) try { @@ -97,10 +141,12 @@ namespace REFrameworkNET { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); } + auto deps = LoadDependencies(); // Pre-loads DLLs in the dependencies folder before loading the plugins + // Try-catch because the user might not have the compiler // dependencies in the plugins directory try { - LoadPlugins_FromSourceCode(param_raw); + LoadPlugins_FromSourceCode(param_raw, deps); } catch (System::Exception^ e) { REFrameworkNET::API::LogError("Could not load plugins from source code: " + e->Message); @@ -180,7 +226,7 @@ namespace REFrameworkNET { return false; } - bool PluginManager::LoadPlugins_FromSourceCode(uintptr_t param_raw) try { + bool PluginManager::LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps) try { if (PluginManager::s_api_instance == nullptr) { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); } @@ -210,7 +256,7 @@ namespace REFrameworkNET { // Compile the C# file, and then call a function in it (REFrameworkPlugin.Main) // This is useful for loading C# plugins that don't want to be compiled into a DLL - auto bytecode = REFrameworkNET::Compiler::Compile(file, self->Location); + auto bytecode = REFrameworkNET::Compiler::Compile(file, self, deps); // Dynamically look for DynamicRun.Builder.Compiler.Compile /*auto type = intermediary->GetType("DynamicRun.Builder.Compiler"); if (type == nullptr) { diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 17b211870..a91ff349b 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -29,7 +29,8 @@ private ref class PluginManager // meant to be executed in the correct context // after loading "ourselves" via System::Reflection::Assembly::LoadFrom + static System::Collections::Generic::List^ LoadDependencies(); static bool LoadPlugins(uintptr_t param_raw); - static bool LoadPlugins_FromSourceCode(uintptr_t param_raw); + static bool LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps); }; } \ No newline at end of file From c2f0f95c0231a12a75ee806fc0720f813e1a7b63 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 20 Mar 2024 18:56:01 -0700 Subject: [PATCH 028/207] PluginLoader: Call initialize inside game thread for safety --- src/mods/PluginLoader.cpp | 16 ++++++++-------- src/mods/PluginLoader.hpp | 4 +++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index fab02759e..63784cd1a 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -616,8 +616,7 @@ void PluginLoader::early_init() try { } spdlog::info("[PluginLoader] Loading plugins..."); - - LoadLibraryA("ijwhost.dll"); + // Load all dlls in the plugins directory. for (auto&& entry : fs::directory_iterator{plugin_path}) { auto&& path = entry.path(); @@ -641,7 +640,13 @@ void PluginLoader::early_init() try { spdlog::error("[PluginLoader] Unknown exception during early init"); } -std::optional PluginLoader::on_initialize() { +void PluginLoader::on_frame() { + if (m_plugins_loaded) { + return; + } + + m_plugins_loaded = true; + std::scoped_lock _{m_mux}; // Call reframework_plugin_required_version on any dlls that export it. @@ -659,9 +664,6 @@ std::optional PluginLoader::on_initialize() { reframework::g_renderer_data.device = d3d12->get_device(); reframework::g_renderer_data.swapchain = d3d12->get_swap_chain(); reframework::g_renderer_data.command_queue = d3d12->get_command_queue(); - } else { - spdlog::error("[PluginLoader] Unsupported renderer type {}", reframework::g_renderer_data.renderer_type); - return "PluginLoader: Unsupported renderer type detected"; } verify_sdk_pointers(); @@ -755,8 +757,6 @@ std::optional PluginLoader::on_initialize() { ++it; } - - return std::nullopt; } void PluginLoader::on_draw_ui() { diff --git a/src/mods/PluginLoader.hpp b/src/mods/PluginLoader.hpp index 5df1580c9..cc5e2a2b8 100644 --- a/src/mods/PluginLoader.hpp +++ b/src/mods/PluginLoader.hpp @@ -35,10 +35,12 @@ class PluginLoader : public Mod { void early_init(); std::string_view get_name() const override { return "PluginLoader"; } - std::optional on_initialize() override; + void on_frame() override; void on_draw_ui() override; private: + bool m_plugins_loaded{false}; + std::mutex m_mux{}; std::map m_plugins{}; std::map m_plugin_load_errors{}; From a2737d0edf635f5adf4ab6ebed1320d7ff1291e5 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 20 Mar 2024 21:40:34 -0700 Subject: [PATCH 029/207] .NET: Migrate project to its own folder caused annoying nuget errors with the main project otherwise. --- .gitignore | 1 + CMakeLists.txt | 179 ------- cmake.toml | 75 +-- .../AssemblyGenerator/ClassGenerator.cs | 331 ++++++++++++ csharp-api/AssemblyGenerator/Generator.cs | 504 ++++++++++++++++++ .../AssemblyGenerator/SyntaxTreeBuilder.cs | 33 ++ csharp-api/CMakeLists.txt | 273 ++++++++++ csharp-api/REFrameworkNET/AssemblyInfo.cpp | 14 + csharp-api/{ => REFrameworkNET}/Plugin.cpp | 4 +- csharp-api/{ => REFrameworkNET}/Plugin.hpp | 0 csharp-api/REFrameworkNET/PluginManager.cpp | 4 +- csharp-api/cmake.toml | 139 +++++ csharp-api/cmkr.cmake | 253 +++++++++ 13 files changed, 1554 insertions(+), 256 deletions(-) create mode 100644 csharp-api/AssemblyGenerator/ClassGenerator.cs create mode 100644 csharp-api/AssemblyGenerator/Generator.cs create mode 100644 csharp-api/AssemblyGenerator/SyntaxTreeBuilder.cs create mode 100644 csharp-api/CMakeLists.txt create mode 100644 csharp-api/REFrameworkNET/AssemblyInfo.cpp rename csharp-api/{ => REFrameworkNET}/Plugin.cpp (91%) rename csharp-api/{ => REFrameworkNET}/Plugin.hpp (100%) create mode 100644 csharp-api/cmake.toml create mode 100644 csharp-api/cmkr.cmake diff --git a/.gitignore b/.gitignore index 8a8086b7e..671dc6203 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ build_vs2019_devmode_DMC5.bat src/CommitHash.autogenerated csharp-api/test/Test/bin csharp-api/test/Test/obj +csharp-api/build/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ac4238b3..ef41b8374 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13122,182 +13122,3 @@ if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework unset(CMKR_SOURCES) endif() -# Target REFCSharpCompiler -if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework - set(CMKR_TARGET REFCSharpCompiler) - set(REFCSharpCompiler_SOURCES "") - - list(APPEND REFCSharpCompiler_SOURCES - "csharp-api/Compiler/Compiler.cs" - ) - - list(APPEND REFCSharpCompiler_SOURCES - cmake.toml - ) - - set(CMKR_SOURCES ${REFCSharpCompiler_SOURCES}) - add_library(REFCSharpCompiler SHARED) - - if(REFCSharpCompiler_SOURCES) - target_sources(REFCSharpCompiler PRIVATE ${REFCSharpCompiler_SOURCES}) - endif() - - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${REFCSharpCompiler_SOURCES}) - - set_target_properties(REFCSharpCompiler PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_RELEASE - "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO - "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE - "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" - DOTNET_SDK - Microsoft.NET.Sdk - DOTNET_TARGET_FRAMEWORK - net8.0 - VS_CONFIGURATION_TYPE - ClassLibrary - CMAKE_CSharp_FLAGS - "/langversion:latest /platform:x64" - ) - - set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.CodeAnalysis_4.9.2") - - unset(CMKR_TARGET) - unset(CMKR_SOURCES) -endif() - -# Target csharp-api -if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework - set(CMKR_TARGET csharp-api) - set(csharp-api_SOURCES "") - - list(APPEND csharp-api_SOURCES - "csharp-api/Plugin.cpp" - "csharp-api/REFrameworkNET/API.cpp" - "csharp-api/REFrameworkNET/Callbacks.cpp" - "csharp-api/REFrameworkNET/ManagedObject.cpp" - "csharp-api/REFrameworkNET/Method.cpp" - "csharp-api/REFrameworkNET/NativeObject.cpp" - "csharp-api/REFrameworkNET/PluginManager.cpp" - "csharp-api/REFrameworkNET/TDB.cpp" - "csharp-api/REFrameworkNET/TypeDefinition.cpp" - ) - - list(APPEND csharp-api_SOURCES - cmake.toml - ) - - set(CMKR_SOURCES ${csharp-api_SOURCES}) - add_library(csharp-api SHARED) - - if(csharp-api_SOURCES) - target_sources(csharp-api PRIVATE ${csharp-api_SOURCES}) - endif() - - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${csharp-api_SOURCES}) - - target_compile_features(csharp-api PUBLIC - cxx_std_20 - ) - - target_compile_options(csharp-api PUBLIC - "/EHa" - "/MD" - ) - - target_include_directories(csharp-api PUBLIC - "include/" - ) - - target_link_libraries(csharp-api PUBLIC - REFCSharpCompiler - ) - - set_target_properties(csharp-api PROPERTIES - OUTPUT_NAME - REFramework.NET - RUNTIME_OUTPUT_DIRECTORY_RELEASE - "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO - "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE - "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" - COMMON_LANGUAGE_RUNTIME - netcore - DOTNET_TARGET_FRAMEWORK - net8.0 - VS_GLOBAL_EnableManagedPackageReferenceSupport - true - ) - - # Copy nuget packages to the output directory - add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ - COMMENT "Copying nuget packages" - ) - add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ - COMMENT "Copying nuget packages" - ) - set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/REFramework.NET.dll") - - unset(CMKR_TARGET) - unset(CMKR_SOURCES) -endif() - -# Target CSharpAPITest -if(REF_BUILD_FRAMEWORK AND CMAKE_SIZEOF_VOID_P EQUAL 8) # build-framework - set(CMKR_TARGET CSharpAPITest) - set(CSharpAPITest_SOURCES "") - - list(APPEND CSharpAPITest_SOURCES - "csharp-api/test/Test/Test.cs" - ) - - list(APPEND CSharpAPITest_SOURCES - cmake.toml - ) - - set(CMKR_SOURCES ${CSharpAPITest_SOURCES}) - add_library(CSharpAPITest SHARED) - - if(CSharpAPITest_SOURCES) - target_sources(CSharpAPITest PRIVATE ${CSharpAPITest_SOURCES}) - endif() - - source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITest_SOURCES}) - - target_link_libraries(CSharpAPITest PUBLIC - csharp-api - ) - - set_target_properties(CSharpAPITest PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_RELEASE - "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO - "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE - "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" - DOTNET_SDK - Microsoft.NET.Sdk - DOTNET_TARGET_FRAMEWORK - net8.0 - VS_CONFIGURATION_TYPE - ClassLibrary - CMAKE_CSharp_FLAGS - "/langversion:latest /platform:x64" - ) - - set_target_properties(CSharpAPITest PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET - "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" - ) - set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET") - - unset(CMKR_TARGET) - unset(CMKR_SOURCES) -endif() - diff --git a/cmake.toml b/cmake.toml index bc2cd90e4..781a0ef49 100644 --- a/cmake.toml +++ b/cmake.toml @@ -267,6 +267,7 @@ LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ARCHIVE_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" +# VS_GLOBAL_ResolveNuGetPackages = "false" [target.RE2SDK] type = "sdk" @@ -391,77 +392,3 @@ link-libraries = [ [target.weapon_stay_big_plugin] type = "plugin" sources = ["examples/weapon_stay_big_plugin/weapon_stay_big.cpp"] - -[template.CSharpSharedTarget] -type = "shared" - -[template.CSharpSharedTarget.properties] -RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" -RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" -LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" -DOTNET_SDK = "Microsoft.NET.Sdk" -DOTNET_TARGET_FRAMEWORK = "net8.0" -VS_CONFIGURATION_TYPE = "ClassLibrary" -CMAKE_CSharp_FLAGS = "/langversion:latest /platform:x64" - -[target.REFCSharpCompiler] -type = "CSharpSharedTarget" -condition = "build-framework" -sources = ["csharp-api/Compiler/Compiler.cs"] - -# Not using .properties here because it overrides the template properties for whatever reason -cmake-after = """ -set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.CodeAnalysis_4.9.2") -""" - -[target.csharp-api] -type = "shared" -include-directories = ["include/"] -sources = ["csharp-api/**.cpp", "csharp-api/**.c"] -compile-features = ["cxx_std_20"] -compile-options = ["/EHa", "/MD"] -condition = "build-framework" -link-libraries = [ - "REFCSharpCompiler" -] -cmake-after = """ -# Copy nuget packages to the output directory -add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ - COMMENT "Copying nuget packages" -) -add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/ - COMMENT "Copying nuget packages" -) -set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}/REFramework.NET.dll") -""" - -[target.csharp-api.properties] -OUTPUT_NAME = "REFramework.NET" -RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" -RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/${CMKR_TARGET}" -LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/${CMKR_TARGET}" -COMMON_LANGUAGE_RUNTIME = "netcore" -DOTNET_TARGET_FRAMEWORK = "net8.0" -# DOTNET_TARGET_FRAMEWORK_VERSION = "net8.0" -VS_GLOBAL_EnableManagedPackageReferenceSupport = "true" -# VS_PACKAGE_REFERENCES = "Microsoft.CodeAnalysis_4.9.2" - -[target.CSharpAPITest] -type = "CSharpSharedTarget" -condition = "build-framework" -sources = ["csharp-api/test/Test/**.cs"] -link-libraries = [ - "csharp-api" -] - -cmake-after = """ -set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET -"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" -) -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET") -""" \ No newline at end of file diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs new file mode 100644 index 000000000..dbe3b4b10 --- /dev/null +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -0,0 +1,331 @@ +#nullable enable + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Reflection; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Emit; +using System.IO; +using System.Dynamic; +using System.Security.Cryptography; +using System.Linq; + + +public class ClassGenerator { + private string className; + private Il2CppDump.Type t; + private Il2CppDump.Method[] methods = []; + public List usingTypes = []; + private TypeDeclarationSyntax? typeDeclaration; + + public TypeDeclarationSyntax? TypeDeclaration { + get { + return typeDeclaration; + } + } + + public void Update(TypeDeclarationSyntax? typeDeclaration_) { + typeDeclaration = typeDeclaration_; + } + + public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] methods_) { + className = className_; + t = t_; + methods = methods_; + + typeDeclaration = Generate(); + } + + private TypeDeclarationSyntax? Generate() { + usingTypes = []; + + SortedSet invalidMethodNames = new SortedSet { + "Finalize", + "MemberwiseClone", + "ToString", + "Equals", + "GetHashCode", + "GetType", + ".ctor", + ".cctor", + "op_Implicit", + "op_Explicit", + /*"op_Addition", + "op_Subtraction", + "op_Multiply", + "op_Division", + "op_Modulus", + "op_BitwiseAnd", + "op_BitwiseOr", + "op_ExclusiveOr",*/ + + }; + + var ogClassName = new string(className); + + // Pull out the last part of the class name (split '.' till last) + if (t.DeclaringType == null) { + className = className.Split('.').Last(); + } + + bool wantsAbstractInstead = false; + + for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { + if (parent.Name == null) { + break; + } + + if (parent.Name == "") { + break; + } + + if (parent.Name == "System.Attribute") { + wantsAbstractInstead = true; + break; + } + } + + if (wantsAbstractInstead) { + typeDeclaration = SyntaxFactory + .ClassDeclaration(className) + .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.AbstractKeyword)}); + } else { + typeDeclaration = SyntaxFactory + .InterfaceDeclaration(className) + .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}); + } + + if (typeDeclaration == null) { + return null; + } + + typeDeclaration = typeDeclaration + .AddMembers(methods.Where(method => method.Name != null && !invalidMethodNames.Contains(method.FriendlyName??method.Name) && !method.Name.Contains('<')).Select(method => { + TypeSyntax? returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); + + if (method.Returns != null && method.Returns.Type != "System.Void" && method.Returns.Type != "") { + // Check for easily convertible types like System.Single, System.Int32, etc. + switch (method.Returns.Type) { + case "System.Single": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword)); + break; + case "System.Double": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)); + break; + case "System.Int32": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)); + break; + case "System.UInt32": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UIntKeyword)); + break; + case "System.Int64": + case "System.IntPtr": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword)); + break; + case "System.UInt64": + case "System.UIntPtr": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ULongKeyword)); + break; + case "System.Boolean": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)); + break; + case "System.String": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)); + break; + case "via.clr.ManagedObject": + case "System.Object": + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + default: + if (method.Returns != null && method.Returns.Type != null && method.Returns.Type != "") { + if (!Program.validTypes.Contains(method.Returns.Type)) { + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + if (method.Returns.Type.Contains('<') || method.Returns.Type.Contains('[')) { + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + if (method.Returns.Type.StartsWith("System.") || !method.Returns.Type.StartsWith("via.")) { + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + if (Program.Dump == null) { + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + var returnName = method.Returns.Type; + /*var returnTypeNamespace = Program.ExtractNamespaceFromTypeName(Program.Dump, returnName); + var typeNamespace = Program.ExtractNamespaceFromTypeName(Program.Dump, ogClassName); + + if (returnTypeNamespace == typeNamespace) { + returnName = returnName.Split('.').Last(); + }*/ + + returnType = SyntaxFactory.ParseTypeName(returnName); + break; + } + + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + } + + var flags = method.Flags?.Split(" | "); + var methodName = new string(method.FriendlyName); + + if (method.Override != null && method.Override == true) { + methodName += "_" + className.Replace('.', '_'); + } + + var methodDeclaration = SyntaxFactory.MethodDeclaration(returnType, methodName ?? "UnknownMethod") + .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) + /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; + + if (wantsAbstractInstead) { + methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); + } + + if (method.Params != null) { + methodDeclaration = methodDeclaration.AddParameterListParameters(method.Params.Where(param => param != null && param.Type != null && param.Name != null).Select(param => { + return SyntaxFactory.Parameter(SyntaxFactory.Identifier(param.Name ?? "UnknownParam")) + .WithType(SyntaxFactory.ParseTypeName(/*param.Type ??*/ "object")); + }).ToArray()); + } + + // find "Virtual" flag + //if (flags != null && flags.Contains("Virtual")) { + //methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); + + if (method.Override != null && method.Override == true) { + //methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); + + } + //} + + return methodDeclaration; + }).ToArray()); + + if (!className.Contains('[') && !className.Contains('<')) { + List baseTypes = new List(); + + SortedSet badBaseTypes = new SortedSet { + "System.Object", + "System.ValueType", + "System.Enum", + "System.Delegate", + "System.MulticastDelegate" + }; + + for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { + if (parent.Name == null) { + break; + } + + if (parent.Name == "") { + break; + } + + if (parent.Name.Contains('[') || parent.Name.Contains('<')) { + break; + } + + if (badBaseTypes.Contains(parent.Name)) { + break; + } + + baseTypes.Add(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(parent.Name))); + usingTypes.Add(parent); + break; + } + + if (baseTypes.Count > 0 && typeDeclaration != null) { + if (wantsAbstractInstead && typeDeclaration is ClassDeclarationSyntax) { + typeDeclaration = (typeDeclaration as ClassDeclarationSyntax)?.AddBaseListTypes(baseTypes.ToArray()); + } else if (typeDeclaration is InterfaceDeclarationSyntax) { + typeDeclaration = (typeDeclaration as InterfaceDeclarationSyntax)?.AddBaseListTypes(baseTypes.ToArray()); + } + } + } + + return GenerateNestedTypes(); + } + + private TypeDeclarationSyntax? GenerateNestedTypes() { + if (this.typeDeclaration == null) { + return null; + } + + foreach (var nestedT in t.NestedTypes ?? []) { + var nestedTypeName = nestedT.Name ?? ""; + + if (nestedTypeName == "") { + continue; + } + + if (nestedTypeName.Contains("[") || nestedTypeName.Contains("]") || nestedTypeName.Contains('<')) { + continue; + } + + if (nestedTypeName.Contains("WrappedArrayContainer")) { + continue; + } + + if (nestedTypeName.Split('.').Last() == "file") { + nestedTypeName = nestedTypeName.Replace("file", "@file"); + } + + var fixedNestedMethods = nestedT.Methods? + .Select(methodPair => { + var method = methodPair.Value; + var methodName = Il2CppDump.StripMethodName(method); + return (methodName, method); + }) + .GroupBy(pair => pair.methodName) + .Select(group => group.First()) // Selects the first method of each group + .ToDictionary(pair => pair.methodName, pair => pair.method); + + var nestedGenerator = new ClassGenerator(nestedTypeName.Split('.').Last(), + nestedT, + fixedNestedMethods != null ? [.. fixedNestedMethods.Values] : [] + ); + + if (nestedGenerator.TypeDeclaration == null) { + continue; + } + + var isolatedNestedName = nestedTypeName.Split('.').Last(); + + bool addedNew = false; + // Add the "new" keyword if this nested type is anywhere in the hierarchy + for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { + if (addedNew) { + break; + } + + // Look for same named nested types + if (parent.NestedTypes != null) { + foreach (var parentNested in parent.NestedTypes) { + var isolatedParentNestedName = parentNested.Name?.Split('.').Last(); + if (isolatedParentNestedName == isolatedNestedName) { + nestedGenerator.Update(nestedGenerator.TypeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword))); + addedNew = true; + break; + } + } + } + } + + if (nestedGenerator.TypeDeclaration != null) { + this.Update(this.typeDeclaration.AddMembers(nestedGenerator.TypeDeclaration)); + } + } + + return typeDeclaration; + } +} \ No newline at end of file diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs new file mode 100644 index 000000000..a45f1d3fe --- /dev/null +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -0,0 +1,504 @@ +#nullable enable + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Reflection; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Emit; +using System.IO; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Text.Json.Serialization; +using System; +using System.Linq; + +public class Il2CppDump { + class Field { + + }; + + public class Method { + public class ReturnValueClass { + [JsonPropertyName("type")] + public string? Type { get; set;} + [JsonPropertyName("name")] + public string? Name { get; set;} + }; + + public class Parameter { + [JsonPropertyName("name")] + public string? Name { get; set;} + [JsonPropertyName("type")] + public string? Type { get; set;} + }; + + [JsonPropertyName("flags")] + public string? Flags { get; set;} + + [JsonPropertyName("function")] + public string? Function { get; set;} + + [JsonPropertyName("id")] + public int? ID { get; set;} + [JsonPropertyName("invoke_id")] + public int? InvokeID { get; set;} + [JsonPropertyName("impl_flags")] + public string? ImplFlags { get; set;} + [JsonPropertyName("returns")] + public ReturnValueClass? Returns { get; set;} + [JsonPropertyName("params")] + public List? Params { get; set;} + + public string? Name { get; set;} // Not from JSON + public string? FriendlyName { get; set;} // Not from JSON + public bool? Override { get; set;} // Not from JSON + } + + public class Type { + [JsonPropertyName("address")] + public string? Address { get; set;} + [JsonPropertyName("fqn")] + public string? FQN { get; set;} + [JsonPropertyName("flags")] + public string? Flags { get; set;} + [JsonPropertyName("crc")] + public string? CRC { get; set;} + [JsonPropertyName("id")] + public int? ID { get; set;} + [JsonPropertyName("is_generic_type")] + public bool? IsGenericType { get; set;} + [JsonPropertyName("is_generic_type_definition")] + public bool? IsGenericTypeDefinition { get; set;} + + [JsonPropertyName("parent")] + public string? Parent { get; set;} + [JsonPropertyName("declaring_type")] + public string? DeclaringTypeName { get; set;} + [JsonPropertyName("methods")] + public Dictionary? Methods { get; set;} + + public Dictionary? StrippedMethods { get; set;} // Not from JSON + + [JsonPropertyName("name_hierarchy")] + public List? NameHierarchy { get; set;} + + // Post processed parent type + public Il2CppDump.Type? ParentType { get; set;} + public Il2CppDump.Type? DeclaringType { get; set;} + public string? Name { get; set;} // Not from JSON + public List? NestedTypes { get; set;} + }; + + public Dictionary? Types; + + public static string StripMethodName(Method method) { + var methodName = method.Name ?? ""; + var methodID = method.ID ?? -1; + + var methodIdStr = methodID.ToString(); + var methodIdIndex = methodName.LastIndexOf(methodIdStr); + if (methodIdIndex != -1) { + return methodName[..methodIdIndex]; + } + + return methodName; + } + + // For adding additional context to the types + public static Dictionary? PostProcessTypes(Dictionary? types) { + if (types == null) { + return null; + } + + foreach (var typePair in types) { + var typeName = typePair.Key; + var type = typePair.Value; + + if (type.Parent != null && types.TryGetValue(type.Parent, out var parentType)) { + type.ParentType = parentType; + } + + if (type.DeclaringTypeName != null && types.TryGetValue(type.DeclaringTypeName, out var declaringType)) { + type.DeclaringType = declaringType; + + if (declaringType.NestedTypes == null) { + declaringType.NestedTypes = []; + } + + declaringType.NestedTypes.Add(type); + } + + type.Name = typeName; + + foreach (var methodPair in type.Methods ?? []) { + if (methodPair.Value.Name == null) { + methodPair.Value.Name = methodPair.Key; + } + + var methodName = StripMethodName(methodPair.Value); + + methodPair.Value.FriendlyName = methodName; + + if (typeName.Contains("ConvolutionReverbParameters") || typeName.Contains("via.audiorender.EffectParametersBase")) { + System.Console.WriteLine("Method: " + methodName); + } + + if (type.StrippedMethods == null) { + type.StrippedMethods = []; + } + + type.StrippedMethods[methodName] = methodPair.Value; + } + } + + foreach (var typePair in types) { + var typeName = typePair.Key; + var type = typePair.Value; + + foreach (var methodPair in type.Methods ?? []) { + var methodName = methodPair.Key; + var method = methodPair.Value; + + var flags = method.Flags?.Split(" | "); + + //if (flags != null && flags.Contains("Virtual")) { + for (var parent = type.ParentType; parent != null; parent = parent.ParentType) { + if (parent.StrippedMethods != null && parent.StrippedMethods.TryGetValue(method.FriendlyName?? "", out var parentMethod)) { + method.Override = true; + break; + } + + if (method.Override?? false) { + break; + } + } + //} + } + } + + return types; + } +} + +public class SnakeCaseToPascalCaseNamingPolicy : JsonNamingPolicy +{ + public override string ConvertName(string name) + { + // Convert snake_case to PascalCase + return Regex.Replace(name, @"_(.)", match => match.Groups[1].Value.ToUpper()); + } +} + +public class Program { + static Dictionary namespaces = []; + + // Start with an empty CompilationUnitSyntax (represents an empty file) + static CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit(); + + static public NamespaceDeclarationSyntax? ExtractNamespaceFromTypeName(Il2CppDump context, string typeName) { + if (context == null || context.Types == null) { + return null; + } + + var parts = typeName.Split('.'); + var currentTypeName = ""; + + NamespaceDeclarationSyntax? currentNamespaceDecl = null; + string currentNamespaceName = ""; + + for (var i = 0; i < parts.Length; i++) { + var part = parts[i]; + currentTypeName += part; + + if (context.Types.TryGetValue(currentTypeName, out var value_)) { + // Return a blank namespace + if (currentNamespaceDecl == null) { + System.Console.WriteLine("Creating blank namespace for " + currentTypeName); + currentNamespaceDecl = SyntaxTreeBuilder.CreateNamespace(""); + } + + return currentNamespaceDecl; + } + + currentNamespaceName += part; + + // Create via namespace in list of namespaces if not exist + if (!namespaces.TryGetValue(currentTypeName, out NamespaceDeclarationSyntax? value)) { + // Clean up the namespace name, remove any non-compliant characters other than "." and alphanumerics + currentNamespaceName = Regex.Replace(currentTypeName, @"[^a-zA-Z0-9.]", "_"); + + Console.WriteLine("Creating namespace " + currentNamespaceName); + value = SyntaxTreeBuilder.CreateNamespace(currentNamespaceName); + namespaces[currentNamespaceName] = value; + currentNamespaceDecl = value; + } else { + currentNamespaceDecl = value; + } + + currentTypeName += "."; + } + + return currentNamespaceDecl; + } + + public static SortedSet validTypes = []; + public static SortedSet generatedTypes = []; + + static void FillValidEntries(Il2CppDump context) { + if (validTypes.Count > 0) { + return; + } + + foreach (var typePair in context.Types ?? []) { + var typeName = typePair.Key; + var t = typePair.Value; + + if (typeName.Length == 0) { + Console.WriteLine("Bad type name"); + continue; + } + + if (typeName.Contains("WrappedArrayContainer")) { + continue; + } + + if (typeName.Contains("[") || typeName.Contains("]") || typeName.Contains('<')) { + continue; + } + + // Skip system types + if (typeName.StartsWith("System.")) { + continue; + } + + validTypes.Add(typeName); + } + } + + static CompilationUnitSyntax MakeFromTypeEntry(Il2CppDump context, string typeName, Il2CppDump.Type? t) { + FillValidEntries(context); + + if (!validTypes.Contains(typeName)) { + return compilationUnit; + } + + if (t == null) { + Console.WriteLine("Failed to find type"); + return compilationUnit; + } + + if (t.DeclaringType != null) { + //MakeFromTypeEntry(context, t.DeclaringType.Name ?? "", t.DeclaringType); + return compilationUnit; // We want to define it inside of its declaring type, not a second time + } + + if (generatedTypes.Contains(typeName)) { + //Console.WriteLine("Skipping already generated type " + typeName); + return compilationUnit; + } + + generatedTypes.Add(typeName); + + // Generate starting from topmost parent first + if (t.ParentType != null) { + MakeFromTypeEntry(context, t.ParentType.Name ?? "", t.ParentType); + } + + var methods = t.Methods; + var fixedMethods = methods? + .Select(methodPair => { + var method = methodPair.Value; + var methodName = Il2CppDump.StripMethodName(method); + return (methodName, method); + }) + .GroupBy(pair => pair.methodName) + .Select(group => group.First()) // Selects the first method of each group + .ToDictionary(pair => pair.methodName, pair => pair.method); + + foreach (var methodPair in fixedMethods ?? []) { + methodPair.Value.Name = methodPair.Key; + } + + var generator = new ClassGenerator( + typeName.Split('.').Last() == "file" ? typeName.Replace("file", "@file") : typeName, + t, + fixedMethods != null ? [.. fixedMethods.Values] : [] + ); + + if (generator.TypeDeclaration == null) { + return compilationUnit; + } + + var generatedNamespace = ExtractNamespaceFromTypeName(context, typeName); + + if (generatedNamespace != null) { + // Split the using types by their namespace + /*foreach(var ut in usingTypes) { + var ns = ExtractNamespaceFromTypeName(context, ut.Name ?? ""); + + if (ns != null) { + generatedNamespace = generatedNamespace.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ut.Name ?? ""))); + } + }*/ + + var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, generator.TypeDeclaration); + + /*compilationUnit = compilationUnit.AddUsings(usingTypes.Select( + type => { + var ret = SyntaxFactory.UsingDirective (SyntaxFactory.ParseName(type.Name ?? "")); + System.Console.WriteLine(ret.GetText()); + + return ret; + } + ).ToArray());*/ + + compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); + } else { + Console.WriteLine("Failed to create namespace for " + typeName); + } + + return compilationUnit; + } + + private static Il2CppDump? dump; + public static Il2CppDump? Dump { + get { + return dump; + } + } + + static void Main(string[] args) { + if (args.Length == 0) { + Console.WriteLine("Usage: dotnet run "); + return; + } + + var il2cpp_dump_json = args[0]; + + if (il2cpp_dump_json == null) { + Console.WriteLine("Usage: dotnet run "); + return; + } + + Console.WriteLine(args[0]); + + List compilationUnits = new List(); + + // Open a JSON file + using (var jsonFile = File.OpenRead(il2cpp_dump_json)) + { + var options = new JsonSerializerOptions + { + PropertyNamingPolicy = new SnakeCaseToPascalCaseNamingPolicy(), + }; + + dump = new Il2CppDump + { + Types = Il2CppDump.PostProcessTypes(JsonSerializer.Deserialize>(jsonFile, options)) + }; + + if (dump != null && dump.Types != null) { + // Get via.SceneManager type + /*var sceneManager = dump.Types?["via.SceneManager"]; + + MakeFromTypeEntry(sceneManager);*/ + + // Look for any types that start with via.* + foreach (var typePair in dump.Types ?? []) { + var typeName = typePair.Key; + var type = typePair.Value; + + if (typeName.StartsWith("via.")) { + var compilationUnit = MakeFromTypeEntry(dump, typeName, type); + compilationUnits.Add(compilationUnit); + } + } + } else { + Console.WriteLine("Failed to parse JSON"); + } + } + + System.Console.WriteLine(compilationUnits[0].NormalizeWhitespace().ToFullString()); + + + /*List syntaxTrees = new List(); + + foreach (var cu in compilationUnits) { + syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu)); + }*/ + + var normalized = compilationUnit.NormalizeWhitespace(); + + // Dump to DynamicAssembly.cs + File.WriteAllText("DynamicAssembly.cs", normalized.ToFullString()); + + var syntaxTrees = SyntaxFactory.SyntaxTree(normalized, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12)); + + string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); + + // get all DLLs in that directory + var dlls = assemblyPath != null? Directory.GetFiles(assemblyPath, "*.dll") : []; + + var systemRuntimePath = dlls.FirstOrDefault(dll => dll.ToLower().EndsWith("system.runtime.dll")); + + var references = new List { + MetadataReference.CreateFromFile(typeof(object).Assembly.Location), + MetadataReference.CreateFromFile(typeof(void).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.NotImplementedException).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location), + }; + + if (systemRuntimePath != null) { + System.Console.WriteLine("Adding System.Runtime from " + systemRuntimePath); + references.Add(MetadataReference.CreateFromFile(systemRuntimePath)); + } + + compilationUnit = compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))); + + var csoptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, + optimizationLevel: OptimizationLevel.Release, + assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default, + platform: Platform.X64); + // Create a compilation + var compilation = CSharpCompilation.Create("DynamicAssembly") + .WithOptions(csoptions) + .AddReferences(references) + .AddSyntaxTrees(syntaxTrees); + + // Emit the assembly to a stream (in-memory assembly) + using (var ms = new MemoryStream()) + { + var result = compilation.Emit(ms); + + if (!result.Success) + { + var textLines = syntaxTrees.GetText().Lines; + List sortedDiagnostics = result.Diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToList(); + sortedDiagnostics.Reverse(); + + foreach (Diagnostic diagnostic in sortedDiagnostics) + { + Console.WriteLine($"{diagnostic.Id}: {diagnostic.GetMessage()}"); + + var lineSpan = diagnostic.Location.GetLineSpan(); + var errorLineNumber = lineSpan.StartLinePosition.Line; + var errorLineText = textLines[errorLineNumber].ToString(); + Console.WriteLine($"Error in line {errorLineNumber + 1}: {errorLineText}"); + } + } + else + { + // Load and use the compiled assembly + ms.Seek(0, SeekOrigin.Begin); + var assembly = Assembly.Load(ms.ToArray()); + + // dump to file + File.WriteAllBytes("DynamicAssembly.dll", ms.ToArray()); + } + } + + } +}; \ No newline at end of file diff --git a/csharp-api/AssemblyGenerator/SyntaxTreeBuilder.cs b/csharp-api/AssemblyGenerator/SyntaxTreeBuilder.cs new file mode 100644 index 000000000..392937d7e --- /dev/null +++ b/csharp-api/AssemblyGenerator/SyntaxTreeBuilder.cs @@ -0,0 +1,33 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Collections.Generic; + +public static class SyntaxTreeBuilder { + public static NamespaceDeclarationSyntax CreateNamespace(string namespaceName) { + return SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(namespaceName)).NormalizeWhitespace(); + } + + public static ClassDeclarationSyntax CreateClass(string className) { + return SyntaxFactory.ClassDeclaration(className) + .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + } + + public static MethodDeclarationSyntax CreateMethod(string methodName) { + return SyntaxFactory.MethodDeclaration(SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)), methodName) + .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)) + .WithBody(SyntaxFactory.Block()); + } + + public static CompilationUnitSyntax AddMembersToCompilationUnit(CompilationUnitSyntax compilationUnit, params MemberDeclarationSyntax[] members) { + return compilationUnit.AddMembers(members); + } + + public static NamespaceDeclarationSyntax AddMembersToNamespace(NamespaceDeclarationSyntax namespaceDeclaration, params MemberDeclarationSyntax[] members) { + return namespaceDeclaration.AddMembers(members); + } + + public static ClassDeclarationSyntax AddMethodsToClass(ClassDeclarationSyntax classDeclaration, params MethodDeclarationSyntax[] methods) { + return classDeclaration.AddMembers(methods); + } +} diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt new file mode 100644 index 000000000..0fe97f4cb --- /dev/null +++ b/csharp-api/CMakeLists.txt @@ -0,0 +1,273 @@ +# This file is automatically generated from cmake.toml - DO NOT EDIT +# See https://github.com/build-cpp/cmkr for more information + +cmake_minimum_required(VERSION 3.15) + +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) + message(FATAL_ERROR "In-tree builds are not supported. Run CMake from a separate directory: cmake -B build") +endif() + +set(CMKR_ROOT_PROJECT OFF) +if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(CMKR_ROOT_PROJECT ON) + + # Bootstrap cmkr and automatically regenerate CMakeLists.txt + include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT) + if(CMKR_INCLUDE_RESULT) + cmkr() + endif() + + # Enable folder support + set_property(GLOBAL PROPERTY USE_FOLDERS ON) + + # Create a configure-time dependency on cmake.toml to improve IDE support + configure_file(cmake.toml cmake.toml COPYONLY) +endif() + +project(CSharpAPIProject + LANGUAGES + CXX + C + CSharp +) + +include(CSharpUtilities) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + +# Disable exceptions +# string(REGEX REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + +set(ASMJIT_STATIC ON CACHE BOOL "" FORCE) +set(DYNAMIC_LOADER ON CACHE BOOL "" FORCE) # OpenXR +set(BUILD_TOOLS OFF CACHE BOOL "" FORCE) # DirectXTK + +if ("${CMAKE_BUILD_TYPE}" MATCHES "Release") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT") + + # Statically compile runtime + string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + + message(NOTICE "Building in Release mode") +endif() + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +set(NUGET_PACKAGES_DIR "$ENV{NUGET_PACKAGES}") + +# If NUGET_PACKAGES_DIR is not set, fall back to the default location +if(NOT NUGET_PACKAGES_DIR) + if(WIN32) + set(DEFAULT_NUGET_PATH "$ENV{USERPROFILE}/.nuget/packages") + else() + set(DEFAULT_NUGET_PATH "$ENV{HOME}/.nuget/packages") + endif() + set(NUGET_PACKAGES_DIR ${DEFAULT_NUGET_PATH}) +endif() + +# Target: REFCSharpCompiler +set(REFCSharpCompiler_SOURCES + "Compiler/Compiler.cs" + cmake.toml +) + +add_library(REFCSharpCompiler SHARED) + +target_sources(REFCSharpCompiler PRIVATE ${REFCSharpCompiler_SOURCES}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${REFCSharpCompiler_SOURCES}) + +set_target_properties(REFCSharpCompiler PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary + CMAKE_CSharp_FLAGS + "/langversion:latest /platform:x64" +) + +set(CMKR_TARGET REFCSharpCompiler) +set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.CodeAnalysis_4.9.2") + +# Target: csharp-api +set(csharp-api_SOURCES + "REFrameworkNET/API.cpp" + "REFrameworkNET/AssemblyInfo.cpp" + "REFrameworkNET/Callbacks.cpp" + "REFrameworkNET/ManagedObject.cpp" + "REFrameworkNET/Method.cpp" + "REFrameworkNET/NativeObject.cpp" + "REFrameworkNET/Plugin.cpp" + "REFrameworkNET/PluginManager.cpp" + "REFrameworkNET/TDB.cpp" + "REFrameworkNET/TypeDefinition.cpp" + "REFrameworkNET/API.hpp" + "REFrameworkNET/Callbacks.hpp" + "REFrameworkNET/Field.hpp" + "REFrameworkNET/InvokeRet.hpp" + "REFrameworkNET/ManagedObject.hpp" + "REFrameworkNET/ManagedSingleton.hpp" + "REFrameworkNET/Method.hpp" + "REFrameworkNET/MethodParameter.hpp" + "REFrameworkNET/NativeObject.hpp" + "REFrameworkNET/Plugin.hpp" + "REFrameworkNET/PluginManager.hpp" + "REFrameworkNET/Property.hpp" + "REFrameworkNET/TDB.hpp" + "REFrameworkNET/TypeDefinition.hpp" + "REFrameworkNET/TypeInfo.hpp" + "REFrameworkNET/Utility.hpp" + cmake.toml +) + +add_library(csharp-api SHARED) + +target_sources(csharp-api PRIVATE ${csharp-api_SOURCES}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${csharp-api_SOURCES}) + +target_compile_features(csharp-api PUBLIC + cxx_std_20 +) + +target_compile_options(csharp-api PUBLIC + "/EHa" + "/MD" +) + +target_include_directories(csharp-api PUBLIC + "../include/" +) + +target_link_libraries(csharp-api PUBLIC + REFCSharpCompiler +) + +set_target_properties(csharp-api PROPERTIES + OUTPUT_NAME + REFramework.NET + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin/" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib/" + COMMON_LANGUAGE_RUNTIME + netcore + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_GLOBAL_EnableManagedPackageReferenceSupport + true +) + +set(CMKR_TARGET csharp-api) +# Copy nuget packages to the output directory +add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin// + COMMENT "Copying nuget packages" +) +add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin// + COMMENT "Copying nuget packages" +) +set(REFRAMEWORK_DOT_NET_ASSEMBLY_DIR "${CMAKE_BINARY_DIR}/bin") +set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/REFramework.NET.dll") + +# Target: AssemblyGenerator +set(AssemblyGenerator_SOURCES + "AssemblyGenerator/ClassGenerator.cs" + "AssemblyGenerator/Generator.cs" + "AssemblyGenerator/SyntaxTreeBuilder.cs" + cmake.toml +) + +add_library(AssemblyGenerator SHARED) + +target_sources(AssemblyGenerator PRIVATE ${AssemblyGenerator_SOURCES}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${AssemblyGenerator_SOURCES}) + +target_link_libraries(AssemblyGenerator PUBLIC + csharp-api + REFCSharpCompiler +) + +set_target_properties(AssemblyGenerator PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary + CMAKE_CSharp_FLAGS + "/langversion:latest /platform:x64" +) + +set(CMKR_TARGET AssemblyGenerator) +set_target_properties(AssemblyGenerator PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET +"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" +) + +# Target: CSharpAPITest +set(CSharpAPITest_SOURCES + "test/Test/Test.cs" + cmake.toml +) + +add_library(CSharpAPITest SHARED) + +target_sources(CSharpAPITest PRIVATE ${CSharpAPITest_SOURCES}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITest_SOURCES}) + +target_link_libraries(CSharpAPITest PUBLIC + csharp-api +) + +set_target_properties(CSharpAPITest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary + CMAKE_CSharp_FLAGS + "/langversion:latest /platform:x64" +) + +set(CMKR_TARGET CSharpAPITest) +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET +"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" +) + +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_via +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/DynamicAssembly.dll" +) + +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "via") diff --git a/csharp-api/REFrameworkNET/AssemblyInfo.cpp b/csharp-api/REFrameworkNET/AssemblyInfo.cpp new file mode 100644 index 000000000..4b54147ca --- /dev/null +++ b/csharp-api/REFrameworkNET/AssemblyInfo.cpp @@ -0,0 +1,14 @@ +using namespace System; +using namespace System::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; +using namespace System::Security::Permissions; + +[assembly:AssemblyTitleAttribute("REFramework.NET")]; +[assembly:AssemblyDescriptionAttribute("")]; +[assembly:AssemblyConfigurationAttribute("")]; +[assembly:AssemblyCompanyAttribute("")]; +[assembly:AssemblyProductAttribute("REFramework.NET")]; +[assembly:AssemblyCopyrightAttribute("Copyright © praydog 2024")]; +[assembly:AssemblyCultureAttribute("")]; +[assembly:AssemblyVersionAttribute("1.0.0")]; \ No newline at end of file diff --git a/csharp-api/Plugin.cpp b/csharp-api/REFrameworkNET/Plugin.cpp similarity index 91% rename from csharp-api/Plugin.cpp rename to csharp-api/REFrameworkNET/Plugin.cpp index 72467d403..6522a07b6 100644 --- a/csharp-api/Plugin.cpp +++ b/csharp-api/REFrameworkNET/Plugin.cpp @@ -11,8 +11,8 @@ extern "C" { #include #include -#include "REFrameworkNET/API.hpp" -#include "REFrameworkNET/PluginManager.hpp" +#include "API.hpp" +#include "PluginManager.hpp" #include "Plugin.hpp" diff --git a/csharp-api/Plugin.hpp b/csharp-api/REFrameworkNET/Plugin.hpp similarity index 100% rename from csharp-api/Plugin.hpp rename to csharp-api/REFrameworkNET/Plugin.hpp diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 8495352e0..4f399fba9 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -16,7 +16,6 @@ namespace REFrameworkNET { return true; } - PluginManager::s_initialized = true; System::Console::WriteLine("Attempting to load plugins from initial context"); // Make sure plugins that are loaded can find a reference to the current assembly @@ -48,6 +47,7 @@ namespace REFrameworkNET { // Invoke LoadPlugins method System::Console::WriteLine("Invoking LoadPlugins..."); method->Invoke(nullptr, gcnew array{reinterpret_cast(param)}); + s_initialized = true; return true; } catch (System::Reflection::ReflectionTypeLoadException^ ex) { @@ -127,6 +127,7 @@ namespace REFrameworkNET { System::Console::WriteLine("LoadPlugins called"); if (PluginManager::s_initialized) { + System::Console::WriteLine("Already initialized"); return true; } @@ -139,6 +140,7 @@ namespace REFrameworkNET { // Create the REFramework::API class first though (managed) if (PluginManager::s_api_instance == nullptr) { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); + System::Console::WriteLine("Created API instance"); } auto deps = LoadDependencies(); // Pre-loads DLLs in the dependencies folder before loading the plugins diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml new file mode 100644 index 000000000..7450ac2de --- /dev/null +++ b/csharp-api/cmake.toml @@ -0,0 +1,139 @@ +# Reference: https://build-cpp.github.io/cmkr/cmake-toml +# to build: +# > cmake -B build +# > cmake --build build --config Release +[project] +name = "CSharpAPIProject" +languages = ["CXX", "C", "CSharp"] +cmake-after = """ +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + +# Disable exceptions +# string(REGEX REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + +set(ASMJIT_STATIC ON CACHE BOOL "" FORCE) +set(DYNAMIC_LOADER ON CACHE BOOL "" FORCE) # OpenXR +set(BUILD_TOOLS OFF CACHE BOOL "" FORCE) # DirectXTK + +if ("${CMAKE_BUILD_TYPE}" MATCHES "Release") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MT") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MT") + + # Statically compile runtime + string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + string(REGEX REPLACE "/MD" "/MT" CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + + message(NOTICE "Building in Release mode") +endif() + +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +set(NUGET_PACKAGES_DIR "$ENV{NUGET_PACKAGES}") + +# If NUGET_PACKAGES_DIR is not set, fall back to the default location +if(NOT NUGET_PACKAGES_DIR) + if(WIN32) + set(DEFAULT_NUGET_PATH "$ENV{USERPROFILE}/.nuget/packages") + else() + set(DEFAULT_NUGET_PATH "$ENV{HOME}/.nuget/packages") + endif() + set(NUGET_PACKAGES_DIR ${DEFAULT_NUGET_PATH}) +endif() +""" + +[template.CSharpSharedTarget] +type = "shared" + +[template.CSharpSharedTarget.properties] +RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/" +RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin" +LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib" +DOTNET_SDK = "Microsoft.NET.Sdk" +DOTNET_TARGET_FRAMEWORK = "net8.0-windows" +VS_CONFIGURATION_TYPE = "ClassLibrary" +CMAKE_CSharp_FLAGS = "/langversion:latest /platform:x64" + +[target.REFCSharpCompiler] +type = "CSharpSharedTarget" +sources = ["Compiler/**.cs"] + +# Not using .properties here because it overrides the template properties for whatever reason +cmake-after = """ +set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.CodeAnalysis_4.9.2") +""" + +[target.csharp-api] +type = "shared" +include-directories = ["../include/"] +sources = ["REFrameworkNET/**.cpp", "REFrameworkNET/**.c"] +headers = ["REFrameworkNET/**.hpp", "REFrameworkNET/**.h"] +compile-features = ["cxx_std_20"] +compile-options = ["/EHa", "/MD"] +link-libraries = [ + "REFCSharpCompiler" +] +cmake-after = """ +# Copy nuget packages to the output directory +add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin// + COMMENT "Copying nuget packages" +) +add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin// + COMMENT "Copying nuget packages" +) +set(REFRAMEWORK_DOT_NET_ASSEMBLY_DIR "${CMAKE_BINARY_DIR}/bin") +set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/REFramework.NET.dll") +""" + +[target.csharp-api.properties] +OUTPUT_NAME = "REFramework.NET" +RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/" +RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/" +LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/" +COMMON_LANGUAGE_RUNTIME = "netcore" +DOTNET_TARGET_FRAMEWORK = "net8.0-windows" +# DOTNET_TARGET_FRAMEWORK_VERSION = "net8.0" +VS_GLOBAL_EnableManagedPackageReferenceSupport = "true" +# VS_PACKAGE_REFERENCES = "Microsoft.CodeAnalysis_4.9.2" + +[target.AssemblyGenerator] +type = "CSharpSharedTarget" +sources = ["AssemblyGenerator/**.cs"] +link-libraries = [ + "csharp-api", + "REFCSharpCompiler" +] +cmake-after = """ +set_target_properties(AssemblyGenerator PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET +"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" +) +""" + +[target.CSharpAPITest] +type = "CSharpSharedTarget" +sources = ["test/Test/**.cs"] +link-libraries = [ + "csharp-api" +] + +cmake-after = """ +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET +"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" +) + +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_via +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/DynamicAssembly.dll" +) + +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "via") + +""" \ No newline at end of file diff --git a/csharp-api/cmkr.cmake b/csharp-api/cmkr.cmake new file mode 100644 index 000000000..cdeea6e27 --- /dev/null +++ b/csharp-api/cmkr.cmake @@ -0,0 +1,253 @@ +include_guard() + +# Change these defaults to point to your infrastructure if desired +set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE) +set(CMKR_TAG "v0.2.26" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE) +set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE) + +# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake +if(CMAKE_SCRIPT_MODE_FILE) + set(CMAKE_BINARY_DIR "${CMAKE_BINARY_DIR}/build") + set(CMAKE_CURRENT_BINARY_DIR "${CMAKE_BINARY_DIR}") + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}") +endif() + +# Set these from the command line to customize for development/debugging purposes +set(CMKR_EXECUTABLE "" CACHE FILEPATH "cmkr executable") +set(CMKR_SKIP_GENERATION OFF CACHE BOOL "skip automatic cmkr generation") +set(CMKR_BUILD_TYPE "Debug" CACHE STRING "cmkr build configuration") +mark_as_advanced(CMKR_REPO CMKR_TAG CMKR_COMMIT_HASH CMKR_EXECUTABLE CMKR_SKIP_GENERATION CMKR_BUILD_TYPE) + +# Disable cmkr if generation is disabled +if(DEFINED ENV{CI} OR CMKR_SKIP_GENERATION OR CMKR_BUILD_SKIP_GENERATION) + message(STATUS "[cmkr] Skipping automatic cmkr generation") + unset(CMKR_BUILD_SKIP_GENERATION CACHE) + macro(cmkr) + endmacro() + return() +endif() + +# Disable cmkr if no cmake.toml file is found +if(NOT CMAKE_SCRIPT_MODE_FILE AND NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") + message(AUTHOR_WARNING "[cmkr] Not found: ${CMAKE_CURRENT_SOURCE_DIR}/cmake.toml") + macro(cmkr) + endmacro() + return() +endif() + +# Convert a Windows native path to CMake path +if(CMKR_EXECUTABLE MATCHES "\\\\") + string(REPLACE "\\" "/" CMKR_EXECUTABLE_CMAKE "${CMKR_EXECUTABLE}") + set(CMKR_EXECUTABLE "${CMKR_EXECUTABLE_CMAKE}" CACHE FILEPATH "" FORCE) + unset(CMKR_EXECUTABLE_CMAKE) +endif() + +# Helper macro to execute a process (COMMAND_ERROR_IS_FATAL ANY is 3.19 and higher) +function(cmkr_exec) + execute_process(COMMAND ${ARGV} RESULT_VARIABLE CMKR_EXEC_RESULT) + if(NOT CMKR_EXEC_RESULT EQUAL 0) + message(FATAL_ERROR "cmkr_exec(${ARGV}) failed (exit code ${CMKR_EXEC_RESULT})") + endif() +endfunction() + +# Windows-specific hack (CMAKE_EXECUTABLE_PREFIX is not set at the moment) +if(WIN32) + set(CMKR_EXECUTABLE_NAME "cmkr.exe") +else() + set(CMKR_EXECUTABLE_NAME "cmkr") +endif() + +# Use cached cmkr if found +if(DEFINED ENV{CMKR_CACHE}) + set(CMKR_DIRECTORY_PREFIX "$ENV{CMKR_CACHE}") + string(REPLACE "\\" "/" CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}") + if(NOT CMKR_DIRECTORY_PREFIX MATCHES "\\/$") + set(CMKR_DIRECTORY_PREFIX "${CMKR_DIRECTORY_PREFIX}/") + endif() + # Build in release mode for the cache + set(CMKR_BUILD_TYPE "Release") +else() + set(CMKR_DIRECTORY_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/_cmkr_") +endif() +set(CMKR_DIRECTORY "${CMKR_DIRECTORY_PREFIX}${CMKR_TAG}") +set(CMKR_CACHED_EXECUTABLE "${CMKR_DIRECTORY}/bin/${CMKR_EXECUTABLE_NAME}") + +# Helper function to check if a string starts with a prefix +# Cannot use MATCHES, see: https://github.com/build-cpp/cmkr/issues/61 +function(cmkr_startswith str prefix result) + string(LENGTH "${prefix}" prefix_length) + string(LENGTH "${str}" str_length) + if(prefix_length LESS_EQUAL str_length) + string(SUBSTRING "${str}" 0 ${prefix_length} str_prefix) + if(prefix STREQUAL str_prefix) + set("${result}" ON PARENT_SCOPE) + return() + endif() + endif() + set("${result}" OFF PARENT_SCOPE) +endfunction() + +# Handle upgrading logic +if(CMKR_EXECUTABLE AND NOT CMKR_CACHED_EXECUTABLE STREQUAL CMKR_EXECUTABLE) + cmkr_startswith("${CMKR_EXECUTABLE}" "${CMAKE_CURRENT_BINARY_DIR}/_cmkr" CMKR_STARTSWITH_BUILD) + cmkr_startswith("${CMKR_EXECUTABLE}" "${CMKR_DIRECTORY_PREFIX}" CMKR_STARTSWITH_CACHE) + if(CMKR_STARTSWITH_BUILD) + if(DEFINED ENV{CMKR_CACHE}) + message(AUTHOR_WARNING "[cmkr] Switching to cached cmkr: '${CMKR_CACHED_EXECUTABLE}'") + if(EXISTS "${CMKR_CACHED_EXECUTABLE}") + set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) + else() + unset(CMKR_EXECUTABLE CACHE) + endif() + else() + message(AUTHOR_WARNING "[cmkr] Upgrading '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") + unset(CMKR_EXECUTABLE CACHE) + endif() + elseif(DEFINED ENV{CMKR_CACHE} AND CMKR_STARTSWITH_CACHE) + message(AUTHOR_WARNING "[cmkr] Upgrading cached '${CMKR_EXECUTABLE}' to '${CMKR_CACHED_EXECUTABLE}'") + unset(CMKR_EXECUTABLE CACHE) + endif() +endif() + +if(CMKR_EXECUTABLE AND EXISTS "${CMKR_EXECUTABLE}") + message(VERBOSE "[cmkr] Found cmkr: '${CMKR_EXECUTABLE}'") +elseif(CMKR_EXECUTABLE AND NOT CMKR_EXECUTABLE STREQUAL CMKR_CACHED_EXECUTABLE) + message(FATAL_ERROR "[cmkr] '${CMKR_EXECUTABLE}' not found") +elseif(NOT CMKR_EXECUTABLE AND EXISTS "${CMKR_CACHED_EXECUTABLE}") + set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) + message(STATUS "[cmkr] Found cached cmkr: '${CMKR_EXECUTABLE}'") +else() + set(CMKR_EXECUTABLE "${CMKR_CACHED_EXECUTABLE}" CACHE FILEPATH "Full path to cmkr executable" FORCE) + message(VERBOSE "[cmkr] Bootstrapping '${CMKR_EXECUTABLE}'") + + message(STATUS "[cmkr] Fetching cmkr...") + if(EXISTS "${CMKR_DIRECTORY}") + cmkr_exec("${CMAKE_COMMAND}" -E rm -rf "${CMKR_DIRECTORY}") + endif() + find_package(Git QUIET REQUIRED) + cmkr_exec("${GIT_EXECUTABLE}" + clone + --config advice.detachedHead=false + --branch ${CMKR_TAG} + --depth 1 + ${CMKR_REPO} + "${CMKR_DIRECTORY}" + ) + if(CMKR_COMMIT_HASH) + execute_process( + COMMAND "${GIT_EXECUTABLE}" checkout -q "${CMKR_COMMIT_HASH}" + RESULT_VARIABLE CMKR_EXEC_RESULT + WORKING_DIRECTORY "${CMKR_DIRECTORY}" + ) + if(NOT CMKR_EXEC_RESULT EQUAL 0) + message(FATAL_ERROR "Tag '${CMKR_TAG}' hash is not '${CMKR_COMMIT_HASH}'") + endif() + endif() + message(STATUS "[cmkr] Building cmkr (using system compiler)...") + cmkr_exec("${CMAKE_COMMAND}" + --no-warn-unused-cli + "${CMKR_DIRECTORY}" + "-B${CMKR_DIRECTORY}/build" + "-DCMAKE_BUILD_TYPE=${CMKR_BUILD_TYPE}" + "-DCMAKE_UNITY_BUILD=ON" + "-DCMAKE_INSTALL_PREFIX=${CMKR_DIRECTORY}" + "-DCMKR_GENERATE_DOCUMENTATION=OFF" + ) + cmkr_exec("${CMAKE_COMMAND}" + --build "${CMKR_DIRECTORY}/build" + --config "${CMKR_BUILD_TYPE}" + --parallel + ) + cmkr_exec("${CMAKE_COMMAND}" + --install "${CMKR_DIRECTORY}/build" + --config "${CMKR_BUILD_TYPE}" + --prefix "${CMKR_DIRECTORY}" + --component cmkr + ) + if(NOT EXISTS ${CMKR_EXECUTABLE}) + message(FATAL_ERROR "[cmkr] Failed to bootstrap '${CMKR_EXECUTABLE}'") + endif() + cmkr_exec("${CMKR_EXECUTABLE}" version) + message(STATUS "[cmkr] Bootstrapped ${CMKR_EXECUTABLE}") +endif() +execute_process(COMMAND "${CMKR_EXECUTABLE}" version + RESULT_VARIABLE CMKR_EXEC_RESULT +) +if(NOT CMKR_EXEC_RESULT EQUAL 0) + message(FATAL_ERROR "[cmkr] Failed to get version, try clearing the cache and rebuilding") +endif() + +# Use cmkr.cmake as a script +if(CMAKE_SCRIPT_MODE_FILE) + if(NOT EXISTS "${CMAKE_SOURCE_DIR}/cmake.toml") + execute_process(COMMAND "${CMKR_EXECUTABLE}" init + RESULT_VARIABLE CMKR_EXEC_RESULT + ) + if(NOT CMKR_EXEC_RESULT EQUAL 0) + message(FATAL_ERROR "[cmkr] Failed to bootstrap cmkr project. Please report an issue: https://github.com/build-cpp/cmkr/issues/new") + else() + message(STATUS "[cmkr] Modify cmake.toml and then configure using: cmake -B build") + endif() + else() + execute_process(COMMAND "${CMKR_EXECUTABLE}" gen + RESULT_VARIABLE CMKR_EXEC_RESULT + ) + if(NOT CMKR_EXEC_RESULT EQUAL 0) + message(FATAL_ERROR "[cmkr] Failed to generate project.") + else() + message(STATUS "[cmkr] Configure using: cmake -B build") + endif() + endif() +endif() + +# This is the macro that contains black magic +macro(cmkr) + # When this macro is called from the generated file, fake some internal CMake variables + get_source_file_property(CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" CMKR_CURRENT_LIST_FILE) + if(CMKR_CURRENT_LIST_FILE) + set(CMAKE_CURRENT_LIST_FILE "${CMKR_CURRENT_LIST_FILE}") + get_filename_component(CMAKE_CURRENT_LIST_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) + endif() + + # File-based include guard (include_guard is not documented to work) + get_source_file_property(CMKR_INCLUDE_GUARD "${CMAKE_CURRENT_LIST_FILE}" CMKR_INCLUDE_GUARD) + if(NOT CMKR_INCLUDE_GUARD) + set_source_files_properties("${CMAKE_CURRENT_LIST_FILE}" PROPERTIES CMKR_INCLUDE_GUARD TRUE) + + file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_PRE) + + # Generate CMakeLists.txt + cmkr_exec("${CMKR_EXECUTABLE}" gen + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + ) + + file(SHA256 "${CMAKE_CURRENT_LIST_FILE}" CMKR_LIST_FILE_SHA256_POST) + + # Delete the temporary file if it was left for some reason + set(CMKR_TEMP_FILE "${CMAKE_CURRENT_SOURCE_DIR}/CMakerLists.txt") + if(EXISTS "${CMKR_TEMP_FILE}") + file(REMOVE "${CMKR_TEMP_FILE}") + endif() + + if(NOT CMKR_LIST_FILE_SHA256_PRE STREQUAL CMKR_LIST_FILE_SHA256_POST) + # Copy the now-generated CMakeLists.txt to CMakerLists.txt + # This is done because you cannot include() a file you are currently in + configure_file(CMakeLists.txt "${CMKR_TEMP_FILE}" COPYONLY) + + # Add the macro required for the hack at the start of the cmkr macro + set_source_files_properties("${CMKR_TEMP_FILE}" PROPERTIES + CMKR_CURRENT_LIST_FILE "${CMAKE_CURRENT_LIST_FILE}" + ) + + # 'Execute' the newly-generated CMakeLists.txt + include("${CMKR_TEMP_FILE}") + + # Delete the generated file + file(REMOVE "${CMKR_TEMP_FILE}") + + # Do not execute the rest of the original CMakeLists.txt + return() + endif() + # Resume executing the unmodified CMakeLists.txt + endif() +endmacro() From e35b0985a7ea472d1244a128b6c8b0300e237a83 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 21 Mar 2024 00:09:04 -0700 Subject: [PATCH 030/207] .NET: Rework ref assembly generator to work on runtime TDB as a plugin --- .../AssemblyGenerator/ClassGenerator.cs | 112 +++---- csharp-api/AssemblyGenerator/Generator.cs | 302 ++++++++---------- csharp-api/REFrameworkNET/Method.hpp | 112 ++++++- csharp-api/REFrameworkNET/TDB.hpp | 58 +++- csharp-api/REFrameworkNET/TypeDefinition.hpp | 150 ++++++++- 5 files changed, 489 insertions(+), 245 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index dbe3b4b10..5f6b7b997 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -14,9 +14,9 @@ public class ClassGenerator { private string className; - private Il2CppDump.Type t; - private Il2CppDump.Method[] methods = []; - public List usingTypes = []; + private REFrameworkNET.TypeDefinition t; + private REFrameworkNET.Method[] methods = []; + public List usingTypes = []; private TypeDeclarationSyntax? typeDeclaration; public TypeDeclarationSyntax? TypeDeclaration { @@ -29,7 +29,7 @@ public void Update(TypeDeclarationSyntax? typeDeclaration_) { typeDeclaration = typeDeclaration_; } - public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] methods_) { + public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFrameworkNET.Method[] methods_) { className = className_; t = t_; methods = methods_; @@ -72,15 +72,16 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] bool wantsAbstractInstead = false; for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - if (parent.Name == null) { + var parentName = parent.FullName ?? ""; + if (parentName == null) { break; } - if (parent.Name == "") { + if (parentName == "") { break; } - if (parent.Name == "System.Attribute") { + if (parentName == "System.Attribute") { wantsAbstractInstead = true; break; } @@ -101,12 +102,15 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] } typeDeclaration = typeDeclaration - .AddMembers(methods.Where(method => method.Name != null && !invalidMethodNames.Contains(method.FriendlyName??method.Name) && !method.Name.Contains('<')).Select(method => { + .AddMembers(methods.Where(method => !invalidMethodNames.Contains(method.Name) && !method.Name.Contains('<')).Select(method => { TypeSyntax? returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); - if (method.Returns != null && method.Returns.Type != "System.Void" && method.Returns.Type != "") { + var methodReturnT = method.ReturnType; + string methodReturnName = methodReturnT != null ? methodReturnT.GetFullName() : ""; + + if (methodReturnT != null && methodReturnName != "System.Void" && methodReturnName != "") { // Check for easily convertible types like System.Single, System.Int32, etc. - switch (method.Returns.Type) { + switch (methodReturnName) { case "System.Single": returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword)); break; @@ -138,36 +142,23 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; default: - if (method.Returns != null && method.Returns.Type != null && method.Returns.Type != "") { - if (!Program.validTypes.Contains(method.Returns.Type)) { - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - if (method.Returns.Type.Contains('<') || method.Returns.Type.Contains('[')) { + if (methodReturnT != null && methodReturnName != "") { + if (!AssemblyGenerator.validTypes.Contains(methodReturnName)) { returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; } - if (method.Returns.Type.StartsWith("System.") || !method.Returns.Type.StartsWith("via.")) { + if (methodReturnName.Contains('<') || methodReturnName.Contains('[')) { returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; } - if (Program.Dump == null) { + if (methodReturnName.StartsWith("System.") || !methodReturnName.StartsWith("via.")) { returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; } - var returnName = method.Returns.Type; - /*var returnTypeNamespace = Program.ExtractNamespaceFromTypeName(Program.Dump, returnName); - var typeNamespace = Program.ExtractNamespaceFromTypeName(Program.Dump, ogClassName); - - if (returnTypeNamespace == typeNamespace) { - returnName = returnName.Split('.').Last(); - }*/ - - returnType = SyntaxFactory.ParseTypeName(returnName); + returnType = SyntaxFactory.ParseTypeName(methodReturnName); break; } @@ -176,10 +167,10 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] } } - var flags = method.Flags?.Split(" | "); - var methodName = new string(method.FriendlyName); + var methodName = new string(method.Name); + var methodExtension = Il2CppDump.GetMethodExtension(method); - if (method.Override != null && method.Override == true) { + if (methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { methodName += "_" + className.Replace('.', '_'); } @@ -191,23 +182,13 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); } - if (method.Params != null) { - methodDeclaration = methodDeclaration.AddParameterListParameters(method.Params.Where(param => param != null && param.Type != null && param.Name != null).Select(param => { + if (method.Parameters.Count > 0) { + methodDeclaration = methodDeclaration.AddParameterListParameters(method.Parameters.Where(param => param != null && param.Type != null && param.Name != null).Select(param => { return SyntaxFactory.Parameter(SyntaxFactory.Identifier(param.Name ?? "UnknownParam")) .WithType(SyntaxFactory.ParseTypeName(/*param.Type ??*/ "object")); }).ToArray()); } - // find "Virtual" flag - //if (flags != null && flags.Contains("Virtual")) { - //methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); - - if (method.Override != null && method.Override == true) { - //methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); - - } - //} - return methodDeclaration; }).ToArray()); @@ -223,23 +204,24 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] }; for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - if (parent.Name == null) { + var parentName = parent.FullName ?? ""; + if (parentName == null) { break; } - if (parent.Name == "") { + if (parentName == "") { break; } - if (parent.Name.Contains('[') || parent.Name.Contains('<')) { + if (parentName.Contains('[') || parentName.Contains('<')) { break; } - if (badBaseTypes.Contains(parent.Name)) { + if (badBaseTypes.Contains(parentName)) { break; } - baseTypes.Add(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(parent.Name))); + baseTypes.Add(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(parentName))); usingTypes.Add(parent); break; } @@ -261,8 +243,12 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] return null; } - foreach (var nestedT in t.NestedTypes ?? []) { - var nestedTypeName = nestedT.Name ?? ""; + HashSet? nestedTypes = Il2CppDump.GetTypeExtension(t)?.NestedTypes; + + foreach (var nestedT in nestedTypes ?? []) { + var nestedTypeName = nestedT.FullName ?? ""; + + //System.Console.WriteLine("Nested type: " + nestedTypeName); if (nestedTypeName == "") { continue; @@ -280,19 +266,17 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] nestedTypeName = nestedTypeName.Replace("file", "@file"); } - var fixedNestedMethods = nestedT.Methods? - .Select(methodPair => { - var method = methodPair.Value; - var methodName = Il2CppDump.StripMethodName(method); - return (methodName, method); - }) - .GroupBy(pair => pair.methodName) - .Select(group => group.First()) // Selects the first method of each group - .ToDictionary(pair => pair.methodName, pair => pair.method); + HashSet nestedMethods = []; + + foreach (var method in t.Methods) { + if (!nestedMethods.Select(nestedMethod => nestedMethod.Name).Contains(method.Name)) { + nestedMethods.Add(method); + } + } var nestedGenerator = new ClassGenerator(nestedTypeName.Split('.').Last(), nestedT, - fixedNestedMethods != null ? [.. fixedNestedMethods.Values] : [] + [.. nestedMethods] ); if (nestedGenerator.TypeDeclaration == null) { @@ -308,10 +292,12 @@ public ClassGenerator(string className_, Il2CppDump.Type t_, Il2CppDump.Method[] break; } + var parentNestedTypes = Il2CppDump.GetTypeExtension(parent)?.NestedTypes; + // Look for same named nested types - if (parent.NestedTypes != null) { - foreach (var parentNested in parent.NestedTypes) { - var isolatedParentNestedName = parentNested.Name?.Split('.').Last(); + if (parentNestedTypes != null) { + foreach (var parentNested in parentNestedTypes) { + var isolatedParentNestedName = parentNested.FullName?.Split('.').Last(); if (isolatedParentNestedName == isolatedNestedName) { nestedGenerator.Update(nestedGenerator.TypeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword))); addedNew = true; diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index a45f1d3fe..b4c219f4d 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -19,184 +19,126 @@ class Field { }; public class Method { - public class ReturnValueClass { - [JsonPropertyName("type")] - public string? Type { get; set;} - [JsonPropertyName("name")] - public string? Name { get; set;} - }; + private REFrameworkNET.Method impl; - public class Parameter { - [JsonPropertyName("name")] - public string? Name { get; set;} - [JsonPropertyName("type")] - public string? Type { get; set;} - }; - - [JsonPropertyName("flags")] - public string? Flags { get; set;} + public Method(REFrameworkNET.Method impl) { + this.impl = impl; + } - [JsonPropertyName("function")] - public string? Function { get; set;} - - [JsonPropertyName("id")] - public int? ID { get; set;} - [JsonPropertyName("invoke_id")] - public int? InvokeID { get; set;} - [JsonPropertyName("impl_flags")] - public string? ImplFlags { get; set;} - [JsonPropertyName("returns")] - public ReturnValueClass? Returns { get; set;} - [JsonPropertyName("params")] - public List? Params { get; set;} - - public string? Name { get; set;} // Not from JSON - public string? FriendlyName { get; set;} // Not from JSON public bool? Override { get; set;} // Not from JSON } public class Type { - [JsonPropertyName("address")] - public string? Address { get; set;} - [JsonPropertyName("fqn")] - public string? FQN { get; set;} - [JsonPropertyName("flags")] - public string? Flags { get; set;} - [JsonPropertyName("crc")] - public string? CRC { get; set;} - [JsonPropertyName("id")] - public int? ID { get; set;} - [JsonPropertyName("is_generic_type")] - public bool? IsGenericType { get; set;} - [JsonPropertyName("is_generic_type_definition")] - public bool? IsGenericTypeDefinition { get; set;} - - [JsonPropertyName("parent")] - public string? Parent { get; set;} - [JsonPropertyName("declaring_type")] - public string? DeclaringTypeName { get; set;} - [JsonPropertyName("methods")] - public Dictionary? Methods { get; set;} - - public Dictionary? StrippedMethods { get; set;} // Not from JSON - - [JsonPropertyName("name_hierarchy")] - public List? NameHierarchy { get; set;} - - // Post processed parent type - public Il2CppDump.Type? ParentType { get; set;} - public Il2CppDump.Type? DeclaringType { get; set;} - public string? Name { get; set;} // Not from JSON - public List? NestedTypes { get; set;} - }; + private REFrameworkNET.TypeDefinition impl; - public Dictionary? Types; + public Type(REFrameworkNET.TypeDefinition impl) { + this.impl = impl; + } - public static string StripMethodName(Method method) { - var methodName = method.Name ?? ""; - var methodID = method.ID ?? -1; + public REFrameworkNET.ManagedObject RuntimeType => impl.GetRuntimeType(); - var methodIdStr = methodID.ToString(); - var methodIdIndex = methodName.LastIndexOf(methodIdStr); - if (methodIdIndex != -1) { - return methodName[..methodIdIndex]; - } + public REFrameworkNET.TypeInfo TypeInfo => impl.GetTypeInfo(); - return methodName; - } + public REFrameworkNET.TypeDefinition UnderlyingType => impl.GetUnderlyingType(); - // For adding additional context to the types - public static Dictionary? PostProcessTypes(Dictionary? types) { - if (types == null) { - return null; - } + public REFrameworkNET.TypeDefinition DeclaringType => impl.GetDeclaringType(); - foreach (var typePair in types) { - var typeName = typePair.Key; - var type = typePair.Value; + public REFrameworkNET.TypeDefinition ParentType => impl.GetParentType(); - if (type.Parent != null && types.TryGetValue(type.Parent, out var parentType)) { - type.ParentType = parentType; - } + public List Properties => impl.GetProperties(); - if (type.DeclaringTypeName != null && types.TryGetValue(type.DeclaringTypeName, out var declaringType)) { - type.DeclaringType = declaringType; + public List Fields => impl.GetFields(); - if (declaringType.NestedTypes == null) { - declaringType.NestedTypes = []; - } + public List Methods => impl.GetMethods(); - declaringType.NestedTypes.Add(type); - } + public string Name => impl.GetName(); + public string FullName => impl.GetFullName(); - type.Name = typeName; + public string Namespace => impl.GetNamespace(); - foreach (var methodPair in type.Methods ?? []) { - if (methodPair.Value.Name == null) { - methodPair.Value.Name = methodPair.Key; - } + public uint FQN => impl.GetFQN(); + + public uint ValueTypeSize => impl.GetValueTypeSize(); - var methodName = StripMethodName(methodPair.Value); + public uint Size => impl.GetSize(); - methodPair.Value.FriendlyName = methodName; + public uint Index => impl.GetIndex(); - if (typeName.Contains("ConvolutionReverbParameters") || typeName.Contains("via.audiorender.EffectParametersBase")) { - System.Console.WriteLine("Method: " + methodName); + + // Custom stuff below + public HashSet? NestedTypes { get; set;} + }; + + static private Dictionary typeExtensions = []; + static private Dictionary methodExtensions = []; + static public Type? GetTypeExtension(REFrameworkNET.TypeDefinition type) { + if (typeExtensions.TryGetValue(type, out Type? value)) { + return value; + } + + return null; + } + + static public Method? GetMethodExtension(REFrameworkNET.Method method) { + if (methodExtensions.TryGetValue(method, out Method? value)) { + return value; + } + + return null; + } + + public static void FillTypeExtensions(REFrameworkNET.TDB context) { + if (typeExtensions.Count > 0) { + return; + } + + // Look for types that have a declaring type and add them to the declaring type's nested types + foreach (REFrameworkNET.TypeDefinition t in context.Types) { + if (t.DeclaringType != null) { + if (!typeExtensions.TryGetValue(t.DeclaringType, out Type? value)) { + value = new Type(t.DeclaringType); + typeExtensions[t.DeclaringType] = value; } - if (type.StrippedMethods == null) { - type.StrippedMethods = []; + //value.NestedTypes ??= []; + if (value.NestedTypes == null) { + value.NestedTypes = new HashSet(); } + value.NestedTypes.Add(t); - type.StrippedMethods[methodName] = methodPair.Value; + //System.Console.WriteLine("Adding nested type " + t.GetFullName() + " to " + t.DeclaringType.GetFullName()); } - } - - foreach (var typePair in types) { - var typeName = typePair.Key; - var type = typePair.Value; - foreach (var methodPair in type.Methods ?? []) { - var methodName = methodPair.Key; - var method = methodPair.Value; + // Look for methods with the same name and mark them as overrides + for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { + if (parent.Methods.Count == 0 || t.Methods.Count == 0) { + continue; + } - var flags = method.Flags?.Split(" | "); + foreach (var method in t.Methods) { + var parentMethod = parent.GetMethod(method.Name); - //if (flags != null && flags.Contains("Virtual")) { - for (var parent = type.ParentType; parent != null; parent = parent.ParentType) { - if (parent.StrippedMethods != null && parent.StrippedMethods.TryGetValue(method.FriendlyName?? "", out var parentMethod)) { - method.Override = true; - break; + if (parentMethod != null) { + if (!methodExtensions.TryGetValue(method, out Method? value)) { + value = new Method(method); + methodExtensions[method] = value; } - if (method.Override?? false) { - break; - } + value.Override = true; } - //} + } } } - - return types; } } -public class SnakeCaseToPascalCaseNamingPolicy : JsonNamingPolicy -{ - public override string ConvertName(string name) - { - // Convert snake_case to PascalCase - return Regex.Replace(name, @"_(.)", match => match.Groups[1].Value.ToUpper()); - } -} - -public class Program { +public class AssemblyGenerator { static Dictionary namespaces = []; // Start with an empty CompilationUnitSyntax (represents an empty file) static CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit(); - static public NamespaceDeclarationSyntax? ExtractNamespaceFromTypeName(Il2CppDump context, string typeName) { + static public NamespaceDeclarationSyntax? ExtractNamespaceFromTypeName(REFrameworkNET.TDB context, string typeName) { if (context == null || context.Types == null) { return null; } @@ -211,7 +153,7 @@ public class Program { var part = parts[i]; currentTypeName += part; - if (context.Types.TryGetValue(currentTypeName, out var value_)) { + if (context.GetType(currentTypeName) != null) { // Return a blank namespace if (currentNamespaceDecl == null) { System.Console.WriteLine("Creating blank namespace for " + currentTypeName); @@ -245,14 +187,14 @@ public class Program { public static SortedSet validTypes = []; public static SortedSet generatedTypes = []; - static void FillValidEntries(Il2CppDump context) { + static void FillValidEntries(REFrameworkNET.TDB context) { if (validTypes.Count > 0) { return; } - foreach (var typePair in context.Types ?? []) { - var typeName = typePair.Key; - var t = typePair.Value; + // TDB only has GetType(index) and GetNumTypes() + foreach (REFrameworkNET.TypeDefinition t in context.Types) { + var typeName = t.GetFullName(); if (typeName.Length == 0) { Console.WriteLine("Bad type name"); @@ -276,7 +218,7 @@ static void FillValidEntries(Il2CppDump context) { } } - static CompilationUnitSyntax MakeFromTypeEntry(Il2CppDump context, string typeName, Il2CppDump.Type? t) { + static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, string typeName, REFrameworkNET.TypeDefinition? t) { FillValidEntries(context); if (!validTypes.Contains(typeName)) { @@ -302,10 +244,10 @@ static CompilationUnitSyntax MakeFromTypeEntry(Il2CppDump context, string typeNa // Generate starting from topmost parent first if (t.ParentType != null) { - MakeFromTypeEntry(context, t.ParentType.Name ?? "", t.ParentType); + MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType); } - var methods = t.Methods; + /*var methods = t.Methods; var fixedMethods = methods? .Select(methodPair => { var method = methodPair.Value; @@ -314,16 +256,22 @@ static CompilationUnitSyntax MakeFromTypeEntry(Il2CppDump context, string typeNa }) .GroupBy(pair => pair.methodName) .Select(group => group.First()) // Selects the first method of each group - .ToDictionary(pair => pair.methodName, pair => pair.method); + .ToDictionary(pair => pair.methodName, pair => pair.method);*/ + + // Make methods a SortedSet of method names + HashSet methods = []; - foreach (var methodPair in fixedMethods ?? []) { - methodPair.Value.Name = methodPair.Key; + foreach (var method in t.Methods) { + //methods.Add(method); + if (!methods.Select(m => m.Name).Contains(method.Name)) { + methods.Add(method); + } } var generator = new ClassGenerator( typeName.Split('.').Last() == "file" ? typeName.Replace("file", "@file") : typeName, t, - fixedMethods != null ? [.. fixedMethods.Values] : [] + [.. methods] ); if (generator.TypeDeclaration == null) { @@ -361,49 +309,34 @@ static CompilationUnitSyntax MakeFromTypeEntry(Il2CppDump context, string typeNa return compilationUnit; } - private static Il2CppDump? dump; - public static Il2CppDump? Dump { - get { - return dump; - } - } - - static void Main(string[] args) { - if (args.Length == 0) { - Console.WriteLine("Usage: dotnet run "); - return; - } - - var il2cpp_dump_json = args[0]; + public static void Main(REFrameworkNET.API api) { + try { + MainImpl(); + } catch (Exception e) { + Console.WriteLine("Exception: " + e); - if (il2cpp_dump_json == null) { - Console.WriteLine("Usage: dotnet run "); - return; + var ex = e; + while (ex.InnerException != null) { + ex = ex.InnerException; + Console.WriteLine("Inner Exception: " + ex); + } } + } - Console.WriteLine(args[0]); + public static void MainImpl() { + Il2CppDump.FillTypeExtensions(REFrameworkNET.API.GetTDB()); List compilationUnits = new List(); // Open a JSON file - using (var jsonFile = File.OpenRead(il2cpp_dump_json)) + /*using (var jsonFile = File.OpenRead(il2cpp_dump_json)) { - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = new SnakeCaseToPascalCaseNamingPolicy(), - }; - dump = new Il2CppDump { Types = Il2CppDump.PostProcessTypes(JsonSerializer.Deserialize>(jsonFile, options)) }; if (dump != null && dump.Types != null) { - // Get via.SceneManager type - /*var sceneManager = dump.Types?["via.SceneManager"]; - - MakeFromTypeEntry(sceneManager);*/ - // Look for any types that start with via.* foreach (var typePair in dump.Types ?? []) { var typeName = typePair.Key; @@ -417,6 +350,17 @@ static void Main(string[] args) { } else { Console.WriteLine("Failed to parse JSON"); } + }*/ + + var tdb = REFrameworkNET.API.GetTDB(); + + foreach (REFrameworkNET.TypeDefinition t in tdb.Types) { + var typeName = t.GetFullName(); + + if (typeName.StartsWith("via.")) { + var compilationUnit = MakeFromTypeEntry(REFrameworkNET.API.GetTDB(), typeName, t); + compilationUnits.Add(compilationUnit); + } } System.Console.WriteLine(compilationUnits[0].NormalizeWhitespace().ToFullString()); @@ -488,6 +432,8 @@ static void Main(string[] args) { var errorLineText = textLines[errorLineNumber].ToString(); Console.WriteLine($"Error in line {errorLineNumber + 1}: {errorLineText}"); } + + REFrameworkNET.API.LogError("Failed to compile DynamicAssembly.dll"); } else { @@ -497,6 +443,8 @@ static void Main(string[] args) { // dump to file File.WriteAllBytes("DynamicAssembly.dll", ms.ToArray()); + + REFrameworkNET.API.LogInfo("Successfully compiled DynamicAssembly.dll"); } } diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 1c6fd993c..971257775 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -8,7 +8,8 @@ #include "MethodParameter.hpp" namespace REFrameworkNET { -public ref class Method { +public ref class Method : public System::IEquatable +{ public: Method(reframework::API::Method* method) : m_method(method) {} @@ -23,6 +24,12 @@ public ref class Method { return gcnew System::String(m_method->get_name()); } + property System::String^ Name { + System::String^ get() { + return GetName(); + } + } + TypeDefinition^ GetDeclaringType() { auto result = m_method->get_declaring_type(); @@ -33,6 +40,12 @@ public ref class Method { return gcnew TypeDefinition(result); } + property TypeDefinition^ DeclaringType { + TypeDefinition^ get() { + return GetDeclaringType(); + } + } + TypeDefinition^ GetReturnType() { auto result = m_method->get_return_type(); @@ -43,10 +56,22 @@ public ref class Method { return gcnew TypeDefinition(result); } + property TypeDefinition^ ReturnType { + TypeDefinition^ get() { + return GetReturnType(); + } + } + uint32_t GetNumParams() { return m_method->get_num_params(); } + property uint32_t NumParams { + uint32_t get() { + return GetNumParams(); + } + } + System::Collections::Generic::List^ GetParameters() { const auto params = m_method->get_params(); @@ -59,14 +84,32 @@ public ref class Method { return ret; } + property System::Collections::Generic::List^ Parameters { + System::Collections::Generic::List^ get() { + return GetParameters(); + } + } + uint32_t GetIndex() { return m_method->get_index(); } + property uint32_t Index { + uint32_t get() { + return GetIndex(); + } + } + int32_t GetVirtualIndex() { return m_method->get_virtual_index(); } + property int32_t VirtualIndex { + int32_t get() { + return GetVirtualIndex(); + } + } + bool IsStatic() { return m_method->is_static(); } @@ -75,14 +118,32 @@ public ref class Method { return m_method->get_flags(); } + property uint16_t Flags { + uint16_t get() { + return GetFlags(); + } + } + uint16_t GetImplFlags() { return m_method->get_impl_flags(); } + property uint16_t ImplFlags { + uint16_t get() { + return GetImplFlags(); + } + } + uint32_t GetInvokeID() { return m_method->get_invoke_id(); } + property uint32_t InvokeID { + uint32_t get() { + return GetInvokeID(); + } + } + // hmm... /*UInt32 AddHook(pre_fn, post_fn, Boolean ignore_jmp) { return m_method->add_hook(pre_fn, post_fn, ignore_jmp); @@ -92,6 +153,55 @@ public ref class Method { m_method->remove_hook(hook_id); } +public: + virtual bool Equals(System::Object^ other) override { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + if (other->GetType() != Method::typeid) { + return false; + } + + return m_method == safe_cast(other)->m_method; + } + + virtual bool Equals(Method^ other) { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + return m_method == other->m_method; + } + + static bool operator ==(Method^ left, Method^ right) { + if (System::Object::ReferenceEquals(left, right)) { + return true; + } + + if (System::Object::ReferenceEquals(left, nullptr) || System::Object::ReferenceEquals(right, nullptr)) { + return false; + } + + return left->m_method == right->m_method; + } + + static bool operator !=(Method^ left, Method^ right) { + return !(left == right); + } + + virtual int GetHashCode() override { + return (gcnew System::UIntPtr((uintptr_t)m_method))->GetHashCode(); + } + private: reframework::API::Method* m_method; }; diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp index 379d806de..d0c0bd4bb 100644 --- a/csharp-api/REFrameworkNET/TDB.hpp +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -10,8 +10,14 @@ #include "Field.hpp" #include "Property.hpp" +using namespace System; +using namespace System::Collections; + namespace REFrameworkNET { public ref class TDB { +private: + reframework::API::TDB* m_tdb; + public: TDB(reframework::API::TDB* tdb) : m_tdb(tdb) {} @@ -149,7 +155,55 @@ public ref class TDB { return gcnew Property(result); } -private: - reframework::API::TDB* m_tdb; +public: + ref class TypeDefinitionIterator : public IEnumerator { + private: + TDB^ m_parent; + int m_currentIndex; + TypeDefinition^ m_currentType; + + public: + TypeDefinitionIterator(TDB^ parent) : m_parent(parent), m_currentIndex(-1), m_currentType(nullptr) {} + + // Required by IEnumerator + virtual bool MoveNext() { + if (m_currentIndex < static_cast(m_parent->GetNumTypes()) - 1) { + m_currentIndex++; + m_currentType = m_parent->GetType(m_currentIndex); + return true; + } else { + return false; + } + } + + virtual void Reset() { + m_currentIndex = -1; + m_currentType = nullptr; + } + + property Object^ Current { + virtual Object^ get() = IEnumerator::Current::get { + return m_currentType; + } + } + }; + + ref class TypeDefinitionCollection : public IEnumerable { + private: + TDB^ m_parent; + + public: + TypeDefinitionCollection(TDB^ parent) : m_parent(parent) {} + + virtual IEnumerator^ GetEnumerator() { + return gcnew TypeDefinitionIterator(m_parent); + } + }; + + property TypeDefinitionCollection^ Types { + TypeDefinitionCollection^ get() { + return gcnew TypeDefinitionCollection(this); + } + } }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 1d12454b3..5b45a8de4 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -22,7 +22,7 @@ public enum VMObjType { ValType = 5, }; -public ref class TypeDefinition +public ref class TypeDefinition : public System::IEquatable { public: TypeDefinition(reframework::API::TypeDefinition* td) : m_type(td) {} @@ -37,36 +37,86 @@ public ref class TypeDefinition return m_type->get_index(); } + property uint32_t Index { + uint32_t get() { + return GetIndex(); + } + } + uint32_t GetSize() { return m_type->get_size(); } + property uint32_t Size { + uint32_t get() { + return GetSize(); + } + } + uint32_t GetValueTypeSize() { return m_type->get_valuetype_size(); } - uint32_t GetFqn() + property uint32_t ValueTypeSize { + uint32_t get() { + return GetValueTypeSize(); + } + } + + uint32_t GetFQN() { return m_type->get_fqn(); } + property uint32_t FQN { + uint32_t get() { + return GetFQN(); + } + } + System::String^ GetName() { + if (m_type->get_name() == nullptr) { + return nullptr; + } + return gcnew System::String(m_type->get_name()); } + property System::String^ Name { + System::String^ get() { + return GetName(); + } + } + System::String^ GetNamespace() { + if (m_type->get_namespace() == nullptr) { + return nullptr; + } + return gcnew System::String(m_type->get_namespace()); } + property System::String^ Namespace { + System::String^ get() { + return GetNamespace(); + } + } + System::String^ GetFullName() { return gcnew System::String(m_type->get_full_name().c_str()); } + property System::String^ FullName { + System::String^ get() { + return GetFullName(); + } + } + bool HasFieldPtrOffset() { return m_type->has_fieldptr_offset(); @@ -154,6 +204,24 @@ public ref class TypeDefinition System::Collections::Generic::List^ GetFields(); System::Collections::Generic::List^ GetProperties(); + property System::Collections::Generic::List^ Methods { + System::Collections::Generic::List^ get() { + return GetMethods(); + } + } + + property System::Collections::Generic::List^ Fields { + System::Collections::Generic::List^ get() { + return GetFields(); + } + } + + property System::Collections::Generic::List^ Properties { + System::Collections::Generic::List^ get() { + return GetProperties(); + } + } + ManagedObject^ CreateInstance(int32_t flags); TypeDefinition^ GetParentType() @@ -167,6 +235,12 @@ public ref class TypeDefinition return gcnew TypeDefinition(result); } + property TypeDefinition^ ParentType { + TypeDefinition^ get() { + return GetParentType(); + } + } + TypeDefinition^ GetDeclaringType() { auto result = m_type->get_declaring_type(); @@ -178,6 +252,12 @@ public ref class TypeDefinition return gcnew TypeDefinition(result); } + property TypeDefinition^ DeclaringType { + TypeDefinition^ get() { + return GetDeclaringType(); + } + } + TypeDefinition^ GetUnderlyingType() { auto result = m_type->get_underlying_type(); @@ -189,8 +269,25 @@ public ref class TypeDefinition return gcnew TypeDefinition(result); } + property TypeDefinition^ UnderlyingType { + TypeDefinition^ get() { + return GetUnderlyingType(); + } + } + REFrameworkNET::TypeInfo^ GetTypeInfo(); + property REFrameworkNET::TypeInfo^ TypeInfo { + REFrameworkNET::TypeInfo^ get() { + return GetTypeInfo(); + } + } + ManagedObject^ GetRuntimeType(); + property ManagedObject^ RuntimeType { + ManagedObject^ get() { + return GetRuntimeType(); + } + } /*Void* GetInstance() { @@ -202,6 +299,55 @@ public ref class TypeDefinition return m_type->create_instance_deprecated(); }*/ +public: + virtual bool Equals(System::Object^ other) override { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + if (other->GetType() != TypeDefinition::typeid) { + return false; + } + + return m_type == safe_cast(other)->m_type; + } + + virtual bool Equals(TypeDefinition^ other) { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + return m_type == other->m_type; + } + + static bool operator ==(TypeDefinition^ left, TypeDefinition^ right) { + if (System::Object::ReferenceEquals(left, right)) { + return true; + } + + if (System::Object::ReferenceEquals(left, nullptr) || System::Object::ReferenceEquals(right, nullptr)) { + return false; + } + + return left->m_type == right->m_type; + } + + static bool operator !=(TypeDefinition^ left, TypeDefinition^ right) { + return !(left == right); + } + + virtual int GetHashCode() override { + return (gcnew System::UIntPtr((uintptr_t)m_type))->GetHashCode(); + } + private: reframework::API::TypeDefinition* m_type; }; From e5a5609ee86567016f6244ae3b1155953fa7e7cf Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 21 Mar 2024 00:09:33 -0700 Subject: [PATCH 031/207] .NET: Update Test to make use of reference assembly --- csharp-api/test/Test/Test.cs | 142 ++++++++++++++++++++++++++++++++--- 1 file changed, 131 insertions(+), 11 deletions(-) diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index d010f235b..dc94c9579 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -1,5 +1,118 @@ // Import REFramework::API using System; +using System.Dynamic; +using System.Reflection; + +public interface IProxy { + void SetInstance(dynamic instance); +} + +// TODO: Put this in its own assembly, or make it part of C++/CLI? +public class Proxy : DispatchProxy, IProxy where T2 : class { + public dynamic Instance { get; private set; } + + public void SetInstance(dynamic instance) { + Instance = instance; + } + + public static T Create(dynamic target) { + var proxy = Create>(); + (proxy as IProxy).SetInstance(target); + return proxy; + } + + protected override object Invoke(MethodInfo targetMethod, object[] args) { + object result = null; + dynamic obj = Instance as T2; + obj.HandleInvokeMember_Internal(targetMethod.Name, args, ref result); + + if (targetMethod.ReturnType == typeof(REFrameworkNET.ManagedObject)) { + return result; + } + + if (targetMethod.ReturnType == typeof(REFrameworkNET.NativeObject)) { + return result; + } + + if (targetMethod.ReturnType == typeof(string)) { + return result; + } + + if (!targetMethod.ReturnType.IsPrimitive && targetMethod.DeclaringType.IsInterface) { + if (result != null && result.GetType() == typeof(REFrameworkNET.ManagedObject)) { + // See if we can do a dynamic lookup and resolve it to a local type + var t = (result as REFrameworkNET.ManagedObject).GetTypeDefinition(); + var fullName = t.GetFullName(); + + // See if we can find a local type with the same name + var localType = typeof(via.Scene).Assembly.GetType(fullName); + + if (localType != null) { + var prox = Create(localType, typeof(Proxy<,>).MakeGenericType(localType, typeof(REFrameworkNET.ManagedObject))); + (prox as IProxy).SetInstance(result); + result = prox; + return result; + } + } else if (result != null && result.GetType() == typeof(REFrameworkNET.NativeObject)) { + // See if we can do a dynamic lookup and resolve it to a local type + var t = (result as REFrameworkNET.NativeObject).GetTypeDefinition(); + var fullName = t.GetFullName(); + + // See if we can find a local type with the same name + var localType = typeof(via.Scene).Assembly.GetType(fullName); + + if (localType != null) { + var prox = Create(localType, typeof(Proxy<,>).MakeGenericType(localType, typeof(REFrameworkNET.NativeObject))); + (prox as IProxy).SetInstance(result); + result = prox; + return result; + } + } + } + + return result; + } +} + +public class ManagedProxy : Proxy { + new public static T Create(dynamic target) { + return Proxy.Create(target); + } +} + +public class NativeProxy : Proxy { + new public static T Create(dynamic target) { + return Proxy.Create(target); + } +} + +public class DangerousFunctions { + public static void Entry() { + // 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 + var sceneManager = NativeProxy.Create(REFrameworkNET.API.GetNativeSingleton("via.SceneManager")); + var scene = sceneManager.get_CurrentScene(); + + scene.set_Pause(true); + var view = sceneManager.get_MainView(); + var name = view.get_Name(); + var go = view.get_PrimaryCamera()?.get_GameObject()?.get_Transform()?.get_GameObject(); + + REFrameworkNET.API.LogInfo("game object name: " + go?.get_Name().ToString()); + REFrameworkNET.API.LogInfo("Scene name: " + name); + + // Testing autocomplete for the concrete ManagedObject + REFrameworkNET.API.LogInfo("Scene: " + scene.ToString() + ": " + (scene as REFrameworkNET.ManagedObject)?.GetTypeDefinition()?.GetFullName()?.ToString()); + + // Testing dynamic invocation + float currentTimescale = scene.get_TimeScale(); + scene.set_TimeScale(0.1f); + + REFrameworkNET.API.LogInfo("Previous timescale: " + currentTimescale.ToString()); + REFrameworkNET.API.LogInfo("Current timescale: " + scene?.get_TimeScale().ToString()); + } +} class REFrameworkPlugin { // Measure time between pre and post @@ -8,6 +121,7 @@ class REFrameworkPlugin { static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); public static void Main(REFrameworkNET.API api) { + try { REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); REFrameworkNET.Callbacks.BeginRendering.Pre += () => { @@ -99,18 +213,8 @@ public static void Main(REFrameworkNET.API api) { } } - dynamic sceneManager = REFrameworkNET.API.GetNativeSingleton("via.SceneManager"); - dynamic scene = sceneManager?.get_CurrentScene(); - - // Testing autocomplete for the concrete ManagedObject - REFrameworkNET.API.LogInfo("Scene: " + scene.ToString() + ": " + scene?.GetTypeDefinition()?.GetFullName()?.ToString()); - - // Testing dynamic invocation - float? currentTimescale = scene?.get_TimeScale(); - scene?.set_TimeScale(0.1f); - REFrameworkNET.API.LogInfo("Previous timescale: " + currentTimescale.ToString()); - REFrameworkNET.API.LogInfo("Current timescale: " + scene?.get_TimeScale().ToString()); + DangerousFunctions.Entry(); dynamic optionManager = REFrameworkNET.API.GetManagedSingleton("app.OptionManager"); @@ -150,5 +254,21 @@ public static void Main(REFrameworkNET.API api) { REFrameworkNET.API.LogInfo("GuiManager runtime type assembly: " + test.get_Assembly()); REFrameworkNET.API.LogInfo("GuiManager runtime type assembly name: " + test.get_Assembly().get_Location()); } + + //IGUIManager proxyGuiManager = ManagedObjectProxy.Create(guiManager); + //var proxyOptionData = proxyGuiManager.getOptionData(); + + //REFrameworkNET.API.LogInfo("ProxyOptionData: " + proxyOptionData?.ToString() + ": " + proxyOptionData?.GetTypeDefinition()?.GetFullName()?.ToString()); + + } catch (Exception e) { + REFrameworkNET.API.LogError(e.ToString()); + + var ex = e; + + while (ex.InnerException != null) { + ex = ex.InnerException; + REFrameworkNET.API.LogError(ex.ToString()); + } + } } }; \ No newline at end of file From 4b2b7b7651f7d16a2eb81ea4a230e7d26301cf54 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 21 Mar 2024 01:33:36 -0700 Subject: [PATCH 032/207] .NET: Generate ref assemblies prior to plugin loading --- .../AssemblyGenerator/ClassGenerator.cs | 2 +- csharp-api/AssemblyGenerator/Generator.cs | 29 ++++++++-- csharp-api/Compiler/Compiler.cs | 11 ++++ csharp-api/REFrameworkNET/PluginManager.cpp | 56 +++++++++++++++++++ csharp-api/REFrameworkNET/PluginManager.hpp | 1 + 5 files changed, 92 insertions(+), 7 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 5f6b7b997..955159035 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -143,7 +143,7 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra break; default: if (methodReturnT != null && methodReturnName != "") { - if (!AssemblyGenerator.validTypes.Contains(methodReturnName)) { + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(methodReturnName)) { returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; } diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index b4c219f4d..9ace842f0 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -132,6 +132,7 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { } } +namespace REFrameworkNET { public class AssemblyGenerator { static Dictionary namespaces = []; @@ -309,9 +310,9 @@ [.. methods] return compilationUnit; } - public static void Main(REFrameworkNET.API api) { + public static List Main(REFrameworkNET.API api) { try { - MainImpl(); + return MainImpl(); } catch (Exception e) { Console.WriteLine("Exception: " + e); @@ -321,9 +322,11 @@ public static void Main(REFrameworkNET.API api) { Console.WriteLine("Inner Exception: " + ex); } } + + return []; } - public static void MainImpl() { + public static List MainImpl() { Il2CppDump.FillTypeExtensions(REFrameworkNET.API.GetTDB()); List compilationUnits = new List(); @@ -373,6 +376,11 @@ public static void MainImpl() { }*/ var normalized = compilationUnit.NormalizeWhitespace(); + string compilationUnitHash = ""; + + using (var sha1 = System.Security.Cryptography.SHA1.Create()) { + compilationUnitHash = BitConverter.ToString(sha1.ComputeHash(compilationUnit.ToFullString().Select(c => (byte)c).ToArray())).Replace("-", ""); + } // Dump to DynamicAssembly.cs File.WriteAllText("DynamicAssembly.cs", normalized.ToFullString()); @@ -439,14 +447,23 @@ public static void MainImpl() { { // Load and use the compiled assembly ms.Seek(0, SeekOrigin.Begin); - var assembly = Assembly.Load(ms.ToArray()); + //var assembly = Assembly.Load(ms.ToArray()); // dump to file - File.WriteAllBytes("DynamicAssembly.dll", ms.ToArray()); + //File.WriteAllBytes("DynamicAssembly.dll", ms.ToArray()); REFrameworkNET.API.LogInfo("Successfully compiled DynamicAssembly.dll"); + + return [ + new REFrameworkNET.Compiler.DynamicAssemblyBytecode { + Bytecode = ms.ToArray(), + Hash = compilationUnitHash + } + ]; } } + return []; } -}; \ No newline at end of file +}; +} \ No newline at end of file diff --git a/csharp-api/Compiler/Compiler.cs b/csharp-api/Compiler/Compiler.cs index 75307c6a5..209a9edfb 100644 --- a/csharp-api/Compiler/Compiler.cs +++ b/csharp-api/Compiler/Compiler.cs @@ -13,6 +13,17 @@ namespace REFrameworkNET { public class Compiler { + public class DynamicAssemblyBytecode { + public byte[] Bytecode { + get; + set; + } + public string Hash { + get; + set; + } + } + static public byte[] Compile(string filepath, Assembly executingAssembly, List deps) { var sourceCode = File.ReadAllText(filepath); diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 4f399fba9..02ed55701 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -121,6 +121,46 @@ namespace REFrameworkNET { return assemblies; } + void PluginManager::GenerateReferenceAssemblies(System::Collections::Generic::List^ deps) { + REFrameworkNET::API::LogInfo("Generating reference assemblies..."); + + // Look for AssemblyGenerator class in the loaded deps + for each (System::Reflection::Assembly^ a in deps) { + if (auto generator = a->GetType("REFrameworkNET.AssemblyGenerator"); generator != nullptr) { + // Look for Main method in the AssemblyGenerator class + auto mainMethod = generator->GetMethod( + "Main", + System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, + gcnew array{REFrameworkNET::API::typeid}); + + if (mainMethod != nullptr) { + REFrameworkNET::API::LogInfo("Found AssemblyGenerator.Main in " + a->Location); + + array^ args = gcnew array{PluginManager::s_api_instance}; + auto result = (List^)mainMethod->Invoke(nullptr, args); + + // Append the generated assemblies to the list of deps + for each (Compiler::DynamicAssemblyBytecode^ bytes in result) { + REFrameworkNET::API::LogInfo("Adding generated assembly to deps..."); + + auto path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies" / (msclr::interop::marshal_as(bytes->Hash) + "_DYNAMIC.dll"); + System::IO::File::WriteAllBytes(gcnew System::String(path.wstring().c_str()), bytes->Bytecode); + REFrameworkNET::API::LogInfo("Wrote generated assembly to " + gcnew System::String(path.wstring().c_str())); + + auto assem = System::Reflection::Assembly::LoadFrom(gcnew System::String(path.wstring().c_str())); + + if (assem != nullptr) { + REFrameworkNET::API::LogInfo("Loaded generated assembly with " + assem->GetTypes()->Length + " types"); + deps->Add(assem); + } + } + + break; + } + } + } + } + // meant to be executed in the correct context // after loading "ourselves" via System::Reflection::Assembly::LoadFrom bool PluginManager::LoadPlugins(uintptr_t param_raw) try { @@ -145,6 +185,22 @@ namespace REFrameworkNET { auto deps = LoadDependencies(); // Pre-loads DLLs in the dependencies folder before loading the plugins + try { + GenerateReferenceAssemblies(deps); + } catch(System::Exception^ e) { + REFrameworkNET::API::LogError("Could not generate reference assemblies: " + e->Message); + + auto ex = e; + while (ex != nullptr) { + REFrameworkNET::API::LogError(ex->StackTrace); + ex = ex->InnerException; + } + } catch (const std::exception& e) { + REFrameworkNET::API::LogError("Could not generate reference assemblies: " + gcnew System::String(e.what())); + } catch (...) { + REFrameworkNET::API::LogError("Could not generate reference assemblies: Unknown exception caught"); + } + // Try-catch because the user might not have the compiler // dependencies in the plugins directory try { diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index a91ff349b..700872589 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -30,6 +30,7 @@ private ref class PluginManager // meant to be executed in the correct context // after loading "ourselves" via System::Reflection::Assembly::LoadFrom static System::Collections::Generic::List^ LoadDependencies(); + static void GenerateReferenceAssemblies(System::Collections::Generic::List^ deps); static bool LoadPlugins(uintptr_t param_raw); static bool LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps); }; From b5d1739723e512cc6b309f0c5a110de74a8c7b26 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 21 Mar 2024 02:19:56 -0700 Subject: [PATCH 033/207] .NET: Add support for treating dynamics as IEnumerable --- csharp-api/REFrameworkNET/Method.cpp | 9 +- csharp-api/REFrameworkNET/NativeObject.hpp | 88 +++++++++++++++++++- csharp-api/REFrameworkNET/TypeDefinition.cpp | 11 +++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 12 ++- csharp-api/test/Test/Test.cs | 8 ++ 5 files changed, 124 insertions(+), 4 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 5f9a8c2ae..88c162892 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -6,11 +6,18 @@ #include "Method.hpp" #include "Field.hpp" +#include "API.hpp" + #include "Utility.hpp" namespace REFrameworkNET { REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { - // We need to convert the managed objects to 8 byte representations + if (obj == nullptr && !this->IsStatic()) { + System::String^ declaringName = this->GetDeclaringType() != nullptr ? this->GetDeclaringType()->GetFullName() : "Unknown"; + System::String^ errorStr = "Cannot invoke a non-static method without an object (" + declaringName + "." + this->GetName() + ")"; + REFrameworkNET::API::LogError(errorStr); + throw gcnew System::InvalidOperationException(errorStr); + } std::vector args2{}; diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index 33fbb46bf..156a4f610 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -3,6 +3,7 @@ #include #include "TypeDefinition.hpp" +#include "InvokeRet.hpp" namespace REFrameworkNET { ref class InvokeRet; @@ -11,19 +12,39 @@ ref class InvokeRet; // However, they still have reflection information associated with them // So this intends to be the "ManagedObject" class for native objects // So we can easily interact with them in C# -public ref class NativeObject : public System::Dynamic::DynamicObject +public ref class NativeObject : public System::Dynamic::DynamicObject, public System::Collections::IEnumerable { public: NativeObject(uintptr_t obj, TypeDefinition^ t){ + if (t == nullptr) { + throw gcnew System::ArgumentNullException("t"); + } + m_object = (void*)obj; m_type = t; } - NativeObject(void* obj, TypeDefinition^ t){ + NativeObject(void* obj, TypeDefinition^ t) { + if (t == nullptr) { + throw gcnew System::ArgumentNullException("t"); + } + m_object = obj; m_type = t; } + // For invoking static methods + // e.g. NativeObject^ obj = new NativeObject(TypeDefinition::GetType("System.AppDomain")); + // obj.get_CurrentDomain().GetAssemblies(); + NativeObject(TypeDefinition^ t) { + if (t == nullptr) { + throw gcnew System::ArgumentNullException("t"); + } + + m_type = t; + m_object = nullptr; + } + TypeDefinition^ GetTypeDefinition() { return m_type; } @@ -41,6 +62,69 @@ public ref class NativeObject : public System::Dynamic::DynamicObject bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; +public: + // IEnumerable implementation + virtual System::Collections::IEnumerator^ GetEnumerator() { + return gcnew NativeObjectEnumerator(this); + } + +private: + ref class NativeObjectEnumerator : public System::Collections::IEnumerator + { + int position = -1; + NativeObject^ nativeObject; + + public: + NativeObjectEnumerator(NativeObject^ nativeObject) { + this->nativeObject = nativeObject; + } + + // IEnumerator implementation + virtual bool MoveNext() { + int itemCount = GetItemCount(); + if (position < itemCount - 1) { + position++; + return true; + } + return false; + } + + virtual void Reset() { + position = -1; + } + + virtual property System::Object^ Current { + System::Object^ get() { + if (position == -1 || position >= GetItemCount()) { + throw gcnew System::InvalidOperationException(); + } + + System::Object^ result = nullptr; + if (nativeObject->HandleInvokeMember_Internal("get_Item", gcnew array{ position }, result)) { + return result; + } + + return nullptr; + } + } + + private: + int GetItemCount() { + //return nativeObject->Invoke("get_Count", gcnew array{})->DWord; + System::Object^ result = nullptr; + + if (nativeObject->HandleInvokeMember_Internal("get_Count", gcnew array{}, result)) { + return (int)result; + } + + if (nativeObject->HandleInvokeMember_Internal("get_Length", gcnew array{}, result)) { + return (int)result; + } + + return 0; + } + }; + private: void* m_object{}; TypeDefinition^ m_type{}; diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index b3630ce5a..86df9ee28 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -3,10 +3,15 @@ #include "Field.hpp" #include "Property.hpp" #include "ManagedObject.hpp" +#include "NativeObject.hpp" #include "TypeDefinition.hpp" namespace REFrameworkNET { + NativeObject^ TypeDefinition::Statics::get() { + return gcnew NativeObject(this); + } + REFrameworkNET::Method^ TypeDefinition::FindMethod(System::String^ name) { auto result = m_type->find_method(msclr::interop::marshal_as(name)); @@ -120,4 +125,10 @@ namespace REFrameworkNET { return gcnew ManagedObject(result); } + + bool TypeDefinition::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) { + // Forward this onto NativeObject.TryInvokeMember (for static methods) + auto native = gcnew NativeObject(this); + return native->TryInvokeMember(binder, args, result); + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 5b45a8de4..7aa81c3a7 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -8,6 +8,7 @@ namespace REFrameworkNET { ref class ManagedObject; +ref class NativeObject; ref class Method; ref class Field; ref class Property; @@ -22,7 +23,7 @@ public enum VMObjType { ValType = 5, }; -public ref class TypeDefinition : public System::IEquatable +public ref class TypeDefinition : public System::Dynamic::DynamicObject, public System::IEquatable { public: TypeDefinition(reframework::API::TypeDefinition* td) : m_type(td) {} @@ -32,6 +33,10 @@ public ref class TypeDefinition : public System::IEquatable return (reframework::API::TypeDefinition*)m_type; } + property NativeObject^ Statics { + NativeObject^ get(); + } + uint32_t GetIndex() { return m_type->get_index(); @@ -299,6 +304,11 @@ public ref class TypeDefinition : public System::IEquatable return m_type->create_instance_deprecated(); }*/ +// DynamicObject methods +public: + virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; + +// IEquatable methods public: virtual bool Equals(System::Object^ other) override { if (System::Object::ReferenceEquals(this, other)) { diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index dc94c9579..b81c20096 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -260,6 +260,14 @@ public static void Main(REFrameworkNET.API api) { //REFrameworkNET.API.LogInfo("ProxyOptionData: " + proxyOptionData?.ToString() + ": " + proxyOptionData?.GetTypeDefinition()?.GetFullName()?.ToString()); + dynamic appdomainT = tdb.GetType("System.AppDomain"); + dynamic appdomain = appdomainT.get_CurrentDomain(); + dynamic assemblies = appdomain?.GetAssemblies(); + + foreach (dynamic assembly in assemblies) { + REFrameworkNET.API.LogInfo("Assembly: " + assembly.get_Location()?.ToString()); + } + } catch (Exception e) { REFrameworkNET.API.LogError(e.ToString()); From 0eede9470888aae7dc57a2f45cc736e189d24e89 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 21 Mar 2024 09:19:35 -0700 Subject: [PATCH 034/207] .NET: Convert System.RuntimeTypeHandle to TypeDefinition --- csharp-api/REFrameworkNET/Method.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 88c162892..6f795b19c 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -13,7 +13,7 @@ namespace REFrameworkNET { REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { if (obj == nullptr && !this->IsStatic()) { - System::String^ declaringName = this->GetDeclaringType() != nullptr ? this->GetDeclaringType()->GetFullName() : "Unknown"; + System::String^ declaringName = this->GetDeclaringType() != nullptr ? this->GetDeclaringType()->GetFullName() : gcnew System::String("UnknownType"); System::String^ errorStr = "Cannot invoke a non-static method without an object (" + declaringName + "." + this->GetName() + ")"; REFrameworkNET::API::LogError(errorStr); throw gcnew System::InvalidOperationException(errorStr); @@ -169,6 +169,10 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::String^ me // for consistency purposes MAKE_TYPE_HANDLER_2(System, Single, double, Double) MAKE_TYPE_HANDLER_2(System, Double, double, Double) + case "System.RuntimeTypeHandle"_fnv: { + result = gcnew REFrameworkNET::TypeDefinition((::REFrameworkTypeDefinitionHandle)tempResult->QWord); + break; + } default: result = tempResult; break; From ff33c863322d0d4cb508a35a18b236fba8c126fe Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 24 Mar 2024 00:07:21 -0700 Subject: [PATCH 035/207] .NET: ManagedObject sanity checks --- csharp-api/REFrameworkNET/ManagedObject.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index e865ee29c..fb9fc825d 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -14,10 +14,14 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S { public: ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj) { - AddRef(); + if (obj != nullptr) { + AddRef(); + } } ManagedObject(::REFrameworkManagedObjectHandle handle) : m_object(reinterpret_cast(handle)) { - AddRef(); + if (handle != nullptr) { + AddRef(); + } } ~ManagedObject() { From d3346b57f1318d2338c6ff0f7cf0bcef476984f6 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 24 Mar 2024 01:22:58 -0700 Subject: [PATCH 036/207] .NET: Significant speed improvement to ref assembly generation --- csharp-api/AssemblyGenerator/Generator.cs | 146 +++++++++---------- csharp-api/CMakeLists.txt | 14 +- csharp-api/REFrameworkNET/TypeDefinition.hpp | 9 +- csharp-api/cmake.toml | 9 +- 4 files changed, 97 insertions(+), 81 deletions(-) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 9ace842f0..0bb25f0e4 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -12,6 +12,8 @@ using System.Text.Json.Serialization; using System; using System.Linq; +using System.Threading.Tasks; +using System.Collections.Concurrent; public class Il2CppDump { class Field { @@ -19,10 +21,10 @@ class Field { }; public class Method { - private REFrameworkNET.Method impl; + public REFrameworkNET.Method Impl; public Method(REFrameworkNET.Method impl) { - this.impl = impl; + this.Impl = impl; } public bool? Override { get; set;} // Not from JSON @@ -66,7 +68,7 @@ public Type(REFrameworkNET.TypeDefinition impl) { // Custom stuff below - public HashSet? NestedTypes { get; set;} + public HashSet NestedTypes = []; }; static private Dictionary typeExtensions = []; @@ -79,6 +81,17 @@ public Type(REFrameworkNET.TypeDefinition impl) { return null; } + static public Type GetOrAddTypeExtension(REFrameworkNET.TypeDefinition type) { + if (typeExtensions.TryGetValue(type, out Type? value)) { + return value; + } + + value = new Type(type); + typeExtensions[type] = value; + + return value; + } + static public Method? GetMethodExtension(REFrameworkNET.Method method) { if (methodExtensions.TryGetValue(method, out Method? value)) { return value; @@ -92,43 +105,52 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { return; } - // Look for types that have a declaring type and add them to the declaring type's nested types + context.GetType(0).GetFullName(); // initialize the types + + //Parallel.For(0, context.GetNumTypes(), i => foreach (REFrameworkNET.TypeDefinition t in context.Types) { - if (t.DeclaringType != null) { - if (!typeExtensions.TryGetValue(t.DeclaringType, out Type? value)) { - value = new Type(t.DeclaringType); - typeExtensions[t.DeclaringType] = value; - } + //var t = context.GetType((uint)i); + if (t == null) { + //Console.WriteLine("Failed to get type " + i); + continue; + } - //value.NestedTypes ??= []; - if (value.NestedTypes == null) { - value.NestedTypes = new HashSet(); - } - value.NestedTypes.Add(t); + var tDeclaringType = t.DeclaringType; + if (tDeclaringType != null) { + var ext = GetOrAddTypeExtension(tDeclaringType); + ext.NestedTypes.Add(t); + } - //System.Console.WriteLine("Adding nested type " + t.GetFullName() + " to " + t.DeclaringType.GetFullName()); + if (t.GetNumMethods() == 0 || t.ParentType == null) { + continue; } // Look for methods with the same name and mark them as overrides - for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - if (parent.Methods.Count == 0 || t.Methods.Count == 0) { + // We dont go through all parents, because GetMethod does that for us + // Going through all parents would exponentially increase the number of checks and they would be redundant + var parent = t.ParentType; + var tMethods = t.GetMethods(); + + //foreach (var method in t.Methods) { + //Parallel.ForEach(tMethods, method => { + foreach (var method in tMethods) { // parallel isn't necessary here because there arent many methods + if (method == null) { continue; } - foreach (var method in t.Methods) { - var parentMethod = parent.GetMethod(method.Name); + if (GetMethodExtension(method) != null) { + continue; + } - if (parentMethod != null) { - if (!methodExtensions.TryGetValue(method, out Method? value)) { - value = new Method(method); - methodExtensions[method] = value; - } + var parentMethod = parent.GetMethod(method.Name); - value.Override = true; - } + if (parentMethod != null) { + methodExtensions.Add(method, new Method(method) { + Override = true + }); } } - } + } } } @@ -139,50 +161,21 @@ public class AssemblyGenerator { // Start with an empty CompilationUnitSyntax (represents an empty file) static CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit(); - static public NamespaceDeclarationSyntax? ExtractNamespaceFromTypeName(REFrameworkNET.TDB context, string typeName) { - if (context == null || context.Types == null) { - return null; - } + static public NamespaceDeclarationSyntax? ExtractNamespaceFromType(REFrameworkNET.TypeDefinition t) { + var ns = t.GetNamespace(); - var parts = typeName.Split('.'); - var currentTypeName = ""; - - NamespaceDeclarationSyntax? currentNamespaceDecl = null; - string currentNamespaceName = ""; - - for (var i = 0; i < parts.Length; i++) { - var part = parts[i]; - currentTypeName += part; - - if (context.GetType(currentTypeName) != null) { - // Return a blank namespace - if (currentNamespaceDecl == null) { - System.Console.WriteLine("Creating blank namespace for " + currentTypeName); - currentNamespaceDecl = SyntaxTreeBuilder.CreateNamespace(""); - } - - return currentNamespaceDecl; - } - - currentNamespaceName += part; - - // Create via namespace in list of namespaces if not exist - if (!namespaces.TryGetValue(currentTypeName, out NamespaceDeclarationSyntax? value)) { - // Clean up the namespace name, remove any non-compliant characters other than "." and alphanumerics - currentNamespaceName = Regex.Replace(currentTypeName, @"[^a-zA-Z0-9.]", "_"); - - Console.WriteLine("Creating namespace " + currentNamespaceName); - value = SyntaxTreeBuilder.CreateNamespace(currentNamespaceName); - namespaces[currentNamespaceName] = value; - currentNamespaceDecl = value; - } else { - currentNamespaceDecl = value; + if (ns != null) { + if (!namespaces.TryGetValue(ns, out NamespaceDeclarationSyntax? value)) { + //ns = Regex.Replace(ns, @"[^a-zA-Z0-9.]", "_"); + Console.WriteLine("Creating namespace " + ns); + value = SyntaxTreeBuilder.CreateNamespace(ns); + namespaces[ns] = value; } - currentTypeName += "."; + return value; } - return currentNamespaceDecl; + return null; } public static SortedSet validTypes = []; @@ -193,28 +186,35 @@ static void FillValidEntries(REFrameworkNET.TDB context) { return; } - // TDB only has GetType(index) and GetNumTypes() - foreach (REFrameworkNET.TypeDefinition t in context.Types) { + ConcurrentBag threadSafeValidTypes = []; + + Parallel.For(0, context.GetNumTypes(), i => { + var t = context.GetType((uint)i); var typeName = t.GetFullName(); if (typeName.Length == 0) { Console.WriteLine("Bad type name"); - continue; + return; } if (typeName.Contains("WrappedArrayContainer")) { - continue; + return; } if (typeName.Contains("[") || typeName.Contains("]") || typeName.Contains('<')) { - continue; + return; } // Skip system types + // TODO: Fix this if (typeName.StartsWith("System.")) { - continue; + return; } + threadSafeValidTypes.Add(typeName); + }); + + foreach (var typeName in threadSafeValidTypes) { validTypes.Add(typeName); } } @@ -279,7 +279,7 @@ [.. methods] return compilationUnit; } - var generatedNamespace = ExtractNamespaceFromTypeName(context, typeName); + var generatedNamespace = ExtractNamespaceFromType(t); if (generatedNamespace != null) { // Split the using types by their namespace diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 0fe97f4cb..13c7cad01 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -35,6 +35,7 @@ include(CSharpUtilities) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") +set(CMAKE_CSharp_FLAGS "${CMAKE_CSHARP_FLAGS} /langversion:latest /platform:x64") # Disable exceptions # string(REGEX REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") @@ -94,8 +95,6 @@ set_target_properties(REFCSharpCompiler PROPERTIES net8.0-windows VS_CONFIGURATION_TYPE ClassLibrary - CMAKE_CSharp_FLAGS - "/langversion:latest /platform:x64" ) set(CMKR_TARGET REFCSharpCompiler) @@ -186,6 +185,13 @@ add_custom_command( set(REFRAMEWORK_DOT_NET_ASSEMBLY_DIR "${CMAKE_BINARY_DIR}/bin") set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/REFramework.NET.dll") +set_target_properties(csharp-api PROPERTIES +VS_DOTNET_REFERENCE_REFCSharpCompiler +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFCSharpCompiler.dll" +) + +set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCSharpCompiler") + # Target: AssemblyGenerator set(AssemblyGenerator_SOURCES "AssemblyGenerator/ClassGenerator.cs" @@ -217,8 +223,6 @@ set_target_properties(AssemblyGenerator PROPERTIES net8.0-windows VS_CONFIGURATION_TYPE ClassLibrary - CMAKE_CSharp_FLAGS - "/langversion:latest /platform:x64" ) set(CMKR_TARGET AssemblyGenerator) @@ -255,8 +259,6 @@ set_target_properties(CSharpAPITest PROPERTIES net8.0-windows VS_CONFIGURATION_TYPE ClassLibrary - CMAKE_CSharp_FLAGS - "/langversion:latest /platform:x64" ) set(CMKR_TARGET CSharpAPITest) diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 7aa81c3a7..91fd6e4c6 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -23,7 +23,9 @@ public enum VMObjType { ValType = 5, }; -public ref class TypeDefinition : public System::Dynamic::DynamicObject, public System::IEquatable +public + ref class TypeDefinition : public System::Dynamic::DynamicObject, + public System::IEquatable { public: TypeDefinition(reframework::API::TypeDefinition* td) : m_type(td) {} @@ -37,6 +39,11 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, public NativeObject^ get(); } + TypeDefinition^ Clone() { + return gcnew TypeDefinition(m_type); + } + + uint32_t GetIndex() { return m_type->get_index(); diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 7450ac2de..7f2741bb3 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -8,6 +8,7 @@ languages = ["CXX", "C", "CSharp"] cmake-after = """ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") +set(CMAKE_CSharp_FLAGS "${CMAKE_CSHARP_FLAGS} /langversion:latest /platform:x64") # Disable exceptions # string(REGEX REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") @@ -54,7 +55,6 @@ LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib" DOTNET_SDK = "Microsoft.NET.Sdk" DOTNET_TARGET_FRAMEWORK = "net8.0-windows" VS_CONFIGURATION_TYPE = "ClassLibrary" -CMAKE_CSharp_FLAGS = "/langversion:latest /platform:x64" [target.REFCSharpCompiler] type = "CSharpSharedTarget" @@ -89,6 +89,13 @@ add_custom_command( ) set(REFRAMEWORK_DOT_NET_ASSEMBLY_DIR "${CMAKE_BINARY_DIR}/bin") set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/REFramework.NET.dll") + +set_target_properties(csharp-api PROPERTIES +VS_DOTNET_REFERENCE_REFCSharpCompiler +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFCSharpCompiler.dll" +) + +set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCSharpCompiler") """ [target.csharp-api.properties] From c5a16a72a2dcfdb5f2c6b31418752189658b2614 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 24 Mar 2024 13:03:53 -0700 Subject: [PATCH 037/207] .NET: Add script for deploying symlinks from game to build dir --- csharp-api/make_symlinks.py | 83 +++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 csharp-api/make_symlinks.py diff --git a/csharp-api/make_symlinks.py b/csharp-api/make_symlinks.py new file mode 100644 index 000000000..1552cdf5a --- /dev/null +++ b/csharp-api/make_symlinks.py @@ -0,0 +1,83 @@ +# Script to deploy symlinks from our game folder to our build folder for various DLLs and other files + +import fire +import os +import ctypes + +def symlink_main(gamedir=None, bindir="build/bin"): + if gamedir is None: + print("Usage: make_symlinks.py --gamedir=") + return + + # Throw an error if the user is not a privileged user + try: + is_admin = os.getuid() == 0 + except AttributeError: + is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 + + if not is_admin: + print("Error: This script must be run as an administrator") + return + + # Get the current working directory + if not os.path.exists(bindir): + print(f"Error: Directory {bindir} does not exist") + return + + plugins_dir_files = [ + "REFramework.NET.dll", + "REFramework.NET.runtimeconfig.json", + "Ijwhost.dll", + ] + + if os.path.exists("Test/Test/Test.cs"): + print("Creating symlink for Test.cs") + src = "Test/Test/Test.cs" + # modify to full path + src = os.path.abspath(src) + dst = os.path.join(gamedir, "reframework", "plugins", "source", "Test.cs") + + os.makedirs(os.path.dirname(dst), exist_ok=True) + + try: + os.remove(dst) + except FileNotFoundError: + pass + + os.symlink(src, dst) + + for file in plugins_dir_files: + src = os.path.join(bindir, file) + src = os.path.abspath(src) + dst = os.path.join(gamedir, "reframework", "plugins", file) + os.makedirs(os.path.dirname(dst), exist_ok=True) + try: + os.remove(dst) + except FileNotFoundError: + pass + os.symlink(src, dst) + + dependencies_dir_files = [ + "AssemblyGenerator.dll", + "REFCSharpCompiler.dll", + "Microsoft.CodeAnalysis.CSharp.dll", + "Microsoft.CodeAnalysis.dll", + "Microsoft.CodeAnalysis.CSharp.xml", + "Microsoft.CodeAnalysis.xml", + ] + + for file in dependencies_dir_files: + src = os.path.join(bindir, file) + src = os.path.abspath(src) + dst = os.path.join(gamedir, "reframework", "plugins", "managed", "dependencies", file) + os.makedirs(os.path.dirname(dst), exist_ok=True) + try: + os.remove(dst) + except FileNotFoundError: + pass + os.symlink(src, dst) + + print("Symlinks created successfully") + +if __name__ == '__main__': + fire.Fire(symlink_main) \ No newline at end of file From e90bbac6702cd0edd69e2d25214e39d76d3b24c5 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 25 Mar 2024 01:54:29 -0700 Subject: [PATCH 038/207] .NET: Initial working method hooking --- csharp-api/CMakeLists.txt | 2 + csharp-api/REFrameworkNET/Method.cpp | 5 ++ csharp-api/REFrameworkNET/Method.hpp | 26 ++++--- .../REFrameworkNET/MethodHookWrapper.cpp | 47 +++++++++++ .../REFrameworkNET/MethodHookWrapper.hpp | 78 +++++++++++++++++++ csharp-api/test/Test/Test.cs | 17 ++++ 6 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 csharp-api/REFrameworkNET/MethodHookWrapper.cpp create mode 100644 csharp-api/REFrameworkNET/MethodHookWrapper.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 13c7cad01..3b7136801 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -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" @@ -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" diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 6f795b19c..f16d769be 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -3,6 +3,7 @@ #include "ManagedObject.hpp" #include "NativeObject.hpp" +#include "MethodHookWrapper.hpp" #include "Method.hpp" #include "Field.hpp" @@ -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^ args) { if (obj == nullptr && !this->IsStatic()) { System::String^ declaringName = this->GetDeclaringType() != nullptr ? this->GetDeclaringType()->GetFullName() : gcnew System::String("UnknownType"); diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 971257775..c6e0f54b3 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -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 { public: Method(reframework::API::Method* method) : m_method(method) {} + void* GetRaw() { + return m_method; + } + REFrameworkNET::InvokeRet^ Invoke(System::Object^ obj, array^ args); bool HandleInvokeMember_Internal(System::Object^ obj, System::String^ methodName, array^ 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()); @@ -144,14 +155,11 @@ public ref class Method : public System::IEquatable } } - // 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^ 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 { diff --git a/csharp-api/REFrameworkNET/MethodHookWrapper.cpp b/csharp-api/REFrameworkNET/MethodHookWrapper.cpp new file mode 100644 index 000000000..c8a2fb6df --- /dev/null +++ b/csharp-api/REFrameworkNET/MethodHookWrapper.cpp @@ -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()); // 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!"); + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/MethodHookWrapper.hpp b/csharp-api/REFrameworkNET/MethodHookWrapper.hpp new file mode 100644 index 000000000..978e1bcec --- /dev/null +++ b/csharp-api/REFrameworkNET/MethodHookWrapper.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include + +#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^ s_hooked_methods = gcnew System::Collections::Generic::Dictionary(); + + 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{}; +}; +} \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index b81c20096..2f70559b3 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -87,7 +87,24 @@ public class NativeProxy : Proxy { } public class DangerousFunctions { + public static REFrameworkNET.PreHookResult isInsidePreHook(System.Collections.Generic.List 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 From 4c414674876275e4b17ff57efdd028ce8fdeaeff Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 25 Mar 2024 03:41:32 -0700 Subject: [PATCH 039/207] .NET: Fix some value types returning null --- csharp-api/REFrameworkNET/Method.cpp | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index f16d769be..248628ea2 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -110,7 +110,7 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::String^ me //auto methodName = binder->Name; auto tempResult = this->Invoke(obj, args); - if (tempResult != nullptr && tempResult->QWord != 0) { + if (tempResult != nullptr) { auto returnType = this->GetReturnType(); if (returnType == nullptr) { @@ -120,22 +120,17 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::String^ me // Check the return type of the method and return it as a NativeObject if possible if (!returnType->IsValueType()) { - if (returnType->GetVMObjType() == VMObjType::Object) { - if (tempResult->QWord == 0) { - result = nullptr; - return true; - } + if (tempResult->QWord == 0) { + result = nullptr; + return true; + } + if (returnType->GetVMObjType() == VMObjType::Object) { result = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); return true; } if (returnType->GetVMObjType() == VMObjType::String) { - if (tempResult->QWord == 0) { - result = nullptr; - return true; - } - // Maybe don't create the GC version and just use the native one? auto strObject = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); auto strType = strObject->GetTypeDefinition(); From 6f7d834b39e934299f157c9e229dd5ea6bfda994 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 25 Mar 2024 03:53:19 -0700 Subject: [PATCH 040/207] Fix red squigglies --- csharp-api/REFrameworkNET/MethodHookWrapper.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/MethodHookWrapper.hpp b/csharp-api/REFrameworkNET/MethodHookWrapper.hpp index 978e1bcec..3aadc1a69 100644 --- a/csharp-api/REFrameworkNET/MethodHookWrapper.hpp +++ b/csharp-api/REFrameworkNET/MethodHookWrapper.hpp @@ -16,7 +16,10 @@ public ref class MethodHookWrapper static MethodHookWrapper^ Create(Method^ method, bool ignore_jmp) { if (s_hooked_methods->ContainsKey(method)) { - return s_hooked_methods[method]; + MethodHookWrapper^ out = nullptr; + s_hooked_methods->TryGetValue(method, out); + + return out; } auto wrapper = gcnew MethodHookWrapper(method, ignore_jmp); From 565c6e0bf0f4359d5605d1a1762f629714495880 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 25 Mar 2024 03:56:29 -0700 Subject: [PATCH 041/207] .NET: MethodHookWrapper->MethodHook --- csharp-api/CMakeLists.txt | 4 ++-- csharp-api/REFrameworkNET/Method.cpp | 6 ++--- csharp-api/REFrameworkNET/Method.hpp | 4 ++-- .../{MethodHookWrapper.cpp => MethodHook.cpp} | 10 ++++----- .../{MethodHookWrapper.hpp => MethodHook.hpp} | 22 +++++++++---------- 5 files changed, 23 insertions(+), 23 deletions(-) rename csharp-api/REFrameworkNET/{MethodHookWrapper.cpp => MethodHook.cpp} (76%) rename csharp-api/REFrameworkNET/{MethodHookWrapper.hpp => MethodHook.hpp} (76%) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 3b7136801..036212653 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -107,7 +107,7 @@ set(csharp-api_SOURCES "REFrameworkNET/Callbacks.cpp" "REFrameworkNET/ManagedObject.cpp" "REFrameworkNET/Method.cpp" - "REFrameworkNET/MethodHookWrapper.cpp" + "REFrameworkNET/MethodHook.cpp" "REFrameworkNET/NativeObject.cpp" "REFrameworkNET/Plugin.cpp" "REFrameworkNET/PluginManager.cpp" @@ -120,7 +120,7 @@ set(csharp-api_SOURCES "REFrameworkNET/ManagedObject.hpp" "REFrameworkNET/ManagedSingleton.hpp" "REFrameworkNET/Method.hpp" - "REFrameworkNET/MethodHookWrapper.hpp" + "REFrameworkNET/MethodHook.hpp" "REFrameworkNET/MethodParameter.hpp" "REFrameworkNET/NativeObject.hpp" "REFrameworkNET/Plugin.hpp" diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 248628ea2..a91b7e012 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -3,7 +3,7 @@ #include "ManagedObject.hpp" #include "NativeObject.hpp" -#include "MethodHookWrapper.hpp" +#include "MethodHook.hpp" #include "Method.hpp" #include "Field.hpp" @@ -12,8 +12,8 @@ #include "Utility.hpp" namespace REFrameworkNET { -MethodHookWrapper^ Method::AddHook(bool ignore_jmp) { - return MethodHookWrapper::Create(this, ignore_jmp); +MethodHook^ Method::AddHook(bool ignore_jmp) { + return MethodHook::Create(this, ignore_jmp); } REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index c6e0f54b3..03d847d89 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -8,7 +8,7 @@ #include "MethodParameter.hpp" namespace REFrameworkNET { -ref class MethodHookWrapper; +ref class MethodHook; public enum class PreHookResult : int32_t { Continue = 0, @@ -159,7 +159,7 @@ public ref class Method : public System::IEquatable delegate PreHookResult REFPreHookDelegate(System::Collections::Generic::List^ args); delegate void REFPostHookDelegate(); - MethodHookWrapper^ AddHook(bool ignore_jmp); + MethodHook^ AddHook(bool ignore_jmp); public: virtual bool Equals(System::Object^ other) override { diff --git a/csharp-api/REFrameworkNET/MethodHookWrapper.cpp b/csharp-api/REFrameworkNET/MethodHook.cpp similarity index 76% rename from csharp-api/REFrameworkNET/MethodHookWrapper.cpp rename to csharp-api/REFrameworkNET/MethodHook.cpp index c8a2fb6df..4d62e5313 100644 --- a/csharp-api/REFrameworkNET/MethodHookWrapper.cpp +++ b/csharp-api/REFrameworkNET/MethodHook.cpp @@ -1,11 +1,11 @@ #include "./API.hpp" -#include "MethodHookWrapper.hpp" +#include "MethodHook.hpp" using namespace System::Runtime::InteropServices; using namespace System::Collections::Generic; namespace REFrameworkNET { - void MethodHookWrapper::InstallHooks(bool ignore_jmp) + void MethodHook::InstallHooks(bool ignore_jmp) { if (m_hooks_installed) { return; @@ -22,7 +22,7 @@ namespace REFrameworkNET { m_hook_id = raw->add_hook((REFPreHookFn)preHookPtr.ToPointer(), (REFPostHookFn)postHookPtr.ToPointer(), ignore_jmp); } - void MethodHookWrapper::UninstallHooks() { + void MethodHook::UninstallHooks() { if (!m_hooks_installed) { return; } @@ -33,14 +33,14 @@ namespace REFrameworkNET { raw->remove_hook(m_hook_id); } - int32_t MethodHookWrapper::OnPreStart_Raw(int argc, void** argv, REFrameworkTypeDefinitionHandle* arg_tys, unsigned long long ret_addr) + int32_t MethodHook::OnPreStart_Raw(int argc, void** argv, REFrameworkTypeDefinitionHandle* arg_tys, unsigned long long ret_addr) { OnPreStart(gcnew List()); // 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) { + void MethodHook::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!"); } diff --git a/csharp-api/REFrameworkNET/MethodHookWrapper.hpp b/csharp-api/REFrameworkNET/MethodHook.hpp similarity index 76% rename from csharp-api/REFrameworkNET/MethodHookWrapper.hpp rename to csharp-api/REFrameworkNET/MethodHook.hpp index 3aadc1a69..52d2df82a 100644 --- a/csharp-api/REFrameworkNET/MethodHookWrapper.hpp +++ b/csharp-api/REFrameworkNET/MethodHook.hpp @@ -9,31 +9,31 @@ 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 ref class MethodHook { public: // Public factory method to create a new hook - static MethodHookWrapper^ Create(Method^ method, bool ignore_jmp) + static MethodHook^ Create(Method^ method, bool ignore_jmp) { if (s_hooked_methods->ContainsKey(method)) { - MethodHookWrapper^ out = nullptr; + MethodHook^ out = nullptr; s_hooked_methods->TryGetValue(method, out); return out; } - auto wrapper = gcnew MethodHookWrapper(method, ignore_jmp); + auto wrapper = gcnew MethodHook(method, ignore_jmp); s_hooked_methods->Add(method, wrapper); return wrapper; } - MethodHookWrapper^ AddPre(Method::REFPreHookDelegate^ callback) + MethodHook^ AddPre(Method::REFPreHookDelegate^ callback) { OnPreStart += callback; return this; } - MethodHookWrapper^ AddPost(Method::REFPostHookDelegate^ callback) + MethodHook^ AddPost(Method::REFPostHookDelegate^ callback) { OnPostStart += callback; return this; @@ -45,22 +45,22 @@ public ref class MethodHookWrapper // This is never meant to publicly be called - MethodHookWrapper(Method^ method, bool ignore_jmp) + MethodHook(Method^ method, bool ignore_jmp) { m_method = method; - m_preHookLambda = gcnew REFPreHookDelegateRaw(this, &MethodHookWrapper::OnPreStart_Raw); - m_postHookLambda = gcnew REFPostHookDelegateRaw(this, &MethodHookWrapper::OnPostStart_Raw); + m_preHookLambda = gcnew REFPreHookDelegateRaw(this, &MethodHook::OnPreStart_Raw); + m_postHookLambda = gcnew REFPostHookDelegateRaw(this, &MethodHook::OnPostStart_Raw); InstallHooks(ignore_jmp); } - ~MethodHookWrapper() + ~MethodHook() { if (m_hooks_installed) { UninstallHooks(); } } - static System::Collections::Generic::Dictionary^ s_hooked_methods = gcnew System::Collections::Generic::Dictionary(); + static System::Collections::Generic::Dictionary^ s_hooked_methods = gcnew System::Collections::Generic::Dictionary(); 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); From 5625c4e452f2fa7abfdfb2ff18b95601338742b8 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 25 Mar 2024 17:03:41 -0700 Subject: [PATCH 042/207] .NET: Port Proxy classes into core API (C++/CLI side) --- csharp-api/CMakeLists.txt | 2 + csharp-api/REFrameworkNET/IObject.hpp | 18 +++ csharp-api/REFrameworkNET/ManagedObject.hpp | 33 ++++- csharp-api/REFrameworkNET/NativeObject.hpp | 13 +- csharp-api/REFrameworkNET/Proxy.cpp | 1 + csharp-api/REFrameworkNET/Proxy.hpp | 144 ++++++++++++++++++++ csharp-api/test/Test/Test.cs | 92 +------------ 7 files changed, 205 insertions(+), 98 deletions(-) create mode 100644 csharp-api/REFrameworkNET/IObject.hpp create mode 100644 csharp-api/REFrameworkNET/Proxy.cpp create mode 100644 csharp-api/REFrameworkNET/Proxy.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 036212653..6bfb0ab5c 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -111,6 +111,7 @@ set(csharp-api_SOURCES "REFrameworkNET/NativeObject.cpp" "REFrameworkNET/Plugin.cpp" "REFrameworkNET/PluginManager.cpp" + "REFrameworkNET/Proxy.cpp" "REFrameworkNET/TDB.cpp" "REFrameworkNET/TypeDefinition.cpp" "REFrameworkNET/API.hpp" @@ -126,6 +127,7 @@ set(csharp-api_SOURCES "REFrameworkNET/Plugin.hpp" "REFrameworkNET/PluginManager.hpp" "REFrameworkNET/Property.hpp" + "REFrameworkNET/Proxy.hpp" "REFrameworkNET/TDB.hpp" "REFrameworkNET/TypeDefinition.hpp" "REFrameworkNET/TypeInfo.hpp" diff --git a/csharp-api/REFrameworkNET/IObject.hpp b/csharp-api/REFrameworkNET/IObject.hpp new file mode 100644 index 000000000..6efa9c4b4 --- /dev/null +++ b/csharp-api/REFrameworkNET/IObject.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace REFrameworkNET { +ref class TypeDefinition; +ref struct InvokeRet; + +// Base interface of ManagedObject and NativeObject +public interface class IObject { + TypeDefinition^ GetTypeDefinition(); + void* Ptr(); + uintptr_t GetAddress(); + + virtual InvokeRet^ Invoke(System::String^ methodName, array^ args); + bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index fb9fc825d..367660ade 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include "IObject.hpp" #pragma managed @@ -10,7 +11,7 @@ ref class TypeInfo; ref class InvokeRet; ref class ManagedObject; -public ref class ManagedObject : public System::Dynamic::DynamicObject, public System::IEquatable +public ref class ManagedObject : public System::Dynamic::DynamicObject, public System::IEquatable, public REFrameworkNET::IObject { public: ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj) { @@ -38,11 +39,11 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S m_object->release(); } - void* Ptr() { + virtual void* Ptr() { return (void*)m_object; } - uintptr_t GetAddress() { + virtual uintptr_t GetAddress() { return (uintptr_t)m_object; } @@ -92,16 +93,36 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S } static bool IsManagedObject(uintptr_t ptr) { + if (ptr == 0) { + return false; + } + static auto fn = reframework::API::get()->param()->sdk->managed_object->is_managed_object; return fn((void*)ptr); } - TypeDefinition^ GetTypeDefinition(); + static ManagedObject^ ToManagedObject(uintptr_t ptr) { + if (ptr == 0) { + return nullptr; + } + + if (IsManagedObject(ptr)) { + return gcnew ManagedObject((reframework::API::ManagedObject*)ptr); + } + + return nullptr; + } + + static ManagedObject^ FromAddress(uintptr_t ptr) { + return ToManagedObject(ptr); + } + + virtual TypeDefinition^ GetTypeDefinition(); TypeInfo^ GetTypeInfo(); - bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); - REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); + virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; virtual bool TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) override; virtual bool TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) override; diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index 156a4f610..4eaa23df3 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -4,6 +4,7 @@ #include "TypeDefinition.hpp" #include "InvokeRet.hpp" +#include "IObject.hpp" namespace REFrameworkNET { ref class InvokeRet; @@ -12,7 +13,7 @@ ref class InvokeRet; // However, they still have reflection information associated with them // So this intends to be the "ManagedObject" class for native objects // So we can easily interact with them in C# -public ref class NativeObject : public System::Dynamic::DynamicObject, public System::Collections::IEnumerable +public ref class NativeObject : public System::Dynamic::DynamicObject, public System::Collections::IEnumerable, public REFrameworkNET::IObject { public: NativeObject(uintptr_t obj, TypeDefinition^ t){ @@ -45,21 +46,21 @@ public ref class NativeObject : public System::Dynamic::DynamicObject, public Sy m_object = nullptr; } - TypeDefinition^ GetTypeDefinition() { + virtual TypeDefinition^ GetTypeDefinition() { return m_type; } - void* Ptr() { + virtual void* Ptr() { return m_object; } - uintptr_t GetAddress() { + virtual uintptr_t GetAddress() { return (uintptr_t)m_object; } - InvokeRet^ Invoke(System::String^ methodName, array^ args); + virtual InvokeRet^ Invoke(System::String^ methodName, array^ args); - bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; public: diff --git a/csharp-api/REFrameworkNET/Proxy.cpp b/csharp-api/REFrameworkNET/Proxy.cpp new file mode 100644 index 000000000..4ab3540f8 --- /dev/null +++ b/csharp-api/REFrameworkNET/Proxy.cpp @@ -0,0 +1 @@ +#include "Proxy.hpp" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp new file mode 100644 index 000000000..786cda083 --- /dev/null +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -0,0 +1,144 @@ +#pragma once + +#include + +#include "./API.hpp" +#include "ManagedObject.hpp" + +using namespace System; + +// Classes for proxying interfaces from auto generated assemblies to our dynamic types +namespace REFrameworkNET { +public interface class IProxy { + void SetInstance(IObject^ instance); +}; + +generic +where T2 : ref class +public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public System::IEquatable^> +{ +public: + static T Create(IObject^ target) { + auto proxy = Reflection::DispatchProxy::Create^>(); + ((IProxy^)proxy)->SetInstance(target); + return proxy; + } + + virtual void SetInstance(IObject^ instance) { + Instance = instance; + } + + virtual bool Equals(Proxy^ other) { + return Instance == other->Instance; + } + + bool Equals(Object^ obj) override { + if (obj == nullptr) { + return false; + } + + auto other = dynamic_cast^>(obj); + + if (other == nullptr) { + return false; + } + + return Equals(other); + } + + int GetHashCode() override { + return Instance->GetHashCode(); + } + + static bool operator ==(Proxy^ left, Proxy^ right) { + return left->Equals(right); + } + + static bool operator !=(Proxy^ left, Proxy^ right) { + return !left->Equals(right); + } + +protected: + virtual Object^ Invoke(Reflection::MethodInfo^ targetMethod, array^ args) override { + Object^ result = nullptr; + auto iobject = dynamic_cast(Instance); + + // how am i gonna handle static methods? + if (iobject != nullptr) { + iobject->HandleInvokeMember_Internal(targetMethod->Name, args, result); + } else { + throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); + } + + if (targetMethod->ReturnType == REFrameworkNET::ManagedObject::typeid) { + return result; + } + + if (targetMethod->ReturnType == REFrameworkNET::NativeObject::typeid) { + return result; + } + + if (targetMethod->ReturnType == String::typeid) { + return result; + } + + if (targetMethod->DeclaringType == nullptr) { + return result; + } + + if (!targetMethod->ReturnType->IsPrimitive && targetMethod->DeclaringType->IsInterface && result != nullptr) { + auto iobjectResult = dynamic_cast(result); + + if (iobjectResult != nullptr) { + auto t = iobjectResult->GetTypeDefinition(); + auto fullName = t->FullName; + auto localType = T::typeid->Assembly->GetType(fullName); + + if (localType != nullptr) { + auto proxy = DispatchProxy::Create(localType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(T::typeid, result->GetType())); + ((IProxy^)proxy)->SetInstance(iobjectResult); + result = proxy; + return result; + } else { + System::Console::WriteLine("Type not found: " + fullName); + } + } + } + + return result; + } + +private: + property IObject^ Instance; +}; + +generic +public ref class ManagedProxy : public Proxy { +public: + static T Create(Object^ target) { + return Proxy::Create(dynamic_cast(target)); + } + static T CreateFromSingleton(System::String^ singletonName) { + return Proxy::Create(REFrameworkNET::API::GetManagedSingleton(singletonName)); + } +}; + +generic +public ref class NativeProxy : public Proxy { +public: + static T Create(Object^ target) { + return Proxy::Create(dynamic_cast(target)); + } + static T CreateFromSingleton(System::String^ singletonName) { + return Proxy::Create(REFrameworkNET::API::GetNativeSingleton(singletonName)); + } +}; + +generic +public ref class AnyProxy : public Proxy { +public: + static T Create(Object^ target) { + return Proxy::Create(dynamic_cast(target)); + } +}; +} \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 2f70559b3..b98da2b67 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -3,89 +3,6 @@ using System.Dynamic; using System.Reflection; -public interface IProxy { - void SetInstance(dynamic instance); -} - -// TODO: Put this in its own assembly, or make it part of C++/CLI? -public class Proxy : DispatchProxy, IProxy where T2 : class { - public dynamic Instance { get; private set; } - - public void SetInstance(dynamic instance) { - Instance = instance; - } - - public static T Create(dynamic target) { - var proxy = Create>(); - (proxy as IProxy).SetInstance(target); - return proxy; - } - - protected override object Invoke(MethodInfo targetMethod, object[] args) { - object result = null; - dynamic obj = Instance as T2; - obj.HandleInvokeMember_Internal(targetMethod.Name, args, ref result); - - if (targetMethod.ReturnType == typeof(REFrameworkNET.ManagedObject)) { - return result; - } - - if (targetMethod.ReturnType == typeof(REFrameworkNET.NativeObject)) { - return result; - } - - if (targetMethod.ReturnType == typeof(string)) { - return result; - } - - if (!targetMethod.ReturnType.IsPrimitive && targetMethod.DeclaringType.IsInterface) { - if (result != null && result.GetType() == typeof(REFrameworkNET.ManagedObject)) { - // See if we can do a dynamic lookup and resolve it to a local type - var t = (result as REFrameworkNET.ManagedObject).GetTypeDefinition(); - var fullName = t.GetFullName(); - - // See if we can find a local type with the same name - var localType = typeof(via.Scene).Assembly.GetType(fullName); - - if (localType != null) { - var prox = Create(localType, typeof(Proxy<,>).MakeGenericType(localType, typeof(REFrameworkNET.ManagedObject))); - (prox as IProxy).SetInstance(result); - result = prox; - return result; - } - } else if (result != null && result.GetType() == typeof(REFrameworkNET.NativeObject)) { - // See if we can do a dynamic lookup and resolve it to a local type - var t = (result as REFrameworkNET.NativeObject).GetTypeDefinition(); - var fullName = t.GetFullName(); - - // See if we can find a local type with the same name - var localType = typeof(via.Scene).Assembly.GetType(fullName); - - if (localType != null) { - var prox = Create(localType, typeof(Proxy<,>).MakeGenericType(localType, typeof(REFrameworkNET.NativeObject))); - (prox as IProxy).SetInstance(result); - result = prox; - return result; - } - } - } - - return result; - } -} - -public class ManagedProxy : Proxy { - new public static T Create(dynamic target) { - return Proxy.Create(target); - } -} - -public class NativeProxy : Proxy { - new public static T Create(dynamic target) { - return Proxy.Create(target); - } -} - public class DangerousFunctions { public static REFrameworkNET.PreHookResult isInsidePreHook(System.Collections.Generic.List args) { Console.WriteLine("Inside pre hook (From C#)"); @@ -108,7 +25,7 @@ public static void Entry() { // 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 - var sceneManager = NativeProxy.Create(REFrameworkNET.API.GetNativeSingleton("via.SceneManager")); + var sceneManager = REFrameworkNET.NativeProxy.CreateFromSingleton("via.SceneManager"); var scene = sceneManager.get_CurrentScene(); scene.set_Pause(true); @@ -230,8 +147,11 @@ public static void Main(REFrameworkNET.API api) { } } - - DangerousFunctions.Entry(); + try { + DangerousFunctions.Entry(); + } catch (Exception e) { + REFrameworkNET.API.LogError(e.ToString()); + } dynamic optionManager = REFrameworkNET.API.GetManagedSingleton("app.OptionManager"); From a64da4a428fe5b7200137c48f8418a007b433912 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 25 Mar 2024 17:23:46 -0700 Subject: [PATCH 043/207] .NET: Add IObject::As --- csharp-api/REFrameworkNET/IObject.hpp | 4 ++++ csharp-api/REFrameworkNET/ManagedObject.cpp | 6 ++++++ csharp-api/REFrameworkNET/ManagedObject.hpp | 3 +++ csharp-api/REFrameworkNET/NativeObject.cpp | 6 ++++++ csharp-api/REFrameworkNET/NativeObject.hpp | 3 +++ 5 files changed, 22 insertions(+) diff --git a/csharp-api/REFrameworkNET/IObject.hpp b/csharp-api/REFrameworkNET/IObject.hpp index 6efa9c4b4..af8cd546d 100644 --- a/csharp-api/REFrameworkNET/IObject.hpp +++ b/csharp-api/REFrameworkNET/IObject.hpp @@ -14,5 +14,9 @@ public interface class IObject { virtual InvokeRet^ Invoke(System::String^ methodName, array^ args); bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + + // For interface types + generic + T As(); }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index a8be63a1a..e71e88aa0 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -6,6 +6,7 @@ #include "Method.hpp" #include "ManagedObject.hpp" +#include "Proxy.hpp" #include "API.hpp" @@ -261,4 +262,9 @@ namespace REFrameworkNET { return false; } + + generic + T ManagedObject::As() { + return ManagedProxy::Create(this); + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 367660ade..43a2e78fa 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -127,6 +127,9 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S virtual bool TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) override; virtual bool TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) override; + generic + virtual T As(); + // TODO methods: /*public Void* GetReflectionProperties() { return _original.get_reflection_properties(); diff --git a/csharp-api/REFrameworkNET/NativeObject.cpp b/csharp-api/REFrameworkNET/NativeObject.cpp index e86672fe9..038ce3b92 100644 --- a/csharp-api/REFrameworkNET/NativeObject.cpp +++ b/csharp-api/REFrameworkNET/NativeObject.cpp @@ -1,6 +1,7 @@ #include "InvokeRet.hpp" #include "Method.hpp" #include "NativeObject.hpp" +#include "Proxy.hpp" #include "API.hpp" @@ -42,4 +43,9 @@ bool NativeObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, { return HandleInvokeMember_Internal(binder->Name, args, result); } + +generic +T NativeObject::As() { + return NativeProxy::Create(this); +} } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index 4eaa23df3..2d39ccd34 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -63,6 +63,9 @@ public ref class NativeObject : public System::Dynamic::DynamicObject, public Sy virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; + generic + virtual T As(); + public: // IEnumerable implementation virtual System::Collections::IEnumerator^ GetEnumerator() { From 2069bfe02a25c2a7f155cc07d1fdfb867bf835cb Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 25 Mar 2024 17:55:56 -0700 Subject: [PATCH 044/207] .NET: Add GetNativeSingletonT/GetManagedSingletonT --- csharp-api/REFrameworkNET/API.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index 40626e937..af99b41b3 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -105,6 +105,18 @@ public ref class API return gcnew NativeObject(result, t); } + generic + static T GetNativeSingletonT() { + auto fullName = T::typeid->FullName; + return GetNativeSingleton(fullName)->As(); + } + + generic + static T GetManagedSingletonT() { + auto fullName = T::typeid->FullName; + return GetManagedSingleton(fullName)->As(); + } + static reframework::API* GetNativeImplementation() { if (s_api == nullptr) { throw gcnew APINotInitializedException(); From e490bd0f587b8967283eadd8250b8528ff286e90 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 26 Mar 2024 01:56:46 -0700 Subject: [PATCH 045/207] .NET: Allow proxies to be casted to IObject --- csharp-api/REFrameworkNET/Proxy.hpp | 36 ++++++++++++++++++++++++++++- csharp-api/test/Test/Test.cs | 2 +- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 786cda083..99b34b10c 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -4,12 +4,16 @@ #include "./API.hpp" #include "ManagedObject.hpp" +#include "TypeDefinition.hpp" using namespace System; // Classes for proxying interfaces from auto generated assemblies to our dynamic types namespace REFrameworkNET { -public interface class IProxy { +// Inherit IObject's interface as well +// so we can easily pretend that this is an IObject, even though it just forwards the calls to the actual object +public interface class IProxy : IObject { + IObject^ GetInstance(); void SetInstance(IObject^ instance); }; @@ -18,12 +22,42 @@ where T2 : ref class public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public System::IEquatable^> { public: + virtual REFrameworkNET::TypeDefinition^ GetTypeDefinition() { + return Instance->GetTypeDefinition(); + } + + virtual void* Ptr() { + return Instance->Ptr(); + } + + virtual uintptr_t GetAddress() { + return Instance->GetAddress(); + } + + virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args) { + return Instance->Invoke(methodName, args); + } + + virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result) { + return Instance->HandleInvokeMember_Internal(methodName, args, result); + } + + // For interface types + generic + virtual T As() { + return Instance->As(); + } + static T Create(IObject^ target) { auto proxy = Reflection::DispatchProxy::Create^>(); ((IProxy^)proxy)->SetInstance(target); return proxy; } + virtual IObject^ GetInstance() { + return Instance; + } + virtual void SetInstance(IObject^ instance) { Instance = instance; } diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index b98da2b67..6386649ef 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -37,7 +37,7 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Scene name: " + name); // Testing autocomplete for the concrete ManagedObject - REFrameworkNET.API.LogInfo("Scene: " + scene.ToString() + ": " + (scene as REFrameworkNET.ManagedObject)?.GetTypeDefinition()?.GetFullName()?.ToString()); + REFrameworkNET.API.LogInfo("Scene: " + scene.ToString() + ": " + (scene as REFrameworkNET.IObject).GetTypeDefinition()?.GetFullName()?.ToString()); // Testing dynamic invocation float currentTimescale = scene.get_TimeScale(); From e339e6edbe408edf22c31c52820ba8eccb9d39dd Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 27 Mar 2024 20:03:54 -0700 Subject: [PATCH 046/207] .NET: Autogenerate ALL types and split into different assemblies --- .../AssemblyGenerator/ClassGenerator.cs | 51 ++++- csharp-api/AssemblyGenerator/Generator.cs | 192 +++++++++++------- csharp-api/Compiler/Compiler.cs | 3 +- csharp-api/REFrameworkNET/PluginManager.cpp | 4 +- 4 files changed, 167 insertions(+), 83 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 955159035..b7838418e 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -10,6 +10,7 @@ using System.Dynamic; using System.Security.Cryptography; using System.Linq; +using Microsoft.CodeAnalysis.Operations; public class ClassGenerator { @@ -71,7 +72,7 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra bool wantsAbstractInstead = false; - for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { + /*for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { var parentName = parent.FullName ?? ""; if (parentName == null) { break; @@ -85,15 +86,15 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra wantsAbstractInstead = true; break; } - } + }*/ if (wantsAbstractInstead) { typeDeclaration = SyntaxFactory - .ClassDeclaration(className) + .ClassDeclaration(REFrameworkNET.AssemblyGenerator.CorrectTypeName(className)) .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.AbstractKeyword)}); } else { typeDeclaration = SyntaxFactory - .InterfaceDeclaration(className) + .InterfaceDeclaration(REFrameworkNET.AssemblyGenerator.CorrectTypeName(className)) .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}); } @@ -110,7 +111,7 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra if (methodReturnT != null && methodReturnName != "System.Void" && methodReturnName != "") { // Check for easily convertible types like System.Single, System.Int32, etc. - switch (methodReturnName) { + switch (methodReturnName) { case "System.Single": returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword)); break; @@ -153,11 +154,20 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra break; } - if (methodReturnName.StartsWith("System.") || !methodReturnName.StartsWith("via.")) { + /*if (methodReturnName.StartsWith("System.") || !methodReturnName.StartsWith("via.")) { + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + }*/ + + // Stuff in System should NOT be referencing via + // how is this even compiling for them? + if (ogClassName.StartsWith("System") && methodReturnName.StartsWith("via")) { returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; } + methodReturnName = "global::" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(methodReturnName); + returnType = SyntaxFactory.ParseTypeName(methodReturnName); break; } @@ -172,6 +182,10 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra if (methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { methodName += "_" + className.Replace('.', '_'); + } else { + if (this.t.FullName == "System.Collections.Stack.SyncStack") { + System.Console.WriteLine("No override for " + this.t.FullName + "." + method.Name); + } } var methodDeclaration = SyntaxFactory.MethodDeclaration(returnType, methodName ?? "UnknownMethod") @@ -196,15 +210,20 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra List baseTypes = new List(); SortedSet badBaseTypes = new SortedSet { - "System.Object", + /*"System.Object", "System.ValueType", "System.Enum", "System.Delegate", - "System.MulticastDelegate" + "System.MulticastDelegate"*/ }; for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - var parentName = parent.FullName ?? ""; + // TODO: Fix this + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { + continue; + } + + var parentName = REFrameworkNET.AssemblyGenerator.CorrectTypeName(parent.FullName ?? ""); if (parentName == null) { break; } @@ -221,6 +240,9 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra break; } + // Forces compiler to start at the global namespace + parentName = "global::" + parentName; + baseTypes.Add(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(parentName))); usingTypes.Add(parent); break; @@ -268,9 +290,11 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra HashSet nestedMethods = []; - foreach (var method in t.Methods) { + foreach (var method in nestedT.Methods) { if (!nestedMethods.Select(nestedMethod => nestedMethod.Name).Contains(method.Name)) { - nestedMethods.Add(method); + if (method.DeclaringType == nestedT) { + nestedMethods.Add(method); + } } } @@ -292,6 +316,11 @@ [.. nestedMethods] break; } + // TODO: Fix this + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { + continue; + } + var parentNestedTypes = Il2CppDump.GetTypeExtension(parent)?.NestedTypes; // Look for same named nested types diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 0bb25f0e4..33011bb48 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -27,6 +27,8 @@ public Method(REFrameworkNET.Method impl) { this.Impl = impl; } + public REFrameworkNET.TypeDefinition DeclaringType => Impl.GetDeclaringType(); + public bool? Override { get; set;} // Not from JSON } @@ -133,7 +135,7 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { //foreach (var method in t.Methods) { //Parallel.ForEach(tMethods, method => { - foreach (var method in tMethods) { // parallel isn't necessary here because there arent many methods + foreach (REFrameworkNET.Method method in tMethods) { // parallel isn't necessary here because there arent many methods if (method == null) { continue; } @@ -142,9 +144,17 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { continue; } + if (method.DeclaringType != t) { + continue; + } + var parentMethod = parent.GetMethod(method.Name); if (parentMethod != null) { + if (method.DeclaringType.FullName == "System.Collections.Stack.SyncStack") { + Console.WriteLine("Found override " + method.Name + " in " + method.DeclaringType.FullName); + } + methodExtensions.Add(method, new Method(method) { Override = true }); @@ -159,12 +169,24 @@ public class AssemblyGenerator { static Dictionary namespaces = []; // Start with an empty CompilationUnitSyntax (represents an empty file) - static CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit(); + //static CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit(); + + public static string CorrectTypeName(string fullName) { + if (fullName.StartsWith("System.") || fullName.StartsWith("Internal.")) { + return "_" + fullName; + } + + return fullName; + } static public NamespaceDeclarationSyntax? ExtractNamespaceFromType(REFrameworkNET.TypeDefinition t) { var ns = t.GetNamespace(); - if (ns != null) { + if (ns != null && ns.Length > 0) { + if (ns.StartsWith("System.") || ns == "System" || ns.StartsWith("Internal.") || ns == "Internal") { + ns = "_" + ns; + } + if (!namespaces.TryGetValue(ns, out NamespaceDeclarationSyntax? value)) { //ns = Regex.Replace(ns, @"[^a-zA-Z0-9.]", "_"); Console.WriteLine("Creating namespace " + ns); @@ -173,6 +195,8 @@ public class AssemblyGenerator { } return value; + } else { + Console.WriteLine("Failed to extract namespace from " + t.GetFullName()); } return null; @@ -186,14 +210,20 @@ static void FillValidEntries(REFrameworkNET.TDB context) { return; } + foreach (REFrameworkNET.TypeDefinition t in context.Types) { + var asdf = t.GetFullName(); + } + ConcurrentBag threadSafeValidTypes = []; + context.GetType(0).GetFullName(); // initialize the types + Parallel.For(0, context.GetNumTypes(), i => { var t = context.GetType((uint)i); var typeName = t.GetFullName(); if (typeName.Length == 0) { - Console.WriteLine("Bad type name"); + Console.WriteLine("Bad type name @ " + i); return; } @@ -201,17 +231,24 @@ static void FillValidEntries(REFrameworkNET.TDB context) { return; } - if (typeName.Contains("[") || typeName.Contains("]") || typeName.Contains('<')) { + // Generics and arrays not yet supported + if (typeName.Contains("[") || typeName.Contains("]") || typeName.Contains('<') || typeName.Contains('!')) { return; } - // Skip system types - // TODO: Fix this - if (typeName.StartsWith("System.")) { + // Dont worry about global namespace types for now... + if (t.Namespace == null || t.Namespace.Length == 0) { return; } - threadSafeValidTypes.Add(typeName); + // Skip system types + // TODO: Fix this + /*if (typeName.StartsWith("System.")) { + //return; + threadSafeValidTypes.Add("_" + typeName); + } else {*/ + threadSafeValidTypes.Add(typeName); + //} }); foreach (var typeName in threadSafeValidTypes) { @@ -220,6 +257,7 @@ static void FillValidEntries(REFrameworkNET.TDB context) { } static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, string typeName, REFrameworkNET.TypeDefinition? t) { + var compilationUnit = SyntaxFactory.CompilationUnit(); FillValidEntries(context); if (!validTypes.Contains(typeName)) { @@ -245,7 +283,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin // Generate starting from topmost parent first if (t.ParentType != null) { - MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType); + compilationUnit = MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType); } /*var methods = t.Methods; @@ -265,7 +303,9 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin foreach (var method in t.Methods) { //methods.Add(method); if (!methods.Select(m => m.Name).Contains(method.Name)) { - methods.Add(method); + if (method.DeclaringType == t) { // really important + methods.Add(method); + } } } @@ -326,66 +366,44 @@ [.. methods] return []; } - public static List MainImpl() { - Il2CppDump.FillTypeExtensions(REFrameworkNET.API.GetTDB()); - - List compilationUnits = new List(); + public static REFrameworkNET.Compiler.DynamicAssemblyBytecode? GenerateForAssembly(dynamic assembly, List previousCompilations) { + var strippedAssemblyName = assembly.get_FullName().Split(',')[0]; - // Open a JSON file - /*using (var jsonFile = File.OpenRead(il2cpp_dump_json)) - { - dump = new Il2CppDump - { - Types = Il2CppDump.PostProcessTypes(JsonSerializer.Deserialize>(jsonFile, options)) - }; - - if (dump != null && dump.Types != null) { - // Look for any types that start with via.* - foreach (var typePair in dump.Types ?? []) { - var typeName = typePair.Key; - var type = typePair.Value; - - if (typeName.StartsWith("via.")) { - var compilationUnit = MakeFromTypeEntry(dump, typeName, type); - compilationUnits.Add(compilationUnit); - } - } - } else { - Console.WriteLine("Failed to parse JSON"); - } - }*/ + // Dont want to conflict with the real .NET System + if (strippedAssemblyName == "System") { + strippedAssemblyName = "_System"; + } + List compilationUnits = []; var tdb = REFrameworkNET.API.GetTDB(); - foreach (REFrameworkNET.TypeDefinition t in tdb.Types) { - var typeName = t.GetFullName(); + foreach (dynamic reEngineT in assembly.GetTypes()) { + var th = reEngineT.get_TypeHandle(); - if (typeName.StartsWith("via.")) { - var compilationUnit = MakeFromTypeEntry(REFrameworkNET.API.GetTDB(), typeName, t); - compilationUnits.Add(compilationUnit); + if (th == null) { + Console.WriteLine("Failed to get type handle for " + reEngineT.get_FullName()); + continue; } - } - - System.Console.WriteLine(compilationUnits[0].NormalizeWhitespace().ToFullString()); - - /*List syntaxTrees = new List(); - - foreach (var cu in compilationUnits) { - syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu)); - }*/ + var t = th as REFrameworkNET.TypeDefinition; - var normalized = compilationUnit.NormalizeWhitespace(); - string compilationUnitHash = ""; + if (t == null) { + Console.WriteLine("Failed to convert type handle for " + reEngineT.get_FullName()); + continue; + } - using (var sha1 = System.Security.Cryptography.SHA1.Create()) { - compilationUnitHash = BitConverter.ToString(sha1.ComputeHash(compilationUnit.ToFullString().Select(c => (byte)c).ToArray())).Replace("-", ""); + var typeName = t.GetFullName(); + var compilationUnit = MakeFromTypeEntry(tdb, typeName, t); + compilationUnits.Add(compilationUnit); } - // Dump to DynamicAssembly.cs - File.WriteAllText("DynamicAssembly.cs", normalized.ToFullString()); + List syntaxTrees = new List(); - var syntaxTrees = SyntaxFactory.SyntaxTree(normalized, CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12)); + var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); + + foreach (var cu in compilationUnits) { + syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu, syntaxTreeParseOption)); + } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); @@ -408,14 +426,20 @@ [.. methods] references.Add(MetadataReference.CreateFromFile(systemRuntimePath)); } - compilationUnit = compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))); + // Add the previous compilations as references + foreach (var compilationbc in previousCompilations) { + var ms = new MemoryStream(compilationbc.Bytecode); + references.Add(MetadataReference.CreateFromStream(ms)); + } + + //compilationUnit = compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))); var csoptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release, assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default, platform: Platform.X64); // Create a compilation - var compilation = CSharpCompilation.Create("DynamicAssembly") + CSharpCompilation compilation = CSharpCompilation.Create(strippedAssemblyName) .WithOptions(csoptions) .AddReferences(references) .AddSyntaxTrees(syntaxTrees); @@ -427,21 +451,24 @@ [.. methods] if (!result.Success) { - var textLines = syntaxTrees.GetText().Lines; + //var textLines = syntaxTrees.GetText().Lines; List sortedDiagnostics = result.Diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToList(); sortedDiagnostics.Reverse(); foreach (Diagnostic diagnostic in sortedDiagnostics) { + var textLines = diagnostic.Location.SourceTree?.GetText().Lines; Console.WriteLine($"{diagnostic.Id}: {diagnostic.GetMessage()}"); var lineSpan = diagnostic.Location.GetLineSpan(); var errorLineNumber = lineSpan.StartLinePosition.Line; - var errorLineText = textLines[errorLineNumber].ToString(); + var errorLineText = textLines?[errorLineNumber].ToString(); Console.WriteLine($"Error in line {errorLineNumber + 1}: {errorLineText}"); + //Console.WriteLine( + //$"Error in line {errorLineNumber + 1}: {lineSpan.StartLinePosition.Character + 1} - {lineSpan.EndLinePosition.Character + 1}"); } - REFrameworkNET.API.LogError("Failed to compile DynamicAssembly.dll"); + REFrameworkNET.API.LogError("Failed to compile " + strippedAssemblyName); } else { @@ -452,18 +479,43 @@ [.. methods] // dump to file //File.WriteAllBytes("DynamicAssembly.dll", ms.ToArray()); - REFrameworkNET.API.LogInfo("Successfully compiled DynamicAssembly.dll"); + REFrameworkNET.API.LogInfo("Successfully compiled " + strippedAssemblyName); - return [ + return new REFrameworkNET.Compiler.DynamicAssemblyBytecode { Bytecode = ms.ToArray(), - Hash = compilationUnitHash - } - ]; + AssemblyName = strippedAssemblyName + }; } } - return []; + return null; + } + + public static List MainImpl() { + Il2CppDump.FillTypeExtensions(REFrameworkNET.API.GetTDB()); + + var tdb = REFrameworkNET.API.GetTDB(); + + dynamic appdomainT = tdb.GetType("System.AppDomain"); + dynamic appdomain = appdomainT.get_CurrentDomain(); + dynamic assemblies = appdomain.GetAssemblies(); + + List bytecodes = []; + + foreach (dynamic assembly in assemblies) { + var strippedAssemblyName = assembly.get_FullName().Split(',')[0]; + REFrameworkNET.API.LogInfo("Assembly: " + (assembly.get_Location()?.ToString() ?? "NONE")); + REFrameworkNET.API.LogInfo("Assembly (stripped): " + strippedAssemblyName); + + var bytecode = GenerateForAssembly(assembly, bytecodes); + + if (bytecode != null) { + bytecodes.Add(bytecode); + } + } + + return bytecodes; } }; } \ No newline at end of file diff --git a/csharp-api/Compiler/Compiler.cs b/csharp-api/Compiler/Compiler.cs index 209a9edfb..452e239f9 100644 --- a/csharp-api/Compiler/Compiler.cs +++ b/csharp-api/Compiler/Compiler.cs @@ -18,7 +18,8 @@ public byte[] Bytecode { get; set; } - public string Hash { + + public string AssemblyName { get; set; } diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 02ed55701..0550213f3 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -143,7 +143,9 @@ namespace REFrameworkNET { for each (Compiler::DynamicAssemblyBytecode^ bytes in result) { REFrameworkNET::API::LogInfo("Adding generated assembly to deps..."); - auto path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies" / (msclr::interop::marshal_as(bytes->Hash) + "_DYNAMIC.dll"); + std::string assembly_name = msclr::interop::marshal_as(bytes->AssemblyName); + + auto path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies" / (assembly_name + "_DYNAMIC.dll"); System::IO::File::WriteAllBytes(gcnew System::String(path.wstring().c_str()), bytes->Bytecode); REFrameworkNET::API::LogInfo("Wrote generated assembly to " + gcnew System::String(path.wstring().c_str())); From cf876ad92e0ab926f99e1ab6291b2d778a1f3715 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 27 Mar 2024 22:11:17 -0700 Subject: [PATCH 047/207] .NET: Harden proxy return logic --- csharp-api/REFrameworkNET/Proxy.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 99b34b10c..a3cbcb7be 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -116,17 +116,18 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public return result; } - if (targetMethod->DeclaringType == nullptr) { + if (targetMethod->DeclaringType == nullptr || targetMethod->ReturnType == nullptr) { return result; } if (!targetMethod->ReturnType->IsPrimitive && targetMethod->DeclaringType->IsInterface && result != nullptr) { auto iobjectResult = dynamic_cast(result); - if (iobjectResult != nullptr) { - auto t = iobjectResult->GetTypeDefinition(); + if (iobjectResult != nullptr && targetMethod->ReturnType->IsInterface) { + /*auto t = iobjectResult->GetTypeDefinition(); auto fullName = t->FullName; - auto localType = T::typeid->Assembly->GetType(fullName); + auto localType = T::typeid->Assembly->GetType(fullName);*/ + auto localType = targetMethod->ReturnType; if (localType != nullptr) { auto proxy = DispatchProxy::Create(localType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(T::typeid, result->GetType())); @@ -134,7 +135,7 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public result = proxy; return result; } else { - System::Console::WriteLine("Type not found: " + fullName); + System::Console::WriteLine("Type not found: " + targetMethod->ReturnType->FullName); } } } From bd30429b2f9c6a408624467df47b5034d7b99a42 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 27 Mar 2024 22:15:21 -0700 Subject: [PATCH 048/207] .NET: Allow TypeDefinitions to be proxied (for statics) --- csharp-api/REFrameworkNET/IObject.hpp | 11 ++++----- csharp-api/REFrameworkNET/IProxyable.hpp | 14 +++++++++++ csharp-api/REFrameworkNET/TypeDefinition.cpp | 18 ++++++++++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 25 +++++++++++++++++++- 4 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 csharp-api/REFrameworkNET/IProxyable.hpp diff --git a/csharp-api/REFrameworkNET/IObject.hpp b/csharp-api/REFrameworkNET/IObject.hpp index af8cd546d..7b73f448c 100644 --- a/csharp-api/REFrameworkNET/IObject.hpp +++ b/csharp-api/REFrameworkNET/IObject.hpp @@ -2,18 +2,17 @@ #include +#include "IProxyable.hpp" + namespace REFrameworkNET { ref class TypeDefinition; ref struct InvokeRet; // Base interface of ManagedObject and NativeObject -public interface class IObject { - TypeDefinition^ GetTypeDefinition(); - void* Ptr(); - uintptr_t GetAddress(); +public interface class IObject : public IProxyable { + InvokeRet^ Invoke(System::String^ methodName, array^ args); - virtual InvokeRet^ Invoke(System::String^ methodName, array^ args); - bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + TypeDefinition^ GetTypeDefinition(); // For interface types generic diff --git a/csharp-api/REFrameworkNET/IProxyable.hpp b/csharp-api/REFrameworkNET/IProxyable.hpp new file mode 100644 index 000000000..115eb7659 --- /dev/null +++ b/csharp-api/REFrameworkNET/IProxyable.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace REFrameworkNET { +ref struct InvokeRet; + +public interface class IProxyable { + void* Ptr(); + uintptr_t GetAddress(); + + bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 86df9ee28..89df7d449 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -4,6 +4,7 @@ #include "Property.hpp" #include "ManagedObject.hpp" #include "NativeObject.hpp" +#include "Proxy.hpp" #include "TypeDefinition.hpp" @@ -126,9 +127,26 @@ namespace REFrameworkNET { return gcnew ManagedObject(result); } + REFrameworkNET::InvokeRet^ TypeDefinition::Invoke(System::String^ methodName, array^ args) { + // Forward this onto NativeObject.Invoke (for static methods) + auto native = gcnew NativeObject(this); + return native->Invoke(methodName, args); + } + + bool TypeDefinition::HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result) { + // Forward this onto NativeObject.HandleInvokeMember_Internal (for static methods) + auto native = gcnew NativeObject(this); + return native->HandleInvokeMember_Internal(methodName, args, result); + } + bool TypeDefinition::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) { // Forward this onto NativeObject.TryInvokeMember (for static methods) auto native = gcnew NativeObject(this); return native->TryInvokeMember(binder, args, result); } + + generic + T TypeDefinition::As() { + return NativeProxy::Create(this); + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 91fd6e4c6..02ceb623d 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -5,6 +5,7 @@ #pragma managed #include +#include "IObject.hpp" namespace REFrameworkNET { ref class ManagedObject; @@ -13,6 +14,7 @@ ref class Method; ref class Field; ref class Property; ref class TypeInfo; +ref struct InvokeRet; public enum VMObjType { NULL_ = 0, @@ -25,7 +27,8 @@ public enum VMObjType { public ref class TypeDefinition : public System::Dynamic::DynamicObject, - public System::IEquatable + public System::IEquatable, + public REFrameworkNET::IObject { public: TypeDefinition(reframework::API::TypeDefinition* td) : m_type(td) {} @@ -311,6 +314,26 @@ public return m_type->create_instance_deprecated(); }*/ +// IObject methods +public: + virtual void* Ptr() { + return (void*)m_type; + } + + virtual uintptr_t GetAddress() { + return (uintptr_t)m_type; + } + + virtual TypeDefinition^ GetTypeDefinition() { + return this; + } + + virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); + virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + + generic + virtual T As(); + // DynamicObject methods public: virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; From 7b8a4bad1b5036e04c571dd01d519c41c2cfa721 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 27 Mar 2024 22:26:05 -0700 Subject: [PATCH 049/207] .NET: Add additional tests for new reference assemblies --- csharp-api/CMakeLists.txt | 22 ++++++++++++++++++--- csharp-api/REFrameworkNET/PluginManager.cpp | 3 ++- csharp-api/cmake.toml | 20 ++++++++++++++++--- csharp-api/test/Test/Test.cs | 8 ++++++++ 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 6bfb0ab5c..521fdfe20 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -117,6 +117,8 @@ set(csharp-api_SOURCES "REFrameworkNET/API.hpp" "REFrameworkNET/Callbacks.hpp" "REFrameworkNET/Field.hpp" + "REFrameworkNET/IObject.hpp" + "REFrameworkNET/IProxyable.hpp" "REFrameworkNET/InvokeRet.hpp" "REFrameworkNET/ManagedObject.hpp" "REFrameworkNET/ManagedSingleton.hpp" @@ -272,8 +274,22 @@ VS_DOTNET_REFERENCE_REFramework.NET ) set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_via -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/DynamicAssembly.dll" +VS_DOTNET_REFERENCE_REFramework.NET._System +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET._System.dll" ) -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "via") +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") + +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.viacore +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.viacore.dll" +) + +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") + +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.application +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.application.dll" +) + +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 0550213f3..b5126e267 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -144,8 +144,9 @@ namespace REFrameworkNET { REFrameworkNET::API::LogInfo("Adding generated assembly to deps..."); std::string assembly_name = msclr::interop::marshal_as(bytes->AssemblyName); + assembly_name = "REFramework.NET." + assembly_name + ".dll"; - auto path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies" / (assembly_name + "_DYNAMIC.dll"); + auto path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies" / assembly_name; System::IO::File::WriteAllBytes(gcnew System::String(path.wstring().c_str()), bytes->Bytecode); REFrameworkNET::API::LogInfo("Wrote generated assembly to " + gcnew System::String(path.wstring().c_str())); diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 7f2741bb3..af2e2c5a6 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -137,10 +137,24 @@ VS_DOTNET_REFERENCE_REFramework.NET ) set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_via -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/DynamicAssembly.dll" +VS_DOTNET_REFERENCE_REFramework.NET._System +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET._System.dll" ) -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "via") +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") + +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.viacore +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.viacore.dll" +) + +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") + +set_target_properties(CSharpAPITest PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.application +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.application.dll" +) + +set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") """ \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 6386649ef..5f0eb3776 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -45,6 +45,14 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Previous timescale: " + currentTimescale.ToString()); REFrameworkNET.API.LogInfo("Current timescale: " + scene?.get_TimeScale().ToString()); + var appdomainStatics = tdb.GetType("System.AppDomain").As<_System.AppDomain>(); + var appdomain = appdomainStatics.get_CurrentDomain(); + dynamic assemblies = appdomain.GetAssemblies(); + + foreach (REFrameworkNET.ManagedObject assemblyRaw in assemblies) { + var assembly = assemblyRaw.As<_System.Reflection.Assembly>(); + REFrameworkNET.API.LogInfo("Assembly: " + assembly.get_Location()?.ToString()); + } } } From 162af49f667edc6906cbc886272d215cd9ceb094 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 27 Mar 2024 22:27:02 -0700 Subject: [PATCH 050/207] .NET: Remove debug stuff from generator --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 4 ---- csharp-api/AssemblyGenerator/Generator.cs | 4 ---- 2 files changed, 8 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index b7838418e..8a7af81d5 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -182,10 +182,6 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra if (methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { methodName += "_" + className.Replace('.', '_'); - } else { - if (this.t.FullName == "System.Collections.Stack.SyncStack") { - System.Console.WriteLine("No override for " + this.t.FullName + "." + method.Name); - } } var methodDeclaration = SyntaxFactory.MethodDeclaration(returnType, methodName ?? "UnknownMethod") diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 33011bb48..e2c46be68 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -151,10 +151,6 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { var parentMethod = parent.GetMethod(method.Name); if (parentMethod != null) { - if (method.DeclaringType.FullName == "System.Collections.Stack.SyncStack") { - Console.WriteLine("Found override " + method.Name + " in " + method.DeclaringType.FullName); - } - methodExtensions.Add(method, new Method(method) { Override = true }); From bf050dc4bd8cfe876f881565f812134b24ee1e5c Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 28 Mar 2024 03:42:01 -0700 Subject: [PATCH 051/207] .NET: Fix string interaction in older games --- csharp-api/REFrameworkNET/ManagedObject.cpp | 10 +++++++++- csharp-api/REFrameworkNET/Method.cpp | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index e71e88aa0..30ce79b94 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -134,7 +134,15 @@ namespace REFrameworkNET { break; } - const auto offset = field_type->IsValueType() ? field_type->GetField("_firstChar")->GetOffsetFromFieldPtr() : field_type->GetField("_firstChar")->GetOffsetFromBase(); + const auto firstCharField = field_type->GetField("_firstChar"); + uint32_t offset = 0; + + if (firstCharField != nullptr) { + offset = field_type->IsValueType() ? firstCharField->GetOffsetFromFieldPtr() : firstCharField->GetOffsetFromBase(); + } else { + const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)strObject - sizeof(void*)); + offset = fieldOffset + 4; + } wchar_t* chars = (wchar_t*)((uintptr_t)strObject + offset); result = gcnew System::String(chars); diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index a91b7e012..f46c309b4 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -134,7 +134,15 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::String^ me // Maybe don't create the GC version and just use the native one? auto strObject = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); auto strType = strObject->GetTypeDefinition(); - const auto offset = strType->GetField("_firstChar")->GetOffsetFromBase(); + const auto firstCharField = strType->GetField("_firstChar"); + uint32_t offset = 0; + + if (firstCharField != nullptr) { + offset = strType->GetField("_firstChar")->GetOffsetFromBase(); + } else { + const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)tempResult->QWord - sizeof(void*)); + offset = fieldOffset + 4; + } wchar_t* chars = (wchar_t*)((uintptr_t)strObject->Ptr() + offset); result = gcnew System::String(chars); From fe6241d08240f733a398a11dc0c23f9cf8eed0fd Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 30 Mar 2024 01:13:58 -0700 Subject: [PATCH 052/207] .NET: Fix System->app reference (why) --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 9 +++++++++ csharp-api/AssemblyGenerator/Generator.cs | 2 ++ 2 files changed, 11 insertions(+) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 8a7af81d5..d0e55e574 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -11,6 +11,7 @@ using System.Security.Cryptography; using System.Linq; using Microsoft.CodeAnalysis.Operations; +using REFrameworkNET; public class ClassGenerator { @@ -162,10 +163,18 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra // Stuff in System should NOT be referencing via // how is this even compiling for them? if (ogClassName.StartsWith("System") && methodReturnName.StartsWith("via")) { + REFrameworkNET.API.LogWarning("Method " + ogClassName + "." + method.Name + " is referencing via class " + methodReturnName); returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; } + if (ogClassName.StartsWith("System") && methodReturnName.StartsWith("app.")) { + REFrameworkNET.API.LogWarning("Method " + ogClassName + "." + method.Name + " is referencing app class " + methodReturnName); + returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + methodReturnName = "global::" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(methodReturnName); returnType = SyntaxFactory.ParseTypeName(methodReturnName); diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index e2c46be68..7d0aa0648 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -370,6 +370,8 @@ [.. methods] strippedAssemblyName = "_System"; } + REFrameworkNET.API.LogInfo("Generating assembly " + strippedAssemblyName); + List compilationUnits = []; var tdb = REFrameworkNET.API.GetTDB(); From 6b717a5ade26ef03d957683002e5fc8929476fff Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 30 Mar 2024 05:06:51 -0700 Subject: [PATCH 053/207] .NET: Use attribute for plugin entrypoint --- csharp-api/AssemblyGenerator/Generator.cs | 3 +- csharp-api/CMakeLists.txt | 2 + .../REFrameworkNET/Attributes/Plugin.cpp | 1 + .../REFrameworkNET/Attributes/Plugin.hpp | 11 ++ csharp-api/REFrameworkNET/PluginManager.cpp | 132 ++++++++++++------ csharp-api/REFrameworkNET/PluginManager.hpp | 6 + csharp-api/test/Test/Test.cs | 73 ++++++++-- 7 files changed, 169 insertions(+), 59 deletions(-) create mode 100644 csharp-api/REFrameworkNET/Attributes/Plugin.cpp create mode 100644 csharp-api/REFrameworkNET/Attributes/Plugin.hpp diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 7d0aa0648..b2f6f37a6 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -346,7 +346,8 @@ [.. methods] return compilationUnit; } - public static List Main(REFrameworkNET.API api) { + [REFrameworkNET.Attributes.PluginEntryPoint] + public static List Main() { try { return MainImpl(); } catch (Exception e) { diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 521fdfe20..57460ed76 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -104,6 +104,7 @@ set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Micros set(csharp-api_SOURCES "REFrameworkNET/API.cpp" "REFrameworkNET/AssemblyInfo.cpp" + "REFrameworkNET/Attributes/Plugin.cpp" "REFrameworkNET/Callbacks.cpp" "REFrameworkNET/ManagedObject.cpp" "REFrameworkNET/Method.cpp" @@ -115,6 +116,7 @@ set(csharp-api_SOURCES "REFrameworkNET/TDB.cpp" "REFrameworkNET/TypeDefinition.cpp" "REFrameworkNET/API.hpp" + "REFrameworkNET/Attributes/Plugin.hpp" "REFrameworkNET/Callbacks.hpp" "REFrameworkNET/Field.hpp" "REFrameworkNET/IObject.hpp" diff --git a/csharp-api/REFrameworkNET/Attributes/Plugin.cpp b/csharp-api/REFrameworkNET/Attributes/Plugin.cpp new file mode 100644 index 000000000..a4290f233 --- /dev/null +++ b/csharp-api/REFrameworkNET/Attributes/Plugin.cpp @@ -0,0 +1 @@ +#include "Plugin.hpp" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Attributes/Plugin.hpp b/csharp-api/REFrameworkNET/Attributes/Plugin.hpp new file mode 100644 index 000000000..ddf3a6d0e --- /dev/null +++ b/csharp-api/REFrameworkNET/Attributes/Plugin.hpp @@ -0,0 +1,11 @@ +#pragma once + +namespace REFrameworkNET { + namespace Attributes { + [System::AttributeUsage(System::AttributeTargets::Method, AllowMultiple = true)] + public ref class PluginEntryPoint : System::Attribute { + public: + PluginEntryPoint() {} + }; + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index b5126e267..50b8aefab 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -1,5 +1,7 @@ #include #include + +#include "Attributes/Plugin.hpp" #include "PluginManager.hpp" using namespace System; @@ -130,14 +132,12 @@ namespace REFrameworkNET { // Look for Main method in the AssemblyGenerator class auto mainMethod = generator->GetMethod( "Main", - System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, - gcnew array{REFrameworkNET::API::typeid}); + System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); if (mainMethod != nullptr) { REFrameworkNET::API::LogInfo("Found AssemblyGenerator.Main in " + a->Location); - array^ args = gcnew array{PluginManager::s_api_instance}; - auto result = (List^)mainMethod->Invoke(nullptr, args); + auto result = (List^)mainMethod->Invoke(nullptr, nullptr); // Append the generated assemblies to the list of deps for each (Compiler::DynamicAssemblyBytecode^ bytes in result) { @@ -228,46 +228,46 @@ namespace REFrameworkNET { std::filesystem::create_directories(managed_path); System::String^ managed_dir = gcnew System::String(managed_path.wstring().c_str()); - - bool ever_found = false; auto files = System::IO::Directory::GetFiles(managed_dir, "*.dll"); - if (files->Length == 0) { - REFrameworkNET::API::LogInfo("No DLLs found in " + managed_dir); - return false; - } + if (files->Length != 0) { + bool ever_found = false; - for each (System::String^ file in files) { - Console::WriteLine(file); - System::Reflection::Assembly^ assem = System::Reflection::Assembly::LoadFrom(file); + for each (System::String^ file in files) { + Console::WriteLine(file); + System::Reflection::Assembly^ assem = System::Reflection::Assembly::LoadFrom(file); - if (assem == nullptr) { - REFrameworkNET::API::LogError("Failed to load assembly from " + file); - continue; - } + if (assem == nullptr) { + REFrameworkNET::API::LogError("Failed to load assembly from " + file); + continue; + } - // Iterate through all types in the assembly - for each (Type^ type in assem->GetTypes()) { - // Attempt to find the Main method with the expected signature in each type - System::Reflection::MethodInfo^ mainMethod = type->GetMethod( - "Main", - System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, - gcnew array{REFrameworkNET::API::typeid}); - - if (mainMethod != nullptr) { - REFrameworkNET::API::LogInfo("Found Main method in " + file); - - array^ args = gcnew array{PluginManager::s_api_instance}; - mainMethod->Invoke(nullptr, args); - ever_found = true; + // Iterate through all types in the assembly + for each (Type^ type in assem->GetTypes()) { + array^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); + + for each (System::Reflection::MethodInfo^ method in methods) { + array^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginEntryPoint::typeid, true); + + if (attributes->Length > 0) { + REFrameworkNET::API::LogInfo("Found PluginEntryPoint in " + method->Name + " in " + type->FullName); + method->Invoke(nullptr, nullptr); + ever_found = true; + } + } } } - } - if (!ever_found) { - REFrameworkNET::API::LogInfo("No Main method found in any DLLs in " + managed_dir); + if (!ever_found) { + REFrameworkNET::API::LogInfo("No Main method found in any DLLs in " + managed_dir); + } + } else { + REFrameworkNET::API::LogInfo("No DLLs found in " + managed_dir); } + // Unload dynamic assemblies (testing) + UnloadDynamicAssemblies(); + return true; } catch(System::Exception^ e) { System::Console::WriteLine(e->Message); @@ -338,28 +338,32 @@ namespace REFrameworkNET { continue; } - auto assem = System::Reflection::Assembly::Load(bytecode); + s_default_context = gcnew System::Runtime::Loader::AssemblyLoadContext("REFrameworkNET", true); + + auto assem = s_default_context->LoadFromStream(gcnew System::IO::MemoryStream(bytecode)); + //auto assem = System::Reflection::Assembly::Load(bytecode); if (assem == nullptr) { REFrameworkNET::API::LogError("Failed to load assembly from " + file); continue; } + s_dynamic_assemblies->Add(assem); + REFrameworkNET::API::LogInfo("Compiled " + file); // Look for the Main method in the compiled assembly for each (Type^ type in assem->GetTypes()) { - System::Reflection::MethodInfo^ mainMethod = type->GetMethod( - "Main", - System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, - gcnew array{REFrameworkNET::API::typeid}); + array^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); - if (mainMethod != nullptr) { - Console::WriteLine("Found Main method in " + file); + for each (System::Reflection::MethodInfo^ method in methods) { + array^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginEntryPoint::typeid, true); - array^ args = gcnew array{PluginManager::s_api_instance}; - mainMethod->Invoke(nullptr, args); - ever_found = true; + if (attributes->Length > 0) { + REFrameworkNET::API::LogInfo("Found PluginEntryPoint in " + method->Name + " in " + type->FullName); + method->Invoke(nullptr, nullptr); + ever_found = true; + } } } } @@ -387,4 +391,44 @@ namespace REFrameworkNET { REFrameworkNET::API::LogError("Unknown exception caught while compiling C# files"); return false; } + + void PluginManager::UnloadDynamicAssemblies() { + if (PluginManager::s_dynamic_assemblies == nullptr) { + REFrameworkNET::API::LogInfo("No dynamic assemblies to unload"); + return; + } + + REFrameworkNET::API::LogInfo("Unloading dynamic assemblies..."); + + for each (System::Reflection::Assembly ^ assem in PluginManager::s_dynamic_assemblies) { + if (assem == nullptr) { + continue; + } + + try { + // Look for the Unload method in the target assembly which takes an REFrameworkNET.API instance + for each (Type ^ t in assem->GetTypes()) { + auto method = t->GetMethod("OnUnload", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, nullptr, gcnew array{REFrameworkNET::API::typeid}, nullptr); + + if (method != nullptr) { + REFrameworkNET::API::LogInfo("Unloading dynamic assembly by calling " + method->Name + " in " + t->FullName); + method->Invoke(nullptr, gcnew array{PluginManager::s_api_instance}); + } + } + } + catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Failed to unload dynamic assembly: " + e->Message); + } + catch (const std::exception& e) { + REFrameworkNET::API::LogError("Failed to unload dynamic assembly: " + gcnew System::String(e.what())); + } + catch (...) { + REFrameworkNET::API::LogError("Unknown exception caught while unloading dynamic assembly"); + } + } + + s_dynamic_assemblies->Clear(); + PluginManager::s_default_context->Unload(); + PluginManager::s_default_context = nullptr; + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 700872589..59ada5d4a 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -33,5 +33,11 @@ private ref class PluginManager static void GenerateReferenceAssemblies(System::Collections::Generic::List^ deps); static bool LoadPlugins(uintptr_t param_raw); static bool LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps); + static void UnloadDynamicAssemblies(); + + static System::Collections::Generic::List^ s_loaded_assemblies{gcnew System::Collections::Generic::List()}; + static System::Collections::Generic::List^ s_dynamic_assemblies{gcnew System::Collections::Generic::List()}; + + static System::Runtime::Loader::AssemblyLoadContext^ s_default_context{nullptr}; }; } \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 5f0eb3776..583069f90 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -1,38 +1,39 @@ // Import REFramework::API using System; +using System.Collections; using System.Dynamic; using System.Reflection; public class DangerousFunctions { - public static REFrameworkNET.PreHookResult isInsidePreHook(System.Collections.Generic.List args) { - Console.WriteLine("Inside pre hook (From C#)"); + public static REFrameworkNET.PreHookResult isInsidePreHook(System.Object args) { + Console.WriteLine("Inside pre hook (From C#) " + args.ToString()); REFrameworkNET.API.LogInfo("isInsidePreHook"); return REFrameworkNET.PreHookResult.Continue; } - public static void isInsidePostHook() { + public static void isInsidePostHook(ref System.Object retval) { Console.WriteLine("Inside post hook (From C#)"); } public static void Entry() { var tdb = REFrameworkNET.API.GetTDB(); - tdb.GetType("app.CameraManager")?. + /*tdb.GetType("app.CameraManager")?. GetMethod("isInside")?. AddHook(false). AddPre(isInsidePreHook). - AddPost(isInsidePostHook); - + 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 - var sceneManager = REFrameworkNET.NativeProxy.CreateFromSingleton("via.SceneManager"); + var sceneManager = REFrameworkNET.API.GetNativeSingletonT(); var scene = sceneManager.get_CurrentScene(); - scene.set_Pause(true); + //scene.set_Pause(true); var view = sceneManager.get_MainView(); var name = view.get_Name(); var go = view.get_PrimaryCamera()?.get_GameObject()?.get_Transform()?.get_GameObject(); - + REFrameworkNET.API.LogInfo("game object name: " + go?.get_Name().ToString()); REFrameworkNET.API.LogInfo("Scene name: " + name); @@ -40,11 +41,12 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Scene: " + scene.ToString() + ": " + (scene as REFrameworkNET.IObject).GetTypeDefinition()?.GetFullName()?.ToString()); // Testing dynamic invocation - float currentTimescale = scene.get_TimeScale(); + /*float currentTimescale = scene.get_TimeScale(); scene.set_TimeScale(0.1f); REFrameworkNET.API.LogInfo("Previous timescale: " + currentTimescale.ToString()); - REFrameworkNET.API.LogInfo("Current timescale: " + scene?.get_TimeScale().ToString()); + REFrameworkNET.API.LogInfo("Current timescale: " + scene?.get_TimeScale().ToString());*/ + var appdomainStatics = tdb.GetType("System.AppDomain").As<_System.AppDomain>(); var appdomain = appdomainStatics.get_CurrentDomain(); dynamic assemblies = appdomain.GetAssemblies(); @@ -53,6 +55,33 @@ public static void Entry() { var assembly = assemblyRaw.As<_System.Reflection.Assembly>(); REFrameworkNET.API.LogInfo("Assembly: " + assembly.get_Location()?.ToString()); } + + via.os os = tdb.GetType("via.os").As(); + var platform = os.getPlatform(); + var platformSubset = os.getPlatformSubset(); + var title = os.getTitle(); + + REFrameworkNET.API.LogInfo("Platform: " + platform); + REFrameworkNET.API.LogInfo("Platform Subset: " + platformSubset); + REFrameworkNET.API.LogInfo("Title: " + title); + + via.os.dialog dialog = tdb.GetType("via.os.dialog").As(); + dialog.open("Hello from C#!"); + } + + public static void TryEnableFrameGeneration() { + var upscalingInterface = REFrameworkNET.API.GetNativeSingletonT(); + var dlssInterface = upscalingInterface.get_DLSSInterface(); + + if (dlssInterface != null && dlssInterface.get_DLSSGEnable() == false) { + dlssInterface.set_DLSSGEnable(true); + } + + var fsr3Interface = upscalingInterface.get_FSR3Interface(); + + if (fsr3Interface != null && fsr3Interface.get_EnableFrameGeneration() == false) { + fsr3Interface.set_EnableFrameGeneration(true); + } } } @@ -62,10 +91,12 @@ class REFrameworkPlugin { static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); - public static void Main(REFrameworkNET.API api) { - try { - REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); + // To be called when the AssemblyLoadContext is unloading the assembly + public static void OnUnload(REFrameworkNET.API api) { + REFrameworkNET.API.LogInfo("Unloading Test"); + } + public static void TestCallbacks() { REFrameworkNET.Callbacks.BeginRendering.Pre += () => { sw.Start(); }; @@ -76,6 +107,12 @@ public static void Main(REFrameworkNET.API api) { Console.WriteLine("BeginRendering took " + sw.ElapsedMilliseconds + "ms"); } + /*try { + DangerousFunctions.TryEnableFrameGeneration(); + } catch (Exception e) { + REFrameworkNET.API.LogError(e.ToString()); + }*/ + sw.Reset(); }; @@ -96,6 +133,14 @@ public static void Main(REFrameworkNET.API api) { REFrameworkNET.Callbacks.PrepareRendering.Post += () => { }; + } + + [REFrameworkNET.Attributes.PluginEntryPoint] + public static void Main() { + try { + REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); + + TestCallbacks(); var tdb = REFrameworkNET.API.GetTDB(); From fe3aa108d97d1e6e5ff644b5d9a8af2f6c5a3a67 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 30 Mar 2024 19:30:05 -0700 Subject: [PATCH 054/207] .NET: Add interface methods for identifying objects/proxies --- csharp-api/REFrameworkNET/IObject.hpp | 1 + csharp-api/REFrameworkNET/IProxyable.hpp | 1 + csharp-api/REFrameworkNET/ManagedObject.hpp | 8 ++++++++ csharp-api/REFrameworkNET/NativeObject.hpp | 8 ++++++++ csharp-api/REFrameworkNET/Proxy.hpp | 8 ++++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 8 ++++++++ 6 files changed, 34 insertions(+) diff --git a/csharp-api/REFrameworkNET/IObject.hpp b/csharp-api/REFrameworkNET/IObject.hpp index 7b73f448c..b659a86c0 100644 --- a/csharp-api/REFrameworkNET/IObject.hpp +++ b/csharp-api/REFrameworkNET/IObject.hpp @@ -13,6 +13,7 @@ public interface class IObject : public IProxyable { InvokeRet^ Invoke(System::String^ methodName, array^ args); TypeDefinition^ GetTypeDefinition(); + bool IsProperObject(); // For interface types generic diff --git a/csharp-api/REFrameworkNET/IProxyable.hpp b/csharp-api/REFrameworkNET/IProxyable.hpp index 115eb7659..5bb44d1a2 100644 --- a/csharp-api/REFrameworkNET/IProxyable.hpp +++ b/csharp-api/REFrameworkNET/IProxyable.hpp @@ -8,6 +8,7 @@ ref struct InvokeRet; public interface class IProxyable { void* Ptr(); uintptr_t GetAddress(); + virtual bool IsProxy(); bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); }; diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 43a2e78fa..e35e2f9b8 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -47,6 +47,14 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S return (uintptr_t)m_object; } + virtual bool IsProxy() { + return false; + } + + virtual bool IsProperObject() { + return true; + } + virtual bool Equals(System::Object^ other) override { if (System::Object::ReferenceEquals(this, other)) { return true; diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index 2d39ccd34..c2863aded 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -58,6 +58,14 @@ public ref class NativeObject : public System::Dynamic::DynamicObject, public Sy return (uintptr_t)m_object; } + virtual bool IsProxy() { + return false; + } + + virtual bool IsProperObject() { + return true; + } + virtual InvokeRet^ Invoke(System::String^ methodName, array^ args); virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index a3cbcb7be..df65116de 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -33,6 +33,14 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public virtual uintptr_t GetAddress() { return Instance->GetAddress(); } + + virtual bool IsProperObject() { + return Instance->IsProperObject(); + } + + virtual bool IsProxy() { + return true; + } virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args) { return Instance->Invoke(methodName, args); diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 02ceb623d..7177d4e87 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -327,6 +327,14 @@ public virtual TypeDefinition^ GetTypeDefinition() { return this; } + + virtual bool IsProperObject() { + return false; + } + + virtual bool IsProxy() { + return false; + } virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); From 670ea96640d34ce34d639bfb8e2bc8164c3b643d Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 30 Mar 2024 19:31:30 -0700 Subject: [PATCH 055/207] .NET: Heavily optimize InvokeRet --- csharp-api/REFrameworkNET/InvokeRet.hpp | 103 ++++++++++++++++++------ 1 file changed, 79 insertions(+), 24 deletions(-) diff --git a/csharp-api/REFrameworkNET/InvokeRet.hpp b/csharp-api/REFrameworkNET/InvokeRet.hpp index 82fb40fb5..5d7f75297 100644 --- a/csharp-api/REFrameworkNET/InvokeRet.hpp +++ b/csharp-api/REFrameworkNET/InvokeRet.hpp @@ -1,35 +1,90 @@ #pragma once #include +#include #pragma managed namespace REFrameworkNET { public ref struct InvokeRet { InvokeRet(const reframework::InvokeRet& ret) { - Bytes = gcnew array(ret.bytes.size()); - for (size_t i = 0; i < ret.bytes.size(); i++) { - Bytes[i] = ret.bytes[i]; - } - Byte = ret.byte; - Word = ret.word; - DWord = ret.dword; - Float = ret.f; - QWord = ret.qword; - Double = ret.d; - Ptr = gcnew System::UIntPtr(ret.ptr); - ExceptionThrown = ret.exception_thrown; - } - - // TODO: improve this? Does .NET have unions? - property array^ Bytes; - property uint8_t Byte; - property uint16_t Word; - property uint32_t DWord; - property float Float; - property uint64_t QWord; - property double Double; - property System::Object^ Ptr; - property bool ExceptionThrown; + using namespace System::Runtime::InteropServices; + Marshal::Copy(System::IntPtr((void*)&ret), m_invokeRetBytes, 0, sizeof(::reframework::InvokeRet)); + } + + reframework::InvokeRet* Marshal() { + pin_ptr pinned_bytes = &m_invokeRetBytes[0]; + uint8_t* bytes = pinned_bytes; + + reframework::InvokeRet* ret = reinterpret_cast(bytes); + + return ret; + } + + property System::Span Bytes { + public: + System::Span get() { + pin_ptr pinned_bytes = &m_invokeRetBytes[0]; + uint8_t* bytes = pinned_bytes; + return System::Span(bytes, 128); + } + }; + + property uint8_t Byte { + public: + uint8_t get() { + return m_invokeRetBytes[0]; + } + } + + property uint16_t Word { + public: + uint16_t get() { + return Marshal()->word; + } + } + + property uint32_t DWord { + public: + uint32_t get() { + return Marshal()->dword; + } + } + property float Float { + public: + float get() { + return Marshal()->f; + } + } + property uint64_t QWord { + public: + uint64_t get() { + return Marshal()->qword; + } + } + + property double Double { + public: + double get() { + return Marshal()->d; + } + } + property System::Object^ Ptr { + public: + System::Object^ get() { + return System::UIntPtr(Marshal()->ptr); + } + } + + property bool ExceptionThrown { + public: + bool get() { + return Marshal()->exception_thrown; + } + } + +private: + //::reframework::InvokeRet m_impl; + array^ m_invokeRetBytes = gcnew array(sizeof(::reframework::InvokeRet)); }; } \ No newline at end of file From f706efc40687dff376be4f069556f65360a9ab36 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 00:03:46 -0700 Subject: [PATCH 056/207] .NET: Add TDB.GetTypeT and adjust API generics to return null --- csharp-api/REFrameworkNET/API.hpp | 19 +++++++++++++++---- csharp-api/REFrameworkNET/TDB.hpp | 10 ++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index af99b41b3..a8157f089 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -105,16 +105,27 @@ public ref class API return gcnew NativeObject(result, t); } - generic + generic where T : ref class static T GetNativeSingletonT() { auto fullName = T::typeid->FullName; - return GetNativeSingleton(fullName)->As(); + + auto no = GetNativeSingleton(fullName); + if (no == nullptr) { + return T(); // nullptr basically + } + + return no->As(); } - generic + generic where T : ref class static T GetManagedSingletonT() { auto fullName = T::typeid->FullName; - return GetManagedSingleton(fullName)->As(); + auto mo = GetManagedSingleton(fullName); + if (mo == nullptr) { + return T(); // nullptr basically + } + + return mo->As(); } static reframework::API* GetNativeImplementation() { diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp index d0c0bd4bb..148b533bf 100644 --- a/csharp-api/REFrameworkNET/TDB.hpp +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -93,6 +93,16 @@ public ref class TDB { return FindType(name); } + generic where T : ref class + T GetTypeT() + { + auto t = GetType(T::typeid->FullName->Replace("+", ".")); + if (t == nullptr) { + return T(); // nullptr basically + } + return t->As(); + } + TypeDefinition^ FindTypeByFqn(uint32_t fqn) { auto result = m_tdb->find_type_by_fqn(fqn); From 21bffa52928e448ce02a46de932f2e47a06d5dcf Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 00:18:26 -0700 Subject: [PATCH 057/207] .NET: Add utilities for creating/interacting with managed strings --- csharp-api/CMakeLists.txt | 6 +++ csharp-api/REFrameworkNET/SystemArray.cpp | 4 ++ csharp-api/REFrameworkNET/SystemArray.hpp | 17 ++++++++ csharp-api/REFrameworkNET/SystemString.cpp | 40 +++++++++++++++++++ csharp-api/REFrameworkNET/SystemString.hpp | 35 ++++++++++++++++ csharp-api/REFrameworkNET/VM.cpp | 46 ++++++++++++++++++++++ csharp-api/REFrameworkNET/VM.hpp | 17 ++++++++ 7 files changed, 165 insertions(+) create mode 100644 csharp-api/REFrameworkNET/SystemArray.cpp create mode 100644 csharp-api/REFrameworkNET/SystemArray.hpp create mode 100644 csharp-api/REFrameworkNET/SystemString.cpp create mode 100644 csharp-api/REFrameworkNET/SystemString.hpp create mode 100644 csharp-api/REFrameworkNET/VM.cpp create mode 100644 csharp-api/REFrameworkNET/VM.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 57460ed76..d0ce84ee1 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -113,8 +113,11 @@ set(csharp-api_SOURCES "REFrameworkNET/Plugin.cpp" "REFrameworkNET/PluginManager.cpp" "REFrameworkNET/Proxy.cpp" + "REFrameworkNET/SystemArray.cpp" + "REFrameworkNET/SystemString.cpp" "REFrameworkNET/TDB.cpp" "REFrameworkNET/TypeDefinition.cpp" + "REFrameworkNET/VM.cpp" "REFrameworkNET/API.hpp" "REFrameworkNET/Attributes/Plugin.hpp" "REFrameworkNET/Callbacks.hpp" @@ -132,10 +135,13 @@ set(csharp-api_SOURCES "REFrameworkNET/PluginManager.hpp" "REFrameworkNET/Property.hpp" "REFrameworkNET/Proxy.hpp" + "REFrameworkNET/SystemArray.hpp" + "REFrameworkNET/SystemString.hpp" "REFrameworkNET/TDB.hpp" "REFrameworkNET/TypeDefinition.hpp" "REFrameworkNET/TypeInfo.hpp" "REFrameworkNET/Utility.hpp" + "REFrameworkNET/VM.hpp" cmake.toml ) diff --git a/csharp-api/REFrameworkNET/SystemArray.cpp b/csharp-api/REFrameworkNET/SystemArray.cpp new file mode 100644 index 000000000..fd246492f --- /dev/null +++ b/csharp-api/REFrameworkNET/SystemArray.cpp @@ -0,0 +1,4 @@ +#include "SystemArray.hpp" + +namespace REFrameworkNET { +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/SystemArray.hpp b/csharp-api/REFrameworkNET/SystemArray.hpp new file mode 100644 index 000000000..a39ce8a9b --- /dev/null +++ b/csharp-api/REFrameworkNET/SystemArray.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "ManagedObject.hpp" + +namespace REFrameworkNET { +// RE Engine's implementation of System.Array +generic +public ref class SystemArray : public ManagedObject/*, System::Collections::Generic::IList*/ +{ +public: + SystemArray(ManagedObject^ obj) : ManagedObject(obj) { } + +protected: + +private: +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/SystemString.cpp b/csharp-api/REFrameworkNET/SystemString.cpp new file mode 100644 index 000000000..ff43087a0 --- /dev/null +++ b/csharp-api/REFrameworkNET/SystemString.cpp @@ -0,0 +1,40 @@ +#include "Field.hpp" +#include "API.hpp" +#include "VM.hpp" +#include "SystemString.hpp" + +namespace REFrameworkNET { +SystemString::SystemString(::System::String^ str) + : ManagedObject(VM::CreateString(str)) +{ + +} + +SystemString::SystemString(std::wstring_view str) + : ManagedObject(VM::CreateString(str)) +{ + +} + +SystemString::SystemString(std::string_view str) + : ManagedObject(VM::CreateString(str)) +{ + +} + +::System::String^ SystemString::ToString() { + auto strType = GetTypeDefinition(); + const auto firstCharField = strType->GetField("_firstChar"); + uint32_t offset = 0; + + if (firstCharField != nullptr) { + offset = strType->GetField("_firstChar")->GetOffsetFromBase(); + } else { + const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)this->Ptr() - sizeof(void*)); + offset = fieldOffset + 4; + } + + wchar_t* chars = (wchar_t*)((uintptr_t)this->Ptr() + offset); + return gcnew System::String(chars); +} +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/SystemString.hpp b/csharp-api/REFrameworkNET/SystemString.hpp new file mode 100644 index 000000000..cb32271fa --- /dev/null +++ b/csharp-api/REFrameworkNET/SystemString.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "TypeDefinition.hpp" +#include "ManagedObject.hpp" + +namespace REFrameworkNET { +public ref class SystemString : public ManagedObject { +public: + SystemString(::REFrameworkManagedObjectHandle handle) + : ManagedObject(handle) + { + const auto td = this->GetTypeDefinition(); + if (td == nullptr || td->GetVMObjType() != VMObjType::String) { + throw gcnew System::ArgumentException("object is not a System.String"); + } + } + + SystemString(ManagedObject^% object) : ManagedObject(object) { + const auto td = object->GetTypeDefinition(); + if (td == nullptr || td->GetVMObjType() != VMObjType::String) { + throw gcnew System::ArgumentException("object is not a System.String"); + } + } + + SystemString(::System::String^ str); + SystemString(std::wstring_view str); + SystemString(std::string_view str); + + ::System::String^ ToString() override; + +private: +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/VM.cpp b/csharp-api/REFrameworkNET/VM.cpp new file mode 100644 index 000000000..f8f9d2c4d --- /dev/null +++ b/csharp-api/REFrameworkNET/VM.cpp @@ -0,0 +1,46 @@ +#include "API.hpp" +#include "SystemString.hpp" + +#include "VM.hpp" + +namespace REFrameworkNET { +SystemString^ VM::CreateString(::System::String^ str) { + static auto fn = REFrameworkNET::API::GetNativeImplementation()->sdk()->functions->create_managed_string; + + pin_ptr p = PtrToStringChars(str); + const auto chars = reinterpret_cast(p); + + auto objHandle = fn(chars); + + if (objHandle == nullptr) { + REFrameworkNET::API::LogWarning("Failed to create managed string"); + return nullptr; + } + + return gcnew SystemString(objHandle); +} + +SystemString^ VM::CreateString(std::wstring_view str) { + static auto fn = REFrameworkNET::API::GetNativeImplementation()->sdk()->functions->create_managed_string; + auto objHandle = fn(str.data()); + + if (objHandle == nullptr) { + REFrameworkNET::API::LogWarning("Failed to create managed string"); + return nullptr; + } + + return gcnew SystemString(objHandle); +} + +SystemString^ VM::CreateString(std::string_view str) { + static auto fn = REFrameworkNET::API::GetNativeImplementation()->sdk()->functions->create_managed_string_normal; + auto objHandle = fn(str.data()); + + if (objHandle == nullptr) { + REFrameworkNET::API::LogWarning("Failed to create managed string"); + return nullptr; + } + + return gcnew SystemString(objHandle); +} +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/VM.hpp b/csharp-api/REFrameworkNET/VM.hpp new file mode 100644 index 000000000..d69c95919 --- /dev/null +++ b/csharp-api/REFrameworkNET/VM.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace REFrameworkNET { +ref class ManagedObject; +ref class SystemString; + +public ref class VM { +public: + static SystemString^ CreateString(::System::String^ str); + static SystemString^ CreateString(std::wstring_view str); + static SystemString^ CreateString(std::string_view str); + +private: +}; +} \ No newline at end of file From 26b4ec443c118e83baa3560bf5294922f7daf077 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 00:56:28 -0700 Subject: [PATCH 058/207] .NET: Enable XML comments --- csharp-api/CMakeLists.txt | 1 + csharp-api/cmake.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index d0ce84ee1..fcfe8488e 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -157,6 +157,7 @@ target_compile_features(csharp-api PUBLIC target_compile_options(csharp-api PUBLIC "/EHa" "/MD" + "/doc" ) target_include_directories(csharp-api PUBLIC diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index af2e2c5a6..52cce2107 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -71,7 +71,7 @@ include-directories = ["../include/"] sources = ["REFrameworkNET/**.cpp", "REFrameworkNET/**.c"] headers = ["REFrameworkNET/**.hpp", "REFrameworkNET/**.h"] compile-features = ["cxx_std_20"] -compile-options = ["/EHa", "/MD"] +compile-options = ["/EHa", "/MD", "/doc"] link-libraries = [ "REFCSharpCompiler" ] From 81103edbbb7197f631dd3d2e5cb1c4a82e3f270c Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 00:58:51 -0700 Subject: [PATCH 059/207] .NET: Cache TDB.GetTypeT lookups --- csharp-api/REFrameworkNET/ManagedObject.hpp | 2 +- csharp-api/REFrameworkNET/TDB.cpp | 21 +++++++++- csharp-api/REFrameworkNET/TDB.hpp | 43 +++++++++++++++++++-- csharp-api/test/Test/Test.cs | 24 ++++++++++-- 4 files changed, 80 insertions(+), 10 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index e35e2f9b8..32184425b 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -151,7 +151,7 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S return _original.get_reflection_method_descriptor(name); }*/ -private: +protected: reframework::API::ManagedObject* m_object; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TDB.cpp b/csharp-api/REFrameworkNET/TDB.cpp index 42f30d771..5bcdfaf7c 100644 --- a/csharp-api/REFrameworkNET/TDB.cpp +++ b/csharp-api/REFrameworkNET/TDB.cpp @@ -1 +1,20 @@ -#include "TDB.hpp" \ No newline at end of file +#include "API.hpp" + +#include "TDB.hpp" + +namespace REFrameworkNET { + TDB^ TDB::Get() { + return REFrameworkNET::API::API::GetTDB(); + } + + generic + reframework::API::TypeDefinition* TDB::GetTypeDefinitionPtr() { + auto t = REFrameworkNET::API::GetTDB()->GetType(T::typeid->FullName->Replace("+", ".")); + + if (t == nullptr) { + return nullptr; + } + + return (reframework::API::TypeDefinition*)t->Ptr(); + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp index 148b533bf..ae0879cc4 100644 --- a/csharp-api/REFrameworkNET/TDB.hpp +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -18,9 +18,27 @@ public ref class TDB { private: reframework::API::TDB* m_tdb; +public: + generic where T : ref class + ref class TypeCacher { + public: + static REFrameworkNET::TypeDefinition^ GetCachedType() { + if (s_cachedType == nullptr) { + return nullptr; + } + + return gcnew REFrameworkNET::TypeDefinition(s_cachedType); + } + + private: + static reframework::API::TypeDefinition* s_cachedType = TDB::GetTypeDefinitionPtr(); + }; + public: TDB(reframework::API::TDB* tdb) : m_tdb(tdb) {} + static TDB^ Get(); + uintptr_t GetAddress() { return (uintptr_t)m_tdb; } @@ -89,17 +107,30 @@ public ref class TDB { return gcnew TypeDefinition(result); } + /// + /// Get a type by its name. + /// + /// The name of the type. + /// The type definition if found, otherwise null. + /// Not cached. TypeDefinition^ GetType(System::String^ name) { return FindType(name); } + /// + /// Get a type by its interface type, generally from a reference assembly. Must match the name exactly. + /// + /// The name of the type. + /// The type definition (casted to an interface) if found, otherwise null.) + /// Cached. generic where T : ref class - T GetTypeT() - { - auto t = GetType(T::typeid->FullName->Replace("+", ".")); + T GetTypeT() { + auto t = TypeCacher::GetCachedType(); + if (t == nullptr) { - return T(); // nullptr basically + return T(); } + return t->As(); } @@ -165,6 +196,10 @@ public ref class TDB { return gcnew Property(result); } +protected: + generic where T : ref class + static reframework::API::TypeDefinition* GetTypeDefinitionPtr(); + public: ref class TypeDefinitionIterator : public IEnumerator { private: diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 583069f90..ef18295f4 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -3,10 +3,11 @@ using System.Collections; using System.Dynamic; using System.Reflection; +using app; public class DangerousFunctions { public static REFrameworkNET.PreHookResult isInsidePreHook(System.Object args) { - Console.WriteLine("Inside pre hook (From C#) " + args.ToString()); + //Console.WriteLine("Inside pre hook (From C#) " + args.ToString()); REFrameworkNET.API.LogInfo("isInsidePreHook"); return REFrameworkNET.PreHookResult.Continue; } @@ -17,11 +18,11 @@ public static void isInsidePostHook(ref System.Object retval) { public static void Entry() { var tdb = REFrameworkNET.API.GetTDB(); - /*tdb.GetType("app.CameraManager")?. + tdb.GetType("app.CameraManager")?. GetMethod("isInside")?. AddHook(false). AddPre(isInsidePreHook). - AddPost(isInsidePostHook);*/ + AddPost(isInsidePostHook); // These via.SceneManager and via.Scene are // loaded from an external reference assembly @@ -65,8 +66,23 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Platform Subset: " + platformSubset); REFrameworkNET.API.LogInfo("Title: " + title); - via.os.dialog dialog = tdb.GetType("via.os.dialog").As(); + var dialog = tdb.GetTypeT(); dialog.open("Hello from C#!"); + + var devUtil = tdb.GetTypeT(); + var currentDirectory = System.IO.Directory.GetCurrentDirectory(); + devUtil.writeLogFile("what the heck", currentDirectory + "\\what_the_frick.txt"); + + var stringTest = REFrameworkNET.VM.CreateString("Hello from C#!"); // inside RE Engine VM + var stringInDotNetVM = stringTest.ToString(); // Back in .NET + + REFrameworkNET.API.LogInfo("Managed string back in .NET: " + stringInDotNetVM); + + var devUtilT = (devUtil as REFrameworkNET.IObject).GetTypeDefinition(); + var dialogT = (dialog as REFrameworkNET.IObject).GetTypeDefinition(); + + REFrameworkNET.API.LogInfo("DevUtil: " + devUtilT.GetFullName()); + REFrameworkNET.API.LogInfo("Dialog: " + dialogT.GetFullName()); } public static void TryEnableFrameGeneration() { From 94fd853a0249f1cbd47a13f79e312676ed1f9d6c Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 02:30:43 -0700 Subject: [PATCH 060/207] .NET: Fix plugin unloading not working correctly w/ dynamics --- csharp-api/CMakeLists.txt | 2 + .../REFrameworkNET/PluginLoadContext.cpp | 1 + .../REFrameworkNET/PluginLoadContext.hpp | 38 +++++++++++++++++++ csharp-api/REFrameworkNET/PluginManager.cpp | 23 ++++++++++- csharp-api/REFrameworkNET/PluginManager.hpp | 4 +- 5 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 csharp-api/REFrameworkNET/PluginLoadContext.cpp create mode 100644 csharp-api/REFrameworkNET/PluginLoadContext.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index fcfe8488e..78953d29a 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -111,6 +111,7 @@ set(csharp-api_SOURCES "REFrameworkNET/MethodHook.cpp" "REFrameworkNET/NativeObject.cpp" "REFrameworkNET/Plugin.cpp" + "REFrameworkNET/PluginLoadContext.cpp" "REFrameworkNET/PluginManager.cpp" "REFrameworkNET/Proxy.cpp" "REFrameworkNET/SystemArray.cpp" @@ -132,6 +133,7 @@ set(csharp-api_SOURCES "REFrameworkNET/MethodParameter.hpp" "REFrameworkNET/NativeObject.hpp" "REFrameworkNET/Plugin.hpp" + "REFrameworkNET/PluginLoadContext.hpp" "REFrameworkNET/PluginManager.hpp" "REFrameworkNET/Property.hpp" "REFrameworkNET/Proxy.hpp" diff --git a/csharp-api/REFrameworkNET/PluginLoadContext.cpp b/csharp-api/REFrameworkNET/PluginLoadContext.cpp new file mode 100644 index 000000000..edce90779 --- /dev/null +++ b/csharp-api/REFrameworkNET/PluginLoadContext.cpp @@ -0,0 +1 @@ +#include "PluginLoadContext.hpp" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginLoadContext.hpp b/csharp-api/REFrameworkNET/PluginLoadContext.hpp new file mode 100644 index 000000000..a3f803cb1 --- /dev/null +++ b/csharp-api/REFrameworkNET/PluginLoadContext.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "API.hpp" + +namespace REFrameworkNET { +public ref class PluginLoadContext : public System::Runtime::Loader::AssemblyLoadContext { +public: + PluginLoadContext() + : System::Runtime::Loader::AssemblyLoadContext("REFrameworkNET.PluginLoadContext", true) + { + + } + + System::Reflection::Assembly^ Load(System::Reflection::AssemblyName^ assemblyName) override + { + // Load the Microsoft.CSharp assembly into our plugin's AssemblyLoadContext + // If we don't do this, we will fail to unload the assembly when any dynamic operation is performed (like creating a dynamic object) + // For some reason this DLL keeps a reference to the assembly that created a dynamic when creating these objects + // Relevant github issue: https://github.com/dotnet/runtime/issues/71629 + if (assemblyName->Name == "Microsoft.CSharp") + { + System::Console::WriteLine("Searching in " + System::Runtime::InteropServices::RuntimeEnvironment::GetRuntimeDirectory() + " for Microsoft.CSharp.dll"); + auto dir = System::Runtime::InteropServices::RuntimeEnvironment::GetRuntimeDirectory(); + if (auto assem = LoadFromAssemblyPath(System::IO::Path::Combine(dir, "Microsoft.CSharp.dll")); assem != nullptr) { + return assem; + } + + REFrameworkNET::API::LogError("Failed to load Microsoft.CSharp.dll, dynamic operations may cause unloading issues"); + } + + REFrameworkNET::API::LogInfo(this->Name + " is requesting " + assemblyName->Name + ", redirecting to default context"); + + return nullptr; + } + +private: +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 50b8aefab..2d5b40230 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -338,7 +338,7 @@ namespace REFrameworkNET { continue; } - s_default_context = gcnew System::Runtime::Loader::AssemblyLoadContext("REFrameworkNET", true); + s_default_context = gcnew PluginLoadContext(); auto assem = s_default_context->LoadFromStream(gcnew System::IO::MemoryStream(bytecode)); //auto assem = System::Reflection::Assembly::Load(bytecode); @@ -428,7 +428,28 @@ namespace REFrameworkNET { } s_dynamic_assemblies->Clear(); + + // make weak ref to default context + System::WeakReference^ weakRef = gcnew System::WeakReference(s_default_context); PluginManager::s_default_context->Unload(); PluginManager::s_default_context = nullptr; + + bool unloaded = false; + + for (int i = 0; i < 10; i++) { + if (weakRef->IsAlive) { + System::GC::Collect(); + System::GC::WaitForPendingFinalizers(); + System::Threading::Thread::Sleep(10); + } else { + unloaded = true; + System::Console::WriteLine("Successfully unloaded default context"); + break; + } + } + + if (!unloaded) { + System::Console::WriteLine("Failed to unload default context"); + } } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 59ada5d4a..08aac5a46 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -9,6 +9,8 @@ extern "C" { #include "./API.hpp" +#include "PluginLoadContext.hpp" + namespace REFrameworkNET { private ref class PluginManager { @@ -38,6 +40,6 @@ private ref class PluginManager static System::Collections::Generic::List^ s_loaded_assemblies{gcnew System::Collections::Generic::List()}; static System::Collections::Generic::List^ s_dynamic_assemblies{gcnew System::Collections::Generic::List()}; - static System::Runtime::Loader::AssemblyLoadContext^ s_default_context{nullptr}; + static PluginLoadContext^ s_default_context{nullptr}; }; } \ No newline at end of file From ee5e64bb2ad74b32d8084775698cfc7e8e1c6298 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 02:45:42 -0700 Subject: [PATCH 061/207] .NET: Add fallback for loading Microsoft.CSharp.dll --- csharp-api/REFrameworkNET/PluginLoadContext.hpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginLoadContext.hpp b/csharp-api/REFrameworkNET/PluginLoadContext.hpp index a3f803cb1..cd11e2376 100644 --- a/csharp-api/REFrameworkNET/PluginLoadContext.hpp +++ b/csharp-api/REFrameworkNET/PluginLoadContext.hpp @@ -19,8 +19,21 @@ public ref class PluginLoadContext : public System::Runtime::Loader::AssemblyLoa // Relevant github issue: https://github.com/dotnet/runtime/issues/71629 if (assemblyName->Name == "Microsoft.CSharp") { - System::Console::WriteLine("Searching in " + System::Runtime::InteropServices::RuntimeEnvironment::GetRuntimeDirectory() + " for Microsoft.CSharp.dll"); - auto dir = System::Runtime::InteropServices::RuntimeEnvironment::GetRuntimeDirectory(); + auto path = Microsoft::CSharp::RuntimeBinder::Binder::typeid->Assembly->Location; + auto dir = System::IO::Path::GetDirectoryName(path); + + REFrameworkNET::API::LogInfo("Searching in " + dir + " for Microsoft.CSharp.dll"); + + if (auto assem = LoadFromAssemblyPath(System::IO::Path::Combine(dir, "Microsoft.CSharp.dll")); assem != nullptr) { + REFrameworkNET::API::LogInfo("Successfully loaded Microsoft.CSharp.dll from " + dir); + return assem; + } + + REFrameworkNET::API::LogWarning("Falling back to loading Microsoft.CSharp.dll from runtime directory"); + + dir = System::Runtime::InteropServices::RuntimeEnvironment::GetRuntimeDirectory(); + REFrameworkNET::API::LogInfo("Searching in " + dir + " for Microsoft.CSharp.dll"); + if (auto assem = LoadFromAssemblyPath(System::IO::Path::Combine(dir, "Microsoft.CSharp.dll")); assem != nullptr) { return assem; } From b41ac82f6502e2327e1b0bb1685c527a8011b992 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 04:27:06 -0700 Subject: [PATCH 062/207] .NET: Simplify callback system a bit --- csharp-api/REFrameworkNET/API.cpp | 5 ++++ csharp-api/REFrameworkNET/Callbacks.cpp | 31 ++++++++++++++------ csharp-api/REFrameworkNET/Callbacks.hpp | 38 +++++++++---------------- 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/csharp-api/REFrameworkNET/API.cpp b/csharp-api/REFrameworkNET/API.cpp index 55e4d01bd..64fe3dc08 100644 --- a/csharp-api/REFrameworkNET/API.cpp +++ b/csharp-api/REFrameworkNET/API.cpp @@ -17,6 +17,11 @@ REFrameworkNET::API::API(uintptr_t param) void REFrameworkNET::API::Init_Internal(const REFrameworkPluginInitializeParam* param) { + if (s_api != nullptr) { + Console::WriteLine("REFrameworkNET.API Init_Internal called but API is already initialized."); + return; + } + Console::WriteLine("REFrameworkNET.API Init_Internal called."); s_api = reframework::API::initialize(param).get(); Callbacks::Impl::Setup(this); diff --git a/csharp-api/REFrameworkNET/Callbacks.cpp b/csharp-api/REFrameworkNET/Callbacks.cpp index 4bcf17a62..7c3e4ebc1 100644 --- a/csharp-api/REFrameworkNET/Callbacks.cpp +++ b/csharp-api/REFrameworkNET/Callbacks.cpp @@ -3,10 +3,19 @@ #include "Callbacks.hpp" using namespace System; +using namespace System::Runtime::InteropServices; +using namespace System::Collections::Generic; namespace REFrameworkNET { namespace Callbacks { void Impl::Setup(REFrameworkNET::API^ api) { + if (s_setup) { + Console::WriteLine("REFrameworkNET.Callbacks SetupCallbacks called but already setup."); + return; + } + + s_setup = true; + Console::WriteLine("REFrameworkNET.Callbacks SetupCallbacks called."); reframework::API* nativeApi = api->GetNativeImplementation(); @@ -23,20 +32,26 @@ void Impl::Setup(REFrameworkNET::API^ api) { continue; } - if (type->GetField("FUNCTION_PRE_CALLBACK_ADDRESS") == nullptr) { - continue; - } + const auto wantedFlags = System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Static; - if (type->GetField("FUNCTION_POST_CALLBACK_ADDRESS") == nullptr) { + if (type->GetField("TriggerPostDelegate", wantedFlags) == nullptr || type->GetField("TriggerPreDelegate", wantedFlags) == nullptr) { continue; } auto eventName = type->Name; auto eventNameStr = msclr::interop::marshal_as(eventName); - auto eventHandlerPre = (uintptr_t)type->GetField("FUNCTION_PRE_CALLBACK_ADDRESS")->GetValue(nullptr); - auto eventHandlerPost = (uintptr_t)type->GetField("FUNCTION_POST_CALLBACK_ADDRESS")->GetValue(nullptr); - nativeApi->param()->functions->on_pre_application_entry(eventNameStr.c_str(), (REFOnPreApplicationEntryCb)eventHandlerPre); - nativeApi->param()->functions->on_post_application_entry(eventNameStr.c_str(), (REFOnPostApplicationEntryCb)eventHandlerPost); + auto triggerPost = type->GetField("TriggerPostDelegate", wantedFlags)->GetValue(nullptr); + auto triggerPre = type->GetField("TriggerPreDelegate", wantedFlags)->GetValue(nullptr); + + if (triggerPre == nullptr || triggerPost == nullptr) { + System::Console::WriteLine("REFrameworkNET.Callbacks SetupCallbacks: TriggerPreDelegate or TriggerPostDelegate is null for " + eventName); + continue; + } + + IntPtr preHookPtr = Marshal::GetFunctionPointerForDelegate(triggerPre); + IntPtr postHookPtr = Marshal::GetFunctionPointerForDelegate(triggerPost); + nativeApi->param()->functions->on_pre_application_entry(eventNameStr.c_str(), (REFOnPreApplicationEntryCb)preHookPtr.ToPointer()); + nativeApi->param()->functions->on_post_application_entry(eventNameStr.c_str(), (REFOnPostApplicationEntryCb)postHookPtr.ToPointer()); } } } diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp index 5845c816b..01a8fd0c5 100644 --- a/csharp-api/REFrameworkNET/Callbacks.hpp +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -8,41 +8,26 @@ using namespace System::Collections::Generic; namespace REFrameworkNET { ref class API; +public ref class BaseCallback { +public: + delegate void Delegate(); +}; + #define GENERATE_POCKET_CLASS(EVENT_NAME) \ -static void EVENT_NAME##PreHandler_Internal(); \ -static void EVENT_NAME##PostHandler_Internal(); \ public ref class EVENT_NAME { \ public: \ - delegate void Delegate(); \ - static event Delegate^ Pre; \ - static event Delegate^ Post; \ + static event BaseCallback::Delegate^ Pre; \ + static event BaseCallback::Delegate^ Post; \ +internal: \ static void TriggerPre() { \ Pre(); \ } \ static void TriggerPost() { \ Post(); \ } \ - static System::Reflection::MethodInfo^ TriggerPreMethod = nullptr;\ - static System::Reflection::MethodInfo^ TriggerPostMethod = nullptr;\ - static uintptr_t FUNCTION_PRE_CALLBACK_ADDRESS = (uintptr_t)&EVENT_NAME##PreHandler_Internal; \ - static uintptr_t FUNCTION_POST_CALLBACK_ADDRESS = (uintptr_t)&EVENT_NAME##PostHandler_Internal; \ + static BaseCallback::Delegate^ TriggerPreDelegate = gcnew BaseCallback::Delegate(&EVENT_NAME::TriggerPre); \ + static BaseCallback::Delegate^ TriggerPostDelegate = gcnew BaseCallback::Delegate(&EVENT_NAME::TriggerPost); \ }; \ -void EVENT_NAME##PreHandler_Internal() { \ - if (Callbacks::##EVENT_NAME::TriggerPreMethod == nullptr) { \ - auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); \ - auto type = self->GetType("REFrameworkNET.Callbacks." #EVENT_NAME); \ - Callbacks::##EVENT_NAME::TriggerPreMethod = type->GetMethod("TriggerPre", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); \ - } \ - Callbacks::##EVENT_NAME::TriggerPreMethod->Invoke(nullptr, nullptr); \ -} \ -void EVENT_NAME##PostHandler_Internal() { \ - if (Callbacks::##EVENT_NAME::TriggerPostMethod == nullptr) { \ - auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); \ - auto type = self->GetType("REFrameworkNET.Callbacks." #EVENT_NAME); \ - Callbacks::##EVENT_NAME::TriggerPostMethod = type->GetMethod("TriggerPost", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); \ - } \ - Callbacks::##EVENT_NAME::TriggerPostMethod->Invoke(nullptr, nullptr); \ -} namespace Callbacks { GENERATE_POCKET_CLASS(Initialize) @@ -425,6 +410,9 @@ namespace Callbacks { public ref class Impl { public: static void Setup(REFrameworkNET::API^ api); + + private: + static bool s_setup = false; }; }; } \ No newline at end of file From 55b6c54951ced15fe4cf6d62075938f0b4016652 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 05:58:10 -0700 Subject: [PATCH 063/207] .NET: Unsubscribe callbacks on unload --- csharp-api/REFrameworkNET/Callbacks.cpp | 84 +++++++++++++++++++++ csharp-api/REFrameworkNET/Callbacks.hpp | 58 +++++++++++--- csharp-api/REFrameworkNET/PluginManager.cpp | 6 +- csharp-api/test/Test/Test.cs | 29 +++---- 4 files changed, 152 insertions(+), 25 deletions(-) diff --git a/csharp-api/REFrameworkNET/Callbacks.cpp b/csharp-api/REFrameworkNET/Callbacks.cpp index 7c3e4ebc1..e05b81320 100644 --- a/csharp-api/REFrameworkNET/Callbacks.cpp +++ b/csharp-api/REFrameworkNET/Callbacks.cpp @@ -48,11 +48,95 @@ void Impl::Setup(REFrameworkNET::API^ api) { continue; } + if (auto pre = type->GetField("PreImplementation", wantedFlags); pre != nullptr) { + s_knownStaticEvents->Add(pre); + } + + if (auto post = type->GetField("PostImplementation", wantedFlags); post != nullptr) { + s_knownStaticEvents->Add(post); + } + IntPtr preHookPtr = Marshal::GetFunctionPointerForDelegate(triggerPre); IntPtr postHookPtr = Marshal::GetFunctionPointerForDelegate(triggerPost); nativeApi->param()->functions->on_pre_application_entry(eventNameStr.c_str(), (REFOnPreApplicationEntryCb)preHookPtr.ToPointer()); nativeApi->param()->functions->on_post_application_entry(eventNameStr.c_str(), (REFOnPostApplicationEntryCb)postHookPtr.ToPointer()); } } + +void Impl::UnsubscribeAssembly(System::Reflection::Assembly^ assembly) { + s_unloading = true; + + if (s_knownStaticEvents->Count == 0) { + REFrameworkNET::API::LogError("REFrameworkNET.Callbacks UnsubscribeAssembly: No known static events to unsubscribe from."); + s_unloading = false; + return; + } + + for each (System::Reflection::FieldInfo ^ ei in s_knownStaticEvents) { + while (true) { + auto pre = ei->GetValue(nullptr); + + if (pre == nullptr) { + break; + } + + auto del = (System::Delegate^)pre; + auto invocationList = del->GetInvocationList(); + + bool set = false; + + for each (System::Delegate ^ d in invocationList) { + // Get the assembly that the delegate is from + auto target = d->Method; + auto targetAssembly = target->DeclaringType->Assembly; + + if (targetAssembly == assembly) { + System::Console::WriteLine("REFrameworkNET.Callbacks UnsubscribeAssembly: Removing " + target->Name + " from " + ei->Name); + auto newDel = (BaseCallback::Delegate^)del - (BaseCallback::Delegate^)d; + ei->SetValue(nullptr, newDel); + set = true; + break; + } + } + + if (!set) { + break; + } + } + + while (true) { + auto post = ei->GetValue(nullptr); + + if (post == nullptr) { + break; + } + + auto del = (System::Delegate^)post; + auto invocationList = del->GetInvocationList(); + + bool set = false; + + for each (System::Delegate ^ d in invocationList) { + // Get the assembly that the delegate is from + auto target = d->Method; + auto targetAssembly = target->DeclaringType->Assembly; + + if (targetAssembly == assembly) { + System::Console::WriteLine("REFrameworkNET.Callbacks UnsubscribeAssembly: Removing " + target->Name + " from " + ei->Name); + auto newDel = (BaseCallback::Delegate^)del - (BaseCallback::Delegate^)d; + ei->SetValue(nullptr, newDel); + set = true; + break; + } + } + + if (!set) { + break; + } + } + } + + s_unloading = false; +} } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp index 01a8fd0c5..1ff5aeb9b 100644 --- a/csharp-api/REFrameworkNET/Callbacks.hpp +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -8,6 +8,27 @@ using namespace System::Collections::Generic; namespace REFrameworkNET { ref class API; +namespace Callbacks { + public ref class Impl { + public: + property bool IsUnloading { + static bool get() { + return s_unloading; + } + } + + internal: + static void Setup(REFrameworkNET::API^ api); + static void UnsubscribeAssembly(System::Reflection::Assembly^ assembly); + + private: + static bool s_setup{false}; + static bool s_unloading{false}; + + static System::Collections::Generic::List^ s_knownStaticEvents = gcnew System::Collections::Generic::List(); + }; +} + public ref class BaseCallback { public: delegate void Delegate(); @@ -16,8 +37,31 @@ public ref class BaseCallback { #define GENERATE_POCKET_CLASS(EVENT_NAME) \ public ref class EVENT_NAME { \ public: \ - static event BaseCallback::Delegate^ Pre; \ - static event BaseCallback::Delegate^ Post; \ + static event BaseCallback::Delegate^ Pre { \ + void add(BaseCallback::Delegate^ value) { \ + PreImplementation += value; \ + } \ + void remove(BaseCallback::Delegate^ value) { \ + PreImplementation -= value; \ + } \ + void raise() { \ + PreImplementation(); \ + } \ + } \ + static event BaseCallback::Delegate^ Post { \ + void add(BaseCallback::Delegate^ value) { \ + PostImplementation += value; \ + } \ + void remove(BaseCallback::Delegate^ value) { \ + PostImplementation -= value; \ + } \ + void raise() { \ + if (Callbacks::Impl::IsUnloading) { \ + return; \ + } \ + PostImplementation(); \ + } \ + } \ internal: \ static void TriggerPre() { \ Pre(); \ @@ -27,6 +71,8 @@ internal: \ } \ static BaseCallback::Delegate^ TriggerPreDelegate = gcnew BaseCallback::Delegate(&EVENT_NAME::TriggerPre); \ static BaseCallback::Delegate^ TriggerPostDelegate = gcnew BaseCallback::Delegate(&EVENT_NAME::TriggerPost); \ + static BaseCallback::Delegate^ PreImplementation; \ + static BaseCallback::Delegate^ PostImplementation; \ }; \ namespace Callbacks { @@ -406,13 +452,5 @@ namespace Callbacks { GENERATE_POCKET_CLASS(FinalizeDialog) GENERATE_POCKET_CLASS(FinalizeMixer) GENERATE_POCKET_CLASS(FinalizeGameCore); - - public ref class Impl { - public: - static void Setup(REFrameworkNET::API^ api); - - private: - static bool s_setup = false; - }; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 2d5b40230..1c0b3df65 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -408,12 +408,14 @@ namespace REFrameworkNET { try { // Look for the Unload method in the target assembly which takes an REFrameworkNET.API instance for each (Type ^ t in assem->GetTypes()) { - auto method = t->GetMethod("OnUnload", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public, nullptr, gcnew array{REFrameworkNET::API::typeid}, nullptr); + auto method = t->GetMethod("OnUnload", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); if (method != nullptr) { REFrameworkNET::API::LogInfo("Unloading dynamic assembly by calling " + method->Name + " in " + t->FullName); - method->Invoke(nullptr, gcnew array{PluginManager::s_api_instance}); + method->Invoke(nullptr, nullptr); } + + Callbacks::Impl::UnsubscribeAssembly(assem); } } catch (System::Exception^ e) { diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index ef18295f4..2cd8ba80e 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -108,7 +108,7 @@ class REFrameworkPlugin { static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); // To be called when the AssemblyLoadContext is unloading the assembly - public static void OnUnload(REFrameworkNET.API api) { + public static void OnUnload() { REFrameworkNET.API.LogInfo("Unloading Test"); } @@ -152,8 +152,22 @@ public static void TestCallbacks() { } [REFrameworkNET.Attributes.PluginEntryPoint] - public static void Main() { + public static void Main() { try { + MainImpl(); + } catch (Exception e) { + REFrameworkNET.API.LogError(e.ToString()); + + var ex = e; + + while (ex.InnerException != null) { + ex = ex.InnerException; + REFrameworkNET.API.LogError(ex.ToString()); + } + } + } + + public static void MainImpl() { REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); TestCallbacks(); @@ -273,16 +287,5 @@ public static void Main() { foreach (dynamic assembly in assemblies) { REFrameworkNET.API.LogInfo("Assembly: " + assembly.get_Location()?.ToString()); } - - } catch (Exception e) { - REFrameworkNET.API.LogError(e.ToString()); - - var ex = e; - - while (ex.InnerException != null) { - ex = ex.InnerException; - REFrameworkNET.API.LogError(ex.ToString()); - } - } } }; \ No newline at end of file From fdbede5ae1a9af7b5601e34a536f6cd4b7f78aab Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 19:19:22 -0700 Subject: [PATCH 064/207] .NET: Additional support for method args and more constructors --- csharp-api/REFrameworkNET/Field.hpp | 3 ++ csharp-api/REFrameworkNET/ManagedObject.hpp | 9 +++++ csharp-api/REFrameworkNET/Method.cpp | 42 ++++++++++++++++++++- csharp-api/REFrameworkNET/Method.hpp | 1 + 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp index 642a440a2..9265b8910 100644 --- a/csharp-api/REFrameworkNET/Field.hpp +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -2,6 +2,8 @@ #include +#include "TypeDefinition.hpp" + #pragma managed namespace REFrameworkNET { @@ -10,6 +12,7 @@ ref class TypeDefinition; public ref class Field { public: Field(const reframework::API::Field* field) : m_field(field) {} + Field(::REFrameworkFieldHandle handle) : m_field(reinterpret_cast(handle)) {} System::String^ GetName() { return gcnew System::String(m_field->get_name()); diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 32184425b..5b620c682 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -25,6 +25,15 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S } } + // Double check if we really want to allow this + // We might be better off having a global managed object cache + // instead of AddRef'ing every time we create a new ManagedObject + ManagedObject(ManagedObject^ obj) : m_object(obj->m_object) { + if (m_object != nullptr) { + AddRef(); + } + } + ~ManagedObject() { if (m_object != nullptr) { Release(); diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index f46c309b4..abd12b158 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -8,9 +8,13 @@ #include "Field.hpp" #include "API.hpp" +#include "VM.hpp" +#include "SystemString.hpp" #include "Utility.hpp" +using namespace System; + namespace REFrameworkNET { MethodHook^ Method::AddHook(bool ignore_jmp) { return MethodHook::Create(this, ignore_jmp); @@ -38,8 +42,34 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, arrayptr(); const auto t = args[i]->GetType(); - if (t == REFrameworkNET::ManagedObject::typeid) { - args2[i] = safe_cast(args[i])->Ptr(); + if (t->IsSubclassOf(REFrameworkNET::IObject::typeid)) { + auto iobj = safe_cast(args[i]); + + if (iobj->IsProperObject()) { + args2[i] = iobj->Ptr(); + } else if (t == REFrameworkNET::TypeDefinition::typeid) { + // TypeDefinitions are wrappers for System.RuntimeTypeHandle + // However there's basically no functions that actually take a System.RuntimeTypeHandle + // so we will just convert it to a System.Type. + if (auto td = iobj->GetTypeDefinition(); td != nullptr) { + if (auto rt = td->GetRuntimeType(); rt != nullptr) { + args2[i] = rt->Ptr(); + } else { + System::Console::WriteLine("TypeDefinition has no runtime type @ arg " + i); + } + } + } else { + args2[i] = nullptr; + System::Console::WriteLine("Unknown IObject type passed to method invocation @ arg " + i); + } + } else if (t == System::String::typeid) { + auto createdStr = VM::CreateString(safe_cast(args[i])); + + if (createdStr != nullptr) { + args2[i] = createdStr->Ptr(); + } else { + System::Console::WriteLine("Error creating string @ arg " + i); + } } else if (t == System::Boolean::typeid) { bool v = System::Convert::ToBoolean(args[i]); args2[i] = (void*)(intptr_t)v; @@ -181,6 +211,14 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::String^ me result = gcnew REFrameworkNET::TypeDefinition((::REFrameworkTypeDefinitionHandle)tempResult->QWord); break; } + case "System.RuntimeMethodHandle"_fnv: { + result = gcnew REFrameworkNET::Method((::REFrameworkMethodHandle)tempResult->QWord); + break; + } + case "System.RuntimeFieldHandle"_fnv: { + result = gcnew REFrameworkNET::Field((::REFrameworkFieldHandle)tempResult->QWord); + break; + } default: result = tempResult; break; diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 03d847d89..8dea53be5 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -19,6 +19,7 @@ public ref class Method : public System::IEquatable { public: Method(reframework::API::Method* method) : m_method(method) {} + Method(::REFrameworkMethodHandle handle) : m_method(reinterpret_cast(handle)) {} void* GetRaw() { return m_method; From 794f383407d01bae7a56bd00cce26027693c66ec Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 31 Mar 2024 23:05:15 -0700 Subject: [PATCH 065/207] .NET: Fix horrible exceptions that drained hard drive space --- csharp-api/REFrameworkNET/Callbacks.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp index 1ff5aeb9b..7408410e9 100644 --- a/csharp-api/REFrameworkNET/Callbacks.hpp +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -45,6 +45,9 @@ public: \ PreImplementation -= value; \ } \ void raise() { \ + if (Callbacks::Impl::IsUnloading || PreImplementation == nullptr) { \ + return; \ + } \ PreImplementation(); \ } \ } \ @@ -56,7 +59,7 @@ public: \ PostImplementation -= value; \ } \ void raise() { \ - if (Callbacks::Impl::IsUnloading) { \ + if (Callbacks::Impl::IsUnloading || PostImplementation == nullptr) { \ return; \ } \ PostImplementation(); \ From 63f5e78579c0720313bbe5db8cbf633b9e1879a0 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 00:11:17 -0700 Subject: [PATCH 066/207] .NET: Fix AssemblyLoadContext logic --- csharp-api/REFrameworkNET/PluginManager.cpp | 43 +++++++++++---------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 1c0b3df65..159868267 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -311,6 +311,7 @@ namespace REFrameworkNET { } auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); + s_default_context = gcnew PluginLoadContext(); for each (System::String^ file in files) { System::Console::WriteLine(file); @@ -338,8 +339,6 @@ namespace REFrameworkNET { continue; } - s_default_context = gcnew PluginLoadContext(); - auto assem = s_default_context->LoadFromStream(gcnew System::IO::MemoryStream(bytecode)); //auto assem = System::Reflection::Assembly::Load(bytecode); @@ -393,7 +392,7 @@ namespace REFrameworkNET { } void PluginManager::UnloadDynamicAssemblies() { - if (PluginManager::s_dynamic_assemblies == nullptr) { + if (PluginManager::s_dynamic_assemblies == nullptr || PluginManager::s_dynamic_assemblies->Count == 0) { REFrameworkNET::API::LogInfo("No dynamic assemblies to unload"); return; } @@ -432,26 +431,28 @@ namespace REFrameworkNET { s_dynamic_assemblies->Clear(); // make weak ref to default context - System::WeakReference^ weakRef = gcnew System::WeakReference(s_default_context); - PluginManager::s_default_context->Unload(); - PluginManager::s_default_context = nullptr; - - bool unloaded = false; - - for (int i = 0; i < 10; i++) { - if (weakRef->IsAlive) { - System::GC::Collect(); - System::GC::WaitForPendingFinalizers(); - System::Threading::Thread::Sleep(10); - } else { - unloaded = true; - System::Console::WriteLine("Successfully unloaded default context"); - break; + if (s_dynamic_assemblies != nullptr) { + System::WeakReference^ weakRef = gcnew System::WeakReference(s_default_context); + PluginManager::s_default_context->Unload(); + PluginManager::s_default_context = nullptr; + + bool unloaded = false; + + for (int i = 0; i < 10; i++) { + if (weakRef->IsAlive) { + System::GC::Collect(); + System::GC::WaitForPendingFinalizers(); + System::Threading::Thread::Sleep(10); + } else { + unloaded = true; + System::Console::WriteLine("Successfully unloaded default context"); + break; + } } - } - if (!unloaded) { - System::Console::WriteLine("Failed to unload default context"); + if (!unloaded) { + System::Console::WriteLine("Failed to unload default context"); + } } } } \ No newline at end of file From 70b9bdcdbe1947f53635bb6e54bb9dc49f40a0a1 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 00:24:50 -0700 Subject: [PATCH 067/207] .NET: Only compile Test.cs if the reference assemblies exist in bin --- csharp-api/CMakeLists.txt | 125 +++++++++++++++++++++++--------------- csharp-api/cmake.toml | 28 +++++++++ 2 files changed, 104 insertions(+), 49 deletions(-) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 78953d29a..45053f047 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -71,6 +71,30 @@ if(NOT NUGET_PACKAGES_DIR) set(NUGET_PACKAGES_DIR ${DEFAULT_NUGET_PATH}) endif() +# Set a bool if the generated reference assemblies exist in the build/bin folder +set(dll1 "${CMAKE_BINARY_DIR}/bin/REFramework.NET._System.dll") +set(dll2 "${CMAKE_BINARY_DIR}/bin/REFramework.NET.application.dll") +set(dll3 "${CMAKE_BINARY_DIR}/bin/REFramework.NET.viacore.dll") + +# Initialize a variable to keep track of the existence of all files +set(REFRAMEWORK_REF_ASSEMBLIES_EXIST TRUE) + +# Check if each DLL exists +foreach(dll IN ITEMS ${dll1} ${dll2} ${dll3}) + if(NOT EXISTS ${dll}) + set(REFRAMEWORK_REF_ASSEMBLIES_EXIST FALSE) + break() # Exit the loop as soon as one file is not found + endif() +endforeach() + + +# Use the result +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) + message(STATUS "All specified DLLs exist.") +else() + message(STATUS "One or more specified DLLs do not exist.") +endif() + # Target: REFCSharpCompiler set(REFCSharpCompiler_SOURCES "Compiler/Compiler.cs" @@ -249,58 +273,61 @@ VS_DOTNET_REFERENCE_REFramework.NET ) # Target: CSharpAPITest -set(CSharpAPITest_SOURCES - "test/Test/Test.cs" - cmake.toml -) - -add_library(CSharpAPITest SHARED) - -target_sources(CSharpAPITest PRIVATE ${CSharpAPITest_SOURCES}) -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITest_SOURCES}) - -target_link_libraries(CSharpAPITest PUBLIC - csharp-api -) - -set_target_properties(CSharpAPITest PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_RELEASE - "${CMAKE_BINARY_DIR}/bin/" - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO - "${CMAKE_BINARY_DIR}/bin" - LIBRARY_OUTPUT_DIRECTORY_RELEASE - "${CMAKE_BINARY_DIR}/lib" - DOTNET_SDK - Microsoft.NET.Sdk - DOTNET_TARGET_FRAMEWORK - net8.0-windows - VS_CONFIGURATION_TYPE - ClassLibrary -) - -set(CMKR_TARGET CSharpAPITest) -set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET -"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" -) - +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) # build-csharp-test + set(CSharpAPITest_SOURCES + "test/Test/Test.cs" + cmake.toml + ) + + add_library(CSharpAPITest SHARED) + + target_sources(CSharpAPITest PRIVATE ${CSharpAPITest_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITest_SOURCES}) + + target_link_libraries(CSharpAPITest PUBLIC + csharp-api + ) + + set_target_properties(CSharpAPITest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary + ) + + set(CMKR_TARGET CSharpAPITest) + set_target_properties(CSharpAPITest PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET + "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" + ) + set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET._System -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET._System.dll" -) - + VS_DOTNET_REFERENCE_REFramework.NET._System + "${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET._System.dll" + ) + set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") - + set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.viacore -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.viacore.dll" -) - + VS_DOTNET_REFERENCE_REFramework.NET.viacore + "${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.viacore.dll" + ) + set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") - + set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.application -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.application.dll" -) - + VS_DOTNET_REFERENCE_REFramework.NET.application + "${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.application.dll" + ) + set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") + +endif() diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 52cce2107..7e558e249 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -43,8 +43,35 @@ if(NOT NUGET_PACKAGES_DIR) endif() set(NUGET_PACKAGES_DIR ${DEFAULT_NUGET_PATH}) endif() + +# Set a bool if the generated reference assemblies exist in the build/bin folder +set(dll1 "${CMAKE_BINARY_DIR}/bin/REFramework.NET._System.dll") +set(dll2 "${CMAKE_BINARY_DIR}/bin/REFramework.NET.application.dll") +set(dll3 "${CMAKE_BINARY_DIR}/bin/REFramework.NET.viacore.dll") + +# Initialize a variable to keep track of the existence of all files +set(REFRAMEWORK_REF_ASSEMBLIES_EXIST TRUE) + +# Check if each DLL exists +foreach(dll IN ITEMS ${dll1} ${dll2} ${dll3}) + if(NOT EXISTS ${dll}) + set(REFRAMEWORK_REF_ASSEMBLIES_EXIST FALSE) + break() # Exit the loop as soon as one file is not found + endif() +endforeach() + + +# Use the result +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) + message(STATUS "All specified DLLs exist.") +else() + message(STATUS "One or more specified DLLs do not exist.") +endif() """ +[conditions] +build-csharp-test = "REFRAMEWORK_REF_ASSEMBLIES_EXIST" + [template.CSharpSharedTarget] type = "shared" @@ -124,6 +151,7 @@ VS_DOTNET_REFERENCE_REFramework.NET """ [target.CSharpAPITest] +condition = "build-csharp-test" type = "CSharpSharedTarget" sources = ["test/Test/**.cs"] link-libraries = [ From 5dcb5c58ac7b753349dec8be00a124b83863c7f7 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 00:49:47 -0700 Subject: [PATCH 068/207] .NET: Add just_copy argument for make_symlinks.py --- csharp-api/make_symlinks.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/csharp-api/make_symlinks.py b/csharp-api/make_symlinks.py index 1552cdf5a..9d0498d76 100644 --- a/csharp-api/make_symlinks.py +++ b/csharp-api/make_symlinks.py @@ -3,8 +3,9 @@ import fire import os import ctypes +import shutil -def symlink_main(gamedir=None, bindir="build/bin"): +def symlink_main(gamedir=None, bindir="build/bin", just_copy=False): if gamedir is None: print("Usage: make_symlinks.py --gamedir=") return @@ -15,7 +16,7 @@ def symlink_main(gamedir=None, bindir="build/bin"): except AttributeError: is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 - if not is_admin: + if not is_admin and not just_copy: print("Error: This script must be run as an administrator") return @@ -44,7 +45,10 @@ def symlink_main(gamedir=None, bindir="build/bin"): except FileNotFoundError: pass - os.symlink(src, dst) + if just_copy == True: + shutil.copy(src, dst) + else: + os.symlink(src, dst) for file in plugins_dir_files: src = os.path.join(bindir, file) @@ -55,7 +59,11 @@ def symlink_main(gamedir=None, bindir="build/bin"): os.remove(dst) except FileNotFoundError: pass - os.symlink(src, dst) + + if just_copy == True: + shutil.copy(src, dst) + else: + os.symlink(src, dst) dependencies_dir_files = [ "AssemblyGenerator.dll", @@ -75,7 +83,11 @@ def symlink_main(gamedir=None, bindir="build/bin"): os.remove(dst) except FileNotFoundError: pass - os.symlink(src, dst) + + if just_copy == True: + shutil.copy(src, dst) + else: + os.symlink(src, dst) print("Symlinks created successfully") From 2b316fdb1e7ec0fc7af2ba7f3ef906c147f5c477 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 01:02:51 -0700 Subject: [PATCH 069/207] .NET: Attempt at getting CI builds going --- .github/workflows/dev-release.yml | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index c9837e08f..d4106cce7 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -3,6 +3,38 @@ on: [push, workflow_dispatch] env: BUILD_TYPE: Release jobs: + csharp-release: + runs-on: windows-latest + strategy: + matrix: + target: [csharp-api] + steps: + - name: Set up Python + uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c + with: + python-version: "3.12" + + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + + - name: Configure CMake + run: cmake -S ${{github.workspace}}/csharp-api -B ${{github.workspace}}/csharp-api/build -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/csharp-api/build --config ${{env.BUILD_TYPE}} --target ALL_BUILD + + - name: Compress release + run: | + python ${{github.workspace}}/csharp-api/make-symlinks.py --gamedir="${{github.workspace}}/csharp-api" --just_copy=True + 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/csharp-api/reframework + + - name: Upload artifacts + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 + with: + name: ${{matrix.target}} + path: ${{github.workspace}}/${{matrix.target}}.zip + if-no-files-found: error + dev-release: runs-on: windows-latest strategy: From 1c81b406fdc5752d3341391493aa4fb21960805d Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 01:08:44 -0700 Subject: [PATCH 070/207] .NET CI: Misspelled the python script --- .github/workflows/dev-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index d4106cce7..3576ce5a7 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -25,7 +25,7 @@ jobs: - name: Compress release run: | - python ${{github.workspace}}/csharp-api/make-symlinks.py --gamedir="${{github.workspace}}/csharp-api" --just_copy=True + python ${{github.workspace}}/csharp-api/make_symlinks.py --gamedir="${{github.workspace}}/csharp-api" --just_copy=True 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/csharp-api/reframework - name: Upload artifacts From 6bf4ea081febcc6904c60cefae6dfe6c5d446aac Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 01:13:36 -0700 Subject: [PATCH 071/207] .NET CI: Fix missing python lib --- .github/workflows/dev-release.yml | 1 + csharp-api/requirements.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 csharp-api/requirements.txt diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index 3576ce5a7..78c37346b 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -25,6 +25,7 @@ jobs: - name: Compress release run: | + pip install -r ${{github.workspace}}/csharp-api/requirements.txt python ${{github.workspace}}/csharp-api/make_symlinks.py --gamedir="${{github.workspace}}/csharp-api" --just_copy=True 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/csharp-api/reframework diff --git a/csharp-api/requirements.txt b/csharp-api/requirements.txt new file mode 100644 index 000000000..69b21656c --- /dev/null +++ b/csharp-api/requirements.txt @@ -0,0 +1 @@ +fire \ No newline at end of file From 5e3929ec62559ca0ddf9c0547ce9124e420c0e1b Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 01:21:40 -0700 Subject: [PATCH 072/207] .NET CI: Another attempted fix --- .github/workflows/dev-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index 78c37346b..ca689dc9b 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -26,7 +26,7 @@ jobs: - name: Compress release run: | pip install -r ${{github.workspace}}/csharp-api/requirements.txt - python ${{github.workspace}}/csharp-api/make_symlinks.py --gamedir="${{github.workspace}}/csharp-api" --just_copy=True + python ${{github.workspace}}/csharp-api/make_symlinks.py --gamedir="${{github.workspace}}/csharp-api" --bindir="${{github.workspace}}/csharp-api/build/bin" --just_copy=True 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/csharp-api/reframework - name: Upload artifacts From db05b3cc434ba23417272dca24980df4a10beea0 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 01:27:49 -0700 Subject: [PATCH 073/207] .NET CI: Possible fix for nested zip --- .github/workflows/dev-release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml index ca689dc9b..8574d9d02 100644 --- a/.github/workflows/dev-release.yml +++ b/.github/workflows/dev-release.yml @@ -26,14 +26,13 @@ jobs: - name: Compress release run: | pip install -r ${{github.workspace}}/csharp-api/requirements.txt - python ${{github.workspace}}/csharp-api/make_symlinks.py --gamedir="${{github.workspace}}/csharp-api" --bindir="${{github.workspace}}/csharp-api/build/bin" --just_copy=True - 7z a ${{github.workspace}}/${{matrix.target}}.zip ${{github.workspace}}/csharp-api/reframework + python ${{github.workspace}}/csharp-api/make_symlinks.py --gamedir="${{github.workspace}}/csharp-api" --bindir="${{github.workspace}}/csharp-api/build/bin" --just_copy=Tru - name: Upload artifacts uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 with: name: ${{matrix.target}} - path: ${{github.workspace}}/${{matrix.target}}.zip + path: ${{github.workspace}}/csharp-api/reframework/ if-no-files-found: error dev-release: From 8764c1c2be098b7ecc965c4a017cb5a22913d986 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 2 Apr 2024 01:30:19 -0700 Subject: [PATCH 074/207] .NET: Add .xml file to python script --- csharp-api/make_symlinks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/csharp-api/make_symlinks.py b/csharp-api/make_symlinks.py index 9d0498d76..ecd70e660 100644 --- a/csharp-api/make_symlinks.py +++ b/csharp-api/make_symlinks.py @@ -28,6 +28,7 @@ def symlink_main(gamedir=None, bindir="build/bin", just_copy=False): plugins_dir_files = [ "REFramework.NET.dll", "REFramework.NET.runtimeconfig.json", + "REFramework.NET.xml", "Ijwhost.dll", ] From c9fff929f19e2f4d0d38f0d9c294028a9cf58ee4 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 4 Apr 2024 10:53:32 -0700 Subject: [PATCH 075/207] .NET: Object type unification/simplification/DRY principle --- csharp-api/CMakeLists.txt | 3 + csharp-api/REFrameworkNET/IObject.hpp | 5 +- csharp-api/REFrameworkNET/IProxyable.hpp | 1 + csharp-api/REFrameworkNET/ManagedObject.cpp | 238 ----------------- csharp-api/REFrameworkNET/ManagedObject.hpp | 83 +----- csharp-api/REFrameworkNET/Method.cpp | 26 +- csharp-api/REFrameworkNET/NativeObject.cpp | 38 --- csharp-api/REFrameworkNET/NativeObject.hpp | 90 +------ .../REFrameworkNET/ObjectEnumerator.hpp | 60 +++++ csharp-api/REFrameworkNET/Proxy.hpp | 29 +- csharp-api/REFrameworkNET/TypeDefinition.cpp | 2 +- csharp-api/REFrameworkNET/TypeDefinition.hpp | 15 +- csharp-api/REFrameworkNET/UnifiedObject.cpp | 249 ++++++++++++++++++ csharp-api/REFrameworkNET/UnifiedObject.hpp | 105 ++++++++ csharp-api/test/Test/Test.cs | 11 +- 15 files changed, 489 insertions(+), 466 deletions(-) create mode 100644 csharp-api/REFrameworkNET/ObjectEnumerator.hpp create mode 100644 csharp-api/REFrameworkNET/UnifiedObject.cpp create mode 100644 csharp-api/REFrameworkNET/UnifiedObject.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 45053f047..eebfce4b1 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -142,6 +142,7 @@ set(csharp-api_SOURCES "REFrameworkNET/SystemString.cpp" "REFrameworkNET/TDB.cpp" "REFrameworkNET/TypeDefinition.cpp" + "REFrameworkNET/UnifiedObject.cpp" "REFrameworkNET/VM.cpp" "REFrameworkNET/API.hpp" "REFrameworkNET/Attributes/Plugin.hpp" @@ -156,6 +157,7 @@ set(csharp-api_SOURCES "REFrameworkNET/MethodHook.hpp" "REFrameworkNET/MethodParameter.hpp" "REFrameworkNET/NativeObject.hpp" + "REFrameworkNET/ObjectEnumerator.hpp" "REFrameworkNET/Plugin.hpp" "REFrameworkNET/PluginLoadContext.hpp" "REFrameworkNET/PluginManager.hpp" @@ -166,6 +168,7 @@ set(csharp-api_SOURCES "REFrameworkNET/TDB.hpp" "REFrameworkNET/TypeDefinition.hpp" "REFrameworkNET/TypeInfo.hpp" + "REFrameworkNET/UnifiedObject.hpp" "REFrameworkNET/Utility.hpp" "REFrameworkNET/VM.hpp" cmake.toml diff --git a/csharp-api/REFrameworkNET/IObject.hpp b/csharp-api/REFrameworkNET/IObject.hpp index b659a86c0..a2e6de48e 100644 --- a/csharp-api/REFrameworkNET/IObject.hpp +++ b/csharp-api/REFrameworkNET/IObject.hpp @@ -9,11 +9,12 @@ ref class TypeDefinition; ref struct InvokeRet; // Base interface of ManagedObject and NativeObject -public interface class IObject : public IProxyable { +public interface class IObject : public IProxyable, public System::IEquatable { InvokeRet^ Invoke(System::String^ methodName, array^ args); + System::Object^ Call(System::String^ methodName, ... array^ args); + System::Object^ GetField(System::String^ fieldName); TypeDefinition^ GetTypeDefinition(); - bool IsProperObject(); // For interface types generic diff --git a/csharp-api/REFrameworkNET/IProxyable.hpp b/csharp-api/REFrameworkNET/IProxyable.hpp index 5bb44d1a2..80ecc22cb 100644 --- a/csharp-api/REFrameworkNET/IProxyable.hpp +++ b/csharp-api/REFrameworkNET/IProxyable.hpp @@ -11,5 +11,6 @@ public interface class IProxyable { virtual bool IsProxy(); bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + bool HandleTryGetMember_Internal(System::String^ fieldName, System::Object^% result); }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 30ce79b94..d88f5c887 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -33,244 +33,6 @@ namespace REFrameworkNET { return gcnew TypeInfo(result); } - REFrameworkNET::InvokeRet^ ManagedObject::Invoke(System::String^ methodName, array^ args) { - // Get method - auto t = this->GetTypeDefinition(); - if (t == nullptr) { - return nullptr; - } - - auto m = t->GetMethod(methodName); - - if (m == nullptr) { - return nullptr; - } - - return m->Invoke(this, args); - } - - bool ManagedObject::HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result) { - auto t = this->GetTypeDefinition(); - - if (t == nullptr) { - return false; - } - - auto method = t->FindMethod(methodName); - - if (method != nullptr) - { - // Re-used with ManagedObject::TryInvokeMember - return method->HandleInvokeMember_Internal(this, methodName, args, result); - } - - REFrameworkNET::API::LogInfo("Method not found: " + methodName); - - result = nullptr; - return false; - } - - bool ManagedObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) - { - auto methodName = binder->Name; - return HandleInvokeMember_Internal(methodName, args, result); - } - - bool ManagedObject::TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) - { - auto memberName = binder->Name; - auto t = this->GetTypeDefinition(); - - if (t == nullptr) { - return false; - } - - auto field = t->FindField(memberName); - - if (field != nullptr) - { - const auto field_type = field->GetType(); - - if (field_type == nullptr) { - return false; - } - - const auto raw_ft = (reframework::API::TypeDefinition*)field_type; - const uintptr_t addr = field->IsStatic() ? 0 : this->GetAddress(); - const auto vm_obj_type = field_type->GetVMObjType(); - - #define MAKE_TYPE_HANDLER(X, Y) \ - case ##X##_fnv: \ - result = gcnew Y(field->GetData<##Y##>(addr, field_type->IsValueType())); \ - break; - - switch (REFrameworkNET::hash(raw_ft->get_full_name())) { - MAKE_TYPE_HANDLER("System.Boolean", bool) - MAKE_TYPE_HANDLER("System.Byte", uint8_t) - MAKE_TYPE_HANDLER("System.SByte", int8_t) - MAKE_TYPE_HANDLER("System.Int16", int16_t) - MAKE_TYPE_HANDLER("System.UInt16", uint16_t) - MAKE_TYPE_HANDLER("System.Int32", int32_t) - MAKE_TYPE_HANDLER("System.UInt32", uint32_t) - MAKE_TYPE_HANDLER("System.Int64", int64_t) - MAKE_TYPE_HANDLER("System.UInt64", uint64_t) - MAKE_TYPE_HANDLER("System.Single", float) - MAKE_TYPE_HANDLER("System.Double", double) - MAKE_TYPE_HANDLER("System.Char", wchar_t) - MAKE_TYPE_HANDLER("System.IntPtr", intptr_t) - MAKE_TYPE_HANDLER("System.UIntPtr", uintptr_t) - case "System.String"_fnv: - { - if (field->IsLiteral()) { - result = gcnew System::String((const char*)field->GetInitDataPtr()); - break; - } - - // TODO: Check if this half of it works - auto strObject = field->GetData(addr, field_type->IsValueType()); - - if (strObject == nullptr) { - result = nullptr; - break; - } - - const auto firstCharField = field_type->GetField("_firstChar"); - uint32_t offset = 0; - - if (firstCharField != nullptr) { - offset = field_type->IsValueType() ? firstCharField->GetOffsetFromFieldPtr() : firstCharField->GetOffsetFromBase(); - } else { - const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)strObject - sizeof(void*)); - offset = fieldOffset + 4; - } - - wchar_t* chars = (wchar_t*)((uintptr_t)strObject + offset); - result = gcnew System::String(chars); - break; - } - default: - if (vm_obj_type > VMObjType::NULL_ && vm_obj_type < VMObjType::ValType) { - switch (vm_obj_type) { - case VMObjType::Array: - //return sol::make_object(l, *(::sdk::SystemArray**)data); - result = nullptr; - break; // TODO: Implement array - default: { - //const auto td = utility::re_managed_object::get_type_definition(*(::REManagedObject**)data); - auto& obj = field->GetData(addr, field_type->IsValueType()); - - if (obj == nullptr) { - result = nullptr; - break; - } - - auto td = gcnew REFrameworkNET::TypeDefinition(obj->get_type_definition()); - - // another fallback incase the method returns an object which is an array - if (td != nullptr && td->GetVMObjType() == VMObjType::Array) { - //return sol::make_object(l, *(::sdk::SystemArray**)data); - result = nullptr; - break; - } - - result = gcnew ManagedObject(obj); - break; - } - } - } else { - switch (field_type->GetSize()) { - case 8: - result = gcnew System::UInt64(field->GetData(addr, field_type->IsValueType())); - break; - case 4: - result = gcnew System::UInt32(field->GetData(addr, field_type->IsValueType())); - break; - case 2: - result = gcnew System::UInt16(field->GetData(addr, field_type->IsValueType())); - break; - case 1: - result = gcnew System::Byte(field->GetData(addr, field_type->IsValueType())); - break; - default: - result = nullptr; - break; - } - - break; - } - }; - - return true; - } - - /*auto property = t->FindProperty(memberName); - - if (property != nullptr) - { - result = property->GetValue(this); - return true; - }*/ - - REFrameworkNET::API::LogInfo("Member not found: " + memberName); - - result = nullptr; - return false; - } - - bool ManagedObject::TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) - { - auto memberName = binder->Name; - auto t = this->GetTypeDefinition(); - - if (t == nullptr) { - return false; - } - - auto field = t->FindField(memberName); - - if (field != nullptr) - { - const auto field_type = field->GetType(); - - if (field_type == nullptr) { - return false; - } - - const auto raw_ft = (reframework::API::TypeDefinition*)field_type; - const uintptr_t addr = field->IsStatic() ? 0 : this->GetAddress(); - const auto vm_obj_type = field_type->GetVMObjType(); - - #define MAKE_TYPE_HANDLER_SET(X, Y) \ - case ##X##_fnv: \ - field->GetData<##Y##>(addr, field_type->IsValueType()) = (Y)value; \ - break; - - switch (REFrameworkNET::hash(raw_ft->get_full_name())) { - MAKE_TYPE_HANDLER_SET("System.Boolean", bool) - MAKE_TYPE_HANDLER_SET("System.Byte", uint8_t) - MAKE_TYPE_HANDLER_SET("System.SByte", int8_t) - MAKE_TYPE_HANDLER_SET("System.Int16", int16_t) - MAKE_TYPE_HANDLER_SET("System.UInt16", uint16_t) - MAKE_TYPE_HANDLER_SET("System.Int32", int32_t) - MAKE_TYPE_HANDLER_SET("System.UInt32", uint32_t) - MAKE_TYPE_HANDLER_SET("System.Int64", int64_t) - MAKE_TYPE_HANDLER_SET("System.UInt64", uint64_t) - MAKE_TYPE_HANDLER_SET("System.Single", float) - MAKE_TYPE_HANDLER_SET("System.Double", double) - MAKE_TYPE_HANDLER_SET("System.Char", wchar_t) - MAKE_TYPE_HANDLER_SET("System.IntPtr", intptr_t) - MAKE_TYPE_HANDLER_SET("System.UIntPtr", uintptr_t) - - default: - break; - } - - return true; - } - - return false; - } - generic T ManagedObject::As() { return ManagedProxy::Create(this); diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 5b620c682..6bbc39dd7 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -3,6 +3,9 @@ #include #include "IObject.hpp" +#include "ObjectEnumerator.hpp" +#include "UnifiedObject.hpp" + #pragma managed namespace REFrameworkNET { @@ -11,7 +14,7 @@ ref class TypeInfo; ref class InvokeRet; ref class ManagedObject; -public ref class ManagedObject : public System::Dynamic::DynamicObject, public System::IEquatable, public REFrameworkNET::IObject +public ref class ManagedObject : public REFrameworkNET::UnifiedObject { public: ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj) { @@ -48,67 +51,6 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S m_object->release(); } - virtual void* Ptr() { - return (void*)m_object; - } - - virtual uintptr_t GetAddress() { - return (uintptr_t)m_object; - } - - virtual bool IsProxy() { - return false; - } - - virtual bool IsProperObject() { - return true; - } - - virtual bool Equals(System::Object^ other) override { - if (System::Object::ReferenceEquals(this, other)) { - return true; - } - - if (System::Object::ReferenceEquals(other, nullptr)) { - return false; - } - - if (other->GetType() != ManagedObject::typeid) { - return false; - } - - return Ptr() == safe_cast(other)->Ptr(); - } - - // Override equality operator - virtual bool Equals(ManagedObject^ other) { - if (System::Object::ReferenceEquals(this, other)) { - return true; - } - - if (System::Object::ReferenceEquals(other, nullptr)) { - return false; - } - - return Ptr() == other->Ptr(); - } - - static bool operator ==(ManagedObject^ left, ManagedObject^ right) { - if (System::Object::ReferenceEquals(left, right)) { - return true; - } - - if (System::Object::ReferenceEquals(left, nullptr) || System::Object::ReferenceEquals(right, nullptr)) { - return false; - } - - return left->Ptr() == right->Ptr(); - } - - static bool operator !=(ManagedObject^ left, ManagedObject^ right) { - return !(left == right); - } - static bool IsManagedObject(uintptr_t ptr) { if (ptr == 0) { return false; @@ -134,18 +76,21 @@ public ref class ManagedObject : public System::Dynamic::DynamicObject, public S return ToManagedObject(ptr); } - virtual TypeDefinition^ GetTypeDefinition(); TypeInfo^ GetTypeInfo(); - virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); +public: // IObject + virtual void* Ptr() override { + return (void*)m_object; + } + + virtual uintptr_t GetAddress() override { + return (uintptr_t)m_object; + } - virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); - virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; - virtual bool TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) override; - virtual bool TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) override; + virtual TypeDefinition^ GetTypeDefinition() override; generic - virtual T As(); + virtual T As() override; // TODO methods: /*public Void* GetReflectionProperties() { diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index abd12b158..a76d34eea 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -44,23 +44,17 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, arrayIsSubclassOf(REFrameworkNET::IObject::typeid)) { auto iobj = safe_cast(args[i]); - - if (iobj->IsProperObject()) { - args2[i] = iobj->Ptr(); - } else if (t == REFrameworkNET::TypeDefinition::typeid) { - // TypeDefinitions are wrappers for System.RuntimeTypeHandle - // However there's basically no functions that actually take a System.RuntimeTypeHandle - // so we will just convert it to a System.Type. - if (auto td = iobj->GetTypeDefinition(); td != nullptr) { - if (auto rt = td->GetRuntimeType(); rt != nullptr) { - args2[i] = rt->Ptr(); - } else { - System::Console::WriteLine("TypeDefinition has no runtime type @ arg " + i); - } - } + args2[i] = iobj->Ptr(); + } else if (t == REFrameworkNET::TypeDefinition::typeid) { + // TypeDefinitions are wrappers for System.RuntimeTypeHandle + // However there's basically no functions that actually take a System.RuntimeTypeHandle + // so we will just convert it to a System.Type. + auto td = safe_cast(args[i]); + + if (auto rt = td->GetRuntimeType(); rt != nullptr) { + args2[i] = rt->Ptr(); } else { - args2[i] = nullptr; - System::Console::WriteLine("Unknown IObject type passed to method invocation @ arg " + i); + System::Console::WriteLine("TypeDefinition has no runtime type @ arg " + i); } } else if (t == System::String::typeid) { auto createdStr = VM::CreateString(safe_cast(args[i])); diff --git a/csharp-api/REFrameworkNET/NativeObject.cpp b/csharp-api/REFrameworkNET/NativeObject.cpp index 038ce3b92..6f63a9390 100644 --- a/csharp-api/REFrameworkNET/NativeObject.cpp +++ b/csharp-api/REFrameworkNET/NativeObject.cpp @@ -6,44 +6,6 @@ #include "API.hpp" namespace REFrameworkNET { -InvokeRet^ NativeObject::Invoke(System::String^ methodName, array^ args) { - auto t = this->GetTypeDefinition(); - - if (t == nullptr) { - return nullptr; - } - - auto m = t->GetMethod(methodName); - - if (m == nullptr) { - return nullptr; - } - - return m->Invoke(this, args); -} - -bool NativeObject::HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result) { - auto t = this->GetTypeDefinition(); - - if (t == nullptr) { - return false; - } - - auto m = t->GetMethod(methodName); - - if (m == nullptr) { - REFrameworkNET::API::LogInfo("Method not found: " + methodName); - return false; - } - - return m->HandleInvokeMember_Internal(this, methodName, args, result); -} - -bool NativeObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) -{ - return HandleInvokeMember_Internal(binder->Name, args, result); -} - generic T NativeObject::As() { return NativeProxy::Create(this); diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index c2863aded..5ec990ea5 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -4,7 +4,9 @@ #include "TypeDefinition.hpp" #include "InvokeRet.hpp" -#include "IObject.hpp" +#include "UnifiedObject.hpp" + +#include "ObjectEnumerator.hpp" namespace REFrameworkNET { ref class InvokeRet; @@ -13,7 +15,7 @@ ref class InvokeRet; // However, they still have reflection information associated with them // So this intends to be the "ManagedObject" class for native objects // So we can easily interact with them in C# -public ref class NativeObject : public System::Dynamic::DynamicObject, public System::Collections::IEnumerable, public REFrameworkNET::IObject +public ref class NativeObject : public REFrameworkNET::UnifiedObject { public: NativeObject(uintptr_t obj, TypeDefinition^ t){ @@ -46,96 +48,20 @@ public ref class NativeObject : public System::Dynamic::DynamicObject, public Sy m_object = nullptr; } - virtual TypeDefinition^ GetTypeDefinition() { + virtual TypeDefinition^ GetTypeDefinition() override { return m_type; } - virtual void* Ptr() { + virtual void* Ptr() override { return m_object; } - virtual uintptr_t GetAddress() { + virtual uintptr_t GetAddress() override { return (uintptr_t)m_object; } - virtual bool IsProxy() { - return false; - } - - virtual bool IsProperObject() { - return true; - } - - virtual InvokeRet^ Invoke(System::String^ methodName, array^ args); - - virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); - virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; - generic - virtual T As(); - -public: - // IEnumerable implementation - virtual System::Collections::IEnumerator^ GetEnumerator() { - return gcnew NativeObjectEnumerator(this); - } - -private: - ref class NativeObjectEnumerator : public System::Collections::IEnumerator - { - int position = -1; - NativeObject^ nativeObject; - - public: - NativeObjectEnumerator(NativeObject^ nativeObject) { - this->nativeObject = nativeObject; - } - - // IEnumerator implementation - virtual bool MoveNext() { - int itemCount = GetItemCount(); - if (position < itemCount - 1) { - position++; - return true; - } - return false; - } - - virtual void Reset() { - position = -1; - } - - virtual property System::Object^ Current { - System::Object^ get() { - if (position == -1 || position >= GetItemCount()) { - throw gcnew System::InvalidOperationException(); - } - - System::Object^ result = nullptr; - if (nativeObject->HandleInvokeMember_Internal("get_Item", gcnew array{ position }, result)) { - return result; - } - - return nullptr; - } - } - - private: - int GetItemCount() { - //return nativeObject->Invoke("get_Count", gcnew array{})->DWord; - System::Object^ result = nullptr; - - if (nativeObject->HandleInvokeMember_Internal("get_Count", gcnew array{}, result)) { - return (int)result; - } - - if (nativeObject->HandleInvokeMember_Internal("get_Length", gcnew array{}, result)) { - return (int)result; - } - - return 0; - } - }; + virtual T As() override; private: void* m_object{}; diff --git a/csharp-api/REFrameworkNET/ObjectEnumerator.hpp b/csharp-api/REFrameworkNET/ObjectEnumerator.hpp new file mode 100644 index 000000000..536323a8a --- /dev/null +++ b/csharp-api/REFrameworkNET/ObjectEnumerator.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "IObject.hpp" + +namespace REFrameworkNET { +ref class ObjectEnumerator : public System::Collections::IEnumerator { +private: + int position = -1; + IObject^ nativeObject; + +public: + ObjectEnumerator(IObject^ nativeObject) { + this->nativeObject = nativeObject; + } + + // IEnumerator implementation + virtual bool MoveNext() { + int itemCount = GetItemCount(); + if (position < itemCount - 1) { + position++; + return true; + } + return false; + } + + virtual void Reset() { + position = -1; + } + + virtual property System::Object^ Current { + System::Object^ get() { + if (position == -1 || position >= GetItemCount()) { + throw gcnew System::InvalidOperationException(); + } + + System::Object^ result = nullptr; + if (nativeObject->HandleInvokeMember_Internal("get_Item", gcnew array{ position }, result)) { + return result; + } + + return nullptr; + } + } + +private: + int GetItemCount() { + //return nativeObject->Invoke("get_Count", gcnew array{})->DWord; + System::Object^ result = nullptr; + + if (nativeObject->HandleInvokeMember_Internal("get_Count", gcnew array{}, result)) { + return (int)result; + } + + if (nativeObject->HandleInvokeMember_Internal("get_Length", gcnew array{}, result)) { + return (int)result; + } + + return 0;} +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index df65116de..0a05538e4 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -33,10 +33,6 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public virtual uintptr_t GetAddress() { return Instance->GetAddress(); } - - virtual bool IsProperObject() { - return Instance->IsProperObject(); - } virtual bool IsProxy() { return true; @@ -50,6 +46,19 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public return Instance->HandleInvokeMember_Internal(methodName, args, result); } + virtual bool HandleTryGetMember_Internal(System::String^ fieldName, System::Object^% result) { + return Instance->HandleTryGetMember_Internal(fieldName, result); + } + + virtual System::Object^ GetField(System::String^ fieldName) { + return Instance->GetField(fieldName); + } + + virtual System::Object^ Call(System::String^ methodName, ... array^ args) { + return Instance->Call(methodName, args); + } + + // For interface types generic virtual T As() { @@ -88,6 +97,18 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public return Equals(other); } + virtual bool Equals(IObject^ other) { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + return Ptr() == other->Ptr(); + } + int GetHashCode() override { return Instance->GetHashCode(); } diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 89df7d449..2f9c8bf73 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -147,6 +147,6 @@ namespace REFrameworkNET { generic T TypeDefinition::As() { - return NativeProxy::Create(this); + return NativeProxy::Create(gcnew NativeObject(this)); } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 7177d4e87..0d763e3bc 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -27,8 +27,7 @@ public enum VMObjType { public ref class TypeDefinition : public System::Dynamic::DynamicObject, - public System::IEquatable, - public REFrameworkNET::IObject + public System::IEquatable { public: TypeDefinition(reframework::API::TypeDefinition* td) : m_type(td) {} @@ -324,18 +323,6 @@ public return (uintptr_t)m_type; } - virtual TypeDefinition^ GetTypeDefinition() { - return this; - } - - virtual bool IsProperObject() { - return false; - } - - virtual bool IsProxy() { - return false; - } - virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); diff --git a/csharp-api/REFrameworkNET/UnifiedObject.cpp b/csharp-api/REFrameworkNET/UnifiedObject.cpp new file mode 100644 index 000000000..845fe6983 --- /dev/null +++ b/csharp-api/REFrameworkNET/UnifiedObject.cpp @@ -0,0 +1,249 @@ +#include "API.hpp" + +#include "TypeDefinition.hpp" +#include "UnifiedObject.hpp" + +#include "./Utility.hpp" + +namespace REFrameworkNET { + REFrameworkNET::InvokeRet^ UnifiedObject::Invoke(System::String^ methodName, array^ args) { + // Get method + auto t = this->GetTypeDefinition(); + if (t == nullptr) { + return nullptr; + } + + auto m = t->GetMethod(methodName); + + if (m == nullptr) { + return nullptr; + } + + return m->Invoke(this, args); + } + + bool UnifiedObject::HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result) { + auto t = this->GetTypeDefinition(); + + if (t == nullptr) { + return false; + } + + auto method = t->FindMethod(methodName); + + if (method != nullptr) + { + // Re-used with UnifiedObject::TryInvokeMember + return method->HandleInvokeMember_Internal(this, methodName, args, result); + } + + REFrameworkNET::API::LogInfo("Method not found: " + methodName); + + result = nullptr; + return false; + } + + bool UnifiedObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) + { + auto methodName = binder->Name; + return HandleInvokeMember_Internal(methodName, args, result); + } + + bool UnifiedObject::TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) { + auto memberName = binder->Name; + return HandleTryGetMember_Internal(memberName, result); + } + + bool UnifiedObject::HandleTryGetMember_Internal(System::String^ memberName, System::Object^% result) { + auto t = this->GetTypeDefinition(); + + if (t == nullptr) { + return false; + } + + auto field = t->FindField(memberName); + + if (field != nullptr) + { + const auto field_type = field->GetType(); + + if (field_type == nullptr) { + return false; + } + + const auto raw_ft = (reframework::API::TypeDefinition*)field_type; + const uintptr_t addr = field->IsStatic() ? 0 : this->GetAddress(); + const auto vm_obj_type = field_type->GetVMObjType(); + + #define MAKE_TYPE_HANDLER(X, Y) \ + case ##X##_fnv: \ + result = gcnew Y(field->GetData<##Y##>(addr, field_type->IsValueType())); \ + break; + + switch (REFrameworkNET::hash(raw_ft->get_full_name())) { + MAKE_TYPE_HANDLER("System.Boolean", bool) + MAKE_TYPE_HANDLER("System.Byte", uint8_t) + MAKE_TYPE_HANDLER("System.SByte", int8_t) + MAKE_TYPE_HANDLER("System.Int16", int16_t) + MAKE_TYPE_HANDLER("System.UInt16", uint16_t) + MAKE_TYPE_HANDLER("System.Int32", int32_t) + MAKE_TYPE_HANDLER("System.UInt32", uint32_t) + MAKE_TYPE_HANDLER("System.Int64", int64_t) + MAKE_TYPE_HANDLER("System.UInt64", uint64_t) + MAKE_TYPE_HANDLER("System.Single", float) + MAKE_TYPE_HANDLER("System.Double", double) + MAKE_TYPE_HANDLER("System.Char", wchar_t) + MAKE_TYPE_HANDLER("System.IntPtr", intptr_t) + MAKE_TYPE_HANDLER("System.UIntPtr", uintptr_t) + case "System.String"_fnv: + { + if (field->IsLiteral()) { + result = gcnew System::String((const char*)field->GetInitDataPtr()); + break; + } + + // TODO: Check if this half of it works + auto strObject = field->GetData(addr, field_type->IsValueType()); + + if (strObject == nullptr) { + result = nullptr; + break; + } + + const auto firstCharField = field_type->GetField("_firstChar"); + uint32_t offset = 0; + + if (firstCharField != nullptr) { + offset = field_type->IsValueType() ? firstCharField->GetOffsetFromFieldPtr() : firstCharField->GetOffsetFromBase(); + } else { + const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)strObject - sizeof(void*)); + offset = fieldOffset + 4; + } + + wchar_t* chars = (wchar_t*)((uintptr_t)strObject + offset); + result = gcnew System::String(chars); + break; + } + default: + if (vm_obj_type > VMObjType::NULL_ && vm_obj_type < VMObjType::ValType) { + switch (vm_obj_type) { + case VMObjType::Array: + //return sol::make_object(l, *(::sdk::SystemArray**)data); + result = nullptr; + break; // TODO: Implement array + default: { + //const auto td = utility::re_managed_object::get_type_definition(*(::REManagedObject**)data); + auto& obj = field->GetData(addr, field_type->IsValueType()); + + if (obj == nullptr) { + result = nullptr; + break; + } + + auto td = gcnew REFrameworkNET::TypeDefinition(obj->get_type_definition()); + + // another fallback incase the method returns an object which is an array + if (td != nullptr && td->GetVMObjType() == VMObjType::Array) { + //return sol::make_object(l, *(::sdk::SystemArray**)data); + result = nullptr; + break; + } + + result = gcnew ManagedObject(obj); + break; + } + } + } else { + switch (field_type->GetSize()) { + case 8: + result = gcnew System::UInt64(field->GetData(addr, field_type->IsValueType())); + break; + case 4: + result = gcnew System::UInt32(field->GetData(addr, field_type->IsValueType())); + break; + case 2: + result = gcnew System::UInt16(field->GetData(addr, field_type->IsValueType())); + break; + case 1: + result = gcnew System::Byte(field->GetData(addr, field_type->IsValueType())); + break; + default: + result = nullptr; + break; + } + + break; + } + }; + + return true; + } + + /*auto property = t->FindProperty(memberName); + + if (property != nullptr) + { + result = property->GetValue(this); + return true; + }*/ + + REFrameworkNET::API::LogInfo("Member not found: " + memberName); + + result = nullptr; + return false; + } + + bool UnifiedObject::TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) + { + auto memberName = binder->Name; + auto t = this->GetTypeDefinition(); + + if (t == nullptr) { + return false; + } + + auto field = t->FindField(memberName); + + if (field != nullptr) + { + const auto field_type = field->GetType(); + + if (field_type == nullptr) { + return false; + } + + const auto raw_ft = (reframework::API::TypeDefinition*)field_type; + const uintptr_t addr = field->IsStatic() ? 0 : this->GetAddress(); + const auto vm_obj_type = field_type->GetVMObjType(); + + #define MAKE_TYPE_HANDLER_SET(X, Y) \ + case ##X##_fnv: \ + field->GetData<##Y##>(addr, field_type->IsValueType()) = (Y)value; \ + break; + + switch (REFrameworkNET::hash(raw_ft->get_full_name())) { + MAKE_TYPE_HANDLER_SET("System.Boolean", bool) + MAKE_TYPE_HANDLER_SET("System.Byte", uint8_t) + MAKE_TYPE_HANDLER_SET("System.SByte", int8_t) + MAKE_TYPE_HANDLER_SET("System.Int16", int16_t) + MAKE_TYPE_HANDLER_SET("System.UInt16", uint16_t) + MAKE_TYPE_HANDLER_SET("System.Int32", int32_t) + MAKE_TYPE_HANDLER_SET("System.UInt32", uint32_t) + MAKE_TYPE_HANDLER_SET("System.Int64", int64_t) + MAKE_TYPE_HANDLER_SET("System.UInt64", uint64_t) + MAKE_TYPE_HANDLER_SET("System.Single", float) + MAKE_TYPE_HANDLER_SET("System.Double", double) + MAKE_TYPE_HANDLER_SET("System.Char", wchar_t) + MAKE_TYPE_HANDLER_SET("System.IntPtr", intptr_t) + MAKE_TYPE_HANDLER_SET("System.UIntPtr", uintptr_t) + + default: + break; + } + + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/UnifiedObject.hpp b/csharp-api/REFrameworkNET/UnifiedObject.hpp new file mode 100644 index 000000000..c6a0fd534 --- /dev/null +++ b/csharp-api/REFrameworkNET/UnifiedObject.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include "IObject.hpp" +#include "ObjectEnumerator.hpp" + +namespace REFrameworkNET { +ref class TypeDefinition; +ref struct InvokeRet; + +// UnifiedObject is the base class that ManagedObject and NativeObject will derive from +// It will have several shared methods but some unimplemented methods that will be implemented in the derived classes +public ref class UnifiedObject abstract : public System::Dynamic::DynamicObject, public System::Collections::IEnumerable, public REFrameworkNET::IObject { +public: + virtual bool IsProxy() { + return false; + } + + // These methods will be implemented in the derived classes + virtual void* Ptr() abstract = 0; + virtual uintptr_t GetAddress() abstract = 0; + virtual REFrameworkNET::TypeDefinition^ GetTypeDefinition() abstract = 0; + + generic + virtual T As() abstract = 0; + + // Shared methods + virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + virtual bool HandleTryGetMember_Internal(System::String^ fieldName, System::Object^% result); + + virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); + virtual bool TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) override; + virtual bool TryGetMember(System::Dynamic::GetMemberBinder^ binder, System::Object^% result) override; + virtual bool TrySetMember(System::Dynamic::SetMemberBinder^ binder, System::Object^ value) override; + + virtual System::Object^ Call(System::String^ methodName, ... array^ args) { + System::Object^ result = nullptr; + HandleInvokeMember_Internal(methodName, args, result); + + return result; + } + + virtual System::Object^ GetField(System::String^ fieldName) { + System::Object^ result = nullptr; + if (!HandleTryGetMember_Internal(fieldName, result)) { + if (!HandleInvokeMember_Internal("get_" + fieldName, gcnew array{}, result)) { + // TODO? what else can we do here? + } + } + + return result; + } + +public: + virtual bool Equals(System::Object^ other) override { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + if (!other->GetType()->IsSubclassOf(IObject::typeid)) { + return false; + } + + return Ptr() == safe_cast(other)->Ptr(); + } + + // Override equality operator + virtual bool Equals(IObject^ other) { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + return Ptr() == other->Ptr(); + } + + static bool operator ==(UnifiedObject^ left, UnifiedObject^ right) { + if (System::Object::ReferenceEquals(left, right)) { + return true; + } + + if (System::Object::ReferenceEquals(left, nullptr) || System::Object::ReferenceEquals(right, nullptr)) { + return false; + } + + return left->Ptr() == right->Ptr(); + } + + static bool operator !=(UnifiedObject^ left, UnifiedObject^ right) { + return !(left == right); + } + +public: + // IEnumerable implementation + virtual System::Collections::IEnumerator^ GetEnumerator() { + return gcnew REFrameworkNET::ObjectEnumerator(this); + } +}; +} \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 2cd8ba80e..a2c7e8e08 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -18,17 +18,24 @@ public static void isInsidePostHook(ref System.Object retval) { public static void Entry() { var tdb = REFrameworkNET.API.GetTDB(); - tdb.GetType("app.CameraManager")?. + /*tdb.GetType("app.CameraManager")?. GetMethod("isInside")?. AddHook(false). AddPre(isInsidePreHook). - AddPost(isInsidePostHook); + 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 var sceneManager = REFrameworkNET.API.GetNativeSingletonT(); var scene = sceneManager.get_CurrentScene(); + var scene2 = sceneManager.get_CurrentScene(); + + if (scene == scene2) { + REFrameworkNET.API.LogInfo("Test success: Scene is the same"); + } else { + REFrameworkNET.API.LogError("Test failure: Scene is not the same"); + } //scene.set_Pause(true); var view = sceneManager.get_MainView(); From 0459bd858eb7a12df2aceffd179580cb64df21bf Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 5 Apr 2024 14:10:54 -0700 Subject: [PATCH 076/207] .NET: Additional Field properties and GetDataT --- csharp-api/REFrameworkNET/Field.hpp | 51 +++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp index 9265b8910..aab0c1348 100644 --- a/csharp-api/REFrameworkNET/Field.hpp +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -18,6 +18,12 @@ public ref class Field { return gcnew System::String(m_field->get_name()); } + property System::String^ Name { + System::String^ get() { + return GetName(); + } + } + TypeDefinition^ GetDeclaringType() { auto t = m_field->get_declaring_type(); @@ -28,6 +34,12 @@ public ref class Field { return gcnew TypeDefinition(t); } + property TypeDefinition^ DeclaringType { + TypeDefinition^ get() { + return GetDeclaringType(); + } + } + TypeDefinition^ GetType() { auto t = m_field->get_type(); @@ -38,18 +50,42 @@ public ref class Field { return gcnew TypeDefinition(t); } + property TypeDefinition^ Type { + TypeDefinition^ get() { + return GetType(); + } + } + uint32_t GetOffsetFromBase() { return m_field->get_offset_from_base(); } + property uint32_t OffsetFromBase { + uint32_t get() { + return GetOffsetFromBase(); + } + } + uint32_t GetOffsetFromFieldPtr() { return m_field->get_offset_from_fieldptr(); } + property uint32_t OffsetFromFieldPtr { + uint32_t get() { + return GetOffsetFromFieldPtr(); + } + } + uint32_t GetFlags() { return m_field->get_flags(); } + property uint32_t Flags { + uint32_t get() { + return GetFlags(); + } + } + bool IsStatic() { return m_field->is_static(); } @@ -72,6 +108,21 @@ public ref class Field { return m_field->get_data((void*)obj, isValueType); } + // For .NET + generic + T GetDataT(uintptr_t obj, bool isValueType) { + const auto raw_data = GetDataRaw(obj, isValueType); + if (raw_data == 0) { + return T(); + } + + if (this->Type->IsValueType()) { + return (T)System::Runtime::InteropServices::Marshal::PtrToStructure(System::IntPtr((void*)raw_data), T::typeid); + } + + throw gcnew System::NotImplementedException(); + } + private: const reframework::API::Field* m_field; }; From 5c919fa93162babbf3b9f234feab1444e59bf4ee Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 5 Apr 2024 14:11:31 -0700 Subject: [PATCH 077/207] .NET: Add TypeDefinition::HasAttribute --- csharp-api/REFrameworkNET/TypeDefinition.cpp | 20 ++++++++++++++++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 2 ++ 2 files changed, 22 insertions(+) diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 2f9c8bf73..24371c181 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -149,4 +149,24 @@ namespace REFrameworkNET { T TypeDefinition::As() { return NativeProxy::Create(gcnew NativeObject(this)); } + + bool TypeDefinition::HasAttribute(REFrameworkNET::ManagedObject^ runtimeAttribute, bool inherit) { + if (runtimeAttribute == nullptr) { + return false; + } + + auto runtimeType = this->GetRuntimeType(); + + if (runtimeType == nullptr) { + return false; + } + + auto attributes = (ManagedObject^)runtimeType->Call("GetCustomAttributes(System.Type, System.Boolean)", runtimeAttribute, inherit); + + if (attributes == nullptr) { + return false; + } + + return (int)attributes->Call("GetLength", gcnew System::Int32(0)) > 0; + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 0d763e3bc..61e2a3a2e 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -303,6 +303,8 @@ public } } + bool HasAttribute(REFrameworkNET::ManagedObject^ runtimeAttribute, bool inherit); + /*Void* GetInstance() { return m_type->get_instance(); From 165caeae9e9132fb254c585a8415585710c67a54 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 5 Apr 2024 14:12:09 -0700 Subject: [PATCH 078/207] .NET: Initial support for generating enums in ref assemblies --- csharp-api/AssemblyGenerator/EnumGenerator.cs | 96 +++++++++++++++++++ csharp-api/AssemblyGenerator/Generator.cs | 93 ++++++++---------- csharp-api/CMakeLists.txt | 1 + 3 files changed, 137 insertions(+), 53 deletions(-) create mode 100644 csharp-api/AssemblyGenerator/EnumGenerator.cs diff --git a/csharp-api/AssemblyGenerator/EnumGenerator.cs b/csharp-api/AssemblyGenerator/EnumGenerator.cs new file mode 100644 index 000000000..456a260ee --- /dev/null +++ b/csharp-api/AssemblyGenerator/EnumGenerator.cs @@ -0,0 +1,96 @@ +#nullable enable + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System.Reflection; +using System.Collections.Generic; +using Microsoft.CodeAnalysis.Emit; +using System.IO; +using System.Dynamic; +using System.Security.Cryptography; +using System.Linq; +using Microsoft.CodeAnalysis.Operations; +using REFrameworkNET; + +public class EnumGenerator { + private string enumName; + private REFrameworkNET.TypeDefinition t; + private EnumDeclarationSyntax? enumDeclaration; + + public EnumDeclarationSyntax? EnumDeclaration { + get { + return enumDeclaration; + } + } + + public EnumGenerator(string enumName, REFrameworkNET.TypeDefinition t) { + this.enumName = enumName; + this.t = t; + + enumDeclaration = Generate(); + } + + public void Update(EnumDeclarationSyntax? typeDeclaration) { + this.enumDeclaration = typeDeclaration; + } + static readonly REFrameworkNET.ManagedObject s_FlagsAttribute = REFrameworkNET.TDB.Get().FindType("System.FlagsAttribute").GetRuntimeType(); + + public EnumDeclarationSyntax? Generate() { + var ogEnumName = new string(enumName); + + // Pull out the last part of the class name (split '.' till last) + if (t.DeclaringType == null) { + enumName = enumName.Split('.').Last(); + } + + enumDeclaration = SyntaxFactory.EnumDeclaration(enumName) + .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + + if (t.HasAttribute(s_FlagsAttribute, true)) { + enumDeclaration = enumDeclaration.AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName("System.FlagsAttribute")))); + } + + foreach (REFrameworkNET.Field field in t.Fields) { + if (!field.IsStatic() || !field.IsLiteral()) { + continue; + } + + if (field.GetDeclaringType() != t) { + continue; + } + + var underlyingType = field.Type.GetUnderlyingType(); + + SyntaxToken literalToken; + + switch (underlyingType.GetValueTypeSize()) { + case 1: + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("byte"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case 2: + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("short"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case 4: + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("int"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case 8: + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("long"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + default: + throw new System.Exception("Unknown enum underlying type size"); + } + + var fieldDeclaration = SyntaxFactory.EnumMemberDeclaration(field.Name); + var valueExpr = SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, literalToken); + fieldDeclaration = fieldDeclaration.WithEqualsValue(SyntaxFactory.EqualsValueClause(valueExpr)); + enumDeclaration = enumDeclaration.AddMembers(fieldDeclaration); + } + + return enumDeclaration; + } +} \ No newline at end of file diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index b2f6f37a6..fae6eea35 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -277,70 +277,57 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin generatedTypes.Add(typeName); - // Generate starting from topmost parent first - if (t.ParentType != null) { - compilationUnit = MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType); - } + if (t.IsEnum()) { + var generator = new EnumGenerator(typeName, t); - /*var methods = t.Methods; - var fixedMethods = methods? - .Select(methodPair => { - var method = methodPair.Value; - var methodName = Il2CppDump.StripMethodName(method); - return (methodName, method); - }) - .GroupBy(pair => pair.methodName) - .Select(group => group.First()) // Selects the first method of each group - .ToDictionary(pair => pair.methodName, pair => pair.method);*/ - - // Make methods a SortedSet of method names - HashSet methods = []; - - foreach (var method in t.Methods) { - //methods.Add(method); - if (!methods.Select(m => m.Name).Contains(method.Name)) { - if (method.DeclaringType == t) { // really important - methods.Add(method); - } + if (generator.EnumDeclaration == null) { + return compilationUnit; } - } - var generator = new ClassGenerator( - typeName.Split('.').Last() == "file" ? typeName.Replace("file", "@file") : typeName, - t, - [.. methods] - ); + var generatedNamespace = ExtractNamespaceFromType(t); - if (generator.TypeDeclaration == null) { - return compilationUnit; - } - - var generatedNamespace = ExtractNamespaceFromType(t); + if (generatedNamespace != null) { + var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, generator.EnumDeclaration); + compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); + } else { + Console.WriteLine("Failed to create namespace for " + typeName); + } + } else { + // Generate starting from topmost parent first + if (t.ParentType != null) { + compilationUnit = MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType); + } - if (generatedNamespace != null) { - // Split the using types by their namespace - /*foreach(var ut in usingTypes) { - var ns = ExtractNamespaceFromTypeName(context, ut.Name ?? ""); + // Make methods a SortedSet of method names + HashSet methods = []; - if (ns != null) { - generatedNamespace = generatedNamespace.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ut.Name ?? ""))); + foreach (var method in t.Methods) { + //methods.Add(method); + if (!methods.Select(m => m.Name).Contains(method.Name)) { + if (method.DeclaringType == t) { // really important + methods.Add(method); + } } - }*/ + } - var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, generator.TypeDeclaration); + var generator = new ClassGenerator( + typeName.Split('.').Last() == "file" ? typeName.Replace("file", "@file") : typeName, + t, + [.. methods] + ); - /*compilationUnit = compilationUnit.AddUsings(usingTypes.Select( - type => { - var ret = SyntaxFactory.UsingDirective (SyntaxFactory.ParseName(type.Name ?? "")); - System.Console.WriteLine(ret.GetText()); + if (generator.TypeDeclaration == null) { + return compilationUnit; + } - return ret; - } - ).ToArray());*/ + var generatedNamespace = ExtractNamespaceFromType(t); - compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); - } else { - Console.WriteLine("Failed to create namespace for " + typeName); + if (generatedNamespace != null) { + var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, generator.TypeDeclaration); + compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); + } else { + Console.WriteLine("Failed to create namespace for " + typeName); + } } return compilationUnit; diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index eebfce4b1..b313892fe 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -239,6 +239,7 @@ set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCSharpComp # Target: AssemblyGenerator set(AssemblyGenerator_SOURCES "AssemblyGenerator/ClassGenerator.cs" + "AssemblyGenerator/EnumGenerator.cs" "AssemblyGenerator/Generator.cs" "AssemblyGenerator/SyntaxTreeBuilder.cs" cmake.toml From 3e2a77e205f77e4fd6b453ba0dbb0dce6ae13789 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 6 Apr 2024 00:19:58 -0700 Subject: [PATCH 079/207] .NET: Add nested enums to reference assemblies --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index d0e55e574..f94e4b61b 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -293,6 +293,16 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra nestedTypeName = nestedTypeName.Replace("file", "@file"); } + if (nestedT.IsEnum()) { + var nestedEnumGenerator = new EnumGenerator(nestedTypeName.Split('.').Last(), nestedT); + + if (nestedEnumGenerator.EnumDeclaration != null) { + this.Update(this.typeDeclaration.AddMembers(nestedEnumGenerator.EnumDeclaration)); + } + + continue; + } + HashSet nestedMethods = []; foreach (var method in nestedT.Methods) { From 6f8c8440a94fd830760b608812659d64bb3f0751 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 6 Apr 2024 09:31:03 -0700 Subject: [PATCH 080/207] .NET: Fix overridden methods not having the original names --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index f94e4b61b..f9e7cbbb0 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -189,14 +189,16 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra var methodName = new string(method.Name); var methodExtension = Il2CppDump.GetMethodExtension(method); - if (methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { - methodName += "_" + className.Replace('.', '_'); - } - var methodDeclaration = SyntaxFactory.MethodDeclaration(returnType, methodName ?? "UnknownMethod") .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; + if (methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { + //methodName += "_" + className.Replace('.', '_'); + + methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + } + if (wantsAbstractInstead) { methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); } @@ -293,9 +295,10 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra nestedTypeName = nestedTypeName.Replace("file", "@file"); } + // Enum if (nestedT.IsEnum()) { var nestedEnumGenerator = new EnumGenerator(nestedTypeName.Split('.').Last(), nestedT); - + if (nestedEnumGenerator.EnumDeclaration != null) { this.Update(this.typeDeclaration.AddMembers(nestedEnumGenerator.EnumDeclaration)); } @@ -303,6 +306,7 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra continue; } + // Class HashSet nestedMethods = []; foreach (var method in nestedT.Methods) { From 7ef160931a672bccbaa474ff592a33f6804c748a Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 6 Apr 2024 09:47:00 -0700 Subject: [PATCH 081/207] .NET: Refactor type syntax output into its own method --- .../AssemblyGenerator/ClassGenerator.cs | 166 +++++++++--------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index f9e7cbbb0..38cecc5b5 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -38,6 +38,91 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra typeDeclaration = Generate(); } + + private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetType, REFrameworkNET.TypeDefinition? containingType) { + TypeSyntax outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); + + string targetTypeName = targetType != null ? targetType.GetFullName() : ""; + + if (targetType == null || targetTypeName == "System.Void" || targetTypeName == "") { + return outSyntax; + } + + // Check for easily convertible types like System.Single, System.Int32, etc. + switch (targetTypeName) { + case "System.Single": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword)); + break; + case "System.Double": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)); + break; + case "System.Int32": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)); + break; + case "System.UInt32": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UIntKeyword)); + break; + case "System.Int64": + case "System.IntPtr": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword)); + break; + case "System.UInt64": + case "System.UIntPtr": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ULongKeyword)); + break; + case "System.Boolean": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)); + break; + case "System.String": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)); + break; + case "via.clr.ManagedObject": + case "System.Object": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + default: + if (targetType != null && targetTypeName != "") { + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(targetTypeName)) { + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + if (targetTypeName.Contains('<') || targetTypeName.Contains('[')) { + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + // Stuff in System should NOT be referencing via + // how is this even compiling for them? + if (containingType != null) { + var ogClassName = containingType.GetFullName(); + + if (ogClassName.StartsWith("System") && targetTypeName.StartsWith("via")) { + //REFrameworkNET.API.LogWarning("Method " + ogClassName + "." + method.Name + " is referencing via class " + methodReturnName); + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + if (ogClassName.StartsWith("System") && targetTypeName.StartsWith("app.")) { + //EFrameworkNET.API.LogWarning("Method " + ogClassName + "." + method.Name + " is referencing app class " + methodReturnName); + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + } + + + targetTypeName = "global::" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(targetTypeName); + + outSyntax = SyntaxFactory.ParseTypeName(targetTypeName); + break; + } + + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + } + + return outSyntax; + } private TypeDeclarationSyntax? Generate() { usingTypes = []; @@ -105,86 +190,7 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFra typeDeclaration = typeDeclaration .AddMembers(methods.Where(method => !invalidMethodNames.Contains(method.Name) && !method.Name.Contains('<')).Select(method => { - TypeSyntax? returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); - - var methodReturnT = method.ReturnType; - string methodReturnName = methodReturnT != null ? methodReturnT.GetFullName() : ""; - - if (methodReturnT != null && methodReturnName != "System.Void" && methodReturnName != "") { - // Check for easily convertible types like System.Single, System.Int32, etc. - switch (methodReturnName) { - case "System.Single": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.FloatKeyword)); - break; - case "System.Double": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.DoubleKeyword)); - break; - case "System.Int32": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.IntKeyword)); - break; - case "System.UInt32": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UIntKeyword)); - break; - case "System.Int64": - case "System.IntPtr": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword)); - break; - case "System.UInt64": - case "System.UIntPtr": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ULongKeyword)); - break; - case "System.Boolean": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.BoolKeyword)); - break; - case "System.String": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.StringKeyword)); - break; - case "via.clr.ManagedObject": - case "System.Object": - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - default: - if (methodReturnT != null && methodReturnName != "") { - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(methodReturnName)) { - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - if (methodReturnName.Contains('<') || methodReturnName.Contains('[')) { - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - /*if (methodReturnName.StartsWith("System.") || !methodReturnName.StartsWith("via.")) { - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - }*/ - - // Stuff in System should NOT be referencing via - // how is this even compiling for them? - if (ogClassName.StartsWith("System") && methodReturnName.StartsWith("via")) { - REFrameworkNET.API.LogWarning("Method " + ogClassName + "." + method.Name + " is referencing via class " + methodReturnName); - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - if (ogClassName.StartsWith("System") && methodReturnName.StartsWith("app.")) { - REFrameworkNET.API.LogWarning("Method " + ogClassName + "." + method.Name + " is referencing app class " + methodReturnName); - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - - methodReturnName = "global::" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(methodReturnName); - - returnType = SyntaxFactory.ParseTypeName(methodReturnName); - break; - } - - returnType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - } + TypeSyntax? returnType = MakeProperType(method.ReturnType, t); var methodName = new string(method.Name); var methodExtension = Il2CppDump.GetMethodExtension(method); From 6731c160abde8c36c7f81d5a851266c30d6da3e8 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 6 Apr 2024 09:55:07 -0700 Subject: [PATCH 082/207] .NET: Initial correct types for parameters --- .../AssemblyGenerator/ClassGenerator.cs | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 38cecc5b5..b14c874b7 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -124,31 +124,31 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy return outSyntax; } + static readonly SortedSet invalidMethodNames = [ + "Finalize", + "MemberwiseClone", + "ToString", + "Equals", + "GetHashCode", + "GetType", + ".ctor", + ".cctor", + "op_Implicit", + "op_Explicit", + /*"op_Addition", + "op_Subtraction", + "op_Multiply", + "op_Division", + "op_Modulus", + "op_BitwiseAnd", + "op_BitwiseOr", + "op_ExclusiveOr",*/ + + ]; + private TypeDeclarationSyntax? Generate() { usingTypes = []; - SortedSet invalidMethodNames = new SortedSet { - "Finalize", - "MemberwiseClone", - "ToString", - "Equals", - "GetHashCode", - "GetType", - ".ctor", - ".cctor", - "op_Implicit", - "op_Explicit", - /*"op_Addition", - "op_Subtraction", - "op_Multiply", - "op_Division", - "op_Modulus", - "op_BitwiseAnd", - "op_BitwiseOr", - "op_ExclusiveOr",*/ - - }; - var ogClassName = new string(className); // Pull out the last part of the class name (split '.' till last) @@ -212,7 +212,7 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy if (method.Parameters.Count > 0) { methodDeclaration = methodDeclaration.AddParameterListParameters(method.Parameters.Where(param => param != null && param.Type != null && param.Name != null).Select(param => { return SyntaxFactory.Parameter(SyntaxFactory.Identifier(param.Name ?? "UnknownParam")) - .WithType(SyntaxFactory.ParseTypeName(/*param.Type ??*/ "object")); + .WithType(SyntaxFactory.ParseTypeName(param.Type != null ? MakeProperType(param.Type, t).ToString() : "object")); }).ToArray()); } From 1caf171ec69890e9a6a4f15e515972fd3151b375 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 6 Apr 2024 10:17:08 -0700 Subject: [PATCH 083/207] .NET: Return arrays as ManagedObject (for now) --- csharp-api/REFrameworkNET/Method.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index a76d34eea..825f43514 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -173,6 +173,12 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, System::String^ me return true; } + if (returnType->GetVMObjType() == VMObjType::Array) { + // TODO? Implement array + result = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); + return true; + } + // TODO: other managed types result = gcnew REFrameworkNET::NativeObject((uintptr_t)tempResult->QWord, returnType); return true; From 171172f627e55641890e36bde0ceb0a7154e58f2 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 6 Apr 2024 10:17:29 -0700 Subject: [PATCH 084/207] .NET: Helper methods for Method --- csharp-api/REFrameworkNET/Method.cpp | 74 ++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 825f43514..e04b0a644 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -20,6 +20,80 @@ MethodHook^ Method::AddHook(bool ignore_jmp) { return MethodHook::Create(this, ignore_jmp); } +ManagedObject^ Method::GetRuntimeMethod() { + auto declaringType = this->GetDeclaringType(); + + if (declaringType == nullptr) { + return nullptr; + } + + // System.Type + auto runtimeType = declaringType->GetRuntimeType()/*->As()*/; + + if (runtimeType == nullptr) { + return nullptr; + } + + // Iterate over all methods in the runtime type + auto methods = (REFrameworkNET::ManagedObject^)runtimeType->Call("GetMethods(System.Reflection.BindingFlags)", System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Instance | System::Reflection::BindingFlags::Static); + + if (methods != nullptr) { + auto methodDefName = this->Name; + for each (REFrameworkNET::ManagedObject^ method in methods) { + // Get the type handle and compare it to this (raw pointer stored in the Method object) + // System.RuntimeMethodHandle automatically gets converted to a Method object + auto methodHandle = (Method^)method->Call("get_MethodHandle"); + + if (methodHandle == nullptr) { + continue; + } + + if (methodHandle->GetRaw() == this->GetRaw()) { + return method; + } + } + } + + return nullptr; +} + +bool Method::IsOverride() { + auto declaringType = this->GetDeclaringType(); + + if (declaringType == nullptr) { + return false; + } + + if (declaringType->ParentType == nullptr) { + return false; + } + + auto returnType = this->GetReturnType(); + auto parameters = this->GetParameters(); + + for (auto parentType = declaringType->ParentType; parentType != nullptr; parentType = parentType->ParentType) { + auto parentMethods = parentType->GetMethods(); + + for each (auto parentMethod in parentMethods) { + if (parentMethod->Name != this->Name) { + continue; + } + + if (parentMethod->GetReturnType() != returnType) { + continue; + } + + if (parentMethod->GetNumParams() != parameters->Count) { + continue; + } + + return true; // TODO: more checks + } + } + + return false; +} + REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { if (obj == nullptr && !this->IsStatic()) { System::String^ declaringName = this->GetDeclaringType() != nullptr ? this->GetDeclaringType()->GetFullName() : gcnew System::String("UnknownType"); From 716d864fd2fa3664817667c57fed185419a1a479 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 6 Apr 2024 10:19:06 -0700 Subject: [PATCH 085/207] .NET: Add support for enum arguments passed to invoke/call --- csharp-api/REFrameworkNET/Method.cpp | 31 +++++++++++++++++----------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index e04b0a644..6edaea2bf 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -114,9 +114,16 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, arrayptr(); - const auto t = args[i]->GetType(); + auto t = args[i]->GetType(); + System::Object^ arg = args[i]; - if (t->IsSubclassOf(REFrameworkNET::IObject::typeid)) { + if (t->IsEnum) { + auto underlyingType = System::Enum::GetUnderlyingType(t); + arg = System::Convert::ChangeType(args[i], underlyingType); + t = underlyingType; + } + + if (REFrameworkNET::IObject::typeid->IsAssignableFrom(t)) { auto iobj = safe_cast(args[i]); args2[i] = iobj->Ptr(); } else if (t == REFrameworkNET::TypeDefinition::typeid) { @@ -139,40 +146,40 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, arrayFullName + ")"); } } catch (System::Exception^ e) { System::Console::WriteLine("Error converting argument " + i + ": " + e->Message); From e486014dff1cd06dbe6dc5619b79964b3bfd461b Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 6 Apr 2024 10:22:09 -0700 Subject: [PATCH 086/207] .NET: Forgot to commit Method.hpp --- csharp-api/REFrameworkNET/Method.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 8dea53be5..bfb7f8b5c 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -162,6 +162,9 @@ public ref class Method : public System::IEquatable MethodHook^ AddHook(bool ignore_jmp); + ManagedObject^ GetRuntimeMethod(); + bool IsOverride(); + public: virtual bool Equals(System::Object^ other) override { if (System::Object::ReferenceEquals(this, other)) { From 004424edd6ef2e2652076657e6e44420899f9c12 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 7 Apr 2024 10:18:12 -0700 Subject: [PATCH 087/207] .NET: More accurate Method::IsOverride --- csharp-api/REFrameworkNET/Method.cpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 6edaea2bf..68b8cf6a8 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -69,7 +69,7 @@ bool Method::IsOverride() { } auto returnType = this->GetReturnType(); - auto parameters = this->GetParameters(); + auto parameters = this->GetParameters()->ToArray(); for (auto parentType = declaringType->ParentType; parentType != nullptr; parentType = parentType->ParentType) { auto parentMethods = parentType->GetMethods(); @@ -83,11 +83,32 @@ bool Method::IsOverride() { continue; } - if (parentMethod->GetNumParams() != parameters->Count) { + if (parentMethod->GetNumParams() != parameters->Length) { continue; } - return true; // TODO: more checks + if (parameters->Length == 0) { + return true; + } + + auto parentParams = parentMethod->GetParameters()->ToArray(); + bool fullParamsMatch = true; + + for (int i = 0; i < parameters->Length; ++i) { + auto param = parameters[i]; + auto parentParam = parentParams[i]; + + if (param->Type != parentParam->Type) { + fullParamsMatch = false; + break; + } + } + + if (!fullParamsMatch) { + continue; + } + + return true; } } From bd102a6ad75bda599a39b6bfc9c1f3633decc4a0 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 7 Apr 2024 10:18:51 -0700 Subject: [PATCH 088/207] .NET: Add TypeDefinition::GetGenericArguments --- csharp-api/REFrameworkNET/TypeDefinition.cpp | 23 ++++++++++++++++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 7 ++++++ 2 files changed, 30 insertions(+) diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 24371c181..4aa20e92f 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -169,4 +169,27 @@ namespace REFrameworkNET { return (int)attributes->Call("GetLength", gcnew System::Int32(0)) > 0; } + + array^ TypeDefinition::GetGenericArguments() { + auto runtimeType = this->GetRuntimeType(); + + if (runtimeType == nullptr) { + return nullptr; + } + + auto arguments = (ManagedObject^)runtimeType->Call("GetGenericArguments"); + + if (arguments == nullptr) { + return nullptr; + } + + auto result = gcnew array((int)arguments->Call("get_Length", gcnew System::Int32(0))); + + for (int i = 0; i < result->Length; i++) { + auto runtimeType = (ManagedObject^)arguments->Call("get_Item", gcnew System::Int32(i)); + result[i] = (TypeDefinition^)runtimeType->Call("get_TypeHandle"); + } + + return result; + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 61e2a3a2e..05ffaa157 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -304,6 +304,13 @@ public } bool HasAttribute(REFrameworkNET::ManagedObject^ runtimeAttribute, bool inherit); + array^ GetGenericArguments(); + + property array^ GenericArguments { + array^ get() { + return GetGenericArguments(); + } + } /*Void* GetInstance() { From e007dda50cdb634afd2d62b7bdcabc381264ebfc Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 8 Apr 2024 03:24:27 -0700 Subject: [PATCH 089/207] .NET: Fix GetTypeT not working with renamed System types --- csharp-api/REFrameworkNET/TDB.cpp | 3 ++- csharp-api/REFrameworkNET/TDB.hpp | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/csharp-api/REFrameworkNET/TDB.cpp b/csharp-api/REFrameworkNET/TDB.cpp index 5bcdfaf7c..9c0bad871 100644 --- a/csharp-api/REFrameworkNET/TDB.cpp +++ b/csharp-api/REFrameworkNET/TDB.cpp @@ -9,7 +9,8 @@ namespace REFrameworkNET { generic reframework::API::TypeDefinition* TDB::GetTypeDefinitionPtr() { - auto t = REFrameworkNET::API::GetTDB()->GetType(T::typeid->FullName->Replace("+", ".")); + auto fullName = T::typeid->FullName->Replace("+", ".")->Replace("_System.", "System."); + auto t = REFrameworkNET::API::GetTDB()->GetType(fullName); if (t == nullptr) { return nullptr; diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp index ae0879cc4..975663658 100644 --- a/csharp-api/REFrameworkNET/TDB.hpp +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -23,15 +23,16 @@ public ref class TDB { ref class TypeCacher { public: static REFrameworkNET::TypeDefinition^ GetCachedType() { - if (s_cachedType == nullptr) { - return nullptr; + if (s_cachedManagedType != nullptr) { + return s_cachedManagedType; } - return gcnew REFrameworkNET::TypeDefinition(s_cachedType); + return s_cachedManagedType; } private: static reframework::API::TypeDefinition* s_cachedType = TDB::GetTypeDefinitionPtr(); + static REFrameworkNET::TypeDefinition^ s_cachedManagedType = s_cachedType != nullptr ? gcnew REFrameworkNET::TypeDefinition(s_cachedType) : nullptr; }; public: From 179b153b466b112541fd3d82464c7627683b6211 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 8 Apr 2024 04:39:26 -0700 Subject: [PATCH 090/207] .NET: API.hpp cleanup --- csharp-api/REFrameworkNET/API.cpp | 146 ++++++++++++++++++++++++++++++ csharp-api/REFrameworkNET/API.hpp | 122 ++----------------------- 2 files changed, 156 insertions(+), 112 deletions(-) diff --git a/csharp-api/REFrameworkNET/API.cpp b/csharp-api/REFrameworkNET/API.cpp index 64fe3dc08..c21ac097c 100644 --- a/csharp-api/REFrameworkNET/API.cpp +++ b/csharp-api/REFrameworkNET/API.cpp @@ -31,4 +31,150 @@ void REFrameworkNET::API::Init_Internal(const REFrameworkPluginInitializeParam* REFrameworkNET::API::~API() { Console::WriteLine("REFrameworkNET.API Destructor called."); +} + + +reframework::API* REFrameworkNET::API::GetNativeImplementation() { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + return s_api; +} + +REFrameworkNET::TDB^ REFrameworkNET::API::GetTDB() { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + return gcnew REFrameworkNET::TDB(s_api->tdb()); +} + +System::Collections::Generic::List^ REFrameworkNET::API::GetManagedSingletons() { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + auto singletons = s_api->get_managed_singletons(); + auto result = gcnew System::Collections::Generic::List(); + + for (auto& singleton : singletons) { + if (singleton.instance == nullptr) { + continue; + } + + result->Add(gcnew REFrameworkNET::ManagedSingleton( + gcnew REFrameworkNET::ManagedObject(singleton.instance), + gcnew REFrameworkNET::TypeDefinition(singleton.t), + gcnew REFrameworkNET::TypeInfo(singleton.type_info) + )); + } + + return result; +} + +System::Collections::Generic::List^ REFrameworkNET::API::GetNativeSingletons() { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + auto singletons = s_api->get_native_singletons(); + auto result = gcnew System::Collections::Generic::List(); + + for (auto& singleton : singletons) { + if (singleton.instance == nullptr) { + continue; + } + + // Not supported for now + if (singleton.t == nullptr) { + continue; + } + + auto nativeObject = gcnew REFrameworkNET::NativeObject(singleton.instance, gcnew REFrameworkNET::TypeDefinition(singleton.t)); + + result->Add(gcnew REFrameworkNET::NativeSingleton( + gcnew REFrameworkNET::NativeObject(singleton.instance, gcnew REFrameworkNET::TypeDefinition(singleton.t)), + singleton.type_info != nullptr ? gcnew REFrameworkNET::TypeInfo(singleton.type_info) : nullptr + )); + } + + return result; +} + +REFrameworkNET::ManagedObject^ REFrameworkNET::API::GetManagedSingleton(System::String^ name) { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + auto result = s_api->get_managed_singleton(msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + return gcnew REFrameworkNET::ManagedObject(result); +} + + +REFrameworkNET::NativeObject^ REFrameworkNET::API::GetNativeSingleton(System::String^ name) { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + auto result = s_api->get_native_singleton(msclr::interop::marshal_as(name)); + + if (result == nullptr) { + return nullptr; + } + + auto t = REFrameworkNET::API::GetTDB()->GetType(name); + + if (t == nullptr) { + return nullptr; + } + + return gcnew NativeObject(result, t); +} + +void REFrameworkNET::API::LogError(System::String^ message) { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + if (LogLevel <= LogLevel::Error) { + s_api->log_error(msclr::interop::marshal_as(message).c_str()); + + if (LogToConsole) { + System::Console::WriteLine(message); + } + } +} + +void REFrameworkNET::API::LogWarning(System::String^ message) { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + if (LogLevel <= LogLevel::Warning) { + s_api->log_warn(msclr::interop::marshal_as(message).c_str()); + + if (LogToConsole) { + System::Console::WriteLine(message); + } + } +} + +void REFrameworkNET::API::LogInfo(System::String^ message) { + if (s_api == nullptr) { + throw gcnew APINotInitializedException(); + } + + if (LogLevel <= LogLevel::Info) { + s_api->log_info(msclr::interop::marshal_as(message).c_str()); + + if (LogToConsole) { + System::Console::WriteLine(message); + } + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index a8157f089..5df41a253 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -10,6 +10,7 @@ #include "ManagedObject.hpp" #include "TDB.hpp" #include "ManagedSingleton.hpp" +#include "NativeSingleton.hpp" #include "NativeObject.hpp" #include "Callbacks.hpp" @@ -39,71 +40,13 @@ public ref class API APINotInitializedException() : System::InvalidOperationException("API is not initialized.") {} }; - static REFrameworkNET::TDB^ GetTDB() { - if (s_api == nullptr) { - throw gcnew APINotInitializedException(); - } - - return gcnew REFrameworkNET::TDB(s_api->tdb()); - } - - static REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name) { - if (s_api == nullptr) { - throw gcnew APINotInitializedException(); - } - - auto result = s_api->get_managed_singleton(msclr::interop::marshal_as(name)); - - if (result == nullptr) { - return nullptr; - } - - return gcnew REFrameworkNET::ManagedObject(result); - } - - static System::Collections::Generic::List^ GetManagedSingletons() { - if (s_api == nullptr) { - throw gcnew APINotInitializedException(); - } - - auto singletons = s_api->get_managed_singletons(); - - auto result = gcnew System::Collections::Generic::List(); - - for (auto& singleton : singletons) { - if (singleton.instance == nullptr) { - continue; - } - - result->Add(gcnew REFrameworkNET::ManagedSingleton( - gcnew REFrameworkNET::ManagedObject(singleton.instance), - gcnew REFrameworkNET::TypeDefinition(singleton.t), - gcnew REFrameworkNET::TypeInfo(singleton.type_info) - )); - } - - return result; - } - - static NativeObject^ GetNativeSingleton(System::String^ name) { - if (s_api == nullptr) { - throw gcnew APINotInitializedException(); - } - - auto result = s_api->get_native_singleton(msclr::interop::marshal_as(name)); - - if (result == nullptr) { - return nullptr; - } - - auto t = REFrameworkNET::API::GetTDB()->GetType(name); + static reframework::API* GetNativeImplementation(); + static REFrameworkNET::TDB^ GetTDB(); - if (t == nullptr) { - return nullptr; - } - - return gcnew NativeObject(result, t); - } + static System::Collections::Generic::List^ GetManagedSingletons(); + static System::Collections::Generic::List^ GetNativeSingletons(); + static REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name); + static NativeObject^ GetNativeSingleton(System::String^ name); generic where T : ref class static T GetNativeSingletonT() { @@ -128,58 +71,13 @@ public ref class API return mo->As(); } - static reframework::API* GetNativeImplementation() { - if (s_api == nullptr) { - throw gcnew APINotInitializedException(); - } - - return s_api; - } - static LogLevel LogLevel{LogLevel::Info}; static bool LogToConsole{true}; - static void LogError(System::String^ message) { - if (s_api == nullptr) { - throw gcnew APINotInitializedException(); - } - - if (LogLevel <= LogLevel::Error) { - s_api->log_error(msclr::interop::marshal_as(message).c_str()); - - if (LogToConsole) { - System::Console::WriteLine(message); - } - } - } + static void LogError(System::String^ message); + static void LogWarning(System::String^ message); - static void LogWarning(System::String^ message) { - if (s_api == nullptr) { - throw gcnew APINotInitializedException(); - } - - if (LogLevel <= LogLevel::Warning) { - s_api->log_warn(msclr::interop::marshal_as(message).c_str()); - - if (LogToConsole) { - System::Console::WriteLine(message); - } - } - } - - static void LogInfo(System::String^ message) { - if (s_api == nullptr) { - throw gcnew APINotInitializedException(); - } - - if (LogLevel <= LogLevel::Info) { - s_api->log_info(msclr::interop::marshal_as(message).c_str()); - - if (LogToConsole) { - System::Console::WriteLine(message); - } - } - } + static void LogInfo(System::String^ message); protected: void Init_Internal(const REFrameworkPluginInitializeParam* param); From 92c22d0aa4edabebc2bbce70e3f13e06d5cd8a8e Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 8 Apr 2024 04:39:50 -0700 Subject: [PATCH 091/207] .NET: Forgot to include NativeSingleton.hpp --- csharp-api/CMakeLists.txt | 1 + csharp-api/REFrameworkNET/NativeSingleton.hpp | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 csharp-api/REFrameworkNET/NativeSingleton.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index b313892fe..65836746a 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -157,6 +157,7 @@ set(csharp-api_SOURCES "REFrameworkNET/MethodHook.hpp" "REFrameworkNET/MethodParameter.hpp" "REFrameworkNET/NativeObject.hpp" + "REFrameworkNET/NativeSingleton.hpp" "REFrameworkNET/ObjectEnumerator.hpp" "REFrameworkNET/Plugin.hpp" "REFrameworkNET/PluginLoadContext.hpp" diff --git a/csharp-api/REFrameworkNET/NativeSingleton.hpp b/csharp-api/REFrameworkNET/NativeSingleton.hpp new file mode 100644 index 000000000..14ae1248d --- /dev/null +++ b/csharp-api/REFrameworkNET/NativeSingleton.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#pragma managed + +namespace REFrameworkNET { +ref class NativeObject; +ref class TypeDefinition; +ref class TypeInfo; + +public ref struct NativeSingleton { + NativeSingleton(NativeObject^ instance, TypeInfo^ typeInfo) + { + Instance = instance; + TypeInfo = typeInfo; + } + + property NativeObject^ Instance; + property TypeInfo^ TypeInfo; +}; +} \ No newline at end of file From bc1dcfce5cf1a99a303de253211272e21874b19c Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 8 Apr 2024 04:45:38 -0700 Subject: [PATCH 092/207] .NET: Add file watchers for auto-reloading of .cs files --- csharp-api/REFrameworkNET/PluginManager.cpp | 115 +++++++++++++++++++- csharp-api/REFrameworkNET/PluginManager.hpp | 18 +++ 2 files changed, 127 insertions(+), 6 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 159868267..91e1293e2 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -185,11 +185,11 @@ namespace REFrameworkNET { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); System::Console::WriteLine("Created API instance"); } - - auto deps = LoadDependencies(); // Pre-loads DLLs in the dependencies folder before loading the plugins + + s_dependencies = LoadDependencies(); // Pre-loads DLLs in the dependencies folder before loading the plugins try { - GenerateReferenceAssemblies(deps); + GenerateReferenceAssemblies(s_dependencies); } catch(System::Exception^ e) { REFrameworkNET::API::LogError("Could not generate reference assemblies: " + e->Message); @@ -207,7 +207,7 @@ namespace REFrameworkNET { // Try-catch because the user might not have the compiler // dependencies in the plugins directory try { - LoadPlugins_FromSourceCode(param_raw, deps); + LoadPlugins_FromSourceCode(param_raw, s_dependencies); } catch (System::Exception^ e) { REFrameworkNET::API::LogError("Could not load plugins from source code: " + e->Message); @@ -266,7 +266,7 @@ namespace REFrameworkNET { } // Unload dynamic assemblies (testing) - UnloadDynamicAssemblies(); + //UnloadDynamicAssemblies(); return true; } catch(System::Exception^ e) { @@ -287,11 +287,108 @@ namespace REFrameworkNET { return false; } + void PluginManager::OnSourceScriptsChanged(System::Object^ sender, System::IO::FileSystemEventArgs^ e) { + System::Console::WriteLine("Source scripts changed"); + System::Console::WriteLine("File " + e->FullPath + " " + e->ChangeType.ToString()); + + if (e->ChangeType == System::IO::WatcherChangeTypes::Created) { + // Add a new symlink watcher if it's a symlink + if ((System::IO::File::GetAttributes(e->FullPath) & System::IO::FileAttributes::ReparsePoint) == System::IO::FileAttributes::ReparsePoint) { + auto link = (gcnew System::IO::FileInfo(e->FullPath))->LinkTarget; + + if (link != nullptr) { + System::Console::WriteLine("Found symlink " + link); + SetupIndividualFileWatcher(link); + } + } + } + + s_wants_reload = true; + } + + void PluginManager::SetupIndividualFileWatcher(System::String^ real_path) { + auto directory = System::IO::Path::GetDirectoryName(real_path); + auto filename = System::IO::Path::GetFileName(real_path); + + // Make sure we don't already have a watcher for this symlink + for each (System::IO::FileSystemWatcher^ watcher in PluginManager::s_symlink_watchers) { + if (watcher->Path == directory && watcher->Filter == filename) { + System::Console::WriteLine("Already watching file " + real_path); + return; + } + } + + auto watcher = gcnew System::IO::FileSystemWatcher(directory); + watcher->Filter = filename; + watcher->Changed += gcnew System::IO::FileSystemEventHandler(OnSourceScriptsChanged); + watcher->Created += gcnew System::IO::FileSystemEventHandler(OnSourceScriptsChanged); + watcher->Deleted += gcnew System::IO::FileSystemEventHandler(OnSourceScriptsChanged); + watcher->EnableRaisingEvents = true; + + PluginManager::s_symlink_watchers->Add(watcher); + } + + void PluginManager::SetupSymlinkWatchers(System::String^ p) { + auto files = System::IO::Directory::GetFiles(p, "*.cs"); + + for each (System::String^ file in files) { + // Check if symlink + if ((System::IO::File::GetAttributes(file) & System::IO::FileAttributes::ReparsePoint) == System::IO::FileAttributes::None) { + continue; + } + + // Resolve symlink to real path + auto real_path = (gcnew System::IO::FileInfo(file))->LinkTarget; + + if (real_path == nullptr) { + continue; // only want to watch symlinks. + } + + System::Console::WriteLine("Found symlink " + real_path); + SetupIndividualFileWatcher(real_path); + } + } + + void PluginManager::SetupFileWatcher() { + if (PluginManager::s_source_scripts_watcher != nullptr) { + return; + } + + System::Console::WriteLine("Setting up source scripts watcher..."); + const auto plugins_path = std::filesystem::current_path() / "reframework" / "plugins"; + const auto cs_files_path = plugins_path / "source"; + + auto p = gcnew System::String(cs_files_path.wstring().c_str()); + PluginManager::s_source_scripts_watcher = gcnew System::IO::FileSystemWatcher(p); + PluginManager::s_source_scripts_watcher->Filter = "*.cs"; + PluginManager::s_source_scripts_watcher->IncludeSubdirectories = true; + PluginManager::s_source_scripts_watcher->Changed += gcnew System::IO::FileSystemEventHandler(OnSourceScriptsChanged); + PluginManager::s_source_scripts_watcher->Created += gcnew System::IO::FileSystemEventHandler(OnSourceScriptsChanged); + PluginManager::s_source_scripts_watcher->Deleted += gcnew System::IO::FileSystemEventHandler(OnSourceScriptsChanged); + PluginManager::s_source_scripts_watcher->EnableRaisingEvents = true; + + // Create individual watchers for each file in the directory as well. + // This is because the main watcher doesn't pick up on changes to symlinks + SetupSymlinkWatchers(p); + } + + void PluginManager::BeginRendering() { + if (s_wants_reload) { + s_wants_reload = false; + + PluginManager::UnloadDynamicAssemblies(); + PluginManager::LoadPlugins_FromSourceCode(0, s_dependencies); + } + + SetupFileWatcher(); + } + bool PluginManager::LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps) try { if (PluginManager::s_api_instance == nullptr) { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); } + System::Console::WriteLine("Test"); REFrameworkNET::API::LogInfo("Attempting to load plugins from source code..."); @@ -299,6 +396,12 @@ namespace REFrameworkNET { const auto cs_files_path = plugins_path / "source"; std::filesystem::create_directories(cs_files_path); + // Set up a filesystem watcher for the source folder + if (PluginManager::s_source_scripts_watcher == nullptr) { + auto ptr = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(s_begin_rendering_delegate).ToPointer(); + REFrameworkNET::API::GetNativeImplementation()->param()->functions->on_pre_application_entry("BeginRendering", (::REFOnPreApplicationEntryCb)ptr); + } + System::String^ cs_files_dir = gcnew System::String(cs_files_path.wstring().c_str()); bool ever_found = false; @@ -353,7 +456,7 @@ namespace REFrameworkNET { // Look for the Main method in the compiled assembly for each (Type^ type in assem->GetTypes()) { - array^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); + array^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic); for each (System::Reflection::MethodInfo^ method in methods) { array^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginEntryPoint::typeid, true); diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 08aac5a46..9fcde81a7 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -40,6 +40,24 @@ private ref class PluginManager static System::Collections::Generic::List^ s_loaded_assemblies{gcnew System::Collections::Generic::List()}; static System::Collections::Generic::List^ s_dynamic_assemblies{gcnew System::Collections::Generic::List()}; + static System::Collections::Generic::List^ s_dependencies{gcnew System::Collections::Generic::List()}; + static PluginLoadContext^ s_default_context{nullptr}; + + // The main watcher + static System::IO::FileSystemWatcher^ s_source_scripts_watcher{nullptr}; + + // We also need a watcher list for symlinks that are in the directory + static System::Collections::Generic::List^ s_symlink_watchers{gcnew System::Collections::Generic::List()}; + static bool s_wants_reload{false}; + + static void SetupFileWatcher(); + static void SetupIndividualFileWatcher(System::String^ p); // individual symlinks + static void SetupSymlinkWatchers(System::String^ p); // all symlinks in a directory + + static void OnSourceScriptsChanged(System::Object^ sender, System::IO::FileSystemEventArgs^ e); + static void BeginRendering(); + delegate void BeginRenderingDelegate(); + static BeginRenderingDelegate^ s_begin_rendering_delegate{gcnew BeginRenderingDelegate(&BeginRendering)}; }; } \ No newline at end of file From b20591655e78e1b697788fe687e4864786e92d8f Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 8 Apr 2024 07:11:41 -0700 Subject: [PATCH 093/207] .NET: Make nuget packages easier to add (imgui prep) --- csharp-api/CMakeLists.txt | 74 +++++++++++++++++++++++++------------ csharp-api/cmake.toml | 69 +++++++++++++++++++++++++--------- csharp-api/make_symlinks.py | 4 +- 3 files changed, 104 insertions(+), 43 deletions(-) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 65836746a..340faca5a 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -95,18 +95,38 @@ else() message(STATUS "One or more specified DLLs do not exist.") endif() -# Target: REFCSharpCompiler -set(REFCSharpCompiler_SOURCES +# Define a list of NuGet packages and their versions +# the last part, the package framework will only be used for copying the files +set(REFRAMEWORK_NUGET_PACKAGES + "Microsoft.CodeAnalysis.Common:4.9.2:net7.0" + "Microsoft.CodeAnalysis.CSharp:4.9.2:net7.0" + "ImGui.NET:1.90.1.1:net6.0" +) + +# Generate the VS_PACKAGE_REFERENCES property value +set(REFRAMEWORK_PACKAGE_REFERENCES "") +foreach(PACKAGE ${REFRAMEWORK_NUGET_PACKAGES}) + # Extract package name and version from the package string + string(REPLACE ":" ";" PACKAGE_PARTS ${PACKAGE}) + list(GET PACKAGE_PARTS 0 PACKAGE_NAME) + list(GET PACKAGE_PARTS 1 PACKAGE_VERSION) + + # Append the package reference to the REFRAMEWORK_PACKAGE_REFERENCES variable + set(REFRAMEWORK_PACKAGE_REFERENCES "${REFRAMEWORK_PACKAGE_REFERENCES}${PACKAGE_NAME}_${PACKAGE_VERSION};") +endforeach() + +# Target: REFCoreDeps +set(REFCoreDeps_SOURCES "Compiler/Compiler.cs" cmake.toml ) -add_library(REFCSharpCompiler SHARED) +add_library(REFCoreDeps SHARED) -target_sources(REFCSharpCompiler PRIVATE ${REFCSharpCompiler_SOURCES}) -source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${REFCSharpCompiler_SOURCES}) +target_sources(REFCoreDeps PRIVATE ${REFCoreDeps_SOURCES}) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${REFCoreDeps_SOURCES}) -set_target_properties(REFCSharpCompiler PROPERTIES +set_target_properties(REFCoreDeps PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/bin/" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO @@ -121,8 +141,8 @@ set_target_properties(REFCSharpCompiler PROPERTIES ClassLibrary ) -set(CMKR_TARGET REFCSharpCompiler) -set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.CodeAnalysis_4.9.2") +set(CMKR_TARGET REFCoreDeps) +set_target_properties(REFCoreDeps PROPERTIES VS_PACKAGE_REFERENCES "${REFRAMEWORK_PACKAGE_REFERENCES}") # Target: csharp-api set(csharp-api_SOURCES @@ -195,7 +215,7 @@ target_include_directories(csharp-api PUBLIC ) target_link_libraries(csharp-api PUBLIC - REFCSharpCompiler + REFCoreDeps ) set_target_properties(csharp-api PROPERTIES @@ -216,26 +236,32 @@ set_target_properties(csharp-api PROPERTIES ) set(CMKR_TARGET csharp-api) -# Copy nuget packages to the output directory -add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin// - COMMENT "Copying nuget packages" -) -add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin// - COMMENT "Copying nuget packages" -) +foreach(PACKAGE ${REFRAMEWORK_NUGET_PACKAGES}) + # Extract package name and version from the package string + string(REPLACE ":" ";" PACKAGE_PARTS ${PACKAGE}) + list(GET PACKAGE_PARTS 0 PACKAGE_NAME) + list(GET PACKAGE_PARTS 1 PACKAGE_VERSION) + list(GET PACKAGE_PARTS 2 PACKAGE_FRAMEWORK) + + # Copy the package files to the output directory + add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${NUGET_PACKAGES_DIR}/${PACKAGE_NAME}/${PACKAGE_VERSION}/lib/${PACKAGE_FRAMEWORK} + ${CMAKE_BINARY_DIR}/bin/ + COMMENT "Copying ${PACKAGE_NAME} NuGet package" + ) +endforeach() + set(REFRAMEWORK_DOT_NET_ASSEMBLY_DIR "${CMAKE_BINARY_DIR}/bin") set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/REFramework.NET.dll") set_target_properties(csharp-api PROPERTIES -VS_DOTNET_REFERENCE_REFCSharpCompiler -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFCSharpCompiler.dll" +VS_DOTNET_REFERENCE_REFCoreDeps +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFCoreDeps.dll" ) -set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCSharpCompiler") +set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCoreDeps") # Target: AssemblyGenerator set(AssemblyGenerator_SOURCES @@ -253,7 +279,7 @@ source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${AssemblyGenerator_SOURCES} target_link_libraries(AssemblyGenerator PUBLIC csharp-api - REFCSharpCompiler + REFCoreDeps ) set_target_properties(AssemblyGenerator PROPERTIES diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 7e558e249..e0c54f740 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -67,6 +67,26 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) else() message(STATUS "One or more specified DLLs do not exist.") endif() + +# Define a list of NuGet packages and their versions +# the last part, the package framework will only be used for copying the files +set(REFRAMEWORK_NUGET_PACKAGES + "Microsoft.CodeAnalysis.Common:4.9.2:net7.0" + "Microsoft.CodeAnalysis.CSharp:4.9.2:net7.0" + "ImGui.NET:1.90.1.1:net6.0" +) + +# Generate the VS_PACKAGE_REFERENCES property value +set(REFRAMEWORK_PACKAGE_REFERENCES "") +foreach(PACKAGE ${REFRAMEWORK_NUGET_PACKAGES}) + # Extract package name and version from the package string + string(REPLACE ":" ";" PACKAGE_PARTS ${PACKAGE}) + list(GET PACKAGE_PARTS 0 PACKAGE_NAME) + list(GET PACKAGE_PARTS 1 PACKAGE_VERSION) + + # Append the package reference to the REFRAMEWORK_PACKAGE_REFERENCES variable + set(REFRAMEWORK_PACKAGE_REFERENCES "${REFRAMEWORK_PACKAGE_REFERENCES}${PACKAGE_NAME}_${PACKAGE_VERSION};") +endforeach() """ [conditions] @@ -83,13 +103,18 @@ DOTNET_SDK = "Microsoft.NET.Sdk" DOTNET_TARGET_FRAMEWORK = "net8.0-windows" VS_CONFIGURATION_TYPE = "ClassLibrary" -[target.REFCSharpCompiler] +# REFCoreDeps is a shared target that is used by the CSharpAPI and AssemblyGenerator targets +# Mainly because adding nuget packages to C++/CLI causes random problems +# Like compiling randomly failing, so we should keep all external nuget packages +# in an actual C# project +# It also contains other stuff like the C# compiler wrapper. +[target.REFCoreDeps] type = "CSharpSharedTarget" sources = ["Compiler/**.cs"] # Not using .properties here because it overrides the template properties for whatever reason cmake-after = """ -set_target_properties(REFCSharpCompiler PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.CodeAnalysis_4.9.2") +set_target_properties(REFCoreDeps PROPERTIES VS_PACKAGE_REFERENCES "${REFRAMEWORK_PACKAGE_REFERENCES}") """ [target.csharp-api] @@ -100,29 +125,35 @@ headers = ["REFrameworkNET/**.hpp", "REFrameworkNET/**.h"] compile-features = ["cxx_std_20"] compile-options = ["/EHa", "/MD", "/doc"] link-libraries = [ - "REFCSharpCompiler" + "REFCoreDeps" ] cmake-after = """ -# Copy nuget packages to the output directory -add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.csharp/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin// - COMMENT "Copying nuget packages" -) -add_custom_command( - TARGET csharp-api POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NUGET_PACKAGES_DIR}/microsoft.codeanalysis.common/4.9.2/lib/net7.0 ${CMAKE_BINARY_DIR}/bin// - COMMENT "Copying nuget packages" -) +foreach(PACKAGE ${REFRAMEWORK_NUGET_PACKAGES}) + # Extract package name and version from the package string + string(REPLACE ":" ";" PACKAGE_PARTS ${PACKAGE}) + list(GET PACKAGE_PARTS 0 PACKAGE_NAME) + list(GET PACKAGE_PARTS 1 PACKAGE_VERSION) + list(GET PACKAGE_PARTS 2 PACKAGE_FRAMEWORK) + + # Copy the package files to the output directory + add_custom_command( + TARGET csharp-api POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${NUGET_PACKAGES_DIR}/${PACKAGE_NAME}/${PACKAGE_VERSION}/lib/${PACKAGE_FRAMEWORK} + ${CMAKE_BINARY_DIR}/bin/ + COMMENT "Copying ${PACKAGE_NAME} NuGet package" + ) +endforeach() + set(REFRAMEWORK_DOT_NET_ASSEMBLY_DIR "${CMAKE_BINARY_DIR}/bin") set(REFRAMEWORK_DOT_NET_ASSEMBLY_PATH "${CMAKE_BINARY_DIR}/bin/REFramework.NET.dll") set_target_properties(csharp-api PROPERTIES -VS_DOTNET_REFERENCE_REFCSharpCompiler -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFCSharpCompiler.dll" +VS_DOTNET_REFERENCE_REFCoreDeps +"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFCoreDeps.dll" ) -set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCSharpCompiler") +set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCoreDeps") """ [target.csharp-api.properties] @@ -141,13 +172,15 @@ type = "CSharpSharedTarget" sources = ["AssemblyGenerator/**.cs"] link-libraries = [ "csharp-api", - "REFCSharpCompiler" + "REFCoreDeps" ] cmake-after = """ set_target_properties(AssemblyGenerator PROPERTIES VS_DOTNET_REFERENCE_REFramework.NET "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" ) + + """ [target.CSharpAPITest] diff --git a/csharp-api/make_symlinks.py b/csharp-api/make_symlinks.py index ecd70e660..21ae490e7 100644 --- a/csharp-api/make_symlinks.py +++ b/csharp-api/make_symlinks.py @@ -68,11 +68,13 @@ def symlink_main(gamedir=None, bindir="build/bin", just_copy=False): dependencies_dir_files = [ "AssemblyGenerator.dll", - "REFCSharpCompiler.dll", + "REFCoreDeps.dll", "Microsoft.CodeAnalysis.CSharp.dll", "Microsoft.CodeAnalysis.dll", "Microsoft.CodeAnalysis.CSharp.xml", "Microsoft.CodeAnalysis.xml", + "ImGui.NET.dll", + "ImGui.NET.xml", ] for file in dependencies_dir_files: From 51d04a775a52595359615f554c7d908750be836a Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 8 Apr 2024 07:47:56 -0700 Subject: [PATCH 094/207] .NET: Add ImGui.NET and ImGui rendering callback for scripts --- csharp-api/CMakeLists.txt | 1 + csharp-api/REFrameworkNET/Callbacks.cpp | 5 + csharp-api/REFrameworkNET/Callbacks.hpp | 4 + csharp-api/REFrameworkNET/PluginManager.cpp | 51 +++++++++- csharp-api/REFrameworkNET/PluginManager.hpp | 5 + csharp-api/REFrameworkNET/TypeDefinition.cpp | 20 ++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 2 + csharp-api/test/Test/Test.cs | 101 +++++++++++++++++-- csharp-api/test/Test/Test2.cs | 18 ++++ 9 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 csharp-api/test/Test/Test2.cs diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 340faca5a..09fca0c7f 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -307,6 +307,7 @@ VS_DOTNET_REFERENCE_REFramework.NET if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) # build-csharp-test set(CSharpAPITest_SOURCES "test/Test/Test.cs" + "test/Test/Test2.cs" cmake.toml ) diff --git a/csharp-api/REFrameworkNET/Callbacks.cpp b/csharp-api/REFrameworkNET/Callbacks.cpp index e05b81320..fb0025adf 100644 --- a/csharp-api/REFrameworkNET/Callbacks.cpp +++ b/csharp-api/REFrameworkNET/Callbacks.cpp @@ -56,6 +56,11 @@ void Impl::Setup(REFrameworkNET::API^ api) { s_knownStaticEvents->Add(post); } + if (type->Name == "ImGuiRender") { + Console::WriteLine("Skipping ImGuiRender"); + continue; + } + IntPtr preHookPtr = Marshal::GetFunctionPointerForDelegate(triggerPre); IntPtr postHookPtr = Marshal::GetFunctionPointerForDelegate(triggerPost); nativeApi->param()->functions->on_pre_application_entry(eventNameStr.c_str(), (REFOnPreApplicationEntryCb)preHookPtr.ToPointer()); diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp index 7408410e9..9b8fb86f9 100644 --- a/csharp-api/REFrameworkNET/Callbacks.hpp +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -455,5 +455,9 @@ namespace Callbacks { GENERATE_POCKET_CLASS(FinalizeDialog) GENERATE_POCKET_CLASS(FinalizeMixer) GENERATE_POCKET_CLASS(FinalizeGameCore); + + // Manually generated callback class for ImGui rendering + // We will manually filter out the ImGuiRender callback + GENERATE_POCKET_CLASS(ImGuiRender); }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 91e1293e2..20b41a037 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -185,7 +185,11 @@ namespace REFrameworkNET { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); System::Console::WriteLine("Created API instance"); } - + + // Must be set up before we load anything as it sets up the LoadLibraryExW hook for cimgui + auto imgui_callback_c = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(s_imgui_callback_delegate).ToPointer(); + REFrameworkNET::API::GetNativeImplementation()->param()->functions->on_imgui_frame((::REFOnImGuiFrameCb)imgui_callback_c); + s_dependencies = LoadDependencies(); // Pre-loads DLLs in the dependencies folder before loading the plugins try { @@ -558,4 +562,49 @@ namespace REFrameworkNET { } } } + + void PluginManager::ImGuiCallback(::REFImGuiFrameCbData* data) { + //System::Console::WriteLine("ImGuiCallback called"); + + // marshal to intptr + /*auto context = System::IntPtr(data->context); + auto mallocFn = System::IntPtr(data->malloc_fn); + auto freeFn = System::IntPtr(data->free_fn); + auto user_data = System::IntPtr(data->user_data); + + ImGuiNET::ImGui::SetCurrentContext(context); + ImGuiNET::ImGui::SetAllocatorFunctions(mallocFn, freeFn, user_data);*/ + + // Draw our REFramework.NET menu which has buttons like reload scripts + if (ImGuiNET::ImGui::Begin("REFramework.NET")) { + if (ImGuiNET::ImGui::Button("Unload Scripts")) { + PluginManager::UnloadDynamicAssemblies(); + } + + if (ImGuiNET::ImGui::Button("Reload Scripts")) { + PluginManager::UnloadDynamicAssemblies(); + PluginManager::LoadPlugins_FromSourceCode(0, s_dependencies); + } + } + + try { + Callbacks::ImGuiRender::TriggerPre(); + } catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Failed to trigger ImGuiRender::Pre: " + e->Message); + } catch (const std::exception& e) { + REFrameworkNET::API::LogError("Failed to trigger ImGuiRender::Pre: " + gcnew System::String(e.what())); + } catch (...) { + REFrameworkNET::API::LogError("Unknown exception caught while triggering ImGuiRender::Pre"); + } + + try { + Callbacks::ImGuiRender::TriggerPost(); + } catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Failed to trigger ImGuiRender::Post: " + e->Message); + } catch (const std::exception& e) { + REFrameworkNET::API::LogError("Failed to trigger ImGuiRender::Post: " + gcnew System::String(e.what())); + } catch (...) { + REFrameworkNET::API::LogError("Unknown exception caught while triggering ImGuiRender::Post"); + } + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 9fcde81a7..ef7ba97e5 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -37,6 +37,11 @@ private ref class PluginManager static bool LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps); static void UnloadDynamicAssemblies(); + static void ImGuiCallback(::REFImGuiFrameCbData* data); + delegate void ImGuiCallbackDelegate(::REFImGuiFrameCbData* data); + + static ImGuiCallbackDelegate^ s_imgui_callback_delegate{gcnew ImGuiCallbackDelegate(&ImGuiCallback)}; + static System::Collections::Generic::List^ s_loaded_assemblies{gcnew System::Collections::Generic::List()}; static System::Collections::Generic::List^ s_dynamic_assemblies{gcnew System::Collections::Generic::List()}; diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 4aa20e92f..ebe91ba1e 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -170,6 +170,26 @@ namespace REFrameworkNET { return (int)attributes->Call("GetLength", gcnew System::Int32(0)) > 0; } + bool TypeDefinition::IsGenericTypeDefinition() { + auto runtimeType = this->GetRuntimeType(); + + if (runtimeType == nullptr) { + return false; + } + + return (bool)runtimeType->Call("get_IsGenericTypeDefinition"); + } + + bool TypeDefinition::IsGenericType() { + auto runtimeType = this->GetRuntimeType(); + + if (runtimeType == nullptr) { + return false; + } + + return (bool)runtimeType->Call("get_IsGenericType"); + } + array^ TypeDefinition::GetGenericArguments() { auto runtimeType = this->GetRuntimeType(); diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 05ffaa157..b5529bb54 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -304,6 +304,8 @@ public } bool HasAttribute(REFrameworkNET::ManagedObject^ runtimeAttribute, bool inherit); + bool IsGenericTypeDefinition(); + bool IsGenericType(); array^ GetGenericArguments(); property array^ GenericArguments { diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index a2c7e8e08..7ee6897d0 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -1,9 +1,10 @@ -// Import REFramework::API using System; using System.Collections; +using System.Collections.Generic; using System.Dynamic; using System.Reflection; -using app; +using ImGuiNET; +using REFrameworkNET.Callbacks; public class DangerousFunctions { public static REFrameworkNET.PreHookResult isInsidePreHook(System.Object args) { @@ -119,6 +120,72 @@ public static void OnUnload() { REFrameworkNET.API.LogInfo("Unloading Test"); } + static List<_System.Type> nativeSingletonTypes = []; + + public static void RenderNativeSingletons() { + var singletons = REFrameworkNET.API.GetNativeSingletons(); + + // Sort by type name + singletons.Sort((a, b) => a.Instance.GetTypeDefinition().GetFullName().CompareTo(b.Instance.GetTypeDefinition().GetFullName())); + + foreach (var singletonDesc in singletons) { + var singleton = singletonDesc.Instance; + if (singleton == null) { + continue; + } + var singletonName = singleton.GetTypeDefinition().GetFullName(); + + if (ImGui.TreeNode(singletonName)) { + var methods = singleton.GetTypeDefinition().GetMethods(); + + foreach (var method in methods) { + var returnT = method.GetReturnType(); + var returnTName = returnT != null ? returnT.GetFullName() : "null"; + + ImGui.Text(" " + returnTName); + ImGui.SameLine(); + + // Set color to blue or something + ImGui.TextColored(new System.Numerics.Vector4(0.0f, 1.0f, 1.0f, 1.0f), method.GetName()); + } + + var fields = singleton.GetTypeDefinition().GetFields(); + + foreach (var field in fields) { + var t = field.GetType(); + string tName = t != null ? t.GetFullName() : "null"; + ImGui.Text(" " + tName + " " + field.GetName() + " @ " + "0x" + field.GetOffsetFromBase().ToString("X")); + } + + ImGui.TreePop(); + } + } + } + + public static void RenderManagedSingletons() { + var singletons = REFrameworkNET.API.GetManagedSingletons(); + + foreach (var singletonDesc in singletons) { + var singleton = singletonDesc.Instance; + if (singleton == null) { + continue; + } + var singletonName = singleton.GetTypeDefinition().GetFullName(); + + if (ImGui.TreeNode(singletonName)) { + var fields = singleton.GetTypeDefinition().GetFields(); + + foreach (var field in fields) { + var t = field.GetType(); + string tName = t != null ? t.GetFullName() : "null"; + ImGui.Text(" " + tName + " " + field.GetName() + " @ " + "0x" + field.GetOffsetFromBase().ToString("X")); + } + + ImGui.TreePop(); + } + } + } + public static void TestCallbacks() { REFrameworkNET.Callbacks.BeginRendering.Pre += () => { sw.Start(); @@ -156,6 +223,28 @@ public static void TestCallbacks() { REFrameworkNET.Callbacks.PrepareRendering.Post += () => { }; + + REFrameworkNET.Callbacks.ImGuiRender.Pre += () => { + // Draw a random window + if (ImGui.Begin("Test Window")) { + ImGui.Text("Hello from ImGui!"); + + try { + if (ImGui.TreeNode("Managed Singletons")) { + RenderManagedSingletons(); + ImGui.TreePop(); + } + + if (ImGui.TreeNode("Native Singletons")) { + RenderNativeSingletons(); + } + } catch (Exception e) { + System.Console.WriteLine(e.ToString()); + } + + ImGui.End(); + } + }; } [REFrameworkNET.Attributes.PluginEntryPoint] @@ -183,7 +272,7 @@ public static void MainImpl() { REFrameworkNET.API.LogInfo(tdb.GetNumTypes().ToString() + " types"); - for (uint i = 0; i < 50; i++) { + /*for (uint i = 0; i < 50; i++) { var type = tdb.GetType(i); REFrameworkNET.API.LogInfo(type.GetFullName()); @@ -203,11 +292,11 @@ public static void MainImpl() { string tName = t != null ? t.GetFullName() : "null"; REFrameworkNET.API.LogInfo(" " + tName + " " + field.GetName() + " @ " + "0x" + field.GetOffsetFromBase().ToString("X")); } - } + }*/ REFrameworkNET.API.LogInfo("Done with types"); - var singletons = REFrameworkNET.API.GetManagedSingletons(); + /*var singletons = REFrameworkNET.API.GetManagedSingletons(); foreach (var singletonDesc in singletons) { var singleton = singletonDesc.Instance; @@ -235,7 +324,7 @@ public static void MainImpl() { foreach (var field in fields) { Console.WriteLine(" " + field.GetName()); } - } + }*/ try { DangerousFunctions.Entry(); diff --git a/csharp-api/test/Test/Test2.cs b/csharp-api/test/Test/Test2.cs new file mode 100644 index 000000000..2d1a37430 --- /dev/null +++ b/csharp-api/test/Test/Test2.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Reflection; +using ImGuiNET; + +class REFrameworkPlugin2 { + [REFrameworkNET.Attributes.PluginEntryPoint] + public static void Main() { + REFrameworkNET.Callbacks.ImGuiRender.Pre += () => { + if (ImGui.Begin("Test2.cs")) { + ImGui.Text("Test2.cs"); + ImGui.End(); + } + }; + } +} \ No newline at end of file From 5138b3ed5f665f2fb4811ef4efc7d5a4d67c9c3a Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 8 Apr 2024 13:02:55 -0700 Subject: [PATCH 095/207] .NET: Turn Test.cs into a nice object explorer --- csharp-api/test/Test/Test.cs | 264 +++++++++++++++++++++++++++-------- 1 file changed, 207 insertions(+), 57 deletions(-) diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 7ee6897d0..7947f8b6c 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -4,6 +4,7 @@ using System.Dynamic; using System.Reflection; using ImGuiNET; +using REFrameworkNET; using REFrameworkNET.Callbacks; public class DangerousFunctions { @@ -109,18 +110,166 @@ public static void TryEnableFrameGeneration() { } } -class REFrameworkPlugin { - // Measure time between pre and post - // get time - static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); - static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); +class ObjectExplorer { + static System.Numerics.Vector4 TYPE_COLOR = new System.Numerics.Vector4(78.0f / 255.0f, 201 / 255.0f, 176 / 255.0f, 1.0f); + static System.Numerics.Vector4 FIELD_COLOR = new(156 / 255.0f, 220 / 255.0f, 254 / 255.0f, 1.0f); + static System.Numerics.Vector4 METHOD_COLOR = new(220 / 255.0f, 220 / 255.0f, 170 / 255.0f, 1.0f); - // To be called when the AssemblyLoadContext is unloading the assembly - public static void OnUnload() { - REFrameworkNET.API.LogInfo("Unloading Test"); + public static void DisplayColorPicker() { + ImGui.ColorEdit4("Type Color", ref TYPE_COLOR); + ImGui.ColorEdit4("Field Color", ref FIELD_COLOR); + ImGui.ColorEdit4("Method Color", ref METHOD_COLOR); } - static List<_System.Type> nativeSingletonTypes = []; + public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field field) { + var t = field.GetType(); + string tName = t != null ? t.GetFullName() : "null"; + + var unified = obj as REFrameworkNET.UnifiedObject; + ulong address = unified != null ? unified.GetAddress() : 0; + + if (field.IsStatic()) { + address = field.GetDataRaw(obj.GetAddress(), false); + } else { + address += field.GetOffsetFromBase(); + } + + // Make a tree node that spans the entire width of the window + ImGui.PushID(address.ToString("X")); + ImGui.SetNextItemOpen(false, ImGuiCond.Once); + var made = ImGui.TreeNodeEx("", ImGuiTreeNodeFlags.SpanFullWidth); + + ImGui.SameLine(); + //ImGui.Text(" " + tName); + ImGui.TextColored(TYPE_COLOR, " " + tName); + + ImGui.SameLine(); + + ImGui.TextColored(FIELD_COLOR, field.GetName()); + + ImGui.SameLine(); + + if (field.IsStatic()) { + // Red text + ImGui.TextColored(new System.Numerics.Vector4(0.75f, 0.2f, 0.0f, 1.0f), "Static"); + } else { + ImGui.Text("0x" + field.GetOffsetFromBase().ToString("X")); + } + + if (made) { + if (!t.IsValueType()) { + var objValue = obj.GetField(field.GetName()) as REFrameworkNET.IObject; + + if (objValue != null) { + DisplayObject(objValue); + } else { + ImGui.Text("Value: null"); + } + } else { + switch (tName) { + case "System.Int32": + ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + break; + case "System.UInt32": + ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + break; + case "System.Int64": + ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + break; + case "System.UInt64": + ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + break; + case "System.Single": + ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + break; + case "System.Boolean": + ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + break; + /*case "System.String": + ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false)); + break;*/ + default: + ImGui.Text("Value: " + field.GetDataRaw(obj.GetAddress(), false).ToString("X")); + break; + } + } + + ImGui.TreePop(); + } + + ImGui.PopID(); + } + + public static void DisplayMethod(REFrameworkNET.IObject obj, REFrameworkNET.Method method) { + var returnT = method.GetReturnType(); + var returnTName = returnT != null ? returnT.GetFullName() : "null"; + + //ImGui.Text(" " + returnTName); + ImGui.TextColored(TYPE_COLOR, " " + returnTName); + ImGui.SameLine(0.0f, 0.0f); + + ImGui.TextColored(METHOD_COLOR, " " + method.GetName()); + + + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text("("); + + var ps = method.GetParameters(); + + if (ps.Count > 0) { + for (int i = 0; i < ps.Count; i++) { + var p = ps[i]; + + ImGui.SameLine(0.0f, 0.0f); + ImGui.TextColored(TYPE_COLOR, p.Type.GetFullName()); + + if (p.Name != null && p.Name.Length > 0) { + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text(" " + p.Name); + } + + if (i < ps.Count - 1) { + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text(", "); + } + //postfix += p.Type.GetFullName() + " " + p.Name + ", "; + } + + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text(")"); + + //postfix = postfix.Substring(0, postfix.Length - 3); + } else { + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text(")"); + } + } + + public static void DisplayObject(REFrameworkNET.IObject obj) { + if (ImGui.TreeNode("Methods")) { + var methods = obj.GetTypeDefinition().GetMethods(); + + // Sort methods by name + methods.Sort((a, b) => a.GetName().CompareTo(b.GetName())); + + foreach (var method in methods) { + DisplayMethod(obj, method); + } + } + + if (ImGui.TreeNode("Fields")) { + var fields = obj.GetTypeDefinition().GetFields(); + + // Sort fields by name + fields.Sort((a, b) => a.GetName().CompareTo(b.GetName())); + + foreach (var field in fields) { + DisplayField(obj, field); + } + + ImGui.TreePop(); + } + } public static void RenderNativeSingletons() { var singletons = REFrameworkNET.API.GetNativeSingletons(); @@ -136,27 +285,7 @@ public static void RenderNativeSingletons() { var singletonName = singleton.GetTypeDefinition().GetFullName(); if (ImGui.TreeNode(singletonName)) { - var methods = singleton.GetTypeDefinition().GetMethods(); - - foreach (var method in methods) { - var returnT = method.GetReturnType(); - var returnTName = returnT != null ? returnT.GetFullName() : "null"; - - ImGui.Text(" " + returnTName); - ImGui.SameLine(); - - // Set color to blue or something - ImGui.TextColored(new System.Numerics.Vector4(0.0f, 1.0f, 1.0f, 1.0f), method.GetName()); - } - - var fields = singleton.GetTypeDefinition().GetFields(); - - foreach (var field in fields) { - var t = field.GetType(); - string tName = t != null ? t.GetFullName() : "null"; - ImGui.Text(" " + tName + " " + field.GetName() + " @ " + "0x" + field.GetOffsetFromBase().ToString("X")); - } - + DisplayObject(singleton); ImGui.TreePop(); } } @@ -173,16 +302,57 @@ public static void RenderManagedSingletons() { var singletonName = singleton.GetTypeDefinition().GetFullName(); if (ImGui.TreeNode(singletonName)) { - var fields = singleton.GetTypeDefinition().GetFields(); + DisplayObject(singleton); + ImGui.TreePop(); + } + } + } - foreach (var field in fields) { - var t = field.GetType(); - string tName = t != null ? t.GetFullName() : "null"; - ImGui.Text(" " + tName + " " + field.GetName() + " @ " + "0x" + field.GetOffsetFromBase().ToString("X")); - } + public static void Render() { + ImGui.SetNextItemOpen(true, ImGuiCond.Once); + if (ImGui.TreeNode("Color Picker")) { + DisplayColorPicker(); + ImGui.TreePop(); + } + + + try { + if (ImGui.TreeNode("Managed Singletons")) { + RenderManagedSingletons(); + ImGui.TreePop(); + } + + if (ImGui.TreeNode("Native Singletons")) { + RenderNativeSingletons(); + } + } catch (Exception e) { + System.Console.WriteLine(e.ToString()); + } + } +} // class ObjectExplorer + +class REFrameworkPlugin { + // Measure time between pre and post + // get time + static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); + + // To be called when the AssemblyLoadContext is unloading the assembly + public static void OnUnload() { + REFrameworkNET.API.LogInfo("Unloading Test"); + } + + // Assigned in a callback below. + public static void RenderImGui() { + if (ImGui.Begin("Test Window")) { + ImGui.SetNextItemOpen(true, ImGuiCond.Once); + if (ImGui.TreeNode("Object Explorer")) { + ObjectExplorer.Render(); ImGui.TreePop(); } + + ImGui.End(); } } @@ -224,27 +394,7 @@ public static void TestCallbacks() { REFrameworkNET.Callbacks.PrepareRendering.Post += () => { }; - REFrameworkNET.Callbacks.ImGuiRender.Pre += () => { - // Draw a random window - if (ImGui.Begin("Test Window")) { - ImGui.Text("Hello from ImGui!"); - - try { - if (ImGui.TreeNode("Managed Singletons")) { - RenderManagedSingletons(); - ImGui.TreePop(); - } - - if (ImGui.TreeNode("Native Singletons")) { - RenderNativeSingletons(); - } - } catch (Exception e) { - System.Console.WriteLine(e.ToString()); - } - - ImGui.End(); - } - }; + REFrameworkNET.Callbacks.ImGuiRender.Pre += RenderImGui; } [REFrameworkNET.Attributes.PluginEntryPoint] From d8bc939bf8af3bde609f7fed1d0b48756463be82 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 8 Apr 2024 13:59:32 -0700 Subject: [PATCH 096/207] .NET: Draw REFramework.NET UI within REFramework itself --- csharp-api/REFrameworkNET/PluginManager.cpp | 36 +++++++++++++-------- csharp-api/REFrameworkNET/PluginManager.hpp | 7 ++++ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 20b41a037..c1a40a4fa 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -188,7 +188,10 @@ namespace REFrameworkNET { // Must be set up before we load anything as it sets up the LoadLibraryExW hook for cimgui auto imgui_callback_c = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(s_imgui_callback_delegate).ToPointer(); - REFrameworkNET::API::GetNativeImplementation()->param()->functions->on_imgui_frame((::REFOnImGuiFrameCb)imgui_callback_c); + auto imgui_draw_ui_callback_c = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(s_imgui_draw_ui_callback_delegate).ToPointer(); + auto api_fns = REFrameworkNET::API::GetNativeImplementation()->param()->functions; + api_fns->on_imgui_frame((::REFOnImGuiFrameCb)imgui_callback_c); + api_fns->on_imgui_draw_ui((::REFOnImGuiFrameCb)imgui_draw_ui_callback_c); s_dependencies = LoadDependencies(); // Pre-loads DLLs in the dependencies folder before loading the plugins @@ -575,18 +578,6 @@ namespace REFrameworkNET { ImGuiNET::ImGui::SetCurrentContext(context); ImGuiNET::ImGui::SetAllocatorFunctions(mallocFn, freeFn, user_data);*/ - // Draw our REFramework.NET menu which has buttons like reload scripts - if (ImGuiNET::ImGui::Begin("REFramework.NET")) { - if (ImGuiNET::ImGui::Button("Unload Scripts")) { - PluginManager::UnloadDynamicAssemblies(); - } - - if (ImGuiNET::ImGui::Button("Reload Scripts")) { - PluginManager::UnloadDynamicAssemblies(); - PluginManager::LoadPlugins_FromSourceCode(0, s_dependencies); - } - } - try { Callbacks::ImGuiRender::TriggerPre(); } catch (System::Exception^ e) { @@ -607,4 +598,23 @@ namespace REFrameworkNET { REFrameworkNET::API::LogError("Unknown exception caught while triggering ImGuiRender::Post"); } } + + void PluginManager::ImGuiDrawUICallback(::REFImGuiFrameCbData* data) { + // Draw our REFramework.NET menu which has buttons like reload scripts + ImGuiNET::ImGui::PushID("REFramework.NET"); + if (ImGuiNET::ImGui::CollapsingHeader("REFramework.NET")) { + if (ImGuiNET::ImGui::Button("Reload Scripts")) { + PluginManager::UnloadDynamicAssemblies(); + PluginManager::LoadPlugins_FromSourceCode(0, s_dependencies); + } + + ImGuiNET::ImGui::SameLine(); + + if (ImGuiNET::ImGui::Button("Unload Scripts")) { + PluginManager::UnloadDynamicAssemblies(); + } + } + + ImGuiNET::ImGui::PopID(); + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index ef7ba97e5..9c007d6c2 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -37,10 +37,16 @@ private ref class PluginManager static bool LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps); static void UnloadDynamicAssemblies(); + // This one is outside of a window context static void ImGuiCallback(::REFImGuiFrameCbData* data); delegate void ImGuiCallbackDelegate(::REFImGuiFrameCbData* data); + // This one is in the middle of drawing REFramework's UI + static void ImGuiDrawUICallback(::REFImGuiFrameCbData* data); + delegate void ImGuiDrawUICallbackDelegate(::REFImGuiFrameCbData* data); + static ImGuiCallbackDelegate^ s_imgui_callback_delegate{gcnew ImGuiCallbackDelegate(&ImGuiCallback)}; + static ImGuiDrawUICallbackDelegate^ s_imgui_draw_ui_callback_delegate{gcnew ImGuiDrawUICallbackDelegate(&ImGuiDrawUICallback)}; static System::Collections::Generic::List^ s_loaded_assemblies{gcnew System::Collections::Generic::List()}; static System::Collections::Generic::List^ s_dynamic_assemblies{gcnew System::Collections::Generic::List()}; @@ -63,6 +69,7 @@ private ref class PluginManager static void OnSourceScriptsChanged(System::Object^ sender, System::IO::FileSystemEventArgs^ e); static void BeginRendering(); delegate void BeginRenderingDelegate(); + static BeginRenderingDelegate^ s_begin_rendering_delegate{gcnew BeginRenderingDelegate(&BeginRendering)}; }; } \ No newline at end of file From e49bb0184f494dc7f65812e0dac10a0a414fa9ad Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 9 Apr 2024 08:05:43 -0700 Subject: [PATCH 097/207] .NET: Add support for additional primitives as return/params --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index b14c874b7..6ab453817 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -62,6 +62,21 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy case "System.UInt32": outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UIntKeyword)); break; + case "System.Int16": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ShortKeyword)); + break; + case "System.UInt16": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.UShortKeyword)); + break; + case "System.Byte": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ByteKeyword)); + break; + case "System.SByte": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.SByteKeyword)); + break; + case "System.Char": + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.CharKeyword)); + break; case "System.Int64": case "System.IntPtr": outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.LongKeyword)); From 335583ceddbe0be5336a5ae414af2de6877c572c Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 07:16:55 -0700 Subject: [PATCH 098/207] .NET: Add Method.GetMatchingParentMethods --- csharp-api/REFrameworkNET/Method.cpp | 35 +++++++++++++++++++--------- csharp-api/REFrameworkNET/Method.hpp | 1 + 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 68b8cf6a8..1e3ba34e6 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -57,15 +57,16 @@ ManagedObject^ Method::GetRuntimeMethod() { return nullptr; } -bool Method::IsOverride() { +System::Collections::Generic::List^ Method::GetMatchingParentMethods() { + auto out = gcnew System::Collections::Generic::List(); auto declaringType = this->GetDeclaringType(); if (declaringType == nullptr) { - return false; + return out; } if (declaringType->ParentType == nullptr) { - return false; + return out; } auto returnType = this->GetReturnType(); @@ -79,7 +80,16 @@ bool Method::IsOverride() { continue; } - if (parentMethod->GetReturnType() != returnType) { + if (parentMethod->DeclaringType != parentType) { + continue; + } + + /*if (parentMethod->GetReturnType() != returnType) { + continue; + }*/ + + // Generic return type check + if (parentMethod->ReturnType->Name->Contains("!")) { continue; } @@ -88,7 +98,8 @@ bool Method::IsOverride() { } if (parameters->Length == 0) { - return true; + out->Add(parentMethod); + continue; } auto parentParams = parentMethod->GetParameters()->ToArray(); @@ -104,15 +115,17 @@ bool Method::IsOverride() { } } - if (!fullParamsMatch) { - continue; + if (fullParamsMatch) { + out->Add(parentMethod); } - - return true; } } - return false; + return out; +} + +bool Method::IsOverride() { + return GetMatchingParentMethods()->Count > 0; } REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { @@ -232,7 +245,7 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args, System::Object^% result) { +bool Method::HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result) { //auto methodName = binder->Name; auto tempResult = this->Invoke(obj, args); diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index bfb7f8b5c..746c3381a 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -163,6 +163,7 @@ public ref class Method : public System::IEquatable MethodHook^ AddHook(bool ignore_jmp); ManagedObject^ GetRuntimeMethod(); + System::Collections::Generic::List^ GetMatchingParentMethods(); // mainly for the assembly generator (temporary?) bool IsOverride(); public: From 05732c9269b1476af1fce838e292f314208cd0ba Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 07:17:59 -0700 Subject: [PATCH 099/207] .NET: Clean up some stuff --- csharp-api/REFrameworkNET/ManagedObject.hpp | 8 +++++ csharp-api/REFrameworkNET/Method.hpp | 2 +- csharp-api/REFrameworkNET/Proxy.hpp | 36 ++++++++++++++------ csharp-api/REFrameworkNET/TypeDefinition.hpp | 2 +- csharp-api/REFrameworkNET/UnifiedObject.cpp | 2 +- 5 files changed, 37 insertions(+), 13 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 6bbc39dd7..d47739bbd 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -44,10 +44,18 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } void AddRef() { + if (m_object == nullptr) { + return; + } + m_object->add_ref(); } void Release() { + if (m_object == nullptr) { + return; + } + m_object->release(); } diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 746c3381a..a50f150a0 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -26,7 +26,7 @@ public ref class Method : public System::IEquatable } REFrameworkNET::InvokeRet^ Invoke(System::Object^ obj, array^ args); - bool HandleInvokeMember_Internal(System::Object^ obj, System::String^ methodName, array^ args, System::Object^% result); + bool HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result); void* GetFunctionPtr() { return m_method->get_function_raw(); diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 0a05538e4..b4867f0ad 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -133,19 +133,35 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); } - if (targetMethod->ReturnType == REFrameworkNET::ManagedObject::typeid) { - return result; - } - - if (targetMethod->ReturnType == REFrameworkNET::NativeObject::typeid) { - return result; - } + auto targetReturnType = targetMethod->ReturnType; - if (targetMethod->ReturnType == String::typeid) { - return result; + if (targetReturnType != nullptr) { + if (targetReturnType == String::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::ManagedObject::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::NativeObject::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::TypeDefinition::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::Method::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::Field::typeid) { + return result; + } } - if (targetMethod->DeclaringType == nullptr || targetMethod->ReturnType == nullptr) { + if (targetMethod->DeclaringType == nullptr || targetReturnType == nullptr) { return result; } diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index b5529bb54..b965df70b 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -16,7 +16,7 @@ ref class Property; ref class TypeInfo; ref struct InvokeRet; -public enum VMObjType { +public enum class VMObjType : uint32_t { NULL_ = 0, Object = 1, Array = 2, diff --git a/csharp-api/REFrameworkNET/UnifiedObject.cpp b/csharp-api/REFrameworkNET/UnifiedObject.cpp index 845fe6983..c558d8abe 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.cpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.cpp @@ -34,7 +34,7 @@ namespace REFrameworkNET { if (method != nullptr) { // Re-used with UnifiedObject::TryInvokeMember - return method->HandleInvokeMember_Internal(this, methodName, args, result); + return method->HandleInvokeMember_Internal(this, args, result); } REFrameworkNET::API::LogInfo("Method not found: " + methodName); From 51cc3943194b5fde78eac59a0abf7008d229b040 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 07:23:26 -0700 Subject: [PATCH 100/207] .NET: Major assembly generator improvements (bad code pls ignore) Global namespace types are now generated. All method overloads are now generated correctly instead of just one. All types now have a REFType static field allowing runtime type lookups. Array types are being worked on. Methods with same sig are now ignored (this is actually a workaround for a bug) --- .../AssemblyGenerator/ClassGenerator.cs | 432 ++++++++++++------ csharp-api/AssemblyGenerator/EnumGenerator.cs | 2 +- csharp-api/AssemblyGenerator/Generator.cs | 275 +++++++++-- csharp-api/test/Test/Test.cs | 78 +++- 4 files changed, 606 insertions(+), 181 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 6ab453817..64dd919e0 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -12,12 +12,38 @@ using System.Linq; using Microsoft.CodeAnalysis.Operations; using REFrameworkNET; +using System; +/*public interface CrappyTest { + public string Concat(object arg0); + + public string Concat(object arg0, object arg1); + + public string Concat(object arg0, object arg1, object arg2); + + public string Concat(global::System.Object[] args); + + public string Concat(string str0, string str1); + + public string Concat(string str0, string str1, string str2); + + public string Concat(string str0, string str1, string str2, string str3); + + public string Concat(object str0, object str1); + + public string Concat(object str0, object str1, object str2); + + public string Concat(object str0, object str1, object str2, object str3); + + public string Concat(global::System.String[] values); + + public string Format(string format, object arg0); +}*/ public class ClassGenerator { private string className; private REFrameworkNET.TypeDefinition t; - private REFrameworkNET.Method[] methods = []; + private List methods = []; public List usingTypes = []; private TypeDeclarationSyntax? typeDeclaration; @@ -31,23 +57,67 @@ public void Update(TypeDeclarationSyntax? typeDeclaration_) { typeDeclaration = typeDeclaration_; } - public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_, REFrameworkNET.Method[] methods_) { - className = className_; + public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { + className = REFrameworkNET.AssemblyGenerator.FixBadChars(className_); t = t_; - methods = methods_; + + foreach (var method in t_.Methods) { + // Means we've entered the parent type + if (method.DeclaringType != t_) { + break; + } + + methods.Add(method); + } typeDeclaration = Generate(); } private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetType, REFrameworkNET.TypeDefinition? containingType) { + if (containingType != null && targetType != null) { + if (containingType.FullName.StartsWith("System.")) { + /*var containingRtType = containingType.GetRuntimeType(); + var targetRtType = targetType.GetRuntimeType(); + + if (containingRtType != null && targetRtType != null) { + var containingRtAssembly = (ManagedObject)containingRtType.Call("get_Assembly"); + var targetRtAssembly = (ManagedObject)targetRtType.Call("get_Assembly"); + + if ((string)containingRtAssembly.Call("get_FullName") != (string)targetRtAssembly.Call("get_FullName")) { + System.Console.WriteLine("Method " + containingType.FullName + " is referencing type " + targetType.FullName + " in a different assembly"); + return SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + } + }*/ + + // Check if any of the generic arguments are not in the same assembly + /*if (containingRtType != null && targetType.IsGenericType()) { + foreach (REFrameworkNET.TypeDefinition td in targetType.GenericArguments) { + if (td != null) { + var rtType = td.GetRuntimeType(); + if (rtType != null) { + if ((ManagedObject)containingRtType.Call("get_Assembly") != (ManagedObject)rtType.Call("get_Assembly")) { + return SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + } + } + } + } + }*/ + } + } + TypeSyntax outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); - string targetTypeName = targetType != null ? targetType.GetFullName() : ""; + string ogTargetTypename = targetType != null ? targetType.GetFullName() : ""; + string targetTypeName = REFrameworkNET.AssemblyGenerator.FixBadChars(ogTargetTypename); if (targetType == null || targetTypeName == "System.Void" || targetTypeName == "") { return outSyntax; } + if (AssemblyGenerator.typeFullRenames.ContainsKey(targetType) && !targetType.IsDerivedFrom(AssemblyGenerator.SystemArrayT)) { + targetTypeName = AssemblyGenerator.typeFullRenames[targetType]; + } + // Check for easily convertible types like System.Single, System.Int32, etc. switch (targetTypeName) { case "System.Single": @@ -96,43 +166,19 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; default: - if (targetType != null && targetTypeName != "") { - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(targetTypeName)) { - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - if (targetTypeName.Contains('<') || targetTypeName.Contains('[')) { - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - // Stuff in System should NOT be referencing via - // how is this even compiling for them? - if (containingType != null) { - var ogClassName = containingType.GetFullName(); - - if (ogClassName.StartsWith("System") && targetTypeName.StartsWith("via")) { - //REFrameworkNET.API.LogWarning("Method " + ogClassName + "." + method.Name + " is referencing via class " + methodReturnName); - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - - if (ogClassName.StartsWith("System") && targetTypeName.StartsWith("app.")) { - //EFrameworkNET.API.LogWarning("Method " + ogClassName + "." + method.Name + " is referencing app class " + methodReturnName); - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - break; - } - } - - - targetTypeName = "global::" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(targetTypeName); - - outSyntax = SyntaxFactory.ParseTypeName(targetTypeName); + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(ogTargetTypename)) { + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); break; } - outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + /*if (targetTypeName.Contains('<')) { + outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + break; + }*/ + + targetTypeName = "global::" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(targetTypeName); + + outSyntax = SyntaxFactory.ParseTypeName(targetTypeName); break; } @@ -170,11 +216,27 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy if (t.DeclaringType == null) { className = className.Split('.').Last(); } + + typeDeclaration = SyntaxFactory + .InterfaceDeclaration(REFrameworkNET.AssemblyGenerator.CorrectTypeName(className)) + .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}); + + if (typeDeclaration == null) { + return null; + } + + // Set up base types + List baseTypes = []; + + for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { + // TODO: Fix this + if (!AssemblyGenerator.validTypes.Contains(parent.FullName)) { + continue; + } - bool wantsAbstractInstead = false; + AssemblyGenerator.typeFullRenames.TryGetValue(parent, out string? parentName); + parentName = AssemblyGenerator.CorrectTypeName(parentName ?? parent.FullName ?? ""); - /*for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - var parentName = parent.FullName ?? ""; if (parentName == null) { break; } @@ -183,30 +245,40 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy break; } - if (parentName == "System.Attribute") { - wantsAbstractInstead = true; + if (parentName.Contains('[')) { break; } - }*/ - - if (wantsAbstractInstead) { - typeDeclaration = SyntaxFactory - .ClassDeclaration(REFrameworkNET.AssemblyGenerator.CorrectTypeName(className)) - .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.AbstractKeyword)}); - } else { - typeDeclaration = SyntaxFactory - .InterfaceDeclaration(REFrameworkNET.AssemblyGenerator.CorrectTypeName(className)) - .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}); - } - if (typeDeclaration == null) { - return null; + // Forces compiler to start at the global namespace + parentName = "global::" + parentName; + + baseTypes.Add(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(parentName))); + usingTypes.Add(parent); + break; } + // Add a static field to the class that holds the REFrameworkNET.TypeDefinition + var refTypeVarDecl = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.TypeDefinition")) + .AddVariables(SyntaxFactory.VariableDeclarator("REFType").WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("global::REFrameworkNET.TDB.Get().FindType(\"" + t.FullName + "\")")))); + + var refTypeFieldDecl = SyntaxFactory.FieldDeclaration(refTypeVarDecl).AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + + // Add a static field that holds a NativeProxy to the class (for static methods) + var refProxyVarDecl = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName(REFrameworkNET.AssemblyGenerator.CorrectTypeName(className))) + .AddVariables(SyntaxFactory.VariableDeclarator("REFProxy").WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.As<" + REFrameworkNET.AssemblyGenerator.CorrectTypeName(t.FullName) + ">()")))); + + var refProxyFieldDecl = SyntaxFactory.FieldDeclaration(refProxyVarDecl).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + + List internalFieldDeclarations = []; + HashSet seenMethodSignatures = []; + typeDeclaration = typeDeclaration - .AddMembers(methods.Where(method => !invalidMethodNames.Contains(method.Name) && !method.Name.Contains('<')).Select(method => { + .AddMembers(methods.Where(method => !invalidMethodNames.Contains(method.Name) && !method.Name.Contains('<') && !method.ReturnType.FullName.Contains('!')).Select(method => { TypeSyntax? returnType = MakeProperType(method.ReturnType, t); + //string simpleMethodSignature = returnType.GetText().ToString(); + string simpleMethodSignature = ""; // Return types are not part of the signature. Return types are not overloaded. + var methodName = new string(method.Name); var methodExtension = Il2CppDump.GetMethodExtension(method); @@ -214,75 +286,162 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; - if (methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { - //methodName += "_" + className.Replace('.', '_'); - - methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - } - - if (wantsAbstractInstead) { - methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); - } + simpleMethodSignature += methodName; if (method.Parameters.Count > 0) { - methodDeclaration = methodDeclaration.AddParameterListParameters(method.Parameters.Where(param => param != null && param.Type != null && param.Name != null).Select(param => { - return SyntaxFactory.Parameter(SyntaxFactory.Identifier(param.Name ?? "UnknownParam")) - .WithType(SyntaxFactory.ParseTypeName(param.Type != null ? MakeProperType(param.Type, t).ToString() : "object")); - }).ToArray()); - } - - return methodDeclaration; - }).ToArray()); + // If any of the params have ! in them, skip this method + if (method.Parameters.Any(param => param.Type.FullName.Contains('!'))) { + return null; + } - if (!className.Contains('[') && !className.Contains('<')) { - List baseTypes = new List(); + var runtimeMethod = method.GetRuntimeMethod(); + var runtimeParams = runtimeMethod.Call("GetParameters") as REFrameworkNET.ManagedObject; + + System.Collections.Generic.List parameters = []; + + bool anyUnsafeParams = false; + + if (runtimeParams != null) { + foreach (dynamic param in runtimeParams) { + if (param.get_IsRetval() == true) { + continue; + } + + var paramName = param.get_Name(); + + if (paramName == null) { + paramName = "UnknownParam"; + } + + var paramType = param.get_ParameterType(); + + if (paramType == null) { + parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName("object"))); + continue; + } + + /*if (param.get_IsGenericParameter() == true) { + return null; // no generic parameters. + }*/ + + var isByRef = paramType.IsByRefImpl(); + var isPointer = paramType.IsPointerImpl(); + var isOut = param.get_IsOut(); + var paramTypeDef = (REFrameworkNET.TypeDefinition)paramType.get_TypeHandle(); + + var paramTypeSyntax = MakeProperType(paramTypeDef, t); + + System.Collections.Generic.List modifiers = []; + + if (isOut == true) { + simpleMethodSignature += "out"; + modifiers.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword)); + } + + if (isByRef == true) { + // can only be either ref or out. + if (!isOut) { + simpleMethodSignature += "ref " + paramTypeSyntax.GetText().ToString(); + modifiers.Add(SyntaxFactory.Token(SyntaxKind.RefKeyword)); + } + + parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName(paramTypeSyntax.ToString())).AddModifiers(modifiers.ToArray())); + } else if (isPointer == true) { + simpleMethodSignature += "ptr " + paramTypeSyntax.GetText().ToString(); + parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName(paramTypeSyntax.ToString() + "*")).AddModifiers(modifiers.ToArray())); + anyUnsafeParams = true; + } else { + simpleMethodSignature += paramTypeSyntax.GetText().ToString(); + parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(paramTypeSyntax).AddModifiers(modifiers.ToArray())); + } + } - SortedSet badBaseTypes = new SortedSet { - /*"System.Object", - "System.ValueType", - "System.Enum", - "System.Delegate", - "System.MulticastDelegate"*/ - }; + methodDeclaration = methodDeclaration.AddParameterListParameters([.. parameters]); - for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - // TODO: Fix this - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { - continue; + if (anyUnsafeParams) { + methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + } + } + } else { + simpleMethodSignature += "()"; } - var parentName = REFrameworkNET.AssemblyGenerator.CorrectTypeName(parent.FullName ?? ""); - if (parentName == null) { - break; + if (method.IsStatic()) { + // Add System.ComponentModel.Description("static") attribute + //methodDeclaration = methodDeclaration.AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName("global::System.ComponentModel.DescriptionAttribute"), SyntaxFactory.ParseAttributeArgumentList("(\"static\")")))); + + // lets see what happens if we just make it static + /*methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + + // Now we must add a body to it that actually calls the method + // We have our REFType field, so we can lookup the method and call it + // Make a private static field to hold the REFrameworkNET.Method + var internalFieldName = "INTERNAL_" + method.GetIndex().ToString(); + var methodVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Method")) + .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetMethod(\"" + method.Name + "\")")))); + + var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + internalFieldDeclarations.Add(methodFieldDeclaration); + + // Now we can add the body + // bool HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result); + if (method.ReturnType.FullName == "System.Void") { + var body = internalFieldName + ".Invoke(null, null)"; + methodDeclaration = methodDeclaration.AddBodyStatements(SyntaxFactory.ParseStatement(body)); + } else { + var body1 = "object INTERNAL_result = null;"; + var body2 = internalFieldName + ".HandleInvokeMember_Internal(null, " + (method.Parameters.Count > 0 ? "new object[] {" + string.Join(", ", method.Parameters.Select(param => param.Name)) + "}" : "null") + ", ref INTERNAL_result);"; + string body3 = "return (" + returnType.GetText() + ")INTERNAL_result;"; + + methodDeclaration = methodDeclaration.AddBodyStatements( + [SyntaxFactory.ParseStatement(body1), SyntaxFactory.ParseStatement(body2), SyntaxFactory.ParseStatement(body3)] + ); + }*/ } - if (parentName == "") { - break; + if (seenMethodSignatures.Contains(simpleMethodSignature)) { + Console.WriteLine("Skipping duplicate method: " + methodDeclaration.GetText().ToString()); + return null; } - if (parentName.Contains('[') || parentName.Contains('<')) { - break; - } + seenMethodSignatures.Add(simpleMethodSignature); - if (badBaseTypes.Contains(parentName)) { - break; + // Add the rest of the modifiers here that would mangle the signature check + if (baseTypes.Count > 0 && methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { + var matchingParentMethods = methodExtension.MatchingParentMethods; + + // Go through the parents, check if the parents are allowed to be generated + // and add the new keyword if the matching method is found in one allowed to be generated + // TODO: We can get rid of this once we start properly generating generic classes. + // Since we just ignore any class that has '<' in it. + foreach (var matchingMethod in matchingParentMethods) { + var parent = matchingMethod.DeclaringType; + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { + continue; + } + + methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + break; + } } - // Forces compiler to start at the global namespace - parentName = "global::" + parentName; + return methodDeclaration; + }).Where(method => method != null).Select(method => method!).ToArray()); - baseTypes.Add(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName(parentName))); - usingTypes.Add(parent); - break; - } + if (internalFieldDeclarations.Count > 0) { + typeDeclaration = typeDeclaration.AddMembers(internalFieldDeclarations.ToArray()); + } - if (baseTypes.Count > 0 && typeDeclaration != null) { - if (wantsAbstractInstead && typeDeclaration is ClassDeclarationSyntax) { - typeDeclaration = (typeDeclaration as ClassDeclarationSyntax)?.AddBaseListTypes(baseTypes.ToArray()); - } else if (typeDeclaration is InterfaceDeclarationSyntax) { - typeDeclaration = (typeDeclaration as InterfaceDeclarationSyntax)?.AddBaseListTypes(baseTypes.ToArray()); - } - } + if (baseTypes.Count > 0 && typeDeclaration != null) { + refTypeFieldDecl = refTypeFieldDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + //fieldDeclaration2 = fieldDeclaration2.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + + typeDeclaration = (typeDeclaration as InterfaceDeclarationSyntax)?.AddBaseListTypes(baseTypes.ToArray()); + } + + if (typeDeclaration != null) { + typeDeclaration = typeDeclaration.AddMembers(refTypeFieldDecl); + //typeDeclaration = typeDeclaration.AddMembers(fieldDeclaration2); } return GenerateNestedTypes(); @@ -308,10 +467,6 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy continue; } - if (nestedTypeName.Contains("WrappedArrayContainer")) { - continue; - } - if (nestedTypeName.Split('.').Last() == "file") { nestedTypeName = nestedTypeName.Replace("file", "@file"); } @@ -320,6 +475,21 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy if (nestedT.IsEnum()) { var nestedEnumGenerator = new EnumGenerator(nestedTypeName.Split('.').Last(), nestedT); + // Generate array type(s) + if (AssemblyGenerator.typesWithArrayTypes.Contains(nestedT) && AssemblyGenerator.elementTypesToTypes.ContainsKey(nestedT)) { + var arrayType = AssemblyGenerator.elementTypesToTypes[nestedT]; + var arrayTypeName = AssemblyGenerator.typeRenames[arrayType]; + + var arrayClassGenerator = new ClassGenerator( + arrayTypeName, + arrayType + ); + + if (arrayClassGenerator.TypeDeclaration != null) { + this.Update(this.typeDeclaration.AddMembers(arrayClassGenerator.TypeDeclaration)); + } + } + if (nestedEnumGenerator.EnumDeclaration != null) { this.Update(this.typeDeclaration.AddMembers(nestedEnumGenerator.EnumDeclaration)); } @@ -327,20 +497,9 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy continue; } - // Class - HashSet nestedMethods = []; - - foreach (var method in nestedT.Methods) { - if (!nestedMethods.Select(nestedMethod => nestedMethod.Name).Contains(method.Name)) { - if (method.DeclaringType == nestedT) { - nestedMethods.Add(method); - } - } - } - - var nestedGenerator = new ClassGenerator(nestedTypeName.Split('.').Last(), - nestedT, - [.. nestedMethods] + var nestedGenerator = new ClassGenerator( + nestedTypeName.Split('.').Last(), + nestedT ); if (nestedGenerator.TypeDeclaration == null) { @@ -376,6 +535,21 @@ [.. nestedMethods] } } + // Generate array type(s) + if (AssemblyGenerator.typesWithArrayTypes.Contains(nestedT) && AssemblyGenerator.elementTypesToTypes.ContainsKey(nestedT)) { + var arrayType = AssemblyGenerator.elementTypesToTypes[nestedT]; + var arrayTypeName = AssemblyGenerator.typeRenames[arrayType]; + + var arrayClassGenerator = new ClassGenerator( + arrayTypeName, + arrayType + ); + + if (arrayClassGenerator.TypeDeclaration != null) { + this.Update(this.typeDeclaration.AddMembers(arrayClassGenerator.TypeDeclaration)); + } + } + if (nestedGenerator.TypeDeclaration != null) { this.Update(this.typeDeclaration.AddMembers(nestedGenerator.TypeDeclaration)); } diff --git a/csharp-api/AssemblyGenerator/EnumGenerator.cs b/csharp-api/AssemblyGenerator/EnumGenerator.cs index 456a260ee..8a2cb203a 100644 --- a/csharp-api/AssemblyGenerator/EnumGenerator.cs +++ b/csharp-api/AssemblyGenerator/EnumGenerator.cs @@ -25,7 +25,7 @@ public EnumDeclarationSyntax? EnumDeclaration { } public EnumGenerator(string enumName, REFrameworkNET.TypeDefinition t) { - this.enumName = enumName; + this.enumName = REFrameworkNET.AssemblyGenerator.FixBadChars(enumName); this.t = t; enumDeclaration = Generate(); diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index fae6eea35..a19c09659 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Threading.Tasks; using System.Collections.Concurrent; +using System.Reflection.Metadata; public class Il2CppDump { class Field { @@ -30,6 +31,7 @@ public Method(REFrameworkNET.Method impl) { public REFrameworkNET.TypeDefinition DeclaringType => Impl.GetDeclaringType(); public bool? Override { get; set;} // Not from JSON + public List MatchingParentMethods = []; } public class Type { @@ -148,12 +150,21 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { continue; } - var parentMethod = parent.GetMethod(method.Name); + /*var parentMethod = parent.GetMethod(method.Name); if (parentMethod != null) { methodExtensions.Add(method, new Method(method) { Override = true }); + }*/ + + var matchingParentMethods = method.GetMatchingParentMethods(); + + if (matchingParentMethods.Count > 0) { + methodExtensions.Add(method, new Method(method) { + Override = true, + MatchingParentMethods = matchingParentMethods + }); } } } @@ -167,12 +178,63 @@ public class AssemblyGenerator { // Start with an empty CompilationUnitSyntax (represents an empty file) //static CompilationUnitSyntax compilationUnit = SyntaxFactory.CompilationUnit(); + static readonly char[] invalidChars = [ + '<', + '>', + ',', + '!', + ' ', + ]; + + static readonly char[] invalidGenericChars = [ + '<', + '>', + ',', + '!', + ' ', + '`', + ]; + + public static string FixBadChars(string name) { + // Find the first <, and the last >, replace any dots in between with underscores + /*int first = name.IndexOf('<'); + int last = name.LastIndexOf('>'); + + if (first != -1 && last != -1) { + name = name.Substring(0, first) + name.Substring(first, last - first).Replace('.', '_') + name.Substring(last); + } + + // Replace any invalid characters with underscores + foreach (var c in invalidGenericChars) { + name = name.Replace(c, '_'); + }*/ + + return name; + } + + /*public static string FixBadCharsForGeneric(string name) { + // Find the first <, and the last >, replace any dots in between with underscores + int first = name.IndexOf('<'); + int last = name.LastIndexOf('>'); + + if (first != -1 && last != -1) { + name = name.Substring(0, first) + name.Substring(first, last - first).Replace('.', '_') + name.Substring(last); + } + + // Replace any invalid characters with underscores + foreach (var c in invalidGenericChars) { + name = name.Replace(c, '_'); + } + + return name; + }*/ + public static string CorrectTypeName(string fullName) { if (fullName.StartsWith("System.") || fullName.StartsWith("Internal.")) { return "_" + fullName; } - return fullName; + return FixBadChars(fullName); } static public NamespaceDeclarationSyntax? ExtractNamespaceFromType(REFrameworkNET.TypeDefinition t) { @@ -191,63 +253,134 @@ public static string CorrectTypeName(string fullName) { } return value; - } else { - Console.WriteLine("Failed to extract namespace from " + t.GetFullName()); + } + + //Console.WriteLine("Failed to extract namespace from " + t.GetFullName()); + if (!namespaces.TryGetValue("_", out NamespaceDeclarationSyntax? value2)) { + value2 = SyntaxTreeBuilder.CreateNamespace("_"); + namespaces["_"] = value2; } - return null; + return value2; } public static SortedSet validTypes = []; public static SortedSet generatedTypes = []; - static void FillValidEntries(REFrameworkNET.TDB context) { - if (validTypes.Count > 0) { - return; + // Array of System.Array derived types + public static List arrayTypes = []; + public static HashSet typesWithArrayTypes = []; + public static Dictionary elementTypesToTypes = []; + + public static Dictionary typeRenames = []; + public static Dictionary typeFullRenames = []; + public static readonly REFrameworkNET.TypeDefinition SystemArrayT = REFrameworkNET.API.GetTDB().GetType("System.Array"); + + private static bool HandleArrayType(REFrameworkNET.TypeDefinition t) { + var rtType = t.GetRuntimeType(); + + if (rtType == null) { + return false; } - foreach (REFrameworkNET.TypeDefinition t in context.Types) { - var asdf = t.GetFullName(); + var elementType = (rtType as dynamic).GetElementType(); + + if (elementType == null) { + return false; + } + + var elementTypeDef = elementType.get_TypeHandle(); + + if (elementTypeDef == null) { + Console.WriteLine("Failed to get type handle for array element type"); + return false; + } + + typesWithArrayTypes.Add(elementTypeDef); + elementTypesToTypes[elementTypeDef] = t; + + // Check if the element type is a System.Array derived type + if (elementTypeDef.IsDerivedFrom(SystemArrayT)) { + if (HandleArrayType(elementTypeDef)) { + typeRenames[t] = typeRenames[elementTypeDef] + "_Array"; + } else { + typeRenames[t] = elementTypeDef.Name + "_Array"; + } + + if (typeFullRenames.ContainsKey(elementTypeDef)) { + typeFullRenames[t] = typeFullRenames[elementTypeDef] + "_Array"; + } + } else { + typeRenames[t] = elementTypeDef.Name + "_Array"; + typeFullRenames[t] = elementTypeDef.GetFullName() + "_Array"; + + if (typeFullRenames.ContainsKey(elementTypeDef)) { + typeFullRenames[t] = typeFullRenames[elementTypeDef] + "_Array"; + } } - ConcurrentBag threadSafeValidTypes = []; + return true; + } + + static void FillValidEntries(REFrameworkNET.TDB context) { + if (validTypes.Count > 0) { + return; + } context.GetType(0).GetFullName(); // initialize the types - Parallel.For(0, context.GetNumTypes(), i => { - var t = context.GetType((uint)i); + foreach (REFrameworkNET.TypeDefinition t in context.Types) { + //var t = context.GetType((uint)i); var typeName = t.GetFullName(); if (typeName.Length == 0) { - Console.WriteLine("Bad type name @ " + i); - return; + Console.WriteLine("Bad type name"); + continue; } - if (typeName.Contains("WrappedArrayContainer")) { - return; + // Generics and arrays not yet supported + if (typeName.Contains("[[") /*|| typeName.Contains("]")*/ || typeName.Contains('!')) { + continue; } - // Generics and arrays not yet supported - if (typeName.Contains("[") || typeName.Contains("]") || typeName.Contains('<') || typeName.Contains('!')) { - return; + if (typeName.Contains('<') && !t.IsGenericTypeDefinition()) { + continue; } - // Dont worry about global namespace types for now... if (t.Namespace == null || t.Namespace.Length == 0) { - return; + if (typeName.Length == 0) { + continue; + } + + if (t.DeclaringType == null) { + typeFullRenames[t] = "_." + t.GetFullName(); + } else { + var lastDeclaringType = t.DeclaringType; + + while (lastDeclaringType.DeclaringType != null) { + lastDeclaringType = lastDeclaringType.DeclaringType; + } + + if (lastDeclaringType.Namespace == null || lastDeclaringType.Namespace.Length == 0) { + typeFullRenames[t] = "_." + t.GetFullName(); + } + } } - // Skip system types - // TODO: Fix this - /*if (typeName.StartsWith("System.")) { - //return; - threadSafeValidTypes.Add("_" + typeName); - } else {*/ - threadSafeValidTypes.Add(typeName); - //} - }); - - foreach (var typeName in threadSafeValidTypes) { + if (t.IsDerivedFrom(SystemArrayT)) { + if (true) { + continue; // TODO + } + + arrayTypes.Add(t); + + HandleArrayType(t); + } + + /*if (t.IsGenericType() && !t.IsGenericTypeDefinition()) { + continue; + }*/ + validTypes.Add(typeName); } } @@ -277,6 +410,16 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin generatedTypes.Add(typeName); + // do not generate array types directly, we do it manually per element type + if (typeName.Contains("[]")) { + Console.WriteLine("Skipping array type " + typeName); + return compilationUnit; + } + + if (typeRenames.TryGetValue(t, out string? renamedTypeName)) { + typeName = renamedTypeName; + } + if (t.IsEnum()) { var generator = new EnumGenerator(typeName, t); @@ -292,28 +435,36 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin } else { Console.WriteLine("Failed to create namespace for " + typeName); } + + // Generate array type(s) + if (typesWithArrayTypes.Contains(t) && elementTypesToTypes.ContainsKey(t)) { + var arrayType = elementTypesToTypes[t]; + var arrayTypeName = typeFullRenames[arrayType]; + + var arrayClassGenerator = new ClassGenerator( + arrayTypeName, + arrayType + ); + + if (arrayClassGenerator.TypeDeclaration == null) { + return compilationUnit; + } + + // We can re-use the namespace from the original type + if (generatedNamespace != null) { + var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, arrayClassGenerator.TypeDeclaration); + compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); + } + } } else { // Generate starting from topmost parent first if (t.ParentType != null) { compilationUnit = MakeFromTypeEntry(context, t.ParentType.FullName ?? "", t.ParentType); } - // Make methods a SortedSet of method names - HashSet methods = []; - - foreach (var method in t.Methods) { - //methods.Add(method); - if (!methods.Select(m => m.Name).Contains(method.Name)) { - if (method.DeclaringType == t) { // really important - methods.Add(method); - } - } - } - var generator = new ClassGenerator( typeName.Split('.').Last() == "file" ? typeName.Replace("file", "@file") : typeName, - t, - [.. methods] + t ); if (generator.TypeDeclaration == null) { @@ -328,6 +479,27 @@ [.. methods] } else { Console.WriteLine("Failed to create namespace for " + typeName); } + + // Generate array type(s) + if (typesWithArrayTypes.Contains(t) && elementTypesToTypes.ContainsKey(t)) { + var arrayType = elementTypesToTypes[t]; + var arrayTypeName = typeFullRenames[arrayType]; + + var arrayClassGenerator = new ClassGenerator( + arrayTypeName, + arrayType + ); + + if (arrayClassGenerator.TypeDeclaration == null) { + return compilationUnit; + } + + // We can re-use the namespace from the original type + if (generatedNamespace != null) { + var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, arrayClassGenerator.TypeDeclaration); + compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); + } + } } return compilationUnit; @@ -388,7 +560,7 @@ [.. methods] var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); foreach (var cu in compilationUnits) { - syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu, syntaxTreeParseOption)); + syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu.NormalizeWhitespace(), syntaxTreeParseOption)); } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); @@ -405,6 +577,9 @@ [.. methods] MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location), MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.ComponentModel.DescriptionAttribute).Assembly.Location), + MetadataReference.CreateFromFile(typeof(REFrameworkNET.API).Assembly.Location), + MetadataReference.CreateFromFile(typeof(System.Dynamic.DynamicObject).Assembly.Location), }; if (systemRuntimePath != null) { @@ -423,7 +598,8 @@ [.. methods] var csoptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release, assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default, - platform: Platform.X64); + platform: Platform.X64, + allowUnsafe: true); // Create a compilation CSharpCompilation compilation = CSharpCompilation.Create(strippedAssemblyName) .WithOptions(csoptions) @@ -450,6 +626,7 @@ [.. methods] var errorLineNumber = lineSpan.StartLinePosition.Line; var errorLineText = textLines?[errorLineNumber].ToString(); Console.WriteLine($"Error in line {errorLineNumber + 1}: {errorLineText}"); + //Console.WriteLine(diagnostic.Location.SourceTree?.GetText()); //Console.WriteLine( //$"Error in line {errorLineNumber + 1}: {lineSpan.StartLinePosition.Character + 1} - {lineSpan.EndLinePosition.Character + 1}"); } diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 7947f8b6c..c2e881cd2 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -92,6 +92,10 @@ public static void Entry() { REFrameworkNET.API.LogInfo("DevUtil: " + devUtilT.GetFullName()); REFrameworkNET.API.LogInfo("Dialog: " + dialogT.GetFullName()); + + var mouse = REFrameworkNET.API.GetNativeSingletonT(); + + mouse.set_ShowCursor(false); } public static void TryEnableFrameGeneration() { @@ -125,12 +129,12 @@ public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field var t = field.GetType(); string tName = t != null ? t.GetFullName() : "null"; - var unified = obj as REFrameworkNET.UnifiedObject; + var unified = obj != null ? obj as REFrameworkNET.UnifiedObject : null; ulong address = unified != null ? unified.GetAddress() : 0; if (field.IsStatic()) { address = field.GetDataRaw(obj.GetAddress(), false); - } else { + } else if (obj != null) { address += field.GetOffsetFromBase(); } @@ -156,6 +160,16 @@ public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field ImGui.Text("0x" + field.GetOffsetFromBase().ToString("X")); } + if (obj == null) { + if (made) { + ImGui.Text("Value: null"); + ImGui.TreePop(); + } + + ImGui.PopID(); + return; + } + if (made) { if (!t.IsValueType()) { var objValue = obj.GetField(field.GetName()) as REFrameworkNET.IObject; @@ -245,7 +259,65 @@ public static void DisplayMethod(REFrameworkNET.IObject obj, REFrameworkNET.Meth } } + public static void DisplayType(REFrameworkNET.TypeDefinition t) { + ImGui.Text("Name: " + t.GetFullName()); + ImGui.Text("Namespace: " + t.GetNamespace()); + + if (t.DeclaringType != null) { + var made = ImGui.TreeNodeEx("Declaring Type: ", ImGuiNET.ImGuiTreeNodeFlags.SpanFullWidth); + ImGui.SameLine(0.0f, 0.0f); + ImGui.TextColored(TYPE_COLOR, t.DeclaringType.GetFullName()); + if (made) { + DisplayType(t.DeclaringType); + ImGui.TreePop(); + } + } + + if (t.ParentType != null) { + var made = ImGui.TreeNodeEx("Parent Type: ", ImGuiNET.ImGuiTreeNodeFlags.SpanFullWidth); + ImGui.SameLine(0.0f, 0.0f); + ImGui.TextColored(TYPE_COLOR, t.ParentType.GetFullName()); + if (made) { + DisplayType(t.ParentType); + ImGui.TreePop(); + } + } + + var runtimeTypeRaw = t.GetRuntimeType(); + + if (runtimeTypeRaw != null) { + var runtimeType = runtimeTypeRaw.As<_System.Type>(); + var assembly = runtimeType.get_Assembly(); + + if (assembly != null) { + if (ImGui.TreeNode("Assembly: " + assembly.get_FullName().Split(',')[0])) { + DisplayObject(assembly as IObject); + ImGui.TreePop(); + } + } + + var baseType = runtimeType.get_BaseType(); + + /*if (baseType != null) { + if (ImGui.TreeNode("Base Type (" + (baseType.get_TypeHandle() as REFrameworkNET.TypeDefinition).FullName + ")")) { + DisplayObject(baseType as IObject); + ImGui.TreePop(); + } + }*/ + + if (ImGui.TreeNode("Runtime Type")) { + DisplayObject(runtimeType as IObject); + ImGui.TreePop(); + } + } + } + public static void DisplayObject(REFrameworkNET.IObject obj) { + if (ImGui.TreeNode("Type Info")) { + DisplayType(obj.GetTypeDefinition()); + ImGui.TreePop(); + } + if (ImGui.TreeNode("Methods")) { var methods = obj.GetTypeDefinition().GetMethods(); @@ -325,6 +397,8 @@ public static void Render() { if (ImGui.TreeNode("Native Singletons")) { RenderNativeSingletons(); } + + DisplayType(REFrameworkNET.API.GetTDB().GetType("JsonParser.Value")); } catch (Exception e) { System.Console.WriteLine(e.ToString()); } From 2b9afb0c4aefc9473281d74fd23396807774b655 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 11:43:27 -0700 Subject: [PATCH 101/207] .NET: Add TypeDefinition.GetElementType --- csharp-api/REFrameworkNET/TypeDefinition.cpp | 16 ++++++++++++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index ebe91ba1e..5595d4a53 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -212,4 +212,20 @@ namespace REFrameworkNET { return result; } + + TypeDefinition^ TypeDefinition::GetElementType() { + auto runtimeType = this->GetRuntimeType(); + + if (runtimeType == nullptr) { + return nullptr; + } + + auto elementType = (ManagedObject^)runtimeType->Call("GetElementType"); + + if (elementType == nullptr) { + return nullptr; + } + + return (TypeDefinition^)elementType->Call("get_TypeHandle"); + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index b965df70b..be4359ed9 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -314,6 +314,14 @@ public } } + TypeDefinition^ GetElementType(); + + property TypeDefinition^ ElementType { + TypeDefinition^ get() { + return GetElementType(); + } + } + /*Void* GetInstance() { return m_type->get_instance(); From a7fc42d60a2042bed4bac91731446d665ef16c11 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 12:49:03 -0700 Subject: [PATCH 102/207] .NET (AssemblyGenerator): Generate array types --- .../AssemblyGenerator/ClassGenerator.cs | 61 ++--- csharp-api/AssemblyGenerator/EnumGenerator.cs | 5 + csharp-api/AssemblyGenerator/Generator.cs | 222 ++++++++++++++---- 3 files changed, 204 insertions(+), 84 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 64dd919e0..57a7420a9 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -46,6 +46,7 @@ public class ClassGenerator { private List methods = []; public List usingTypes = []; private TypeDeclarationSyntax? typeDeclaration; + private bool addedNewKeyword = false; public TypeDeclarationSyntax? TypeDeclaration { get { @@ -53,6 +54,12 @@ public TypeDeclarationSyntax? TypeDeclaration { } } + public bool AddedNewKeyword { + get { + return addedNewKeyword; + } + } + public void Update(TypeDeclarationSyntax? typeDeclaration_) { typeDeclaration = typeDeclaration_; } @@ -114,8 +121,8 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy return outSyntax; } - if (AssemblyGenerator.typeFullRenames.ContainsKey(targetType) && !targetType.IsDerivedFrom(AssemblyGenerator.SystemArrayT)) { - targetTypeName = AssemblyGenerator.typeFullRenames[targetType]; + if (AssemblyGenerator.typeFullRenames.TryGetValue(targetType, out string? value)) { + targetTypeName = value; } // Check for easily convertible types like System.Single, System.Int32, etc. @@ -225,6 +232,12 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy return null; } + // Check if we need to add the new keyword to this. + if (AssemblyGenerator.NestedTypeExistsInParent(t)) { + typeDeclaration = typeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + addedNewKeyword = true; + } + // Set up base types List baseTypes = []; @@ -441,7 +454,7 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy if (typeDeclaration != null) { typeDeclaration = typeDeclaration.AddMembers(refTypeFieldDecl); - //typeDeclaration = typeDeclaration.AddMembers(fieldDeclaration2); + //typeDeclaration = typeDeclaration.AddMembers(refProxyFieldDecl); } return GenerateNestedTypes(); @@ -475,9 +488,7 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy if (nestedT.IsEnum()) { var nestedEnumGenerator = new EnumGenerator(nestedTypeName.Split('.').Last(), nestedT); - // Generate array type(s) - if (AssemblyGenerator.typesWithArrayTypes.Contains(nestedT) && AssemblyGenerator.elementTypesToTypes.ContainsKey(nestedT)) { - var arrayType = AssemblyGenerator.elementTypesToTypes[nestedT]; + AssemblyGenerator.ForEachArrayType(nestedT, (arrayType) => { var arrayTypeName = AssemblyGenerator.typeRenames[arrayType]; var arrayClassGenerator = new ClassGenerator( @@ -488,7 +499,7 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy if (arrayClassGenerator.TypeDeclaration != null) { this.Update(this.typeDeclaration.AddMembers(arrayClassGenerator.TypeDeclaration)); } - } + }); if (nestedEnumGenerator.EnumDeclaration != null) { this.Update(this.typeDeclaration.AddMembers(nestedEnumGenerator.EnumDeclaration)); @@ -506,38 +517,7 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy continue; } - var isolatedNestedName = nestedTypeName.Split('.').Last(); - - bool addedNew = false; - // Add the "new" keyword if this nested type is anywhere in the hierarchy - for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { - if (addedNew) { - break; - } - - // TODO: Fix this - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { - continue; - } - - var parentNestedTypes = Il2CppDump.GetTypeExtension(parent)?.NestedTypes; - - // Look for same named nested types - if (parentNestedTypes != null) { - foreach (var parentNested in parentNestedTypes) { - var isolatedParentNestedName = parentNested.FullName?.Split('.').Last(); - if (isolatedParentNestedName == isolatedNestedName) { - nestedGenerator.Update(nestedGenerator.TypeDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword))); - addedNew = true; - break; - } - } - } - } - - // Generate array type(s) - if (AssemblyGenerator.typesWithArrayTypes.Contains(nestedT) && AssemblyGenerator.elementTypesToTypes.ContainsKey(nestedT)) { - var arrayType = AssemblyGenerator.elementTypesToTypes[nestedT]; + AssemblyGenerator.ForEachArrayType(nestedT, (arrayType) => { var arrayTypeName = AssemblyGenerator.typeRenames[arrayType]; var arrayClassGenerator = new ClassGenerator( @@ -546,9 +526,10 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy ); if (arrayClassGenerator.TypeDeclaration != null) { + //this.Update(this.typeDeclaration.AddMembers(arrayClassGenerator.TypeDeclaration)); this.Update(this.typeDeclaration.AddMembers(arrayClassGenerator.TypeDeclaration)); } - } + }); if (nestedGenerator.TypeDeclaration != null) { this.Update(this.typeDeclaration.AddMembers(nestedGenerator.TypeDeclaration)); diff --git a/csharp-api/AssemblyGenerator/EnumGenerator.cs b/csharp-api/AssemblyGenerator/EnumGenerator.cs index 8a2cb203a..4702889cd 100644 --- a/csharp-api/AssemblyGenerator/EnumGenerator.cs +++ b/csharp-api/AssemblyGenerator/EnumGenerator.cs @@ -47,6 +47,11 @@ public void Update(EnumDeclarationSyntax? typeDeclaration) { enumDeclaration = SyntaxFactory.EnumDeclaration(enumName) .AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + // Check if we need to add the new keyword to this. + if (AssemblyGenerator.NestedTypeExistsInParent(t)) { + enumDeclaration = enumDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + } + if (t.HasAttribute(s_FlagsAttribute, true)) { enumDeclaration = enumDeclaration.AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName("System.FlagsAttribute")))); } diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index a19c09659..c1320568b 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -270,52 +270,200 @@ public static string CorrectTypeName(string fullName) { // Array of System.Array derived types public static List arrayTypes = []; public static HashSet typesWithArrayTypes = []; - public static Dictionary elementTypesToTypes = []; + private static Dictionary> elementTypesToArrayTypes = []; public static Dictionary typeRenames = []; public static Dictionary typeFullRenames = []; public static readonly REFrameworkNET.TypeDefinition SystemArrayT = REFrameworkNET.API.GetTDB().GetType("System.Array"); - private static bool HandleArrayType(REFrameworkNET.TypeDefinition t) { - var rtType = t.GetRuntimeType(); + public static void ForEachArrayType( + TypeDefinition elementType, Action action, + HashSet? visited = null, + HashSet? visitedArrayTypes = null + ) + { + if (visited == null) { + visited = new HashSet(); + } - if (rtType == null) { - return false; + if (visitedArrayTypes == null) { + visitedArrayTypes = new HashSet(); + } + + if (visited.Contains(elementType)) { + return; } - var elementType = (rtType as dynamic).GetElementType(); + visited.Add(elementType); - if (elementType == null) { - return false; + if (!elementTypesToArrayTypes.TryGetValue(elementType, out List? arrayTypes)) { + return; + } + + foreach (var arrayType in arrayTypes) { + if (visitedArrayTypes.Contains(arrayType)) { + continue; + } + + action(arrayType); + visitedArrayTypes.Add(arrayType); + + // Array types can have array types themselves. + ForEachArrayType(arrayType, action, visited); + } + } + + public static REFrameworkNET.TypeDefinition? GetEquivalentNestedTypeInParent(REFrameworkNET.TypeDefinition nestedT) { + var isolatedNestedName = nestedT.FullName?.Split('.').Last(); + + if (nestedT.DeclaringType == null && nestedT.IsDerivedFrom(SystemArrayT)) { + // Types derived from System.Array do not have a proper declaring type + // so we need to get the element type and find the declaring type of that + TypeDefinition? elementType = nestedT.GetElementType(); + + while (elementType != null && elementType.IsDerivedFrom(SystemArrayT)) { + elementType = elementType.GetElementType(); + } + + if (elementType != null) { + var equivalentElementType = GetEquivalentNestedTypeInParent(elementType); + + if (equivalentElementType != null) { + // Now go through all possible array types for that equivalent type + TypeDefinition? equivalentArray = null; + ForEachArrayType(equivalentElementType, (arrayType) => { + if (equivalentArray != null) { + return; + } + + var isolatedArrayTypeName = arrayType.FullName?.Split('.').Last(); + + if (isolatedArrayTypeName == isolatedNestedName) { + System.Console.WriteLine("Found equivalent array type for " + nestedT.FullName); + equivalentArray = arrayType; + return; + } + }); + } + } + } + + var t = nestedT.DeclaringType; + + if (t == null) { + return null; + } + + // Add the "new" keyword if this nested type is anywhere in the hierarchy + for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { + // TODO: Fix this + if (!validTypes.Contains(parent.FullName)) { + continue; + } + + var parentNestedTypes = Il2CppDump.GetTypeExtension(parent)?.NestedTypes; + + // Look for same named nested types + if (parentNestedTypes != null) { + foreach (var parentNested in parentNestedTypes) { + var isolatedParentNestedName = parentNested.FullName?.Split('.').Last(); + if (isolatedParentNestedName == isolatedNestedName) { + return parentNested; + } + } + } } - var elementTypeDef = elementType.get_TypeHandle(); + return null; + } + + public static bool NestedTypeExistsInParent(REFrameworkNET.TypeDefinition nestedT) { + return GetEquivalentNestedTypeInParent(nestedT) != null; + } + + private static string? GetOptionalPrefix(REFrameworkNET.TypeDefinition t) { + if (t.Namespace == null || t.Namespace.Length == 0) { + if (t.DeclaringType == null) { + return "_."; + } else { + var lastDeclaringType = t.DeclaringType; + + while (lastDeclaringType.DeclaringType != null) { + lastDeclaringType = lastDeclaringType.DeclaringType; + } + + if (lastDeclaringType.Namespace == null || lastDeclaringType.Namespace.Length == 0) { + return "_."; + } + } + } + + return null; + } + + private static string MakePrefixedTypeName(REFrameworkNET.TypeDefinition t) { + var prefix = GetOptionalPrefix(t); + + if (prefix != null) { + return prefix + t.GetFullName(); + } + + return t.GetFullName(); + } + + private static bool HandleArrayType(REFrameworkNET.TypeDefinition t) { + var elementTypeDef = t.GetElementType(); - if (elementTypeDef == null) { - Console.WriteLine("Failed to get type handle for array element type"); + if (elementTypeDef == null || !t.IsDerivedFrom(SystemArrayT)) { return false; } + string arrayDims = "1D"; + + // Look for the last "[]" in the type name + // however we can have stuff like "[,]" or "[,,]" etc + var tFullName = t.GetFullName(); + var lastDims = tFullName.LastIndexOf('['); + var lastDimsEnd = tFullName.LastIndexOf(']'); + + if (lastDims != -1 && lastDimsEnd != -1) { + // Count how many , there are + var dimCount = 0; + + for (int i = lastDims+1; i < lastDimsEnd; i++) { + if (tFullName[i] == ',') { + dimCount++; + } + } + + arrayDims = (dimCount + 1).ToString() + "D"; + } + typesWithArrayTypes.Add(elementTypeDef); - elementTypesToTypes[elementTypeDef] = t; + + if (!elementTypesToArrayTypes.ContainsKey(elementTypeDef)) { + elementTypesToArrayTypes[elementTypeDef] = []; + } + + elementTypesToArrayTypes[elementTypeDef].Add(t); // Check if the element type is a System.Array derived type if (elementTypeDef.IsDerivedFrom(SystemArrayT)) { if (HandleArrayType(elementTypeDef)) { - typeRenames[t] = typeRenames[elementTypeDef] + "_Array"; + typeRenames[t] = typeRenames[elementTypeDef] + "_Array" + arrayDims; } else { - typeRenames[t] = elementTypeDef.Name + "_Array"; + typeRenames[t] = elementTypeDef.Name + "_Array" + arrayDims; } if (typeFullRenames.ContainsKey(elementTypeDef)) { - typeFullRenames[t] = typeFullRenames[elementTypeDef] + "_Array"; + typeFullRenames[t] = typeFullRenames[elementTypeDef] + "_Array" + arrayDims; } } else { - typeRenames[t] = elementTypeDef.Name + "_Array"; - typeFullRenames[t] = elementTypeDef.GetFullName() + "_Array"; + typeRenames[t] = elementTypeDef.Name + "_Array" + arrayDims; + typeFullRenames[t] = MakePrefixedTypeName(elementTypeDef) + "_Array" + arrayDims; if (typeFullRenames.ContainsKey(elementTypeDef)) { - typeFullRenames[t] = typeFullRenames[elementTypeDef] + "_Array"; + typeFullRenames[t] = typeFullRenames[elementTypeDef] + "_Array" + arrayDims; } } @@ -352,26 +500,14 @@ static void FillValidEntries(REFrameworkNET.TDB context) { continue; } - if (t.DeclaringType == null) { - typeFullRenames[t] = "_." + t.GetFullName(); - } else { - var lastDeclaringType = t.DeclaringType; + var optionalPrefix = GetOptionalPrefix(t); - while (lastDeclaringType.DeclaringType != null) { - lastDeclaringType = lastDeclaringType.DeclaringType; - } - - if (lastDeclaringType.Namespace == null || lastDeclaringType.Namespace.Length == 0) { - typeFullRenames[t] = "_." + t.GetFullName(); - } + if (optionalPrefix != null) { + typeFullRenames[t] = optionalPrefix + t.GetFullName(); } } if (t.IsDerivedFrom(SystemArrayT)) { - if (true) { - continue; // TODO - } - arrayTypes.Add(t); HandleArrayType(t); @@ -416,7 +552,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin return compilationUnit; } - if (typeRenames.TryGetValue(t, out string? renamedTypeName)) { + if (typeFullRenames.TryGetValue(t, out string? renamedTypeName)) { typeName = renamedTypeName; } @@ -436,9 +572,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin Console.WriteLine("Failed to create namespace for " + typeName); } - // Generate array type(s) - if (typesWithArrayTypes.Contains(t) && elementTypesToTypes.ContainsKey(t)) { - var arrayType = elementTypesToTypes[t]; + ForEachArrayType(t, (arrayType) => { var arrayTypeName = typeFullRenames[arrayType]; var arrayClassGenerator = new ClassGenerator( @@ -447,7 +581,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin ); if (arrayClassGenerator.TypeDeclaration == null) { - return compilationUnit; + return; } // We can re-use the namespace from the original type @@ -455,7 +589,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, arrayClassGenerator.TypeDeclaration); compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); } - } + }); } else { // Generate starting from topmost parent first if (t.ParentType != null) { @@ -480,18 +614,18 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin Console.WriteLine("Failed to create namespace for " + typeName); } - // Generate array type(s) - if (typesWithArrayTypes.Contains(t) && elementTypesToTypes.ContainsKey(t)) { - var arrayType = elementTypesToTypes[t]; + ForEachArrayType(t, (arrayType) => { var arrayTypeName = typeFullRenames[arrayType]; + System.Console.WriteLine("Generating array type " + arrayTypeName + " from " + t.FullName); + var arrayClassGenerator = new ClassGenerator( arrayTypeName, arrayType ); if (arrayClassGenerator.TypeDeclaration == null) { - return compilationUnit; + return; } // We can re-use the namespace from the original type @@ -499,7 +633,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var myNamespace = SyntaxTreeBuilder.AddMembersToNamespace(generatedNamespace, arrayClassGenerator.TypeDeclaration); compilationUnit = SyntaxTreeBuilder.AddMembersToCompilationUnit(compilationUnit, myNamespace); } - } + }); } return compilationUnit; From 5443d1f36cbf164018bbe20a74acb25bacbed8e0 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 18:07:41 -0700 Subject: [PATCH 103/207] .NET: Attempt to use .NET 8.0 ref assemblies when possible --- csharp-api/AssemblyGenerator/Generator.cs | 23 +------- csharp-api/Compiler/Compiler.cs | 64 ++++++++++++++++++----- 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index c1320568b..548f5e444 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -698,28 +698,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); - - // get all DLLs in that directory - var dlls = assemblyPath != null? Directory.GetFiles(assemblyPath, "*.dll") : []; - - var systemRuntimePath = dlls.FirstOrDefault(dll => dll.ToLower().EndsWith("system.runtime.dll")); - - var references = new List { - MetadataReference.CreateFromFile(typeof(object).Assembly.Location), - MetadataReference.CreateFromFile(typeof(void).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.NotImplementedException).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Linq.Enumerable).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.ComponentModel.DescriptionAttribute).Assembly.Location), - MetadataReference.CreateFromFile(typeof(REFrameworkNET.API).Assembly.Location), - MetadataReference.CreateFromFile(typeof(System.Dynamic.DynamicObject).Assembly.Location), - }; - - if (systemRuntimePath != null) { - System.Console.WriteLine("Adding System.Runtime from " + systemRuntimePath); - references.Add(MetadataReference.CreateFromFile(systemRuntimePath)); - } + var references = REFrameworkNET.Compiler.GenerateExhaustiveMetadataReferences(typeof(REFrameworkNET.API).Assembly, new List()); // Add the previous compilations as references foreach (var compilationbc in previousCompilations) { diff --git a/csharp-api/Compiler/Compiler.cs b/csharp-api/Compiler/Compiler.cs index 452e239f9..867e5a807 100644 --- a/csharp-api/Compiler/Compiler.cs +++ b/csharp-api/Compiler/Compiler.cs @@ -50,31 +50,60 @@ static public byte[] Compile(string filepath, Assembly executingAssembly, List deps) - { - var codeString = SourceText.From(sourceCode); - var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); - string assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); - // get all DLLs in that directory - var dlls = Directory.GetFiles(assemblyPath, "*.dll"); + public static List GenerateExhaustiveMetadataReferences(Assembly executingAssembly, List deps) { + string assemblyPath = System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory(); + string[] dlls = []; - var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options); + System.Console.WriteLine("assemblyPath: " + assemblyPath); + + string targetFramework = System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription.Split(' ')[1].TrimStart('v'); + System.Console.WriteLine("targetFramework: " + targetFramework); + + // Extract only the major + minor out + string moniker = string.Concat("net", targetFramework.AsSpan(0, targetFramework.LastIndexOf('.'))); + System.Console.WriteLine("moniker: " + moniker); + + // Go backwards from assemblyPath to construct the baseRefDir path + string dotnetDirectory = Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(assemblyPath)))); // least hacky code am i right + string baseRefDir = Path.Combine(dotnetDirectory, "packs", "Microsoft.NETCore.App.Ref"); + + if (Directory.Exists(baseRefDir)) { + var refFolder = Path.Combine(baseRefDir, targetFramework, "ref", moniker); + + System.Console.WriteLine("Looking for reference assemblies in " + refFolder); + + if (Directory.Exists(refFolder)) { + System.Console.WriteLine("Found reference assemblies in " + refFolder); + dlls = Directory.GetFiles(refFolder, "*.dll"); + } + } else { + Console.WriteLine("No reference assemblies found in " + baseRefDir); + } + + if (dlls.Length == 0) { + Console.WriteLine("No reference assemblies found in " + baseRefDir + ". Falling back to implementation assemblies in " + assemblyPath); + + dlls = Directory.GetFiles(assemblyPath, "*.dll"); + } var referencesStr = new System.Collections.Generic.SortedSet { - typeof(object).Assembly.Location, + /*typeof(object).Assembly.Location, typeof(Console).Assembly.Location, typeof(System.Linq.Enumerable).Assembly.Location, typeof(System.Runtime.AssemblyTargetedPatchBandAttribute).Assembly.Location, - typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location, - executingAssembly.Location + typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly.Location*/ }; + if (executingAssembly != null) { + referencesStr.Add(executingAssembly.Location); + } + // Add all the dependencies to the references foreach (var dep in deps) { @@ -112,7 +141,16 @@ private static CSharpCompilation GenerateCode(string sourceCode, string filePath return false; }); - var references = referencesStr.Select(r => MetadataReference.CreateFromFile(r)).ToArray(); + return referencesStr.Select(r => MetadataReference.CreateFromFile(r)).ToList(); + } + + private static CSharpCompilation GenerateCode(string sourceCode, string filePath, Assembly executingAssembly, List deps) + { + var codeString = SourceText.From(sourceCode); + var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); + var parsedSyntaxTree = SyntaxFactory.ParseSyntaxTree(codeString, options); + + var references = GenerateExhaustiveMetadataReferences(executingAssembly, deps); foreach (var reference in references) { From b0a17475d052b4bc52ad36ea9823feca9a08c936 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 19:04:38 -0700 Subject: [PATCH 104/207] .NET: Keep ref assemblies in their own generated folder to be replaced --- csharp-api/AssemblyGenerator/Generator.cs | 8 ++-- csharp-api/REFrameworkNET/PluginManager.cpp | 45 +++++++++------------ csharp-api/REFrameworkNET/PluginManager.hpp | 1 + 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 548f5e444..8ee72b33b 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -523,7 +523,6 @@ static void FillValidEntries(REFrameworkNET.TDB context) { static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, string typeName, REFrameworkNET.TypeDefinition? t) { var compilationUnit = SyntaxFactory.CompilationUnit(); - FillValidEntries(context); if (!validTypes.Contains(typeName)) { return compilationUnit; @@ -669,6 +668,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin List compilationUnits = []; var tdb = REFrameworkNET.API.GetTDB(); + // Is this parallelizable? foreach (dynamic reEngineT in assembly.GetTypes()) { var th = reEngineT.get_TypeHandle(); @@ -694,7 +694,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); foreach (var cu in compilationUnits) { - syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu.NormalizeWhitespace(), syntaxTreeParseOption)); + syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu/*.NormalizeWhitespace()*/, syntaxTreeParseOption)); } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); @@ -769,9 +769,9 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin } public static List MainImpl() { - Il2CppDump.FillTypeExtensions(REFrameworkNET.API.GetTDB()); - var tdb = REFrameworkNET.API.GetTDB(); + Il2CppDump.FillTypeExtensions(tdb); + FillValidEntries(tdb); dynamic appdomainT = tdb.GetType("System.AppDomain"); dynamic appdomain = appdomainT.get_CurrentDomain(); diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index c1a40a4fa..21467d672 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -79,17 +79,16 @@ namespace REFrameworkNET { return false; } - System::Collections::Generic::List^ PluginManager::LoadDependencies() { - REFrameworkNET::API::LogInfo("Loading managed dependencies..."); + List^ PluginManager::LoadAssemblies(System::String^ dependencies_dir) { + REFrameworkNET::API::LogInfo("Loading Assemblies from " + dependencies_dir + "..."); - const auto dependencies_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies"; + //const auto dependencies_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies"; - std::filesystem::create_directories(dependencies_path); + std::filesystem::create_directories(msclr::interop::marshal_as(dependencies_dir)); - auto files = System::IO::Directory::GetFiles(gcnew System::String(dependencies_path.wstring().c_str()), "*.dll"); + auto files = System::IO::Directory::GetFiles(dependencies_dir, "*.dll"); - auto dependencies_dir = gcnew System::String(dependencies_path.wstring().c_str()); - auto assemblies = gcnew System::Collections::Generic::List(); + auto assemblies = gcnew List(); if (files->Length == 0) { REFrameworkNET::API::LogInfo("No dependencies found in " + dependencies_dir); @@ -123,9 +122,17 @@ namespace REFrameworkNET { return assemblies; } - void PluginManager::GenerateReferenceAssemblies(System::Collections::Generic::List^ deps) { + List^ PluginManager::LoadDependencies() { + const auto dependencies_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies"; + return LoadAssemblies(gcnew System::String(dependencies_path.wstring().c_str())); + } + + void PluginManager::GenerateReferenceAssemblies(List^ deps) { REFrameworkNET::API::LogInfo("Generating reference assemblies..."); + auto generatedFolder = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "generated"; + std::filesystem::create_directories(generatedFolder); + // Look for AssemblyGenerator class in the loaded deps for each (System::Reflection::Assembly^ a in deps) { if (auto generator = a->GetType("REFrameworkNET.AssemblyGenerator"); generator != nullptr) { @@ -143,10 +150,10 @@ namespace REFrameworkNET { for each (Compiler::DynamicAssemblyBytecode^ bytes in result) { REFrameworkNET::API::LogInfo("Adding generated assembly to deps..."); - std::string assembly_name = msclr::interop::marshal_as(bytes->AssemblyName); - assembly_name = "REFramework.NET." + assembly_name + ".dll"; + std::string assemblyName = msclr::interop::marshal_as(bytes->AssemblyName); + assemblyName = "REFramework.NET." + assemblyName + ".dll"; - auto path = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "dependencies" / assembly_name; + auto path = generatedFolder / assemblyName; System::IO::File::WriteAllBytes(gcnew System::String(path.wstring().c_str()), bytes->Bytecode); REFrameworkNET::API::LogInfo("Wrote generated assembly to " + gcnew System::String(path.wstring().c_str())); @@ -395,8 +402,6 @@ namespace REFrameworkNET { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); } - - System::Console::WriteLine("Test"); REFrameworkNET::API::LogInfo("Attempting to load plugins from source code..."); const auto plugins_path = std::filesystem::current_path() / "reframework" / "plugins"; @@ -429,20 +434,6 @@ namespace REFrameworkNET { // Compile the C# file, and then call a function in it (REFrameworkPlugin.Main) // This is useful for loading C# plugins that don't want to be compiled into a DLL auto bytecode = REFrameworkNET::Compiler::Compile(file, self, deps); - // Dynamically look for DynamicRun.Builder.Compiler.Compile - /*auto type = intermediary->GetType("DynamicRun.Builder.Compiler"); - if (type == nullptr) { - REFrameworkNET::API::LogError("Failed to get type DynamicRun.Builder.Compiler"); - continue; - } - auto method = type->GetMethod("Compile", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); - - if (method == nullptr) { - REFrameworkNET::API::LogError("Failed to get method DynamicRun.Builder.Compiler.Compile"); - continue; - } - - auto bytecode = (array^)method->Invoke(nullptr, gcnew array{file, self->Location});*/ if (bytecode == nullptr) { REFrameworkNET::API::LogError("Failed to compile " + file); diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 9c007d6c2..2734d9796 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -31,6 +31,7 @@ private ref class PluginManager // meant to be executed in the correct context // after loading "ourselves" via System::Reflection::Assembly::LoadFrom + static System::Collections::Generic::List^ LoadAssemblies(System::String^ path); static System::Collections::Generic::List^ LoadDependencies(); static void GenerateReferenceAssemblies(System::Collections::Generic::List^ deps); static bool LoadPlugins(uintptr_t param_raw); From ed2bc0f43a8c4dbfe95c6180ea93300a7f2c5d87 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 19:41:13 -0700 Subject: [PATCH 105/207] .NET: Fix hooks causing crashes --- csharp-api/REFrameworkNET/Method.hpp | 4 ---- csharp-api/REFrameworkNET/MethodHook.cpp | 9 +++++++- csharp-api/REFrameworkNET/MethodHook.hpp | 26 ++++++++++++++++++++---- csharp-api/test/Test/Test.cs | 6 +++--- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index a50f150a0..d4b9d22c6 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -156,10 +156,6 @@ public ref class Method : public System::IEquatable } } - // More palatable C# versions - delegate PreHookResult REFPreHookDelegate(System::Collections::Generic::List^ args); - delegate void REFPostHookDelegate(); - MethodHook^ AddHook(bool ignore_jmp); ManagedObject^ GetRuntimeMethod(); diff --git a/csharp-api/REFrameworkNET/MethodHook.cpp b/csharp-api/REFrameworkNET/MethodHook.cpp index 4d62e5313..723eea84e 100644 --- a/csharp-api/REFrameworkNET/MethodHook.cpp +++ b/csharp-api/REFrameworkNET/MethodHook.cpp @@ -14,6 +14,8 @@ namespace REFrameworkNET { REFrameworkNET::API::LogInfo("Creating .NET hook for method: " + m_method->GetName()); m_hooks_installed = true; + m_is_static = m_method->IsStatic(); + m_parameters = m_method->GetParameters(); reframework::API::Method* raw = (reframework::API::Method*)m_method->GetRaw(); @@ -27,6 +29,8 @@ namespace REFrameworkNET { return; } + REFrameworkNET::API::LogInfo("Removing .NET hook for method: " + m_method->GetName()); + reframework::API::Method* raw = (reframework::API::Method*)m_method->GetRaw(); m_hooks_installed = false; @@ -35,7 +39,10 @@ namespace REFrameworkNET { int32_t MethodHook::OnPreStart_Raw(int argc, void** argv, REFrameworkTypeDefinitionHandle* arg_tys, unsigned long long ret_addr) { - OnPreStart(gcnew List()); // todo: pass the arguments + // Create a System::Span for the arguments + auto argspan = System::Span((uint64_t*)argv, argc); + + OnPreStart(argspan); System::Console::WriteLine("Hello from" + m_method->GetName() + " pre-hook!"); return 0; // Or another appropriate value } diff --git a/csharp-api/REFrameworkNET/MethodHook.hpp b/csharp-api/REFrameworkNET/MethodHook.hpp index 52d2df82a..e6873a338 100644 --- a/csharp-api/REFrameworkNET/MethodHook.hpp +++ b/csharp-api/REFrameworkNET/MethodHook.hpp @@ -12,6 +12,11 @@ namespace REFrameworkNET { public ref class MethodHook { public: + delegate PreHookResult PreHookDelegate(System::Span args); + delegate void PostHookDelegate(System::Object^% retval); + + //delegate PreHookResult PreHookEasyDelegate(REFrameworkNET::NativeObject^ thisPtr, System::Object^% arg1, System::Object^% arg2, System::Object^% arg3, System::Object^% arg4); + // Public factory method to create a new hook static MethodHook^ Create(Method^ method, bool ignore_jmp) { @@ -27,21 +32,30 @@ public ref class MethodHook return wrapper; } - MethodHook^ AddPre(Method::REFPreHookDelegate^ callback) + MethodHook^ AddPre(PreHookDelegate^ callback) { OnPreStart += callback; return this; } - MethodHook^ AddPost(Method::REFPostHookDelegate^ callback) + MethodHook^ AddPost(PostHookDelegate^ callback) { OnPostStart += callback; return this; } + System::Collections::Generic::List^ GetAllHooks() { + auto out = gcnew System::Collections::Generic::List(); + for each (auto kvp in s_hooked_methods) { + out->Add(kvp.Value); + } + + return out; + } + private: - event Method::REFPreHookDelegate^ OnPreStart; - event Method::REFPostHookDelegate^ OnPostStart; + event PreHookDelegate^ OnPreStart; + event PostHookDelegate^ OnPostStart; // This is never meant to publicly be called @@ -74,6 +88,10 @@ public ref class MethodHook Method^ m_method{}; uint32_t m_hook_id{}; bool m_hooks_installed{false}; + + // Cached info at install time + bool m_is_static{false}; + System::Collections::Generic::List^ m_parameters{}; REFPreHookDelegateRaw^ m_preHookLambda{}; REFPostHookDelegateRaw^ m_postHookLambda{}; diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index c2e881cd2..efa0b34f3 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -8,7 +8,7 @@ using REFrameworkNET.Callbacks; public class DangerousFunctions { - public static REFrameworkNET.PreHookResult isInsidePreHook(System.Object args) { + public static REFrameworkNET.PreHookResult isInsidePreHook(Span args) { //Console.WriteLine("Inside pre hook (From C#) " + args.ToString()); REFrameworkNET.API.LogInfo("isInsidePreHook"); return REFrameworkNET.PreHookResult.Continue; @@ -20,11 +20,11 @@ public static void isInsidePostHook(ref System.Object retval) { public static void Entry() { var tdb = REFrameworkNET.API.GetTDB(); - /*tdb.GetType("app.CameraManager")?. + tdb.GetType("app.CameraManager")?. GetMethod("isInside")?. AddHook(false). AddPre(isInsidePreHook). - AddPost(isInsidePostHook);*/ + AddPost(isInsidePostHook); // These via.SceneManager and via.Scene are // loaded from an external reference assembly From 4f33e39909f8737d5b568655dadb262ae6be558d Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 20:16:09 -0700 Subject: [PATCH 106/207] .NET: Hook feature parity with Lua (skip and retval modification) --- csharp-api/REFrameworkNET/MethodHook.cpp | 10 ++-- csharp-api/REFrameworkNET/MethodHook.hpp | 57 +++++++++++++++++++-- csharp-api/REFrameworkNET/PluginManager.cpp | 11 ---- csharp-api/test/Test/Test.cs | 6 +-- 4 files changed, 60 insertions(+), 24 deletions(-) diff --git a/csharp-api/REFrameworkNET/MethodHook.cpp b/csharp-api/REFrameworkNET/MethodHook.cpp index 723eea84e..2eb3a3289 100644 --- a/csharp-api/REFrameworkNET/MethodHook.cpp +++ b/csharp-api/REFrameworkNET/MethodHook.cpp @@ -37,18 +37,14 @@ namespace REFrameworkNET { raw->remove_hook(m_hook_id); } - int32_t MethodHook::OnPreStart_Raw(int argc, void** argv, REFrameworkTypeDefinitionHandle* arg_tys, unsigned long long ret_addr) - { + int32_t MethodHook::OnPreStart_Raw(int argc, void** argv, REFrameworkTypeDefinitionHandle* arg_tys, unsigned long long ret_addr) { // Create a System::Span for the arguments auto argspan = System::Span((uint64_t*)argv, argc); - OnPreStart(argspan); - System::Console::WriteLine("Hello from" + m_method->GetName() + " pre-hook!"); - return 0; // Or another appropriate value + return (int32_t)OnPreStart(argspan); } void MethodHook::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!"); + OnPostStart(*(uint64_t*)ret_val); } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/MethodHook.hpp b/csharp-api/REFrameworkNET/MethodHook.hpp index e6873a338..7a0622c62 100644 --- a/csharp-api/REFrameworkNET/MethodHook.hpp +++ b/csharp-api/REFrameworkNET/MethodHook.hpp @@ -13,7 +13,7 @@ public ref class MethodHook { public: delegate PreHookResult PreHookDelegate(System::Span args); - delegate void PostHookDelegate(System::Object^% retval); + delegate void PostHookDelegate(uint64_t% retval); //delegate PreHookResult PreHookEasyDelegate(REFrameworkNET::NativeObject^ thisPtr, System::Object^% arg1, System::Object^% arg2, System::Object^% arg3, System::Object^% arg4); @@ -54,8 +54,59 @@ public ref class MethodHook } private: - event PreHookDelegate^ OnPreStart; - event PostHookDelegate^ OnPostStart; + event PreHookDelegate^ OnPreStart { + void add(PreHookDelegate^ callback) { + OnPreStartImpl = (PreHookDelegate^)System::Delegate::Combine(OnPreStartImpl, callback); + } + void remove(PreHookDelegate^ callback) { + OnPreStartImpl = (PreHookDelegate^)System::Delegate::Remove(OnPreStartImpl, callback); + } + REFrameworkNET::PreHookResult raise(System::Span args) { + if (OnPreStartImpl != nullptr) { + REFrameworkNET::PreHookResult result = REFrameworkNET::PreHookResult::Continue; + + for each (PreHookDelegate^ callback in OnPreStartImpl->GetInvocationList()) { + try { + auto currentResult = callback(args); + if (currentResult != REFrameworkNET::PreHookResult::Continue) { + result = currentResult; + } + } catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Exception in pre-hook callback for method: " + m_method->GetName() + " - " + e->Message); + } catch (...) { + REFrameworkNET::API::LogError("Unknown exception in pre-hook callback for method: " + m_method->GetName()); + } + } + + return result; + } + + return REFrameworkNET::PreHookResult::Continue; + } + }; + + event PostHookDelegate^ OnPostStart { + void add(PostHookDelegate^ callback) { + OnPostStartImpl = (PostHookDelegate^)System::Delegate::Combine(OnPostStartImpl, callback); + } + void remove(PostHookDelegate^ callback) { + OnPostStartImpl = (PostHookDelegate^)System::Delegate::Remove(OnPostStartImpl, callback); + } + void raise(uint64_t% retval) { + if (OnPostStartImpl != nullptr) { + try { + OnPostStartImpl(retval); + } catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Exception in post-hook callback for method: " + m_method->GetName() + " - " + e->Message); + } catch (...) { + REFrameworkNET::API::LogError("Unknown exception in post-hook callback for method: " + m_method->GetName()); + } + } + } + }; + + PreHookDelegate^ OnPreStartImpl; + PostHookDelegate^ OnPostStartImpl; // This is never meant to publicly be called diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 21467d672..27f471109 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -558,17 +558,6 @@ namespace REFrameworkNET { } void PluginManager::ImGuiCallback(::REFImGuiFrameCbData* data) { - //System::Console::WriteLine("ImGuiCallback called"); - - // marshal to intptr - /*auto context = System::IntPtr(data->context); - auto mallocFn = System::IntPtr(data->malloc_fn); - auto freeFn = System::IntPtr(data->free_fn); - auto user_data = System::IntPtr(data->user_data); - - ImGuiNET::ImGui::SetCurrentContext(context); - ImGuiNET::ImGui::SetAllocatorFunctions(mallocFn, freeFn, user_data);*/ - try { Callbacks::ImGuiRender::TriggerPre(); } catch (System::Exception^ e) { diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index efa0b34f3..0b3b2fdef 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -14,13 +14,13 @@ public static REFrameworkNET.PreHookResult isInsidePreHook(Span args) { return REFrameworkNET.PreHookResult.Continue; } - public static void isInsidePostHook(ref System.Object retval) { - Console.WriteLine("Inside post hook (From C#)"); + public static void isInsidePostHook(ref ulong retval) { + Console.WriteLine("Inside post hook (From C#), retval: " + (retval & 1).ToString()); } public static void Entry() { var tdb = REFrameworkNET.API.GetTDB(); - tdb.GetType("app.CameraManager")?. + tdb.GetType(app.CameraManager.REFType.FullName). GetMethod("isInside")?. AddHook(false). AddPre(isInsidePreHook). From df78dcd52bd191baa2fde21d550c28e7a80f22d4 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 10 Apr 2024 20:42:44 -0700 Subject: [PATCH 107/207] .NET: Unsubscribe all hooks upon script unload/reload --- csharp-api/REFrameworkNET/MethodHook.cpp | 66 +++++++++++++++++++++ csharp-api/REFrameworkNET/MethodHook.hpp | 8 ++- csharp-api/REFrameworkNET/PluginManager.cpp | 6 +- 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/csharp-api/REFrameworkNET/MethodHook.cpp b/csharp-api/REFrameworkNET/MethodHook.cpp index 2eb3a3289..97e5f0176 100644 --- a/csharp-api/REFrameworkNET/MethodHook.cpp +++ b/csharp-api/REFrameworkNET/MethodHook.cpp @@ -47,4 +47,70 @@ namespace REFrameworkNET { void MethodHook::OnPostStart_Raw(void** ret_val, REFrameworkTypeDefinitionHandle ret_ty, unsigned long long ret_addr) { OnPostStart(*(uint64_t*)ret_val); } + + void MethodHook::UnsubscribeAssembly(System::Reflection::Assembly^ assembly) { + auto allHooks = GetAllHooks(); + + if (allHooks == nullptr || allHooks->Count == 0) { + return; + } + + for each (auto hook in allHooks) { + while (true) { + if (hook->OnPreStartImpl == nullptr) { + break; + } + + auto del = (System::Delegate^)hook->OnPreStartImpl; + auto invocationList = del->GetInvocationList(); + + bool set = false; + + for each (System::Delegate ^ d in invocationList) { + // Get the assembly that the delegate is from + auto target = d->Method; + auto targetAssembly = target->DeclaringType->Assembly; + + if (targetAssembly == assembly) { + System::Console::WriteLine("REFrameworkNET.MethodHook UnsubscribeAssembly: Removing pre hook" + target->Name + " from " + hook->m_method->GetName()); + hook->OnPreStart -= (MethodHook::PreHookDelegate^)d; + set = true; + break; + } + } + + if (!set) { + break; + } + } + + while (true) { + if (hook->OnPostStartImpl == nullptr) { + break; + } + + auto del = (System::Delegate^)hook->OnPostStartImpl; + auto invocationList = del->GetInvocationList(); + + bool set = false; + + for each (System::Delegate ^ d in invocationList) { + // Get the assembly that the delegate is from + auto target = d->Method; + auto targetAssembly = target->DeclaringType->Assembly; + + if (targetAssembly == assembly) { + System::Console::WriteLine("REFrameworkNET.MethodHook UnsubscribeAssembly: Removing post hook" + target->Name + " from " + hook->m_method->GetName()); + hook->OnPostStart -= (MethodHook::PostHookDelegate^)d; + set = true; + break; + } + } + + if (!set) { + break; + } + } + } + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/MethodHook.hpp b/csharp-api/REFrameworkNET/MethodHook.hpp index 7a0622c62..08bb77eed 100644 --- a/csharp-api/REFrameworkNET/MethodHook.hpp +++ b/csharp-api/REFrameworkNET/MethodHook.hpp @@ -44,7 +44,7 @@ public ref class MethodHook return this; } - System::Collections::Generic::List^ GetAllHooks() { + static System::Collections::Generic::List^ GetAllHooks() { auto out = gcnew System::Collections::Generic::List(); for each (auto kvp in s_hooked_methods) { out->Add(kvp.Value); @@ -53,7 +53,9 @@ public ref class MethodHook return out; } -private: +internal: + static void UnsubscribeAssembly(System::Reflection::Assembly^ assembly); + event PreHookDelegate^ OnPreStart { void add(PreHookDelegate^ callback) { OnPreStartImpl = (PreHookDelegate^)System::Delegate::Combine(OnPreStartImpl, callback); @@ -104,7 +106,7 @@ public ref class MethodHook } } }; - + PreHookDelegate^ OnPreStartImpl; PostHookDelegate^ OnPostStartImpl; diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 27f471109..d71370584 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -2,6 +2,7 @@ #include #include "Attributes/Plugin.hpp" +#include "MethodHook.hpp" #include "PluginManager.hpp" using namespace System; @@ -514,9 +515,10 @@ namespace REFrameworkNET { REFrameworkNET::API::LogInfo("Unloading dynamic assembly by calling " + method->Name + " in " + t->FullName); method->Invoke(nullptr, nullptr); } - - Callbacks::Impl::UnsubscribeAssembly(assem); } + + Callbacks::Impl::UnsubscribeAssembly(assem); + MethodHook::UnsubscribeAssembly(assem); } catch (System::Exception^ e) { REFrameworkNET::API::LogError("Failed to unload dynamic assembly: " + e->Message); From 45a0a77625618d87a4064d9dd308ff2abbc6764f Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 09:48:20 -0700 Subject: [PATCH 108/207] .NET: Give each plugin its own PluginLoadContext Also non-dynamic assemblies can now be unloaded --- csharp-api/REFrameworkNET/PluginManager.cpp | 275 +++++++++++++------- csharp-api/REFrameworkNET/PluginManager.hpp | 40 ++- 2 files changed, 215 insertions(+), 100 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index d71370584..ac39f2e31 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -238,50 +238,8 @@ namespace REFrameworkNET { } System::Console::WriteLine("Continue with managed plugins..."); - - const auto managed_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed"; - std::filesystem::create_directories(managed_path); - - System::String^ managed_dir = gcnew System::String(managed_path.wstring().c_str()); - auto files = System::IO::Directory::GetFiles(managed_dir, "*.dll"); - - if (files->Length != 0) { - bool ever_found = false; - - for each (System::String^ file in files) { - Console::WriteLine(file); - System::Reflection::Assembly^ assem = System::Reflection::Assembly::LoadFrom(file); - - if (assem == nullptr) { - REFrameworkNET::API::LogError("Failed to load assembly from " + file); - continue; - } - // Iterate through all types in the assembly - for each (Type^ type in assem->GetTypes()) { - array^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); - - for each (System::Reflection::MethodInfo^ method in methods) { - array^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginEntryPoint::typeid, true); - - if (attributes->Length > 0) { - REFrameworkNET::API::LogInfo("Found PluginEntryPoint in " + method->Name + " in " + type->FullName); - method->Invoke(nullptr, nullptr); - ever_found = true; - } - } - } - } - - if (!ever_found) { - REFrameworkNET::API::LogInfo("No Main method found in any DLLs in " + managed_dir); - } - } else { - REFrameworkNET::API::LogInfo("No DLLs found in " + managed_dir); - } - - // Unload dynamic assemblies (testing) - //UnloadDynamicAssemblies(); + LoadPlugins_FromDLLs(param_raw, s_dependencies); return true; } catch(System::Exception^ e) { @@ -302,6 +260,34 @@ namespace REFrameworkNET { return false; } + bool PluginManager::TriggerPluginLoad(PluginManager::PluginState^ state) { + auto assem = state->assembly; + + if (assem == nullptr) { + REFrameworkNET::API::LogError("Failed to load assembly from " + state->script_path); + return false; + } + + bool ever_found = false; + + // Iterate through all types in the assembly + for each (Type^ type in assem->GetTypes()) { + array^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic); + + for each (System::Reflection::MethodInfo^ method in methods) { + array^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginEntryPoint::typeid, true); + + if (attributes->Length > 0) { + REFrameworkNET::API::LogInfo("Found PluginEntryPoint in " + method->Name + " in " + type->FullName); + method->Invoke(nullptr, nullptr); + ever_found = true; + } + } + } + + return ever_found; + } + void PluginManager::OnSourceScriptsChanged(System::Object^ sender, System::IO::FileSystemEventArgs^ e) { System::Console::WriteLine("Source scripts changed"); System::Console::WriteLine("File " + e->FullPath + " " + e->ChangeType.ToString()); @@ -391,13 +377,93 @@ namespace REFrameworkNET { if (s_wants_reload) { s_wants_reload = false; - PluginManager::UnloadDynamicAssemblies(); - PluginManager::LoadPlugins_FromSourceCode(0, s_dependencies); + PluginManager::UnloadPlugins(); + + try { + PluginManager::LoadPlugins_FromSourceCode(0, s_dependencies); + } catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Failed to reload plugins: " + e->Message); + + auto ex = e; + while (ex != nullptr) { + REFrameworkNET::API::LogError(ex->StackTrace); + ex = ex->InnerException; + } + } catch (const std::exception& e) { + REFrameworkNET::API::LogError("Failed to reload plugins: " + gcnew System::String(e.what())); + } catch (...) { + REFrameworkNET::API::LogError("Failed to reload plugins: Unknown exception caught"); + } + + PluginManager::LoadPlugins_FromDLLs(0, s_dependencies); } SetupFileWatcher(); } + bool PluginManager::LoadPlugins_FromDLLs(uintptr_t param_raw, System::Collections::Generic::List^ deps) try { + if (PluginManager::s_api_instance == nullptr) { + PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); + } + + const auto managed_path = std::filesystem::current_path() / "reframework" / "plugins" / "managed"; + std::filesystem::create_directories(managed_path); + + System::String^ managed_dir = gcnew System::String(managed_path.wstring().c_str()); + auto files = System::IO::Directory::GetFiles(managed_dir, "*.dll"); + + bool ever_found = false; + + if (files->Length != 0) { + bool ever_found = false; + + for each (System::String^ file in files) { + Console::WriteLine(file); + auto state = gcnew PluginState(file, false); + + state->assembly = state->load_context->LoadFromAssemblyPath(file); + + if (state->assembly == nullptr) { + REFrameworkNET::API::LogError("Failed to load assembly from " + file); + state->Unload(); + continue; + } + + PluginManager::s_plugin_states->Add(state); + + if (TriggerPluginLoad(state)) { + ever_found = true; + } + } + + if (!ever_found) { + REFrameworkNET::API::LogInfo("No Main method found in any DLLs in " + managed_dir); + } + } else { + REFrameworkNET::API::LogInfo("No DLLs found in " + managed_dir); + } + + return ever_found; + } catch(System::Exception^ e) { + REFrameworkNET::API::LogError(e->Message); + + // log stack + auto ex = e; + while (ex != nullptr) { + REFrameworkNET::API::LogError(ex->StackTrace); + ex = ex->InnerException; + } + + return false; + } catch(const std::exception& e) { + REFrameworkNET::API::LogError(gcnew System::String(e.what())); + return false; + } catch(...) { + REFrameworkNET::API::LogError("Unknown exception caught while loading DLLs"); + return false; + } + + bool PluginManager::LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps) try { if (PluginManager::s_api_instance == nullptr) { PluginManager::s_api_instance = gcnew REFrameworkNET::API(param_raw); @@ -427,7 +493,6 @@ namespace REFrameworkNET { } auto self = System::Reflection::Assembly::LoadFrom(System::Reflection::Assembly::GetExecutingAssembly()->Location); - s_default_context = gcnew PluginLoadContext(); for each (System::String^ file in files) { System::Console::WriteLine(file); @@ -441,36 +506,27 @@ namespace REFrameworkNET { continue; } - auto assem = s_default_context->LoadFromStream(gcnew System::IO::MemoryStream(bytecode)); - //auto assem = System::Reflection::Assembly::Load(bytecode); + PluginState^ state = gcnew PluginState(file, true); + state->assembly = state->load_context->LoadFromStream(gcnew System::IO::MemoryStream(bytecode)); - if (assem == nullptr) { + if (state->assembly == nullptr) { REFrameworkNET::API::LogError("Failed to load assembly from " + file); + state->Unload(); continue; } - s_dynamic_assemblies->Add(assem); + //s_dynamic_assemblies->Add(assem); + s_plugin_states->Add(state); REFrameworkNET::API::LogInfo("Compiled " + file); - // Look for the Main method in the compiled assembly - for each (Type^ type in assem->GetTypes()) { - array^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic); - - for each (System::Reflection::MethodInfo^ method in methods) { - array^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginEntryPoint::typeid, true); - - if (attributes->Length > 0) { - REFrameworkNET::API::LogInfo("Found PluginEntryPoint in " + method->Name + " in " + type->FullName); - method->Invoke(nullptr, nullptr); - ever_found = true; - } - } + if (TriggerPluginLoad(state)) { + ever_found = true; } } if (!ever_found) { - Console::WriteLine("No C# files compiled in " + cs_files_dir); + Console::WriteLine("No C# plugins with an entry point found in " + cs_files_dir); } return true; @@ -493,26 +549,31 @@ namespace REFrameworkNET { return false; } - void PluginManager::UnloadDynamicAssemblies() { - if (PluginManager::s_dynamic_assemblies == nullptr || PluginManager::s_dynamic_assemblies->Count == 0) { - REFrameworkNET::API::LogInfo("No dynamic assemblies to unload"); + void PluginManager::UnloadPlugins() { + if (PluginManager::s_plugin_states == nullptr || PluginManager::s_plugin_states->Count == 0) { + REFrameworkNET::API::LogInfo("No plugins to unload"); return; } REFrameworkNET::API::LogInfo("Unloading dynamic assemblies..."); - for each (System::Reflection::Assembly ^ assem in PluginManager::s_dynamic_assemblies) { - if (assem == nullptr) { - continue; - } - + //for each (System::Reflection::Assembly ^ assem in PluginManager::s_dynamic_assemblies) { + for each (PluginState^ state in PluginManager::s_plugin_states) { try { + auto assem = state->assembly; + if (assem == nullptr) { + continue; + } + + auto path = state->script_path; + REFrameworkNET::API::LogInfo("Attempting to initiate first phase unload of " + state->script_path); + // Look for the Unload method in the target assembly which takes an REFrameworkNET.API instance for each (Type ^ t in assem->GetTypes()) { auto method = t->GetMethod("OnUnload", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); if (method != nullptr) { - REFrameworkNET::API::LogInfo("Unloading dynamic assembly by calling " + method->Name + " in " + t->FullName); + REFrameworkNET::API::LogInfo("Unloading plugin by calling " + method->Name + " in " + t->FullName); method->Invoke(nullptr, nullptr); } } @@ -521,42 +582,71 @@ namespace REFrameworkNET { MethodHook::UnsubscribeAssembly(assem); } catch (System::Exception^ e) { - REFrameworkNET::API::LogError("Failed to unload dynamic assembly: " + e->Message); + REFrameworkNET::API::LogError("Failed to unload plugin: " + e->Message); } catch (const std::exception& e) { - REFrameworkNET::API::LogError("Failed to unload dynamic assembly: " + gcnew System::String(e.what())); + REFrameworkNET::API::LogError("Failed to unload plugin: " + gcnew System::String(e.what())); } catch (...) { - REFrameworkNET::API::LogError("Unknown exception caught while unloading dynamic assembly"); + REFrameworkNET::API::LogError("Unknown exception caught while unloading plugin"); } } - s_dynamic_assemblies->Clear(); + // Now do a second pass to actually unload the assemblies + for each (PluginState^ state in PluginManager::s_plugin_states) { + try { + if (state->load_context == nullptr) { + continue; + } + + REFrameworkNET::API::LogInfo("Attempting to initiate second phase unload of " + state->script_path); + + state->load_context_weak = gcnew System::WeakReference(state->load_context); + state->load_context->Unload(); + state->load_context = nullptr; + } + catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Failed to unload plugin: " + e->Message); + } + } - // make weak ref to default context - if (s_dynamic_assemblies != nullptr) { - System::WeakReference^ weakRef = gcnew System::WeakReference(s_default_context); - PluginManager::s_default_context->Unload(); - PluginManager::s_default_context = nullptr; + System::GC::Collect(); + System::GC::WaitForPendingFinalizers(); + System::Threading::Thread::Sleep(10); - bool unloaded = false; + bool all_collected_final = false; - for (int i = 0; i < 10; i++) { - if (weakRef->IsAlive) { + // And now, on the third pass, we wait for the load contexts to be collected (to a reasonable extent) + for (int i = 0; i < 10; i++) { + bool all_collected = true; + + // If any of the load contexts are still alive, we wait a bit and try again + for each (PluginState^ state in PluginManager::s_plugin_states) { + if (state->load_context_weak->IsAlive) { System::GC::Collect(); System::GC::WaitForPendingFinalizers(); System::Threading::Thread::Sleep(10); - } else { - unloaded = true; - System::Console::WriteLine("Successfully unloaded default context"); + all_collected = false; break; + } else { + REFrameworkNET::API::LogInfo("Successfully unloaded " + state->script_path); } } - if (!unloaded) { - System::Console::WriteLine("Failed to unload default context"); + // If we've gone through all the load contexts and none are alive, we're done + if (all_collected) { + all_collected_final = true; + break; } } + + if (!all_collected_final) { + REFrameworkNET::API::LogError("Failed to unload all plugins"); + } else { + REFrameworkNET::API::LogInfo("Successfully unloaded all plugins"); + } + + s_plugin_states->Clear(); } void PluginManager::ImGuiCallback(::REFImGuiFrameCbData* data) { @@ -586,14 +676,13 @@ namespace REFrameworkNET { ImGuiNET::ImGui::PushID("REFramework.NET"); if (ImGuiNET::ImGui::CollapsingHeader("REFramework.NET")) { if (ImGuiNET::ImGui::Button("Reload Scripts")) { - PluginManager::UnloadDynamicAssemblies(); - PluginManager::LoadPlugins_FromSourceCode(0, s_dependencies); + s_wants_reload = true; } ImGuiNET::ImGui::SameLine(); if (ImGuiNET::ImGui::Button("Unload Scripts")) { - PluginManager::UnloadDynamicAssemblies(); + PluginManager::UnloadPlugins(); } } diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 2734d9796..fbac67b27 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -24,7 +24,9 @@ private ref class PluginManager return LoadPlugins_FromDefaultContext(param); } -private: +internal: + ref class PluginState; + // Executed initially when we get loaded via LoadLibrary // which is not in the correct context to actually load the managed plugins static bool LoadPlugins_FromDefaultContext(const REFrameworkPluginInitializeParam* param); @@ -36,7 +38,9 @@ private ref class PluginManager static void GenerateReferenceAssemblies(System::Collections::Generic::List^ deps); static bool LoadPlugins(uintptr_t param_raw); static bool LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps); - static void UnloadDynamicAssemblies(); + static bool LoadPlugins_FromDLLs(uintptr_t param_raw, System::Collections::Generic::List^ deps); + static bool TriggerPluginLoad(PluginState^ state); + static void UnloadPlugins(); // This one is outside of a window context static void ImGuiCallback(::REFImGuiFrameCbData* data); @@ -49,13 +53,8 @@ private ref class PluginManager static ImGuiCallbackDelegate^ s_imgui_callback_delegate{gcnew ImGuiCallbackDelegate(&ImGuiCallback)}; static ImGuiDrawUICallbackDelegate^ s_imgui_draw_ui_callback_delegate{gcnew ImGuiDrawUICallbackDelegate(&ImGuiDrawUICallback)}; - static System::Collections::Generic::List^ s_loaded_assemblies{gcnew System::Collections::Generic::List()}; - static System::Collections::Generic::List^ s_dynamic_assemblies{gcnew System::Collections::Generic::List()}; - static System::Collections::Generic::List^ s_dependencies{gcnew System::Collections::Generic::List()}; - static PluginLoadContext^ s_default_context{nullptr}; - // The main watcher static System::IO::FileSystemWatcher^ s_source_scripts_watcher{nullptr}; @@ -72,5 +71,32 @@ private ref class PluginManager delegate void BeginRenderingDelegate(); static BeginRenderingDelegate^ s_begin_rendering_delegate{gcnew BeginRenderingDelegate(&BeginRendering)}; + + ref class PluginState { + internal: + PluginState(System::String^ path, bool is_dynamic) : script_path(path), is_dynamic(is_dynamic) { } + ~PluginState() { + Unload(); + } + + void Unload() { + if (load_context != nullptr) { + load_context->Unload(); + load_context = nullptr; + + System::GC::Collect(); + System::GC::WaitForPendingFinalizers(); + } + } + + PluginLoadContext^ load_context{gcnew PluginLoadContext()}; + System::Reflection::Assembly^ assembly{nullptr}; + System::String^ script_path{nullptr}; // Either to a .cs file or a .dll file, don't think the assembly will contain the path so we need to keep it + System::WeakReference^ load_context_weak{nullptr}; + + bool is_dynamic{false}; + }; + + static System::Collections::Generic::List^ s_plugin_states{gcnew System::Collections::Generic::List()}; }; } \ No newline at end of file From 778ebe2e4e2f61ded094d707850666e2e33024fe Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 09:56:19 -0700 Subject: [PATCH 109/207] .NET: Fix plugins not unloading correctly after last commit --- csharp-api/REFrameworkNET/PluginManager.cpp | 6 ++---- csharp-api/REFrameworkNET/PluginManager.hpp | 12 ++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index ac39f2e31..293bafe82 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -601,9 +601,7 @@ namespace REFrameworkNET { REFrameworkNET::API::LogInfo("Attempting to initiate second phase unload of " + state->script_path); - state->load_context_weak = gcnew System::WeakReference(state->load_context); - state->load_context->Unload(); - state->load_context = nullptr; + state->Unload(); } catch (System::Exception^ e) { REFrameworkNET::API::LogError("Failed to unload plugin: " + e->Message); @@ -622,7 +620,7 @@ namespace REFrameworkNET { // If any of the load contexts are still alive, we wait a bit and try again for each (PluginState^ state in PluginManager::s_plugin_states) { - if (state->load_context_weak->IsAlive) { + if (state->IsAlive()) { System::GC::Collect(); System::GC::WaitForPendingFinalizers(); System::Threading::Thread::Sleep(10); diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index fbac67b27..046eddf3b 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -74,7 +74,10 @@ private ref class PluginManager ref class PluginState { internal: - PluginState(System::String^ path, bool is_dynamic) : script_path(path), is_dynamic(is_dynamic) { } + PluginState(System::String^ path, bool is_dynamic) : script_path(path), is_dynamic(is_dynamic) + { + } + ~PluginState() { Unload(); } @@ -83,16 +86,21 @@ private ref class PluginManager if (load_context != nullptr) { load_context->Unload(); load_context = nullptr; + assembly = nullptr; System::GC::Collect(); System::GC::WaitForPendingFinalizers(); } } + bool IsAlive() { + return load_context_weak != nullptr && load_context_weak->IsAlive; + } + PluginLoadContext^ load_context{gcnew PluginLoadContext()}; System::Reflection::Assembly^ assembly{nullptr}; System::String^ script_path{nullptr}; // Either to a .cs file or a .dll file, don't think the assembly will contain the path so we need to keep it - System::WeakReference^ load_context_weak{nullptr}; + System::WeakReference^ load_context_weak{gcnew System::WeakReference(load_context)}; bool is_dynamic{false}; }; From 0a9605fe22d8ca76d2cfe079bae429e6f3c06f41 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 10:16:34 -0700 Subject: [PATCH 110/207] .NET: Add menu options to unload specific scripts --- csharp-api/REFrameworkNET/PluginManager.cpp | 71 +++++++++++++++------ csharp-api/REFrameworkNET/PluginManager.hpp | 20 +++--- 2 files changed, 60 insertions(+), 31 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 293bafe82..078f27f42 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -577,9 +577,8 @@ namespace REFrameworkNET { method->Invoke(nullptr, nullptr); } } - - Callbacks::Impl::UnsubscribeAssembly(assem); - MethodHook::UnsubscribeAssembly(assem); + + state->Unload(); } catch (System::Exception^ e) { REFrameworkNET::API::LogError("Failed to unload plugin: " + e->Message); @@ -592,22 +591,6 @@ namespace REFrameworkNET { } } - // Now do a second pass to actually unload the assemblies - for each (PluginState^ state in PluginManager::s_plugin_states) { - try { - if (state->load_context == nullptr) { - continue; - } - - REFrameworkNET::API::LogInfo("Attempting to initiate second phase unload of " + state->script_path); - - state->Unload(); - } - catch (System::Exception^ e) { - REFrameworkNET::API::LogError("Failed to unload plugin: " + e->Message); - } - } - System::GC::Collect(); System::GC::WaitForPendingFinalizers(); System::Threading::Thread::Sleep(10); @@ -682,8 +665,58 @@ namespace REFrameworkNET { if (ImGuiNET::ImGui::Button("Unload Scripts")) { PluginManager::UnloadPlugins(); } + + for each (PluginState^ state in PluginManager::s_plugin_states) { + state->DisplayOptions(); + } } ImGuiNET::ImGui::PopID(); } + + void PluginManager::PluginState::Unload() { + if (load_context != nullptr) { + REFrameworkNET::Callbacks::Impl::UnsubscribeAssembly(assembly); + REFrameworkNET::MethodHook::UnsubscribeAssembly(assembly); + + load_context->Unload(); + load_context = nullptr; + assembly = nullptr; + + System::GC::Collect(); + } + } + + bool PluginManager::PluginState::SynchronousUnload() { + Unload(); + + for (int i = 0; i < 10; ++i) { + if (!IsAlive()) { + return true; + } + + System::GC::Collect(); + System::GC::WaitForPendingFinalizers(); + System::Threading::Thread::Sleep(10); + } + + return !IsAlive(); + } + + void PluginManager::PluginState::DisplayOptions() { + if (ImGuiNET::ImGui::TreeNode(System::IO::Path::GetFileName(script_path))) { + if (ImGuiNET::ImGui::Button("Unload")) { + if (SynchronousUnload()) { + REFrameworkNET::API::LogInfo("Successfully unloaded " + script_path); + // Remove this state from the list + PluginManager::s_plugin_states->Remove(this); + return; + } else { + REFrameworkNET::API::LogError("Failed to unload " + script_path); + } + } + + ImGuiNET::ImGui::TreePop(); + } + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 046eddf3b..9a9e10722 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -77,30 +77,26 @@ private ref class PluginManager PluginState(System::String^ path, bool is_dynamic) : script_path(path), is_dynamic(is_dynamic) { } - + ~PluginState() { Unload(); } - void Unload() { - if (load_context != nullptr) { - load_context->Unload(); - load_context = nullptr; - assembly = nullptr; - - System::GC::Collect(); - System::GC::WaitForPendingFinalizers(); - } - } + void Unload(); + bool SynchronousUnload(); bool IsAlive() { return load_context_weak != nullptr && load_context_weak->IsAlive; } + void DisplayOptions(); + PluginLoadContext^ load_context{gcnew PluginLoadContext()}; + System::WeakReference^ load_context_weak{gcnew System::WeakReference(load_context)}; System::Reflection::Assembly^ assembly{nullptr}; System::String^ script_path{nullptr}; // Either to a .cs file or a .dll file, don't think the assembly will contain the path so we need to keep it - System::WeakReference^ load_context_weak{gcnew System::WeakReference(load_context)}; + + // TODO: Add a friendly name that the plugins can expose? bool is_dynamic{false}; }; From e0c42f449ee2479843673fb57cdc8331a514c72c Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 10:26:38 -0700 Subject: [PATCH 111/207] .NET: Safer way of removing plugins --- csharp-api/REFrameworkNET/PluginManager.cpp | 10 ++++++++-- csharp-api/REFrameworkNET/PluginManager.hpp | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 078f27f42..c9e5125c6 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -669,6 +669,12 @@ namespace REFrameworkNET { for each (PluginState^ state in PluginManager::s_plugin_states) { state->DisplayOptions(); } + + for each (PluginState^ state in PluginManager::s_plugin_states_to_remove) { + PluginManager::s_plugin_states->Remove(state); + } + + PluginManager::s_plugin_states_to_remove->Clear(); } ImGuiNET::ImGui::PopID(); @@ -709,8 +715,8 @@ namespace REFrameworkNET { if (SynchronousUnload()) { REFrameworkNET::API::LogInfo("Successfully unloaded " + script_path); // Remove this state from the list - PluginManager::s_plugin_states->Remove(this); - return; + //PluginManager::s_plugin_states->Remove(this); + s_plugin_states_to_remove->Add(this); } else { REFrameworkNET::API::LogError("Failed to unload " + script_path); } diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 9a9e10722..4d4d18b24 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -102,5 +102,6 @@ private ref class PluginManager }; static System::Collections::Generic::List^ s_plugin_states{gcnew System::Collections::Generic::List()}; + static System::Collections::Generic::List^ s_plugin_states_to_remove{gcnew System::Collections::Generic::List()}; }; } \ No newline at end of file From f09e4a86cd27b9a56a2abf3407afad2c4625888b Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 11:30:59 -0700 Subject: [PATCH 112/207] .NET: Add "Auto Reload" checkbox --- csharp-api/REFrameworkNET/PluginManager.cpp | 8 ++++++-- csharp-api/REFrameworkNET/PluginManager.hpp | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index c9e5125c6..d317fa287 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -304,7 +304,7 @@ namespace REFrameworkNET { } } - s_wants_reload = true; + s_wants_reload_automatic = true; } void PluginManager::SetupIndividualFileWatcher(System::String^ real_path) { @@ -374,8 +374,10 @@ namespace REFrameworkNET { } void PluginManager::BeginRendering() { - if (s_wants_reload) { + bool should_reload = s_wants_reload || (s_wants_reload_automatic && s_auto_reload_plugins); + if (should_reload) { s_wants_reload = false; + s_wants_reload_automatic = false; PluginManager::UnloadPlugins(); @@ -665,6 +667,8 @@ namespace REFrameworkNET { if (ImGuiNET::ImGui::Button("Unload Scripts")) { PluginManager::UnloadPlugins(); } + + ImGuiNET::ImGui::Checkbox("Auto Reload", s_auto_reload_plugins); for each (PluginState^ state in PluginManager::s_plugin_states) { state->DisplayOptions(); diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 4d4d18b24..025ef2753 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -61,6 +61,7 @@ private ref class PluginManager // We also need a watcher list for symlinks that are in the directory static System::Collections::Generic::List^ s_symlink_watchers{gcnew System::Collections::Generic::List()}; static bool s_wants_reload{false}; + static bool s_wants_reload_automatic{false}; static void SetupFileWatcher(); static void SetupIndividualFileWatcher(System::String^ p); // individual symlinks @@ -103,5 +104,6 @@ private ref class PluginManager static System::Collections::Generic::List^ s_plugin_states{gcnew System::Collections::Generic::List()}; static System::Collections::Generic::List^ s_plugin_states_to_remove{gcnew System::Collections::Generic::List()}; + static bool s_auto_reload_plugins{true}; }; } \ No newline at end of file From 00b551879699e38a7db65d8a81fec34b3d40eeb8 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 15:16:50 -0700 Subject: [PATCH 113/207] .NET: Add support for enum returns from method invocations --- csharp-api/REFrameworkNET/Method.cpp | 6 ++++ csharp-api/REFrameworkNET/Proxy.hpp | 17 ++++++++-- csharp-api/test/Test/Test.cs | 46 ++++++++++++++++++++++------ 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 1e3ba34e6..40de1798a 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -299,6 +299,12 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, arrayIsEnum()) { + if (auto underlying = returnType->GetUnderlyingType(); underlying != nullptr) { + returnType = underlying; // easy mode + } + } + const auto raw_rt = (reframework::API::TypeDefinition*)returnType; #define CONCAT_X_C(X, DOT, C) X ## DOT ## C diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index b4867f0ad..5173c51d2 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -135,7 +135,11 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public auto targetReturnType = targetMethod->ReturnType; - if (targetReturnType != nullptr) { + if (targetReturnType == nullptr) { + return nullptr; + } + + if (!targetReturnType->IsPrimitive && !targetReturnType->IsEnum) { if (targetReturnType == String::typeid) { return result; } @@ -161,7 +165,16 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public } } - if (targetMethod->DeclaringType == nullptr || targetReturnType == nullptr) { + if (targetMethod->ReturnType->IsEnum) { + auto underlyingType = targetMethod->ReturnType->GetEnumUnderlyingType(); + + if (underlyingType != nullptr) { + auto underlyingResult = Convert::ChangeType(result, underlyingType); + return Enum::ToObject(targetMethod->ReturnType, underlyingResult); + } + } + + if (targetMethod->DeclaringType == nullptr) { return result; } diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 0b3b2fdef..2cef275c3 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -10,21 +10,46 @@ public class DangerousFunctions { public static REFrameworkNET.PreHookResult isInsidePreHook(Span args) { //Console.WriteLine("Inside pre hook (From C#) " + args.ToString()); - REFrameworkNET.API.LogInfo("isInsidePreHook"); + //REFrameworkNET.API.LogInfo("isInsidePreHook"); return REFrameworkNET.PreHookResult.Continue; } public static void isInsidePostHook(ref ulong retval) { - Console.WriteLine("Inside post hook (From C#), retval: " + (retval & 1).ToString()); + if ((retval & 1) != 0) { + REFrameworkNET.API.LogInfo("Camera is inside"); + } + //Console.WriteLine("Inside post hook (From C#), retval: " + (retval & 1).ToString()); } public static void Entry() { + var mouse = REFrameworkNET.API.GetNativeSingletonT(); + + mouse.set_ShowCursor(false); + var tdb = REFrameworkNET.API.GetTDB(); tdb.GetType(app.CameraManager.REFType.FullName). GetMethod("isInside")?. AddHook(false). AddPre(isInsidePreHook). AddPost(isInsidePostHook); + + tdb.GetType(app.PlayerInputProcessorDetail.REFType.FullName). + GetMethod("processNormalAttack"). + AddHook(false). + AddPre((args) => { + var inputProcessor = ManagedObject.ToManagedObject(args[1]).As(); + var asIObject = inputProcessor as REFrameworkNET.IObject; + ulong flags = args[2]; + bool isCombo = (args[4] & 1) != 0; + API.LogInfo("processNormalAttack: " + + inputProcessor.ToString() + " " + + asIObject.GetTypeDefinition()?.GetFullName()?.ToString() + " " + + flags.ToString() + " " + + isCombo.ToString()); + return PreHookResult.Continue; + }). + AddPost((ref ulong retval) => { + }); // These via.SceneManager and via.Scene are // loaded from an external reference assembly @@ -59,10 +84,13 @@ public static void Entry() { var appdomainStatics = tdb.GetType("System.AppDomain").As<_System.AppDomain>(); var appdomain = appdomainStatics.get_CurrentDomain(); - dynamic assemblies = appdomain.GetAssemblies(); + var assemblies = appdomain.GetAssemblies(); - foreach (REFrameworkNET.ManagedObject assemblyRaw in assemblies) { - var assembly = assemblyRaw.As<_System.Reflection.Assembly>(); + // TODO: Make this work! get_length, get_item is ugly! + //foreach (REFrameworkNET.ManagedObject assemblyRaw in assemblies) { + for (int i = 0; i < assemblies.get_Length(); i++) { + //var assembly = assemblyRaw.As<_System.Reflection.Assembly>(); + var assembly = assemblies.get_Item(i); REFrameworkNET.API.LogInfo("Assembly: " + assembly.get_Location()?.ToString()); } @@ -76,7 +104,9 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Title: " + title); var dialog = tdb.GetTypeT(); - dialog.open("Hello from C#!"); + var dialogError = dialog.open("Hello from C#!"); + + REFrameworkNET.API.LogInfo("Dialog error: " + dialogError.ToString()); var devUtil = tdb.GetTypeT(); var currentDirectory = System.IO.Directory.GetCurrentDirectory(); @@ -92,10 +122,6 @@ public static void Entry() { REFrameworkNET.API.LogInfo("DevUtil: " + devUtilT.GetFullName()); REFrameworkNET.API.LogInfo("Dialog: " + dialogT.GetFullName()); - - var mouse = REFrameworkNET.API.GetNativeSingletonT(); - - mouse.set_ShowCursor(false); } public static void TryEnableFrameGeneration() { From 100f49be16a7c011cf68a509fca4e8278af8870e Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 15:24:56 -0700 Subject: [PATCH 114/207] .NET: Micro-optimizations to proxy invoke --- csharp-api/REFrameworkNET/Proxy.hpp | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 5173c51d2..2a516ba45 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -136,7 +136,7 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public auto targetReturnType = targetMethod->ReturnType; if (targetReturnType == nullptr) { - return nullptr; + return result; } if (!targetReturnType->IsPrimitive && !targetReturnType->IsEnum) { @@ -165,12 +165,12 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public } } - if (targetMethod->ReturnType->IsEnum) { - auto underlyingType = targetMethod->ReturnType->GetEnumUnderlyingType(); + if (targetReturnType->IsEnum) { + auto underlyingType = targetReturnType->GetEnumUnderlyingType(); if (underlyingType != nullptr) { auto underlyingResult = Convert::ChangeType(result, underlyingType); - return Enum::ToObject(targetMethod->ReturnType, underlyingResult); + return Enum::ToObject(targetReturnType, underlyingResult); } } @@ -178,23 +178,14 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public return result; } - if (!targetMethod->ReturnType->IsPrimitive && targetMethod->DeclaringType->IsInterface && result != nullptr) { + if (!targetReturnType->IsPrimitive && targetMethod->DeclaringType->IsInterface && result != nullptr) { auto iobjectResult = dynamic_cast(result); - if (iobjectResult != nullptr && targetMethod->ReturnType->IsInterface) { - /*auto t = iobjectResult->GetTypeDefinition(); - auto fullName = t->FullName; - auto localType = T::typeid->Assembly->GetType(fullName);*/ - auto localType = targetMethod->ReturnType; - - if (localType != nullptr) { - auto proxy = DispatchProxy::Create(localType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(T::typeid, result->GetType())); - ((IProxy^)proxy)->SetInstance(iobjectResult); - result = proxy; - return result; - } else { - System::Console::WriteLine("Type not found: " + targetMethod->ReturnType->FullName); - } + if (iobjectResult != nullptr && targetReturnType->IsInterface) { + auto proxy = DispatchProxy::Create(targetReturnType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(T::typeid, result->GetType())); + ((IProxy^)proxy)->SetInstance(iobjectResult); + result = proxy; + return result; } } From ce78b7d3eb634928cbae2330837763d5807657e4 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 19:39:15 -0700 Subject: [PATCH 115/207] .NET: Add some missing types to invocation arguments --- csharp-api/REFrameworkNET/Method.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 40de1798a..ff8e2ad1c 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -188,6 +188,15 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, arrayFullName + ")"); From 27f0a2389a56e00e923163f02ea4d82a6ddd7aa4 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 19:40:43 -0700 Subject: [PATCH 116/207] .NET (AssemblyGenerator): Fix enums only having signed types --- csharp-api/AssemblyGenerator/EnumGenerator.cs | 48 ++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/csharp-api/AssemblyGenerator/EnumGenerator.cs b/csharp-api/AssemblyGenerator/EnumGenerator.cs index 4702889cd..69314cc0e 100644 --- a/csharp-api/AssemblyGenerator/EnumGenerator.cs +++ b/csharp-api/AssemblyGenerator/EnumGenerator.cs @@ -68,8 +68,52 @@ public void Update(EnumDeclarationSyntax? typeDeclaration) { var underlyingType = field.Type.GetUnderlyingType(); SyntaxToken literalToken; + bool foundRightType = true; - switch (underlyingType.GetValueTypeSize()) { + switch (underlyingType.FullName) { + case "System.Byte": + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("byte"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case "System.SByte": + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("sbyte"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case "System.Int16": + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("short"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case "System.UInt16": + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("ushort"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case "System.Int32": + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("int"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case "System.UInt32": + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("uint"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case "System.Int64": + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("long"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + case "System.UInt64": + enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("ulong"))); + literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); + break; + default: + literalToken = SyntaxFactory.Literal(0); + foundRightType = false; + break; + } + + // The uber fallback + if (!foundRightType) { + REFrameworkNET.API.LogWarning($"Enum {enumName} has an unknown underlying type {underlyingType.FullName}"); + + switch (underlyingType.GetValueTypeSize()) { case 1: enumDeclaration = enumDeclaration.AddBaseListTypes(SyntaxFactory.SimpleBaseType(SyntaxFactory.ParseTypeName("byte"))); literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); @@ -87,7 +131,9 @@ public void Update(EnumDeclarationSyntax? typeDeclaration) { literalToken = SyntaxFactory.Literal(field.GetDataT(0, false)); break; default: + literalToken = SyntaxFactory.Literal(0); throw new System.Exception("Unknown enum underlying type size"); + } } var fieldDeclaration = SyntaxFactory.EnumMemberDeclaration(field.Name); From cc78b3e140d4e3aba67a14c3e53fd84b18671681 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 23:33:23 -0700 Subject: [PATCH 117/207] .NET (AssemblyGenerator): Add method attribute for overloads --- .../AssemblyGenerator/ClassGenerator.cs | 7 ++++++ csharp-api/CMakeLists.txt | 2 ++ .../REFrameworkNET/Attributes/Method.cpp | 1 + .../REFrameworkNET/Attributes/Method.hpp | 21 ++++++++++++++++ csharp-api/REFrameworkNET/Method.hpp | 24 +++++++++++++++++++ 5 files changed, 55 insertions(+) create mode 100644 csharp-api/REFrameworkNET/Attributes/Method.cpp create mode 100644 csharp-api/REFrameworkNET/Attributes/Method.hpp diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 57a7420a9..b75fd653b 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -301,6 +301,13 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy simpleMethodSignature += methodName; + // Add full method name as a MethodName attribute to the method + methodDeclaration = methodDeclaration.AddAttributeLists( + SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( + SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), + SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ")"))) + ); + if (method.Parameters.Count > 0) { // If any of the params have ! in them, skip this method if (method.Parameters.Any(param => param.Type.FullName.Contains('!'))) { diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 09fca0c7f..3658fea82 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -148,6 +148,7 @@ set_target_properties(REFCoreDeps PROPERTIES VS_PACKAGE_REFERENCES "${REFRAMEWOR set(csharp-api_SOURCES "REFrameworkNET/API.cpp" "REFrameworkNET/AssemblyInfo.cpp" + "REFrameworkNET/Attributes/Method.cpp" "REFrameworkNET/Attributes/Plugin.cpp" "REFrameworkNET/Callbacks.cpp" "REFrameworkNET/ManagedObject.cpp" @@ -165,6 +166,7 @@ set(csharp-api_SOURCES "REFrameworkNET/UnifiedObject.cpp" "REFrameworkNET/VM.cpp" "REFrameworkNET/API.hpp" + "REFrameworkNET/Attributes/Method.hpp" "REFrameworkNET/Attributes/Plugin.hpp" "REFrameworkNET/Callbacks.hpp" "REFrameworkNET/Field.hpp" diff --git a/csharp-api/REFrameworkNET/Attributes/Method.cpp b/csharp-api/REFrameworkNET/Attributes/Method.cpp new file mode 100644 index 000000000..af613b143 --- /dev/null +++ b/csharp-api/REFrameworkNET/Attributes/Method.cpp @@ -0,0 +1 @@ +#include "./Method.hpp" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Attributes/Method.hpp b/csharp-api/REFrameworkNET/Attributes/Method.hpp new file mode 100644 index 000000000..acc901b24 --- /dev/null +++ b/csharp-api/REFrameworkNET/Attributes/Method.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../TDB.hpp" + +namespace REFrameworkNET::Attributes { + // Attribute to mark a method as a method name + [System::AttributeUsage(System::AttributeTargets::Method)] + public ref class Method : public System::Attribute { + public: + Method(uint32_t methodIndex) { + method = REFrameworkNET::TDB::Get()->GetMethod(methodIndex); + } + + REFrameworkNET::Method^ GetMethod() { + return method; + } + + private: + REFrameworkNET::Method^ method; + }; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index d4b9d22c6..43f10632e 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -162,6 +162,30 @@ public ref class Method : public System::IEquatable System::Collections::Generic::List^ GetMatchingParentMethods(); // mainly for the assembly generator (temporary?) bool IsOverride(); + System::String^ GetMethodSignature() { + auto name = GetName(); + + if (GetNumParams() == 0) { + return name + "()"; + } + + auto params = GetParameters(); + + System::String^ ret = name + "("; + + for (int i = 0; i < params->Count; i++) { + ret += params[i]->Type->FullName; + + if (i < params->Count - 1) { + ret += ", "; + } + } + + ret += ")"; + + return ret; + } + public: virtual bool Equals(System::Object^ other) override { if (System::Object::ReferenceEquals(this, other)) { From 57f5e2936f9ad66d76adf75eca67be70f1f1b85c Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 11 Apr 2024 23:55:27 -0700 Subject: [PATCH 118/207] .NET: Use attributes to look up methods in the interface proxies --- csharp-api/REFrameworkNET/IProxyable.hpp | 3 ++ csharp-api/REFrameworkNET/Proxy.hpp | 32 ++++++++++++++++++--- csharp-api/REFrameworkNET/UnifiedObject.cpp | 25 ++++++++++++++++ csharp-api/REFrameworkNET/UnifiedObject.hpp | 3 ++ 4 files changed, 59 insertions(+), 4 deletions(-) diff --git a/csharp-api/REFrameworkNET/IProxyable.hpp b/csharp-api/REFrameworkNET/IProxyable.hpp index 80ecc22cb..c8c8e5a42 100644 --- a/csharp-api/REFrameworkNET/IProxyable.hpp +++ b/csharp-api/REFrameworkNET/IProxyable.hpp @@ -11,6 +11,9 @@ public interface class IProxyable { virtual bool IsProxy(); bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + bool HandleInvokeMember_Internal(uint32_t methodIndex, array^ args, System::Object^% result); + bool HandleInvokeMember_Internal(System::Object^ methodObj, array^ args, System::Object^% result); + bool HandleTryGetMember_Internal(System::String^ fieldName, System::Object^% result); }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 2a516ba45..91e480627 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -5,6 +5,7 @@ #include "./API.hpp" #include "ManagedObject.hpp" #include "TypeDefinition.hpp" +#include "Attributes/Method.hpp" using namespace System; @@ -42,10 +43,18 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public return Instance->Invoke(methodName, args); } + virtual bool HandleInvokeMember_Internal(uint32_t methodIndex, array^ args, System::Object^% result) { + return Instance->HandleInvokeMember_Internal(methodIndex, args, result); + } + virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result) { return Instance->HandleInvokeMember_Internal(methodName, args, result); } + virtual bool HandleInvokeMember_Internal(System::Object^ methodObj, array^ args, System::Object^% result) { + return Instance->HandleInvokeMember_Internal(methodObj, args, result); + } + virtual bool HandleTryGetMember_Internal(System::String^ fieldName, System::Object^% result) { return Instance->HandleTryGetMember_Internal(fieldName, result); } @@ -123,14 +132,29 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public protected: virtual Object^ Invoke(Reflection::MethodInfo^ targetMethod, array^ args) override { + // Get the REFrameworkNET::Attributes::Method attribute from the method + auto methodAttributes = targetMethod->GetCustomAttributes(REFrameworkNET::Attributes::Method::typeid, false); + Object^ result = nullptr; auto iobject = dynamic_cast(Instance); - // how am i gonna handle static methods? - if (iobject != nullptr) { - iobject->HandleInvokeMember_Internal(targetMethod->Name, args, result); + if (methodAttributes->Length != 0) { + // Get the first attribute's method + auto attr = (REFrameworkNET::Attributes::Method^)methodAttributes[0]; + auto method = attr->GetMethod(); + + if (iobject != nullptr) { + iobject->HandleInvokeMember_Internal(method, args, result); + } else { + throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); + } } else { - throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); + // This is a fallback + if (iobject != nullptr) { + iobject->HandleInvokeMember_Internal(targetMethod->Name, args, result); + } else { + throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); + } } auto targetReturnType = targetMethod->ReturnType; diff --git a/csharp-api/REFrameworkNET/UnifiedObject.cpp b/csharp-api/REFrameworkNET/UnifiedObject.cpp index c558d8abe..1b80db0f3 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.cpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.cpp @@ -43,6 +43,31 @@ namespace REFrameworkNET { return false; } + bool UnifiedObject::HandleInvokeMember_Internal(uint32_t methodIndex, array^ args, System::Object^% result) { + auto method = REFrameworkNET::TDB::Get()->GetMethod(methodIndex); + + if (method != nullptr) + { + // Re-used with UnifiedObject::TryInvokeMember + return method->HandleInvokeMember_Internal(this, args, result); + } + + result = nullptr; + return false; + } + + bool UnifiedObject::HandleInvokeMember_Internal(System::Object^ methodObj, array^ args, System::Object^% result) { + auto method = dynamic_cast(methodObj); + if (method != nullptr) + { + // Re-used with UnifiedObject::TryInvokeMember + return method->HandleInvokeMember_Internal(this, args, result); + } + + result = nullptr; + return false; + } + bool UnifiedObject::TryInvokeMember(System::Dynamic::InvokeMemberBinder^ binder, array^ args, System::Object^% result) { auto methodName = binder->Name; diff --git a/csharp-api/REFrameworkNET/UnifiedObject.hpp b/csharp-api/REFrameworkNET/UnifiedObject.hpp index c6a0fd534..48af611c6 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.hpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.hpp @@ -25,6 +25,9 @@ public ref class UnifiedObject abstract : public System::Dynamic::DynamicObject, // Shared methods virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); + virtual bool HandleInvokeMember_Internal(uint32_t methodIndex, array^ args, System::Object^% result); + virtual bool HandleInvokeMember_Internal(System::Object^ methodObj, array^ args, System::Object^% result); + virtual bool HandleTryGetMember_Internal(System::String^ fieldName, System::Object^% result); virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); From c1e97cd7fc8261147daf3afaf738bcd61c2e2f5b Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 00:11:55 -0700 Subject: [PATCH 119/207] .NET: Use singular attribute lookup instead --- csharp-api/REFrameworkNET/Proxy.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 91e480627..0ece13da7 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -133,15 +133,14 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public protected: virtual Object^ Invoke(Reflection::MethodInfo^ targetMethod, array^ args) override { // Get the REFrameworkNET::Attributes::Method attribute from the method - auto methodAttributes = targetMethod->GetCustomAttributes(REFrameworkNET::Attributes::Method::typeid, false); + //auto methodAttributes = targetMethod->GetCustomAttributes(REFrameworkNET::Attributes::Method::typeid, false); + auto methodAttribute = (REFrameworkNET::Attributes::Method^)System::Attribute::GetCustomAttribute(targetMethod, REFrameworkNET::Attributes::Method::typeid); Object^ result = nullptr; auto iobject = dynamic_cast(Instance); - if (methodAttributes->Length != 0) { - // Get the first attribute's method - auto attr = (REFrameworkNET::Attributes::Method^)methodAttributes[0]; - auto method = attr->GetMethod(); + if (methodAttribute != nullptr) { + auto method = methodAttribute->GetMethod(); if (iobject != nullptr) { iobject->HandleInvokeMember_Internal(method, args, result); From 19023370a6b39f80f9bf9dea2da1a83c9b765ad8 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 09:12:59 -0700 Subject: [PATCH 120/207] .NET: Add various documentation --- csharp-api/REFrameworkNET/Method.hpp | 49 +++++++++++++++++++- csharp-api/REFrameworkNET/TypeDefinition.hpp | 34 +++++++++++++- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 43f10632e..53f02b048 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -15,6 +15,12 @@ public enum class PreHookResult : int32_t { Skip = 1, }; +/// +/// Represents a method in the RE Engine's IL2CPP metadata. +/// Equivalent to System.RuntimeMethodHandle in .NET. +/// +/// +/// public ref class Method : public System::IEquatable { public: @@ -25,13 +31,32 @@ public ref class Method : public System::IEquatable return m_method; } + /// + /// Invokes this method with the given arguments. + /// + /// The object to invoke the method on. Null for static methods. + /// The arguments to pass to the method. + /// The return value of the method. 128 bytes in size internally. + /// + /// Generally should not be used unless you know what you're doing. + /// Use the other invoke method to automatically convert the return value correctly into a usable object. + /// REFrameworkNET::InvokeRet^ Invoke(System::Object^ obj, array^ args); + + /// + /// Invokes this method with the given arguments. + /// + /// The object to invoke the method on. Null for static methods. + /// The arguments to pass to the method. + /// The return value of the method. REFramework will attempt to convert this into a usable object. + /// True if the method was successfully found and invoked, false otherwise. bool HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result); void* GetFunctionPtr() { return m_method->get_function_raw(); } + /// The name of the method. System::String^ GetName() { return gcnew System::String(m_method->get_name()); } @@ -42,6 +67,7 @@ public ref class Method : public System::IEquatable } } + /// The declaring of the method. TypeDefinition^ GetDeclaringType() { auto result = m_method->get_declaring_type(); @@ -58,6 +84,7 @@ public ref class Method : public System::IEquatable } } + /// The return of the method. TypeDefinition^ GetReturnType() { auto result = m_method->get_return_type(); @@ -74,6 +101,7 @@ public ref class Method : public System::IEquatable } } + /// The number of parameters of the method. uint32_t GetNumParams() { return m_method->get_num_params(); } @@ -84,6 +112,8 @@ public ref class Method : public System::IEquatable } } + /// The parameters of the method. + /// System::Collections::Generic::List^ GetParameters() { const auto params = m_method->get_params(); @@ -122,6 +152,10 @@ public ref class Method : public System::IEquatable } } + bool IsVirtual() { + return VirtualIndex >= 0; + } + bool IsStatic() { return m_method->is_static(); } @@ -146,6 +180,10 @@ public ref class Method : public System::IEquatable } } + /// + /// Gets the invoke ID of this method. This is the index into the global invoke wrapper table. + /// + /// The invoke ID. uint32_t GetInvokeID() { return m_method->get_invoke_id(); } @@ -155,13 +193,22 @@ public ref class Method : public System::IEquatable return GetInvokeID(); } } - + + /// + /// Adds a hook to this method. Additional operations can be performed on the returned hook object. + /// + /// If true, the hook will not look for a nearby jmp to locate the "real" function. + /// The hook object. MethodHook^ AddHook(bool ignore_jmp); + /// The System.MethodInfo object for this method. Not cached. ManagedObject^ GetRuntimeMethod(); + + System::Collections::Generic::List^ GetMatchingParentMethods(); // mainly for the assembly generator (temporary?) bool IsOverride(); + /// The method signature as a string in the form of "MethodName(Type1, Type2, ...)" System::String^ GetMethodSignature() { auto name = GetName(); diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index be4359ed9..53f8676e9 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -16,6 +16,9 @@ ref class Property; ref class TypeInfo; ref struct InvokeRet; +/// +/// A shorthand enum for determining how a is used in the VM. +/// public enum class VMObjType : uint32_t { NULL_ = 0, Object = 1, @@ -25,8 +28,14 @@ public enum class VMObjType : uint32_t { ValType = 5, }; -public - ref class TypeDefinition : public System::Dynamic::DynamicObject, + +/// +/// Represents a type in the RE Engine's IL2CPP metadata. +/// Equivalent to System.RuntimeTypeHandle in .NET. +/// +/// +/// +public ref class TypeDefinition : public System::Dynamic::DynamicObject, public System::IEquatable { public: @@ -272,6 +281,8 @@ public } } + /// The underlying type of this . + /// Usually used for enums. TypeDefinition^ GetUnderlyingType() { auto result = m_type->get_underlying_type(); @@ -296,6 +307,7 @@ public } } + /// The System.Type for this . ManagedObject^ GetRuntimeType(); property ManagedObject^ RuntimeType { ManagedObject^ get() { @@ -342,7 +354,25 @@ public return (uintptr_t)m_type; } + /// + /// Invokes a with the given arguments. + /// + /// The object to invoke the method on. Null for static methods. + /// The arguments to pass to the method. + /// The return value of the method. 128 bytes in size internally. + /// + /// Generally should not be used unless you know what you're doing. + /// Use the other invoke method to automatically convert the return value correctly into a usable object. + /// virtual REFrameworkNET::InvokeRet^ Invoke(System::String^ methodName, array^ args); + + /// + /// Invokes a with the given arguments. + /// + /// The object to invoke the method on. Null for static methods. + /// The arguments to pass to the method. + /// The return value of the method. REFramework will attempt to convert this into a usable object. + /// True if the method was successfully found and invoked, false otherwise. virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); generic From 68e2de873ca7207c89f1d4c3bcd6badaae6f74ec Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 09:13:53 -0700 Subject: [PATCH 121/207] .NET: Major method lookup optimization + fixes for virtual methods --- .../REFrameworkNET/Attributes/Method.hpp | 58 ++++++++++++++++++- csharp-api/REFrameworkNET/Proxy.hpp | 6 +- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/csharp-api/REFrameworkNET/Attributes/Method.hpp b/csharp-api/REFrameworkNET/Attributes/Method.hpp index acc901b24..d508cd9d5 100644 --- a/csharp-api/REFrameworkNET/Attributes/Method.hpp +++ b/csharp-api/REFrameworkNET/Attributes/Method.hpp @@ -3,19 +3,73 @@ #include "../TDB.hpp" namespace REFrameworkNET::Attributes { - // Attribute to mark a method as a method name + /// Attribute to mark a reference assembly method for easier lookup. [System::AttributeUsage(System::AttributeTargets::Method)] public ref class Method : public System::Attribute { + public: + static Method^ GetCachedAttribute(System::Reflection::MethodInfo^ target) { + Method^ attr = nullptr; + + // Lock the cache for reading + cacheLock->EnterReadLock(); + try { + if (cache->TryGetValue(target, attr) && attr != nullptr) { + return attr; + } + } finally { + cacheLock->ExitReadLock(); + } + + // Lock the cache for writing + cacheLock->EnterWriteLock(); + try { + if (cache->TryGetValue(target, attr) && attr != nullptr) { + return attr; + } + + if (attr = (REFrameworkNET::Attributes::Method^)System::Attribute::GetCustomAttribute(target, REFrameworkNET::Attributes::Method::typeid)) { + cache->Add(target, attr); + +#ifdef REFRAMEWORK_VERBOSE + System::Console::WriteLine("Cached Method attribute for {0}", target->Name); +#endif + return attr; + } + } finally { + cacheLock->ExitWriteLock(); + } + + return attr; + } + + private: + static System::Collections::Generic::Dictionary^ cache = gcnew System::Collections::Generic::Dictionary(); + static System::Threading::ReaderWriterLockSlim^ cacheLock = gcnew System::Threading::ReaderWriterLockSlim(); + public: Method(uint32_t methodIndex) { method = REFrameworkNET::TDB::Get()->GetMethod(methodIndex); + + if (method != nullptr && method->IsVirtual()) { + signature = method->GetMethodSignature(); + isVirtual = method->IsVirtual(); + } } - REFrameworkNET::Method^ GetMethod() { + REFrameworkNET::Method^ GetMethod(REFrameworkNET::TypeDefinition^ td) { + // Signature is used for virtual methods + if (signature != nullptr && isVirtual && td != method->GetDeclaringType()) { + if (auto result = td->GetMethod(signature); result != nullptr) { + return result; + } + } + return method; } private: REFrameworkNET::Method^ method; + System::String^ signature; + bool isVirtual; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 0ece13da7..47e768c66 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -133,16 +133,14 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public protected: virtual Object^ Invoke(Reflection::MethodInfo^ targetMethod, array^ args) override { // Get the REFrameworkNET::Attributes::Method attribute from the method - //auto methodAttributes = targetMethod->GetCustomAttributes(REFrameworkNET::Attributes::Method::typeid, false); - auto methodAttribute = (REFrameworkNET::Attributes::Method^)System::Attribute::GetCustomAttribute(targetMethod, REFrameworkNET::Attributes::Method::typeid); + auto methodAttribute = REFrameworkNET::Attributes::Method::GetCachedAttribute(targetMethod); Object^ result = nullptr; auto iobject = dynamic_cast(Instance); if (methodAttribute != nullptr) { - auto method = methodAttribute->GetMethod(); - if (iobject != nullptr) { + auto method = methodAttribute->GetMethod(iobject->GetTypeDefinition()); iobject->HandleInvokeMember_Internal(method, args, result); } else { throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); From 8a2ca6dda62c5debc2e59e69fd6d1c0196fec125 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 10:42:26 -0700 Subject: [PATCH 122/207] .NET: Fix huge problem where local objects were getting globalized --- csharp-api/REFrameworkNET/ManagedObject.cpp | 23 +++++ csharp-api/REFrameworkNET/ManagedObject.hpp | 103 ++++++++++++++++--- csharp-api/REFrameworkNET/TypeDefinition.hpp | 11 ++ 3 files changed, 124 insertions(+), 13 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index d88f5c887..78b05afa9 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -13,6 +13,29 @@ #include "Utility.hpp" namespace REFrameworkNET { + ManagedObject^ ManagedObject::Globalize() { + if (m_object == nullptr || IsGlobalized()) { + return this; + } + + AddRef(); + return this; + } + + void ManagedObject::AddRef() { + if (m_object == nullptr) { + return; + } + +#ifdef REFRAMEWORK_VERBOSE + if (!IsGlobalized()) { + System::Console::WriteLine("WARNING: Globalizing managed object " + GetTypeDefinition()->FullName + " with negative ref count: " + ref_count); + } +#endif + + m_object->add_ref(); + } + TypeDefinition^ ManagedObject::GetTypeDefinition() { auto result = m_object->get_type_definition(); diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index d47739bbd..05ecf9c57 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -18,14 +18,11 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject { public: ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj) { - if (obj != nullptr) { - AddRef(); - } + AddRefIfGlobalized(); } + ManagedObject(::REFrameworkManagedObjectHandle handle) : m_object(reinterpret_cast(handle)) { - if (handle != nullptr) { - AddRef(); - } + AddRefIfGlobalized(); } // Double check if we really want to allow this @@ -33,30 +30,110 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject // instead of AddRef'ing every time we create a new ManagedObject ManagedObject(ManagedObject^ obj) : m_object(obj->m_object) { if (m_object != nullptr) { - AddRef(); + AddRefIfGlobalized(); } } ~ManagedObject() { if (m_object != nullptr) { - Release(); + ReleaseIfGlobalized(); } } - void AddRef() { + /// + /// + /// Globalizes the managed object if it is not already globalized
+ /// This is usually useful in instances where objects are newly created, and you want
+ /// to keep long term references to them, otherwise the object will be quickly destroyed
+ /// A local object is an object that can only be referenced by the spawned thread
+ /// A global object is an object that can be referenced by any thread
+ ///
+ /// To quote the talk, "Achieve Rapid Iteration: RE ENGINE Design":
+ ///
+ /// Local object
+ /// Objects that can only be referenced by the spawned thread
+ /// Registered in the local table for each thread
+ /// Reference counter (RC) is negative and points to the index of the local table
+ /// All objects created from C# will be local objects
+ ///
+ /// Local => Global conversion
+ /// Convert when it becomes available to all threads
+ /// When storing in a static field
+ /// When storing in a field of a global object
+ /// Cleared from local table and RC becomes 1
+ /// Convert all references globally
+ ///
+ /// Global object
+ /// Objects that can be referenced by all threads
+ /// Reference counter (RC) is positive and represents the number of object references
+ /// All objects generated from C++ become global objects
+ ///
+ ///
+ ///
+ /// The current managed object + /// + /// + /// This should only need to be called in the following instances:
+ /// You are manually creating an instance of a managed object
+ /// A method you are calling is freshly creating a new managed object (usually arrays or some other kind of constructor)
+ /// More information:
https://github.com/kasicass/blog/blob/master/3d-reengine/2021_03_10_achieve_rapid_iteration_re_engine_design.md#framegc-algorithm-17
+ /// + /// + ManagedObject^ Globalize(); + + /// + /// Adds a reference to the managed object + /// + /// Try to avoid calling this manually except in extraordinary circumstances + void AddRef(); + + /// + /// Releases a reference to the managed object + /// + /// Try to avoid calling this manually except in extraordinary circumstances + void Release() { if (m_object == nullptr) { return; } - m_object->add_ref(); + m_object->release(); } - void Release() { - if (m_object == nullptr) { + void AddRefIfGlobalized() { + if (m_object == nullptr || !IsGlobalized()) { return; } - m_object->release(); + AddRef(); + } + + void ReleaseIfGlobalized() { + if (m_object == nullptr || !IsGlobalized()) { + return; + } + + Release(); + } + + int32_t GetReferenceCount() { + if (m_object == nullptr) { + return 0; + } + + // TODO: Make this less hacky/pull from the C++ API? + return *(int32_t*)((uintptr_t)m_object + 0x8); + } + + bool IsLocalVariable() { + return GetReferenceCount() < 0; + } + + bool IsGlobalized() { + return GetReferenceCount() >= 0; + } + + bool IsGoingToBeDestroyed() { + return GetReferenceCount() == 0; } static bool IsManagedObject(uintptr_t ptr) { diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 53f8676e9..633e5a53a 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -245,6 +245,17 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, } } + /// + /// + /// THIS IS IMPORTANT! Please refer to + /// Objects returned from CreateInstance are NOT globalized + /// meaning you cannot assign them to a global variable, static field, or some other long term storage + /// because they will be quickly destroyed by the Garbage Collector. + /// You need to manually call ManagedObject::Globalize if you intend to keep the object around + /// + /// + /// The flags to use when creating the instance. + /// A new instance of type . ManagedObject^ CreateInstance(int32_t flags); TypeDefinition^ GetParentType() From 7112c5d63fb601cc77db13e72b31e2dac97c94db Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 11:39:01 -0700 Subject: [PATCH 123/207] .NET: Fix objects not actually getting released (TODO: global caching?) --- csharp-api/REFrameworkNET/ManagedObject.cpp | 14 +++++++++++ csharp-api/REFrameworkNET/ManagedObject.hpp | 28 +++++++++++++++++---- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 78b05afa9..07be9c8ed 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -34,6 +34,20 @@ namespace REFrameworkNET { #endif m_object->add_ref(); + m_weak = false; // Once we add a concrete reference, we can't be weak anymore + } + + void ManagedObject::Release() { + if (m_object == nullptr || m_weak) { + return; + } + +#ifdef REFRAMEWORK_VERBOSE + System::Console::WriteLine("Releasing a reference to" + GetTypeDefinition()->FullName); +#endif + + m_object->release(); + m_weak = true; } TypeDefinition^ ManagedObject::GetTypeDefinition() { diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 05ecf9c57..f85b27425 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -17,24 +17,39 @@ ref class ManagedObject; public ref class ManagedObject : public REFrameworkNET::UnifiedObject { public: - ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj) { + ManagedObject(reframework::API::ManagedObject* obj) + : m_object(obj), + m_weak(true) + { AddRefIfGlobalized(); } - ManagedObject(::REFrameworkManagedObjectHandle handle) : m_object(reinterpret_cast(handle)) { + ManagedObject(::REFrameworkManagedObjectHandle handle) + : m_object(reinterpret_cast(handle)), + m_weak(true) + { AddRefIfGlobalized(); } // Double check if we really want to allow this // We might be better off having a global managed object cache // instead of AddRef'ing every time we create a new ManagedObject - ManagedObject(ManagedObject^ obj) : m_object(obj->m_object) { + ManagedObject(ManagedObject^ obj) + : m_object(obj->m_object), + m_weak(true) + { if (m_object != nullptr) { AddRefIfGlobalized(); } } + // Dispose ~ManagedObject() { + this->!ManagedObject(); + } + + // Finalizer + !ManagedObject() { if (m_object != nullptr) { ReleaseIfGlobalized(); } @@ -91,7 +106,9 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject /// Releases a reference to the managed object /// /// Try to avoid calling this manually except in extraordinary circumstances - void Release() { + void Release(); + + void ForceRelease() { if (m_object == nullptr) { return; } @@ -129,7 +146,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } bool IsGlobalized() { - return GetReferenceCount() >= 0; + return GetReferenceCount() >= 0 || !m_weak; } bool IsGoingToBeDestroyed() { @@ -192,5 +209,6 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject protected: reframework::API::ManagedObject* m_object; + bool m_weak{true}; }; } \ No newline at end of file From be0467fefdc9b0cac407840289b5515bf05f25c6 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 12:02:45 -0700 Subject: [PATCH 124/207] .NET (Test script): Improvements to object explorer --- csharp-api/test/Test/Test.cs | 89 +++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 2cef275c3..710cd883b 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -54,7 +54,7 @@ public static void Entry() { // 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 - var sceneManager = REFrameworkNET.API.GetNativeSingletonT(); + var sceneManager = API.GetNativeSingletonT(); var scene = sceneManager.get_CurrentScene(); var scene2 = sceneManager.get_CurrentScene(); @@ -241,6 +241,10 @@ public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field } public static void DisplayMethod(REFrameworkNET.IObject obj, REFrameworkNET.Method method) { + ImGui.PushID(method.GetDeclaringType().FullName + method.GetMethodSignature()); + var made = ImGui.TreeNodeEx("", ImGuiTreeNodeFlags.SpanFullWidth); + ImGui.SameLine(0.0f, 0.0f); + var returnT = method.GetReturnType(); var returnTName = returnT != null ? returnT.GetFullName() : "null"; @@ -283,6 +287,27 @@ public static void DisplayMethod(REFrameworkNET.IObject obj, REFrameworkNET.Meth ImGui.SameLine(0.0f, 0.0f); ImGui.Text(")"); } + + if (made) { + if ((method.Name.StartsWith("get_") || method.Name.StartsWith("Get") || method.Name == "ToString") && ps.Count == 0) { + object result = null; + obj.HandleInvokeMember_Internal(method, null, ref result); + + if (result != null) { + if (result is IObject objResult) { + DisplayObject(objResult); + } else { + ImGui.Text("Result: " + result.ToString()); + } + } else { + ImGui.Text("Result: null"); + } + } + + ImGui.TreePop(); + } + + ImGui.PopID(); } public static void DisplayType(REFrameworkNET.TypeDefinition t) { @@ -338,6 +363,8 @@ public static void DisplayType(REFrameworkNET.TypeDefinition t) { } } + private static TypeDefinition SystemArrayT = REFrameworkNET.TDB.Get().GetType("System.Array"); + public static void DisplayObject(REFrameworkNET.IObject obj) { if (ImGui.TreeNode("Type Info")) { DisplayType(obj.GetTypeDefinition()); @@ -367,6 +394,42 @@ public static void DisplayObject(REFrameworkNET.IObject obj) { ImGui.TreePop(); } + + if (obj.GetTypeDefinition().IsDerivedFrom(SystemArrayT)) { + ImGui.Text("Array Length: " + (int)obj.Call("get_Length")); + + var easyArray = obj.As<_System.Array>(); + var elementType = obj.GetTypeDefinition().GetElementType(); + + for (int i = 0; i < easyArray.get_Length(); i++) { + var element = easyArray.GetValue(i); + if (element == null) { + ImGui.Text("Element " + i + ": null"); + continue; + } + + var made = ImGui.TreeNodeEx("Element " + i, ImGuiTreeNodeFlags.SpanFullWidth); + + ImGui.SameLine(0.0f, 0.0f); + + if (element is IObject) { + var asString = (element as IObject).Call("ToString") as string; + ImGui.TextColored(TYPE_COLOR, " ("+ asString + ")"); + } else { + ImGui.TextColored(TYPE_COLOR, " ("+ elementType.GetFullName() + ")"); + } + + if (made) { + if (element is IObject objElement) { + DisplayObject(objElement); + } else { + ImGui.Text("Element: " + element.ToString()); + } + + ImGui.TreePop(); + } + } + } } public static void RenderNativeSingletons() { @@ -424,6 +487,30 @@ public static void Render() { RenderNativeSingletons(); } + var appdomainT = REFrameworkNET.API.GetTDB().GetTypeT<_System.AppDomain>(); + var appdomain = appdomainT.get_CurrentDomain(); + var assemblies = appdomain.GetAssemblies(); + + if (ImGui.TreeNode("AppDomain")) { + if (assemblies != null && ImGui.TreeNode("Assemblies")) { + for (int i = 0; i < assemblies.get_Length(); i++) { + var assembly = assemblies.get_Item(i); + var assemblyT = (assembly as IObject).GetTypeDefinition(); + var location = assembly.get_Location() ?? "null"; + + if (ImGui.TreeNode(location)) { + DisplayObject(assembly as IObject); + ImGui.TreePop(); + } + } + + ImGui.TreePop(); + } + + DisplayObject(appdomain as IObject); + ImGui.TreePop(); + } + DisplayType(REFrameworkNET.API.GetTDB().GetType("JsonParser.Value")); } catch (Exception e) { System.Console.WriteLine(e.ToString()); From e1c3d5c909c1a839e4d9eb7c8594edfc017a97ed Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 12:55:59 -0700 Subject: [PATCH 125/207] .NET: Just return arrays as managed objects for now --- csharp-api/REFrameworkNET/UnifiedObject.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/csharp-api/REFrameworkNET/UnifiedObject.cpp b/csharp-api/REFrameworkNET/UnifiedObject.cpp index 1b80db0f3..ef444701c 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.cpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.cpp @@ -153,9 +153,9 @@ namespace REFrameworkNET { if (vm_obj_type > VMObjType::NULL_ && vm_obj_type < VMObjType::ValType) { switch (vm_obj_type) { case VMObjType::Array: - //return sol::make_object(l, *(::sdk::SystemArray**)data); - result = nullptr; - break; // TODO: Implement array + /* + Just return it as an managed object for now + */ default: { //const auto td = utility::re_managed_object::get_type_definition(*(::REManagedObject**)data); auto& obj = field->GetData(addr, field_type->IsValueType()); @@ -169,9 +169,9 @@ namespace REFrameworkNET { // another fallback incase the method returns an object which is an array if (td != nullptr && td->GetVMObjType() == VMObjType::Array) { - //return sol::make_object(l, *(::sdk::SystemArray**)data); - result = nullptr; - break; + /* + Just return it as an managed object for now + */ } result = gcnew ManagedObject(obj); From 720b867e337c2299a4c6a3340570459a2891994e Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 16:36:29 -0700 Subject: [PATCH 126/207] .NET (test script): Display additional info, enum names --- csharp-api/test/Test/Test.cs | 53 ++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 710cd883b..b55812a5e 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -145,6 +145,8 @@ class ObjectExplorer { static System.Numerics.Vector4 FIELD_COLOR = new(156 / 255.0f, 220 / 255.0f, 254 / 255.0f, 1.0f); static System.Numerics.Vector4 METHOD_COLOR = new(220 / 255.0f, 220 / 255.0f, 170 / 255.0f, 1.0f); + static _System.Enum SystemEnumT = REFrameworkNET.TDB.Get().GetTypeT<_System.Enum>(); + public static void DisplayColorPicker() { ImGui.ColorEdit4("Type Color", ref TYPE_COLOR); ImGui.ColorEdit4("Field Color", ref FIELD_COLOR); @@ -229,7 +231,7 @@ public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false)); break;*/ default: - ImGui.Text("Value: " + field.GetDataRaw(obj.GetAddress(), false).ToString("X")); + ImGui.Text("Value (unknown): " + field.GetDataRaw(obj.GetAddress(), false).ToString("X")); break; } } @@ -297,7 +299,15 @@ public static void DisplayMethod(REFrameworkNET.IObject obj, REFrameworkNET.Meth if (result is IObject objResult) { DisplayObject(objResult); } else { - ImGui.Text("Result: " + result.ToString()); + var returnType = method.GetReturnType(); + + if (returnType.IsEnum()) { + long longValue = Convert.ToInt64(result); + var boxedEnum = SystemEnumT.boxEnum(returnType.GetRuntimeType().As<_System.Type>(), longValue); + ImGui.Text("Result: " + (boxedEnum as IObject).Call("ToString()") + " (" + result.ToString() + ")"); + } else { + ImGui.Text("Result: " + result.ToString() + " (" + result.GetType().FullName + ")"); + } } } else { ImGui.Text("Result: null"); @@ -366,24 +376,48 @@ public static void DisplayType(REFrameworkNET.TypeDefinition t) { private static TypeDefinition SystemArrayT = REFrameworkNET.TDB.Get().GetType("System.Array"); public static void DisplayObject(REFrameworkNET.IObject obj) { - if (ImGui.TreeNode("Type Info")) { - DisplayType(obj.GetTypeDefinition()); + if (ImGui.TreeNode("Internals")) { + if (ImGui.TreeNode("Type Info")) { + DisplayType(obj.GetTypeDefinition()); + ImGui.TreePop(); + } + + if (obj is REFrameworkNET.ManagedObject) { + var managed = obj as REFrameworkNET.ManagedObject; + ImGui.Text("Reference count: " + managed.GetReferenceCount().ToString()); + } + ImGui.TreePop(); } if (ImGui.TreeNode("Methods")) { - var methods = obj.GetTypeDefinition().GetMethods(); + var tdef = obj.GetTypeDefinition(); + List methods = new List(); + + for (var parent = tdef; parent != null; parent = parent.ParentType) { + var parentMethods = parent.GetMethods(); + methods.AddRange(parentMethods); + } // Sort methods by name methods.Sort((a, b) => a.GetName().CompareTo(b.GetName())); + // Remove methods that have "!" in their parameters + methods.RemoveAll((m) => m.GetParameters().Exists((p) => p.Type.Name.Contains("!"))); + foreach (var method in methods) { DisplayMethod(obj, method); } } if (ImGui.TreeNode("Fields")) { - var fields = obj.GetTypeDefinition().GetFields(); + var tdef = obj.GetTypeDefinition(); + List fields = new List(); + + for (var parent = tdef; parent != null; parent = parent.ParentType) { + var parentFields = parent.GetFields(); + fields.AddRange(parentFields); + } // Sort fields by name fields.Sort((a, b) => a.GetName().CompareTo(b.GetName())); @@ -400,6 +434,7 @@ public static void DisplayObject(REFrameworkNET.IObject obj) { var easyArray = obj.As<_System.Array>(); var elementType = obj.GetTypeDefinition().GetElementType(); + var elementSize = elementType.GetSize(); for (int i = 0; i < easyArray.get_Length(); i++) { var element = easyArray.GetValue(i); @@ -413,15 +448,17 @@ public static void DisplayObject(REFrameworkNET.IObject obj) { ImGui.SameLine(0.0f, 0.0f); if (element is IObject) { - var asString = (element as IObject).Call("ToString") as string; + var asString = (element as IObject).Call("ToString()") as string; ImGui.TextColored(TYPE_COLOR, " ("+ asString + ")"); } else { - ImGui.TextColored(TYPE_COLOR, " ("+ elementType.GetFullName() + ")"); + ImGui.TextColored(TYPE_COLOR, " ("+ element.ToString() + ")"); } if (made) { if (element is IObject objElement) { + ImGui.PushID((obj.GetAddress() + 0x10 + (ulong)(i * elementSize)).ToString("X")); DisplayObject(objElement); + ImGui.PopID(); } else { ImGui.Text("Element: " + element.ToString()); } From c5a454b0e1d7a1c1b46ac05c219b557b78928c44 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 17:46:17 -0700 Subject: [PATCH 127/207] .NET: Add ManagedObject::Cache for big performance gains --- csharp-api/CMakeLists.txt | 1 - csharp-api/REFrameworkNET/API.cpp | 4 +- csharp-api/REFrameworkNET/ManagedObject.cpp | 1 + csharp-api/REFrameworkNET/ManagedObject.hpp | 71 ++++++++++++++++++++ csharp-api/REFrameworkNET/Method.cpp | 6 +- csharp-api/REFrameworkNET/SystemArray.cpp | 4 -- csharp-api/REFrameworkNET/SystemArray.hpp | 17 ----- csharp-api/REFrameworkNET/SystemString.hpp | 3 + csharp-api/REFrameworkNET/TypeDefinition.cpp | 4 +- csharp-api/REFrameworkNET/UnifiedObject.cpp | 2 +- csharp-api/REFrameworkNET/VM.cpp | 6 +- csharp-api/test/Test/Test.cs | 50 +++++++++++--- 12 files changed, 128 insertions(+), 41 deletions(-) delete mode 100644 csharp-api/REFrameworkNET/SystemArray.cpp delete mode 100644 csharp-api/REFrameworkNET/SystemArray.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 3658fea82..e9cc806be 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -159,7 +159,6 @@ set(csharp-api_SOURCES "REFrameworkNET/PluginLoadContext.cpp" "REFrameworkNET/PluginManager.cpp" "REFrameworkNET/Proxy.cpp" - "REFrameworkNET/SystemArray.cpp" "REFrameworkNET/SystemString.cpp" "REFrameworkNET/TDB.cpp" "REFrameworkNET/TypeDefinition.cpp" diff --git a/csharp-api/REFrameworkNET/API.cpp b/csharp-api/REFrameworkNET/API.cpp index c21ac097c..c88a62650 100644 --- a/csharp-api/REFrameworkNET/API.cpp +++ b/csharp-api/REFrameworkNET/API.cpp @@ -64,7 +64,7 @@ System::Collections::Generic::List^ REFramewo } result->Add(gcnew REFrameworkNET::ManagedSingleton( - gcnew REFrameworkNET::ManagedObject(singleton.instance), + REFrameworkNET::ManagedObject::Get(singleton.instance), gcnew REFrameworkNET::TypeDefinition(singleton.t), gcnew REFrameworkNET::TypeInfo(singleton.type_info) )); @@ -113,7 +113,7 @@ REFrameworkNET::ManagedObject^ REFrameworkNET::API::GetManagedSingleton(System:: return nullptr; } - return gcnew REFrameworkNET::ManagedObject(result); + return REFrameworkNET::ManagedObject::Get(result); } diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 07be9c8ed..1cf7f106b 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -7,6 +7,7 @@ #include "ManagedObject.hpp" #include "Proxy.hpp" +#include "SystemString.hpp" #include "API.hpp" diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index f85b27425..38c085c7f 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -16,7 +16,75 @@ ref class ManagedObject; public ref class ManagedObject : public REFrameworkNET::UnifiedObject { +internal: + template + ref class Cache { + internal: + using WeakT = System::WeakReference; + + static T^ Get(uintptr_t addr) { + if (addr == 0) { + return nullptr; + } + + WeakT^ result = nullptr; + + if (s_cache->TryGetValue(addr, result)) { + T^ strong = nullptr; + + if (result->TryGetTarget(strong)) { + return strong; + } + + Cleanup(addr); + } + + auto obj = gcnew T((::REFrameworkManagedObjectHandle)addr); + result = gcnew WeakT(obj); + if (!s_cache->TryAdd(addr, result)) { + REFrameworkNET::API::LogWarning("Duplicate managed object cache entry for " + addr.ToString("X") + "!, finding the existing entry..."); + + delete obj; + delete result; + if (s_cache->TryGetValue(addr, result)) { + if (result->TryGetTarget(obj)) { + REFrameworkNET::API::LogInfo("Found the existing entry for " + addr.ToString("X") + "!"); + return obj; + } + + Cleanup(addr); + } + + REFrameworkNET::API::LogError("Failed to find the existing entry for " + addr.ToString("X") + "!"); + } + + return obj; + } + + static void Cleanup(uintptr_t entry) { + WeakT^ result = nullptr; + + if (s_cache->TryRemove(entry, result)) { + delete result; + } + } + + private: + static System::Collections::Concurrent::ConcurrentDictionary^ s_cache = gcnew System::Collections::Concurrent::ConcurrentDictionary(); + }; + public: + template + static T^ Get(reframework::API::ManagedObject* obj) { + return Cache::Get((uintptr_t)obj); + } + + template + static T^ Get(::REFrameworkManagedObjectHandle handle) { + return Cache::Get((uintptr_t)handle); + } + +protected: ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj), m_weak(true) @@ -43,6 +111,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } } +internal: // Dispose ~ManagedObject() { this->!ManagedObject(); @@ -55,6 +124,8 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } } +public: + /// /// /// Globalizes the managed object if it is not already globalized
diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index ff8e2ad1c..fd3f28d79 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -273,13 +273,13 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, arrayGetVMObjType() == VMObjType::Object) { - result = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); + result = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult->QWord); return true; } if (returnType->GetVMObjType() == VMObjType::String) { // Maybe don't create the GC version and just use the native one? - auto strObject = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); + auto strObject = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult->QWord); auto strType = strObject->GetTypeDefinition(); const auto firstCharField = strType->GetField("_firstChar"); uint32_t offset = 0; @@ -298,7 +298,7 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, arrayGetVMObjType() == VMObjType::Array) { // TODO? Implement array - result = gcnew REFrameworkNET::ManagedObject((::REFrameworkManagedObjectHandle)tempResult->QWord); + result = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult->QWord); return true; } diff --git a/csharp-api/REFrameworkNET/SystemArray.cpp b/csharp-api/REFrameworkNET/SystemArray.cpp deleted file mode 100644 index fd246492f..000000000 --- a/csharp-api/REFrameworkNET/SystemArray.cpp +++ /dev/null @@ -1,4 +0,0 @@ -#include "SystemArray.hpp" - -namespace REFrameworkNET { -} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/SystemArray.hpp b/csharp-api/REFrameworkNET/SystemArray.hpp deleted file mode 100644 index a39ce8a9b..000000000 --- a/csharp-api/REFrameworkNET/SystemArray.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "ManagedObject.hpp" - -namespace REFrameworkNET { -// RE Engine's implementation of System.Array -generic -public ref class SystemArray : public ManagedObject/*, System::Collections::Generic::IList*/ -{ -public: - SystemArray(ManagedObject^ obj) : ManagedObject(obj) { } - -protected: - -private: -}; -} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/SystemString.hpp b/csharp-api/REFrameworkNET/SystemString.hpp index cb32271fa..2047a9a21 100644 --- a/csharp-api/REFrameworkNET/SystemString.hpp +++ b/csharp-api/REFrameworkNET/SystemString.hpp @@ -8,6 +8,8 @@ namespace REFrameworkNET { public ref class SystemString : public ManagedObject { public: + +internal: SystemString(::REFrameworkManagedObjectHandle handle) : ManagedObject(handle) { @@ -28,6 +30,7 @@ public ref class SystemString : public ManagedObject { SystemString(std::wstring_view str); SystemString(std::string_view str); +public: ::System::String^ ToString() override; private: diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 5595d4a53..071252d2a 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -102,7 +102,7 @@ namespace REFrameworkNET { return nullptr; } - return gcnew ManagedObject(result); + return ManagedObject::Get(result); } REFrameworkNET::TypeInfo^ TypeDefinition::GetTypeInfo() @@ -124,7 +124,7 @@ namespace REFrameworkNET { return nullptr; } - return gcnew ManagedObject(result); + return ManagedObject::Get(result); } REFrameworkNET::InvokeRet^ TypeDefinition::Invoke(System::String^ methodName, array^ args) { diff --git a/csharp-api/REFrameworkNET/UnifiedObject.cpp b/csharp-api/REFrameworkNET/UnifiedObject.cpp index ef444701c..a008fe0c9 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.cpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.cpp @@ -174,7 +174,7 @@ namespace REFrameworkNET { */ } - result = gcnew ManagedObject(obj); + result = ManagedObject::Get(obj); break; } } diff --git a/csharp-api/REFrameworkNET/VM.cpp b/csharp-api/REFrameworkNET/VM.cpp index f8f9d2c4d..48e87b369 100644 --- a/csharp-api/REFrameworkNET/VM.cpp +++ b/csharp-api/REFrameworkNET/VM.cpp @@ -17,7 +17,7 @@ SystemString^ VM::CreateString(::System::String^ str) { return nullptr; } - return gcnew SystemString(objHandle); + return ManagedObject::Get(objHandle); } SystemString^ VM::CreateString(std::wstring_view str) { @@ -29,7 +29,7 @@ SystemString^ VM::CreateString(std::wstring_view str) { return nullptr; } - return gcnew SystemString(objHandle); + return ManagedObject::Get(objHandle); } SystemString^ VM::CreateString(std::string_view str) { @@ -41,6 +41,6 @@ SystemString^ VM::CreateString(std::string_view str) { return nullptr; } - return gcnew SystemString(objHandle); + return ManagedObject::Get(objHandle); } } \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index b55812a5e..f87d43a36 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -170,6 +170,15 @@ public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field ImGui.PushID(address.ToString("X")); ImGui.SetNextItemOpen(false, ImGuiCond.Once); var made = ImGui.TreeNodeEx("", ImGuiTreeNodeFlags.SpanFullWidth); + + // Context menu to copy address to clipboard + if (ImGui.BeginPopupContextItem()) { + if (ImGui.MenuItem("Copy Address to Clipboard")) { + ImGui.SetClipboardText(address.ToString("X")); + } + + ImGui.EndPopup(); + } ImGui.SameLine(); //ImGui.Text(" " + tName); @@ -208,32 +217,57 @@ public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field ImGui.Text("Value: null"); } } else { - switch (tName) { + object fieldData = null; + var finalName = t.IsEnum() ? t.GetUnderlyingType().GetFullName() : tName; + + switch (finalName) { + case "System.Byte": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.SByte": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.Int16": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.UInt16": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; case "System.Int32": - ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + fieldData = field.GetDataT(obj.GetAddress(), false); break; case "System.UInt32": - ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + fieldData = field.GetDataT(obj.GetAddress(), false); break; case "System.Int64": - ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + fieldData = field.GetDataT(obj.GetAddress(), false); break; case "System.UInt64": - ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + fieldData = field.GetDataT(obj.GetAddress(), false); break; case "System.Single": - ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + fieldData = field.GetDataT(obj.GetAddress(), false); break; case "System.Boolean": - ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false).ToString()); + fieldData = field.GetDataT(obj.GetAddress(), false); break; /*case "System.String": ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false)); break;*/ default: - ImGui.Text("Value (unknown): " + field.GetDataRaw(obj.GetAddress(), false).ToString("X")); break; } + + if (t.IsEnum() && fieldData != null) { + long longValue = Convert.ToInt64(fieldData); + var boxedEnum = SystemEnumT.boxEnum(t.GetRuntimeType().As<_System.Type>(), longValue); + ImGui.Text("Result: " + (boxedEnum as IObject).Call("ToString()") + " (" + fieldData.ToString() + ")"); + } else if (fieldData != null) { + ImGui.Text("Value: " + fieldData.ToString()); + //ImGui.Text("Value (" + t.FullName + ")" + field.GetDataRaw(obj.GetAddress(), false).ToString("X")); + } else { + ImGui.Text("Value: null"); + } } ImGui.TreePop(); From 429ddd45873ea56b0d4477173e7f291bf946e3c3 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 20:04:14 -0700 Subject: [PATCH 128/207] .NET: Reduce GC hitching somewhat --- csharp-api/CMakeLists.txt | 1 - csharp-api/REFrameworkNET/ManagedObject.hpp | 7 ++++--- csharp-api/REFrameworkNET/SystemString.hpp | 8 ++++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index e9cc806be..0e46333bc 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -185,7 +185,6 @@ set(csharp-api_SOURCES "REFrameworkNET/PluginManager.hpp" "REFrameworkNET/Property.hpp" "REFrameworkNET/Proxy.hpp" - "REFrameworkNET/SystemArray.hpp" "REFrameworkNET/SystemString.hpp" "REFrameworkNET/TDB.hpp" "REFrameworkNET/TypeDefinition.hpp" diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 38c085c7f..ec2234fcb 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -44,8 +44,9 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject if (!s_cache->TryAdd(addr, result)) { REFrameworkNET::API::LogWarning("Duplicate managed object cache entry for " + addr.ToString("X") + "!, finding the existing entry..."); - delete obj; - delete result; + + obj = nullptr; + result = nullptr; if (s_cache->TryGetValue(addr, result)) { if (result->TryGetTarget(obj)) { REFrameworkNET::API::LogInfo("Found the existing entry for " + addr.ToString("X") + "!"); @@ -65,7 +66,6 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject WeakT^ result = nullptr; if (s_cache->TryRemove(entry, result)) { - delete result; } } @@ -121,6 +121,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject !ManagedObject() { if (m_object != nullptr) { ReleaseIfGlobalized(); + ManagedObject::Cache::Cleanup((uintptr_t)m_object); } } diff --git a/csharp-api/REFrameworkNET/SystemString.hpp b/csharp-api/REFrameworkNET/SystemString.hpp index 2047a9a21..30fe12a04 100644 --- a/csharp-api/REFrameworkNET/SystemString.hpp +++ b/csharp-api/REFrameworkNET/SystemString.hpp @@ -30,6 +30,14 @@ public ref class SystemString : public ManagedObject { SystemString(std::wstring_view str); SystemString(std::string_view str); + ~SystemString() { + this->!SystemString(); + } + + !SystemString() { + ManagedObject::Cache::Cleanup((uintptr_t)m_object); + } + public: ::System::String^ ToString() override; From 6dcec13639fb8a84bac8a9927ee0b48786090fd8 Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 12 Apr 2024 22:16:27 -0700 Subject: [PATCH 129/207] .NET: Further optimizations to the managed object cache --- csharp-api/REFrameworkNET/ManagedObject.cpp | 11 ++- csharp-api/REFrameworkNET/ManagedObject.hpp | 74 +++++++++++++-------- csharp-api/REFrameworkNET/SystemString.hpp | 9 +++ csharp-api/test/Test/Test.cs | 2 + 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 1cf7f106b..54295466d 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -15,7 +15,16 @@ namespace REFrameworkNET { ManagedObject^ ManagedObject::Globalize() { - if (m_object == nullptr || IsGlobalized()) { + if (m_object == nullptr) { + return this; + } + + if (IsGlobalized()) { + // If someone wants to globalize a weak reference, upgrade it to a concrete reference + if (m_weak) { + AddRef(); + } + return this; } diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index ec2234fcb..5c7099c24 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -22,44 +22,45 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject internal: using WeakT = System::WeakReference; + static WeakT^ AddValueFactory(uintptr_t key, T^ arg) { + return gcnew WeakT(arg); + } + + static System::Func^ addValueFactory = gcnew System::Func(AddValueFactory); + + static WeakT^ UpdateFactory(uintptr_t key, WeakT^ value, T^ arg) { + return gcnew WeakT(arg); + } + + static System::Func^ updateFactory = gcnew System::Func(UpdateFactory); + static T^ Get(uintptr_t addr) { if (addr == 0) { return nullptr; } + // Do not cache local objects, it's just a burden on the cache + if (*(int32_t*)(addr + 0x8) < 0) { + return gcnew T((::REFrameworkManagedObjectHandle)addr, false); // Local object, false for not cached + } + WeakT^ result = nullptr; + T^ strong = nullptr; if (s_cache->TryGetValue(addr, result)) { - T^ strong = nullptr; - if (result->TryGetTarget(strong)) { return strong; } - Cleanup(addr); +#ifdef REFRAMEWORK_VERBOSE + REFrameworkNET::API::LogWarning("Existing entry for " + addr.ToString("X") + " is dead! Updating..."); +#endif } - auto obj = gcnew T((::REFrameworkManagedObjectHandle)addr); - result = gcnew WeakT(obj); - if (!s_cache->TryAdd(addr, result)) { - REFrameworkNET::API::LogWarning("Duplicate managed object cache entry for " + addr.ToString("X") + "!, finding the existing entry..."); - - - obj = nullptr; - result = nullptr; - if (s_cache->TryGetValue(addr, result)) { - if (result->TryGetTarget(obj)) { - REFrameworkNET::API::LogInfo("Found the existing entry for " + addr.ToString("X") + "!"); - return obj; - } + strong = gcnew T((::REFrameworkManagedObjectHandle)addr); + s_cache->AddOrUpdate(addr, addValueFactory, updateFactory, strong); - Cleanup(addr); - } - - REFrameworkNET::API::LogError("Failed to find the existing entry for " + addr.ToString("X") + "!"); - } - - return obj; + return strong; } static void Cleanup(uintptr_t entry) { @@ -70,7 +71,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } private: - static System::Collections::Concurrent::ConcurrentDictionary^ s_cache = gcnew System::Collections::Concurrent::ConcurrentDictionary(); + static System::Collections::Concurrent::ConcurrentDictionary^ s_cache = gcnew System::Collections::Concurrent::ConcurrentDictionary(8, 8192); }; public: @@ -85,7 +86,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } protected: - ManagedObject(reframework::API::ManagedObject* obj) + ManagedObject(reframework::API::ManagedObject* obj) : m_object(obj), m_weak(true) { @@ -99,6 +100,14 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject AddRefIfGlobalized(); } + ManagedObject(::REFrameworkManagedObjectHandle handle, bool cached) + : m_object(reinterpret_cast(handle)), + m_weak(true), + m_cached(cached) + { + AddRefIfGlobalized(); + } + // Double check if we really want to allow this // We might be better off having a global managed object cache // instead of AddRef'ing every time we create a new ManagedObject @@ -119,9 +128,17 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject // Finalizer !ManagedObject() { - if (m_object != nullptr) { + if (m_object == nullptr) { + return; + } + + // Only if we are not marked as weak are we allowed to release the object, even if the object is globalized + if (!m_weak) { + if (m_cached) { + ManagedObject::Cache::Cleanup((uintptr_t)m_object); + } + ReleaseIfGlobalized(); - ManagedObject::Cache::Cleanup((uintptr_t)m_object); } } @@ -281,6 +298,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject protected: reframework::API::ManagedObject* m_object; - bool m_weak{true}; + bool m_weak{true}; // Can be upgraded to a global object after it's created, but not to a cached object. + bool m_cached{false}; // Cannot be upgraded to a cached object if it was created on a non-globalized object }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/SystemString.hpp b/csharp-api/REFrameworkNET/SystemString.hpp index 30fe12a04..48b0c578b 100644 --- a/csharp-api/REFrameworkNET/SystemString.hpp +++ b/csharp-api/REFrameworkNET/SystemString.hpp @@ -10,6 +10,15 @@ public ref class SystemString : public ManagedObject { public: internal: + SystemString(::REFrameworkManagedObjectHandle handle, bool cached) + : ManagedObject(handle, cached) + { + const auto td = this->GetTypeDefinition(); + if (td == nullptr || td->GetVMObjType() != VMObjType::String) { + throw gcnew System::ArgumentException("object is not a System.String"); + } + } + SystemString(::REFrameworkManagedObjectHandle handle) : ManagedObject(handle) { diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index f87d43a36..38eee6167 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -416,6 +416,8 @@ public static void DisplayObject(REFrameworkNET.IObject obj) { ImGui.TreePop(); } + ImGui.Text("Address: 0x" + obj.GetAddress().ToString("X")); + if (obj is REFrameworkNET.ManagedObject) { var managed = obj as REFrameworkNET.ManagedObject; ImGui.Text("Reference count: " + managed.GetReferenceCount().ToString()); From 6264f8379cc48e2a04d360966dda99a8d90bbbdf Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 15 Apr 2024 18:36:47 -0700 Subject: [PATCH 130/207] .NET: Use an object pooling system instead for managed objects --- csharp-api/REFrameworkNET/ManagedObject.cpp | 26 +++ csharp-api/REFrameworkNET/ManagedObject.hpp | 170 ++++++++++++++------ csharp-api/REFrameworkNET/PluginManager.cpp | 10 ++ csharp-api/REFrameworkNET/SystemString.hpp | 15 +- 4 files changed, 163 insertions(+), 58 deletions(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 54295466d..9edb8d733 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -13,7 +13,33 @@ #include "Utility.hpp" +using namespace ImGuiNET; + namespace REFrameworkNET { + void ManagedObject::CleanupKnownCaches() { + Cache::CleanupAll(); + Cache::CleanupAll(); + } + + ManagedObject::!ManagedObject() { + if (m_object == nullptr) { + return; + } + + // Only if we are not marked as weak are we allowed to release the object, even if the object is globalized + if (!m_weak) { + if (m_cached) { + if (!ShuttingDown) { + m_finalizerDelegate(this); + } else { + // Nothing? + } + } + + ReleaseIfGlobalized(); + } + } + ManagedObject^ ManagedObject::Globalize() { if (m_object == nullptr) { return this; diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 5c7099c24..94060af64 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -17,22 +17,41 @@ ref class ManagedObject; public ref class ManagedObject : public REFrameworkNET::UnifiedObject { internal: - template + delegate void FinalizerDelegate(ManagedObject^ obj); + + template ref class Cache { internal: - using WeakT = System::WeakReference; + using WeakT = System::WeakReference; - static WeakT^ AddValueFactory(uintptr_t key, T^ arg) { - return gcnew WeakT(arg); + static WeakT^ AddValueFactory(uintptr_t key, ManagedObject^ arg) { + return gcnew WeakT(arg, true); } - static System::Func^ addValueFactory = gcnew System::Func(AddValueFactory); + static System::Func^ addValueFactory = gcnew System::Func(AddValueFactory); - static WeakT^ UpdateFactory(uintptr_t key, WeakT^ value, T^ arg) { - return gcnew WeakT(arg); + static WeakT^ UpdateFactory(uintptr_t key, WeakT^ value, ManagedObject^ arg) { + return gcnew WeakT(arg, true); } - static System::Func^ updateFactory = gcnew System::Func(UpdateFactory); + static System::Func^ updateFactory = gcnew System::Func(UpdateFactory); + + static T^ GetIfExists(uintptr_t addr) { + if (addr == 0) { + return nullptr; + } + + WeakT^ result = nullptr; + T^ strong = nullptr; + + if (s_impl->cache->TryGetValue(addr, result)) { + if (result->TryGetTarget(strong)) { + return strong; + } + } + + return nullptr; + } static T^ Get(uintptr_t addr) { if (addr == 0) { @@ -47,33 +66,83 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject WeakT^ result = nullptr; T^ strong = nullptr; - if (s_cache->TryGetValue(addr, result)) { + if (s_impl->cache->TryGetValue(addr, result)) { if (result->TryGetTarget(strong)) { return strong; } #ifdef REFRAMEWORK_VERBOSE - REFrameworkNET::API::LogWarning("Existing entry for " + addr.ToString("X") + " is dead! Updating..."); + System::Console::WriteLine("Existing entry for " + addr.ToString("X") + " is dead! Updating..."); #endif } - strong = gcnew T((::REFrameworkManagedObjectHandle)addr); - s_cache->AddOrUpdate(addr, addValueFactory, updateFactory, strong); + strong = GetFromPool((::REFrameworkManagedObjectHandle)addr); + s_impl->cache->AddOrUpdate(addr, addValueFactory, updateFactory, (ManagedObject^)strong); return strong; } - static void Cleanup(uintptr_t entry) { - WeakT^ result = nullptr; + static void ReturnToPool(ManagedObject^ obj) { + // Remove the weak reference from the cache + WeakT^ weak = nullptr; + s_impl->cache->TryRemove((uintptr_t)obj->GetAddress(), weak); + + System::GC::ReRegisterForFinalize(obj); + obj->Deinitialize(); + s_impl->pool->Enqueue((ManagedObject^)obj); + } - if (s_cache->TryRemove(entry, result)) { + static void OnObjectFinalize(ManagedObject^ obj) { + ReturnToPool(obj); + } + + static T^ GetFromPool(::REFrameworkManagedObjectHandle handle) { + ManagedObject^ obj = nullptr; + if (s_impl->pool->TryDequeue(obj)) { + obj->Initialize(handle); + return (T^)obj; } + + // Create a new entry + obj = (ManagedObject^)(gcnew T(handle, true)); + obj->SetFinalizerDelegate(s_finalizerDelegate); + + // Object will be returned to pool once the consumer is done with it + return (T^)obj; + } + + static void DisplayStats() { + if (ImGuiNET::ImGui::TreeNode(T::typeid->Name + " Cache")) { + ImGuiNET::ImGui::Text("Cache size: " + s_impl->cache->Count.ToString()); + ImGuiNET::ImGui::Text("Pool size: " + s_impl->pool->Count.ToString()); + ImGuiNET::ImGui::TreePop(); + } + } + + static void CleanupAll() { + s_impl = gcnew Impl(); } private: - static System::Collections::Concurrent::ConcurrentDictionary^ s_cache = gcnew System::Collections::Concurrent::ConcurrentDictionary(8, 8192); + ref struct Impl { + ~Impl() { + this->!Impl(); + } + + !Impl() { + } + + System::Collections::Concurrent::ConcurrentDictionary^ cache = gcnew System::Collections::Concurrent::ConcurrentDictionary(8, 8192); + System::Collections::Concurrent::ConcurrentQueue^ pool = gcnew System::Collections::Concurrent::ConcurrentQueue(); + }; + + static Impl^ s_impl = gcnew Impl(); + static FinalizerDelegate^ s_finalizerDelegate = gcnew FinalizerDelegate(OnObjectFinalize); }; +internal: + static void CleanupKnownCaches(); + public: template static T^ Get(reframework::API::ManagedObject* obj) { @@ -86,20 +155,6 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } protected: - ManagedObject(reframework::API::ManagedObject* obj) - : m_object(obj), - m_weak(true) - { - AddRefIfGlobalized(); - } - - ManagedObject(::REFrameworkManagedObjectHandle handle) - : m_object(reinterpret_cast(handle)), - m_weak(true) - { - AddRefIfGlobalized(); - } - ManagedObject(::REFrameworkManagedObjectHandle handle, bool cached) : m_object(reinterpret_cast(handle)), m_weak(true), @@ -111,7 +166,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject // Double check if we really want to allow this // We might be better off having a global managed object cache // instead of AddRef'ing every time we create a new ManagedObject - ManagedObject(ManagedObject^ obj) + ManagedObject(ManagedObject^% obj) : m_object(obj->m_object), m_weak(true) { @@ -127,20 +182,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } // Finalizer - !ManagedObject() { - if (m_object == nullptr) { - return; - } - - // Only if we are not marked as weak are we allowed to release the object, even if the object is globalized - if (!m_weak) { - if (m_cached) { - ManagedObject::Cache::Cleanup((uintptr_t)m_object); - } - - ReleaseIfGlobalized(); - } - } + !ManagedObject(); public: @@ -256,8 +298,15 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject return nullptr; } + auto existingEntry = ManagedObject::Cache::GetIfExists(ptr); + + if (existingEntry != nullptr) { + return existingEntry; + } + + // IsManagedObject can be expensive, so only call it if there's no existing entry if (IsManagedObject(ptr)) { - return gcnew ManagedObject((reframework::API::ManagedObject*)ptr); + return ManagedObject::Get((::REFrameworkManagedObjectHandle)ptr); } return nullptr; @@ -296,9 +345,38 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject return _original.get_reflection_method_descriptor(name); }*/ +internal: + static bool ShuttingDown = false; + + void Deinitialize() { + m_initialized = false; + } + + void Initialize(::REFrameworkManagedObjectHandle handle) { + m_object = reinterpret_cast(handle); + m_initialized = true; + m_weak = true; + m_cached = true; + + AddRefIfGlobalized(); + } + + void SetFinalizerDelegate(FinalizerDelegate^ delegate) { + m_finalizerDelegate = delegate; + } + + bool IsInitialized() { + return m_initialized; + } + protected: reframework::API::ManagedObject* m_object; + bool m_weak{true}; // Can be upgraded to a global object after it's created, but not to a cached object. bool m_cached{false}; // Cannot be upgraded to a cached object if it was created on a non-globalized object + bool m_initialized{false}; // Used for the object pool + +internal: + FinalizerDelegate^ m_finalizerDelegate{nullptr}; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index d317fa287..4d9de301c 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -3,6 +3,7 @@ #include "Attributes/Plugin.hpp" #include "MethodHook.hpp" +#include "SystemString.hpp" #include "PluginManager.hpp" using namespace System; @@ -669,6 +670,9 @@ namespace REFrameworkNET { } ImGuiNET::ImGui::Checkbox("Auto Reload", s_auto_reload_plugins); + + ManagedObject::Cache::DisplayStats(); + ManagedObject::Cache::DisplayStats(); for each (PluginState^ state in PluginManager::s_plugin_states) { state->DisplayOptions(); @@ -686,6 +690,9 @@ namespace REFrameworkNET { void PluginManager::PluginState::Unload() { if (load_context != nullptr) { + ManagedObject::ShuttingDown = true; + ManagedObject::CleanupKnownCaches(); + REFrameworkNET::Callbacks::Impl::UnsubscribeAssembly(assembly); REFrameworkNET::MethodHook::UnsubscribeAssembly(assembly); @@ -694,6 +701,9 @@ namespace REFrameworkNET { assembly = nullptr; System::GC::Collect(); + System::GC::WaitForPendingFinalizers(); + + ManagedObject::ShuttingDown = false; } } diff --git a/csharp-api/REFrameworkNET/SystemString.hpp b/csharp-api/REFrameworkNET/SystemString.hpp index 48b0c578b..335db40c7 100644 --- a/csharp-api/REFrameworkNET/SystemString.hpp +++ b/csharp-api/REFrameworkNET/SystemString.hpp @@ -19,15 +19,6 @@ public ref class SystemString : public ManagedObject { } } - SystemString(::REFrameworkManagedObjectHandle handle) - : ManagedObject(handle) - { - const auto td = this->GetTypeDefinition(); - if (td == nullptr || td->GetVMObjType() != VMObjType::String) { - throw gcnew System::ArgumentException("object is not a System.String"); - } - } - SystemString(ManagedObject^% object) : ManagedObject(object) { const auto td = object->GetTypeDefinition(); if (td == nullptr || td->GetVMObjType() != VMObjType::String) { @@ -39,13 +30,13 @@ public ref class SystemString : public ManagedObject { SystemString(std::wstring_view str); SystemString(std::string_view str); - ~SystemString() { + /*~SystemString() { this->!SystemString(); } !SystemString() { - ManagedObject::Cache::Cleanup((uintptr_t)m_object); - } + ManagedObject::!ManagedObject(); + }*/ public: ::System::String^ ToString() override; From 3955aa40aa8158b9c540fda8d3e17eb31245c2fe Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 15 Apr 2024 21:01:07 -0700 Subject: [PATCH 131/207] .NET: Rework plugin unloading to use an attribute --- csharp-api/REFrameworkNET/Attributes/Plugin.hpp | 6 ++++++ csharp-api/REFrameworkNET/PluginManager.cpp | 16 ++++++++++------ csharp-api/test/Test/Test.cs | 11 ++++++----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/csharp-api/REFrameworkNET/Attributes/Plugin.hpp b/csharp-api/REFrameworkNET/Attributes/Plugin.hpp index ddf3a6d0e..334f0d41f 100644 --- a/csharp-api/REFrameworkNET/Attributes/Plugin.hpp +++ b/csharp-api/REFrameworkNET/Attributes/Plugin.hpp @@ -7,5 +7,11 @@ namespace REFrameworkNET { public: PluginEntryPoint() {} }; + + [System::AttributeUsage(System::AttributeTargets::Method, AllowMultiple = true)] + public ref class PluginExitPoint : System::Attribute { + public: + PluginExitPoint() {} + }; } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 4d9de301c..4aa1818e7 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -571,14 +571,18 @@ namespace REFrameworkNET { auto path = state->script_path; REFrameworkNET::API::LogInfo("Attempting to initiate first phase unload of " + state->script_path); - // Look for the Unload method in the target assembly which takes an REFrameworkNET.API instance + // Look for the PluginExitPoint attribute in the assembly for each (Type ^ t in assem->GetTypes()) { - auto method = t->GetMethod("OnUnload", System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public); + auto methods = t->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic); - if (method != nullptr) { - REFrameworkNET::API::LogInfo("Unloading plugin by calling " + method->Name + " in " + t->FullName); - method->Invoke(nullptr, nullptr); - } + for each (System::Reflection::MethodInfo^ method in methods) { + array^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginExitPoint::typeid, true); + + if (attributes->Length > 0) { + REFrameworkNET::API::LogInfo("Unloading plugin by calling " + method->Name + " in " + t->FullName); + method->Invoke(nullptr, nullptr); + } + } } state->Unload(); diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 38eee6167..ea8d0f8d2 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -597,11 +597,6 @@ class REFrameworkPlugin { static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); - // To be called when the AssemblyLoadContext is unloading the assembly - public static void OnUnload() { - REFrameworkNET.API.LogInfo("Unloading Test"); - } - // Assigned in a callback below. public static void RenderImGui() { if (ImGui.Begin("Test Window")) { @@ -657,6 +652,12 @@ public static void TestCallbacks() { REFrameworkNET.Callbacks.ImGuiRender.Pre += RenderImGui; } + // To be called when the AssemblyLoadContext is unloading the assembly + [REFrameworkNET.Attributes.PluginExitPoint] + public static void OnUnload() { + REFrameworkNET.API.LogInfo("Unloading Test"); + } + [REFrameworkNET.Attributes.PluginEntryPoint] public static void Main() { try { From 626eae86bc0b8aa1d62be3fd4b888f3f2700bdc9 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 16 Apr 2024 01:48:52 -0700 Subject: [PATCH 132/207] .NET: Optimize InvokeRet to use stack allocation --- csharp-api/REFrameworkNET/IObject.hpp | 2 +- csharp-api/REFrameworkNET/IProxyable.hpp | 2 +- csharp-api/REFrameworkNET/InvokeRet.hpp | 101 ++++------- csharp-api/REFrameworkNET/ManagedObject.hpp | 2 +- csharp-api/REFrameworkNET/Method.cpp | 171 +++++++++---------- csharp-api/REFrameworkNET/Method.hpp | 7 +- csharp-api/REFrameworkNET/NativeObject.hpp | 2 +- csharp-api/REFrameworkNET/TypeDefinition.hpp | 2 +- csharp-api/REFrameworkNET/UnifiedObject.hpp | 2 +- 9 files changed, 128 insertions(+), 163 deletions(-) diff --git a/csharp-api/REFrameworkNET/IObject.hpp b/csharp-api/REFrameworkNET/IObject.hpp index a2e6de48e..45f874115 100644 --- a/csharp-api/REFrameworkNET/IObject.hpp +++ b/csharp-api/REFrameworkNET/IObject.hpp @@ -6,7 +6,7 @@ namespace REFrameworkNET { ref class TypeDefinition; -ref struct InvokeRet; +value struct InvokeRet; // Base interface of ManagedObject and NativeObject public interface class IObject : public IProxyable, public System::IEquatable { diff --git a/csharp-api/REFrameworkNET/IProxyable.hpp b/csharp-api/REFrameworkNET/IProxyable.hpp index c8c8e5a42..812eb16b7 100644 --- a/csharp-api/REFrameworkNET/IProxyable.hpp +++ b/csharp-api/REFrameworkNET/IProxyable.hpp @@ -3,7 +3,7 @@ #include namespace REFrameworkNET { -ref struct InvokeRet; +value struct InvokeRet; public interface class IProxyable { void* Ptr(); diff --git a/csharp-api/REFrameworkNET/InvokeRet.hpp b/csharp-api/REFrameworkNET/InvokeRet.hpp index 5d7f75297..cfb63d450 100644 --- a/csharp-api/REFrameworkNET/InvokeRet.hpp +++ b/csharp-api/REFrameworkNET/InvokeRet.hpp @@ -6,85 +6,46 @@ #pragma managed namespace REFrameworkNET { -public ref struct InvokeRet { - InvokeRet(const reframework::InvokeRet& ret) { - using namespace System::Runtime::InteropServices; - Marshal::Copy(System::IntPtr((void*)&ret), m_invokeRetBytes, 0, sizeof(::reframework::InvokeRet)); - } +[System::Runtime::InteropServices::StructLayout(System::Runtime::InteropServices::LayoutKind::Explicit, Pack = 1, Size = 129)] +public value struct InvokeRet { + [System::Runtime::InteropServices::FieldOffset(0), System::Runtime::InteropServices::MarshalAs( + System::Runtime::InteropServices::UnmanagedType::ByValArray, + ArraySubType = System::Runtime::InteropServices::UnmanagedType::U1, + SizeConst = 128) + ] + unsigned char Bytes; - reframework::InvokeRet* Marshal() { - pin_ptr pinned_bytes = &m_invokeRetBytes[0]; - uint8_t* bytes = pinned_bytes; + [System::Runtime::InteropServices::FieldOffset(0)] + uint8_t Byte; - reframework::InvokeRet* ret = reinterpret_cast(bytes); + [System::Runtime::InteropServices::FieldOffset(0)] + uint16_t Word; - return ret; - } + [System::Runtime::InteropServices::FieldOffset(0)] + uint32_t DWord; - property System::Span Bytes { - public: - System::Span get() { - pin_ptr pinned_bytes = &m_invokeRetBytes[0]; - uint8_t* bytes = pinned_bytes; - return System::Span(bytes, 128); - } - }; + [System::Runtime::InteropServices::FieldOffset(0)] + uint64_t QWord; - property uint8_t Byte { - public: - uint8_t get() { - return m_invokeRetBytes[0]; - } - } + [System::Runtime::InteropServices::FieldOffset(0)] + float Float; - property uint16_t Word { - public: - uint16_t get() { - return Marshal()->word; - } - } + [System::Runtime::InteropServices::FieldOffset(0)] + double Double; - property uint32_t DWord { - public: - uint32_t get() { - return Marshal()->dword; - } - } - property float Float { - public: - float get() { - return Marshal()->f; - } - } - property uint64_t QWord { - public: - uint64_t get() { - return Marshal()->qword; - } - } + [System::Runtime::InteropServices::FieldOffset(0)] + uintptr_t Ptr; - property double Double { - public: - double get() { - return Marshal()->d; - } - } - property System::Object^ Ptr { - public: - System::Object^ get() { - return System::UIntPtr(Marshal()->ptr); - } - } + [System::Runtime::InteropServices::FieldOffset(128)] + bool ExceptionThrown; - property bool ExceptionThrown { - public: - bool get() { - return Marshal()->exception_thrown; - } + // Method to convert unmanaged InvokeRet to managed InvokeRet + static InvokeRet FromNative(const reframework::InvokeRet& native) { + InvokeRet managed; + pin_ptr pinned = &managed.Bytes; + memcpy(pinned, &native.bytes, 129); + managed.ExceptionThrown = native.exception_thrown; + return managed; } - -private: - //::reframework::InvokeRet m_impl; - array^ m_invokeRetBytes = gcnew array(sizeof(::reframework::InvokeRet)); }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 94060af64..fda437612 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -11,7 +11,7 @@ namespace REFrameworkNET { ref class TypeDefinition; ref class TypeInfo; -ref class InvokeRet; +value struct InvokeRet; ref class ManagedObject; public ref class ManagedObject : public REFrameworkNET::UnifiedObject diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index fd3f28d79..abcd230bf 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -128,7 +128,11 @@ bool Method::IsOverride() { return GetMatchingParentMethods()->Count > 0; } -REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, array^ args) { +REFrameworkNET::InvokeRet Method::Invoke(System::Object^ obj, array^ args) { + return REFrameworkNET::InvokeRet::FromNative(Invoke_Internal(obj, args)); +} + +::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, array^ args) { if (obj == nullptr && !this->IsStatic()) { System::String^ declaringName = this->GetDeclaringType() != nullptr ? this->GetDeclaringType()->GetFullName() : gcnew System::String("UnknownType"); System::String^ errorStr = "Cannot invoke a non-static method without an object (" + declaringName + "." + this->GetName() + ")"; @@ -248,110 +252,105 @@ REFrameworkNET::InvokeRet^ Method::Invoke(System::Object^ obj, arrayMessage); } - const auto native_result = m_method->invoke((reframework::API::ManagedObject*)obj_ptr, args2); - - return gcnew REFrameworkNET::InvokeRet(native_result); + return m_method->invoke((reframework::API::ManagedObject*)obj_ptr, args2); } bool Method::HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result) { - //auto methodName = binder->Name; - auto tempResult = this->Invoke(obj, args); + auto tempResult = this->Invoke_Internal(obj, args); + auto returnType = this->GetReturnType(); - if (tempResult != nullptr) { - auto returnType = this->GetReturnType(); + if (returnType == nullptr) { + // box the result + result = safe_cast(REFrameworkNET::InvokeRet::FromNative(tempResult)); + return true; + } - if (returnType == nullptr) { - result = tempResult; + // Check the return type of the method and return it as a NativeObject if possible + if (!returnType->IsValueType()) { + if (tempResult.qword == 0) { + result = nullptr; return true; } - // Check the return type of the method and return it as a NativeObject if possible - if (!returnType->IsValueType()) { - if (tempResult->QWord == 0) { - result = nullptr; - return true; - } - - if (returnType->GetVMObjType() == VMObjType::Object) { - result = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult->QWord); - return true; - } - - if (returnType->GetVMObjType() == VMObjType::String) { - // Maybe don't create the GC version and just use the native one? - auto strObject = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult->QWord); - auto strType = strObject->GetTypeDefinition(); - const auto firstCharField = strType->GetField("_firstChar"); - uint32_t offset = 0; - - if (firstCharField != nullptr) { - offset = strType->GetField("_firstChar")->GetOffsetFromBase(); - } else { - const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)tempResult->QWord - sizeof(void*)); - offset = fieldOffset + 4; - } + if (returnType->GetVMObjType() == VMObjType::Object) { + result = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult.qword); + return true; + } - wchar_t* chars = (wchar_t*)((uintptr_t)strObject->Ptr() + offset); - result = gcnew System::String(chars); - return true; - } + if (returnType->GetVMObjType() == VMObjType::String) { + // Maybe don't create the GC version and just use the native one? + auto strObject = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult.qword); + auto strType = strObject->GetTypeDefinition(); + const auto firstCharField = strType->GetField("_firstChar"); + uint32_t offset = 0; - if (returnType->GetVMObjType() == VMObjType::Array) { - // TODO? Implement array - result = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult->QWord); - return true; + if (firstCharField != nullptr) { + offset = strType->GetField("_firstChar")->GetOffsetFromBase(); + } else { + const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)tempResult.qword - sizeof(void*)); + offset = fieldOffset + 4; } - // TODO: other managed types - result = gcnew REFrameworkNET::NativeObject((uintptr_t)tempResult->QWord, returnType); + wchar_t* chars = (wchar_t*)((uintptr_t)strObject->Ptr() + offset); + result = gcnew System::String(chars); return true; } - if (returnType->IsEnum()) { - if (auto underlying = returnType->GetUnderlyingType(); underlying != nullptr) { - returnType = underlying; // easy mode - } + if (returnType->GetVMObjType() == VMObjType::Array) { + // TODO? Implement array + result = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult.qword); + return true; } - const auto raw_rt = (reframework::API::TypeDefinition*)returnType; - - #define CONCAT_X_C(X, DOT, C) X ## DOT ## C - - #define MAKE_TYPE_HANDLER_2(X, C, Y, Z) \ - case CONCAT_X_C(#X, ".", #C)_fnv: \ - result = gcnew X::C((Y)tempResult->Z); \ - break; - - switch (REFrameworkNET::hash(raw_rt->get_full_name().c_str())) { - MAKE_TYPE_HANDLER_2(System, Boolean, bool, Byte) - MAKE_TYPE_HANDLER_2(System, Byte, uint8_t, Byte) - MAKE_TYPE_HANDLER_2(System, UInt16, uint16_t, Word) - MAKE_TYPE_HANDLER_2(System, UInt32, uint32_t, DWord) - MAKE_TYPE_HANDLER_2(System, UInt64, uint64_t, QWord) - MAKE_TYPE_HANDLER_2(System, SByte, int8_t, Byte) - MAKE_TYPE_HANDLER_2(System, Int16, int16_t, Word) - MAKE_TYPE_HANDLER_2(System, Int32, int32_t, DWord) - MAKE_TYPE_HANDLER_2(System, Int64, int64_t, QWord) - // Because invoke wrappers returning a single actually return a double - // for consistency purposes - MAKE_TYPE_HANDLER_2(System, Single, double, Double) - MAKE_TYPE_HANDLER_2(System, Double, double, Double) - case "System.RuntimeTypeHandle"_fnv: { - result = gcnew REFrameworkNET::TypeDefinition((::REFrameworkTypeDefinitionHandle)tempResult->QWord); - break; - } - case "System.RuntimeMethodHandle"_fnv: { - result = gcnew REFrameworkNET::Method((::REFrameworkMethodHandle)tempResult->QWord); - break; - } - case "System.RuntimeFieldHandle"_fnv: { - result = gcnew REFrameworkNET::Field((::REFrameworkFieldHandle)tempResult->QWord); - break; + // TODO: other managed types + result = gcnew REFrameworkNET::NativeObject((uintptr_t)tempResult.qword, returnType); + return true; + } + + if (returnType->IsEnum()) { + if (auto underlying = returnType->GetUnderlyingType(); underlying != nullptr) { + returnType = underlying; // easy mode } - default: - result = tempResult; + } + + const auto raw_rt = (reframework::API::TypeDefinition*)returnType; + + #define CONCAT_X_C(X, DOT, C) X ## DOT ## C + + #define MAKE_TYPE_HANDLER_2(X, C, Y, Z) \ + case CONCAT_X_C(#X, ".", #C)_fnv: \ + result = gcnew X::C((Y)tempResult.Z); \ break; - } + + switch (REFrameworkNET::hash(raw_rt->get_full_name().c_str())) { + MAKE_TYPE_HANDLER_2(System, Boolean, bool, byte) + MAKE_TYPE_HANDLER_2(System, Byte, uint8_t, byte) + MAKE_TYPE_HANDLER_2(System, UInt16, uint16_t, word) + MAKE_TYPE_HANDLER_2(System, UInt32, uint32_t, dword) + MAKE_TYPE_HANDLER_2(System, UInt64, uint64_t, qword) + MAKE_TYPE_HANDLER_2(System, SByte, int8_t, byte) + MAKE_TYPE_HANDLER_2(System, Int16, int16_t, word) + MAKE_TYPE_HANDLER_2(System, Int32, int32_t, dword) + MAKE_TYPE_HANDLER_2(System, Int64, int64_t, qword) + // Because invoke wrappers returning a single actually return a double + // for consistency purposes + MAKE_TYPE_HANDLER_2(System, Single, double, d) + MAKE_TYPE_HANDLER_2(System, Double, double, d) + case "System.RuntimeTypeHandle"_fnv: { + result = gcnew REFrameworkNET::TypeDefinition((::REFrameworkTypeDefinitionHandle)tempResult.qword); + break; + } + case "System.RuntimeMethodHandle"_fnv: { + result = gcnew REFrameworkNET::Method((::REFrameworkMethodHandle)tempResult.qword); + break; + } + case "System.RuntimeFieldHandle"_fnv: { + result = gcnew REFrameworkNET::Field((::REFrameworkFieldHandle)tempResult.qword); + break; + } + default: + result = safe_cast(REFrameworkNET::InvokeRet::FromNative(tempResult)); + break; } return true; diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 53f02b048..0eb2e81a2 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -41,7 +41,12 @@ public ref class Method : public System::IEquatable /// Generally should not be used unless you know what you're doing. /// Use the other invoke method to automatically convert the return value correctly into a usable object. /// - REFrameworkNET::InvokeRet^ Invoke(System::Object^ obj, array^ args); + REFrameworkNET::InvokeRet Invoke(System::Object^ obj, array^ args); + +private: + ::reframework::InvokeRet Invoke_Internal(System::Object^ obj, array^ args); + +public: /// /// Invokes this method with the given arguments. diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index 5ec990ea5..77b21e913 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -9,7 +9,7 @@ #include "ObjectEnumerator.hpp" namespace REFrameworkNET { -ref class InvokeRet; +value struct InvokeRet; // Native objects are objects that are NOT managed objects // However, they still have reflection information associated with them diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 633e5a53a..890230e80 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -14,7 +14,7 @@ ref class Method; ref class Field; ref class Property; ref class TypeInfo; -ref struct InvokeRet; +value struct InvokeRet; /// /// A shorthand enum for determining how a is used in the VM. diff --git a/csharp-api/REFrameworkNET/UnifiedObject.hpp b/csharp-api/REFrameworkNET/UnifiedObject.hpp index 48af611c6..f89ca36cb 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.hpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.hpp @@ -5,7 +5,7 @@ namespace REFrameworkNET { ref class TypeDefinition; -ref struct InvokeRet; +value struct InvokeRet; // UnifiedObject is the base class that ManagedObject and NativeObject will derive from // It will have several shared methods but some unimplemented methods that will be implemented in the derived classes From 20da842b5537e3286c480aa735d13d32118850ae Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 16 Apr 2024 01:56:40 -0700 Subject: [PATCH 133/207] .NET: Managed object fixes --- csharp-api/CMakeLists.txt | 2 ++ csharp-api/REFrameworkNET/ManagedObject.cpp | 20 +++++++++----------- csharp-api/REFrameworkNET/ManagedObject.hpp | 11 +++++++---- csharp-api/REFrameworkNET/SystemString.hpp | 6 +++--- csharp-api/cmake.toml | 1 + 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 0e46333bc..1baeab1c0 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -233,6 +233,8 @@ set_target_properties(csharp-api PROPERTIES net8.0-windows VS_GLOBAL_EnableManagedPackageReferenceSupport true + VS_GLOBAL_ConcurrentGarbageCollection + true ) set(CMKR_TARGET csharp-api) diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index 9edb8d733..d53e7c028 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -21,22 +21,20 @@ namespace REFrameworkNET { Cache::CleanupAll(); } - ManagedObject::!ManagedObject() { + void ManagedObject::Internal_Finalize() { if (m_object == nullptr) { return; } + ReleaseIfGlobalized(); + // Only if we are not marked as weak are we allowed to release the object, even if the object is globalized - if (!m_weak) { - if (m_cached) { - if (!ShuttingDown) { - m_finalizerDelegate(this); - } else { - // Nothing? - } + if (m_cached) { + if (!ShuttingDown) { + m_finalizerDelegate(this); + } else { + // Nothing? } - - ReleaseIfGlobalized(); } } @@ -74,7 +72,7 @@ namespace REFrameworkNET { } void ManagedObject::Release() { - if (m_object == nullptr || m_weak) { + if (m_object == nullptr || m_weak || !m_initialized) { return; } diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index fda437612..275f85295 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -25,13 +25,13 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject using WeakT = System::WeakReference; static WeakT^ AddValueFactory(uintptr_t key, ManagedObject^ arg) { - return gcnew WeakT(arg, true); + return gcnew WeakT(arg); } static System::Func^ addValueFactory = gcnew System::Func(AddValueFactory); static WeakT^ UpdateFactory(uintptr_t key, WeakT^ value, ManagedObject^ arg) { - return gcnew WeakT(arg, true); + return gcnew WeakT(arg); } static System::Func^ updateFactory = gcnew System::Func(UpdateFactory); @@ -85,7 +85,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject static void ReturnToPool(ManagedObject^ obj) { // Remove the weak reference from the cache WeakT^ weak = nullptr; - s_impl->cache->TryRemove((uintptr_t)obj->GetAddress(), weak); + s_impl->cache->TryRemove(obj->GetAddress(), weak); System::GC::ReRegisterForFinalize(obj); obj->Deinitialize(); @@ -182,7 +182,10 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject } // Finalizer - !ManagedObject(); + !ManagedObject() { + Internal_Finalize(); + } + void Internal_Finalize(); public: diff --git a/csharp-api/REFrameworkNET/SystemString.hpp b/csharp-api/REFrameworkNET/SystemString.hpp index 335db40c7..15747d2d2 100644 --- a/csharp-api/REFrameworkNET/SystemString.hpp +++ b/csharp-api/REFrameworkNET/SystemString.hpp @@ -30,13 +30,13 @@ public ref class SystemString : public ManagedObject { SystemString(std::wstring_view str); SystemString(std::string_view str); - /*~SystemString() { + ~SystemString() { this->!SystemString(); } !SystemString() { - ManagedObject::!ManagedObject(); - }*/ + ManagedObject::Internal_Finalize(); + } public: ::System::String^ ToString() override; diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index e0c54f740..d162fb7d5 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -166,6 +166,7 @@ DOTNET_TARGET_FRAMEWORK = "net8.0-windows" # DOTNET_TARGET_FRAMEWORK_VERSION = "net8.0" VS_GLOBAL_EnableManagedPackageReferenceSupport = "true" # VS_PACKAGE_REFERENCES = "Microsoft.CodeAnalysis_4.9.2" +VS_GLOBAL_ConcurrentGarbageCollection = "true" [target.AssemblyGenerator] type = "CSharpSharedTarget" From 0e55d03b57d045168d34f1107ab585ca341dad9f Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 16 Apr 2024 03:52:25 -0700 Subject: [PATCH 134/207] .NET: Reduction of GC allocated objects --- csharp-api/REFrameworkNET/Method.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index abcd230bf..7f9d305ac 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -241,10 +241,8 @@ ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, array(obj).ToPointer(); } else if (obj_t == System::UIntPtr::typeid) { obj_ptr = (void*)(uintptr_t)safe_cast(obj).ToUInt64(); - } else if (obj_t == REFrameworkNET::ManagedObject::typeid) { - obj_ptr = safe_cast(obj)->Ptr(); - } else if (obj_t == REFrameworkNET::NativeObject::typeid) { - obj_ptr = safe_cast(obj)->Ptr(); + } else if (REFrameworkNET::IObject::typeid->IsAssignableFrom(obj_t)) { + obj_ptr = safe_cast(obj)->Ptr(); } else { System::Console::WriteLine("Unknown type passed to method invocation @ obj"); } @@ -278,20 +276,19 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, arrayGetVMObjType() == VMObjType::String) { - // Maybe don't create the GC version and just use the native one? - auto strObject = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult.qword); - auto strType = strObject->GetTypeDefinition(); - const auto firstCharField = strType->GetField("_firstChar"); + auto strObject = (reframework::API::ManagedObject*)tempResult.qword; + auto strType = strObject->get_type_definition(); + const auto firstCharField = strType->find_field("_firstChar"); uint32_t offset = 0; if (firstCharField != nullptr) { - offset = strType->GetField("_firstChar")->GetOffsetFromBase(); + offset = firstCharField->get_offset_from_base(); } else { const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)tempResult.qword - sizeof(void*)); offset = fieldOffset + 4; } - wchar_t* chars = (wchar_t*)((uintptr_t)strObject->Ptr() + offset); + wchar_t* chars = (wchar_t*)((uintptr_t)strObject + offset); result = gcnew System::String(chars); return true; } From e8e00e2dbb25cac8089e0238293c2109231afbae Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 16 Apr 2024 21:25:15 -0700 Subject: [PATCH 135/207] .NET: Initialize ManagedObject by default --- csharp-api/REFrameworkNET/ManagedObject.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 275f85295..1c4ba659e 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -377,7 +377,7 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject bool m_weak{true}; // Can be upgraded to a global object after it's created, but not to a cached object. bool m_cached{false}; // Cannot be upgraded to a cached object if it was created on a non-globalized object - bool m_initialized{false}; // Used for the object pool + bool m_initialized{true}; // Used for the object pool internal: FinalizerDelegate^ m_finalizerDelegate{nullptr}; From 9a7393192d5503f63d9bad4809e2da4e605f6b09 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 17 Apr 2024 17:08:10 -0700 Subject: [PATCH 136/207] API.hpp changes from master --- include/reframework/API.hpp | 356 ++++++++++++++++++++++++------------ 1 file changed, 239 insertions(+), 117 deletions(-) diff --git a/include/reframework/API.hpp b/include/reframework/API.hpp index 90b6c2552..adc73ab71 100644 --- a/include/reframework/API.hpp +++ b/include/reframework/API.hpp @@ -104,15 +104,18 @@ class API { } inline const auto tdb() const { - return (TDB*)sdk()->functions->get_tdb(); + static const auto fn = sdk()->functions->get_tdb; + return (TDB*)fn(); } inline const auto resource_manager() const { - return (ResourceManager*)sdk()->functions->get_resource_manager(); + static const auto fn = sdk()->functions->get_resource_manager; + return (ResourceManager*)fn(); } inline const auto reframework() const { - return (REFramework*)param()->functions; + static const auto fn = param()->functions; + return (REFramework*)fn; } void lock_lua() { @@ -130,28 +133,34 @@ class API { template void log_info(const char* format, Args... args) { m_param->functions->log_info(format, args...); } API::VMContext* get_vm_context() const { - return (API::VMContext*)sdk()->functions->get_vm_context(); + static const auto fn = sdk()->functions->get_vm_context; + return (API::VMContext*)fn(); } API::ManagedObject* typeof(const char* name) const { - return (API::ManagedObject*)sdk()->functions->typeof_(name); + static const auto fn = sdk()->functions->typeof_; + return (API::ManagedObject*)fn(name); } API::ManagedObject* get_managed_singleton(std::string_view name) const { - return (API::ManagedObject*)sdk()->functions->get_managed_singleton(name.data()); + static const auto fn = sdk()->functions->get_managed_singleton; + return (API::ManagedObject*)fn(name.data()); } void* get_native_singleton(std::string_view name) const { - return sdk()->functions->get_native_singleton(name.data()); + static const auto fn = sdk()->functions->get_native_singleton; + return fn(name.data()); } std::vector get_managed_singletons() const { + static const auto fn = sdk()->functions->get_managed_singletons; + std::vector out{}; out.resize(512); uint32_t count{}; - auto result = sdk()->functions->get_managed_singletons(&out[0], out.size() * sizeof(REFrameworkManagedSingleton), &count); + auto result = fn(&out[0], out.size() * sizeof(REFrameworkManagedSingleton), &count); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -168,12 +177,14 @@ class API { } std::vector get_native_singletons() const { + static const auto fn = sdk()->functions->get_native_singletons; + std::vector out{}; out.resize(512); uint32_t count{}; - auto result = sdk()->functions->get_native_singletons(&out[0], out.size() * sizeof(REFrameworkNativeSingleton), &count); + auto result = fn(&out[0], out.size() * sizeof(REFrameworkNativeSingleton), &count); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -196,67 +207,83 @@ class API { } uint32_t get_num_types() const { - return API::s_instance->sdk()->tdb->get_num_types(*this); + static const auto fn = API::s_instance->sdk()->tdb->get_num_types; + return fn(*this); } uint32_t get_num_methods() const { - return API::s_instance->sdk()->tdb->get_num_methods(*this); + static const auto fn = API::s_instance->sdk()->tdb->get_num_methods; + return fn(*this); } uint32_t get_num_fields() const { - return API::s_instance->sdk()->tdb->get_num_fields(*this); + static const auto fn = API::s_instance->sdk()->tdb->get_num_fields; + return fn(*this); } uint32_t get_num_properties() const { - return API::s_instance->sdk()->tdb->get_num_properties(*this); + static const auto fn = API::s_instance->sdk()->tdb->get_num_properties; + return fn(*this); } uint32_t get_strings_size() const { - return API::s_instance->sdk()->tdb->get_strings_size(*this); + static const auto fn = API::s_instance->sdk()->tdb->get_strings_size; + return fn(*this); } uint32_t get_raw_data_size() const { - return API::s_instance->sdk()->tdb->get_raw_data_size(*this); + static const auto fn = API::s_instance->sdk()->tdb->get_raw_data_size; + return fn(*this); } const char* get_string_database() const { - return API::s_instance->sdk()->tdb->get_string_database(*this); + static const auto fn = API::s_instance->sdk()->tdb->get_string_database; + return fn(*this); } uint8_t* get_raw_database() const { - return (uint8_t*)API::s_instance->sdk()->tdb->get_raw_database(*this); + static const auto fn = API::s_instance->sdk()->tdb->get_raw_database; + return (uint8_t*)fn(*this); } API::TypeDefinition* get_type(uint32_t index) const { - return (API::TypeDefinition*)API::s_instance->sdk()->tdb->get_type(*this, index); + static const auto fn = API::s_instance->sdk()->tdb->get_type; + return (API::TypeDefinition*)fn(*this, index); } API::TypeDefinition* find_type(std::string_view name) const { - return (API::TypeDefinition*)API::s_instance->sdk()->tdb->find_type(*this, name.data()); + static const auto fn = API::s_instance->sdk()->tdb->find_type; + return (API::TypeDefinition*)fn(*this, name.data()); } API::TypeDefinition* find_type_by_fqn(uint32_t fqn) const { - return (API::TypeDefinition*)API::s_instance->sdk()->tdb->find_type_by_fqn(*this, fqn); + static const auto fn = API::s_instance->sdk()->tdb->find_type_by_fqn; + return (API::TypeDefinition*)fn(*this, fqn); } API::Method* get_method(uint32_t index) const { - return (API::Method*)API::s_instance->sdk()->tdb->get_method(*this, index); + static const auto fn = API::s_instance->sdk()->tdb->get_method; + return (API::Method*)fn(*this, index); } API::Method* find_method(std::string_view type_name, std::string_view name) const { - return (API::Method*)API::s_instance->sdk()->tdb->find_method(*this, type_name.data(), name.data()); + static const auto fn = API::s_instance->sdk()->tdb->find_method; + return (API::Method*)fn(*this, type_name.data(), name.data()); } API::Field* get_field(uint32_t index) const { - return (API::Field*)API::s_instance->sdk()->tdb->get_field(*this, index); + static const auto fn = API::s_instance->sdk()->tdb->get_field; + return (API::Field*)fn(*this, index); } API::Field* find_field(std::string_view type_name, std::string_view name) const { - return (API::Field*)API::s_instance->sdk()->tdb->find_field(*this, type_name.data(), name.data()); + static const auto fn = API::s_instance->sdk()->tdb->find_field; + return (API::Field*)fn(*this, type_name.data(), name.data()); } API::Property* get_property(uint32_t index) const { - return (API::Property*)API::s_instance->sdk()->tdb->get_property(*this, index); + static const auto fn = API::s_instance->sdk()->tdb->get_property; + return (API::Property*)fn(*this, index); } }; @@ -266,7 +293,8 @@ class API { } bool is_drawing_ui() const { - return API::s_instance->param()->functions->is_drawing_ui(); + static const auto fn = API::s_instance->param()->functions->is_drawing_ui; + return fn(); } }; @@ -276,37 +304,43 @@ class API { } uint32_t get_index() const { - return API::s_instance->sdk()->type_definition->get_index(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_index; + return fn(*this); } uint32_t get_size() const { - return API::s_instance->sdk()->type_definition->get_size(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_size; + return fn(*this); } uint32_t get_valuetype_size() const { - return API::s_instance->sdk()->type_definition->get_valuetype_size(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_valuetype_size; + return fn(*this); } uint32_t get_fqn() const { - return API::s_instance->sdk()->type_definition->get_fqn(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_fqn; + return fn(*this); } const char* get_name() const { - return API::s_instance->sdk()->type_definition->get_name(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_name; + return fn(*this); } const char* get_namespace() const { - return API::s_instance->sdk()->type_definition->get_namespace(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_namespace; + return fn(*this); } std::string get_full_name() const { + static const auto fn = API::s_instance->sdk()->type_definition->get_full_name; + std::string buffer{}; buffer.resize(512); uint32_t real_size{0}; - - const auto sdk = API::s_instance->sdk(); - auto result = sdk->type_definition->get_full_name(*this, &buffer[0], buffer.size(), &real_size); + auto result = fn(*this, &buffer[0], buffer.size(), &real_size); if (result != REFRAMEWORK_ERROR_NONE) { return ""; @@ -317,74 +351,92 @@ class API { } bool has_fieldptr_offset() const { - return API::s_instance->sdk()->type_definition->has_fieldptr_offset(*this); + static const auto fn = API::s_instance->sdk()->type_definition->has_fieldptr_offset; + return fn(*this); } int32_t get_fieldptr_offset() const { - return API::s_instance->sdk()->type_definition->get_fieldptr_offset(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_fieldptr_offset; + return fn(*this); } uint32_t get_num_methods() const { - return API::s_instance->sdk()->type_definition->get_num_methods(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_num_methods; + return fn(*this); } uint32_t get_num_fields() const { - return API::s_instance->sdk()->type_definition->get_num_fields(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_num_fields; + return fn(*this); } uint32_t get_num_properties() const { - return API::s_instance->sdk()->type_definition->get_num_properties(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_num_properties; + return fn(*this); } bool is_derived_from(API::TypeDefinition* other) { - return API::s_instance->sdk()->type_definition->is_derived_from(*this, *other); + static const auto fn = API::s_instance->sdk()->type_definition->is_derived_from; + return fn(*this, *other); } bool is_derived_from(std::string_view other) { - return API::s_instance->sdk()->type_definition->is_derived_from_by_name(*this, other.data()); + static const auto fn = API::s_instance->sdk()->type_definition->is_derived_from_by_name; + return fn(*this, other.data()); } bool is_valuetype() const { - return API::s_instance->sdk()->type_definition->is_valuetype(*this); + static const auto fn = API::s_instance->sdk()->type_definition->is_valuetype; + return fn(*this); } bool is_enum() const { - return API::s_instance->sdk()->type_definition->is_enum(*this); + static const auto fn = API::s_instance->sdk()->type_definition->is_enum; + return fn(*this); } bool is_by_ref() const { - return API::s_instance->sdk()->type_definition->is_by_ref(*this); + static const auto fn = API::s_instance->sdk()->type_definition->is_by_ref; + return fn(*this); } bool is_pointer() const { - return API::s_instance->sdk()->type_definition->is_pointer(*this); + static const auto fn = API::s_instance->sdk()->type_definition->is_pointer; + return fn(*this); } bool is_primitive() const { - return API::s_instance->sdk()->type_definition->is_primitive(*this); + static const auto fn = API::s_instance->sdk()->type_definition->is_primitive; + return fn(*this); } ::REFrameworkVMObjType get_vm_obj_type() const { - return API::s_instance->sdk()->type_definition->get_vm_obj_type(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_vm_obj_type; + return fn(*this); } API::Method* find_method(std::string_view name) const { - return (API::Method*)API::s_instance->sdk()->type_definition->find_method(*this, name.data()); + static const auto fn = API::s_instance->sdk()->type_definition->find_method; + return (API::Method*)fn(*this, name.data()); } API::Field* find_field(std::string_view name) const { - return (API::Field*)API::s_instance->sdk()->type_definition->find_field(*this, name.data()); + static const auto fn = API::s_instance->sdk()->type_definition->find_field; + return (API::Field*)fn(*this, name.data()); } API::Property* find_property(std::string_view name) const { - return (API::Property*)API::s_instance->sdk()->type_definition->find_property(*this, name.data()); + static const auto fn = API::s_instance->sdk()->type_definition->find_property; + return (API::Property*)fn(*this, name.data()); } std::vector get_methods() const { + static const auto fn = API::s_instance->sdk()->type_definition->get_methods; + std::vector methods; methods.resize(get_num_methods()); - auto result = API::s_instance->sdk()->type_definition->get_methods(*this, (REFrameworkMethodHandle*)&methods[0], methods.size() * sizeof(API::Method*), nullptr); + auto result = fn(*this, (REFrameworkMethodHandle*)&methods[0], methods.size() * sizeof(API::Method*), nullptr); if (result != REFRAMEWORK_ERROR_NONE) { return {}; @@ -394,10 +446,12 @@ class API { } std::vector get_fields() const { + static const auto fn = API::s_instance->sdk()->type_definition->get_fields; + std::vector fields; fields.resize(get_num_fields()); - auto result = API::s_instance->sdk()->type_definition->get_fields(*this, (REFrameworkFieldHandle*)&fields[0], fields.size() * sizeof(API::Field*), nullptr); + auto result = fn(*this, (REFrameworkFieldHandle*)&fields[0], fields.size() * sizeof(API::Field*), nullptr); if (result != REFRAMEWORK_ERROR_NONE) { return {}; @@ -412,35 +466,43 @@ class API { } void* get_instance() const { - return API::s_instance->sdk()->type_definition->get_instance(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_instance; + return fn(*this); } void* create_instance_deprecated() const { - return API::s_instance->sdk()->type_definition->create_instance_deprecated(*this); + static const auto fn = API::s_instance->sdk()->type_definition->create_instance_deprecated; + return fn(*this); } API::ManagedObject* create_instance(int flags = 0) const { - return (API::ManagedObject*)API::s_instance->sdk()->type_definition->create_instance(*this, flags); + static const auto fn = API::s_instance->sdk()->type_definition->create_instance; + return (API::ManagedObject*)fn(*this, flags); } API::TypeDefinition* get_parent_type() const { - return (API::TypeDefinition*)API::s_instance->sdk()->type_definition->get_parent_type(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_parent_type; + return (API::TypeDefinition*)fn(*this); } API::TypeDefinition* get_declaring_type() const { - return (API::TypeDefinition*)API::s_instance->sdk()->type_definition->get_declaring_type(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_declaring_type; + return (API::TypeDefinition*)fn(*this); } API::TypeDefinition* get_underlying_type() const { - return (API::TypeDefinition*)API::s_instance->sdk()->type_definition->get_underlying_type(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_underlying_type; + return (API::TypeDefinition*)fn(*this); } API::TypeInfo* get_type_info() const { - return (API::TypeInfo*)API::s_instance->sdk()->type_definition->get_type_info(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_type_info; + return (API::TypeInfo*)fn(*this); } API::ManagedObject* get_runtime_type() const { - return (API::ManagedObject*)API::s_instance->sdk()->type_definition->get_runtime_type(*this); + static const auto fn = API::s_instance->sdk()->type_definition->get_runtime_type; + return (API::ManagedObject*)fn(*this); } }; @@ -450,9 +512,10 @@ class API { } reframework::InvokeRet invoke(API::ManagedObject* obj, const std::vector& args) { + static const auto fn = API::s_instance->sdk()->method->invoke; reframework::InvokeRet out{}; - auto result = API::s_instance->sdk()->method->invoke(*this, obj, (void**)&args[0], args.size() * sizeof(void*), &out, sizeof(out)); + auto result = fn(*this, obj, (void**)&args[0], args.size() * sizeof(void*), &out, sizeof(out)); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -480,11 +543,13 @@ class API { template T get_function() const { - return (T)API::s_instance->sdk()->method->get_function(*this); + static const auto fn = API::s_instance->sdk()->method->get_function; + return (T)fn(*this); } void* get_function_raw() const { - return API::s_instance->sdk()->method->get_function(*this); + static const auto fn = API::s_instance->sdk()->method->get_function; + return fn(*this); } // e.g. call(sdk->get_vm_context(), obj, args...); @@ -494,26 +559,32 @@ class API { } const char* get_name() const { + static const auto fn = API::s_instance->sdk()->method->get_name; return API::s_instance->sdk()->method->get_name(*this); } API::TypeDefinition* get_declaring_type() const { - return (API::TypeDefinition*)API::s_instance->sdk()->method->get_declaring_type(*this); + static const auto fn = API::s_instance->sdk()->method->get_declaring_type; + return (API::TypeDefinition*)fn(*this); } API::TypeDefinition* get_return_type() const { - return (API::TypeDefinition*)API::s_instance->sdk()->method->get_return_type(*this); + static const auto fn = API::s_instance->sdk()->method->get_return_type; + return (API::TypeDefinition*)fn(*this); } uint32_t get_num_params() const { - return API::s_instance->sdk()->method->get_num_params(*this); + static const auto fn = API::s_instance->sdk()->method->get_num_params; + return fn(*this); } std::vector get_params() const { + static const auto fn = API::s_instance->sdk()->method->get_params; + std::vector params; params.resize(get_num_params()); - auto result = API::s_instance->sdk()->method->get_params(*this, (REFrameworkMethodParameter*)¶ms[0], params.size() * sizeof(REFrameworkMethodParameter), nullptr); + auto result = fn(*this, (REFrameworkMethodParameter*)¶ms[0], params.size() * sizeof(REFrameworkMethodParameter), nullptr); #ifdef REFRAMEWORK_API_EXCEPTIONS if (result != REFRAMEWORK_ERROR_NONE) { @@ -529,35 +600,44 @@ class API { } uint32_t get_index() const { - return API::s_instance->sdk()->method->get_index(*this); + static const auto fn = API::s_instance->sdk()->method->get_index; + return fn(*this); } int get_virtual_index() const { - return API::s_instance->sdk()->method->get_virtual_index(*this); + static const auto fn = API::s_instance->sdk()->method->get_virtual_index; + return fn(*this); } bool is_static() const { - return API::s_instance->sdk()->method->is_static(*this); + static const auto fn = API::s_instance->sdk()->method->is_static; + return fn(*this); } uint16_t get_flags() const { - return API::s_instance->sdk()->method->get_flags(*this); + static const auto fn = API::s_instance->sdk()->method->get_flags; + return fn(*this); } uint16_t get_impl_flags() const { - return API::s_instance->sdk()->method->get_impl_flags(*this); + static const auto fn = API::s_instance->sdk()->method->get_impl_flags; + return fn(*this); } uint32_t get_invoke_id() const { - return API::s_instance->sdk()->method->get_invoke_id(*this); + static const auto fn = API::s_instance->sdk()->method->get_invoke_id; + return fn(*this); } unsigned int add_hook(REFPreHookFn pre_fn, REFPostHookFn post_fn, bool ignore_jmp) const { - return API::s_instance->sdk()->functions->add_hook(*this, pre_fn, post_fn, ignore_jmp); + static const auto fn = API::s_instance->sdk()->functions->add_hook; + return fn(*this, pre_fn, post_fn, ignore_jmp); } void remove_hook(unsigned int hook_id) const { - API::s_instance->sdk()->functions->remove_hook(*this, hook_id); + + static const auto fn = API::s_instance->sdk()->functions->remove_hook; + fn(*this, hook_id); } }; @@ -567,43 +647,53 @@ class API { } const char* get_name() const { - return API::s_instance->sdk()->field->get_name(*this); + static const auto fn = API::s_instance->sdk()->field->get_name; + return fn(*this); } API::TypeDefinition* get_declaring_type() const { - return (API::TypeDefinition*)API::s_instance->sdk()->field->get_declaring_type(*this); + static const auto fn = API::s_instance->sdk()->field->get_declaring_type; + return (API::TypeDefinition*)fn(*this); } API::TypeDefinition* get_type() const { - return (API::TypeDefinition*)API::s_instance->sdk()->field->get_type(*this); + static const auto fn = API::s_instance->sdk()->field->get_type; + return (API::TypeDefinition*)fn(*this); } uint32_t get_offset_from_base() const { - return API::s_instance->sdk()->field->get_offset_from_base(*this); + static const auto fn = API::s_instance->sdk()->field->get_offset_from_base; + return fn(*this); } uint32_t get_offset_from_fieldptr() const { - return API::s_instance->sdk()->field->get_offset_from_fieldptr(*this); + static const auto fn = API::s_instance->sdk()->field->get_offset_from_fieldptr; + return fn(*this); } uint32_t get_flags() const { - return API::s_instance->sdk()->field->get_flags(*this); + static const auto fn = API::s_instance->sdk()->field->get_flags; + return fn(*this); } bool is_static() const { - return API::s_instance->sdk()->field->is_static(*this); + static const auto fn = API::s_instance->sdk()->field->is_static; + return fn(*this); } bool is_literal() const { - return API::s_instance->sdk()->field->is_literal(*this); + static const auto fn = API::s_instance->sdk()->field->is_literal; + return fn(*this); } void* get_init_data() const { - return API::s_instance->sdk()->field->get_init_data(*this); + static const auto fn = API::s_instance->sdk()->field->get_init_data; + return fn(*this); } void* get_data_raw(void* obj, bool is_value_type = false) const { - return API::s_instance->sdk()->field->get_data_raw(*this, obj, is_value_type); + static const auto fn = API::s_instance->sdk()->field->get_data_raw; + return fn(*this, obj, is_value_type); } template T& get_data(void* object = nullptr, bool is_value_type = false) const { return *(T*)get_data_raw(object, is_value_type); } @@ -623,43 +713,53 @@ class API { } void add_ref() { - API::s_instance->sdk()->managed_object->add_ref(*this); + static const auto fn = API::s_instance->sdk()->managed_object->add_ref; + fn(*this); } void release() { - API::s_instance->sdk()->managed_object->release(*this); + static const auto fn = API::s_instance->sdk()->managed_object->release; + fn(*this); } API::TypeDefinition* get_type_definition() const { - return (API::TypeDefinition*)API::s_instance->sdk()->managed_object->get_type_definition(*this); + static const auto fn = API::s_instance->sdk()->managed_object->get_type_definition; + return (API::TypeDefinition*)fn(*this); } bool is_managed_object() const { - return API::s_instance->sdk()->managed_object->is_managed_object(*this); + static const auto fn = API::s_instance->sdk()->managed_object->is_managed_object; + return fn(*this); } uint32_t get_ref_count() const { - return API::s_instance->sdk()->managed_object->get_ref_count(*this); + static const auto fn = API::s_instance->sdk()->managed_object->get_ref_count; + return fn(*this); } uint32_t get_vm_obj_type() const { - return API::s_instance->sdk()->managed_object->get_vm_obj_type(*this); + static const auto fn = API::s_instance->sdk()->managed_object->get_vm_obj_type; + return fn(*this); } API::TypeInfo* get_type_info() const { - return (API::TypeInfo*)API::s_instance->sdk()->managed_object->get_type_info(*this); + static const auto fn = API::s_instance->sdk()->managed_object->get_type_info; + return (API::TypeInfo*)fn(*this); } void* get_reflection_properties() const { - return API::s_instance->sdk()->managed_object->get_reflection_properties(*this); + static const auto fn = API::s_instance->sdk()->managed_object->get_reflection_properties; + return fn(*this); } API::ReflectionProperty* get_reflection_property_descriptor(std::string_view name) { - return (API::ReflectionProperty*)API::s_instance->sdk()->managed_object->get_reflection_property_descriptor(*this, name.data()); + static const auto fn = API::s_instance->sdk()->managed_object->get_reflection_property_descriptor; + return (API::ReflectionProperty*)fn(*this, name.data()); } API::ReflectionMethod* get_reflection_method_descriptor(std::string_view name) { - return (API::ReflectionMethod*)API::s_instance->sdk()->managed_object->get_reflection_method_descriptor(*this, name.data()); + static const auto fn = API::s_instance->sdk()->managed_object->get_reflection_method_descriptor; + return (API::ReflectionMethod*)fn(*this, name.data()); } template @@ -719,7 +819,8 @@ class API { } API::Resource* create_resource(std::string_view type_name, std::string_view name) { - return (API::Resource*)API::s_instance->sdk()->resource_manager->create_resource(*this, type_name.data(), name.data()); + static const auto fn = API::s_instance->sdk()->resource_manager->create_resource; + return (API::Resource*)fn(*this, type_name.data(), name.data()); } }; @@ -729,11 +830,13 @@ class API { } void add_ref() { - API::s_instance->sdk()->resource->add_ref(*this); + static const auto fn = API::s_instance->sdk()->resource->add_ref; + fn(*this); } void release() { - API::s_instance->sdk()->resource->release(*this); + static const auto fn = API::s_instance->sdk()->resource->release; + fn(*this); } }; @@ -743,47 +846,58 @@ class API { } const char* get_name() const { - return API::s_instance->sdk()->type_info->get_name(*this); + static const auto fn = API::s_instance->sdk()->type_info->get_name; + return fn(*this); } API::TypeDefinition* get_type_definition() const { - return (API::TypeDefinition*)API::s_instance->sdk()->type_info->get_type_definition(*this); + static const auto fn = API::s_instance->sdk()->type_info->get_type_definition; + return (API::TypeDefinition*)fn(*this); } bool is_clr_type() const { - return API::s_instance->sdk()->type_info->is_clr_type(*this); + static const auto fn = API::s_instance->sdk()->type_info->is_clr_type; + return fn(*this); } bool is_singleton() const { - return API::s_instance->sdk()->type_info->is_singleton(*this); + static const auto fn = API::s_instance->sdk()->type_info->is_singleton; + return fn(*this); } void* get_singleton_instance() const { - return API::s_instance->sdk()->type_info->get_singleton_instance(*this); + static const auto fn = API::s_instance->sdk()->type_info->get_singleton_instance; + return fn(*this); } void* get_reflection_properties() const { - return API::s_instance->sdk()->type_info->get_reflection_properties(*this); + static const auto fn = API::s_instance->sdk()->type_info->get_reflection_properties; + return fn(*this); } API::ReflectionProperty* get_reflection_property_descriptor(std::string_view name) { + static const auto fn = API::s_instance->sdk()->type_info->get_reflection_property_descriptor; return (API::ReflectionProperty*)API::s_instance->sdk()->type_info->get_reflection_property_descriptor(*this, name.data()); } API::ReflectionMethod* get_reflection_method_descriptor(std::string_view name) { - return (API::ReflectionMethod*)API::s_instance->sdk()->type_info->get_reflection_method_descriptor(*this, name.data()); + static const auto fn = API::s_instance->sdk()->type_info->get_reflection_method_descriptor; + return (API::ReflectionMethod*)fn(*this, name.data()); } void* get_deserializer_fn() const { - return API::s_instance->sdk()->type_info->get_deserializer_fn(*this); + static const auto fn = API::s_instance->sdk()->type_info->get_deserializer_fn; + return fn(*this); } API::TypeInfo* get_parent() const { - return (API::TypeInfo*)API::s_instance->sdk()->type_info->get_parent(*this); + static const auto fn = API::s_instance->sdk()->type_info->get_parent; + return (API::TypeInfo*)fn(*this); } uint32_t get_crc() const { - return API::s_instance->sdk()->type_info->get_crc(*this); + static const auto fn = API::s_instance->sdk()->type_info->get_crc; + return fn(*this); } }; @@ -793,19 +907,23 @@ class API { } bool has_exception() const { - return API::s_instance->sdk()->vm_context->has_exception(*this); + static const auto fn = API::s_instance->sdk()->vm_context->has_exception; + return fn(*this); } void unhandled_exception() { - API::s_instance->sdk()->vm_context->unhandled_exception(*this); + static const auto fn = API::s_instance->sdk()->vm_context->unhandled_exception; + fn(*this); } void local_frame_gc() { - API::s_instance->sdk()->vm_context->local_frame_gc(*this); + static const auto fn = API::s_instance->sdk()->vm_context->local_frame_gc; + fn(*this); } void cleanup_after_exception(int32_t old_ref_count) { - API::s_instance->sdk()->vm_context->cleanup_after_exception(*this, old_ref_count); + static const auto fn = API::s_instance->sdk()->vm_context->cleanup_after_exception; + fn(*this, old_ref_count); } }; @@ -815,7 +933,8 @@ class API { } ::REFrameworkInvokeMethod get_function() const { - return API::s_instance->sdk()->reflection_method->get_function(*this); + static const auto fn = API::s_instance->sdk()->reflection_method->get_function; + return fn(*this); } }; @@ -825,15 +944,18 @@ class API { } ::REFrameworkReflectionPropertyMethod get_getter() const { - return API::s_instance->sdk()->reflection_property->get_getter(*this); + static const auto fn = API::s_instance->sdk()->reflection_property->get_getter; + return fn(*this); } bool is_static() const { - return API::s_instance->sdk()->reflection_property->is_static(*this); + static const auto fn = API::s_instance->sdk()->reflection_property->is_static; + return fn(*this); } uint32_t get_size() const { - return API::s_instance->sdk()->reflection_property->get_size(*this); + static const auto fn = API::s_instance->sdk()->reflection_property->get_size; + return fn(*this); } }; From e43b004ed62ec63527f4afe6a84cdde84e9ae02e Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 17 Apr 2024 20:40:29 -0700 Subject: [PATCH 137/207] .NET: Further reduction of GC allocation, more caching Base memory usage increased by ~100mb but seems okay --- csharp-api/REFrameworkNET/API.cpp | 6 +- csharp-api/REFrameworkNET/Field.hpp | 23 +++- csharp-api/REFrameworkNET/ManagedObject.cpp | 2 +- csharp-api/REFrameworkNET/Method.cpp | 16 ++- csharp-api/REFrameworkNET/Method.hpp | 22 +++- csharp-api/REFrameworkNET/MethodParameter.hpp | 2 +- csharp-api/REFrameworkNET/NativePool.hpp | 42 +++++++ csharp-api/REFrameworkNET/PluginManager.cpp | 12 +- csharp-api/REFrameworkNET/TDB.hpp | 16 +-- csharp-api/REFrameworkNET/TypeDefinition.cpp | 105 +++++++++++++----- csharp-api/REFrameworkNET/TypeDefinition.hpp | 52 +++++++-- csharp-api/REFrameworkNET/UnifiedObject.cpp | 2 +- 12 files changed, 234 insertions(+), 66 deletions(-) create mode 100644 csharp-api/REFrameworkNET/NativePool.hpp diff --git a/csharp-api/REFrameworkNET/API.cpp b/csharp-api/REFrameworkNET/API.cpp index c88a62650..1813c3783 100644 --- a/csharp-api/REFrameworkNET/API.cpp +++ b/csharp-api/REFrameworkNET/API.cpp @@ -65,7 +65,7 @@ System::Collections::Generic::List^ REFramewo result->Add(gcnew REFrameworkNET::ManagedSingleton( REFrameworkNET::ManagedObject::Get(singleton.instance), - gcnew REFrameworkNET::TypeDefinition(singleton.t), + TypeDefinition::GetInstance(singleton.t), gcnew REFrameworkNET::TypeInfo(singleton.type_info) )); } @@ -91,10 +91,10 @@ System::Collections::Generic::List^ REFramewor continue; } - auto nativeObject = gcnew REFrameworkNET::NativeObject(singleton.instance, gcnew REFrameworkNET::TypeDefinition(singleton.t)); + auto nativeObject = gcnew REFrameworkNET::NativeObject(singleton.instance, TypeDefinition::GetInstance(singleton.t)); result->Add(gcnew REFrameworkNET::NativeSingleton( - gcnew REFrameworkNET::NativeObject(singleton.instance, gcnew REFrameworkNET::TypeDefinition(singleton.t)), + gcnew REFrameworkNET::NativeObject(singleton.instance, TypeDefinition::GetInstance(singleton.t)), singleton.type_info != nullptr ? gcnew REFrameworkNET::TypeInfo(singleton.type_info) : nullptr )); } diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp index aab0c1348..cbb148896 100644 --- a/csharp-api/REFrameworkNET/Field.hpp +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -3,6 +3,7 @@ #include #include "TypeDefinition.hpp" +#include "NativePool.hpp" #pragma managed @@ -11,9 +12,27 @@ ref class TypeDefinition; public ref class Field { public: + static Field^ GetInstance(reframework::API::Field* fd) { + return NativePool::GetInstance((uintptr_t)fd, s_createFromPointer); + } + + static Field^ GetInstance(::REFrameworkFieldHandle handle) { + return NativePool::GetInstance((uintptr_t)handle, s_createFromPointer); + } + +private: + static Field^ createFromPointer(uintptr_t ptr) { + return gcnew Field((reframework::API::Field*)ptr); + } + + static NativePool::CreatorDelegate^ s_createFromPointer = gcnew NativePool::CreatorDelegate(createFromPointer); + + +private: Field(const reframework::API::Field* field) : m_field(field) {} Field(::REFrameworkFieldHandle handle) : m_field(reinterpret_cast(handle)) {} +public: System::String^ GetName() { return gcnew System::String(m_field->get_name()); } @@ -31,7 +50,7 @@ public ref class Field { return nullptr; } - return gcnew TypeDefinition(t); + return TypeDefinition::GetInstance(t); } property TypeDefinition^ DeclaringType { @@ -47,7 +66,7 @@ public ref class Field { return nullptr; } - return gcnew TypeDefinition(t); + return TypeDefinition::GetInstance(t); } property TypeDefinition^ Type { diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index d53e7c028..f5722c479 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -91,7 +91,7 @@ namespace REFrameworkNET { return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } TypeInfo^ ManagedObject::GetTypeInfo() { diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 7f9d305ac..3fa2017aa 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -237,12 +237,12 @@ ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, arrayGetType(); - if (obj_t == System::IntPtr::typeid) { + if (REFrameworkNET::IObject::typeid->IsAssignableFrom(obj_t)) { + obj_ptr = safe_cast(obj)->Ptr(); + } else if (obj_t == System::IntPtr::typeid) { obj_ptr = (void*)(intptr_t)safe_cast(obj).ToPointer(); } else if (obj_t == System::UIntPtr::typeid) { obj_ptr = (void*)(uintptr_t)safe_cast(obj).ToUInt64(); - } else if (REFrameworkNET::IObject::typeid->IsAssignableFrom(obj_t)) { - obj_ptr = safe_cast(obj)->Ptr(); } else { System::Console::WriteLine("Unknown type passed to method invocation @ obj"); } @@ -310,8 +310,6 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, arrayget_full_name().c_str())) { + switch (returnType->GetFNV64Hash()) { MAKE_TYPE_HANDLER_2(System, Boolean, bool, byte) MAKE_TYPE_HANDLER_2(System, Byte, uint8_t, byte) MAKE_TYPE_HANDLER_2(System, UInt16, uint16_t, word) @@ -334,15 +332,15 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, array { public: + static Method^ GetInstance(reframework::API::Method* m) { + return NativePool::GetInstance((uintptr_t)m, s_createFromPointer); + } + + static Method^ GetInstance(::REFrameworkMethodHandle handle) { + return NativePool::GetInstance((uintptr_t)handle, s_createFromPointer); + } + +private: Method(reframework::API::Method* method) : m_method(method) {} Method(::REFrameworkMethodHandle handle) : m_method(reinterpret_cast(handle)) {} +public: + static Method^ createFromPointer(uintptr_t ptr) { + return gcnew Method((reframework::API::Method*)ptr); + } + + static NativePool::CreatorDelegate^ s_createFromPointer = gcnew NativePool::CreatorDelegate(createFromPointer); + + void* GetRaw() { return m_method; } @@ -80,7 +98,7 @@ public ref class Method : public System::IEquatable return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } property TypeDefinition^ DeclaringType { @@ -97,7 +115,7 @@ public ref class Method : public System::IEquatable return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } property TypeDefinition^ ReturnType { diff --git a/csharp-api/REFrameworkNET/MethodParameter.hpp b/csharp-api/REFrameworkNET/MethodParameter.hpp index 7b39ae5d8..15e0a0055 100644 --- a/csharp-api/REFrameworkNET/MethodParameter.hpp +++ b/csharp-api/REFrameworkNET/MethodParameter.hpp @@ -6,7 +6,7 @@ namespace REFrameworkNET { public ref struct MethodParameter { MethodParameter(const REFrameworkMethodParameter& p) { Name = gcnew System::String(p.name); - Type = gcnew REFrameworkNET::TypeDefinition(p.t); + Type = REFrameworkNET::TypeDefinition::GetInstance(p.t); } property System::String^ Name; diff --git a/csharp-api/REFrameworkNET/NativePool.hpp b/csharp-api/REFrameworkNET/NativePool.hpp new file mode 100644 index 000000000..e71c1c87f --- /dev/null +++ b/csharp-api/REFrameworkNET/NativePool.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include + +namespace REFrameworkNET { + // Class to pool frequently re-used classes like TypeDefinition, Method, Field + // which only hold a pointer to an unmanaged object. + // And this internal object never moves or anything, so we can just keep one instance of it instead of creating a new one every time. + // The goal is to minimize GC pressure and improve performance. + template + public ref class NativePool + { + private: + static System::Collections::Concurrent::ConcurrentDictionary^ s_cache = + gcnew System::Collections::Concurrent::ConcurrentDictionary(); + + public: + delegate T^ CreatorDelegate(uintptr_t nativePtr); + + static T^ GetInstance(uintptr_t nativePtr, CreatorDelegate^ creator) { + if (nativePtr == 0) { + throw gcnew System::ArgumentNullException("nativePtr cannot be Zero"); + } + + T^ cachedItem; + if (s_cache->TryGetValue(nativePtr, cachedItem)) { + return cachedItem; + } + + T^ newItem = creator(nativePtr); + s_cache->TryAdd(nativePtr, newItem); + return newItem; + } + + static void DisplayStats() { + if (ImGuiNET::ImGui::TreeNode(T::typeid->Name + " Cache")) { + ImGuiNET::ImGui::Text("Cache size: " + s_cache->Count.ToString()); + ImGuiNET::ImGui::TreePop(); + } + } + }; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 4aa1818e7..62918eb4f 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -4,6 +4,7 @@ #include "Attributes/Plugin.hpp" #include "MethodHook.hpp" #include "SystemString.hpp" +#include "NativePool.hpp" #include "PluginManager.hpp" using namespace System; @@ -675,8 +676,15 @@ namespace REFrameworkNET { ImGuiNET::ImGui::Checkbox("Auto Reload", s_auto_reload_plugins); - ManagedObject::Cache::DisplayStats(); - ManagedObject::Cache::DisplayStats(); + if (ImGuiNET::ImGui::TreeNode("Debug Stats")) { + ManagedObject::Cache::DisplayStats(); + ManagedObject::Cache::DisplayStats(); + NativePool::DisplayStats(); + NativePool::DisplayStats(); + NativePool::DisplayStats(); + + ImGuiNET::ImGui::TreePop(); + } for each (PluginState^ state in PluginManager::s_plugin_states) { state->DisplayOptions(); diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp index 975663658..b2cc1979b 100644 --- a/csharp-api/REFrameworkNET/TDB.hpp +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -32,7 +32,7 @@ public ref class TDB { private: static reframework::API::TypeDefinition* s_cachedType = TDB::GetTypeDefinitionPtr(); - static REFrameworkNET::TypeDefinition^ s_cachedManagedType = s_cachedType != nullptr ? gcnew REFrameworkNET::TypeDefinition(s_cachedType) : nullptr; + static REFrameworkNET::TypeDefinition^ s_cachedManagedType = s_cachedType != nullptr ? TypeDefinition::GetInstance(s_cachedType) : nullptr; }; public: @@ -95,7 +95,7 @@ public ref class TDB { return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } TypeDefinition^ FindType(System::String^ name) { @@ -105,7 +105,7 @@ public ref class TDB { return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } /// @@ -142,11 +142,11 @@ public ref class TDB { return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } Method^ GetMethod(uint32_t index) { - return gcnew Method(m_tdb->get_method(index)); + return Method::GetInstance(m_tdb->get_method(index)); } Method^ FindMethod(System::String^ type_name, System::String^ name) { @@ -156,7 +156,7 @@ public ref class TDB { return nullptr; } - return gcnew Method(result); + return Method::GetInstance(result); } Method^ GetMethod(System::String^ type_name, System::String^ name) { @@ -170,7 +170,7 @@ public ref class TDB { return nullptr; } - return gcnew Field(result); + return Field::GetInstance(result); } Field^ FindField(System::String^ type_name, System::String^ name) { @@ -180,7 +180,7 @@ public ref class TDB { return nullptr; } - return gcnew Field(result); + return Field::GetInstance(result); } Field^ GetField(System::String^ type_name, System::String^ name) { diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 071252d2a..b5f91793a 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -1,3 +1,5 @@ +#include + #include "TypeInfo.hpp" #include "Method.hpp" #include "Field.hpp" @@ -5,6 +7,7 @@ #include "ManagedObject.hpp" #include "NativeObject.hpp" #include "Proxy.hpp" +#include "Utility.hpp" #include "TypeDefinition.hpp" @@ -13,6 +16,21 @@ namespace REFrameworkNET { return gcnew NativeObject(this); } + size_t TypeDefinition::GetFNV64Hash() { + if (m_cachedFNV64Hash == 0) { + m_lock->EnterWriteLock(); + try { + if (m_cachedFNV64Hash == 0) { + m_cachedFNV64Hash = REFrameworkNET::hash(m_type->get_full_name().c_str()); + } + } finally { + m_lock->ExitWriteLock(); + } + } + + return m_cachedFNV64Hash; + } + REFrameworkNET::Method^ TypeDefinition::FindMethod(System::String^ name) { auto result = m_type->find_method(msclr::interop::marshal_as(name)); @@ -21,7 +39,7 @@ namespace REFrameworkNET { return nullptr; } - return gcnew REFrameworkNET::Method(result); + return REFrameworkNET::Method::GetInstance(result); } REFrameworkNET::Field^ TypeDefinition::FindField(System::String^ name) @@ -32,7 +50,7 @@ namespace REFrameworkNET { return nullptr; } - return gcnew Field(result); + return REFrameworkNET::Field::GetInstance(result); } REFrameworkNET::Property^ TypeDefinition::FindProperty(System::String^ name) @@ -48,50 +66,77 @@ namespace REFrameworkNET { System::Collections::Generic::List^ TypeDefinition::GetMethods() { - auto methods = m_type->get_methods(); - auto result = gcnew System::Collections::Generic::List(); - - for (auto& method : methods) { - if (method == nullptr) { - continue; + if (m_methods == nullptr) { + m_lock->EnterWriteLock(); + try { + if (m_methods == nullptr) { + auto methods = m_type->get_methods(); + m_methods = gcnew System::Collections::Generic::List(); + + for (auto& method : methods) { + if (method == nullptr) { + continue; + } + + m_methods->Add(Method::GetInstance(method)); + } + } + } finally { + m_lock->ExitWriteLock(); } - - result->Add(gcnew Method(method)); } - return result; + return m_methods; } System::Collections::Generic::List^ TypeDefinition::GetFields() { - auto fields = m_type->get_fields(); - auto result = gcnew System::Collections::Generic::List(); - - for (auto& field : fields) { - if (field == nullptr) { - continue; + if (m_fields == nullptr) { + m_lock->EnterWriteLock(); + + try { + if (m_fields == nullptr ){ + auto fields = m_type->get_fields(); + m_fields = gcnew System::Collections::Generic::List(); + + for (auto& field : fields) { + if (field == nullptr) { + continue; + } + + m_fields->Add(Field::GetInstance(field)); + } + } + } finally { + m_lock->ExitWriteLock(); } - - result->Add(gcnew Field(field)); } - - return result; + + return m_fields; } System::Collections::Generic::List^ TypeDefinition::GetProperties() { - auto properties = m_type->get_properties(); - auto result = gcnew System::Collections::Generic::List(); - - for (auto& property : properties) { - if (property == nullptr) { - continue; + if (m_properties == nullptr) { + m_lock->EnterWriteLock(); + + try { + auto properties = m_type->get_properties(); + m_properties = gcnew System::Collections::Generic::List(); + + for (auto& property : properties) { + if (property == nullptr) { + continue; + } + + m_properties->Add(gcnew Property(property)); + } + } finally { + m_lock->ExitWriteLock(); } - - result->Add(gcnew Property(property)); } - return result; + return m_properties; } ManagedObject^ TypeDefinition::CreateInstance(int32_t flags) diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 890230e80..e661145c5 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -6,6 +6,7 @@ #include #include "IObject.hpp" +#include "NativePool.hpp" namespace REFrameworkNET { ref class ManagedObject; @@ -39,9 +40,25 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, public System::IEquatable { public: + static TypeDefinition^ GetInstance(reframework::API::TypeDefinition* td) { + return NativePool::GetInstance((uintptr_t)td, s_createFromPointer); + } + + static TypeDefinition^ GetInstance(::REFrameworkTypeDefinitionHandle handle) { + return NativePool::GetInstance((uintptr_t)handle, s_createFromPointer); + } + +private: + static TypeDefinition^ createFromPointer(uintptr_t ptr) { + return gcnew TypeDefinition((reframework::API::TypeDefinition*)ptr); + } + + static NativePool::CreatorDelegate^ s_createFromPointer = gcnew NativePool::CreatorDelegate(createFromPointer); + TypeDefinition(reframework::API::TypeDefinition* td) : m_type(td) {} TypeDefinition(::REFrameworkTypeDefinitionHandle handle) : m_type(reinterpret_cast(handle)) {} +public: operator reframework::API::TypeDefinition*() { return (reframework::API::TypeDefinition*)m_type; } @@ -54,7 +71,6 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, return gcnew TypeDefinition(m_type); } - uint32_t GetIndex() { return m_type->get_index(); @@ -129,9 +145,21 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, } } - System::String^ GetFullName() - { - return gcnew System::String(m_type->get_full_name().c_str()); +public: + System::String^ GetFullName() { + if (m_cachedFullName == nullptr) { + m_lock->EnterWriteLock(); + + try { + if (m_cachedFullName == nullptr) { + m_cachedFullName = gcnew System::String(m_type->get_full_name().c_str()); + } + } finally { + m_lock->ExitWriteLock(); + } + } + + return m_cachedFullName; } property System::String^ FullName { @@ -140,6 +168,9 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, } } +public: + size_t GetFNV64Hash(); + bool HasFieldPtrOffset() { return m_type->has_fieldptr_offset(); @@ -266,7 +297,7 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } property TypeDefinition^ ParentType { @@ -283,7 +314,7 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } property TypeDefinition^ DeclaringType { @@ -302,7 +333,7 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, return nullptr; } - return gcnew TypeDefinition(result); + return TypeDefinition::GetInstance(result); } property TypeDefinition^ UnderlyingType { @@ -445,5 +476,12 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, private: reframework::API::TypeDefinition* m_type; + System::Threading::ReaderWriterLockSlim^ m_lock{gcnew System::Threading::ReaderWriterLockSlim()}; + System::String^ m_cachedFullName{nullptr}; + size_t m_cachedFNV64Hash{0}; + + System::Collections::Generic::List^ m_methods{nullptr}; + System::Collections::Generic::List^ m_fields{nullptr}; + System::Collections::Generic::List^ m_properties{nullptr}; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/UnifiedObject.cpp b/csharp-api/REFrameworkNET/UnifiedObject.cpp index a008fe0c9..7e78d4e84 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.cpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.cpp @@ -165,7 +165,7 @@ namespace REFrameworkNET { break; } - auto td = gcnew REFrameworkNET::TypeDefinition(obj->get_type_definition()); + auto td = TypeDefinition::GetInstance(obj->get_type_definition()); // another fallback incase the method returns an object which is an array if (td != nullptr && td->GetVMObjType() == VMObjType::Array) { From 79b4f2a6fb519592332051510da050bf551921ec Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 17 Apr 2024 21:55:45 -0700 Subject: [PATCH 138/207] .NET: Add GC display to debug dropdown --- csharp-api/CMakeLists.txt | 4 +- .../{Compiler => REFCoreDeps}/Compiler.cs | 0 .../REFCoreDeps/GarbageCollectionDisplay.cs | 71 +++++++++++++++++++ csharp-api/REFrameworkNET/PluginManager.cpp | 10 +++ csharp-api/cmake.toml | 2 +- 5 files changed, 85 insertions(+), 2 deletions(-) rename csharp-api/{Compiler => REFCoreDeps}/Compiler.cs (100%) create mode 100644 csharp-api/REFCoreDeps/GarbageCollectionDisplay.cs diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 1baeab1c0..de380434e 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -117,7 +117,8 @@ endforeach() # Target: REFCoreDeps set(REFCoreDeps_SOURCES - "Compiler/Compiler.cs" + "REFCoreDeps/Compiler.cs" + "REFCoreDeps/GarbageCollectionDisplay.cs" cmake.toml ) @@ -178,6 +179,7 @@ set(csharp-api_SOURCES "REFrameworkNET/MethodHook.hpp" "REFrameworkNET/MethodParameter.hpp" "REFrameworkNET/NativeObject.hpp" + "REFrameworkNET/NativePool.hpp" "REFrameworkNET/NativeSingleton.hpp" "REFrameworkNET/ObjectEnumerator.hpp" "REFrameworkNET/Plugin.hpp" diff --git a/csharp-api/Compiler/Compiler.cs b/csharp-api/REFCoreDeps/Compiler.cs similarity index 100% rename from csharp-api/Compiler/Compiler.cs rename to csharp-api/REFCoreDeps/Compiler.cs diff --git a/csharp-api/REFCoreDeps/GarbageCollectionDisplay.cs b/csharp-api/REFCoreDeps/GarbageCollectionDisplay.cs new file mode 100644 index 000000000..f43f959ec --- /dev/null +++ b/csharp-api/REFCoreDeps/GarbageCollectionDisplay.cs @@ -0,0 +1,71 @@ +using ImGuiNET; +using System; + +namespace REFrameworkNET { + public class GarbageCollectionDisplay { + static bool doFullGC = false; + public static void Render() { + ImGui.Checkbox("Do Full GC", ref doFullGC); + + if (ImGui.Button("Do Gen 2 GC")) { + GC.Collect(2, GCCollectionMode.Forced, false); + } + + var heapBytes = GC.GetTotalMemory(false); + var heapKb = heapBytes / 1024; + var heapMb = heapKb / 1024; + var heapGb = heapMb / 1024; + + if (doFullGC) { + GC.Collect(0, GCCollectionMode.Forced, false); + GC.Collect(1, GCCollectionMode.Forced, false); + GC.Collect(2, GCCollectionMode.Forced, false); + } + + ImGui.Text("GC Memory: " + GC.GetTotalMemory(false) / 1024 + " KB (" + heapMb + " MB, " + heapGb + " GB)"); + ImGui.Text("GC Collection Count (gen 0): " + GC.CollectionCount(0)); + ImGui.Text("GC Collection Count (gen 1): " + GC.CollectionCount(1)); + ImGui.Text("GC Collection Count (gen 2): " + GC.CollectionCount(2)); + ImGui.Text("GC Latency Mode: " + System.Runtime.GCSettings.LatencyMode.ToString()); + ImGui.Text("GC Is Server GC: " + System.Runtime.GCSettings.IsServerGC); + ImGui.Text("GC Max Generation: " + GC.MaxGeneration); + ImGui.Text("GC Force Full Collection: " + System.Runtime.GCSettings.LargeObjectHeapCompactionMode); + + // memory info + var memoryInfo = GC.GetGCMemoryInfo(); + ImGui.Text("GC Is Concurrent: " + memoryInfo.Concurrent); + ImGui.Text("GC Fragmentation: " + memoryInfo.FragmentedBytes); + + var gcHeapSize = GC.GetGCMemoryInfo().HeapSizeBytes; + var gcHeapSizeKb = gcHeapSize / 1024; + var gcHeapSizeMb = gcHeapSizeKb / 1024; + var gcHeapSizeGb = gcHeapSizeMb / 1024; + ImGui.Text("GC Heap Size During Last GC: " + memoryInfo.HeapSizeBytes + " bytes (" + gcHeapSizeKb + " KB, " + gcHeapSizeMb + " MB, " + gcHeapSizeGb + " GB)"); + ImGui.Text("GC High Memory Load Threshold: " + memoryInfo.HighMemoryLoadThresholdBytes); + ImGui.Text("GC Memory Load: " + memoryInfo.MemoryLoadBytes); + + // Combo box for latency mode + // Turn enum into string + var latencyModeEnum = typeof(System.Runtime.GCLatencyMode); + var latencyModeNames = Enum.GetNames(latencyModeEnum); + + int currentLatencyMode = (int)System.Runtime.GCSettings.LatencyMode; + + if (ImGui.BeginCombo("Latency Mode", latencyModeNames[currentLatencyMode])) { + for (int i = 0; i < latencyModeNames.Length; i++) { + bool isSelected = i == currentLatencyMode; + + if (ImGui.Selectable(latencyModeNames[i], isSelected)) { + System.Runtime.GCSettings.LatencyMode = (System.Runtime.GCLatencyMode)i; + } + + if (isSelected) { + ImGui.SetItemDefaultFocus(); + } + } + + ImGui.EndCombo(); + } + } + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 62918eb4f..bda7efc0d 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -172,6 +172,11 @@ namespace REFrameworkNET { } } } + + // Unclog the GC after all that + System::GC::Collect(0, System::GCCollectionMode::Forced, false); + System::GC::Collect(1, System::GCCollectionMode::Forced, false); + System::GC::Collect(2, System::GCCollectionMode::Forced, false); } // meant to be executed in the correct context @@ -677,6 +682,11 @@ namespace REFrameworkNET { ImGuiNET::ImGui::Checkbox("Auto Reload", s_auto_reload_plugins); if (ImGuiNET::ImGui::TreeNode("Debug Stats")) { + if (ImGuiNET::ImGui::TreeNode("Garbage Collection")) { + REFrameworkNET::GarbageCollectionDisplay::Render(); + ImGuiNET::ImGui::TreePop(); + } + ManagedObject::Cache::DisplayStats(); ManagedObject::Cache::DisplayStats(); NativePool::DisplayStats(); diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index d162fb7d5..2c3c1c7df 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -110,7 +110,7 @@ VS_CONFIGURATION_TYPE = "ClassLibrary" # It also contains other stuff like the C# compiler wrapper. [target.REFCoreDeps] type = "CSharpSharedTarget" -sources = ["Compiler/**.cs"] +sources = ["REFCoreDeps/**.cs"] # Not using .properties here because it overrides the template properties for whatever reason cmake-after = """ From a1c676794fe61b6f533923c1ea6c7c1c53f4f603 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 18 Apr 2024 02:04:55 -0700 Subject: [PATCH 139/207] .NET: Major performance gains (unmanaged interop reduction & caching) --- .../REFrameworkNET/Attributes/Method.hpp | 38 ++++++------------- csharp-api/REFrameworkNET/Method.cpp | 23 +++++++++-- csharp-api/REFrameworkNET/Method.hpp | 23 +++++++++-- csharp-api/REFrameworkNET/NativePool.hpp | 6 ++- csharp-api/REFrameworkNET/Proxy.hpp | 2 +- csharp-api/REFrameworkNET/TypeDefinition.cpp | 18 +++++++-- csharp-api/REFrameworkNET/TypeDefinition.hpp | 34 ++++++++++++++++- 7 files changed, 101 insertions(+), 43 deletions(-) diff --git a/csharp-api/REFrameworkNET/Attributes/Method.hpp b/csharp-api/REFrameworkNET/Attributes/Method.hpp index d508cd9d5..aff52a92d 100644 --- a/csharp-api/REFrameworkNET/Attributes/Method.hpp +++ b/csharp-api/REFrameworkNET/Attributes/Method.hpp @@ -6,46 +6,30 @@ namespace REFrameworkNET::Attributes { /// Attribute to mark a reference assembly method for easier lookup. [System::AttributeUsage(System::AttributeTargets::Method)] public ref class Method : public System::Attribute { + private: + static System::Collections::Concurrent::ConcurrentDictionary^ cache = gcnew System::Collections::Concurrent::ConcurrentDictionary(8, 8192); + public: static Method^ GetCachedAttribute(System::Reflection::MethodInfo^ target) { Method^ attr = nullptr; - // Lock the cache for reading - cacheLock->EnterReadLock(); - try { - if (cache->TryGetValue(target, attr) && attr != nullptr) { - return attr; - } - } finally { - cacheLock->ExitReadLock(); + if (cache->TryGetValue(target, attr)) { + return attr; } - // Lock the cache for writing - cacheLock->EnterWriteLock(); - try { - if (cache->TryGetValue(target, attr) && attr != nullptr) { - return attr; - } - - if (attr = (REFrameworkNET::Attributes::Method^)System::Attribute::GetCustomAttribute(target, REFrameworkNET::Attributes::Method::typeid)) { - cache->Add(target, attr); + if (attr = (REFrameworkNET::Attributes::Method^)System::Attribute::GetCustomAttribute(target, REFrameworkNET::Attributes::Method::typeid); attr != nullptr) { + cache->TryAdd(target, attr); #ifdef REFRAMEWORK_VERBOSE - System::Console::WriteLine("Cached Method attribute for {0}", target->Name); + System::Console::WriteLine("Cached Method attribute for {0}", target->Name); #endif - return attr; - } - } finally { - cacheLock->ExitWriteLock(); + return attr; } + return attr; } - private: - static System::Collections::Generic::Dictionary^ cache = gcnew System::Collections::Generic::Dictionary(); - static System::Threading::ReaderWriterLockSlim^ cacheLock = gcnew System::Threading::ReaderWriterLockSlim(); - public: Method(uint32_t methodIndex) { method = REFrameworkNET::TDB::Get()->GetMethod(methodIndex); @@ -58,7 +42,7 @@ namespace REFrameworkNET::Attributes { REFrameworkNET::Method^ GetMethod(REFrameworkNET::TypeDefinition^ td) { // Signature is used for virtual methods - if (signature != nullptr && isVirtual && td != method->GetDeclaringType()) { + if (isVirtual && signature != nullptr && td != method->GetDeclaringType()) { if (auto result = td->GetMethod(signature); result != nullptr) { return result; } diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 3fa2017aa..d728fa459 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -162,13 +162,13 @@ ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, arrayIsAssignableFrom(t)) { - auto iobj = safe_cast(args[i]); + auto iobj = static_cast(args[i]); args2[i] = iobj->Ptr(); } else if (t == REFrameworkNET::TypeDefinition::typeid) { // TypeDefinitions are wrappers for System.RuntimeTypeHandle // However there's basically no functions that actually take a System.RuntimeTypeHandle // so we will just convert it to a System.Type. - auto td = safe_cast(args[i]); + auto td = static_cast(args[i]); if (auto rt = td->GetRuntimeType(); rt != nullptr) { args2[i] = rt->Ptr(); @@ -238,7 +238,7 @@ ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, arrayGetType(); if (REFrameworkNET::IObject::typeid->IsAssignableFrom(obj_t)) { - obj_ptr = safe_cast(obj)->Ptr(); + obj_ptr = static_cast(obj)->Ptr(); } else if (obj_t == System::IntPtr::typeid) { obj_ptr = (void*)(intptr_t)safe_cast(obj).ToPointer(); } else if (obj_t == System::UIntPtr::typeid) { @@ -259,7 +259,7 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, array(REFrameworkNET::InvokeRet::FromNative(tempResult)); + result = static_cast(REFrameworkNET::InvokeRet::FromNative(tempResult)); return true; } @@ -332,14 +332,29 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, array /// The return of the method. TypeDefinition^ GetReturnType() { - auto result = m_method->get_return_type(); + if (m_returnType == nullptr) { + m_lock->EnterWriteLock(); - if (result == nullptr) { - return nullptr; + try { + if (m_returnType == nullptr) { + auto result = m_method->get_return_type(); + + if (result == nullptr) { + return nullptr; + } + + m_returnType = TypeDefinition::GetInstance(result); + } + } finally { + m_lock->ExitWriteLock(); + } } - return TypeDefinition::GetInstance(result); + return m_returnType; } property TypeDefinition^ ReturnType { @@ -307,5 +319,8 @@ public ref class Method : public System::IEquatable private: reframework::API::Method* m_method; + + System::Threading::ReaderWriterLockSlim^ m_lock{gcnew System::Threading::ReaderWriterLockSlim()}; + TypeDefinition^ m_returnType{nullptr}; }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/NativePool.hpp b/csharp-api/REFrameworkNET/NativePool.hpp index e71c1c87f..0236e8c42 100644 --- a/csharp-api/REFrameworkNET/NativePool.hpp +++ b/csharp-api/REFrameworkNET/NativePool.hpp @@ -12,7 +12,7 @@ namespace REFrameworkNET { { private: static System::Collections::Concurrent::ConcurrentDictionary^ s_cache = - gcnew System::Collections::Concurrent::ConcurrentDictionary(); + gcnew System::Collections::Concurrent::ConcurrentDictionary(8, 8192); public: delegate T^ CreatorDelegate(uintptr_t nativePtr); @@ -34,6 +34,10 @@ namespace REFrameworkNET { static void DisplayStats() { if (ImGuiNET::ImGui::TreeNode(T::typeid->Name + " Cache")) { + if (ImGuiNET::ImGui::Button("Clear Cache")) { + s_cache->Clear(); + } + ImGuiNET::ImGui::Text("Cache size: " + s_cache->Count.ToString()); ImGuiNET::ImGui::TreePop(); } diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 47e768c66..003607c92 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -136,7 +136,7 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public auto methodAttribute = REFrameworkNET::Attributes::Method::GetCachedAttribute(targetMethod); Object^ result = nullptr; - auto iobject = dynamic_cast(Instance); + auto iobject = static_cast(Instance); if (methodAttribute != nullptr) { if (iobject != nullptr) { diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index b5f91793a..6f72259dc 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -163,13 +163,23 @@ namespace REFrameworkNET { ManagedObject^ TypeDefinition::GetRuntimeType() { - auto result = m_type->get_runtime_type(); + if (m_runtimeType == nullptr) { + m_lock->EnterWriteLock(); - if (result == nullptr) { - return nullptr; + try { + if (m_runtimeType == nullptr) { + auto result = m_type->get_runtime_type(); + + if (result != nullptr) { + m_runtimeType = ManagedObject::Get(result); + } + } + } finally { + m_lock->ExitWriteLock(); + } } - return ManagedObject::Get(result); + return m_runtimeType; } REFrameworkNET::InvokeRet^ TypeDefinition::Invoke(System::String^ methodName, array^ args) { diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index e661145c5..0562ee0c8 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -208,12 +208,36 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, bool IsValueType() { - return m_type->is_valuetype(); + if (!m_cachedIsValueType.HasValue) { + m_lock->EnterWriteLock(); + + try { + if (!m_cachedIsValueType.HasValue) { + m_cachedIsValueType = m_type->is_valuetype(); + } + } finally { + m_lock->ExitWriteLock(); + } + } + + return m_cachedIsValueType.Value; } bool IsEnum() { - return m_type->is_enum(); + if (!m_cachedIsEnum.HasValue) { + m_lock->EnterWriteLock(); + + try { + if (!m_cachedIsEnum.HasValue) { + m_cachedIsEnum = m_type->is_enum(); + } + } finally { + m_lock->ExitWriteLock(); + } + } + + return m_cachedIsEnum.Value; } bool IsByRef() @@ -477,8 +501,14 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, private: reframework::API::TypeDefinition* m_type; System::Threading::ReaderWriterLockSlim^ m_lock{gcnew System::Threading::ReaderWriterLockSlim()}; + + // Cached values + ManagedObject^ m_runtimeType{nullptr}; System::String^ m_cachedFullName{nullptr}; size_t m_cachedFNV64Hash{0}; + + System::Nullable m_cachedIsValueType{}; + System::Nullable m_cachedIsEnum{}; System::Collections::Generic::List^ m_methods{nullptr}; System::Collections::Generic::List^ m_fields{nullptr}; From aa1be9b28e289dcd03baa13dc4b705e2b3ede0d8 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 05:31:21 -0700 Subject: [PATCH 140/207] .NET: Further optimizations --- csharp-api/REFrameworkNET/Method.cpp | 2 +- csharp-api/REFrameworkNET/TypeDefinition.cpp | 26 ++++++++++++++------ csharp-api/REFrameworkNET/TypeDefinition.hpp | 20 +++++++++++---- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index d728fa459..58700d35b 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -176,7 +176,7 @@ ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, array(args[i])); + auto createdStr = VM::CreateString(static_cast(args[i])); if (createdStr != nullptr) { args2[i] = createdStr->Ptr(); diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 6f72259dc..f82edec2c 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -269,18 +269,28 @@ namespace REFrameworkNET { } TypeDefinition^ TypeDefinition::GetElementType() { - auto runtimeType = this->GetRuntimeType(); + if (m_elementType == nullptr) { + m_lock->EnterWriteLock(); - if (runtimeType == nullptr) { - return nullptr; - } + try { + auto runtimeType = this->GetRuntimeType(); + + if (runtimeType == nullptr) { + return nullptr; + } - auto elementType = (ManagedObject^)runtimeType->Call("GetElementType"); + auto elementType = (ManagedObject^)runtimeType->Call("GetElementType"); - if (elementType == nullptr) { - return nullptr; + if (elementType == nullptr) { + return nullptr; + } + + return (TypeDefinition^)elementType->Call("get_TypeHandle"); + } finally { + m_lock->ExitWriteLock(); + } } - return (TypeDefinition^)elementType->Call("get_TypeHandle"); + return m_elementType; } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index 0562ee0c8..e4ef8c428 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -351,13 +351,21 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, /// Usually used for enums. TypeDefinition^ GetUnderlyingType() { - auto result = m_type->get_underlying_type(); + if (m_underlyingType == nullptr) { + m_lock->EnterWriteLock(); - if (result == nullptr) { - return nullptr; + try { + auto result = m_type->get_underlying_type(); + + if (result != nullptr) { + m_underlyingType = TypeDefinition::GetInstance(result); + } + } finally { + m_lock->ExitWriteLock(); + } } - return TypeDefinition::GetInstance(result); + return m_underlyingType; } property TypeDefinition^ UnderlyingType { @@ -500,10 +508,12 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, private: reframework::API::TypeDefinition* m_type; - System::Threading::ReaderWriterLockSlim^ m_lock{gcnew System::Threading::ReaderWriterLockSlim()}; + System::Threading::ReaderWriterLockSlim^ m_lock{gcnew System::Threading::ReaderWriterLockSlim(System::Threading::LockRecursionPolicy::SupportsRecursion)}; // Cached values ManagedObject^ m_runtimeType{nullptr}; + TypeDefinition^ m_underlyingType{nullptr}; + TypeDefinition^ m_elementType{nullptr}; System::String^ m_cachedFullName{nullptr}; size_t m_cachedFNV64Hash{0}; From f16f0f00850e0bdabbf1355051965c811575759e Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 05:32:51 -0700 Subject: [PATCH 141/207] .NET: Fix RE2/3 reference assembly generation causing crashes --- .../AssemblyGenerator/ClassGenerator.cs | 346 +++++++++++------- csharp-api/AssemblyGenerator/Generator.cs | 42 ++- csharp-api/REFrameworkNET/API.hpp | 4 + 3 files changed, 248 insertions(+), 144 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index b75fd653b..324d90f96 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Operations; using REFrameworkNET; using System; +using System.ComponentModel.DataAnnotations; /*public interface CrappyTest { public string Concat(object arg0); @@ -74,6 +75,15 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { break; } + if (method.Name == null) { + continue; + } + + if (method.ReturnType == null || method.ReturnType.FullName == null) { + REFrameworkNET.API.LogError("Method " + method.Name + " has a null return type"); + continue; + } + methods.Add(method); } @@ -282,189 +292,241 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy var refProxyFieldDecl = SyntaxFactory.FieldDeclaration(refProxyVarDecl).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + typeDeclaration = GenerateMethods(baseTypes); + List internalFieldDeclarations = []; - HashSet seenMethodSignatures = []; - typeDeclaration = typeDeclaration - .AddMembers(methods.Where(method => !invalidMethodNames.Contains(method.Name) && !method.Name.Contains('<') && !method.ReturnType.FullName.Contains('!')).Select(method => { - TypeSyntax? returnType = MakeProperType(method.ReturnType, t); + if (internalFieldDeclarations.Count > 0) { + typeDeclaration = typeDeclaration.AddMembers(internalFieldDeclarations.ToArray()); + } - //string simpleMethodSignature = returnType.GetText().ToString(); - string simpleMethodSignature = ""; // Return types are not part of the signature. Return types are not overloaded. + if (baseTypes.Count > 0 && typeDeclaration != null) { + refTypeFieldDecl = refTypeFieldDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + //fieldDeclaration2 = fieldDeclaration2.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - var methodName = new string(method.Name); - var methodExtension = Il2CppDump.GetMethodExtension(method); + typeDeclaration = (typeDeclaration as InterfaceDeclarationSyntax)?.AddBaseListTypes(baseTypes.ToArray()); + } - var methodDeclaration = SyntaxFactory.MethodDeclaration(returnType, methodName ?? "UnknownMethod") - .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) - /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; + if (typeDeclaration != null) { + typeDeclaration = typeDeclaration.AddMembers(refTypeFieldDecl); + //typeDeclaration = typeDeclaration.AddMembers(refProxyFieldDecl); + } - simpleMethodSignature += methodName; + return GenerateNestedTypes(); + } - // Add full method name as a MethodName attribute to the method - methodDeclaration = methodDeclaration.AddAttributeLists( - SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( - SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ")"))) - ); + private TypeDeclarationSyntax GenerateMethods(List baseTypes) { + if (typeDeclaration == null) { + throw new Exception("Type declaration is null"); // This should never happen + } - if (method.Parameters.Count > 0) { - // If any of the params have ! in them, skip this method - if (method.Parameters.Any(param => param.Type.FullName.Contains('!'))) { - return null; - } + if (methods.Count == 0) { + return typeDeclaration!; + } - var runtimeMethod = method.GetRuntimeMethod(); - var runtimeParams = runtimeMethod.Call("GetParameters") as REFrameworkNET.ManagedObject; + HashSet seenMethodSignatures = []; - System.Collections.Generic.List parameters = []; + var validMethods = new List(); - bool anyUnsafeParams = false; + try { + foreach(REFrameworkNET.Method m in methods) { + if (m == null) { + continue; + } - if (runtimeParams != null) { - foreach (dynamic param in runtimeParams) { - if (param.get_IsRetval() == true) { - continue; - } + if (invalidMethodNames.Contains(m.Name)) { + continue; + } - var paramName = param.get_Name(); + if (m.Name.Contains('<')) { + continue; + } - if (paramName == null) { - paramName = "UnknownParam"; - } + if (m.ReturnType.FullName.Contains('!')) { + continue; + } + + validMethods.Add(m); + } + } catch (Exception e) { + Console.WriteLine("ASDF Error: " + e.Message); + } - var paramType = param.get_ParameterType(); + var matchingMethods = validMethods + .Select(method => + { + var returnType = MakeProperType(method.ReturnType, t); - if (paramType == null) { - parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName("object"))); - continue; - } + //string simpleMethodSignature = returnType.GetText().ToString(); + string simpleMethodSignature = ""; // Return types are not part of the signature. Return types are not overloaded. - /*if (param.get_IsGenericParameter() == true) { - return null; // no generic parameters. - }*/ + var methodName = new string(method.Name); + var methodExtension = Il2CppDump.GetMethodExtension(method); - var isByRef = paramType.IsByRefImpl(); - var isPointer = paramType.IsPointerImpl(); - var isOut = param.get_IsOut(); - var paramTypeDef = (REFrameworkNET.TypeDefinition)paramType.get_TypeHandle(); + var methodDeclaration = SyntaxFactory.MethodDeclaration(returnType, methodName ?? "UnknownMethod") + .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) + /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; - var paramTypeSyntax = MakeProperType(paramTypeDef, t); - - System.Collections.Generic.List modifiers = []; + simpleMethodSignature += methodName; - if (isOut == true) { - simpleMethodSignature += "out"; - modifiers.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword)); - } + // Add full method name as a MethodName attribute to the method + methodDeclaration = methodDeclaration.AddAttributeLists( + SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( + SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), + SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ")"))) + ); - if (isByRef == true) { - // can only be either ref or out. - if (!isOut) { - simpleMethodSignature += "ref " + paramTypeSyntax.GetText().ToString(); - modifiers.Add(SyntaxFactory.Token(SyntaxKind.RefKeyword)); - } + if (method.Parameters.Count > 0) { + // If any of the params have ! in them, skip this method + if (method.Parameters.Any(param => param != null && (param.Type == null || (param.Type != null && param.Type.FullName.Contains('!'))))) { + return null; + } - parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName(paramTypeSyntax.ToString())).AddModifiers(modifiers.ToArray())); - } else if (isPointer == true) { - simpleMethodSignature += "ptr " + paramTypeSyntax.GetText().ToString(); - parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName(paramTypeSyntax.ToString() + "*")).AddModifiers(modifiers.ToArray())); - anyUnsafeParams = true; - } else { - simpleMethodSignature += paramTypeSyntax.GetText().ToString(); - parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(paramTypeSyntax).AddModifiers(modifiers.ToArray())); - } + var runtimeMethod = method.GetRuntimeMethod(); + + if (runtimeMethod == null) { + REFrameworkNET.API.LogWarning("Method " + method.DeclaringType.FullName + "." + method.Name + " has a null runtime method"); + return null; + } + + var runtimeParams = runtimeMethod.Call("GetParameters") as REFrameworkNET.ManagedObject; + + System.Collections.Generic.List parameters = []; + + bool anyUnsafeParams = false; + + if (runtimeParams != null) { + foreach (dynamic param in runtimeParams) { + if (param.get_IsRetval() == true) { + continue; } - methodDeclaration = methodDeclaration.AddParameterListParameters([.. parameters]); + var paramName = param.get_Name(); - if (anyUnsafeParams) { - methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); + if (paramName == null) { + paramName = "UnknownParam"; } - } - } else { - simpleMethodSignature += "()"; - } - if (method.IsStatic()) { - // Add System.ComponentModel.Description("static") attribute - //methodDeclaration = methodDeclaration.AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName("global::System.ComponentModel.DescriptionAttribute"), SyntaxFactory.ParseAttributeArgumentList("(\"static\")")))); - - // lets see what happens if we just make it static - /*methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); - - // Now we must add a body to it that actually calls the method - // We have our REFType field, so we can lookup the method and call it - // Make a private static field to hold the REFrameworkNET.Method - var internalFieldName = "INTERNAL_" + method.GetIndex().ToString(); - var methodVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Method")) - .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetMethod(\"" + method.Name + "\")")))); - - var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - internalFieldDeclarations.Add(methodFieldDeclaration); - - // Now we can add the body - // bool HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result); - if (method.ReturnType.FullName == "System.Void") { - var body = internalFieldName + ".Invoke(null, null)"; - methodDeclaration = methodDeclaration.AddBodyStatements(SyntaxFactory.ParseStatement(body)); - } else { - var body1 = "object INTERNAL_result = null;"; - var body2 = internalFieldName + ".HandleInvokeMember_Internal(null, " + (method.Parameters.Count > 0 ? "new object[] {" + string.Join(", ", method.Parameters.Select(param => param.Name)) + "}" : "null") + ", ref INTERNAL_result);"; - string body3 = "return (" + returnType.GetText() + ")INTERNAL_result;"; - - methodDeclaration = methodDeclaration.AddBodyStatements( - [SyntaxFactory.ParseStatement(body1), SyntaxFactory.ParseStatement(body2), SyntaxFactory.ParseStatement(body3)] - ); - }*/ - } + var paramType = param.get_ParameterType(); - if (seenMethodSignatures.Contains(simpleMethodSignature)) { - Console.WriteLine("Skipping duplicate method: " + methodDeclaration.GetText().ToString()); - return null; - } + if (paramType == null) { + parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName("object"))); + continue; + } - seenMethodSignatures.Add(simpleMethodSignature); + /*if (param.get_IsGenericParameter() == true) { + return null; // no generic parameters. + }*/ - // Add the rest of the modifiers here that would mangle the signature check - if (baseTypes.Count > 0 && methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { - var matchingParentMethods = methodExtension.MatchingParentMethods; + var isByRef = paramType.IsByRefImpl(); + var isPointer = paramType.IsPointerImpl(); + var isOut = param.get_IsOut(); + var paramTypeDef = (REFrameworkNET.TypeDefinition)paramType.get_TypeHandle(); - // Go through the parents, check if the parents are allowed to be generated - // and add the new keyword if the matching method is found in one allowed to be generated - // TODO: We can get rid of this once we start properly generating generic classes. - // Since we just ignore any class that has '<' in it. - foreach (var matchingMethod in matchingParentMethods) { - var parent = matchingMethod.DeclaringType; - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { - continue; + var paramTypeSyntax = MakeProperType(paramTypeDef, t); + + System.Collections.Generic.List modifiers = []; + + if (isOut == true) { + simpleMethodSignature += "out"; + modifiers.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword)); + } + + if (isByRef == true) { + // can only be either ref or out. + if (!isOut) { + simpleMethodSignature += "ref " + paramTypeSyntax.GetText().ToString(); + modifiers.Add(SyntaxFactory.Token(SyntaxKind.RefKeyword)); + } + + parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName(paramTypeSyntax.ToString())).AddModifiers(modifiers.ToArray())); + } else if (isPointer == true) { + simpleMethodSignature += "ptr " + paramTypeSyntax.GetText().ToString(); + parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName(paramTypeSyntax.ToString() + "*")).AddModifiers(modifiers.ToArray())); + anyUnsafeParams = true; + } else { + simpleMethodSignature += paramTypeSyntax.GetText().ToString(); + parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(paramTypeSyntax).AddModifiers(modifiers.ToArray())); } + } - methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - break; + methodDeclaration = methodDeclaration.AddParameterListParameters([.. parameters]); + + if (anyUnsafeParams) { + methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.UnsafeKeyword)); } } + } else { + simpleMethodSignature += "()"; + } - return methodDeclaration; - }).Where(method => method != null).Select(method => method!).ToArray()); + if (method.IsStatic()) { + // Add System.ComponentModel.Description("static") attribute + //methodDeclaration = methodDeclaration.AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName("global::System.ComponentModel.DescriptionAttribute"), SyntaxFactory.ParseAttributeArgumentList("(\"static\")")))); + + // lets see what happens if we just make it static + /*methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + + // Now we must add a body to it that actually calls the method + // We have our REFType field, so we can lookup the method and call it + // Make a private static field to hold the REFrameworkNET.Method + var internalFieldName = "INTERNAL_" + method.GetIndex().ToString(); + var methodVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Method")) + .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetMethod(\"" + method.Name + "\")")))); + + var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + internalFieldDeclarations.Add(methodFieldDeclaration); + + // Now we can add the body + // bool HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result); + if (method.ReturnType.FullName == "System.Void") { + var body = internalFieldName + ".Invoke(null, null)"; + methodDeclaration = methodDeclaration.AddBodyStatements(SyntaxFactory.ParseStatement(body)); + } else { + var body1 = "object INTERNAL_result = null;"; + var body2 = internalFieldName + ".HandleInvokeMember_Internal(null, " + (method.Parameters.Count > 0 ? "new object[] {" + string.Join(", ", method.Parameters.Select(param => param.Name)) + "}" : "null") + ", ref INTERNAL_result);"; + string body3 = "return (" + returnType.GetText() + ")INTERNAL_result;"; - if (internalFieldDeclarations.Count > 0) { - typeDeclaration = typeDeclaration.AddMembers(internalFieldDeclarations.ToArray()); - } + methodDeclaration = methodDeclaration.AddBodyStatements( + [SyntaxFactory.ParseStatement(body1), SyntaxFactory.ParseStatement(body2), SyntaxFactory.ParseStatement(body3)] + ); + }*/ + } - if (baseTypes.Count > 0 && typeDeclaration != null) { - refTypeFieldDecl = refTypeFieldDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); - //fieldDeclaration2 = fieldDeclaration2.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + if (seenMethodSignatures.Contains(simpleMethodSignature)) { + Console.WriteLine("Skipping duplicate method: " + methodDeclaration.GetText().ToString()); + return null; + } - typeDeclaration = (typeDeclaration as InterfaceDeclarationSyntax)?.AddBaseListTypes(baseTypes.ToArray()); - } + seenMethodSignatures.Add(simpleMethodSignature); - if (typeDeclaration != null) { - typeDeclaration = typeDeclaration.AddMembers(refTypeFieldDecl); - //typeDeclaration = typeDeclaration.AddMembers(refProxyFieldDecl); - } + // Add the rest of the modifiers here that would mangle the signature check + if (baseTypes.Count > 0 && methodExtension != null && methodExtension.Override != null && methodExtension.Override == true) { + var matchingParentMethods = methodExtension.MatchingParentMethods; - return GenerateNestedTypes(); + // Go through the parents, check if the parents are allowed to be generated + // and add the new keyword if the matching method is found in one allowed to be generated + // TODO: We can get rid of this once we start properly generating generic classes. + // Since we just ignore any class that has '<' in it. + foreach (var matchingMethod in matchingParentMethods) { + var parent = matchingMethod.DeclaringType; + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { + continue; + } + + methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + break; + } + } + + return methodDeclaration; + }).Where(method => method != null).Select(method => method!); + + if (matchingMethods == null) { + return typeDeclaration; + } + + return typeDeclaration.AddMembers(matchingMethods.ToArray()); } private TypeDeclarationSyntax? GenerateNestedTypes() { diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 8ee72b33b..c8b090486 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -663,13 +663,27 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin strippedAssemblyName = "_System"; } + if (strippedAssemblyName == "mscorlib") { + strippedAssemblyName = "_mscorlib"; + } + REFrameworkNET.API.LogInfo("Generating assembly " + strippedAssemblyName); List compilationUnits = []; var tdb = REFrameworkNET.API.GetTDB(); - // Is this parallelizable? + List typeList = []; + foreach (dynamic reEngineT in assembly.GetTypes()) { + typeList.Add(reEngineT); + } + + // Clean up all the local objects + // Mainly because some of the older games don't play well with a ton of objects on the thread local heap + REFrameworkNET.API.LocalFrameGC(); + + // Is this parallelizable? + foreach (dynamic reEngineT in typeList) { var th = reEngineT.get_TypeHandle(); if (th == null) { @@ -687,6 +701,10 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var typeName = t.GetFullName(); var compilationUnit = MakeFromTypeEntry(tdb, typeName, t); compilationUnits.Add(compilationUnit); + + // Clean up all the local objects + // Mainly because some of the older games don't play well with a ton of objects on the thread local heap + REFrameworkNET.API.LocalFrameGC(); } List syntaxTrees = new List(); @@ -777,9 +795,25 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin dynamic appdomain = appdomainT.get_CurrentDomain(); dynamic assemblies = appdomain.GetAssemblies(); - List bytecodes = []; + List assembliesList = []; + // Pre-emptively add all assemblies to the list + // because the assemblies list that was returned will be cleaned up by the call to LocalFrameGC foreach (dynamic assembly in assemblies) { + if (assembly == null) { + continue; + } + + if (!(assembly as ManagedObject).IsGlobalized()) { + (assembly as ManagedObject).Globalize(); + } + + assembliesList.Add(assembly); + } + + List bytecodes = []; + + foreach (dynamic assembly in assembliesList) { var strippedAssemblyName = assembly.get_FullName().Split(',')[0]; REFrameworkNET.API.LogInfo("Assembly: " + (assembly.get_Location()?.ToString() ?? "NONE")); REFrameworkNET.API.LogInfo("Assembly (stripped): " + strippedAssemblyName); @@ -789,6 +823,10 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin if (bytecode != null) { bytecodes.Add(bytecode); } + + // Clean up all the local objects + // Mainly because some of the older games don't play well with a ton of objects on the thread local heap + REFrameworkNET.API.LocalFrameGC(); } return bytecodes; diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index 5df41a253..d61ea7c11 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -48,6 +48,10 @@ public ref class API static REFrameworkNET::ManagedObject^ GetManagedSingleton(System::String^ name); static NativeObject^ GetNativeSingleton(System::String^ name); + static void LocalFrameGC() { + s_api->get_vm_context()->local_frame_gc(); + } + generic where T : ref class static T GetNativeSingletonT() { auto fullName = T::typeid->FullName; From f566b8bedfa6313f3d4280fa5dd676aae3e611a3 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 05:43:36 -0700 Subject: [PATCH 142/207] .NET: Migrate ObjectExplorer test script into its own file --- csharp-api/CMakeLists.txt | 1 + csharp-api/test/Test/ObjectExplorer.cs | 492 ++++++++++++++++++++++++ csharp-api/test/Test/Test.cs | 510 +++---------------------- 3 files changed, 553 insertions(+), 450 deletions(-) create mode 100644 csharp-api/test/Test/ObjectExplorer.cs diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index de380434e..d84b1e2a6 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -310,6 +310,7 @@ VS_DOTNET_REFERENCE_REFramework.NET # Target: CSharpAPITest if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) # build-csharp-test set(CSharpAPITest_SOURCES + "test/Test/ObjectExplorer.cs" "test/Test/Test.cs" "test/Test/Test2.cs" cmake.toml diff --git a/csharp-api/test/Test/ObjectExplorer.cs b/csharp-api/test/Test/ObjectExplorer.cs new file mode 100644 index 000000000..84c5fa41c --- /dev/null +++ b/csharp-api/test/Test/ObjectExplorer.cs @@ -0,0 +1,492 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Reflection; +using ImGuiNET; +using REFrameworkNET; +using REFrameworkNET.Callbacks; + +public class ObjectExplorerPlugin { + public static void RenderImGui() { + if (ImGui.Begin("Test Window")) { + ImGui.SetNextItemOpen(true, ImGuiCond.Once); + if (ImGui.TreeNode("Object Explorer")) { + ObjectExplorer.Render(); + + ImGui.TreePop(); + } + + ImGui.End(); + } + } + + [REFrameworkNET.Attributes.PluginExitPoint] + public static void OnUnload() { + } + + [REFrameworkNET.Attributes.PluginEntryPoint] + public static void Main() { + REFrameworkNET.Callbacks.ImGuiRender.Pre += RenderImGui; + } +} + +class ObjectExplorer { + static System.Numerics.Vector4 TYPE_COLOR = new System.Numerics.Vector4(78.0f / 255.0f, 201 / 255.0f, 176 / 255.0f, 1.0f); + static System.Numerics.Vector4 FIELD_COLOR = new(156 / 255.0f, 220 / 255.0f, 254 / 255.0f, 1.0f); + static System.Numerics.Vector4 METHOD_COLOR = new(220 / 255.0f, 220 / 255.0f, 170 / 255.0f, 1.0f); + + static _System.Enum SystemEnumT = REFrameworkNET.TDB.Get().GetTypeT<_System.Enum>(); + + public static void DisplayColorPicker() { + ImGui.ColorEdit4("Type Color", ref TYPE_COLOR); + ImGui.ColorEdit4("Field Color", ref FIELD_COLOR); + ImGui.ColorEdit4("Method Color", ref METHOD_COLOR); + } + + public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field field) { + var t = field.GetType(); + string tName = t != null ? t.GetFullName() : "null"; + + var unified = obj != null ? obj as REFrameworkNET.UnifiedObject : null; + ulong address = unified != null ? unified.GetAddress() : 0; + + if (field.IsStatic()) { + address = field.GetDataRaw(obj.GetAddress(), false); + } else if (obj != null) { + address += field.GetOffsetFromBase(); + } + + // Make a tree node that spans the entire width of the window + ImGui.PushID(address.ToString("X")); + ImGui.SetNextItemOpen(false, ImGuiCond.Once); + var made = ImGui.TreeNodeEx("", ImGuiTreeNodeFlags.SpanFullWidth); + + // Context menu to copy address to clipboard + if (ImGui.BeginPopupContextItem()) { + if (ImGui.MenuItem("Copy Address to Clipboard")) { + ImGui.SetClipboardText(address.ToString("X")); + } + + ImGui.EndPopup(); + } + + ImGui.SameLine(); + //ImGui.Text(" " + tName); + ImGui.TextColored(TYPE_COLOR, " " + tName); + + ImGui.SameLine(); + + ImGui.TextColored(FIELD_COLOR, field.GetName()); + + ImGui.SameLine(); + + if (field.IsStatic()) { + // Red text + ImGui.TextColored(new System.Numerics.Vector4(0.75f, 0.2f, 0.0f, 1.0f), "Static"); + } else { + ImGui.Text("0x" + field.GetOffsetFromBase().ToString("X")); + } + + if (obj == null) { + if (made) { + ImGui.Text("Value: null"); + ImGui.TreePop(); + } + + ImGui.PopID(); + return; + } + + if (made) { + if (!t.IsValueType()) { + var objValue = obj.GetField(field.GetName()) as REFrameworkNET.IObject; + + if (objValue != null) { + DisplayObject(objValue); + } else { + ImGui.Text("Value: null"); + } + } else { + object fieldData = null; + var finalName = t.IsEnum() ? t.GetUnderlyingType().GetFullName() : tName; + + switch (finalName) { + case "System.Byte": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.SByte": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.Int16": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.UInt16": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.Int32": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.UInt32": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.Int64": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.UInt64": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.Single": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + case "System.Boolean": + fieldData = field.GetDataT(obj.GetAddress(), false); + break; + /*case "System.String": + ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false)); + break;*/ + default: + break; + } + + if (t.IsEnum() && fieldData != null) { + long longValue = Convert.ToInt64(fieldData); + var boxedEnum = SystemEnumT.boxEnum(t.GetRuntimeType().As<_System.Type>(), longValue); + ImGui.Text("Result: " + (boxedEnum as IObject).Call("ToString()") + " (" + fieldData.ToString() + ")"); + } else if (fieldData != null) { + ImGui.Text("Value: " + fieldData.ToString()); + //ImGui.Text("Value (" + t.FullName + ")" + field.GetDataRaw(obj.GetAddress(), false).ToString("X")); + } else { + ImGui.Text("Value: null"); + } + } + + ImGui.TreePop(); + } + + ImGui.PopID(); + } + + public static void DisplayMethod(REFrameworkNET.IObject obj, REFrameworkNET.Method method) { + ImGui.PushID(method.GetDeclaringType().FullName + method.GetMethodSignature()); + var made = ImGui.TreeNodeEx("", ImGuiTreeNodeFlags.SpanFullWidth); + ImGui.SameLine(0.0f, 0.0f); + + var returnT = method.GetReturnType(); + var returnTName = returnT != null ? returnT.GetFullName() : "null"; + + //ImGui.Text(" " + returnTName); + ImGui.TextColored(TYPE_COLOR, " " + returnTName); + ImGui.SameLine(0.0f, 0.0f); + + ImGui.TextColored(METHOD_COLOR, " " + method.GetName()); + + + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text("("); + + var ps = method.GetParameters(); + + if (ps.Count > 0) { + for (int i = 0; i < ps.Count; i++) { + var p = ps[i]; + + ImGui.SameLine(0.0f, 0.0f); + ImGui.TextColored(TYPE_COLOR, p.Type.GetFullName()); + + if (p.Name != null && p.Name.Length > 0) { + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text(" " + p.Name); + } + + if (i < ps.Count - 1) { + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text(", "); + } + //postfix += p.Type.GetFullName() + " " + p.Name + ", "; + } + + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text(")"); + + //postfix = postfix.Substring(0, postfix.Length - 3); + } else { + ImGui.SameLine(0.0f, 0.0f); + ImGui.Text(")"); + } + + if (made) { + if ((method.Name.StartsWith("get_") || method.Name.StartsWith("Get") || method.Name == "ToString") && ps.Count == 0) { + object result = null; + obj.HandleInvokeMember_Internal(method, null, ref result); + + if (result != null) { + if (result is IObject objResult) { + DisplayObject(objResult); + } else { + var returnType = method.GetReturnType(); + + if (returnType.IsEnum()) { + long longValue = Convert.ToInt64(result); + var boxedEnum = SystemEnumT.boxEnum(returnType.GetRuntimeType().As<_System.Type>(), longValue); + ImGui.Text("Result: " + (boxedEnum as IObject).Call("ToString()") + " (" + result.ToString() + ")"); + } else { + ImGui.Text("Result: " + result.ToString() + " (" + result.GetType().FullName + ")"); + } + } + } else { + ImGui.Text("Result: null"); + } + } + + ImGui.TreePop(); + } + + ImGui.PopID(); + } + + public static void DisplayType(REFrameworkNET.TypeDefinition t) { + ImGui.Text("Name: " + t.GetFullName()); + ImGui.Text("Namespace: " + t.GetNamespace()); + + if (t.DeclaringType != null) { + var made = ImGui.TreeNodeEx("Declaring Type: ", ImGuiNET.ImGuiTreeNodeFlags.SpanFullWidth); + ImGui.SameLine(0.0f, 0.0f); + ImGui.TextColored(TYPE_COLOR, t.DeclaringType.GetFullName()); + if (made) { + DisplayType(t.DeclaringType); + ImGui.TreePop(); + } + } + + if (t.ParentType != null) { + var made = ImGui.TreeNodeEx("Parent Type: ", ImGuiNET.ImGuiTreeNodeFlags.SpanFullWidth); + ImGui.SameLine(0.0f, 0.0f); + ImGui.TextColored(TYPE_COLOR, t.ParentType.GetFullName()); + if (made) { + DisplayType(t.ParentType); + ImGui.TreePop(); + } + } + + var runtimeTypeRaw = t.GetRuntimeType(); + + if (runtimeTypeRaw != null) { + var runtimeType = runtimeTypeRaw.As<_System.Type>(); + var assembly = runtimeType.get_Assembly(); + + if (assembly != null) { + if (ImGui.TreeNode("Assembly: " + assembly.get_FullName().Split(',')[0])) { + DisplayObject(assembly as IObject); + ImGui.TreePop(); + } + } + + var baseType = runtimeType.get_BaseType(); + + /*if (baseType != null) { + if (ImGui.TreeNode("Base Type (" + (baseType.get_TypeHandle() as REFrameworkNET.TypeDefinition).FullName + ")")) { + DisplayObject(baseType as IObject); + ImGui.TreePop(); + } + }*/ + + if (ImGui.TreeNode("Runtime Type")) { + DisplayObject(runtimeType as IObject); + ImGui.TreePop(); + } + } + } + + private static TypeDefinition SystemArrayT = REFrameworkNET.TDB.Get().GetType("System.Array"); + + public static void DisplayObject(REFrameworkNET.IObject obj) { + bool isPinnedObject = obj is REFrameworkNET.ManagedObject ? (obj as REFrameworkNET.ManagedObject).GetReferenceCount() >= 0 : false; + + if (isPinnedObject) { + ImGui.PushID(obj.GetAddress().ToString("X")); + } + + if (ImGui.TreeNode("Internals")) { + if (ImGui.TreeNode("Type Info")) { + DisplayType(obj.GetTypeDefinition()); + ImGui.TreePop(); + } + + ImGui.Text("Address: 0x" + obj.GetAddress().ToString("X")); + + if (obj is REFrameworkNET.ManagedObject) { + var managed = obj as REFrameworkNET.ManagedObject; + ImGui.Text("Reference count: " + managed.GetReferenceCount().ToString()); + } + + ImGui.TreePop(); + } + + if (ImGui.TreeNode("Methods")) { + var tdef = obj.GetTypeDefinition(); + List methods = new List(); + + for (var parent = tdef; parent != null; parent = parent.ParentType) { + var parentMethods = parent.GetMethods(); + methods.AddRange(parentMethods); + } + + // Sort methods by name + methods.Sort((a, b) => a.GetName().CompareTo(b.GetName())); + + // Remove methods that have "!" in their parameters + methods.RemoveAll((m) => m.GetParameters().Exists((p) => p.Type.Name.Contains("!"))); + + foreach (var method in methods) { + DisplayMethod(obj, method); + } + } + + if (ImGui.TreeNode("Fields")) { + var tdef = obj.GetTypeDefinition(); + List fields = new List(); + + for (var parent = tdef; parent != null; parent = parent.ParentType) { + var parentFields = parent.GetFields(); + fields.AddRange(parentFields); + } + + // Sort fields by name + fields.Sort((a, b) => a.GetName().CompareTo(b.GetName())); + + foreach (var field in fields) { + DisplayField(obj, field); + } + + ImGui.TreePop(); + } + + if (obj.GetTypeDefinition().IsDerivedFrom(SystemArrayT)) { + ImGui.Text("Array Length: " + (int)obj.Call("get_Length")); + + var easyArray = obj.As<_System.Array>(); + var elementType = obj.GetTypeDefinition().GetElementType(); + var elementSize = elementType.GetSize(); + + for (int i = 0; i < easyArray.get_Length(); i++) { + var element = easyArray.GetValue(i); + if (element == null) { + ImGui.Text("Element " + i + ": null"); + continue; + } + + var made = ImGui.TreeNodeEx("Element " + i, ImGuiTreeNodeFlags.SpanFullWidth); + + ImGui.SameLine(0.0f, 0.0f); + + if (element is IObject) { + var asString = (element as IObject).Call("ToString()") as string; + ImGui.TextColored(TYPE_COLOR, " ("+ asString + ")"); + } else { + ImGui.TextColored(TYPE_COLOR, " ("+ element.ToString() + ")"); + } + + if (made) { + if (element is IObject objElement) { + //ImGui.PushID((obj.GetAddress() + 0x10 + (ulong)(i * elementSize)).ToString("X")); + ImGui.PushID(i); + DisplayObject(objElement); + ImGui.PopID(); + } else { + ImGui.Text("Element: " + element.ToString()); + } + + ImGui.TreePop(); + } + } + } + + if (isPinnedObject) { + ImGui.PopID(); + } + } + + public static void RenderNativeSingletons() { + var singletons = REFrameworkNET.API.GetNativeSingletons(); + + // Sort by type name + singletons.Sort((a, b) => a.Instance.GetTypeDefinition().GetFullName().CompareTo(b.Instance.GetTypeDefinition().GetFullName())); + + foreach (var singletonDesc in singletons) { + var singleton = singletonDesc.Instance; + if (singleton == null) { + continue; + } + var singletonName = singleton.GetTypeDefinition().GetFullName(); + + if (ImGui.TreeNode(singletonName)) { + DisplayObject(singleton); + ImGui.TreePop(); + } + } + } + + public static void RenderManagedSingletons() { + var singletons = REFrameworkNET.API.GetManagedSingletons(); + + foreach (var singletonDesc in singletons) { + var singleton = singletonDesc.Instance; + if (singleton == null) { + continue; + } + var singletonName = singleton.GetTypeDefinition().GetFullName(); + + if (ImGui.TreeNode(singletonName)) { + DisplayObject(singleton); + ImGui.TreePop(); + } + } + } + + public static void Render() { + ImGui.SetNextItemOpen(true, ImGuiCond.Once); + if (ImGui.TreeNode("Color Picker")) { + DisplayColorPicker(); + ImGui.TreePop(); + } + + + try { + if (ImGui.TreeNode("Managed Singletons")) { + RenderManagedSingletons(); + ImGui.TreePop(); + } + + if (ImGui.TreeNode("Native Singletons")) { + RenderNativeSingletons(); + } + + var appdomainT = REFrameworkNET.API.GetTDB().GetTypeT<_System.AppDomain>(); + var appdomain = appdomainT.get_CurrentDomain(); + var assemblies = appdomain.GetAssemblies(); + + if (ImGui.TreeNode("AppDomain")) { + if (assemblies != null && ImGui.TreeNode("Assemblies")) { + for (int i = 0; i < assemblies.get_Length(); i++) { + var assembly = assemblies.get_Item(i); + var assemblyT = (assembly as IObject).GetTypeDefinition(); + var location = assembly.get_Location() ?? "null"; + + if (ImGui.TreeNode(location)) { + DisplayObject(assembly as IObject); + ImGui.TreePop(); + } + } + + ImGui.TreePop(); + } + + DisplayObject(appdomain as IObject); + ImGui.TreePop(); + } + } catch (Exception e) { + System.Console.WriteLine(e.ToString()); + } + } +} // class ObjectExplorer \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index ea8d0f8d2..3511c3175 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -139,471 +139,81 @@ public static void TryEnableFrameGeneration() { } } } +class REFrameworkPlugin { + // Measure time between pre and post + // get time + static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); + static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); -class ObjectExplorer { - static System.Numerics.Vector4 TYPE_COLOR = new System.Numerics.Vector4(78.0f / 255.0f, 201 / 255.0f, 176 / 255.0f, 1.0f); - static System.Numerics.Vector4 FIELD_COLOR = new(156 / 255.0f, 220 / 255.0f, 254 / 255.0f, 1.0f); - static System.Numerics.Vector4 METHOD_COLOR = new(220 / 255.0f, 220 / 255.0f, 170 / 255.0f, 1.0f); - - static _System.Enum SystemEnumT = REFrameworkNET.TDB.Get().GetTypeT<_System.Enum>(); - - public static void DisplayColorPicker() { - ImGui.ColorEdit4("Type Color", ref TYPE_COLOR); - ImGui.ColorEdit4("Field Color", ref FIELD_COLOR); - ImGui.ColorEdit4("Method Color", ref METHOD_COLOR); - } - - public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field field) { - var t = field.GetType(); - string tName = t != null ? t.GetFullName() : "null"; - - var unified = obj != null ? obj as REFrameworkNET.UnifiedObject : null; - ulong address = unified != null ? unified.GetAddress() : 0; - - if (field.IsStatic()) { - address = field.GetDataRaw(obj.GetAddress(), false); - } else if (obj != null) { - address += field.GetOffsetFromBase(); - } - - // Make a tree node that spans the entire width of the window - ImGui.PushID(address.ToString("X")); - ImGui.SetNextItemOpen(false, ImGuiCond.Once); - var made = ImGui.TreeNodeEx("", ImGuiTreeNodeFlags.SpanFullWidth); - - // Context menu to copy address to clipboard - if (ImGui.BeginPopupContextItem()) { - if (ImGui.MenuItem("Copy Address to Clipboard")) { - ImGui.SetClipboardText(address.ToString("X")); - } - - ImGui.EndPopup(); - } - - ImGui.SameLine(); - //ImGui.Text(" " + tName); - ImGui.TextColored(TYPE_COLOR, " " + tName); - - ImGui.SameLine(); - - ImGui.TextColored(FIELD_COLOR, field.GetName()); - - ImGui.SameLine(); - - if (field.IsStatic()) { - // Red text - ImGui.TextColored(new System.Numerics.Vector4(0.75f, 0.2f, 0.0f, 1.0f), "Static"); - } else { - ImGui.Text("0x" + field.GetOffsetFromBase().ToString("X")); - } - - if (obj == null) { - if (made) { - ImGui.Text("Value: null"); - ImGui.TreePop(); - } - - ImGui.PopID(); - return; - } - - if (made) { - if (!t.IsValueType()) { - var objValue = obj.GetField(field.GetName()) as REFrameworkNET.IObject; - - if (objValue != null) { - DisplayObject(objValue); - } else { - ImGui.Text("Value: null"); - } - } else { - object fieldData = null; - var finalName = t.IsEnum() ? t.GetUnderlyingType().GetFullName() : tName; - - switch (finalName) { - case "System.Byte": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.SByte": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.Int16": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.UInt16": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.Int32": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.UInt32": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.Int64": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.UInt64": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.Single": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - case "System.Boolean": - fieldData = field.GetDataT(obj.GetAddress(), false); - break; - /*case "System.String": - ImGui.Text("Value: " + field.GetDataT(obj.GetAddress(), false)); - break;*/ - default: - break; - } - - if (t.IsEnum() && fieldData != null) { - long longValue = Convert.ToInt64(fieldData); - var boxedEnum = SystemEnumT.boxEnum(t.GetRuntimeType().As<_System.Type>(), longValue); - ImGui.Text("Result: " + (boxedEnum as IObject).Call("ToString()") + " (" + fieldData.ToString() + ")"); - } else if (fieldData != null) { - ImGui.Text("Value: " + fieldData.ToString()); - //ImGui.Text("Value (" + t.FullName + ")" + field.GetDataRaw(obj.GetAddress(), false).ToString("X")); - } else { - ImGui.Text("Value: null"); - } - } - - ImGui.TreePop(); - } - - ImGui.PopID(); - } - - public static void DisplayMethod(REFrameworkNET.IObject obj, REFrameworkNET.Method method) { - ImGui.PushID(method.GetDeclaringType().FullName + method.GetMethodSignature()); - var made = ImGui.TreeNodeEx("", ImGuiTreeNodeFlags.SpanFullWidth); - ImGui.SameLine(0.0f, 0.0f); - - var returnT = method.GetReturnType(); - var returnTName = returnT != null ? returnT.GetFullName() : "null"; - - //ImGui.Text(" " + returnTName); - ImGui.TextColored(TYPE_COLOR, " " + returnTName); - ImGui.SameLine(0.0f, 0.0f); - - ImGui.TextColored(METHOD_COLOR, " " + method.GetName()); - - - ImGui.SameLine(0.0f, 0.0f); - ImGui.Text("("); - - var ps = method.GetParameters(); - - if (ps.Count > 0) { - for (int i = 0; i < ps.Count; i++) { - var p = ps[i]; - - ImGui.SameLine(0.0f, 0.0f); - ImGui.TextColored(TYPE_COLOR, p.Type.GetFullName()); - - if (p.Name != null && p.Name.Length > 0) { - ImGui.SameLine(0.0f, 0.0f); - ImGui.Text(" " + p.Name); - } - - if (i < ps.Count - 1) { - ImGui.SameLine(0.0f, 0.0f); - ImGui.Text(", "); - } - //postfix += p.Type.GetFullName() + " " + p.Name + ", "; - } - - ImGui.SameLine(0.0f, 0.0f); - ImGui.Text(")"); - - //postfix = postfix.Substring(0, postfix.Length - 3); - } else { - ImGui.SameLine(0.0f, 0.0f); - ImGui.Text(")"); - } - - if (made) { - if ((method.Name.StartsWith("get_") || method.Name.StartsWith("Get") || method.Name == "ToString") && ps.Count == 0) { - object result = null; - obj.HandleInvokeMember_Internal(method, null, ref result); - - if (result != null) { - if (result is IObject objResult) { - DisplayObject(objResult); - } else { - var returnType = method.GetReturnType(); - - if (returnType.IsEnum()) { - long longValue = Convert.ToInt64(result); - var boxedEnum = SystemEnumT.boxEnum(returnType.GetRuntimeType().As<_System.Type>(), longValue); - ImGui.Text("Result: " + (boxedEnum as IObject).Call("ToString()") + " (" + result.ToString() + ")"); - } else { - ImGui.Text("Result: " + result.ToString() + " (" + result.GetType().FullName + ")"); - } - } - } else { - ImGui.Text("Result: null"); - } - } - - ImGui.TreePop(); - } - - ImGui.PopID(); - } - - public static void DisplayType(REFrameworkNET.TypeDefinition t) { - ImGui.Text("Name: " + t.GetFullName()); - ImGui.Text("Namespace: " + t.GetNamespace()); - - if (t.DeclaringType != null) { - var made = ImGui.TreeNodeEx("Declaring Type: ", ImGuiNET.ImGuiTreeNodeFlags.SpanFullWidth); - ImGui.SameLine(0.0f, 0.0f); - ImGui.TextColored(TYPE_COLOR, t.DeclaringType.GetFullName()); - if (made) { - DisplayType(t.DeclaringType); - ImGui.TreePop(); - } - } - - if (t.ParentType != null) { - var made = ImGui.TreeNodeEx("Parent Type: ", ImGuiNET.ImGuiTreeNodeFlags.SpanFullWidth); - ImGui.SameLine(0.0f, 0.0f); - ImGui.TextColored(TYPE_COLOR, t.ParentType.GetFullName()); - if (made) { - DisplayType(t.ParentType); - ImGui.TreePop(); - } - } - - var runtimeTypeRaw = t.GetRuntimeType(); + static bool doFullGC = false; - if (runtimeTypeRaw != null) { - var runtimeType = runtimeTypeRaw.As<_System.Type>(); - var assembly = runtimeType.get_Assembly(); + // Assigned in a callback below. + public static void RenderImGui() { + if (ImGui.Begin("Test Window")) { + // Debug info about GC state + if (ImGui.TreeNode(".NET Debug")) { + ImGui.Checkbox("Do Full GC", ref doFullGC); - if (assembly != null) { - if (ImGui.TreeNode("Assembly: " + assembly.get_FullName().Split(',')[0])) { - DisplayObject(assembly as IObject); - ImGui.TreePop(); + if (ImGui.Button("Do Gen 2 GC")) { + GC.Collect(2, GCCollectionMode.Forced, false); } - } - var baseType = runtimeType.get_BaseType(); + var heapBytes = GC.GetTotalMemory(false); + var heapKb = heapBytes / 1024; + var heapMb = heapKb / 1024; + var heapGb = heapMb / 1024; - /*if (baseType != null) { - if (ImGui.TreeNode("Base Type (" + (baseType.get_TypeHandle() as REFrameworkNET.TypeDefinition).FullName + ")")) { - DisplayObject(baseType as IObject); - ImGui.TreePop(); + if (doFullGC) { + GC.Collect(0, GCCollectionMode.Forced, false); + GC.Collect(1, GCCollectionMode.Forced, false); + GC.Collect(2, GCCollectionMode.Forced, false); } - }*/ - - if (ImGui.TreeNode("Runtime Type")) { - DisplayObject(runtimeType as IObject); - ImGui.TreePop(); - } - } - } - private static TypeDefinition SystemArrayT = REFrameworkNET.TDB.Get().GetType("System.Array"); - - public static void DisplayObject(REFrameworkNET.IObject obj) { - if (ImGui.TreeNode("Internals")) { - if (ImGui.TreeNode("Type Info")) { - DisplayType(obj.GetTypeDefinition()); - ImGui.TreePop(); - } - - ImGui.Text("Address: 0x" + obj.GetAddress().ToString("X")); - - if (obj is REFrameworkNET.ManagedObject) { - var managed = obj as REFrameworkNET.ManagedObject; - ImGui.Text("Reference count: " + managed.GetReferenceCount().ToString()); - } - - ImGui.TreePop(); - } - - if (ImGui.TreeNode("Methods")) { - var tdef = obj.GetTypeDefinition(); - List methods = new List(); - - for (var parent = tdef; parent != null; parent = parent.ParentType) { - var parentMethods = parent.GetMethods(); - methods.AddRange(parentMethods); - } - - // Sort methods by name - methods.Sort((a, b) => a.GetName().CompareTo(b.GetName())); - - // Remove methods that have "!" in their parameters - methods.RemoveAll((m) => m.GetParameters().Exists((p) => p.Type.Name.Contains("!"))); - - foreach (var method in methods) { - DisplayMethod(obj, method); - } - } - - if (ImGui.TreeNode("Fields")) { - var tdef = obj.GetTypeDefinition(); - List fields = new List(); - - for (var parent = tdef; parent != null; parent = parent.ParentType) { - var parentFields = parent.GetFields(); - fields.AddRange(parentFields); - } - - // Sort fields by name - fields.Sort((a, b) => a.GetName().CompareTo(b.GetName())); - - foreach (var field in fields) { - DisplayField(obj, field); - } - - ImGui.TreePop(); - } - - if (obj.GetTypeDefinition().IsDerivedFrom(SystemArrayT)) { - ImGui.Text("Array Length: " + (int)obj.Call("get_Length")); - - var easyArray = obj.As<_System.Array>(); - var elementType = obj.GetTypeDefinition().GetElementType(); - var elementSize = elementType.GetSize(); - - for (int i = 0; i < easyArray.get_Length(); i++) { - var element = easyArray.GetValue(i); - if (element == null) { - ImGui.Text("Element " + i + ": null"); - continue; - } + ImGui.Text("GC Memory: " + GC.GetTotalMemory(false) / 1024 + " KB (" + heapMb + " MB, " + heapGb + " GB)"); + ImGui.Text("GC Collection Count (gen 0): " + GC.CollectionCount(0)); + ImGui.Text("GC Collection Count (gen 1): " + GC.CollectionCount(1)); + ImGui.Text("GC Collection Count (gen 2): " + GC.CollectionCount(2)); + ImGui.Text("GC Latency Mode: " + System.Runtime.GCSettings.LatencyMode.ToString()); + ImGui.Text("GC Is Server GC: " + System.Runtime.GCSettings.IsServerGC); + ImGui.Text("GC Max Generation: " + GC.MaxGeneration); + ImGui.Text("GC Force Full Collection: " + System.Runtime.GCSettings.LargeObjectHeapCompactionMode); - var made = ImGui.TreeNodeEx("Element " + i, ImGuiTreeNodeFlags.SpanFullWidth); - - ImGui.SameLine(0.0f, 0.0f); - - if (element is IObject) { - var asString = (element as IObject).Call("ToString()") as string; - ImGui.TextColored(TYPE_COLOR, " ("+ asString + ")"); - } else { - ImGui.TextColored(TYPE_COLOR, " ("+ element.ToString() + ")"); - } - - if (made) { - if (element is IObject objElement) { - ImGui.PushID((obj.GetAddress() + 0x10 + (ulong)(i * elementSize)).ToString("X")); - DisplayObject(objElement); - ImGui.PopID(); - } else { - ImGui.Text("Element: " + element.ToString()); - } - - ImGui.TreePop(); - } - } - } - } - - public static void RenderNativeSingletons() { - var singletons = REFrameworkNET.API.GetNativeSingletons(); - - // Sort by type name - singletons.Sort((a, b) => a.Instance.GetTypeDefinition().GetFullName().CompareTo(b.Instance.GetTypeDefinition().GetFullName())); - - foreach (var singletonDesc in singletons) { - var singleton = singletonDesc.Instance; - if (singleton == null) { - continue; - } - var singletonName = singleton.GetTypeDefinition().GetFullName(); - - if (ImGui.TreeNode(singletonName)) { - DisplayObject(singleton); - ImGui.TreePop(); - } - } - } - - public static void RenderManagedSingletons() { - var singletons = REFrameworkNET.API.GetManagedSingletons(); - - foreach (var singletonDesc in singletons) { - var singleton = singletonDesc.Instance; - if (singleton == null) { - continue; - } - var singletonName = singleton.GetTypeDefinition().GetFullName(); - - if (ImGui.TreeNode(singletonName)) { - DisplayObject(singleton); - ImGui.TreePop(); - } - } - } - - public static void Render() { - ImGui.SetNextItemOpen(true, ImGuiCond.Once); - if (ImGui.TreeNode("Color Picker")) { - DisplayColorPicker(); - ImGui.TreePop(); - } - - - try { - if (ImGui.TreeNode("Managed Singletons")) { - RenderManagedSingletons(); - ImGui.TreePop(); - } - - if (ImGui.TreeNode("Native Singletons")) { - RenderNativeSingletons(); - } + // memory info + var memoryInfo = GC.GetGCMemoryInfo(); + ImGui.Text("GC Is Concurrent: " + memoryInfo.Concurrent); + ImGui.Text("GC Fragmentation: " + memoryInfo.FragmentedBytes); + + var gcHeapSize = GC.GetGCMemoryInfo().HeapSizeBytes; + var gcHeapSizeKb = gcHeapSize / 1024; + var gcHeapSizeMb = gcHeapSizeKb / 1024; + var gcHeapSizeGb = gcHeapSizeMb / 1024; + ImGui.Text("GC Heap Size During Last GC: " + memoryInfo.HeapSizeBytes + " bytes (" + gcHeapSizeKb + " KB, " + gcHeapSizeMb + " MB, " + gcHeapSizeGb + " GB)"); + ImGui.Text("GC High Memory Load Threshold: " + memoryInfo.HighMemoryLoadThresholdBytes); + ImGui.Text("GC Memory Load: " + memoryInfo.MemoryLoadBytes); + + // Combo box for latency mode + // Turn enum into string + var latencyModeEnum = typeof(System.Runtime.GCLatencyMode); + var latencyModeNames = Enum.GetNames(latencyModeEnum); + + int currentLatencyMode = (int)System.Runtime.GCSettings.LatencyMode; + + if (ImGui.BeginCombo("Latency Mode", latencyModeNames[currentLatencyMode])) { + for (int i = 0; i < latencyModeNames.Length; i++) { + bool isSelected = i == currentLatencyMode; + + if (ImGui.Selectable(latencyModeNames[i], isSelected)) { + System.Runtime.GCSettings.LatencyMode = (System.Runtime.GCLatencyMode)i; + } - var appdomainT = REFrameworkNET.API.GetTDB().GetTypeT<_System.AppDomain>(); - var appdomain = appdomainT.get_CurrentDomain(); - var assemblies = appdomain.GetAssemblies(); - - if (ImGui.TreeNode("AppDomain")) { - if (assemblies != null && ImGui.TreeNode("Assemblies")) { - for (int i = 0; i < assemblies.get_Length(); i++) { - var assembly = assemblies.get_Item(i); - var assemblyT = (assembly as IObject).GetTypeDefinition(); - var location = assembly.get_Location() ?? "null"; - - if (ImGui.TreeNode(location)) { - DisplayObject(assembly as IObject); - ImGui.TreePop(); + if (isSelected) { + ImGui.SetItemDefaultFocus(); } } - ImGui.TreePop(); + ImGui.EndCombo(); } - DisplayObject(appdomain as IObject); - ImGui.TreePop(); - } - - DisplayType(REFrameworkNET.API.GetTDB().GetType("JsonParser.Value")); - } catch (Exception e) { - System.Console.WriteLine(e.ToString()); - } - } -} // class ObjectExplorer - -class REFrameworkPlugin { - // Measure time between pre and post - // get time - static System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); - static System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch(); - - // Assigned in a callback below. - public static void RenderImGui() { - if (ImGui.Begin("Test Window")) { - ImGui.SetNextItemOpen(true, ImGuiCond.Once); - if (ImGui.TreeNode("Object Explorer")) { - ObjectExplorer.Render(); - ImGui.TreePop(); } From 6947e92efe51c0b8d8e9bd1b850c2dd5248fe5cc Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 08:12:25 -0700 Subject: [PATCH 143/207] .NET: Add test script for RE2 --- csharp-api/CMakeLists.txt | 1 + csharp-api/make_symlinks.py | 28 ++++++++++++++------------- csharp-api/test/Test/TestRE2.cs | 34 +++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 13 deletions(-) create mode 100644 csharp-api/test/Test/TestRE2.cs diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index d84b1e2a6..acbf5bb48 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -313,6 +313,7 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) # build-csharp-test "test/Test/ObjectExplorer.cs" "test/Test/Test.cs" "test/Test/Test2.cs" + "test/Test/TestRE2.cs" cmake.toml ) diff --git a/csharp-api/make_symlinks.py b/csharp-api/make_symlinks.py index 21ae490e7..be475f8ef 100644 --- a/csharp-api/make_symlinks.py +++ b/csharp-api/make_symlinks.py @@ -25,22 +25,17 @@ def symlink_main(gamedir=None, bindir="build/bin", just_copy=False): print(f"Error: Directory {bindir} does not exist") return - plugins_dir_files = [ - "REFramework.NET.dll", - "REFramework.NET.runtimeconfig.json", - "REFramework.NET.xml", - "Ijwhost.dll", + source_dir_files = [ + "Test/Test/Test.cs", + "Test/Test/TestRE2.cs", + "Test/Test/ObjectExplorer.cs", ] - if os.path.exists("Test/Test/Test.cs"): - print("Creating symlink for Test.cs") - src = "Test/Test/Test.cs" - # modify to full path - src = os.path.abspath(src) - dst = os.path.join(gamedir, "reframework", "plugins", "source", "Test.cs") - + for file in source_dir_files: + src = os.path.abspath(file) + filename_only = os.path.basename(file) + dst = os.path.join(gamedir, "reframework", "plugins", "source", filename_only) os.makedirs(os.path.dirname(dst), exist_ok=True) - try: os.remove(dst) except FileNotFoundError: @@ -51,6 +46,13 @@ def symlink_main(gamedir=None, bindir="build/bin", just_copy=False): else: os.symlink(src, dst) + plugins_dir_files = [ + "REFramework.NET.dll", + "REFramework.NET.runtimeconfig.json", + "REFramework.NET.xml", + "Ijwhost.dll", + ] + for file in plugins_dir_files: src = os.path.join(bindir, file) src = os.path.abspath(src) diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs new file mode 100644 index 000000000..284f0dcf2 --- /dev/null +++ b/csharp-api/test/Test/TestRE2.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Reflection; +using ImGuiNET; +using REFrameworkNET; +using REFrameworkNET.Callbacks; + +public class TestRE2 { + [REFrameworkNET.Attributes.PluginEntryPoint] + public static void Main() { + // Get executable name + string executableName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; + + if (executableName.ToLower().Contains("re2")) { + Console.WriteLine("Running in RE2"); + } else { + Console.WriteLine("Not running in RE2"); + return; + } + + ImGuiRender.Pre += () => { + if (ImGui.Begin("TestRE2")) { + ImGui.Text("Hello, world!"); + ImGui.End(); + } + }; + } + + [REFrameworkNET.Attributes.PluginExitPoint] + public static void Unload() { + } +} \ No newline at end of file From b78378cb4d7c92996c1fdb87fd024a5c52d6e583 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 08:39:07 -0700 Subject: [PATCH 144/207] .NET (RE2): Fix System.Core dll name --- csharp-api/AssemblyGenerator/Generator.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index c8b090486..28af748aa 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -667,6 +667,10 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin strippedAssemblyName = "_mscorlib"; } + if (strippedAssemblyName == "System.Core") { + strippedAssemblyName = "_System.Core"; + } + REFrameworkNET.API.LogInfo("Generating assembly " + strippedAssemblyName); List compilationUnits = []; From 71ac34399bc003944065ff5e23db43b5e27721a3 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 08:45:52 -0700 Subject: [PATCH 145/207] .NET: Compile RE2 project with reference assemblies --- csharp-api/AssemblyGenerator/Generator.cs | 9 +- csharp-api/CMakeLists.txt | 121 +++++++++++++++++++--- csharp-api/cmake.toml | 101 +++++++++++++++--- csharp-api/test/Test/TestRE2.cs | 30 ++++-- 4 files changed, 220 insertions(+), 41 deletions(-) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 28af748aa..08d2c4a36 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -797,19 +797,20 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin dynamic appdomainT = tdb.GetType("System.AppDomain"); dynamic appdomain = appdomainT.get_CurrentDomain(); - dynamic assemblies = appdomain.GetAssemblies(); + var assemblies = (appdomain.GetAssemblies() as ManagedObject)!; List assembliesList = []; // Pre-emptively add all assemblies to the list // because the assemblies list that was returned will be cleaned up by the call to LocalFrameGC - foreach (dynamic assembly in assemblies) { + foreach (ManagedObject assembly in assemblies) { if (assembly == null) { continue; } - if (!(assembly as ManagedObject).IsGlobalized()) { - (assembly as ManagedObject).Globalize(); + // The object will get destroyed by the LocalFrameGC call if we don't do this + if (!assembly.IsGlobalized()) { + assembly.Globalize(); } assembliesList.Add(assembly); diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index acbf5bb48..a19dba4e6 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -72,27 +72,46 @@ if(NOT NUGET_PACKAGES_DIR) endif() # Set a bool if the generated reference assemblies exist in the build/bin folder -set(dll1 "${CMAKE_BINARY_DIR}/bin/REFramework.NET._System.dll") -set(dll2 "${CMAKE_BINARY_DIR}/bin/REFramework.NET.application.dll") -set(dll3 "${CMAKE_BINARY_DIR}/bin/REFramework.NET.viacore.dll") +set(dd2_systemdll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET._System.dll") +set(dd2_applicationdll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET.application.dll") +set(dd2_viacoredll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET.viacore.dll") + +set(re2_mscorlibdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._mscorlib.dll") +set(re2_systemdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._System.dll") +set(re2_systemcoredll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._System.Core.dll") +set(re2_applicationdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET.application.dll") +set(re2_viacoredll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET.viacore.dll") # Initialize a variable to keep track of the existence of all files -set(REFRAMEWORK_REF_ASSEMBLIES_EXIST TRUE) +set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2 TRUE) +set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2 TRUE) # Check if each DLL exists -foreach(dll IN ITEMS ${dll1} ${dll2} ${dll3}) +foreach(dll IN ITEMS ${dd2_systemdll} ${dd2_applicationdll} ${dd2_viacoredll}) if(NOT EXISTS ${dll}) - set(REFRAMEWORK_REF_ASSEMBLIES_EXIST FALSE) + set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2 FALSE) break() # Exit the loop as soon as one file is not found endif() endforeach() +foreach(dll IN ITEMS ${re2_mscorlibdll} ${re2_systemdll} ${re2_systemcoredll} ${re2_applicationdll} ${re2_viacoredll}) + if(NOT EXISTS ${dll}) + set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2 FALSE) + break() # Exit the loop as soon as one file is not found + endif() +endforeach() # Use the result -if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) - message(STATUS "All specified DLLs exist.") +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2) + message(STATUS "All specified DLLs exist (DD2)") +else() + message(STATUS "One or more specified DLLs do not exist (DD2)") +endif() + +if (REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) + message(STATUS "All specified DLLs exist (RE2)") else() - message(STATUS "One or more specified DLLs do not exist.") + message(STATUS "One or more specified DLLs do not exist (RE2)") endif() # Define a list of NuGet packages and their versions @@ -308,12 +327,9 @@ VS_DOTNET_REFERENCE_REFramework.NET ) # Target: CSharpAPITest -if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) # build-csharp-test +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2) # build-csharp-test-dd2 set(CSharpAPITest_SOURCES - "test/Test/ObjectExplorer.cs" "test/Test/Test.cs" - "test/Test/Test2.cs" - "test/Test/TestRE2.cs" cmake.toml ) @@ -349,23 +365,96 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) # build-csharp-test set_target_properties(CSharpAPITest PROPERTIES VS_DOTNET_REFERENCE_REFramework.NET._System - "${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET._System.dll" + "${dd2_systemdll}" ) set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") set_target_properties(CSharpAPITest PROPERTIES VS_DOTNET_REFERENCE_REFramework.NET.viacore - "${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.viacore.dll" + "${dd2_viacoredll}" ) set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") set_target_properties(CSharpAPITest PROPERTIES VS_DOTNET_REFERENCE_REFramework.NET.application - "${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.application.dll" + "${dd2_applicationdll}" ) set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") endif() +# Target: CSharpAPITestRE2 +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) # build-csharp-test-re2 + set(CSharpAPITestRE2_SOURCES + "test/Test/TestRE2.cs" + cmake.toml + ) + + add_library(CSharpAPITestRE2 SHARED) + + target_sources(CSharpAPITestRE2 PRIVATE ${CSharpAPITestRE2_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITestRE2_SOURCES}) + + target_link_libraries(CSharpAPITestRE2 PUBLIC + csharp-api + ) + + set_target_properties(CSharpAPITestRE2 PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary + ) + + set(CMKR_TARGET CSharpAPITestRE2) + set_target_properties(CSharpAPITestRE2 PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET + "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" + ) + +set_target_properties(CSharpAPITestRE2 PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET._mscorlib + "${re2_mscorlibdll}" + ) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib") + +set_target_properties(CSharpAPITestRE2 PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET._System + "${re2_systemdll}" + ) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") + +set_target_properties(CSharpAPITestRE2 PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET.System.Core + "${re2_systemcoredll}" + ) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core") + +set_target_properties(CSharpAPITestRE2 PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET.viacore + "${re2_viacoredll}" + ) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") + +set_target_properties(CSharpAPITestRE2 PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET.application + "${re2_applicationdll}" + ) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") + +endif() diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 2c3c1c7df..8235f1067 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -45,27 +45,46 @@ if(NOT NUGET_PACKAGES_DIR) endif() # Set a bool if the generated reference assemblies exist in the build/bin folder -set(dll1 "${CMAKE_BINARY_DIR}/bin/REFramework.NET._System.dll") -set(dll2 "${CMAKE_BINARY_DIR}/bin/REFramework.NET.application.dll") -set(dll3 "${CMAKE_BINARY_DIR}/bin/REFramework.NET.viacore.dll") +set(dd2_systemdll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET._System.dll") +set(dd2_applicationdll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET.application.dll") +set(dd2_viacoredll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET.viacore.dll") + +set(re2_mscorlibdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._mscorlib.dll") +set(re2_systemdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._System.dll") +set(re2_systemcoredll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._System.Core.dll") +set(re2_applicationdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET.application.dll") +set(re2_viacoredll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET.viacore.dll") # Initialize a variable to keep track of the existence of all files -set(REFRAMEWORK_REF_ASSEMBLIES_EXIST TRUE) +set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2 TRUE) +set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2 TRUE) # Check if each DLL exists -foreach(dll IN ITEMS ${dll1} ${dll2} ${dll3}) +foreach(dll IN ITEMS ${dd2_systemdll} ${dd2_applicationdll} ${dd2_viacoredll}) if(NOT EXISTS ${dll}) - set(REFRAMEWORK_REF_ASSEMBLIES_EXIST FALSE) + set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2 FALSE) break() # Exit the loop as soon as one file is not found endif() endforeach() +foreach(dll IN ITEMS ${re2_mscorlibdll} ${re2_systemdll} ${re2_systemcoredll} ${re2_applicationdll} ${re2_viacoredll}) + if(NOT EXISTS ${dll}) + set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2 FALSE) + break() # Exit the loop as soon as one file is not found + endif() +endforeach() # Use the result -if(REFRAMEWORK_REF_ASSEMBLIES_EXIST) - message(STATUS "All specified DLLs exist.") +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2) + message(STATUS "All specified DLLs exist (DD2)") else() - message(STATUS "One or more specified DLLs do not exist.") + message(STATUS "One or more specified DLLs do not exist (DD2)") +endif() + +if (REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) + message(STATUS "All specified DLLs exist (RE2)") +else() + message(STATUS "One or more specified DLLs do not exist (RE2)") endif() # Define a list of NuGet packages and their versions @@ -90,7 +109,8 @@ endforeach() """ [conditions] -build-csharp-test = "REFRAMEWORK_REF_ASSEMBLIES_EXIST" +build-csharp-test-dd2 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2" +build-csharp-test-re2 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2" [template.CSharpSharedTarget] type = "shared" @@ -185,9 +205,9 @@ VS_DOTNET_REFERENCE_REFramework.NET """ [target.CSharpAPITest] -condition = "build-csharp-test" +condition = "build-csharp-test-dd2" type = "CSharpSharedTarget" -sources = ["test/Test/**.cs"] +sources = ["test/Test/Test.cs"] link-libraries = [ "csharp-api" ] @@ -200,23 +220,74 @@ VS_DOTNET_REFERENCE_REFramework.NET set_target_properties(CSharpAPITest PROPERTIES VS_DOTNET_REFERENCE_REFramework.NET._System -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET._System.dll" +"${dd2_systemdll}" ) set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") set_target_properties(CSharpAPITest PROPERTIES VS_DOTNET_REFERENCE_REFramework.NET.viacore -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.viacore.dll" +"${dd2_viacoredll}" ) set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") set_target_properties(CSharpAPITest PROPERTIES VS_DOTNET_REFERENCE_REFramework.NET.application -"${REFRAMEWORK_DOT_NET_ASSEMBLY_DIR}/REFramework.NET.application.dll" +"${dd2_applicationdll}" ) set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") +""" + +[target.CSharpAPITestRE2] +condition = "build-csharp-test-re2" +type = "CSharpSharedTarget" +sources = ["test/Test/TestRE2.cs"] +link-libraries = [ + "csharp-api" +] + +cmake-after = """ +set_target_properties(CSharpAPITestRE2 PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET +"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" +) + +set_target_properties(CSharpAPITestRE2 PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET._mscorlib +"${re2_mscorlibdll}" +) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib") + +set_target_properties(CSharpAPITestRE2 PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET._System +"${re2_systemdll}" +) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") + +set_target_properties(CSharpAPITestRE2 PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.System.Core +"${re2_systemcoredll}" +) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core") + +set_target_properties(CSharpAPITestRE2 PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.viacore +"${re2_viacoredll}" +) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") + +set_target_properties(CSharpAPITestRE2 PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.application +"${re2_applicationdll}" +) + +set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") + """ \ No newline at end of file diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index 284f0dcf2..ec220873f 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -7,13 +7,12 @@ using REFrameworkNET; using REFrameworkNET.Callbacks; -public class TestRE2 { +public class TestRE2Plugin { + static bool IsRunningRE2 => Environment.ProcessPath.Contains("re2", StringComparison.CurrentCultureIgnoreCase); + [REFrameworkNET.Attributes.PluginEntryPoint] public static void Main() { - // Get executable name - string executableName = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName; - - if (executableName.ToLower().Contains("re2")) { + if (IsRunningRE2) { Console.WriteLine("Running in RE2"); } else { Console.WriteLine("Not running in RE2"); @@ -25,10 +24,29 @@ public static void Main() { ImGui.Text("Hello, world!"); ImGui.End(); } + + if (ImGui.Begin("Test Window")) { + ImGui.Text("RE2"); + ImGui.Separator(); + + if (ImGui.TreeNode("Player")) { + var playerManager = API.GetManagedSingletonT(); + var player = playerManager.get_CurrentPlayer(); + if (player != null) { + ImGui.Text("Player is not null"); + } else { + ImGui.Text("Player is null"); + } + ImGui.TreePop(); + } + + ImGui.End(); + } }; } [REFrameworkNET.Attributes.PluginExitPoint] public static void Unload() { } -} \ No newline at end of file +} + From 17efc67d7f148c287f156e40185e9b944e911d92 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 11:06:15 -0700 Subject: [PATCH 146/207] .NET: Make MethodHook thread safe --- csharp-api/CMakeLists.txt | 30 +++++++++++++++++ csharp-api/REFrameworkNET/MethodHook.hpp | 42 +++++++++++++++++------- csharp-api/cmake.toml | 6 ++++ 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index a19dba4e6..20528879a 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -151,8 +151,14 @@ set_target_properties(REFCoreDeps PROPERTIES "${CMAKE_BINARY_DIR}/bin/" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib" DOTNET_SDK Microsoft.NET.Sdk DOTNET_TARGET_FRAMEWORK @@ -246,8 +252,14 @@ set_target_properties(csharp-api PROPERTIES "${CMAKE_BINARY_DIR}/bin/" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin/" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib/" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib/" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib/" COMMON_LANGUAGE_RUNTIME netcore DOTNET_TARGET_FRAMEWORK @@ -310,8 +322,14 @@ set_target_properties(AssemblyGenerator PROPERTIES "${CMAKE_BINARY_DIR}/bin/" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib" DOTNET_SDK Microsoft.NET.Sdk DOTNET_TARGET_FRAMEWORK @@ -347,8 +365,14 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2) # build-csharp-test-dd2 "${CMAKE_BINARY_DIR}/bin/" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib" DOTNET_SDK Microsoft.NET.Sdk DOTNET_TARGET_FRAMEWORK @@ -406,8 +430,14 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) # build-csharp-test-re2 "${CMAKE_BINARY_DIR}/bin/" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin" LIBRARY_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib" DOTNET_SDK Microsoft.NET.Sdk DOTNET_TARGET_FRAMEWORK diff --git a/csharp-api/REFrameworkNET/MethodHook.hpp b/csharp-api/REFrameworkNET/MethodHook.hpp index 08bb77eed..654503070 100644 --- a/csharp-api/REFrameworkNET/MethodHook.hpp +++ b/csharp-api/REFrameworkNET/MethodHook.hpp @@ -20,16 +20,28 @@ public ref class MethodHook // Public factory method to create a new hook static MethodHook^ Create(Method^ method, bool ignore_jmp) { - if (s_hooked_methods->ContainsKey(method)) { - MethodHook^ out = nullptr; - s_hooked_methods->TryGetValue(method, out); + MethodHook^ wrapper; + { + s_hooked_methods_lock->EnterReadLock(); - return out; + try { + if (s_hooked_methods->TryGetValue(method, wrapper)) { + return wrapper; + } + } finally { + s_hooked_methods_lock->ExitReadLock(); + } } - auto wrapper = gcnew MethodHook(method, ignore_jmp); - s_hooked_methods->Add(method, wrapper); - return wrapper; + s_hooked_methods_lock->EnterWriteLock(); + + try { + wrapper = gcnew MethodHook(method, ignore_jmp); + s_hooked_methods->Add(method, wrapper); + return wrapper; + } finally { + s_hooked_methods_lock->ExitWriteLock(); + } } MethodHook^ AddPre(PreHookDelegate^ callback) @@ -45,12 +57,17 @@ public ref class MethodHook } static System::Collections::Generic::List^ GetAllHooks() { - auto out = gcnew System::Collections::Generic::List(); - for each (auto kvp in s_hooked_methods) { - out->Add(kvp.Value); - } + s_hooked_methods_lock->EnterReadLock(); + try { + auto out = gcnew System::Collections::Generic::List(); + for each (auto kvp in s_hooked_methods) { + out->Add(kvp.Value); + } - return out; + return out; + } finally { + s_hooked_methods_lock->ExitReadLock(); + } } internal: @@ -128,6 +145,7 @@ public ref class MethodHook } static System::Collections::Generic::Dictionary^ s_hooked_methods = gcnew System::Collections::Generic::Dictionary(); + static System::Threading::ReaderWriterLockSlim^ s_hooked_methods_lock = gcnew System::Threading::ReaderWriterLockSlim(); 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); diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 8235f1067..642ac2527 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -118,7 +118,10 @@ type = "shared" [template.CSharpSharedTarget.properties] RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin" +RUNTIME_OUTPUT_DIRECTORY_DEBUG = "${CMAKE_BINARY_DIR}/bin" LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib" +LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/lib" +LIBRARY_OUTPUT_DIRECTORY_DEBUG = "${CMAKE_BINARY_DIR}/lib" DOTNET_SDK = "Microsoft.NET.Sdk" DOTNET_TARGET_FRAMEWORK = "net8.0-windows" VS_CONFIGURATION_TYPE = "ClassLibrary" @@ -180,7 +183,10 @@ set_target_properties(csharp-api PROPERTIES VS_PACKAGE_REFERENCES "REFCoreDeps") OUTPUT_NAME = "REFramework.NET" RUNTIME_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/bin/" RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/bin/" +RUNTIME_OUTPUT_DIRECTORY_DEBUG = "${CMAKE_BINARY_DIR}/bin/" LIBRARY_OUTPUT_DIRECTORY_RELEASE = "${CMAKE_BINARY_DIR}/lib/" +LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO = "${CMAKE_BINARY_DIR}/lib/" +LIBRARY_OUTPUT_DIRECTORY_DEBUG = "${CMAKE_BINARY_DIR}/lib/" COMMON_LANGUAGE_RUNTIME = "netcore" DOTNET_TARGET_FRAMEWORK = "net8.0-windows" # DOTNET_TARGET_FRAMEWORK_VERSION = "net8.0" From c7fd9f150c9397b2e9deb683c90ebd9b847a9733 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 12:14:04 -0700 Subject: [PATCH 147/207] .NET: Benchmark stuff for RE2 --- csharp-api/test/Test/TestRE2.cs | 119 ++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 5 deletions(-) diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index ec220873f..347cd1f27 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -19,16 +19,18 @@ public static void Main() { return; } + RE2HookBenchmark.InstallHook(); + ImGuiRender.Pre += () => { - if (ImGui.Begin("TestRE2")) { - ImGui.Text("Hello, world!"); - ImGui.End(); - } - if (ImGui.Begin("Test Window")) { ImGui.Text("RE2"); ImGui.Separator(); + ImGui.Text("Benchmark average: " + RE2HookBenchmark.RunningAvg.ToString("0.000") + " µs"); + ImGui.Text("Benchmark count: " + RE2HookBenchmark.MeasureCount); + + ImGui.PlotLines("Benchmark", ref RE2HookBenchmark.BenchmarkData[0], 1000, 0, "µs", 0, (float)RE2HookBenchmark.HighestMicros, new System.Numerics.Vector2(0, 80)); + if (ImGui.TreeNode("Player")) { var playerManager = API.GetManagedSingletonT(); var player = playerManager.get_CurrentPlayer(); @@ -43,10 +45,117 @@ public static void Main() { ImGui.End(); } }; + + // Benchmarking the effects of threading on invoking game code + for (int i = 0; i < 8; ++i) { + threads.Add(new System.Threading.Thread(() => { + while (!cts.Token.IsCancellationRequested) { + RE2HookBenchmark.Bench(BenchFnAction); + // We must manually call the GC in our own threads not owned by the game + API.LocalFrameGC(); + } + })); + } + + foreach (var thread in threads) { + thread.Start(); + } } [REFrameworkNET.Attributes.PluginExitPoint] public static void Unload() { + cts.Cancel(); + foreach (var thread in threads) { + thread.Join(); + } } + + public static void BenchFn() { + var playerManager = API.GetManagedSingletonT(); + var player = playerManager.get_CurrentPlayer(); + if (player != null) { + var playerController = player.getComponent(app.Collision.HitController.REFType.GetRuntimeType().As<_System.Type>()); + if (playerController != null) { + for (int i = 0; i < 10000; ++i) { + get_GameObjectFn.Invoke(playerController, null); + } + } + } + } + + public static System.Action BenchFnAction = BenchFn; + + static Method get_GameObjectFn = via.Component.REFType.GetMethod("get_GameObject"); + + static List threads = new(); + + static System.Threading.CancellationTokenSource cts = new(); } +public class RE2HookBenchmark { + public static int MeasureCount { get; private set; } + static int callCount = 0; + static double totalMicros = 0.0; + public static double RunningAvg { get; private set; } + + public static double HighestMicros { get; private set; } = 0.0; + public static float[] BenchmarkData { get; private set; } = new float[1000]; + + static System.Threading.ReaderWriterLockSlim rwl = new(); + + public static void Bench(System.Action action) { + var sw = _System.Diagnostics.Stopwatch.REFType.CreateInstance(0).As<_System.Diagnostics.Stopwatch>(); + //var sw2 = new System.Diagnostics.Stopwatch(); + sw.Start(); + + action(); + + sw.Stop(); + var elapsedTicks = (double)sw.get_ElapsedTicks(); + + rwl.EnterWriteLock(); + + callCount++; + + if (callCount >= 5) { + var elapsedMicros = elapsedTicks / (double)TimeSpan.TicksPerMicrosecond; + + totalMicros += elapsedMicros; + MeasureCount++; + RunningAvg = totalMicros / MeasureCount; + BenchmarkData[callCount % 1000] = (float)elapsedMicros; + + if (elapsedMicros > HighestMicros) { + HighestMicros = elapsedMicros; + } + } + + if (MeasureCount >= 1000) { + MeasureCount = 0; + totalMicros = 0.0; + } + + rwl.ExitWriteLock(); + } + + static PreHookResult Pre(System.Span args) { + var hitController = ManagedObject.ToManagedObject(args[1]).As(); + + /*Bench(() => { + hitController.get_GameObject(); + });*/ + + return PreHookResult.Continue; + } + + static void Post(ref ulong retval) { + } + + public static void InstallHook() { + app.Collision.HitController.REFType + .GetMethod("update") + .AddHook(false) + .AddPre(Pre) + .AddPost(Post); + } +} \ No newline at end of file From ec355ebf1bc39f4cafeaac72f8657e7a0f91b81d Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 12:15:10 -0700 Subject: [PATCH 148/207] .NET: Singleton sorting --- csharp-api/test/Test/ObjectExplorer.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/csharp-api/test/Test/ObjectExplorer.cs b/csharp-api/test/Test/ObjectExplorer.cs index 84c5fa41c..89253e8fb 100644 --- a/csharp-api/test/Test/ObjectExplorer.cs +++ b/csharp-api/test/Test/ObjectExplorer.cs @@ -430,6 +430,11 @@ public static void RenderNativeSingletons() { public static void RenderManagedSingletons() { var singletons = REFrameworkNET.API.GetManagedSingletons(); + // Sort singletons by type name + // Remove any singletons that are null first + singletons.RemoveAll((s) => s.Instance == null); + singletons.Sort((a, b) => a.Instance.GetTypeDefinition().GetFullName().CompareTo(b.Instance.GetTypeDefinition().GetFullName())); + foreach (var singletonDesc in singletons) { var singleton = singletonDesc.Instance; if (singleton == null) { From fa8693a3bd8a3b5bd49e17d7f19aecda44e5bca6 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 21 Apr 2024 12:39:38 -0700 Subject: [PATCH 149/207] .NET: Reduce heap allocation on Method::Invoke calls --- csharp-api/REFrameworkNET/Method.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 58700d35b..60223cf4a 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -140,11 +140,10 @@ ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, array args2{}; + //std::vector args2{}; + std::array args2{}; // what function has more than 32 arguments? if (args != nullptr && args->Length > 0) { - args2.resize(args->Length); - for (int i = 0; i < args->Length; ++i) try { if (args[i] == nullptr) { args2[i] = nullptr; @@ -250,7 +249,9 @@ ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, arrayMessage); } - return m_method->invoke((reframework::API::ManagedObject*)obj_ptr, args2); + const auto argcount = args != nullptr ? args->Length : 0; + + return m_method->invoke((::reframework::API::ManagedObject*)obj_ptr, std::span(args2.data(), argcount)); } bool Method::HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result) { From 828d65491c9aff71b3aa786198ff3c34eb1ff751 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 22 Apr 2024 04:05:18 -0700 Subject: [PATCH 150/207] .NET: Increase concurrent dictionary readers --- csharp-api/REFrameworkNET/Attributes/Method.hpp | 3 ++- csharp-api/REFrameworkNET/ManagedObject.hpp | 3 ++- csharp-api/REFrameworkNET/NativePool.hpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/csharp-api/REFrameworkNET/Attributes/Method.hpp b/csharp-api/REFrameworkNET/Attributes/Method.hpp index aff52a92d..f6f4ee04d 100644 --- a/csharp-api/REFrameworkNET/Attributes/Method.hpp +++ b/csharp-api/REFrameworkNET/Attributes/Method.hpp @@ -7,7 +7,8 @@ namespace REFrameworkNET::Attributes { [System::AttributeUsage(System::AttributeTargets::Method)] public ref class Method : public System::Attribute { private: - static System::Collections::Concurrent::ConcurrentDictionary^ cache = gcnew System::Collections::Concurrent::ConcurrentDictionary(8, 8192); + static System::Collections::Concurrent::ConcurrentDictionary^ cache + = gcnew System::Collections::Concurrent::ConcurrentDictionary(System::Environment::ProcessorCount * 2, 8192); public: static Method^ GetCachedAttribute(System::Reflection::MethodInfo^ target) { diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 1c4ba659e..eae288f06 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -132,7 +132,8 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject !Impl() { } - System::Collections::Concurrent::ConcurrentDictionary^ cache = gcnew System::Collections::Concurrent::ConcurrentDictionary(8, 8192); + System::Collections::Concurrent::ConcurrentDictionary^ cache + = gcnew System::Collections::Concurrent::ConcurrentDictionary(System::Environment::ProcessorCount * 2, 8192); System::Collections::Concurrent::ConcurrentQueue^ pool = gcnew System::Collections::Concurrent::ConcurrentQueue(); }; diff --git a/csharp-api/REFrameworkNET/NativePool.hpp b/csharp-api/REFrameworkNET/NativePool.hpp index 0236e8c42..ddf917430 100644 --- a/csharp-api/REFrameworkNET/NativePool.hpp +++ b/csharp-api/REFrameworkNET/NativePool.hpp @@ -12,7 +12,7 @@ namespace REFrameworkNET { { private: static System::Collections::Concurrent::ConcurrentDictionary^ s_cache = - gcnew System::Collections::Concurrent::ConcurrentDictionary(8, 8192); + gcnew System::Collections::Concurrent::ConcurrentDictionary(System::Environment::ProcessorCount * 2, 8192); public: delegate T^ CreatorDelegate(uintptr_t nativePtr); From cda00ff364a6c2ccae882f483232b836d45cdfc9 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 22 Apr 2024 08:58:55 -0700 Subject: [PATCH 151/207] .NET: More detailed bench stats for RE2 --- csharp-api/test/Test/TestRE2.cs | 119 ++++++++++++++++++++++++++++---- 1 file changed, 104 insertions(+), 15 deletions(-) diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index 347cd1f27..692fd48ec 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -3,6 +3,8 @@ using System.Collections.Generic; using System.Dynamic; using System.Reflection; +using System.Threading; +using app.ropeway; using ImGuiNET; using REFrameworkNET; using REFrameworkNET.Callbacks; @@ -26,11 +28,36 @@ public static void Main() { ImGui.Text("RE2"); ImGui.Separator(); - ImGui.Text("Benchmark average: " + RE2HookBenchmark.RunningAvg.ToString("0.000") + " µs"); - ImGui.Text("Benchmark count: " + RE2HookBenchmark.MeasureCount); + System.Collections.Generic.List threadRanks = new(); - ImGui.PlotLines("Benchmark", ref RE2HookBenchmark.BenchmarkData[0], 1000, 0, "µs", 0, (float)RE2HookBenchmark.HighestMicros, new System.Numerics.Vector2(0, 80)); + foreach(var tdata in RE2HookBenchmark.threadData) { + threadRanks.Add(tdata.Value.threadID); + } + + // Sort by highest running average + threadRanks.Sort((a, b) => { + var aData = RE2HookBenchmark.threadData[a]; + var bData = RE2HookBenchmark.threadData[b]; + return aData.runningAvg.CompareTo(bData.runningAvg); + }); + + var totalThreadRanks = threadRanks.Count; + + foreach(var tdata in RE2HookBenchmark.threadData) { + var rank = threadRanks.IndexOf(tdata.Value.threadID) + 1; + var greenColor = 1.0f - (float)rank / (float)totalThreadRanks; + var redColor = (float)rank / (float)totalThreadRanks; + //ImGui.Text("Thread ID: " + tdata.Value.threadID + " Avg: " + tdata.Value.runningAvg.ToString("0.000") + " µs"); + ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(redColor, greenColor, 0f, 1.0f)); + ImGui.PlotLines("Thread " + tdata.Value.threadID, ref tdata.Value.benchmarkData[0], 1000, tdata.Value.callCount % 1000, tdata.Value.runningAvg.ToString("0.000") + " µs", 0, (float)tdata.Value.runningAvg * 2.0f, new System.Numerics.Vector2(0, 30)); + ImGui.PopStyleColor(); + } + + ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(0.5f, 1f, 0.4f, 1.0f)); + ImGui.PlotLines("Overall Benchmark", ref RE2HookBenchmark.BenchmarkData[0], 1000, RE2HookBenchmark.MeasureCount % 1000, RE2HookBenchmark.RunningAvg.ToString("0.000") + " µs", 0, (float)RE2HookBenchmark.RunningAvg * 2.0f, new System.Numerics.Vector2(0, 40)); + ImGui.PopStyleColor(); + if (ImGui.TreeNode("Player")) { var playerManager = API.GetManagedSingletonT(); var player = playerManager.get_CurrentPlayer(); @@ -47,13 +74,15 @@ public static void Main() { }; // Benchmarking the effects of threading on invoking game code - for (int i = 0; i < 8; ++i) { + for (int i = 0; i < 2; ++i) { threads.Add(new System.Threading.Thread(() => { - while (!cts.Token.IsCancellationRequested) { + /*while (!cts.Token.IsCancellationRequested) { RE2HookBenchmark.Bench(BenchFnAction); // We must manually call the GC in our own threads not owned by the game API.LocalFrameGC(); - } + }*/ + + API.LocalFrameGC(); })); } @@ -70,15 +99,36 @@ public static void Unload() { } } + static System.Threading.ReaderWriterLockSlim rwl = new(); + static System.Collections.Concurrent.ConcurrentDictionary test = new(Environment.ProcessorCount * 2, 8192); public static void BenchFn() { var playerManager = API.GetManagedSingletonT(); var player = playerManager.get_CurrentPlayer(); if (player != null) { - var playerController = player.getComponent(app.Collision.HitController.REFType.GetRuntimeType().As<_System.Type>()); - if (playerController != null) { - for (int i = 0; i < 10000; ++i) { - get_GameObjectFn.Invoke(playerController, null); + via.Component playerControllerRaw = player.getComponent(app.Collision.HitController.REFType.GetRuntimeType().As<_System.Type>()); + if (playerControllerRaw != null) { + var playerController = (playerControllerRaw as IObject).As(); + for (int i = 0; i < 1; ++i) { + //rwl.EnterReadLock(); + //rwl.ExitReadLock(); + //playerController. + //playerController.get_DeltaTime(); + var gameobj = playerController.get_GameObject(); + + if (gameobj != null) { + /*var backToPlayerController = gameobj.getComponent(app.Collision.HitController.REFType.GetRuntimeType().As<_System.Type>()); + + if (backToPlayerController != null) { + //System.Console.WriteLine("Back to player controller!!!"); + }*/ + } + //get_GameObjectFn.Invoke(playerController, null); + //object result = null; + //get_GameObjectFn.HandleInvokeMember_Internal(playerController, null, ref result); } + + var refCount = ((playerController.get_GameObject() as IProxy).GetInstance() as ManagedObject).GetReferenceCount(); + System.Console.WriteLine("PlayerController ref count: " + refCount); } } } @@ -103,7 +153,27 @@ public class RE2HookBenchmark { static System.Threading.ReaderWriterLockSlim rwl = new(); + internal class ThreadData { + internal long threadID; + internal double totalMicros; + internal int callCount; + internal double highestMicros; + internal double runningAvg; + internal float[] benchmarkData = new float[1000]; + }; + + internal static System.Collections.Concurrent.ConcurrentDictionary threadData = new(Environment.ProcessorCount * 2, 8192); + public static void Bench(System.Action action) { + var threadID = System.Threading.Thread.CurrentThread.ManagedThreadId; + ThreadData? data = null; + if (!threadData.ContainsKey(threadID)) { + data = new ThreadData() { threadID = threadID }; + threadData.TryAdd(threadID, data); + } else { + threadData.TryGetValue(threadID, out data); + } + var sw = _System.Diagnostics.Stopwatch.REFType.CreateInstance(0).As<_System.Diagnostics.Stopwatch>(); //var sw2 = new System.Diagnostics.Stopwatch(); sw.Start(); @@ -112,14 +182,28 @@ public static void Bench(System.Action action) { sw.Stop(); var elapsedTicks = (double)sw.get_ElapsedTicks(); + var elapsedMicros = elapsedTicks / (double)TimeSpan.TicksPerMicrosecond; + + data.totalMicros += elapsedMicros; + data.callCount++; + + if (elapsedMicros > data.highestMicros) { + data.highestMicros = elapsedMicros; + } + + data.runningAvg = data.totalMicros / (double)data.callCount; + data.benchmarkData[data.callCount % 1000] = (float)elapsedMicros; + + if (data.callCount >= 1000) { + data.callCount = 0; + data.totalMicros = 0.0; + } rwl.EnterWriteLock(); callCount++; if (callCount >= 5) { - var elapsedMicros = elapsedTicks / (double)TimeSpan.TicksPerMicrosecond; - totalMicros += elapsedMicros; MeasureCount++; RunningAvg = totalMicros / MeasureCount; @@ -141,9 +225,14 @@ public static void Bench(System.Action action) { static PreHookResult Pre(System.Span args) { var hitController = ManagedObject.ToManagedObject(args[1]).As(); - /*Bench(() => { - hitController.get_GameObject(); - });*/ + //Bench(TestRE2Plugin.BenchFnAction); + Bench(() => { + for (int i = 0; i < 10000; ++i) { + var gameobj = hitController.get_GameObject(); + if (gameobj != null) { + } + } + }); return PreHookResult.Continue; } From c306316e8912bcf5a2782f79d7ddb766ee7b5dc0 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 22 Apr 2024 09:00:57 -0700 Subject: [PATCH 152/207] .NET: Add caching for proxy objects --- csharp-api/REFrameworkNET/IObject.hpp | 3 + csharp-api/REFrameworkNET/ManagedObject.cpp | 18 ++- csharp-api/REFrameworkNET/ManagedObject.hpp | 23 ++-- csharp-api/REFrameworkNET/NativeObject.cpp | 17 +++ csharp-api/REFrameworkNET/PluginManager.cpp | 1 + csharp-api/REFrameworkNET/Proxy.hpp | 23 ++++ csharp-api/REFrameworkNET/UnifiedObject.hpp | 119 +++++++++++++++++++- 7 files changed, 191 insertions(+), 13 deletions(-) diff --git a/csharp-api/REFrameworkNET/IObject.hpp b/csharp-api/REFrameworkNET/IObject.hpp index 45f874115..f24415cbc 100644 --- a/csharp-api/REFrameworkNET/IObject.hpp +++ b/csharp-api/REFrameworkNET/IObject.hpp @@ -6,6 +6,7 @@ namespace REFrameworkNET { ref class TypeDefinition; +interface class IProxy; value struct InvokeRet; // Base interface of ManagedObject and NativeObject @@ -19,5 +20,7 @@ public interface class IObject : public IProxyable, public System::IEquatable T As(); + + IProxy^ GetProxy(System::Type^ proxyType); }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.cpp b/csharp-api/REFrameworkNET/ManagedObject.cpp index f5722c479..565785040 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.cpp +++ b/csharp-api/REFrameworkNET/ManagedObject.cpp @@ -19,13 +19,15 @@ namespace REFrameworkNET { void ManagedObject::CleanupKnownCaches() { Cache::CleanupAll(); Cache::CleanupAll(); + UnifiedObject::ProxyPool::Clear(); } void ManagedObject::Internal_Finalize() { - if (m_object == nullptr) { + if (m_object == nullptr || !m_initialized) { return; } + UnifiedObject::ProxyPool::Remove(this); ReleaseIfGlobalized(); // Only if we are not marked as weak are we allowed to release the object, even if the object is globalized @@ -106,6 +108,18 @@ namespace REFrameworkNET { generic T ManagedObject::As() { - return ManagedProxy::Create(this); + // Don't bother caching if we are not globalized (ephemeral/local objects) + if (!IsGlobalized()) { + return ManagedProxy::Create(this); + } + + if (auto existingProxy = this->GetProxy(T::typeid)) { + return (T)existingProxy; + } + + auto result = ManagedProxy::Create(this); + + this->AddProxy(T::typeid, (IProxy^)result); + return result; } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index eae288f06..3015d6b7d 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -336,23 +336,28 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject generic virtual T As() override; - // TODO methods: - /*public Void* GetReflectionProperties() { - return _original.get_reflection_properties(); - }*/ + virtual IProxy^ GetProxy(System::Type^ type) override { + if (!IsGlobalized()) { + return nullptr; + } - /*public ReflectionProperty GetReflectionPropertyDescriptor(basic_string_view> name) { - return _original.get_reflection_property_descriptor(name); + return UnifiedObject::GetProxy(type); } - public ReflectionMethod GetReflectionMethodDescriptor(basic_string_view> name) { - return _original.get_reflection_method_descriptor(name); - }*/ +internal: + virtual void AddProxy(System::Type^ type, IProxy^ proxy) override { + if (!IsGlobalized()) { + return; + } + + UnifiedObject::AddProxy(type, proxy); + } internal: static bool ShuttingDown = false; void Deinitialize() { + m_object = nullptr; m_initialized = false; } diff --git a/csharp-api/REFrameworkNET/NativeObject.cpp b/csharp-api/REFrameworkNET/NativeObject.cpp index 6f63a9390..d4c3a3f5e 100644 --- a/csharp-api/REFrameworkNET/NativeObject.cpp +++ b/csharp-api/REFrameworkNET/NativeObject.cpp @@ -8,6 +8,23 @@ namespace REFrameworkNET { generic T NativeObject::As() { + // Cannot cache nullptr objects + /*if (this->GetAddress() == 0) { + return NativeProxy::Create(this); + } + + if (auto existingProxy = this->GetProxy(T::typeid)) { + return (T)existingProxy; + } + + auto result = NativeProxy::Create(this); + + this->AddProxy(T::typeid, (IProxy^)result); + return result;*/ + + // Not gonna bother with this for now. Just create a new proxy every time + // Reason being, this is not a managed object, so we have no idea if the address + // still points to the original object or not. It's better to just create a new one. return NativeProxy::Create(this); } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index bda7efc0d..10188c7e3 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -692,6 +692,7 @@ namespace REFrameworkNET { NativePool::DisplayStats(); NativePool::DisplayStats(); NativePool::DisplayStats(); + UnifiedObject::ProxyPool::DisplayStats(); ImGuiNET::ImGui::TreePop(); } diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 003607c92..538a2f64c 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -74,6 +74,10 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public return Instance->As(); } + virtual IProxy^ GetProxy(System::Type^ type) { + return Instance->GetProxy(type); + } + static T Create(IObject^ target) { auto proxy = Reflection::DispatchProxy::Create^>(); ((IProxy^)proxy)->SetInstance(target); @@ -130,6 +134,15 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public return !left->Equals(right); } +internal: + ~Proxy() { + this->!Proxy(); + } + + !Proxy() { + + } + protected: virtual Object^ Invoke(Reflection::MethodInfo^ targetMethod, array^ args) override { // Get the REFrameworkNET::Attributes::Method attribute from the method @@ -203,8 +216,18 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public auto iobjectResult = dynamic_cast(result); if (iobjectResult != nullptr && targetReturnType->IsInterface) { + // Caching mechanism to prevent creating multiple proxies for the same object and type so we dont stress the GC + if (auto existingProxy = iobjectResult->GetProxy(targetReturnType); existingProxy != nullptr) { + return existingProxy; + } + auto proxy = DispatchProxy::Create(targetReturnType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(T::typeid, result->GetType())); ((IProxy^)proxy)->SetInstance(iobjectResult); + + if (auto unified = dynamic_cast(iobjectResult); unified != nullptr) { + unified->AddProxy(targetReturnType, (IProxy^)proxy); + } + result = proxy; return result; } diff --git a/csharp-api/REFrameworkNET/UnifiedObject.hpp b/csharp-api/REFrameworkNET/UnifiedObject.hpp index f89ca36cb..93beb0ba2 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.hpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.hpp @@ -5,6 +5,7 @@ namespace REFrameworkNET { ref class TypeDefinition; +interface class IProxy; value struct InvokeRet; // UnifiedObject is the base class that ManagedObject and NativeObject will derive from @@ -22,8 +23,11 @@ public ref class UnifiedObject abstract : public System::Dynamic::DynamicObject, generic virtual T As() abstract = 0; - - // Shared methods + + virtual IProxy^ GetProxy(System::Type^ type) { + return ProxyPool::GetIfExists(this, type); + } + virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); virtual bool HandleInvokeMember_Internal(uint32_t methodIndex, array^ args, System::Object^% result); virtual bool HandleInvokeMember_Internal(System::Object^ methodObj, array^ args, System::Object^% result); @@ -104,5 +108,116 @@ public ref class UnifiedObject abstract : public System::Dynamic::DynamicObject, virtual System::Collections::IEnumerator^ GetEnumerator() { return gcnew REFrameworkNET::ObjectEnumerator(this); } + +internal: + using WeakProxy = System::WeakReference; + + generic + T AsCached() { + auto strongProxy = GetProxy(T::typeid); + + if (strongProxy != nullptr) { + return (T)strongProxy; + } + + T instance = As(); + //m_proxies->TryAdd(T::typeid, gcnew WeakProxy((IProxy^)instance)); + ProxyPool::Add(this, T::typeid, (IProxy^)instance); + + return instance; + } + + virtual void AddProxy(System::Type^ type, IProxy^ proxy) { + //m_proxies->TryAdd(type, gcnew WeakProxy(proxy)); + ProxyPool::Add(this, type, proxy); + } + +protected: + // It's weak because internally the proxy will hold a strong reference to the object + /*System::Collections::Concurrent::ConcurrentDictionary^ m_proxies + = gcnew System::Collections::Concurrent::ConcurrentDictionary(System::Environment::ProcessorCount * 2, 8);*/ + +internal: + // Using a proxy cache that is separate from the object itself + // because holding weak references in the object itself is asking for trouble + ref class ProxyPool { + internal: + using WeakT = System::WeakReference; + + static WeakT^ AddValueFactory(System::Type^ key, IProxy^ arg) { + return gcnew WeakT(arg); + } + + static System::Func^ addValueFactory = gcnew System::Func(AddValueFactory); + + static WeakT^ UpdateFactory(System::Type^ key, WeakT^ value, IProxy^ arg) { + return gcnew WeakT(arg); + } + + static System::Func^ updateFactory = gcnew System::Func(UpdateFactory); + + static IProxy^ GetIfExists(IObject^ obj, System::Type^ type) { + const auto addr = obj->GetAddress(); + + if (addr == 0) { + return nullptr; + } + + InternalDict^ dict = nullptr; + if (!s_cache->TryGetValue(addr, dict)) { + return nullptr; + } + + WeakT^ weak = nullptr; + if (!dict->TryGetValue(type, weak)) { + return nullptr; + } + + IProxy^ target = nullptr; + if (!weak->TryGetTarget(target)) { + return nullptr; + } + + return target; + } + + static void Add(IObject^ obj, System::Type^ type, IProxy^ proxy) { + InternalDict^ dict = nullptr; + const auto hashcode = obj->GetAddress(); + + if (hashcode == 0) { + return; + } + + if (!s_cache->TryGetValue(hashcode, dict)) { + dict = gcnew InternalDict(System::Environment::ProcessorCount * 2, 2); + s_cache->TryAdd(hashcode, dict); + } + + dict->AddOrUpdate(type, addValueFactory, updateFactory, proxy); + } + + static void Remove(IObject^ obj) { + InternalDict^ dict = nullptr; + s_cache->TryRemove(obj->GetAddress(), dict); + } + + static void Clear() { + s_cache->Clear(); + } + + static void DisplayStats() { + if (ImGuiNET::ImGui::TreeNode("ProxyPool")) { + ImGuiNET::ImGui::Text("Cache size: " + s_cache->Count.ToString()); + ImGuiNET::ImGui::TreePop(); + } + } + + private: + // Not using the actual object as the key so that we can avoid holding strong references to the objects + using InternalDict = System::Collections::Concurrent::ConcurrentDictionary; + using DictT = System::Collections::Concurrent::ConcurrentDictionary; + static DictT^ s_cache = gcnew DictT(System::Environment::ProcessorCount * 2, 8192); + }; }; } \ No newline at end of file From d50a6a7334976b9b2a97dc81d48950a542fc89e2 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 22 Apr 2024 10:06:35 -0700 Subject: [PATCH 153/207] .NET: Prettier RE2 bench --- csharp-api/test/Test/TestRE2.cs | 72 ++++++++++++++++++++++++--------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index 692fd48ec..5b0c78383 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -11,6 +11,7 @@ public class TestRE2Plugin { static bool IsRunningRE2 => Environment.ProcessPath.Contains("re2", StringComparison.CurrentCultureIgnoreCase); + static System.Diagnostics.Stopwatch imguiStopwatch = new(); [REFrameworkNET.Attributes.PluginEntryPoint] public static void Main() { @@ -24,10 +25,38 @@ public static void Main() { RE2HookBenchmark.InstallHook(); ImGuiRender.Pre += () => { + var deltaSeconds = imguiStopwatch.Elapsed.TotalMilliseconds / 1000.0; + imguiStopwatch.Restart(); + if (ImGui.Begin("Test Window")) { ImGui.Text("RE2"); ImGui.Separator(); + + if (ImGui.TreeNode("Player")) { + var playerManager = API.GetManagedSingletonT(); + var player = playerManager.get_CurrentPlayer(); + if (player != null) { + ImGui.Text("Player is not null"); + } else { + ImGui.Text("Player is null"); + } + ImGui.TreePop(); + } + + ImGui.End(); + } + + // ROund the window + ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 10.0f); + + ImGui.SetNextWindowSize(new System.Numerics.Vector2(500, 500), ImGuiCond.FirstUseEver); + if (ImGui.Begin("RE2 Bench")) { + + ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(0.5f, 1f, 0.4f, 1.0f)); + ImGui.PlotLines("Overall Benchmark", ref RE2HookBenchmark.BenchmarkData[0], 1000, RE2HookBenchmark.MeasureCount % 1000, RE2HookBenchmark.RunningAvg.ToString("0.000") + " µs", 0, (float)RE2HookBenchmark.RunningAvg * 2.0f, new System.Numerics.Vector2(0, 40)); + ImGui.PopStyleColor(); + System.Collections.Generic.List threadRanks = new(); foreach(var tdata in RE2HookBenchmark.threadData) { @@ -43,34 +72,36 @@ public static void Main() { var totalThreadRanks = threadRanks.Count; + System.Collections.Generic.List threadData = new(); + foreach(var tdata in RE2HookBenchmark.threadData) { - var rank = threadRanks.IndexOf(tdata.Value.threadID) + 1; - var greenColor = 1.0f - (float)rank / (float)totalThreadRanks; - var redColor = (float)rank / (float)totalThreadRanks; + threadData.Add(tdata.Value); + } + + threadData.Sort((a, b) => { + return a.lerp.CompareTo(b.lerp); + }); + + foreach(var tdata in threadData) { + var rank = threadRanks.IndexOf(tdata.threadID) + 1; + var rankRatio = (double)rank / (double)totalThreadRanks; + var towards = Math.Max(80.0 * rankRatio, 20.0); + tdata.lerp = towards * deltaSeconds + tdata.lerp * (1.0 - deltaSeconds); + var lerpRatio = tdata.lerp / 80.0; + + var greenColor = 1.0 - lerpRatio; + var redColor = lerpRatio; //ImGui.Text("Thread ID: " + tdata.Value.threadID + " Avg: " + tdata.Value.runningAvg.ToString("0.000") + " µs"); - ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(redColor, greenColor, 0f, 1.0f)); - ImGui.PlotLines("Thread " + tdata.Value.threadID, ref tdata.Value.benchmarkData[0], 1000, tdata.Value.callCount % 1000, tdata.Value.runningAvg.ToString("0.000") + " µs", 0, (float)tdata.Value.runningAvg * 2.0f, new System.Numerics.Vector2(0, 30)); + ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4((float)redColor, (float)greenColor, 0f, 1.0f)); + ImGui.PlotLines("Thread " + tdata.threadID, ref tdata.benchmarkData[0], 1000, tdata.callCount % 1000, tdata.runningAvg.ToString("0.000") + " µs", (float)tdata.runningAvg, (float)tdata.runningAvg * 2.0f, new System.Numerics.Vector2(0, (int)tdata.lerp)); ImGui.PopStyleColor(); } - ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(0.5f, 1f, 0.4f, 1.0f)); - ImGui.PlotLines("Overall Benchmark", ref RE2HookBenchmark.BenchmarkData[0], 1000, RE2HookBenchmark.MeasureCount % 1000, RE2HookBenchmark.RunningAvg.ToString("0.000") + " µs", 0, (float)RE2HookBenchmark.RunningAvg * 2.0f, new System.Numerics.Vector2(0, 40)); - ImGui.PopStyleColor(); - - if (ImGui.TreeNode("Player")) { - var playerManager = API.GetManagedSingletonT(); - var player = playerManager.get_CurrentPlayer(); - if (player != null) { - ImGui.Text("Player is not null"); - } else { - ImGui.Text("Player is null"); - } - ImGui.TreePop(); - } - ImGui.End(); } + + ImGui.PopStyleVar(); }; // Benchmarking the effects of threading on invoking game code @@ -160,6 +191,7 @@ internal class ThreadData { internal double highestMicros; internal double runningAvg; internal float[] benchmarkData = new float[1000]; + internal double lerp = 40.0; }; internal static System.Collections.Concurrent.ConcurrentDictionary threadData = new(Environment.ProcessorCount * 2, 8192); From 58abc29353b272e42d51ceb07a08c4f7f8aa518d Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 24 Apr 2024 16:17:09 -0700 Subject: [PATCH 154/207] .NET: Add support for hooking via attributes --- csharp-api/CMakeLists.txt | 2 + .../REFrameworkNET/Attributes/MethodHook.cpp | 1 + .../REFrameworkNET/Attributes/MethodHook.hpp | 91 +++++++++++++++++++ csharp-api/REFrameworkNET/MethodHook.hpp | 5 + csharp-api/REFrameworkNET/PluginManager.cpp | 34 ++++++- csharp-api/test/Test/TestRE2.cs | 9 +- 6 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 csharp-api/REFrameworkNET/Attributes/MethodHook.cpp create mode 100644 csharp-api/REFrameworkNET/Attributes/MethodHook.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 20528879a..0ef68404c 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -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" @@ -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" diff --git a/csharp-api/REFrameworkNET/Attributes/MethodHook.cpp b/csharp-api/REFrameworkNET/Attributes/MethodHook.cpp new file mode 100644 index 000000000..8907f354d --- /dev/null +++ b/csharp-api/REFrameworkNET/Attributes/MethodHook.cpp @@ -0,0 +1 @@ +#include "MethodHook.hpp" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Attributes/MethodHook.hpp b/csharp-api/REFrameworkNET/Attributes/MethodHook.hpp new file mode 100644 index 000000000..a5e5075a0 --- /dev/null +++ b/csharp-api/REFrameworkNET/Attributes/MethodHook.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include + +#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 { + /// Attribute to mark a method as a hook. + [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 }; + }; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/MethodHook.hpp b/csharp-api/REFrameworkNET/MethodHook.hpp index 654503070..345eb5197 100644 --- a/csharp-api/REFrameworkNET/MethodHook.hpp +++ b/csharp-api/REFrameworkNET/MethodHook.hpp @@ -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; diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 10188c7e3..fbfc996a8 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -2,6 +2,7 @@ #include #include "Attributes/Plugin.hpp" +#include "Attributes/MethodHook.hpp" #include "MethodHook.hpp" #include "SystemString.hpp" #include "NativePool.hpp" @@ -282,12 +283,43 @@ namespace REFrameworkNET { array^ 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^ 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"); } } } diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index 5b0c78383..9253f1b60 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -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 args) { var hitController = ManagedObject.ToManagedObject(args[1]).As(); @@ -269,14 +270,16 @@ static PreHookResult Pre(System.Span 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);*/ } } \ No newline at end of file From ee07c6a78c1158ce17163d0d6f3dc45408b54a52 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Apr 2024 03:44:17 -0700 Subject: [PATCH 155/207] .NET: Add support for adding callbacks via attributes --- csharp-api/CMakeLists.txt | 74 +++++++++ .../REFrameworkNET/Attributes/Callback.cpp | 1 + .../REFrameworkNET/Attributes/Callback.hpp | 95 +++++++++++ .../REFrameworkNET/Attributes/Method.hpp | 16 +- .../REFrameworkNET/Attributes/MethodHook.hpp | 4 +- csharp-api/REFrameworkNET/Callbacks.hpp | 7 +- csharp-api/REFrameworkNET/ManagedObject.hpp | 13 ++ csharp-api/REFrameworkNET/PluginManager.cpp | 24 +++ csharp-api/REFrameworkNET/Proxy.hpp | 2 +- csharp-api/cmake.toml | 45 ++++++ csharp-api/test/Test/ObjectExplorer.cs | 4 +- csharp-api/test/Test/Test.cs | 112 ++++++------- csharp-api/test/Test/Test2.cs | 17 +- csharp-api/test/Test/TestRE2.cs | 153 +++++++++--------- 14 files changed, 407 insertions(+), 160 deletions(-) create mode 100644 csharp-api/REFrameworkNET/Attributes/Callback.cpp create mode 100644 csharp-api/REFrameworkNET/Attributes/Callback.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 0ef68404c..0648a8d36 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -174,6 +174,7 @@ set_target_properties(REFCoreDeps PROPERTIES VS_PACKAGE_REFERENCES "${REFRAMEWOR set(csharp-api_SOURCES "REFrameworkNET/API.cpp" "REFrameworkNET/AssemblyInfo.cpp" + "REFrameworkNET/Attributes/Callback.cpp" "REFrameworkNET/Attributes/Method.cpp" "REFrameworkNET/Attributes/MethodHook.cpp" "REFrameworkNET/Attributes/Plugin.cpp" @@ -192,6 +193,7 @@ set(csharp-api_SOURCES "REFrameworkNET/UnifiedObject.cpp" "REFrameworkNET/VM.cpp" "REFrameworkNET/API.hpp" + "REFrameworkNET/Attributes/Callback.hpp" "REFrameworkNET/Attributes/Method.hpp" "REFrameworkNET/Attributes/MethodHook.hpp" "REFrameworkNET/Attributes/Plugin.hpp" @@ -490,3 +492,75 @@ set_target_properties(CSharpAPITestRE2 PROPERTIES set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") endif() +# Target: ObjectExplorer +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) # build-csharp-test-re2 + set(ObjectExplorer_SOURCES + "test/Test/ObjectExplorer.cs" + cmake.toml + ) + + add_library(ObjectExplorer SHARED) + + target_sources(ObjectExplorer PRIVATE ${ObjectExplorer_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${ObjectExplorer_SOURCES}) + + target_link_libraries(ObjectExplorer PUBLIC + csharp-api + ) + + set_target_properties(ObjectExplorer PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary + ) + + set(CMKR_TARGET ObjectExplorer) + set_target_properties(ObjectExplorer PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET + "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" + ) + +set_target_properties(ObjectExplorer PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET._mscorlib + "${re2_mscorlibdll}" + ) + +set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib") + +set_target_properties(ObjectExplorer PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET._System + "${re2_systemdll}" + ) + +set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") + +set_target_properties(ObjectExplorer PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET.System.Core + "${re2_systemcoredll}" + ) + +set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core") + +set_target_properties(ObjectExplorer PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET.viacore + "${re2_viacoredll}" + ) + +set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") + +endif() diff --git a/csharp-api/REFrameworkNET/Attributes/Callback.cpp b/csharp-api/REFrameworkNET/Attributes/Callback.cpp new file mode 100644 index 000000000..2ee0df53b --- /dev/null +++ b/csharp-api/REFrameworkNET/Attributes/Callback.cpp @@ -0,0 +1 @@ +#include "Callback.hpp" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Attributes/Callback.hpp b/csharp-api/REFrameworkNET/Attributes/Callback.hpp new file mode 100644 index 000000000..bcb63e318 --- /dev/null +++ b/csharp-api/REFrameworkNET/Attributes/Callback.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include + +#include "../Callbacks.hpp" + +namespace REFrameworkNET { + public enum class CallbackType : uint8_t { + Pre, + Post + }; +} + +namespace REFrameworkNET::Attributes { + [System::AttributeUsage(System::AttributeTargets::Method)] + public ref class CallbackAttribute : public System::Attribute { + public: + CallbackAttribute(System::Type^ declaringType, CallbackType type) { + if (declaringType == nullptr) { + throw gcnew System::ArgumentNullException("declaringType"); + } + + if (!REFrameworkNET::ICallback::typeid->IsAssignableFrom(declaringType)) { + throw gcnew System::ArgumentException("Type does not implement ICallback"); + } + + const auto wantedFlags = System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Static; + + // Double check that the type has Pre and Post events + auto preEvent = declaringType->GetField("PreImplementation", wantedFlags); + auto postEvent = declaringType->GetField("PostImplementation", wantedFlags); + + if (preEvent == nullptr || postEvent == nullptr) { + throw gcnew System::ArgumentException("Callback type does not have Pre and Post events"); + } + + m_declaringType = declaringType; + } + + property bool Valid { + public: + bool get() { + return m_declaringType != nullptr; + } + } + + void Install(System::Reflection::MethodInfo^ destination) { + if (!Valid) { + throw gcnew System::InvalidOperationException("Callback is not valid"); + } + + if (!destination->IsStatic) { + throw gcnew System::ArgumentException("Destination method must be static"); + } + + const auto wantedFlags = System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Static; + + auto preEvent = m_declaringType->GetField("PreImplementation", wantedFlags); + auto postEvent = m_declaringType->GetField("PostImplementation", wantedFlags); + + if (preEvent == nullptr || postEvent == nullptr) { + throw gcnew System::ArgumentException("Callback type does not have Pre and Post fields"); + } + + auto preDelegate = (BaseCallback::Delegate^)preEvent->GetValue(nullptr); + auto postDelegate = (BaseCallback::Delegate^)postEvent->GetValue(nullptr); + + if (m_callbackType == CallbackType::Pre) { + auto del = (BaseCallback::Delegate^)System::Delegate::CreateDelegate(BaseCallback::Delegate::typeid, destination); + + if (preDelegate == nullptr) { + preDelegate = del; + } else { + preDelegate = preDelegate + del; + } + + preEvent->SetValue(nullptr, preDelegate); + } else { + auto del = (BaseCallback::Delegate^)System::Delegate::CreateDelegate(BaseCallback::Delegate::typeid, destination); + + if (postDelegate == nullptr) { + postDelegate = del; + } else { + postDelegate = postDelegate + del; + } + + postEvent->SetValue(nullptr, postDelegate); + } + } + + private: + System::Type^ m_declaringType{nullptr}; + CallbackType m_callbackType; + }; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Attributes/Method.hpp b/csharp-api/REFrameworkNET/Attributes/Method.hpp index f6f4ee04d..6ba6b9219 100644 --- a/csharp-api/REFrameworkNET/Attributes/Method.hpp +++ b/csharp-api/REFrameworkNET/Attributes/Method.hpp @@ -1,24 +1,26 @@ #pragma once +#include + #include "../TDB.hpp" namespace REFrameworkNET::Attributes { /// Attribute to mark a reference assembly method for easier lookup. [System::AttributeUsage(System::AttributeTargets::Method)] - public ref class Method : public System::Attribute { + public ref class MethodAttribute : public System::Attribute { private: - static System::Collections::Concurrent::ConcurrentDictionary^ cache - = gcnew System::Collections::Concurrent::ConcurrentDictionary(System::Environment::ProcessorCount * 2, 8192); + static System::Collections::Concurrent::ConcurrentDictionary^ cache + = gcnew System::Collections::Concurrent::ConcurrentDictionary(System::Environment::ProcessorCount * 2, 8192); public: - static Method^ GetCachedAttribute(System::Reflection::MethodInfo^ target) { - Method^ attr = nullptr; + static MethodAttribute^ GetCachedAttribute(System::Reflection::MethodInfo^ target) { + MethodAttribute^ attr = nullptr; if (cache->TryGetValue(target, attr)) { return attr; } - if (attr = (REFrameworkNET::Attributes::Method^)System::Attribute::GetCustomAttribute(target, REFrameworkNET::Attributes::Method::typeid); attr != nullptr) { + if (attr = (MethodAttribute^)System::Attribute::GetCustomAttribute(target, MethodAttribute::typeid); attr != nullptr) { cache->TryAdd(target, attr); #ifdef REFRAMEWORK_VERBOSE @@ -32,7 +34,7 @@ namespace REFrameworkNET::Attributes { } public: - Method(uint32_t methodIndex) { + MethodAttribute(uint32_t methodIndex) { method = REFrameworkNET::TDB::Get()->GetMethod(methodIndex); if (method != nullptr && method->IsVirtual()) { diff --git a/csharp-api/REFrameworkNET/Attributes/MethodHook.hpp b/csharp-api/REFrameworkNET/Attributes/MethodHook.hpp index a5e5075a0..4713c1c10 100644 --- a/csharp-api/REFrameworkNET/Attributes/MethodHook.hpp +++ b/csharp-api/REFrameworkNET/Attributes/MethodHook.hpp @@ -83,8 +83,8 @@ namespace REFrameworkNET::Attributes { } protected: - REFrameworkNET::TypeDefinition^ m_declaringType; - REFrameworkNET::Method^ m_method; + REFrameworkNET::TypeDefinition^ m_declaringType{nullptr}; + REFrameworkNET::Method^ m_method{nullptr}; MethodHookType m_hookType; bool m_skipJmp{ false }; }; diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp index 9b8fb86f9..d0f3c502c 100644 --- a/csharp-api/REFrameworkNET/Callbacks.hpp +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -34,8 +34,13 @@ public ref class BaseCallback { delegate void Delegate(); }; +/// Internal interface used to prove that a class is a callback belonging to REFrameworkNET +interface class ICallback { + +}; + #define GENERATE_POCKET_CLASS(EVENT_NAME) \ -public ref class EVENT_NAME { \ +public ref class EVENT_NAME : public ICallback { \ public: \ static event BaseCallback::Delegate^ Pre { \ void add(BaseCallback::Delegate^ value) { \ diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 3015d6b7d..8d706ad2f 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -53,6 +53,11 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject return nullptr; } + /// + /// Retrieves a managed object from the pool or creates a new one if it doesn't exist. + /// + /// The address of the managed object. + /// The managed object. static T^ Get(uintptr_t addr) { if (addr == 0) { return nullptr; @@ -145,11 +150,19 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject static void CleanupKnownCaches(); public: + /// + /// Retrieves a managed object from the pool or creates a new one if it doesn't exist. + /// + /// The managed object. template static T^ Get(reframework::API::ManagedObject* obj) { return Cache::Get((uintptr_t)obj); } + /// + /// Retrieves a managed object from the pool or creates a new one if it doesn't exist. + /// + /// The managed object. template static T^ Get(::REFrameworkManagedObjectHandle handle) { return Cache::Get((uintptr_t)handle); diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index fbfc996a8..f00e39981 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -3,6 +3,7 @@ #include "Attributes/Plugin.hpp" #include "Attributes/MethodHook.hpp" +#include "Attributes/Callback.hpp" #include "MethodHook.hpp" #include "SystemString.hpp" #include "NativePool.hpp" @@ -314,12 +315,35 @@ namespace REFrameworkNET { } else { REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName); } + continue; } catch(System::Exception^ e) { REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": " + e->Message); + continue; } catch(const std::exception& e) { REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": " + gcnew System::String(e.what())); + continue; } catch(...) { REFrameworkNET::API::LogError("Failed to install MethodHook in " + method->Name + " in " + type->FullName + ": Unknown exception caught"); + continue; + } + + // Callback attribute(s) + attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::CallbackAttribute::typeid, true); + + if (attributes->Length > 0) try { + REFrameworkNET::API::LogInfo("Found Callback in " + method->Name + " in " + type->FullName); + auto callbackAttr = (REFrameworkNET::Attributes::CallbackAttribute^)attributes[0]; + callbackAttr->Install(method); + continue; + } catch(System::Exception^ e) { + REFrameworkNET::API::LogError("Failed to install Callback in " + method->Name + " in " + type->FullName + ": " + e->Message); + continue; + } catch(const std::exception& e) { + REFrameworkNET::API::LogError("Failed to install Callback in " + method->Name + " in " + type->FullName + ": " + gcnew System::String(e.what())); + continue; + } catch(...) { + REFrameworkNET::API::LogError("Failed to install Callback in " + method->Name + " in " + type->FullName + ": Unknown exception caught"); + continue; } } } diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 538a2f64c..ec9ab6c7e 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -146,7 +146,7 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public protected: virtual Object^ Invoke(Reflection::MethodInfo^ targetMethod, array^ args) override { // Get the REFrameworkNET::Attributes::Method attribute from the method - auto methodAttribute = REFrameworkNET::Attributes::Method::GetCachedAttribute(targetMethod); + auto methodAttribute = REFrameworkNET::Attributes::MethodAttribute::GetCachedAttribute(targetMethod); Object^ result = nullptr; auto iobject = static_cast(Instance); diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 642ac2527..9d4a48eb3 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -296,4 +296,49 @@ VS_DOTNET_REFERENCE_REFramework.NET.application set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") +""" + +# Even though this targets RE2 it is not game specific +# It just uses the System/via DLLs which are game agnostic +[target.ObjectExplorer] +condition = "build-csharp-test-re2" +type = "CSharpSharedTarget" +sources = ["test/Test/ObjectExplorer.cs"] +link-libraries = [ + "csharp-api" +] + +cmake-after = """ +set_target_properties(ObjectExplorer PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET +"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" +) + +set_target_properties(ObjectExplorer PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET._mscorlib +"${re2_mscorlibdll}" +) + +set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib") + +set_target_properties(ObjectExplorer PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET._System +"${re2_systemdll}" +) + +set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") + +set_target_properties(ObjectExplorer PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.System.Core +"${re2_systemcoredll}" +) + +set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core") + +set_target_properties(ObjectExplorer PROPERTIES +VS_DOTNET_REFERENCE_REFramework.NET.viacore +"${re2_viacoredll}" +) + +set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") """ \ No newline at end of file diff --git a/csharp-api/test/Test/ObjectExplorer.cs b/csharp-api/test/Test/ObjectExplorer.cs index 89253e8fb..480f5f921 100644 --- a/csharp-api/test/Test/ObjectExplorer.cs +++ b/csharp-api/test/Test/ObjectExplorer.cs @@ -6,8 +6,10 @@ using ImGuiNET; using REFrameworkNET; using REFrameworkNET.Callbacks; +using REFrameworkNET.Attributes; public class ObjectExplorerPlugin { + [Callback(typeof(ImGuiRender), CallbackType.Pre)] public static void RenderImGui() { if (ImGui.Begin("Test Window")) { ImGui.SetNextItemOpen(true, ImGuiCond.Once); @@ -27,7 +29,7 @@ public static void OnUnload() { [REFrameworkNET.Attributes.PluginEntryPoint] public static void Main() { - REFrameworkNET.Callbacks.ImGuiRender.Pre += RenderImGui; + //REFrameworkNET.Callbacks.ImGuiRender.Pre += RenderImGui; } } diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 3511c3175..9a7955f7e 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -5,15 +5,18 @@ using System.Reflection; using ImGuiNET; using REFrameworkNET; +using REFrameworkNET.Attributes; using REFrameworkNET.Callbacks; public class DangerousFunctions { + [MethodHook(typeof(app.CameraManager), nameof(app.CameraManager.isInside), MethodHookType.Pre)] public static REFrameworkNET.PreHookResult isInsidePreHook(Span args) { //Console.WriteLine("Inside pre hook (From C#) " + args.ToString()); //REFrameworkNET.API.LogInfo("isInsidePreHook"); return REFrameworkNET.PreHookResult.Continue; } + [MethodHook(typeof(app.CameraManager), nameof(app.CameraManager.isInside), MethodHookType.Post)] public static void isInsidePostHook(ref ulong retval) { if ((retval & 1) != 0) { REFrameworkNET.API.LogInfo("Camera is inside"); @@ -21,35 +24,26 @@ public static void isInsidePostHook(ref ulong retval) { //Console.WriteLine("Inside post hook (From C#), retval: " + (retval & 1).ToString()); } + [MethodHook(typeof(app.PlayerInputProcessorDetail), nameof(app.PlayerInputProcessorDetail.processNormalAttack), MethodHookType.Pre)] + public static REFrameworkNET.PreHookResult processNormalAttackPreHook(Span args) { + var inputProcessor = ManagedObject.ToManagedObject(args[1]).As(); + var asIObject = inputProcessor as REFrameworkNET.IObject; + ulong flags = args[2]; + bool isCombo = (args[4] & 1) != 0; + API.LogInfo("processNormalAttack: " + + inputProcessor.ToString() + " " + + asIObject.GetTypeDefinition()?.GetFullName()?.ToString() + " " + + flags.ToString() + " " + + isCombo.ToString()); + return PreHookResult.Continue; + } + public static void Entry() { var mouse = REFrameworkNET.API.GetNativeSingletonT(); mouse.set_ShowCursor(false); var tdb = REFrameworkNET.API.GetTDB(); - tdb.GetType(app.CameraManager.REFType.FullName). - GetMethod("isInside")?. - AddHook(false). - AddPre(isInsidePreHook). - AddPost(isInsidePostHook); - - tdb.GetType(app.PlayerInputProcessorDetail.REFType.FullName). - GetMethod("processNormalAttack"). - AddHook(false). - AddPre((args) => { - var inputProcessor = ManagedObject.ToManagedObject(args[1]).As(); - var asIObject = inputProcessor as REFrameworkNET.IObject; - ulong flags = args[2]; - bool isCombo = (args[4] & 1) != 0; - API.LogInfo("processNormalAttack: " + - inputProcessor.ToString() + " " + - asIObject.GetTypeDefinition()?.GetFullName()?.ToString() + " " + - flags.ToString() + " " + - isCombo.ToString()); - return PreHookResult.Continue; - }). - AddPost((ref ulong retval) => { - }); // These via.SceneManager and via.Scene are // loaded from an external reference assembly @@ -147,7 +141,7 @@ class REFrameworkPlugin { static bool doFullGC = false; - // Assigned in a callback below. + [Callback(typeof(ImGuiRender), CallbackType.Pre)] public static void RenderImGui() { if (ImGui.Begin("Test Window")) { // Debug info about GC state @@ -221,54 +215,56 @@ public static void RenderImGui() { } } - public static void TestCallbacks() { - REFrameworkNET.Callbacks.BeginRendering.Pre += () => { - sw.Start(); - }; - REFrameworkNET.Callbacks.BeginRendering.Post += () => { - sw.Stop(); + [Callback(typeof(BeginRendering), CallbackType.Pre)] + static void BeginRenderingPre() { + sw.Start(); + } - if (sw.ElapsedMilliseconds >= 6) { - Console.WriteLine("BeginRendering took " + sw.ElapsedMilliseconds + "ms"); - } + [Callback(typeof(BeginRendering), CallbackType.Post)] + static void BeginRenderingPost() { + sw.Stop(); - /*try { - DangerousFunctions.TryEnableFrameGeneration(); - } catch (Exception e) { - REFrameworkNET.API.LogError(e.ToString()); - }*/ + if (sw.ElapsedMilliseconds >= 6) { + Console.WriteLine("BeginRendering took " + sw.ElapsedMilliseconds + "ms"); + } - sw.Reset(); - }; + /*try { + DangerousFunctions.TryEnableFrameGeneration(); + } catch (Exception e) { + REFrameworkNET.API.LogError(e.ToString()); + }*/ - REFrameworkNET.Callbacks.EndRendering.Post += () => { - if (!sw2.IsRunning) { - sw2.Start(); - } + sw.Reset(); + } - if (sw2.ElapsedMilliseconds >= 5000) { - sw2.Restart(); - Console.WriteLine("EndRendering"); - } - }; + [Callback(typeof(EndRendering), CallbackType.Post)] + static void EndRenderingPost() { + if (!sw2.IsRunning) { + sw2.Start(); + } - REFrameworkNET.Callbacks.FinalizeRenderer.Pre += () => { - Console.WriteLine("Finalizing Renderer"); - }; + if (sw2.ElapsedMilliseconds >= 5000) { + sw2.Restart(); + Console.WriteLine("EndRendering"); + } + } - REFrameworkNET.Callbacks.PrepareRendering.Post += () => { - }; + [Callback(typeof(FinalizeRenderer), CallbackType.Pre)] + static void FinalizeRendererPre() { + Console.WriteLine("Finalizing Renderer"); + } - REFrameworkNET.Callbacks.ImGuiRender.Pre += RenderImGui; + [Callback(typeof(PrepareRendering), CallbackType.Post)] + static void PrepareRenderingPost() { } // To be called when the AssemblyLoadContext is unloading the assembly - [REFrameworkNET.Attributes.PluginExitPoint] + [PluginExitPoint] public static void OnUnload() { REFrameworkNET.API.LogInfo("Unloading Test"); } - [REFrameworkNET.Attributes.PluginEntryPoint] + [PluginEntryPoint] public static void Main() { try { MainImpl(); @@ -287,8 +283,6 @@ public static void Main() { public static void MainImpl() { REFrameworkNET.API.LogInfo("Testing REFrameworkAPI..."); - TestCallbacks(); - var tdb = REFrameworkNET.API.GetTDB(); REFrameworkNET.API.LogInfo(tdb.GetNumTypes().ToString() + " types"); diff --git a/csharp-api/test/Test/Test2.cs b/csharp-api/test/Test/Test2.cs index 2d1a37430..a7f5d27cd 100644 --- a/csharp-api/test/Test/Test2.cs +++ b/csharp-api/test/Test/Test2.cs @@ -4,15 +4,16 @@ using System.Dynamic; using System.Reflection; using ImGuiNET; +using REFrameworkNET.Callbacks; +using REFrameworkNET.Attributes; +using REFrameworkNET; class REFrameworkPlugin2 { - [REFrameworkNET.Attributes.PluginEntryPoint] - public static void Main() { - REFrameworkNET.Callbacks.ImGuiRender.Pre += () => { - if (ImGui.Begin("Test2.cs")) { - ImGui.Text("Test2.cs"); - ImGui.End(); - } - }; + [Callback(typeof(ImGuiRender), CallbackType.Pre)] + public static void ImGuiCallback() { + if (ImGui.Begin("Test2.cs")) { + ImGui.Text("Test2.cs"); + ImGui.End(); + } } } \ No newline at end of file diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index 9253f1b60..066286680 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -8,101 +8,101 @@ using ImGuiNET; using REFrameworkNET; using REFrameworkNET.Callbacks; +using REFrameworkNET.Attributes; public class TestRE2Plugin { static bool IsRunningRE2 => Environment.ProcessPath.Contains("re2", StringComparison.CurrentCultureIgnoreCase); static System.Diagnostics.Stopwatch imguiStopwatch = new(); - [REFrameworkNET.Attributes.PluginEntryPoint] - public static void Main() { - if (IsRunningRE2) { - Console.WriteLine("Running in RE2"); - } else { - Console.WriteLine("Not running in RE2"); - return; - } - - RE2HookBenchmark.InstallHook(); - - ImGuiRender.Pre += () => { - var deltaSeconds = imguiStopwatch.Elapsed.TotalMilliseconds / 1000.0; - imguiStopwatch.Restart(); + [Callback(typeof(ImGuiRender), CallbackType.Pre)] + public static void ImGuiCallback() { + var deltaSeconds = imguiStopwatch.Elapsed.TotalMilliseconds / 1000.0; + imguiStopwatch.Restart(); - if (ImGui.Begin("Test Window")) { - ImGui.Text("RE2"); - ImGui.Separator(); + if (ImGui.Begin("Test Window")) { + ImGui.Text("RE2"); + ImGui.Separator(); - - if (ImGui.TreeNode("Player")) { - var playerManager = API.GetManagedSingletonT(); - var player = playerManager.get_CurrentPlayer(); - if (player != null) { - ImGui.Text("Player is not null"); - } else { - ImGui.Text("Player is null"); - } - ImGui.TreePop(); + + if (ImGui.TreeNode("Player")) { + var playerManager = API.GetManagedSingletonT(); + var player = playerManager.get_CurrentPlayer(); + if (player != null) { + ImGui.Text("Player is not null"); + } else { + ImGui.Text("Player is null"); } - - ImGui.End(); + ImGui.TreePop(); } - - // ROund the window - ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 10.0f); - ImGui.SetNextWindowSize(new System.Numerics.Vector2(500, 500), ImGuiCond.FirstUseEver); - if (ImGui.Begin("RE2 Bench")) { + ImGui.End(); + } + + // ROund the window + ImGui.PushStyleVar(ImGuiStyleVar.WindowRounding, 10.0f); - ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(0.5f, 1f, 0.4f, 1.0f)); - ImGui.PlotLines("Overall Benchmark", ref RE2HookBenchmark.BenchmarkData[0], 1000, RE2HookBenchmark.MeasureCount % 1000, RE2HookBenchmark.RunningAvg.ToString("0.000") + " µs", 0, (float)RE2HookBenchmark.RunningAvg * 2.0f, new System.Numerics.Vector2(0, 40)); - ImGui.PopStyleColor(); + ImGui.SetNextWindowSize(new System.Numerics.Vector2(500, 500), ImGuiCond.FirstUseEver); + if (ImGui.Begin("RE2 Bench")) { - System.Collections.Generic.List threadRanks = new(); + ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(0.5f, 1f, 0.4f, 1.0f)); + ImGui.PlotLines("Overall Benchmark", ref RE2HookBenchmark.BenchmarkData[0], 1000, RE2HookBenchmark.MeasureCount % 1000, RE2HookBenchmark.RunningAvg.ToString("0.000") + " µs", 0, (float)RE2HookBenchmark.RunningAvg * 2.0f, new System.Numerics.Vector2(0, 40)); + ImGui.PopStyleColor(); - foreach(var tdata in RE2HookBenchmark.threadData) { - threadRanks.Add(tdata.Value.threadID); - } + System.Collections.Generic.List threadRanks = new(); - // Sort by highest running average - threadRanks.Sort((a, b) => { - var aData = RE2HookBenchmark.threadData[a]; - var bData = RE2HookBenchmark.threadData[b]; - return aData.runningAvg.CompareTo(bData.runningAvg); - }); + foreach(var tdata in RE2HookBenchmark.threadData) { + threadRanks.Add(tdata.Value.threadID); + } - var totalThreadRanks = threadRanks.Count; + // Sort by highest running average + threadRanks.Sort((a, b) => { + var aData = RE2HookBenchmark.threadData[a]; + var bData = RE2HookBenchmark.threadData[b]; + return aData.runningAvg.CompareTo(bData.runningAvg); + }); - System.Collections.Generic.List threadData = new(); + var totalThreadRanks = threadRanks.Count; - foreach(var tdata in RE2HookBenchmark.threadData) { - threadData.Add(tdata.Value); - } + System.Collections.Generic.List threadData = new(); - threadData.Sort((a, b) => { - return a.lerp.CompareTo(b.lerp); - }); + foreach(var tdata in RE2HookBenchmark.threadData) { + threadData.Add(tdata.Value); + } - foreach(var tdata in threadData) { - var rank = threadRanks.IndexOf(tdata.threadID) + 1; - var rankRatio = (double)rank / (double)totalThreadRanks; - var towards = Math.Max(80.0 * rankRatio, 20.0); - tdata.lerp = towards * deltaSeconds + tdata.lerp * (1.0 - deltaSeconds); - var lerpRatio = tdata.lerp / 80.0; + threadData.Sort((a, b) => { + return a.lerp.CompareTo(b.lerp); + }); - var greenColor = 1.0 - lerpRatio; - var redColor = lerpRatio; + foreach(var tdata in threadData) { + var rank = threadRanks.IndexOf(tdata.threadID) + 1; + var rankRatio = (double)rank / (double)totalThreadRanks; + var towards = Math.Max(80.0 * rankRatio, 20.0); + tdata.lerp = towards * deltaSeconds + tdata.lerp * (1.0 - deltaSeconds); + var lerpRatio = tdata.lerp / 80.0; - //ImGui.Text("Thread ID: " + tdata.Value.threadID + " Avg: " + tdata.Value.runningAvg.ToString("0.000") + " µs"); - ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4((float)redColor, (float)greenColor, 0f, 1.0f)); - ImGui.PlotLines("Thread " + tdata.threadID, ref tdata.benchmarkData[0], 1000, tdata.callCount % 1000, tdata.runningAvg.ToString("0.000") + " µs", (float)tdata.runningAvg, (float)tdata.runningAvg * 2.0f, new System.Numerics.Vector2(0, (int)tdata.lerp)); - ImGui.PopStyleColor(); - } + var greenColor = 1.0 - lerpRatio; + var redColor = lerpRatio; - ImGui.End(); + //ImGui.Text("Thread ID: " + tdata.Value.threadID + " Avg: " + tdata.Value.runningAvg.ToString("0.000") + " µs"); + ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4((float)redColor, (float)greenColor, 0f, 1.0f)); + ImGui.PlotLines("Thread " + tdata.threadID, ref tdata.benchmarkData[0], 1000, tdata.callCount % 1000, tdata.runningAvg.ToString("0.000") + " µs", (float)tdata.runningAvg, (float)tdata.runningAvg * 2.0f, new System.Numerics.Vector2(0, (int)tdata.lerp)); + ImGui.PopStyleColor(); } - ImGui.PopStyleVar(); - }; + ImGui.End(); + } + + ImGui.PopStyleVar(); + } + + [REFrameworkNET.Attributes.PluginEntryPoint] + public static void Main() { + if (IsRunningRE2) { + Console.WriteLine("Running in RE2"); + } else { + Console.WriteLine("Not running in RE2"); + return; + } // Benchmarking the effects of threading on invoking game code for (int i = 0; i < 2; ++i) { @@ -255,10 +255,9 @@ public static void Bench(System.Action action) { } [REFrameworkNET.Attributes.MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Pre, false)] - static PreHookResult Pre(System.Span args) { + static PreHookResult Pre(Span args) { var hitController = ManagedObject.ToManagedObject(args[1]).As(); - //Bench(TestRE2Plugin.BenchFnAction); Bench(() => { for (int i = 0; i < 10000; ++i) { var gameobj = hitController.get_GameObject(); @@ -274,12 +273,4 @@ static PreHookResult Pre(System.Span args) { static void Post(ref ulong retval) { } - - public static void InstallHook() { - /*app.Collision.HitController.REFType - .GetMethod("update") - .AddHook(false) - .AddPre(Pre) - .AddPost(Post);*/ - } } \ No newline at end of file From 1a023f4ce502fa593c2b5b1a2f06aff3b65d866c Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Apr 2024 12:54:04 -0700 Subject: [PATCH 156/207] .NET: Add NativeObject.FromAddress --- csharp-api/REFrameworkNET/NativeObject.hpp | 30 ++++++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index 77b21e913..bbe3600d6 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -11,10 +11,12 @@ namespace REFrameworkNET { value struct InvokeRet; -// Native objects are objects that are NOT managed objects -// However, they still have reflection information associated with them -// So this intends to be the "ManagedObject" class for native objects -// So we can easily interact with them in C# +/// +// Native objects are objects that are NOT managed objects
+// However, they still have reflection information associated with them
+// So this intends to be the "ManagedObject" class for native objects
+// So we can easily interact with them in C#
+///
public ref class NativeObject : public REFrameworkNET::UnifiedObject { public: @@ -48,14 +50,17 @@ public ref class NativeObject : public REFrameworkNET::UnifiedObject m_object = nullptr; } + /// The type of the object virtual TypeDefinition^ GetTypeDefinition() override { return m_type; } + /// The address of the object as a void* pointer virtual void* Ptr() override { return m_object; } + /// The address of the object virtual uintptr_t GetAddress() override { return (uintptr_t)m_object; } @@ -63,7 +68,22 @@ public ref class NativeObject : public REFrameworkNET::UnifiedObject generic virtual T As() override; -private: + /// + /// Creates a NativeObject wrapper over the given address + /// + /// + /// This function can be very dangerous if used incorrectly.
+ /// Always double check that you are feeding the correct address
+ /// and type to this function.
+ ///
+ /// The address of the object + /// The type of the object + /// A NativeObject wrapper over the given address + static NativeObject^ FromAddress(uintptr_t obj, TypeDefinition^ t) { + return gcnew NativeObject(obj, t); + } + +protected: void* m_object{}; TypeDefinition^ m_type{}; }; From 4e258f3a7b3ae800d9b60e794dca7bedc236c357 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 25 Apr 2024 13:17:03 -0700 Subject: [PATCH 157/207] .NET: Add direct static method calling in generated ref assemblies --- .../AssemblyGenerator/ClassGenerator.cs | 119 ++++++------------ csharp-api/REFrameworkNET/Method.cpp | 80 ++++++++++++ csharp-api/REFrameworkNET/Method.hpp | 2 + csharp-api/REFrameworkNET/Proxy.hpp | 76 ++--------- csharp-api/test/Test/ObjectExplorer.cs | 7 +- csharp-api/test/Test/TestRE2.cs | 29 ++++- 6 files changed, 155 insertions(+), 158 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 324d90f96..a56696f78 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -13,33 +13,6 @@ using Microsoft.CodeAnalysis.Operations; using REFrameworkNET; using System; -using System.ComponentModel.DataAnnotations; - -/*public interface CrappyTest { - public string Concat(object arg0); - - public string Concat(object arg0, object arg1); - - public string Concat(object arg0, object arg1, object arg2); - - public string Concat(global::System.Object[] args); - - public string Concat(string str0, string str1); - - public string Concat(string str0, string str1, string str2); - - public string Concat(string str0, string str1, string str2, string str3); - - public string Concat(object str0, object str1); - - public string Concat(object str0, object str1, object str2); - - public string Concat(object str0, object str1, object str2, object str3); - - public string Concat(global::System.String[] values); - - public string Format(string format, object arg0); -}*/ public class ClassGenerator { private string className; @@ -49,6 +22,8 @@ public class ClassGenerator { private TypeDeclarationSyntax? typeDeclaration; private bool addedNewKeyword = false; + private List internalFieldDeclarations = []; + public TypeDeclarationSyntax? TypeDeclaration { get { return typeDeclaration; @@ -91,37 +66,6 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { } private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetType, REFrameworkNET.TypeDefinition? containingType) { - if (containingType != null && targetType != null) { - if (containingType.FullName.StartsWith("System.")) { - /*var containingRtType = containingType.GetRuntimeType(); - var targetRtType = targetType.GetRuntimeType(); - - if (containingRtType != null && targetRtType != null) { - var containingRtAssembly = (ManagedObject)containingRtType.Call("get_Assembly"); - var targetRtAssembly = (ManagedObject)targetRtType.Call("get_Assembly"); - - if ((string)containingRtAssembly.Call("get_FullName") != (string)targetRtAssembly.Call("get_FullName")) { - System.Console.WriteLine("Method " + containingType.FullName + " is referencing type " + targetType.FullName + " in a different assembly"); - return SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - } - }*/ - - // Check if any of the generic arguments are not in the same assembly - /*if (containingRtType != null && targetType.IsGenericType()) { - foreach (REFrameworkNET.TypeDefinition td in targetType.GenericArguments) { - if (td != null) { - var rtType = td.GetRuntimeType(); - if (rtType != null) { - if ((ManagedObject)containingRtType.Call("get_Assembly") != (ManagedObject)rtType.Call("get_Assembly")) { - return SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); - } - } - } - } - }*/ - } - } - TypeSyntax outSyntax = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.VoidKeyword)); string ogTargetTypename = targetType != null ? targetType.GetFullName() : ""; @@ -294,12 +238,6 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy typeDeclaration = GenerateMethods(baseTypes); - List internalFieldDeclarations = []; - - if (internalFieldDeclarations.Count > 0) { - typeDeclaration = typeDeclaration.AddMembers(internalFieldDeclarations.ToArray()); - } - if (baseTypes.Count > 0 && typeDeclaration != null) { refTypeFieldDecl = refTypeFieldDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); //fieldDeclaration2 = fieldDeclaration2.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); @@ -312,6 +250,11 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy //typeDeclaration = typeDeclaration.AddMembers(refProxyFieldDecl); } + // Logically needs to come after the REFType field is added as they reference it + if (internalFieldDeclarations.Count > 0 && typeDeclaration != null) { + typeDeclaration = typeDeclaration.AddMembers(internalFieldDeclarations.ToArray()); + } + return GenerateNestedTypes(); } @@ -326,7 +269,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp HashSet seenMethodSignatures = []; - var validMethods = new List(); + List validMethods = []; try { foreach(REFrameworkNET.Method m in methods) { @@ -376,6 +319,8 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ")"))) ); + bool anyOutParams = false; + if (method.Parameters.Count > 0) { // If any of the params have ! in them, skip this method if (method.Parameters.Any(param => param != null && (param.Type == null || (param.Type != null && param.Type.FullName.Contains('!'))))) { @@ -395,6 +340,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp bool anyUnsafeParams = false; + if (runtimeParams != null) { foreach (dynamic param in runtimeParams) { if (param.get_IsRetval() == true) { @@ -430,6 +376,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp if (isOut == true) { simpleMethodSignature += "out"; modifiers.Add(SyntaxFactory.Token(SyntaxKind.OutKeyword)); + anyOutParams = true; } if (isByRef == true) { @@ -461,36 +408,42 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp } if (method.IsStatic()) { - // Add System.ComponentModel.Description("static") attribute - //methodDeclaration = methodDeclaration.AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute(SyntaxFactory.ParseName("global::System.ComponentModel.DescriptionAttribute"), SyntaxFactory.ParseAttributeArgumentList("(\"static\")")))); - // lets see what happens if we just make it static - /*methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + methodDeclaration = methodDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); // Now we must add a body to it that actually calls the method // We have our REFType field, so we can lookup the method and call it // Make a private static field to hold the REFrameworkNET.Method - var internalFieldName = "INTERNAL_" + method.GetIndex().ToString(); + var internalFieldName = "INTERNAL_" + method.Name + method.GetIndex().ToString(); var methodVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Method")) - .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetMethod(\"" + method.Name + "\")")))); - + .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetMethod(\"" + method.GetMethodSignature() + "\")")))); + var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); internalFieldDeclarations.Add(methodFieldDeclaration); - // Now we can add the body - // bool HandleInvokeMember_Internal(System::Object^ obj, array^ args, System::Object^% result); + List bodyStatements = []; + if (method.ReturnType.FullName == "System.Void") { - var body = internalFieldName + ".Invoke(null, null)"; - methodDeclaration = methodDeclaration.AddBodyStatements(SyntaxFactory.ParseStatement(body)); + if (method.Parameters.Count == 0) { + bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, null);")); + } else if (!anyOutParams) { + bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, new object[] {" + string.Join(", ", method.Parameters.Select(param => param.Name)) + "});")); + } else { + bodyStatements.Add(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();")); // TODO: Implement this + } } else { - var body1 = "object INTERNAL_result = null;"; - var body2 = internalFieldName + ".HandleInvokeMember_Internal(null, " + (method.Parameters.Count > 0 ? "new object[] {" + string.Join(", ", method.Parameters.Select(param => param.Name)) + "}" : "null") + ", ref INTERNAL_result);"; - string body3 = "return (" + returnType.GetText() + ")INTERNAL_result;"; + if (method.Parameters.Count == 0) { + bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + returnType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + returnType.GetText().ToString() + "), null, null);")); + } else if (!anyOutParams) { + bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + returnType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + returnType.GetText().ToString() + "), null, new object[] {" + string.Join(", ", method.Parameters.Select(param => param.Name)) + "});")); + } else { + bodyStatements.Add(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();")); // TODO: Implement this + } + } - methodDeclaration = methodDeclaration.AddBodyStatements( - [SyntaxFactory.ParseStatement(body1), SyntaxFactory.ParseStatement(body2), SyntaxFactory.ParseStatement(body3)] - ); - }*/ + methodDeclaration = methodDeclaration.AddBodyStatements( + [.. bodyStatements] + ); } if (seenMethodSignatures.Contains(simpleMethodSignature)) { diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 60223cf4a..878645108 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -10,6 +10,7 @@ #include "API.hpp" #include "VM.hpp" #include "SystemString.hpp" +#include "Proxy.hpp" #include "Utility.hpp" @@ -132,6 +133,85 @@ REFrameworkNET::InvokeRet Method::Invoke(System::Object^ obj, array^ args) { + System::Object^ result = nullptr; + this->HandleInvokeMember_Internal(obj, args, result); + + if (result == nullptr) { + return nullptr; // ez + } + + if (targetReturnType == nullptr) { + return result; + } + + if (!targetReturnType->IsPrimitive && !targetReturnType->IsEnum && !targetReturnType->IsInterface) { + if (targetReturnType == String::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::ManagedObject::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::NativeObject::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::TypeDefinition::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::Method::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::Field::typeid) { + return result; + } + } + + if (targetReturnType->IsEnum) { + auto underlyingType = targetReturnType->GetEnumUnderlyingType(); + + if (underlyingType != nullptr) { + auto underlyingResult = Convert::ChangeType(result, underlyingType); + return Enum::ToObject(targetReturnType, underlyingResult); + } + } + + if (this->DeclaringType == nullptr) { + return result; + } + + if (!targetReturnType->IsPrimitive && targetReturnType->IsInterface) { + auto iobjectResult = dynamic_cast(result); + + if (iobjectResult != nullptr && targetReturnType->IsInterface) { + // Caching mechanism to prevent creating multiple proxies for the same object and type so we dont stress the GC + if (auto existingProxy = iobjectResult->GetProxy(targetReturnType); existingProxy != nullptr) { + return existingProxy; + } + + auto proxy = System::Reflection::DispatchProxy::Create(targetReturnType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(targetReturnType, result->GetType())); + ((IProxy^)proxy)->SetInstance(iobjectResult); + + if (auto unified = dynamic_cast(iobjectResult); unified != nullptr) { + unified->AddProxy(targetReturnType, (IProxy^)proxy); + } + + result = proxy; + return result; + } + } + + return result; +} + ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, array^ args) { if (obj == nullptr && !this->IsStatic()) { System::String^ declaringName = this->GetDeclaringType() != nullptr ? this->GetDeclaringType()->GetFullName() : gcnew System::String("UnknownType"); diff --git a/csharp-api/REFrameworkNET/Method.hpp b/csharp-api/REFrameworkNET/Method.hpp index 31b574a19..ce789937c 100644 --- a/csharp-api/REFrameworkNET/Method.hpp +++ b/csharp-api/REFrameworkNET/Method.hpp @@ -61,6 +61,8 @@ public ref class Method : public System::IEquatable /// REFrameworkNET::InvokeRet Invoke(System::Object^ obj, array^ args); + System::Object^ InvokeBoxed(System::Type^ targetReturnType, System::Object^ obj, array^ args); + private: ::reframework::InvokeRet Invoke_Internal(System::Object^ obj, array^ args); diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index ec9ab6c7e..34b25e0ca 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -154,86 +154,24 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public if (methodAttribute != nullptr) { if (iobject != nullptr) { auto method = methodAttribute->GetMethod(iobject->GetTypeDefinition()); - iobject->HandleInvokeMember_Internal(method, args, result); + return method->InvokeBoxed(targetMethod->ReturnType, iobject, args); } else { throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); } } else { // This is a fallback if (iobject != nullptr) { - iobject->HandleInvokeMember_Internal(targetMethod->Name, args, result); - } else { - throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); - } - } - - auto targetReturnType = targetMethod->ReturnType; - - if (targetReturnType == nullptr) { - return result; - } - - if (!targetReturnType->IsPrimitive && !targetReturnType->IsEnum) { - if (targetReturnType == String::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::ManagedObject::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::NativeObject::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::TypeDefinition::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::Method::typeid) { - return result; - } + auto method = iobject->GetTypeDefinition()->GetMethod(targetMethod->Name); - if (targetReturnType == REFrameworkNET::Field::typeid) { - return result; - } - } - - if (targetReturnType->IsEnum) { - auto underlyingType = targetReturnType->GetEnumUnderlyingType(); - - if (underlyingType != nullptr) { - auto underlyingResult = Convert::ChangeType(result, underlyingType); - return Enum::ToObject(targetReturnType, underlyingResult); - } - } - - if (targetMethod->DeclaringType == nullptr) { - return result; - } - - if (!targetReturnType->IsPrimitive && targetMethod->DeclaringType->IsInterface && result != nullptr) { - auto iobjectResult = dynamic_cast(result); - - if (iobjectResult != nullptr && targetReturnType->IsInterface) { - // Caching mechanism to prevent creating multiple proxies for the same object and type so we dont stress the GC - if (auto existingProxy = iobjectResult->GetProxy(targetReturnType); existingProxy != nullptr) { - return existingProxy; - } - - auto proxy = DispatchProxy::Create(targetReturnType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(T::typeid, result->GetType())); - ((IProxy^)proxy)->SetInstance(iobjectResult); - - if (auto unified = dynamic_cast(iobjectResult); unified != nullptr) { - unified->AddProxy(targetReturnType, (IProxy^)proxy); + if (method != nullptr) { + return method->InvokeBoxed(targetMethod->ReturnType, iobject, args); } - - result = proxy; - return result; + } else { + throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); } } - return result; + return nullptr; } private: diff --git a/csharp-api/test/Test/ObjectExplorer.cs b/csharp-api/test/Test/ObjectExplorer.cs index 480f5f921..96f15d4f1 100644 --- a/csharp-api/test/Test/ObjectExplorer.cs +++ b/csharp-api/test/Test/ObjectExplorer.cs @@ -153,7 +153,7 @@ public static void DisplayField(REFrameworkNET.IObject obj, REFrameworkNET.Field if (t.IsEnum() && fieldData != null) { long longValue = Convert.ToInt64(fieldData); - var boxedEnum = SystemEnumT.boxEnum(t.GetRuntimeType().As<_System.Type>(), longValue); + var boxedEnum = _System.Enum.boxEnum(t.GetRuntimeType().As<_System.Type>(), longValue); ImGui.Text("Result: " + (boxedEnum as IObject).Call("ToString()") + " (" + fieldData.ToString() + ")"); } else if (fieldData != null) { ImGui.Text("Value: " + fieldData.ToString()); @@ -230,7 +230,7 @@ public static void DisplayMethod(REFrameworkNET.IObject obj, REFrameworkNET.Meth if (returnType.IsEnum()) { long longValue = Convert.ToInt64(result); - var boxedEnum = SystemEnumT.boxEnum(returnType.GetRuntimeType().As<_System.Type>(), longValue); + var boxedEnum = _System.Enum.boxEnum(returnType.GetRuntimeType().As<_System.Type>(), longValue); ImGui.Text("Result: " + (boxedEnum as IObject).Call("ToString()") + " (" + result.ToString() + ")"); } else { ImGui.Text("Result: " + result.ToString() + " (" + result.GetType().FullName + ")"); @@ -469,8 +469,7 @@ public static void Render() { RenderNativeSingletons(); } - var appdomainT = REFrameworkNET.API.GetTDB().GetTypeT<_System.AppDomain>(); - var appdomain = appdomainT.get_CurrentDomain(); + var appdomain = _System.AppDomain.get_CurrentDomain(); var assemblies = appdomain.GetAssemblies(); if (ImGui.TreeNode("AppDomain")) { diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index 066286680..59e2aa4ca 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -27,6 +27,7 @@ public static void ImGuiCallback() { if (ImGui.TreeNode("Player")) { var playerManager = API.GetManagedSingletonT(); var player = playerManager.get_CurrentPlayer(); + if (player != null) { ImGui.Text("Player is not null"); } else { @@ -254,7 +255,7 @@ public static void Bench(System.Action action) { rwl.ExitWriteLock(); } - [REFrameworkNET.Attributes.MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Pre, false)] + [MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Pre, false)] static PreHookResult Pre(Span args) { var hitController = ManagedObject.ToManagedObject(args[1]).As(); @@ -269,8 +270,32 @@ static PreHookResult Pre(Span args) { return PreHookResult.Continue; } - [REFrameworkNET.Attributes.MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Post, false)] + [MethodHook(typeof(app.Collision.HitController), nameof(app.Collision.HitController.update), MethodHookType.Post, false)] static void Post(ref ulong retval) { } + + static double cameraFovLerp = 1; + static double cameraFovLerpSpeed = 0.5; + static System.Diagnostics.Stopwatch cameraFovStopwatch = new(); + static System.Diagnostics.Stopwatch cameraFovStopwatch2 = new(); + + [Callback(typeof(BeginRendering), CallbackType.Pre)] + static void BeginRenderingCallback() { + if (!cameraFovStopwatch2.IsRunning) { + cameraFovStopwatch2.Start(); + } + + cameraFovStopwatch.Stop(); + var deltaSeconds = cameraFovStopwatch.Elapsed.TotalMilliseconds / 1000.0; + var elapsedSeconds = cameraFovStopwatch2.Elapsed.TotalMilliseconds / 1000.0; + cameraFovStopwatch.Restart(); + + var wantedValue = Math.Sin(elapsedSeconds) + 1.0; + cameraFovLerp = double.Lerp(cameraFovLerp, wantedValue, cameraFovLerpSpeed * deltaSeconds); + + var camera = via.SceneManager.get_MainView().get_PrimaryCamera(); + var degrees = cameraFovLerp * 180.0 / Math.PI; + camera.set_FOV((float)degrees); + } } \ No newline at end of file From cdb9c929364ecaaa7738dc97700d8eb5cf524af1 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 11 May 2024 07:48:30 -0700 Subject: [PATCH 158/207] .NET: Fix assembly generation in DD2 --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index a56696f78..7e167bacc 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -320,6 +320,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp ); bool anyOutParams = false; + System.Collections.Generic.List paramNames = []; if (method.Parameters.Count > 0) { // If any of the params have ! in them, skip this method @@ -356,10 +357,13 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp var paramType = param.get_ParameterType(); if (paramType == null) { + paramNames.Add(paramName); parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName("object"))); continue; } + var parsedParamName = new string(paramName as string); + /*if (param.get_IsGenericParameter() == true) { return null; // no generic parameters. }*/ @@ -391,10 +395,14 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp simpleMethodSignature += "ptr " + paramTypeSyntax.GetText().ToString(); parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(SyntaxFactory.ParseTypeName(paramTypeSyntax.ToString() + "*")).AddModifiers(modifiers.ToArray())); anyUnsafeParams = true; + + parsedParamName = "(global::System.IntPtr) " + parsedParamName; } else { simpleMethodSignature += paramTypeSyntax.GetText().ToString(); parameters.Add(SyntaxFactory.Parameter(SyntaxFactory.Identifier(paramName)).WithType(paramTypeSyntax).AddModifiers(modifiers.ToArray())); } + + paramNames.Add(parsedParamName); } methodDeclaration = methodDeclaration.AddParameterListParameters([.. parameters]); @@ -427,7 +435,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp if (method.Parameters.Count == 0) { bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, null);")); } else if (!anyOutParams) { - bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, new object[] {" + string.Join(", ", method.Parameters.Select(param => param.Name)) + "});")); + bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, new object[] {" + string.Join(", ", paramNames) + "});")); } else { bodyStatements.Add(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();")); // TODO: Implement this } @@ -435,7 +443,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp if (method.Parameters.Count == 0) { bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + returnType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + returnType.GetText().ToString() + "), null, null);")); } else if (!anyOutParams) { - bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + returnType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + returnType.GetText().ToString() + "), null, new object[] {" + string.Join(", ", method.Parameters.Select(param => param.Name)) + "});")); + bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + returnType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + returnType.GetText().ToString() + "), null, new object[] {" + string.Join(", ", paramNames) + "});")); } else { bodyStatements.Add(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();")); // TODO: Implement this } From e86651e4e0161e16e23ee0e40d4ca11a40a9f94c Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 11 May 2024 08:32:31 -0700 Subject: [PATCH 159/207] .NET: Update DD2 test script with proper statics --- csharp-api/test/Test/Test.cs | 40 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 9a7955f7e..558b4d6d9 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -41,16 +41,18 @@ public static REFrameworkNET.PreHookResult processNormalAttackPreHook(Span(); - mouse.set_ShowCursor(false); + via.hid.Mouse.set_ShowCursor(false); var tdb = REFrameworkNET.API.GetTDB(); // 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 - var sceneManager = API.GetNativeSingletonT(); - var scene = sceneManager.get_CurrentScene(); - var scene2 = sceneManager.get_CurrentScene(); + //var sceneManager = API.GetNativeSingletonT(); + //var scene = sceneManager.get_CurrentScene(); + //var scene2 = sceneManager.get_CurrentScene(); + var scene = via.SceneManager.get_CurrentScene(); + var scene2 = via.SceneManager.get_CurrentScene(); if (scene == scene2) { REFrameworkNET.API.LogInfo("Test success: Scene is the same"); @@ -59,7 +61,8 @@ public static void Entry() { } //scene.set_Pause(true); - var view = sceneManager.get_MainView(); + //var view = sceneManager.get_MainView(); + var view = via.SceneManager.get_MainView(); var name = view.get_Name(); var go = view.get_PrimaryCamera()?.get_GameObject()?.get_Transform()?.get_GameObject(); @@ -76,8 +79,7 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Previous timescale: " + currentTimescale.ToString()); REFrameworkNET.API.LogInfo("Current timescale: " + scene?.get_TimeScale().ToString());*/ - var appdomainStatics = tdb.GetType("System.AppDomain").As<_System.AppDomain>(); - var appdomain = appdomainStatics.get_CurrentDomain(); + var appdomain = _System.AppDomain.get_CurrentDomain(); var assemblies = appdomain.GetAssemblies(); // TODO: Make this work! get_length, get_item is ugly! @@ -88,45 +90,35 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Assembly: " + assembly.get_Location()?.ToString()); } - via.os os = tdb.GetType("via.os").As(); - var platform = os.getPlatform(); - var platformSubset = os.getPlatformSubset(); - var title = os.getTitle(); + var platform = via.os.getPlatform(); + var platformSubset = via.os.getPlatformSubset(); + var title = via.os.getTitle(); REFrameworkNET.API.LogInfo("Platform: " + platform); REFrameworkNET.API.LogInfo("Platform Subset: " + platformSubset); REFrameworkNET.API.LogInfo("Title: " + title); - var dialog = tdb.GetTypeT(); - var dialogError = dialog.open("Hello from C#!"); + var dialogError = via.os.dialog.open("Hello from C#!"); REFrameworkNET.API.LogInfo("Dialog error: " + dialogError.ToString()); - var devUtil = tdb.GetTypeT(); var currentDirectory = System.IO.Directory.GetCurrentDirectory(); - devUtil.writeLogFile("what the heck", currentDirectory + "\\what_the_frick.txt"); + via.sound.dev.DevUtil.writeLogFile("what the heck", currentDirectory + "\\what_the_frick.txt"); var stringTest = REFrameworkNET.VM.CreateString("Hello from C#!"); // inside RE Engine VM var stringInDotNetVM = stringTest.ToString(); // Back in .NET REFrameworkNET.API.LogInfo("Managed string back in .NET: " + stringInDotNetVM); - - var devUtilT = (devUtil as REFrameworkNET.IObject).GetTypeDefinition(); - var dialogT = (dialog as REFrameworkNET.IObject).GetTypeDefinition(); - - REFrameworkNET.API.LogInfo("DevUtil: " + devUtilT.GetFullName()); - REFrameworkNET.API.LogInfo("Dialog: " + dialogT.GetFullName()); } public static void TryEnableFrameGeneration() { - var upscalingInterface = REFrameworkNET.API.GetNativeSingletonT(); - var dlssInterface = upscalingInterface.get_DLSSInterface(); + var dlssInterface = via.render.UpscalingInterface.get_DLSSInterface(); if (dlssInterface != null && dlssInterface.get_DLSSGEnable() == false) { dlssInterface.set_DLSSGEnable(true); } - var fsr3Interface = upscalingInterface.get_FSR3Interface(); + var fsr3Interface = via.render.UpscalingInterface.get_FSR3Interface(); if (fsr3Interface != null && fsr3Interface.get_EnableFrameGeneration() == false) { fsr3Interface.set_EnableFrameGeneration(true); From bd4634ec07700593d0108545a5df015543741f28 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 11 May 2024 17:01:40 -0700 Subject: [PATCH 160/207] .NET: Initial support for creating ValueTypes --- csharp-api/CMakeLists.txt | 2 ++ csharp-api/REFrameworkNET/ValueType.cpp | 17 ++++++++++ csharp-api/REFrameworkNET/ValueType.hpp | 42 +++++++++++++++++++++++++ csharp-api/test/Test/Test.cs | 16 +++++++++- 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 csharp-api/REFrameworkNET/ValueType.cpp create mode 100644 csharp-api/REFrameworkNET/ValueType.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 0648a8d36..b0525aa7e 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -192,6 +192,7 @@ set(csharp-api_SOURCES "REFrameworkNET/TypeDefinition.cpp" "REFrameworkNET/UnifiedObject.cpp" "REFrameworkNET/VM.cpp" + "REFrameworkNET/ValueType.cpp" "REFrameworkNET/API.hpp" "REFrameworkNET/Attributes/Callback.hpp" "REFrameworkNET/Attributes/Method.hpp" @@ -223,6 +224,7 @@ set(csharp-api_SOURCES "REFrameworkNET/UnifiedObject.hpp" "REFrameworkNET/Utility.hpp" "REFrameworkNET/VM.hpp" + "REFrameworkNET/ValueType.hpp" cmake.toml ) diff --git a/csharp-api/REFrameworkNET/ValueType.cpp b/csharp-api/REFrameworkNET/ValueType.cpp new file mode 100644 index 000000000..4c8d90a41 --- /dev/null +++ b/csharp-api/REFrameworkNET/ValueType.cpp @@ -0,0 +1,17 @@ +#include "Proxy.hpp" +#include "TDB.hpp" +#include "ValueType.hpp" + +namespace REFrameworkNET { +generic +T ValueType::As() { + return AnyProxy::Create(this); +} + +generic +T ValueType::New() { + auto t = TDB::TypeCacher::GetCachedType(); + auto valueType = gcnew ValueType(t); + return valueType->As(); +} +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/ValueType.hpp b/csharp-api/REFrameworkNET/ValueType.hpp new file mode 100644 index 000000000..60bc87493 --- /dev/null +++ b/csharp-api/REFrameworkNET/ValueType.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "UnifiedObject.hpp" +#include "TypeDefinition.hpp" + +namespace REFrameworkNET { +public ref class ValueType : public UnifiedObject { +public: + ValueType(TypeDefinition^ t) + { + m_data = gcnew array(t->ValueTypeSize); + pin_ptr data = &m_data[0]; + } + + /// The type of the object + virtual TypeDefinition^ GetTypeDefinition() override { + return m_type; + } + + /// The address of the object as a void* pointer + virtual void* Ptr() override { + pin_ptr data = &m_data[0]; + return data; + } + + /// The address of the object + virtual uintptr_t GetAddress() override { + return (uintptr_t)Ptr(); + } + + generic + virtual T As() override; + + generic + where T : ref class + static T New(); + +private: + array^ m_data{}; + TypeDefinition^ m_type{}; +}; +} \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 558b4d6d9..ec3f60a4e 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -19,7 +19,7 @@ public static REFrameworkNET.PreHookResult isInsidePreHook(Span args) { [MethodHook(typeof(app.CameraManager), nameof(app.CameraManager.isInside), MethodHookType.Post)] public static void isInsidePostHook(ref ulong retval) { if ((retval & 1) != 0) { - REFrameworkNET.API.LogInfo("Camera is inside"); + //REFrameworkNET.API.LogInfo("Camera is inside"); } //Console.WriteLine("Inside post hook (From C#), retval: " + (retval & 1).ToString()); } @@ -39,6 +39,8 @@ public static REFrameworkNET.PreHookResult processNormalAttackPreHook(Span(); via.hid.Mouse.set_ShowCursor(false); @@ -109,6 +111,18 @@ public static void Entry() { var stringInDotNetVM = stringTest.ToString(); // Back in .NET REFrameworkNET.API.LogInfo("Managed string back in .NET: " + stringInDotNetVM); + + var meshes = via.SceneManager.get_MainScene().findComponents(via.render.Mesh.REFType.RuntimeType.As<_System.Type>()); + //var range = via.RangeI.REFType.CreateInstance(0).As(); + var range = REFrameworkNET.ValueType.New(); + range.setMinMax(0, 10); + // print min max to test if this works + REFrameworkNET.API.LogInfo("Range min: " + range.getMin().ToString()); + REFrameworkNET.API.LogInfo("Range max: " + range.getMax().ToString()); + for (int i = 0; i < meshes.get_Length(); i++) { + var mesh = (meshes.get_Item(i) as IObject).As(); + mesh.set_DrawRaytracing(true); + } } public static void TryEnableFrameGeneration() { From 2727d61aeeedc6f272141b58ee8aaa174a45d82f Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 11 May 2024 17:15:56 -0700 Subject: [PATCH 161/207] .NET: Fix T not getting assigned to valuetype --- csharp-api/REFrameworkNET/ValueType.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/csharp-api/REFrameworkNET/ValueType.hpp b/csharp-api/REFrameworkNET/ValueType.hpp index 60bc87493..b3a0c83dd 100644 --- a/csharp-api/REFrameworkNET/ValueType.hpp +++ b/csharp-api/REFrameworkNET/ValueType.hpp @@ -9,6 +9,7 @@ public ref class ValueType : public UnifiedObject { ValueType(TypeDefinition^ t) { m_data = gcnew array(t->ValueTypeSize); + m_type = t; pin_ptr data = &m_data[0]; } From 69a9c38599b42ea7e12532ca3c184c87011fced9 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 12 May 2024 17:14:23 -0700 Subject: [PATCH 162/207] .NET: Add CreateValueType --- csharp-api/REFrameworkNET/TypeDefinition.cpp | 6 ++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 2 ++ 2 files changed, 8 insertions(+) diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index f82edec2c..e9818c656 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -8,6 +8,7 @@ #include "NativeObject.hpp" #include "Proxy.hpp" #include "Utility.hpp" +#include "ValueType.hpp" #include "TypeDefinition.hpp" @@ -150,6 +151,11 @@ namespace REFrameworkNET { return ManagedObject::Get(result); } + REFrameworkNET::ValueType^ TypeDefinition::CreateValueType() + { + return gcnew REFrameworkNET::ValueType(this); + } + REFrameworkNET::TypeInfo^ TypeDefinition::GetTypeInfo() { auto result = m_type->get_type_info(); diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index e4ef8c428..df24f0ae9 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -15,6 +15,7 @@ ref class Method; ref class Field; ref class Property; ref class TypeInfo; +ref class ValueType; value struct InvokeRet; /// @@ -312,6 +313,7 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, /// The flags to use when creating the instance. /// A new instance of type . ManagedObject^ CreateInstance(int32_t flags); + REFrameworkNET::ValueType^ CreateValueType(); TypeDefinition^ GetParentType() { From fd25f3474035ae2098f06f9488d37e4cc4750ef7 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 12 May 2024 17:15:14 -0700 Subject: [PATCH 163/207] .NET: Initial work on generating fields (as properties) --- .../AssemblyGenerator/ClassGenerator.cs | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 7e167bacc..6afe5559d 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -18,6 +18,7 @@ public class ClassGenerator { private string className; private REFrameworkNET.TypeDefinition t; private List methods = []; + private List fields = []; public List usingTypes = []; private TypeDeclarationSyntax? typeDeclaration; private bool addedNewKeyword = false; @@ -62,6 +63,34 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { methods.Add(method); } + foreach (var field in t_.Fields) { + // Means we've entered the parent type + if (field.DeclaringType != t_) { + break; + } + + if (field.Name == null) { + continue; + } + + if (field.Type == null || field.Type.FullName == null) { + REFrameworkNET.API.LogError("Field " + field.Name + " has a null field type"); + continue; + } + + fields.Add(field); + + var fieldName = new string(field.Name); + + if (fieldName.StartsWith("<") && fieldName.EndsWith("k__BackingField")) { + fieldName = fieldName[1..fieldName.IndexOf(">k__")]; + } + + // remove any methods that start with get/set_{field.Name} + // because we're going to make them properties instead + methods.RemoveAll(method => method.Name == "get_" + fieldName || method.Name == "set_" + fieldName); + } + typeDeclaration = Generate(); } @@ -237,6 +266,7 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy var refProxyFieldDecl = SyntaxFactory.FieldDeclaration(refProxyVarDecl).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); typeDeclaration = GenerateMethods(baseTypes); + typeDeclaration = GenerateFields(baseTypes); if (baseTypes.Count > 0 && typeDeclaration != null) { refTypeFieldDecl = refTypeFieldDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); @@ -258,6 +288,83 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy return GenerateNestedTypes(); } + private TypeDeclarationSyntax GenerateFields(List baseTypes) { + + if (typeDeclaration == null) { + throw new Exception("Type declaration is null"); // This should never happen + } + + if (fields.Count == 0) { + return typeDeclaration!; + } + + List validFields = []; + + foreach (var field in fields) { + if (field == null) { + continue; + } + + if (field.Name == null) { + continue; + } + + if (field.Type == null) { + continue; + } + + if (field.Type.FullName == null) { + continue; + } + + if (field.Type.FullName.Contains('!')) { + continue; + } + + // We don't want any of the properties to be "void" properties + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(field.Type.FullName)) { + continue; + } + + validFields.Add(field); + } + + var matchingFields = validFields + .Select(field => { + var fieldType = MakeProperType(field.Type, t); + var fieldName = new string(field.Name); + + // Replace the k backingfield crap + if (fieldName.StartsWith("<") && fieldName.EndsWith("k__BackingField")) { + fieldName = fieldName[1..fieldName.IndexOf(">k__")]; + } + + // So this is actually going to be made a property with get/set instead of an actual field + // 1. Because interfaces can't have fields + // 2. Because we don't actually have a concrete reference to the field in our VM, so we'll be a facade for the field + var properyDeclaration = SyntaxFactory.PropertyDeclaration(fieldType, fieldName) + .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) + .AddAccessorListAccessors( + SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), + SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) + ); + + if (field.IsStatic()) { + properyDeclaration = properyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + } + + if (this.t.ParentType != null && this.t.ParentType.FindField(field.Name) != null) { + properyDeclaration = properyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + } + + return properyDeclaration; + }); + + return typeDeclaration.AddMembers(matchingFields.ToArray()); + } + private TypeDeclarationSyntax GenerateMethods(List baseTypes) { if (typeDeclaration == null) { throw new Exception("Type declaration is null"); // This should never happen From 47e167479b5df229814be7dd05c30487c40a3e9e Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 12 May 2024 17:47:10 -0700 Subject: [PATCH 164/207] .NET: Add Field.GetIndex --- csharp-api/REFrameworkNET/Field.hpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp index cbb148896..fa56a555b 100644 --- a/csharp-api/REFrameworkNET/Field.hpp +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -142,6 +142,17 @@ public ref class Field { throw gcnew System::NotImplementedException(); } + uint32_t GetIndex() { + return m_field->get_index(); + } + + property uint32_t Index { + public: + uint32_t get() { + return GetIndex(); + } + } + private: const reframework::API::Field* m_field; }; From 4d78afa37a68681b4cd660aca8b0ca3c9c3edac8 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 12 May 2024 18:58:39 -0700 Subject: [PATCH 165/207] .NET: Initial work on field facades --- .../AssemblyGenerator/ClassGenerator.cs | 16 ++++-- .../REFrameworkNET/Attributes/Method.hpp | 53 ++++++++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 6afe5559d..634ea4056 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -342,12 +342,22 @@ private TypeDeclarationSyntax GenerateFields(List baseType // So this is actually going to be made a property with get/set instead of an actual field // 1. Because interfaces can't have fields // 2. Because we don't actually have a concrete reference to the field in our VM, so we'll be a facade for the field + var fieldFacadeGetter = SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( + SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), + SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET::FieldFacadeType.Getter)")) + ); + + var fieldFacadeSetter = SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( + SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), + SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET::FieldFacadeType.Setter)")) + ); + var properyDeclaration = SyntaxFactory.PropertyDeclaration(fieldType, fieldName) .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) .AddAccessorListAccessors( - SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).AddAttributeLists(fieldFacadeGetter) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), - SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).AddAttributeLists(fieldFacadeSetter) .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) ); @@ -423,7 +433,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp methodDeclaration = methodDeclaration.AddAttributeLists( SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ")"))) + SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ", global::REFrameworkNET::FieldFacadeType.None)"))) ); bool anyOutParams = false; diff --git a/csharp-api/REFrameworkNET/Attributes/Method.hpp b/csharp-api/REFrameworkNET/Attributes/Method.hpp index 6ba6b9219..56fa75b77 100644 --- a/csharp-api/REFrameworkNET/Attributes/Method.hpp +++ b/csharp-api/REFrameworkNET/Attributes/Method.hpp @@ -4,6 +4,14 @@ #include "../TDB.hpp" +namespace REFrameworkNET { + public enum class FieldFacadeType : uint8_t { + None, + Getter, + Setter + }; +} + namespace REFrameworkNET::Attributes { /// Attribute to mark a reference assembly method for easier lookup. [System::AttributeUsage(System::AttributeTargets::Method)] @@ -34,8 +42,21 @@ namespace REFrameworkNET::Attributes { } public: - MethodAttribute(uint32_t methodIndex) { + MethodAttribute(uint32_t methodIndex, FieldFacadeType fieldFacadeType) { + if (fieldFacadeType != FieldFacadeType::None) { + field = REFrameworkNET::TDB::Get()->GetField(methodIndex); + fieldFacade = fieldFacadeType; + + if (field != nullptr) { + signature = nullptr; + isVirtual = false; + } + + return; + } + method = REFrameworkNET::TDB::Get()->GetMethod(methodIndex); + fieldFacade = FieldFacadeType::None; if (method != nullptr && method->IsVirtual()) { signature = method->GetMethodSignature(); @@ -43,7 +64,23 @@ namespace REFrameworkNET::Attributes { } } + property bool IsField { + bool get() { + return field != nullptr; + } + } + + property FieldFacadeType FieldFacade { + FieldFacadeType get() { + return fieldFacade; + } + } + REFrameworkNET::Method^ GetMethod(REFrameworkNET::TypeDefinition^ td) { + if (method == nullptr) { + return nullptr; + } + // Signature is used for virtual methods if (isVirtual && signature != nullptr && td != method->GetDeclaringType()) { if (auto result = td->GetMethod(signature); result != nullptr) { @@ -54,8 +91,22 @@ namespace REFrameworkNET::Attributes { return method; } + REFrameworkNET::Field^ GetField() { + return field; + } + + System::Object^ GetUnderlyingObject(REFrameworkNET::TypeDefinition^ td) { + if (auto method = GetMethod(td); method != nullptr) { + return method; + } + + return field; + } + private: REFrameworkNET::Method^ method; + REFrameworkNET::Field^ field; + FieldFacadeType fieldFacade{ FieldFacadeType::None }; System::String^ signature; bool isVirtual; }; From e19b67a06ff9b584df0bb646f859b8b0632ae51d Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 01:28:04 -0700 Subject: [PATCH 166/207] .NET: Fix incorrect field facade naming --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 634ea4056..6e962e830 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -344,12 +344,12 @@ private TypeDeclarationSyntax GenerateFields(List baseType // 2. Because we don't actually have a concrete reference to the field in our VM, so we'll be a facade for the field var fieldFacadeGetter = SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET::FieldFacadeType.Getter)")) + SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.Getter)")) ); var fieldFacadeSetter = SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET::FieldFacadeType.Setter)")) + SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.Setter)")) ); var properyDeclaration = SyntaxFactory.PropertyDeclaration(fieldType, fieldName) @@ -433,7 +433,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp methodDeclaration = methodDeclaration.AddAttributeLists( SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), - SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ", global::REFrameworkNET::FieldFacadeType.None)"))) + SyntaxFactory.ParseAttributeArgumentList("(" + method.GetIndex().ToString() + ", global::REFrameworkNET.FieldFacadeType.None)"))) ); bool anyOutParams = false; From f2f3e2dc72c3330c171ddc86cb4ebb703772d67f Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 01:28:57 -0700 Subject: [PATCH 167/207] .NET: Migrate data boxing into reusable function --- csharp-api/CMakeLists.txt | 1 + csharp-api/REFrameworkNET/Method.cpp | 101 +------------------- csharp-api/REFrameworkNET/Utility.cpp | 131 ++++++++++++++++++++++++++ csharp-api/REFrameworkNET/Utility.hpp | 7 ++ 4 files changed, 140 insertions(+), 100 deletions(-) create mode 100644 csharp-api/REFrameworkNET/Utility.cpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index b0525aa7e..f1b9d20cb 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -191,6 +191,7 @@ set(csharp-api_SOURCES "REFrameworkNET/TDB.cpp" "REFrameworkNET/TypeDefinition.cpp" "REFrameworkNET/UnifiedObject.cpp" + "REFrameworkNET/Utility.cpp" "REFrameworkNET/VM.cpp" "REFrameworkNET/ValueType.cpp" "REFrameworkNET/API.hpp" diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 878645108..555886347 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -344,106 +344,7 @@ bool Method::HandleInvokeMember_Internal(System::Object^ obj, arrayIsValueType()) { - if (tempResult.qword == 0) { - result = nullptr; - return true; - } - - if (returnType->GetVMObjType() == VMObjType::Object) { - result = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult.qword); - return true; - } - - if (returnType->GetVMObjType() == VMObjType::String) { - auto strObject = (reframework::API::ManagedObject*)tempResult.qword; - auto strType = strObject->get_type_definition(); - const auto firstCharField = strType->find_field("_firstChar"); - uint32_t offset = 0; - - if (firstCharField != nullptr) { - offset = firstCharField->get_offset_from_base(); - } else { - const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)tempResult.qword - sizeof(void*)); - offset = fieldOffset + 4; - } - - wchar_t* chars = (wchar_t*)((uintptr_t)strObject + offset); - result = gcnew System::String(chars); - return true; - } - - if (returnType->GetVMObjType() == VMObjType::Array) { - // TODO? Implement array - result = REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)tempResult.qword); - return true; - } - - // TODO: other managed types - result = gcnew REFrameworkNET::NativeObject((uintptr_t)tempResult.qword, returnType); - return true; - } - - if (returnType->IsEnum()) { - if (auto underlying = returnType->GetUnderlyingType(); underlying != nullptr) { - returnType = underlying; // easy mode - } - } - - #define CONCAT_X_C(X, DOT, C) X ## DOT ## C - - #define MAKE_TYPE_HANDLER_2(X, C, Y, Z) \ - case CONCAT_X_C(#X, ".", #C)_fnv: \ - result = gcnew X::C((Y)tempResult.Z); \ - break; - - switch (returnType->GetFNV64Hash()) { - MAKE_TYPE_HANDLER_2(System, Boolean, bool, byte) - MAKE_TYPE_HANDLER_2(System, Byte, uint8_t, byte) - MAKE_TYPE_HANDLER_2(System, UInt16, uint16_t, word) - MAKE_TYPE_HANDLER_2(System, UInt32, uint32_t, dword) - MAKE_TYPE_HANDLER_2(System, UInt64, uint64_t, qword) - MAKE_TYPE_HANDLER_2(System, SByte, int8_t, byte) - MAKE_TYPE_HANDLER_2(System, Int16, int16_t, word) - MAKE_TYPE_HANDLER_2(System, Int32, int32_t, dword) - MAKE_TYPE_HANDLER_2(System, Int64, int64_t, qword) - // Because invoke wrappers returning a single actually return a double - // for consistency purposes - MAKE_TYPE_HANDLER_2(System, Single, double, d) - MAKE_TYPE_HANDLER_2(System, Double, double, d) - case "System.RuntimeTypeHandle"_fnv: { - if (tempResult.qword == 0) { - result = nullptr; - return true; - } - - result = TypeDefinition::GetInstance((::REFrameworkTypeDefinitionHandle)tempResult.qword); - break; - } - case "System.RuntimeMethodHandle"_fnv: { - if (tempResult.qword == 0) { - result = nullptr; - return true; - } - - result = Method::GetInstance((::REFrameworkMethodHandle)tempResult.qword); - break; - } - case "System.RuntimeFieldHandle"_fnv: { - if (tempResult.qword == 0) { - result = nullptr; - return true; - } - - result = Field::GetInstance((::REFrameworkFieldHandle)tempResult.qword); - break; - } - default: - result = safe_cast(REFrameworkNET::InvokeRet::FromNative(tempResult)); - break; - } - + result = Utility::BoxData((uintptr_t*)tempResult.bytes.data(), returnType, true); return true; } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Utility.cpp b/csharp-api/REFrameworkNET/Utility.cpp new file mode 100644 index 000000000..25b455963 --- /dev/null +++ b/csharp-api/REFrameworkNET/Utility.cpp @@ -0,0 +1,131 @@ +#include "ManagedObject.hpp" +#include "NativeObject.hpp" +#include "ValueType.hpp" + +#include "TypeDefinition.hpp" +#include "Field.hpp" +#include "Method.hpp" + +#include "Utility.hpp" + +namespace REFrameworkNET { +System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke) { + System::Object^ result = nullptr; + + if (t == nullptr) { + return nullptr; + } + + // Check the return type of the method and return it as a NativeObject if possible + if (!t->IsValueType()) { + if (ptr == nullptr || *ptr == 0) { + return nullptr; + } + + const auto vm_object_type = t->GetVMObjType(); + + if (vm_object_type == VMObjType::Object) { + return REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)*ptr); + } + + // TODO: Clean this up + if (vm_object_type == VMObjType::String) { + auto strObject = (reframework::API::ManagedObject*)*ptr; + auto strType = strObject->get_type_definition(); + const auto firstCharField = strType->find_field("_firstChar"); + uint32_t offset = 0; + + if (firstCharField != nullptr) { + offset = firstCharField->get_offset_from_base(); + } else { + const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)*ptr - sizeof(void*)); + offset = fieldOffset + 4; + } + + wchar_t* chars = (wchar_t*)((uintptr_t)strObject + offset); + return gcnew System::String(chars); + } + + if (vm_object_type == VMObjType::Array) { + // TODO? Implement array + return REFrameworkNET::ManagedObject::Get((::REFrameworkManagedObjectHandle)*ptr); + } + + // TODO: other managed types + return gcnew REFrameworkNET::NativeObject((uintptr_t)*ptr, t); + } + + if (t->IsEnum()) { + if (auto underlying = t->GetUnderlyingType(); underlying != nullptr) { + t = underlying; // easy mode + } + } + + #define CONCAT_X_C(X, DOT, C) X ## DOT ## C + + #define MAKE_TYPE_HANDLER_2(X, C, Y, Z) \ + case CONCAT_X_C(#X, ".", #C)_fnv: \ + result = gcnew X::C(*(Y*)ptr); \ + break; + + switch (t->GetFNV64Hash()) { + MAKE_TYPE_HANDLER_2(System, Boolean, bool, byte) + MAKE_TYPE_HANDLER_2(System, Byte, uint8_t, byte) + MAKE_TYPE_HANDLER_2(System, UInt16, uint16_t, word) + MAKE_TYPE_HANDLER_2(System, UInt32, uint32_t, dword) + MAKE_TYPE_HANDLER_2(System, UInt64, uint64_t, qword) + MAKE_TYPE_HANDLER_2(System, SByte, int8_t, byte) + MAKE_TYPE_HANDLER_2(System, Int16, int16_t, word) + MAKE_TYPE_HANDLER_2(System, Int32, int32_t, dword) + MAKE_TYPE_HANDLER_2(System, Int64, int64_t, qword) + MAKE_TYPE_HANDLER_2(System, Double, double, d) + // Because invoke wrappers returning a single actually return a double + // for consistency purposes + //MAKE_TYPE_HANDLER_2(System, Single, double, d) + case "System.Single"_fnv: { + if (fromInvoke) { + result = gcnew System::Double(*(double*)ptr); + break; + } + + result = gcnew System::Single(*(float*)ptr); + break; + } + case "System.RuntimeTypeHandle"_fnv: { + if (ptr == 0) { + return nullptr; + } + + result = TypeDefinition::GetInstance((::REFrameworkTypeDefinitionHandle)*ptr); + break; + } + case "System.RuntimeMethodHandle"_fnv: { + if (ptr == 0) { + return nullptr; + } + + result = Method::GetInstance((::REFrameworkMethodHandle)*ptr); + break; + } + case "System.RuntimeFieldHandle"_fnv: { + if (ptr == 0) { + result = nullptr; + return true; + } + + result = Field::GetInstance((::REFrameworkFieldHandle)*ptr); + break; + } + default: + //result = safe_cast(REFrameworkNET::InvokeRet::FromNative(tempResult)); + { + // Here's hoping this works + auto vt = gcnew REFrameworkNET::ValueType(t); + memcpy(vt->Ptr(), ptr, t->ValueTypeSize); + } + break; + } + + return result; +} +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Utility.hpp b/csharp-api/REFrameworkNET/Utility.hpp index bffbe576f..8cc870b25 100644 --- a/csharp-api/REFrameworkNET/Utility.hpp +++ b/csharp-api/REFrameworkNET/Utility.hpp @@ -4,6 +4,8 @@ #include namespace REFrameworkNET { + ref class TypeDefinition; + static constexpr auto hash(std::string_view data) { size_t result = 0xcbf29ce484222325; @@ -18,4 +20,9 @@ namespace REFrameworkNET { consteval auto operator "" _fnv(const char* s, size_t) { return hash(s); } + + ref class Utility { + public: + static System::Object^ BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke); + }; } \ No newline at end of file From ea85cdd6007794b7d8b39657a36ec43ef885b7e2 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 02:17:25 -0700 Subject: [PATCH 168/207] .NET: Initial support for getting/setting fields w/ reference assemblies --- csharp-api/CMakeLists.txt | 1 + csharp-api/REFrameworkNET/Field.cpp | 89 +++++++++++++++++++++ csharp-api/REFrameworkNET/Field.hpp | 29 ++++--- csharp-api/REFrameworkNET/IProxyable.hpp | 1 + csharp-api/REFrameworkNET/ManagedObject.hpp | 4 + csharp-api/REFrameworkNET/Proxy.hpp | 23 +++++- csharp-api/REFrameworkNET/UnifiedObject.cpp | 36 +-------- csharp-api/REFrameworkNET/UnifiedObject.hpp | 4 + csharp-api/test/Test/Test.cs | 6 ++ 9 files changed, 145 insertions(+), 48 deletions(-) create mode 100644 csharp-api/REFrameworkNET/Field.cpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index f1b9d20cb..8bcc8c223 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -179,6 +179,7 @@ set(csharp-api_SOURCES "REFrameworkNET/Attributes/MethodHook.cpp" "REFrameworkNET/Attributes/Plugin.cpp" "REFrameworkNET/Callbacks.cpp" + "REFrameworkNET/Field.cpp" "REFrameworkNET/ManagedObject.cpp" "REFrameworkNET/Method.cpp" "REFrameworkNET/MethodHook.cpp" diff --git a/csharp-api/REFrameworkNET/Field.cpp b/csharp-api/REFrameworkNET/Field.cpp new file mode 100644 index 000000000..28acd2cdb --- /dev/null +++ b/csharp-api/REFrameworkNET/Field.cpp @@ -0,0 +1,89 @@ +#include "ManagedObject.hpp" +#include "Utility.hpp" + +#include "Field.hpp" + +namespace REFrameworkNET { + generic + T Field::GetDataT(uintptr_t obj, bool isValueType) { + const auto raw_data = GetDataRaw(obj, isValueType); + if (raw_data == 0) { + return T(); + } + + if (T::typeid->IsValueType) { + return (T)System::Runtime::InteropServices::Marshal::PtrToStructure(System::IntPtr((void*)raw_data), T::typeid); + } + + auto mo = ManagedObject::Get((reframework::API::ManagedObject*)raw_data); + + if (T::typeid == ManagedObject::typeid) { + return (T)mo; + } + + //throw gcnew System::NotImplementedException(); + return mo->As(); + } + + System::Object^ Field::GetDataBoxed(uintptr_t obj, bool isValueType) { + auto raw_data = GetDataRaw(obj, isValueType); + + if (raw_data == 0) { + return nullptr; + } + + return Utility::BoxData((uintptr_t*)raw_data, this->Type, false); + } + + void Field::SetDataBoxed(uintptr_t obj, System::Object^ value, bool isValueType) { + auto data_ptr = GetDataRaw(obj, isValueType); + + if (data_ptr == 0) { + return; + } + + const auto field_type = this->GetType(); + + if (field_type == nullptr) { + return; + } + + if (!field_type->IsValueType()) { + const auto iobject = dynamic_cast(value); + + if (iobject != nullptr) { + *(uintptr_t*)data_ptr = iobject->GetAddress(); + } + + return; // Don't think there's any other way to set a reference type + } + + const uintptr_t addr = IsStatic() ? 0 : obj; + const auto vm_obj_type = field_type->GetVMObjType(); + + #define MAKE_TYPE_HANDLER_SET(X, Y) \ + case ##X##_fnv: \ + this->GetData<##Y##>(addr, isValueType) = (Y)value; \ + break; + + switch (field_type->GetFNV64Hash()) { + MAKE_TYPE_HANDLER_SET("System.Boolean", bool) + MAKE_TYPE_HANDLER_SET("System.Byte", uint8_t) + MAKE_TYPE_HANDLER_SET("System.SByte", int8_t) + MAKE_TYPE_HANDLER_SET("System.Int16", int16_t) + MAKE_TYPE_HANDLER_SET("System.UInt16", uint16_t) + MAKE_TYPE_HANDLER_SET("System.Int32", int32_t) + MAKE_TYPE_HANDLER_SET("System.UInt32", uint32_t) + MAKE_TYPE_HANDLER_SET("System.Int64", int64_t) + MAKE_TYPE_HANDLER_SET("System.UInt64", uint64_t) + MAKE_TYPE_HANDLER_SET("System.Single", float) + MAKE_TYPE_HANDLER_SET("System.Double", double) + MAKE_TYPE_HANDLER_SET("System.Char", wchar_t) + MAKE_TYPE_HANDLER_SET("System.IntPtr", intptr_t) + MAKE_TYPE_HANDLER_SET("System.UIntPtr", uintptr_t) + + default: + break; + } + } +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp index fa56a555b..f96427e3e 100644 --- a/csharp-api/REFrameworkNET/Field.hpp +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -129,18 +129,23 @@ public ref class Field { // For .NET generic - T GetDataT(uintptr_t obj, bool isValueType) { - const auto raw_data = GetDataRaw(obj, isValueType); - if (raw_data == 0) { - return T(); - } - - if (this->Type->IsValueType()) { - return (T)System::Runtime::InteropServices::Marshal::PtrToStructure(System::IntPtr((void*)raw_data), T::typeid); - } - - throw gcnew System::NotImplementedException(); - } + T GetDataT(uintptr_t obj, bool isValueType); + + /// + /// Get the field data as a boxed object + /// + /// The object to get the field data from + /// Whether the object holding the field is a value type + /// The field data as a boxed object + System::Object^ GetDataBoxed(uintptr_t obj, bool isValueType); + + /// + /// Set the field data from a boxed object + /// + /// The object to set the field data on + /// The value to set the field data to + /// Whether the object holding the field is a value type + void SetDataBoxed(uintptr_t obj, System::Object^ value, bool isValueType); uint32_t GetIndex() { return m_field->get_index(); diff --git a/csharp-api/REFrameworkNET/IProxyable.hpp b/csharp-api/REFrameworkNET/IProxyable.hpp index 812eb16b7..f21759c44 100644 --- a/csharp-api/REFrameworkNET/IProxyable.hpp +++ b/csharp-api/REFrameworkNET/IProxyable.hpp @@ -9,6 +9,7 @@ public interface class IProxyable { void* Ptr(); uintptr_t GetAddress(); virtual bool IsProxy(); + virtual bool IsManaged(); bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); bool HandleInvokeMember_Internal(uint32_t methodIndex, array^ args, System::Object^% result); diff --git a/csharp-api/REFrameworkNET/ManagedObject.hpp b/csharp-api/REFrameworkNET/ManagedObject.hpp index 8d706ad2f..d77e0b26f 100644 --- a/csharp-api/REFrameworkNET/ManagedObject.hpp +++ b/csharp-api/REFrameworkNET/ManagedObject.hpp @@ -336,6 +336,10 @@ public ref class ManagedObject : public REFrameworkNET::UnifiedObject TypeInfo^ GetTypeInfo(); public: // IObject + virtual bool IsManaged() override { + return true; + } + virtual void* Ptr() override { return (void*)m_object; } diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 34b25e0ca..032b069bf 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -77,6 +77,10 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public virtual IProxy^ GetProxy(System::Type^ type) { return Instance->GetProxy(type); } + + virtual bool IsManaged() { + return Instance->IsManaged(); + } static T Create(IObject^ target) { auto proxy = Reflection::DispatchProxy::Create^>(); @@ -153,7 +157,22 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public if (methodAttribute != nullptr) { if (iobject != nullptr) { - auto method = methodAttribute->GetMethod(iobject->GetTypeDefinition()); + auto underlyingObject = methodAttribute->GetUnderlyingObject(iobject->GetTypeDefinition()); + + // Property getter/setters for fields + if (methodAttribute->IsField) { + auto field = (REFrameworkNET::Field^)underlyingObject; + bool isValueType = !IsManaged() && GetTypeDefinition()->IsValueType(); + + if (methodAttribute->FieldFacade == REFrameworkNET::FieldFacadeType::Getter) { + return field->GetDataBoxed(iobject->GetAddress(), isValueType); + } + + field->SetDataBoxed(iobject->GetAddress(), args[0], isValueType); + return nullptr; + } + + auto method = (REFrameworkNET::Method^)underlyingObject; return method->InvokeBoxed(targetMethod->ReturnType, iobject, args); } else { throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); @@ -166,6 +185,8 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public if (method != nullptr) { return method->InvokeBoxed(targetMethod->ReturnType, iobject, args); } + + throw gcnew System::InvalidOperationException("Proxy: Method not found"); } else { throw gcnew System::InvalidOperationException("Proxy: T2 must be IObject derived"); } diff --git a/csharp-api/REFrameworkNET/UnifiedObject.cpp b/csharp-api/REFrameworkNET/UnifiedObject.cpp index 7e78d4e84..08cc545fe 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.cpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.cpp @@ -231,41 +231,7 @@ namespace REFrameworkNET { if (field != nullptr) { - const auto field_type = field->GetType(); - - if (field_type == nullptr) { - return false; - } - - const auto raw_ft = (reframework::API::TypeDefinition*)field_type; - const uintptr_t addr = field->IsStatic() ? 0 : this->GetAddress(); - const auto vm_obj_type = field_type->GetVMObjType(); - - #define MAKE_TYPE_HANDLER_SET(X, Y) \ - case ##X##_fnv: \ - field->GetData<##Y##>(addr, field_type->IsValueType()) = (Y)value; \ - break; - - switch (REFrameworkNET::hash(raw_ft->get_full_name())) { - MAKE_TYPE_HANDLER_SET("System.Boolean", bool) - MAKE_TYPE_HANDLER_SET("System.Byte", uint8_t) - MAKE_TYPE_HANDLER_SET("System.SByte", int8_t) - MAKE_TYPE_HANDLER_SET("System.Int16", int16_t) - MAKE_TYPE_HANDLER_SET("System.UInt16", uint16_t) - MAKE_TYPE_HANDLER_SET("System.Int32", int32_t) - MAKE_TYPE_HANDLER_SET("System.UInt32", uint32_t) - MAKE_TYPE_HANDLER_SET("System.Int64", int64_t) - MAKE_TYPE_HANDLER_SET("System.UInt64", uint64_t) - MAKE_TYPE_HANDLER_SET("System.Single", float) - MAKE_TYPE_HANDLER_SET("System.Double", double) - MAKE_TYPE_HANDLER_SET("System.Char", wchar_t) - MAKE_TYPE_HANDLER_SET("System.IntPtr", intptr_t) - MAKE_TYPE_HANDLER_SET("System.UIntPtr", uintptr_t) - - default: - break; - } - + field->SetDataBoxed(this->GetAddress(), value, !this->IsManaged() && this->GetTypeDefinition()->IsValueType()); return true; } diff --git a/csharp-api/REFrameworkNET/UnifiedObject.hpp b/csharp-api/REFrameworkNET/UnifiedObject.hpp index 93beb0ba2..7380673a6 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.hpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.hpp @@ -28,6 +28,10 @@ public ref class UnifiedObject abstract : public System::Dynamic::DynamicObject, return ProxyPool::GetIfExists(this, type); } + virtual bool IsManaged() { + return false; + } + virtual bool HandleInvokeMember_Internal(System::String^ methodName, array^ args, System::Object^% result); virtual bool HandleInvokeMember_Internal(uint32_t methodIndex, array^ args, System::Object^% result); virtual bool HandleInvokeMember_Internal(System::Object^ methodObj, array^ args, System::Object^% result); diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index ec3f60a4e..ccb991031 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Dynamic; using System.Reflection; +using app; using ImGuiNET; using REFrameworkNET; using REFrameworkNET.Attributes; @@ -116,6 +117,11 @@ public static void Entry() { //var range = via.RangeI.REFType.CreateInstance(0).As(); var range = REFrameworkNET.ValueType.New(); range.setMinMax(0, 10); + var testVec = REFrameworkNET.ValueType.New(); + System.Console.WriteLine("Test vec x: " + testVec.x); + testVec.x = 1.0f; + System.Console.WriteLine("Test vec x: " + testVec.x); + //testVec[0] = 1.0f; // print min max to test if this works REFrameworkNET.API.LogInfo("Range min: " + range.getMin().ToString()); REFrameworkNET.API.LogInfo("Range max: " + range.getMax().ToString()); From 695c1de2175353b278dc138af59baee2d262f6a5 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 03:41:25 -0700 Subject: [PATCH 169/207] .NET: Initial support for static fields w/ reference assemblies --- .../AssemblyGenerator/ClassGenerator.cs | 43 ++++++++--- csharp-api/AssemblyGenerator/Generator.cs | 2 + csharp-api/REFrameworkNET/Field.cpp | 30 +++++++- csharp-api/REFrameworkNET/Field.hpp | 9 +++ csharp-api/REFrameworkNET/Method.cpp | 74 +------------------ csharp-api/REFrameworkNET/Utility.cpp | 74 +++++++++++++++++++ csharp-api/REFrameworkNET/Utility.hpp | 1 + csharp-api/test/Test/Test.cs | 19 ++++- 8 files changed, 164 insertions(+), 88 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 6e962e830..b2734a408 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -352,24 +352,45 @@ private TypeDeclarationSyntax GenerateFields(List baseType SyntaxFactory.ParseAttributeArgumentList("(" + field.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.Setter)")) ); - var properyDeclaration = SyntaxFactory.PropertyDeclaration(fieldType, fieldName) - .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) - .AddAccessorListAccessors( - SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).AddAttributeLists(fieldFacadeGetter) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)), - SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).AddAttributeLists(fieldFacadeSetter) - .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)) - ); + AccessorDeclarationSyntax getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).AddAttributeLists(fieldFacadeGetter); + AccessorDeclarationSyntax setter = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).AddAttributeLists(fieldFacadeSetter); + + var propertyDeclaration = SyntaxFactory.PropertyDeclaration(fieldType, fieldName) + .AddModifiers([SyntaxFactory.Token(SyntaxKind.PublicKeyword)]); if (field.IsStatic()) { - properyDeclaration = properyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + + // Now we must add a body to it that actually calls the method + // We have our REFType field, so we can lookup the method and call it + // Make a private static field to hold the REFrameworkNET.Method + var internalFieldName = "INTERNAL_" + fieldName + field.GetIndex().ToString(); + var fieldVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Field")) + .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetField(\"" + field.GetName() + "\")")))); + + var fieldDeclaration = SyntaxFactory.FieldDeclaration(fieldVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + internalFieldDeclarations.Add(fieldDeclaration); + + List bodyStatementsSetter = []; + List bodyStatementsGetter = []; + + bodyStatementsGetter.Add(SyntaxFactory.ParseStatement("return (" + fieldType.GetText().ToString() + ")" + internalFieldName + ".GetDataBoxed(typeof(" + fieldType.GetText().ToString() + "), 0, false);")); + bodyStatementsSetter.Add(SyntaxFactory.ParseStatement(internalFieldName + ".SetDataBoxed(0, new object[] {value}, false);")); + + getter = getter.AddBodyStatements(bodyStatementsGetter.ToArray()); + setter = setter.AddBodyStatements(bodyStatementsSetter.ToArray()); + } else { + getter = getter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + setter = setter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); } + propertyDeclaration = propertyDeclaration.AddAccessorListAccessors(getter, setter); + if (this.t.ParentType != null && this.t.ParentType.FindField(field.Name) != null) { - properyDeclaration = properyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); } - return properyDeclaration; + return propertyDeclaration; }); return typeDeclaration.AddMembers(matchingFields.ToArray()); diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 08d2c4a36..ddbbe52a0 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -711,6 +711,8 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin REFrameworkNET.API.LocalFrameGC(); } + System.Console.WriteLine("Compiling " + strippedAssemblyName + " with " + compilationUnits.Count + " compilation units..."); + List syntaxTrees = new List(); var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); diff --git a/csharp-api/REFrameworkNET/Field.cpp b/csharp-api/REFrameworkNET/Field.cpp index 28acd2cdb..063e030ac 100644 --- a/csharp-api/REFrameworkNET/Field.cpp +++ b/csharp-api/REFrameworkNET/Field.cpp @@ -1,5 +1,7 @@ #include "ManagedObject.hpp" #include "Utility.hpp" +#include "ValueType.hpp" +#include "Proxy.hpp" #include "Field.hpp" @@ -35,6 +37,10 @@ namespace REFrameworkNET { return Utility::BoxData((uintptr_t*)raw_data, this->Type, false); } + System::Object^ Field::GetDataBoxed(System::Type^ targetReturnType, uintptr_t obj, bool isValueType) { + return Utility::TranslateBoxedData(targetReturnType, GetDataBoxed(obj, isValueType)); + } + void Field::SetDataBoxed(uintptr_t obj, System::Object^ value, bool isValueType) { auto data_ptr = GetDataRaw(obj, isValueType); @@ -49,10 +55,19 @@ namespace REFrameworkNET { } if (!field_type->IsValueType()) { - const auto iobject = dynamic_cast(value); + auto iobject = dynamic_cast(value); if (iobject != nullptr) { *(uintptr_t*)data_ptr = iobject->GetAddress(); + + // Add a reference onto the object now that something else is holding onto it + auto proxy = dynamic_cast(iobject); + auto managedObject = proxy != nullptr ? dynamic_cast(proxy->GetInstance()) : dynamic_cast(iobject); + + if (managedObject != nullptr) { + managedObject->Globalize(); // Globalize it if it's not already + managedObject->AddRef(); // Add a "dangling" reference + } } return; // Don't think there's any other way to set a reference type @@ -83,7 +98,20 @@ namespace REFrameworkNET { MAKE_TYPE_HANDLER_SET("System.UIntPtr", uintptr_t) default: + { + const auto iobject = dynamic_cast(value); + const auto iobject_td = iobject != nullptr ? iobject->GetTypeDefinition() : nullptr; + + if (iobject != nullptr && iobject_td == field_type) { + if (iobject->IsManaged()) { + memcpy((void*)data_ptr, (void*)((uintptr_t)iobject->Ptr() + iobject_td->GetFieldPtrOffset()), field_type->ValueTypeSize); + } else { + memcpy((void*)data_ptr, iobject->Ptr(), field_type->ValueTypeSize); + } + } + break; } + } } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp index f96427e3e..708470072 100644 --- a/csharp-api/REFrameworkNET/Field.hpp +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -139,6 +139,15 @@ public ref class Field { /// The field data as a boxed object System::Object^ GetDataBoxed(uintptr_t obj, bool isValueType); + /// + /// Get the field data as a boxed object, but proxies the object to the target type + /// + /// The target type to proxy the object to. + /// The object to get the field data from + /// Whether the object holding the field is a value type + /// The field data as a boxed object + System::Object^ GetDataBoxed(System::Type^ targetReturnType, uintptr_t obj, bool isValueType); + /// /// Set the field data from a boxed object /// diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index 555886347..d6a5bcae7 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -133,83 +133,11 @@ REFrameworkNET::InvokeRet Method::Invoke(System::Object^ obj, array^ args) { System::Object^ result = nullptr; this->HandleInvokeMember_Internal(obj, args, result); - if (result == nullptr) { - return nullptr; // ez - } - - if (targetReturnType == nullptr) { - return result; - } - - if (!targetReturnType->IsPrimitive && !targetReturnType->IsEnum && !targetReturnType->IsInterface) { - if (targetReturnType == String::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::ManagedObject::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::NativeObject::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::TypeDefinition::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::Method::typeid) { - return result; - } - - if (targetReturnType == REFrameworkNET::Field::typeid) { - return result; - } - } - - if (targetReturnType->IsEnum) { - auto underlyingType = targetReturnType->GetEnumUnderlyingType(); - - if (underlyingType != nullptr) { - auto underlyingResult = Convert::ChangeType(result, underlyingType); - return Enum::ToObject(targetReturnType, underlyingResult); - } - } - - if (this->DeclaringType == nullptr) { - return result; - } - - if (!targetReturnType->IsPrimitive && targetReturnType->IsInterface) { - auto iobjectResult = dynamic_cast(result); - - if (iobjectResult != nullptr && targetReturnType->IsInterface) { - // Caching mechanism to prevent creating multiple proxies for the same object and type so we dont stress the GC - if (auto existingProxy = iobjectResult->GetProxy(targetReturnType); existingProxy != nullptr) { - return existingProxy; - } - - auto proxy = System::Reflection::DispatchProxy::Create(targetReturnType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(targetReturnType, result->GetType())); - ((IProxy^)proxy)->SetInstance(iobjectResult); - - if (auto unified = dynamic_cast(iobjectResult); unified != nullptr) { - unified->AddProxy(targetReturnType, (IProxy^)proxy); - } - - result = proxy; - return result; - } - } - - return result; + return Utility::TranslateBoxedData(targetReturnType, result); } ::reframework::InvokeRet Method::Invoke_Internal(System::Object^ obj, array^ args) { diff --git a/csharp-api/REFrameworkNET/Utility.cpp b/csharp-api/REFrameworkNET/Utility.cpp index 25b455963..ec2ec3c15 100644 --- a/csharp-api/REFrameworkNET/Utility.cpp +++ b/csharp-api/REFrameworkNET/Utility.cpp @@ -5,10 +5,15 @@ #include "TypeDefinition.hpp" #include "Field.hpp" #include "Method.hpp" +#include "Proxy.hpp" #include "Utility.hpp" namespace REFrameworkNET { +public interface class DummyInterface { + +}; + System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke) { System::Object^ result = nullptr; @@ -122,10 +127,79 @@ System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInv // Here's hoping this works auto vt = gcnew REFrameworkNET::ValueType(t); memcpy(vt->Ptr(), ptr, t->ValueTypeSize); + result = vt; } break; } return result; } + +System::Object^ Utility::TranslateBoxedData(System::Type^ targetReturnType, System::Object^ result) { + if (result == nullptr) { + return nullptr; // ez + } + + if (targetReturnType == nullptr) { + return result; + } + + if (!targetReturnType->IsPrimitive && !targetReturnType->IsEnum && !targetReturnType->IsInterface) { + if (targetReturnType == System::String::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::ManagedObject::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::NativeObject::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::TypeDefinition::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::Method::typeid) { + return result; + } + + if (targetReturnType == REFrameworkNET::Field::typeid) { + return result; + } + } + + if (targetReturnType->IsEnum) { + auto underlyingType = targetReturnType->GetEnumUnderlyingType(); + + if (underlyingType != nullptr) { + auto underlyingResult = System::Convert::ChangeType(result, underlyingType); + return System::Enum::ToObject(targetReturnType, underlyingResult); + } + } + + if (!targetReturnType->IsPrimitive && targetReturnType->IsInterface) { + auto iobjectResult = dynamic_cast(result); + + if (iobjectResult != nullptr && targetReturnType->IsInterface) { + // Caching mechanism to prevent creating multiple proxies for the same object and type so we dont stress the GC + if (auto existingProxy = iobjectResult->GetProxy(targetReturnType); existingProxy != nullptr) { + return existingProxy; + } + + auto proxy = System::Reflection::DispatchProxy::Create(targetReturnType, Proxy::typeid->GetGenericTypeDefinition()->MakeGenericType(targetReturnType, result->GetType())); + ((IProxy^)proxy)->SetInstance(iobjectResult); + + if (auto unified = dynamic_cast(iobjectResult); unified != nullptr) { + unified->AddProxy(targetReturnType, (IProxy^)proxy); + } + + result = proxy; + return result; + } + } + + return result; +} } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Utility.hpp b/csharp-api/REFrameworkNET/Utility.hpp index 8cc870b25..0e4f20fba 100644 --- a/csharp-api/REFrameworkNET/Utility.hpp +++ b/csharp-api/REFrameworkNET/Utility.hpp @@ -24,5 +24,6 @@ namespace REFrameworkNET { ref class Utility { public: static System::Object^ BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke); + static System::Object^ TranslateBoxedData(System::Type^ targetReturnType, System::Object^ boxedData); }; } \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index ccb991031..60804ee54 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -116,13 +116,26 @@ public static void Entry() { var meshes = via.SceneManager.get_MainScene().findComponents(via.render.Mesh.REFType.RuntimeType.As<_System.Type>()); //var range = via.RangeI.REFType.CreateInstance(0).As(); var range = REFrameworkNET.ValueType.New(); - range.setMinMax(0, 10); var testVec = REFrameworkNET.ValueType.New(); - System.Console.WriteLine("Test vec x: " + testVec.x); + System.Console.WriteLine("Test vec x before: " + testVec.x); testVec.x = 1.0f; - System.Console.WriteLine("Test vec x: " + testVec.x); + testVec.y = 2.0f; + testVec.z = 3.0f; + System.Console.WriteLine("Test vec x after: " + testVec.x); + System.Console.WriteLine("Test vec y after: " + testVec.y); + System.Console.WriteLine("Test vec z after: " + testVec.z); + + var axisXStatic = via.vec3.AxisX; + var axisYStatic = via.vec3.AxisY; + var axisZStatic = via.vec3.AxisZ; + + System.Console.WriteLine("Axis X: " + axisXStatic.x + " " + axisXStatic.y + " " + axisXStatic.z); + System.Console.WriteLine("Axis Y: " + axisYStatic.x + " " + axisYStatic.y + " " + axisYStatic.z); + System.Console.WriteLine("Axis Z: " + axisZStatic.x + " " + axisZStatic.y + " " + axisZStatic.z); + //testVec[0] = 1.0f; // print min max to test if this works + range.setMinMax(1, 10); REFrameworkNET.API.LogInfo("Range min: " + range.getMin().ToString()); REFrameworkNET.API.LogInfo("Range max: " + range.getMax().ToString()); for (int i = 0; i < meshes.get_Length(); i++) { From 2f3673f2228cb382c95af357b4a25ee71aa26b7b Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 03:50:56 -0700 Subject: [PATCH 170/207] .NET: Fix non-static fields not getting proxied --- csharp-api/REFrameworkNET/Proxy.hpp | 2 +- csharp-api/test/Test/Test.cs | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/csharp-api/REFrameworkNET/Proxy.hpp b/csharp-api/REFrameworkNET/Proxy.hpp index 032b069bf..fb86bce3a 100644 --- a/csharp-api/REFrameworkNET/Proxy.hpp +++ b/csharp-api/REFrameworkNET/Proxy.hpp @@ -165,7 +165,7 @@ public ref class Proxy : public Reflection::DispatchProxy, public IProxy, public bool isValueType = !IsManaged() && GetTypeDefinition()->IsValueType(); if (methodAttribute->FieldFacade == REFrameworkNET::FieldFacadeType::Getter) { - return field->GetDataBoxed(iobject->GetAddress(), isValueType); + return field->GetDataBoxed(targetMethod->ReturnType, iobject->GetAddress(), isValueType); } field->SetDataBoxed(iobject->GetAddress(), args[0], isValueType); diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 60804ee54..ab8a52196 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -117,13 +117,14 @@ public static void Entry() { //var range = via.RangeI.REFType.CreateInstance(0).As(); var range = REFrameworkNET.ValueType.New(); var testVec = REFrameworkNET.ValueType.New(); - System.Console.WriteLine("Test vec x before: " + testVec.x); + + System.Console.WriteLine("Test vec before: " + testVec.x + " " + testVec.y + " " + testVec.z); + testVec.x = 1.0f; testVec.y = 2.0f; testVec.z = 3.0f; - System.Console.WriteLine("Test vec x after: " + testVec.x); - System.Console.WriteLine("Test vec y after: " + testVec.y); - System.Console.WriteLine("Test vec z after: " + testVec.z); + + System.Console.WriteLine("Test vec after: " + testVec.x + " " + testVec.y + " " + testVec.z); var axisXStatic = via.vec3.AxisX; var axisYStatic = via.vec3.AxisY; @@ -142,6 +143,15 @@ public static void Entry() { var mesh = (meshes.get_Item(i) as IObject).As(); mesh.set_DrawRaytracing(true); } + + var characterManager = API.GetManagedSingletonT(); + if (characterManager.ManualPlayer != null) { + var playergo = characterManager.ManualPlayer.get_GameObject(); + var transform = playergo.get_Transform(); + var position = transform.get_Position(); + + REFrameworkNET.API.LogInfo("Player position: " + position.x + " " + position.y + " " + position.z); + } } public static void TryEnableFrameGeneration() { From 38db0c22aee89e356babf6137085a8e942c79c8f Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 04:12:54 -0700 Subject: [PATCH 171/207] .NET: Add support for nulling out object fields --- csharp-api/REFrameworkNET/Field.cpp | 12 ++++++++++++ csharp-api/test/Test/Test.cs | 3 +++ 2 files changed, 15 insertions(+) diff --git a/csharp-api/REFrameworkNET/Field.cpp b/csharp-api/REFrameworkNET/Field.cpp index 063e030ac..78d73da59 100644 --- a/csharp-api/REFrameworkNET/Field.cpp +++ b/csharp-api/REFrameworkNET/Field.cpp @@ -55,6 +55,18 @@ namespace REFrameworkNET { } if (!field_type->IsValueType()) { + if (value == nullptr) { + // Lightweight managed object + auto originalObject = *(reframework::API::ManagedObject**)data_ptr; + + if (originalObject != nullptr && originalObject->get_ref_count() > 0) { + originalObject->release(); + } + + *(uintptr_t*)data_ptr = 0; + return; + } + auto iobject = dynamic_cast(value); if (iobject != nullptr) { diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index ab8a52196..5febd6815 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -151,6 +151,9 @@ public static void Entry() { var position = transform.get_Position(); REFrameworkNET.API.LogInfo("Player position: " + position.x + " " + position.y + " " + position.z); + + position.y += 5.0f; + transform.set_Position(position); } } From 2aa2eedc287b1c61b51a2fcafca47cba5ff31b62 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 06:45:50 -0700 Subject: [PATCH 172/207] .NET: Release original object within the field data --- csharp-api/REFrameworkNET/Field.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/csharp-api/REFrameworkNET/Field.cpp b/csharp-api/REFrameworkNET/Field.cpp index 78d73da59..3b9460794 100644 --- a/csharp-api/REFrameworkNET/Field.cpp +++ b/csharp-api/REFrameworkNET/Field.cpp @@ -70,6 +70,13 @@ namespace REFrameworkNET { auto iobject = dynamic_cast(value); if (iobject != nullptr) { + // Lightweight managed object + auto originalObject = *(reframework::API::ManagedObject**)data_ptr; + + if (originalObject != nullptr && originalObject->get_ref_count() > 0) { + originalObject->release(); + } + *(uintptr_t*)data_ptr = iobject->GetAddress(); // Add a reference onto the object now that something else is holding onto it From 96a8ea457fac72fd55cce1bf5c866ce6ef8b089b Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 07:33:46 -0700 Subject: [PATCH 173/207] .NET: Unify TryGetMember code with GetDataBoxed --- csharp-api/REFrameworkNET/Field.cpp | 2 +- csharp-api/REFrameworkNET/UnifiedObject.cpp | 111 +------------------- csharp-api/REFrameworkNET/Utility.cpp | 11 +- csharp-api/REFrameworkNET/Utility.hpp | 8 +- csharp-api/test/Test/Test.cs | 16 +-- 5 files changed, 27 insertions(+), 121 deletions(-) diff --git a/csharp-api/REFrameworkNET/Field.cpp b/csharp-api/REFrameworkNET/Field.cpp index 3b9460794..d1d4c3f69 100644 --- a/csharp-api/REFrameworkNET/Field.cpp +++ b/csharp-api/REFrameworkNET/Field.cpp @@ -34,7 +34,7 @@ namespace REFrameworkNET { return nullptr; } - return Utility::BoxData((uintptr_t*)raw_data, this->Type, false); + return Utility::BoxData((uintptr_t*)raw_data, this->Type, false, this); } System::Object^ Field::GetDataBoxed(System::Type^ targetReturnType, uintptr_t obj, bool isValueType) { diff --git a/csharp-api/REFrameworkNET/UnifiedObject.cpp b/csharp-api/REFrameworkNET/UnifiedObject.cpp index 08cc545fe..5c7014f29 100644 --- a/csharp-api/REFrameworkNET/UnifiedObject.cpp +++ b/csharp-api/REFrameworkNET/UnifiedObject.cpp @@ -90,116 +90,7 @@ namespace REFrameworkNET { if (field != nullptr) { - const auto field_type = field->GetType(); - - if (field_type == nullptr) { - return false; - } - - const auto raw_ft = (reframework::API::TypeDefinition*)field_type; - const uintptr_t addr = field->IsStatic() ? 0 : this->GetAddress(); - const auto vm_obj_type = field_type->GetVMObjType(); - - #define MAKE_TYPE_HANDLER(X, Y) \ - case ##X##_fnv: \ - result = gcnew Y(field->GetData<##Y##>(addr, field_type->IsValueType())); \ - break; - - switch (REFrameworkNET::hash(raw_ft->get_full_name())) { - MAKE_TYPE_HANDLER("System.Boolean", bool) - MAKE_TYPE_HANDLER("System.Byte", uint8_t) - MAKE_TYPE_HANDLER("System.SByte", int8_t) - MAKE_TYPE_HANDLER("System.Int16", int16_t) - MAKE_TYPE_HANDLER("System.UInt16", uint16_t) - MAKE_TYPE_HANDLER("System.Int32", int32_t) - MAKE_TYPE_HANDLER("System.UInt32", uint32_t) - MAKE_TYPE_HANDLER("System.Int64", int64_t) - MAKE_TYPE_HANDLER("System.UInt64", uint64_t) - MAKE_TYPE_HANDLER("System.Single", float) - MAKE_TYPE_HANDLER("System.Double", double) - MAKE_TYPE_HANDLER("System.Char", wchar_t) - MAKE_TYPE_HANDLER("System.IntPtr", intptr_t) - MAKE_TYPE_HANDLER("System.UIntPtr", uintptr_t) - case "System.String"_fnv: - { - if (field->IsLiteral()) { - result = gcnew System::String((const char*)field->GetInitDataPtr()); - break; - } - - // TODO: Check if this half of it works - auto strObject = field->GetData(addr, field_type->IsValueType()); - - if (strObject == nullptr) { - result = nullptr; - break; - } - - const auto firstCharField = field_type->GetField("_firstChar"); - uint32_t offset = 0; - - if (firstCharField != nullptr) { - offset = field_type->IsValueType() ? firstCharField->GetOffsetFromFieldPtr() : firstCharField->GetOffsetFromBase(); - } else { - const auto fieldOffset = *(uint32_t*)(*(uintptr_t*)strObject - sizeof(void*)); - offset = fieldOffset + 4; - } - - wchar_t* chars = (wchar_t*)((uintptr_t)strObject + offset); - result = gcnew System::String(chars); - break; - } - default: - if (vm_obj_type > VMObjType::NULL_ && vm_obj_type < VMObjType::ValType) { - switch (vm_obj_type) { - case VMObjType::Array: - /* - Just return it as an managed object for now - */ - default: { - //const auto td = utility::re_managed_object::get_type_definition(*(::REManagedObject**)data); - auto& obj = field->GetData(addr, field_type->IsValueType()); - - if (obj == nullptr) { - result = nullptr; - break; - } - - auto td = TypeDefinition::GetInstance(obj->get_type_definition()); - - // another fallback incase the method returns an object which is an array - if (td != nullptr && td->GetVMObjType() == VMObjType::Array) { - /* - Just return it as an managed object for now - */ - } - - result = ManagedObject::Get(obj); - break; - } - } - } else { - switch (field_type->GetSize()) { - case 8: - result = gcnew System::UInt64(field->GetData(addr, field_type->IsValueType())); - break; - case 4: - result = gcnew System::UInt32(field->GetData(addr, field_type->IsValueType())); - break; - case 2: - result = gcnew System::UInt16(field->GetData(addr, field_type->IsValueType())); - break; - case 1: - result = gcnew System::Byte(field->GetData(addr, field_type->IsValueType())); - break; - default: - result = nullptr; - break; - } - - break; - } - }; + result = field->GetDataBoxed(this->GetAddress(), !this->IsManaged() && this->GetTypeDefinition()->IsValueType()); return true; } diff --git a/csharp-api/REFrameworkNET/Utility.cpp b/csharp-api/REFrameworkNET/Utility.cpp index ec2ec3c15..2f61b5ffa 100644 --- a/csharp-api/REFrameworkNET/Utility.cpp +++ b/csharp-api/REFrameworkNET/Utility.cpp @@ -14,7 +14,7 @@ public interface class DummyInterface { }; -System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke) { +System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke, Field^ field) { System::Object^ result = nullptr; if (t == nullptr) { @@ -35,6 +35,10 @@ System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInv // TODO: Clean this up if (vm_object_type == VMObjType::String) { + if (field != nullptr && field->IsLiteral()) { + return gcnew System::String((const char*)field->GetInitDataPtr()); + } + auto strObject = (reframework::API::ManagedObject*)*ptr; auto strType = strObject->get_type_definition(); const auto firstCharField = strType->find_field("_firstChar"); @@ -76,6 +80,7 @@ System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInv switch (t->GetFNV64Hash()) { MAKE_TYPE_HANDLER_2(System, Boolean, bool, byte) MAKE_TYPE_HANDLER_2(System, Byte, uint8_t, byte) + MAKE_TYPE_HANDLER_2(System, Char, wchar_t, byte) MAKE_TYPE_HANDLER_2(System, UInt16, uint16_t, word) MAKE_TYPE_HANDLER_2(System, UInt32, uint32_t, dword) MAKE_TYPE_HANDLER_2(System, UInt64, uint64_t, qword) @@ -83,13 +88,15 @@ System::Object^ Utility::BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInv MAKE_TYPE_HANDLER_2(System, Int16, int16_t, word) MAKE_TYPE_HANDLER_2(System, Int32, int32_t, dword) MAKE_TYPE_HANDLER_2(System, Int64, int64_t, qword) + MAKE_TYPE_HANDLER_2(System, IntPtr, intptr_t, qword) + MAKE_TYPE_HANDLER_2(System, UIntPtr, uintptr_t, qword) MAKE_TYPE_HANDLER_2(System, Double, double, d) // Because invoke wrappers returning a single actually return a double // for consistency purposes //MAKE_TYPE_HANDLER_2(System, Single, double, d) case "System.Single"_fnv: { if (fromInvoke) { - result = gcnew System::Double(*(double*)ptr); + result = gcnew System::Single((float)*(double*)ptr); break; } diff --git a/csharp-api/REFrameworkNET/Utility.hpp b/csharp-api/REFrameworkNET/Utility.hpp index 0e4f20fba..f1a1ad600 100644 --- a/csharp-api/REFrameworkNET/Utility.hpp +++ b/csharp-api/REFrameworkNET/Utility.hpp @@ -5,6 +5,7 @@ namespace REFrameworkNET { ref class TypeDefinition; + ref class Field; static constexpr auto hash(std::string_view data) { size_t result = 0xcbf29ce484222325; @@ -23,7 +24,12 @@ namespace REFrameworkNET { ref class Utility { public: - static System::Object^ BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke); + // field can be null, just used for more information (particularly for static strings that can be literal) + static System::Object^ BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke) { + return BoxData(ptr, t, fromInvoke, nullptr); + } + + static System::Object^ BoxData(uintptr_t* ptr, TypeDefinition^ t, bool fromInvoke, Field^ field); static System::Object^ TranslateBoxedData(System::Type^ targetReturnType, System::Object^ boxedData); }; } \ No newline at end of file diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 5febd6815..930de567b 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -394,19 +394,21 @@ public static void MainImpl() { REFrameworkNET.API.LogInfo("HasAnySave: " + hasAnySave.ToString()); dynamic guiManager = REFrameworkNET.API.GetManagedSingleton("app.GuiManager"); - ulong guiManagerAddress = guiManager != null ? (guiManager as REFrameworkNET.ManagedObject).GetAddress() : 0; - dynamic fadeOwnerCmn = guiManager?.FadeOwnerCmn; - dynamic optionData = guiManager?.OptionData; - dynamic optionDataFromGet = guiManager?.getOptionData(); - bool? isDispSubtitle = optionData?._IsDispSubtitle; - REFrameworkNET.API.LogInfo("GuiManager: " + guiManager.ToString() + " @ " + guiManagerAddress.ToString("X")); + + dynamic fadeOwnerCmn = guiManager?.FadeOwnerCmn; REFrameworkNET.API.LogInfo(" FadeOwnerCmn: " + fadeOwnerCmn.ToString()); + + dynamic optionData = guiManager?.OptionData; REFrameworkNET.API.LogInfo(" OptionData: " + optionData.ToString() + ": " + optionData?.GetTypeDefinition()?.GetFullName()?.ToString()); + + dynamic optionDataFromGet = guiManager?.getOptionData(); REFrameworkNET.API.LogInfo(" OptionDataFromGet: " + optionDataFromGet.ToString() + ": " + optionDataFromGet?.GetTypeDefinition()?.GetFullName()?.ToString()); REFrameworkNET.API.LogInfo(" OptionDataFromGet same: " + (optionData?.Equals(optionDataFromGet)).ToString() + (" {0} vs {1}", optionData?.GetAddress().ToString("X"), optionDataFromGet?.GetAddress().ToString("X"))); - + + bool? isDispSubtitle = optionData?._IsDispSubtitle; + REFrameworkNET.API.LogInfo(" IsDispSubtitle: " + isDispSubtitle.ToString()); if (optionData != null) { From 92a49237cc9f4c977132b2d8ed226936759102d8 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 09:00:49 -0700 Subject: [PATCH 174/207] .NET: Fix weird case with invalid field names --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index b2734a408..fdbdeb4c9 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -321,6 +321,12 @@ private TypeDeclarationSyntax GenerateFields(List baseType continue; } + // Make sure field name only contains ASCII characters + if (field.Name.Any(c => c > 127)) { + System.Console.WriteLine("Skipping field with non-ASCII characters: " + field.Name + " " + field.Index); + continue; + } + // We don't want any of the properties to be "void" properties if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(field.Type.FullName)) { continue; From ac743267d7f375cd352795b1d564f9f3bdf150ac Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 09:05:56 -0700 Subject: [PATCH 175/207] .NET: Add property support, remove explicit getter/setter methods --- .../AssemblyGenerator/ClassGenerator.cs | 165 +++++++++++++++++- csharp-api/AssemblyGenerator/Generator.cs | 2 +- csharp-api/test/Test/ObjectExplorer.cs | 14 +- csharp-api/test/Test/Test.cs | 61 ++++--- csharp-api/test/Test/TestRE2.cs | 16 +- 5 files changed, 214 insertions(+), 44 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index fdbdeb4c9..c74c2f95e 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -15,6 +15,14 @@ using System; public class ClassGenerator { + public class PseudoProperty { + public REFrameworkNET.Method? getter; + public REFrameworkNET.Method? setter; + public REFrameworkNET.TypeDefinition? type; + }; + + private Dictionary pseudoProperties = []; + private string className; private REFrameworkNET.TypeDefinition t; private List methods = []; @@ -60,7 +68,27 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { continue; } - methods.Add(method); + if (method.Name.StartsWith("get_") && method.Parameters.Count == 0 && method.ReturnType.FullName != "System.Void") { + // Add the getter to the pseudo property (create if it doesn't exist) + var propertyName = method.Name[4..]; + if (!pseudoProperties.ContainsKey(propertyName)) { + pseudoProperties[propertyName] = new PseudoProperty(); + } + + pseudoProperties[propertyName].getter = method; + pseudoProperties[propertyName].type = method.ReturnType; + } else if (method.Name.StartsWith("set_") && method.Parameters.Count == 1) { + // Add the setter to the pseudo property (create if it doesn't exist) + var propertyName = method.Name[4..]; + if (!pseudoProperties.ContainsKey(propertyName)) { + pseudoProperties[propertyName] = new PseudoProperty(); + } + + pseudoProperties[propertyName].setter = method; + pseudoProperties[propertyName].type = method.Parameters[0].Type; + } else { + methods.Add(method); + } } foreach (var field in t_.Fields) { @@ -89,6 +117,7 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { // remove any methods that start with get/set_{field.Name} // because we're going to make them properties instead methods.RemoveAll(method => method.Name == "get_" + fieldName || method.Name == "set_" + fieldName); + pseudoProperties.Remove(fieldName); } typeDeclaration = Generate(); @@ -267,6 +296,7 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy typeDeclaration = GenerateMethods(baseTypes); typeDeclaration = GenerateFields(baseTypes); + typeDeclaration = GenerateProperties(baseTypes); if (baseTypes.Count > 0 && typeDeclaration != null) { refTypeFieldDecl = refTypeFieldDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); @@ -288,8 +318,139 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy return GenerateNestedTypes(); } - private TypeDeclarationSyntax GenerateFields(List baseTypes) { + private TypeDeclarationSyntax GenerateProperties(List baseTypes) { + if (typeDeclaration == null) { + throw new Exception("Type declaration is null"); // This should never happen + } + + if (pseudoProperties.Count == 0) { + return typeDeclaration!; + } + + var matchingProperties = pseudoProperties + .Select(property => { + var propertyType = MakeProperType(property.Value.type, t); + var propertyName = new string(property.Key); + + var propertyDeclaration = SyntaxFactory.PropertyDeclaration(propertyType, propertyName) + .AddModifiers([SyntaxFactory.Token(SyntaxKind.PublicKeyword)]); + + bool shouldAddNewKeyword = false; + bool shouldAddStaticKeyword = false; + + if (property.Value.getter != null) { + var getter = SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) + .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( + SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), + SyntaxFactory.ParseAttributeArgumentList("(" + property.Value.getter.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.None)")) + )); + + if (property.Value.getter.IsStatic()) { + shouldAddStaticKeyword = true; + + // Now we must add a body to it that actually calls the method + // We have our REFType field, so we can lookup the method and call it + // Make a private static field to hold the REFrameworkNET.Method + var internalFieldName = "INTERNAL_" + propertyName + property.Value.getter.Index.ToString(); + var methodVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Method")) + .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetMethod(\"" + property.Value.getter.GetMethodSignature() + "\")")))); + + var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + internalFieldDeclarations.Add(methodFieldDeclaration); + + List bodyStatements = []; + bodyStatements.Add(SyntaxFactory.ParseStatement("return (" + propertyType.GetText().ToString() + ")" + internalFieldName + ".InvokeBoxed(typeof(" + propertyType.GetText().ToString() + "), null, null);")); + + getter = getter.AddBodyStatements(bodyStatements.ToArray()); + } else { + getter = getter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + } + + propertyDeclaration = propertyDeclaration.AddAccessorListAccessors(getter); + + var getterExtension = Il2CppDump.GetMethodExtension(property.Value.getter); + + if (baseTypes.Count > 0 && getterExtension != null && getterExtension.Override != null && getterExtension.Override == true) { + var matchingParentMethods = getterExtension.MatchingParentMethods; + // Go through the parents, check if the parents are allowed to be generated + // and add the new keyword if the matching method is found in one allowed to be generated + foreach (var matchingMethod in matchingParentMethods) { + var parent = matchingMethod.DeclaringType; + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { + continue; + } + + shouldAddNewKeyword = true; + break; + } + } + } + + if (property.Value.setter != null) { + var setter = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) + .AddAttributeLists(SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( + SyntaxFactory.ParseName("global::REFrameworkNET.Attributes.Method"), + SyntaxFactory.ParseAttributeArgumentList("(" + property.Value.setter.Index.ToString() + ", global::REFrameworkNET.FieldFacadeType.None)")) + )); + + if (property.Value.setter.IsStatic()) { + shouldAddStaticKeyword = true; + + // Now we must add a body to it that actually calls the method + // We have our REFType field, so we can lookup the method and call it + // Make a private static field to hold the REFrameworkNET.Method + var internalFieldName = "INTERNAL_" + propertyName + property.Value.setter.Index.ToString(); + var methodVariableDeclaration = SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName("global::REFrameworkNET.Method")) + .AddVariables(SyntaxFactory.VariableDeclarator(internalFieldName).WithInitializer(SyntaxFactory.EqualsValueClause(SyntaxFactory.ParseExpression("REFType.GetMethod(\"" + property.Value.setter.GetMethodSignature() + "\")")))); + + var methodFieldDeclaration = SyntaxFactory.FieldDeclaration(methodVariableDeclaration).AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword), SyntaxFactory.Token(SyntaxKind.StaticKeyword), SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); + internalFieldDeclarations.Add(methodFieldDeclaration); + + List bodyStatements = []; + bodyStatements.Add(SyntaxFactory.ParseStatement(internalFieldName + ".Invoke(null, new object[] {value});")); + + setter = setter.AddBodyStatements(bodyStatements.ToArray()); + } else { + setter = setter.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + } + + propertyDeclaration = propertyDeclaration.AddAccessorListAccessors(setter); + + var setterExtension = Il2CppDump.GetMethodExtension(property.Value.setter); + + if (baseTypes.Count > 0 && setterExtension != null && setterExtension.Override != null && setterExtension.Override == true) { + var matchingParentMethods = setterExtension.MatchingParentMethods; + + // Go through the parents, check if the parents are allowed to be generated + // and add the new keyword if the matching method is found in one allowed to be generated + foreach (var matchingMethod in matchingParentMethods) { + var parent = matchingMethod.DeclaringType; + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { + continue; + } + + shouldAddNewKeyword = true; + break; + } + } + } + + if (shouldAddStaticKeyword) { + propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + } + + if (shouldAddNewKeyword) { + propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + } + + return propertyDeclaration; + }); + + return typeDeclaration.AddMembers(matchingProperties.ToArray()); + } + + private TypeDeclarationSyntax GenerateFields(List baseTypes) { if (typeDeclaration == null) { throw new Exception("Type declaration is null"); // This should never happen } diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index ddbbe52a0..b32e1f5b4 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -718,7 +718,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); foreach (var cu in compilationUnits) { - syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu/*.NormalizeWhitespace()*/, syntaxTreeParseOption)); + syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu.NormalizeWhitespace(), syntaxTreeParseOption)); } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); diff --git a/csharp-api/test/Test/ObjectExplorer.cs b/csharp-api/test/Test/ObjectExplorer.cs index 96f15d4f1..9b36369ae 100644 --- a/csharp-api/test/Test/ObjectExplorer.cs +++ b/csharp-api/test/Test/ObjectExplorer.cs @@ -275,16 +275,16 @@ public static void DisplayType(REFrameworkNET.TypeDefinition t) { if (runtimeTypeRaw != null) { var runtimeType = runtimeTypeRaw.As<_System.Type>(); - var assembly = runtimeType.get_Assembly(); + var assembly = runtimeType.Assembly; if (assembly != null) { - if (ImGui.TreeNode("Assembly: " + assembly.get_FullName().Split(',')[0])) { + if (ImGui.TreeNode("Assembly: " + assembly.FullName.Split(',')[0])) { DisplayObject(assembly as IObject); ImGui.TreePop(); } } - var baseType = runtimeType.get_BaseType(); + var baseType = runtimeType.BaseType; /*if (baseType != null) { if (ImGui.TreeNode("Base Type (" + (baseType.get_TypeHandle() as REFrameworkNET.TypeDefinition).FullName + ")")) { @@ -371,7 +371,7 @@ public static void DisplayObject(REFrameworkNET.IObject obj) { var elementType = obj.GetTypeDefinition().GetElementType(); var elementSize = elementType.GetSize(); - for (int i = 0; i < easyArray.get_Length(); i++) { + for (int i = 0; i < easyArray.Length; i++) { var element = easyArray.GetValue(i); if (element == null) { ImGui.Text("Element " + i + ": null"); @@ -469,15 +469,15 @@ public static void Render() { RenderNativeSingletons(); } - var appdomain = _System.AppDomain.get_CurrentDomain(); + var appdomain = _System.AppDomain.CurrentDomain; var assemblies = appdomain.GetAssemblies(); if (ImGui.TreeNode("AppDomain")) { if (assemblies != null && ImGui.TreeNode("Assemblies")) { - for (int i = 0; i < assemblies.get_Length(); i++) { + for (int i = 0; i < assemblies.Length; i++) { var assembly = assemblies.get_Item(i); var assemblyT = (assembly as IObject).GetTypeDefinition(); - var location = assembly.get_Location() ?? "null"; + var location = assembly.Location ?? "null"; if (ImGui.TreeNode(location)) { DisplayObject(assembly as IObject); diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 930de567b..46d384882 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -40,11 +40,17 @@ public static REFrameworkNET.PreHookResult processNormalAttackPreHook(Span(); + //via.render.RayTracingManager.set_EnableLod(false); + //via.render.RayTracingManager.set_PreferShadowCast(true); + via.render.RayTracingManager.EnableLod = false; + via.render.RayTracingManager.PreferShadowCast = true; - via.hid.Mouse.set_ShowCursor(false); + // Print the current value + Console.WriteLine("RayTracingManager.EnableLod: " + via.render.RayTracingManager.EnableLod.ToString()); + Console.WriteLine("RayTracingManager.PreferShadowCast: " + via.render.RayTracingManager.PreferShadowCast.ToString()); + + //via.hid.Mouse.set_ShowCursor(false); + via.hid.Mouse.ShowCursor = false; var tdb = REFrameworkNET.API.GetTDB(); @@ -54,8 +60,10 @@ public static void Entry() { //var sceneManager = API.GetNativeSingletonT(); //var scene = sceneManager.get_CurrentScene(); //var scene2 = sceneManager.get_CurrentScene(); - var scene = via.SceneManager.get_CurrentScene(); - var scene2 = via.SceneManager.get_CurrentScene(); + //var scene = via.SceneManager.get_CurrentScene(); + //var scene2 = via.SceneManager.get_CurrentScene(); + var scene = via.SceneManager.CurrentScene; + var scene2 = via.SceneManager.CurrentScene; if (scene == scene2) { REFrameworkNET.API.LogInfo("Test success: Scene is the same"); @@ -65,11 +73,11 @@ public static void Entry() { //scene.set_Pause(true); //var view = sceneManager.get_MainView(); - var view = via.SceneManager.get_MainView(); - var name = view.get_Name(); - var go = view.get_PrimaryCamera()?.get_GameObject()?.get_Transform()?.get_GameObject(); + var view = via.SceneManager.MainView; + var name = view.Name; + var go = view.PrimaryCamera?.GameObject?.Transform?.GameObject; - REFrameworkNET.API.LogInfo("game object name: " + go?.get_Name().ToString()); + REFrameworkNET.API.LogInfo("game object name: " + go?.Name.ToString()); REFrameworkNET.API.LogInfo("Scene name: " + name); // Testing autocomplete for the concrete ManagedObject @@ -82,15 +90,15 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Previous timescale: " + currentTimescale.ToString()); REFrameworkNET.API.LogInfo("Current timescale: " + scene?.get_TimeScale().ToString());*/ - var appdomain = _System.AppDomain.get_CurrentDomain(); + var appdomain = _System.AppDomain.CurrentDomain; var assemblies = appdomain.GetAssemblies(); // TODO: Make this work! get_length, get_item is ugly! //foreach (REFrameworkNET.ManagedObject assemblyRaw in assemblies) { - for (int i = 0; i < assemblies.get_Length(); i++) { + for (int i = 0; i < assemblies.Length; i++) { //var assembly = assemblyRaw.As<_System.Reflection.Assembly>(); var assembly = assemblies.get_Item(i); - REFrameworkNET.API.LogInfo("Assembly: " + assembly.get_Location()?.ToString()); + REFrameworkNET.API.LogInfo("Assembly: " + assembly.Location?.ToString()); } var platform = via.os.getPlatform(); @@ -113,7 +121,6 @@ public static void Entry() { REFrameworkNET.API.LogInfo("Managed string back in .NET: " + stringInDotNetVM); - var meshes = via.SceneManager.get_MainScene().findComponents(via.render.Mesh.REFType.RuntimeType.As<_System.Type>()); //var range = via.RangeI.REFType.CreateInstance(0).As(); var range = REFrameworkNET.ValueType.New(); var testVec = REFrameworkNET.ValueType.New(); @@ -139,35 +146,37 @@ public static void Entry() { range.setMinMax(1, 10); REFrameworkNET.API.LogInfo("Range min: " + range.getMin().ToString()); REFrameworkNET.API.LogInfo("Range max: " + range.getMax().ToString()); - for (int i = 0; i < meshes.get_Length(); i++) { + + var meshes = via.SceneManager.MainScene.findComponents(via.render.Mesh.REFType.RuntimeType.As<_System.Type>()); + for (int i = 0; i < meshes.Length; i++) { var mesh = (meshes.get_Item(i) as IObject).As(); - mesh.set_DrawRaytracing(true); + mesh.DrawRaytracing = true; } var characterManager = API.GetManagedSingletonT(); if (characterManager.ManualPlayer != null) { - var playergo = characterManager.ManualPlayer.get_GameObject(); - var transform = playergo.get_Transform(); - var position = transform.get_Position(); + var playergo = characterManager.ManualPlayer.GameObject; + var transform = playergo.Transform; + var position = transform.Position; REFrameworkNET.API.LogInfo("Player position: " + position.x + " " + position.y + " " + position.z); position.y += 5.0f; - transform.set_Position(position); + transform.Position = position; } } public static void TryEnableFrameGeneration() { - var dlssInterface = via.render.UpscalingInterface.get_DLSSInterface(); + var dlssInterface = via.render.UpscalingInterface.DLSSInterface; - if (dlssInterface != null && dlssInterface.get_DLSSGEnable() == false) { - dlssInterface.set_DLSSGEnable(true); + if (dlssInterface != null && dlssInterface.DLSSGEnable == false) { + dlssInterface.DLSSGEnable = true; } - var fsr3Interface = via.render.UpscalingInterface.get_FSR3Interface(); + var fsr3Interface = via.render.UpscalingInterface.FSR3Interface; - if (fsr3Interface != null && fsr3Interface.get_EnableFrameGeneration() == false) { - fsr3Interface.set_EnableFrameGeneration(true); + if (fsr3Interface != null && fsr3Interface.EnableFrameGeneration == false) { + fsr3Interface.EnableFrameGeneration = true; } } } diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index 59e2aa4ca..cff7e55fd 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -26,7 +26,7 @@ public static void ImGuiCallback() { if (ImGui.TreeNode("Player")) { var playerManager = API.GetManagedSingletonT(); - var player = playerManager.get_CurrentPlayer(); + var player = playerManager.CurrentPlayer; if (player != null) { ImGui.Text("Player is not null"); @@ -135,7 +135,7 @@ public static void Unload() { static System.Collections.Concurrent.ConcurrentDictionary test = new(Environment.ProcessorCount * 2, 8192); public static void BenchFn() { var playerManager = API.GetManagedSingletonT(); - var player = playerManager.get_CurrentPlayer(); + var player = playerManager.CurrentPlayer; if (player != null) { via.Component playerControllerRaw = player.getComponent(app.Collision.HitController.REFType.GetRuntimeType().As<_System.Type>()); if (playerControllerRaw != null) { @@ -145,7 +145,7 @@ public static void BenchFn() { //rwl.ExitReadLock(); //playerController. //playerController.get_DeltaTime(); - var gameobj = playerController.get_GameObject(); + var gameobj = playerController.GameObject; if (gameobj != null) { /*var backToPlayerController = gameobj.getComponent(app.Collision.HitController.REFType.GetRuntimeType().As<_System.Type>()); @@ -159,7 +159,7 @@ public static void BenchFn() { //get_GameObjectFn.HandleInvokeMember_Internal(playerController, null, ref result); } - var refCount = ((playerController.get_GameObject() as IProxy).GetInstance() as ManagedObject).GetReferenceCount(); + var refCount = ((playerController.GameObject as IProxy).GetInstance() as ManagedObject).GetReferenceCount(); System.Console.WriteLine("PlayerController ref count: " + refCount); } } @@ -214,7 +214,7 @@ public static void Bench(System.Action action) { action(); sw.Stop(); - var elapsedTicks = (double)sw.get_ElapsedTicks(); + var elapsedTicks = (double)sw.ElapsedTicks; var elapsedMicros = elapsedTicks / (double)TimeSpan.TicksPerMicrosecond; data.totalMicros += elapsedMicros; @@ -261,7 +261,7 @@ static PreHookResult Pre(Span args) { Bench(() => { for (int i = 0; i < 10000; ++i) { - var gameobj = hitController.get_GameObject(); + var gameobj = hitController.GameObject; if (gameobj != null) { } } @@ -294,8 +294,8 @@ static void BeginRenderingCallback() { var wantedValue = Math.Sin(elapsedSeconds) + 1.0; cameraFovLerp = double.Lerp(cameraFovLerp, wantedValue, cameraFovLerpSpeed * deltaSeconds); - var camera = via.SceneManager.get_MainView().get_PrimaryCamera(); + var camera = via.SceneManager.MainView.PrimaryCamera; var degrees = cameraFovLerp * 180.0 / Math.PI; - camera.set_FOV((float)degrees); + camera.FOV = (float)degrees; } } \ No newline at end of file From 9f4556f2036eff627aceb56fc2b17691c84c72d0 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 09:58:15 -0700 Subject: [PATCH 176/207] .NET: Fix GC pressure from proxies --- csharp-api/REFrameworkNET/NativeObject.hpp | 4 ++++ csharp-api/REFrameworkNET/ValueType.hpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/csharp-api/REFrameworkNET/NativeObject.hpp b/csharp-api/REFrameworkNET/NativeObject.hpp index bbe3600d6..32ecaa6c3 100644 --- a/csharp-api/REFrameworkNET/NativeObject.hpp +++ b/csharp-api/REFrameworkNET/NativeObject.hpp @@ -83,6 +83,10 @@ public ref class NativeObject : public REFrameworkNET::UnifiedObject return gcnew NativeObject(obj, t); } + virtual void AddProxy(System::Type^ type, IProxy^ proxy) override { + // Nothing, don't bother + } + protected: void* m_object{}; TypeDefinition^ m_type{}; diff --git a/csharp-api/REFrameworkNET/ValueType.hpp b/csharp-api/REFrameworkNET/ValueType.hpp index b3a0c83dd..72f19edf5 100644 --- a/csharp-api/REFrameworkNET/ValueType.hpp +++ b/csharp-api/REFrameworkNET/ValueType.hpp @@ -29,6 +29,10 @@ public ref class ValueType : public UnifiedObject { return (uintptr_t)Ptr(); } + virtual void AddProxy(System::Type^ type, IProxy^ proxy) override { + // Nothing, don't bother + } + generic virtual T As() override; From 3ab5846a563bef3d120a524b288f1ab2ad255e30 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 13 May 2024 11:12:21 -0700 Subject: [PATCH 177/207] .NET: Add support for indexers --- .../AssemblyGenerator/ClassGenerator.cs | 68 ++++++++++++++----- csharp-api/test/Test/ObjectExplorer.cs | 2 +- csharp-api/test/Test/Test.cs | 5 +- csharp-api/test/Test/TestRE2.cs | 35 ++++++---- 4 files changed, 77 insertions(+), 33 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index c74c2f95e..495e642c0 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -19,6 +19,8 @@ public class PseudoProperty { public REFrameworkNET.Method? getter; public REFrameworkNET.Method? setter; public REFrameworkNET.TypeDefinition? type; + public bool indexer = false; + public REFrameworkNET.TypeDefinition? indexType; }; private Dictionary pseudoProperties = []; @@ -68,24 +70,50 @@ public ClassGenerator(string className_, REFrameworkNET.TypeDefinition t_) { continue; } - if (method.Name.StartsWith("get_") && method.Parameters.Count == 0 && method.ReturnType.FullName != "System.Void") { - // Add the getter to the pseudo property (create if it doesn't exist) - var propertyName = method.Name[4..]; - if (!pseudoProperties.ContainsKey(propertyName)) { - pseudoProperties[propertyName] = new PseudoProperty(); - } + if (method.Name.StartsWith("get_") && method.ReturnType.FullName != "System.Void") { + if (method.Parameters.Count == 0) { + // Add the getter to the pseudo property (create if it doesn't exist) + var propertyName = method.Name[4..]; + if (!pseudoProperties.ContainsKey(propertyName)) { + pseudoProperties[propertyName] = new PseudoProperty(); + } + + pseudoProperties[propertyName].getter = method; + pseudoProperties[propertyName].type = method.ReturnType; + } else if (method.Parameters.Count == 1 && method.Name == "get_Item") { + // This is an indexer property + var propertyName = method.Name[4..]; + if (!pseudoProperties.ContainsKey(propertyName)) { + pseudoProperties[propertyName] = new PseudoProperty(); + } - pseudoProperties[propertyName].getter = method; - pseudoProperties[propertyName].type = method.ReturnType; - } else if (method.Name.StartsWith("set_") && method.Parameters.Count == 1) { - // Add the setter to the pseudo property (create if it doesn't exist) - var propertyName = method.Name[4..]; - if (!pseudoProperties.ContainsKey(propertyName)) { - pseudoProperties[propertyName] = new PseudoProperty(); + pseudoProperties[propertyName].getter = method; + pseudoProperties[propertyName].type = method.ReturnType; + pseudoProperties[propertyName].indexer = true; + pseudoProperties[propertyName].indexType = method.Parameters[0].Type; } + } else if (method.Name.StartsWith("set_")) { + if (method.Parameters.Count == 1) { + // Add the setter to the pseudo property (create if it doesn't exist) + var propertyName = method.Name[4..]; + if (!pseudoProperties.ContainsKey(propertyName)) { + pseudoProperties[propertyName] = new PseudoProperty(); + } - pseudoProperties[propertyName].setter = method; - pseudoProperties[propertyName].type = method.Parameters[0].Type; + pseudoProperties[propertyName].setter = method; + pseudoProperties[propertyName].type = method.Parameters[0].Type; + } else if (method.Parameters.Count == 2 && method.Name == "set_Item") { + // This is an indexer property + var propertyName = method.Name[4..]; + if (!pseudoProperties.ContainsKey(propertyName)) { + pseudoProperties[propertyName] = new PseudoProperty(); + } + + pseudoProperties[propertyName].setter = method; + pseudoProperties[propertyName].type = method.Parameters[1].Type; + pseudoProperties[propertyName].indexer = true; + pseudoProperties[propertyName].indexType = method.Parameters[0].Type; + } } else { methods.Add(method); } @@ -332,9 +360,17 @@ private TypeDeclarationSyntax GenerateProperties(List base var propertyType = MakeProperType(property.Value.type, t); var propertyName = new string(property.Key); - var propertyDeclaration = SyntaxFactory.PropertyDeclaration(propertyType, propertyName) + BasePropertyDeclarationSyntax propertyDeclaration = SyntaxFactory.PropertyDeclaration(propertyType, propertyName) .AddModifiers([SyntaxFactory.Token(SyntaxKind.PublicKeyword)]); + if (property.Value.indexer) { + ParameterSyntax parameter = SyntaxFactory.Parameter(SyntaxFactory.Identifier("index")).WithType(MakeProperType(property.Value.indexType, t)); + + propertyDeclaration = SyntaxFactory.IndexerDeclaration(propertyType) + .AddModifiers([SyntaxFactory.Token(SyntaxKind.PublicKeyword)]) + .AddParameterListParameters(parameter); + } + bool shouldAddNewKeyword = false; bool shouldAddStaticKeyword = false; diff --git a/csharp-api/test/Test/ObjectExplorer.cs b/csharp-api/test/Test/ObjectExplorer.cs index 9b36369ae..c65efe49a 100644 --- a/csharp-api/test/Test/ObjectExplorer.cs +++ b/csharp-api/test/Test/ObjectExplorer.cs @@ -475,7 +475,7 @@ public static void Render() { if (ImGui.TreeNode("AppDomain")) { if (assemblies != null && ImGui.TreeNode("Assemblies")) { for (int i = 0; i < assemblies.Length; i++) { - var assembly = assemblies.get_Item(i); + var assembly = assemblies[i]; var assemblyT = (assembly as IObject).GetTypeDefinition(); var location = assembly.Location ?? "null"; diff --git a/csharp-api/test/Test/Test.cs b/csharp-api/test/Test/Test.cs index 46d384882..94407a9bb 100644 --- a/csharp-api/test/Test/Test.cs +++ b/csharp-api/test/Test/Test.cs @@ -93,11 +93,10 @@ public static void Entry() { var appdomain = _System.AppDomain.CurrentDomain; var assemblies = appdomain.GetAssemblies(); - // TODO: Make this work! get_length, get_item is ugly! //foreach (REFrameworkNET.ManagedObject assemblyRaw in assemblies) { for (int i = 0; i < assemblies.Length; i++) { //var assembly = assemblyRaw.As<_System.Reflection.Assembly>(); - var assembly = assemblies.get_Item(i); + var assembly = assemblies[i]; REFrameworkNET.API.LogInfo("Assembly: " + assembly.Location?.ToString()); } @@ -149,7 +148,7 @@ public static void Entry() { var meshes = via.SceneManager.MainScene.findComponents(via.render.Mesh.REFType.RuntimeType.As<_System.Type>()); for (int i = 0; i < meshes.Length; i++) { - var mesh = (meshes.get_Item(i) as IObject).As(); + var mesh = (meshes[i] as IObject).As(); mesh.DrawRaytracing = true; } diff --git a/csharp-api/test/Test/TestRE2.cs b/csharp-api/test/Test/TestRE2.cs index cff7e55fd..ee39af29d 100644 --- a/csharp-api/test/Test/TestRE2.cs +++ b/csharp-api/test/Test/TestRE2.cs @@ -44,6 +44,7 @@ public static void ImGuiCallback() { ImGui.SetNextWindowSize(new System.Numerics.Vector2(500, 500), ImGuiCond.FirstUseEver); if (ImGui.Begin("RE2 Bench")) { + ImGui.Text(RE2HookBenchmark.MeasureCount.ToString() + " calls"); ImGui.PushStyleColor(ImGuiCol.Text, new System.Numerics.Vector4(0.5f, 1f, 0.4f, 1.0f)); ImGui.PlotLines("Overall Benchmark", ref RE2HookBenchmark.BenchmarkData[0], 1000, RE2HookBenchmark.MeasureCount % 1000, RE2HookBenchmark.RunningAvg.ToString("0.000") + " µs", 0, (float)RE2HookBenchmark.RunningAvg * 2.0f, new System.Numerics.Vector2(0, 40)); @@ -260,7 +261,7 @@ static PreHookResult Pre(Span args) { var hitController = ManagedObject.ToManagedObject(args[1]).As(); Bench(() => { - for (int i = 0; i < 10000; ++i) { + for (int i = 0; i < 10; ++i) { var gameobj = hitController.GameObject; if (gameobj != null) { } @@ -280,22 +281,30 @@ static void Post(ref ulong retval) { static System.Diagnostics.Stopwatch cameraFovStopwatch = new(); static System.Diagnostics.Stopwatch cameraFovStopwatch2 = new(); - [Callback(typeof(BeginRendering), CallbackType.Pre)] - static void BeginRenderingCallback() { - if (!cameraFovStopwatch2.IsRunning) { - cameraFovStopwatch2.Start(); + [Callback(typeof(LockScene), CallbackType.Post)] + static void LockSceneCallback() { + var playerManager = API.GetManagedSingletonT(); + if (playerManager == null) { + return; } - cameraFovStopwatch.Stop(); - var deltaSeconds = cameraFovStopwatch.Elapsed.TotalMilliseconds / 1000.0; - var elapsedSeconds = cameraFovStopwatch2.Elapsed.TotalMilliseconds / 1000.0; - cameraFovStopwatch.Restart(); + var player = playerManager.CurrentPlayer; + if (player == null) { + return; + } - var wantedValue = Math.Sin(elapsedSeconds) + 1.0; - cameraFovLerp = double.Lerp(cameraFovLerp, wantedValue, cameraFovLerpSpeed * deltaSeconds); + var transform = player.Transform; + var headJoint = transform.getJointByName("head"); + + if (headJoint == null) { + return; + } var camera = via.SceneManager.MainView.PrimaryCamera; - var degrees = cameraFovLerp * 180.0 / Math.PI; - camera.FOV = (float)degrees; + camera.GameObject.Transform.Position = headJoint.Position; + camera.GameObject.Transform.Joints[0].Position = headJoint.Position; + + // Shrink head scale + headJoint.LocalScale = via.vec3.Zero; } } \ No newline at end of file From 6c001dac9fbbbd0ba1ec4a69427e6e07eadad14f Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 14 May 2024 21:29:42 -0700 Subject: [PATCH 178/207] .NET: Add support for operator overloads --- .../AssemblyGenerator/ClassGenerator.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 495e642c0..c560d2080 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -599,6 +599,33 @@ private TypeDeclarationSyntax GenerateFields(List baseType return typeDeclaration.AddMembers(matchingFields.ToArray()); } + private static readonly Dictionary operatorTokens = new() { + ["op_Addition"] = SyntaxFactory.Token(SyntaxKind.PlusToken), + ["op_UnaryPlus"] = SyntaxFactory.Token(SyntaxKind.PlusToken), + ["op_Subtraction"] = SyntaxFactory.Token(SyntaxKind.MinusToken), + ["op_UnaryNegation"] = SyntaxFactory.Token(SyntaxKind.MinusToken), + ["op_Multiply"] = SyntaxFactory.Token(SyntaxKind.AsteriskToken), + ["op_Division"] = SyntaxFactory.Token(SyntaxKind.SlashToken), + ["op_Modulus"] = SyntaxFactory.Token(SyntaxKind.PercentToken), + ["op_BitwiseAnd"] = SyntaxFactory.Token(SyntaxKind.AmpersandToken), + ["op_BitwiseOr"] = SyntaxFactory.Token(SyntaxKind.BarToken), + ["op_ExclusiveOr"] = SyntaxFactory.Token(SyntaxKind.CaretToken), + ["op_LeftShift"] = SyntaxFactory.Token(SyntaxKind.LessThanLessThanToken), + ["op_RightShift"] = SyntaxFactory.Token(SyntaxKind.GreaterThanGreaterThanToken), + ["op_Equality"] = SyntaxFactory.Token(SyntaxKind.EqualsEqualsToken), + ["op_Inequality"] = SyntaxFactory.Token(SyntaxKind.ExclamationEqualsToken), + ["op_LessThan"] = SyntaxFactory.Token(SyntaxKind.LessThanToken), + ["op_LessThanOrEqual"] = SyntaxFactory.Token(SyntaxKind.LessThanEqualsToken), + ["op_GreaterThan"] = SyntaxFactory.Token(SyntaxKind.GreaterThanToken), + ["op_GreaterThanOrEqual"] = SyntaxFactory.Token(SyntaxKind.GreaterThanEqualsToken), + ["op_LogicalNot"] = SyntaxFactory.Token(SyntaxKind.ExclamationToken), + ["op_OnesComplement"] = SyntaxFactory.Token(SyntaxKind.TildeToken), + ["op_True"] = SyntaxFactory.Token(SyntaxKind.TrueKeyword), + ["op_False"] = SyntaxFactory.Token(SyntaxKind.FalseKeyword), + ["op_Implicit"] = SyntaxFactory.Token(SyntaxKind.ImplicitKeyword), + ["op_Explicit"] = SyntaxFactory.Token(SyntaxKind.ExplicitKeyword), + }; + private TypeDeclarationSyntax GenerateMethods(List baseTypes) { if (typeDeclaration == null) { throw new Exception("Type declaration is null"); // This should never happen @@ -651,6 +678,15 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; + if (operatorTokens.ContainsKey(methodName ?? "UnknownMethod")) { + // Add SpecialName attribute to the method + methodDeclaration = methodDeclaration.AddAttributeLists( + SyntaxFactory.AttributeList().AddAttributes(SyntaxFactory.Attribute( + SyntaxFactory.ParseName("global::System.Runtime.CompilerServices.SpecialName")) + ) + ); + } + simpleMethodSignature += methodName; // Add full method name as a MethodName attribute to the method From 5a60d5266281de5b5f9ead3387e4d0b2b96b728d Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 25 May 2024 21:37:41 -0700 Subject: [PATCH 179/207] .NET: Add RE4 test script and easier way of adding test projects --- csharp-api/CMakeLists.txt | 218 +++++++++++++------------------- csharp-api/cmake.toml | 214 +++++++++---------------------- csharp-api/make_symlinks.py | 1 + csharp-api/test/Test/TestRE4.cs | 34 +++++ 4 files changed, 184 insertions(+), 283 deletions(-) create mode 100644 csharp-api/test/Test/TestRE4.cs diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 8bcc8c223..2febd5527 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -71,48 +71,56 @@ if(NOT NUGET_PACKAGES_DIR) set(NUGET_PACKAGES_DIR ${DEFAULT_NUGET_PATH}) endif() -# Set a bool if the generated reference assemblies exist in the build/bin folder -set(dd2_systemdll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET._System.dll") -set(dd2_applicationdll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET.application.dll") -set(dd2_viacoredll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET.viacore.dll") - -set(re2_mscorlibdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._mscorlib.dll") -set(re2_systemdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._System.dll") -set(re2_systemcoredll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._System.Core.dll") -set(re2_applicationdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET.application.dll") -set(re2_viacoredll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET.viacore.dll") - -# Initialize a variable to keep track of the existence of all files -set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2 TRUE) -set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2 TRUE) - -# Check if each DLL exists -foreach(dll IN ITEMS ${dd2_systemdll} ${dd2_applicationdll} ${dd2_viacoredll}) - if(NOT EXISTS ${dll}) - set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2 FALSE) - break() # Exit the loop as soon as one file is not found - endif() -endforeach() +# Helper function for setting target properties with custom DLLs +function(set_dotnet_references TARGET DLL_PREFIX DLLS) + foreach(dll IN ITEMS ${DLLS}) + string(REPLACE "REFramework.NET." "" DLL_NAME ${dll}) + string(REPLACE ".dll" "" DLL_NAME ${DLL_NAME}) + set_target_properties(${TARGET} PROPERTIES + VS_DOTNET_REFERENCE_${DLL_NAME} + "${CMAKE_BINARY_DIR}/bin/${DLL_PREFIX}/REFramework.NET.${DLL_NAME}.dll" + ) + set_target_properties(${TARGET} PROPERTIES + VS_PACKAGE_REFERENCES "REFramework.NET.${DLL_NAME}" + ) + endforeach() + + set_target_properties(${TARGET} PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET + "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" + ) +endfunction() + +# Helper function for checking custom DLL existence +function(check_dlls_exist DLL_PREFIX DLLS VAR_NAME) + message(STATUS "${VAR_NAME} Checking for DLLs in ${CMAKE_BINARY_DIR}/bin/${DLL_PREFIX}") + set(${VAR_NAME} TRUE PARENT_SCOPE) + foreach(dll IN ITEMS ${DLLS}) + if(NOT EXISTS "${CMAKE_BINARY_DIR}/bin/${DLL_PREFIX}/${dll}") + set(${VAR_NAME} FALSE) + message(STATUS "DLL ${dll} does not exist in ${CMAKE_BINARY_DIR}/bin/${DLL_PREFIX}") + break() + endif() + endforeach() -foreach(dll IN ITEMS ${re2_mscorlibdll} ${re2_systemdll} ${re2_systemcoredll} ${re2_applicationdll} ${re2_viacoredll}) - if(NOT EXISTS ${dll}) - set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2 FALSE) - break() # Exit the loop as soon as one file is not found + if (NOT ${${VAR_NAME}}) + message(STATUS "One or more specified DLLs do not exist (${DLL_PREFIX})") + else() + message(STATUS "All specified DLLs exist (${DLL_PREFIX})") endif() -endforeach() +endfunction() -# Use the result -if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2) - message(STATUS "All specified DLLs exist (DD2)") -else() - message(STATUS "One or more specified DLLs do not exist (DD2)") -endif() +# RE2 +set(RE2_DLLS "REFramework.NET._mscorlib.dll;REFramework.NET._System.dll;REFramework.NET._System.Core.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("re2" "${RE2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2") -if (REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) - message(STATUS "All specified DLLs exist (RE2)") -else() - message(STATUS "One or more specified DLLs do not exist (RE2)") -endif() +# RE4 +set(RE4_DLLS "REFramework.NET._mscorlib.dll;REFramework.NET._System.dll;REFramework.NET._System.Core.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("re4" "${RE4_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4") + +# DD2 +set(DD2_DLLS "REFramework.NET._System.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("dd2" "${DD2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2") # Define a list of NuGet packages and their versions # the last part, the package framework will only be used for copying the files @@ -390,31 +398,7 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2) # build-csharp-test-dd2 ) set(CMKR_TARGET CSharpAPITest) - set_target_properties(CSharpAPITest PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET - "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" - ) - -set_target_properties(CSharpAPITest PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET._System - "${dd2_systemdll}" - ) - -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") - -set_target_properties(CSharpAPITest PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET.viacore - "${dd2_viacoredll}" - ) - -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") - -set_target_properties(CSharpAPITest PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET.application - "${dd2_applicationdll}" - ) - -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") + set_dotnet_references(CSharpAPITest "dd2" "${DD2_DLLS}") endif() # Target: CSharpAPITestRE2 @@ -455,45 +439,48 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) # build-csharp-test-re2 ) set(CMKR_TARGET CSharpAPITestRE2) - set_target_properties(CSharpAPITestRE2 PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET - "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" - ) - -set_target_properties(CSharpAPITestRE2 PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET._mscorlib - "${re2_mscorlibdll}" - ) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib") - -set_target_properties(CSharpAPITestRE2 PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET._System - "${re2_systemdll}" - ) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") - -set_target_properties(CSharpAPITestRE2 PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET.System.Core - "${re2_systemcoredll}" + set_dotnet_references(CSharpAPITestRE2 "re2" "${RE2_DLLS}") + +endif() +# Target: CSharpAPITestRE4 +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4) # build-csharp-test-re4 + set(CSharpAPITestRE4_SOURCES + "test/Test/TestRE4.cs" + cmake.toml ) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core") - -set_target_properties(CSharpAPITestRE2 PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET.viacore - "${re2_viacoredll}" + + add_library(CSharpAPITestRE4 SHARED) + + target_sources(CSharpAPITestRE4 PRIVATE ${CSharpAPITestRE4_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITestRE4_SOURCES}) + + target_link_libraries(CSharpAPITestRE4 PUBLIC + csharp-api ) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") - -set_target_properties(CSharpAPITestRE2 PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET.application - "${re2_applicationdll}" + + set_target_properties(CSharpAPITestRE4 PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary ) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") + + set(CMKR_TARGET CSharpAPITestRE4) + set_dotnet_references(CSharpAPITestRE4 "re4" "${RE4_DLLS}") endif() # Target: ObjectExplorer @@ -534,37 +521,6 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) # build-csharp-test-re2 ) set(CMKR_TARGET ObjectExplorer) - set_target_properties(ObjectExplorer PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET - "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" - ) - -set_target_properties(ObjectExplorer PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET._mscorlib - "${re2_mscorlibdll}" - ) - -set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib") - -set_target_properties(ObjectExplorer PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET._System - "${re2_systemdll}" - ) - -set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") - -set_target_properties(ObjectExplorer PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET.System.Core - "${re2_systemcoredll}" - ) - -set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core") - -set_target_properties(ObjectExplorer PROPERTIES - VS_DOTNET_REFERENCE_REFramework.NET.viacore - "${re2_viacoredll}" - ) - -set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") + set_dotnet_references(ObjectExplorer "re2" "${RE2_DLLS}") endif() diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 9d4a48eb3..11f7ebe46 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -44,48 +44,56 @@ if(NOT NUGET_PACKAGES_DIR) set(NUGET_PACKAGES_DIR ${DEFAULT_NUGET_PATH}) endif() -# Set a bool if the generated reference assemblies exist in the build/bin folder -set(dd2_systemdll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET._System.dll") -set(dd2_applicationdll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET.application.dll") -set(dd2_viacoredll "${CMAKE_BINARY_DIR}/bin/dd2/REFramework.NET.viacore.dll") - -set(re2_mscorlibdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._mscorlib.dll") -set(re2_systemdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._System.dll") -set(re2_systemcoredll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET._System.Core.dll") -set(re2_applicationdll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET.application.dll") -set(re2_viacoredll "${CMAKE_BINARY_DIR}/bin/re2/REFramework.NET.viacore.dll") - -# Initialize a variable to keep track of the existence of all files -set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2 TRUE) -set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2 TRUE) - -# Check if each DLL exists -foreach(dll IN ITEMS ${dd2_systemdll} ${dd2_applicationdll} ${dd2_viacoredll}) - if(NOT EXISTS ${dll}) - set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2 FALSE) - break() # Exit the loop as soon as one file is not found - endif() -endforeach() +# Helper function for setting target properties with custom DLLs +function(set_dotnet_references TARGET DLL_PREFIX DLLS) + foreach(dll IN ITEMS ${DLLS}) + string(REPLACE "REFramework.NET." "" DLL_NAME ${dll}) + string(REPLACE ".dll" "" DLL_NAME ${DLL_NAME}) + set_target_properties(${TARGET} PROPERTIES + VS_DOTNET_REFERENCE_${DLL_NAME} + "${CMAKE_BINARY_DIR}/bin/${DLL_PREFIX}/REFramework.NET.${DLL_NAME}.dll" + ) + set_target_properties(${TARGET} PROPERTIES + VS_PACKAGE_REFERENCES "REFramework.NET.${DLL_NAME}" + ) + endforeach() + + set_target_properties(${TARGET} PROPERTIES + VS_DOTNET_REFERENCE_REFramework.NET + "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" + ) +endfunction() + +# Helper function for checking custom DLL existence +function(check_dlls_exist DLL_PREFIX DLLS VAR_NAME) + message(STATUS "${VAR_NAME} Checking for DLLs in ${CMAKE_BINARY_DIR}/bin/${DLL_PREFIX}") + set(${VAR_NAME} TRUE PARENT_SCOPE) + foreach(dll IN ITEMS ${DLLS}) + if(NOT EXISTS "${CMAKE_BINARY_DIR}/bin/${DLL_PREFIX}/${dll}") + set(${VAR_NAME} FALSE) + message(STATUS "DLL ${dll} does not exist in ${CMAKE_BINARY_DIR}/bin/${DLL_PREFIX}") + break() + endif() + endforeach() -foreach(dll IN ITEMS ${re2_mscorlibdll} ${re2_systemdll} ${re2_systemcoredll} ${re2_applicationdll} ${re2_viacoredll}) - if(NOT EXISTS ${dll}) - set(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2 FALSE) - break() # Exit the loop as soon as one file is not found + if (NOT ${${VAR_NAME}}) + message(STATUS "One or more specified DLLs do not exist (${DLL_PREFIX})") + else() + message(STATUS "All specified DLLs exist (${DLL_PREFIX})") endif() -endforeach() +endfunction() -# Use the result -if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2) - message(STATUS "All specified DLLs exist (DD2)") -else() - message(STATUS "One or more specified DLLs do not exist (DD2)") -endif() +# RE2 +set(RE2_DLLS "REFramework.NET._mscorlib.dll;REFramework.NET._System.dll;REFramework.NET._System.Core.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("re2" "${RE2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2") -if (REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) - message(STATUS "All specified DLLs exist (RE2)") -else() - message(STATUS "One or more specified DLLs do not exist (RE2)") -endif() +# RE4 +set(RE4_DLLS "REFramework.NET._mscorlib.dll;REFramework.NET._System.dll;REFramework.NET._System.Core.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("re4" "${RE4_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4") + +# DD2 +set(DD2_DLLS "REFramework.NET._System.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("dd2" "${DD2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2") # Define a list of NuGet packages and their versions # the last part, the package framework will only be used for copying the files @@ -111,6 +119,7 @@ endforeach() [conditions] build-csharp-test-dd2 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2" build-csharp-test-re2 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2" +build-csharp-test-re4 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4" [template.CSharpSharedTarget] type = "shared" @@ -206,139 +215,40 @@ set_target_properties(AssemblyGenerator PROPERTIES VS_DOTNET_REFERENCE_REFramework.NET "${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" ) - - """ [target.CSharpAPITest] condition = "build-csharp-test-dd2" type = "CSharpSharedTarget" sources = ["test/Test/Test.cs"] -link-libraries = [ - "csharp-api" -] - +link-libraries = ["csharp-api"] cmake-after = """ -set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET -"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" -) - -set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET._System -"${dd2_systemdll}" -) - -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") - -set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.viacore -"${dd2_viacoredll}" -) - -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") - -set_target_properties(CSharpAPITest PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.application -"${dd2_applicationdll}" -) - -set_target_properties(CSharpAPITest PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") - +set_dotnet_references(CSharpAPITest "dd2" "${DD2_DLLS}") """ [target.CSharpAPITestRE2] condition = "build-csharp-test-re2" type = "CSharpSharedTarget" sources = ["test/Test/TestRE2.cs"] -link-libraries = [ - "csharp-api" -] - +link-libraries = ["csharp-api"] cmake-after = """ -set_target_properties(CSharpAPITestRE2 PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET -"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" -) - -set_target_properties(CSharpAPITestRE2 PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET._mscorlib -"${re2_mscorlibdll}" -) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib") - -set_target_properties(CSharpAPITestRE2 PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET._System -"${re2_systemdll}" -) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") - -set_target_properties(CSharpAPITestRE2 PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.System.Core -"${re2_systemcoredll}" -) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core") - -set_target_properties(CSharpAPITestRE2 PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.viacore -"${re2_viacoredll}" -) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") - -set_target_properties(CSharpAPITestRE2 PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.application -"${re2_applicationdll}" -) - -set_target_properties(CSharpAPITestRE2 PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.application") +set_dotnet_references(CSharpAPITestRE2 "re2" "${RE2_DLLS}") +""" +[target.CSharpAPITestRE4] +condition = "build-csharp-test-re4" +type = "CSharpSharedTarget" +sources = ["test/Test/TestRE4.cs"] +link-libraries = ["csharp-api"] +cmake-after = """ +set_dotnet_references(CSharpAPITestRE4 "re4" "${RE4_DLLS}") """ -# Even though this targets RE2 it is not game specific -# It just uses the System/via DLLs which are game agnostic [target.ObjectExplorer] condition = "build-csharp-test-re2" type = "CSharpSharedTarget" sources = ["test/Test/ObjectExplorer.cs"] -link-libraries = [ - "csharp-api" -] - +link-libraries = ["csharp-api"] cmake-after = """ -set_target_properties(ObjectExplorer PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET -"${REFRAMEWORK_DOT_NET_ASSEMBLY_PATH}" -) - -set_target_properties(ObjectExplorer PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET._mscorlib -"${re2_mscorlibdll}" -) - -set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._mscorlib") - -set_target_properties(ObjectExplorer PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET._System -"${re2_systemdll}" -) - -set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET._System") - -set_target_properties(ObjectExplorer PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.System.Core -"${re2_systemcoredll}" -) - -set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.System.Core") - -set_target_properties(ObjectExplorer PROPERTIES -VS_DOTNET_REFERENCE_REFramework.NET.viacore -"${re2_viacoredll}" -) - -set_target_properties(ObjectExplorer PROPERTIES VS_PACKAGE_REFERENCES "REFramework.NET.viacore") -""" \ No newline at end of file +set_dotnet_references(ObjectExplorer "re2" "${RE2_DLLS}") +""" diff --git a/csharp-api/make_symlinks.py b/csharp-api/make_symlinks.py index be475f8ef..aa5fd72e8 100644 --- a/csharp-api/make_symlinks.py +++ b/csharp-api/make_symlinks.py @@ -28,6 +28,7 @@ def symlink_main(gamedir=None, bindir="build/bin", just_copy=False): source_dir_files = [ "Test/Test/Test.cs", "Test/Test/TestRE2.cs", + "Test/Test/TestRE4.cs", "Test/Test/ObjectExplorer.cs", ] diff --git a/csharp-api/test/Test/TestRE4.cs b/csharp-api/test/Test/TestRE4.cs new file mode 100644 index 000000000..6ddf3e6e5 --- /dev/null +++ b/csharp-api/test/Test/TestRE4.cs @@ -0,0 +1,34 @@ +using System; + +using ImGuiNET; +using REFrameworkNET; +using REFrameworkNET.Callbacks; +using REFrameworkNET.Attributes; +using _; + +public class TestRE4Plugin { + static bool IsRunningRE2 => Environment.ProcessPath.Contains("re4", StringComparison.CurrentCultureIgnoreCase); + + [Callback(typeof(ImGuiRender), CallbackType.Pre)] + public static void ImGuiCallback() { + if (ImGui.Begin("Test Window")) { + ImGui.Text("RE4"); + ImGui.Separator(); + + + if (ImGui.TreeNode("Player")) { + var context = GlobalService.Chainsaw._sCharacterManagerCache.getPlayerContextRef(); + + if (context == null) { + ImGui.Text("Player context is null"); + } else { + ImGui.Text($"Player context: {(context as IObject).Call("ToString", null) as string}"); + } + + ImGui.TreePop(); + } + + ImGui.End(); + } + } +} \ No newline at end of file From eab296a950069af5d4b7d6f52992360d10f3a9b2 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 25 May 2024 21:52:39 -0700 Subject: [PATCH 180/207] .NET: Fix test projects getting compiled without required files --- csharp-api/CMakeLists.txt | 1 + csharp-api/cmake.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 2febd5527..923aa76f9 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -104,6 +104,7 @@ function(check_dlls_exist DLL_PREFIX DLLS VAR_NAME) endforeach() if (NOT ${${VAR_NAME}}) + set(${VAR_NAME} FALSE PARENT_SCOPE) message(STATUS "One or more specified DLLs do not exist (${DLL_PREFIX})") else() message(STATUS "All specified DLLs exist (${DLL_PREFIX})") diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 11f7ebe46..389f08cc4 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -77,6 +77,7 @@ function(check_dlls_exist DLL_PREFIX DLLS VAR_NAME) endforeach() if (NOT ${${VAR_NAME}}) + set(${VAR_NAME} FALSE PARENT_SCOPE) message(STATUS "One or more specified DLLs do not exist (${DLL_PREFIX})") else() message(STATUS "All specified DLLs exist (${DLL_PREFIX})") From 0eeccc01d50fe520bfc45e166bbcf76aee5d64fe Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 25 May 2024 22:00:33 -0700 Subject: [PATCH 181/207] .NET: Allow various other method names to be generated --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index c560d2080..cf79ff27d 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -234,16 +234,16 @@ private static TypeSyntax MakeProperType(REFrameworkNET.TypeDefinition? targetTy static readonly SortedSet invalidMethodNames = [ "Finalize", - "MemberwiseClone", - "ToString", - "Equals", - "GetHashCode", - "GetType", + //"MemberwiseClone", + //"ToString", + //"Equals", + //"GetHashCode", + //"GetType", ".ctor", ".cctor", - "op_Implicit", + /*"op_Implicit", "op_Explicit", - /*"op_Addition", + "op_Addition", "op_Subtraction", "op_Multiply", "op_Division", From b9651a1d0b7c7cd25683ce818599287e111b6475 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 25 May 2024 22:40:59 -0700 Subject: [PATCH 182/207] .NET: Update RE4 script --- csharp-api/test/Test/TestRE4.cs | 43 ++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/csharp-api/test/Test/TestRE4.cs b/csharp-api/test/Test/TestRE4.cs index 6ddf3e6e5..1ddfa0375 100644 --- a/csharp-api/test/Test/TestRE4.cs +++ b/csharp-api/test/Test/TestRE4.cs @@ -5,12 +5,16 @@ using REFrameworkNET.Callbacks; using REFrameworkNET.Attributes; using _; +using chainsaw; public class TestRE4Plugin { static bool IsRunningRE2 => Environment.ProcessPath.Contains("re4", StringComparison.CurrentCultureIgnoreCase); + static System.Diagnostics.Stopwatch imguiStopwatch = new(); [Callback(typeof(ImGuiRender), CallbackType.Pre)] public static void ImGuiCallback() { + imguiStopwatch.Restart(); + if (ImGui.Begin("Test Window")) { ImGui.Text("RE4"); ImGui.Separator(); @@ -22,12 +26,49 @@ public static void ImGuiCallback() { if (context == null) { ImGui.Text("Player context is null"); } else { - ImGui.Text($"Player context: {(context as IObject).Call("ToString", null) as string}"); + ImGui.Text($"Player context: {context}"); + + var body = context.BodyGameObject; + var head = context.HeadGameObject; + + bool drawBody = body.DrawSelf; + if (ImGui.Checkbox("Draw body", ref drawBody)) { + body.DrawSelf = drawBody; + } + + ImGui.Text($"Body: {body}"); + ImGui.Text($"Head: {head}"); + + if (ImGui.TreeNode("Components (Body)")) { + var components = body.Components; + + for (int i = 0; i < components.Count; i++) { + var component = components[i]; + ImGui.Text($"Component {i}: {component}"); + } + + ImGui.TreePop(); + } + + if (ImGui.TreeNode("Components (Head)")) { + var components = head.Components; + + for (int i = 0; i < components.Count; i++) { + var component = components[i]; + ImGui.Text($"Component {i}: {component}"); + } + + ImGui.TreePop(); + } } ImGui.TreePop(); } + imguiStopwatch.Stop(); + var microseconds = imguiStopwatch.ElapsedTicks / (System.Diagnostics.Stopwatch.Frequency / 1000000); + ImGui.Text($"Test execution time: {microseconds} µs"); + ImGui.End(); } } From 92ce4cb0469dd95774528dcdb02e7a44bb8d2e17 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 15 Jun 2024 15:42:47 -0700 Subject: [PATCH 183/207] .NET: Make Field IEquatable --- csharp-api/REFrameworkNET/Field.hpp | 52 ++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/Field.hpp b/csharp-api/REFrameworkNET/Field.hpp index 708470072..ece757fc1 100644 --- a/csharp-api/REFrameworkNET/Field.hpp +++ b/csharp-api/REFrameworkNET/Field.hpp @@ -10,7 +10,8 @@ namespace REFrameworkNET { ref class TypeDefinition; -public ref class Field { +public ref class Field : public System::IEquatable +{ public: static Field^ GetInstance(reframework::API::Field* fd) { return NativePool::GetInstance((uintptr_t)fd, s_createFromPointer); @@ -167,6 +168,55 @@ public ref class Field { } } +public: + virtual bool Equals(System::Object^ other) override { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + if (other->GetType() != Field::typeid) { + return false; + } + + return m_field == safe_cast(other)->m_field; + } + + virtual bool Equals(Field^ other) { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + return m_field == other->m_field; + } + + static bool operator ==(Field^ left, Field^ right) { + if (System::Object::ReferenceEquals(left, right)) { + return true; + } + + if (System::Object::ReferenceEquals(left, nullptr) || System::Object::ReferenceEquals(right, nullptr)) { + return false; + } + + return left->m_field == right->m_field; + } + + static bool operator !=(Field^ left, Field^ right) { + return !(left == right); + } + + virtual int GetHashCode() override { + return (gcnew System::UIntPtr((uintptr_t)m_field))->GetHashCode(); + } + private: const reframework::API::Field* m_field; }; From 9c603bef086aaf07201075e512ed39c3f79024dd Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 15 Jun 2024 16:19:50 -0700 Subject: [PATCH 184/207] .NET: Fix a ton of errors in MHRise generation --- .../AssemblyGenerator/ClassGenerator.cs | 89 ++++++++++- csharp-api/AssemblyGenerator/EnumGenerator.cs | 10 ++ csharp-api/AssemblyGenerator/Generator.cs | 151 +++++++++++++----- 3 files changed, 212 insertions(+), 38 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index cf79ff27d..26229f1ad 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Operations; using REFrameworkNET; using System; +using System.ComponentModel.DataAnnotations; public class ClassGenerator { public class PseudoProperty { @@ -421,6 +422,17 @@ private TypeDeclarationSyntax GenerateProperties(List base break; } } + + if (baseTypes.Count > 0 && !shouldAddNewKeyword) { + var declaringType = property.Value.getter.DeclaringType; + if (declaringType != null) { + var parent = declaringType.ParentType; + + if (parent != null && (parent.FindField(propertyName) != null || parent.FindField("<" + propertyName + ">k__BackingField") != null)) { + shouldAddNewKeyword = true; + } + } + } } if (property.Value.setter != null) { @@ -470,6 +482,17 @@ private TypeDeclarationSyntax GenerateProperties(List base break; } } + + if (baseTypes.Count > 0 && !shouldAddNewKeyword) { + var declaringType = property.Value.setter.DeclaringType; + if (declaringType != null) { + var parent = declaringType.ParentType; + + if (parent != null && (parent.FindField(propertyName) != null || parent.FindField("<" + propertyName + ">k__BackingField") != null)) { + shouldAddNewKeyword = true; + } + } + } } if (shouldAddStaticKeyword) { @@ -577,6 +600,7 @@ private TypeDeclarationSyntax GenerateFields(List baseType List bodyStatementsSetter = []; List bodyStatementsGetter = []; + bodyStatementsGetter.Add(SyntaxFactory.ParseStatement("return (" + fieldType.GetText().ToString() + ")" + internalFieldName + ".GetDataBoxed(typeof(" + fieldType.GetText().ToString() + "), 0, false);")); bodyStatementsSetter.Add(SyntaxFactory.ParseStatement(internalFieldName + ".SetDataBoxed(0, new object[] {value}, false);")); @@ -589,10 +613,60 @@ private TypeDeclarationSyntax GenerateFields(List baseType propertyDeclaration = propertyDeclaration.AddAccessorListAccessors(getter, setter); - if (this.t.ParentType != null && this.t.ParentType.FindField(field.Name) != null) { - propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + // Search for k__BackingField version and the corrected version + if (this.t.ParentType != null) { + var matchingField = this.t.ParentType.FindField(fieldName); + matchingField ??= this.t.ParentType.FindField(field.Name); + var matchingMethod = this.t.ParentType.FindMethod("get_" + fieldName); + matchingMethod ??= this.t.ParentType.FindMethod("set_" + fieldName); + + bool added = false; + + if (matchingField != null) { + var parentT = matchingField.DeclaringType; + + if (parentT != null && REFrameworkNET.AssemblyGenerator.validTypes.Contains(parentT.FullName)) { + propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + added = true; + } + } + + if (!added && matchingMethod != null) { + var parentT = matchingMethod.DeclaringType; + + if (parentT != null && REFrameworkNET.AssemblyGenerator.validTypes.Contains(parentT.FullName)) { + propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + } + } + + /*if ((this.t.ParentType.FindField(field.Name) != null || this.t.ParentType.FindField(fieldName) != null) || + (this.t.ParentType.FindMethod("get_" + fieldName) != null || this.t.ParentType.FindMethod("set_" + fieldName) != null)) + { + //propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + }*/ } + /*var fieldExtension = Il2CppDump.GetFieldExtension(field); + + if (fieldExtension != null && fieldExtension.MatchingParentFields.Count > 0) { + var matchingParentFields = fieldExtension.MatchingParentFields; + + // Go through the parents, check if the parents are allowed to be generated + // and add the new keyword if the matching field is found in one allowed to be generated + foreach (var matchingField in matchingParentFields) { + var parent = matchingField.DeclaringType; + if (parent == null) { + continue; + } + if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(parent.FullName)) { + continue; + } + + propertyDeclaration = propertyDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + break; + } + }*/ + return propertyDeclaration; }); @@ -674,6 +748,13 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp var methodName = new string(method.Name); var methodExtension = Il2CppDump.GetMethodExtension(method); + // Hacky fix for MHR because parent classes have the same method names + // while we support that, we don't support constructed generic arguments yet, they are just "object" + if (methodName == "sortCountList") { + Console.WriteLine("Skipping sortCountList"); + return null; + } + var methodDeclaration = SyntaxFactory.MethodDeclaration(returnType, methodName ?? "UnknownMethod") .AddModifiers(new SyntaxToken[]{SyntaxFactory.Token(SyntaxKind.PublicKeyword)}) /*.AddBodyStatements(SyntaxFactory.ParseStatement("throw new System.NotImplementedException();"))*/; @@ -731,6 +812,10 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp paramName = "UnknownParam"; } + if (paramName == "object") { + paramName = "object_"; // object is a reserved keyword. + } + var paramType = param.get_ParameterType(); if (paramType == null) { diff --git a/csharp-api/AssemblyGenerator/EnumGenerator.cs b/csharp-api/AssemblyGenerator/EnumGenerator.cs index 69314cc0e..558ae2e91 100644 --- a/csharp-api/AssemblyGenerator/EnumGenerator.cs +++ b/csharp-api/AssemblyGenerator/EnumGenerator.cs @@ -50,6 +50,16 @@ public void Update(EnumDeclarationSyntax? typeDeclaration) { // Check if we need to add the new keyword to this. if (AssemblyGenerator.NestedTypeExistsInParent(t)) { enumDeclaration = enumDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + } else { + var declaringType = t.DeclaringType; + + if (declaringType != null) { + var existingField = declaringType.FindField(t.Name); + + if (existingField != null && AssemblyGenerator.validTypes.Contains(existingField.DeclaringType.FullName)) { + enumDeclaration = enumDeclaration.AddModifiers(SyntaxFactory.Token(SyntaxKind.NewKeyword)); + } + } } if (t.HasAttribute(s_FlagsAttribute, true)) { diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index b32e1f5b4..6daec304a 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -17,8 +17,13 @@ using System.Reflection.Metadata; public class Il2CppDump { - class Field { + public class Field { + public REFrameworkNET.Field Impl; + public Field(REFrameworkNET.Field impl) { + this.Impl = impl; + } + public List MatchingParentFields = []; }; public class Method { @@ -77,6 +82,7 @@ public Type(REFrameworkNET.TypeDefinition impl) { static private Dictionary typeExtensions = []; static private Dictionary methodExtensions = []; + static private Dictionary fieldExtensions = []; static public Type? GetTypeExtension(REFrameworkNET.TypeDefinition type) { if (typeExtensions.TryGetValue(type, out Type? value)) { return value; @@ -104,6 +110,14 @@ static public Type GetOrAddTypeExtension(REFrameworkNET.TypeDefinition type) { return null; } + static public Field? GetFieldExtension(REFrameworkNET.Field field) { + if (fieldExtensions.TryGetValue(field, out Field? value)) { + return value; + } + + return null; + } + public static void FillTypeExtensions(REFrameworkNET.TDB context) { if (typeExtensions.Count > 0) { return; @@ -125,46 +139,75 @@ public static void FillTypeExtensions(REFrameworkNET.TDB context) { ext.NestedTypes.Add(t); } - if (t.GetNumMethods() == 0 || t.ParentType == null) { - continue; - } + if (t.GetNumMethods() != 0 && t.ParentType != null) { + // Look for methods with the same name and mark them as overrides + // We dont go through all parents, because GetMethod does that for us + // Going through all parents would exponentially increase the number of checks and they would be redundant + var parent = t.ParentType; + var tMethods = t.GetMethods(); - // Look for methods with the same name and mark them as overrides - // We dont go through all parents, because GetMethod does that for us - // Going through all parents would exponentially increase the number of checks and they would be redundant - var parent = t.ParentType; - var tMethods = t.GetMethods(); + //foreach (var method in t.Methods) { + //Parallel.ForEach(tMethods, method => { + foreach (REFrameworkNET.Method method in tMethods) { // parallel isn't necessary here because there arent many methods + if (method == null) { + continue; + } - //foreach (var method in t.Methods) { - //Parallel.ForEach(tMethods, method => { - foreach (REFrameworkNET.Method method in tMethods) { // parallel isn't necessary here because there arent many methods - if (method == null) { - continue; - } + if (GetMethodExtension(method) != null) { + continue; + } - if (GetMethodExtension(method) != null) { - continue; - } + if (method.DeclaringType != t) { + continue; + } - if (method.DeclaringType != t) { - continue; + /*var parentMethod = parent.GetMethod(method.Name); + + if (parentMethod != null) { + methodExtensions.Add(method, new Method(method) { + Override = true + }); + }*/ + + var matchingParentMethods = method.GetMatchingParentMethods(); + + if (matchingParentMethods.Count > 0) { + methodExtensions.Add(method, new Method(method) { + Override = true, + MatchingParentMethods = matchingParentMethods + }); + } } + } - /*var parentMethod = parent.GetMethod(method.Name); + if (t.GetNumFields() != 0 && t.ParentType != null) { + var tFields = t.GetFields(); - if (parentMethod != null) { - methodExtensions.Add(method, new Method(method) { - Override = true - }); - }*/ + foreach (REFrameworkNET.Field field in tFields) { + if (field == null) { + continue; + } - var matchingParentMethods = method.GetMatchingParentMethods(); + if (GetFieldExtension(field) != null) { + continue; + } - if (matchingParentMethods.Count > 0) { - methodExtensions.Add(method, new Method(method) { - Override = true, - MatchingParentMethods = matchingParentMethods - }); + List matchingParentFields = []; + for (var parent = t.ParentType; parent != null; parent = parent.ParentType) { + var parentFields = parent.GetFields(); + + foreach (var parentField in parentFields) { + if (parentField.Name == field.Name) { + matchingParentFields.Add(parentField); + } + } + } + + if (matchingParentFields.Count > 0) { + fieldExtensions.Add(field, new Field(field) { + MatchingParentFields = matchingParentFields + }); + } } } } @@ -193,6 +236,7 @@ public class AssemblyGenerator { '!', ' ', '`', + '\'' ]; public static string FixBadChars(string name) { @@ -212,6 +256,25 @@ public static string FixBadChars(string name) { return name; } + public static string FixBadChars_Internal(string name) { + int first = name.IndexOf('<'); + int last = name.LastIndexOf('>'); + + if (first != -1 && last != -1) { + name = name.Substring(0, first) + name.Substring(first, last - first).Replace('.', '_') + name.Substring(last); + } + + // Replace any invalid characters with underscores + foreach (var c in invalidGenericChars) { + name = name.Replace(c, '_'); + } + + // Replace any "[[", "]]" with "_" + name = name.Replace("[[", "_").Replace("]]", "_"); + + return name; + } + /*public static string FixBadCharsForGeneric(string name) { // Find the first <, and the last >, replace any dots in between with underscores int first = name.IndexOf('<'); @@ -344,6 +407,10 @@ public static void ForEachArrayType( return; } }); + + if (equivalentArray != null) { + return equivalentArray; + } } } } @@ -487,14 +554,24 @@ static void FillValidEntries(REFrameworkNET.TDB context) { } // Generics and arrays not yet supported - if (typeName.Contains("[[") /*|| typeName.Contains("]")*/ || typeName.Contains('!')) { + if (typeName.Contains("[[") || typeName.Contains('!')) { continue; } - if (typeName.Contains('<') && !t.IsGenericTypeDefinition()) { + if (typeName.Contains('<') || typeName.Contains('`')) { continue; } + // Check if abstract type and skip + /*var runtimeType = t.GetRuntimeType(); + + if (runtimeType != null && (runtimeType as dynamic).get_IsInterface()) { + System.Console.WriteLine("Skipping interface " + typeName); + continue; + } + + var friendlyTypeName = FixBadChars_Internal(typeName);*/ + if (t.Namespace == null || t.Namespace.Length == 0) { if (typeName.Length == 0) { continue; @@ -503,7 +580,7 @@ static void FillValidEntries(REFrameworkNET.TDB context) { var optionalPrefix = GetOptionalPrefix(t); if (optionalPrefix != null) { - typeFullRenames[t] = optionalPrefix + t.GetFullName(); + typeFullRenames[t] = optionalPrefix + typeName; } } @@ -602,7 +679,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin if (generator.TypeDeclaration == null) { return compilationUnit; - } + } var generatedNamespace = ExtractNamespaceFromType(t); @@ -732,6 +809,8 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin //compilationUnit = compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName("System"))); + System.Console.WriteLine("Compiling " + strippedAssemblyName + " with " + syntaxTrees.Count + " syntax trees..."); + var csoptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, optimizationLevel: OptimizationLevel.Release, assemblyIdentityComparer: DesktopAssemblyIdentityComparer.Default, From fce175eca2385db846a62c1b102c98d791139e7d Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 15 Jun 2024 16:32:48 -0700 Subject: [PATCH 185/207] .NET: Stop field generation when it exceeds runtime limitation --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 26229f1ad..95ff00582 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -520,6 +520,8 @@ private TypeDeclarationSyntax GenerateFields(List baseType List validFields = []; + int totalFields = 0; + foreach (var field in fields) { if (field == null) { continue; @@ -552,7 +554,15 @@ private TypeDeclarationSyntax GenerateFields(List baseType continue; } + ++totalFields; + validFields.Add(field); + + // Some kind of limitation in the runtime prevents too many methods in the class + if (totalFields >= (ushort.MaxValue - 15) / 2) { + System.Console.WriteLine("Skipping fields in " + t.FullName + " because it has too many fields (" + fields.Count + ")"); + break; + } } var matchingFields = validFields From 50cd0006cae94afc1ae6feb6b1c82edcc6711883 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 15 Jun 2024 16:54:41 -0700 Subject: [PATCH 186/207] Plugins: Revert back to initializing plugins on separate threads --- src/mods/PluginLoader.cpp | 25 +++++++++++++++++-------- src/mods/PluginLoader.hpp | 5 ++++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index a012b6ffe..2595479f9 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -643,16 +643,11 @@ void PluginLoader::early_init() try { } void PluginLoader::on_frame() { - if (m_plugins_loaded) { - return; - } - - m_plugins_loaded = true; - - std::scoped_lock _{m_mux}; + init_d3d_pointers(); +} +void PluginLoader::init_d3d_pointers() { // Call reframework_plugin_required_version on any dlls that export it. - g_plugin_initialize_param.reframework_module = g_framework->get_reframework_module(); reframework::g_renderer_data.renderer_type = (int)g_framework->get_renderer_type(); if (reframework::g_renderer_data.renderer_type == REFRAMEWORK_RENDERER_D3D11) { @@ -667,9 +662,15 @@ void PluginLoader::on_frame() { reframework::g_renderer_data.swapchain = d3d12->get_swap_chain(); reframework::g_renderer_data.command_queue = d3d12->get_command_queue(); } +} + +std::optional PluginLoader::on_initialize() { + std::scoped_lock _{m_mux}; verify_sdk_pointers(); + g_plugin_initialize_param.reframework_module = g_framework->get_reframework_module(); + for (auto it = m_plugins.begin(); it != m_plugins.end();) { auto name = it->first; auto mod = it->second; @@ -759,6 +760,10 @@ void PluginLoader::on_frame() { ++it; } + + m_plugins_loaded = true; + + return Mod::on_initialize(); } void PluginLoader::on_draw_ui() { @@ -892,6 +897,8 @@ bool reframework_on_imgui_frame(REFOnImGuiFrameCb cb) { if (cb == nullptr) { return false; } + + PluginLoader::get()->init_d3d_pointers(); return APIProxy::get()->add_on_imgui_frame(cb); } @@ -902,5 +909,7 @@ bool reframework_on_imgui_draw_ui(REFOnImGuiFrameCb cb) { return false; } + PluginLoader::get()->init_d3d_pointers(); + return APIProxy::get()->add_on_imgui_draw_ui(cb); } \ No newline at end of file diff --git a/src/mods/PluginLoader.hpp b/src/mods/PluginLoader.hpp index cc5e2a2b8..cb830e824 100644 --- a/src/mods/PluginLoader.hpp +++ b/src/mods/PluginLoader.hpp @@ -35,9 +35,12 @@ class PluginLoader : public Mod { void early_init(); std::string_view get_name() const override { return "PluginLoader"; } + std::optional on_initialize() override; void on_frame() override; void on_draw_ui() override; - + + void init_d3d_pointers(); + private: bool m_plugins_loaded{false}; From 8e6f3aad4700aebc71808be030bb6dd224c09934 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 15 Jun 2024 16:54:51 -0700 Subject: [PATCH 187/207] .NET: Speed up generation --- csharp-api/AssemblyGenerator/Generator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 6daec304a..318d486f0 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -795,7 +795,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); foreach (var cu in compilationUnits) { - syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu.NormalizeWhitespace(), syntaxTreeParseOption)); + syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu, syntaxTreeParseOption)); } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); From fe3b0d5e18da9933d6e426eb6686b25d1786b822 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 16 Sep 2024 18:00:00 -0700 Subject: [PATCH 188/207] .NET: Speed up generation time, fix massive local GC heap overflow --- csharp-api/REFrameworkNET/Method.cpp | 34 +++++++------------- csharp-api/REFrameworkNET/TypeDefinition.cpp | 29 +++++++++++++++++ csharp-api/REFrameworkNET/TypeDefinition.hpp | 4 +++ 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index d6a5bcae7..fad307282 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -28,30 +28,18 @@ ManagedObject^ Method::GetRuntimeMethod() { return nullptr; } - // System.Type - auto runtimeType = declaringType->GetRuntimeType()/*->As()*/; - - if (runtimeType == nullptr) { - return nullptr; - } - - // Iterate over all methods in the runtime type - auto methods = (REFrameworkNET::ManagedObject^)runtimeType->Call("GetMethods(System.Reflection.BindingFlags)", System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Instance | System::Reflection::BindingFlags::Static); - - if (methods != nullptr) { - auto methodDefName = this->Name; - for each (REFrameworkNET::ManagedObject^ method in methods) { - // Get the type handle and compare it to this (raw pointer stored in the Method object) - // System.RuntimeMethodHandle automatically gets converted to a Method object - auto methodHandle = (Method^)method->Call("get_MethodHandle"); - - if (methodHandle == nullptr) { - continue; - } + auto methods = declaringType->GetRuntimeMethods(); + for each (REFrameworkNET::ManagedObject^ method in methods) { + // Get the type handle and compare it to this (raw pointer stored in the Method object) + // System.RuntimeMethodHandle automatically gets converted to a Method object + auto methodHandle = (Method^)method->Call("get_MethodHandle"); + + if (methodHandle == nullptr) { + continue; + } - if (methodHandle->GetRaw() == this->GetRaw()) { - return method; - } + if (methodHandle->GetRaw() == this->GetRaw()) { + return method; } } diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index e9818c656..5e37f866a 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -299,4 +299,33 @@ namespace REFrameworkNET { return m_elementType; } + + System::Collections::Generic::List^ TypeDefinition::GetRuntimeMethods() { + if (m_runtimeMethods == nullptr) { + m_runtimeMethods = gcnew System::Collections::Generic::List(); + auto runtimeType = GetRuntimeType(); + + m_lock->EnterWriteLock(); + + try { + if (runtimeType != nullptr) { + auto methods = (REFrameworkNET::ManagedObject^)runtimeType->Call("GetMethods(System.Reflection.BindingFlags)", System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Instance | System::Reflection::BindingFlags::Static); + + if (methods != nullptr) { + for each (REFrameworkNET::ManagedObject^ method in methods) { + if (method == nullptr) { + continue; + } + + m_runtimeMethods->Add(method); + } + } + } + } finally { + m_lock->ExitWriteLock(); + } + } + + return m_runtimeMethods; + } } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TypeDefinition.hpp b/csharp-api/REFrameworkNET/TypeDefinition.hpp index df24f0ae9..280500b5f 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.hpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.hpp @@ -410,6 +410,8 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, } } + System::Collections::Generic::List^ GetRuntimeMethods(); + /*Void* GetInstance() { return m_type->get_instance(); @@ -525,5 +527,7 @@ public ref class TypeDefinition : public System::Dynamic::DynamicObject, System::Collections::Generic::List^ m_methods{nullptr}; System::Collections::Generic::List^ m_fields{nullptr}; System::Collections::Generic::List^ m_properties{nullptr}; + + System::Collections::Generic::List^ m_runtimeMethods{nullptr}; }; } \ No newline at end of file From 8136d488b26159fb132a1ad62064af54d3fda756 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 16 Sep 2024 18:01:38 -0700 Subject: [PATCH 189/207] .NET: Move list creation into write lock --- csharp-api/REFrameworkNET/TypeDefinition.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/TypeDefinition.cpp b/csharp-api/REFrameworkNET/TypeDefinition.cpp index 5e37f866a..8c48cd30d 100644 --- a/csharp-api/REFrameworkNET/TypeDefinition.cpp +++ b/csharp-api/REFrameworkNET/TypeDefinition.cpp @@ -302,15 +302,16 @@ namespace REFrameworkNET { System::Collections::Generic::List^ TypeDefinition::GetRuntimeMethods() { if (m_runtimeMethods == nullptr) { - m_runtimeMethods = gcnew System::Collections::Generic::List(); auto runtimeType = GetRuntimeType(); m_lock->EnterWriteLock(); + m_runtimeMethods = gcnew System::Collections::Generic::List(); + try { if (runtimeType != nullptr) { auto methods = (REFrameworkNET::ManagedObject^)runtimeType->Call("GetMethods(System.Reflection.BindingFlags)", System::Reflection::BindingFlags::Public | System::Reflection::BindingFlags::NonPublic | System::Reflection::BindingFlags::Instance | System::Reflection::BindingFlags::Static); - + if (methods != nullptr) { for each (REFrameworkNET::ManagedObject^ method in methods) { if (method == nullptr) { From 9d7d203860cf4cfb8a49d0714c3391c8d039bd9e Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 16 Sep 2024 18:27:25 -0700 Subject: [PATCH 190/207] .NET: Fix non-ASCII characters failing compilation --- csharp-api/AssemblyGenerator/Generator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index 318d486f0..bb194a314 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -562,6 +562,11 @@ static void FillValidEntries(REFrameworkNET.TDB context) { continue; } + if (typeName.Any(c => c > 127)) { + System.Console.WriteLine("Skipping type with non-ascii characters " + typeName); + continue; + } + // Check if abstract type and skip /*var runtimeType = t.GetRuntimeType(); From 331022b738923fa9e4b3ff3e216e02a14a3ce9b3 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 16 Sep 2024 18:45:08 -0700 Subject: [PATCH 191/207] .NET: Fix ObjectExplorer.cs compilation in DMC5 --- csharp-api/CMakeLists.txt | 6 ++++++ csharp-api/cmake.toml | 6 ++++++ csharp-api/test/Test/ObjectExplorer.cs | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 923aa76f9..d544b7e14 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -119,10 +119,16 @@ check_dlls_exist("re2" "${RE2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2") set(RE4_DLLS "REFramework.NET._mscorlib.dll;REFramework.NET._System.dll;REFramework.NET._System.Core.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") check_dlls_exist("re4" "${RE4_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4") +# DMC5 +set(DMC5_DLLS "REFramework.NET._mscorlib.dll;REFramework.NET._System.dll;REFramework.NET._System.Core.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("dmc5" "${DMC5_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DMC5") + # DD2 set(DD2_DLLS "REFramework.NET._System.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") check_dlls_exist("dd2" "${DD2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2") + + # Define a list of NuGet packages and their versions # the last part, the package framework will only be used for copying the files set(REFRAMEWORK_NUGET_PACKAGES diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 389f08cc4..46bd4b62d 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -92,10 +92,16 @@ check_dlls_exist("re2" "${RE2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2") set(RE4_DLLS "REFramework.NET._mscorlib.dll;REFramework.NET._System.dll;REFramework.NET._System.Core.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") check_dlls_exist("re4" "${RE4_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4") +# DMC5 +set(DMC5_DLLS "REFramework.NET._mscorlib.dll;REFramework.NET._System.dll;REFramework.NET._System.Core.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("dmc5" "${DMC5_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DMC5") + # DD2 set(DD2_DLLS "REFramework.NET._System.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") check_dlls_exist("dd2" "${DD2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2") + + # Define a list of NuGet packages and their versions # the last part, the package framework will only be used for copying the files set(REFRAMEWORK_NUGET_PACKAGES diff --git a/csharp-api/test/Test/ObjectExplorer.cs b/csharp-api/test/Test/ObjectExplorer.cs index c65efe49a..1597d438e 100644 --- a/csharp-api/test/Test/ObjectExplorer.cs +++ b/csharp-api/test/Test/ObjectExplorer.cs @@ -475,7 +475,7 @@ public static void Render() { if (ImGui.TreeNode("AppDomain")) { if (assemblies != null && ImGui.TreeNode("Assemblies")) { for (int i = 0; i < assemblies.Length; i++) { - var assembly = assemblies[i]; + var assembly = assemblies.Get(i); // There is a strange thing in the generation where newer REE games do not generate an accessor for this, so we have to use Get instead var assemblyT = (assembly as IObject).GetTypeDefinition(); var location = assembly.Location ?? "null"; From 4cac9a4dbcefd7a3630045431fefb1dff4ee37c7 Mon Sep 17 00:00:00 2001 From: praydog Date: Sat, 15 Mar 2025 23:09:45 -0700 Subject: [PATCH 192/207] .NET: Expose Module APIs --- csharp-api/CMakeLists.txt | 2 + csharp-api/REFrameworkNET/Module.cpp | 1 + csharp-api/REFrameworkNET/Module.hpp | 165 +++++++++++++++++++++++++++ csharp-api/REFrameworkNET/TDB.hpp | 17 +++ 4 files changed, 185 insertions(+) create mode 100644 csharp-api/REFrameworkNET/Module.cpp create mode 100644 csharp-api/REFrameworkNET/Module.hpp diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index d544b7e14..19d7a544d 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -198,6 +198,7 @@ set(csharp-api_SOURCES "REFrameworkNET/ManagedObject.cpp" "REFrameworkNET/Method.cpp" "REFrameworkNET/MethodHook.cpp" + "REFrameworkNET/Module.cpp" "REFrameworkNET/NativeObject.cpp" "REFrameworkNET/Plugin.cpp" "REFrameworkNET/PluginLoadContext.cpp" @@ -225,6 +226,7 @@ set(csharp-api_SOURCES "REFrameworkNET/Method.hpp" "REFrameworkNET/MethodHook.hpp" "REFrameworkNET/MethodParameter.hpp" + "REFrameworkNET/Module.hpp" "REFrameworkNET/NativeObject.hpp" "REFrameworkNET/NativePool.hpp" "REFrameworkNET/NativeSingleton.hpp" diff --git a/csharp-api/REFrameworkNET/Module.cpp b/csharp-api/REFrameworkNET/Module.cpp new file mode 100644 index 000000000..a089b51b7 --- /dev/null +++ b/csharp-api/REFrameworkNET/Module.cpp @@ -0,0 +1 @@ +#include "Module.hpp" \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/Module.hpp b/csharp-api/REFrameworkNET/Module.hpp new file mode 100644 index 000000000..e4b990706 --- /dev/null +++ b/csharp-api/REFrameworkNET/Module.hpp @@ -0,0 +1,165 @@ +#pragma once + +#include + +#include "NativePool.hpp" + +#pragma managed + +namespace REFrameworkNET { +public ref class Module : public System::IEquatable +{ +public: + static Module^ GetInstance(reframework::API::Module* fd) { + return NativePool::GetInstance((uintptr_t)fd, s_createFromPointer); + } + + static Module^ GetInstance(::REFrameworkModuleHandle handle) { + return NativePool::GetInstance((uintptr_t)handle, s_createFromPointer); + } + +private: + static Module^ createFromPointer(uintptr_t ptr) { + return gcnew Module((reframework::API::Module*)ptr); + } + + static NativePool::CreatorDelegate^ s_createFromPointer = gcnew NativePool::CreatorDelegate(createFromPointer); + +private: + Module(const reframework::API::Module* module) : m_module(module) {} + Module(::REFrameworkModuleHandle* handle) : m_module(reinterpret_cast(handle)) {} + +public: + uint32_t GetMajor() { + return m_module->get_major(); + } + + uint32_t GetMinor() { + return m_module->get_minor(); + } + + uint32_t GetRevision() { + return m_module->get_revision(); + } + + uint32_t GetBuild() { + return m_module->get_build(); + } + + System::String^ GetAssemblyName() { + return gcnew System::String(m_module->get_assembly_name()); + } + + System::String^ GetLocation() { + return gcnew System::String(m_module->get_location()); + } + + System::String^ GetModuleName() { + return gcnew System::String(m_module->get_module_name()); + } + + System::Span GetTypes() { + const auto cpp_span = m_module->get_types(); + return System::Span(cpp_span.data(), cpp_span.size()); + } + + property System::Span Types { + System::Span get() { + return GetTypes(); + } + } + + property uint32_t Major { + uint32_t get() { + return GetMajor(); + } + } + + property uint32_t Minor { + uint32_t get() { + return GetMinor(); + } + } + + property uint32_t Revision { + uint32_t get() { + return GetRevision(); + } + } + + property uint32_t Build { + uint32_t get() { + return GetBuild(); + } + } + + property System::String^ AssemblyName { + System::String^ get() { + return GetAssemblyName(); + } + } + + property System::String^ Location { + System::String^ get() { + return GetLocation(); + } + } + + property System::String^ ModuleName { + System::String^ get() { + return GetModuleName(); + } + } + + virtual bool Equals(System::Object^ other) override { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + if (other->GetType() != Module::typeid) { + return false; + } + + return m_module == safe_cast(other)->m_module; + } + + virtual bool Equals(Module^ other) { + if (System::Object::ReferenceEquals(this, other)) { + return true; + } + + if (System::Object::ReferenceEquals(other, nullptr)) { + return false; + } + + return m_module == other->m_module; + } + + static bool operator ==(Module^ left, Module^ right) { + if (System::Object::ReferenceEquals(left, right)) { + return true; + } + + if (System::Object::ReferenceEquals(left, nullptr) || System::Object::ReferenceEquals(right, nullptr)) { + return false; + } + + return left->m_module == right->m_module; + } + + static bool operator !=(Module^ left, Module^ right) { + return !(left == right); + } + + virtual int GetHashCode() override { + return (gcnew System::UIntPtr((uintptr_t)m_module))->GetHashCode(); + } + +private: + const reframework::API::Module* m_module; +}; +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/TDB.hpp b/csharp-api/REFrameworkNET/TDB.hpp index b2cc1979b..12a51468f 100644 --- a/csharp-api/REFrameworkNET/TDB.hpp +++ b/csharp-api/REFrameworkNET/TDB.hpp @@ -1,10 +1,13 @@ #pragma once +#include + #include #pragma managed #include +#include "Module.hpp" #include "TypeDefinition.hpp" #include "Method.hpp" #include "Field.hpp" @@ -44,6 +47,10 @@ public ref class TDB { return (uintptr_t)m_tdb; } + uint32_t GetNumModules() { + return m_tdb->get_num_modules(); + } + uint32_t GetNumTypes() { return m_tdb->get_num_types(); } @@ -197,6 +204,16 @@ public ref class TDB { return gcnew Property(result); } + Module^ GetModule(uint32_t index) { + auto result = m_tdb->get_module(index); + + if (result == nullptr) { + return nullptr; + } + + return Module::GetInstance(result); + } + protected: generic where T : ref class static reframework::API::TypeDefinition* GetTypeDefinitionPtr(); From 28b35af8d8351d32a27aec1d6141d4806e8e0d77 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Mar 2025 13:00:37 -0700 Subject: [PATCH 193/207] .NET: Workaround for get_MethodHandle not existing in Wilds --- csharp-api/REFrameworkNET/Method.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/Method.cpp b/csharp-api/REFrameworkNET/Method.cpp index fad307282..90b4cca5a 100644 --- a/csharp-api/REFrameworkNET/Method.cpp +++ b/csharp-api/REFrameworkNET/Method.cpp @@ -32,7 +32,18 @@ ManagedObject^ Method::GetRuntimeMethod() { for each (REFrameworkNET::ManagedObject^ method in methods) { // Get the type handle and compare it to this (raw pointer stored in the Method object) // System.RuntimeMethodHandle automatically gets converted to a Method object - auto methodHandle = (Method^)method->Call("get_MethodHandle"); + auto rawMethodHandle = (System::IntPtr^)method->GetField("_runtimeHandle"); + auto methodHandle = rawMethodHandle != nullptr ? Method::GetInstance(reinterpret_cast(rawMethodHandle->ToPointer())) : (Method^)method->Call("get_MethodHandle"); + + if (methodHandle == nullptr) { + // This is some stupid way to deal with the absence of get_MethodHandle + // in monster hunter wilds. + uintptr_t hacky_ptr = *(uintptr_t*)((uintptr_t)method->Ptr() + 0x10); + + if (hacky_ptr == (uintptr_t)this->GetRaw()) { + return method; + } + } if (methodHandle == nullptr) { continue; From 606c5c9654277ff9572744fcba68fc9853f1117c Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Mar 2025 20:06:24 -0700 Subject: [PATCH 194/207] .NET: Initial (unfinished) fixes for Wilds assembly generation --- .../AssemblyGenerator/ClassGenerator.cs | 17 ++-- csharp-api/AssemblyGenerator/Generator.cs | 82 +++++++++++++------ 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index 95ff00582..be5c95c2f 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -809,17 +809,22 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp bool anyUnsafeParams = false; - if (runtimeParams != null) { + var methodActualRetval = method.GetReturnType(); + UInt32 unknownArgCount = 0; + foreach (dynamic param in runtimeParams) { - if (param.get_IsRetval() == true) { + /*if (param.get_IsRetval() == true) { continue; - } + }*/ + var paramDef = (REFrameworkNET.TypeDefinition)param.GetTypeDefinition(); var paramName = param.get_Name(); - if (paramName == null) { - paramName = "UnknownParam"; + if (paramName == null || paramName == "") { + //paramName = "UnknownParam"; + paramName = "arg" + unknownArgCount.ToString(); + ++unknownArgCount; } if (paramName == "object") { @@ -842,7 +847,7 @@ private TypeDeclarationSyntax GenerateMethods(List baseTyp var isByRef = paramType.IsByRefImpl(); var isPointer = paramType.IsPointerImpl(); - var isOut = param.get_IsOut(); + var isOut = paramDef != null && paramDef.FindMethod("get_IsOut") != null ? param.get_IsOut() : false; var paramTypeDef = (REFrameworkNET.TypeDefinition)paramType.get_TypeHandle(); var paramTypeSyntax = MakeProperType(paramTypeDef, t); diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index bb194a314..d74cea5a9 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -482,6 +482,7 @@ private static bool HandleArrayType(REFrameworkNET.TypeDefinition t) { var elementTypeDef = t.GetElementType(); if (elementTypeDef == null || !t.IsDerivedFrom(SystemArrayT)) { + System.Console.WriteLine("Failed to get element type for " + t.FullName); return false; } @@ -700,6 +701,12 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin System.Console.WriteLine("Generating array type " + arrayTypeName + " from " + t.FullName); + // Actually I don't care!!! + if (arrayTypeName == "_.System.Array[]") { + System.Console.WriteLine("Skipping array type " + arrayTypeName); + return; + } + var arrayClassGenerator = new ClassGenerator( arrayTypeName, arrayType @@ -737,8 +744,9 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin return []; } - public static REFrameworkNET.Compiler.DynamicAssemblyBytecode? GenerateForAssembly(dynamic assembly, List previousCompilations) { - var strippedAssemblyName = assembly.get_FullName().Split(',')[0]; + public static REFrameworkNET.Compiler.DynamicAssemblyBytecode? GenerateForAssembly(REFrameworkNET.Module assembly, List previousCompilations) { + //var strippedAssemblyName = assembly.get_FullName().Split(',')[0]; + var strippedAssemblyName = assembly.AssemblyName; // Dont want to conflict with the real .NET System if (strippedAssemblyName == "System") { @@ -753,6 +761,10 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin strippedAssemblyName = "_System.Core"; } + if (strippedAssemblyName == "System.Private.CoreLib") { + strippedAssemblyName = "_System.Private.CoreLib"; + } + REFrameworkNET.API.LogInfo("Generating assembly " + strippedAssemblyName); List compilationUnits = []; @@ -760,8 +772,26 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin List typeList = []; - foreach (dynamic reEngineT in assembly.GetTypes()) { - typeList.Add(reEngineT); + foreach (var tIndex in assembly.Types) { + var t = tdb.GetType(tIndex); + + if (t == null) { + Console.WriteLine("Failed to get type " + tIndex); + continue; + } + + dynamic runtimeType = t.GetRuntimeType(); + + if (runtimeType == null) { + Console.WriteLine("Failed to get runtime type for " + t.GetFullName()); + continue; + } + + // We don't want array types, pointers, etc + // Assembly.GetTypes usually filters this out but we have to manually do it + if (runtimeType.IsPointerImpl() == false && runtimeType.IsByRefImpl() == false) { + typeList.Add(runtimeType); + } } // Clean up all the local objects @@ -800,7 +830,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); foreach (var cu in compilationUnits) { - syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu, syntaxTreeParseOption)); + syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu.NormalizeWhitespace(), syntaxTreeParseOption)); } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); @@ -881,35 +911,39 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin Il2CppDump.FillTypeExtensions(tdb); FillValidEntries(tdb); - dynamic appdomainT = tdb.GetType("System.AppDomain"); - dynamic appdomain = appdomainT.get_CurrentDomain(); - var assemblies = (appdomain.GetAssemblies() as ManagedObject)!; + List modules = []; - List assembliesList = []; + // First module is an invalid module + for (uint i = 0; i < tdb.GetNumModules(); i++) { + var module = tdb.GetModule(i); - // Pre-emptively add all assemblies to the list - // because the assemblies list that was returned will be cleaned up by the call to LocalFrameGC - foreach (ManagedObject assembly in assemblies) { - if (assembly == null) { + if (module == null) { continue; } - // The object will get destroyed by the LocalFrameGC call if we don't do this - if (!assembly.IsGlobalized()) { - assembly.Globalize(); - } - - assembliesList.Add(assembly); + modules.Add(module); } List bytecodes = []; - foreach (dynamic assembly in assembliesList) { - var strippedAssemblyName = assembly.get_FullName().Split(',')[0]; - REFrameworkNET.API.LogInfo("Assembly: " + (assembly.get_Location()?.ToString() ?? "NONE")); - REFrameworkNET.API.LogInfo("Assembly (stripped): " + strippedAssemblyName); + foreach (Module module in modules) { + var assemblyName = module.AssemblyName; + var location = module.Location; + var moduleName = module.ModuleName; + + if (assemblyName == null || location == null || moduleName == null) { + continue; + } + + if (assemblyName == "") { + continue; + } + + REFrameworkNET.API.LogInfo("Assembly: " + assemblyName); + REFrameworkNET.API.LogInfo("Location: " + location); + REFrameworkNET.API.LogInfo("Module: " + moduleName); - var bytecode = GenerateForAssembly(assembly, bytecodes); + var bytecode = GenerateForAssembly(module, bytecodes); if (bytecode != null) { bytecodes.Add(bytecode); From d0084f6de9a938ac636c9075827df8bf96016ae2 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Mar 2025 20:19:37 -0700 Subject: [PATCH 195/207] .NET: Fix MHWilds assembly generation --- csharp-api/AssemblyGenerator/Generator.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index d74cea5a9..ff403fc40 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -701,10 +701,9 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin System.Console.WriteLine("Generating array type " + arrayTypeName + " from " + t.FullName); - // Actually I don't care!!! if (arrayTypeName == "_.System.Array[]") { - System.Console.WriteLine("Skipping array type " + arrayTypeName); - return; + typeFullRenames[arrayType] = "System.Array_Array1D"; + arrayTypeName = "System.Array_Array1D"; } var arrayClassGenerator = new ClassGenerator( From dc8feb7386aee6accf5f5295c9de9ee7059f0913 Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Mar 2025 20:27:04 -0700 Subject: [PATCH 196/207] .NET: Revert whitespace normalization change --- csharp-api/AssemblyGenerator/Generator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp-api/AssemblyGenerator/Generator.cs b/csharp-api/AssemblyGenerator/Generator.cs index ff403fc40..42fc3b0cc 100644 --- a/csharp-api/AssemblyGenerator/Generator.cs +++ b/csharp-api/AssemblyGenerator/Generator.cs @@ -829,7 +829,7 @@ static CompilationUnitSyntax MakeFromTypeEntry(REFrameworkNET.TDB context, strin var syntaxTreeParseOption = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12); foreach (var cu in compilationUnits) { - syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu.NormalizeWhitespace(), syntaxTreeParseOption)); + syntaxTrees.Add(SyntaxFactory.SyntaxTree(cu, syntaxTreeParseOption)); } string? assemblyPath = Path.GetDirectoryName(typeof(object).Assembly.Location); From aeae2a4d46b248831c0d006070f88dcfc128bbff Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Mar 2025 21:43:26 -0700 Subject: [PATCH 197/207] .NET: Add MHWilds test --- csharp-api/CMakeLists.txt | 46 ++++++++++++++++++++++++++++- csharp-api/cmake.toml | 14 ++++++++- csharp-api/make_symlinks.py | 1 + csharp-api/test/Test/TestMHWilds.cs | 34 +++++++++++++++++++++ 4 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 csharp-api/test/Test/TestMHWilds.cs diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index 19d7a544d..a0715a7cb 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -127,7 +127,9 @@ check_dlls_exist("dmc5" "${DMC5_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DMC5") set(DD2_DLLS "REFramework.NET._System.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") check_dlls_exist("dd2" "${DD2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2") - +# MHWILDS +set(MHWILDS_DLLS "REFramework.NET._System.Private.CoreLib.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("mhwilds" "${MHWILDS_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_MHWILDS") # Define a list of NuGet packages and their versions # the last part, the package framework will only be used for copying the files @@ -153,6 +155,7 @@ endforeach() set(REFCoreDeps_SOURCES "REFCoreDeps/Compiler.cs" "REFCoreDeps/GarbageCollectionDisplay.cs" + "REFCoreDeps/HashHelper.cs" cmake.toml ) @@ -491,6 +494,47 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4) # build-csharp-test-re4 set(CMKR_TARGET CSharpAPITestRE4) set_dotnet_references(CSharpAPITestRE4 "re4" "${RE4_DLLS}") +endif() +# Target: CSharpAPITestMHWilds +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_MHWILDS) # build-csharp-test-mhwilds + set(CSharpAPITestMHWilds_SOURCES + "test/Test/TestMHWilds.cs" + cmake.toml + ) + + add_library(CSharpAPITestMHWilds SHARED) + + target_sources(CSharpAPITestMHWilds PRIVATE ${CSharpAPITestMHWilds_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITestMHWilds_SOURCES}) + + target_link_libraries(CSharpAPITestMHWilds PUBLIC + csharp-api + ) + + set_target_properties(CSharpAPITestMHWilds PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary + ) + + set(CMKR_TARGET CSharpAPITestMHWilds) + set_dotnet_references(CSharpAPITestMHWilds "mhwilds" "${MHWILDS_DLLS}") + endif() # Target: ObjectExplorer if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) # build-csharp-test-re2 diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 46bd4b62d..1f15bf12c 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -100,7 +100,9 @@ check_dlls_exist("dmc5" "${DMC5_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DMC5") set(DD2_DLLS "REFramework.NET._System.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") check_dlls_exist("dd2" "${DD2_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2") - +# MHWILDS +set(MHWILDS_DLLS "REFramework.NET._System.Private.CoreLib.dll;REFramework.NET.application.dll;REFramework.NET.viacore.dll") +check_dlls_exist("mhwilds" "${MHWILDS_DLLS}" "REFRAMEWORK_REF_ASSEMBLIES_EXIST_MHWILDS") # Define a list of NuGet packages and their versions # the last part, the package framework will only be used for copying the files @@ -127,6 +129,7 @@ endforeach() build-csharp-test-dd2 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2" build-csharp-test-re2 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2" build-csharp-test-re4 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4" +build-csharp-test-mhwilds = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_MHWILDS" [template.CSharpSharedTarget] type = "shared" @@ -251,6 +254,15 @@ cmake-after = """ set_dotnet_references(CSharpAPITestRE4 "re4" "${RE4_DLLS}") """ +[target.CSharpAPITestMHWilds] +condition = "build-csharp-test-mhwilds" +type = "CSharpSharedTarget" +sources = ["test/Test/TestMHWilds.cs"] +link-libraries = ["csharp-api"] +cmake-after = """ +set_dotnet_references(CSharpAPITestMHWilds "mhwilds" "${MHWILDS_DLLS}") +""" + [target.ObjectExplorer] condition = "build-csharp-test-re2" type = "CSharpSharedTarget" diff --git a/csharp-api/make_symlinks.py b/csharp-api/make_symlinks.py index aa5fd72e8..d85cc46bb 100644 --- a/csharp-api/make_symlinks.py +++ b/csharp-api/make_symlinks.py @@ -29,6 +29,7 @@ def symlink_main(gamedir=None, bindir="build/bin", just_copy=False): "Test/Test/Test.cs", "Test/Test/TestRE2.cs", "Test/Test/TestRE4.cs", + "Test/Test/TestMHWilds.cs", "Test/Test/ObjectExplorer.cs", ] diff --git a/csharp-api/test/Test/TestMHWilds.cs b/csharp-api/test/Test/TestMHWilds.cs new file mode 100644 index 000000000..5f515a4c6 --- /dev/null +++ b/csharp-api/test/Test/TestMHWilds.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Reflection; +using ImGuiNET; +using REFrameworkNET.Callbacks; +using REFrameworkNET.Attributes; +using REFrameworkNET; + +class REFrameworkPluginWilds { + [Callback(typeof(ImGuiRender), CallbackType.Pre)] + public static void ImGuiCallback() { + if (ImGui.Begin("TestMHWilds.cs")) { + var playerManager = API.GetManagedSingletonT(); + if (playerManager == null) { + ImGui.End(); + return; + } + + var player = playerManager.getMasterPlayer(); + + if (player == null) { + ImGui.End(); + return; + } + + ImGui.Text("Name: " + player.ContextHolder.Pl._PlayerName); + + ImGui.Text("TestMHWilds.cs"); + ImGui.End(); + } + } +} \ No newline at end of file From 2b38d4e8be54dd510398a031be3b0363f2df271c Mon Sep 17 00:00:00 2001 From: praydog Date: Sun, 16 Mar 2025 21:50:45 -0700 Subject: [PATCH 198/207] .NET: Do not compile reference assemblies unless game or plugin changes --- csharp-api/REFCoreDeps/HashHelper.cs | 19 +++++ csharp-api/REFrameworkNET/PluginManager.cpp | 85 +++++++++++++++++++++ csharp-api/REFrameworkNET/PluginManager.hpp | 2 + 3 files changed, 106 insertions(+) create mode 100644 csharp-api/REFCoreDeps/HashHelper.cs diff --git a/csharp-api/REFCoreDeps/HashHelper.cs b/csharp-api/REFCoreDeps/HashHelper.cs new file mode 100644 index 000000000..e46b53273 --- /dev/null +++ b/csharp-api/REFCoreDeps/HashHelper.cs @@ -0,0 +1,19 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Text; + +namespace REFrameworkNET { +public static class HashHelper +{ + public static string ComputeSHA256(string filePath) + { + using (var sha256 = SHA256.Create()) + using (var stream = File.OpenRead(filePath)) + { + byte[] hash = sha256.ComputeHash(stream); + return BitConverter.ToString(hash).Replace("-", "").ToLower(); + } + } +} +} \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index f00e39981..bfa531fc4 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -13,6 +13,7 @@ using namespace System; using namespace System::IO; using namespace System::Reflection; using namespace System::Collections::Generic; +using namespace System::Text::Json; using namespace msclr::interop; namespace REFrameworkNET { @@ -132,12 +133,92 @@ namespace REFrameworkNET { return LoadAssemblies(gcnew System::String(dependencies_path.wstring().c_str())); } + bool PluginManager::ShouldRecompile(System::String^ metadataPath, System::String^ currentGameHash, System::String^ currentFrameworkHash) { + if (!File::Exists(metadataPath)) { + return true; // No metadata file = force recompile + } + + try { + auto jsonText = File::ReadAllText(metadataPath); + auto jsonDocument = JsonDocument::Parse(jsonText); + auto root = jsonDocument->RootElement; + + System::String^ previousGameHash = root.GetProperty("GameExecutableHash").GetString(); + System::String^ previousFrameworkHash = root.GetProperty("REFrameworkNetHash").GetString(); + + return (previousGameHash != currentGameHash || previousFrameworkHash != currentFrameworkHash); + } catch (Exception^ ex) { + REFrameworkNET::API::LogError("Error reading metadata.json: " + ex->Message); + return true; // Force recompile if there's an issue with the file + } + } + + bool PluginManager::WriteMetadata(System::String^ metadataPath, System::String^ currentGameHash, System::String^ currentFrameworkHash) { + auto metadata = gcnew System::Collections::Generic::Dictionary(); + metadata->Add("GameExecutableHash", currentGameHash); + metadata->Add("REFrameworkNetHash", currentFrameworkHash); + + try { + auto jsonText = JsonSerializer::Serialize(metadata); + File::WriteAllText(metadataPath, jsonText); + System::Console::WriteLine("Wrote metadata.json"); + System::Console::WriteLine(jsonText); + return true; + } catch (Exception^ ex) { + REFrameworkNET::API::LogError("Error writing metadata.json: " + ex->Message); + return false; + } + } + void PluginManager::GenerateReferenceAssemblies(List^ deps) { REFrameworkNET::API::LogInfo("Generating reference assemblies..."); auto generatedFolder = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "generated"; std::filesystem::create_directories(generatedFolder); + auto pathToGame = System::Diagnostics::Process::GetCurrentProcess()->MainModule->FileName; + auto gameHash = REFrameworkNET::HashHelper::ComputeSHA256(pathToGame); + + auto pathToFramework = System::Reflection::Assembly::GetExecutingAssembly()->Location; + auto frameworkHash = REFrameworkNET::HashHelper::ComputeSHA256(pathToFramework); + + System::Console::WriteLine("Game hash: " + gameHash); + System::Console::WriteLine("Framework hash: " + frameworkHash); + System::Console::WriteLine("Game path: " + pathToGame); + System::Console::WriteLine("Framework path: " + pathToFramework); + + auto metadataPath = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "metadata.json"; + auto files = System::IO::Directory::GetFiles(gcnew System::String(generatedFolder.wstring().c_str()), "*.dll"); + + if (files->Length > 0 && !ShouldRecompile(gcnew System::String(metadataPath.wstring().c_str()), gameHash, frameworkHash)) { + REFrameworkNET::API::LogInfo("No need to recompile reference assemblies"); + + // Loop through all DLLs in the generated folder and load them instead + for each (System::String^ file in files) { + try { + REFrameworkNET::API::LogInfo("Loading generated assembly " + file + "..."); + if (auto assem = System::Reflection::Assembly::LoadFrom(file); assem != nullptr) { + deps->Add(assem); + REFrameworkNET::API::LogInfo("Loaded " + file); + } + } catch(System::Exception^ e) { + REFrameworkNET::API::LogInfo(e->Message); + // log stack + auto ex = e; + while (ex != nullptr) { + REFrameworkNET::API::LogInfo(ex->StackTrace); + ex = ex->InnerException; + } + } catch(const std::exception& e) { + REFrameworkNET::API::LogInfo(gcnew System::String(e.what())); + } catch(...) { + REFrameworkNET::API::LogInfo("Unknown exception caught while loading generated assembly " + file); + } + } + + return; + } + // Look for AssemblyGenerator class in the loaded deps for each (System::Reflection::Assembly^ a in deps) { if (auto generator = a->GetType("REFrameworkNET.AssemblyGenerator"); generator != nullptr) { @@ -151,6 +232,10 @@ namespace REFrameworkNET { auto result = (List^)mainMethod->Invoke(nullptr, nullptr); + if (result->Count != 0) { + WriteMetadata(gcnew System::String(metadataPath.wstring().c_str()), gameHash, frameworkHash); + } + // Append the generated assemblies to the list of deps for each (Compiler::DynamicAssemblyBytecode^ bytes in result) { REFrameworkNET::API::LogInfo("Adding generated assembly to deps..."); diff --git a/csharp-api/REFrameworkNET/PluginManager.hpp b/csharp-api/REFrameworkNET/PluginManager.hpp index 025ef2753..f0ebaa9d2 100644 --- a/csharp-api/REFrameworkNET/PluginManager.hpp +++ b/csharp-api/REFrameworkNET/PluginManager.hpp @@ -35,6 +35,8 @@ private ref class PluginManager // after loading "ourselves" via System::Reflection::Assembly::LoadFrom static System::Collections::Generic::List^ LoadAssemblies(System::String^ path); static System::Collections::Generic::List^ LoadDependencies(); + static bool ShouldRecompile(System::String^ metadataPath, System::String^ currentGameHash, System::String^ currentFrameworkHash); + static bool WriteMetadata(System::String^ metadataPath, System::String^ currentGameHash, System::String^ currentFrameworkHash); static void GenerateReferenceAssemblies(System::Collections::Generic::List^ deps); static bool LoadPlugins(uintptr_t param_raw); static bool LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List^ deps); From 49561be2f8faa5abbf0973b13f6ecad8cdc9b3fa Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 17 Mar 2025 00:06:50 -0700 Subject: [PATCH 199/207] Plugins: Initialize plugins in game thread for safety/speed --- src/mods/PluginLoader.cpp | 13 ++++++++++++- src/mods/PluginLoader.hpp | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/mods/PluginLoader.cpp b/src/mods/PluginLoader.cpp index deed17587..482290c2a 100644 --- a/src/mods/PluginLoader.cpp +++ b/src/mods/PluginLoader.cpp @@ -666,6 +666,10 @@ void PluginLoader::early_init() try { void PluginLoader::on_frame() { init_d3d_pointers(); + + if (auto error = initialize_plugins(); error.has_value()) { + spdlog::error("[PluginLoader] Failed to initialize plugins: {}", error.value()); + } } void PluginLoader::init_d3d_pointers() { @@ -686,7 +690,14 @@ void PluginLoader::init_d3d_pointers() { } } -std::optional PluginLoader::on_initialize() { +std::optional PluginLoader::initialize_plugins() { + if (m_plugins_loaded) { + return std::nullopt; + } + + // Plugin init can take a really long time so don't try to re-hook d3d while it's happening. + auto do_not_hook_d3d = g_framework->acquire_do_not_hook_d3d(); + std::scoped_lock _{m_mux}; verify_sdk_pointers(); diff --git a/src/mods/PluginLoader.hpp b/src/mods/PluginLoader.hpp index cb830e824..855d76fef 100644 --- a/src/mods/PluginLoader.hpp +++ b/src/mods/PluginLoader.hpp @@ -35,7 +35,7 @@ class PluginLoader : public Mod { void early_init(); std::string_view get_name() const override { return "PluginLoader"; } - std::optional on_initialize() override; + std::optional initialize_plugins(); void on_frame() override; void on_draw_ui() override; From 4b61d2fc293a2483c33a173edd19e4cf072299f5 Mon Sep 17 00:00:00 2001 From: praydog Date: Mon, 17 Mar 2025 13:48:07 -0700 Subject: [PATCH 200/207] .NET: Migrate generated metadata.json to generated folder --- csharp-api/REFrameworkNET/PluginManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index bfa531fc4..5a1f7256e 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -187,7 +187,7 @@ namespace REFrameworkNET { System::Console::WriteLine("Game path: " + pathToGame); System::Console::WriteLine("Framework path: " + pathToFramework); - auto metadataPath = std::filesystem::current_path() / "reframework" / "plugins" / "managed" / "metadata.json"; + auto metadataPath = generatedFolder / "metadata.json"; auto files = System::IO::Directory::GetFiles(gcnew System::String(generatedFolder.wstring().c_str()), "*.dll"); if (files->Length > 0 && !ShouldRecompile(gcnew System::String(metadataPath.wstring().c_str()), gameHash, frameworkHash)) { From dae782330d6e3b8cc05b95adbe03164c3e075aa4 Mon Sep 17 00:00:00 2001 From: praydog Date: Tue, 18 Mar 2025 19:39:51 -0700 Subject: [PATCH 201/207] .NET: Fix classes missing fields with generic types --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index be5c95c2f..ddbc9c325 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -548,12 +548,7 @@ private TypeDeclarationSyntax GenerateFields(List baseType System.Console.WriteLine("Skipping field with non-ASCII characters: " + field.Name + " " + field.Index); continue; } - - // We don't want any of the properties to be "void" properties - if (!REFrameworkNET.AssemblyGenerator.validTypes.Contains(field.Type.FullName)) { - continue; - } - + ++totalFields; validFields.Add(field); From ea06e072055a5af83e1c16a0d3175f47e44c43f4 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 19 Mar 2025 16:12:35 -0700 Subject: [PATCH 202/207] .NET: Add API.IsDrawingUI --- csharp-api/REFrameworkNET/API.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/csharp-api/REFrameworkNET/API.hpp b/csharp-api/REFrameworkNET/API.hpp index d61ea7c11..00fa07d49 100644 --- a/csharp-api/REFrameworkNET/API.hpp +++ b/csharp-api/REFrameworkNET/API.hpp @@ -52,6 +52,10 @@ public ref class API s_api->get_vm_context()->local_frame_gc(); } + static bool IsDrawingUI() { + return s_api->reframework()->is_drawing_ui(); + } + generic where T : ref class static T GetNativeSingletonT() { auto fullName = T::typeid->FullName; From 2c5486ace32739cedaf37a4c2c1d8780e8f837de Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 19 Mar 2025 16:26:22 -0700 Subject: [PATCH 203/207] .NET: Add ImGuiDrawUI callback for script generated UI asdf --- csharp-api/REFrameworkNET/Callbacks.hpp | 1 + csharp-api/REFrameworkNET/PluginManager.cpp | 22 +++++++++++++++++++++ csharp-api/test/Test/TestMHWilds.cs | 18 ++++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/Callbacks.hpp b/csharp-api/REFrameworkNET/Callbacks.hpp index d0f3c502c..9cb55d39a 100644 --- a/csharp-api/REFrameworkNET/Callbacks.hpp +++ b/csharp-api/REFrameworkNET/Callbacks.hpp @@ -464,5 +464,6 @@ namespace Callbacks { // Manually generated callback class for ImGui rendering // We will manually filter out the ImGuiRender callback GENERATE_POCKET_CLASS(ImGuiRender); + GENERATE_POCKET_CLASS(ImGuiDrawUI); }; } \ No newline at end of file diff --git a/csharp-api/REFrameworkNET/PluginManager.cpp b/csharp-api/REFrameworkNET/PluginManager.cpp index 5a1f7256e..ed303ae64 100644 --- a/csharp-api/REFrameworkNET/PluginManager.cpp +++ b/csharp-api/REFrameworkNET/PluginManager.cpp @@ -849,6 +849,28 @@ namespace REFrameworkNET { PluginManager::s_plugin_states_to_remove->Clear(); } + if (ImGuiNET::ImGui::CollapsingHeader("REFramework.NET Script Generated UI")) { + try { + Callbacks::ImGuiDrawUI::TriggerPre(); + } catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Failed to trigger ImGuiDrawUI::Pre: " + e->Message); + } catch (const std::exception& e) { + REFrameworkNET::API::LogError("Failed to trigger ImGuiDrawUI::Pre: " + gcnew System::String(e.what())); + } catch (...) { + REFrameworkNET::API::LogError("Unknown exception caught while triggering ImGuiDrawUI::Pre"); + } + + try { + Callbacks::ImGuiDrawUI::TriggerPost(); + } catch (System::Exception^ e) { + REFrameworkNET::API::LogError("Failed to trigger ImGuiDrawUI::Post: " + e->Message); + } catch (const std::exception& e) { + REFrameworkNET::API::LogError("Failed to trigger ImGuiDrawUI::Post: " + gcnew System::String(e.what())); + } catch (...) { + REFrameworkNET::API::LogError("Unknown exception caught while triggering ImGuiDrawUI::Post"); + } + } + ImGuiNET::ImGui::PopID(); } diff --git a/csharp-api/test/Test/TestMHWilds.cs b/csharp-api/test/Test/TestMHWilds.cs index 5f515a4c6..f1c7033b7 100644 --- a/csharp-api/test/Test/TestMHWilds.cs +++ b/csharp-api/test/Test/TestMHWilds.cs @@ -26,9 +26,25 @@ public static void ImGuiCallback() { } ImGui.Text("Name: " + player.ContextHolder.Pl._PlayerName); + ImGui.Text("Level: " + player.ContextHolder.Pl._CurrentStage); + ImGui.Text("Network position: " + player._ContextHolder.Pl.NetworkPosition.ToString()); + ImGui.Text(player.ContextHolder.Pl._GeneralPos.ToString()); + ImGui.Text(player.ContextHolder.Pl._NetMemberInfo.IsMasterRow.ToString()); + ImGui.Text(player.ContextHolder.Pl._DistToCamera.ToString()); + if (ImGui.Button("Test")) { + player.ContextHolder.Chara.HealthManager._Health.write(0.0f); + } + + if (ImGui.Button("Test2")) { + player.ContextHolder.Chara.HealthManager.addHealth(1.0f); + } - ImGui.Text("TestMHWilds.cs"); ImGui.End(); } } + + [Callback(typeof(ImGuiDrawUI), CallbackType.Pre)] + public static void ImGuiDrawUICallback() { + ImGui.Text("Hello from TestMHWilds.cs"); + } } \ No newline at end of file From 4fc977e4f96da9c8618a96f8f0ba31e00853b875 Mon Sep 17 00:00:00 2001 From: praydog Date: Wed, 19 Mar 2025 17:05:30 -0700 Subject: [PATCH 204/207] .NET: Ignore ImGuiDrawUI --- csharp-api/REFrameworkNET/Callbacks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/csharp-api/REFrameworkNET/Callbacks.cpp b/csharp-api/REFrameworkNET/Callbacks.cpp index fb0025adf..db1dcd89d 100644 --- a/csharp-api/REFrameworkNET/Callbacks.cpp +++ b/csharp-api/REFrameworkNET/Callbacks.cpp @@ -56,7 +56,7 @@ void Impl::Setup(REFrameworkNET::API^ api) { s_knownStaticEvents->Add(post); } - if (type->Name == "ImGuiRender") { + if (type->Name == "ImGuiRender" || type->Name == "ImGuiDrawUI") { Console::WriteLine("Skipping ImGuiRender"); continue; } From 57a3fbc2484e01137ec3a146de92d41c0fd70cfc Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 20 Mar 2025 18:31:49 -0700 Subject: [PATCH 205/207] .NET: Add DMC5 test --- csharp-api/CMakeLists.txt | 41 +++++++++++ csharp-api/cmake.toml | 10 +++ csharp-api/make_symlinks.py | 1 + csharp-api/test/Test/TestDMC5.cs | 117 +++++++++++++++++++++++++++++++ 4 files changed, 169 insertions(+) create mode 100644 csharp-api/test/Test/TestDMC5.cs diff --git a/csharp-api/CMakeLists.txt b/csharp-api/CMakeLists.txt index a0715a7cb..8440a7a18 100644 --- a/csharp-api/CMakeLists.txt +++ b/csharp-api/CMakeLists.txt @@ -535,6 +535,47 @@ if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_MHWILDS) # build-csharp-test-mhwilds set(CMKR_TARGET CSharpAPITestMHWilds) set_dotnet_references(CSharpAPITestMHWilds "mhwilds" "${MHWILDS_DLLS}") +endif() +# Target: CSharpAPITestDMC5 +if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_DMC5) # build-csharp-test-dmc5 + set(CSharpAPITestDMC5_SOURCES + "test/Test/TestDMC5.cs" + cmake.toml + ) + + add_library(CSharpAPITestDMC5 SHARED) + + target_sources(CSharpAPITestDMC5 PRIVATE ${CSharpAPITestDMC5_SOURCES}) + source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${CSharpAPITestDMC5_SOURCES}) + + target_link_libraries(CSharpAPITestDMC5 PUBLIC + csharp-api + ) + + set_target_properties(CSharpAPITestDMC5 PROPERTIES + RUNTIME_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/bin/" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/bin" + RUNTIME_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/bin" + LIBRARY_OUTPUT_DIRECTORY_RELEASE + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO + "${CMAKE_BINARY_DIR}/lib" + LIBRARY_OUTPUT_DIRECTORY_DEBUG + "${CMAKE_BINARY_DIR}/lib" + DOTNET_SDK + Microsoft.NET.Sdk + DOTNET_TARGET_FRAMEWORK + net8.0-windows + VS_CONFIGURATION_TYPE + ClassLibrary + ) + + set(CMKR_TARGET CSharpAPITestDMC5) + set_dotnet_references(CSharpAPITestDMC5 "dmc5" "${DMC5_DLLS}") + endif() # Target: ObjectExplorer if(REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2) # build-csharp-test-re2 diff --git a/csharp-api/cmake.toml b/csharp-api/cmake.toml index 1f15bf12c..1e03f4f89 100644 --- a/csharp-api/cmake.toml +++ b/csharp-api/cmake.toml @@ -126,6 +126,7 @@ endforeach() """ [conditions] +build-csharp-test-dmc5 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DMC5" build-csharp-test-dd2 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_DD2" build-csharp-test-re2 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE2" build-csharp-test-re4 = "REFRAMEWORK_REF_ASSEMBLIES_EXIST_RE4" @@ -263,6 +264,15 @@ cmake-after = """ set_dotnet_references(CSharpAPITestMHWilds "mhwilds" "${MHWILDS_DLLS}") """ +[target.CSharpAPITestDMC5] +condition = "build-csharp-test-dmc5" +type = "CSharpSharedTarget" +sources = ["test/Test/TestDMC5.cs"] +link-libraries = ["csharp-api"] +cmake-after = """ +set_dotnet_references(CSharpAPITestDMC5 "dmc5" "${DMC5_DLLS}") +""" + [target.ObjectExplorer] condition = "build-csharp-test-re2" type = "CSharpSharedTarget" diff --git a/csharp-api/make_symlinks.py b/csharp-api/make_symlinks.py index d85cc46bb..eb5bab4e2 100644 --- a/csharp-api/make_symlinks.py +++ b/csharp-api/make_symlinks.py @@ -27,6 +27,7 @@ def symlink_main(gamedir=None, bindir="build/bin", just_copy=False): source_dir_files = [ "Test/Test/Test.cs", + "Test/Test/TestDMC5.cs", "Test/Test/TestRE2.cs", "Test/Test/TestRE4.cs", "Test/Test/TestMHWilds.cs", diff --git a/csharp-api/test/Test/TestDMC5.cs b/csharp-api/test/Test/TestDMC5.cs new file mode 100644 index 000000000..e164b057c --- /dev/null +++ b/csharp-api/test/Test/TestDMC5.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Dynamic; +using System.Reflection; +using ImGuiNET; +using REFrameworkNET.Callbacks; +using REFrameworkNET.Attributes; +using REFrameworkNET; +using via; + +class REFrameworkPluginWilds { + // STore time + public static System.DateTime lastTime = System.DateTime.Now; + + [Callback(typeof(ImGuiRender), CallbackType.Pre)] + public static void ImGuiCallback() { + var now = System.DateTime.Now; + + if ((now - lastTime).TotalSeconds > 0.1 && !GC.GetGCMemoryInfo().Concurrent) { + //GC.Collect(1, GCCollectionMode.Forced, false); + //lastTime = now; + } + + if (ImGui.Begin("TestDMC5.cs")) { + ImGui.Text("Hello from TestDMC5.cs"); + + try { + var playerManager = REFrameworkNET.API.GetManagedSingletonT(); + + if (playerManager == null) { + return; + } + + var player = playerManager.manualPlayer; + + if (player == null) { + return; + } + + ImGui.Text("Name: " + player.GameObject.Name); + ImGui.Text("Network name: " + player.NetworkName); + ImGui.Text("HP: " + player.hp); + ImGui.Text("Max HP: " + player.maxHp); + ImGui.Text("Position: " + player.GameObject.Transform.Position); + + if (ImGui.TreeNode("Components")) { + var components = player.GameObject.Components; + + //foreach (ManagedObject componentRaw in (components as IProxy).GetInstance() as ManagedObject) { + //var component = componentRaw.As(); + for (int i = 0; i < components.Count; i++) { + var component = components.Get(i); + if (ImGui.TreeNode("Component: " + component.ToString())) { + var t = component.GetType(); + var fields = t.GetFields(); + + for (int j = 0; j < fields.Length; j++) { + var field = fields.Get(j); + var fieldval = field.GetValue(component); + + ImGui.Text("Field: " + field.Name + " = " + (fieldval as IObject)?.Call("ToString", [])); + + // Dispose of the field value + (fieldval as IDisposable)?.Dispose(); + //(field as IDisposable)?.Dispose(); + } + + var props = t.GetProperties(); + + for (int j = 0; j < props.Length; j++) { + var prop = props.Get(j); + var propval = prop.GetValue(component); + + ImGui.Text("Property: " + prop.Name + " = " + (propval as IObject)?.Call("ToString", [])); + + // Dispose of the property value + (propval as IDisposable)?.Dispose(); + //(prop as IDisposable)?.Dispose(); + } + + (fields as IDisposable)?.Dispose(); + (props as IDisposable)?.Dispose(); + (t as IDisposable)?.Dispose(); + + ImGui.TreePop(); + } + } + + (components as IDisposable)?.Dispose(); + + ImGui.TreePop(); + } + + if (ImGui.Button("Test")) { + player.hp = 0; + } + + if (ImGui.Button("Air Hike")) { + var pos = player.GameObject.Transform.Position; + pos.y += 1; + player.GameObject.Transform.Position = pos; + + player.airHike(1.0f); + } + + } finally { + ImGui.End(); + } + } + } + + [Callback(typeof(ImGuiDrawUI), CallbackType.Pre)] + public static void ImGuiDrawUICallback() { + ImGui.Text("Hello from TestDMC5.cs"); + } +} \ No newline at end of file From 9793d2f468bdd669fd8ce2670955edc9957167dd Mon Sep 17 00:00:00 2001 From: praydog Date: Fri, 28 Mar 2025 13:57:38 -0700 Subject: [PATCH 206/207] .NET: Fix enum fields not being settable --- csharp-api/REFrameworkNET/Field.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/csharp-api/REFrameworkNET/Field.cpp b/csharp-api/REFrameworkNET/Field.cpp index d1d4c3f69..cc1503e3a 100644 --- a/csharp-api/REFrameworkNET/Field.cpp +++ b/csharp-api/REFrameworkNET/Field.cpp @@ -48,7 +48,7 @@ namespace REFrameworkNET { return; } - const auto field_type = this->GetType(); + auto field_type = this->GetType(); if (field_type == nullptr) { return; @@ -92,8 +92,14 @@ namespace REFrameworkNET { return; // Don't think there's any other way to set a reference type } + auto underlying_type = field_type->GetUnderlyingType(); + + // For enums + if (underlying_type != nullptr) { + field_type = underlying_type; + } + const uintptr_t addr = IsStatic() ? 0 : obj; - const auto vm_obj_type = field_type->GetVMObjType(); #define MAKE_TYPE_HANDLER_SET(X, Y) \ case ##X##_fnv: \ From c247ef84753e91d8eff0728e5fe806f106ffe0f0 Mon Sep 17 00:00:00 2001 From: praydog Date: Thu, 29 May 2025 15:41:51 -0700 Subject: [PATCH 207/207] .NET: Fix void fields --- csharp-api/AssemblyGenerator/ClassGenerator.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/csharp-api/AssemblyGenerator/ClassGenerator.cs b/csharp-api/AssemblyGenerator/ClassGenerator.cs index ddbc9c325..629d9b98e 100644 --- a/csharp-api/AssemblyGenerator/ClassGenerator.cs +++ b/csharp-api/AssemblyGenerator/ClassGenerator.cs @@ -565,6 +565,11 @@ private TypeDeclarationSyntax GenerateFields(List baseType var fieldType = MakeProperType(field.Type, t); var fieldName = new string(field.Name); + // Check if field type is void and just change it to object. + if (fieldType.GetText().ToString() == "void" || fieldType.GetText().ToString() == "System.Void") { + fieldType = SyntaxFactory.PredefinedType(SyntaxFactory.Token(SyntaxKind.ObjectKeyword)); + } + // Replace the k backingfield crap if (fieldName.StartsWith("<") && fieldName.EndsWith("k__BackingField")) { fieldName = fieldName[1..fieldName.IndexOf(">k__")];