BPVM Snack Pack #16 - Reading Bytecode: The Matrix Revealed | ebp BPVM Snack Pack #16 - Reading Bytecode: The Matrix Revealed | ebp BPVM Snack Pack #16 - Reading Bytecode: The Matrix Revealed | ebp
Post

BPVM Snack Pack #16 - Reading Bytecode: The Matrix Revealed

Ever wondered what your compiled Blueprint actually looks like? Here's how to read the bytecode output and understand what your nodes became.

BPVM Snack Pack #16 - Reading Bytecode: The Matrix Revealed

The content in this post is based on Unreal Engine 5.6.0

BPVM Snack Pack - Quick Blueprint knowledge drops! Part of the Blueprint to Bytecode series.

Enable Bytecode Output

First, you need to see the bytecode! Add this to your config:

1
2
[Kismet]
CompileDisplaysBinaryBackend=True

Now when you compile, the output log shows the actual bytecode!

The Bytecode Format

Your Blueprint becomes text like this:

1
2
3
4
5
6
7
8
9
10
11
12
LogK2Compiler: [function ExecuteUbergraph_BPA_MyActor]:
Label_0x0:
    $4E: Computed Jump, offset specified by expression:
        $0: Local variable of type int32 named EntryPoint
Label_0x10:
    $44: EX_CallFunction (FFrame::Step)
        $8: Function PrintString
        $B: EX_Nothing
    $4: Return expression
        $B: EX_Nothing
Label_0x20:
    $53: EX_EndOfScript

It looks like Assembly language for Blueprint!

Understanding the Symbols

$XX: EExprToken (instruction or data)

1
2
3
4
$44 = EX_CallFunction  // Call a function
$0  = EX_LocalVariable // Local variable
$4  = EX_Return        // Return from function
$53 = EX_EndOfScript   // End of bytecode

These are the VM opcodes!

Labels Are Jump Targets

1
2
3
Label_0x0:   // Offset 0 bytes
Label_0x10:  // Offset 16 bytes
Label_0x20:  // Offset 32 bytes

Labels mark where jumps go. The number is the byte offset from function start!

Reading a Function Call

1
2
3
4
$44: EX_CallFunction (FFrame::Step)
    $8: Function PrintString
    "Hello World"
    $B: EX_Nothing

Translation:

  1. $44 = “I’m calling a function”
  2. $8 = “Here’s the function pointer”
  3. “Hello World” = “Here’s the parameter”
  4. $B = “End of parameters”

The Ubergraph Mystery

1
2
3
4
[function ExecuteUbergraph_BPA_MyActor]:
Label_0x0:
    $4E: Computed Jump, offset specified by expression:
        $0: Local variable of type int32 named EntryPoint

Remember the Ubergraph? It starts with a jump table:

  • EntryPoint 0 = BeginPlay
  • EntryPoint 1 = Tick
  • EntryPoint 2 = Your custom event

The VM jumps to the right entry based on which event fired!

Reading Variables

1
2
3
$0: Local variable of type float named Health
$1A: Self
$11: Object variable Property /Script/Engine.Actor:RootComponent

Variables show:

  • Type (float, int, object)
  • Name (Health, RootComponent)
  • Scope (Local, Self, Property)

Common EExprToken Values

Here’s a cheat sheet ($ prefix indicates hex values as shown in disassembly):

1
2
3
4
5
6
7
8
9
10
11
12
$00 = EX_LocalVariable       // Local var (hex: 0x00)
$0B = EX_Nothing             // Null/empty (hex: 0x0B)
$04 = EX_Return              // Return (hex: 0x04)
$06 = EX_Jump                // Unconditional jump (hex: 0x06)
$07 = EX_JumpIfNot           // Conditional jump (hex: 0x07)
$1A = EX_Self                // The 'this' pointer (hex: 0x1A)
$1C = EX_IntConst            // Integer literal (hex: 0x1C)
$1F = EX_StringConst         // String literal (hex: 0x1F)
$27 = EX_ObjectConst         // Object reference (hex: 0x27)
$44 = EX_CallFunction        // Function call (hex: 0x44)
$4E = EX_ComputedJump        // Jump table (hex: 0x4E)
$53 = EX_EndOfScript         // End marker (hex: 0x53)

A Complete Example

Your Blueprint:

1
BeginPlay → Print("Hello")

The Bytecode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[function ExecuteUbergraph_BP_MyActor]:
Label_0x0:
    $4E: Computed Jump            // Entry jump table
        $0: EntryPoint

Label_0x10:                       // BeginPlay entry
    $44: EX_CallFunction          // Call function
        $8: PrintString           // Function to call
        $1F: String "Hello"       // Parameter
        $B: EX_Nothing            // End params
    $4: Return                    // Return
        $B: EX_Nothing

Label_0x30:
    $53: EX_EndOfScript           // All done

The Stack Machine

The VM is a stack machine:

1
2
3
4
5
6
7
// Your code: A = B + 5

// Bytecode:
Push B        // Put B on stack
Push 5        // Put 5 on stack
Add           // Pop two, add, push result
Pop A         // Pop result into A

Most operations work on a virtual stack!

Why Offsets Matter

1
2
3
Label_0x10: CallFunction
Label_0x20: Return
Label_0x22: EX_EndOfScript

The VM uses byte offsets for jumps:

1
2
// Jump 16 bytes forward
JumpIfFalse 0x10  // Goes to Label_0x10

It’s all pointer arithmetic under the hood!

Reading Complex Logic

Branch node:

1
2
3
4
5
6
7
8
9
$7: EX_JumpIfNot              // If condition is false
    $0: Local bool Condition   // Check this variable
    Label_0x30                 // Jump here

// True path
CallFunction(DoSomething)

Label_0x30:                    // False path
CallFunction(DoSomethingElse)

Branches become conditional jumps!

Quick Takeaway

  • Enable bytecode output in DefaultEngine.ini
  • $XX = EExprToken (instruction/data)
  • Label_0xXX = Jump target at byte offset XX
  • Ubergraph starts with a computed jump table
  • VM is a stack machine (push/pop operations)
  • Function calls show function + parameters + end marker
  • Branches become conditional jumps

Seeing The Matrix

Once you enable bytecode output, you can see exactly what your Blueprint becomes. It’s like seeing The Matrix - those pretty nodes are just a facade for the raw bytecode underneath!

Want More Details?

For complete bytecode deep-dive with real examples:

Next: How function calls work in bytecode!


🍿 BPVM Snack Pack Series

This post is licensed under CC BY 4.0 by the author.