Intro to the bass (badass) 6502 assembler
Part 1
bass is a new 6502 assembler.It is designed to be powerful (like Kickassembler) but with a less complex syntax, and more unified, functional design.
Let’s start with a minimal C64 program, a 3 opcode routine to make a rainbow border:
!org $c000
loop:
lda $d012
sta $d020
jmp loop
You should be able to load this into a C64 emulator and start it with sys 49152
.
Now let’s add a basic start snippet so we can just run the prg instead:
!section "main", $801
!byte $0b,$08,$01,$00,$9e,str(start)," BY SASQ",$00,$00,$00
start:
lda #3
sta $d020
What happens here? Well first we use the !section
command instead of !org!
. Sections are more powerful in that you can control layout of memory in different ways (but we wont get into that here).
Then, the !byte
meta command accepts both numbers
and strings, and for strings it inserts each character one by one.
The str()
function takes a value and converts it to a string. So this
is how we insert the correct sys address into the basic, regardless of
where that start:
label is placed.
Let’s add some more code;
!section "main", $801
!byte $0b,$08,$01,$00,$9e,str(start)," BY SASQ",$00,$00,$00
start:
jsr $1000
.loop
lda $d012
cmp #100
bne .loop
lda #3
sta $d020
jsr $1003
lda #0
sta $d020
jmp .loop
!section "music", $1000
!incbin "music.raw"
This is a short program that waits for a certain scan line, calls a music routine,
and loops. The music is expected to reside in the file music.raw
and must
be placed at address $1000.
But what if music is a PRG with 2 bytes load address first. Or a sid file?
The functional approach of bass lets us manipulate not only numeric data, but also byte arrays. So to strip out the 2 first byte of a file and place it in memory we can do:
data = load("music.bin")
!fill data[2:]
Now we no longer use !incbin
to include data, instead we load it into a byte array and store it in the symbol table, just like any symbol.
Then to actually place it in memory, we use the !fill
meta command.
The [2:]
part is called slicing. To extract a range from a byte array you use
it like: data[start:end]
(where start is inclusive and end exclusive). So in this case we skip the first 2 bytes (the load address) and keep the rest of the array.
So what about the load address, can we actually use it? Sure;
loadAdr = word(data[0:2])
!section "music", loadAdr
music:
!fill data[2:]
As you may have figured out, the word()
function decodes a little endian 16-bit word from an array. We could have also done this using just math operations; data[0] | (data[1]<<8)
.
Except what if the load address is not compatible with our layout? If we load another song at a different address it will probably break our code.
So instead of blindly trusting the load address we can verify it:
musicStart = $1000
!assert word(data[0:2]) == musicStart
Let’s put it all together:
musicStart = $1000
!section "main", $801
!byte $0b, $08,$01,$00,$9e,str(start)," BY SASQ", $00,$00,$00
start:
jsr musicStart
.loop
lda $d012
cmp #100
bne .loop
lda #3
sta $d020
jsr musicStart+3
lda #0
sta $d020
jmp .loop
data = load("music.prg")
!assert word(data[0:2]) == musicStart
!section "music", musicStart
!fill data[2:]