SFD700 Version: Updated to have MZ700 and MZ80A drivers selected based on jumper pin

This commit is contained in:
Philip Smart
2026-04-03 14:20:43 +01:00
parent 3f5216dd2b
commit 5b004979d5
15 changed files with 2373 additions and 71 deletions

View File

@@ -102,7 +102,7 @@ HWSEL2: LD A,(BNKCTRLRST)
LD A,BNKDEFMROM_MZ700 ; Setup default MROM for an MZ700, this is a 4K Window into the UROM at F000.
HWSEL21: OUT (REG_FXXX),A
LD A,BNKDEFUROM ; Setup default UROM, this is a 2K Window into the UROM at E800 and contains the RFS.
OUT (REG_EXXX),A
OUT (REG_EXXX),A
NOP ; Nops to allocate space to match RomDisk block.
NOP
NOP
@@ -112,7 +112,6 @@ HWSEL21: OUT (REG_FXXX),A
NOP
NOP
NOP
NOP
ENDIF
ENDM

View File

@@ -284,7 +284,6 @@ MMIO7 EQU 0E7H ; MZ-70
;
REG_EXXX EQU 060H ; A write copies D6:0 into the EXXX page address register to set a uniform 4K block in the region E300:EFFF window.
REG_FXXX EQU 061H ; A write copies D6:0 into the FXXX page address register to set a uniform 4k block in the region F000:FFFF.
REG_MEMMODE EQU 062H ; A write with D0 = low enables FlashROM, D0 = high enables RAM.
SFD700_MODE EQU 063H ; FDC Interface card configured target mode.
FDC_CMD EQU 0D8H ; WD1773 Command Register.
FDC_STATUS EQU 0D8H ; WD1773 Status Register.

View File

@@ -93,9 +93,15 @@ CMDTABLE2: IF BUILD_SFD700 = 1
DB 000H | 018H | 001H
DB 'D' ; Dump Memory.
DW DUMPX
DB 000H | 018H | 002H
DB "FC" ; Save memory to Floppy.
DW SAVEFDCARD
DB 000H | 008H | 002H
DB "FL" ; 'FL' Floppy disk boot (built-in WD1773 FDC).
DW FLOPPY
DB 000H | 018H | 004H
DB "FD2T" ; Copy Floppy to Tape.
DW FD2TAPE
DB 000H | 008H | 002H
DB "FD" ; 'FD' Floppy disk directory listing.
DW FDDIR
@@ -144,6 +150,9 @@ CMDTABLE2: IF BUILD_SFD700 = 1
DB 000H | 020H | 001H
DB 'S' ; Save to CMT
DW SAVEX
DB 000H | 018H | 004H
DB "T2FD" ; Copy Tape to Floppy.
DW TAPE2FD
DB 000H | 000H | 004H
DB "TEST" ; A test function used in debugging.
DW LOCALTEST
@@ -571,15 +580,19 @@ CMDTABLE: IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
DB 000H | 010H | 002H
DB "EC" ; Erase file.
DW ERASESD
IF BUILD_MZ700 = 1
DB 000H | 008H | 002H
DB "FL" ; 'FL' RFS Floppy load/boot.
DB "FL" ; 'FL' Floppy boot (built-in MZ-700 WD1773).
DW FLOPPY
DB 000H | 008H | 002H
DB "FD" ; 'FD' Floppy directory.
DW FDDIR
DB 000H | 008H | 001H
DB 0AAH ; 'f' Original Floppy boot code.
ENDIF
IF BUILD_MZ80A = 1
DB 000H | 008H | 002H
DB "FL" ; 'FL' Floppy boot (external FI ROM at F000H).
DW FDCK
ENDIF
DB 000H | 058H | 001H
DB 'H' ; Help screen (bank 11).
DW HELP
@@ -1316,9 +1329,11 @@ LOADBASIC: LD DE,BASICFILENM
ENDIF ; BUILD_SFD700
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
LOADPROG: LD HL,LOADSDCARD
CALL BKSW0to2
RET
ENDIF
;-------------------------------------------------------------------------------
; END OF RFS COMMAND FUNCTIONS.
@@ -1575,10 +1590,14 @@ DEFAULTFNE: EQU $
INCLUDE "rfs_utilities.asm"
;
; Ensure we fill the entire 2K by padding with FF's.
; RomDisk/picoZ80: EFF8-EFFF are coded latch control registers.
; SFD700: no control registers, bank switching via I/O ports.
;
ALIGN 0EFF8h
ORG 0EFF8h
DB 0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0AAh
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
ALIGN 0EFF8h
ORG 0EFF8h
DB 0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0AAh
ENDIF
IF BUILD_SFD700 = 1
ALIGN 0F000H

View File

