Voltmace Delta 14B/1 JOYSTIK loader

Updated 2 Sep 2026

← All Voltmace Delta 14B Driver versions

; Decoy stub BASIC line (&0D, line 13, RTS bytes) so a naive ; *LOAD/LIST at PAGE sees junk, not the loader
1900 .decoy
EQUB &0D, &00, &0D, &60, &60, &60, &60, &60, &60

*RUN entry: decrypt and auto-run

The DFS execution address. Decrypts the resident drivers and BASIC in place, repairs the BASIC's first line, then queues PAGE=&1C00 / OLD / RUN so the now-plain BASIC configuration program starts.

1909 .main
PHA ; Preserve A
190A TXA ; Preserve X...
190B PHA ; ...on the stack
190C TYA ; Preserve Y...
190D PHA ; ...on the stack
190E JSR decode_basic ; Decrypt the resident drivers and BASIC in place
1911 JSR patch_header ; Repair the BASIC's first-line length byte
1914 JSR os_dependent_setup ; Queue the auto-run commands (OS-dependent)
1917 PLA ; Restore Y...
1918 TAY ; ...
1919 PLA ; Restore X...
191A TAX ; ...
191B PLA ; Restore A
191C RTS ; Return to the MOS; the queued commands then run the BASIC

Decrypt the resident drivers and BASIC

Rotate every byte of &1A00-&4AFF left one bit (the inverse of the ROL-1 storage protection), in place. That covers both resident driver variants (&1A00, &1B00) and the tokenised BASIC from PAGE=&1C00; the loop stops at page &4B, leaving the BASIC's raw tail untouched.

191D .decode_basic←1← 190E JSR
LDA decode_ptr ; Save the caller decode_ptr low byte...
191F STA decode_ptr_save ; ...
1922 LDA decode_ptr_hi ; ...and high byte...
1924 STA decode_ptr_save_hi ; ...at decode_ptr_save
1927 LDX #0 ; Point decode_ptr at &1A00: low byte 0...
1929 STX decode_ptr ; ...
192B LDX #&1a ; ...high byte &1A...
192D LDY #0 ; byte index 0
192F .decode_next_page←1← 1941 BNE
STX decode_ptr_hi ; Set the page being decrypted
1931 .decode_next_byte←1← 193A BNE
LDA (decode_ptr),y ; Read an encrypted byte...
1933 CLC ; clear carry for the rotate
1934 ASL ; ...rotate it left one bit (undo the ROL-1 protection)...
1935 ADC #0 ; ...carrying bit 7 into bit 0...
1937 STA (decode_ptr),y ; ...and store it back
1939 INY ; Next byte...
193A BNE decode_next_byte ; ...to the end of the page
193C LDX decode_ptr_hi ; Next page...
193E INX ; ...
193F CPX #&4b ; ...until page &4B (the BASIC raw tail is left alone)
1941 BNE decode_next_page ; ...
1943 LDA decode_ptr_save ; Restore the caller decode_ptr...
1946 STA decode_ptr ; ...
1948 LDA decode_ptr_save_hi ; ...
194B STA decode_ptr_hi ; ...
194D RTS ; Done

OS-version-dependent setup

Reads the three-byte "OS " ROM signature to pick how the auto-run commands are queued.

194E .os_dependent_setup←1← 1914 JSR
LDY #0 ; Count matching signature bytes in Y
1950 LDA os_signature ; First OS ROM byte...
1953 CMP #&4f ; ...'O'?
1955 BNE os_check_1 ; ...
1957 INY ; match: bump the count
1958 .os_check_1←1← 1955 BNE
LDA os_signature_1 ; Second OS ROM byte...
195B CMP #&53 ; ...'S'?
195D BNE os_check_2 ; ...
195F INY ; match: bump the count
1960 .os_check_2←1← 195D BNE
LDA os_signature_2 ; Third OS ROM byte...
1963 CMP #&20 ; ...' '?
1965 BNE os_check_done ; ...
1967 INY ; match: bump the count
1968 .os_check_done←1← 1965 BNE
CPY #3 ; All three bytes matched "OS "?
196A BEQ use_direct_poke ; yes -> poke the buffer directly
196C JSR setup_keys ; no -> insert via OSBYTE
196F RTS ; Done
1970 .use_direct_poke←1← 196A BEQ
JSR queue_autorun ; Queue by poking the keyboard buffer
1973 RTS ; Done
1974 .decode_ptr_save←2← 191F STA← 1943 LDA
EQUW &0000 ; Scratch: caller's decode_ptr saved across decode_basic

Repair the BASIC program's first line

Write &17 (23) into basic_line10_len, the length byte of the line-10 REM at PAGE=&1C00, which is stored as 0 by the protection.

1976 .patch_header←1← 1911 JSR
LDA #&17 ; The first BASIC line is 23 bytes long...
1978 STA basic_line10_len ; ...restore its length byte (stored as 0 by the protection)
197B RTS ; Done

Queue the auto-run commands (direct poke)

