Skip to content

RMF commands

Commands 0x00–0x0A. These are the eleven entries of the PutText jump table at . All of them operate on the current _MES_DATA block; the offsets in the last column are fields of that structure. Everything in this section is confirmed from the jump-table handlers.

CmdNameHandlerPayloadEffect / state touched
00End0x2D53180 words

Terminates the run. Commits the max line width to +0x1C/+0x1E and clears the stream pointer +0x34. Sets renderer state 2.

01Space0x2D53500 words

Region-dependent. English builds advance the pen by (width * 12) >> 12, about three quarters of a cell; the Japanese build advances by the full cell, (width * 32) >> 12, and never computes the narrow term at all. Either way one character is refunded from the typewriter budget. See §11.1.

02Color0x2D53702 words

color32 = (word[1] << 16) | word[0], i.e. the four payload bytes are R G B A in stream order. Sub 0 → text colour (+0x00); sub 1 → drop-shadow colour (+0x04). Alpha is PS2-style: 0x80 is fully opaque.

03Dimensions0x2D53A02 words

Glyph cell width (+0x10) and height (+0x12) as U8.8 fixed point — two plain 16-bit values, with no fixed marker bytes. Sub 1 multiplies into the current values instead of replacing them. Also recomputes the cached advances; the English builds derive one term more than the Japanese build does (§11.1). Defaults are 11.20 × 22.40 in English data and 14.40 × 16.00 in Japanese.

04Position0x2D54002 words

Two signed 16-bit coordinates. Sub 0 sets the pen and the line origin absolutely (+0x14/+0x16 and +0x18/+0x1A); sub 1 adds to them.

05TextStyle0x2D54980 words

Stores subcommand % 3 into +0x0C. That value is folded into bits 16+ of every following character code (§5) and is compared against the cached font bank in +0x0D, so it selects which font source subsequent glyphs come from. Dialogue consistently uses 05 01. inferred (purpose), confirmed (mechanism)

06Signal—1 word

No renderer handler; falls through to (§7).

07TextSpeed0x2D54C81 word

Writes the payload word to the typewriter rate +0x20, again U8.8 fixed point: 07 10 00 03 is speed 3.0. The renderer accumulates this into +0x28 and spends 0x100 per glyph.

08———Jump-table entry points at the skip label. Inert.
09StyleSave / StyleRestore0x2D54D81 word

The payload word is a field mask. Sub 0 allocates a 0x58-byte scratch block (+0x2C), records the mask at +0x28 of that block, and copies the selected fields out. Sub 1 copies them back, frees the block and restores the pen. See the mask table below.

0ANewline0x2D56600 words

Updates the running max line width in +0x1C, resets the pen X to the line origin +0x18, and advances the pen Y by the current line height.

Each bit selects one field of _MES_DATA to push or pop. This is a general-purpose style stack; ruby text (§12) is simply its most visible user.

Bit Field Meaning
0x01 u32 +0x00 Text colour
0x02 u32 +0x04 Shadow colour
0x04 s16 +0x14 Pen X
0x08 s16 +0x16 Pen Y
0x10 u16 +0x10 Glyph width
0x20 u16 +0x12 Glyph height
0x40 u8 +0x0C Text style (cmd 05)
0x80 u32 +0x20 Text speed

Ruby annotations use mask 0x00BC = pen position + dimensions + speed, deliberately omitting the two colour bits so the annotation inherits the base text’s colour.

Commands 0x0E and 0x0F. turns a record into a table index confirmed:

00317EF4 lbu $v1, 0($t0) ; cmd[0]
00317EF8 bne $v1, 0x0E, check_0F
00317F00 lbu $v0, 1($t0)
00317F04 andi $v0, $v0, 0xf ; subcommand nibble
00317F08 addiu $v0, $v0, 0x10 ; 0x0E xN -> 0x10 + N
; check_0F:
00317F24 lbu $v0, 1($t0)
00317F28 andi $v0, $v0, 0xf
00317F2C addiu $v0, $v0, 0x20 ; 0x0F xN -> 0x20 + N
; dispatch: index * 12 into ms_pCommandFuncTable
00317F3C sll $v0, $v1, 1
00317F44 addu $v1, $v0, $v1 ; index * 3
00317F50 sll $v1, $v1, 2 ; ... * 4 = index * 12
00317F4C lui $v0, 0x3b
00317F54 addiu $v0, $v0, 0x3a80 ; CTalk::ms_pCommandFuncTable
00317F5C jal 0x149400 ; __ptmf_scall

