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();