How I use beebasm to build my games (with a .bat file)

Development tools discussion area.
Post Reply
User avatar
tricky
Posts: 4039
Joined: Tue Jun 21, 2011 8:25 am
Contact:

How I use beebasm to build my games (with a .bat file)

Post by tricky » Sun Mar 22, 2020 11:43 am

I thought I would post how I use beebasm to build my games.
I use visual studio (setup as an NMake project - no make involved) as my editor, but all that matters is that the editor can call a .bat with some optional parameters.
I use "Rebuild all" to pass "-v", this used to be used differently, but I still use it to flag verbose assemble, NO SSD produced.

Code: Select all

SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION

IF "%~1"=="-v" (
    ..\Tools\beebasm.exe -w -vc -i game.asm %1 %2 %3 %4 %5 %6 %7 %8 %9
    goto :EOF
)

..\Tools\beebasm.exe -w -vc -i game.asm -d >labels.txt
if ERRORLEVEL 1 goto :EOF

for /F "tokens=1,2,3,4,5,6*" %%O IN (labels.txt) DO (
    if "LOG:"=="%%O" (
        echo.%%P %%Q %%R %%S %%T %%U
    ) else if "OUT:"=="%%O" (
        set "name=%%P"
        set "load=%%Q"
        set "end=%%R"
        set "exec=%%S"
    ) else if "ERROR:"=="%%O" (
        echo.%%O:%%P %%Q %%R %%S %%T
        EXIT 1
    )
)
if ERRORLEVEL 1 goto :EOF
echo.name=!name! load=!load! end=!end! exec=!exec!

for /F "tokens=1,2,4,5,6" %%P IN ('..\tools\beebop.exe PATROL') DO set "name=%%P" & set "uncom=%%Q" & set "comp=%%R" & set "pad=%%S" & set "equb=%%T"
if ERRORLEVEL 1 goto :EOF
echo.name=!name! uncom=!uncom! comp=!comp! pad=!pad! equb=!equb!

echo.load=!load! >build.asm
echo.exec=!exec! >>build.asm
echo.len=!end!-!load! >>build.asm
echo.uncom=!uncom! >>build.asm
echo.comp=!comp! >>build.asm
echo.pad=!pad! >>build.asm
echo.INCLUDE "make_rom.asm" >>build.asm

..\tools\beebasm.exe -w -vc -i build.asm -do MoonPatrol.ssd -opt 3 -title "MoonPatrol"
if ERRORLEVEL 1 goto :EOF
I'll break that down:

Code: Select all

SETLOCAL ENABLEEXTENSIONS
SETLOCAL ENABLEDELAYEDEXPANSION
Enable extended features in batch files (I always have these explicitly) like % variables.

Code: Select all

IF "%~1"=="-v" (
    ..\Tools\beebasm.exe -w -vc -i game.asm %1 %2 %3 %4 %5 %6 %7 %8 %9
    goto :EOF
)
This just looks for the "-v" from above and does a simple verbose assemble, assuming a .SSD has just been produced - purely for looking at assemble/addresses to debug and then quits "goto :EOF". The extra parameters are no longer used.

Code: Select all

..\Tools\beebasm.exe -w -vc -i game.asm -d >labels.txt
if ERRORLEVEL 1 goto :EOF
Assembles the code to produce the executable code and dumps all "PRINT" statement output and the lables, capturing all output ">" to labels.txt and quits if an error code was returned "if ERRORLEVEL 1". I have also hacked my beebem to load the labels file to make them available for named breakpoints, watch-points and to display while single stepping.

Code: Select all

for /F "tokens=1,2,3,4,5,6*" %%O IN (labels.txt) DO (
    if "LOG:"=="%%O" (
        echo.%%P %%Q %%R %%S %%T %%U
    ) else if "OUT:"=="%%O" (
        set "name=%%P"
        set "load=%%Q"
        set "end=%%R"
        set "exec=%%S"
    ) else if "ERROR:"=="%%O" (
        echo.%%O:%%P %%Q %%R %%S %%T
        EXIT 1
    )
)
if ERRORLEVEL 1 goto :EOF
echo.name=!name! load=!load! end=!end! exec=!exec!
Search labels.txt (the full assembler log) for "LOG:" to log to the output window, "OUT:" to record the output for the assembled game or "ERROR:" to stop and return an error code to calling environment. I use this method as the internal ERROR statement will kill the assemble and loose any debug text. eg the actual value that was wrong. Finally, it checks for an error and if OK, logs the variables that it recorded ("OUT:") for the main game. These are generated by "PRINT" statements in the assembler: PRINT "ERROR: Screen ",~scr_top ,"to",~scr_end

Code: Select all

