Skip to content

Mono IL2CPP Dissector

brkzlr edited this page Jun 25, 2026 · 7 revisions

The Mono/IL2CPP Dissector is for games that have a managed .NET-style layer, most commonly Unity games.

Normal scans and assembly work still matter, but managed games also carry type metadata. That means the game often knows about things like Player, Inventory, health, TakeDamage() and GameManager.Instance. The Dissector lets you browse that layer instead of starting from raw addresses only.

Use it when:

  • The game is Unity, Mono, IL2CPP or another supported Mono/.NET target
  • You want to find class fields instead of guessing every value by scanning
  • You want live object instances, not just one random address from a scan
  • You want to turn a managed class into a PINCE structure
  • You want to jump from a managed method back into native disassembly
  • You want to call a managed method and see what happens

Supported targets are native Linux Mono/IL2CPP and WINE/Proton Mono/IL2CPP, both x86 and x64.

Behind the scenes PINCE injects a small collector into the target process and talks to it over a local socket. If injection fails, the runtime is not loaded yet, the game uses something unsupported or some sandbox/anti-cheat blocks injection, the tool will not open properly. The usual error is No supported managed runtime found in this process.

How can I access it?

  • Attach to the process first
  • Open Memory View
  • Go to Tools -> Dissect Mono/IL2CPP

There is no keyboard shortcut for this menu item.

If the target has no supported managed runtime loaded, PINCE shows No supported managed runtime found in this process. If the runtime was detected but the collector connection fails later, you may see Mono collector is not connected.

The normal workflow

A typical use looks like this:

  1. Open the Dissector
  2. Search for a class name like Player, Character, Inventory or GameManager
  3. Expand the class and look at Fields
  4. If the class has a singleton/static instance, add fields straight to the address table
  5. If it does not, use Find Instances, pick a live object, then add fields from that object
  6. If the class is interesting, export it as a structure or dissect a live instance as a structure
  7. If a method is interesting, disassemble it, set a breakpoint on it or invoke it

That is the main idea. The rest of this page explains what each step actually does.

The tree

The Dissector shows a tree with two columns: Name and Value.

The Name column is the hierarchy. The Value column is mostly for field type and offset information.

Assembly-CSharp.dll
  Game.Player
    Fields
      health       Int32 @ 0x18
      position     Vector3 @ 0x20
      inventory    Inventory @ 0x30
    Methods
      TakeDamage(System.Int32)
      GetHealth() : System.Int32

The tree is loaded lazily. PINCE does not fetch every class, field and method at once. It fetches children when you expand nodes, which keeps huge Unity games usable.

Assemblies

The top level is loaded assemblies.

For Unity games, Assembly-CSharp.dll is usually the first place to look because game scripts often live there. Engine code and third-party libraries usually show up in other assemblies.

Classes

Expanding an assembly shows classes. Classes are shown with their namespace when one exists, for example Game.Player.

Use the search box when the list is too big. It matches the Name column only, so it finds assembly names, class names, field names and method names. It does not search field types or offsets in the Value column.

Fields

Each class has a Fields node.

Fields show their type and offset in the Value column:

  • Instance fields look like {type} @ {offset}, for example Int32 @ 0x18
  • Static fields look like static {type} @ {offset}, for example static String @ 0x18
  • Const fields look like const {type}, for example const Int32

Instance field offsets are offsets from the object base. If a live Player object is at 0x7f00100000 and health is at 0x18, then that field is at 0x7f00100018 for that object.

Static field offsets are not absolute runtime addresses. They are offsets inside the runtime's static field storage. PINCE resolves the real address only when you use a context menu action like Copy Address or Add this address to address list.

Const fields are compile-time constants. They have no runtime address, so they do not get the normal field context menu.

Methods

Each class also has a Methods node.

Methods show their signature in the Name column, for example TakeDamage(System.Int32) or GetHealth() : System.Int32. The Value column is empty for methods.

On Mono, methods may be JIT compiled. On IL2CPP, methods are already native code. Either way, the Dissector can ask the runtime for the native address and send you to the normal disassembly view.

Showing inherited members

The Show inherited members checkbox includes fields and methods from parent classes.

Inherited members appear greyed out. Fields also show the declaring class in the Value column, for example Int32 @ 0x18 (PlayerBase).

A few details:

  • PINCE walks up to 32 parent classes
  • Toggling the checkbox rebuilds the tree
  • Rebuilding the tree collapses nodes you had expanded

Adding fields to the address table

This is probably the most useful part of the Dissector: turning a field into an address table entry.

The right way depends on what kind of field you are dealing with.

Static fields

Static fields have one runtime storage location per class/runtime context.

Right click a static field and use Add this address to address list. PINCE resolves the runtime address and adds it to the address table.

Important caveat: only the field name and address are passed to the table. The value type is not always set for you, so check the row type afterwards. If the field says Int32, set the address table row to Int32. If it says Single, that usually means Float32.

You can also use Copy Address to copy the resolved runtime address.

