Development builds of Unplug provide the ability to disassemble Chibi-Robo! event scripts into an assembly language syntax and then reassemble them back into bytecode. This is a powerful feature which gives you full control over the game's scripting engine for cutscenes and object interactions.
This document aims to provide an overview of how to write your own scripts. It is not meant to be thorough and you should reference the existing scripts in the game to learn more about how specific things work.
Note that this feature is still early in development. Error reporting is bad, code is difficult to read, there may be bugs, and future changes to Unplug may completely break your scripts. This might not be a great experience yet.
- Rationale
- Getting Started
- Disassembling Scripts
- Assembling Scripts
- Source Structure
- Directives
- Commands
- Operands
- Expressions
- Message Commands
The goal behind the assembly language feature is to provide a means of making script data readable and editable at a low-level. For the most part, each instruction corresponds 1:1 with bytes in the stage file. While this makes scripts verbose, it has some important benefits:
- It is easy to exactly understand the structure of script data.
- Correctness is easy to evaluate by comparing disassembly output.
- The assembler implementation is relatively simple compared to a high-level language compiler.
- High-level languages can be built on top of this as a solid foundation. (No promises though!)
Obviously, this language is not the same as the one that the developers used to originally make the game, as we have no means of knowing what that language actually looked like. Interestingly, though, whatever language the developers used appears to be rather low-level as well - integers have varying sizes for seemingly no reason, and there is human error in one of the stage files which resulted in invalid bytecode that Unplug has to paper over. (In fact, Unplug is designed so that it cannot produce invalid bytecode, so it's probably more robust than the official compiler in some ways!)
You'll first want to grab a development build of Unplug from the GitHub Actions page. Usually you should just be able to grab the latest build with a green checkmark next to it. Scroll down to the Artifacts window on a build's page and download the zip for your platform of choice. This guide assumes that you already know how to configure and use Unplug - if not, read through the Tour Guide.
It is recommended that you use Visual Studio Code to edit script
files - there's an Unplug extension for auto-formatting and syntax highlighting, and it's also a
great editor to have in general. Once VSCode is installed, download the unplug-vscode.zip artifact
and unzip it to get unplug-vscode.vsix. Open VSCode, go to the command palette using F1 (or
Ctrl+Shift+P/Cmd+Shift+P), search for Extensions: Install from VSIX..., press Enter, and then
browse to the file you downloaded. VSCode will install the extension and then ask you to reload it.
If the extension is installed correctly, you should see something like this in the Extensions tab on
the sidebar:
In order to edit anything, you first need to disassemble the game scripts.
Use the script disassemble-all command to do this:
$ unplug --default-iso script disassemble-all -o scriptsThis will make a "scripts" directory with several .us (Unplug Source) files in it. These are the
assembly source files for all the script code in the game.
Once you've edited a script, all you need to do to see it in-game is to use the script assemble
command:
$ unplug script assemble stage07.usMake sure to open a new project first if you haven't:
$ unplug project new asm
$ unplug project open asmYou can use the dolphin command to test your changes if you've configured it:
$ unplug dolphinA label associates a bytecode location with a name that can be referenced elsewhere. To define a label, simply type the label's name followed by a colon:
my_cool_label:
To use the label elsewhere in the code, type its name prefixed with an asterisk:
goto *my_cool_label
A command is an instruction for the script interpreter. These range from control-flow operations to manipulating objects and showing cutscenes. Each command must be on its own line, and consists of an opcode followed by a comma-separated list of operands:
pushbp
setsp mul(var(0), var(1))
setsp var(1)
setsp var(0)
msg voice(0), format("%d"), " * ", format("%d"), " = ", format("%d"), "!", wait(254)
popbp
return
A directive is an instruction for the assembler - that is, it is interpreted at compile-time and often does not correspond to bytecode. These allow you to embed raw data into a script and tell the assembler where entry points are. Their syntax is similar to commands, but they start with a period:
.stage "stage07"
loc_3246:
.db "cb_robo_4.dat"
.prologue *evt_prologue
evt_prologue:
Note that every script must contain either a .stage or .globals directive - this is called a
"target specifier" and informs the assembler what to do with the compiled bytecode.
You can also write comments using ;. These are ignored by the assembler:
; Telly is a calculator!
setsp mul(var(0), var(1)) ; a * b
setsp var(1) ; b
setsp var(0) ; a
Requires Unplug 0.5.1 or newer.
You can define named constants in scripts which get replaced with their actual values when the code
is processed. They are just NAME = VALUE lines, and can be either integers or strings:
CB_ROBO = 20000
MY_VAR = 500
LEFT = 254
HELLO = "Hello!"
dir CB_ROBO, 0 ; Same as `dir 20000, 0`
set var(MY_VAR), 0 ; Same as `set var(500), 0`
msg HELLO, wait(LEFT) ; Same as `msg "Hello!", wait(254)`
Constants cannot be anything more than simple values, though:
FOUR = add(1, 3) ; ERROR!
LABEL = *label ; ERROR!
Future versions of Unplug may ship with constants built in that make code easier to read.
| Syntax | Description | Context |
|---|---|---|
.globals |
Target specifier: the script is for globals.bin | Beginning of the file |
.stage <name> |
Target specifier: the script is for a stage | Beginning of the file |
.db <byte>, ... |
Embed raw bytes or text | After a label or other data |
.dw <word>, ... |
Embed raw 16-bit words | After a label or other data |
.dd <dword>, ... |
Embed raw 32-bit dwords | After a label or other data |
.lib *<label> |
Declare a library function | .globals scripts |
.prologue *<label> |
Declare a prologue function | .stage scripts |
.startup *<label> |
Declare a startup function | .stage scripts |
.dead *<label> |
Declare a player death handler | .stage scripts |
.pose *<label> |
Declare a pose handler | .stage scripts |
.time_cycle *<label> |
Declare a day-night cycle handler | .stage scripts |
.time_up *<label> |
An odd event which never seems to be called | .stage scripts |
.interact <obj>, *<label> |
Declare an object interaction event | .stage scripts |
These commands are available for use in script code. There are many different ways to use a command and the arguments some commands accept can change based on the values of other arguments. Refer to the existing scripts to find examples of these.
| Name | Description |
|---|---|
break |
Jump to a label after a case (equivalent to goto) |
case |
Test a condition (equivalent to if) |
elif |
Test a condition in an else branch (equivalent to if) |
endif |
Jump to a label after an if (equivalent to goto) |
expr |
Test an expression (equivalent to if) |
goto |
Jump to a label |
if |
Test a condition and jump if it's false |
lib |
Call a library function in globals.bin |
return |
Return back to the caller |
run |
Run a subroutine by its label |
while |
Loop while a condition is true (equivalent to if) |
| Name | Description |
|---|---|
popbp |
Restore the stack pointer |
pushbp |
Save the stack pointer |
set |
Assign or update a variable |
setsp |
Push a value onto the stack |
| Name | Description |
|---|---|
anim |
Animation control |
anim1 |
Animation control (?) |
anim2 |
Animation control (?) |
attach |
Attach a background subroutine to an object |
born |
Spawn an object |
camera |
Camera control |
check |
Test a game condition |
color |
Change an object's colors |
detach |
Remove a subroutine from an object |
dir |
Rotate an object |
disp |
Hide or show an object |
kill |
Destroy an object |
light |
Lighting control |
mdir |
Advanced object rotation (?) |
move |
Move an object |
moveto |
Advanced object movement (?) |
movie |
Play a video file |
mscale |
Advanced object scaling (?) |
msg |
Display a message |
pos |
Move an object (?) |
ptcl |
Particle effects |
scale |
Scale an object |
scrn |
Screen effects |
select |
Show a custom menu |
sfx |
Sound effect control |
timer |
Call a subroutine after time has elapsed |
wait |
Wait for a game condition |
win |
Message window control |
| Name | Description |
|---|---|
abort |
Abnormally terminate script execution |
printf |
Log a message in debug builds of the game |
Note: There are no known debug builds of the game, so printf will never actually do anything
because its code is compiled out. There is no known way to make it work.
| Name | Description |
|---|---|
call |
Invoke a system call, sometimes on an object |
menu |
Show a predefined menu |
read |
Load a resource into memory |
warp |
Warp to a stage |
Operands are arguments that can be passed to commands or directives. There are many different types:
| Type | Examples | Description |
|---|---|---|
| Auto | 123450xabcdef |
Integer of any size |
| Byte | 1.b0xcc.b |
8-bit integer |
| Word | 12345.w0xabcd.w |
16-bit integer |
| Dword | 12345678.d0xabcdef.d |
32-bit integer |
| Atom | @anim |
Alters the semantics of a command |
| Text | "Hello, world!" |
Text string (Latin-1/SHIFT-JIS) |
| Label | *my_label |
Memory address of a label |
| Else Label | else *my_label |
Label reference used to make if commands more readable |
| Offset | *0x10 |
Memory address of a file offset (typically used to read stage metadata) |
| Expression | mul(1, 2) |
Complex expression (see below) |
| Message | "Hello, world!"speed(1) |
Message commands (see below) |
Whether or not an integer is interpreted as signed or unsigned is dependent on context, so Unplug accepts both everywhere.
You may notice that most integers have type suffixes (.b, .w, .d). These indicate to the
assembler how many bytes an integer takes up. They are used to preserve the original code and you do
not need to use them in new code that you write (unless you want to), because the assembler can
determine how large a number should be based on context.
Expressions are special operands which can be passed to commands and other expressions to perform
complex calculations. Some expressions take no arguments, e.g. money, whereas others can take
several and some even accept atoms that alter their semantics. There is no practical limit to how
many expressions can be nested inside each other - some if statements in the game code are
extremely long!
Note that all expressions evaluate to 32-bit integers, as every value in a script is an integer. There is no floating-point support and no native string type (they're just memory addresses under the hood).
| Expression | Description |
|---|---|
eq(x, y) |
1 if x is equal to y, 0 otherwise |
ne(x, y) |
1 if x is not equal to y, 0 otherwise |
lt(x, y) |
1 if x is less than y, 0 otherwise |
le(x, y) |
1 if x is less than or equal to y, 0 otherwise |
gt(x, y) |
1 if x is greater than y, 0 otherwise |
ge(x, y) |
1 if x is greater than or equal to y, 0 otherwise |
not(x) |
Invert a condition (1 if false, 0 if true) |
add(x, y) |
x + y |
sub(x, y) |
x - y |
mul(x, y) |
x * y |
div(x, y) |
x / y |
mod(x, y) |
x % y |
and(x, y) |
Bitwise AND of x and y |
or(x, y) |
Bitwise OR of x and y |
xor(x, y) |
Bitwise XOR of x and y |
adda(x, y) |
x += y (only valid in set) |
suba(x, y) |
x -= y (only valid in set) |
mula(x, y) |
x *= y (only valid in set) |
diva(x, y) |
x /= y (only valid in set) |
moda(x, y) |
x %= y (only valid in set) |
anda(x, y) |
x &= y (only valid in set) |
ora(x, y) |
x |= y (only valid in set) |
xora(x, y) |
x ^= y (only valid in set) |
sp(i) |
Value on the stack at i |
bp(i) |
Value on the parent stack at i |
flag(i) |
Value of global flag i |
var(i) |
Value of global variable i |
result |
Temporary variable holding the result of the last command |
result2 |
Unused temporary variable |
pad(x) |
Gamepad state (details) |
battery(x) |
Player's current (x = 0) or max (x = 1) battery level in hundredths of watts |
money |
Player's moolah count |
item(id) |
Inventory count of item id |
atc(id) |
1 if attachment id is unlocked, 0 otherwise |
rank |
Player's chibi-ranking |
exp |
Player's happy point count |
level |
Player's upgrade level (14 = super) |
hold |
ID of the held item |
map(x) |
Current (x = 0) or previous (x = 1) map ID |
actor_name(id) |
Display name of actor id |
item_name(id) |
Display name of item id |
time(x) |
In-game time data:x = 0: day/night flagx = 1: time as a percentage from 0-100x = 2: time rate |
cur_suit |
Currently-equipped suit |
scrap |
Player's scrap count |
cur_atc |
Currently-equipped attachment |
use |
ID of the item that triggered an interaction |
hit |
ID of a projectile that triggered an interaction |
sticker_name(id) |
Display name of sticker id |
obj(...) |
Object properties |
rand(max) |
Random number between 0 and max (inclusive) |
sin(x) |
Sine of x in hundredths of degrees |
cos(x) |
Cosine of x in hundredths of degrees |
array(type, index, address) |
Access an array element (details) |
The msg and select commands do not accept expressions like other commands do - rather, they
accept a sequence of message commands. These commands can either be text strings to display or
special modifiers which change the appearance or behavior of the text:
| Command | Description |
|---|---|
anim(flags, obj, anim) |
Animation control |
ask(flags, default) |
Ask the player a yes/no question |
center(x) |
If x is 1, center the text, otherwise left-align it |
color(i) |
Use predefined color i |
def(flags, i) |
Set the default item in a select command |
format(str) |
A printf format specifier which reads values off the stack |
icon(i) |
Display predefined icon i |
input(digits, editable, selected) |
Ask the player for a number |
layout(x) |
If x is 0, use monospace text layout, otherwise 1 for default |
rgba(color) |
RGBA color |
rotate(deg) |
Character rotation |
scale(x, y) |
Text scale |
sfx(...) |
Sound effect control |
shake(flags, strength, speed) |
Shaky text |
size(x) |
Text size (22 = default, 255 = reset) |
speed(x) |
Text speed (2 = default, higher is slower) |
stay |
Keep the message on-screen after it's done |
voice(id) |
Voice selection |
wait(x) |
Wait for a certain amount of time or a button press (254, 255) |
Some of these have unknown functions right now. You will have to search the game scripts to find how to actually use them.
abort
return
break ADDRESS
endif ADDRESS
goto ADDRESS
lib INDEX
run ADDRESS
case EXPR, else ADDRESS
elif EXPR, else ADDRESS
expr EXPR, else ADDRESS
if EXPR, else ADDRESS
while EXPR, else ADDRESS
popbp
pushbp
set TARGET, VALUE
set UPDATE_EXPR
setsp EXPR
anim OBJECT_EXPR, EXPR...
anim1 OBJECT_EXPR, EXPR...
anim2 OBJECT_EXPR, EXPR...
attach OBJECT_EXPR, ADDRESS
detach OBJECT_EXPR
born EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, ADDRESS
call OBJECT_EXPR, EXPR...
disp OBJECT_EXPR, EXPR
kill EXPR
timer EXPR, EVENT_EXPR
warp EXPR, EXPR
camera @anim, EXPR, EXPR, EXPR
camera @distance, EXPR, EXPR, EXPR
camera @lead, EXPR
camera @obj, EXPR, EXPR, EXPR
camera @pos, EXPR, EXPR, EXPR, EXPR, EXPR
camera @reset, EXPR, EXPR
camera @unk211, EXPR, EXPR, EXPR, EXPR
camera @unk227, EXPR, EXPR, EXPR, EXPR, EXPR
camera @unk229, EXPR, EXPR, EXPR
camera @unk230
camera @unk232, -1
camera @unk232, -2
camera @unk232, 0
camera @unk232, 1
camera @unk232, 2, EXPR
camera @unk232, 3, EXPR
camera @unk232, 4, EXPR
camera @unk236, EXPR
camera @unk237, EXPR
camera @unk238, EXPR
camera @unk240, EXPR, EXPR, EXPR, EXPR
camera @unk243, EXPR, EXPR, EXPR, EXPR
camera @unk251, EXPR, EXPR, EXPR, EXPR
camera @unk252, EXPR, EXPR, EXPR, EXPR
check @anim, OBJECT_EXPR, EXPR
check @cam
check @color, OBJECT_EXPR
check @cue
check @dir, OBJECT_EXPR
check @fade
check @letterbox
check @mono
check @move, OBJECT_EXPR
check @read, OBJECT_EXPR
check @real, EXPR
check @scale, OBJECT_EXPR
check @sfx, SOUND_EXPR
check @shake
check @time, EXPR
check @unk203
check @unk246, EXPR
check @wipe
check @zblur
wait @anim, OBJECT_EXPR, EXPR
wait @cam
wait @color, OBJECT_EXPR
wait @cue
wait @dir, OBJECT_EXPR
wait @fade
wait @letterbox
wait @mono
wait @move, OBJECT_EXPR
wait @read, OBJECT_EXPR
wait @real, EXPR
wait @scale, OBJECT_EXPR
wait @sfx, SOUND_EXPR
wait @shake
wait @time, EXPR
wait @unk203
wait @unk246, EXPR
wait @wipe
wait @zblur
color OBJECT_EXPR, @blend, EXPR, EXPR, EXPR, EXPR
color OBJECT_EXPR, @modulate, EXPR, EXPR, EXPR, EXPR
dir OBJECT_EXPR, EXPR
mdir OBJECT_EXPR, @cam, EXPR, EXPR
mdir OBJECT_EXPR, @dir, EXPR, EXPR, EXPR
mdir OBJECT_EXPR, @obj, EXPR, EXPR, EXPR
mdir OBJECT_EXPR, @pos, EXPR, EXPR, EXPR, EXPR
light EXPR, @color, EXPR, EXPR, EXPR
light EXPR, @pos, EXPR, EXPR, EXPR
light EXPR, @unk227, EXPR, EXPR, EXPR
menu 0
menu 1
menu 2
menu 3
menu 4
menu 5
menu 6
menu 7
menu 1000, EXPR
menu 1001, EXPR, EXPR
move OBJECT_EXPR, EXPR, EXPR, EXPR, EXPR
moveto OBJECT_EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
pos OBJECT_EXPR, EXPR, EXPR, EXPR
movie STRING_EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
msg MESSAGE...
printf STRING
select MESSAGE...
ptcl EXPR, @lead, OBJECT_EXPR, Variadic
ptcl EXPR, @obj, OBJECT_EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
ptcl EXPR, @pos, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
ptcl EXPR, @unk210
read @anim, OBJECT_EXPR, STRING_EXPR
read @sfx, OBJECT_EXPR, STRING_EXPR
mscale OBJECT_EXPR, EXPR, EXPR, EXPR, EXPR
scale OBJECT_EXPR, EXPR, EXPR, EXPR
scrn @fade, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
scrn @hud, 0, EXPR
scrn @hud, 1, EXPR
scrn @hud, 2, EXPR
scrn @hud, 3, EXPR, EXPR, EXPR, EXPR
scrn @hud, 4, -1
scrn @hud, 4, -2
scrn @hud, 4, -3, EXPR
scrn @hud, 4, -4
scrn @hud, 4, 0
scrn @hud, 4, 1
scrn @hud, 4, 2
scrn @hud, 4, 3
scrn @letterbox, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
scrn @mono, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
scrn @shake, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
scrn @wipe, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR, EXPR
scrn @zblur, EXPR, EXPR, EXPR, EXPR, EXPR
sfx SOUND_EXPR, @cue
sfx SOUND_EXPR, 0
sfx SOUND_EXPR, 1
sfx SOUND_EXPR, 2, EXPR
sfx SOUND_EXPR, 3, EXPR
sfx SOUND_EXPR, 4, EXPR, EXPR
sfx SOUND_EXPR, 5
sfx SOUND_EXPR, 6
win @color, EXPR, EXPR, EXPR, EXPR
win @letterbox
win @obj, OBJECT_EXPR, EXPR, EXPR, EXPR
win @pos, EXPR, EXPR
win @reset
Some of these have unknown functions right now. You will have to search the game scripts to find how to actually use them. This list does not include things like integer literals.
cur_atc
cur_suit
exp
hit
hold
level
money
rank
result
result2
scrap
use
eq(EXPR, EXPR)
ge(EXPR, EXPR)
gt(EXPR, EXPR)
le(EXPR, EXPR)
lt(EXPR, EXPR)
ne(EXPR, EXPR)
not(EXPR)
add(EXPR, EXPR)
and(EXPR, EXPR)
div(EXPR, EXPR)
mod(EXPR, EXPR)
mul(EXPR, EXPR)
or(EXPR, EXPR)
sub(EXPR, EXPR)
xor(EXPR, EXPR)
adda(EXPR, EXPR)
anda(EXPR, EXPR)
diva(EXPR, EXPR)
moda(EXPR, EXPR)
mula(EXPR, EXPR)
ora(EXPR, EXPR)
suba(EXPR, EXPR)
xora(EXPR, EXPR)
cos(EXPR)
rand(EXPR)
sin(EXPR)
atc(EXPR)
battery(EXPR)
item(ITEM_EXPR)
map(EXPR)
pad(EXPR)
time(EXPR)
addr(ADDRESS)
array(EXPR, EXPR, ADDRESS)
bp(INTEGER)
flag(EXPR)
sp(INTEGER)
var(EXPR)
obj(@anim, OBJECT_EXPR)
obj(@bone_x, ADDRESS)
obj(@bone_y, ADDRESS)
obj(@bone_z, ADDRESS)
obj(@dir_to, ADDRESS)
obj(@dir, OBJECT_EXPR)
obj(@distance, ADDRESS)
obj(@pos_x, OBJECT_EXPR)
obj(@pos_y, OBJECT_EXPR)
obj(@pos_z, OBJECT_EXPR)
obj(@unk235, OBJECT_EXPR)
obj(@unk247, OBJECT_EXPR)
obj(@unk248, OBJECT_EXPR)
obj(@unk249, ADDRESS)
obj(@unk250, ADDRESS)
actor_name(OBJECT_EXPR)
item_name(ITEM_EXPR)
sticker_name(EXPR)
anim(INTEGER, INTEGER, INTEGER)
center(INTEGER)
color(INTEGER)
format(String)
icon(INTEGER)
layout(INTEGER)
rgba(INTEGER)
rotate(INTEGER)
scale(INTEGER, INTEGER)
shake(INTEGER, INTEGER, INTEGER)
size(INTEGER)
speed(INTEGER)
stay()
voice(INTEGER)
wait(INTEGER)
ask(INTEGER, INTEGER)
def(INTEGER, INTEGER)
input(INTEGER, INTEGER, INTEGER)
sfx(SOUND, -1)
sfx(SOUND, 0)
sfx(SOUND, 1)
sfx(SOUND, 2, INTEGER)
sfx(SOUND, 3, INTEGER)
sfx(SOUND, 4, INTEGER, INTEGER)
sfx(SOUND, 5)
sfx(SOUND, 6)
