; DOSGEN  The SYSGEN for 86-DOS  version 1.10
;   by Broken Pipe
; Runs on the Z80 under CP/M

;
; Disk Format Parameters
;
SECSIZ:	EQU	128		;128 bytes per sector
SPT:	EQU	26		;26 sectors per track
SYSTRK:	EQU	2		;2 tracks for just system
NEWTRK:	EQU	4		;4 tracks for new disk
CLSSIZ:	EQU	4		;4 records per cluster

;
; File System Parameters
;
OBOOT:	EQU	0		;Sector 0
OBIOS:	EQU	128		;Sector 1
ODOS:	EQU	1152		;Sector 9
OFAT1:	EQU	6656		;Sector 52
OFAT2:	EQU	7424		;Sector 58
ODIR:	EQU	8192		;Sector 64
OUSER:	EQU	9216		;Sector 72

FATSIZ:	EQU	OFAT2-OFAT1	;FAT size in bytes
DIRSIZ:	EQU	OUSER-ODIR	;Directory size in bytes

;
; BIOS call offsets.
;
SELDSK:	EQU	24
SETTRK:	EQU	27
SETSEC:	EQU	30
SETDMA:	EQU	33
WRITE:	EQU	39

;
; BDOS call numbers.
;
WRITESTR: EQU	9
DRVSET:	EQU	14
OPEN:	EQU	15
READ:	EQU	20
DRVGET:	EQU	25
DMAOFF:	EQU	26

;
; Zero Page and FCB fields.
;
BDOS:	EQU	5
FCB:	EQU	5CH
FCB2:	EQU	6CH
EX:	EQU	12
S1:	EQU	13
S2:	EQU	14
RC:	EQU	15

;
; Program entry point.
;
	ORG	100H

	LD	SP,STACK	;Setup new stack
	LD	C,DRVGET
	CALL	BDOS		;Get current drive
	LD	(CURDRV),A	;Save it for EXIT
	LD	A,(FCB)		;Get dest drive letter
	OR	A		;Default drive?
	JR	NZ,DRVOK	;No, all good
	LD	DE,DRVBAD	;Cannot transfer system to default drive
	JR	EXIT
DRVOK:	DEC	A		;Convert to actual drive number
	LD	(DRIVE),A
	CALL	SETSYS		;Setup system area
	LD	IX,BRCFCB	;Load boot record FCB
	CALL	FOPEN		;Open it
	LD	HL,PBOOT	;Set DMA to boot record in memory
	CALL	FREAD		;Read it
	LD	IX,BIOFCB	;Load BIOS FCB
	CALL	FOPEN		;Open it
	LD	HL,PBIOS	;Set DMA to BIOS in memory
	CALL	FREAD		;Read it
	LD	IX,DOSFCB	;Load DOS FCB
	CALL	FOPEN		;Open it
	LD	HL,PDOS		;Set DMA to DOS in memory
	CALL	FREAD		;Read it
	LD	A,(FCB2+1)	;Get first char of second param
	OR	20H		;Convert to lower case
	CP	'n'		;New disk?
	JR	NZ,SKIP		;No, skip writing file system
	CALL	SETFAT		;Setup FAT image
	LD	IX,CMDFCB	;Load COMMAND FCB
	CALL	ADDFIL		;Add it to FAT and directory
	LD	IX,RDCFCB	;Load RDCPM FCB
	CALL	ADDFIL		;Add it to FAT and directory
SKIP:	CALL	WRTSYS		;Write system in memory to disk
	LD	DE,DONE		;Fall through to EXIT

;
; Prints the string in DE and exits. Does not return.
;
EXIT:
	LD	C,WRITESTR
	CALL	BDOS		;Print message
	LD	A,(CURDRV)
	LD	C,A
	PUSH	BC		;Save drive number
	CALL	SEL		;Restore old drive
	LD	C,DRVSET
	POP	DE		;Restore drive number
	CALL	BDOS
	JP	0

