REM > Host40/src REM REM Spectrum host code for BBC BASIC REM Interface between &FFxx entries and Spectrum system REM v0.33 - Spare space in BBC BASIC compacted, Header added REM v0.34 - Trimmed superflous code, *Quit enters OSQUIT, RdHex uses BBC BASIC code REM v0.35 - Checks Z/SPACE on startup, &0060 entry block REM v0.36 - Rearrange &FFxx entry points REM v0.37 - Generalised/patchable ROMPAGE routine REM Rewritten reset code, IRQFix vectors via &FDFD with I=&FD REM v0.38 - ROMPAGE usable with Spectrum SE and external &FD ROMBox REM v0.39 - Ensures ZXROM and Screen paged in as appropriate REM - Looks for ROMs, doesn't need to be told what ROM it is in REM - File code deselects shadow screen REM v0.40 - Uses BBC BASIC v1.25, LOAD no longer requires returned length REM - Bit of tidying up, COLOUR implements flash/bright REM - Tidied up osfile/osfsc, implemented &FFFFxxxx, &0000xxxx load addresses REM - Escape within Load/Save no longer causes a crash REM - Displays memory size on startup, DOCK RAM used when HOME ROMs used REM - Removed null language REM - &FFFFxxxx/&0000xxxx corrected REM - CheckBreak moved to ZXROM REM - WORKSP=><80>+1 instead of =>+1 in microdrive code REM - Microdrive Load/Save/Cat works REM - If Int#1 present, set CURDRV="1" REM * Pass service calls arounds ROMs REM ? Use &10BF for paging if ZXCF present REM ? Add keyclick in INPUT : ver$="0.40":se%=FALSE:hibasic%=FALSE : A%=0:X%=1:os%=(USR&FFF4 AND &FF00)DIV256:IF HIMEM>&FFFF:PRINT"Running Z80...":SYS "OS_GetEnv" TO A$:OSCLI"TextToBas "+MID$(A$,INSTR(A$," ",1+INSTR(A$," ")))+" -crunch *Z80":END unix%=os%=8:quit%=?&80<>0:?&80=0 ON ERROR REPORT:PRINT" at line ";ERL:IFNOTquit%:END ELSE *Quit DIM mcode% &C00,Name$(2),Block%(2),len%(2),free%(2),Workspace%(2),wslen%(2):elen%=FALSE : _file%=TRUE :REM FALSE=no *Load/*Save/*Go/RdHex _fx%=TRUE*0 :REM FALSE=no *Fx/RdDec : : PROCAssemEquates FOR P=0 TO 1:opt%=P*3+4 PROCAssemHdr PROCAssemHost IFelen%:P=2 NEXT IF extra1%<0:PRINT"Unused space at &FDFD: ";-extra1%;" bytes" FOR A%=1 TO 2:PRINT"Unused code space ";A%;": ";free%(A%);" bytes":NEXT PRINT"Unused source memory: &";~HIMEM-END;" bytes" IFquit%:*Quit END : DEFPROCAssemEquates REM Filenames to save code blocks as: Name$(1)="Hdr" Name$(2)="Host" : REM Memory to put code in: Block%(1)=&0000:len%(1)=&100-Block%(1):mtop%=&F000:IFhibasic%:Block%(1)=&BB00:mtop%=&B800 Block%(2)=&F4E0:len%(2)=&10000-Block%(2) REM BBC BASIC 1.25 now spare from &F4C6 onwards REM BBC BASIC 1.26 now spare from &F4B5 onwards REM High MOS workspace runs to &F4DF, MOS code can't start earlier : REM High workspace areas: Workspace%(2)=&F400:wslen%(2)=Block%(2)-Workspace%(2):REM MOS workspace Workspace%(1)=&F300:wslen%(1)=&100 :REM MOS workspace Workspace%(0)=&F000:wslen%(0)=&300 :REM Language workspace REM TO DO: Language workspace must be at HIMEM : REM Workspace areas: inpbuf%=&F200:NMI=&F300:errbuf%=&F3E0:ctrl=&F4CE : REM VDU Workspace: UDC90=Workspace%(2) :REM f400-f47f VduBase =UDC90+128 VduQ =VduBase+0 :REM f480 VduStatus=VduQ+1 :REM f481 VduQStart=VduStatus+1 :REM f482 VduQEnd =VduQStart+9 :REM f48b VduJmp =VduQEnd+0 :REM f48b VduJmpHi =VduJmp+1 :REM f48c REM f48d,e,f - spare : REM MOS Workspace: ROMTYPES=&F490 MOSWS =&F4A0 TICKER =MOSWS+0 :REM f4a0 COPYADD =MOSWS+2 :REM f4a2 : REM Filing system workspace: FSWS =MOSWS+8 :REM f4a8 CURDRV =FSWS+0 :REM f4a8 CATFLG =FSWS+1 :REM f4a9 REM Spare: f4aa, f4ab LD_HDR =FSWS+4 :REM f4ac LD_NAME =LD_HDR+1:LD_LEN =LD_HDR+11:LD_LOAD=LD_HDR+13:LD_EXEC=LD_HDR+15 SV_HDR =LD_HDR+17 SV_NAME =SV_HDR+1:SV_LEN =SV_HDR+11:SV_LOAD=SV_HDR+13:SV_EXEC=SV_HDR+15 : REM Spectrum system variables: CHARS=&5C36:ERR_SP=&5C3D:UDC=&5C7B:DFCC=&5C84 : REM Routines in BBC BASIC v0.36+/v1.25 BasicEntry=&0100:IFhibasic%:BasicEntry=&BC00 Off=BasicEntry-&0100 BBCHex0=&2CFE+Off :REM L2D10 - zeros HL, HL' and C BBCHexRd=&2F0C+Off :REM L2F1E - get from (IY), check if valid hex BBCHexScan=&1F7E+Off:REM L1F90 - loop(inc IY, HLHL*16, add digit) : REM High memory locations needed by low memory startup code: ROMSEL=&FF8F:ZXROM=&FF90:BBCROM=&FF91:ROMPAGE=&FF92:OSCOLD=&FF98 : REM Filename prefix: Pre$="Lo.":IFhibasic%:Pre$="Hi." ENDPROC : DEFFNif(A%):IFA%:z%=-1:=opt% ELSE z%=P%:=opt% DEFFNendif:IFz%=-1:=opt% ELSE z%=P%-z%:P%=P%-z%:O%=O%-z%:=opt% DEFFNelse:IFz%=-1:z%=P%:=opt% ELSE z%=P%-z%:P%=P%-z%:O%=O%-z%:z%=-1:=opt% DEFFNm(A$)=LEFT$(A$,LENA$-1)+CHR$(ASCRIGHT$(A$,1)OR128) : DEFPROCBlockStart(B%):O%=mcode%:P%=Block%(B%) start%=P%:load%=P%:IFNOThibasic%:load%=load%AND&3FFF ENDPROC : DEFPROCBlockSave(B%,extra%) IFB%:end%=P%OR(&10000 AND(P%<&8000 AND start%>&7FFF)):free%(B%)=len%(B%)+start%-end%-extra% IFB%:IFfree%(B%)<0:PRINT"Code overrun in block ";B%;" of ";-free%(B%);" bytes":elen%=TRUE REM IFB%:IFfree%(B%)>=0:P."Spare bytes in block ";B%;": ";free%(B%) IFB%:IFNOTelen%:load%=load%OR&30000:X%=O%:Y%=X%DIV256:A%=0:$(X%+20)=Pre$+Name$(B%):!X%=X%+20:X%!&A=mcode%:X%!&E=O%:X%!2=load%:X%!6=load%:IFP:CALL &FFDD ENDPROC PROCBlockStart(B%+1):ENDPROC : : DEFPROCAssemHdr PROCBlockStart(1):[OPT opt% .reset ; Initally paged in at address &0000 .rst00 DI:JR Reset1 ; Jump forward to deal with rst 0 and reset NOP:NOP ; Fake an IOBYTE and DRIVE for CP/M ; OPT FNif(hibasic%) JP &FF95 OPT FNelse JR reset+&FFFFFF95:NOP OPT FNendif ; JR wraps round backwards to SERVICE entry. Must be ; other than a JP to prevent the destination field ; being used as MEMTOP, as this is ROM. ; .rst08 LD (&FF81),A ; Store A in TempA JR rst8a ; Jump ahead to continue NOP ; .PCtoHL ; CALL &000E returns the PC in HL POP HL ; Effectively a LD HL,PC instruction .HLtoPC ; CALL &000F jumps to the address in HL JP (HL) ; Effectively a CALL (HL) instruction ; .rst10 ; May use these later LD (&FF81),A ; eg for CALBAS, and similar LD A,&82 JP &FF95 ; .rst18 LD (&FF81),A LD A,&83 JP &FF95 ; .rst20 LD (&FF81),A LD A,&84 JP &FF95 ; .rst28 LD (&FF81),A LD A,&85 JP &FF95 ; .rst30 LD (&FF81),A LD A,&86 .rst30a JP &FF95 ; .rst38 JP OSERROR ; Jump to OSERROR to generate an error ; .rst8a ; Continue RST &08 LD A,&81 ; Signal 'RST08' JR rst30a ; Jump to vector chain ; ; ; RESET and RST &00 ; ================= .Reset1 LD SP,&5C00 ; Set up a temporary stack : OPT FNif(se%) LD A,&7F:IN A,(&FE) ; Check SPACE key AND 1:JR Z,ROMEnter ; SPACE pressed, boot BBC ROM OPT FNelse LD A,&FE:IN A,(&FE) ; Check Z key AND 2:JR NZ,ROMEnter ; Z not pressed, boot BBC ROM OPT FNendif : CALL MOSCOPY ; Copy paging routine to high memory LD HL,0:PUSH HL ; Make return address &0000 JP PageZX ; Select ZX ROM and enter it : .MOSCOPY OPT FNif(NOT hibasic%) LD HL,&3400 ; Copy MOS code from ROM LD DE,&F400 ; Copy to RAM in high memory LD BC,&10000-&F400 LDIR OPT FNendif .ret RET ; Do nothing if running in RAM : DEFM STRING$(&60-(P%AND&FF),CHR$255) : : ; ROM Entry Points ; ================ .ROMEnter ; &0060 - Enter this ROM JP ResetBASIC ; Enter BASIC .ROMService ; &0063 - Service Calls JP ret ; No service handler .nmi ; &0066 - NMI call JP NMI ; Jump to NMI space. Default is RETN ; .ResetBASIC ; Reset system and enter BBC BASIC LD SP,&5C00 ; Set up a temporary stack CALL MOSCOPY ; Copy MOS code to high memory JP OSCOLD ; Enter main code : : ; Use remaining space for code called from high memory ; ---------------------------------------------------- .InitSpectrum LD HL,&5C00:LD DE,&5C01 LD BC,&6000-&5C00-1 LD (HL),0:LDIR ; Clear Spectrum workspace LD HL,&45ED:LD (NMI),HL ; Put a RETN in NMI space LD A,8:LD (&5C6A),A ; Turn CAPS on LD HL,&5FFF:LD (&5CB4),HL ; Set P_RAMT LD (&5CB2),HL:LD (HL),&3E ; Set RAMTOP ;DEC HL:;DEC HL ; This is done by InitErr ;DEC HL:;LD (ERR_SP),HL ; Set up Spectrum error vector LD HL,&3C00:LD (&5C36),HL ; Set up CHARS ;LD HL,&0040:;LD (&5C38),HL ; Set RASP and PIP LD HL,&0523:LD (&5C09),HL ; Set REPDEL and REPPER LD A,&CC:LD (&5C3B),A ; Set up FLAGS XOR A:DEC A LD (&5C00),A:LD (&5C04),A ; Initialise KEYSTATE LD A,&FD:LD I,A:IM 2 ; Set up interrupt to vector via &FDFE or &FDFF LD DE,&5CB6:LD (&5C4F),DE ; Set up CHANS, returning DE JP &FFBF ; Call InitErr, set up error vectors : ; Call bits within BASIC to scan hex ; ---------------------------------- .GetHexBase push iy:push hl:pop iy ; Copy HL to IY exx:push hl:call BBCHex0 ; Zero registers call BBCHexRd:JP C,BadNumber ; Get hex digit call BBCHexScan ; Scan hex ld (ix+3),h:ld (ix+2),l pop hl:exx ld (ix+1),h:ld (ix+0),l push iy:pop hl:pop iy ; Copy IY back to HL RET : : ; 19 spare bytes here ; ]:z%=&00F8-(P%AND&FF):extra0%=-z%:[OPT opt% ; DEFM STRING$(z%,CHR$255) : ; Some pointers to identify this as a valid ROM. ; DEFB &41 ; Language, System DEFB &12 ; Version 1.20 DEFW BasicEntry+&56 ; -> "Acorn BBC BASIC..." DEFW BasicEntry+&76 ; -> "(C)...", checked for validity DEFW &0000 ; Spare ; ] PROCBlockSave(1,extra0%) ENDPROC : : DEFPROCAssemHost PROCBlockStart(2):[OPT opt% : ; 26 bytes spare here in ROM space : ; Host Filing System ; ================== : ; FSC - Misc. file system calls ; ----------------------------- .osFSC ; *Cat, *Run, *com - ignored ; A=2 - */ ; A=3 - *command ; A=4 - *Run ; A=5 - *Cat ; CP 5:JP Z,Catalogue ; *CAT CP 2:JR Z,osFSC2 ; */ CP 3:JR Z,osFSC2 ; *command CP 4:RET NZ ; Only support *CAT, *command, */ and *Run : : ; FSCV 2 - */, *RUN, *command ; --------------------------- ; Load and run a file from storage ; .osFSC2 CALL ScanFilename ; Parse filename at HL LD A,(SV_NAME) ; Get first character of name CP 32:JR NZ,osFSCRun ; There is a name, so not *D: LD A,C .osFSC2drv LD (CURDRV),A ; Set current drive XOR A:RET ; Claim call .Drive LD A,(HL):JR osFSC2drv .osFSCRun LD A,&FF:LD (SV_EXEC),A ; Load to file's load address LD B,&F0:JR osFileFF ; Ensure loaded to IO memory : : ; FILE - Operate on whole files ; ----------------------------- .osFile INC A:CP 2:JP C,osFileFF DEC A:RET ; Unsupported, return A preserved .osFileFF PUSH IX:PUSH HL:PUSH DE PUSH BC:PUSH AF:INC A CALL NZ,ScanFilenameIX ; Scan filename if not already done ; SV_HDR holds filename and addresses ; B=memory flags to pass to ROMPAGE ; C=drive to access ; DE=start address for save : POP HL ; Get command back &FF/&00/&01 for Run/Load/Save PUSH IY:LD IY,&5C3A ; Set IY for ZXROM calls LD A,(ROMSEL):PUSH AF ; Save current ROM LD A,(ZXROM):AND &0F OR B:CALL ROMPAGE ; Page in ZXROM and selected memory DEC H:JP Z,osSave ; H=&FE/&FF/&00 for Run/Load/Save : ; Run file, Load file ; ------------------- .osLoad PUSH HL:LD A,C ; Save Run/Load flag ;CP ASC"S":;JR Z,SerLoad ; Drive S - Serial CP ASC"T":JR Z,TapeLoad ; Drive T - Tape CP ASC"1":JR C,TapeLoad ; Drive 1..8 - Microdrive CP ASC"9":JR C,MDLoad : ; Load a file from Tape ; --------------------- .SerLoad .TapeLoad CALL TapeHeader ; Wait for a tape header LD HL,LD_NAME-1 ; Point to loaded header LD DE,SV_NAME-1 ; Point to OSFILE header LD B,11 ; Match type byte and ten characters .TapeName LD A,(DE):CP (HL) JR Z,TapeMatch CP ASC"#":JR Z,TapeMatch CP ASC"*":JR Z,TapeMatched CP ASC"@":JP C,TapeLoad ; No match, go back for another XOR &20:CP (HL) ; Try changing case JP NZ,TapeLoad ; No match, go back for another .TapeMatch INC DE:INC HL DJNZ TapeName ; Loop back to match all chars : .TapeMatched CALL LoadAddrs LD A,&FF:SCF:CALL &0556 ; Load data portion LD HL,(LD_EXEC) ; HL=entry address .TapeExec XOR A:POP DE:INC D ; Get exec address and Run/Load flag PUSH DE:CALL NZ,JumpHL ; If *Run, call the code, A=0 no code header POP DE ; HL=entry address, (LPTR)=>parameters, Z=0, DE,BC=undefined .TapeDone CALL PageZXRestore ; Restore IY and ROM paging LD A,D:INC A ; A=0 (run), A=1 (load) POP BC:POP DE:POP HL POP IX:RET : .TapeHeader CALL TestEscape ; Check for Escape LD IX,LD_HDR:LD DE,17 XOR A:SCF:CALL &0556 ; Load a header JR NC,TapeHeader ; Loop back if nothing loaded RET ; Z80Em seems to return C even if nothing loaded : .LoadAddrs LD DE,(LD_LEN) LD IX,(LD_LOAD) ; Get file's load address and length LD A,(SV_EXEC) AND A:RET NZ ; Use these if SV_EXEC<>0 LD IX,(SV_LOAD) ; Load to specified address RET : ; Save to Microdrive ; ------------------ .MDSave LD (SV_LOAD),DE ; Start address to save from LD B,&FF:PUSH BC:LD B,&F8 ; B='SAVE', C='not Run' JR MDLoadSave : ; Load from Microdrive ; -------------------- .MDLoad LD B,&EF ; B='LOAD' .MDLoadSave ; A=drive, B=LOAD/SAVE, C=Run/NoRun LD DE,MDHdr1:CALL MDInit ; Wake up Int1, copy initial command PUSH BC:LD (IX+8),A ; Save Load/Save, set drive LD HL,SV_NAME:LD B,10 ; Copy up to 10 characters .MDLoadLp LD A,(HL) CP ASC"!":JR C,MDLoad2 LD (DE),A:INC DE:INC HL DJNZ MDLoadLp .MDLoad2 PUSH DE:POP IX ; IX points to end of filename LD HL,MDHdr2 LD BC,17:LDIR ; Copy '"CODE 0,0' LD HL,(SV_LOAD) LD (IX+6),L:LD (IX+7),H ; Set load address LD HL,(SV_LEN) LD (IX+14),L:LD (IX+15),H ; Set length POP AF ; Get SAVE/LOAD byte back CP &F8:JR Z,MDLoadAddr ; Save - use full line LD (IX+9),13 ; Prepare to Load to address LD A,(SV_EXEC) AND A:JR Z,MDLoadAddr LD (IX+2),13 ; Truncate after 'CODE' .MDLoadAddr CALL MDCommand ; Execute command at E_LINE LD HL,(&5CE9) ; HL=MD_LOAD ;LD DE,(&5CEB) ; DE=MD_EXEC ;INC DE:;LD A,D:;OR E ; Check if MD_EXEC=&FFFF ;DEC DE:;JR Z,MDLoadExec ; If MD_EXEC=&FFFF, use MD_LOAD ;EX DE,HL ; HL=MD_EXEC ;.MDLoadExec JP TapeExec ; HL=entry point, pushed DE=Run/Load flag : .MDInit ; A=drive, B=LOAD/SAVE, C=Run/NoRun, DE=>Command PUSH BC:PUSH DE ; Wakeup trashes all registers except AF RST &08:DEFB &31 ; Wake up Interface 1 LD IX,(&5C59):POP HL ; Use E_LINE to build command PUSH IX:POP DE:INC DE ; IX points to start of line LD BC,11:LDIR ; Copy '*"m";VAL"0";"' POP BC:LD (IX+0),B ; Store command token RET : ; Execute a Interface 1 command at E_LINE ; --------------------------------------- ; NB! Interface 1 commands *ALWAYS* return via (ERR_SP), even when OK ; DE=><80> to be put in .MDCommand EX DE,HL:CALL &16B3 ; Insert <80>, point WORKSP to end of line SET 7,(IY+1) ; Set 'Not syntax checking' SET 2,(IY+124) ; Ensure ERR_SP used for errors LD (IY+10),1 ; Set ZXBasic statement 1 LD HL,(ERR_SP):PUSH HL ; Save ERR_SP LD HL,&FFFE:ADD HL,SP ; Point to where return address will be LD (ERR_SP),HL ; Set up new ERR_SP CALL &1B8A ; Execute command at E_LINE LD A,(23610) ; Get ERR_NR INC A:JR Z,MDNoError ; No error occured RES 1,(IY+124) ; Ensure autoload trap avoided LD HL,&0046:LD (&5CED),HL ; Point to part of error handler EX AF,AF' ; Save ERR_NR+1 (Next call doesn't like PUSH) RST &08:DEFB &32 ; Reclaim channels and turn off drives, reset ERR_NR EX AF,AF' ; Get ERR_NR+1 back JP SpectrumErrorA ; Translate error ; ERR_NR+1=&15 - BREAK ; ERR_NR+1=&18 - Drive missing ; ERR_NR+1=&0C - Interface 1 error ; Could be Invalid name/Missing name/Drive write protected/Microdrive full/Microdrive not present ; File not found/Wrong file type/Writing to a 'read' file .MDNoError POP HL:LD (ERR_SP),HL ; Restore ERR_SP RET : : .MDHdr1 DEFM "*":DEFB &22:DEFM "m":DEFB &22:DEFM ";" ; *"m"; .MDHdr3 DEFB &B0:DEFB &22:DEFM "1":DEFB &22:DEFM ";" ; VAL"1"; DEFB &22 ; " .MDHdr2 DEFB &22:DEFB &AF:DEFM "0":DEFB &0E:DEFB &00 ; "CODE0<0e><00> DEFB &00:DEFB &00:DEFB &00:DEFB &00:DEFM "," ; <00><00><00><00><00>, DEFM "0":DEFB &0E:DEFB &00:DEFB &00:DEFB &00 ; <00>,0<0e><00><00><00> DEFB &00:DEFB &00 ; <00><00> : : ; Save file ; --------- .osSave LD A,C ;CP ASC"S":;JR Z,SerSave ; Drive S - Serial CP ASC"T":JR Z,TapeSave ; Drive T - Tape CP ASC"1":JR C,TapeSave ; Drive 1..8 - Microdrive CP ASC"9":JP C,MDSave : ; Save a file to Tape ; ------------------- .SerSave .TapeSave CALL ZXSave LD D,0:JP TapeDone ; Exit, returning A=1 .ZXSave PUSH DE ; Stack data start address LD IX,SV_HDR:JP &0984 ; Save header and data : : ; Scan a filename into SV_HDR ; --------------------------- ; On entry, IX or HL=>filename ; On exit, SV_HDR contains filename info ; SV_HDR =&03 ; SV_NAME=filename ; SV_LEN =end-start - IX+14/IX+15 - IX+10/IX+11 ; SV_LOAD=load - IX+2/IX+3 ; SV_EXEC=exec - IX+6/IX+7 ; DE =start - IX+10/IX+11 ; HL =length ; BC =memory/drive ; A =undefined ; .ScanFilenameIX PUSH HL:POP IX LD L,(IX+0):LD H,(IX+1) ; Get address of filename .ScanFilename CALL SkipSpace LD C,(HL):INC HL ; Get first character LD A,(HL):INC HL ; Get second character CP ASC":":JR Z,ScanName1; Drive specifier, jump to do rest of name DEC HL:DEC HL ; Point back to first character LD BC,(CURDRV) ; Get current drive .ScanName1 BIT 6,C:JR Z,ScanName2 RES 5,C:.ScanName2 ; Ensure upper case LD DE,SV_NAME ; Point to filename buffer LD B,11 ; 10+1 characters .ScanNameLp1 LD A,(HL) CP ASC"!":JR C,ScanNameEnd LD (DE),A:INC HL:INC DE DJNZ ScanNameLp1 .BadFilename CALL OSERROR DEFB 204:DEFM "Bad filename":NOP : .ScanNameEnd LD A,ASC" " .ScanNameLp2 LD (DE),A:INC DE DJNZ ScanNameLp2 CALL SkipSpace:LD (LPTR),HL ; Update LPTR LD L,(IX+2):LD H,(IX+3) ; HL=load LD (SV_LOAD),HL LD L,(IX+6):LD H,(IX+7) ; HL=exec LD (SV_EXEC),HL LD E,(IX+10):LD D,(IX+11); DE=start LD L,(IX+14):LD H,(IX+15); HL=end AND A:SBC HL,DE ; HL=length LD (SV_LEN),HL LD A,3:LD (SV_HDR),A ; Type=CODE LD A,(IX+4):AND &F0:LD B,A ; B=memory flags from load &--X-aaaa RET : : ; *CAT subcode ; ------------ .Catalogue ; Makeshift routine CALL ScanFilename ; Parse filename at HL CALL PageZXSave:LD A,C ; Page in ZXROM and screen ;CP ASC"S":;JR Z,SerCat ; Drive S - Serial CP ASC"T":JR Z,TapeCat ; Drive T - Tape CP ASC"1":JR C,TapeCat ; Drive 1..8 - Microdrive CP ASC"9":JP C,MDCat : ; Catalogue from tape ; ------------------- .SerCat .TapeCat XOR A:LD (CATFLG),A ; Clear catalogue flag .TapeCatLoop CALL TapeHeader ; Wait for a tape header XOR A:LD B,17 .TapeCatCheck1 ; Form a checksum from header ADD A,(HL):ADD A,B:INC HL DJNZ TapeCatCheck1 LD B,A:LD A,(CATFLG) AND A:JR Z,TapeCatFirst ; First header, so save checksum CP B:JR Z,TapeCatLooped ; Sums match, looped back to start entry JR NZ,TapeCatSubseq .TapeCatFirst LD A,B:LD (CATFLG),A .TapeCatSubseq LD HL,LD_HDR:LD B,10 LD A,(HL):CALL PRHEX ; Block type CALL PrSpace .TapeCatPrLoop INC HL:LD A,(HL):CALL OSWRCH DJNZ TapeCatPrLoop LD HL,(LD_LOAD):CALL PrSpcHex ; Load LD HL,(LD_EXEC):CALL PrSpcHex ; Exec LD HL,(LD_LEN):CALL PrSpcHex ; Length CALL OSNEWL:JR TapeCatLoop : ; Catalogue microdrive ; -------------------- .MDCat LD B,&CF:LD DE,MDHdr3 ; B='CAT', DE=>'VAL"0".....' CALL MDInit ; Wake up Int#1, copy command LD (IX+3),A ; Store drive number LD (IX+5),13 ; Terminate line CALL MDCommand ; Then fall through to... ;JR TapeCatLooped : .TapeCatLooped CALL PageZXRestore ; Restore ROM, then fall into... : : .osArgs .osFind:XOR A ; Open=0 .osGBPB .osBPut .osBGet:RET : : ; Find default drive ; ------------------ .FSInit LD BC,&FF00 ; Loop 255 times .FSInitLp IN A,(&F7):OR C:LD C,A ; See if Interface 1 responds DJNZ FSInitLp:LD A,C:OR A LD A,ASC"T":RET M ; Return 'T' if b7 not zero LD A,ASC"1":RET ; Return '1' : : .TestEscape LD A,(ESCFLG) ADD A,A:RET NC ; Exit if no escape state .Escape LD A,&7E:CALL OSBYTE ; Ack. Escape state CALL OSERROR DEFB 17:DEFM "Escape":NOP : : ; OSCLI - *Commands - Execute command line ; ======================================== : ; *Command table - minimum needed to interface to the system ; ---------------------------------------------------------- .CommandTable DEFM FNm("CAT") :DEFW Cat .BasicString :DEFM FNm("BASIC"):DEFW Basic DEFM FNm("DRIVE"):DEFW Drive DEFM FNm("ESC") :DEFW Null;Esc OPT FNif(_fx%) DEFM FNm("FX") :DEFW Fx OPT FNelse DEFM FNm("FX") :DEFW Null OPT FNendif OPT FNif(_file%) DEFM FNm("GO") :DEFW Go DEFM FNm("HELP") :DEFW Help DEFM FNm("LOAD") :DEFW Load DEFM FNm("QUIT") :DEFW OSQUIT DEFM FNm("RUN") :DEFW Run DEFM FNm("SAVE") :DEFW Save OPT FNelse DEFM FNm("HELP") :DEFW Help DEFM FNm("QUIT") :DEFW OSQUIT DEFM FNm("RUN") :DEFW Run OPT FNendif DEFB &FF ; End marker : .HelpTable ; No *HELP strings : ; CLI - Execute command line ; -------------------------- ; HL=>string ; BC, DE, IX, IY preserved ; HL, AF can be corrupted .osCLI DEC HL .SkipStars CALL SkipSpace1 CP ASC"*":JR Z,SkipStars CP ASC"/":JR Z,Slash CP ASC"|":RET Z ; Comment CP 13:RET Z ; Null string PUSH DE LD DE,CommandTable ; Now look through table : .CommandLoop PUSH HL .comm_lp2 LD A,(HL):CP ASC".":JR Z,dot_found LD A,(DE):AND 127 CP (HL):JR Z,char_ok XOR 32:CP (HL):JR Z,char_ok \ char doesn't match CALL SkipEntry POP HL INC DE:INC DE LD A,(DE):CP &FF JR NZ,CommandLoop ; Not found in internal commands ; HL=>command string POP DE ;EX DE,HL ;.osCLIPassOn ;EX DE,HL .osCLIexternal LD A,4:CALL SERVICE; Call other modules AND A:RET Z ; Exit if matched LD A,3 .osCLIfsc CALL osFSC ; Call filing system AND A:RET Z ; Exit if matched .BadCommand CALL OSERROR DEFB 254:DEFM "Bad command":NOP : : ; */name () ; --------------------- .Slash CALL SkipSpace1 LD A,2:JR osCLIfsc : : ; *Run () ; -------------------------- .Run LD A,4:JR osCLIfsc : : ; *Cat () ; ------------ .Cat LD A,5:JR osCLIfsc : : .dot_found INC HL CALL SkipEntry JR entry_found .char_ok LD A,(DE) INC DE:INC HL CP &80:JR C,comm_lp2 .entry_found CALL SkipSpace ; Z= met LD (LPTR),HL ; Pointer to command tail POP HL PUSH BC PUSH IX LD BC,command_ret:PUSH BC LD A,(DE):LD C,A:INC DE LD A,(DE):LD B,A PUSH BC LD D,H:LD E,L LD HL,(LPTR) \ HL=first char of parameters \ DE=first char of command \ BC=addr of command \ F=Z if null parameters \ SP=>addr,commnd_ret,IX,BC,DE RET .command_ret POP IX POP BC POP DE RET .SkipEntry LD A,(DE):INC DE CP &80:JR C,SkipEntry RET ; DE=byte after b7=1 : : OPT FNif(_file%) ; Skip past a name ; ---------------- .SkipName ld a,(hl) cp 13:ret z inc hl cp 32:jr nz,SkipName dec hl ; Then skip any spaces OPT FNendif : ; Skip past any spaces ; -------------------- .SkipSpace1 inc hl .SkipSpace ld a,(hl) cp 32:jr z,SkipSpace1 cp 13 ret : : ; Filing System Commands ; ~~~~~~~~~~~~~~~~~~~~~~ ; *Load () - Load file to memory ; ------------------------------------------- OPT FNif(_file%) .Load ld a,&FF:ld (ctrl+6),a ; Set to load to file's own load address ld (ctrl),hl ; Point to filename call SkipName:jr z,Load2 ; Z=, no address follows call GetHexIX ; Read load address to ctrl+2 inc (ix+4) ; Set to load to supplied address .Load2 ld A,&FF:jr Save5 ; Set A=&FF for Load, jump to OSFILE : : ; *Save ( ()) - Save memory ; ---------------------------------------------------------- .Save ld (ctrl),hl ; Point to filename call SkipName ; Move past filename call GetStartEnd ; Read start and end, set exec=reload=start call SkipSpace:jr z,Save4 ; No more parameters, jump to save ld ix,ctrl+6:call GetHex ; Read exec address jr z,Save4:call GetHexIX ; Read reload address jp nz,BadCommand ; More parameters, generate error .Save4 xor a ; Set A=0 for Save .Save5 ld hl,ctrl:jp OSFILE ; Point to control block and call OSFILE : : ; *Go () - Call memory location ; ----------------------------------- .Go JP Z,CLICOM ; No parameters, enter CLICOM CALL GetHexIX:LD (LPTR),HL ; Should allow &FFrraaaa : ;ld a,(ROMSEL) ;push af ; Save current ROM number ;ld hl,(ctrl+4) ; Get address high word ;ld a,l ; Get ROM number into A ;inc h ;call z,ROMPAGE ; Page ROM in if &FFxxxxxx ;ld hl,(ctrl+2) ; Get call address ;call JumpHL ;pop af ; Get saved ROM number ;jp ROMPAGE ; Page it back in and return : LD HL,(ctrl+2):JP (HL) ; Jump to address : : .GetStartEnd call GetHexIX:\ load= ld de,(ctrl+2):ld (ctrl+6),de:ld (ctrl+10),de ld de,(ctrl+4):ld (ctrl+8),de:ld (ctrl+12),de \ exec,start also= ld a,(hl):;call SkipSpace cp ASC"+":jr nz,start_end2 inc hl .start_end2 push af ld ix,ctrl+14 call GetHex:\ end= or pop af:jr nz,start_end3 push hl ld hl,(ctrl+10):ld de,(ctrl+14) add hl,de:ld (ctrl+14),hl ld hl,(ctrl+12):ld de,(ctrl+16) adc hl,de:ld (ctrl+16),hl \ =+ pop hl .start_end3 ret \ (ctrl+2), (ctrl+6), (ctrl+10)=start \ (ctrl+14)=end : ; Read a hex value ; ---------------- .GetHexIX ld ix,ctrl+2 OPT FNendif .GetHex ; Ensure label OPT FNif(_file%) call SkipSpace ld a,(ROMSEL):push af ; Save current ROM call PageBBC ; Page in BBC BASIC CALL GetHexBase ; Call BASIC to scan hex pop af:CALL ROMPAGE ; Restore current ROM OPT FNendif JP SkipSpace : : .BadNumber CALL OSERROR DEFB 252:DEFM "Bad number":NOP : : ; *Fx - Issue an OSBYTE call ; -------------------------- .Fx OPT FNif(_fx%) LD BC,&0000 ; Default no parameters CALL GetDec:PUSH AF CALL SkipSpace:JR Z,FxCall ; No more parameters CP ASC",":CALL SkipSpace1 ; Skip past any comma CALL GetDec:LD C,A ; Fetch first parameter CALL SkipSpace:JR Z,FxCall ; No more parameters CP ASC",":CALL SkipSpace1 ; Skip past any comma CALL GetDec:LD B,A ; Fetch second parameter .FxCall PUSH BC:POP HL ; Pass parameters to HL ;POP AF:;PUSH AF:;CALL PRHEX ; Debug output ;LD A,B:;CALL PRHEX ;LD A,C:;CALL PRHEX ;LD A,&3A:;CALL OSWRCH POP AF:JP OSBYTE:;CALL OSBYTE ; Call OSBYTE ;CALL PR_HEX:;CALL PR2HEX ; Debug output ;JP OSNEWL : : ; Read a decimal value ; -------------------- OPT FNendif .GetDec ; Ensure label OPT FNif(_fx%) CALL SkipSpace ; Skip past any spaces push bc:ld c,0 call GetDigit:jr c,BadNumber .GetDecLp inc hl:and 15:ld b,a:ld a,c add a,a:add a,a:add a,c:add a,a:add a,b ld c,a:jr c,BadNumber call GetDigit:jr nc,GetDecLp ld a,c:pop bc ret : \ C=invalid digit, NC=valid digit \ A=48..57 for 0..9 \ HL=>CR or next character .GetDigit ld a,(hl) cp 13:ccf:ret z cp ASC"0":ret c cp ASC"9"+1:ccf OPT FNendif ret : : ; Default null language ; ===================== .ErrorHandler LD SP,(MEMTOP) ; Initialise stack CALL OSNEWL:LD HL,(FAULT) ; Get error pointer CALL PrString1:CALL OSNEWL; Print it, and continue to... : : ; Environment commands ; ~~~~~~~~~~~~~~~~~~~~ ; Command prompt ; -------------- .CLI_Com ; Just enter BASIC to save space .CLICOM : : ; *Basic - Enter Basic ; -------------------- .Basic CALL PageBBC:JP BasicEntry : : ; *Quit - Quit from environment ; ----------------------------- ; *Quit jumps to OSQUIT ; Default OSQUIT action is to enter CLICOM : : ; *Help - Provide help ; -------------------- .Help ; HL=>parameters CALL PRTEXT:DEFB 13:DEFM "ZX Host "+ver$:DEFB 13:NOP LD A,9:JP SERVICE ; Could use startup string instead : : ; BYTE - Various byte operations ; ============================== ; BASIC needs a minumum of fxEscAck, fxMEM, fxPAGE, fxHIMEM ; Useful to also have fx0, fx4, fxEOF, fxADVAL, fxINKEY, fxPOS, fxCHR ; To do, preserve A over call to routine ; .osByte PUSH HL:PUSH DE LD HL,osByteTable LD D,A .osByteSearch LD A,(HL):INC HL CP D:JR Z,osByteFound AND A:JR Z,osByteUnknown INC HL:INC HL JR osByteSearch .osByteUnknown LD A,D:POP DE POP HL LD (&FF81),A LD A,7:JP SERVICE ; Unknown OsByte .osByteFound LD A,(HL):INC HL LD H,(HL):LD L,A ; HL=routine address LD A,D ; Get A back to A POP DE ; Get DE back EX (SP),HL ; Get HL back and put addr on stack RET ; Enter routine, A/HL=params, Cy=NC : .osByteTable DEFB &86:DEFW osBytePOS DEFB &84:DEFW osByteHimem DEFB &83:DEFW osBytePage DEFB &81:DEFW osByteInkey DEFB &80:DEFW osByteAdval DEFB &7E:DEFW osByte7E DEFB &00:DEFW osByte00 : .osByte00 LD L,31 ; host_type='Spectrum' RET : .osByte7E CALL PageZXSave ; Ensure screen memory paged in .osByte7ELp LD A,(&5C3B):PUSH AF AND &DF:LD (&5C3B),A ; Clear keypressed flag POP AF:AND 32 JR NZ,osByte7ELp ; Loop until no key pressed CALL &1F54 JR NC,osByte7ELp ; Loop until BREAK not pressed XOR A:LD (ESCFLG),A ; Clear Escape flag CALL PageZXRestore ; Restore previous memory RET ; PageZXRestore changes stack : .osByteInkey BIT 7,H:JR NZ,InkeyNeg LD (TICKER),HL .InkeyLp CALL RdChRaw:RET NZ ; Key pressed ;LD L,A:;LD H,0:;RET NZ ; Key pressed LD HL,(TICKER) LD A,H:OR L JR NZ,InkeyLp ; Loop until timeout DEC HL:SCF:RET ; HL=-1, Cy=1 .InkeyNeg BIT 7,L LD HL,&00E0:RET Z ; INKEY-256 - &Ex=Spectrum LD L,&0 ; Keyscan, nothing for the moment RET : .osByteHimem LD HL,(MEMTOP) RET : .osBytePage LD HL,(MEMBOT) RET : : ; WORD - Read Input Line, TIME, etc. ; ================================== .osWord CP 3:JR NC,osWordUnknown PUSH IX:PUSH HL ; Save registers PUSH DE:PUSH BC PUSH HL:POP IX ; IX=>control block ADD A,A:LD E,A:LD D,0 LD HL,osWordTable ADD HL,DE LD A,(HL):INC HL LD H,(HL):LD L,A CALL JumpToHL POP BC:POP DE POP HL:POP IX ; Restore registers RET : .JumpToHL PUSH HL ; Push HL to jump to LD L,(IX+0):LD H,(IX+1) ; Preload HL with (IX+0/1) RET : .osWordUnknown LD (&FF81),A LD A,8:JP SERVICE ; Unknown OsWords : .osWordTable DEFW osWord0:DEFW osWord1:DEFW osWord2 : : ; OSWORD 0 - Read a line of input ; ------------------------------- ; HL=>addr,lochar,hichar,max ; On exit, Cy=0 - Ok ; L = line length ; Cy=1 - Escape set ; .osWord0 LD B,0 .word0loop CALL OSRDCH:RET C CP 127:JR Z,word0delete LD (HL),A:CALL OSWRCH INC HL:INC B CP 13:JR nz,word0loop LD A,10:CALL OSWRCH LD H,B:AND A RET .word0delete LD A,B:CP 0:JR Z,word0loop LD a,127:call OSWRCH dec b:dec hl:jr word0loop : : ; OSWORD 1 - Read TIME ; -------------------- ; HL=>word to return TIME to : .osWord1 ; Read TIME CALL PageZXSave ; Page screen memory in DI:LD HL,(&5C78) LD DE,(&5C7A):LD D,0:EI ; Get FRAMES CALL PageZXRestore ; Restore memory SLA L:RL H:RL E:RL D ; TIME=FRAMES*2 LD (IX+0),L:LD (IX+1),H LD (IX+2),E:LD (IX+3),D RET : : ; OSWORD 2 - Write TIME ; --------------------- ; HL=>word with TIME to write : .osWord2 ; Write TIME PUSH HL:POP BC ; Pass HL to BC CALL PageZXSave ; Page screen memory in LD A,(IX+2):LD D,(IX+3) RR D:RR A:RR B:RR C ; FRAMES=TIME/2 DI:LD (&5C78),BC LD (&5C7A),A:EI ; Store FRAMES CALL PageZXRestore ; Restore memory RET ; PageZXRestore changes stack : : ; RDCH - Character Input Routine ; ============================== ; Returns Cy=0, A=character read ; Cy=1, Escape state : .osRdCh PUSH HL .osRdChLp CALL RdChRaw:JR Z,osRdChLp POP HL:RET : : ; Raw RDCH - Raw Character Input Routine ; ====================================== ; Checks if a key is pressed. If a key is pressed, actions ; it. Converts CAPS, cursor keys, COPY, Escape, etc. ; Returns Z=no key pressed, NZ=key pressed ; A=key, NC if not escape, C if escape ; Allowed to corrupt HL ; ; Need to ensure screen memory and ZXROM paged in ; Should check Shift/Symbol + Enter/Space ; Should have a *FX4 and *ESC ON/OFF option ; .RdChCaps LD A,(&5C6A):XOR 8:LD (&5C6A),A .RdChRaw LD HL,27 ; Prepare HL=Escape LD A,(ESCFLG):AND 128 ; MI+NZ if Escape pending LD A,L:SCF:RET M ; Return it with CS CALL RdChar:RET Z ; Nothing pressed CP 6:JR Z,RdChCaps ; CAPS, toggle it CP &CB:CCF:RET NC ; char<&CB, NC, NZ - char ok ; ; A = &CB &CC &CD &CE &CF ; COPY <- -> Dn Up ; LD HL,(COPYADD) JR NZ,RdChNotCopy ; >&CB, cursor key LD A,H:OR L:RET Z ; Z=no key pressed ; ; HL=&58xx &59xx &5Axx ; HL=%0101 10yy yyyx xxxx ; needs: ; %010y y000 yyyx xxxx ; PUSH BC PUSH DE LD A,H ; A=%010110yy ADD A,A:ADD A,A:ADD A,A ; A=%110yy000 XOR &80:LD D,A:LD E,L ; A=%010yy000 LD HL,(&5C63):LD (&5C65),HL ; Clear calc space CALL PageZXSave ; Corrupts HL LD HL,(CHARS):INC H ; HL=>' ' CALL &254D:LD E,A ; Call SCREEN$ for 96 chars CALL PageZXRestore CALL CopyCursor ; Cursor off INC HL:CALL RdChMoveAddr ; Move cursor, cursor on LD A,E:POP DE:POP BC JR RdChOk ; .RdChNotCopy PUSH BC PUSH AF CALL CopyCursor ; Remove any cursor JR C,RdChMove ; Jump to move if cursor exists LD HL,(DFCC) ; HL=%010y y000 yyyx xxxx ; needs: ; HL=%0101 10yy yyyx xxxx LD A,H RRA:RRA:RRA ; A=%0000 10yy OR &50:LD H,A ; A=%0101 10yy LD (COPYADD),HL .RdChMove ; A=CC, CD, CE, CF ; -1, +1, +32,-32 POP AF ; Get char back LD BC,&FFFF CP &CD:JR C,RdChAdd INC BC:INC BC JR Z,RdChAdd LD BC,32 CP &CE:JR Z,RdChAdd POP BC .CopyUp PUSH BC LD BC,&FFE0 .RdChAdd ADD HL,BC:POP BC .RdChMoveAddr LD A,H CP &58:JR C,RdChTooLow CP &5B:JR C,RdChRestore LD H,&58:JR RdChRestore .RdChTooLow:LD H,&5A .RdChRestore LD (COPYADD),HL ; Store and fall through : .CopyCursor LD HL,(COPYADD) LD A,H:OR L:RET Z ; NC=COPYADD inactive LD A,(HL):XOR 128:LD (HL),A CP A:SCF:RET ; Z=No key, CS=COPYADD active : : .osByteAdval .RdChar LD HL,&5C3B ; HL=>FLAGS BIT 5,(HL):JR Z,RdChNone; Exit if no key pressed LD A,(&5C08):RES 5,(HL) ; Get key from LAST_K CP 127:JR NC,RdChLook ; Convert symbols and controls CP 16:JR NC,RdChOk .RdChLook AND &EF:LD HL,RdChTable .RdChLoop ; Scan through conversion table CP (HL):INC HL:JR Z,RdChMatch INC HL:BIT 4,(HL) JR Z,RdChLoop:JR NZ,RdChOk .RdChMatch LD A,(HL) ; Fetch char to convert to .RdChOk ; A='real' character LD H,0:LD L,A:AND A:RET ; NZ=key pressed .RdChNone LD HL,&FFFF:RET ; .RdChTable ; Convert LAST_K values to RDCH values ; 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F ; TRUE INV CAPS EDIT <- -> Dn Up DEL ENT EXT GRA ; Shift+ 3 4 2 1 5 8 6 7 0 SYM 9 ; &80 &81 CAPS &CB &CC &CD &CE &CF &7F &0D &09 &89 : DEFB 4:DEFB &80 ; Sh-3 - TRUE -> &80 DEFB 5:DEFB &81 ; Sh-4 - INV -> &81 ;DEFB 6;DEFB 6 ; Sh-2 - CAPS DEFB 7:DEFB &CB ; Sh-1 - EDIT -> COPY DEFB 8:DEFB &CC ; Sh-5 - LEFT -> LEFT DEFB 9:DEFB &CD ; Sh-8 - RIGHT -> RIGHT DEFB 10:DEFB &CE ; Sh-6 - DOWN -> DOWN DEFB 11:DEFB &CF ; Sh-7 - UP -> UP DEFB 12:DEFB &7F ; Sh-0 - DELETE -> DELETE ;DEFB 13;DEFB &0D ; ENTER - ENTER -> ENTER DEFB 14:DEFB &09 ; Sh-Sym - EXTEND -> TAB DEFB 15:DEFB &89 ; Sh-9 - GRAPHICS -> &89 ; Should allow Shift+Enter ; Shift+SPACE -> &1B if *ESC OFF : ; Symbols DEFB 195:DEFB ASC"|":DEFB 197:DEFB ASC"]" DEFB 198:DEFB ASC"[":DEFB 203:DEFB ASC"}" DEFB 204:DEFB ASC"{":DEFB 205:DEFB ASC"\" DEFB 226:DEFB ASC"~":DEFB 199:DEFB &82 DEFB 201:DEFB &83 :DEFB 200:DEFB &84 DEFB 172:DEFB &88 :DEFB 111:DEFB &88 ; For JGH ROM - SS-I gives &7F DEFB &FF ; Table terminated by b4=1 ; Should allow Symbol+Enter, Symbol+SPACE : : ; WRCH - Character Output Routine ; =============================== ; A=character to write ; All registers preserved, F may be corrupted : ; Currently OSWRCH only access the VDU - the Spectrum screen. ; OSWRCH should output to streams according to *FX3 state. ; ; f580 VduQ ; f581 VduStatus ; f582 VduQStart char ; f583 p1 ; f584 p2 ; f585 p3 ; f586 l p4 k ; f587 p p5 x1 x1 x1 ; f588 r p6 x2 y1 x2 ; f589 mask g p7 y1 x2 y1 x ; f58a char col col b mode p8 y2 y2 y2 y ; 1 17 18 19 22 23 25 28 29 31 ; f58b VduJmp ; f58c VduJmpHi ; f58d ; f58e ; f58f ; ; osWrch control code table is before osWrch, so it can be found by looking ; at destination of OSWRCH .wrchAddr DEFW (wrchNull AND &FFF) - &0000 DEFW (wrchPrNxt AND &FFF) - &1000 DEFW (wrchPrOn AND &FFF) - &0000 DEFW (wrchPrOff AND &FFF) - &0000 DEFW (wrchTX AND &FFF) - &0000 DEFW (wrchGR AND &FFF) - &0000 DEFW (wrchOn AND &FFF) - &0000 DEFW (wrchBell AND &FFF) - &0000 DEFW (wrchBS AND &FFF) - &0000 DEFW (wrchRT AND &FFF) - &0000 DEFW (wrchDN AND &FFF) - &0000 DEFW (wrchUP AND &FFF) - &0000 DEFW (wrchCLS AND &FFF) - &0000 DEFW (wrchCR AND &FFF) - &0000 DEFW (wrchPOn AND &FFF) - &0000 DEFW (wrchPOff AND &FFF) - &0000 DEFW (wrchCLG AND &FFF) - &0000 DEFW (wrchCOL AND &FFF) - &1000 DEFW (wrchGCOL AND &FFF) - &2000 DEFW (wrch19 AND &FFF) - &5000 DEFW (wrch20 AND &FFF) - &0000 DEFW (wrchOff AND &FFF) - &0000 DEFW (wrchMode AND &FFF) - &1000 DEFW (wrch23 AND &FFF) - &9000 DEFW (wrch24 AND &FFF) - &8000 DEFW (wrchPlot AND &FFF) - &5000 DEFW (wrch26 AND &FFF) - &0000 DEFW (wrchEsc AND &FFF) - &0000 DEFW (wrch28 AND &FFF) - &4000 DEFW (wrch29 AND &FFF) - &4000 DEFW (wrchHome AND &FFF) - &0000 DEFW (wrchTab AND &FFF) - &2000 DEFW (wrchDel AND &FFF) - &0000 : .osWrch PUSH AF:PUSH HL:PUSH DE ; Save registers PUSH BC:LD DE,(VduQ) ; E=VduQ, D=VduStatus BIT 7,E:JR NZ,wrchQ ; VDU queue pending CP 127:JR Z,wrch127 ; DEL - treat as control character CP 32:JR C,wrchCtrl ; Control character BIT 7,D:JR NZ,wrchExit ; VDU disabled with VDU 21 : LD E,A:CALL PageZXSave ; Select for ZXROM+Screen - Pushes ROMSEL, IY ;LD A,&FF:;LD (23692),A ; Prevent 'Scroll?' messages LD (IY+82),&FF ; Prevent 'Scroll?' messages CALL CopyCursor ; Turn any COPY cursor off LD A,E:CP 160:JR C,wrchRST ; CHARS or UDC? AND 127 ; Just mirror low chars for the moment .wrchRST EXX:RST &10:EXX ; Call Spectrum's PRINT_A1 routine ; Assumes CHAN_OPEN has been called to set OUT ; to 'S'creen (by CLS). Needs IY=&5C3A. .wrchScroll ;LD A,&FF:;LD (23692),A ; Could this be done here? Prevent 'Scroll?' messages ;LD (IY+82),&FF ; Could this be done here? Prevent 'Scroll?' messages CALL wrchCursorOn ; Forces the screen to scroll if needed LD A,(23692):INC A ; Fetch scroll counter JR Z,wrchExit2 ; Screen hasn't scrolled LD HL,(COPYADD) ; Get Copy cursor LD A,H:OR L:JR Z,wrchExit2 ; No copy cursor CALL CopyUp:JR wrchExit1 ; Scroll copy cursor : .wrchExit2 CALL CopyCursor ; Turn any copy cursor back on .wrchExit1 CALL PageZXRestore ; Restore ROM state and IY .wrchExit POP BC:POP DE:POP HL:POP AF; Restore registers RET : .wrchQ ; Outstanding characters queued LD D,0:LD HL,VduQEnd-256 ; Index into VDU Queue ADD HL,DE:LD (HL),A ; Store queued byte LD A,E:INC A:LD (VduQ),A ; Update queue JR NZ,wrchExit ; Exit if more pending JR wrchJump : .wrch127 LD A,32 ; Treat DEL as offset 32 .wrchCtrl ADD A,A:LD E,A:LD D,0 LD HL,wrchAddr:ADD HL,DE ; Index into control character addresses LD A,(HL):LD (VduJmp+0),A INC HL:LD A,(HL) OR &F0:LD (VduJmp+1),A ; (VduJmp)=>routine LD A,(HL):AND &F0 JR Z,wrchJump ; No parameters, execute routine RRCA:RRCA:RRCA:RRCA:OR &F0 ; A=256-parameters LD (VduQ),A:JR wrchExit : .wrchJump LD A,E:CP 12:JR Z,wrchJump6; Ignore current status if VDU 6 LD A,(VduStatus):ADD A,A ; Check VDU status JR C,wrchExit ; VDU disabled with VDU 21 .wrchJump6 CALL PageZXSave ; Select and set up for ZX ROM ;LD A,&FF:;LD (23692),A ; Prevent 'Scroll?' messages LD (IY+82),&FF ; Prevent 'Scroll?' messages CALL wrchCursorOff ; Turn text cursor off, returns A=0, NC LD B,1 ; B=1 is useful later LD HL,(VduJmp):CALL JumpHL ; Call control character routine, A=0, B=1, NC JR wrchScroll ; Check for scroll and exit : .wrchCursorOff CALL CopyCursor .wrchCursorOn OPT FNif(TRUE) ; Doing this way is slow CALL wrchOVER1 ; Transparent ATTR, OVER 1 EXX:LD A,ASC"_":RST &10 ; '_',BS LD A,8:RST &10:EXX ; Continue to restore ATTR, OVER 0 OPT FNelse ; Doing this way doesn't scroll at bottom right corner, ; writing into attributes LD HL,(DFCC):LD A,H ; Get current position ADD A,7:LD H,A ; Step down 7 lines LD A,(HL):CPL:LD (HL),A ; Toggle it RET OPT FNendif : .wrchOVER0 RES 0,(IY+87) ; P_FLAG - unset OVER XOR A ; T_MASK - use screen attr .wrchOVER LD (&5C90),A:RET : .wrchOVER1 SET 0,(IY+87) ; P_FLAG - set OVER LD A,255:JR wrchOVER ; T_MASK - use colour attr : ; On entry B=1. Needs B=count, C=char .wrchUP ; Cursor up by 32*BS LD B,&20 .wrchBS ; Cursor left by 1*BS LD C,&08:JR wrchChrLp0 .wrchDN ; Cursor right by 32*SPC LD B,&20 .wrchRT ; Cursor right by 1*SPC LD C,&20 .wrchChrLp0 CALL wrchOVER1 ; Set transparent ATTR, OVER 1 .wrchChrLp PUSH BC:LD A,C:EXX:RST &10 ; Print a SPACE or BACKSPACE EXX:POP BC:DJNZ wrchChrLp ; Loop to print 1 or 32 times JR wrchOVER0 ; Reset ATTR, OVER 0 : .wrchCR LD HL,0:LD (COPYADD),HL ; Turn off any copy cursor LD A,(DFCC):AND &E0 ; Force DFCC to column 0 LD (DFCC),A LD A,&21:LD (23688),A ; Force POSN to column 0 RET : .wrchScreenInit ; Note, screen must be paged in LD HL,0:LD (VduQ),HL ; Clear VDU queue and VDU status LD IY,&5C3A ; IY needs to be set for calling from outside .wrchMode ; Mode number ignored CALL wrch20 ; Set default colours .wrchCLS CALL &0DAF ; CL_ALL - also selects channel &FE->'S' XOR A:LD (23659),A ; Bottom has no lines RET : .wrch26 ; Collapse windows also does HOME .wrchHome JP &0DD6 ; CL_SET-3 - Set posn to (0,0) : .wrchTab LD HL,(VduQEnd-2) ; L=X, H=Y LD A,33:SUB L:LD C,A ; C=33-X - 33...2 DEC A:DEC A:CP 32:RET NC ; Out of screen LD A,24:SUB H:LD B,A ; B=24-Y - 24...1 DEC A:CP 24:RET NC ; Out of screen JP &0DD9 ; CL_SET : .wrchDel EXX:LD A,8:RST &10 ; BS,SPC,BS LD A,ASC" ":RST &10 LD A,8:RST &10:EXX RET : .wrch20 LD B,0:LD A,&07 ; Use &47 for bright, if called from MODE A=mode SCF:JR wrchATTR ; Set default ATTR=A, BORDCR=A and border=B : .wrchCOL LD A,(VduQEnd-1) ; &00+n=ink, &40+n=flash/bright LD B,A:AND 3:BIT 2,B ; &80+n=paper, &C0+n=border RES 2,B:JR Z,wrchCOLa:SCF ; Rotate RGB bits into Spectrum colour .wrchCOLa ADC A,B:LD B,A ADD A,A:ADD A,A:ADD A,A ; A=%fibgr000, B=%ldefibgr BIT 7,B:JR NZ,wrchPaper ; If Colour>&7F -> paper/border AND &C0:XOR B:AND &F8:XOR B; A=%fi000grb, colour in position for ink LD C,A:LD A,&38 ; A=%00111000 to keep paper BIT 6,B:JR Z,ColourSet ; Set flash,bright,ink if &00..&3F LD A,&BF ; A=%10111111 to drop bright BIT 5,B:JR Z,wrchCOLb ; If b5=0, just set bright RLCA:JR wrchCOLb ; A=%01111111 to drop flash .wrchPaper BIT 6,B:JR NZ,wrchBorder ; Colour>&BF -> border LD C,A:LD A,&07 ; A=%00000111 to keep ink .ColourSet BIT 5,B:JR Z,wrchCOLb ; If b5=0, set F and I as well OR &C0 ; A=%11xxxxxx to keep F and I .wrchCOLb ; ; colour %000figrb A=%00111000 C=%fi000grb ; colour %001figrb A=%11111000 C=%fi000grb ; colour %010figrb A=%10111111 C=%fi000grb ; colour %011figrb A=%01111111 C=%fi000grb ; colour %100figrb A=%00000111 C=%fibgr000 ; colour %101figrb A=%11000111 C=%fibgr000 ; LD B,A:CPL:AND C:LD C,A ; Mask supplied colour LD A,(&5C8D) ; Get P_ATTR AND B:OR C ; Mask out old, map in new .wrchATTR LD (&5C8D),A:LD (&5C8F),A ; Set P_ATTR and T_ATTR RET NC ; Return if ColourSet .wrchBorder LD (&5C48),A ; Set BORDCR LD A,B:OUT (&FE),A ; Set border RET : .wrchOff LD B,129 .wrchOn ; On entry B=1 LD A,(VduStatus):AND 127 ; Lose old VDU21 bit DEC B:OR B:LD (VduStatus),A ; Mask in new VDU21 bit : .wrchNull:.wrchPrNxt:.wrchPrOn:.wrchPrOff .wrchTX :.wrchGR :.wrchBell:.wrchPOn .wrchPOff:.wrchCLG :.wrchGCOL:.wrch19 .wrch24 :.wrchPlot :.wrchEsc .wrch28 :.wrch29 RET : .wrch23 LD A,(VduQStart):CP &80 LD BC,(CHARS):JR C,wrch23a LD BC,(UDC):AND 15 .wrch23a LD L,A:LD H,0 ADD HL,HL:ADD HL,HL:ADD HL,HL ADD HL,BC:EX DE,HL ; DE->char LD HL,VduQStart+1 ; HL->supplied bitmap LD BC,8:LDIR RET : .osBytePOS LD HL,(23688) ; SPOSN LD A,24:SUB H:LD H,A ; VPOS LD A,33:SUB L:LD L,A ; POS RET : : ; Printout routines ; ================= .PrSpace LD A,ASC" ":JP OSWRCH .PrSpcHex CALL PrSpace:JP PR2HEX : .PrText ; Print embedded string EX (SP),HL:CALL PrString EX (SP),HL:RET : .PrString ; Print string at HL LD A,(HL):AND A:RET Z CALL OSASCI .PrString1 INC HL:JR PrString : .Pr2Hex ; Print HL in hex LD A,H:CALL PrHex LD A,L : .PrHex ; Print A in hex PUSH AF RRA:RRA:RRA:RRA CALL PrNybble POP AF .PrNybble AND &0F:CP 10:JR C,PrDigit ADD A,7 .PrDigit ADD A,ASC"0":JP OSWRCH : : ; Some ROM paging code ; ==================== .PageZXSave POP HL ; Get return address PUSH IY:LD IY,&5C3A ; Set up IY for ZX ROM LD A,(ROMSEL):PUSH AF ; Save current ROM state PUSH HL ; Put the return address back ; Page in the ZX ROM, returning to caller .PageZX LD A,(ZXROM):JP ROMPAGE ; Page ZXBasic in .PageBBC LD A,(BBCROM):JP ROMPAGE ; Page BBC BASIC in : ; Moved into higher memory .PageZXRestore POP IY:POP AF:EX (SP),IY ; Restore IY and get previous ROM JP ROMPAGE ; Page previous ROM in : : ; Service call dispatcher ; ======================= .Service OPT FNif(FALSE) ; A=number ; DE, HL=parameters ; BC corrupted ; Calls ROMs. Support ROM implements vector chains LD C,A:LD A,(ROMSEL):PUSH AF ; Save current ROM LD B,15 ; Start at ROM 15 .ServLp LD A,B:CALL ROMPAGE ; Page the ROM in LD A,(&00F8):ADD A,A:JR NC,ServSkip ; No service entry, skip LD A,(&0063):CP &C3:JR NZ,ServSkip ; No JP at service entry, skip ;LD A,B:;CALL PRHEX LD A,C:;CALL &0063 ; Calls, A=num, B=rom, DE,HL=params ; ; *** Actually calling &0063 causes crashes ; AND A:LD C,A:JR Z,ServDone ; Claimed, exit .ServSkip DEC B:JP P,ServLp ; Loop until done ROM 0 .ServDone POP AF:CALL ROMPAGE ; Restore ROM LD A,C:AND A OPT FNendif RET : : ; Prevent stack being paged out ; ============================= ; If SP in &4000-&7FFF, then paging screen and shadow ; memory will lose stack. Temporary check until a better ; solution found. ; ;.CheckSP:;RET ;LD A,(SCREEN):;LD HL,0 ;ADD HL,SP:;OR H:;RET M ; Return if SP>&7FFF or no MMU ;LD SP,(MEMTOP) ; Reset stack ;CALL INITERR ; Claim BRKV ;CALL OSERROR:;DEFB 255 ; Generate error into CLICOM ;DEFM "Stack too low":;NOP : : ;.MemoryInit ;LD HL,(MEMTOP):;LD DE,(MEMBOT) ; Inform ROMs of memory limits ;LD A,1:;CALL SERVICE ;LD (MEMTOP),HL:;LD (MEMBOT),DE ; Update memory limits ;RET : : ; IM2 INT Vector ; ============== ; Deal with IM2 interupts. Documentation states that hardware Z80 forms IM2 ; interrupt vector from I*256+(data AND 254), ie I*256+&FE. Documentation is ; wrong. Actual hardware and some emulators form the vector from I*256+data, ; ie I*256+&FF. This code deals with both cases. If data is &FE, then the ; vector is &FDF7 which fetches the real IRQV and jumps to it. If the data ; is &FF, the vector is &FDFD which jumps to &FDF7 as above. ; ]:z%=&FDF7-P%:extra1%=-z%:[OPT opt% DEFM STRING$(&FDF7-P%,CHR$255) .IRQFix PUSH HL:LD HL,(&FFFE) ; Save HL and get IRQV EX (SP),HL:RET ; Restore HL and jump to IRQV ; &FDFD JP &FDF7:DEFB &FD ; If IM2 gives 256*I+255, jumps to &FDFD, then to &FDF7 ; If IM2 gives 256*I+254, jumps straight to &FDF7 : : ; Interrupt Handlers ; ================== ; Need to ensure ZXROM and screen memory paged in ; .InterruptHandler CALL USERINT:PUSH AF ; Allow user routine to get a look in PUSH HL:LD HL,(TICKER) ; Get 100Hz ticker RES 0,L ; Force to even value LD A,L:OR H:JR Z,IntTicker ; Don't pass zero DEC HL:DEC HL ; IRQs are 50Hz, so dec by 2 .IntTicker LD (TICKER),HL ; Store ticker ;CALL CheckSP ; Ensure stack won't be paged out CALL PageZXSave:RST &38 ; Call ZX ROM interupt routine CALL &1F54:JR C,IntNoEsc ; Check Shift-Break RRCA:LD (ESCFLG),A ; Set ESCFLG if BREAK pressed .IntNoEsc CALL PageZXRestore POP HL:POP AF ; Restore registers RET : : ; Initial Startup ; =============== ; On entering here at ColdStart, assumes nothing about the state ; of the Spectrum system, other than the MOS code has been copied ; to high memory and the ROM paging data is valid. One of the first ; things we must do is a CLS to select a screen output stream. ; ; Walks through ROMs to see what is available and to find where ; BBC BASIC is. ; .ColdRestart OPT FNif(NOT hibasic%) DI:CALL PageBBC:JP &0060 ; Reinitialise memory OPT FNendif .ColdStart DI ; Ensure INTs are off OPT FNif(NOT hibasic%) LD HL,ColdRestart LD (OSCOLD+1),HL ; Redirect ColdStart to ColdRestart OPT FNendif LD SP,(MEMTOP):IN A,(&FF) ; Set stack at top of memory AND A:JR NZ,ColdNoMMU ; Is there a Timex MMU? LD (SCREEN),A ; If so, clear SCREEN b7 LD A,&40:LD (MEMBOT+1),A ; Drop MEMBOT to &4000 .ColdNoMMU : LD HL,ROMTYPES+15 ; Point to end of ROM table LD B,&0F ; Start at ROM 15 .RomLoop LD A,B:CALL ROMPAGE ; Page this ROM in LD A,(&00F8):LD (HL),A ; Copy ROM type to ROM table CP &41:JR NZ,ColdNotBasic ; Simple check for BASIC LD A,B:LD (BBCROM),A ; Set BASIC ROM number .ColdNotBasic DEC HL ; Move to next table entry DEC B:JP P,RomLoop ; Loop though past ROM 0 : LD A,(BBCROM):OR &30 CALL ROMPAGE ; Page BBC BASIC ROM and screen memory in CALL InitSpectrum ; Initialise Spectrum system ; Returns DE=CHANS CALL PageZX ; Page ZXBasic+Screen in LD HL,&15AF:LD BC,&0015 LDIR:EX DE,HL ; Copy initial channel information DEC HL:LD (&5C57),HL ; Set DATADD INC HL:LD (&5C53),HL ; Set PROG LD (&5C4B),HL:LD (HL),&80 ; Set VARS INC HL:LD (&5C59),HL ; Set up E_LINE CALL &16B0 ; Set WORKSP, STKBOT, STKEND LD HL,&15C6:LD DE,&5C10 LD C,&0E:LDIR ; Initialise STREAMS ;CALL InitUDC ; Initialise UDCs ;.InitUDC LD HL,UDC90:LD (UDC),HL LD DE,&3C00+8*ASC"A" LD B,16*8 .InitUDCLp LD A,(DE):CPL:LD (HL),A INC HL:INC DE DJNZ InitUDCLp ;RET LD A,7:CALL wrchScreenInit ; Initialise screen in MODE 7 EI ; Enable interrupts ;CALL MemoryInit ; Inform ROMs of memory limits ;LD SP,HL ; Reset stack to (possibly new) MEMTOP : CALL PrText ; Display startup banner .StartupMessage DEFM "Sinclair Spectrum":;NOP ;LD A,(SCREEN):;ADD A,A ; Work out memory size to display ;LD A,&48:;JR C,Cold48 ;LD A,&64:;.Cold48 ;CALL PRHEX:;CALL PrText:;DEFM "K" .StartupCR DEFB 13:DEFB 13:NOP CALL FSInit:LD (CURDRV),A ; Wake up IF1 and find default drive ;LD A,2:;CALL SERVICE ; Allow ROMs to start up LD HL,StartupCR ; Point to empty command line LD (LPTR),HL:JP Basic ; Enter BASIC : : ; Error Handlers ; ============== ; ; Set up vectors to capture errors ; -------------------------------- .InitErr LD HL,(&FF84):LD (BRKV),HL ; Set default BRKV CALL PageZXSave ; Page ZX ROM and screen in LD HL,SpectrumError ; Capture any Spectrum-generated errors LD (&5FFC),HL LD HL,&5FFC:LD (ERR_SP),HL ; Set up ERR_SP CALL PageZXRestore ; Restore ROM and return .EventHandler ; Default EventV - nothing .Null RET : ; Spectrum errors generated with RST &08 ; -------------------------------------- ; Should only ever get ; &08 - End of file ; &0C - Nonsense in BASIC - Not found/Exists/ReadOnly ; &0D - BREAK - CONT repeats ; &13 - Invalid device ; &15 - BREAK into program ; &18 - Invalid stream - Drive not present ; &1B - Tape loading error ; &32 - Interface 1 absent ; .SpectrumError ; NB Anything coming via SpectrumError ; will have modified (IY+nn) ; At this point, ZXBasic will be paged in LD A,(23610):INC A ; Get ERR_NR+1 .SpectrumErrorA LD SP,&5FFE ; Use Spectrum stack LD HL,SpectrumError:PUSH HL; Reset Spectrum error vector LD (ERR_SP),SP ; Ensure ERR_SP set up PUSH AF ; Save ERR_NR+1 CP &0D:JP Z,Escape ; Convert "BREAK - CONT repeats" to "Escape" CP &0C:JR Z,SpecErrorInt2 ; Nonsense in BASIC CP &1C:JR NC,SpecErrorInt1 ; Interface 1 not present LD DE,&1391:CALL &0C41 ; PO_SEARCH, find text of error LD HL,errbuf%:POP AF ; DE->first char of message PUSH HL ; Point to error buffer ADD A,128:LD (HL),A:INC HL ; Store Spectrum error code+&80 .SpecErrLp ; Gives &80 to &9B LD A,(DE):AND 127:LD (HL),A ; Store error string LD A,(DE):INC HL:INC DE ADD A,A:JR NC,SpecErrLp .SpecErrEnd XOR A:LD (HL),A ; Error string terminator JP OSERROR ; Enter error handler .SpecErrorInt1 CALL OSERROR ; Should check for actual error DEFB &FF ; Eg 'serial port not present', etc. \DEFM "Interface 1 not present":\NOP DEFM "NoInt#1":NOP .SpecErrorInt2 CALL OSERROR:DEFB &FF:DEFM "Int#1 error":NOP : : ; Called by RST &38 or CALL OSERROR ; --------------------------------- .RestartHandler POP HL:LD (FAULT),HL ; Get address of string after CALL LD HL,(BRKV):CALL PageBBC ; Page BBC BASIC in .JumpHL JP (HL) ; Jump via error vector : : ; ROM paging system ; ================= ; A=ROM/RAM number to page in ; +---+---+---+---+---+---+---+---+ ; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ; +---+---+---+---+---+---+---+---+ ; | | | | | | | | ; b5 b4 b3 b2 +---+-------+----- &7FFD ROM latch ; \ Timex MMU / | ; +--------- 0 - HOME in ROM space, DOCK in RAM space ; 1 - b4=0 DOCK paged in, b4=1 EX paged in ; ; This gives the following values: ; Spectrum SE Spectrum128 Spectrum 48 ; &x0 - HOME ROM 0 &x0 - HOME ROM 0 &xx - ZXROM ; &x1 - HOME ROM 1 &x1 - HOME ROM 1 ; &x2 - DOCK ; &x3 - EX ; &x4+ - external ROMs, needs additional hardware ; ; &0x - shadow memory, ie as much usable memory as possible ; &Fx - use normal memory, ie, screen memory ; ; Host code requires a ZXROM in bank &x1 and expects microdrive, screen, etc ; to be accessible when bank &F1 paged in. ; Could speed up by only paging if needed. ; .PageROM LD (ROMSEL),A:PUSH BC ; Set currently selected ROM RLCA:RLCA:RLCA:RLCA ; Rotate into hardware value LD B,A:LD A,(SCREEN) ; Check bank availability ADD A,A:LD C,A:JR C,PageFD ; Only one paging register LD A,B:ADD A,A:ADD A,A ; HOME or DOCK/EX in ROM area? JP P,PageF4:OR 3 ; Otherwise page bottom 16K to DOCK/EX .PageF4 AND &3F:XOR &C:OUT (&F4),A ; Set paging bits in MMU LD A,B:AND &30:CP &30 ; Select EX if %xx11xxxx, DOCK otherwise CCF:LD A,C:RRA:OUT (&FF),A ; Set DOCK/EX .PageFD LD A,B:AND &D0:LD BC,&7FFD OUT (C),A:POP BC:RET ; Set ROM bank : ]:z%=&FF80-P%:extra2%=-z%:[OPT opt% DEFM STRING$(&FF80-P%,CHR$255) : : ; High memory data and entry points ; ================================= \ Being on a Spectrum, we are running in RAM. \ ROMSEL needs to be in shared memory, so depends on top 16K not being paged out. \ \ &FF80 :.ESCFLG :DEFB &00 ; Escape flag 6502 equiv-> &FF \ &FF81 :.FF81 :DEFB &00 ; TERM flag / TempA &EF \ &FF82 :.FAULT :DEFW StartupMessage ; Fault pointer &FD/E \ &FF84 :.FF84 :DEFW ErrorHandler ; Default error handler \ &FF86 :.LPTR :DEFW StartupCR ; Command line tail pointer &F2/3 \ &FF88 :.MEMBOT :DEFW &6000 ; Bottom of available memory \ &FF8A :.MEMTOP :DEFW mtop% ; Top of available memory \ &FF8C :.ROMPORT:DEFW &7FFD ; I/O port to page bottom 16K (now spare) \ &FF8E :.SCREEN :DEFB &FF ; b7=1, no DOCK/EX memory, b6-0=normal screen bits \ &FF8F :.ROMSEL :DEFB &00 ; Currently selected ROM \ &FF90 :.ZXROM :DEFB &F1 ; ROM containing ZX BASIC \ &FF91 :.BBCROM :DEFB &FF ; ROM containing BBC BASIC (found at startup) \ &FF92 :.ROMPAGE:JP PageROM ; Page memory into bottom 16K \ &FF95 :.SERVICE:JP Service ; Service calls \ &FF98 :.OSCOLD :JP ColdStart ; RESET code jumps here to start up \ &FF9B :.PRSTRNG:JP PrString \ &FF9E :.SCANDEC:JP GetDec \ &FFA1 :.SCANHEX:JP GetHex \ &FFA4 :.DISKACC:JP Null ; SPARE \ &FFA7 :.DISKCCP:DEFW &FDFD:RET ; SPARE \ &FFAA :.PRHEX :JP PrHex \ &FFAD :.PR2HEX :JP Pr2Hex \ &FFB0 :.USERINT:JP Null \ &FFB3 :.PRTEXT :JP PrText \ &FFB6 :.LFFB6 :LD A,C:JR OSWRCH ; SPARE \ &FFB9 :.OSQUIT :DEFW &FDFD:RET \ &FFBC :.OSERROR:JP RestartHandler ; Use this instead of RST &38 \ &FFBF :.OSINIT :JP InitErr \ &FFC2 :.DISKRST:LD A,&FF:RET ; SPARE \ &FFC5 :.LFFC5 :RET:RET:RET ; SPARE \ &FFC8 :.OSTERM :RET:RET:RET ; SPARE \ &FFCB :.OSFSC :JP osFSC ; Misc filing system calls (*Cat, *Run, etc) \ &FFCE :.OSFIND :JP osFind \ &FFD1 :.OSGBPB :JP osGBPB \ &FFD4 :.OSBPUT :JP osBPut \ &FFD7 :.OSBGET :JP osBGet \ &FFDA :.OSARGS :JP osArgs \ &FFDD :.OSFILE :JP osFile \ &FFE0 :.OSRDCH :JP osRdCh \ &FFE3 :.OSASCI :CP 13:JR NZ,OSWRCH \ &FFE7 :.OSNEWL :LD A,10:CALL OSWRCH \ &FFEC :.OSWRCR :LD A,13 \ &FFEE :.OSWRCH :JP osWrch \ &FFF1 :.OSWORD :JP osWord \ &FFF4 :.OSBYTE :JP osByte \ &FFF7 :.OS_CLI :JP osCLI \ &FFFA :.BRKV :DEFW ErrorHandler ; jump to with errors \ &FFFC :.EVENTV :DEFW EventHandler ; jump to with events \ &FFFE :.IRQV :DEFW InterruptHandler; jump to with INTs (NMI jumps to &0066) ] REM extra1: spare space up to IRQ handler at &FDFD REM extra2: spare space up to ESCFLG/etc. at &FF80 PROCBlockSave(2,extra1%+extra2%) IFP:IFextra1%>0:PRINT"Code overrun at &FDFD of ";extra1%;" bytes" IFP:IFextra2%>0:PRINT"Code overrun at &FF80 of ";extra2%;" bytes" ENDPROC