for /F "tokens=1,2,4,5,6" %%P IN ('..\tools\beebop.exe PATROL') DO set "name=%%P" & set "uncom=%%Q" & set "comp=%%R" & set "pad=%%S" & set "equb=%%T"
if ERRORLEVEL 1 goto :EOF
echo.name=!name! uncom=!uncom! comp=!comp! pad=!pad! equb=!equb!
Here I call my compressor (documented with source in a thread somewhere - TODO replace with link!). This "for" loop reads each line output from beebop (only one) and sets up some more variables about the compression: "pad" is the minimum bytes after the uncompressed data that the compressed data must end. Again check for error and finish.

Code: Select all

echo.load=!load! >build.asm
echo.exec=!exec! >>build.asm
echo.len=!end!-!load! >>build.asm
echo.uncom=!uncom! >>build.asm
echo.comp=!comp! >>build.asm
echo.pad=!pad! >>build.asm
echo.INCLUDE "make_rom.asm" >>build.asm
Write all the parameters to build.asm ">" writing the first line and then ">>" appending the rest with the final one being the .asm that will use these values. Again, there may be a better way to do this, but it hasn't changed for quite a while.

Code: Select all

..\tools\beebasm.exe -w -vc -i build.asm -do MoonPatrol.ssd -opt 3 -title "MoonPatrol"
if ERRORLEVEL 1 goto :EOF
Do that final assemble with a ROM header and decompression code so that the final game will fit in 16K and be runable or useable as a sideways ROM.

User avatar
tricky
Posts: 4039
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: How I use beebasm to build my games (with a .bat file)

Post by tricky » Sun Mar 22, 2020 11:51 am

In game.asm for MoonPatrol (copied from Scramble) I have:

Code: Select all

.END
PRINT "LOG: Load", ~BEGIN, "To", ~END, "Run", ~MAIN, "EoC", ~end_of_code
SAVE "PATROL", BEGIN, END, MAIN
PRINT "LOG: free", scr_top - end_of_code, "zp", irq_A_save-zp_end
PRINT "OUT: Scrambl", ~BEGIN, ~END, ~MAIN
These are to:
PRINT Just info for me to keep an eye on what memory things are using - there are several more of these.
SAVE Saves the actual binary assembled file.
PRINT Shows me how much main memory and how much zero page I have left.
PRINT Passes the parameters to the .bat file that will be used to compress and finally execute the code.

Code: Select all

MACRO LOG_ALIGN al
  IF (al - (P% MOD al)) MOD al > 8
    PRINT "LOG: Skipping",al - (P% AND (al - 1)),"at",~P%
  ENDIF
  ALIGN al
ENDMACRO
I also have a MACRO that I use instead of the ALIGN command, this is so that I can see how much memory I have just wasted and in this case warn me if it is more than 8 bytes.

User avatar
tricky
Posts: 4039
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: How I use beebasm to build my games (with a .bat file)

Post by tricky » Sun Mar 22, 2020 12:45 pm

This is my make_rom.asm, it provides a ROM header that will report a short help text and decompress then run the game, but also has an EXEC address that will also decompress (from a different address) the run the game. This allows the final "exe" to be run from disc or burnt to a sideways "ROM".

Code: Select all

OSBYTE = &FFF4
OSWRCH = &FFE3

fx0_os_ver = 0 ;; address where the result of a *FX0 will be storred.
keys_to_scan = 1   ;; address where the 6 key codes are stored that the game will use.
keys_to_use  = &80 ;; set from launcher or defaulted from ROM, address where the key codes are currently.

decompress_src = &70 ; decompressor
decompress_dst = &72 ; work
decompress_tmp = &74 ; space

SysViaRegB = &FE40
SysViaDDRA = &FE43
SysViaIFR  = &FE4D
SysViaIER  = &FE4E
SysViaRegA = &FE4F
IntCA1      =  2 \\ CA1 active edge
SysIntVSync = IntCA1 \\ 6845 vsync

UsrViaIFR  = &FE6D
UsrViaIER  = &FE6E

PaletteBlack   = &07

CrtcReg   = &FE00
CrtcVal   = &FE01
VideoULA  = &FE20
VideoULAVideoControlRegister = VideoULA + 0 \\ 8b WO b7 master crsr size b5-6 crsr bytes wide 00:1 m0,3-4,6 01:undef 10:2 m1,5,6 11:4 m2
	                                        \\ b567=000 disables the crsr b4 6845 clock rate 0:m4-7 1:m0-3
	                                        \\ b2-3 chars per line 11:80 10:40 01:20 00:10 b1 teletext b0 flash colour set
	                                        \\ m0:&9C m1:&D8 m2:&F4 m3:&9C m4:&88 m5:&C4 m6:&88 m7:&4B \\ NB check erata
VideoUlaMode_1NoCrsrFlash0   = &18 \\ non-flash colours
VideoULAPalette              = VideoULA + 1 \\ b4-7 logical colour 2c:b7xxx 4c:b7xb5x 16c:b7-4, b0-3 actual colour (^7)
	                                        \\ c0:black, c1:red, c2:green, c3:yellow, c4:blue, c5:magenta, c6:cyan, c7:white, c8-15:c0-7/c7-0