;
; Sets up the system area in memory. AF, BC, DE and HL destroyed.
;
SETSYS:
	LD	HL,PBOOT	;Fill system area with 0
	LD	DE,PBOOT+1
	LD	BC,PFAT1-PBOOT-1
	LD	(HL),0
	LDIR
	LD	A,SYSTRK	;Set track count to SYSTRK
	LD	(NUMTRK),A
	RET

;
; Sets up a FAT image after the system area. AF, BC, DE and HL
; destroyed.
;
SETFAT:
	LD	HL,PFAT1	;Fill FATs with 0
	LD	DE,PFAT1+1
	LD	BC,FATSIZ+FATSIZ-1	
	LD	(HL),0
	LDIR
	LD	HL,PDIR		;Fill directory with 0E5H
	LD	DE,PDIR+1
	LD	BC,DIRSIZ-1	
	LD	(HL),0E5H
	LDIR
	LD	HL,0		;Set current cluster to 0
	LD	(CURCLS),HL
	LD	HL,PDIR		;Set current directory entry
	LD	(CURDIR),HL
	LD	HL,PUSER	;Set current data pointer
	LD	(CURPTR),HL
	LD	A,NEWTRK	;Set track count to NEWTRK
	LD	(NUMTRK),A
	LD	DE,0FFFH	;Write the first 2 FAT entries
	CALL	PUTCLS
	CALL	PUTCLS
	RET

;
; Opens a file with its FCB in IX. IX preserved, all other
; registers destroyed. Does not return if the requested file
; cannot be opened.
;
FOPEN:
	LD	(IX+EX),0	;Set EX to 0
	LD	(IX+S1),0	;Set S1 to 0
	LD	(IX+S2),0	;Set S2 to 0
	LD	(IX+RC),0	;Set RC to 0
	LD	C,OPEN
	PUSH	IX
	POP	DE		;DE points to FCB
	PUSH	IX		;Save registers
	CALL	BDOS		;Open file
	POP	IX		;Restore registers
	CP	-1		;Error?
	RET	NZ		;No, return
	LD	DE,OFAIL
	LD	C,WRITESTR
	PUSH	IX		;Save registers
	CALL	BDOS		;Print first part of open fail message
	POP	IX		;Restore registers
	LD	(IX+12),'$'	;Turn FCB file name into a string
	INC	IX		;Point to the file name
	PUSH	IX
	POP	DE		;DE points to file name
	LD	C,WRITESTR
	CALL	BDOS		;Print it
	LD	DE,OPENER
	JP	EXIT		;Exit with last part of the message.

;
; Reads all records of a file pointed to by its FCB in IX to the
; DMA address in HL. IX preserved, all other registers destroyed.
; Does not return if reading fails. Returns number of records
; read in BC.
;
FREAD:
	LD	IY,0		;Set record count to 0
RDLOOP:	PUSH	HL
	POP	DE		;DE points to DMA
	LD	C,DMAOFF
	PUSH	IX		;Save registers
	PUSH	HL
	PUSH	IY
	CALL	BDOS		;Set DMA before reading
	POP	IY
	POP	HL		;Restore registers
	POP	IX
LDREC:	LD	C,READ
	PUSH	IX
	POP	DE
	PUSH	IX		;Save registers
	PUSH	HL
	PUSH	IY
	CALL	BDOS		;Read a record
	POP	IY
	POP	HL		;Restore registers
	POP	IX
	CP	0		;Success?
	JR	NZ,CHKEND	;No, check if EOF
	INC	IY		;Increment record count
	LD	DE,128
	ADD	HL,DE		;Increment DMA by 128 bytes
	JR	RDLOOP		;Read next record
