Skip to content

Structures

brkzlr edited this page Jun 20, 2026 · 6 revisions

Structures (or Structure Types) let you define custom data layouts and apply them to memory addresses. If you've ever reverse engineered a game struct or class and wanted to see all its fields laid out nicely with their current values, this is your bread n butter.

It's basically like creating your own mini type system on top of raw memory. You tell PINCE "at this address, there's an int at offset 0, a float at offset 4, a pointer at offset 8" and so on and the Structure view will read and display all those values for you, updating in real time as the game runs.

How can I access it?

  • You need to be attached to a process first.
  • After that, open Memory View.
  • Go to Tools -> Structures.

This opens the Structures window where you can manage your structure definitions.

Creating a Structure

Click New in the Structures window to open the Structure Editor (titled Edit structure). You'll need to give your structure a name and then add members to it.

Adding Members

Each member represents a field in your structure. Click Add to create a new member, which opens the Member Editor (titled Member). You'll see these options:

  • Name: What you want to call this field (like health, position_x, ptr_to_inventory).
  • Offset (hex): The byte offset from the structure's base address. You can enter it in hex with or without the 0x prefix. When editing an existing member, the field shows the offset with the 0x prefix.
  • Kind: What type of member this is:
    • Value: A primitive value (int, float, string, etc.)
    • Pointer: A pointer to another structure (dereferences the pointer at this offset)
    • Inline: Another structure embedded directly at this offset (no pointer dereference)

The Pointer and Inline kinds are disabled if no structures exist yet. You need to create at least one structure before you can reference it from another.

Value Members

For value members, you pick the data type from the Value type dropdown:

  • Int8, Int16, Int32, Int64: Integer types. Pick a representation from the Repr dropdown (Unsigned, Signed or Hex). The Repr dropdown only appears for integer types, not floats.
  • Float32, Float64: Float types. No repr option since floats don't have signed/unsigned variants.
  • String_ASCII, String_UTF8, String_UTF16, String_UTF32: String types. Set the Length field. The Length field only appears for string and byte array types.
  • ByteArray: Raw bytes with a specified length.

You can also set the Endianness (Host, Little or Big) for any value member. Endianness affects UTF-16 and UTF-32 string encoding by selecting the appropriate little-endian or big-endian codec variant. ASCII and UTF-8 ignore endianness.

Note: The data model supports a zero-termination flag but the manual Member Editor doesn't expose it. Zero termination is always on for manually created members. (The Mono/IL2CPP Dissector's auto-generated System.String structure sets zero termination off for the chars member, but you can't change this from the UI.)

Pointer and Inline Members

These let you nest structures. Say you have a Player structure with a field that points to an Inventory structure:

  1. First create the Inventory structure with its own members.
  2. Then in your Player structure, add a member at the right offset.
  3. Set Kind to Pointer (if the field holds an address) or Inline (if the inventory data is embedded directly).
  4. Select Inventory from the Structure dropdown.

In the Structure Editor's member tree, pointer members display as ptr → StructName and inline members as inline → StructName in the Type column. The same prefixes are shown in the Structure View.

If you reference a structure that later gets deleted, the member keeps its link. The deleted structure's name is still shown in the Structure dropdown when editing that member, so you don't lose the reference silently.

Viewing a Structure

Once you've defined a structure, you can view it at any address:

From the Structures Window

  1. Select the structure in the Structures window.
  2. Click View at address….
  3. Enter the base address in the dialog that pops up.

Base address formats