VideoNuLACtrl = VideoULA + 2
VideoNuLAPal  = VideoULA + 3

ORG &7000 - comp ; leave &1000 for ROM header and minimal startup code

.BEGIN

; ROM header

EQUB 0,0,0                   ; 8000 .. .. .. Language entry point
jmp service + &8000 - BEGIN  ; 8003 .. .. .. Service entry point : A=CMD X=ROM Y=etc
EQUB %10000010               ; 8006 tt ROM type byte: b7:service, b6:language, b5:Tube, b4:Electron, b3-b0:2 6502
EQUB copywright - BEGIN      ; 8007 cc Offset to copyright string
EQUB 1                       ; 8008 bb Binary version number, for user inspection only
.nm : EQUS "MPATROL",0 : .en ; 8009 .. .. .. ROM title, displayed before entering language
EQUB "1.00"                  ; 8011 .. .. (Optional) Version string in the format NUL + "n.nn (dd mmm yyyy)"
.copywright                  ; 8015 .. .. (Optional) Other data here is ignored.
EQUB 0, "(C) Tricky 2019", 0 ; 8015 NUL and "(C)", required for MOS acceptance, name of author, etc

.service                     ; ll ml hm hh (Optional) Tube transfer address.
{
	STACK_A_CMD = &103 : STACK_X_ROM = &102 : STACK_Y_OPT = &101
	pha : txa : pha : tya : pha : tsx : lda STACK_A_CMD,x

	cmp #4 : bne not_star_command     ;; Check (&F2),Y for *nm and launch
	jsr check_for_name + &8000 - BEGIN : bne done
	lda (&F2),y : cmp #13 : beq launch : bne done
.not_star_command

	cmp #9 : bne not_star_help        ;; Check (&F2),Y for null or *nm and list info
	lda (&F2),Y : cmp #13 : beq plain_help
	jsr check_for_name + &8000 - BEGIN : bne done
	lda (&F2),Y : cmp #13 : bne done
.plain_help
	lda #13 : jsr OSWRCH
	ldx #0 : {.lp : lda nm + &8000 - BEGIN,x : beq lf : jsr OSWRCH : inx : bne lp : .lf}
	ldx #0 : {.lp : lda controls + &8000 - BEGIN,x : beq done : jsr OSWRCH : inx : bne lp} : beq done
.not_star_help

	cmp #&10 : beq setup_defaults     ;; SPOOL/EXEC file closure warning.
	cmp #&25 : bne not_setup_defaults ;; Return Filing System Information.
.setup_defaults
	beq done
.not_setup_defaults

	cmp #&FE : bne not_tube_init      ;; Tube system post initialisation.
	lda STACK_X_ROM,x : bne check_32K
.launch
	{ldy #3 : .lp : lda default_keys + &8000 - BEGIN,y : sta keys_to_use,y : dey : bpl lp}
	lda #LO(data + &8000 - BEGIN) : sta decompress_src
	lda #HI(data + &8000 - BEGIN) : sta decompress_src+1
	jmp START + &8000 - BEGIN
.check_32K
	lda #133 : ldx #7 : jsr OSBYTE : cpy #&40 : bcs done
	SEI : LDA #&7F : STA &FE4E : JMP (&FFFC) ; RESET (near enough?)
.not_tube_init

.done
	pla : tay : pla : tax : pla
	RTS

.default_keys : EQUB &42, &61, &00, &49 ;; FBLRUDxx : Return Z X

.controls
	EQUS 13, "  Z X speed RETURN fire SHIFT jump", 13, "  (fire button to activate joystick)", 13, 0
}
.check_for_name ; returns =0 if (&F2),Y strats with name
{
	ldx #0
.check
	lda nm + &8000 - BEGIN,x : eor (&F2),y : and #&DF : bne done
	iny : inx : cpx #en - nm - 1
	bne check
.done
	rts
}

.data

INCLUDE "PATROL.equb"

.MAIN

lda #LO(data) : sta decompress_src
lda #HI(data) : sta decompress_src+1

.START

lda #LO(load) : sta decompress_dst
lda #HI(load) : sta decompress_dst+1

ldx #&6F : txs ; games starting stack pointer - it shouldn't need anything like this, so we may steal some more later
lda #0 : ldx #1 : jsr OSBYTE : txa : pha ;; get OS/machine type

SEI ;; turn off all interrupts
lda #&7F : sta SysViaIER : sta SysViaIFR \\ disable and clear all interrupts
           sta UsrViaIER : sta UsrViaIFR \\ disable and clear all interrupts

lda #&40 : sta VideoNuLACtrl ; RESET NuLA (logical palette mode) ; &cRGB
lda #HI(&0000) : sta VideoNuLAPal : lda #LO(&0000) : sta VideoNuLAPal ; K
lda #HI(&1F95) : sta VideoNuLAPal : lda #LO(&1F95) : sta VideoNuLAPal ; R  sandy red ground
lda #HI(&2090) : sta VideoNuLAPal : lda #LO(&2090) : sta VideoNuLAPal ; G  dark green hill shadows
lda #HI(&3CC0) : sta VideoNuLAPal : lda #LO(&3CC0) : sta VideoNuLAPal ; Y  alien yellow
lda #HI(&400F) : sta VideoNuLAPal : lda #LO(&400F) : sta VideoNuLAPal ; B  mountains blue blue
lda #HI(&5C0A) : sta VideoNuLAPal : lda #LO(&5C0A) : sta VideoNuLAPal ; M  buggy magenta
lda #HI(&64AD) : sta VideoNuLAPal : lda #LO(&64AD) : sta VideoNuLAPal ; C  bright cyan alien cyan
lda #HI(&9D00) : sta VideoNuLAPal : lda #LO(&9D00) : sta VideoNuLAPal ; R_ red car/status red
lda #HI(&A0BF) : sta VideoNuLAPal : lda #LO(&A0BF) : sta VideoNuLAPal ; G_ cyan status green
lda #HI(&B0D5) : sta VideoNuLAPal : lda #LO(&B0D5) : sta VideoNuLAPal ; Y_ light green hill sunny
lda #HI(&CB6B) : sta VideoNuLAPal : lda #LO(&CB6B) : sta VideoNuLAPal ; B_ blue status blue
lda #HI(&DF95) : sta VideoNuLAPal : lda #LO(&DF95) : sta VideoNuLAPal ; M_ sandy red ground
lda #HI(&E09A) : sta VideoNuLAPal : lda #LO(&E09A) : sta VideoNuLAPal ; C_ dull cyan mountains cyan

lda #SysIntVSync {.wait_vsync : bit SysViaIFR : beq wait_vsync}
lda #VideoUlaMode_1NoCrsrFlash0 : sta VideoULAVideoControlRegister        \\ no crsr, M4, non-flash
ldx #0 : lda #127             : stx CrtcReg : sta CrtcVal ;; R0HorizontalTotal       
   inx : lda #60              : stx CrtcReg : sta CrtcVal ;; R1HorizontalDisplayed    ;; 64
   inx : lda #88              : stx CrtcReg : sta CrtcVal ;; R2HorizontalSyncPosition ;; 90
   inx : lda #&29             : stx CrtcReg : sta CrtcVal ;; R3SyncPulseWidths       
   inx : lda #r4              : stx CrtcReg : sta CrtcVal ;; R4VerticalTotal         
   inx : lda #0               : stx CrtcReg : sta CrtcVal ;; R5VerticalTotalAdjust   
   inx : lda #1               : stx CrtcReg : sta CrtcVal ;; R6VerticalDisplayed     
   inx : lda #r7              : stx CrtcReg : sta CrtcVal ;; R7VerticalSyncPosition  
   inx : lda #CrtcR8_M06_Game : stx CrtcReg : sta CrtcVal ;; R8InterlaceAndControl   
   inx : lda #7               : stx CrtcReg : sta CrtcVal ;; R9CharacterScanLines 
   inx : lda #&20             : stx CrtcReg : sta CrtcVal ;; R10CursorControlStart
   inx : lda #8               : stx CrtcReg : sta CrtcVal ;; R11CursorEnd         
   inx : lda #HI(scr_addr/8)  : stx CrtcReg : sta CrtcVal ;; R12Screen1stCharHi   
   inx : lda #LO(scr_addr/8)  : stx CrtcReg : sta CrtcVal ;; R13Screen1stCharLo   
ldx #4+0 : stx SysViaRegB \\ display wrap size
ldx #5+0 : stx SysViaRegB \\ display wrap size

lda #PaletteBlack : clc {.lp : sta VideoULAPalette : adc #&10 : bcc lp} ; set all colours to black

{
	sound_silence = %10011111
	lda #&FF : sta SysViaDDRA
	lda %01100000 OR sound_silence : sec
.stop_channel
	sta SysViaRegA            \\ sample says SysViaRegH but OS uses no handshake \\ handshake regA
	ldy #0+0 : sty SysViaRegB \\ enable sound for 8us
	PHP : PLP : NOP : NOP     \\ 3+4+3+4 + 2(lda #) = 16 clocks = 8us
	ldy #0+8 : sty SysViaRegB \\ disable sound
	sbc #%00100000
	bmi stop_channel
}

INCLUDE "..\tools\beebop.asm"

pla ; *FX0 OS version
sta fx0_os_ver

{ldy #3 : .lp : lda keys_to_use,y : sta keys_to_scan,y : dey : bpl lp}

jmp exec

row_bytes   = &200
scr_addr    = &8000 - 17 * row_bytes
R4_AT_VSYNC   = 12
R7_AT_VSYNC   =  6
r4 = 38 ;; 50Hz
r7 = r4 - R4_AT_VSYNC + R7_AT_VSYNC
CrtcR8_M06_Game    = &C0 \\ no interlace, no crsr

.END

MACRO HEX1 v
	d = v AND &F
	IF d<10
		EQUB '0'+d
	ELSE
		EQUB 'A'+d-10
	ENDIF
ENDMACRO
MACRO HEX2 v
	HEX1 v DIV &10
	HEX1 v
ENDMACRO
MACRO HEX4 v
	HEX2 v DIV &100
	HEX2 v
ENDMACRO

ORG &C000
EQUS "B%=&" : HEX4 MAIN : EQUS 13
EQUS "CH.", 34, "MOON", 34, 13
save "!BOOT", &C000, P%, 0, 0

ORG &D000
INCBIN "$.LOADER"
save "MOON", &D000, P%, &8023, &31900

PRINT "PATROL", ~BEGIN, ~END, ~MAIN, ~(&30000+BEGIN)
SAVE "PATROL", BEGIN, END, MAIN, BEGIN + &30000
I'll break this down too, remember that it is being included from build.asm that has set up the variables used in this file.

Code: Select all

OSBYTE = &FFF4
OSWRCH = &FFE3

fx0_os_ver = 0 ;; address where the result of a *FX0 will be storred.
keys_to_scan = 1   ;; address where the 6 key codes are stored that the game will use.
keys_to_use  = &80 ;; set from launcher or defaulted from ROM, address where the key codes are currently.

decompress_src = &70 ; decompressor
decompress_dst = &72 ; work
decompress_tmp = &74 ; space

SysViaRegB = &FE40
SysViaDDRA = &FE43
SysViaIFR  = &FE4D
SysViaIER  = &FE4E
SysViaRegA = &FE4F
IntCA1      =  2 \\ CA1 active edge
SysIntVSync = IntCA1 \\ 6845 vsync

UsrViaIFR  = &FE6D
UsrViaIER  = &FE6E

PaletteBlack   = &07

CrtcReg   = &FE00
CrtcVal   = &FE01
VideoULA  = &FE20
VideoULAVideoControlRegister = VideoULA + 0 \\ 8b WO b7 master crsr size b5-6 crsr bytes wide 00:1 m0,3-4,6 01:undef 10:2 m1,5,6 11:4 m2
	                                        \\ b567=000 disables the crsr b4 6845 clock rate 0:m4-7 1:m0-3
	                                        \\ b2-3 chars per line 11:80 10:40 01:20 00:10 b1 teletext b0 flash colour set
	                                        \\ m0:&9C m1:&D8 m2:&F4 m3:&9C m4:&88 m5:&C4 m6:&88 m7:&4B \\ NB check erata
VideoUlaMode_1NoCrsrFlash0   = &18 \\ non-flash colours
VideoULAPalette              = VideoULA + 1 \\ b4-7 logical colour 2c:b7xxx 4c:b7xb5x 16c:b7-4, b0-3 actual colour (^7)
	                                        \\ c0:black, c1:red, c2:green, c3:yellow, c4:blue, c5:magenta, c6:cyan, c7:white, c8-15:c0-7/c7-0
VideoNuLACtrl = VideoULA + 2
VideoNuLAPal  = VideoULA + 3
Addresses and values that will probably needed to implement the ROM and setup ready for the game.

Code: Select all

ORG &7000 - comp ; leave &1000 for ROM header and minimal startup code

.BEGIN
Use the variable comp to make sure that the ROM code will start at &7000 (will be loaded at &8000).

Code: Select all

; ROM header

EQUB 0,0,0                   ; 8000 .. .. .. Language entry point
jmp service + &8000 - BEGIN  ; 8003 .. .. .. Service entry point : A=CMD X=ROM Y=etc
EQUB %10000010               ; 8006 tt ROM type byte: b7:service, b6:language, b5:Tube, b4:Electron, b3-b0:2 6502
EQUB copywright - BEGIN      ; 8007 cc Offset to copyright string
EQUB 1                       ; 8008 bb Binary version number, for user inspection only
.nm : EQUS "MPATROL",0 : .en ; 8009 .. .. .. ROM title, displayed before entering language
EQUB "1.00"                  ; 8011 .. .. (Optional) Version string in the format NUL + "n.nn (dd mmm yyyy)"
.copywright                  ; 8015 .. .. (Optional) Other data here is ignored.
EQUB 0, "(C) Tricky 2019", 0 ; 8015 NUL and "(C)", required for MOS acceptance, name of author, etc
Standard ROM header - probably need to change that date now.

Code: Select all

.service                     ; ll ml hm hh (Optional) Tube transfer address.
{
	STACK_A_CMD = &103 : STACK_X_ROM = &102 : STACK_Y_OPT = &101
	pha : txa : pha : tya : pha : tsx : lda STACK_A_CMD,x

	cmp #4 : bne not_star_command     ;; Check (&F2),Y for *nm and launch
	jsr check_for_name + &8000 - BEGIN : bne done
	lda (&F2),y : cmp #13 : beq launch : bne done
.not_star_command

	cmp #9 : bne not_star_help        ;; Check (&F2),Y for null or *nm and list info
	lda (&F2),Y : cmp #13 : beq plain_help
	jsr check_for_name + &8000 - BEGIN : bne done
	lda (&F2),Y : cmp #13 : bne done
.plain_help
	lda #13 : jsr OSWRCH
	ldx #0 : {.lp : lda nm + &8000 - BEGIN,x : beq lf : jsr OSWRCH : inx : bne lp : .lf}
	ldx #0 : {.lp : lda controls + &8000 - BEGIN,x : beq done : jsr OSWRCH : inx : bne lp} : beq done
.not_star_help
Possibly not a very standard service handler as I'm sure it doesn't handle all *HELP combos.

Code: Select all

	cmp #&10 : beq setup_defaults     ;; SPOOL/EXEC file closure warning.
	cmp #&25 : bne not_setup_defaults ;; Return Filing System Information.
.setup_defaults
	beq done
.not_setup_defaults
These service calls have been determined by experimentation and I use them because games like Astro Blaster have up to three ROMs that all affect behaviour but I didn't want them to actually "talk to eachother".

Code: Select all

	cmp #&FE : bne not_tube_init      ;; Tube system post initialisation.
	lda STACK_X_ROM,x : bne check_32K
.launch
	{ldy #3 : .lp : lda default_keys + &8000 - BEGIN,y : sta keys_to_use,y : dey : bpl lp}
	lda #LO(data + &8000 - BEGIN) : sta decompress_src
	lda #HI(data + &8000 - BEGIN) : sta decompress_src+1
	jmp START + &8000 - BEGIN
Check if we are in slot 0 (Master cart) and auto boot - defiantly unofficial!

Code: Select all

.check_32K
	lda #133 : ldx #7 : jsr OSBYTE : cpy #&40 : bcs done
	SEI : LDA #&7F : STA &FE4E : JMP (&FFFC) ; RESET (near enough?)
.not_tube_init
If we aren't booting and we don't have 32K, The last game was probably one of mine a trashed everything, so do a near as we can "power on" boot.

Code: Select all

.done
	pla : tay : pla : tax : pla
	RTS

Code: Select all

.default_keys : EQUB &42, &61, &00, &49 ;; FBLRUDxx : Return Z X
Keys that will be used if run from ROM.

Code: Select all

.controls
	EQUS 13, "  Z X speed RETURN fire SHIFT jump", 13, "  (fire button to activate joystick)", 13, 0
}
.check_for_name ; returns =0 if (&F2),Y strats with name
{
	ldx #0
.check
	lda nm + &8000 - BEGIN,x : eor (&F2),y : and #&DF : bne done
	iny : inx : cpx #en - nm - 1
	bne check
.done
	rts
}
Help message and code to detect if we should print it.

Code: Select all

.data

INCLUDE "PATROL.equb"
Compressed game code including game setup.

Code: Select all

.MAIN

lda #LO(data) : sta decompress_src
lda #HI(data) : sta decompress_src+1
Entry point if *RUN

Code: Select all

.START

lda #LO(load) : sta decompress_dst
lda #HI(load) : sta decompress_dst+1
Common (ROM or *RUN) code path from here on, inc setup of decompression address

Code: Select all

ldx #&6F : txs ; games starting stack pointer - it shouldn't need anything like this, so we may steal some more later
lda #0 : ldx #1 : jsr OSBYTE : txa : pha ;; get OS/machine type

Code: Select all

SEI ;; turn off all interrupts
lda #&7F : sta SysViaIER : sta SysViaIFR \\ disable and clear all interrupts
           sta UsrViaIER : sta UsrViaIFR \\ disable and clear all interrupts
Disable all interrupts as we are about to take control - I used to claim NMIs here, but it didn't always work and I didn't find out why.

Code: Select all

lda #&40 : sta VideoNuLACtrl ; RESET NuLA (logical palette mode) ; &cRGB
lda #HI(&0000) : sta VideoNuLAPal : lda #LO(&0000) : sta VideoNuLAPal ; K
lda #HI(&1F95) : sta VideoNuLAPal : lda #LO(&1F95) : sta VideoNuLAPal ; R  sandy red ground
lda #HI(&2090) : sta VideoNuLAPal : lda #LO(&2090) : sta VideoNuLAPal ; G  dark green hill shadows
lda #HI(&3CC0) : sta VideoNuLAPal : lda #LO(&3CC0) : sta VideoNuLAPal ; Y  alien yellow
lda #HI(&400F) : sta VideoNuLAPal : lda #LO(&400F) : sta VideoNuLAPal ; B  mountains blue blue
lda #HI(&5C0A) : sta VideoNuLAPal : lda #LO(&5C0A) : sta VideoNuLAPal ; M  buggy magenta
lda #HI(&64AD) : sta VideoNuLAPal : lda #LO(&64AD) : sta VideoNuLAPal ; C  bright cyan alien cyan
lda #HI(&9D00) : sta VideoNuLAPal : lda #LO(&9D00) : sta VideoNuLAPal ; R_ red car/status red
lda #HI(&A0BF) : sta VideoNuLAPal : lda #LO(&A0BF) : sta VideoNuLAPal ; G_ cyan status green
lda #HI(&B0D5) : sta VideoNuLAPal : lda #LO(&B0D5) : sta VideoNuLAPal ; Y_ light green hill sunny
lda #HI(&CB6B) : sta VideoNuLAPal : lda #LO(&CB6B) : sta VideoNuLAPal ; B_ blue status blue
lda #HI(&DF95) : sta VideoNuLAPal : lda #LO(&DF95) : sta VideoNuLAPal ; M_ sandy red ground
lda #HI(&E09A) : sta VideoNuLAPal : lda #LO(&E09A) : sta VideoNuLAPal ; C_ dull cyan mountains cyan
Setup the VideoNULA palette using (I think) logical colours, anyway, we will get Red without a NULA and sandy red ground with one. We will also get flashing Red (that never swaps to the opposite colour) or red for the car/status red bits.

Code: Select all

lda #SysIntVSync {.wait_vsync : bit SysViaIFR : beq wait_vsync}
lda #VideoUlaMode_1NoCrsrFlash0 : sta VideoULAVideoControlRegister        \\ no crsr, M4, non-flash
ldx #0 : lda #127             : stx CrtcReg : sta CrtcVal ;; R0HorizontalTotal       
   inx : lda #60              : stx CrtcReg : sta CrtcVal ;; R1HorizontalDisplayed    ;; 64
   inx : lda #88              : stx CrtcReg : sta CrtcVal ;; R2HorizontalSyncPosition ;; 90
   inx : lda #&29             : stx CrtcReg : sta CrtcVal ;; R3SyncPulseWidths       
   inx : lda #r4              : stx CrtcReg : sta CrtcVal ;; R4VerticalTotal         
   inx : lda #0               : stx CrtcReg : sta CrtcVal ;; R5VerticalTotalAdjust   
   inx : lda #1               : stx CrtcReg : sta CrtcVal ;; R6VerticalDisplayed     
   inx : lda #r7              : stx CrtcReg : sta CrtcVal ;; R7VerticalSyncPosition  
   inx : lda #CrtcR8_M06_Game : stx CrtcReg : sta CrtcVal ;; R8InterlaceAndControl   
   inx : lda #7               : stx CrtcReg : sta CrtcVal ;; R9CharacterScanLines 
   inx : lda #&20             : stx CrtcReg : sta CrtcVal ;; R10CursorControlStart
   inx : lda #8               : stx CrtcReg : sta CrtcVal ;; R11CursorEnd         
   inx : lda #HI(scr_addr/8)  : stx CrtcReg : sta CrtcVal ;; R12Screen1stCharHi   
   inx : lda #LO(scr_addr/8)  : stx CrtcReg : sta CrtcVal ;; R13Screen1stCharLo   
ldx #4+0 : stx SysViaRegB \\ display wrap size
ldx #5+0 : stx SysViaRegB \\ display wrap size
We wait for a VSync and then setup the basic screen mode used by the game.

Code: Select all

lda #PaletteBlack : clc {.lp : sta VideoULAPalette : adc #&10 : bcc lp} ; set all colours to black
to hide any data using screen memory while starting up - will only be a few frames and will give the TV time to sync if necessary.

Code: Select all

{
	sound_silence = %10011111
	lda #&FF : sta SysViaDDRA
	lda %01100000 OR sound_silence : sec
.stop_channel
	sta SysViaRegA            \\ sample says SysViaRegH but OS uses no handshake \\ handshake regA
	ldy #0+0 : sty SysViaRegB \\ enable sound for 8us
	PHP : PLP : NOP : NOP     \\ 3+4+3+4 + 2(lda #) = 16 clocks = 8us
	ldy #0+8 : sty SysViaRegB \\ disable sound
	sbc #%00100000
	bmi stop_channel
}
Silence any sounds like the leftover hi-beep if this was from a ctrl-shift-break or power-on-break.

Code: Select all

INCLUDE "..\tools\beebop.asm"
My decompression code - 72 bytes.

Code: Select all

pla ; *FX0 OS version
sta fx0_os_ver
Save OS version as it will be checked to setup the joystick and maybe speech later

Code: Select all

{ldy #3 : .lp : lda keys_to_use,y : sta keys_to_scan,y : dey : bpl lp}
Move the key codes down to make clearing the rest of zero page easier!

Code: Select all

jmp exec
Start actual game code including any one time setup (setup code at this point will be "off the end" in screen memory)

Code: Select all

row_bytes   = &200
scr_addr    = &8000 - 17 * row_bytes
R4_AT_VSYNC   = 12
R7_AT_VSYNC   =  6
r4 = 38 ;; 50Hz
r7 = r4 - R4_AT_VSYNC + R7_AT_VSYNC
CrtcR8_M06_Game    = &C0 \\ no interlace, no crsr
Other values useful for setting up the display.

Code: Select all

.END

Code: Select all

MACRO HEX1 v
	d = v AND &F
	IF d<10
		EQUB '0'+d
	ELSE
		EQUB 'A'+d-10
	ENDIF
ENDMACRO
MACRO HEX2 v
	HEX1 v DIV &10
	HEX1 v
ENDMACRO
MACRO HEX4 v
	HEX2 v DIV &100
	HEX2 v
ENDMACRO
MACROs for generating strings for hex numbers

Code: Select all

ORG &C000
EQUS "B%=&" : HEX4 MAIN : EQUS 13
EQUS "CH.", 34, "MOON", 34, 13
save "!BOOT", &C000, P%, 0, 0
Dummy address for assembling the !BOOT file to run the BASIC loader including splash screen.

Code: Select all

ORG &D000
INCBIN "$.LOADER"
save "MOON", &D000, P%, &8023, &31900
Loads a binary version of the basic loader that I have written in beebem, saved to a .SSD and extracted! Note the &3xxxx address, which the DFS will show as &FFxxxx and be loaded by the beeb, not any CoPro.

Code: Select all

PRINT "PATROL", ~BEGIN, ~END, ~MAIN, ~(&30000+BEGIN)
SAVE "PATROL", BEGIN, END, MAIN, BEGIN + &30000
Actual final EXE/ROM, note the +&30000 again to load it into non-CoPro memory. Should I have also added &30000 to MAIN?

User avatar
Diminished
Posts: 233
Joined: Fri Dec 08, 2017 9:47 pm
Contact:

Re: How I use beebasm to build my games (with a .bat file)

Post by Diminished » Fri Mar 27, 2020 6:14 pm

What's the deal with your compressor, tricky? Is it some clever domain-specific thing for 6502 code or something?

User avatar
tricky
Posts: 4039
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: How I use beebasm to build my games (with a .bat file)

Post by tricky » Fri Mar 27, 2020 8:05 pm

No, it is very simple and encodes up to 128 uncompressed or repeated bytes from anywhere in the last 256 bytes. If you have the same eight bytes repeated eight times, you would have 8 uncompressed bytes and then 56 repeated bytes starting 8 bytes before, so as they are copied, they intern are copied.
I'm sure that there is a proper name for this system, but I've never heard of it anywhere else.
My compressor is PC based and just brute forces the compression and archives a saving of about 25%, enough to get a game plus starup code and decompressor of about 20k to fit in a 16k
EPROM.
The decompressor is very fast for a decompressor and most importantly for my use case very small.
Compressing 20k on a desktop PC takes about one second :)

I have a seapate compressor for the text of my gotec/mmc menu system, that even on short pieces of text can get it to about 1/2 size and I'm sure no one has used the exact scheme I use for that (well probably someone has!)

User avatar
davidb
Posts: 2646
Joined: Sun Nov 11, 2007 10:11 pm
Contact:

Re: How I use beebasm to build my games (with a .bat file)

Post by davidb » Fri Mar 27, 2020 8:45 pm

tricky wrote:
Fri Mar 27, 2020 8:05 pm
No, it is very simple and encodes up to 128 uncompressed or repeated bytes from anywhere in the last 256 bytes. If you have the same eight bytes repeated eight times, you would have 8 uncompressed bytes and then 56 repeated bytes starting 8 bytes before, so as they are copied, they intern are copied.
I'm sure that there is a proper name for this system, but I've never heard of it anywhere else.
Like my (de)compressor, I think yours is related to the Lempel-Ziv algorithms.

User avatar
tricky
Posts: 4039
Joined: Tue Jun 21, 2011 8:25 am
Contact:

Re: How I use beebasm to build my games (with a .bat file)

Post by tricky » Sat Mar 28, 2020 9:23 am

LZSS it is, I experimented with different length vs distance splits and different length ranges for compressed vs uncompressed.
1b flag, 7b length 8b distance gave about 25% on my sample games, while the best was more like 32% but the decompression code was smallest with 178.
As most of my games only decompress one thing (them selves), I guess I could generate a decompressor along with the compressed data.
One other little thing I like is that the code is position independent, although it does need to know where its 5 bytes of zero page workspace are. SRC, DST and a tmp byte.
I've not bothered, but it would be easy to make it a streaming decompressor for only a few extra bytes plus the IO code.

The text one uses recursive tokens which can be a character or two more tokens and I think about an 8 byte stack. I haven't checked the code size, but it is pretty small :)

Post Reply