If the runtime cannot resolve the static field address, PINCE shows Static field address is unavailable for this runtime. This is more common with some IL2CPP games because static field storage can be harder to calibrate.

Instance fields with a singleton

Instance fields need an object first. health @ 0x18 is not enough by itself because every Player object has its own health.

Some classes solve this nicely with a singleton pattern:

Player.Instance -> Player object
Player.health   -> +0x18 from that object

PINCE detects a simple singleton when a class has a static field whose type is the same class. Examples are Player.Instance, GameManager.Current or similar patterns.

When this exists, right clicking an instance field in the main tree can show Add this address to address list. PINCE creates a pointer chain rooted at the singleton static field, then follows the field offset.

This is usually much better than adding one absolute object address, because it can survive game restarts as long as the singleton field keeps pointing to the right object.

Dissect as structure also appears on classes with a singleton. It opens the Structure View on the live singleton object. If a structure with the same class name already exists, PINCE reuses it instead of making a duplicate.

Instance fields without a singleton

If there is no singleton, use Find Instances.

Right click the class and choose Find Instances. PINCE scans memory for live objects of that class. Then you can expand a specific object and add fields from there.

In the Find Instances tree, Add this address to address list is available for instance fields because PINCE has a concrete object base.

For a one-hop field, the address table entry is an absolute address:

0x7f8a12340000 + 0x18

For a drilled nested field, PINCE creates a pointer chain from the object base.

Finding instances

Find Instances scans memory for live objects of a class.

This is useful for questions like:

  • Which of these many enemies is the one I am looking at?
  • Where are all Player objects?
  • Does this class even have live objects right now?
  • Can I pick a real object and inspect its fields as a structure?

How it works internally: every managed object starts with a header word that identifies its type. For Mono this is usually a vtable pointer. For IL2CPP this is usually a class pointer. PINCE asks the collector for that marker, then scans readable/writable memory for it.

The instance scan:

  • Uses a private scanner, so it does not disturb the main scan tab
  • Scans ALL_RW memory regions
  • Uses 4-byte alignment on x86 and 8-byte alignment on x64
  • Shows at most the first 2000 instances in the UI

If no objects are found, PINCE shows No live instances found.

If the runtime cannot provide the marker, PINCE shows Could not resolve an instance marker for this class. This can happen with some stripped or unusual IL2CPP builds.

Instance window menu

Right clicking a found instance gives you:

  • Add this address to address list: adds the object base address
  • Dissect as structure: opens Structure View on this exact instance
  • Copy Address: copies the object address
  • Invoke...: shows class methods and opens the Invoke dialog with this instance already filled in

The Invoke... submenu is disabled if the class has no methods.

Drilling through reference fields

If a non-static field is another managed object, you can expand it.

Example:

inventory    Inventory @ 0x30
  items      Item[] @ 0x10  [Inventory]
  gold       Int32 @ 0x18   [Inventory]

This lets you walk object references without manually calculating every pointer hop.

PINCE keeps the offset path while you drill. If you add a deeply nested field to the address table, it creates the right pointer chain automatically.

Some limits and caveats:

  • Static object fields cannot be drilled into from the tree
  • Drilling is limited to 16 levels to avoid circular references looping forever
  • At the limit you see (maximum drill depth reached)
  • If the field type cannot be resolved, you see (type could not be resolved)
  • Generic type parameters and interfaces are common cases where resolving the exact class may fail

Working with methods

Right click a method to get method actions.

Disassemble

Disassemble opens the method's native address in the disassembly view.

This is especially useful for IL2CPP. The class/method names help you find the interesting function, then the native disassembly is where you patch, NOP, trace, set watchpoints or make code caves.

Set Breakpoint

Set Breakpoint sets a breakpoint at the method entry point.

This is useful when you know the method name but want to see when the game calls it, what thread calls it, what arguments look like or what code path led there.

Copy Address

Copy Address copies the method's native address.

Invoke

Invoke... opens the managed method invocation dialog.

This can be very useful, but it is also one of the easier ways to crash or confuse the game. You are asking the target runtime to run a method now, maybe at a time the game did not expect.

Invoking methods

The Invoke dialog lets PINCE call a managed method through the collector.

It can be used to:

  • Test whether a method does what you think
  • Trigger game logic manually
  • Read a computed return value
  • Call a method on a specific object found with Find Instances

For static methods, Instance (this) is disabled and set to 0.

For instance methods, you need a valid object pointer. There are a few ways to get one:

  • Open Invoke... from a found instance, which fills it in automatically
  • Use Find instance... in the Invoke dialog
  • Let PINCE fill it from a singleton, if the class has one
  • Paste an object address manually

Find instance... scans for live instances of the declaring class and lets you pick from a dropdown. It shows up to 2000 addresses and says Select a live instance:. If truncated, it adds (showing first 2000).

Parameter types

The Invoke dialog handles these argument kinds:

  • Primitive values: normal text input, parsed as int/float/bool/etc
  • Strings: text input for managed string arguments
  • Objects: hex address input for object references
  • Simple structs/value types: one input per primitive field
  • Complex structs/value types: raw hex bytes, with a hint like 24 raw bytes (hex)

