10 REM > ROMBox/s
   20 REM Provides emulated ROMBox for Spectrum
   30 REM Memory at &0000-&3FFF paged at OUT &xxFD
   40 REM
   50 REM 23-Mar-2002 v1.00 JGH: Initial version
   60 REM 09-Nov-2005 v1.05 JGH: Added ROMBoxPort command
   70 REM
   80 ON ERROR REPORT:PRINT ERL:END
   90 :
  100 ioport%=&00FD
  110 :
  120 DIM mcode% &8000:ver$="1.05"
  130 sp=13:link=14:pc=15
  140 FOR p=0 TO 1
  150   P%=0:O%=mcode%
  160   [OPT p*3+4
  170   EQUD 0:\start
  180   EQUD Initialise
  190   EQUD Finalise
  200   EQUD Service
  210   EQUD TitleStr
  220   EQUD HelpStr
  230   EQUD CommandTable
  240   EQUD 0:\ SWI chunk number
  250   EQUD 0:\ SWI chunk handler
  260   EQUD 0:\ SWI decoding table
  270   EQUD 0:\ SWI decoding code
  280   :
  290   .TitleStr
  300   EQUS "ROMBox"
  310   EQUB 0
  320   ALIGN
  330   .HelpStr
  340   EQUS "Spectrum ROMBox"+CHR$9+ver$+" ("+MID$(TIME$,5,11)+")  2002 J.G.Harston"
  350   EQUB 0
  360   ALIGN
  370   :
  380   .CommandTable
  390   EQUS "ROMBox":EQUB 0:ALIGN:EQUD ROMBoxInit:EQUD &00010001:EQUD ROMBoxInit_Syntax:EQUD ROMBoxInit_Help
  400   EQUS "ROMBoxLoad":EQUB 0:ALIGN:EQUD ROMBoxLoad:EQUD &00020002:EQUD ROMBoxLoad_Syntax:EQUD ROMBoxLoad_Help
  410   EQUS "ROMBoxList":EQUB 0:ALIGN:EQUD ROMBoxList:EQUD &00000000:EQUD ROMBoxList_Syntax:EQUD ROMBoxList_Help
  420   EQUS "ROMBoxPort":EQUB 0:ALIGN:EQUD ROMBoxPort:EQUD &00000000:EQUD ROMBoxPort_Syntax:EQUD ROMBoxPort_Help
  430   \ ROMBoxRAM
  440   \ ROMBoxROM
  450   \ ?
  460   EQUB 0:ALIGN
  470   :
  480   .ROMBoxInit_Help
  490   EQUS "Registers a module or application with ROMBox":EQUB 13
  500   .ROMBoxInit_Syntax
  510   EQUS "Syntax: ROMBox <module name>|<address>"
  520   EQUB 0:ALIGN
  530   :
  540   .ROMBoxLoad_Help
  550   EQUS "Loads a ROM image into a ROMBox slot":EQUB 13
  560   .ROMBoxLoad_Syntax
  570   EQUS "Syntax: ROMBoxLoad <ROM number> <filename>"
  580   EQUB 0:ALIGN
  590   :
  600   .ROMBoxList_Help
  610   EQUS "Lists loaded ROMBox ROMs":EQUB 13
  620   .ROMBoxList_Syntax
  630   EQUS "Syntax: ROMBoxList"
  640   EQUB 0:ALIGN
  650   :
  660   .ROMBoxPort_Help
  670   EQUS "Sets or displays ROMBox I/O port":EQUB 13
  680   .ROMBoxPort_Syntax
  690   EQUS "Syntax: ROMBoxPort (<port>)"
  700   EQUB 0:ALIGN
  710   :
  720   :
  730   .Initialise
  740   stmfd   (sp)!,{link}
  750   ldr     r0,[r12]
  760   orrs    r0,r0,#0
  770   bne     Reinitialise  ; We already have workspace
  780   mov     r0,#6
  790   mov     r3,#10*4      ; We want 10 words
  800   swi     "XOS_Module"
  810   ldmvsfd (sp)!,{pc}    ; Memory claim failed
  820   str     r2,[r12]      ; Store in w/s pointer
  830   mov     r0,#0         ; Clear ROM pointers
  840   mov     r1,#0
  850   .InitClear
  860   str     r0,[r2,r1]
  870   add     r1,r1,#4
  880   cmp     r1,#10*4      ; 8 ROM pointers, module pointer, OUT copy
  890   bcc     InitClear
  900   .Reinitialise
  910   ldmfd   (sp)!,{pc}
  920   :
  930   :
  940   .Finalise
  950   stmfd   (sp)!,{link}
  960   ldr     r3,[r12]      ; Get pointer to module's workspace
  970   orrs    r3,r3,#0
  980   beq     Finalised     ; No workspace (shouldn't ever get called this way)
  990   mov     r4,#0         ; Loop from workspace+0
 1000   .FinalLoop
 1010   ldr     r2,[r3,r4]    ; Get pointer to ROM space
 1020   orrs    r2,r2,#0      ; Is is zero?
 1030   add     r4,r4,#4      ; Point to next pointer
 1040   movne   r0,#7
 1050   swine   "XOS_Module"  ; Release ROM space if it exists
 1060   cmp     r4,#4*8
 1070   bcc     FinalLoop     ; 8 ROM pointers
 1080   ldr     r2,[r12]      ; Pointer to module's workspace
 1090   mov     r0,#7
 1100   swi     "XOS_Module"  ; Release it
 1110   .Finalised
 1120   ldmfd   (sp)!,{pc}
 1130   :
 1140   :
 1150   .ROMBoxInit           ; *ROMBox <module name>|<address>
 1160   mov pc,link
 1170   :
 1180   :
 1190   .ROMBoxLoad           ; *ROMBoxLoad <ROM number> <filename>
 1200   stmfd (sp)!,{link}
 1210   bl    ROMNumber       ; r6=ROM number, r0=>filename
 1220   mov   r7,r0           ; r7=>filename
 1230   mov   r1,r7
 1240   mov   r0,#5
 1250   swi   "OS_File"       ; Read info on file
 1260   cmp   r0,#1           ; File present?
 1270   bne   ROMNotFound
 1280   cmp   r4,#&4000       ; Check file's length
 1290   beq   ROMLoadOk
 1300   bcs   ROMFileTooLong  ; Can only load up to 16k
 1310   .ROMLoadOk
 1320   ldr   r12,[r12]       ; Get address of workspace
 1330   ldr   r2,[r12,r6,lsl #2] ; Get ROM's address
 1340   orrs  r2,r2,#0
 1350   moveq r0,#6           ; If zero, claim 16k of RMA
 1360   moveq r3,#&4000
 1370   addeq r3,r3,#12       ; Claims &400C bytes to quad-align
 1380   swieq "OS_Module"
 1390   streq r2,[r12,r6,lsl #2]
 1400   ; r7=>filename
 1410   ; r3=index into ROM memory
 1420   ; r2=>ROM memory-12
 1430   ; r1=index into filename
 1440   ; r0=byte
 1450   mov   r3,#0           ; index into ROM memory
 1460   mov   r1,#0           ; index into filename
 1470   .ROMLoadScan          ; Look for end of filename
 1480   ldrb  r0,[r7,r1]
 1490   add   r1,r1,#1
 1500   cmp   r0,#ASC"!"
 1510   bcs   ROMLoadScan
 1520   .ROMLoadScan2
 1530   subs  r1,r1,#1
 1540   beq   ROMLoadName     ; Moved back to start of filename
 1550   ldrb  r0,[r7,r1]
 1560   cmp   r0,#ASC"."
 1570   cmpne r0,#ASC":"      ; Loop back until dot or colon
 1580   bne   ROMLoadScan2
 1590   add   r1,r1,#1
 1600   .ROMLoadName
 1610   ldrb  r0,[r7,r1]
 1620   strb  r0,[r2,r3]
 1630   add   r1,r1,#1
 1640   add   r3,r3,#1
 1650   cmp   r3,#11
 1660   bcs   ROMLoadLeaf
 1670   cmp   r0,#ASC"!"
 1680   bcs   ROMLoadName
 1690   sub   r3,r3,#1
 1700   .ROMLoadLeaf
 1710   mov   r0,#0
 1720   strb  r0,[r2,r3]      ; zero terminator
 1730   ;
 1740   add   r2,r2,#12       ; Point to ROM image start
 1750   mov   r0,#&FF         ; 'LOAD'
 1760   mov   r1,r7
 1770   mov   r3,#0           ; Load to address in r2
 1780   swi   "OS_File"
 1790   sub   r2,r2,#12       ; Point to before ROM memory
 1800   ldmfd (sp)!,{pc}
 1810   :
 1820   .ROMFileTooLong
 1830   ADR   r0,ROMFileTooLongErr
 1840   SWI   "OS_GenerateError"
 1850   .ROMFileTooLongErr
 1860   EQUD 255
 1870   EQUS "ROM too long"
 1880   EQUB 0:ALIGN
 1890   :
 1900   .ROMNotFound
 1910   ADR   r0,ROMNotFoundErr
 1920   SWI   "OS_GenerateError"
 1930   .ROMNotFoundErr
 1940   EQUD 214
 1950   EQUS "ROM not found"
 1960   EQUB 0:ALIGN
 1970   :
 1980   :
 1990   .ROMBoxSave           ; *ROMBoxLoad <ROM number> <filename>
 2000   mov pc,link
 2010   :
 2020   :
 2030   .ROMNumber
 2040   ldrb  r6,[r0,#1]      ; Look at character after current
 2050   cmp   r6,#ASC" "
 2060   addne r0,r0,#1
 2070   bne   ROMNumber       ; Skip until last digit
 2080   ldrb  r6,[r0],#1      ; Get last digit and inc. pointer
 2090   and   r6,r6,#7        ; Reduce to 0..7
 2100   .ROMNumberSpc
 2110   ldrb  r1,[r0]
 2120   cmp   r1,#ASC" "
 2130   addeq r0,r0,#1
 2140   beq   ROMNumberSpc    ; Skip spaces after digit
 2150   mov   pc,link         ; r6=ROM number 0..7
 2160   :
 2170   :
 2180   .ROMBoxList           ; *ROMBoxList
 2190   stmfd (sp)!,{link}
 2200   ldr   r12,[r12]       ; Get workspace pointer
 2210   mov   r3,#0           ; Start with ROM 0
 2220   .ROMListLoop
 2230   ldr   r2,[r12,r3,lsl #2]
 2240   orrs  r2,r2,#0        ; Get address of ROM image
 2250   beq   ROMListEmpty
 2260   orr   r0,r3,#ASC"0"
 2270   swi   "OS_WriteC"
 2280   swi   256+ASC":"
 2290   swi   256+ASC" "      ; Output ROM number
 2300   mov   r1,#11          ; Max of 11 characters
 2310   .ROMListName
 2320   ldrb  r0,[r2],#1      ; Get character
 2330   cmp   r0,#ASC"!"
 2340   bcc   ROMListPad      ; End of leafname
 2350   swi   "OS_WriteC"
 2360   subs  r1,r1,#1
 2370   bne   ROMListName
 2380   .ROMListPad
 2390   swi   256+32          ; Pad with spaces
 2400   add   r2,r2,#1
 2410   subs  r1,r1,#1
 2420   bpl   ROMListPad
 2430   add   r2,r2,#256      ; Point to near begining of ROM image
 2440   .ROMListLook
 2450   add   r2,r2,#1
 2460   ldrb  r0,[r2,#3]      ; Look for four ASCII values
 2470   cmp   r0,#ASC"!":bcc   ROMListLook
 2480   cmp   r0,#ASC"z":bcs   ROMListLook
 2490   ldrb  r0,[r2,#2]
 2500   cmp   r0,#ASC"!":bcc   ROMListLook
 2510   cmp   r0,#ASC"z":bcs   ROMListLook
 2520   ldrb  r0,[r2,#1]
 2530   cmp   r0,#ASC"!":bcc   ROMListLook
 2540   cmp   r0,#ASC"z":bcs   ROMListLook
 2550   ldrb  r0,[r2,#0]
 2560   cmp   r0,#ASC"!":bcc   ROMListLook
 2570   cmp   r0,#ASC"z":bcs   ROMListLook
 2580   .ROMListTitle
 2590   swi   "OS_WriteC"
 2600   add   r2,r2,#1
 2610   ldrb  r0,[r2]
 2620   cmp   r0,#ASC" "
 2630   bcc   ROMListDone
 2640   cmp   r0,#ASC"~"+1
 2650   bcc   ROMListTitle
 2660   .ROMListDone
 2670   swi   "OS_NewLine"
 2680   .ROMListEmpty
 2690   add   r3,r3,#1
 2700   cmp   r3,#8
 2710   bcc   ROMListLoop
 2720   ldmfd (sp)!,{pc}
 2730   :
 2740   :
 2750   .ROMBoxPort           ; *ROMBoxPort (<ioport>)
 2760   mov pc,link
 2770   :
 2780   :
 2790   .Service
 2800   \ In r0=mem[0]
 2810   \    r1=Srv_Z80Reset - &80AC2
 2820   \    r1=Srv_Z80OUT   - &80AC1
 2830   \    r2=port
 2840   \    r3=value
 2850   \    r4=Module base
 2860   \
 2870   stmfd (sp)!,{r1}      ; Check for Srv_Z80OUT or Z80Reset
 2880   and   r1,r1,#&FF000
 2890   cmp   r1,#&80000      ; &80xxx ?
 2900   ldmfd (sp)!,{r1}
 2910   movne pc,link         ; No
 2920   stmfd (sp)!,{r1}
 2930   and   r1,r1,#&FF0
 2940   cmp   r1,#&AC0        ; &80ACx ?
 2950   ldmfd (sp)!,{r1}
 2960   movne pc,link         ; No
 2970   ldr   r12,[r12]       ; Get workspace address
 2980   stmfd (sp)!,{r1}
 2990   and   r1,r1,#&F
 3000   cmp   r1,#&1          ; &80AC1 ?
 3010   beq   Service_Z80OUT
 3020   cmp   r1,#&2          ; &80AC2 ?
 3030   beq   Service_Z80Reset
 3040   ldmfd (sp)!,{r1}
 3050   mov   pc,link         ; No
 3060   :
 3070   :
 3080   .Service_Z80Reset
 3090   stmfd (sp)!,{link}
 3100   mov   r3,#0
 3110   bl    ROMReset        ; Select ROM 0 on Reset
 3120   ldmfd (sp)!,{link}
 3130   ldmfd (sp)!,{r1}      ; Restore call number
 3140   mov   pc,link         ; Don't claim
 3150   :
 3160   :
 3170   .Service_Z80OUT
 3180   ldmfd (sp)!,{r1}
 3190   stmfd (sp)!,{r2}        ; Now check for port &xxFD
 3200   and   r2,r2,#&FF
 3210   cmp   r2,#ioport%AND&FF ; port &xxFD ?
 3220   ldmfd (sp)!,{r2}
 3230   movne pc,link           ; No
 3240                           ; OUT &xxFD,romnumber*16 (missing b5)
 3250   ; r0=z80mem[0]
 3260   ; r1=corruptable
 3270   ; r2=corruptable
 3280   ; r3=OUT value [ROM number] - must preserve!
 3290   ; r4=corruptable
 3300   ; stack balanced
 3310   .ROMSelect
 3320   ldrb  r2,[r12,#9*4]   ; Last OUT value
 3330   cmp   r3,r2           ; Is this OUT same as last?
 3340   beq   ROMSelected     ; Already paged in
 3350   .ROMReset
 3360   strb  r3,[r12,#9*4]   ; Store flags word
 3370   movs  r2,r3,lsr #5    ; r2=OUT value DIV 32, Cy=OUT value b4
 3380   and   r2,r2,#6        ; ROM number = 0,2,4,6
 3390   orrcs r2,r2,#1        ; ROM number = 0..7
 3400   ;
 3410   ;cmp   r2,#8
 3420   ;and   r2,r2,#13
 3430   ;orrcs r2,r2,#2        ; ROM number = 0..7
 3440   ldr   r2,[r12,r2,lsl #2]
 3450   orrs  r2,r2,#0        ; Get address of ROM memory
 3460   moveq r1,#0           ; No ROM in this slot, so
 3470   moveq pc,link         ; claim and exit doing nothing
 3480                         ; Enter fast quad-word copy loop
 3490   stmfd (sp)!,{r3-r10}  ; Save everything for speed
 3500   add   r8,r2,#12       ; Point to quad-aligned ROM image start
 3510   mov   r9,r0           ; Point to z80mem[0]
 3520   mov   r10,#0          ; Copy address pointer
 3530   ;
 3540   ; r8=ROM address - src
 3550   ; r9=z80mem[0]   - dst
 3560   ; r10=loop offset
 3570   ;
 3580   .ROMReadLoop
 3590   ldmia r8!,{r0-r7}     ; Fetch 8 quad-aligned words
 3600   stmia r9!,{r0-r7}     ; Store them (may not be quad-aligned)
 3610   add   r10,r10,#32
 3620   cmp   r10,#&4000      ; Loop for 16k
 3630   bcc   ROMReadLoop
 3640   ldmfd (sp)!,{r3-r10}  ; Get registers back
 3650   :
 3660   .ROMSelected
 3670   mov r1,#0             ; Claim the call
 3680   mov pc,link
 3690   :
 3700   ALIGN
 3710 ]NEXT
 3720 OSCLI"SAVE ROMBox "+STR$~mcode%+" "+STR$~O%
 3730 *SetType ROMBox Module
 3740 *Stamp ROMBox
 3750 END