Futaba
Futaba is a low-level assembler specifically targetting the Super Famicom (SFC) and Super Nintendo Entertainment System (SNES). Futaba is an open-source project with its source code hosted on GitHub.
Futaba is a single-pass assembler, prioritizing fast assembly and a smaller memory footprint. One of this project's goals is also to move away from the archaic and C-flavored syntax prevalent in other assemblers. Currently, Futaba also aims to be as low-level as possible. Higher level program flow such as while statements are left out in favor of encouraging source generators.
As a .NET application, Futaba is cross-platform. The only prerequisite is having the .NET (10.0+) runtime. The core library is separate from the command-line interface, allowing it to be cleanly imported into other projects. Futaba is also AOT-compatible, enabling direct compilation to a native executable, negating the need for the runtime entirely.
Release timeline
2026-04-06 – private, closed-source development
- 2026-09-15 – public beta; open source
- TBD – preview
- TBD – release
Futaba is currently in the public beta stage. Not every feature has been fully implemented or tested, but its source is available for review and contribution. Bugs definitely exist and no guarantees are yet made. Once the core library has reached a stable state, we will enter a year-long preview stage during which syntax and naming may be adjusted, and features might be removed or modified. After the preview stage all of these aforementioned components will be locked and permanent. Until the full release, guarantees about determinism across release should not be considered valid. Expect potentially breaking changes.
Currently untested/broken/unimplemented features:
- MMC banking is not implemented (avoid the SA-1 assembler for now)
- ExLorom and ExHirom not fully tested
- Not all coprocessors are properly implemented
- SuperFX not fully tested
- Docks/protected segments not really tested
- CLI does not interact with breakpoints
- CLI symbols output not yet validated
Quickstart
All versions of Futaba require the .NET (10.0+) runtime, which can be downloaded from Microsoft.com.
Windows
- Download the file futaba-win-x64.zip from the latest release of Futaba.
- Extract the contents of the archive to a permanent home for the application.
- Navigate to that folder in File Explorer.
- Click the address bar and type cmd. This will open the command prompt.
- Type futaba register in the command prompt then press ENTER.
Linux
- Download the file futaba-linux-x64.zip from the latest release of Futaba.
- Extract the contents of the archive to a permanent home for the application.
- Open the command prompt.
- cd to that folder.
- Type futaba register in the command prompt then press ENTER.
Features and Syntax
Numbers
Number literals can be written in decimal, hexadecimal, or binary.
print 16 ; no prefix - decimal
print $10 ; $ prefix - hexadecimal
print %10000 ; % prefix - binary
Number literals can be split with underscores (_) as digit separators to improve readability.
print 2_000_000
print $10_BEBE
print %1111_0000
NOTE: To avoid ambiguity with sublabels, numbers of magnitude less than 1 must be written with a leading 0; e.g., 0.9 not .9.
Strings
Strings are an important data type, but they may be intepreted in multiple ways. In normal expressions, a string either behaves as a string object or, when a numeric value is expected, it will behave as either 0 (empty string) or the encoding of its first character.
A single character can be inserted by enclosing it in apostrophes (').
Note: Futaba supports source files encoded in ASCII, UTF-8, and UTF-16, but does not support codepoints beyond the Basic Multilingual Plane (U+0000–U+FFFF) outside of comments. Surrogates are treated as separate characters.
Escape sequences
In some cases, a character may need to be escaped in a string or character:
- \\ – backslash
- \" – quotation mark
- \! – exclamation mark
- \' – apostrophe
- \{, \} – braces
Encoders
The default encoding is ASCII, which maps the unprintable characters and characters outside of the ASCII code block to space (U+0020). One other built-in encoder exists for unicode, which maps characters directly to their code point. Different encodings are selected with the encoder directive.
encoder ascii
encoder unicode
Additional encoders can be declared by using the encoder directive followed by an identifier and a file path. User-defined encoders can be selected the same way as the built-in encoders. The identifier must be a unique name among encoders and cannot be the reserved names ascii or unicode.
encoder «IDENTIFIER» «"FILE"»
The format of an encoding file is one definition per line, with the first character of that line containing the character to encode followed by an equals sign (=) then a prefix-less hexadecimal value. Any character not defined by the encoder will be mapped to 0.
A = 20
B = 21
C = 22
D = 23
E = 24
F = 25
Z = 3A
For contiguous mappings, use an ellipsis (...) followed by the last character in the range. Optionally, put a character in front to specify the beginning of the range. The example below encodes the uppercase letters beginning at $20 followed by the lowercase letters, all consecutively:
A = 20
...Z
a...z
If a character is by itself on a line, it will be inserted as the next code point. The example below encodes the first 6 uppercase letters as $20 through $25:
A = 20
B
C
D
E
F
String functions
When used in a data statement, functions can be invoked on a string by appending a triple-slash (///). Multiple function can be provided for a single string by separating each function with a single-slash (/).
The enc function selects an encoder for a string without changing the default encoder.
encoder ascii
db "Hello world!" ; uses the ascii encoder
db "I am encoded!"///enc:unicode
; uses the unicode encoder
db "I too am that." ; uses the ascii encoder
The upper and lower functions transform a string to upper- and lowercase, respectively.
db "hEllO WoRlD!"///upper
; "HELLO WORLD!"
db "HelLo WOrLd!"///lower
; "hello world!"
The pad function pads a string to a given number of characters with a specified alignment. By default, the string will be padded with spaces. Enclose a character in apostrophes to specify it as the padding character.
Padding only lengthens strings that are too short. To force an exact length instead of a minimum, use the len function with the same arguments.
pad modes:
- l – pad and left align
- r – pad and right align
- cl – pad and center-left align (alias: c)
- cr – pad and center-right align
db "Hello world!"///pad:l20
; "Hello world! "
db "Hello world!"///pad:l20'!'
; "Hello world!!!!!!!!!"
db "Hello world!"///pad:r20'!'
; "!!!!!!!!Hello world!"
db "Hello world!"///pad:c20
; " Hello world "
db "Hello world!"///pad:cr20
; " Hello world "
db "Hello world!"///pad:cl20
; " Hello world "
db "Hello world!"///pad:r5
; "Hello world!"
db "Hello world!"///len:l20
; "Hello world! "
db "Hello world!"///len:l5
; "Hello"
db "Hello world!"///len:r5
; "Hello"
Commands
Each line of source code ends when it reaches the end of the line, a comment, or a single-line token.
Comments are denoted with a semicolon (;) and continue to the end of the line.
Multiple statements can be written on a single line by using a colon surrounded by white space on both sides ( : ).
print 1
print 2 ; This is a comment.
print 1 : print 2
print 1 :print 2 ; error! missing space on the right
Languages
Three languages are supported, selected by using the lang or arch directive:
| Language |
Names |
Mnemonics |
| WDC 65c816 |
wdc 65816 65c816 wdc65816 wdc65c816 |
ADC AND ASL BCC BCS BEQ BIT BMI BNE BPL BRA BRK BRL BVC BVS CLC CLD CLI CLV CMP COP CPX CPY DEC DEX DEY EOR INC INX INY JML JMP JSL JSR LDA LDX LDY LSR MVN MVP NOP ORA PEA PEI PER PHA PHB PHD PHK PHP PHX PHY PLA PLB PLD PLP PLX PLY REP ROL ROR RTI RTL RTS SBC SEC SED SEI SEP STA STP STX STY STZ TAX TAY TCD TCS TDC TRB TSB TSC TSX TXA TXS TXY TYA TYX WAI WDM XBA XCE |
| SPC700 |
spc spc700 |
ADC ADDW AND AND0 AND1 AND2 AND3 AND4 AND5 AND6 AND7 ASL BBC0 BBC1 BBC2 BBC3 BBC4 BBC5 BBC6 BBC7 BBS0 BBS1 BBS2 BBS3 BBS4 BBS5 BBS6 BBS7 BCC BCS BEQ BMI BNE BPL BRA BRK BVC BVS CALL CBNE CLR0 CLR1 CLR2 CLR3 CLR4 CLR5 CLR6 CLR7 CLRC CLRP CLRV CMP CMPW DAA DAS DBNZ DEC DECW DI DIV EI EOR EOR0 EOR1 EOR2 EOR3 EOR4 EOR5 EOR6 EOR7 INC INCW JMP LSR MOV MOV0 MOV1 MOV2 MOV3 MOV4 MOV5 MOV6 MOV7 MOVW MUL NOP NOT0 NOT1 NOT2 NOT3 NOT4 NOT5 NOT6 NOT7 NOTC OR OR0 OR1 OR2 OR3 OR4 OR5 OR6 OR7 PCALL POP PUSH RET RETI ROL ROR SBC SET0 SET1 SET2 SET3 SET4 SET5 SET6 SET7 SETC SETP SLEEP STOP SUBW TCALL TCLR TSET XCN |
| Super FX |
sfx superfx |
ADC ADD ALT1 ALT2 ALT3 AND ASR BCC BCS BEQ BGE BIC BLT BMI BNE BPL BRA BVC BVS CACHE CMODE CMP COLOR DEC DIV2 FMULT FROM GETB GETBH GETBL GETBS GETC HIB IBT INC IWT JMP LDB LDW LEA LINK LJMP LM LMS LMULT LOB LOOP LSR MERGE MOVE MOVEB MOVES MOVEW MULT NOP NOT OR PLOT RAMB ROL ROMB ROR RPIX SBC SBK SEX SM SMS STB STOP STW SUB SWAP TO UMULT WITH XOR |
Differences from official documentation
| Official syntax |
Futaba syntax |
| mov1 C, arg.n |
movn C, arg |
| mov1 arg.n, C |
movn arg, C |
| and1 C, arg.n |
andn C, arg |
| and1 C, /arg.n |
andn C, /arg |
| or1 C, arg.n |
orn C, arg |
| or1 C, /arg.n |
orn C, /arg |
| eor1 C, arg.n |
eorn C, arg |
| not1 arg.n |
notn arg |
| set1 arg.n |
setn arg |
| clr1 arg.n |
clrn arg |
| tclr1 arg |
tclr arg |
| tset1 arg |
tset arg |
| daa A |
daa |
| das A |
das |
| xcn A |
xcn |
| mov A, (X)+ |
mov A, (X+) |
| mov (X)+, A |
mov (X+), A |
For the SPC700, the 1-bit instructions use mnemonics that address the bit directly rather than at the end of the operand.
Using ! for absolute/16-bit addressing is not supported, as it conflicts with the syntax for variables. Use .w instead.
Miscellaneous
When it is safe to do so, dummy operands will be assembled to help keep code aligned even with invalid syntax. For example, JSR #1 will assemble as 20 00 00. An error will still be emitted, because this is not a valid addressing mode; however, the given mnemonic can only ever assemble with a 16-bit operand, so a placeholder instruction is added to help maintain the address of code that follows.
Program counter
Futaba tracks three different positions as it assembles:
The program counter or pc of code or data is the physical location as it appears on the SNES system bus.
The provenance or site of code or data is the location in memory that it should be interpreted as being in. For most program code, provenance and program counter are identical, but for content that is accessed elsewhere, such as sound engine code on the APU, provenance will differ.
The binary offset of code or data is the physical location of it in the final .sfc file. This can be vastly different from the program counter or provenance, depending on the mapper mode.
Controlling the program counter
The org directive moves the program counter (and thus assembly) to a specific address. This address is affected by the mapping mode of the ROM.
The site directive stages assembly elsewhere without changing the actual program counter. This is useful for writing source code that is relocated, such as to WRAM or ARAM. To reset provenance back to being the same as the program counter, use SPLAT (*) as its argument.
The org and site directives both require an expression to be immediately resolved. The default symbol context of an org expression is ROM address. Likewise, the default symbol context of site is provenance.
The current program counter can be saved and recovered using the pushpc and pullpc directives. The current provenance can be saved and recovered using the pushsite and pullsite directives.
Assembly can be advanced an arbitrary number of bytes without writing using the skip directive.
The align and arrange directives can be used to skip ahead the necessary number of bytes for the program counter and provenance, respectively, to be an exact multiple of the value supplied.
The skipto directive advances forward until the given provenance is reached. If this results in a negative change, an error will be emitted.
The warnpc and warnsite directive can be used to emit errors if the current program counter or provenance, respectively, is beyond a given address. Warning and relocating the program counter can be performed together with the safeorg directive.
The rebank directive can be used to change only the bank of the current program counter. This directive includes special syntax to control paging: prefixing the expression with the > token will force bit 15 of the address to be set, while the < token will force it to be reset.
site $998123 ; provenance: $99:8123
rebank $BE ; provenance: $BE:8123
rebank <$7E ; provenance: $7E:0123
rebank >$7E ; provenance: $7E:8123
The bankguard directive can be used to change when errors are emitted if certain boundaries are crossed by the program counter:
- bankguard off – disables all errors
- bankguard half – error on crossing from xx:FFFF to xx+1:0000 and from xx:7FFF to xx:8000
- bankguard full – error on crossing from xx:FFFF to xx+1:0000
Data statements
Four directives are supplied for adding arbitrary data directly into a program:
- db (8-bit)
- dw (16-bit)
- dl (24-bit)
- dd (32-bit)
These directives take a comma separated list of expressions, variables, and/or strings. Expressions will insert the value they resolve to. Strings will be treated as an array of characters and insert each encoded character separately. All values will be padded or truncated to the element size.
Individual elements can be skipped with a SPLAT (*). This is useful for patching larger tables with partial edits.
Symbols
A symbol is a read-only token that represents an integer. Symbols can be used to represent memory addresses or program constants.
All symbols must have a unique name; however, special syntax allows symbols with similar names to be created as part of larger hierarchies.
User-created symbol names must begin with a letter or underscore and may be followed by zero or more letters, numbers, and/or underscores.
Dragon: ; valid!
Wyvern: ; valid!
Dragon2: ; valid!
_Dragon: ; valid!
3LeggedWyvern: ; invalid!
Three-LeggedWyvern: ; invalid!
A: ; invalid! see below
A handful of keywords are reserved and cannot be used as a symbol name:
- A
- X
- Y
- a
- x
- y
- C
- c
- SP
- Sp
- sp
- sP
- R0
- R1
- R2
- R3
- R4
- R5
- R6
- R7
- R8
- R9
- R10
- R11
- R12
- R13
- R14
- R15
- r0
- r1
- r2
- r3
- r4
- r5
- r6
- r7
- r8
- r9
- r10
- r11
- r12
- r13
- r14
- r15
Labels
Labels are symbols declared inline to define the location of code or data. Labels are declared by affixing a colon (:) to an identifier with no space in between.
Sublabels are declared by prefixing an alphanumeric name with a dot (.). Sublabels can be accessed within their scope by dot-prefix-name alone. Sublabels can also be accessed explicitly through their hierarchy. Nested sublabels can be declared by prepending additional dots, up to 20 levels deep.
Dragon:
.main ; Dragon.main
LDA.b #$04
..exit ; Dragon.main.exit
BRA .draw ; becomes Dragon.draw
.draw ; Dragon.draw
STA.b $00
..exit ; Dragon.draw.exit
RTS
Wyrm:
LDA.b #$05
BRA Dragon.draw
Dog:
JMP Dragon.draw.exit
..exit ; invalid! no single-dot sublabel exists to be its parent
Each declaration of a top-level label creates a new hierarchy for sublabels. An extrinsic label that exists outside any hierarchy can be declared by prefixing a label with the pound (#) token.
Dragon:
.draw
#Wyrm:
BRA .draw ; valid, uses Dragon.draw
Dog:
BRA .draw ; invalid!
Assignments
A basic symbol can be declared and assigned by placing an equals sign (=) followed by an expression after an identifier.
MAXHP = 128
The assignment expression may include other symbols, but only if they have already been declared and assigned.
DRAGONHP = MAXHP ; invalid! MAXHP has not been declared yet
MAXHP = 128
WYVERNHP = MAXHP ; valid
Relative labels
Relative labels can be created by stringing together any number of repeating + or - to create forward- and backward-relative labels, respectively. These labels can then be used in other expressions to find the nearest matching label in the respective direction.
--
; let's call this location A
BCS --
; branches to A
BCC --
; branches to A
--
; let's call this location B
BCS ++
; branches to C
BCC --
; branches to B
++
; let's call this location C
To be used in any larger expression, relative labels must be enclosed in parentheses.
LDA.l ++++
; valid
LDA.l (++++)+2
; valid
JMP.w (----)+2
; valid
JMP.w ----+2
; invalid! this is interpreted as 5 separate unary operators
Pools
Pools are special groups declared with the pool directive followed by one or more top-level label names. Pools can be used to declare sublabels under a hierarchy before that hierarchy exists. Pools can also be used to create sublabels that belong to multiple distinct hierarchies. Pools are closed using the endpool directive.
pool Dragon Wyvern
.health ; Creates both Dragon.health and Wyvern.health
.attack ; Creates both Dragon.attack and Wyvern.attack
endpool
Dragon:
LDA.w .health
LDX.w .attack
Wyvern:
LDA.w .health
LDX.w .attack
New top-level hierarchies cannot be created inside pools.
Buoys
Buoys are labels used to reference relative offsets and are declared by prefixing a sublabel declaration with one or more carets (^). Each caret denotes a reference to an ancestor one level earlier in the hierarchy; thus, the maximum number of carets on a given sublabel is the same as the number of dots. The default value of a buoy is the distance from it to its parent.
Label:
.sublabel
db 1
^.buoy1 ; relative offset is 1 (distance from "Label")
db 1
^..buoy1a ; relative offset is 1 (distance from ".buoy1")
^^..buoy1b ; relative offset is 2 (distance from "Label")
^^^..buoy1c ; invalid declaration
Tip: Use ^.size at the end of a block to create an accessible size symbol under any circumstances.
Docks
Docks are reusable labels representing a growing region that can be optionally protected. Declaring a dock automatically enters it as well. To unmount a dock, use the undock directive.
dock «IDENTIFIER»
dock «IDENTIFIER» :: «SIZE»
Attempting to add code or data to a protected dock under normal circumstances will trigger an error. Content may be added to a docked region in one of two ways: using the org directive with the dock's name as its only operand.
dock Sprites :: $2000
; 8kb protected; dock position is 0
...
org Sprites
; valid! the region is entered at Sprites+0
Dragon:
JSR DragonStuff
org somewhereelse
org Sprites
; valid! the region is entered at Sprites+3
JSR MoreDragonStuff
org Sprites
; valid! the region is entered at Sprites+6
org Sprites+2
; invalid! the region is not entered properly
Pilings
The piling directive can be used to create an anchor point for a self-incrementing data region.
piling «IDENTIFIER»
piling «IDENTIFIER» :: «CAPACITY»
Pilings can then be added to by using the pile directive to assemble a single data statement by placing an allocator token (::) followed by the statement.
piling Health :: $20
; 32 bytes capacity; piling position is 0
...
Dragon:
pile Health ::
db 100 ; writes 1 byte to piling; position becomes 1
JSR DragonStuff
JSR MoreDragonStuff
RTL
Wyvern:
pile Health ::
db 75; writes 1 byte to piling; position becomes 2
JSR DragonStuff
JSR WyvernStuff
RTL
Plopping
The plop directive behaves similarly to pile with the difference that its first operand is an expression for a SNES address.
plop «ADDRESS» :: «DATA STATEMENT»
Plopping allows completely arbitrary data writes with a simplified syntax that is useful for single data entries.
Dragon:
plop Health+0 ::
db 100
Wyvern:
plop Health+1 ::
db 75
Note: The plop and pile directives do not affect the position being assembled. For example, the internal variable !:pc will return the current program counter and not the position of the data. This is intentional behavior that facilitates the creation of vector tables.
Allocations
The allocate directive can be used to create blocks of allocated address space using the following syntaxes:
allocate «ADDRESS»
allocate «ADDRESS» :: «SIZE»
Inside an allocation block, symbols may be declared in 3 ways:
- «IDENTIFIER»: – creates a label at the current allocation pointer
- «IDENTIFIER» :: «SIZE» – creates a label at the current allocation pointer with the specified size and advances that amount. Labels declared this way also include a size accessible via the :size property.
- «IDENTIFIER» = «EXPRESSION» – assigns a symbol with the standard rules
To close the current allocation block, use the endallocate directive.
Allocation blocks can be chained by reusing the allocate directive. To continue a block where the previous block ends, use a SPLAT (*) for the address operand. This address is remembered across invocations.
allocate $7E0000 :: $0100 ; block begins at $7E0000 256 bytes
allocate * :: $0100 ; block begins at $7E0100
allocate * ; block begins at $7E0200
SpriteHP :: 16
allocate * ; block begins at $7E0210
Allocation blocks are more restricted than code blocks, only allowing the following keywords:
- allocate
- endallocate
- print
- printf
- warn
- error
- macro
- endmacro
- function
- if
- else
- endif
- encoder
- skip
Allocation blocks use a separate program counter from code. All allocated symbols return the same value for their provenance, program counter, and binary offset.
Post-label white space
Inline label declarations that are not created with keywords automatically begin a new command after their declaration; however, they must be followed by white space, a line break, a comment, or an end of file.
LabelA:
; valid! followed by a newline
.sublabel ; valid! ditto
LabelB:
;comment ; valid! followed by a comment
.sublabel;comment ; valid! ditto
LabelC:
NOP ; valid! followed by a space
.sublabel NOP; valid! ditto
LabelD: :
NOP ; valid!
.sublabel :
NOP ; valid!
LabelE:NOP
; invalid! followed by normal text
.sublabel!VAR = 1
; invalid! ditto
.sublabel: ; invalid! colons don't belong on sublabels
++ --
; two on the same line? perfectly valid!
Symbol properties and context
All symbols represent at least 3 values: a binary offset, a ROM address, and a provenance.
By default, labels and sublabels return their provenance when used in an expression; however, certain directives force a different context to be used. For example, the org directive evaluates expressions with the "ROM address" context, causing all symbols to default to the ROM address they hold.
The expression context can be overridden on individual symbols by appending a property accessor (:) and a property name. No spaces should separate the symbol name, specifier token, and property name.
print Dragon ; provenance
print Dragon:offset ; binary offset
Expressions
Futaba supports expressions for nearly all operands and arguments. Expressions use standard programmer syntax for mathematical operations with C-style function calls, and several assembly-specific operators.
Expressions follow C#'s operator precedence with custom operators given the precedence that makes the most sense under those rules.
The value of all expressions and subexpressions are stored as a decimal type value. This offers higher precision than double with negligible performance impact for Futaba's usecase.
| Operator precedence |
| (a) |
| func(a) |
| a? |
| -a +a <a >a ^a <&a >&a ^&a |
| a*b a/b a//b a%b |
| a+b a-b |
| a<<b a>>b a>>>b |
| a&b |
| a^b |
| a|b a][b |
| a&&b |
| a||b |
| a>b a<b a>=b a<=b |
| a==b a!=b |
| a??b |
White space
Any number of space or tab characters may appear between most elements in an expression; however, inidivual elements cannot have white space within them.
The following invocations are considered a single element and thus cannot have space between their components:
- func( – opening parenthesis must immediately follow function names
- symbol:property – no white space before or after colon when acessing symbol properties
Truthiness
Any value or expression that evaluates to exactly 0 is considered false. All other results are considered true.
The logical AND (&&) and logical OR (||) operators are not short-circuiting. Both operands will always be evaluated (unless the left operand fails to resolve, in which case the failure will immediately propagate upwards).
Null coalescence
The null coalescence operators can be used to provide fallbacks for invalid subexpressions in addition to undeclared symbols and variables.
print (1 / 0) ; error
print (1 / 0)? ; evaluates to 0
print (1 / 0) ?? 5 ; evaluates to 5
Bitwise restrictions
The bitwise operators only function on integral types. The fractional part of any operand will be automatically discarded.
print 1 & 3 ; evaluates to 1
print 1 & 3.14 ; evaluates to 1
Variables
Variables in Futaba are dynamically typed and can be either numbers or strings. Variables are declared with the following syntax. A variable's identifier can only contain letters, numbers, and underscores; however, unlike symbols, there are no restrictions on the first character.
!«IDENTIFIER» = «EXPRESSION»
!«IDENTIFIER» = "«STRING»"
A variable's type is dictated by the operand of its assignment. String variables must enclose their contents in quotation marks ("). All other operands will be treated as mathematical expressions and must resolve to some value. A variable can be reassigned at any point, with subsequent dereferences using the new value.
!VAR1 = 3 ; value type
!VAR2 = "3" ; string type
!VAR3 = !VAR2 ; !VAR3 is a string type
!VAR4 = !VAR3 + 3 ; !VAR3 is treated as a string inside an expression
Variables inside strings are automatically interpolated. To prevent interpolation, escape the variable with a backslash (\).
!VAR1 = 3
print !VAR1 ; prints 3
print "!VAR1" ; prints 3
print "\!VAR1" ; prints !VAR1
The ?= assignment operator can be used to assign a variable only when it does not yet exist.
!VAR1 = 3
print !VAR1 ; prints 3
!VAR1 ?= 15
print !VAR1 ; prints 3
The following compound assignments are available for numeric variables: +=, -=, *=, /=. String variables only support the += compound assignment, which appends the operand to the end of the string. No other string concatenation operator is supported. To concatenate string variables to a new variable, use string interpolation; e.g.: !C="!A!B".
Note: Using the += operator on a string variable when the operand is an expression will interpret the expression as a naked string without parsing it. Conversely, using the += operator on a numeric variable with a string will first cast the value to a string then append it.
Internal variables
A handful of internal variables are provided to expose properties of the assembler or useful constants. These may be accessed by including a colon (:) before the variable's name. Internal variables are read-only.
Functions
Functions are declared by using the function keyword with the following syntax:
function «IDENTIFIER»( [PARAMETERS, ...] ) => «EXPRESSION»
Parameters in the function body are dereferenced by enclosing their names with curly braces ({name}).
function SquareHalf(a, b) => ({a}*{a} + {b}*{b}) / 2
print SquareHalf(10, 8) ; calculates (10*10 + 8*8) / 2
On declaration, functions create a syntax tree in place and capture the current state of any variables.
!VAR = 3
function GetVar() => !VAR ; !VAR is captured in its current state
print GetVar() ; prints 3
!VAR = 5
print GetVar() ; prints 3
Built-in functions
A number of built-in functions are provided. These functions' names are case-sensitive.
Note: Future releases may potentially add more functions; however, these functions will always have lowercase identifiers.
Code/data management directives
Macros
Macros provide robust, parameterized text substitution in source code. Macros are declared with the macro directive followed by a unique alphanumeric identifier then zero or more parameters enclosed in parentheses. After the macro header is any number of valid assembly source lines, with the macro body terminated by the endmacro directive.
macro «IDENTIFIER»( [PARAMETERS, ...] )
«...»
endmacro
Parameters in the macro body are dereferenced by enclosing their names with curly braces ({name}). The special variable {?} can be used to insert an invocation-unique string which may be used for macro-scoped labels or other similar purposes.
macro Sequence(
a)
db {a},
{a}+3,
{a}+6
endmacro
%Sequence(2)
; assembles db 2, 5, 8
macro MakeLabel()
{?}Label:
db {?}Label
endmacro
%MakeLabel()
%MakeLabel()
; no conflict
The arguments to a macro call are raw strings that are inserted directly into the macro body. To prevent trimming of whitespace or early comma detection, wrap indivudual arguments in quotes.
%SomeMacro( Stuff ) ; arg0 is Stuff
%SomeMacro( "Stuff" ) ; arg0 is Stuff
%SomeMacro( ""Stuff"" ) ; arg0 is "Stuff"
%SomeMacro(" Stuff ") ; arg0 is Stuff
%SomeMacro( "" Stuff "" ) ; arg0 is " Stuff "
Weirdness: Due to how macro variables are inserted, currently only the closing brace can be escaped to prevent a parameter dereference from being parsed.
incsrc
The incsrc directive can be used to assemble source from other files. This directive takes a comma-separated list of strings containing file paths to other source code.
incsrc «"FILE"»
incsrc «"FILE1"», «"FILE2"»
Files may be nested and/or chained up to 512 levels deep. For the purposes of this limit, files listed together in a single directive are considered nested inside each other. Macros also count towards this limit, as they are replaced with ephemeral source objects during assembly.
incbin
The incbin directive can be used to insert raw data by copying it from another file. This directive takes a comma-separated list of strings containing file paths to data. There is no hard limit to the number of files that can be listed with a single directive.
incbin «"FILE"»
incbin «"FILE1"», «"FILE2"»
Slices of data can be inserted by specifying a range after the file path string, enclosed in brackets. This range is an inclusive start to an exclusive end, with both operands taking an expression.
incbin «"FILE"»[«START»::«END»]
incbin «"FILE"»[::«END»] ; start is implicitly 0
incbin «"FILE"»[«START»::] ; end is implicitly the size of the file
A label can be attached to a data block by including a bracket-enclosed identifier before the file list:
incbin [«IDENTIFIER»] «"FILE"»
This label will include a size property that can be accessed using the :size property. If multiple files are assembled together in a list, this size will be the length of all data inserted by the directive.
incbin [HealthData] "health_data.bin"
printf "Health data contains {0} bytes!", HealthData:size
fill
The fill directive can be used to fill a span of data with a repeated value using the following syntax:
fill «TYPE» «MODE» «AMOUNT» :: «VALUE»
Types:
- byte – 8-bit values
- word – 16-bit values
- long – 24-bit values
- double – 32-bit values
- random – 8-bit values
Modes:
- size «VALUE» – fills the exact size given in bytes. If a larger type is used, this may result in only part of the final value being written.
- count «VALUE» – fills with this many copies of the supplied value.
- until «ADDRESS» – fills with as many bytes as are needed to reach the given address (dictated by provenance).
- align «ADDRESS» – fills with as many bytes as are needed to reach the given alignment (dictated by program counter).
- arrange «ADDRESS» – fills with as many bytes as are needed to reach the given alignment (dictated by provenance).
The fill value is an expression or, in the case of random, an optional seed. If a value is omitted for random fills, the block will consume values from the assembler's shared instance RNG; otherwise, the operand will be treated as a string and its contents will be hashed to seed a new sequence.
Note that the fill value is only calculated once—when it is parsed in this command. This is still the case even when that value must be resolved later.
fill byte count 200 :: $00 ; 200 bytes
fill word size 200 :: $1234 ; 200 bytes
fill word count 200 :: $1234 ; 400 bytes
Conditional logic
Simple conditional logic can be checked using the if, else, and endif directives. If-statements evaluate an expression for truthiness.
Output and Diagnostics
Printing
Four directives are provided for printing to a text stream. The print directive prints a comma-separated list of tokens, strings, or expressions on a single line. The warn and error directives behave similarly, but they also emit a warning or error, respectively.
The printf directive can be used to print a formatted string with the first argument being the format specifier and the remaining arguments being the objects passed to the formatter. This directive uses the .NET string format syntax.
Futaba includes a special format specifier for numbers: A, which can be used to print a number as a 6-digit hexadecimal string in the form $XX:XXXX. Futaba also reroutes the X and B specifiers on decimal values by truncating the fractional part and converting the value to a 64-bit integer.
All print commands include special shorthands to facilitate printing. These are not reserved words, but they will take priority over symbols with the same name. To work around this, prefix those symbols with a plus sign (+), which is effectively a null operation.
Breakpoints
Breakpoints can be created for a program by inserting a tick mark (`) optionally followed by other arguments:
`[TRIGGERS][+INSERT]
Any combination of flags can be combined to declare what actions the breakpoint should break on. If none are specified, then the default triggers will be used.
- r – trigger this breakpoint on read
- w – trigger this breakpoint on write
- x – trigger this breakpoint on execute
Zero or one items may be specified as an inserted instruction. The insert is delimited with a plus sign (+); used by itself, this is treated as "no insert". If not specified at all, then the default instruction will be inserted.
- + – insert nothing with this breakpoint
- +n – insert NOP with this breakpoint
- +b – insert BRK with this breakpoint
- +w – insert WDM with this breakpoint
- +c – insert COP with this breakpoint
The same arguments above can be used with the breakpoints directive to declare the default behavior of naked breakpoints. The default behavior is breakpoints rwx+.
` ; break on Read/Write/Execute, inserts nothing
` ; break on Read/Write/Execute, inserts nothing
breakpoints rw+n ; default breakpoint triggers are Read/Write; inserts NOP
` ; break on Read/Write, inserts NOP
`x ; break on Execute, inserts NOP
`+b ; break on Read/Write, inserts BRK
`+ ; break on Read/Write, inserts nothing
Two special keywords exist for the breakpoints directive:
- off – disables breakpoints until the next directive
- kill – disables breakpoints for the rest of assembly; future breakpoints directives are completely ignored
Tip: Use breakpoints kill at the beginning of your entry file to cleanly produce release builds.
Special markup
Address markers
Address markers are beginning-of-the-line annotations that identify where in memory code or data is expected to assemble. These are primarily designed for disassemblies but are not restricted from use in projects of other types.
|AAAAAA| ; program counter
|AAAAAA:PPPP| ; program counter and provenance
Address markers behave as their own command; a single-line token is not required to put content on the same line.
In addition to providing useful annotation for readers, address markers also emit warnings when they are incorrect.
org $008000
|10BEBE| db 0
; warning! this annotation is wrong
site $8000
|10BEBE| db 0
; warning! provenance is missing
site *
|10BEBE:9000| db 0
; warning! provenance is not necessary
Syntactic sugar
Processor flags
Using square brackets, the processor flags can be referred to by initial in the REP and SEP instructions.
REP #[MXC] ; assembles as REP #$31
brop
Branch instructions can be assembled as brops (BRanch OPcodes) by using SPLAT (*) as their operand. This allows for simpler patches where the type of branch instruction should be changed but the location it branches to should not.
org $01D4DB
BCS * ; assembles as B0 and skips the next byte
Branch literals
Branch instructions can be assembled with literal values for their operands by using the # token. This may have some usecases, but generally, branch operands should be left to the assembler.
No-op jumps
A no-op jump can be assembled by supplying & as the operand to branch and jump instructions. These will assemble with the necessary value to move to the next instruction. This is useful for blanking out code without inefficient NOP usage or for jumping to fastrom after an interrupt.
NMI:
JML & ; jumps to the next instruction
REP #$30
BCS & ; assembles as B0 00
BTYS
The Branch To YourSelf—invoked with a single less-than (<)—can be used to point a branch instruction to itself. This is primarily intended for use with the complex branching instructions in the SPC700 architecture, but it is available for any branching instruction in any language.
mov A,
#$FC
cbne $F4,
< ; assembles as 2E F4 FE
Repeat pseudo-instructions
The following instructions can be repeated an arbitrary number of times by using the # token before an expression. This expression must be immediately resolvable and evaluate to a positive number:
- ASL, LSR, ROL, ROR
- INC, INX, INY, DEC, DEX, DEY
- NOP, XBA
Command line interface
Manifest files
The recommended method of building projects is with a .futaba manifest file. These manifests dictate and control complex settings in a human-readable format, and, after registering the application, enable double-click assembly for easy building.
Comments may be added to manifest files using a semicolon (;); however, they may only appear on their own lines.
Options
Below is a table of options available for manifest files. Keys marked with an asterisk (*) are required.
| Key |
Description |
| *entry
|
The main entry point where assembly begins |
| type
|
The type of project being assembled
Accepted values: new, homebrew, patch, hack, disassembly, other |
| output
|
Relative path to the desired output file |
| addsymbols
|
Preload symbols into the symbols table for use during assembly
Accepted values:
| ppu |
PPU registers on page $21 |
| cpu |
S-CPU registers on page $42 |
| joypad |
Joypad registers on page $40 |
| dma |
DMA and HDMA registers on page $43 |
| spc |
SPC registers on page $00 in ARAM |
| dsp |
Sound DSP registers |
| sa1 |
SA-1 registers on pages $22 and $23 |
| sfx |
GSU registers on page $30 |
| standard |
ppu, cpu, joypad, dma (+aliases), spc, dsp |
| snes |
ppu, cpu, joypad, dma |
| apu |
spc, dsp |
|
| nullfill
|
Pre-initialize the output buffer before assembly
Accepted values:
| zero |
(default) filled with 0s |
| byte, «val», «...» |
filled the given pattern of bytes |
| string, «text» |
UTF-8-encoded text |
| random |
random data |
| random, «string» |
deterministic random data, using a string for seeding |
This option only initializes the buffer before assembly.
If the file grows in size, the expanded region will be initialized to all 0.
|
| randomseed
|
Seeds the assembler for deterministic random numbers using the given string |
| base
|
Relative path to a file to use as a base binary |
| *mapmode
|
Mapper mode used by the program
Accepted values: lorom, hirom, exlorom, exhirom, sa1, none |
| autofill
|
Automatically populates the ROM header using the data supplied in this block.
Accepted values: true, false |
| checksum
|
Calculate and insert a checksum at the end of assembly. This option has lower priority than autofill. |
| fastrom
|
Indicates whether this program utilizes FastROM
Accepted values: true, false |
| title
|
ASCII title inserted into the header The title will be trimmed of leading white space and padded to 21 characters. |
| romsize
|
Initial program size in bytes
Accepted values: 32kb, 64kb, 128kb, 256kb, 512kb, 1mb, 1.5mb, 2mb, 3mb, 4mb, 6mb, 8mb This value may be limited by the mapper mode chosen. |
| ramsize
|
Amount of on-board RAM in bytes
Accepted values: none, 2kb, 4kb, 8kb, 16kb, 32kb, 64kb, 128kb, 256kb |
| version
|
ROM version (1 byte value) |
| region
|
Target distribution region/language
Accepted values: japan, north america, europe, france, dutch, spain, germany, italy, china, korea, quebec, brazil, australia, scandinavia, common |
| extended
|
Indicates this program uses the extended header
Accepted values: true, false |
| coprocessor
|
What coprocessor, if any is present on board
Accepted values: none, sa1, sfx, dsp, obc1, sdd1, srtc, custom, other |
| gamecode
|
ASCII 4-character game code |
| makercode
|
ASCII 2-character game code |
| symbols
|
Output format for symbols found during assembly
Accepted values: mlb, wla |
| stdout
|
Relative path to text file for print directives. If unspecified, everything is written to the console. |
| errorout
|
Relative path to text file for warnings and errors. If unspecified, everything is written to the console. |
| overflow
|
Action to take if assembly occurs outside the buffer.
Accepted values: error, grow, expand |
| tokens
|
Set the warning level for instructions without a size token.
Accepted values: off, ambiguous, aggressive, absolute, all |
| warnaserror
|
Treat warnings as errors during assembly.
Accepted values: true, false |
| maxerrors
|
Set the maximum number of errors. If this limit is exceeded, assembly immediately halts. |
| crc
|
A string literal to display and compare against for CRC32 hashing over the output file. |
| md5
|
A string literal to display and compare against for MD5 hashing over the output file. |
| sha1
|
A string literal to display and compare against for SHA-1 hashing over the output file. |
| diff
|
Relative path to a nominal file to compare output against. |
Template
[FUTABA:assemble]
assembly:
entry =
type =
output =
addsymbols =
nullfill =
randomseed =
base =
header:
mapmode =
autofill =
checksum =
fastrom =
title =
romsize =
ramsize =
version =
region =
extended =
coprocessor =
gamecode =
makercode =
debug:
symbols =
stdout =
errorout =
overflow =
tokens =
warnaserror =
maxerrors =
disassembly:
crc =
md5 =
sha1 =
diff =
!VARIABLE1 = "STRING"
!VARIABLE2 = 1.2823
#SYMBOL1 = 1234
#SYMBOL2 = $1ABC
#SYMBOL3 = %01011
.NET package
Futaba's core library is available as a nuget package from nuget.org for .NET projects. It can be found under the name Spannerisms.futaba.
The following APIs are exposed:
Futaba namespace
Assembler abstract class
UnmappedAssembler class
LoromAssembler class
HiromAssembler class
ExLoromAssembler class
ExHiromAssembler class
Sa1Assembler class
Breakpoint class
MissingTokenSeverity enum
RomOverflowAction enum
Variable class
Futaba.Symbols namespace
Symbol abstract class
AllocatedSymbol class
AssignedSymbol class
Buoy class
DataBlockLabel class
Dock class
Label class
Piling class
Register class
Futaba.Snes namespace
Coprocessor enum
MapperMode enum
Region class
RomHeader static class