This is a template for developing BepInEx plugins for AiComi using IL2CPP and .NET 6.0.
Caution
THIS TEMPLATE IS KINDA - WORK IN PROGRESS -
I WILL VERY LIKELY DO SOME MAYOR CHANGES TO THE TEMPLATE AT SOME POINT.
THE CURRENT TEMPLATE IS FAR FROM PERFECT, BUT SHOULD BE USABLE NONETHELESS!
THE PLUGIN STRUCTURE USED FOR THE TEMPLATE AT THE MOMENT IS FAR TOO COMPLEX.
80% OF THE STUFF IS JUST BLOATING THE TEMPLATE AND WOULD NOT BE NEEDED FOR A WORKING PLUGIN!
WHENEVER I WILL UPDATE THE TEMPLATE - IT'S GOING TO BE A LOT SIMPLER AND EASIER FOR BEGINNERS TO UNDERSTAND.
- Rename the project folders and files from
ExamplePlugin→AC_YourPluginName - Update the namespace in
ExamplePlugin.csfromAC_ExamplePlugintoAC_YourPluginNameAY S3. Update theGUIDandPluginNameconstants - Update the output path in
PluginCode.csprojif needed - Build and the plugin will auto-deploy to
bin/BepInEx/plugins/
- Engine: Unity IL2CPP
- BepInEx: 6.x (Unity)
- Base Class:
BepInEx.Unity.IL2CPP.BasePlugin - Target Framework:
.net6.0 - Harmony:
HarmonyLib(Postfix/Prefix Patches) - Packages:
IllusionLibs.Aicomi.AllPackages(2024.10.3)
AC_YourPluginName/
├── ExamplePlugin.cs # Main plugin class (rename as needed)
├── PluginCode.csproj # SDK-style project file
├── packages.config # (no longer needed with SDK style)
└── Properties/
└── AssemblyInfo.cs # Assembly metadata
using BepInEx;
using BepInEx.Unity.IL2CPP; // not BepInEx alone!
using HarmonyLib;
[BepInPlugin(GUID, PluginName, Version)]
public class Plugin : BasePlugin // not BaseUnityPlugin!
{
public const string GUID = "com.author.myplugin";
public const string PluginName = "My Plugin";
public const string Version = "1.0.0";
internal static BepInEx.Logging.ManualLogSource Logger = null!;
public override void Load() // not Awake()!
{
Logger = Log;
Harmony.CreateAndPatchAll(typeof(MyPatch));
}
}- Use
BasePlugin(notBaseUnityPlugin) fromBepInEx.Unity.IL2CPP - Entry point is
Load()method (notAwake()) - Static logger must be initialized:
Logger = Log;
- Standard .NET reflection won't work for native game types — only wrapper fields (
isWrapped,pooledPtr) will be visible - Real fields/methods → look them up in
BepInEx/interop/via dnSpy - Private methods in interop assemblies are often
public(IL2CPP wrapper makes everything accessible) GetComponentInChildren<T>()works normally ✅- Use
Il2CppInterop.Runtime.Il2CppType.Of<T>()instead oftypeof(T).GetFields()for IL2CPP reflection
[HarmonyPatch(typeof(TargetClass))]
internal static class MyPatch
{
[HarmonyPostfix, HarmonyPatch(nameof(TargetClass.TargetMethod))]
private static void AfterMethod(TargetClass __instance)
{
// __instance = the object the method was called on
}
}ANSI escape codes are natively supported in the BepInEx console window:
private const string GREEN = "\u001b[32m";
private const string RESET = "\u001b[0m";
Logger.LogInfo($"{GREEN}Success!{RESET}");- Log in game:
UnityEngine.Debug.Log(...)→ appears as[Message: Unity]in BepInEx log - Find fields: RuntimeUnityEditor (F7) → Object Browser → click on component
- Find methods/types: dnSpy → open
BepInEx/interop/DLLs → search for class - IL2CPP Reflection:
Il2CppInterop.Runtime.Il2CppType.Of<T>()instead oftypeof(T).GetFields()
// Normal setting
Config.Bind("Section", "Key", defaultValue, "Description");
// Advanced/Debug setting (only visible when Debug mode enabled)
Config.Bind("Debug", "Key", false,
new ConfigDescription("Description", tags: new[] { "Advanced" })
);// LoopGridView works with rows, not flat item indices
int row = selectedIndex / columns;
var scrollRect = win.GetComponentInChildren<UnityEngine.UI.ScrollRect>();
float scrollable = scrollRect.content.rect.height - scrollRect.viewport.rect.height;
// 1.0 = top, 0.0 = bottom → invert!
float normalized = 1f - Mathf.Clamp01((row * itemHeight) / scrollable);
scrollRect.verticalNormalizedPosition = normalized;BasePluginnotBaseUnityPlugin— plugin won't load otherwiseLoad()notAwake()— the entry point has a different name in BepInEx 6 IL2CPPLogis an instance property → store it statically so patch classes can access it\u001bvs\\u001b— double backslash means no ANSI escape, colors won't appearGetComponentIndex()on LoopGridView returns UI hierarchy depth, not item index → look for a game-specific method instead (e.g.GetCurrentIndex())GenerateAssemblyInfo— set<GenerateAssemblyInfo>false</GenerateAssemblyInfo>in the csproj when usingProperties/AssemblyInfo.cs, otherwise you'll get duplicate attribute errors
Build the project and the plugin DLL will automatically copy to bin/BepInEx/plugins/AC_YourPluginName/
For releases, use the included release.ps1 script.