CHKEND:	PUSH	AF		;Save AF
	PUSH	IX		;Save IX
	PUSH	IY		;Save record count
	LD	C,DMAOFF
	LD	DE,80H
	CALL	BDOS		;Reset DMA to default
	POP	BC		;Restore record count to BC
	POP	IX		;Restore IX
	POP	AF		;Restore AF
	CP	1		;EOF?
	RET	Z		;Yes, done
	LD	DE,RFAIL
	LD	C,WRITESTR
	PUSH	IX		;Save registers
	CALL	BDOS		;Print first part of read fail message
	POP	IX		;Restore registers
	LD	(IX+12),'$'	;Turn FCB file name into a string
	INC	IX		;Point to the file name
	PUSH	IX
	POP	DE		;DE points to file name
	LD	C,WRITESTR
	CALL	BDOS		;Print it
	LD	DE,RDERR
	JP	EXIT		;Exit with last part of the message.

;
; Multiplies BC by DE and returns the result in HL. AF and BC destroyed.
;
MLTPLY:
	LD	A,B
	LD	B,16
MLOOP:	ADD	HL,HL
	SLA	C
	RLA
	JR	NC,MNOADD
	ADD	HL,DE
MNOADD:	DJNZ	MLOOP
	RET

;
; Divides BC by DE and returns the result in BC, with remainder in HL.
; AF and BC destroyed. 
;
DIVIDE:
	LD	HL,0
	LD	A,B
	LD	B,8
DIVLP1:	RLA
	ADC	HL,HL
	SBC	HL,DE
	JR	NC,DNADD1
	ADD	HL,DE
DNADD1:	DJNZ	DIVLP1
	RLA
	CPL
	LD	B,A
	LD	A,C
	LD	C,B
	LD	B,8
DIVLP2:	RLA
	ADC	HL,HL
	SBC	HL,DE
	JR	NC,DNADD2
	ADD	HL,DE
DNADD2:	DJNZ	DIVLP2
	RLA
	CPL
	LD	B,C
	LD	C,A
	RET

;
; Writes the lowest 12 bits of DE to the BCth entry of the FAT pointed
; to by HL. AF and HL destroyed.
;
FATPUT:
	PUSH	BC		;Save registers
	PUSH	DE
	LD	A,D		;Make DE a 12-bit number
	AND	0FH
	LD	D,A
	BIT	0,C		;Is cluster even?
	PUSH	AF		;Save flags
	ADD	HL,BC		;HL = HL + BC * 1.5
	SRL	B
	RR	C
	ADD	HL,BC
	POP	AF		;Restore flags
	JR	Z,NOTODD	;Jump if even
	LD	A,(HL)		;Odd, get byte at HL
	AND	0FH		;Clear high bits
	PUSH	HL		;Save HL
	LD	H,D		;Shift DE by 4 bits
	LD	L,E
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	ADD	HL,HL
	LD	D,H
	LD	E,L
	POP	HL		;Restore HL
	OR	E		;Combine with high bits of E
	LD	(HL),A		;Save it back
	INC	HL		;Point to next byte
	LD	(HL),D		;Save high byte of DE
	JR	PUTDON		;Done, pop registers and return
NOTODD:	LD	(HL),E		;Even, save low byte of DE
	INC	HL		;Point to next byte
	LD	A,(HL)		;Get byte at HL
	AND	0F0H		;Clear low 4 bits
	OR	D		;Combine with high byte of DE
	LD	(HL),A		;Save it back
PUTDON:	POP	DE		;Restore registers
	POP	BC
	RET

;
; Writes the lowest 12 bits of DE to next entry of the FATs at
; PFAT1 and PFAT2. AF, BC and HL destroyed. (CURCLS) automatically
; incremented.
;
PUTCLS:
	LD	HL,PFAT1
	LD	BC,(CURCLS)
	CALL	FATPUT
	LD	HL,PFAT2
	CALL	FATPUT
	INC	BC
	LD	(CURCLS),BC
	RET

;
; Converts the record count in BC to cluster count. AF and HL
; destroyed.
;
RECTOCLS:
	LD	HL,CLSSIZ-1	;Add CLSSIZ-1
	ADD	HL,BC
	LD	B,H
	LD	C,L
	LD	DE,CLSSIZ
	CALL	DIVIDE		;Divide by CLSSIZ
	RET

