Skip to content

Commit

Permalink
.NET: Use attribute for plugin entrypoint
Browse files Browse the repository at this point in the history
  • Loading branch information
praydog committed Mar 31, 2024
1 parent 5eead2d commit a0fd502
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 59 deletions.
3 changes: 2 additions & 1 deletion csharp-api/AssemblyGenerator/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,8 @@ [.. methods]
return compilationUnit;
}

public static List<REFrameworkNET.Compiler.DynamicAssemblyBytecode> Main(REFrameworkNET.API api) {
[REFrameworkNET.Attributes.PluginEntryPoint]
public static List<REFrameworkNET.Compiler.DynamicAssemblyBytecode> Main() {
try {
return MainImpl();
} catch (Exception e) {
Expand Down
2 changes: 2 additions & 0 deletions csharp-api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions csharp-api/REFrameworkNET/Attributes/Plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#include "Plugin.hpp"
11 changes: 11 additions & 0 deletions csharp-api/REFrameworkNET/Attributes/Plugin.hpp
Original file line number Diff line number Diff line change
@@ -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() {}
};
}
}
132 changes: 88 additions & 44 deletions csharp-api/REFrameworkNET/PluginManager.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include <exception>
#include <filesystem>

#include "Attributes/Plugin.hpp"
#include "PluginManager.hpp"

using namespace System;
Expand Down Expand Up @@ -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<Type^>{REFrameworkNET::API::typeid});
System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public);

if (mainMethod != nullptr) {
REFrameworkNET::API::LogInfo("Found AssemblyGenerator.Main in " + a->Location);

array<Object^>^ args = gcnew array<Object^>{PluginManager::s_api_instance};
auto result = (List<Compiler::DynamicAssemblyBytecode^>^)mainMethod->Invoke(nullptr, args);
auto result = (List<Compiler::DynamicAssemblyBytecode^>^)mainMethod->Invoke(nullptr, nullptr);

// Append the generated assemblies to the list of deps
for each (Compiler::DynamicAssemblyBytecode^ bytes in result) {
Expand Down Expand Up @@ -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<Type^>{REFrameworkNET::API::typeid});

if (mainMethod != nullptr) {
REFrameworkNET::API::LogInfo("Found Main method in " + file);

array<Object^>^ args = gcnew array<Object^>{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<System::Reflection::MethodInfo^>^ methods = type->GetMethods(System::Reflection::BindingFlags::Static | System::Reflection::BindingFlags::Public);

for each (System::Reflection::MethodInfo^ method in methods) {
array<Object^>^ 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);
Expand Down Expand Up @@ -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<Type^>{REFrameworkNET::API::typeid});
array<System::Reflection::MethodInfo^>^ 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<Object^>^ attributes = method->GetCustomAttributes(REFrameworkNET::Attributes::PluginEntryPoint::typeid, true);

array<Object^>^ args = gcnew array<Object^>{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;
}
}
}
}
Expand Down Expand Up @@ -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<Type^>{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<Object^>{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;
}
}
6 changes: 6 additions & 0 deletions csharp-api/REFrameworkNET/PluginManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,11 @@ private ref class PluginManager
static void GenerateReferenceAssemblies(System::Collections::Generic::List<System::Reflection::Assembly^>^ deps);
static bool LoadPlugins(uintptr_t param_raw);
static bool LoadPlugins_FromSourceCode(uintptr_t param_raw, System::Collections::Generic::List<System::Reflection::Assembly^>^ deps);
static void UnloadDynamicAssemblies();

static System::Collections::Generic::List<System::Reflection::Assembly^>^ s_loaded_assemblies{gcnew System::Collections::Generic::List<System::Reflection::Assembly^>()};
static System::Collections::Generic::List<System::Reflection::Assembly^>^ s_dynamic_assemblies{gcnew System::Collections::Generic::List<System::Reflection::Assembly^>()};

static System::Runtime::Loader::AssemblyLoadContext^ s_default_context{nullptr};
};
}
73 changes: 59 additions & 14 deletions csharp-api/test/Test/Test.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,52 @@
// 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<object> 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<via.SceneManager>.CreateFromSingleton("via.SceneManager");
var sceneManager = REFrameworkNET.API.GetNativeSingletonT<via.SceneManager>();
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);

// Testing autocomplete for the concrete ManagedObject
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();
Expand All @@ -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<via.os>();
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<via.os.dialog>();
dialog.open("Hello from C#!");
}

public static void TryEnableFrameGeneration() {
var upscalingInterface = REFrameworkNET.API.GetNativeSingletonT<via.render.UpscalingInterface>();
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);
}
}
}

Expand All @@ -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();
};
Expand All @@ -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();
};

Expand All @@ -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();

Expand Down

0 comments on commit a0fd502

Please sign in to comment.