Wikifang:Network Translation Patchsite/BugVM
Bugsite contains a virtual machine which executes all (non-IRQ) game logic. It is a stack-based virtual machine with a large number of opcodes specific to the game.
Data and Link Stacks
Two stacks are managed by the VM: a link stack and a data stack. The link stack ($C100) supports up to $3F call frames of 4 bytes each, which is manipulated entirely by the call, jump-far, and return opcodes. The data stack ($C200) supports up to $55 data items of 3 bytes each. Each data item consists of a little-endian 16-bit word followed by a tag byte, which specifies if it's...
- $3D an immediate value
- $1D a word index into the indirect memory array
- $1E a bit index into the predicate array
Data items are pushed onto the stack as immediate values and then cast to indirect or predicate offsets as necessary. Both stacks grow upwards from their base address.
Indirect & Predicate Memory
BugVM allows direct access (read and write) to indirect and predicate memory. Indirect memory covers WRAM3:$C400 to WRAM3:$D7FF, and is indexed using word offsets. Predicate memory covers WRAM3:$D800 to WRAM3:$DFFF and is indexed as a massive bitfield.
Indirect and predicate memory can be saved to and loaded from SRAM using opcodes $47 and $48, along with additional data in SRAM.
Known Indirect Memory Slots
Index | WRAM Addr. | Name | Purpose |
---|---|---|---|
$01 | $C402 | W_System_GameVersion | Stores the version of the game the user is playing.
0 is Alpha version, 1 is Beta version |
$12 | $C424 | W_BugVM_StringArena | Stores the current location of the end of the string-builder arena, used by the DB opcode to store embedded string data. |
$172 | $C6E4 | W_MainScript_PortraitID | Stores the currently selected character portrait, if any. |
$580 - $59F | $CF00-$CF3F | W_Battle_AttrStaging | Stores the most recently loaded RPG Attribute Table entry. |
Linkage Directory (BugFS)
Bugsite contains a filesystem which almost pervasively enumerates all resources within the game. Each file is exclusively referred to with it's 16-bit linkage identifier; there are no file names and only one directory. Almost all resource loads, except some game data tables, are mediated through BugFS. Graphics and tilemaps are loaded by ID and BugVM code can farjump/farcall to the start of another section by ID.
The directory's structure is that of an array of entry structures. The directory array starts at $A:$4000, and the ID indexes this array by 8 bytes. Directory entries appear to be stored in the same order as where their data is stored in ROM. The directory spans $1000 entries, across 2 banks; though it could be extended further if the referenced data were to be moved to a later bank. Approximately 40 entries at the end of the directory are zero-size files (pointing to ROM $7F:70FF) and may be unused filler entries.
Offset | Name | Type | |
---|---|---|---|
$0 | Bank | 8-bit bank index | |
$1 | Offset | 16-bit offset | Offset is relative to $4000, not a pointer |
$3 | Size | 16-bit size |
Each directory entry is 5 bytes long. Fields include a bank index, byte offset into that bank (0 means a pointer of $4000), and total size. Much like the directory itself, files are allowed and expected to span across bank boundaries. Native code that reads BugFS files will need to account for bank switching. Directory entries are stored with an 8-byte alignment (3 padding bytes).
RPG Attribute Tables
Certain tables of game attributes are not stored as BugFS files. These data tables include listings for monsters, battle moves, chips, key items, and encounters. Not much is known about these tables, except for the fact that they are universally arrays of $40 bytes and often contain a name string at offset $30. Specialized BugVM instructions exist to load a single table entry into W_Battle_AttrStaging and to print the name at offset $30.
Location | Size | Contents | |
---|---|---|---|
$4:4000, $5:4000 | $4000 | Bugs | Instructions that read from the monster table will select bank $4 for Alpha Version and bank $5 for Beta Version. Beta's table is present and unmodified in Alpha, and vice versa. |
$6:4000 | $4000 | Moves | |
$7:4000 | $A80 | Chips | After the entry at $7:4A40 none of the remaining items in this table appear to have valid name data and are thus likely to be something else. |
$7:6000 | $1240 | Key Items | After the entry at $7:7200 none of the remaining items in this table appear to have valid name data and are thus likely to be something else. |
$8:4000, $9:4000 | $4000 | Encounters | Instructions that read from the encounter table will select bank $8 for Alpha Version and bank $9 for Beta Version. Beta's table is present and unmodified in Alpha, and vice versa. |
Initial State
Execution of BugVM always starts from the beginning of linkage $0. Indirect and predicate memory is set to $0 upon game initialization.
Instruction Set
BugVM takes instructions as 8-bit opcodes which can optionally accept additional parameters. Most opcodes take arguments from the stack, rather than from the instruction stream. Native implementations for a particular opcode are referenced from the opcode table at $3E00, reproduced below:
x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x | NOP $5E9 |
ENOP $01 $5E9 |
ENOP $02 $5E9 |
ARFREE $671 |
ENOP $04 $5E9 |
ENOP $05 $5E9 |
STR $68C |
SUML $6C0 |
ANDL $6CA |
OR $6D7 |
XOR $6E2 |
AND $6ED |
CMP_EQ $6F8 |
CMP_NEQ $705 |
CMP_LT $712 |
CMP_LEQ $723 |
1x | CMP_GT $752 |
CMP_GEQ $763 |
??? $772 |
SLA $784 |
SUB $796 |
ADD $7A1 |
MOD $7A9 |
DIV $7C3 |
MUL $7ED |
PNOP $19 $80B |
PNOP $1A $80C |
PNOP $1B $80D |
PNOP $1C $80E |
INDIR $80F |
PRED $820 |
ENOP $1F $5E9 |
2x | ENOP $20 $5E9 |
ENOP $21 $5E9 |
ENOP $22 $5E9 |
ENOP $23 $5E9 |
ENOP $24 $5E9 |
ENOP $25 $5E9 |
ENOP $26 $5E9 |
ENOP $27 $5E9 |
ENOP $28 $5E9 |
ENOP $29 $5E9 |
ENOP $2A $5E9 |
ENOP $2B $5E9 |
POPALL $831 |
ENOP $2D $5E9 |
ENOP $2E $5E9 |
PNOP $2F $835 |
3x | PNOP $30 $836 |
PNOP $31 $837 |
PNOP $32 $838 |
PNOP $33 $839 |
PNOP $34 $83A |
PNOP $35 $87B |
NPREF $87C |
JMPT $887 |
JMP $8AC |
RET $8BB |
PNOP $3A $8DB |
PNOP $3B $8DC |
PNOP $3C $8DD |
IMMED $8DE |
DB $941 |
JAL $8F0 |
4x | WINWAIT $A12 |
PRINT $A84 |
||||||||||||||
5x | WINCLR $97C |
RESLD $9C6 | ||||||||||||||
6x | FARCALL $91B |
FARJMP $83B |
||||||||||||||
7x | TILELD $1BAC |
|||||||||||||||
8x | WINBRK $ADA |
|||||||||||||||
9x | PRKEY $165A | |||||||||||||||
Ax | BLKKEY $42A |
PRMOVE $1668 |
BLKMOVE $3FE |
PRMON $1622 | ||||||||||||
Bx | BLKMON $39C |
|||||||||||||||
Cx | ||||||||||||||||
Dx | ||||||||||||||||
Ex | BLKCHIP $456 |
PRCHIP $1661 |
||||||||||||||
Fx | BLKENC $3CD |
Boolean Logic & Comparison Operators
As convention for this table we treat zero as boolean TRUE and one as boolean FALSE. Other non-zero values are treated as FALSE, but boolean opcodes will not return nonstandard values.
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
SUML (SUM Logical) |
$07 | $6C0 | None | arg1, arg2 (TOP) -> bool (TOP) | Add arg1 and arg2. Push TRUE if result is zero, FALSE if non-zero. |
ANDL (AND Logical) |
$08 | $6CA | None | arg1, arg2 (TOP) -> bool (TOP) | Bitwise-AND arg1 and arg2. Push TRUE if result is zero, FALSE if non-zero. |
CMP_EQ (CoMParison EQual) |
$0C | $6F8 | None | arg1, arg2 (TOP) -> bool (TOP) | Compare arg1 and arg2. Push TRUE if both arguments are equal, FALSE otherwise. |
CMP_NEQ (CoMParison Not EQual) |
$0D | $705 | None | arg1, arg2 (TOP) -> bool (TOP) | Compare arg1 and arg2. Push TRUE if both arguments are not equal, FALSE otherwise. |
CMP_LT (CoMParison Less Than) |
$0E | $712 | None | arg1, arg2 (TOP) -> bool (TOP) | Compare arg1 and arg2. Push TRUE if arg1 is less than arg2, FALSE otherwise. |
CMP_LEQ (CoMParison Less or EQual) |
$0F | $723 | None | arg1, arg2 (TOP) -> bool (TOP) | Compare arg1 and arg2. Push TRUE if arg1 is less than or equal to arg2, FALSE otherwise. |
CMP_GT (CoMParison Greater Than) |
$10 | $752 | None | arg1, arg2 (TOP) -> bool (TOP) | Compare arg1 and arg2. Push TRUE if arg1 is greater than arg2, FALSE otherwise. |
CMP_GEQ (CoMParison Greater or EQual) |
$11 | $763 | None | arg1, arg2 (TOP) -> bool (TOP) | Compare arg1 and arg2. Push TRUE if arg1 is greater than or equal to arg2, FALSE otherwise. |
Bitwise logic
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
OR (bitwise OR) |
$09 | $6D7 | None | arg1, arg2 (TOP) -> value (TOP) | Bitwise-OR arg1 and arg2 as the return value. |
XOR (bitwise eXclusive OR) |
$0A | $6E2 | None | arg1, arg2 (TOP) -> value (TOP) | Bitwise-XOR arg1 and arg2 as the return value. |
AND (bitwise AND) |
$0B | $6ED | None | arg1, arg2 (TOP) -> value (TOP) | Bitwise-AND arg1 and arg2 as the return value. |
SLA (Shift-Left Arithmetic) |
$13 | $784 | None | bits, shift (TOP) -> value (TOP) | Shift bits left, shift times, while inserting zero bits, to produce value. |
Arithmetic
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
SUB (SUBtraction) |
$14 | $796 | None | minuend, subtrahend (TOP) -> difference (TOP) | Subtract subtrahend from minuend to produce difference. |
ADD (ADDition) |
$15 | $7A1 | None | addend1, addend2 (TOP) -> sum (TOP) | Add addend1 to addend2 to produce sum. |
MOD (MODulo) |
$16 | $7A9 | None | dividend, divisor (TOP) -> remainder (TOP) | Integer-divide dividend by divisor and return only the remainder. |
DIV (DIVide) |
$17 | $7C3 | None | dividend, divisor (TOP) -> quotient (TOP) | Integer-divide dividend by divisor and return only the quotient. |
MUL (MULtiply) |
$18 | $7ED | None | multiplicand, multiplier (TOP) -> product (TOP) | Multiply multiplicand by multiplier to produce the product. |
Memory & Stack Manipulation
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
STR (SToRe) |
$06 | $68C | None | &address, value (TOP) -> (TOP) | Store the value at the memory location referenced by &address. The exact memory operations performed depend on the target of *address.
If address is an indirect index, the memory location referenced by *address will be set to value. If address is a predicate index, the bit referenced by *address will be set or reset based on if value is zero or non-zero. It is illegal to STR into an immediate value, and doing so will cause the VM to halt operation. |
INDIR (INDIRect) |
$1D | $80F | None | immed (TOP) -> &address (TOP) | Cast the value immed into an indirect memory index &address. |
PRED (PREDicate) |
$1E | $820 | None | immed (TOP) -> &pred (TOP) | Cast the value immed into a predicate memory index &pred. |
POPALL (POP ALL) |
$2C | $831 | None | anything (TOP) -> (EMPTY) | Empty the stack. |
IMMED (IMMEDiate) |
$3D | $8DE | immed (16b) | (TOP) -> immed (TOP) | Push immed onto the data stack as an immediate value. |
DB (Declare Bytes) |
$3E | $941 | string (null-terminated) | (TOP) -> straddr (TOP) | Copy null-terminated string data from the instruction stream into the string-building arena referred to by $C424.
The address of the newly copied string data will be pushed onto the data stack as an immediate value. |
ARFREE (ARena FREE) |
$03 | $671 | None | None | Free the data in the string arena (or set up the string arena if it hasn't already been done).
This opcode should be used as soon as possible after you are done with the data in the string arena. The arena has a maximum capacity of 255 bytes, and memory is not reclaimed from use by a DB opcode until you ARFREE the entire arena. |
Execution Control
Execution Control opcodes control the address of the next instruction to execute, determined by BugVM's program counter. It consists of a bank address, base address, and offset address. The linkage directory determines what the bank and base addresses of a section are. The offset address is added to the base in order to calculate the address of the next instruction.
Caution: Parameters named "offset" for jump instructions refer to the program counter offset address, and do not imply the existence of PC-relative jumps instructions within Bugsite.
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
JMPT (JuMP if True) |
$37 | $887 | offset (16b) | bool (TOP) -> (TOP) | Jump to a new offset within the current linkage section if bool is TRUE. |
JMP (JuMP) |
$38 | $8AC | offset (16b) | None | Jump to a new offset within the current linkage section. |
RET (RETurn) |
$39 | $8BB | None | None | Return to the previously linked return address. |
JAL (Jump And Link) |
$3F | $8F0 | offset (16b) | None | Jump to a new offset within the current linkage section. Store the offset of the next instruction on the link stack. |
FARCALL | $6A | $91B | None | linkage_index (TOP) -> (TOP) | Jump to the start of a new linkage section. Store the offset of the next instruction on the stack. |
FARJMP (FAR JuMP) |
$6B | $83B | None | linkage_index (TOP) -> (TOP) | Jump to the start of a new linkage section, erasing the link stack in the process.
The previous linkage section is stored for later use. |
NOP and NOP-alikes
BugVM contains a number of opcodes which do nothing, including both a single implementation of a NOP opcode used to fill in blank spots in the table as well as individual implementations which presumably did something in the past.
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
NOP (Null OPeration), ENOP $nn (Effective Null OPeration) |
Too many to list | $5E9 | None | None | Does nothing.
This particular null-op implementation is used many times to fill space in the opcode table. |
PNOP $19 (Probable Null OPeration) |
$19 | $80B | None | None | Does nothing. |
PNOP $1A (Probable Null OPeration) |
$1A | $80C | None | None | Does nothing. |
PNOP $1B (Probable Null OPeration) |
$1B | $80D | None | None | Does nothing. |
PNOP $1C (Probable Null OPeration) |
$1C | $80E | None | None | Does nothing. |
PNOP $2F (Probable Null OPeration) |
$2F | $835 | None | None | Does nothing. |
PNOP $30 (Probable Null OPeration) |
$30 | $836 | None | None | Does nothing. |
PNOP $31 (Probable Null OPeration) |
$31 | $837 | None | None | Does nothing. |
PNOP $32 (Probable Null OPeration) |
$32 | $838 | None | None | Does nothing. |
PNOP $33 (Probable Null OPeration) |
$33 | $839 | None | None | Does nothing. |
PNOP $34 (Probable Null OPeration) |
$34 | $83A | None | None | Does nothing. |
PNOP $35 (Probable Null OPeration) |
$35 | $87B | None | None | Does nothing. |
NPREF (Null PREFix) |
$36 | $87C | opcode, ??? | None | Executes the next opcode as normal.
This would appear to be just another NOP, but it's native implementation actually loads and executes the next byte as an opcode. Hence, it works like a prefix byte that does nothing. Perhaps in a previous revision of the game, NPREF had a use. |
PNOP $3A (Probable Null OPeration) |
$3A | $8DB | None | None | Does nothing. |
PNOP $3B (Probable Null OPeration) |
$3B | $8DC | None | None | Does nothing. |
PNOP $3C (Probable Null OPeration) |
$3C | $8DB | None | None | Does nothing. |
Graphics Functions
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
TILELD (TILE LoaD) |
$72 | $1BAC | None | base_attr, base_tile, tile_y, tile_x, linkage_index (TOP) -> (TOP) | Load a precomposed tilemap from BugFS file linkage_index into the first tilemap in VRAM ($9800).
The base_tile and base_attr parameters allow the user to add a dynamic tile index and palette index offset to the tilemap. For example, if you loaded the tilemap's graphics into slot $30 on the second VRAM tile bank, with color data starting at BG palette 3, you could set base_tile to $30 and base_attr to Bank 1, Palette 3. Tile X and Y coordinates allow you to control where the tilemap data is written. These coordinates are in tile units and are relative to the first visible tile on the screen (based on the current screen scroll). |
RESLD (RESource LoaD) |
$5F | $9C6 | None | linkage_index, copy_start, copy_len, target_addr, target_bank (TOP) -> (TOP) | Load graphics resources from BugFS file linkage_index into tile memory.
copy_start determines how far into the resource to copy from, and copy_len determines how much to copy from that point. Both of these are in units of 32 bytes - exactly the size of a single tile. target_addr and target_bank determines where to place the copied resources in VRAM. They are native memory pointers, not indexed to anything. |
PRINT (PRINT) |
$4C | $A84 | None | string_addr (TOP) -> (TOP) | Print tile text onto the screen.
string_addr points to a null-terminated string in unbanked memory - usually some portion of the string arena that is written to by DB. Since that opcode returns an unbanked memory pointer on stack, one can print to the screen using just DB and PRINT opcodes. The character $7F is special-cased in this function to print the player's name at $C9F0. PRINT uses tile text in order to draw letters. A tile font must have been previously loaded in VRAM at tile location 0; it's encoded character value will be used as a tile index to draw with. |
Window Management
Windows are regions of the background layer adorned with a border, intended to be drawn over the active playfield to represent dialogue or certain kinds of menus.
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
WINCLR (WINdow CLeaR) |
$5B | $97C | None | None | Clear the active window region.
Tiles will be set to $20, which is the encoded byte for a space. In any loaded font, this tile is intended to be blank. |
WINWAIT (WINdow WAIT) |
$60 | $A12 | None | None | Wait for user input.
BugVM execution will halt until the user presses the "A" button on the system. A blinking A-button prompt will be displayed at the bottom-right of the window range. |
WINBRK (WINdow BReaK) |
$82 | $ADA | None | None | Insert a 'page break' of sorts between two full windows' worth of text.
Internally this is equivalent to a WINWAIT followed by a WINCLR. |
Attribute Table Utilities
Opcode | Encoding(s) | Native Impl. | Operand Args | Stack Args | Description |
---|---|---|---|---|---|
PRKEY (PRint KEY item) |
$9F | $165A | None | attr_index (TOP) -> (TOP) | Read the given Key Item's Attribute Table block and print it's name. |
BLKKEY (BLocK KEY item) |
$A0 | $42A | None | attr_index (TOP) -> (TOP) | Read the given Key Item's Attribute Table block into W_Battle_AttrStaging. |
PRMOVE (PRint MOVE) |
$A1 | $1668 | None | attr_index (TOP) -> (TOP) | Read the given Battle Move's Attribute Table block and print it's name. |
BLKMOVE (BLocK MOVE) |
$A2 | $3FE | None | attr_index (TOP) -> (TOP) | Read the given Battle Move's Attribute Table block into W_Battle_AttrStaging. |
PRMON (PRint MONster) |
$AF | $1622 | None | attr_index (TOP) -> (TOP) | Read the given Bug's Attribute Table block and print it's name. |
BLKMON (BLocK MONster) |
$BB | $39C | None | attr_index (TOP) -> (TOP) | Read the given Bug's Attribute Table block into W_Battle_AttrStaging.
This instruction switches between two different Attribute Tables based on the value of W_System_GameVersion. |
PRCHIP (PRint CHIP) |
$E3 | $1661 | None | attr_index (TOP) -> (TOP) | Read the given Chip's Attribute Table block and print it's name. |
BLKCHIP (BLocK CHIP) |
$E2 | $456 | None | attr_index (TOP) -> (TOP) | Read the given Chip's Attribute Table block into W_Battle_AttrStaging. |
BLKENC (BLocK ENCounter) |
$F1 | $3CD | None | attr_index (TOP) -> (TOP) | Read the given Encounter's Attribute Table block into W_Battle_AttrStaging.
There does not appear to be a PRENC instruction. This instruction switches between two different Attribute Tables based on the value of W_System_GameVersion. |
PRNAME (PRint NAME) |
$A5 | $162A | None | None | Print the name of the currently loaded Attribute Table block.
All PRint instructions that reference Attribute Table data execute this opcode. It prints data from $CF30, which is an offset into W_Battle_AttrStaging that appears to be a standard offset for this kind of data. |