Skip to content

Plugins: Design considerations for plugins

Katy edited this page Jan 4, 2021 · 3 revisions

Design considerations for plugins

One plugin is one instance

Your plugin is loaded once when Il2CppInspector initializes, but standalone applications can create multiple instances of Il2CppInspector with multiple sets of plugins. Therefore you cannot assume there will only ever be one instance of your plugin at any one time. For this reason, do not use static constructors or members in your plugin except for const literals. Also avoid using the singleton pattern.

Throw exceptions on failure

The expected behaviour is that plugins should throw exceptions if an unexpected circumstance arises. Il2CppInspector will deal with these exceptions gracefully, reporting an error to the user and/or disabling the plugin as appropriate. The exact exception type does not matter as long as it derives from System.Exception. This behaviour holds for all properties and methods implemented by the plugin.

Assume that your plugin is enabled even if not in use

The GUI saves the state of plugins between sessions. In addition, plugins are designed to be chained with multiple plugins performing their own operations on the same interface hook. Hooks are executed on enabled plugins in the order they are displayed in the GUI (which can be rearranged by the user) or the order their arguments are supplied via the CLI.

This has two main consequences:

1. Never prevent execution of later plugins unless absolutely necessary

The PluginEventInfo member passed to each hook has a FullyProcessed member flag which can be set to true to prevent any further plugins from executing on the current hook. Never set this - even if your workload completes successfully - unless allowing additional plugins to perform processing would cause a crash due to the way you have modified the data.

2. Only take workloads that your plugin is designed to process

If your plugin is only designed to work with certain kinds of IL2CPP application, you should check at the earliest possible hook whether the user-supplied application is one that the plugin can process. If not, all subsequent hook calls should be ignored. This can be implemented using a private boolean field as follows:

public class MyPlugin : IPlugin, ILoadPipeline
{
  // ...

  // This flag denotes whether we are processing the current IL2CPP application
  private bool isOurs;

  // This executes every time the user requests to load a new IL2CPP application
  public void LoadPipelineStarting(PluginLoadPipelineStartingEventInfo info) {
    isOurs = true;
  }

  // In this example, we use this as the earliest hook to determine whether we will process the IL2CPP application
  public void PostProcessPackage(Il2CppInspector package, PluginPostProcessPackageEventInfo info) {
    if (! /* some checks */ ) {
      isOurs = false;
      return;
    }
  }

  // In hooks we use afterwards, we check the flag before doing anything
  public void PostProcessTypeModel(TypeModel model, PluginPostProcessTypeModelEventInfo info) {
    if (!isOurs)
      return;
  }
}

Multi-threading

Il2CppInspector will not call plugin hooks from multiple threads. Plugin code does not need to be thread-safe.

Loading multiple IL2CPP applications and reentrancy

You may wish to load another IL2CPP application in a hook if you are performing a comparison, merge or other operation that requires multiple IL2CPP applications to be loaded into memory at the same time. You can do this as follows:

var services = PluginServices.For(this);

// Load second IL2CPP application
var secondApp = Il2CppInspector.Il2CppInspector.LoadFromFile("some-app/libil2cpp.so", "some-app/global-metadata.dat", statusCallback: services.StatusUpdate)[0];

// Create a type model from it if desired
var secondTypeModel = new TypeModel(secondApp);

When you do this, any ILoadPipeline hooks you implemented will be called again for the second application. To prevent infinite recursion, Il2CppInspector tracks the call stack for each plugin and will not call a hook that is already running.

Sometimes, you may want the hook from which you are loading another IL2CPP application to be called regardless. To enable this, add ReentrantAttribute to the hook method, eg:

[Reentrant]
public void PostProcessTypeModel(TypeModel model, PluginPostProcessTypeModelEventInfo info) {
  // perform additional load
}

Hooks that have ReentrantAttribute will be called by Il2CppInspector even if they are already executing in the call stack.

Tracking the primary IL2CPP application

If you are using ILoadPipeline hooks and loading multiple IL2CPP applications, you must track the primary application loaded by the user to avoid making accidental changes to additional applications you load. You can do this by creating a boolean flag as a private field and checking it on subsequent loads as follows:

public bool isPrimaryLoaded;

// In this example we assume this is the first ILoadPipeline hook we implement
public void PreProcessImage(BinaryObjectStream stream, PluginPreProcessImageEventInfo info) {
  // Don't do anything to a second or subsequent loading IL2CPP application - only the first
  if (isPrimaryLoaded)
    return;
}

public void PostProcessImage<T>(FileFormatStream<T> stream, PluginPostProcessImageEventInfo info) {
  // Continue to ignore subsequent loads in every hook used
  if (isPrimaryLoaded)
    return;
}

// In this example, this is the hook in which the second application is loaded
public void PostProcessBinary(Il2CppBinary binary, PluginPostProcessBinaryEventInfo info) {
  if (isPrimaryLoaded)
    return;

  isPrimaryLoaded = true;

  // isPrimaryLoaded is set, so all the previous hooks will be ignored when this loads
  var secondApp = Il2CppInspector.Il2CppInspector.LoadFromFile("some-app/libil2cpp.so", "some-app/global-metadata.dat")[0];
  var secondModel = new TypeModel(secondApp);

  // Since plugins persist over the lifetime of Il2CppInspector, you MUST set this to false
  // to enable a new primary application to be loaded in case the GUI or 3rd party application
  // loads another IL2CPP application in the same session
  isPrimaryLoaded = false;
}

IL2CPPINSPECTOR WIKI

Plugin Development

Tutorials & Guides

Example plugins

Il2CppInspectorPlugins repository

IL2CPP Articles on Katy's blog

Support

If you find Il2CppInspector useful, please consider supporting its continued development - or just buy me some cookies!

Donate via PayPal

Donate via BTC: 3FoRUqUXgYj8NY8sMQfhX6vv9LqR3e2kzz

Thank you!

Clone this wiki locally