;
; Adds a file pointed to by its FCB in IX to the system FAT image.
; All registers destroyed. Does not return if the file cannopt be
; opened and read. (CURCLS), (CURDIR) and (CURPTR) automatically
; incremented.
;
ADDFIL:
	CALL	FOPEN		;Open the file
	LD	HL,(CURPTR)	;Set DMA to current file location
	CALL	FREAD		;Read it
	PUSH	BC		;Save record count
	CALL	RECTOCLS	;Convert to number of clusters
	PUSH	BC		;Save cluster count
	LD	H,B		;Multiply by 128
	LD	L,C
	XOR	A
	SRL	H
	RR	L
	RRA
	LD	H,L
	LD	L,A
	LD	B,H		;Multiply by CLSSIZ for bytes
	LD	C,L
	LD	DE,CLSSIZ
	CALL	MLTPLY
	LD	BC,(CURPTR)	;Load current data pointer
	ADD	HL,BC		;Add size and pointer
	LD	(CURPTR),HL	;Save new data pointer
	POP	BC		;Restore cluster count
	LD	H,B		;Save cluster count to HL
	LD	L,C
	LD	DE,(CURCLS)	;Load current cluster
	PUSH	DE		;Save as starting cluster
FATLP:	OR	A		;Decrement cluster count
	LD	DE,1
	SBC	HL,DE
	JR	Z,LSTCLS	;Handle last cluster if 0
	JP	M,EMPTY		;Handle empty file if negative
	LD	DE,(CURCLS)	;Get current cluster
	INC	DE		;Next cluster = current + 1
	PUSH	HL
	CALL	PUTCLS		;Write FAT entry
	POP	HL
	JR	FATLP		;Go to next cluster
LSTCLS:	LD	DE,0FFFH	;Load end-of-chain value
	CALL	PUTCLS		;Write FAT entry
	JR	WRTDIR		;Go write directory entry
EMPTY:	POP	DE		;Discard saved starting cluster
	LD	DE,0		;Save 0 as new starting cluster
	PUSH	DE
WRTDIR:	LD	DE,(CURDIR)	;Load pointer to current dir entry
	PUSH	IX
	POP	HL		;HL points to FCB
	INC	HL		;Increment to point to file name
	LD	BC,11		;11 characters to copy
	LDIR			;Copy file name over
	POP	HL		;Restore starting cluster number
	LD	A,L		;Write it to directory entry
	LD	(DE),A
	INC	DE
	LD	A,H
	LD	(DE),A
	INC	DE
	POP	HL		;Restore record count
	XOR	A		;Multiply by 128 to get file size
	SRL	H
	RR	L
	RRA
	LD	H,L
	LD	L,A
	LD	A,L		;Write file size
	LD	(DE),A
	INC	DE
	LD	A,H
	LD	(DE),A
	INC	DE
	XOR	A		;Write 0 for high byte of file size
	LD	(DE),A
	INC	DE
	LD	(CURDIR),DE	;Update current directory pointer
	RET

;
; BIOS drive select routine, drive in C. All registers destroyed.
;
SEL:
	LD	HL,(1)
	LD	DE,SELDSK
	ADD	HL,DE
	JP	(HL)

;
; BIOS set track routine, track in C. All registers destroyed.
;
TRK:
	LD	HL,(1)
	LD	DE,SETTRK
	ADD	HL,DE
	JP	(HL)

;
; BIOS set sector routine, sector in C. All registers destroyed.
;
SEC:
	LD	HL,(1)
	LD	DE,SETSEC
	ADD	HL,DE
	JP	(HL)

;
; BIOS set DMA routine, DMA in BC. All registers destroyed.
;
DMA:
	LD	HL,(1)
	LD	DE,SETDMA
	ADD	HL,DE
	JP	(HL)

;
; BIOS disk write routine. All registers destroyed.
;
DWRITE:
	LD	HL,(1)
	LD	DE,WRITE
	ADD	HL,DE
	JP	(HL)

