Unity Integration
The Polytoria Executor uses UnityResolve for IL2CPP reflection. This document explains how to interact with the Unity engine from C++.
Overview
UnityResolve is a custom IL2CPP reflection library that provides:
- Assembly loading and inspection
- Class and method resolution
- Field access (static and instance)
- Method invocation
Unity Namespace
All Unity-related functionality is in the Unity namespace:
Type Aliases
| C++ |
|---|
| using UnityAssembly = UnityResolve::Assembly;
using UnityClass = UnityResolve::Class;
using UnityField = UnityResolve::Field;
using UnityMethod = UnityResolve::Method;
using UnityString = UnityType::String;
using UnityObject = UnityType::Object;
using UnityGameObject = UnityType::GameObject;
using UnityComponent = UnityType::Component;
|
Assembly Constants
| C++ |
|---|
| namespace Unity {
static constexpr auto AssemblyCSharp = "Assembly-CSharp.dll";
static constexpr auto AssemblyFirstPass = "Assembly-CSharp-firstpass.dll";
static constexpr auto AssemblyUnityEngine = "UnityEngine.CoreModule.dll";
static constexpr auto Mirror = "Mirror.dll";
}
|
Basic Operations
Getting an Assembly
| C++ |
|---|
| // Get Assembly-CSharp
UnityAssembly* assembly = Unity::GetAssembly<Unity::AssemblyCSharp>();
// Get UnityEngine
UnityAssembly* unityEngine = Unity::GetAssembly<Unity::AssemblyUnityEngine>();
|
Getting a Class
| C++ |
|---|
| // Basic class lookup
UnityClass* instanceClass = Unity::GetClass<"Instance", Unity::AssemblyCSharp>();
// With namespace
UnityClass* playerClass = Unity::GetClass<"Player", Unity::AssemblyCSharp, "Polytoria">();
// With parent class
UnityClass* toolClass = Unity::GetClass<"Tool", Unity::AssemblyCSharp, "*", "Instance">();
|
Template parameters:
- Name - Class name
- Assembly - Assembly name
- Namespace - Namespace (default: "*")
- Parent - Parent class (default: "*")
Getting a Method
| C++ |
|---|
| UnityClass* toolClass = StaticClass<Tool>();
// Get method with no parameters
UnityMethod* activateMethod = Unity::GetMethod<"CmdActivate">(toolClass);
// Get method with parameters (for overloads)
UnityMethod* sendChatMethod = Unity::GetMethod<"SendChat", "System.String">(chatClass);
|
Invoking Methods
| C++ |
|---|
| // Instance method
toolClass->CmdActivate(); // Calls method on 'this'
// Static method
Unity::GetMethod<"SomeStaticMethod">(klass)->Invoke<void>();
// With parameters
Unity::GetMethod<"SendChat", "System.String">(chatClass)
->Invoke<void, void*, UnityString*>(chatInstance, message);
|
Object System
The executor uses a CRTP (Curiously Recurring Template Pattern) system for type-safe game object access.
Defining Game Types
| C++ |
|---|
| // ptoria/tool.h
struct Tool : public Object<Tool, "Tool", Unity::AssemblyCSharp> {
void CmdActivate();
void CmdUnequip();
void CmdEquip();
};
// ptoria/tool.cpp
void Tool::CmdActivate() {
Unity::GetMethod<"CmdActivate">(StaticClass<Tool>())->Invoke<void>(this);
}
|
Using the Object System
| C++ |
|---|
| // Get static class reference
UnityClass* klass = StaticClass<Tool>();
// Create objects
UnityGameObject* go = UnityGameObject::Create("MyTool");
Tool* tool = go->AddComponent<Tool*>(StaticClass<Tool>());
// Call methods
tool->CmdActivate();
|
StaticClass Helper
The StaticClass<T>() function automatically resolves the class:
| C++ |
|---|
| template<typename T>
auto StaticClass() -> UnityClass* {
using Params = ObjectParamsT<T>;
return Unity::GetClass<Params::_class,
Params::_assembly,
Params::_namespace,
Params::_parent>();
}
|
Field Access
Instance Fields
| C++ |
|---|
| // Get field value
auto value = Unity::GetFieldValue<int, "someField">(klass, object);
// Set field value
Unity::SetFieldValue<int, "someField">(klass, object, 42);
|
Static Fields
| C++ |
|---|
| // Get static field
auto instance = Unity::GetStaticFieldValue<UnityObject*, "Instance">(klass);
// Set static field
Unity::SetStaticFieldValue<UnityObject*>(klass, &instance);
|
Working with Strings
Creating Strings
| C++ |
|---|
| UnityString* str = UnityString::New("Hello World");
|
Converting to std::string
| C++ |
|---|
| UnityString* str = /* get from game */;
std::string cppStr = str->ToString();
|
Using in Method Calls
| C++ |
|---|
| // Create and pass to method
UnityString* msg = UnityString::New("Chat message");
Unity::GetMethod<"SendChat", "System.String">(chatClass)
->Invoke<void, void*, UnityString*>(chatInstance, msg);
|
Thread Safety
ThreadAttach
Every thread that calls Unity APIs must be attached:
| C++ |
|---|
| // Attach current thread
Unity::ThreadAttach();
// Now you can make Unity API calls
// Detach when done (optional)
Unity::ThreadDetach();
|
When is this needed?
- DX11 render thread (first call in HookPresent)
- Pipe server thread (if making Unity calls)
- Any new threads you create
Important Notes
- Objects can be garbage collected between frames
- Don't hold references across long periods
- Always null-check returned objects
Advanced: Creating Objects
Creating GameObjects
| C++ |
|---|
| UnityGameObject* go = UnityGameObject::Create("GameObjectName");
|
Adding Components
| C++ |
|---|
| // Add a component to a GameObject
Tool* tool = go->AddComponent<Tool*>(StaticClass<Tool>());
|
Creating via IL2CPP
| C++ |
|---|
| // Direct IL2CPP object creation
auto obj = UnityResolve::Invoke<UnityObject*>("il2cpp_object_new", classType);
|
MoonSharp Integration
The Unity integration works with MoonSharp to expose game objects to Lua:
UserData Registration
Game objects are exposed as MoonSharp UserData:
| C++ |
|---|
| // In DynValue
UnityObject* Cast(void* csType);
// Usage in callbacks
DynValue* tool = args->RawGet(0, false);
Tool* toolObj = (Tool*)tool->Cast(StaticClass<Tool>()->GetType());
|
| C++ |
|---|
| DynValue* equiptool(void*, ScriptExecutionContext*, CallbackArguments* args) {
// Get tool from arguments
DynValue* toolArg = args->RawGet(0, false);
// Verify type
if (toolArg->Type() != DynValue::DataType::UserData) {
return DynValue::FromString("Invalid argument, expected Tool");
}
// Cast to Tool*
Tool* tool = (Tool*)toolArg->Cast(StaticClass<Tool>()->GetType());
// Call method
tool->CmdEquip();
return DynValue::FromNil();
}
|
Common Patterns
Waiting for Singleton
| C++ |
|---|
| // Wait for Game singleton
void* game = nullptr;
while (!game) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
game = Unity::GetStaticFieldValue<void*, "singleton">(StaticClass<Game>());
}
|
Finding Children
| C++ |
|---|
| // Iterate children
for (auto& child : gameInstance->Children()->ToVector()) {
spdlog::info("Child: {}", child->Name()->ToString());
}
|
Error Handling
| C++ |
|---|
| UnityMethod* method = Unity::GetMethod<"SomeMethod">(klass);
nasec::Assert(method != nullptr, "Failed to get method");
|
Best Practices
- Use StaticClass() - Type-safe class resolution
- Cache results - Methods are internally cached
- Always attach threads - Before any Unity API call
- Null check - Objects can be null
- Use nasec::Assert - For critical validation
File Reference