Dispatch rules:

  • 0x00–0x0D: index is the command byte itself.
  • 0x0E: index is 0x10 + (cmd[1] & 0x0F).
  • 0x0F: index is 0x20 + (cmd[1] & 0x0F).

The runtime table lives in BSS, size 0x240 = 48 entries × 12 bytes (a Metrowerks pointer-to-member function). It is populated from a that uses 16-byte records shaped { u32 0, u32 0xFFFFFFFF, u32 callback_addr, u32 0 }, 48 of them. Both forms were dumped and agree. confirmed

Click any callback name for its address in all three builds.

IndexRecordCallbackPayloadFields and behaviour
Direct commands — 0x00 through 0x0F
0x0000 000 words

Ends the message. Writes renderer state 2 to this+0x1E0, then tears down the talk session (releases bustups, restores the camera, clears the name plate). This is the word that terminates every packet.

0x0101 000 wordsNo CTalk action; handled by the renderer as space.
0x0202 2x2 wordsRenderer-only: Color (§6).
0x0303 2x2 wordsRenderer-only: Dimensions (§6).
0x0404 2x2 wordsRenderer-only: Position (§6).
0x0505 0x0 wordsRenderer-only: TextStyle (§6).
0x0606 1x1 word

this+0x114 = u16[2]. A one-word value the event script can poll mid-sentence — the standard way an EVD script synchronises an action with a point in the dialogue.

0x0707 1x1 wordRenderer-only: TextSpeed (§6).
0x0808 xx—Unused.
0x0909 1x1 word

Renderer-only: StyleSave / StyleRestore (§6).

0x0A0A 000 wordsRenderer-only: Newline (§6).
0x0B–0x0D——Inert in both layers. Never observed in real data.
0x0E, 0x0F——Never reached: these command bytes are always remapped by the subcommand nibble.
0x0E xN — bustup and speaker control
0x100E 202 words

u16[2] = speaker table index (1-based), resolved through GetCharaNo.
u16[4] = mode, stored at this+0x1DA; only bits 0–1 are used: 0 = create bustup, auto facing (0xFF) · 1 = no bustup · 2 = create bustup facing 0 · 3 = create bustup facing 1.
Any previously active bustup slot is released first. Only modes 0 and 1 appear in observed data.

0x110E 1x / 0E 2xvaries

u16[2] = speaker index, u8[4] = visibility mode. Shows or hides an already-created bustup without destroying it.

0x120E x2varies

u16[2] = speaker index. Resolves the bustup via SearchBustup; the body is a stub in this build. inferred

0x130E x3variesByte-identical to _CbBupDirection here. inferred
0x140E 242 words

u16[2] = speaker index, u16[4] = face id. Changes the 3D model’s facial expression mid-sentence. Note the engine name is Face, not “expression”.

0x150E x52 words

u16[2] = speaker index, u16[4] = speak flags. Starts and stops mouth animation independently of a voice clip.

0x160E x62 words

u16[2] = speaker index, u8[4]/u8[5] = open/close cycle timings.

0x170E x7varies

u16[2] = speaker index, u8[4] = emotion flags, s16[5] = magnitude/delta.

0x18, 0x190E x8 / 0E x9—Assertion trap. Not valid data.
0x1A0E xAvaries

u16[2] = speaker index, u8[4] = look-at target, u8[5] = duration, u8[6]/u8[7] = X/Y offsets.

0x1B0E xB—Assertion trap.
0x1C0E xC2 wordsu16[2] = speaker index, u8[4] = eye variant index.
0x1D0E xD2 words

u16[2] = speaker index, u16[4] written to this[0x1BC + slot*2]. A per-slot refcount controlling how long the bustup survives after the message ends.

0x1E0E 4E4 words

u16[2] = speaker index · u8[4] = mode · u16[6] = vertical offset of the plate, signed · u16[8] = override name id.
Mode bits: &3 selects the plate style and 0 disables the plate entirely; &4 is a layout flag; &8 switches the window template from 6 to 5. The vertical offset tracks the line count of the message it belongs to (§11.1).