If a parameter type cannot be marshalled, the input is disabled. Calling the method shows This method has a parameter type that can't be marshalled yet.

If parameter names are not exposed by the runtime, the collector makes names like arg0, arg1 and so on.

Return values

After a call, the result area shows what happened:

  • Primitive return values show as Result: {value}
  • String returns show the managed string content
  • Struct returns show Result: field=value, field=value, ... when fields are primitive
  • Complex struct returns show hex bytes
  • Void methods show Returned (void)
  • Exceptions show Exception thrown (object @ {hex})

Bad input gives simpler errors:

  • Invalid this pointer: Invalid instance pointer
  • Bad arguments: Could not parse the argument values

Exporting classes as structures

Right click a class and choose Export as structure to turn the managed class layout into a PINCE structure.

This is useful when you want a more comfortable view of object memory, especially after Find Instances gives you a real object address.

Exporting does this:

  1. Creates a structure from instance fields
  2. Includes inherited instance fields, up to 32 parent levels
  3. Converts primitive fields into matching PINCE value types
  4. Turns object references into pointer fields
  5. Inlines simple value types like Vector3 when their fields are primitive
  6. Falls back to byte arrays for fields it cannot represent nicely
  7. Opens the Structure Editor so you can review it

If you cancel the Structure Editor, PINCE deletes the structure it just created.

If a structure with the same name already exists, PINCE appends a counter like Player_1 or Player_2. Self-referential types use cycle detection so recursive classes do not create infinite nested structures.

Dissecting as structure

Dissect as structure skips the manual address step and opens Structure View on a live object.

You can use it from:

  • A class with a detected singleton
  • A specific object in the Find Instances window

If the structure does not exist yet, PINCE creates it. If one with the same name exists, PINCE reuses it.

Managed strings

System.String fields get special handling when structures are exported.

PINCE creates a System.String structure for the managed string layout.

64-bit layout:

  • vtable_ptr at 0x00: object type identifier
  • sync at 0x08: synchronization block
  • length at 0x10: character count
  • chars at 0x14: UTF-16 character data

32-bit layout:

  • vtable_ptr at 0x00: object type identifier
  • sync at 0x04: synchronization block
  • length at 0x08: character count
  • chars at 0x0C: UTF-16 character data

When a System.String structure is added to the address table, PINCE reads length at runtime and adjusts the chars member length. It caps this at 4096 characters. Before that dynamic adjustment, the default preview is 32 bytes.

Good places to start

Unity games often have hundreds or thousands of classes. These names are usually worth searching for:

  • Player, Character, Actor: player-related classes
  • Enemy, NPC, AI: enemy behavior
  • Inventory, Item, Equipment: item systems
  • Health, Stats, Attributes: values you may want to watch
  • Manager, Controller, System: game state and high-level logic
  • Save, Config, Settings: persistent data

Search only matches the Name column, so search for class, field or method names. It will not find all fields of type Int32 just because their Value column says Int32 @ 0x18.

What field types usually mean

Some common managed types map pretty cleanly to memory concepts:

  • System.Int32: 4 bytes integer
  • System.Int64: 8 bytes integer
  • System.Single: Float32
  • System.Double: Float64
  • System.Boolean: usually 1 byte boolean
  • System.String: pointer to a managed string object
  • Class types: pointer/reference to another managed object
  • Arrays like T[]: pointer/reference to a managed array object
  • Value types like Vector3 or Quaternion: inline structs, not object pointers

For address table entries, always check the type after adding. The Dissector gives you the field information, but the address table may still need you to pick the right value type manually.

Common failures

  • No supported managed runtime found in this process means PINCE did not detect a supported Mono/IL2CPP runtime in the process maps. The runtime may not be loaded yet, the game may not be managed, the runtime may be unsupported or the target may be hiding/blocking things.
  • Mono collector is not connected means the collector is not ready anymore or failed while talking to the target. Reopening the tool after reattaching may help, but some failures need a game restart.
  • Static field address is unavailable for this runtime means the collector could see the field metadata but could not resolve a real runtime address for that static field.
  • Could not resolve an instance marker for this class means PINCE could not get the type marker needed for Find Instances.
  • No live instances found does not always mean the class is useless. It can mean no object exists yet, the object exists only in another scene, the class is abstract/base-only or the object layout is not what the scanner expects.

Combining it with normal PINCE tools

The Dissector is not a replacement for the Memory Viewer, scanner or Libpince Engine. It gives you better names and better entry points.

Common combinations:

  • Use Find Instances, add a field to the address table, then freeze or watch it
  • Use Disassemble on TakeDamage, then patch the native code in Memory Viewer
  • Use Set Breakpoint on a managed method, then inspect registers/stack when it hits
  • Export a class as a structure, then use Structure View to inspect different instances
  • Use method names to find the code path, then use Search Opcodes/Watchpoints for lower-level work

For IL2CPP especially, remember that the method body is native code. The Dissector helps you find Player.TakeDamage, but the patching work still happens in the normal native-code tools.

Clone this wiki locally