This section provides information about BNM's advanced API, that requires some knowledge and understating to use.
Compile time classes
This is the first step of creating your own classes and modifying existed ones.
It's used to save path to a class at compile time. And BNM will unwind that path at runtime. Because we can't get any class from il2cpp when it's not initialized.
Assume we have such classes structure in app's Assembly-CSharp.dll
:
namespace ExampleNamespace {
class ExampleClass {
class ExampleInnerClass {
class ExampleInnerGenericClass<T1, T2, T3> {
}
}
}
}
And we want to get ExampleInnerGenericClass<int, float, ExampleClass>[]
.
The usage of BNM::CompileTimeClassBuilder will look like this:
auto targetClass =
CompileTimeClassBuilder(BNM_OBFUSCATE_TMP("ExampleNamespace"), BNM_OBFUSCATE_TMP("ExampleClass"), BNM_OBFUSCATE_TMP("Assembly-CSharp.dll"))
.Class(BNM_OBFUSCATE_TMP("ExampleInnerClass"))
.Class(BNM_OBFUSCATE_TMP("ExampleInnerGenericClass`3"))
.Generic({
CompileTimeClassBuilder(BNM_OBFUSCATE_TMP("ExampleNamespace"), BNM_OBFUSCATE_TMP("ExampleClass")).Build()
})
.Modifier(CompileTimeClass::ModifierType::Array)
.Build();
constexpr DefaultTypeRef Get()
Method that helps to get il2cpp class type from C++ and BNM types.
Definition Defaults.hpp:132
Classes management
- Note
- Requires BNM_CLASSES_MANAGEMENT setting enabled
BNM's powerful high-level system that allows you to:
- Create your own classes with possibility to override virtual (and non-virtual in some cases) methods.
- Add methods and fields to the existing classes.
- Hook methods of the existing classes.
To use it, BNM provides some macros that you can check in Custom classes macros.
There are some examples of all classes management capabilities.
Examples
1. Simple MonoBehaviour class
BNM_OBFUSCATE_TMP("BNM_Example"),
BNM_OBFUSCATE_TMP("BNM_ExampleObject")
).Build(),
void Constructor() {
*this = BNM_ExampleObject();
}
int Value{};
uintptr_t veryImportantValue{0x424E4D};
void Start() {
BNM_LOG_INFO("BNM_ExampleObject::Start! Is veryImportantValue valid: %d", veryImportantValue == 0x424E4D);
}
};
#define BNM_CustomMethod(_method_, _isStatic_, _type_, _name_,...)
Define info about C++ method for il2cpp.
Definition ClassesManagement.hpp:307
#define BNM_CustomClass(_class_, _targetType_, _baseType_, _owner_,...)
Define info of C++ class for il2cpp.
Definition ClassesManagement.hpp:248
#define BNM_CustomField(_field_, _type_, _name_)
Define info about C++ field for il2cpp.
Definition ClassesManagement.hpp:278
Struct for building CompileTimeClass.
Definition Class.hpp:633
UnityEngine.MonoBehaviour implementation.
Definition UnityStructures.hpp:72
2. Add methods to class
The example in game class:
public class SomeObject : MonoBehaviour {
void Start() { }
}
BNM side:
BNM_OBFUSCATE_TMP("SomeObject"),
BNM_OBFUSCATE_TMP(
"Assembly-CSharp.dll")).
Build(),
void Update() { }
}
CompileTimeClass Build()
Build CompileTimeClass.
Definition Class.hpp:710
3. Hook classes' methods
The example in game class:
public class SomeObject : MonoBehaviour {
void Start() { }
int CalcSth() { }
virtual float VirtCalcSth(float a, float b) { }
static void StaticSth() { }
}
BNM side:
BNM_OBFUSCATE_TMP("SomeObject"),
BNM_OBFUSCATE_TMP(
"Assembly-CSharp.dll")).
Build(),
}
#define BNM_CustomMethodMarkAsBasicHook(_method_)
Mark method to prefer BasicHook.
Definition ClassesManagement.hpp:337
#define BNM_CustomMethodMarkAsInvokeHook(_method_)
Mark method to prefer InvokeHook.
Definition ClassesManagement.hpp:328
#define BNM_CallCustomMethodOrigin(_method_,...)
Call method origin, if it exists.
Definition ClassesManagement.hpp:356
#define BNM_CustomMethodSkipTypeMatch(_method_)
Skip method parameters type matching.
Definition ClassesManagement.hpp:346
4. Override methods
The example in game class:
public class SomeObject : MonoBehaviour {
void Start() { }
virtual float VirtCalcSth(float a, float b) { }
}
BNM side:
BNM_OBFUSCATE_TMP(
"SomeObjectChild")).
Build(),
BNM_OBFUSCATE_TMP("SomeObject"),
BNM_OBFUSCATE_TMP(
"Assembly-CSharp.dll")).
Build(), {});
void Start() {
}
float VirtCalcSth(float a, float b) {
}
}
As shown in the example, the whole interaction with the app can be done using classes management, instead of basic hooks. This make code cleaner and more readable, because in BNM_CustomClass you can easily see which class you’re currently working with.
Of course this adds some overhead at app's startup, but this is very low-cost.
Coroutines
- Note
- Requires BNM_CLASSES_MANAGEMENT and BNM_COROUTINE settings enabled
This is the imitation of Unity's coroutines powered by classes management. It uses C++20 coroutine API and translates it to il2cpp's.
You can see a bit more in BNM::Coroutine.
Simple coroutine example
BNM_LOG_DEBUG("Coroutine step 1");
BNM_LOG_DEBUG("Coroutine step 2 (WaitForEndOfFrame)");
BNM_LOG_DEBUG("Coroutine step 3 (WaitForFixedUpdate)");
BNM_LOG_DEBUG("Coroutine step 4 (WaitForSeconds)");
BNM_LOG_DEBUG("Coroutine step 5 (WaitForSecondsRealtime)");
return false;
});
BNM_LOG_DEBUG("Coroutine ended");
co_return;
}
void Code() {
auto someBehaviourInstance = ...;
auto enumerator = Example().Get();
auto enumerator = Example()();
auto r = StartCoroutine[someBehaviourInstance](enumerator);
}
Analog of C# IEnumerator that is based on C++20 coroutines to emulate work of Unity's coroutines.
Definition Coroutine.hpp:97
BNM's custom wrapper for UnityEngine.WaitForEndOfFrame.
Definition Coroutine.hpp:53
BNM's custom wrapper for UnityEngine.WaitForFixedUpdate.
Definition Coroutine.hpp:60
BNM's custom wrapper for UnityEngine.WaitForSecondsRealtime.
Definition Coroutine.hpp:74
BNM's custom wrapper for UnityEngine.WaitForSeconds.
Definition Coroutine.hpp:67
BNM's custom implementation of UnityEngine.WaitUntil.
Definition Coroutine.hpp:88
Typed class for working with il2cpp methods.
Definition Method.hpp:20
Also using classes management you can create custom yield instructions
struct CustomYieldInstruction : BNM::IL2CPP::Il2CppObject {
BNM::CompileTimeClassBuilder("System.Collections", "IEnumerator", "mscorlib.dll").Build());
std::chrono::time_point<std::chrono::system_clock> waitUntilTime;
void Finalize() { this->~CustomYieldInstruction(); }
bool MoveNext() { return waitUntilTime > std::chrono::system_clock::now(); }
void Reset() { waitUntilTime = {}; }
Il2CppObject *Current() { return nullptr; }
void Setup(long long seconds) {
Reset();
waitUntilTime = std::chrono::system_clock::now() + std::chrono::seconds(seconds);
}
static BNM::IL2CPP::Il2CppObject *New(long long seconds) {
auto instance = (CustomYieldInstruction *) BNM::Class(BNMCustomClass.myClass).CreateNewInstance();
instance->Setup(seconds);
return instance;
}
};
BNM_LOG_DEBUG("Coroutine started waiting for 3s");
co_yield CustomYieldInstruction::New(3);
BNM_LOG_DEBUG("Coroutine waited for 3s");
co_return;
}