The field accepts any GDB expression, not only a raw hex value:

  • absolute address: 0x7f1234abcd
  • symbol: g_player
  • module-relative address: libgame.so+0x4F2A10 (resolved against the process's loaded modules)
  • pointer dereference: *(long*)(libgame.so+0x18) to base the structure on whatever a pointer currently points to

The same formats apply when adding to the address table and when changing a base by double-clicking a structure row's address column.

From the Address Table

Right-click on any entry in the address table and hover over View as structure. You'll see a submenu listing all your defined structures. Pick one and the Structure View opens at that entry's address.

This is handy when you've already found an interesting address and want to interpret it as a known structure without having to copy the address over manually. The submenu is hidden for script entries, struct rows and when no row is selected.

The Structure View window (titled Structure view) opens showing all your members with their current values. The view has four columns:

  • Offset: The byte offset from the base address.
  • Name: Your member name.
  • Type: The data type or nested structure name (with ptr → or inline → prefix for nested structs).
  • Value: The current value read from memory. Shows ?? if the memory read fails.

Live Updates

The Structure View polls memory at the same interval as the address table, so values update automatically as the game runs. You can double-click any value in the Value column to edit it directly. The change is written to memory immediately and that single item is refreshed right away without waiting for the next poll.

Polling pauses while the Structure View is hidden or closed, so it doesn't waste CPU when you're not looking at it.

Expanding Nested Structures

If you have pointer or inline members, you'll see an expand arrow next to them. Click it to drill down into the nested structure and see its fields. This works recursively up to 16 levels deep.

For pointer members, PINCE reads the pointer value at that offset and uses it as the base address for the nested structure. If the pointer is null, the expansion just shows nothing. For inline members, the bytes at that offset are interpreted directly as the nested structure.

Adding to the Address Table

You can push an entire structure's members into the address table as a group:

From the Structures Window

  1. Select your structure in the Structures window.
  2. Click Add to address table.
  3. Enter the base address (see Base address formats under Viewing a Structure).

From the Structure View

Click the Add to address table button at the bottom of the Structure View window. This uses the view's current base address, so you don't need to re-enter it.

All the structure's members appear in the address table as a collapsible group, with their offsets shown as relative addresses (like +0x10). Nested structures become sub-groups. Pointer members are materialized as pointer chains so the address table can resolve them. This is handy when you want to monitor or freeze multiple related values at once.

Structure Rows in the Address Table

When you add a structure to the address table, the parent row is a special type that only holds an address, it doesn't have an editable value or type like normal entries. The child rows underneath it are the actual members with their values.

You can change the base address of a structure row by double-clicking its address column. This updates all the child rows' addresses automatically since they're calculated as offsets from the base.

Structure rows have some limitations compared to normal entries:

  • No freeze: The freeze checkbox on a structure row just unchecks itself as you can't freeze the parent row. Individual child members can still be frozen though.
  • No "View as structure": Right-clicking a structure row doesn't show the View as structure submenu since it's already a structure.
  • Relative addresses: Child rows show their offset as the address (like +0x10) rather than the resolved absolute address. The resolved address is used internally for reads and writes.

Note: A pointer-dereference base (e.g. *(long*)(libgame.so+0x18)) is resolved when the row is added and re-resolved on a manual table refresh. It does not automatically follow a pointer that moves on its own. This is done to avoid the cost of a GDB call on each automatic address table refresh which happens very frequently (every 0.5s by default).

Editing and Deleting

  • Edit: Modify an existing structure's name or members. Double-clicking a structure in the list also opens the editor.
  • Delete: Remove a structure definition. You'll get a confirmation prompt asking Delete structure '{}'? where the name is shown in single quotes. Deleting a structure doesn't affect any address table entries you've already created from it.

In the Structure Editor, you can reorder members with the Up and Down buttons or remove them with Remove. You can also click Edit to modify an individual member (this opens the Member Editor pre-loaded with that member's settings).

If you try to save a structure with an empty name, you'll get Structure name cannot be empty. If you try to use a name that already exists, you'll get A structure with this name already exists.

Saving with Sessions

Your structure definitions are saved alongside your address table when you save a session (.pct file). This means when you load a session later, all your structures come back with it.

If you share a .pct file with someone, they'll get your structures too, which is useful for distributing cheat tables that rely on specific structure layouts.

Practical Example

Let's say you've found a player object at 0x7f1234560000 and you know from reverse engineering that it has:

  • Health (int32) at offset 0x10
  • Mana (float32) at offset 0x14
  • Position X (float32) at offset 0x20
  • Position Y (float32) at offset 0x24
  • A pointer to an Inventory struct at offset 0x30

You'd create a Player structure like this:

Player
├── health        0x10    Int32
├── mana          0x14    Float32
├── position_x    0x20    Float32
├── position_y    0x24    Float32
└── inventory     0x30    ptr → Inventory

Then create an Inventory structure with whatever fields you've found there:

Inventory
├── gold          0x08    Int32
├── item_count    0x0C    Int32
└── items_ptr     0x10    ptr → ItemArray

Now when you view the Player structure at 0x7f1234560000, you see all the player's stats and you can expand inventory to see the nested Inventory fields. If you expand items_ptr, it follows that pointer too and shows the item array structure.

Tips and Tricks

Finding Offsets

If you're not sure about the exact offsets, you can use the address table and Memory Viewer to figure them out. Add a variable at a known offset, then use Find out what accesses this address to see what code touches it. The disassembly will show you the offset being used (like [rax+0x14]).

Structure Size

The structure's size field is mostly informational, it doesn't affect how members are read. But it's useful to keep track of how big your structure is, especially if you're dealing with arrays of structures. The size is only set automatically by the Mono/IL2CPP Dissector when exporting structures, the manual editor doesn't set it.

Shared Structures

You can reuse the same structure definition across multiple addresses. If you have 5 enemies all using the same Enemy struct layout, just create one Enemy structure and view it at each enemy's base address.

Combining with Mono/IL2CPP Dissector

If you're working with a Mono or IL2CPP game, the Mono/IL2CPP Dissector can automatically generate structure definitions from the game's managed classes:

  • Export as structure: Right-click on a class to create a structure definition with all the fields already filled in. Opens the Structure Editor for confirmation.
  • Dissect as structure: Right-click on a class with a singleton pattern (like Player.Instance) or on a live instance in the Find Instances window to immediately open the Structure View on that object.

Common Patterns

Games often use common patterns you can recognize:

  • Object headers: Most objects start with a vtable pointer (8 bytes on 64-bit, 4 on 32-bit) followed by a sync block or reference count.
  • Arrays: Managed arrays usually have a length field followed by the elements. Native arrays might just be a pointer and size stored separately.
  • Strings: UTF-16 strings often have a length prefix. C-style strings are just null-terminated byte sequences.

Clone this wiki locally