Copy autorun_commands into the MOS keyboard buffer so the OS reads them as typed. autorun_index wraps into the 32-byte buffer at &03E0-&03FF.

197C .queue_autorun←1← 1970 JSR
LDY #0 ; Start at the first command byte
197E .queue_next_byte←1← 1997 JMP
LDX autorun_index ; Current buffer fill position...
1981 LDA autorun_commands,y ; read a command byte...
1984 BEQ queue_done ; ...&00 terminates
1986 STA kbd_buffer,x ; Poke it into the keyboard buffer
1989 INC autorun_index ; Advance the fill position...
198C LDA autorun_index ; ...
198F BNE queue_advance ; ...
1991 LDA #&e0 ; ...wrapping back to &E0 (buffer at &03E0)...
1993 STA autorun_index ; ...
1996 .queue_advance←1← 198F BNE
INY ; Next command byte
1997 JMP queue_next_byte ; ...
199A .queue_done←1← 1984 BEQ
RTS ; Done

Queue the auto-run commands (OSBYTE path)

The non-"OS "-signature variant of queue_autorun: set the BREAK/ESCAPE behaviour, then insert each autorun_commands byte with OSBYTE &8A.

199B .setup_keys←1← 196C JSR
LDA #&c8 ; OSBYTE 200: set the BREAK/ESCAPE behaviour...
199D LDX #3 ; ...
199F JSR osbyte ; call OSBYTE
19A2 LDX #0 ; Start at the first command byte
19A4 .insert_next_key←1← 19BA JMP
LDA autorun_commands,x ; Read a command byte...
19A7 BEQ setup_keys_done ; ...&00 terminates
19A9 TAY ; The character to insert
19AA TXA ; Save the loop index...
19AB STA setup_key_index ; ...
19AE LDX #0 ; OSBYTE &8A: insert Y into buffer 0 (keyboard)...
19B0 LDA #&8a ; A = &8A
19B2 JSR osbyte ; call OSBYTE
19B5 LDA setup_key_index ; Restore the loop index...
19B8 TAX ; ...
19B9 INX ; Next command byte
19BA JMP insert_next_key ; ...
19BD .setup_keys_done←1← 19A7 BEQ
RTS ; Done
19BE .setup_key_index←2← 19AB STA← 19B5 LDA
EQUB &EA ; Scratch: saved loop index (&EA at rest)
19BF .autorun_commands←2Used as index base by← 1981 LDA← 19A4 LDA
EQUB &15, &0D ; CTRL-U + CR: clear the input line
19C1 EQUS "PA.=&1C00" ; set PAGE to &1C00, where the BASIC lives
19CA EQUB &0D ; submit
19CB EQUS "OLD" ; reinstate the decoded program (OLD)
19CE EQUB &0D ; submit
19CF EQUS "RUN" ; run it (RUN)
19D2 EQUB &0D, &00 ; submit; the &00 then stops the queue copy
; Unused bytes after the command list, up to the resident driver ; at &1A00; referenced by nothing and inert.
19D4 .loader_tail
EQUB &0D, &54, &67, &42, &CC, &6D, &F6, &B0, &C0, &FF, &38, &25, &4D, &05, &55, &06, &B7, &84, &20, &D6, &88, &B0, &40, &BD, &D3, &FA, &C8, &10, &F8, &F1, &EB, &44, &1B, &4C, &01, &63, &F6, &50, &74, &21, &39, &71, &C0, &86
; ROL-encoded: resident driver variant A (&1A00), resident ; driver variant B (&1B00), then the tokenised BASIC from ; PAGE=&1C00 (raw past &4B00). Resident drivers: see ; driver_a.asm/driver_b.asm; BASIC: ; basic/voltmace-delta-14b-driver-joystik.bas.
1A00 .encoded_region
; BBC BASIC's variable heap as it stood when the author saved ; the &1900-&4D00 image: variable-name/value records for the ; program globals (e.g. REV$="Rev 2.0", and LR%, R1%, EV$, LVL%, ; LVH%, S% ...). Not used by the loader; PAGE=&1C00 sits below ; it, so BASIC rebuilds its own variables on RUN.
4C93 .basic_variables
EQUB &E5, &E5, &E5, &E5, &E5, &A1, &4C, &52, &25, &00, &00, &00, &00, &00, &E5, &00, &52, &31, &25, &00, &00, &00, &00, &00, &E5, &00, &45, &56, &24, &00, &B5, &4C, &07, &07, &52, &65, &76, &20, &32, &2E, &30, &C6, &4C, &56, &4C, &25, &00, &72, &00, &00, &00, &D0, &4C, &56, &48, &25, &00, &E7, &00, &00, &00, &E5, &00, &53, &25, &00, &01, &00, &00, &00, &E5, &00, &25, &28, &00, &03, &49, &00, &21, &00, &00, &00, &72, &00, &00, &00, &73, &00, &00, &00, &74, &00, &00, &00, &15, &00, &00, &00, &75, &00, &00, &00, &76, &00, &00, &00, &17, &00, &00