; ZXZVM: Z-Code interpreter for the Z80 processor ; Copyright (C) 1998-9,2006,2016 John Elliott ; ; This program is free software; you can redistribute it and/or modify ; it under the terms of the GNU General Public License as published by ; the Free Software Foundation; either version 2 of the License, or ; (at your option) any later version. ; ; This program is distributed in the hope that it will be useful, ; but WITHOUT ANY WARRANTY; without even the implied warranty of ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ; GNU General Public License for more details. ; ; You should have received a copy of the GNU General Public License ; along with this program; if not, write to the Free Software ; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ; +3-specific routines. ;Spectrum addresses between 4000h and 0BFFFh always refer to the same area ;of memory. Those above 0C000h can refer to one of eight memory banks, so ;I'll write them as 0:C000 to 7:C000. Memory banks 2 and 5 mirror what is ;in memory below 0C000h, so they aren't very useful. Addresses below 4000h ;can refer to one of four ROMS, so I'll write them as R0:0000 to R3:0000. ; ;The I/O subsystem lives in the Spectrum's screen RAM (so I can be sure that ;code which needs to bank switch is below 0BFE0h). Thus screen output goes ;to the shadow screen at 7:C000, which can be switched out when not needed. ; ;A bug in +3DOS necessitates the following workaround: ; ; - Despite what it says in the manual, it is necessary to have at least ; one buffer in the disc cache, or files are read incorrectly. So put the ; cache at the top of Bank 6, and run the interpreter with Bank 6 paged. ; The game file then loads in the 1,3,4,0 environment. ; ; - Although this was forced on me by +3DOS, I might have done it anyway; ; my previous plan (game in 1,3,4,6) would have meant implementing my own ; cache manually, a nasty business. org ZJT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ;Other source files ; include zxio.zsm ;ZX I/O - must come first include zxp3dos.zsm ;+3DOS specific functions include zxvers.zsm ;Get Spectrum version include zx64.zsm ;64-column printing include in_wrhex.inc ;Render numbers into hex or decimal ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ;Spectrum system variables: ; ONERR EQU 5B3Ah ;Memory paging subroutine used in reporting errors BANKM EQU 5B5Ch ;Memory paging latch OLDSP EQU 5B6Ah ;Saved stack pointer used while BASIC is calling +3DOS SYNRET EQU 5B6Ch ;Return address for ONERR PRAMT EQU 5CB4H ;Top of physical memory BANKIO EQU 7FFDh ;I/O port for memory paging SAVEPG EQU 6 ; memory page for savefile HDRADDR EQU 0C000h SCRADDR EQU 0C000h typeset EQU 070ffh ; poked by BASIC to 0 or 1 ; ;Enter a filename ; NAMEBUF: DEFS 20 FNBUF: DEFS 20 FNAME$: DEFB 13,10,'Filename> $' GNAME: ; LD DE,FNAME$ LD C,9 CALL ZXFDOS LD A,3 ;Z-address 4000h CALL SELMEM LD DE,NAMEBUF LD HL,0C000h LD BC,20 LDIR ;Backup Z-machine memory at 4000h LD HL,0C000h LD (HL),16 ;Max. 16 characters in name INC HL LD (HL),0 ;None provided LD A,6 CALL SELMEM LD DE,0 LD HL,4000h ;Filename buffer CALL LINEINP LD A,3 CALL SELMEM LD DE,FNBUF LD HL,0C000h LD BC,20 PUSH BC PUSH HL LDIR POP DE POP BC ;Length of buffer LD HL,NAMEBUF LD DE,0C000h LDIR ;Original contents of buffer LD HL,FNBUF+1 LD E,(HL) LD D,0 INC HL ADD HL,DE LD (HL),0FFh ;FFh-terminated filename string LD A,6 CALL SELMEM LD HL,FNBUF+2 LD A,E ;<< v1.01 A = length of input OR A SCF ; If it's 0, then no name was input. RET NZ CCF ;>> v1.01 RET ; ; Chime an error ; CHIME: CALL PUSHA LD DE,0E00H LD HL,CHIMEBF LD BC,0FFFDH CHIME1: LD A,(HL) OUT (C),E LD B,0BFH OUT (C),A LD B,0FFH INC HL INC E DEC D JP Z,POPA JR CHIME1 ; CHIMEBF: DEFW 01A6H,014FH,011AH DEFB 0,78H,10H,10H,10H,0,8,0,0 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ;Utility function to call +3DOS. ;This takes an inline word parameter, the address within +3DOS ;that should be called. ; dodos: ld (dosde),de ex (sp),hl ld e,(hl) inc hl ld d,(hl) inc hl ex (sp),hl ld (dosjmp + 1),de ld de,(dosde) call seldos dosjmp: call 0 call sel48 ret c push af ;Error no. ld l,a ld h,0 ld de,p3eno call spdec2 ;2-digit ex de,hl dec hl set 7,(hl) ;Set bit 7 on last character, since this is a ld hl,p3der ;fatal error message pop af ret ; ;One message (not really very informative) ; p3der: defb '+3DOS error ' p3eno: defb '00000' ; ;Bank-switching code to take us to/from the +3DOS environment ; seldos: push af push bc ld a,(bankm) res 4,a or 7 ld bc,bankio di ld (bankm),a out (c),a ei pop bc pop af ret ; sel48: push af push bc ld a,(bankm) set 4,a and 0F8h ld bc,bankio di ld (bankm),a out (c),a ei pop bc pop af ret ; FLAGS3 equ 23398 ;<< v1.03 >> System variable holding no. of drives ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dosinit: ; ;Disable the disc cache and the RAMdisc. We need the space for the ;Z-machine's memory. ; call dodos defw GET_1346 ld (old_1346),de ld (old_1346+2),hl ld hl,7F00h ;No RAMdisc ; ;+3DOS bug workaround: Unless at least one cache buffer is allocated, the ;disc routines get directory data mixed up with file data. The cache also ;means faster access to the bits of the Z-code file not loaded in 1/3/4/0. ; ld de,6020h ;Allow 16k for disc cache. This should speed call dodos ;up access of paged memory. defw SET_1346 ret nc call dodos defw DOS_VERS ld (p3dver),de ; ; << v1.03 Point the CHANGE DISK callback at us. ; ld c,0 ;One drive ld a,(FLAGS3) bit 5,a jr z,setcv inc c ;Two drives setcv: ld a,c ld (unit_b),a ld hl,chgsub call dodos ;Map B: to whatever it's mapped to anyway. defw DOS_MAPB ; ; >> v1.03 ; scf ret ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; dosunit: ; ;Restore the cache and the RAMdisc. ; ld hl,(old_1346+2) ld de,(old_1346) call dodos defw SET_1346 ld a,(unit_b) ;<< v1.03 reverse CHANGE DISK code ld c,a ld hl,(chgdsk) ;+3BASIC "Change disk" routine call dodos defw DOS_MAPB ;>> v1.03 call dodos defw DOS_INIT ret ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Ask the user to change the disc ; chgsub: ld (chgms1),a call sel48 ;Switch to 48 BASIC ROM for keyboard input ld de,chgmsg call print chgs1: call con6 and a jr z,chgs1 ld e,0Dh call opchar ;Carriage return ld e,' ' ld b,63 call string$ ld e,0Dh call opchar jp seldos ;Go back to +3DOS ; chgmsg: defb 13,10,'Please insert the disc for ' chgms1: defb 'Z: and press ENTER',13,'$' chgdsk: defw 0 ;Original CHANGE DISK routine unit_b: defb 0 ;Drive B is unit 0 or 1? old_1346: defw 0 ;Original cache and RAMdisc settings defw 0 p3dver: defw 0 ;The version of +3DOS we are using dosde: defw 0 ;Used to store DE while entering +3DOS ; ;Read/write BC bytes of z-machine memory at HL. ; p3rmem: ld a,b or c scf ret z ld d,b ld e,c ;DE = no. of bytes to read p3rm1: push hl push de call blkadd ;Returns HL = block base, DE = block lenth, C=bank push de ld b,SAVENO call dodos defw DOS_READ jr nc,p3rend pop bc ;BC = no. of bytes read pop hl ;HL = total bytes to transfer and a sbc hl,bc ;HL = bytes left to transfer jr c,p3re2 jr z,p3re2 ;End of transfer ex de,hl ;DE = no. of bytes left to transfer pop hl add hl,bc ;HL = new base address jr p3rm1 ; p3wmem: ld a,b or c scf ret z ld d,b ld e,c ;DE = no. of bytes to write p3wm1: push hl push de call blkadd ;Returns HL = block base, DE = block lenth, C=bank push de ld b,SAVENO call dodos defw DOS_WRITE jr nc,p3rend pop bc ;BC = no. of bytes read pop hl ;HL = total bytes to transfer and a sbc hl,bc ;HL = bytes left to transfer jr c,p3re2 jr z,p3re2 ;End of transfer ex de,hl ;DE = no. of bytes left to transfer pop hl add hl,bc ;HL = new base address jr p3wm1 ; p3re2: pop de scf ret ; p3rend: pop de ;Read failed pop de pop de ret ; blkadd: ld a,h ;Let HL = true address of block, C the bank it is in, rlca ; DE = amount of block within this bank. rlca ;A = 0 - 3, bank number and 3 inc a ;A is 1, 2, 3, 4 cp 1 jr z,blkad1 inc a ;A is 3, 4 or 5 cp 5 jr nz,blkad1 xor a blkad1: ld c,a ;C is bank - 1, 3, 4, 0 push hl ld a,h and 03Fh ;Limit HL to 14 bits ld h,a ;<< v0.02 >> arithmetic bug fix push de ;DE = length of block ld de,4000h and a ex de,hl ;<< v0.02 >> arithmetic bug fix sbc hl,de ;HL = max length in this bank pop de ;DE = length of block ld a,h cp d jr nc,blkad2 ;If HL >= DE, return DE ld a,l cp e jr nc,blkad2 ex de,hl ;DE = length to read/write blkad2: pop hl ;HL = base address ld a,h or 0C0h ld h,a ret ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ;Open the story file ; st_open: ld b,FILENO ld c,05h ;Open read-only ld de,2 ;Open if exists, else complain call dodos defw DOS_OPEN ret nc jr st_op1 ; st_reload: ld b,FILENO ld de,0 ld hl,0 call dodos defw SETPOS st_op1: ret nc ld c,1 ;Load up to the first 64k of the file call ldbank ret nc ld c,3 call ldbank ret nc ld c,4 call ldbank ret nc ld c,0 call ldbank ret ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ; Load 16k from the file into memory bank C ; ldbank: ld b,FILENO ld de,4000h ld hl,0C000h call dodos defw DOS_READ jr c,rdok cp 25 jr z,rdok and a ret ; rdok: scf ret ; ; Initialisation. ; init1: ld (story),de ld a,(typeset) ; choose typeset and a ld hl,set532 jr z,type32 ld hl,set564 type32: ld de,scrset5 ld bc,set564 - set532 ldir call vers ld de,0201h ;0201 => +3 with Amstrad ROMs and a sbc hl,de jr z,init2 ld hl,reqp3 xor a ret reqp3: defb 'Spectrum +3 require' defb 0E4h ;'d'+80h ; init2: ld hl,0FFFFh ;<< v1.12 ld (icpage),hl ld (dcpage),hl ;>> v1.12 Init cache to empty di ld a,(bankm) and 0F8h ;Switch to bank 6 at the same time... or 0Eh ;Activate the shadow screen ld bc,bankio di ld (bankm),a out (c),a ei call dosinit ;Initialise +3DOS ret nc call erall ;Clear the screen ld a,1 ld (cfont),a ;Set current font ld de,signon call print ;Sign on ld hl,(story) pslp: ld e,(hl) inc e ;Print story name (FF-terminated) jr z,pslend dec e call opchar inc hl jr pslp pslend: ld de,sign1 ;Second part of sign-on message call print ld hl,(story) call st_open ;Load the story file ret nc call ZXCLS ld a,6 call selmem scf ret ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; exit1: push af push hl call st_close call dosunit ;Undo anything we did to +3DOS pop hl pop af jp nc,zxerr ld hl,finished jp zxerr ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ICACHE equ 6800h ; << v1.12 Instruction cache... DCACHE equ 6900h ; ... and data cache icpage: defw 0FFFFh dcpage: defw 0FFFFh ; peek1: jr nc,peek2 ;<< 1.12 Handle instruction fetches ld a,e ; a bit differently and a jr z,peek64 ; inst fetch in RAM = same ld a,(icpage+1) cp e jr nz,icmiss ld a,(icpage) cp h jr nz,icmiss ; Cache hit! Yay! ichit: push de ld de,ICACHE chit: push hl ld h,0 add hl,de ld a,(hl) pop hl pop de scf ret ;>> 1.12 ; peek2: ld a,e ;First 64k? If not, access file on disc and a jr z,peek64 peek3: ld a,(dcpage+1) cp e jr nz,dcmiss ld a,(dcpage) cp h jr nz,dcmiss dchit: push de ld de,DCACHE jr chit peek64: push bc push hl ; ; Inline zbank to save on pushes and pops ; ld bc,bankio ld a,h rlca rlca and 3 ;A = Z-machine bank, 0-3 inc a ;A = 1-4 cp 1 jr z,p64a inc a ;A = 1, 3, 4 or 5 cp 5 jr c,p64a xor a ;A = 1, 3, 4 or 0 p64a: di or 8 ;Shadow screen out (c),a pop hl push hl ld a,h or 0C0h ld h,a ;HL = address ld l,(hl) ; L = value ld a,(bankm) out (c),a ei ld a,l pop hl pop bc scf ret ; ; Cache miss. Load the cache with page EH. ; icmiss: push hl ld hl,ICACHE ld (CMADDR),HL pop hl call cmiss ld a,e ld (icpage+1),a ld a,h ld (icpage),a jr ichit ; dcmiss: push hl ld hl,DCACHE ld (CMADDR),HL pop hl call cmiss ld a,e ld (dcpage+1),a ld a,h ld (dcpage),a jr dchit ; CMADDR: defw 0 ; cmiss: push hl push de push ix push bc ld b,FILENO ld d,0 ld l,0 call dodos defw SETPOS jp nc,st_err ld b,FILENO ld c,7 ;Page doesn't matter, the cache is in low 48k. ld de,256 ld hl,(CMADDR) call dodos defw DOS_READ jp nc,st_err pop bc pop ix pop de pop hl ret ; slowpw: and a call peek1 ld b,a inc hl ld a,h or l jr nz,slowp1 inc e slowp1: and a call peek1 inc hl ld c,a ld a,h or l jr nz,slowp2 inc e slowp2: pop af scf ret ; peekw: push hl push de call ipeekw pop de pop hl ret ; ipeekw: push af ld a,e or a ;Is the word in RAM? jr nz,slowpw ld a,l cp 0FFH ;Is there a chance the word might go across 2 pages? jr z,slowpw ; ;Read a word from the low 64k; we know it does not extend over a page boundary ; push de push hl ; ; Inline zbank to save on pushes and pops ; ld bc,bankio ld a,h rlca rlca and 3 ;A = Z-machine bank, 0-3 inc a ;A = 1-4 cp 1 jr z,p64wa inc a ;A = 1, 3, 4 or 5 cp 5 jr c,p64wa xor a ;A = 1, 3, 4 or 0 p64wa: di or 8 ;Shadow screen out (c),a pop hl push hl ld a,h or 0C0h ld h,a ;HL = address ld d,(hl) inc hl ld e,(hl) ;DE = value ld a,(bankm) out (c),a ei ld c,e ld b,d ;Move value to BC pop hl pop de ;EHL = address inc hl inc hl ld a,h ;<< v1.03 Check for rollover at FFFE -> 10000 or l jr nz,fpw1 inc e fpw1: ;>> v1.03 pop af scf ret ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; poke1: push hl push de push bc ld d,a call zbank ld a,0c0h or h ld h,a ;HL |= 0C000h ld (hl),d call nbank pop bc pop de pop hl scf ret ; ; Page header in and out. ; headerin: ld a,1 jp selmem headerout: ld a,6 jp selmem ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; tmem1: ld hl,0bfffh ;Top of memory. The top 16k is a ret ;disk cache; we may have to shrink this. ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ;Switch to the memory bank containing Z-address HL. Pushes the previous ;bank onto the stack. ; zbank: pop bc ;Return address ld a,(bankm) push af push bc and 0F8h ;Only change the memory banking settings ld c,a ld a,h rlca rlca and 3 ;A = Z-machine bank, 0-3 inc a ;A = 1-4 cp 1 jr z,zbank1 inc a ;A = 1, 3, 4 or 5 cp 5 jr c,zbank1 xor a ;A = 1, 3, 4 or 0 zbank1: or c ld bc,bankio di ld (bankm),a out (c),a ret ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; nbank: ld l,a pop bc ;Switch out of the Zmachine bank pop af ;NOTE: pops 1 word off the stack push bc ld bc,bankio di ld (bankm),a out (c),a ei ld a,l ret ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ;Select memory bank in A ; selmem: push af push bc and 7 ld b,a ld a,(bankm) and 0F8h ;Only change the bits we want to or b ld bc,bankio di ld (bankm),a out (c),a ei pop bc pop af ret ; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; ZXERR: push hl call erall pop de call print7 LD IY,5C3AH ;Spectrum system veriables EI ld sp,(ERRSP) ld a,$ff ld (ERRNO),a zxerr2: call con6 or a jr z,zxerr2 ; wait for a keypress di ;Switch back to the conventional screen ld a,(bankm) and 0F0h ld bc,bankio di ld (bankm),a out (c),a ei ret end