@@ -145,6 +145,14 @@ TRK0FD3 EQU 01004H
TRK0FD4 EQU 01005H
RETRIES EQU 01006H
BPARA EQU 01008H
; SAVEFDX work variables (after BPARA's 11 bytes at 1008H-1012H).
FREEDSEC EQU 01014H ; Logical sector of free dir entry.
FREEDDIX EQU 01016H ; Index (0-7) of free entry in sector.
NXTDATASEC EQU 01017H ; Next free data sector (2 bytes LE).
SAVEDRVNO EQU 01019H ; Saved drive number for save operations.
; Temporary variables for TMPSTACKP save/restore (MUST be in RAM, not ROM).
FD7RS_TSP EQU 0101AH ; FD7READSAFE saved TMPSTACKP (2 bytes).
FD7ERR_TSP EQU 0101CH ; FD7ERR saved TMPSTACKP (2 bytes).
;-------------------------------------------------------------------------------
; START OF FLOPPY DISK CONTROLLER FUNCTIONALITY
@@ -160,7 +168,12 @@ FDCKROM: LD A,(0F000h)
RET
FLOPPY: IF BUILD_ROMDISK+BUILD_PICOZ80+BUILD_SFD700 = 1
IF BUILD_MZ80A = 1
;
; NOTE: MZ-80A FDC code removed from bank 1.
; - SFD700+MZ80A: handled by bank 8 (FD80A_BOOT) with HW acceleration.
; - ROMDISK/picoZ80+MZ80A: uses physical MZ-80A FI ROM at F000H via FDCK.
;
IF 0 = 1 ; MZ-80A section removed.
PUSH DE ; Preserve pointer to input buffer.
LD DE,BPARA ; Copy disk parameter block into RAM work area. (From)
LD HL,PRMBLK ; (To)
@@ -545,8 +558,24 @@ L0300: IN A,(0D8H) ; State
; RETRIES (1006H): Retry counter
;-------------------------------------------------------------------------------
; Method to boot from MZ-700 floppy disk.
;
; Boot entry. MZ-80A mode dispatch temporarily disabled for testing.
; Initialize timer (as MZ-1E05 does) and reset FDC.
PUSH DE ; Save command line pointer.
XOR A
LD DE,0
CALL ?TMST ; Timer init.
POP DE ; Restore command line pointer.
XOR A
OUT (FDC_DRIVE),A ; Deselect all drives, motor off.
LD A,0D8H ; Force interrupt.
CPL
OUT (FDC_CMD),A
LD B,0 ; ~1.7ms delay for FDC to settle.
FLDLY1: DJNZ FLDLY1
XOR A
LD (MOTON),A
LD (TRK0FD1),A
LD (FDCCMD),A
PUSH DE ; Preserve pointer to input buffer.
LD DE,BPARA ; Copy disk parameter block into RAM work area.
LD HL,PRMBLK
@@ -556,7 +585,6 @@ L0300: IN A,(0D8H) ; State
LD A,(DE) ; Check if drive number given on command line.
CP 00DH
JR NZ,FD7BOOT
CALL FD7INIT ; Initialise disk and flags.
FD7PRMPT: LDDE MSGBOOTDRV
LD HL,PRINTMSG
CALL BKSW1to6
@@ -602,6 +630,7 @@ FD7CHK: LD C,(HL)
CALL BKSW1to6
;
; Extract load parameters from boot sector.
LD IX,BPARA ; Restore IX (PRINTMSG may corrupt it).
LD HL,(0CF16H) ; Load/target address.
LD A,H
OR L
@@ -649,12 +678,18 @@ FD7NOTMST: CALL FD7INIT
LDDE MSGDSKNOTMST
JR FD7ERR
FD7LOADERR: LDDE MSGLOADERR
FD7ERR: LD HL,PRINTMSG
FD7ERR: LD HL,(TMPSTACKP) ; Save TMPSTACKP before BKSW overwrites it.
LD (FD7ERR_TSP),HL
LD HL,PRINTMSG
CALL BKSW1to6
LD DE,ERRTONE
CALL MELDY
LD SP,(TMPSTACKP) ; Recover stack pointer.
LD HL,(FD7ERR_TSP) ; Restore saved TMPSTACKP.
LD SP,HL ; Recover stack pointer.
RET
; FD7ERR_TSP now in RAM (EQU 0101CH).
;-------------------------------------------------------------------------------
; MZ-700 FDC LOW-LEVEL ROUTINES
@@ -687,6 +722,7 @@ FD7INIT: PUSH AF
; Motor on: enable motor and wait for spinup.
FD7MOTON: LD A,080H
OUT (FDC_DRIVE),A
CALL FD7DLY80U ; Settle delay after motor on.
LD B,16
FD7MTD1: CALL FD7DLY60M
DJNZ FD7MTD1
@@ -701,6 +737,7 @@ FD7READY: LD A,(MOTON)
LD A,(IX+0) ; Drive number.
OR 084H
OUT (FDC_DRIVE),A ; Drive select + motor on.
CALL FD7DLY80U ; Settle delay after drive select.
XOR A
LD (FDCCMD),A
CALL FD7DLY60M
@@ -798,6 +835,33 @@ FD7BSF3: DEC E
POP DE
JP FD7DSKERR
;-------------------------------------------------------------------------------
; FD7READSAFE - Safe read wrapper. Catches FD7ERR longjmp and returns
; with carry set instead of corrupting the caller's stack.
; IX = parameter block (BPARA). Returns: carry clear=OK, carry set=error.
; Mechanism: pushes a recovery address on the stack, then sets TMPSTACKP
; to SP. FD7ERR's "LD SP,(TMPSTACKP) / RET" pops the recovery address,
; landing at FD7RS_REC instead of unwinding the entire call chain.
;-------------------------------------------------------------------------------
FD7READSAFE: LD HL,(TMPSTACKP)
LD (FD7RS_TSP),HL ; Save original TMPSTACKP.
LD HL,FD7RS_REC ; Recovery address.
PUSH HL ; Push onto stack.
LD (TMPSTACKP),SP ; FD7ERR longjmp lands here → pops FD7RS_REC.
CALL FD7READ ; Normal return if success.
POP HL ; Remove recovery address.
LD HL,(FD7RS_TSP)
LD (TMPSTACKP),HL ; Restore TMPSTACKP.
OR A ; Clear carry = success.
RET
; FD7ERR longjmp caught: SP restored, RET popped FD7RS_REC → here.
; Error message and tone already displayed by FD7ERR.
FD7RS_REC: LD HL,(FD7RS_TSP)
LD (TMPSTACKP),HL ; Restore TMPSTACKP.
SCF ; Set carry = error.
RET
; FD7RS_TSP now in RAM (EQU 0101AH).
; Sequential read from disk.
; IX = parameter block (BPARA).
FD7READ: CALL FD7CNVRT ; Convert logical sector to track/sector.
@@ -952,7 +1016,22 @@ FD7DLYT: DEC DE
; Input: DE = pointer to optional drive number on command line.
;-------------------------------------------------------------------------------
FDDIR: ; Parse drive number (default = drive 1 = index 0).
FDDIR: ; Initialize timer and reset FDC.
XOR A
LD DE,0
CALL ?TMST ; Timer init.
XOR A
OUT (FDC_DRIVE),A ; Deselect all drives, motor off.
LD A,0D8H ; Force interrupt.
CPL
OUT (FDC_CMD),A
LD B,0 ; ~1.7ms delay.
FDDLY1: DJNZ FDDLY1
XOR A
LD (MOTON),A
LD (TRK0FD1),A
LD (FDCCMD),A
; Parse drive number (default = drive 1 = index 0).
LD A,(DE)
CP 00DH
JR Z,FDDIR_DEF
@@ -990,6 +1069,7 @@ FDDIR_GO: LD (BPARA),A ; Store
; Read boot sector (sector 0 = IPL) and verify signature.
LD IX,BPARA
CALL FD7READ
JP C,FDDIR_END ; Read error.
LD HL,0CF00H
LD DE,DSKID
LD B,7
@@ -1003,11 +1083,11 @@ FDDIR_IPL: LD A,(DE)
LD DE,0CF07H
LD HL,PRTFN
CALL BKSW1to6
LD HL,(0CF16H)
PUSH HL
LD HL,(0CF18H)
PUSH HL
LD BC,(0CF14H)
LD HL,(0CF18H) ; Exec addr.
PUSH HL ; Stack bottom: exec.
LD HL,(0CF16H) ; Load addr.
PUSH HL ; Stack top: load.
LD BC,(0CF14H) ; Size.
LDDE MSGFDINFO
LD HL,PRINTMSG
CALL BKSW1to6
@@ -1022,17 +1102,19 @@ FDDIR_IPL: LD A,(DE)
; Get number of directory sectors from IPL offset +1EH.
LD A,(0CF1EH) ; Start data sector (low byte).
SUB 16 ; Directory sectors = startDataSec - 16.
JR Z,FDDIR_END ; No directory sectors.
JR C,FDDIR_END
JP Z,FDDIR_END ; No directory sectors.
JP C,FDDIR_END
LD C,A ; C = number of directory sectors.
LD A,16 ; First directory sector.
;
FDDIR_SEC: ; Read next directory sector.
PUSH AF ; Save current sector number.
PUSH BC ; Save remaining sector count.
LD IX,BPARA ; Restore IX (may be corrupted by PRINTMSG).
LD (IX+1),A
LD (IX+2),0
CALL FD7READ
CALL FD7READ
JR C,FDDIR_RERR ; Read error → clean stack and exit.
;
; First sector (16): skip entry 0 (FAT header), scan entries 1-7.
; Subsequent sectors: scan all 8 entries.
@@ -1048,12 +1130,25 @@ FDDIR_SEC: ; Read next directory sector.
FDDIR_AL8: LD HL,0CF00H ; Start from entry 0.
LD B,8 ; 8 entries per sector.
;
FDDIR_ENT: ; Check type byte at (HL). 00 = empty/deleted, FF = unused → skip.
FDDIR_ENT: ; Check type byte at (HL). Only 01-7F are valid file entries.
LD A,(HL)
OR A
JR Z,FDDIR_NXE
JR Z,FDDIR_NXE ; 00 = deleted/empty.
CP 080H
JR NC,FDDIR_NXE ; >= 80H = header/system/uninit.
; Validate: check SectorAddress is not 0000 or FFFF (uninit garbage).
PUSH HL
LD DE,01EH
ADD HL,DE
LD A,(HL)
INC HL
LD D,(HL) ; D=high, A=low of SectorAddress.
POP HL
OR D
JR Z,FDDIR_NXE ; SectorAddress=0000 → skip.
AND D
CP 0FFH
JR Z,FDDIR_NXE
JR Z,FDDIR_NXE ; SectorAddress=FFFF → skip.
;
; Valid entry: print filename and load/exec/size.
LD (TMPADR),HL ; Save entry base.
@@ -1063,23 +1158,27 @@ FDDIR_ENT: ; Check type byte at (HL). 00 = empty/deleted, FF = unused → skip
EX DE,HL
LD HL,PRTFN
CALL BKSW1to6
; Read size from base+12H, load from base+14H, exec from base+16H.
; Read size from +14H, load from +16H, exec from +18H.
; (Dir entry: +12=LockFlag, +13=DummyFlag, +14=FileSize,
; +16=LoadAddress, +18=ExecAddress, +1E=SectorAddress.)
LD HL,(TMPADR)
LD DE,012H
LD DE,014H
ADD HL,DE
LD C,(HL)
INC HL
LD B,(HL) ; BC = size.
INC HL ; +14H = load addr.
LD B,(HL) ; BC = file size.
INC HL ; +16H = load addr.
LD E,(HL)
INC HL
LD D,(HL)
PUSH DE ; Stack: load addr.
INC HL ; +16H = exec addr.
PUSH DE ; Save load addr.
INC HL ; +18H = exec addr.
LD E,(HL)
INC HL
LD D,(HL)
PUSH DE ; Stack: exec addr.
LD D,(HL) ; DE = exec addr.
POP HL ; HL = load addr.
PUSH DE ; Stack bottom: exec.
PUSH HL ; Stack top: load (MSGFDINFO pops load first).
LDDE MSGFDINFO
LD HL,PRINTMSG
CALL BKSW1to6
@@ -1098,10 +1197,474 @@ FDDIR_NXE: LD DE,32 ; Next 3
INC A ; Next sector.
DEC C
JR NZ,FDDIR_SEC ; More directory sectors.
JP FDDIR_END ; Done — skip error recovery POPs.
;
FDDIR_RERR: POP BC ; Clean stack from FDDIR_SEC PUSH.
POP AF
FDDIR_END: CALL FD7INIT
RET
;-------------------------------------------------------------------------------
; FD7WRITE - Sequential write to disk.
; Mirror of FD7READ but uses OUTI for data output.
; IX = parameter block (BPARA): drive, logical sector, size, source addr.
;-------------------------------------------------------------------------------
; Sequential write to disk.
; Returns: carry clear = success, carry set = error.
; Does NOT call FD7DSKERR — caller handles errors via carry flag.
FD7WRITE: CALL FD7CNVRT ; Convert logical sector to track/sector.
CALL FD7PRST1 ; Setup params, HL = source address.
FD7WE8: CALL FD7SIDST ; Set side/head.
CALL FD7SEEK
JR NZ,FD7WERR ; Seek failed.
CALL FD7PRST2 ; Set track & sector registers.
DI
LD A,0A4H ; Write sector (multiple records).
CALL FD7CMD2
FD7WE6: LD B,0 ; 256 bytes per sector.
FD7WE4: IN A,(FDC_CMD)
RRCA
JR C,FD7WE3 ; Busy dropped = done.
RRCA
JR C,FD7WE4 ; DRQ not set, keep waiting.
OUTI ; Write byte: OUT(C)=(HL), B--, HL++.
JR NZ,FD7WE4
;
INC (IX+8) ; Next sector.
LD A,(IX+8)
CP 17 ; Past last sector on track?
JR Z,FD7WESEC
DEC D ; Decrement sector count.
JR NZ,FD7WE6 ; More sectors to write.
JR FD7WEDONE
FD7WESEC: DEC D
FD7WEDONE: LD A,0D8H ; Force interrupt.
CPL
OUT (FDC_CMD),A
CALL FD7BSYON
FD7WE3: EI
IN A,(FDC_CMD)
CPL
AND 07CH ; Check error bits (WP, WF, RNF, CRC, lost data).
JR NZ,FD7WERR ; Write error.
CALL FD7ADJ ; Adjust sector/track.
JR Z,FD7WEND ; All sectors written.
LD A,(IX+7) ; Next track.
JR FD7WE8
FD7WEND: LD A,080H
OUT (FDC_DRIVE),A ; Keep motor on.
OR A ; Clear carry = success.
RET
FD7WERR: EI
SCF ; Set carry = error.
RET
;-------------------------------------------------------------------------------
; LOADFDCP - Load file from floppy into memory, populating CMT header.
; Reads boot sector, extracts file info, reads program data to 0x1200.
; Sets RESULT=0 on success, non-zero on error.
; Called from bank 3 for FD2T command.
;-------------------------------------------------------------------------------
;-------------------------------------------------------------------------------
; LOADFDCP - Load FIRST file from floppy directory into memory at 0x1200.
; Scans directory for first valid entry, populates CMT header, loads data.
; Output: RESULT = 0 success, 0xFE FD7ERR handled, 0xFF caller shows error.
;-------------------------------------------------------------------------------
LOADFDCP: LD A,0FEH
LD (RESULT),A ; 0xFE = FD7ERR will handle errors.
; Full FDC init: timer, deselect, force interrupt, clear flags.
PUSH DE
XOR A
LD DE,0
CALL ?TMST
POP DE
XOR A
OUT (FDC_DRIVE),A
LD A,0D8H
CPL
OUT (FDC_CMD),A
LD B,0
LFDCP_DLY: DJNZ LFDCP_DLY
XOR A
LD (MOTON),A
LD (TRK0FD1),A
; Init BPARA.
LD HL,PRMBLK
LD DE,BPARA
LD BC,11
LDIR
LD IX,BPARA
;
; Scan directory for first valid file entry.
LD A,16 ; First directory sector.
LD C,16 ; 16 sectors.
LFDCP_DSC: PUSH AF
PUSH BC
LD (IX+1),A
LD (IX+2),0
LD HL,0CF00H
LD (IX+5),L
LD (IX+6),H
LD (IX+3),0
LD (IX+4),1
CALL FD7READ ; (FD7ERR longjumps on error.)
POP BC
POP AF
;
LD HL,0CF00H
LD B,8
LFDCP_ENT: LD A,(HL)
OR A
JR Z,LFDCP_NXE ; 00 = empty.
CP 080H
JR NC,LFDCP_NXE ; >=80 = header/system.
; Valid entry — check if filename matches NAME.
PUSH HL
PUSH BC
INC HL ; +01 = filename in entry.
LD DE,NAME
LD B,17 ; Compare 17 bytes.
LFDCP_CMP: LD A,(DE)
CP (HL)
JR NZ,LFDCP_NOM ; Mismatch.
CP 00DH ; Both end with CR?
JR Z,LFDCP_MAT ; Match!
INC HL
INC DE
DJNZ LFDCP_CMP
LFDCP_MAT: POP BC
POP HL
JP LFDCP_GOT ; Found matching file.
LFDCP_NOM: POP BC
POP HL
LFDCP_NXE: LD DE,32
ADD HL,DE
DJNZ LFDCP_ENT
; No entry in this sector, try next.
POP AF ; Wait, stack issue...
; Actually the POP AF/BC was already done above. Just continue.
INC A
DEC C
JP NZ,LFDCP_DSC
; No file found on disk.
JP LFDCP_ERR
;
LFDCP_GOT: ; HL = entry base of first valid file.
; Populate CMT header from directory entry.
LD A,(HL) ; +00 = FileType.
LD (ATRB),A
PUSH HL
INC HL ; +01 = Filename.
LD DE,NAME
LD BC,17
LDIR
POP HL
PUSH HL
LD DE,014H
ADD HL,DE ; +14 = FileSize.
LD E,(HL)
INC HL
LD D,(HL)
LD (SIZE),DE
INC HL ; +16 = LoadAddress.
LD E,(HL)
INC HL
LD D,(HL)
LD (DTADR),DE
INC HL ; +18 = ExecAddress.
LD E,(HL)
INC HL
LD D,(HL)
LD (EXADR),DE
POP HL
PUSH HL
LD DE,01EH
ADD HL,DE ; +1E = SectorAddress.
LD E,(HL)
INC HL
LD D,(HL)
POP HL
; DE = start sector, load data to 0x1200.
LD (IX+1),E
LD (IX+2),D
LD HL,(SIZE)
LD (IX+3),L
LD (IX+4),H
LD HL,01200H
LD (IX+5),L
LD (IX+6),H
LD (DTADR),HL ; Override load addr for copy.
CALL FD7READ
CALL FD7INIT
XOR A
LD (RESULT),A ; Success.
RET
LFDCP_ERR: CALL FD7INIT
LD A,0FFH
LD (RESULT),A
RET
;-------------------------------------------------------------------------------
; SAVEFDX - Save memory to floppy disk with IPL boot header.
; Writes an IPL boot sector at sector 0 and program data from sector 1+.
; Uses CMT header fields: NAME, SIZE, DTADR, EXADR.
; Called from bank 3 for T2FD and FC commands.
;-------------------------------------------------------------------------------
;-------------------------------------------------------------------------------
; SAVEFDX - Save file to floppy disk directory.
; Scans directory for a free entry, calculates next free data sector,
; writes program data, then updates the directory entry.
; Input: NAME, SIZE, DTADR, EXADR, ATRB set by caller.
; Output: RESULT = 0 on success, 0xFF on error.
;-------------------------------------------------------------------------------
SAVEFDX: LD A,0FFH
LD (RESULT),A ; Default = error.
; Full FDC init: timer, deselect, force interrupt, clear flags.
PUSH DE
XOR A
LD DE,0
CALL ?TMST ; Timer init (required for FDC).
POP DE
XOR A
OUT (FDC_DRIVE),A ; Deselect drives.
LD A,0D8H
CPL
OUT (FDC_CMD),A ; Force interrupt.
LD B,0
SFDX_DLY: DJNZ SFDX_DLY ; Settle delay.
XOR A
LD (MOTON),A
LD (TRK0FD1),A
; Init BPARA from PRMBLK and set drive 0.
LD HL,PRMBLK
LD DE,BPARA
LD BC,11
LDIR
LD IX,BPARA
; Init scan variables.
LD A,0FFH
LD (FREEDSEC),A ; FF = no free entry found yet.
LD HL,32 ; First data sector after directory.
LD (NXTDATASEC),HL
;
; --- Scan directory: logical sectors 16-31 (Track 0, Head 1). ---
LD A,16 ; First directory sector.
LD C,16 ; 16 sectors to scan.
SFDX_DSEC: LD (SAVEDRVNO),A ; Save current sector for SFDX_FREE.
PUSH AF
PUSH BC
LD IX,BPARA
LD (IX+1),A ; Logical sector.
LD (IX+2),0
LD HL,0CF00H ; Buffer.
LD (IX+5),L
LD (IX+6),H
LD (IX+3),0
LD (IX+4),1 ; 1 sector = 256 bytes.
CALL FD7READ
POP BC
POP AF
JP C,SFDX_ERR ; Read error.
PUSH AF
PUSH BC
;
; Scan 8 entries in this sector.
LD HL,0CF00H
LD B,8
SFDX_DENT: PUSH HL
PUSH BC
LD A,(HL) ; FileType.
OR A
JR Z,SFDX_FREE ; 00 = deleted/free entry.
CP 0FFH
JR Z,SFDX_FREE ; FF = uninitialized/free entry.
CP 080H
JR NC,SFDX_NXTE ; >=80H = header/system, skip.
; Check SectorAddress — if 0000 or FFFF, treat as free (uninit garbage).
PUSH HL
LD DE,01EH
ADD HL,DE
LD E,(HL)
INC HL
LD D,(HL) ; DE = SectorAddress.
POP HL
LD A,E
OR D
JR Z,SFDX_FREE ; SectorAddress=0000 → free.
LD A,E
AND D
CP 0FFH
JR Z,SFDX_FREE ; SectorAddress=FFFF → free.
PUSH HL
LD BC,014H
ADD HL,BC
LD C,(HL)
INC HL
LD B,(HL) ; BC = FileSize.
POP HL
; Sectors used = (FileSize + 255) / 256 = (FileSize >> 8) + ((FileSize & FF) ? 1 : 0).
LD A,C
OR A
JR Z,SFDX_NRU
INC B ; Round up.
SFDX_NRU: LD C,B
LD B,0 ; BC = sectors used.
EX DE,HL ; HL = SectorAddress.
ADD HL,BC ; HL = end sector.
EX DE,HL ; DE = end sector.
; Update NXTDATASEC if this file ends later.
PUSH HL
LD HL,(NXTDATASEC)
OR A
SBC HL,DE ; HL = NXTDATASEC - endSec.
POP HL
JR NC,SFDX_NXTE ; NXTDATASEC >= endSec, no update.
LD (NXTDATASEC),DE ; Update with higher value.
JR SFDX_NXTE
;
SFDX_FREE: ; Found a free entry. Record if first one found.
LD A,(FREEDSEC)
CP 0FFH
JR NZ,SFDX_NXTE ; Already have one.
; Record: FREEDSEC = current logical sector, FREEDDIX = entry index.
; Entry index = 8 - B (since B counts down from 8).
POP BC ; B = entries remaining.
POP HL
PUSH HL
PUSH BC
LD A,8
SUB B
LD (FREEDDIX),A
LD A,(SAVEDRVNO) ; Sector number saved at loop start.
LD (FREEDSEC),A
;
SFDX_NXTE: POP BC
POP HL
LD DE,32 ; Next 32-byte entry.
ADD HL,DE
DEC B
JR NZ,SFDX_DENT
;
POP BC
POP AF
INC A ; Next directory sector.
DEC C
JP NZ,SFDX_DSEC
;
; --- Check we found a free entry. ---
LD A,(FREEDSEC)
CP 0FFH
RET Z ; No free entry → error.
;
; --- Write file data at NXTDATASEC. ---
LD IX,BPARA
LD HL,(NXTDATASEC)
LD (IX+1),L
LD (IX+2),H
LD HL,(SIZE)
LD (IX+3),L
LD (IX+4),H
LD HL,(DTADR)
LD (IX+5),L
LD (IX+6),H
CALL FD7WRITE
JP C,SFDX_ERR
;
; --- Re-read directory sector with free entry. ---
LD IX,BPARA
LD A,(FREEDSEC)
LD (IX+1),A
LD (IX+2),0
LD HL,0CF00H
LD (IX+5),L
LD (IX+6),H
LD (IX+3),0
LD (IX+4),1
CALL FD7READ
JP C,SFDX_ERR ; Read error.
;
; --- Fill directory entry. ---
LD A,(FREEDDIX)
LD HL,0CF00H
OR A
JR Z,SFDX_FILL ; Index 0 → HL already at CF00.
LD DE,32
SFDX_FMUL: ADD HL,DE
DEC A
JR NZ,SFDX_FMUL
SFDX_FILL: ; HL = entry base. Clear 32 bytes.
PUSH HL
LD D,H
LD E,L
INC DE
LD (HL),0
LD BC,31
LDIR
POP HL
; +00: FileType from ATRB (with CMT→FD type conversion).
LD A,(ATRB)
CP 005H ; CMT type 05 (RB) → FD type 02 (BTX).
JR NZ,SFDX_FT1
LD A,002H
SFDX_FT1: LD (HL),A
; +01: FileName from NAME (17 bytes).
PUSH HL
INC HL
EX DE,HL
LD HL,NAME
LD BC,17
LDIR
POP HL
; +14: FileSize from SIZE.
PUSH HL
LD DE,014H
ADD HL,DE
LD DE,(SIZE)
LD (HL),E
INC HL
LD (HL),D
; +16: LoadAddress from DTADR.
INC HL
LD DE,(DTADR)
LD (HL),E
INC HL
LD (HL),D
; +18: ExecAddress from EXADR.
INC HL
LD DE,(EXADR)
LD (HL),E
INC HL
LD (HL),D
POP HL
; +1E: SectorAddress = NXTDATASEC.
PUSH HL
LD DE,01EH
ADD HL,DE
LD DE,(NXTDATASEC)
LD (HL),E
INC HL
LD (HL),D
POP HL
;
; --- Write directory sector back. ---
LD A,(FREEDSEC)
LD (IX+1),A
LD (IX+2),0
LD HL,0CF00H
LD (IX+5),L
LD (IX+6),H
LD (IX+3),0
LD (IX+4),1
CALL FD7WRITE
JP C,SFDX_ERR
;
CALL FD7INIT
XOR A
LD (RESULT),A ; Success.
RET
SFDX_ERR: CALL FD7INIT
RET ; RESULT still = 0xFF.
ENDIF
ENDIF
@@ -1119,19 +1682,27 @@ FDDIR_END: CALL FD7INIT
; Error tone.
ERRTONE: DB "A0", 0D7H, "ARA", 0D7H, "AR", 00DH
; Boot disk identifier - 002H prefix for MZ-80A, 003H prefix for MZ-700.
DSKID: IF BUILD_MZ80A = 1
DB 002H, "IPLPRO"
; Boot disk identifier - prefix set at runtime for SFD700, compile-time otherwise.
; (Glass: label must be outside IF for global visibility.)
DSKID: IF BUILD_SFD700 = 1
DB 003H, "IPLPRO" ; Default MZ-700; compile-time value for BUILD_MZ700.
ELSE
DB 003H, "IPLPRO"
IF BUILD_MZ80A = 1
DB 002H, "IPLPRO"
ELSE
DB 003H, "IPLPRO"
ENDIF
ENDIF
; Parameter block: drive 0, sector 0, 256 bytes, load address differs.
; MZ-80A loads boot sector to CE00H, MZ-700 loads to CF00H.
PRMBLK: IF BUILD_MZ80A = 1
DB 000H, 000H, 000H, 000H, 001H, 000H, 0CEH, 000H, 000H, 000H, 000H
; Parameter block: drive 0, sector 0, 256 bytes, load address set at runtime for SFD700.
PRMBLK: IF BUILD_SFD700 = 1
DB 000H, 000H, 000H, 000H, 001H, 000H, 0CFH, 000H, 000H, 000H, 000H ; Default MZ-700; patched at runtime.
ELSE
DB 000H, 000H, 000H, 000H, 001H, 000H, 0CFH, 000H, 000H, 000H, 000H
IF BUILD_MZ80A = 1
DB 000H, 000H, 000H, 000H, 001H, 000H, 0CEH, 000H, 000H, 000H, 000H
ELSE
DB 000H, 000H, 000H, 000H, 001H, 000H, 0CFH, 000H, 000H, 000H, 000H
ENDIF
ENDIF
IF BUILD_MZ80A = 1

View File

@@ -389,7 +389,10 @@ HELPSCR: IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
DB "DXXXX[YYYY] - dump mem X to Y.", 00DH
DB "DASMXXXX[YYYY]", 00DH
DB " disassemble X to Y", 00DH
DB "FC[XXXXYYYYZZZZ] - save mem to floppy.", 00DH
DB " X=start,Y=end,Z=exec", 00DH
DB "FD/FL - fd dir/boot", 00DH
DB "FD2T - copy floppy to tape.", 00DH
DB "H - this help screen.", 00DH
DB "IR - rfs rom dir listing.", 00DH
DB "JXXXX - jump to location X.", 00DH
@@ -400,12 +403,13 @@ HELPSCR: IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
DB "P - test printer.", 00DH
;DB "QD/QL - QD dir/boot", 00DH
DB "R - test dram memory.", 00DH
DB "SD2T - copy sd card to tape.", 00DH
;DB "SD2T - copy sd card to tape.", 00DH
DB "ST[XXXXYYYYZZZZ] - save mem to tape.", 00DH
DB "SC[XXXXYYYYZZZZ] - save mem to card.", 00DH
;DB "SC[XXXXYYYYZZZZ] - save mem to card.", 00DH
DB " X=start,Y=end,Z=exec", 00DH
DB "T - test timer.", 00DH
DB "T2SD - copy tape to sd card.", 00DH
;DB "T2SD - copy tape to sd card.", 00DH
DB "T2FD - copy tape to floppy.", 00DH
DB "V - verify tape save.", 00DH
DB 000H
ENDIF

View File

@@ -218,7 +218,28 @@ TDELAYB1: RRA
;-------------------------------------------------------------------------------
; START OF SD CONTROLLER FUNCTIONALITY
; SD card hardware not present on SFD700 — exclude for that build.
;-------------------------------------------------------------------------------
; Dummy EQUs for SD functions — not used on SFD700 (no SD hardware).
; Bank 0 CMT intercept handlers reference these. Must be outside IF for Glass visibility.
SDINIT EQU BUILD_SFD700 * 0
LOADSDCARD EQU BUILD_SFD700 * 0
LOADSDCARDX EQU BUILD_SFD700 * 0
LOADSDCP EQU BUILD_SFD700 * 0
LOADSDINF EQU BUILD_SFD700 * 0
LOADSDDATA EQU BUILD_SFD700 * 0
LOADSD3 EQU BUILD_SFD700 * 0
SAVESDCARD EQU BUILD_SFD700 * 0
SAVESDCARDX EQU BUILD_SFD700 * 0
SAVESDDATA EQU BUILD_SFD700 * 0
DIRSDCARD EQU BUILD_SFD700 * 0
FINDSDX EQU BUILD_SFD700 * 0
SDPRINT EQU BUILD_SFD700 * 0
LOADSD9BC EQU BUILD_SFD700 * 0
LOADSD9 EQU BUILD_SFD700 * 0
LOADSD11 EQU BUILD_SFD700 * 0
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
;-------------------------------------------------------------------------------
; Hardware SPI SD Controller (HW_SPI_ENA = 1)
@@ -1612,6 +1633,7 @@ LOADSD9BC: LD H,B
;-------------------------------------------------------------------------------
; END OF SD CONTROLLER FUNCTIONALITY
;-------------------------------------------------------------------------------
ENDIF ; BUILD_ROMDISK+BUILD_PICOZ80 (SD controller)
; RomDisk, top 8 bytes are used by the control registers when enabled so dont use the space.
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1

View File

@@ -143,21 +143,16 @@ BKSWRET3: POP AF ; G
RET
;-------------------------------------------------------------------------------
; START OF TAPE/SD CMDLINE TOOLS FUNCTIONALITY
; START OF TAPE/SD/FD CMDLINE TOOLS FUNCTIONALITY
;-------------------------------------------------------------------------------
; Method to copy an application on a tape to an SD stored application. The tape drive is read and the first
; encountered program is loaded into memory at 0x1200. The CMT header is populated with the correct details (even if
; the load address isnt 0x1200, the CMT Header contains the correct value).
; A call is then made to write the application to the SD card.
;
TAPE2SD: ; Load from tape into memory, filling the tape CMT header and loading data into location 0x1200.
LD HL,LOADTAPECP ; Call the Loadtape command, non execute version to get the tape contents into memory.
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
; Method to copy an application on a tape to an SD stored application.
TAPE2SD: LD HL,LOADTAPECP
CALL BKSW3to4
LD A,(RESULT)
OR A
JR NZ,TAPE2SDERR
; Save to SD Card.
LD HL,SAVESDCARDX
CALL BKSW3to2
LD A,(RESULT)
@@ -171,12 +166,7 @@ TAPE2SDERR2:LD HL,PRINTMSG
RET
; Method to copy an SD stored application to a Cassette tape in the CMT.
; The directory entry number or filename is passed to the command and the entry is located within the SD
; directory structure. The file is then loaded into memory and the CMT header populated. A call is then made
; to write out the data to tap.
;
SD2TAPE: ; Load from SD, fill the CMT header then call CMT save.
LD HL,LOADSDCP
SD2TAPE: LD HL,LOADSDCP
CALL BKSW3to2
LD A,(RESULT)
OR A
@@ -190,8 +180,97 @@ SD2TAPE: ; Load from SD, fill the CMT header then call CMT save.
SD2TAPEERR: LDDE MSGSD2TERR
JR TAPE2SDERR2
RET
ENDIF
IF BUILD_SFD700 = 1
;-------------------------------------------------------------------------------
; END OF TAPE/SD CMDLINE TOOLS FUNCTIONALITY
; FD2TAPE - Copy floppy disk file to cassette tape.
; Reads boot sector + file from floppy, populates CMT header, writes to tape.
;-------------------------------------------------------------------------------
FD2TAPE: ; Prompt for filename to extract from floppy.
CALL NL
LDDE MSGFDFNAME
CALL MSG ; "FILENAME? "
LD DE,BUFER
CALL GETL ; Get user input.
LD HL,BUFER+10 ; Skip past prompt echo.
LD DE,NAME
LD BC,FNSIZE
LDIR ; Copy filename to NAME.
;
LD HL,LOADFDCP ; Load named file from floppy (bank 1).
CALL BKSW3to1
LD A,(RESULT)
OR A
JR Z,FD2T_SAV ; Success → save to tape.
CP 0FEH
RET Z ; 0xFE = FD7ERR already showed error.
JR FD2TERR ; 0xFF = show our error message.
FD2T_SAV: LD HL,SAVECMT ; Write to tape (bank 4).
CALL BKSW3to4
LD A,(RESULT)
OR A
JR NZ,FD2TERR
LDDE MSGFD2TOK
JR FDTMSG
FD2TERR: LDDE MSGFD2TERR
; Print local message: DE = string in THIS bank (CR-terminated).
; Uses monitor ROM MSG function directly (no bank switch needed).
FDTMSG: CALL NL
CALL MSG ; Print CR-terminated string at DE.
RET
;-------------------------------------------------------------------------------
; TAPE2FD - Copy cassette tape file to floppy disk.
; Loads from tape, then writes IPL header + data to floppy.
;-------------------------------------------------------------------------------
TAPE2FD: LD HL,LOADTAPECP ; Load from tape (bank 4).
CALL BKSW3to4
LD A,(RESULT)
OR A
JR NZ,T2FDERR
LD HL,SAVEFDX ; Write to floppy (bank 1).
CALL BKSW3to1
LD A,(RESULT)
OR A
JR NZ,T2FDERR
LDDE MSGT2FDOK
JR FDTMSG
T2FDERR: LDDE MSGT2FDERR
JR FDTMSG
;-------------------------------------------------------------------------------
; SAVEFDCARD - Save memory region to floppy disk (FC command).
; Gets CMT parameters (name, start, size, exec) then writes to floppy.
;-------------------------------------------------------------------------------
SAVEFDCARD: CALL GETCMTPARM ; Get parameters (same bank).
LD A,C
OR A
JR NZ,SFDC_ERR ; C=1 means input error.
LD A,001H ; Default file type = OBJ.
LD (ATRB),A
LD HL,SAVEFDX ; Write to floppy (bank 1).
CALL BKSW3to1
LD A,(RESULT)
OR A
JR NZ,SFDC_ERR
LDDE MSGFCSAVEOK
JR FDTMSG
SFDC_ERR: LDDE MSGFCSAVEERR
JR FDTMSG
; Messages for floppy/tape transfer commands (local to bank 3).
MSGFDFNAME: DB "FILENAME? ", 00DH
MSGFD2TOK: DB "FLOPPY -> TAPE OK.", 00DH
MSGFD2TERR: DB "FLOPPY -> TAPE ERROR.", 00DH
MSGT2FDOK: DB "TAPE -> FLOPPY OK.", 00DH
MSGT2FDERR: DB "TAPE -> FLOPPY ERROR.", 00DH
MSGFCSAVEOK:DB "SAVED TO FLOPPY OK.", 00DH
MSGFCSAVEERR:DB "SAVE TO FLOPPY ERROR.", 00DH
ENDIF
;-------------------------------------------------------------------------------
; END OF TAPE/SD/FD CMDLINE TOOLS FUNCTIONALITY
;-------------------------------------------------------------------------------
@@ -402,7 +481,7 @@ GETCMTPARM: CALL READ4HEX ; Start
CALL NL
LDDE MSGSAVE ; 'FILENAME? '
LD HL,PRINTMSG
CALL BKSW2to6 ; Print out the filename.
CALL BKSW3to6 ; Print out the filename prompt.
LD DE,BUFER
CALL GETL
LD HL,BUFER+10

View File

@@ -586,13 +586,355 @@ CPY2SP_EX8: RET
; END OF METHODS
;-------------------------------------------------------------------------------
;===============================================================================
; MZ-80A FLOPPY DISK CONTROLLER (SFD700) — Bank 8 (8K: E300-FFFF).
;
; Hardware-accelerated read using A10 address toggle:
; F3FE: DD E9 = JP (IX) — spin loop (A10=0, DRQ low).
; F7FE: FD E9 = JP (IY) — data handler (A10=1, DRQ high).
; These are the ORIGINAL MZ-80A FI ROM addresses that BASIC expects.
;
; Bank 8 = ROMBANK8 = 12 (>= ROMBANK6), so E-page + F-page are both mapped.
;===============================================================================
IF BUILD_SFD700 = 1
; MZ-80A specific data.
DSKID80A: DB 002H, "IPLPRO"
PRMBLK80A: DB 000H, 000H, 000H, 000H, 001H, 000H, 0CEH, 000H, 000H, 000H, 000H
ERRTONE8: DB "A0", 0D7H, "ARA", 0D7H, "AR", 00DH
FD80A_TSP EQU 0101EH ; In RAM, not ROM.
ENDIF
; Glass: label outside IF for global visibility.
FD80A_BOOT:
IF BUILD_SFD700 = 1
PUSH DE
LD DE,BPARA
LD HL,PRMBLK80A
LD BC,11
LDIR
POP DE
LD A,(DE)
CP 00DH
JR NZ,FD80A_GDSK
CALL FD80A_INIT
FD80A_PRM: LDDE MSGBOOTDRV
LD HL,PRINTMSG
CALL BKSW8to6
LD DE,011A3H
CALL GETL
LD A,(DE)
CP 01BH
JP Z,FD80A_BRK
LD HL,19
ADD HL,DE
LD A,(HL)
CP 00DH
JR Z,FD80A_RD1
FD80A_GDSK: CALL HEX
JR C,FD80A_PRM
DEC A
CP 004H
JR NC,FD80A_PRM
LD (BPARA),A
FD80A_RD1: LD IX,BPARA
CALL FD80A_READ
LD HL,0CE00H
LD DE,DSKID80A
LD B,007H
FD80A_CHK: LD C,(HL)
LD A,(DE)
CP C
JP NZ,FD80A_NMS
INC HL
INC DE
DJNZ FD80A_CHK
LDDE MSGIPLLOAD
LD HL,PRINTMSG
CALL BKSW8to6
LD DE,0CE07H
LD HL,PRTFN
CALL BKSW8to6
LD IX,BPARA
LD HL,(0CE16H)
LD (IX+5),L
LD (IX+6),H
LD HL,(0CE14H)
LD (IX+3),L
LD (IX+4),H
LD HL,(0CE1EH)
LD (IX+1),L
LD (IX+2),H
CALL FD80A_READ
CALL FD80A_INIT
LD HL,(0CE18H)
JP (HL)
FD80A_LERR: LDDE MSGLOADERR
JR FD80A_ERR
FD80A_NMS: LDDE MSGDSKNOTMST
FD80A_ERR: LD HL,(TMPSTACKP)
LD (FD80A_TSP),HL
LD HL,PRINTMSG
CALL BKSW8to6
LD DE,ERRTONE8
CALL MELDY
LD HL,(FD80A_TSP)
LD SP,HL
RET
FD80A_BRK: LD SP,(TMPSTACKP)
RET
; --- Low-level FDC routines ---
FD80A_RDY: LD A,(MOTON)
RRCA
CALL NC,FD80A_MON
LD A,(IX+0)
OR 084H
OUT (FDC_DRIVE),A
XOR A
LD (FDCCMD),A
LD HL,0
FD80A_RDW: DEC HL
LD A,H
OR L
JP Z,FD80A_DSKE
IN A,(FDC_CMD)
CPL
RLCA
JR C,FD80A_RDW
LD C,(IX+0)
LD HL,TRK0FD1
LD B,0
ADD HL,BC
BIT 0,(HL)
RET NZ
CALL FD80A_SK0
SET 0,(HL)
RET
FD80A_MON: LD A,080H
OUT (FDC_DRIVE),A
LD B,16
FD80A_MNW: CALL FD80A_DL60
DJNZ FD80A_MNW
LD A,1
LD (MOTON),A
RET
FD80A_SEEK: LD A,01BH
CALL FD80A_CMD
AND 099H
RET
FD80A_INIT: XOR A
OUT (FDC_DRIVE),A
LD (TRK0FD1),A
LD (TRK0FD2),A
LD (TRK0FD3),A
LD (TRK0FD4),A
LD (MOTON),A
RET
FD80A_SK0: LD A,00BH
CALL FD80A_CMD
AND 085H
XOR 004H
RET Z
JP FD80A_DSKE
FD80A_CMD: LD (FDCCMD),A
CPL
OUT (FDC_CMD),A
CALL FD80A_WBR
IN A,(FDC_CMD)
CPL
RET
FD80A_WBR: PUSH DE
PUSH HL
CALL FD80A_DL7
LD E,7
FD80A_WB1: LD HL,0
FD80A_WB2: DEC HL
LD A,H
OR L
JR Z,FD80A_WB3
IN A,(FDC_CMD)
CPL
RRCA
JR C,FD80A_WB2
POP HL
POP DE
RET
FD80A_WB3: DEC E
JR NZ,FD80A_WB1
POP HL
POP DE
JP FD80A_DSKE
FD80A_WBA: PUSH DE
PUSH HL
CALL FD80A_DL7
LD E,7
FD80A_WA1: LD HL,0
FD80A_WA2: DEC HL
LD A,H
OR L
JR Z,FD80A_WA3
IN A,(FDC_CMD)
CPL
RRCA
JR NC,FD80A_WA2
POP HL
POP DE
RET
FD80A_WA3: DEC E
JR NZ,FD80A_WA1
POP HL
POP DE
JP FD80A_DSKE
; --- Hardware-accelerated sector read ---
; Uses A10 toggle: F3FE=JP(IX) spin, F7FE=JP(IY) data.
FD80A_READ: CALL FD80A_CNV
FD80A_R1: CALL FD80A_PRS
FD80A_R2: CALL FD80A_SID
CALL FD80A_SEEK
JR NZ,FD80A_RET
CALL FD80A_STR
PUSH IX
LD IX,0F3FEH ; Spin loop at F3FE (original FI ROM addr).
LD IY,FD80A_INI ; Data handler.
LD A,094H
CALL FD80A_CM2
FD80A_R3: LD B,0
JP (IX) ; Enter spin loop.
FD80A_INI: INI
JP NZ,0F3FEH ; Back to spin loop.
POP IX
INC (IX+8)
LD A,(IX+8)
PUSH IX
LD IX,0F3FEH
CP 17
JR Z,FD80A_SEC
DEC D
JR NZ,FD80A_R3
JR FD80A_RDN
FD80A_SEC: DEC D
FD80A_RDN: CALL FD80A_FI
POP IX
IN A,(FDC_CMD)
CPL
AND 0FFH
JR NZ,FD80A_RET
CALL FD80A_ADJ
JP Z,FD80A_END
LD A,(IX+7)
JR FD80A_R2
FD80A_RET: LD A,(RETRIES)
DEC A
LD (RETRIES),A
JP Z,FD80A_DSKE
CALL FD80A_SK0
JR FD80A_R1
FD80A_END: LD A,080H
OUT (FDC_DRIVE),A
RET
; --- Helpers ---
FD80A_PRS: CALL FD80A_RDY
LD D,(IX+4)
LD A,(IX+3)
OR A
JR Z,FD80A_PS1
INC D
FD80A_PS1: LD A,(IX+10)
LD (IX+8),A
LD A,(IX+9)
LD (IX+7),A
LD L,(IX+5)
LD H,(IX+6)
RET
FD80A_SID: SRL A
CPL
OUT (FDC_DATA),A
JR NC,FD80A_S0
LD A,1
JR FD80A_S1
FD80A_S0: XOR A
FD80A_S1: CPL
OUT (FDC_SIDE),A
RET
FD80A_STR: LD C,FDC_DATA
LD A,(IX+7)
SRL A
CPL
OUT (FDC_TRACK),A
LD A,(IX+8)
CPL
OUT (FDC_SECTOR),A
RET
FD80A_ADJ: LD A,(IX+8)
CP 17
JR NZ,FD80A_AJ1
LD A,1
LD (IX+8),A
INC (IX+7)
FD80A_AJ1: LD A,D
OR A
RET
FD80A_CNV: LD B,0
LD DE,16
LD L,(IX+1)
LD H,(IX+2)
XOR A
FD80A_CN1: SBC HL,DE
JR C,FD80A_CN2
INC B
JR FD80A_CN1
FD80A_CN2: ADD HL,DE
LD H,B
INC L
LD (IX+9),H
LD (IX+10),L
LD A,10
LD (RETRIES),A
RET
FD80A_CM2: LD (FDCCMD),A
CPL
OUT (FDC_CMD),A
CALL FD80A_WBA
RET
FD80A_FI: LD A,0D8H
CPL
OUT (FDC_CMD),A
CALL FD80A_WBR
RET
FD80A_DSKE: CALL FD80A_INIT
JP FD80A_LERR
FD80A_DL7: PUSH DE
LD DE,7
JR FD80A_DLP
FD80A_DL60: PUSH DE
LD DE,01013H
FD80A_DLP: DEC DE
LD A,E
OR D
JR NZ,FD80A_DLP
POP DE
RET
; --- Hardware acceleration alignment points (F-page) ---
; JP (IX) at F3FE: A10=0, DRQ low spin loop.
ALIGN_NOPS 0F3FEH
JP (IX) ; DD E9 at F3FE/F3FF.
; JP (IY) at F7FE: A10=1, DRQ high data handler.
ALIGN_NOPS 0F7FEH
JP (IY) ; FD E9 at F7FE/F7FF.
; SFD700 - Pad to end of 8K bank (F-page through FFFFH).
ALIGN 10000H
ENDIF ; BUILD_SFD700
; RomDisk - Pad to EFFF boundary.
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
ALIGN 0EFF8h
ORG 0EFF8h
DB 0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh
ENDIF
; SFD700 - Pad to 10000H
IF BUILD_SFD700 = 1
ALIGN 10000H
DB 0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh
ENDIF

BIN
roms/rfs.rom vendored

Binary file not shown.

BIN
tools/MZFD/MZFDTool vendored Executable file

Binary file not shown.

618
tools/MZFD/MZFDTool.c Executable file
View File

@@ -0,0 +1,618 @@
/* =========================================================================
* MZFDTool — Sharp MZ-700 Floppy Disk image tool
*
* Creates and manages raw floppy disk images for the MZ-700 FDC system.
* Supports format, directory listing, adding/extracting MZF files, and
* setting a boot program.
*
* Original V1.01 (c) 2002 by BKK (DOS BIOS int13h version)
* Updated V2.00 (c) 2026 Philip Smart — rewritten for Linux with raw
* image file I/O, proper directory management, MZF conversion,
* and robust error handling.
*
* MZ-700 Floppy Disk format (327,680 bytes raw image):
*
* Geometry: 40 cylinders, 2 heads, 16 sectors/track, 256 bytes/sector.
* Data on disk is INVERTED (XOR 0xFF) due to MZ-700 hardware.
*
* Logical sector = (cylinder * 2 + head) * 16 + (phys_sector - 1)
* Total sectors: 40 * 2 * 16 = 1280
*
* Boot sector: Logical sector 0 (Cyl 0, Head 0, Sector 1)
* Directory: Logical sectors 16-31 (Cyl 0, Head 1, Sectors 1-16)
* 16 sectors * 8 entries/sector = 128 entries
* Entry 0 of sector 16 = volume/FAT header
* Data area: Logical sector 32+ (Cyl 1+)
*
* Boot sector (32 bytes used):
* +00: Type (03 = MZ-700 bootable)
* +01: Signature "IPLPRO" (6 bytes)
* +07: Program name (11 bytes, CR-terminated)
* +12: Load address (2 bytes LE)
* +14: File size (2 bytes LE)
* +16: Reserved (8 bytes)
* +1E: Start data sector (2 bytes LE)
*
* Directory entry (32 bytes):
* +00: FileType (01=OBJ, 02=BTX, 00=deleted, >=80h=header)
* +01: FileName (17 bytes, CR-terminated, space-padded)
* +12: LockFlag (1 byte)
* +13: DummyFlag (1 byte)
* +14: FileSize (2 bytes LE, in bytes)
* +16: LoadAddress (2 bytes LE)
* +18: ExecAddress (2 bytes LE)
* +1A: Reserved (4 bytes)
* +1E: SectorAddress (2 bytes LE, logical)
*
* Usage:
* MZFDTool format [-o disk.img] Format empty disk image
* MZFDTool dir [-o disk.img] List directory
* MZFDTool add <file.mzf> [-o disk.img] Add MZF file to directory
* MZFDTool extract <name> [-o disk.img] Extract file to MZF
* MZFDTool boot <file.mzf> [-o disk.img] Set boot program
* ========================================================================= */
#define VERSION "2.00"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#define CYLS 40
#define HEADS 2
#define SECTORS 16
#define SECSIZE 256
#define TOTAL_SECTORS (CYLS * HEADS * SECTORS)
#define IMGSIZE (TOTAL_SECTORS * SECSIZE)
#define DIR_FIRST_SEC 16 /* First directory logical sector */
#define DIR_SECTORS 16 /* Number of directory sectors */
#define DIR_ENTRIES 8 /* Entries per sector */
#define DATA_FIRST_SEC 32 /* First data logical sector */
#define MAXFILETYPES 12
#define DEFAULTIMAGE "MZ700.img"
#define CMTHDRSIZE 128 /* MZF/CMT file header size */
#define BOOT_SIG_TYPE 3 /* MZ-700 boot type */
#define BOOT_SIG "IPLPRO"
#define BOOT_SIG_LEN 6
#pragma pack(push, 1)
/* Boot sector structure (first 32 bytes of logical sector 0) */
struct BootSector {
uint8_t type; /* +00: 03 = MZ-700 bootable */
char signature[6]; /* +01: "IPLPRO" */
char name[11]; /* +07: Program name (CR-terminated) */
uint16_t loadAddress; /* +12: Load address */
uint16_t fileSize; /* +14: File size in bytes */
uint8_t reserved[8]; /* +16: Reserved */
uint16_t sectorAddress; /* +1E: Start data sector */
};
/* Directory entry (32 bytes) */
struct DirEntry {
uint8_t fileType; /* +00: 01=OBJ, 02=BTX, 00=deleted */
char fileName[17]; /* +01: CR-terminated, space-padded */
uint8_t lockFlag; /* +12: Lock flag */
uint8_t dummyFlag; /* +13: Reserved */
uint16_t fileSize; /* +14: File size in bytes */
uint16_t loadAddress; /* +16: Load address */
uint16_t execAddress; /* +18: Exec address */
uint8_t dummy[4]; /* +1A: Reserved */
uint16_t sectorAddress; /* +1E: Start sector (logical) */
};
/* MZF/CMT tape file header (128 bytes) */
struct CMTHeader {
uint8_t attribute; /* File type: 01=OBJ, 02=BTX, etc. */
char name[17]; /* Filename (CR-terminated) */
uint16_t size; /* Data size */
uint16_t loadAddress; /* Load address */
uint16_t execAddress; /* Execution address */
char comment[104]; /* Comment area */
};
#pragma pack(pop)
static const char *FileTypes[MAXFILETYPES] = {
"???", "OBJ", "BTX", "BSD", "BRD", "RB ",
"???", "LIB", "???", "???", "SYS", "GR "
};
static uint8_t diskImage[IMGSIZE];
static char imgFileName[256] = DEFAULTIMAGE;
/* ------- Sector I/O helpers ------- */
/* Convert logical sector to byte offset in raw image */
static int sec_offset(int logical_sec)
{
/* Logical sector mapping:
* phys_sector = logical % 16 + 1 (but stored 0-based in image)
* head = (logical / 16) % 2
* cylinder = logical / 16 / 2
* Raw image is sequential: cyl0/head0/sec1..16, cyl0/head1/sec1..16, ... */
return logical_sec * SECSIZE;
}
/* Invert a buffer (XOR 0xFF) — MZ-700 data bus inversion */
static void invert_buf(uint8_t *buf, int len)
{
for (int i = 0; i < len; i++)
buf[i] ^= 0xFF;
}
/* Read a logical sector from image into buffer (un-inverted) */
static void read_sector(int logical_sec, uint8_t *buf)
{
memcpy(buf, &diskImage[sec_offset(logical_sec)], SECSIZE);
invert_buf(buf, SECSIZE);
}
/* Write a buffer to a logical sector in image (inverts before writing) */
static void write_sector(int logical_sec, const uint8_t *buf)
{
memcpy(&diskImage[sec_offset(logical_sec)], buf, SECSIZE);
invert_buf(&diskImage[sec_offset(logical_sec)], SECSIZE);
}
/* Load disk image from file */
static int load_image(void)
{
FILE *f = fopen(imgFileName, "rb");
if (!f) {
fprintf(stderr, "ERROR: Cannot open '%s'\n", imgFileName);
return 0;
}
size_t n = fread(diskImage, 1, IMGSIZE, f);
fclose(f);
if (n != IMGSIZE) {
fprintf(stderr, "ERROR: '%s' is not a valid disk image (%zu bytes, expected %d)\n",
imgFileName, n, IMGSIZE);
return 0;
}
return 1;
}
/* Save disk image to file */
static int save_image(void)
{
FILE *f = fopen(imgFileName, "wb");
if (!f) {
fprintf(stderr, "ERROR: Cannot write '%s'\n", imgFileName);
return 0;
}
fwrite(diskImage, IMGSIZE, 1, f);
fclose(f);
return 1;
}
/* Format a filename for display: stop at CR, null-terminate */
static void format_name(char *dst, const char *src, int maxlen)
{
memcpy(dst, src, maxlen);
dst[maxlen] = '\0';
for (int i = 0; i < maxlen; i++) {
if (dst[i] == '\r' || dst[i] == '\0') { dst[i] = '\0'; break; }
}
}
/* ------- Commands ------- */
/* Format: create an empty formatted disk image */
static void cmd_format(void)
{
uint8_t sec[SECSIZE];
printf("Formatting '%s' (%d bytes, %d sectors)\n", imgFileName, IMGSIZE, TOTAL_SECTORS);
/* Fill entire image with 0xFF (which inverts to 0x00 = empty) */
memset(diskImage, 0xFF, IMGSIZE);
/* Write volume header in directory sector 16, entry 0 */
memset(sec, 0, SECSIZE);
sec[0] = 0x81; /* Volume header marker */
sec[1] = 'O'; /* Volume type */
/* +14: capacity hint = 100 (not used by RFS) */
sec[0x14] = 100;
/* +1E: next free sector (first data sector) */
sec[0x1E] = DATA_FIRST_SEC & 0xFF;
sec[0x1F] = DATA_FIRST_SEC >> 8;
write_sector(DIR_FIRST_SEC, sec);
if (!save_image()) exit(1);
puts("Done.");
}
/* Dir: list directory contents */
static void cmd_dir(void)
{
uint8_t sec[SECSIZE];
char name[18];
int files = 0;
if (!load_image()) exit(1);
/* Check boot sector */
read_sector(0, sec);
struct BootSector *boot = (struct BootSector *)sec;
if (boot->type == BOOT_SIG_TYPE && memcmp(boot->signature, BOOT_SIG, BOOT_SIG_LEN) == 0) {
format_name(name, boot->name, 11);
printf("\nBoot program: %-17s Size=%u Load=0x%04X Sector=%u\n",
name, boot->fileSize, boot->loadAddress, boot->sectorAddress);
} else {
printf("\nDisk is not bootable.\n");
}
printf("\nDirectory of '%s':\n\n", imgFileName);
printf(" %-17s %-3s %5s %s %4s %4s %6s (C H S)\n",
"Name", "Typ", "Size", "L", "Load", "Exec", "Sector");
printf(" %-17s %-3s %5s %s %4s %4s %6s %s\n",
"-----------------", "---", "-----", "-", "----", "----", "------", "-------");
for (int ds = 0; ds < DIR_SECTORS; ds++) {
read_sector(DIR_FIRST_SEC + ds, sec);
for (int e = 0; e < DIR_ENTRIES; e++) {
/* Skip entry 0 of first directory sector (volume header) */
if (ds == 0 && e == 0) continue;
struct DirEntry *ent = (struct DirEntry *)&sec[e * sizeof(struct DirEntry)];
if (ent->fileType == 0x00 || ent->fileType >= 0x80)
continue;
uint8_t ft = ent->fileType;
if (ft >= MAXFILETYPES) ft = 0;
format_name(name, ent->fileName, 17);
int ls = ent->sectorAddress;
int ps = ls % 16 + 1;
int hd = (ls / 16) % 2;
int cy = ls / 16 / 2;
printf(" %-17s %s %5u %c %04X %04X %5u (%2d %d %2d)\n",
name, FileTypes[ft], ent->fileSize,
ent->lockFlag ? 'X' : ' ',
ent->loadAddress, ent->execAddress,
ent->sectorAddress, cy, hd, ps);
files++;
}
}
/* Calculate free space */
int highSec = DATA_FIRST_SEC;
for (int ds = 0; ds < DIR_SECTORS; ds++) {
read_sector(DIR_FIRST_SEC + ds, sec);
for (int e = 0; e < DIR_ENTRIES; e++) {
struct DirEntry *ent = (struct DirEntry *)&sec[e * sizeof(struct DirEntry)];
if (ent->fileType > 0 && ent->fileType < 0x80 && ent->sectorAddress > 0) {
int endSec = ent->sectorAddress + (ent->fileSize + SECSIZE - 1) / SECSIZE;
if (endSec > highSec) highSec = endSec;
}
}
}
printf("\n%d file(s), %d sectors used, %d sectors free\n",
files, highSec, TOTAL_SECTORS - highSec);
}
/* Add: add an MZF file to the disk directory */
static void cmd_add(const char *mzfFileName)
{
FILE *mzfFile;
uint8_t sec[SECSIZE];
struct CMTHeader cmtHdr;
char name[18];
if (!load_image()) exit(1);
/* Open and read MZF file */
mzfFile = fopen(mzfFileName, "rb");
if (!mzfFile) {
fprintf(stderr, "ERROR: Cannot open '%s'\n", mzfFileName);
exit(1);
}
fseek(mzfFile, 0, SEEK_END);
long mzfSize = ftell(mzfFile);
fseek(mzfFile, 0, SEEK_SET);
if (mzfSize < CMTHDRSIZE) {
fprintf(stderr, "ERROR: '%s' too small for MZF format (%ld bytes)\n", mzfFileName, mzfSize);
fclose(mzfFile);
exit(1);
}
fread(&cmtHdr, CMTHDRSIZE, 1, mzfFile);
uint16_t dataSize = cmtHdr.size;
if (dataSize == 0) dataSize = (uint16_t)(mzfSize - CMTHDRSIZE);
/* Find free directory entry and highest used sector */
int freeDirSec = -1, freeDirIdx = -1;
int highSec = DATA_FIRST_SEC;
for (int ds = 0; ds < DIR_SECTORS; ds++) {
read_sector(DIR_FIRST_SEC + ds, sec);
for (int e = 0; e < DIR_ENTRIES; e++) {
if (ds == 0 && e == 0) continue; /* Skip volume header */
struct DirEntry *ent = (struct DirEntry *)&sec[e * sizeof(struct DirEntry)];
if (ent->fileType == 0x00 && freeDirSec < 0) {
freeDirSec = ds;
freeDirIdx = e;
}
if (ent->fileType > 0 && ent->fileType < 0x80 && ent->sectorAddress > 0) {
int endSec = ent->sectorAddress + (ent->fileSize + SECSIZE - 1) / SECSIZE;
if (endSec > highSec) highSec = endSec;
}
}
}
if (freeDirSec < 0) {
fprintf(stderr, "ERROR: Directory full\n");
fclose(mzfFile);
exit(1);
}
int dataSectors = (dataSize + SECSIZE - 1) / SECSIZE;
if (highSec + dataSectors > TOTAL_SECTORS) {
fprintf(stderr, "ERROR: Not enough space (need %d sectors, have %d)\n",
dataSectors, TOTAL_SECTORS - highSec);
fclose(mzfFile);
exit(1);
}
/* Write file data sectors */
for (int s = 0; s < dataSectors; s++) {
memset(sec, 0, SECSIZE);
fread(sec, 1, SECSIZE, mzfFile);
write_sector(highSec + s, sec);
}
fclose(mzfFile);
/* Update directory entry */
read_sector(DIR_FIRST_SEC + freeDirSec, sec);
struct DirEntry *ent = (struct DirEntry *)&sec[freeDirIdx * sizeof(struct DirEntry)];
memset(ent, 0, sizeof(struct DirEntry));
ent->fileType = cmtHdr.attribute;
if (ent->fileType == 0x05) ent->fileType = 0x02; /* CMT type 05 → FD type 02 (BTX) */
memcpy(ent->fileName, cmtHdr.name, 17);
ent->fileSize = dataSize;
ent->loadAddress = cmtHdr.loadAddress;
ent->execAddress = cmtHdr.execAddress;
ent->sectorAddress = highSec;
write_sector(DIR_FIRST_SEC + freeDirSec, sec);
format_name(name, cmtHdr.name, 17);
printf(" Added: \"%s\" type=%s size=%u load=0x%04X exec=0x%04X sector=%d\n",
name, FileTypes[ent->fileType < MAXFILETYPES ? ent->fileType : 0],
dataSize, cmtHdr.loadAddress, cmtHdr.execAddress, highSec);
printf(" %d sectors free\n", TOTAL_SECTORS - highSec - dataSectors);
if (!save_image()) exit(1);
}
/* Extract: extract a file from disk to MZF format */
static void cmd_extract(const char *fileName)
{
uint8_t sec[SECSIZE];
char entName[18], outName[256];
if (!load_image()) exit(1);
/* Search directory for matching filename */
static struct DirEntry foundEntry;
int foundIt = 0;
for (int ds = 0; ds < DIR_SECTORS && !foundIt; ds++) {
read_sector(DIR_FIRST_SEC + ds, sec);
for (int e = 0; e < DIR_ENTRIES && !foundIt; e++) {
if (ds == 0 && e == 0) continue;
struct DirEntry *ent = (struct DirEntry *)&sec[e * sizeof(struct DirEntry)];
if (ent->fileType > 0 && ent->fileType < 0x80) {
format_name(entName, ent->fileName, 17);
if (strcmp(fileName, entName) == 0) {
memcpy(&foundEntry, ent, sizeof(foundEntry));
foundIt = 1;
}
}
}
}
if (!foundIt) {
/* Also check boot sector */
read_sector(0, sec);
struct BootSector *boot = (struct BootSector *)sec;
if (boot->type == BOOT_SIG_TYPE && memcmp(boot->signature, BOOT_SIG, BOOT_SIG_LEN) == 0) {
format_name(entName, boot->name, 11);
if (strcmp(fileName, entName) == 0) {
memset(&foundEntry, 0, sizeof(foundEntry));
foundEntry.fileType = 0x01;
memcpy(foundEntry.fileName, boot->name, 11);
foundEntry.fileSize = boot->fileSize;
foundEntry.loadAddress = boot->loadAddress;
foundEntry.sectorAddress = boot->sectorAddress;
foundIt = 1;
}
}
}
if (!foundIt) {
fprintf(stderr, "ERROR: '%s' not found on disk\n", fileName);
exit(1);
}
struct DirEntry *found = &foundEntry;
/* Build output filename */
snprintf(outName, sizeof(outName), "%s.mzf", fileName);
for (char *p = outName; *p; p++) {
if (*p == ' ') *p = '_';
}
/* Write MZF file */
FILE *out = fopen(outName, "wb");
if (!out) {
fprintf(stderr, "ERROR: Cannot create '%s'\n", outName);
exit(1);
}
struct CMTHeader hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.attribute = (found->fileType == 0x02) ? 0x05 : found->fileType;
memcpy(hdr.name, found->fileName, 17);
hdr.size = found->fileSize;
hdr.loadAddress = found->loadAddress;
hdr.execAddress = found->execAddress;
snprintf(hdr.comment, sizeof(hdr.comment), "Extracted by MZFDTool V%s", VERSION);
fwrite(&hdr, sizeof(hdr), 1, out);
/* Read and write data sectors */
uint16_t remaining = found->fileSize;
int curSec = found->sectorAddress;
while (remaining > 0) {
read_sector(curSec, sec);
uint16_t chunk = (remaining > SECSIZE) ? SECSIZE : remaining;
fwrite(sec, 1, chunk, out);
remaining -= chunk;
curSec++;
}
fclose(out);
format_name(entName, found->fileName, 17);
printf(" Extracted: \"%s\" → %s (%u bytes)\n", entName, outName, found->fileSize);
}
/* Boot: set boot program from MZF file */
static void cmd_boot(const char *mzfFileName)
{
FILE *mzfFile;
uint8_t sec[SECSIZE];
struct CMTHeader cmtHdr;
char name[18];
if (!load_image()) exit(1);
mzfFile = fopen(mzfFileName, "rb");
if (!mzfFile) {
fprintf(stderr, "ERROR: Cannot open '%s'\n", mzfFileName);
exit(1);
}
fseek(mzfFile, 0, SEEK_END);
long mzfSize = ftell(mzfFile);
fseek(mzfFile, 0, SEEK_SET);
if (mzfSize < CMTHDRSIZE) {
fprintf(stderr, "ERROR: '%s' too small for MZF format\n", mzfFileName);
fclose(mzfFile);
exit(1);
}
fread(&cmtHdr, CMTHDRSIZE, 1, mzfFile);
uint16_t dataSize = cmtHdr.size;
if (dataSize == 0) dataSize = (uint16_t)(mzfSize - CMTHDRSIZE);
/* Find space for boot data — use sectors 1-15 (logical), before directory */
int dataSectors = (dataSize + SECSIZE - 1) / SECSIZE;
int bootDataStart = 1; /* Sector 1 (sector 0 is the boot sector itself) */
if (dataSectors > 15) {
fprintf(stderr, "ERROR: Boot program too large (%d sectors, max 15)\n", dataSectors);
fclose(mzfFile);
exit(1);
}
/* Write boot data to sectors 1-15 */
for (int s = 0; s < dataSectors; s++) {
memset(sec, 0, SECSIZE);
fread(sec, 1, SECSIZE, mzfFile);
write_sector(bootDataStart + s, sec);
}
fclose(mzfFile);
/* Write boot sector (sector 0) */
memset(sec, 0, SECSIZE);
struct BootSector *boot = (struct BootSector *)sec;
boot->type = BOOT_SIG_TYPE;
memcpy(boot->signature, BOOT_SIG, BOOT_SIG_LEN);
format_name(name, cmtHdr.name, 11);
memcpy(boot->name, cmtHdr.name, 11);
boot->loadAddress = cmtHdr.loadAddress;
boot->fileSize = dataSize;
boot->sectorAddress = bootDataStart;
write_sector(0, sec);
format_name(name, cmtHdr.name, 11);
printf(" Boot set: \"%s\" size=%u load=0x%04X exec=0x%04X sector=%d\n",
name, dataSize, cmtHdr.loadAddress, cmtHdr.execAddress, bootDataStart);
if (!save_image()) exit(1);
}
/* ------- Usage and main ------- */
static void usage(void)
{
printf("Usage:\n");
printf(" MZFDTool format [-o disk.img] Format empty disk image\n");
printf(" MZFDTool dir [-o disk.img] List directory\n");
printf(" MZFDTool add <file.mzf> [-o disk.img] Add MZF file to disk\n");
printf(" MZFDTool extract <name> [-o disk.img] Extract file to MZF\n");
printf(" MZFDTool boot <file.mzf> [-o disk.img] Set boot program\n");
printf("\n");
printf("Options:\n");
printf(" -o <filename> Disk image file (default: %s)\n", DEFAULTIMAGE);
printf("\n");
printf("Disk geometry: %d cyls, %d heads, %d sectors, %d bytes/sector = %d bytes\n",
CYLS, HEADS, SECTORS, SECSIZE, IMGSIZE);
printf("\n");
}
int main(int argc, char *argv[])
{
printf("\nMZFDTool V%s (c) 2002 BKK, 2026 Philip Smart\n\n", VERSION);
if (argc < 2) {
usage();
return 1;
}
/* Parse -o option from any position */
for (int i = 1; i < argc - 1; i++) {
if (strcmp("-o", argv[i]) == 0) {
strncpy(imgFileName, argv[i + 1], sizeof(imgFileName) - 1);
imgFileName[sizeof(imgFileName) - 1] = '\0';
for (int j = i; j < argc - 2; j++)
argv[j] = argv[j + 2];
argc -= 2;
break;
}
}
if (strcmp("format", argv[1]) == 0) {
cmd_format();
} else if (strcmp("dir", argv[1]) == 0) {
cmd_dir();
} else if (strcmp("add", argv[1]) == 0) {
if (argc < 3) {
fprintf(stderr, "ERROR: No MZF file specified\n");
return 2;
}
cmd_add(argv[2]);
} else if (strcmp("extract", argv[1]) == 0) {
if (argc < 3) {
fprintf(stderr, "ERROR: No filename specified\n");
return 2;
}
cmd_extract(argv[2]);
} else if (strcmp("boot", argv[1]) == 0) {
if (argc < 3) {
fprintf(stderr, "ERROR: No MZF file specified\n");
return 2;
}
cmd_boot(argv[2]);
} else {
fprintf(stderr, "ERROR: Unknown command '%s'\n", argv[1]);
usage();
return 1;
}
return 0;
}

630
tools/MZFD/MZFDTool.c.original vendored Executable file
View File

@@ -0,0 +1,630 @@
#define VERSION "1.01"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <mem.h>
#include <dos.h>
union REGS regs;
struct SREGS sregs;
struct MZ700BootStruct
{
unsigned char Type;
char Signature[6];
char Name[11];
unsigned short StartAddress;
unsigned short FileSize;
unsigned char Dummy[8];
unsigned short SectorAddress;
};
struct MZ700DirectoryStruct
{
unsigned char FileType; // 0x00 -> 1
char FileName[17]; // 0x01 ... 0x11 -> 17
unsigned char LockFlag; // 0x12 -> 1
unsigned char DummyFlag; // 0x13 -> 1
unsigned short FileSize; // 0x14 ... 0x15 -> 2
unsigned short LoadAddress; // 0x16 ... 0x17 -> 2
unsigned short ExecAddress; // 0x18 ... 0x19 -> 2
char Dummy[4]; // 0x1A ... 0x1D -> 4
unsigned short SectorAddress; // 0x1E ... 0x1F -> 2
};
struct CMTHeader
{
unsigned char Attribute;
char Name[17];
unsigned short Size;
unsigned short LoadAddress;
unsigned short ExecAddress;
char Comment[104];
};
char FileTypes[][4] = {
"???",
"OBJ",
"BTX",
"BSD",
"BRD",
"RB ",
"???",
"LIB",
"???",
"???",
"SYS",
"GR "
};
unsigned char DiskParameters[11] =
{0xDF, 0x02, 0x25, 0x01, 16, 0x4E, 0xFF, 0x6C, 0xE5, 100, 8};
// {0xDF, 0x02, 0x25, 0x02, 18, 0x1B, 0xFF, 0x6C, 0xF6, 100, 8}; /* 1.44MB */
char *ErrorMsg[] =
{
"success",
"invalid function",
"address mark not found",
"disk write-protected",
"sector not found / read error",
"reset faild",
"data did not verify correctly",
"disk changed",
"drive parameter activity failed",
"DMA overrun",
"data boundary error",
"bad sector detected",
"bad track detected",
"unsupported track or invalid media",
"invalid number of sectors",
"control data address mark detected",
"DMA arbitration level out of range",
"uncorrectable CRC or ECCerror",
"data ECC corrected"
};
unsigned char SectorBuffer[1024];
unsigned char Drive;
unsigned char Cylinder;
unsigned char Head;
unsigned char Sector;
unsigned int OrgInt1EOffset;
unsigned int OrgInt1ESegment;
void Error(unsigned char Index)
{
printf("Error: %02X - ", Index);
if(Index < 0x12) printf("%s\n", ErrorMsg[Index]);
if(Index == 0x80) puts("No disc in drive!");
exit(1);
}
void ShowSectorBuffer(void)
{
unsigned char ByteCounter;
unsigned char LineCounter;
int Counter;
Counter = 0;
for(LineCounter = 0; LineCounter < 16; LineCounter++)
{
printf("\n%04X : ", Counter);
for(ByteCounter = 0; ByteCounter < 16; ByteCounter++)
{
printf("%02X ", SectorBuffer[Counter + ByteCounter]);
}
printf(" ");
for(ByteCounter = 0; ByteCounter < 16; ByteCounter++)
{
printf("%c", iscntrl(SectorBuffer[Counter]) ? ' ' : SectorBuffer[Counter]);
Counter++;
}
}
puts("");
}
int ResetFloppy(char Drive)
{
regs.h.ah = 0;
regs.h.dl = Drive;
int86(0x13, &regs, &regs);
return(regs.h.ah);
}
int ReadSector(char Drive, char Cylinder, char Head, char Sector)
{
int Counter;
if(Head == 0) Head = 1;
else Head = 0;
regs.h.ah = 0x02;
Counter = 3;
while((regs.h.ah != 0) && (Counter != 0))
{
regs.h.ah = 0x02;
regs.h.al = 1;
regs.h.ch = Cylinder;
regs.h.cl = Sector;
regs.h.dh = Head;
regs.h.dl = Drive;
sregs.es = FP_SEG(SectorBuffer);
regs.x.bx = FP_OFF(SectorBuffer);
int86x(0x13, &regs, &regs, &sregs);
Counter--;
}
for(Counter = 0; Counter < sizeof(SectorBuffer); Counter++)
SectorBuffer[Counter] = ~SectorBuffer[Counter];
return(regs.x.ax);
}
void SaveInt1E(void)
{
OrgInt1EOffset = peek(0x0000, 0x1E * 4);
OrgInt1ESegment = peek(0x0000, 0x1E * 4 + 2);
}
void SetInt1E(unsigned char Type)
{
Type++;
asm cli;
poke(0x0000, 0x1E * 4, FP_OFF(DiskParameters));
poke(0x0000, 0x1E * 4 + 2, FP_SEG(DiskParameters));
asm sti;
}
void ResetInt1E(void)
{
asm cli;
poke(0x0000, 0x1E * 4, OrgInt1EOffset);
poke(0x0000, 0x1E * 4 + 2, OrgInt1ESegment);
asm sti;
}
void ShowDirectory(void)
{
unsigned char SectorCounter;
unsigned char Counter;
unsigned char C, H, S;
struct MZ700BootStruct *Boot;
struct MZ700DirectoryStruct *Entry;
char FileName[18];
unsigned int Result;
// unsigned short LastUsedSector;
puts("");
Result = ReadSector(Drive, 0, 0, 1);
Result = Result >> 8;
if(Result != 0) Error(Result);
Boot = (struct MZ700BootStruct *) SectorBuffer;
memcpy(FileName, Boot->Signature, 6);
FileName[6] = '\0';
if((Boot->Type == 3) && (strcmp(FileName, "IPLPRO") == 0))
{
puts("Floppy is bootable:");
puts("");
memcpy(FileName, Boot->Name, 11);
if(strchr(FileName, 0x0D) != NULL) *strchr(FileName, 0x0D) = '\0';
else FileName[11] = '\0';
printf(" %-17s OBJ %5u ", FileName, Boot->FileSize);
S = Boot->SectorAddress % 16 + 1;
H = (Boot->SectorAddress / 16) % 2;
C = Boot->SectorAddress / 16 / 2;
printf(" %4u (%2u %u %2u)\n", Boot->SectorAddress, C, H, S);
}
else
{
puts("Floppy is not bootable.");
}
puts("");
puts("Directory:");
puts("");
puts(" Name Type Size Lock Load Exec Pos ( C H S)");
puts("");
for(SectorCounter = 1; SectorCounter < 17; SectorCounter++)
{
ReadSector(Drive, 0, 1, SectorCounter);
for(Counter = 0; Counter < 256 / 32; Counter++)
{
Entry = (struct MZ700DirectoryStruct *) &SectorBuffer[Counter * sizeof(struct MZ700DirectoryStruct)];
// if((SectorCounter == 1) && (Counter == 0)) LastUsedSector = Entry->SectorAddress;
if((Entry->FileType > 0) && (Entry->FileType < 0x80))
{
memcpy(FileName, Entry->FileName, 17);
if(strchr(FileName, 0x0D) != NULL) *strchr(FileName, 0x0D) = '\0';
else FileName[17] = '\0';
printf(" %-17s %s %5u %c %04X %04X", FileName, FileTypes[Entry->FileType], Entry->FileSize, Entry->LockFlag ? 'X' : ' ', Entry->LoadAddress, Entry->ExecAddress);
S = Entry->SectorAddress % 16 + 1;
H = (Entry->SectorAddress / 16) % 2;
C = Entry->SectorAddress / 16 / 2;
printf(" %4u (%2u %u %2u)\n", Entry->SectorAddress, C, H, S);
}
}
}
// printf("\nLast used Sector: %u\n\n", LastUsedSector);
}
void CopyFDToMZF(char *FileName)
{
char MZFFileName[13];
char EntryFileName[18];
unsigned char SectorCounter, Counter;
FILE *MZFFile;
struct MZ700DirectoryStruct *Entry;
struct CMTHeader MZFHeader;
unsigned char C, H, S;
unsigned short WriteSize;
unsigned int Result;
S = 0;
Result = ReadSector(Drive, 0, 0, 1); // BootSector
Result = Result >> 8;
if(Result != 0) Error(Result);
SectorBuffer[33] = '\0'; // emergency stop
if(strstr(SectorBuffer, FileName) != NULL)
{
Entry = (struct MZ700DirectoryStruct *) SectorBuffer;
S = Entry->SectorAddress % 16 + 1;
H = (Entry->SectorAddress / 16) % 2;
C = Entry->SectorAddress / 16 / 2;
Entry->FileType = 1;
}
else
{
for(SectorCounter = 1; (SectorCounter < 17) && (S == 0); SectorCounter++)
{
ReadSector(Drive, 0, 1, SectorCounter);
for(Counter = 0; (Counter < 256 / 32) && (S == 0); Counter++)
{
Entry = (struct MZ700DirectoryStruct *) &SectorBuffer[Counter * sizeof(struct MZ700DirectoryStruct)];
if((Entry->FileType > 0) && (Entry->FileType < 0x80))
{
memcpy(EntryFileName, Entry->FileName, 17);
if(strchr(EntryFileName, 0x0D) != NULL) *strchr(EntryFileName, 0x0D) = '\0';
else EntryFileName[17] = '\0';
if(strcmp(FileName, EntryFileName) == 0)
{
S = Entry->SectorAddress % 16 + 1;
H = (Entry->SectorAddress / 16) % 2;
C = Entry->SectorAddress / 16 / 2;
}
}
}
}
}
if(S != 0)
{
memset(MZFFileName, 0, sizeof(MZFFileName));
strncpy(MZFFileName, FileName, 8);
strcat(MZFFileName, ".MZF");
printf("%s found save it as %s\n", FileName, MZFFileName);
MZFFile = fopen(MZFFileName , "wb");
memset(&MZFHeader, 0, sizeof(MZFHeader));
if(Entry->FileType == 0x02) MZFHeader.Attribute = 0x05;
else MZFHeader.Attribute = Entry->FileType;
memcpy(MZFHeader.Name, Entry->FileName, 17);
MZFHeader.Size = Entry->FileSize;
strcat(MZFHeader.Comment, "by MZFDTool");
if((Entry->FileType == 0x01) && (Entry->LoadAddress == 0x0000))
{
MZFHeader.Comment[24] = 0x21;
MZFHeader.Comment[25] = 0x30;
MZFHeader.Comment[26] = 0x11;
MZFHeader.Comment[27] = 0x01;
MZFHeader.Comment[28] = 0x10;
MZFHeader.Comment[29] = 0x00;
MZFHeader.Comment[30] = 0x11;
MZFHeader.Comment[31] = 0xF0;
MZFHeader.Comment[32] = 0xCF;
MZFHeader.Comment[33] = 0xED;
MZFHeader.Comment[34] = 0xB0;
MZFHeader.Comment[35] = 0xC3;
MZFHeader.Comment[36] = 0xF0;
MZFHeader.Comment[37] = 0xCF;
MZFHeader.Comment[40] = 0x21;
MZFHeader.Comment[41] = 0x00;
MZFHeader.Comment[42] = 0x12;
MZFHeader.Comment[43] = 0x01;
MZFHeader.Comment[44] = (unsigned char) Entry->FileSize & 0xFF;
MZFHeader.Comment[45] = Entry->FileSize >> 8;
MZFHeader.Comment[46] = 0x11;
MZFHeader.Comment[47] = 0x00;
MZFHeader.Comment[48] = 0x00;
MZFHeader.Comment[49] = 0xD3;
MZFHeader.Comment[50] = 0xE0;
MZFHeader.Comment[51] = 0xED;
MZFHeader.Comment[52] = 0xB0;
MZFHeader.Comment[53] = 0xC3;
MZFHeader.Comment[54] = 0x00;
MZFHeader.Comment[55] = 0x00;
MZFHeader.LoadAddress = 0x1200;
MZFHeader.ExecAddress = 0x1120;
}
else
{
MZFHeader.LoadAddress = Entry->LoadAddress;
MZFHeader.ExecAddress = Entry->ExecAddress;
}
fwrite(&MZFHeader, sizeof(MZFHeader), 1, MZFFile);
while(MZFHeader.Size != 0)
{
WriteSize = 256;
if(MZFHeader.Size < WriteSize) WriteSize = MZFHeader.Size;
MZFHeader.Size = MZFHeader.Size - WriteSize;
ReadSector(Drive, C, H, S);
fwrite(SectorBuffer, WriteSize, 1, MZFFile);
S++;
if(S == 17)
{
S = 1;
if(H == 0) H = 1;
else
{
C++;
H = 0;
}
}
}
fclose(MZFFile);
}
else
{
puts("");
printf("%s not found!\n", FileName);
puts("");
puts("Remember that capital letters are differenced!");
}
}
void CopyMZFToFD(char *FileName)
{
printf("Not implemented yet (%s)\n", FileName);
}
void Copy(char *FileName)
{
if((strstr(FileName, ".MZF") == NULL) && (strstr(FileName, ".mzf") == NULL)) CopyFDToMZF(FileName);
else CopyMZFToFD(FileName);
}
void ShowMap(void)
{
unsigned short UsedSectors;
unsigned short Counter;
unsigned char BitCounter;
// unsigned char BitMask;
unsigned short BitMask;
unsigned short *SectorsPerTrack;
unsigned char StartTrack;
unsigned int Result;
puts("");
Result = ReadSector(Drive, 0, 0, 16);
Result = Result >> 8;
if(Result != 0) Error(Result);
UsedSectors = SectorBuffer[3] * 256 + SectorBuffer[4];
StartTrack = SectorBuffer[5];
printf("Volume: %c%c%c Used Sectors: %4u\n\n", SectorBuffer[0], SectorBuffer[1], SectorBuffer[2], UsedSectors);
puts("");
puts(" 1111111");
puts("Track ( C H S) 1234567890123456");
puts("");
for(Counter = StartTrack; Counter <= 80; Counter++)
{
printf(" %3d (%2d %s 1) ", Counter, (Counter - 1) / 2, (Counter - 1) % 2 ? "1" : "0");
BitMask = 0x0001;
SectorsPerTrack = (unsigned short *) &SectorBuffer[6 + 2 * (Counter - StartTrack)];
for(BitCounter = 0; BitCounter < 16; BitCounter++)
{
if(*SectorsPerTrack & BitMask) printf("X");
else printf("-");
BitMask = BitMask << 1;
}
puts("");
}
puts("");
}
void Init(void)
{
memset(SectorBuffer, 0, sizeof(SectorBuffer));
Drive = 0;
Cylinder = 0;
Head = 0;
Sector = 1;
}
void Usage(void)
{
puts("usage: mzfdtool dir [DRIVE:]");
puts(" map [DRIVE:]");
puts(" copy [DRIVE:] FILENAME");
puts(" DRIVE: C H S");
puts("");
exit(1);
}
int main(int argc, char *argv[])
{
unsigned int Result;
printf("\nMZFDTool V %s (c) 2002 by BKK\n\n", VERSION);
if(argc == 1) Usage();
Init();
SetInt1E(0);
ResetFloppy(Drive);
if(strcmp(argv[1], "dir") == 0)
{
if(argc == 3)
if(toupper(argv[2][0]) == 'B') Drive = 1;
ShowDirectory();
}
else
{
if(strcmp(argv[1], "copy") == 0)
{
if(argc == 4)
{
if(toupper(argv[2][0]) == 'B') Drive = 1;
Copy(argv[3]);
}
else Copy(argv[2]);
}
else
{
if(strcmp(argv[1], "map") == 0)
{
if(argc == 3)
if(toupper(argv[2][0]) == 'B') Drive = 1;
ShowMap();
}
else
{
if(argc < 3) exit(1);
if(toupper(argv[1][0]) == 'B') Drive = 1;
Cylinder = atoi(argv[2]);
Head = atoi(argv[3]);
Sector = atoi(argv[4]);
printf("Drive: %c CHS: %u %u %u\n", Drive ? 'B' : 'A', Cylinder, Head, Sector);
// Set_Floppy_Medis_Type();
Result = ReadSector(Drive, Cylinder, Head, Sector);
Result = Result >> 8;
if(Result == 0) ShowSectorBuffer();
else Error(Result);
}
}
}
ResetInt1E();
ResetFloppy(Drive);
return(0);
}

19
tools/MZFD/Makefile vendored Normal file
View File

@@ -0,0 +1,19 @@
# MZFDTool - Sharp MZ-700 Floppy Disk image tool
# (c) 2002 BKK, 2026 Philip Smart
#
# Usage:
# make Build MZFDTool
# make clean Remove build artifacts
CC = gcc
CFLAGS = -Wall -Wno-unused-result -O2
TARGET = MZFDTool
SRC = MZFDTool.c
$(TARGET): $(SRC)
$(CC) $(CFLAGS) -o $@ $<
clean:
rm -f $(TARGET)
.PHONY: clean

BIN
tools/MZFDTool vendored Executable file

Binary file not shown.

View File

@@ -147,8 +147,8 @@ cat ${ROM_PATH}/rfs.rom \
# The SFD700 ROM is built as follows:
# 0x00000 : 0x00FFF - MZ80A Floppy ROM MZ80AFI.rom (duplicated ROM at 0x00400)
# 0x01000 : 0x01FFF - MZ-1E05 MZ700 Floppy ROM mz-1e05.rom
# 0x02000 : 0x0BFFF - RFS rfs.rom
# 0x0C000 : 0x7FFFF - USER / ROM File System
# 0x02000 : 0x11FFF - RFS rfs.rom (6x4096 pages, 6x8192 pages).
# 0x12000 : 0x7FFFF - USER / ROM File System
cat ${ROM_PATH}/mz80afi_sfd700.rom ${ROM_PATH}/mz80afi_sfd700.rom ${ROM_PATH}/mz-1e05.rom ${ROM_PATH}/rfs.rom \
>> ${SFD700_ROM}