;
; Writes the system in memory at LOADP to the first (NUMTRK) tracks of
; the disk in (DRIVE). All registers destroyed.
;
WRTSYS:
	LD	HL,DRIVE
	LD	C,(HL)
	CALL	SEL		;Select dest drive
	LD	HL,LOADP-SECSIZ	;Load point in RAM for system - SECSIZ
	LD	(DMADDR),HL	;Save it
	LD	A,-1		;Start with track = -1
	LD	(TRACK),A
WRTTRK:	LD	HL,TRACK
	INC	(HL)		;Next track
	LD	A,(NUMTRK)	;Number of tracks to write
	CP	(HL)		;All tracks done?
	RET	Z		;Yes, system transfer complete
	LD	C,(HL)
	CALL	TRK		;Set track number
	LD	A,-1		;Start with sector = -1
	LD	(SECTOR),A
WRTSEC:	LD	A,SPT		;Sectors per track
	LD	HL,SECTOR
	INC	(HL)		;Next sector
	CP	(HL)		;All sectors done?
	JR	Z,WRTTRK	;Yes, track write complete
	LD	C,(HL)		;Get sector number
	INC	C		;One-based indexing
	CALL	SEC		;Set sector number
	LD	HL,(DMADDR)
	LD	BC,SECSIZ
	ADD	HL,BC		;Increment DMA
	LD	(DMADDR),HL
	LD	B,H
	LD	C,L		;BC = HL for DMA call
	CALL	DMA		;Set DMA
	XOR	A
	LD	(RETRY),A	;Clear retry count
TRYSEC:	LD	A,(RETRY)	;Get retry count
	CP	10		;Too many (more than 10) retries?
	JR	C,TRYOK		;No, can perform write
	LD	DE,WRTER	;Load write error message
	JP	EXIT		;Print and quit
TRYOK:	INC	A
	LD	(RETRY),A	;Increment retry count
	CALL	DWRITE		;Raw disk write
	OR	A		;Success?
	JR	Z,WRTSEC	;Yes, next sector
	JR	TRYSEC		;Retry the write

;
; Messages.
;
DRVBAD:	DB	'Bad drive specification',13,10,'$'
OFAIL:	DB	'Failed to open "$'
OPENER:	DB	'"',13,10,'$'
RFAIL:	DB	'Failed to read "$'
RDERR:	DB	'"',13,10,'$'
WRTER:	DB	'Disk write error',13,10,'$'
DONE:	DB	'System transfered',13,10,'$'

;
; FCBs. Open these from bottom to top, one at a time.
;
RDCFCB:	DB	0,'RDCPM   COM'
CMDFCB:	DB	0,'COMMAND COM'
DOSFCB:	DB	0,'86DOS   COM'
BIOFCB:	DB	0,'DOSIO   COM'
BRCFCB:	DB	0,'BOOT    COM'
	DS	24

;
; Global variables.
;
CURDRV:	DS	1
DRIVE:	DS	1
TRACK:	DS	1
SECTOR:	DS	1
DMADDR:	DS	2
RETRY:	DS	1
CURCLS:	DS	2
CURDIR:	DS	2
CURPTR:	DS	2
NUMTRK:	DS	1

;
; Stack of 128 bytes.
;
	DS	128
STACK:

;
; Load point of the 86-DOS system and pointers to different system
; components.
;
LOADP:
PBOOT:	EQU	$+OBOOT		;Relative offset of BOOT 
PBIOS:	EQU	$+OBIOS		;Relative offset of BIOS
PDOS:	EQU	$+ODOS		;Relative offset of 86-DOS
PFAT1:	EQU	$+OFAT1		;Relative offset of FAT 1
PFAT2:	EQU	$+OFAT2		;Relative offset of FAT 2
PDIR:	EQU	$+ODIR		;Relative offset of directory
PUSER:	EQU	$+OUSER		;Relative offset of user area
