Editor
PS3 PPU LabDocs
Guide

Getting started

Write PowerPC in the editor and hit Assemble (or Ctrl+Enter). The assembled words show on the right; pick a shape from the format dropdown: hex, raw bytes, C#/C arrays, RPCS3 patch.yml, or NetCheat. Save a file with Ctrl+S.

li r3, 0x1
blr

Addresses & hooks

address sets where the following words live in memory. The address-based output (RPCS3, NetCheat, hex blocks) needs it. hook writes a branch at an address into the next block.

address 0x00A5EA30
li r3, 0
blr

Register aliases

Name a register so you can read the code instead of the number. set binds a name, unset drops one, clearset clears them all.

set r31, base
lwz r3, 0x10(base)
unset base

Structs

A struct names a memory layout. Fields lay out by their type size (or an explicit @ offset). Give it an @ address and field values and it also emits that data at the address, zeroing any unset fields.

struct Player @ 0x016C7C30 {
name : string[16]
health : f32 = 150
ammo : i32
}

To read fields in code, get the base into a register. Use loadstruct rX, Type(loads the struct's address), or set rX, name as Type to type a pointer a caller already gave you. Then Type.field resolves to offset(rX).

loadstruct r3, Player
lfs f1, Player.health // -> lfs f1, 0x10(r3)
lbz r4, Player.ammo

Fields align naturally by default, matching how a game struct was compiled, so overlaying an existing structure lines up for free. Add packed after the name or address to lay fields end to end with no padding, for data you own. An explicit @ offset still wins either way.

struct SaveHeader @ 0x100 packed {
version : u8 // 0x0
flags : u8 // 0x1
score : i32 // 0x2, not padded to 0x4
}

Functions

func (or sub) declares a function. Header params bind register names with the same grammar as struct fields, name : Type @ register. So the params double as the function's contract, and player.field resolves in the body. Params can also be written as param lines at the top of the function.

func applyDamage: player: Player @ r3, amount: i32 @ r4
lfs f1, player.health
fsubs f1, f1, f2
.clamp:
fcmpu cr0, f1, f2
bge .store
.store:
stfs f1, player.health
blr

A leading dot marks a local label (.clamp, .store), scoped to its function, so you can reuse .loop in every func. global name: Type @ rX binds a name visible in every function.

global save: SaveData @ r31
 
func a:
lwz r3, save.gold
blr

Imports

importpulls another saved file's exports (struct types, register aliases, and function addresses) into the current one. It's transitive, and it never copies code bytes.

import combat
 
bl applyDamage

A file is referenced by its name, not its folder, so import combat works wherever combat sits in the Files rail. Start typing after import and your saved files autocomplete.

Debugging with traps

A trap halts the CPU where it fires, so you can drop one into game code and read the register state when it hits (in the RPCS3 debugger, or a crash log). trap (or a bare tw) is unconditional. To fire only on a condition, compare two registers with tweq / twne / twlt / twgt / twle / twge, or a register against a constant with the …i forms.

trap // stop here, always
tweq r3, r4 // stop if r3 == r4
tweqi r0, 10 // stop if r0 == 10

The reference lists every form. The raw tw TO, rA, rBtakes a 5-bit condition mask (16 lt, 8 gt, 4 eq, 2 ltu, 1 gtu, added together) for a combination the aliases don't cover.

Disassembler

Open Disassemble hex in the Tools list. Paste big-endian words almost any way (plain, 0x/\\x/$ prefixes, byte streams, objdump or hexdump output, memory dumps with addresses) and it strips the noise. Set a base address to get PC-relative branches right, then Promote to file to drop the result into the editor.