0x1F0E xF—Assertion trap.
0x0F xN — flow, sentences and audio
0x200F 202 words

u16[2] = frames to wait. 0xFFFF loads the “infinite” float 0x7F7FFFFF into the timer at this+0x118.
u16[4] = flags, masked with 0xFD and stored at this+0x11C. Bit 0 shows the blinking continue cursor. Bit 1 is discarded by the mask.
Observed values are 03 00 for a normal press-to-continue box and 02 00 for a timed wait with no cursor.

0x210F x1—Assertion trap.
0x220F 2x2 words

u16[2] = time limit (0 is rewritten to 0xFFFF = no limit), u16[4] = option count, stored at this+0x11D. The low 7 bits are the count and bit 0x80 is a flag; a list of count*4 + 0x10 bytes is allocated for the cursor state. Options follow in the stream, each terminated by 00 00.

0x230F 3x1 word

u8[2] non-zero sets this+0x10A bit 0 (skipping disallowed) and forces the current text to finish instantly; zero clears it.

0x240F 242 words

Same field layout as _CbWaitSelect, different presentation (the standard windowed choice list). This is the form used by ordinary dialogue choices, always as 0F 24 00 00 <count>.

0x250F 252 words

The wrapper that introduces a message. u16[2] = flags, u16[4] = glyph count. The sentence’s own token stream starts at cmd+6 and runs to its terminator.
The callback calls SetSprtOutput(glyph_count, …) to reserve that many sprites, SetDefaultTextParam to reset style, then CTextMessage::TextOut(cmd+6, …) to render into a fresh slot, and returns 0x802 to retire the driver pass.
Flags: if flags & 3 is non-zero the callback first runs a measuring pass over the stream and back-patches the X field of every 04 20 record with -(line_width / 2) — the engine can auto-centre in place. In all observed field dialogue the flags are 0 and the coordinates are pre-computed instead.

0x260F 6xsee note

Proper-noun substitution. On first encounter it calls CTalk::SetProper(u16*, const RMFCOMMAND*) to expand a name into the scratch buffer at this+0xC8, appends a zero-payload 0F 6x terminator, saves the resume address in this+0xC4 and redirects the stream pointer into the buffer. The terminator hits the same callback, which restores the saved pointer. Returns 0x200 both times (“I moved the stream pointer myself”).
Sizing exception: this is the one record whose length uses the whole second byte, not the high nibble — the payload is 2 * cmd[1] bytes.

0x270F 7xup to 7 words

Reads u8[2] plus up to six more words at +4, +6, +8, +0xA, +0xC, +0xE.
Special role: loops over leading 0F 7x records at the head of a packet and executes them before any rendering starts, returning the first non-0F 7x token. So Jump is effectively the packet prologue — scene setup that must run before the first sentence.

0x280F 282 words

u8[2] = channel, u16[4] = sound id.
If the channel byte is ≥ 0x10 it is treated as signed (slot -= 0x20). The value 0x1F therefore means -1 = “allocate a channel automatically”, which additionally binds the playing handle to the current bustup so lip-sync follows the clip. 0x1F is what voiced dialogue uses throughout.
Suppressed entirely when the global sound-disable flag is set.

0x29–0x2F0F 9x–0F Fx—Assertion trap. Seven unused subcommands.

Every callback returns a value through this+0x1E0. PutText reads it as a bitfield — the low byte is a state, the high bits are directives. This is what makes constructs like _CbProper and _CbSentence possible. confirmed from the loop tail at 0x002D5CF8.

Value Name Effect on PutText
low byte = 0 continue Keep walking the stream (subject to the typewriter budget).
low byte = 1 pause Stop for this frame; refund 0x100 of typewriter budget.
low byte = 2 end Finish this text run. Clears the stream pointer.
0x100 slot_gone Do not write the pen position back — the block is being freed.
0x200 ptr_owned The callback already advanced the stream pointer; do not add (payload+1)*2.
0x400 handled Skip the renderer’s own jump-table handler for this record.
0x800 release With state 2: also free the sprite lists, the style block and the _MES_DATA itself.
0xFF error Triggers an assertion and aborts the run.

Observed combinations: _CbSentence returns 0x802, _CbProper returns 0x200, _CbEnd returns 2, and everything else returns 0.