;-------------------------------------------------------------------------------
; FATware - firmware and file system driver for ZX Spectrum divIDE interface
; Copyright (c) 2005-2009 Milos Bazelides (baze)
;-------------------------------------------------------------------------------
; This software is provided "as is" without warranties or conditions of any
; kind, either express or implied. In no event shall the author(s) be held
; liable for any damages arising from the use of this software.
; See accompanying "licence.txt" file for copyright and terms of use.
;-------------------------------------------------------------------------------

	global	SysTemp1
	global	SysBootOK
	global	SysNumVolumes
	global	SysCurrVolume
	global	SysAY
	global	SysNumber
	global	SysCursor
	global	SysColour

	global	SaveAY
	global	LoadAY
	global	Num32ToASCII
	global	StrCmpNoCase
	global	PrintString
	global	PrintChar

	; filesystem driver calls

	global	FileOpen
	global	FileClose
	global	FileRead
	global	FileWrite
	global	FileSeek
	global	IsEOF

	global	FindVolume		; TODO: also GetCurrentVolume?

	global	OpenDir
	global	OpenCurrentDir

	global	FindDirEntry		; TODO: GetNext?
	global	ReadDirEntry		; TODO: GetVolumeLabel
	global	FileOpenRaw

	; driver system variables

	global	VolDesc
	global	FileName
	global	DirEntry


	include "z80_undoc.inc"
	include "zx_spectrum.inc"
	include "ata.inc"
	include "fat.inc"
	include "fat_driver.inc"

	define	EEPROM		; EEPROM image
	segment	EEPROM

;-------------------------------------------------------------------------------
; ROM ENTRY POINT: RST 0 (RESET)
;-------------------------------------------------------------------------------

	org	0

	di
	ld	a,80h
	out	(DIV_CTRL),a
	jp	Startup

;-------------------------------------------------------------------------------
; ROM ENTRY POINT: RST 8
;-------------------------------------------------------------------------------

	org	8

	; jump to Rst8 when paged in, otherwise perform dummy LD HL,(...)

	ld	hl,(5C5Dh)

	; this code gets executed if we get here from ZX ROM

In8h	push	hl
	ld	hl,In8h
	ex	(sp),hl
	jp	OutRET

;-------------------------------------------------------------------------------
; IM 1 HANDLER
;-------------------------------------------------------------------------------

	org	1Fh

	; refresh error timer

	push	af
	ld	a,(SysTimeOut)
	or	a
	jr	nz,Im1Cont	
Im1Ret	pop	af
	ei
	ret

;-------------------------------------------------------------------------------
; RST 30 HANDLER: Call MAPRAM/EEPROM routine
;-------------------------------------------------------------------------------

	org	30h

	ex	(sp),hl
	ld	(SysTemp1),de
	jp	CallBank

;-------------------------------------------------------------------------------
; ROM ENTRY POINT: IM 1
;-------------------------------------------------------------------------------

	org	38h

	; jump to 1Fh if we get here while divIDE ROM is mapped

	db	24
In38h	push	hl		; JR to 1Fh (38h - 19h)

	; if we get here from ZX ROM just return back

	ld	hl,In38h
	ex	(sp),hl
	jp	OutRET

Im1Cont	dec	a
	ld	(SysTimeOut),a
	jr	nz,Im1Ret

	; timeout

	pop	af
	ld	sp,(SysErrorSP)
	ld	a,ERR_DEV_NOT_READY
	ld	(SysError),a
	scf
	ei
	ret

;-------------------------------------------------------------------------------
; ROM ENTRY POINT: NMI
;-------------------------------------------------------------------------------

	org	66h

	ret				; prevent nested NMI calls
In66h	push	hl
	call	Beep
	ld	hl,In66h
	ex	(sp),hl
	jp	OutRET

;-------------------------------------------------------------------------------
; LOW-LEVEL CORE ROUTINES
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
SelectDevice:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
;-------------------------------------------------------------------------------

$$	in	a,(ATA_STAT)
	rla
	jr	c,$b			; 400 ns delay

	ld	a,(ix + vdDevice)
	out	(ATA_DH),a
	nop				; 400 ns delay

$$	in	a,(ATA_STAT)
	and	STA_BSY | STA_DRDY	; | STA_DSC?
	cp	STA_DRDY
	jr	nz,$b
	ret

;-------------------------------------------------------------------------------
IdentifyDevice:
;-------------------------------------------------------------------------------
; Input:
; C  - command code (Identify Device / Identify Packet Device)
; IX - address of volume descriptor
; HL - location of read data
;-------------------------------------------------------------------------------
; Output:
; c = 0 - OK
; c = 1 - read or device error
;-------------------------------------------------------------------------------

	ld	(SysErrorSP),sp
	ld	a,ERROR_TIMEOUT
	ld	(SysTimeOut),a

	call	SelectDevice

	ld	a,c
	out	(ATA_CMD),a
	nop	; 400 ns delay

IdBusy	in	a,(ATA_STAT)
	rlca
	jr	c,IdBusy

	; some drives might assert DRQ even on error

	bit	4,a
	jr	z,IdError

	; we don't care about speed too much here

	ld	bc,ATA_DATA
	inir
	inir

	and	STL_DF | STL_ERR
	jr	nz,IdError

	ld	(SysTimeOut),a

	; OK, swap bytes to get ASCII strings in usual order

	dec	h
	dec	h
	ld	l,idSerial
	ld	b,5
	call	IdSwap

	ld	l,idFirmware
	ld	b,24
IdSwap	ld	e,(hl)
	inc	l
	ld	d,(hl)
	ld	(hl),e
	dec	l
	ld	(hl),d
	inc	l
	inc	l
	djnz	IdSwap
	ret

IdError	scf
	ret

;-------------------------------------------------------------------------------
InitDeviceParams:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; B  - number of heads minus 1
; C  - number of sectors per track
;-------------------------------------------------------------------------------
; Output:
; c = 0 - OK
; c = 1 - read or device error
;-------------------------------------------------------------------------------

	ld	(SysErrorSP),sp
	ld	a,ERROR_TIMEOUT
	ld	(SysTimeOut),a

	call	SelectDevice

	ld	a,(ix + vdDevice)
	or	b
	out	(ATA_DH),a
	ld	a,c
	out	(ATA_SC),a
	ld	a,91h
	out	(ATA_CMD),a

$$	in	a,(ATA_STAT)
	rlca
	jr	c,$b

	and	STL_DF | STL_DRQ | STL_ERR
	jr	nz,$f
	ld	(SysTimeOut),a
	ret

$$	ld	a,ERR_DEV_NOT_READY
	ld	(SysError),a
	scf
	ret

;-------------------------------------------------------------------------------
SetLBA:
;-------------------------------------------------------------------------------
; Common ReadSectors/WriteSectors procedure taking care of LBA -> CHS.
;-------------------------------------------------------------------------------
; Input:
; BC:DE - LBA address of the first sector
; IX    - address of volume descriptor
;-------------------------------------------------------------------------------

	ld	a,(ix + vdDevice)	; is the device LBA capable?
	bit	6,a
	jr	z,$f

	or	b
	out	(ATA_LBA3),a
	ld	a,c
	out	(ATA_LBA2),a
	ld	a,d
	out	(ATA_LBA1),a
	ld	a,e
	out	(ATA_LBA0),a
	ret

	; S = (LBA mod NumSects) + 1; Temp = LBA div NumSects

$$	push	hl
	ex	de,hl
	ld	d,b
	ld	e,c
	ld	c,(ix + vdSecs)
	call	Divide
	inc	a
	out	(ATA_SN),a

	; H = Temp mod NumHeads; C = Temp div NumHeads

	ld	c,(ix + vdHeads)
	call	Divide
	or	(ix + vdDevice)
	out	(ATA_DH),a
	ld	a,h
	out	(ATA_CH),a
	ld	a,l
	out	(ATA_CL),a
	pop	hl
	ret

;-------------------------------------------------------------------------------
ReadSectors:
;-------------------------------------------------------------------------------
; Input:
; BC:DE - LBA address of the first sector
; IX    - address of volume descriptor
; HL    - destination address
; A     - number of sectors to read
;-------------------------------------------------------------------------------
; Output:
; c = 0 - OK
; c = 1 - read or device error
;-------------------------------------------------------------------------------

	ex	af,af'

	ld	(SysErrorSP),sp
	ld	a,ERROR_TIMEOUT
	ld	(SysTimeOut),a

	call	SelectDevice
	call	SetLBA

RdCmd	ex	af,af'
	out	(ATA_SC),a
	ld	d,a

	ld	a,20h
	out	(ATA_CMD),a
	nop				; 400 ns delay

RdLoop	in	a,(ATA_STAT)		; wait until BSY = 0
	rlca
	jr	c,RdLoop
	bit	4,a			; DRQ = 1? (might be set even on error)
	jr	z,RdError

	ld	bc,32 * 256 + ATA_DATA
RdSect	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	ini
	djnz	RdSect			; some routines depend on B = 0 !!!

	and	STL_DF | STL_ERR
	jr	nz,RdError
	dec	d
	jr	nz,RdLoop		; read status yet once?

	ld	(SysTimeOut),a
	ret				; Carry = 0

RdError	ld	a,ERR_READ_ERROR
	ld	(SysError),a
	scf
	ret

;-------------------------------------------------------------------------------
WriteSectors:
;-------------------------------------------------------------------------------
; Input:
; BC:DE - LBA address of the first sector
; IX    - address of volume descriptor
; HL    - source address
; A     - number of sectors to write
;-------------------------------------------------------------------------------
; Output:
; c = 0 - OK
; c = 1 - write or device error
;-------------------------------------------------------------------------------

	ex	af,af'

	ld	(SysErrorSP),sp
	ld	a,ERROR_TIMEOUT
	ld	(SysTimeOut),a

	call	SelectDevice
	call	SetLBA

	ex	af,af'
	out	(ATA_SC),a
	ld	d,a

	ld	a,30h
	out	(ATA_CMD),a
	nop				; 400 ns delay

WrLoop	in	a,(ATA_STAT)		; wait until BSY = 0
	rlca
	jr	c,WrLoop
	bit	4,a			; DRQ = 1? (might be set even on error)
	jr	z,WrError

	ld	bc,ATA_DATA
	otir
	otir

	and	STL_DF | STL_ERR
	jr	nz,WrError
	dec	d
	jr	nz,WrLoop		; read status yet once?

	ld	(SysTimeOut),a		; A = 0, Carry = 0
	ret

WrError	ld	a,ERR_WRITE_ERROR
	ld	(SysError),a
	scf
	ret

;-------------------------------------------------------------------------------
ScanDevice:
;-------------------------------------------------------------------------------

	; force Identify Device

	ld	c,0ECh
	ld	hl,IdSector
	call	IdentifyDevice
	jr	nc,DevHere

	; failed, force Identify Packet Device

	ld	c,0A1h
	ld	hl,IdSector
	call	IdentifyDevice
	jr	nc,DevHere

	; failed, no device found

DevNone	ld	hl,TxtNone
	ld	b,4
	call	PrintString
	ret

DevHere	ld	hl,IdSector + idModel
	ld	b,21
	call	PrintString

	; check LBA capability (only valid for ATA-1 and ATA-2)

DevAta	ld	hl,TxtLBA
	ld	a,(IdSector + idCaps + 1)
	and	2
	jr	nz,DevMode

	ld	hl,TxtCHS
	res	6,(ix + vdDevice)

DevMode	ld	b,5
	call	PrintString

	; explicitly initialise default geometry (WD drives need it)

	ld	a,(IdSector + idNumHeads)
	dec	a
	ld	b,a
	ld	a,(IdSector + idNumSecs)
	ld	c,a
	call	InitDeviceParams
	ret	c

	; HACK: fake volume descriptor to enable MBR read for CHS devices

	ld	a,(IdSector + idNumHeads)
	ld	(ix + vdHeads),a
	ld	a,(IdSector + idNumSecs)
	ld	(ix + vdSecs),a

	; SysTemp1 - LBA of current extended partition
	; SysTemp2 - LBA of first extended partition (stored in MBR)

	; read the Master Boot Record

	xor	a
	ld	b,a
	ld	c,a
	ld	d,a
	ld	e,a

;-------------------------------------------------------------------------------
MountVolumes:
;-------------------------------------------------------------------------------
; Input:
; BC:DE - LBA address of boot sector
; HL    - address of sector buffer
; (SYS_TEMP) - LBA offset of current extended partition
;-------------------------------------------------------------------------------

	ld	a,1
	ld	hl,MbrSector
	call	ReadSectors
	ret	c

	call	CheckBootSign
	ret	nz

	ld	l,-62
ScanPri	push	hl

	ld	a,(hl)
	cp	04h		; DOS FAT16 < 32 MB
	jr	z,PriHere
	cp	14h		; hidden DOS FAT16 < 32 MB
	jr	z,PriHere
	cp	06h		; DOS FAT16 > 32 MB
	jr	z,PriHere
	cp	16h		; hidden DOS FAT16 > 32 MB
	jr	z,PriHere
	cp	0Eh		; Win95 FAT16
	jr	z,PriHere
	cp	1Eh		; hidden Win95 FAT16
	jp	nz,NextPri

PriHere	ld	a,l
	add	a,4
	ld	l,a
	ld	e,(hl)
	inc	l
	ld	d,(hl)
	inc	l
	ld	c,(hl)
	inc	l
	ld	b,(hl)

	; store LBA address of volume start

	call	BCDEplusTemp1
	ld	(ix + vdStart + 0),e
	ld	(ix + vdStart + 1),d
	ld	(ix + vdStart + 2),c
	ld	(ix + vdStart + 3),b

	ld	a,1
	ld	hl,BootSector
	call	ReadSectors
	ret	c

	call	CheckBootSign
	ret	nz

	; HERE COMES THE FAT-RELATED CODE

	; check the number of bytes per sector (we only support usual 512)

	ld	hl,(BootSector + bpbBytsPerSec)
	ld	a,h
	sub	2
	or	l
	jp	nz,NextPri

	; check the number of FATs (we only support usual 2)

	ld	a,(BootSector + bpbNumFATs)
	cp	2
	jp	nz,NextPri

	; store the number of sectors per cluster

	ld	a,(BootSector + bpbSecPerClus)
	ld	(ix + vdSecsPerClus),a

	; calculate LBA address of FAT Region

	ld	e,(ix + vdStart + 0)
	ld	d,(ix + vdStart + 1)
	ld	c,(ix + vdStart + 2)
	ld	b,(ix + vdStart + 3)

	ld	hl,(BootSector + bpbRsvdSecCnt)
	call	BCDEplusHL

	ld	(ix + vdFatLBA + 0),e
	ld	(ix + vdFatLBA + 1),d
	ld	(ix + vdFatLBA + 2),c
	ld	(ix + vdFatLBA + 3),b

	; calculate LBA address of Root Directory Region

	ld	hl,(BootSector + bpbFATSz16)
	ld	(ix + vdFatSize + 0),l
	ld	(ix + vdFatSize + 1),h
	xor	a
	ld	(ix + vdFatSize + 2),a
	ld	(ix + vdFatSize + 3),a
	add	hl,hl
	call	BCDEplusHL

	ld	(ix + vdRootLBA + 0),e
	ld	(ix + vdRootLBA + 1),d
	ld	(ix + vdRootLBA + 2),c
	ld	(ix + vdRootLBA + 3),b

	; we support maximum 7FFh root entries (less than 65536 bytes)

	ld	hl,(BootSector + bpbRootEntCnt)
	ld	a,7
	cp	h
	ret	c

	; calculate LBA address of Data Region
	; RootDirSectors = (BPB_RootEntCnt * 32 + 511) / 512

	add	hl,hl
	add	hl,hl
	add	hl,hl
	add	hl,hl
	add	hl,hl
	ld	(ix + vdRootSize + 0),l
	ld	(ix + vdRootSize + 1),h

	inc	h
	inc	h
	dec	hl
	ld	l,h
	ld	h,0
	srl	l
	call	BCDEplusHL

	ld	(ix + vdDataLBA + 0),e
	ld	(ix + vdDataLBA + 1),d
	ld	(ix + vdDataLBA + 2),c
	ld	(ix + vdDataLBA + 3),b

	; subtract start LBA so that we get the number of used sectors

	or	a
	ex	de,hl
	ld	e,(ix + vdStart + 0)
	ld	d,(ix + vdStart + 1)
	sbc	hl,de
	ex	de,hl

	ld	h,b
	ld	l,c
	ld	c,(ix + vdStart + 2)
	ld	b,(ix + vdStart + 3)
	sbc	hl,bc

	push	hl
	push	de

	; get total sector count

	ld	de,0
	ld	hl,(BootSector + bpbTotSec16)
	ld	a,h
	or	l
	jr	nz,$f

	ld	hl,(BootSector + bpbTotSec32 + 0)
	ld	de,(BootSector + bpbTotSec32 + 2)

$$	pop	bc
	sbc	hl,bc
	ex	de,hl
	pop	bc
	sbc	hl,bc

	; HL:DE now contains total number of data sectors

	ld	a,(BootSector + bpbSecPerClus)
$$	rra
	jr	c,$f
	srl	h
	rr	l
	rr	d
	rr	e
	jr	$b

$$	ld	(ix + vdDataClus + 0),e
	ld	(ix + vdDataClus + 1),d
	ld	(ix + vdDataClus + 2),c
	ld	(ix + vdDataClus + 3),b

	; determine free clusters

	ld	hl,2
	call	GetFreeCluster
	ld	(ix + vdFstFreeClus + 0),l
	ld	(ix + vdFstFreeClus + 1),h

	call	GetFreeClusterCount
	push	iy
	pop	hl
	ld	(ix + vdFreeClus + 0),l
	ld	(ix + vdFreeClus + 1),h

	; store volume letter

	ld	a,'C'
	ld	hl,SysNumVolumes
	add	a,(hl)
	ld	(ix + vdLetter),a
	inc	(hl)

	; create another volume descriptor
	; WARNING: It writes data one "dummy" descriptor ahead!

	ld	a,(ix + vdDevice)
	ld	bc,vdSizeOf
	add	ix,bc
	ld	(ix + vdDevice),a

	; store device geometry into new volume descriptor for CHS devices

	ld	a,(IdSector + idNumHeads)
	ld	(ix + vdHeads),a
	ld	a,(IdSector + idNumSecs)
	ld	(ix + vdSecs),a

	; move to the next partition table entry

NextPri	pop	hl
	ld	a,l
	add	a,16
	ld	l,a
	jp	nc,ScanPri

	; scan for an extended partition

	ld	hl,MbrSector + 512 - 62
	ld	de,16

	ld	b,4
ScanExt	ld	a,(hl)
	cp	05h		; DOS Extended
	jr	z,ExtHere
	cp	0Fh		; Win95 Extended
	jr	z,ExtHere
	cp	85h		; Linux Extended
	jr	nz,NextExt

	; advance LBA start of logical drive

ExtHere	ld	e,4
	add	hl,de

	ld	e,(hl)
	inc	l
	ld	d,(hl)
	inc	l
	ld	c,(hl)
	inc	l
	ld	b,(hl)

	call	BCDEplusTemp2
	ld	(SysTemp1 + 0),de
	ld	(SysTemp1 + 2),bc

	; store LBA offset of 1st extended partition in MBR

	ld	hl,(SysTemp2 + 0)
	ld	a,h
	or	l
	ld	hl,(SysTemp2 + 2)
	or	h
	or	l
	jr	nz,No1Ext

	ld	(SysTemp2 + 0),de
	ld	(SysTemp2 + 2),bc

No1Ext	jp	MountVolumes

NextExt	add	hl,de
	djnz	ScanExt				; jr nc,... ?
	ret

;-------------------------------------------------------------------------------
; UTILITY FUNCTIONS
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
CheckBootSign:
;-------------------------------------------------------------------------------
; Input:
; HL - address of the last byte of sector + 1
;-------------------------------------------------------------------------------
; Output:
; z = 1 - OK
; z = 0 - invalid boot sector signature
;-------------------------------------------------------------------------------
; Function checks the last two bytes of a sector. If there's valid boot sector
; these bytes shall be 55h, AAh.
;-------------------------------------------------------------------------------

	ld	a,0AAh
	dec	hl
	cp	(hl)
	ret	nz
	cpl
	dec	l
	cp	(hl)
	ret

;-------------------------------------------------------------------------------
Check48k:
;-------------------------------------------------------------------------------
; Output:
; A = 0, z = 1 - 128k machine found
; A = 1, z = 0 - 48k machine found
;-------------------------------------------------------------------------------

	ld	bc,7FFDh
	ld	h,c
	ld	a,17
	out	(c),a
	ld	(hl),a
	dec	a
	out	(c),a
	ld	(hl),a
	inc	a
	out	(c),a
	sub	(hl)
	ret

;-------------------------------------------------------------------------------
LoadAY:
;-------------------------------------------------------------------------------
; Input:
; HL - address containing AY register values
;-------------------------------------------------------------------------------

	ld	de,16
$$	ld	bc,0FFFDh
	out	(c),d
	ld	b,0BFh
	ld	a,(hl)
	out	(c),a
	inc	hl
	inc	d
	dec	e
	jr	nz,$b
	ret

;-------------------------------------------------------------------------------
SaveAY:
;-------------------------------------------------------------------------------
; Input:
; HL - address to be filled up by AY register values
;-------------------------------------------------------------------------------
; The routine also mutes AY
;-------------------------------------------------------------------------------

	ld	hl,SysAY
	ld	de,16
$$	ld	bc,0FFFDh
	out	(c),d
	in	a,(c)
	ld	(hl),a
	ld	b,0BFh
	xor	a
	out	(c),a
	inc	hl
	inc	d
	dec	e
	jr	nz,$b
	ret

;-------------------------------------------------------------------------------
Beep:
;-------------------------------------------------------------------------------

	ld	a,(BORDCR)	; attempt to restore border colour
	rra
	rra
	rra
	and	7

	ld	c,0
$$	out	(254),a
	xor	16
	ld	b,c
	djnz	$
	inc	c
	jr	nz,$b
	ret

;-------------------------------------------------------------------------------
Num16ToASCII:
;-------------------------------------------------------------------------------

;	ld	de,SysNumber

;	ld	bc,-10000
;	call	NumAsc1
;	ld	bc,-1000
;	call	NumAsc1
;	ld	bc,-100
;	call	NumAsc1
;	ld	c,-10
;	call	NumAsc1
;	ld	c,b

;NumAsc1	ld	a,'0'-1
;NumAsc2	inc	a
;	add	hl,bc
;	jr	c,NumAsc2
;	sbc	hl,bc

;	ld	(de),a
;	inc	de
;	ret

;-------------------------------------------------------------------------------
Num32ToASCII:
;-------------------------------------------------------------------------------
; Input:
; A     - padding character (typically ' ' or '0')
; DE:HL - unsigned 32-bit number to convert
;-------------------------------------------------------------------------------
; Output:
; SysNumber - decimal number ASCII string
;-------------------------------------------------------------------------------

	ex	af,af'

	; clear buffer

	push	hl
	ld	hl,SysNumber
	ld	bc,10*256
$$	ld	(hl),c
	inc	l
	djnz	$b
	pop	hl

	; convert binary number to decimal number positions

	ld	b,32
PrnNum1	push	bc
	add	hl,hl
	rl	e
	rl	d
	push	de
	ld	de,SysNumber + 9
	ld	bc,0A0Ah
PrnNum2	ld	a,(de)
	rla
	cp	c
	jr	c,$f
	sub	c
$$	ld	(de),a
	dec	e
	ccf
	djnz	PrnNum2
	pop	de
	pop	bc
	djnz	PrnNum1

	; convert decimal numbers to ASCII

	ld	hl,SysNumber
	ex	af,af'
	ld	c,a
	ld	b,9
PrnNum4	ld	a,(hl)
	or	a
	jr	nz,$f
	db	11h
$$	ld	c,'0'
	or	c
	ld	(hl),a
	inc	l
	djnz	PrnNum4

	ld	a,(hl)
	or	'0'
	ld	(hl),a
	ret

;-------------------------------------------------------------------------------
; ROM ENTRY POINT: SAVE
;-------------------------------------------------------------------------------

	org	4C6h

	ld	hl,1F80h		; ZX ROM instrucion
In4C6h	push	hl
	ld	hl,In4C6h
	ex	(sp),hl
	jp	OutRET

;-------------------------------------------------------------------------------
; ROM ENTRY POINT: LOAD
;-------------------------------------------------------------------------------

	org	562h

	in	a,(254)
	ld	hl,564h
	push	hl
	in	a,(254)
	jp	OutRET

;-------------------------------------------------------------------------------
Startup:
;-------------------------------------------------------------------------------

	ld	sp,5C00h

	; mute AY

;	ld	hl,SysAY
	ld	hl,5B00h		; doesn't matter
	call	SaveAY			; leaves A = 0

;	xor	a
	out	(254),a
	ld	(BORDCR),a		; just for the case of Beep :)

	call	DisplayLogo

	; unpack font

	ld	a,82h
	out	(DIV_CTRL),a

	xor	a
	ld	hl,FontRLE
	ld	de,2000h
	call	UnpackRLE

	ld	a,80h
	out	(DIV_CTRL),a

	; on +2A/+3, page-in 48k ROM

	ld	bc,1FFDh
	ld	a,4
	out	(c),a

	; on 128k, page-in bank 0 (and videoram 0)

	ld	b,7Fh
	ld	a,16
	out	(c),a
	push	bc			; reused as delay loop counter

	; display copyright messages

	ld	hl,16384 + 2048 + 96
	ld	(SysCursor),hl

	ld	a,PAP_BLACK | INK_WHITE
	ld	(SysColour),a
	ld	hl,TxtCopyright
	call	PrintLine
	inc	hl
	call	PrintLine
	call	LineFeed

	ld	a,PAP_BLACK | INK_WHITE | BRIGHT
	ld	(SysColour),a
	ld	hl,TxtForceInit
	call	PrintLine
	call	LineFeed

	; delay loop to suppress reset oscillations and to show logo

	pop	bc			; delay loop count
$$	ex	(sp),hl
	ex	(sp),hl
	dec	bc
	inc	b
	djnz	$b

	; perform cold reset either if the user holds SPACE...

	call	TestForSpace
	jr	nc,ColdRst

	; ...or if the signature in RAM doesn't match (power-on, reflash, crash...)

	ld	hl,(RomSign)
	ld	bc,(SysSign)
	or	a
	sbc	hl,bc
	jr	nz,ColdRst

	; ...or if the bank 3 boot didn't boot correctly

	ld	a,(SysBootOK)
	dec	a
	jp	z,ZxReset

	; cold startup: zero memory and perform complete init

ColdRst	ld	hl,2000h
	xor	a
$$	ld	(hl),a
	inc	hl
	bit	6,h
	jr	z,$b

	; write signature

	ld	hl,(RomSign)
	ld	(SysSign),hl

	call	InvalidateCache

	; IM2 table also serves as dummy BetaDisk entry points (LD A,3Eh...)

	ld	hl,3D00h
$$	ld	(hl),3Eh
	inc	l
	jr	nz,$b
	inc	h
	ld	(hl),h
	inc	l
;	ld	(hl),0			; NOP to handle odd entry points
	inc	l
	ld	(hl),0C3h		; JP OutRET
	inc	l
	ld	(hl),<OutRET
	inc	l
	ld	(hl),>OutRET
	ld	l,h
	ld	(hl),3Ch		; INC A
	inc	l
	ld	(hl),0C9h		; RET

	; display autodetecting message

	ld	hl,16384 + 2048 + 192
	ld	(SysCursor),hl
 
	ld	a,PAP_BLACK | INK_WHITE | BRIGHT
	ld	(SysColour),a

	ld	hl,TxtDetect
	call	PrintLine
	call	LineFeed

	; enable interrupts for error timer

	im	1
	ei

	; detect device 0 and its volumes

	ld	ix,VolDesc
	ld	(SysCurrVolume),ix

	ld	hl,TxtDev0
	ld	b,2
	call	PrintString

	ld	(ix + vdDevice),DEV_0 | DEV_LBA
	call	ScanDevice
	call	LineFeed

	; detect device 1 and its volumes

	ld	hl,TxtDev1
	ld	b,2
	call	PrintString

	ld	(ix + vdDevice),DEV_1 | DEV_LBA
	call	ScanDevice
	call	LineFeed

	; display the number of FAT volumes found

	call	LineFeed
	ld	a,(SysNumVolumes)
	add	a,'0'
	call	PrintChar
	ld	hl,TxtVolFound
	call	PrintLine
	call	LineFeed

;	ld	a,PAP_BLACK | INK_WHITE
;	ld	(SysColour),a
;	ld	hl,TxtAnyKey
;	call	PrintLine

;	ld	ix,VolDesc
;	ld	l,(ix + vdDataClus + 0)
;	ld	h,(ix + vdDataClus + 1)
;	ld	de,0
;	ld	a,' '
;	call	Num32ToASCII
;	ld	hl,SysNumber
;	ld	b,10
;	call	PrintString
;	ld	a,' '
;	call	PrintChar

;	ld	hl,2
;	call	GetFreeCluster
;	push	hl
;	ld	de,0
;	ld	a,' '
;	call	Num32ToASCII
;	ld	hl,SysNumber
;	ld	b,10
;	call	PrintString

;	call	LineFeed
;	pop	hl
;	call	GetFreeClusterCount
;	push	iy
;	pop	hl
;	ld	de,0
;	ld	a,' '
;	call	Num32ToASCII
;	ld	hl,SysNumber
;	ld	b,10
;	call	PrintString

;	call	WaitForKey

;-------------------------------------------------------------------------------
Boot:
;-------------------------------------------------------------------------------

	; signal boot error if there is no volume to boot from

	ld	a,(SysNumVolumes)
	or	a
	jp	z,BootErr		; jr?

	; check whether there is anything to flash

	ld	hl,TxtFlashPath
	ld	a,1
	call	FileOpen
	jr	c,NoFlash

	ld	hl,FlashImage		; TODO: make as a subroutine?
	ld	de,2000h
	push	de
	call	FileRead
	call	FileClose
	pop	de
	jp	c,BootErr		; jr?
	sbc	hl,de			; check if 8192 bytes were read
	jp	nz,BootErr		; jr?

	; display flash prompt

	ld	a,PAP_BLACK | INK_YELLOW | BRIGHT
	ld	(SysColour),a

	ld	hl,TxtFlashPrompt
	ld	b,64
	call	PrintString

	call	WaitForKey
	call	TestForEnter
	jr	c,NoFlash

	ld	hl,16384 + 2048 + 192
	ld	(SysCursor),hl
	ld	a,INK_WHITE | BRIGHT
	ld	(SysColour),a

	ld	hl,TxtFlashInfo
	ld	b,5 * 32
	call	PrintString

	ld	b,4			; really stupid way of clearing screen area :)
$$	push	bc
	call	PrintLine
	pop	bc
	djnz	$b

$$	call	TestForSpace
	jr	c,$b

	; perform the flash procedure

	ld	hl,FlashCode
	ld	de,5B00h
	push	de
	ld	bc,FlashCodeSize
	ldir
	ret

	; open bank 3 image file and read it to host memory

NoFlash	ld	hl,TxtBootPath
	ld	a,1
	call	FileOpen
	jr	c,BootErr

	ld	hl,6000h
	ld	de,2000h
	push	hl
	push	de
	call	FileRead
	call	FileClose
	pop	de
	jr	c,BootErr

	; check whether exactly 8192 bytes have been read
	; TODO: checksum / version match checks

	sbc	hl,de
	jr	nz,BootErr

	; put the image to bank 3 and set up MAPRAM mode

	di
	ld	a,83h
	out	(DIV_CTRL),a

;	ld	hl,6000h
	pop	hl
	ld	b,d
	ld	c,e
	ldir

	ld	a,80h
	out	(DIV_CTRL),a

	ld	hl,TxtBootOK
	call	BootMsg

	; reuse CallBank routine to switch to MAPRAM and invoke bank 3 init

	di
	ld	hl,5
	push	hl
	push	af
	ld	a,40h
	jp	PageMap

BootErr	call	Beep
	ld	a,PAP_BLACK | INK_RED | BRIGHT
	ld	(SysColour),a
	ld	hl,TxtBootErr
	call	BootMsg

	; perform ZX ROM reset (reset CONMEM and jump to address 1 via off-area)

ZxReset	di
	ld	hl,OutJPHL
	push	hl
	ld	hl,1
	jp	PageOut

;-------------------------------------------------------------------------------
BootMsg:
;-------------------------------------------------------------------------------

	ld	de,16384 + 4096 + 160
	ld	(SysCursor),de

;	ld	a,PAP_BLACK | INK_RED | BRIGHT
;	ld	(SysColour),a
	call	PrintLine

	ld	a,PAP_BLACK | INK_WHITE
	ld	(SysColour),a
	ld	hl,TxtAnyKey
	call	PrintLine

	call	WaitForKey
	ret

;-------------------------------------------------------------------------------
FindVolume:
;-------------------------------------------------------------------------------
; Input:
; A - volume letter
;-------------------------------------------------------------------------------
; Output:
; c = 0, HL - volume descriptor address
; c = 1     - invalid volume
;-------------------------------------------------------------------------------

	ld	c,a
	ld	a,(SysNumVolumes)
	or	a
	jr	z,FndVol2

	ld	b,a
	ld	a,c
	call	UpperCase
	ld	hl,VolDesc
	ld	de,vdSizeOf
FndVol1	cp	(hl)
	ret	z			; Carry = 0
	add	hl,de
	djnz	FndVol1

FndVol2	ld	a,ERR_INVALID_VOLUME
	ld	(SysError),a
	scf
	ret

;-------------------------------------------------------------------------------
AllocFileDesc:
;-------------------------------------------------------------------------------
; Output:
; c = 0, IY - address of file descriptor
; c = 1     - maximum number of file descriptors reached
;-------------------------------------------------------------------------------

	ld	iy,FileDesc
	ld	de,fdSizeOf
	ld	b,MAX_FILE_DESC
$$	ld	a,(iy + fdMode)
	or	a
	ret	z			; Carry = 0
	add	iy,de
	djnz	$b

	ld	a,ERR_TOO_MANY_FILES
	ld	(SysError),a
	scf
	ret

;-------------------------------------------------------------------------------
FileOpen:
;-------------------------------------------------------------------------------
; Input:
; HL - path string address
; A  - file opening mode (1 - read, 2 - write)
;-------------------------------------------------------------------------------
; Output:
; IY - address of newly opened file descriptor
; c = 0 - OK
; c = 1 - Error
;-------------------------------------------------------------------------------

	; allocate a new file descriptor

	call	AllocFileDesc		; might not be needed in case of OpenDir
	ret	c

	; check for volume letter, find and load volume

	inc	hl
	ld	a,(hl)
	cp	':'
	jr	nz,Fopen1

	push	hl
	dec	hl
	ld	a,(hl)
	call	FindVolume
	ex	de,hl
	pop	hl
	jr	nc,Fopen2

	; TODO: file/volume not found error
	ret

	; load current volume if there was no volume letter

Fopen1	dec	hl
	ld	de,(SysCurrVolume)
Fopen2	ld	(iy + fdVolume + 0),e
	ld	(iy + fdVolume + 1),d
	push	de			; TODO: undocumented instructions
	pop	ix

	; check path length (max. 256 characters including zero is allowed)
	; TODO: call proper StrLength?

	push	hl
	ld	bc,257
	xor	a
	cpir
	pop	hl
	jp	pe,Fopen3

	ld	a,ERR_INVALID_PATH
	ld	(SysError),a
	scf
	ret

	; copy path to buffer (TODO: only BC times)

Fopen3	ld	de,PathBuffer
	push	de
	push	de
	push	de
	ld	bc,256
	ldir

	; convert slashes (even ':' to '\' for Root Directory descend)

	pop	hl
	ld	a,(hl)
	cp	':'
	jr	nz,$f
	ld	(hl),'\\'
$$	call	ConvertSlash

	; convert path to upper case (FAT file system is not case sensitive)

	pop	hl
$$	ld	a,(hl)
	call	UpperCase
	ld	(hl),a
	inc	l
	jr	nz,$b

	; now, check if we are descending from the root

	ld	a,(hl)
	sub	'\\'
	jr	nz,Fopen6

	; TODO: replace the following stuff by more general OpenDir

	; it's root, load file descriptor with Root Directory

	ld	h,a
	ld	l,a
	ld	(iy + fdSize + 3),a
	ld	(iy + fdSize + 2),a
	ld	a,(ix + vdRootSize + 1)
	ld	(iy + fdSize + 1),a
	ld	a,(ix + vdRootSize + 0)
	ld	(iy + fdSize + 0),a
	jr	Fopen7

	; it's not root, load volume's current directory
	; ERROR: even now, the current directory might be root! Use OpenDir!

Fopen6	ld	l,(ix + vdDirClus + 0)
	ld	h,(ix + vdDirClus + 1)
	ld	a,0FFh
	ld	(iy + fdSize + 0),a
	ld	(iy + fdSize + 1),a
	ld	(iy + fdSize + 2),a
	ld	(iy + fdSize + 3),a

Fopen7	call	ResetFilePos
	pop	hl

DescendPath:

	; search for first non-slash character

	ld	a,'\\'
	dec	l
Slash1	inc	l
	cp	(hl)
	jr	z,Slash1

	ld	(SysTemp1),hl

	; search for ending slash, if there's zero we have reached the filename

	dec	l
Slash2	inc	l
	ld	a,(hl)
	or	a
	jr	z,FindFileName
	sub	'\\'
	jr	nz,Slash2
	ld	(hl),a

	inc	l
	push	hl

	; path component is now ASCIIZ string, find next directory entry

FindDirName:

	; (ATR_VOLUME_ID | ATR_DIRECTORY << 8) + ATR_DIRECTORY

	ld	bc,1810h
	call	FindDirEntry
	jr	c,OpnErr1

DirName	ld	hl,(SysTemp1)
	ld	de,FileName
	call	StrCmpNoCase
	jr	nz,FindDirName

	; directory found, load values into descriptor

	call	LoadFileDesc
	pop	hl
	jr	DescendPath

FindFileName:

	ld	bc,1800h
	call	FindDirEntry
	jr	c,OpnErr2

FilName	ld	hl,(SysTemp1)
	ld	de,FileName
	call	StrCmpNoCase
	jr	nz,FindFileName

	call	LoadFileDesc
	ret				; Carry = 0

	; remap "end of directory" to "file not found"

OpnErr1	pop	hl
;	ld	a,(SysError)
OpnErr2	or	a
	jr	nz,OpnErr3
	ld	a,ERR_FILE_NOT_FOUND
	ld	(SysError),a
OpnErr3	scf
	ret

;-------------------------------------------------------------------------------
FileOpenRaw:
;-------------------------------------------------------------------------------
; Input:
; DE - volume descriptor core
;-------------------------------------------------------------------------------

	call	AllocFileDesc
	ret	c

	ld	(iy + fdMode),1
	call	StoreVolume

	ex	de,hl
	push	iy
	pop	hl
	ld	bc,fdAttr
	add	hl,bc
	ld	c,7
	ex	de,hl
	ldir

	; TODO: unify this with reset file pos

	ld	l,(iy + fdStartClus + 0)
	ld	h,(iy + fdStartClus + 1)
	jp	ResetFilePos

;-------------------------------------------------------------------------------
LoadFileDesc:
;-------------------------------------------------------------------------------
; Loads existing file descriptor with directory entry data from DirEntry.
;-------------------------------------------------------------------------------

	; TODO: load also volume address?

	; load file attributes

	ld	a,(DirEntry + dirAttr)
	ld	(iy + fdAttr),a

	ld	hl,(DirEntry + dirFileSize + 0)
	ld	de,(DirEntry + dirFileSize + 2)

	and	ATR_DIRECTORY
	jr	z,NoDir

	; directories have file size 0, set maximum (im)possible size

	ld	hl,0FFFFh
	ld	d,h
	ld	e,l

	; load file size

NoDir	ld	(iy + fdSize + 0),l
	ld	(iy + fdSize + 1),h
	ld	(iy + fdSize + 2),e
	ld	(iy + fdSize + 3),d

	; load starting and current cluster
	; TODO: check cluster = 0 for root directory

	ld	hl,(DirEntry + dirFstClusLo)

ResetFilePos:

	ld	(iy + fdStartClus + 0),l
	ld	(iy + fdStartClus + 1),h
	ld	(iy + fdClus + 0),l
	ld	(iy + fdClus + 1),h

	xor	a
	ld	(iy + fdBytePos + 0),a
	ld	(iy + fdBytePos + 1),a
	ld	(iy + fdClusPos + 0),a
	ld	(iy + fdClusPos + 1),a
	ret

;-------------------------------------------------------------------------------
StoreVolume:
;-------------------------------------------------------------------------------

	LD__A_XL
	ld	(iy + fdVolume + 0),a
	LD__A_XH
	ld	(iy + fdVolume + 1),a
	ret

;-------------------------------------------------------------------------------
OpenCurrentDir:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
;-------------------------------------------------------------------------------
; Output:
; c = 1 - Error
; c = 0 - OK
;-------------------------------------------------------------------------------

	ld	l,(ix + vdDirClus + 0)
	ld	h,(ix + vdDirClus + 1)

;-------------------------------------------------------------------------------
OpenDir:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; HL - starting directory cluster (0 means root directory)
;-------------------------------------------------------------------------------
; Output:
; c = 1 - Error
; c = 0 - OK
;-------------------------------------------------------------------------------

	push	hl
	call	AllocFileDesc
	pop	hl
	ret	c

	call	StoreVolume
	ld	(iy + fdAttr),ATR_DIRECTORY
	ld	(iy + fdMode),1

	ld	a,h			; is it root directory?
	or	l
	jr	z,$f

	; this hack might not be needed if attributes are set properly

	ld	a,255
	ld	(iy + fdSize + 0),a
	ld	(iy + fdSize + 1),a
	ld	(iy + fdSize + 2),a
	ld	(iy + fdSize + 3),a
	jr	RstPos

$$	ld	(iy + fdSize + 2),a
	ld	(iy + fdSize + 3),a
	ld	a,(ix + vdRootSize + 0)
	ld	(iy + fdSize + 0),a
	ld	a,(ix + vdRootSize + 1)
	ld	(iy + fdSize + 1),a

RstPos	call	ResetFilePos
	ret

;-------------------------------------------------------------------------------
FileClose:
;-------------------------------------------------------------------------------
; Input:
; IY - address of file descriptor
;-------------------------------------------------------------------------------
; This function MUST NOT change Carry as it is often used in error exits.
;-------------------------------------------------------------------------------

	; TODO: in case of write, there might be possibly some flushing to do

	ld	(iy + fdMode),0
	ret

;-------------------------------------------------------------------------------
FindDirEntry:
;-------------------------------------------------------------------------------
; Input:
; IY - address of file descriptor
; B  - attribute mask (attributes we care about)
; C  - allowable attributes (attributes which can be both set or unset)
;-------------------------------------------------------------------------------
; Output:
; c = 1 - error or end of directory
; c = 0 - FileName and DirEntry hold valid directory entry data
;-------------------------------------------------------------------------------

	push	bc
	call	ReadDirEntry
	pop	bc
	ret	c

	ld	a,(DirEntry + dirAttr)
	and	b
	xor	c
	jr	nz,FindDirEntry
	ret				; Carry = 0

;-------------------------------------------------------------------------------
ReadDirEntry:
;-------------------------------------------------------------------------------
; Input:
; IY - address of file descriptor
;-------------------------------------------------------------------------------
; Output:
; c = 0 - OK, file name at [StringName] is valid
; c = 1, A > 0 - error
; c = 1, A = 0 - end of directory (0 is a code for ERR_OK)
;-------------------------------------------------------------------------------

RdeOrph	ld	c,0E5h			; reset count to "impossible" number

RdeDir	push	bc
	push	de
	ld	hl,DirEntry
	ld	de,32
	call	FileRead
	pop	de
	pop	bc
	ret	c

	; test EOF (this can happen e.g. for Root Directory)

	ld	a,h
	or	l
	jr	z,RdeEnd

	; skip erased entries

RdeCont	ld	a,(DirEntry)
	cp	0E5h
	jr	z,RdeDir

	; return on "end of directory" mark (if present)

	or	a
	jr	nz,RdeAttr

RdeEnd	ld	(SysError),a
	scf
	ret

	; is it a long file name entry?

RdeAttr	ld	a,(DirEntry + dirAttr)
	and	ATR_MASK
	cp	ATR_LONG_NAME
	jr	nz,RdeShortName

	; is it the last long file name entry?

	ld	a,(DirEntry + lfnOrd)	; ex af,af'?
	cp	40h			; warning, this will work even for 80h
	jr	c,RdeNext

	; yes, load counter, calculate string address, load checksum

	and	1Fh
	ld	c,a

	add	a,a
	add	a,a
	ld	e,a
	add	a,a
	add	a,e
	add	a,c
	ld	d,>FileName
	ld	e,a

	; store terminating zero for long file names that are dir entry aligned

	xor	a
	ld	(de),a
	dec	e

	ld	a,(DirEntry + lfnChksum)
	ld	b,a
	jr	RdeExtractLong

	; next long entry found, check order...

RdeNext	cp	c
	jr	nz,RdeOrph

	; ...check checksum (otherwise it's orphan entry)

	ld	a,(DirEntry + lfnChksum)
	cp	b
	jr	nz,RdeOrph

	; decrement counter and extract partial long name into buffer

RdeExtractLong:

	ld	hl,DirEntry + 30
	ld	a,c
	ld	c,l			; dummy value to prevent overflow and preserve B
	ldd
	dec	l
	ldd
	ld	l,<DirEntry + 24
	ldd
	dec	l
	ldd
	dec	l
	ldd
	dec	l
	ldd
	dec	l
	ldd
	dec	l
	ldd
	ld	l,<DirEntry + 9
	ldd
	dec	l
	ldd
	dec	l
	ldd
	dec	l
	ldd
	dec	l
	ldd
	ld	c,a
	dec	c
	jr	RdeDir

RdeShortName:

	; after sequence of LFN entries count must be 0

	xor	a
	cp	c
	jr	nz,RdeExtractShort

	; it looks like there is SFN associated with LFN, check checksum

	ld	hl,DirEntry
	call	ShortNameChecksum
	cp	b
	ret	z			; Carry = 0

	; wrong checksum, extract ordinary SFN

RdeExtractShort:

	ld	de,FileName

	; remap Japan KANJI character E5h (characters 0..31 are illegal except 5)

;	ld	hl,DirEntry
;	ld	a,(hl)
;	cp	6
;	jr	nc,$+3
;	ld	(hl),0xE5

	; remove trailing spaces from the name (1st character is always non-space)

	ld	hl,DirEntry + 8
	ld	a,' '
ExtSho1	dec	l
	cp	(hl)
	jr	z,ExtSho1

	inc	l
	ld	de,FileName
	ld	b,e
	ld	c,l
	ld	l,e
	ldir

	; remove trailing spaces from the extension

	ld	l,<DirEntry + 10
	ld	c,3
	ld	a,' '
	cp	(hl)
	jr	nz,ExtSho2
	dec	l
	dec	c
	cp	(hl)
	jr	nz,ExtSho2
	dec	l
	dec	c
	cp	(hl)
	jr	z,ExtSho3

;	ld	l,10
;	ld	c,3
;	ld	a,' '
;Holala	cp	(hl)
;	jr	nz,ExtSho2
;	dec	l
;	dec	c
;	jr	nz,Holala
;	jr	ExtSho3

ExtSho2	ld	a,'.'
	ld	(de),a
	inc	e
	ld	l,<DirEntry + 8
	ldir

ExtSho3	xor	a
	ld	(de),a
	ret				; Carry = 0

;-------------------------------------------------------------------------------
ShortNameChecksum:
;-------------------------------------------------------------------------------
; Input:
; HL - address of file name
;-------------------------------------------------------------------------------
; Output:
; A - checksum
;-------------------------------------------------------------------------------

	ld	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	inc	l
	rrca
	add	a,(hl)
	ret

;-------------------------------------------------------------------------------
IsEOC:
;-------------------------------------------------------------------------------
; Input:
; IY - address of file descriptor
;-------------------------------------------------------------------------------
; Output:
; z = 1 - END OF CLUSTER chain (EOC)
; z = 0 - no EOC
;-------------------------------------------------------------------------------

	ld	a,(iy + fdClus + 0)	; cluster >= FFF0h ?
	or	0Fh
	and	(iy + fdClus + 1)
	inc	a
	ret

;-------------------------------------------------------------------------------
IsEOF:
;-------------------------------------------------------------------------------
; Input:
; IY - address of file descriptor
;-------------------------------------------------------------------------------
; Output:
; z = 1 - END OF FILE (EOF)
; z = 0 - no EOF (HL:DE = bytes remaining to the end of file)
;-------------------------------------------------------------------------------

	; 1) test EOC cluster mark (valid EOF condition for directories
	; and error condition for files, signalling inconsistence between FAT
	; chain and the file size stored in a directory entry)

	call	IsEOC
	ret	z

	; 2) test (FilePos == FileSize) for standard files and root directory
	; (directories have a fake length FFFFFFFFh so this test always fails)
	; TODO: remove fake length and use proper directory attribute in FD?

	ld	a,(iy + fdVolume + 0)
	LD__XL_A
	ld	a,(iy + fdVolume + 1)
	LD__XH_A			; 54 T but undocumented

	call	FilePosToLinear		; Carry = 0

	ld	l,(iy + fdSize + 0)
	ld	h,(iy + fdSize + 1)
	sbc	hl,de
	ret	nz
	ld	l,(iy + fdSize + 2)
	ld	h,(iy + fdSize + 3)
	sbc	hl,bc
	ret

;-------------------------------------------------------------------------------
FileRead:
;-------------------------------------------------------------------------------
; Input:
; IY - address of file descriptor
; HL - destination address
; DE - number of bytes to read
;-------------------------------------------------------------------------------
; Output:
; HL - number of bytes read
; c = 1 - Error
;-------------------------------------------------------------------------------

	ld	(SysBlockAddr),hl
	push	hl

	; test EOC mark (which is a valid EOF condition for directories)

	call	IsEOC
	jp	z,FileRdEnd

	; load volume descriptor address

	ld	a,(iy + fdVolume + 0)
	LD__XL_A
	ld	a,(iy + fdVolume + 1)
	LD__XH_A			; 54 T but undocumented

	; truncate block length to min(FileSize - FilePos, BlockLength)

	push	de
	call	FilePosToLinear		; Carry = 0

	ld	l,(iy + fdSize + 0)
	ld	h,(iy + fdSize + 1)
	sbc	hl,de
	ex	de,hl
	ld	l,(iy + fdSize + 2)
	ld	h,(iy + fdSize + 3)
	sbc	hl,bc
	pop	hl

	; if higher word is non-zero, take input block length as minimum

	jr	nz,$f
	sbc	hl,de
	add	hl,de
	jr	c,$f
	ex	de,hl
$$	ld	(SysBlockLng),hl

FileRdCluster:

	ld	bc,(SysBlockLng)
	ld	a,b
	or	c
	jr	z,FileRdEnd

	; if we are at cluster boundary, read clusters up to block length

	ld	a,(iy + fdBytePos + 0)
	or	(iy + fdBytePos + 1)
	jr	nz,FileRdSector

	; calculate the number of clusters to read

	ld	a,(ix + vdSecsPerClus)
$$	srl	b			; sets Zero flag
	rra
	jr	nc,$b
	jr	z,FileRdSector

	; read clusters

RdClus	push	bc

	call	FilePosToLBA

	ld	a,(ix + vdSecsPerClus)
	ld	hl,(SysBlockAddr)
	call	ReadSectors
;	jr	c,Error
	ld	(SysBlockAddr),hl

	; update bytes left

	ld	b,(ix + vdSecsPerClus)
	ld	hl,SysBlockLng + 1
	ld	a,(hl)
	sub	b
	sub	b
	ld	(hl),a

	; advance cluster position (bytes in cluster don't change)

	call	AdvanceFileClus
;	jr	c,Error

	pop	bc
	djnz	RdClus

FileRdSector:

	ld	hl,(SysBlockLng)
	ld	a,h
	or	l
	jr	z,FileRdEnd

	ld	a,(iy + fdBytePos + 1)
	ld	l,a
	and	1
	or	(iy + fdBytePos + 0)
	jr	nz,FileRdByte

	srl	h
	jr	z,FileRdByte

	ld	a,(ix + vdSecsPerClus)
	srl	l
	sub	l

	; take the minimum of the two

	cp	h
	jr	c,$f
	ld	a,h
$$	push	af

	ex	af,af'
	call	FilePosToLBA
	ex	af,af'

	ld	hl,(SysBlockAddr)
	call	ReadSectors
;	jr	c,Error			; mind the stack! (PUSH AF)
	ld	(SysBlockAddr),hl

	; update bytes left

	pop	af
	add	a,a
	ld	b,a
	ld	c,0
	ld	hl,SysBlockLng + 1
	ld	a,(hl)
	sub	b
	ld	(hl),a

	; update file position

	call	AdvanceFilePos
;	jr	c,ErrorPop
	jr	FileRdCluster

FileRdEnd:

	; calculate bytes read

	ld	hl,(SysBlockAddr)
	pop	bc
	sbc	hl,bc
	or	a			; SBC might cause Carry = 1 if the last byte was at FFFFh
	ret

FileRdByte:

	ld	hl,(SysBlockLng)
	ld	a,h
	or	l
	jr	z,FileRdEnd

	; calculate bytes left in sector (increment one's complement to 512)

	ld	a,(iy + fdBytePos + 0)
	cpl
	ld	e,a
	ld	a,(iy + fdBytePos + 1)
	cpl
	and	1
	ld	d,a
	inc	de

	; take min(bytes left in sector, bytes left in block)

	sbc	hl,de
	add	hl,de
	jr	c,$f
	ex	de,hl
$$	push	hl

	call	FilePosToLBA
	call	ReadSectorCached
;	jr	c,Error

	ld	h,a
	ld	l,(iy + fdBytePos + 0)
	ld	a,(iy + fdBytePos + 1)
	and	1
	or	h
	ld	h,a

	ld	de,(SysBlockAddr)
	pop	bc
	push	bc
	ldir
	ld	(SysBlockAddr),de

	; update bytes left

	pop	bc
	ld	hl,(SysBlockLng)
	sbc	hl,bc
	ld	(SysBlockLng),hl

	; update file position

	call	AdvanceFilePos
;	jr	c,Error
	jp	FileRdCluster

;-------------------------------------------------------------------------------
FileWrite:
;-------------------------------------------------------------------------------
; Input:
; IY - address of file descriptor
; HL - source address
; DE - number of bytes to write
;-------------------------------------------------------------------------------
; Output:
; HL - number of bytes read
; c = 1 - Error
;-------------------------------------------------------------------------------

;	ld	(SysFileSP),sp
	ld	(SysBlockAddr),hl
	ld	(SysBlockLng),de
	push	hl

	; seek to target write position

	ld	e,(iy + fdWritePos + 0)
	ld	d,(iy + fdWritePos + 1)
	ld	c,(iy + fdWritePos + 2)
	ld	b,(iy + fdWritePos + 3)
	xor	a
	call	FileSeek

	; load volume descriptor address

	ld	a,(iy + fdVolume + 0)
	LD__XL_A
	ld	a,(iy + fdVolume + 1)
	LD__XH_A			; 54 T but undocumented

FileWrCluster:

	ld	hl,(SysBlockLng)
	ld	a,h
	or	l
	jr	z,FileWrEnd

	; if we're at the cluster boundary, try to write whole cluster at once

	ld	a,(iy + fdBytePos + 0)
	or	(iy + fdBytePos + 1)
	jr	nz,FileWrSector

	; but if we're at the end of file we must first allocate new cluster

	push	hl
	call	IsEOC
;	call	z,AllocNewCluster
	pop	hl

	; any cluster to write?

	ld	a,(ix + vdSecsPerClus)
	add	a,a
	dec	a
	cpl
	and	h
	jr	z,FileWrSector

	; write cluster

	call	FilePosToLBA

	ld	a,(ix + vdSecsPerClus)
	ld	hl,(SysBlockAddr)
	call	WriteSectors
;	jr	c,FileWrError
	ld	(SysBlockAddr),hl

	ld	b,(ix + vdSecsPerClus)
	ld	hl,SysBlockLng + 1	; update byte counter
	ld	a,(hl)
	sub	b
	sub	b
	ld	(hl),a

	call	AdvanceFileClus
;	jr	c,FileWrError
;	call	UpdateFileSize		; can be at the end?
	jr	FileWrCluster

FileWrSector:

	ld	hl,(SysBlockLng)	; test for end of block
	ld	a,h
	or	l
	jr	z,FileWrEnd

	; sector boundary?

	ld	a,(iy + fdBytePos + 1)
	ld	l,a
	and	1
	or	(iy + fdBytePos + 0)
	jr	nz,FileWrByte

	; any sectors to write?

	srl	h
	jr	z,FileWrByte

	ld	a,(ix + vdSecsPerClus)
	srl	l
	sub	l

	; take the minimum of the two

	cp	h
	jr	c,$f
	ld	a,h
$$	push	af

	ex	af,af'
	call	FilePosToLBA
	ex	af,af'

	ld	hl,(SysBlockAddr)
	call	WriteSectors
;	jr	c,FileWrError
	ld	(SysBlockAddr),hl

	; update bytes left

	pop	af
	add	a,a
	ld	b,a
	ld	c,0
	ld	hl,SysBlockLng + 1	; update byte counter
	ld	a,(hl)
	sub	b
	ld	(hl),a

	call	AdvanceFilePos
;	jr	c,FileWrError
	jr	FileWrCluster

FileWrEnd:

	call	FilePosToLinear		; Carry = 0

	; update WritePos to current FilePos

	ld	(iy + fdWritePos + 0),e
	ld	(iy + fdWritePos + 1),d
	ld	(iy + fdWritePos + 2),c
	ld	(iy + fdWritePos + 3),b

	; update file size if FilePos > FileSize

	ld	l,(iy + fdSize + 0)
	ld	h,(iy + fdSize + 1)
	sbc	hl,de
	ld	l,(iy + fdSize + 2)
	ld	h,(iy + fdSize + 3)
	sbc	hl,bc
	jr	nc,$f

	ld	(iy + fdSize + 0),e
	ld	(iy + fdSize + 1),d
	ld	(iy + fdSize + 2),c
	ld	(iy + fdSize + 3),b

	; TODO: update dir entry if it's not dir

$$

;	ld	sp,(SysFileSP)
	ld	hl,(SysBlockAddr)
	pop	bc
	sbc	hl,bc
	or	a
	ret

FileWrByte:

	ld	hl,(SysBlockLng)
	ld	a,h
	or	l
	jr	z,FileWrEnd

	; calculate bytes left in sector (increment one's complement to 512)

	ld	a,(iy + fdBytePos + 0)
	cpl
	ld	e,a
	ld	a,(iy + fdBytePos + 1)
	cpl
	and	1
	ld	d,a
	inc	de

	sbc	hl,de
	add	hl,de
	jr	c,$f
	ex	de,hl
$$	ld	(SysBlockSec),hl

	call	FilePosToLBA
	push	bc
	push	de
	call	ReadSectorCached
;	jr	c,FileWrError

	ld	d,a			; TODO: SLL D optim?
	ld	e,(iy + fdBytePos + 0)
	ld	a,(iy + fdBytePos + 1)
	and	1
	or	d
	ld	d,a

	ld	bc,(SysBlockSec)
	ld	hl,(SysBlockAddr)
	ldir
	ld	(SysBlockAddr),hl

	pop	de
	pop	bc
	and	0FEh
	ld	h,a
	xor	a
	ld	l,a
	inc	a
	call	WriteSectors
;	jr	c,FileWrError

	; update bytes left

	ld	bc,(SysBlockSec)
	ld	hl,(SysBlockLng)
	sbc	hl,bc
	ld	(SysBlockLng),hl
	jp	FileWrCluster

;-------------------------------------------------------------------------------
; SPECIAL NOTE REGARDING ROOT DIRECTORY
;
; In order to handle Root Directory transparently via FileRead, Root Directory
; is emulated as a file fully contained in cluster 0 (0 is normally an invalid
; value). Byte offset in this "cluster" then equals to current file position.
; This limits Root Directory to maximum 65536 bytes but typical Root Directory
; is only 16384 bytes long (512 * 32). Also note that cluster 0 shall NEVER be
; updated as we always read 32-byte items (only byte position is updated).
; For ".." directory entries, cluster 0 means Root Directory and is a valid
; number. Our approach can handle this special case implicitly.
;
; It could be more elegant to use StartClus = 0 (not Clus = 0) and allow full
; 32-bit file position but using Clus enables little optimisation of
; FilePosToLBA.
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
AdvanceFilePos:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; IY - address of file descriptor
; BC - number of bytes to advance (must be less than or equal to cluster size)
;-------------------------------------------------------------------------------
; Output:
; c = 0 - OK
; c = 1 - Error
;-------------------------------------------------------------------------------

	ld	l,(iy + fdBytePos + 0)
	ld	h,(iy + fdBytePos + 1)

	ld	a,(iy + fdClus + 0)	; is it root directory?
	or	(iy + fdClus + 1)

	add	hl,bc			; won't set carry for < 64k root dir
	jr	z,AdvByte
	jr	c,AdvClus		; this can only happen for 64k cluster

	ld	a,(ix + vdSecsPerClus)
	add	a,a
	dec	a
	and	h
	cp	h			; sets carry on cluster overflow
	ld	h,a
	jr	nc,AdvByte

	; now we have to update also cluster position

AdvClus	push	hl
	call	AdvanceFileClus
	pop	hl
	ret	c

	; eventual disc operations are OK, we may update file descriptor

AdvByte	ld	(iy + fdBytePos + 0),l
	ld	(iy + fdBytePos + 1),h
	ret

;-------------------------------------------------------------------------------
AdvanceFileClus:
;-------------------------------------------------------------------------------

	ld	l,(iy + fdClus + 0)
	ld	h,(iy + fdClus + 1)
	ld	(iy + fdPrevClus + 0),l
	ld	(iy + fdPrevClus + 1),h
	call	ReadFatEntry
	ret	c
	ld	(iy + fdClus + 0),l
	ld	(iy + fdClus + 1),h

	; increment linear cluster position

	inc	(iy + fdClusPos + 0)
	ret	nz
	inc	(iy + fdClusPos + 1)
	ret

;-------------------------------------------------------------------------------
FilePosToLBA:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; IY - address of file descriptor
;-------------------------------------------------------------------------------
; Output:
; BC:DE - LBA address of the sector to read
;-------------------------------------------------------------------------------

	ld	l,(iy + fdClus + 0)
	ld	h,(iy + fdClus + 1)
	ld	a,h
	or	l
	jr	z,PosRoot		; jump in less probable case

	; LBA = (Cluster - 2) * SecsPerClus + SecsInClus + DataRegionStart

	dec	hl
	dec	hl

	xor	a
	ld	b,a
	ld	c,(ix + vdSecsPerClus)
PosLba1	rr	c
	jr	c,PosLba2
	add	hl,hl
	rla
	jr	PosLba1

PosLba2	ld	c,(iy + fdBytePos + 1)
	srl	c
	add	hl,bc
	adc	a,b

	ld	e,(ix + vdDataLBA + 0)
	ld	d,(ix + vdDataLBA + 1)
	ld	c,(ix + vdDataLBA + 2)
	ld	b,(ix + vdDataLBA + 3)
	jp	BCDEplusAHL

PosRoot	ld	l,(iy + fdBytePos + 1)
	srl	l

	ld	e,(ix + vdRootLBA + 0)
	ld	d,(ix + vdRootLBA + 1)
	ld	c,(ix + vdRootLBA + 2)
	ld	b,(ix + vdRootLBA + 3)
	jp	BCDEplusHL

;-------------------------------------------------------------------------------
FilePosToLinear:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; IY - address of file descriptor
;-------------------------------------------------------------------------------
; Output:
; BC:DE - current file position in bytes
;-------------------------------------------------------------------------------

	ld	l,(iy + fdClusPos + 0)
	ld	h,(iy + fdClusPos + 1)

	ld	b,0
	ld	a,(ix + vdSecsPerClus)
PosLin	add	hl,hl
	rl	b
	rra
	jr	nc,PosLin

	ld	c,h
	ld	a,l
	or	(iy + fdBytePos + 1)
	ld	d,a
	ld	e,(iy + fdBytePos + 0)
	ret

;-------------------------------------------------------------------------------
FileSeek:
;-------------------------------------------------------------------------------
; Input:
; IY - address of file descriptor
; BC:DE - number of bytes to seek (can be also negative)
; A - seeking mode (0 - SEEK_SET, 1 - SEEK_CUR, 2 - SEEK_END)
;-------------------------------------------------------------------------------
; c = 0 - OK
; c - 1 - error (seek beyond end / seek before start of file)
;-------------------------------------------------------------------------------

	push	bc
	push	de

SeekSet	or	a
	jr	nz,SeekCur
	ld	b,a
	ld	c,a
	ld	d,a
	ld	e,a
	jr	SeekAdd

SeekCur	dec	a
	jr	nz,SeekEnd
	call	FilePosToLinear
	jr	SeekAdd

	; here we cheat a little, no explicit test for A = 2

SeekEnd	ld	e,(iy + fdSize + 0)
	ld	d,(iy + fdSize + 1)
	ld	c,(iy + fdSize + 2)
	ld	b,(iy + fdSize + 3)

SeekAdd	pop	hl
	add	hl,de
	ex	de,hl
	pop	hl
	adc	hl,bc
	ld	b,h
	ld	c,l

	; offset must be in interval <0, file size>
	; TODO: only for reading!

	ld	l,(iy + fdSize + 0)
	ld	h,(iy + fdSize + 1)
	or	a
	sbc	hl,de
	ld	l,(iy + fdSize + 2)
	ld	h,(iy + fdSize + 3)
	sbc	hl,bc
	ret	c			; invalid offset

	push	de			; DE contains byte offset in cluster

	; load volume descriptor address

	ld	a,(iy + fdVolume + 0)
	LD__XL_A
	ld	a,(iy + fdVolume + 1)
	LD__XH_A			; 54 T but undocumented

	ld	l,d
	ld	h,c
	ld	a,(ix + vdSecsPerClus)
SeekCls	srl	b
	rr	h
	rr	l
	rra
	jr	nc,SeekCls

	push	hl			; HL now contains linear cluster position

	; scan cluster chain from the current cluster if possible

	ld	e,(iy + fdClusPos + 0)
	ld	d,(iy + fdClusPos + 1)
	or	a
	sbc	hl,de
	jr	nc,SeekUse

	; we seek backwards, scan from the start of cluster chain

	add	hl,de
	ex	de,hl
	ld	l,(iy + fdStartClus + 0)
	ld	h,(iy + fdStartClus + 1)
	jr	SeekChn

SeekUse	ex	de,hl
	ld	l,(iy + fdClus + 0)
	ld	h,(iy + fdClus + 1)

	; scan file's cluster chain from the start

SeekChn	ld	(iy + fdPrevClus + 0),l
	ld	(iy + fdPrevClus + 1),h
	ld	a,d
	or	e
	jr	z,SeekFD
	push	de
	call	ReadFatEntry
	pop	de
	dec	de
	jr	nc,SeekChn

	pop	de
	pop	de
	ret

	; now that disk operations are complete, we may fill file descriptor

SeekFD	ld	(iy + fdClus + 0),l
	ld	(iy + fdClus + 1),h

	pop	hl
	ld	(iy + fdClusPos + 0),l
	ld	(iy + fdClusPos + 1),h

	pop	hl
	ld	a,(ix + vdSecsPerClus)
	add	a,a
	dec	a
	and	h
	ld	(iy + fdBytePos + 0),l
	ld	(iy + fdBytePos + 1),a
	ret

;-------------------------------------------------------------------------------
FatUnlink:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; HL - number of FAT entry (cluster)
;-------------------------------------------------------------------------------
; Output:
; c = 0 - OK
; c = 1 - read/write error
;-------------------------------------------------------------------------------
; Marks cluster chain starting from given cluster up to EOC as free.
;-------------------------------------------------------------------------------

	push	hl
	call	ReadFatEntry
	ex	de,hl
	pop	hl
	ret	c

	push	de
	ld	de,0
	call	WriteFatEntry
	pop	hl
	ret	c

	; increment the count of free clusters

	ld	e,(ix + vdFreeClus + 0)
	ld	d,(ix + vdFreeClus + 1)
	ld	c,(ix + vdFreeClus + 2)
	ld	b,(ix + vdFreeClus + 3)
	call	IncBCDE
	ld	(ix + vdFreeClus + 0),e
	ld	(ix + vdFreeClus + 1),d
	ld	(ix + vdFreeClus + 2),c
	ld	(ix + vdFreeClus + 3),b

	ld	a,l			; end of cluster chain?
	or	0Fh
	and	h
	inc	a
	jr	nz,FatUnlink
	ret

;-------------------------------------------------------------------------------
ReadFatEntry:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; HL - number of FAT entry (cluster)
;-------------------------------------------------------------------------------
; Output:
; c = 0, HL - number of next cluster
; c = 1 - read error
;-------------------------------------------------------------------------------

	; for FAT 16

	ld	e,(ix + vdFatLBA + 0)
	ld	d,(ix + vdFatLBA + 1)
	ld	c,(ix + vdFatLBA + 2)
	ld	b,(ix + vdFatLBA + 3)
	ld	a,h
	call	BCDEplusA

	push	hl
	call	ReadSectorCached
	pop	hl
	ret	c

	rra				; Carry = 0
	ld	h,a
	add	hl,hl
	ld	a,(hl)
	inc	l
	ld	h,(hl)
	ld	l,a
	ret

;-------------------------------------------------------------------------------
WriteFatEntry:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; HL - number of FAT entry (cluster)
; DE - value to be written
;-------------------------------------------------------------------------------
; Output:
; c = 0, HL - number of next cluster
; c = 1 - read error
;-------------------------------------------------------------------------------

	; TODO: iterate over all FATs

	push	de

	ld	e,(ix + vdFatLBA + 0)
	ld	d,(ix + vdFatLBA + 1)
	ld	c,(ix + vdFatLBA + 2)
	ld	b,(ix + vdFatLBA + 3)
	ld	a,h
	call	BCDEplusA

	push	hl
	call	ReadSectorCached
	pop	hl
	pop	de
	ret	c

	rra
	ld	h,a
	add	hl,hl
	ld	(hl),e
	inc	l
	ld	(hl),d

	; flush sector to disk immediately

	res	0,h
	xor	a
	ld	l,a
	inc	a
	call	WriteSectors
	ret

; Note: As INI/INIR from disc is as fast as LDI/LDIR from memory, there's
; little point in reading a sector to cache and then copying it to the
; destination address. Furthermore, today's drive caches are much bigger
; than Z80 address space so even repeated reads won't cause much seeking.
;
; Therefore, ReadSectorCached merely returns a pointer to the sector in cache
; and leaves the rest to the caller. This approach saves multiple reads of
; frequently used sectors (such as FAT or directories).

;-------------------------------------------------------------------------------
ReadSectorCached:
;-------------------------------------------------------------------------------
; Input:
; IX    - address of volume descriptor
; BC:DE - sector LBA
;-------------------------------------------------------------------------------
; Output:
; A     - higher byte of sector address (lower byte is always 0)
; c = 0 - OK
; c = 1 - Error
;-------------------------------------------------------------------------------

	; bypass cache (for debugging)

;	ld	hl,SectorCache
;	ld	a,1
;	call	ReadSectors
;	ld	a,SectorCache / 256
;	ret

	; extend LBA by device number

	ld	a,b
	or	(ix + vdDevice)
	ld	b,a

	ld	hl,CacheLBAs + 4 * MAX_CACHED_SECS - 1
CachCmp	ld	a,b
	cp	(hl)
	jr	nz,CachNxt
	dec	l
	ld	a,c
	cp	(hl)
	jr	nz,CachNxt
	dec	l
	ld	a,d
	cp	(hl)
	jr	nz,CachNxt
	dec	l
	ld	a,e
	cp	(hl)
	jr	z,CachFnd
CachNxt	ld	a,l
	or	3
	sub	4
	ld	l,a
	jr	nc,CachCmp

	; sector not found, find a "victim" to be replaced

	push	bc

	ld	l,MAX_CACHED_SECS * 4
	ld	a,(hl)
	ld	c,l
	inc	l
	ld	b,MAX_CACHED_SECS - 1
CachOld	cp	(hl)
	jr	nc,$+4
	ld	a,(hl)
	ld	c,l
	inc	l
	djnz	CachOld

	ld	l,c
	pop	bc
	push	bc
	push	de
	push	hl

	; read new sector into cache

	ld	a,l
	add	a,a
	add	a,>SectorCache - MAX_CACHED_SECS * 8

	ld	h,a
	xor	a
	ld	l,a
	inc	a
	call	ReadSectors
	pop	hl
	pop	de
	pop	bc
	ret	c

	; update newly read sector's LBA and timestamp

;	ld	(hl),a			; A = 0 after ReadSectors
	ld	(hl),0

	ld	a,l
	sub	MAX_CACHED_SECS * 4
	add	a,a
	add	a,a
	ld	l,a
	rra
	add	a,>SectorCache

	ld	(hl),e
	inc	l
	ld	(hl),d
	inc	l
	ld	(hl),c
	inc	l
	ld	(hl),b
	ret

	; sector found in cache

CachFnd	ld	a,l
	rra
	ld	e,a
	rra
	add	a,MAX_CACHED_SECS * 4
	ld	l,a

	ld	a,(hl)
	ld	(hl),0			; this is OK if initial stamps are non-zero
	ld	l,MAX_CACHED_SECS * 4
	ld	b,MAX_CACHED_SECS
CachInc	cp	(hl)
	jr	c,$+3
	inc	(hl)
	inc	l
	djnz	CachInc

	ld	a,e
	add	a,>SectorCache
	ret

;-------------------------------------------------------------------------------
InvalidateCache:
;-------------------------------------------------------------------------------

	; TODO: reverse order optimization?

	ld	hl,CacheLBAs
	xor	a
InvLBA	ld	(hl),a
	inc	l
	bit	5,l			; hardwired to 8 sector cache
	jr	z,InvLBA

	; just set some non-zero timestamps

InvStmp	ld	(hl),l
	inc	l
	bit	3,l			; hardwired to 8 sector cache
	jr	z,InvStmp
	ret

;-------------------------------------------------------------------------------
; UTILITY ROUTINES
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
WaitForKey:
;-------------------------------------------------------------------------------

	xor	a
	in	a,(254)
	cpl
	and	31
	jr	z,WaitForKey
	ret

;-------------------------------------------------------------------------------
TestForSpace:
;-------------------------------------------------------------------------------
; Output:
; c = 0 - SPACE key is pressed
; c = 1 - SPACE key is not pressed
;-------------------------------------------------------------------------------

	ld	a,127
	in	a,(254)
	rra
	ret

;-------------------------------------------------------------------------------
TestForEnter:
;-------------------------------------------------------------------------------
; Output:
; c = 0 - ENTER key is pressed
; c = 1 - ENTER key is not pressed
;-------------------------------------------------------------------------------

	ld	a,191
	in	a,(254)
	rra
	ret

;-------------------------------------------------------------------------------
PrintChar:
;-------------------------------------------------------------------------------
; Input:
; A  - character to print
;-------------------------------------------------------------------------------

	ld	b,1
	ld	de,(SysCursor)
	jr	AddChar

;-------------------------------------------------------------------------------
PrintLine:
;-------------------------------------------------------------------------------

	ld	b,32

;-------------------------------------------------------------------------------
PrintString:
;-------------------------------------------------------------------------------
; Input:
; B  - number of characters to print
; HL - location of the ASCIIz string
;-------------------------------------------------------------------------------
; Function prints an ASCIIz string at the current cursor position.
; The string is either padded or truncated to the requested length.
;-------------------------------------------------------------------------------

	ld	de,(SysCursor)

RdChar	ld	a,(hl)
AddChar	add	a,a
	jr	z,$f
	inc	hl
$$	push	hl

	ld	h,8
	ld	l,a
	add	hl,hl
	add	hl,hl
	push	de

	ld	a,82h			; switch to bank where font is stored
	di
	out	(DIV_CTRL),a

	ld	a,(hl)
	ld	(de),a
	inc	d
	inc	l
	ld	a,(hl)
	ld	(de),a
	inc	d
	inc	l
	ld	a,(hl)
	ld	(de),a
	inc	d
	inc	l
	ld	a,(hl)
	ld	(de),a
	inc	d
	inc	l
	ld	a,(hl)
	ld	(de),a
	inc	d
	inc	l
	ld	a,(hl)
	ld	(de),a
	inc	d
	inc	l
	ld	a,(hl)
	ld	(de),a
	inc	d
	inc	l
	ld	a,(hl)
	ld	(de),a

	ld	a,80h
	ei
	out	(DIV_CTRL),a

	; also write character's colour

	inc	d
	ld	a,d
	rrca
	rrca
	rrca
	add	a,79
	ld	d,a
	ld	a,(SysColour)
	ld	(de),a

	pop	de

	; move to the next character position (with line wrap and page wrap)

	inc	e
	jr	nz,$f
	ld	a,d
	add	a,8
	ld	d,a
	cp	88
	jr	c,$f
	ld	d,64

$$	pop	hl
	djnz	RdChar
;	inc	hl		; might be handy to point to the next string?

EndStr	ld	(SysCursor),de
	ret

;-------------------------------------------------------------------------------
LineFeed:
;-------------------------------------------------------------------------------

	ld	hl,(SysCursor)
	ld	a,l
	or	11111b
	inc	a
	ld	l,a
	jr	nz,EndFeed
	ld	a,h
	add	a,8
	ld	h,a
	cp	88
	jr	c,EndFeed
	ld	h,64
EndFeed	ld	(SysCursor),hl
	ret

;-------------------------------------------------------------------------------
SetCursorPosition:
;-------------------------------------------------------------------------------
; Input:
; B - X coordinate (0..31)
; C - Y coordinate (0..23)
;-------------------------------------------------------------------------------

;	ld	a,c
;	add	a,a
;	add	a,a
;	add	a,a
;	ld	h,22
;	add	hl,hl
;	add	hl,hl
;	ld	a,l
;	add	a,b
;	ld	l,a
;	ld	(SYS_ATRCUR),hl
;	ld	a,h
;	add	a,a
;	add	a,a
;	add	a,a
;	xor	1010000b
;	ld	h,a
;	ld	(SysCursor),hl
;	ret

;	ld	a,c
;	add	a,a
;	add	a,a
;	add	a,a
;	ld	h,22
;	ld	l,a
;	add	hl,hl
;	add	hl,hl
;	ld	a,h
;	add	a,a
;	add	a,a
;	add	a,a
;	ld	h,a
;	ld	(SysCursor),hl
;	ret

;-------------------------------------------------------------------------------
; divIDE FONT AND LOGO
;-------------------------------------------------------------------------------

	include	"font_rle.asm"
	include	"logo.asm"

TxtSign	db	"baze|||", 0		; signature to detect warm startup

;-------------------------------------------------------------------------------
FontInst:
;-------------------------------------------------------------------------------

;	call	1FFBh			; reset auto-mapper

;	push	de
;	ld	hl,15616
;	ld	b,3
;	ldir

	; copy characters 0..31 from divIDE ROM

;	ld	a,81h
;	out	(DIV_CTRL),a

;	ld	hl,Font
;	ld	d,20h
;	inc	b
;	ldir
;	pop	hl
;	ld	b,3
;	ldir

;	xor	a
;	out	(DIV_CTRL),a

;	jp	3D00h

FontInstSize	equ	$ - FontInst

;-------------------------------------------------------------------------------
DisplayLogo:
;-------------------------------------------------------------------------------

	; clear screen

	ld	hl,4000h
	ld	de,4001h
	ld	bc,6911
	ld	(hl),l
	ldir

	; unpack logo bitmap

	ld	hl,Logo
	ld	de,16384 + 40
	ld	b,55
Logo1	or	(hl)
	inc	hl
	jr	z,Logo4
	rra
	jr	c,$f
	inc	a
$$	inc	a
Logo2	ex	af,af'
	ld	a,(hl)
	ld	(de),a
	inc	hl
	call	DownDE
	djnz	Logo3
	ld	a,e
	add	a,65
	ld	e,a
	ld	d,64
	ld	b,55
Logo3	ex	af,af'
	dec	a
	jr	z,Logo1
	jr	c,Logo2
	dec	hl
	jr	Logo2

	; unpack logo colours

Logo4	ld	de,22528 + 47

;-------------------------------------------------------------------------------
UnpackRLE:
;-------------------------------------------------------------------------------

	or	(hl)
	inc	hl
	ret	z
	rra
	jr	c,$f
	inc	a
$$	inc	a
$$	ldi
	dec	a
	jr	z,UnpackRLE
	jr	c,$b
	dec	hl
	jr	$b

;-------------------------------------------------------------------------------
DownDE:
;-------------------------------------------------------------------------------

	inc	d
	ld	a,d
	and	7
	ret	nz
	ld	a,e
	add	a,32
	ld	e,a
	ret	c
	ld	a,d
	sub	8
	ld	d,a
	ret

;-------------------------------------------------------------------------------
; ARITHMETIC ROUTINES
;-------------------------------------------------------------------------------

IncBCDE:

	inc	e
	ret	nz
	inc	d
	ret	nz
	inc	bc
	ret

DecBCDE:

	ld	a,d
	or	e
	dec	de
	ret	nz
	dec	bc
	ret

BCDEplusA:

	add	a,e
	ld	e,a
	ret	nc
	inc	d
	ret	nz
	inc	bc
	ret

BCDEplusHL:

	add	hl,de
	ex	de,hl
	ret	nc
	inc	bc
	ret

BCDEplusAHL:

	add	hl,de
	ex	de,hl
	adc	a,c
	ld	c,a
	ret	nc
	inc	b
	ret

BCDEplusTemp1:

	ld	hl,(SysTemp1 + 0)
	add	hl,de
	ex	de,hl
	ld	hl,(SysTemp1 + 2)
	adc	hl,bc
	ld	b,h
	ld	c,l
	ret

BCDEplusTemp2:

	ld	hl,(SysTemp2 + 0)
	add	hl,de
	ex	de,hl
	ld	hl,(SysTemp2 + 2)
	adc	hl,bc
	ld	b,h
	ld	c,l
	ret

; setup for 24-bit * 8-bit multiply

Mul8x24	ld	b,0
	ld	l,b
	ld	ix,0
	ld	a,8

;-------------------------------------------------------------------------------
Multiply:
;-------------------------------------------------------------------------------
; Input:
; A     - number of bits of multiplier
; HL:IX - multiplier:0
; BC:DE - 0:multiplicand
;-------------------------------------------------------------------------------
; Output:
; HL:IX - product
;-------------------------------------------------------------------------------

	add	ix,ix
	adc	hl,hl
	jr	nc,$f
	add	ix,de
	adc	hl,bc
$$	dec	a
	jr	nz,Multiply
	ret

;-------------------------------------------------------------------------------
Divide:
;-------------------------------------------------------------------------------
; Input:
; DE:HL - dividend
; C     - divisor
;-------------------------------------------------------------------------------
; Output:
; DE:HL - quotient
; A     - remainder
;-------------------------------------------------------------------------------

	xor	a
	ld	b,32
Div1	add	hl,hl
	rl	e
	rl	d
	rla
	cp	c
	jr	c,Div2
	sub	c
	inc	l
Div2	djnz	Div1
	ret

;-------------------------------------------------------------------------------
StringCompare:
;-------------------------------------------------------------------------------
; Input:
; DE - address of string 1
; HL - address of string 2
;-------------------------------------------------------------------------------
; Output:
; z = 1        - string 1 == string 2
; z = 0, c = 1 - string 1 <= string 2
; z = 0, c = 0 - string 1 >= string 2
;-------------------------------------------------------------------------------

	ld	a,(de)
	inc	de
	or	a
	jr	z,StrCmp
	cp	(hl)
	inc	hl
	jr	z,StringCompare
	ret
StrCmp	cp	(hl)
	ret

;	ld	a,(de)
;	inc	de
;	cpi
;	ret	nz
;	ret	po
;	jr	StringCompare

;-------------------------------------------------------------------------------
StrCmpNoCase:
;-------------------------------------------------------------------------------
; DE - address of string 1
; HL - address of string 2 (this string must be already upper cased!)
;-------------------------------------------------------------------------------
; Output:
; z = 1        - string 1 == string 2
; z = 0, c = 1 - string 1 <= string 2
; z = 0, c = 0 - string 1 >= string 2
;-------------------------------------------------------------------------------

	ld	a,(de)
	or	a
	jr	z,$f
	inc	de
	call	UpperCase
	cp	(hl)
	inc	hl
	jr	z,StrCmpNoCase
	ret
$$	cp	(hl)
	ret

;-------------------------------------------------------------------------------
UpperCase:
;-------------------------------------------------------------------------------

	ld	c,a
	sub	'a'
	cp	26
	sbc	a,a
	and	20h
	xor	c
	ret

;-------------------------------------------------------------------------------
StrLength:
;-------------------------------------------------------------------------------
; Input:
; HL - string address (string length is limited to 256 including zero)
;-------------------------------------------------------------------------------
; Output:
; A - string length not including terminating zero
;-------------------------------------------------------------------------------

	push	hl
	xor	a
	ld	c,a
	cpir
	sub	c
	dec	a
	pop	hl
	ret

	; 16-bit length, includes trailing zero

;	ld	d,h
;	ld	e,l
;	xor	a
;	ld	b,a
;	ld	c,a
;	cpir
;	sbc	hl,de
;	ld	b,h
;	ld	c,l
;	ex	de,hl
;	ret

;-------------------------------------------------------------------------------
ConvertSlash:
;-------------------------------------------------------------------------------
; Input:
; HL - address of ASCIIZ string
;-------------------------------------------------------------------------------

	; for convenience we allow both '/' and '\' path delimiters

	dec	l
CnvSlsh	inc	l
	ld	a,(hl)
	or	a
	ret	z
	cp	'/'
	jr	nz,CnvSlsh
	ld	(hl),'\\'
	jr	CnvSlsh

;-------------------------------------------------------------------------------
CheckChar:
;-------------------------------------------------------------------------------
; Input:
; DE - address of ASCIIZ string
;-------------------------------------------------------------------------------
; Output:
; z - OK
; nz - there is illegal character in string
;-------------------------------------------------------------------------------

	ld	a,(de)
	cp	32
	jr	nc,ChkChar
	cp	5
	ret
ChkChar	ld	hl,IllegalCharsShort
	ld	bc,16
	cpir
	ret

;-------------------------------------------------------------------------------
FlashCode:
;-------------------------------------------------------------------------------
; This routine performs EEPROM reflashing. It must be relocable since it is
; copied to main memory if needed.
;-------------------------------------------------------------------------------

	di
	ld	hl,0
	ld	de,FlashImage

Flash	ld	a,(de)
	ld	(hl),a
	ld	b,7
$$	cp	(hl)
	jr	z,$f
	dec	bc
	inc	b
	djnz	$b

	; flash failed, signal error by continuous beep

ErrBeep	out	(254),a
	xor	16
	djnz	$
	jr	ErrBeep

$$	or	10101b
	out	(254),a
	inc	de
	inc	hl
	bit	5,h
	jr	z,Flash

	; flash successful, set black border and wait for reset

	xor	a
	out	(254),a
	jr	$

FlashCodeSize	equ	$ - FlashCode


ILLEGAL_CHARS_SHORT	equ	16
IllegalCharsShort:	db	"""*+,./:;<=>?[\\]|"

;-------------------------------------------------------------------------------
; TEXT MESSAGES
;-------------------------------------------------------------------------------

TxtCopyright	db	"divIDE ", 127, " Zilogator", 0
		db	"FATware 0.14a", 127, " 2005 baze", 0

TxtForceInit	db	"Hold SPACE to force init...", 0
TxtDetect	db	"Detecting ATA(PI) devices...", 0
TxtDev0		db	"0", 0					; REMOVE AND REPLACE BY PRINT CHARACTER
TxtDev1		db	"1", 0
TxtNone		db	"none", 0
TxtCHS		db	" CHS", 0
TxtLBA		db	" LBA", 0
TxtATAPI	db	" ATAPI", 0
TxtVolFound	db	" volume(s) found", 0
TxtAnyKey	db	"Press any key...", 0
TxtBootPath	db	"c:\\fatware\\boot.bin", 0
TxtBootOK	db	"Boot OK.", 0
TxtBootErr	db	"Loading error.", 0

TxtFlashPath	db	"c:\\flash.bin", 0
TxtFlashPrompt	db	"Press ENTER to flash new image  "
		db	"or any other key to skip...", 0
TxtFlashInfo	db	"Please remove the E/JP2 jumper  "
		db	"and press SPACE. When the border"
		db	"stops flashing, insert E/JP2    "
		db	"back and reset the computer.    "
		db	"Continuous beep signals error.", 0

WrTest	call	AllocFileDesc

	call	StoreVolume
	ld	(iy + fdMode),2

	ld	hl,6912
	ld	(iy + fdSize + 0),l
	ld	(iy + fdSize + 1),h
	ld	(iy + fdSize + 2),0
	ld	(iy + fdSize + 3),0

	ld	hl,3258
	ld	(iy + fdClus + 0),l
	ld	(iy + fdClus + 1),h
	ld	(iy + fdStartClus + 0),l
	ld	(iy + fdStartClus + 1),h

	xor	a
	ld	(iy + fdBytePos + 0),a
	ld	(iy + fdBytePos + 1),a
	ld	(iy + fdClusPos + 0),a
	ld	(iy + fdClusPos + 1),a

	ld	(iy + fdWritePos + 0),a
	ld	(iy + fdWritePos + 1),a
	ld	(iy + fdWritePos + 2),a
	ld	(iy + fdWritePos + 3),a

	; test write

	ld	hl,Lala
	ld	de,2
	call	FileWrite

	ld	hl,Lala2
	ld	de,2
	call	FileWrite

	call	FileClose

	ret

Lala	dw	55AAh
Lala2	dw	0055h

;-------------------------------------------------------------------------------
GetFreeCluster:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; HL - cluster to start search from
;-------------------------------------------------------------------------------
; Output:
; c = 0, HL - first free cluster
; c = 1 - read error
;-------------------------------------------------------------------------------

	; determine which sector of FAT to load
	; TODO: optimize so that search can start somewhere in the middle

	ld	e,(ix + vdFatLBA + 0)
	ld	d,(ix + vdFatLBA + 1)
	ld	c,(ix + vdFatLBA + 2)
	ld	b,(ix + vdFatLBA + 3)
	ld	a,h
	call	BCDEplusA

	ld	a,(ix + vdFatSize)
	sub	h
FcLoop	push	af

	push	bc
	push	de
	call	ReadSectorCached
	pop	de
	pop	bc
;	jr	c,Err

	ld	h,a
	ld	l,0
	call	FcScan
	jr	nc,FcFound
	inc	h
	call	FcScan
	jr	nc,FcFound

	call	IncBCDE
	pop	af
	dec	a
	jr	nz,FcLoop

FcFull	ld	a,ERR_VOLUME_FULL
	ld	(SysError),a
	scf
	ret

	; calculate cluster number from the loop counter and sector pointer

FcFound	ld	a,(ix + vdFatSize)
	pop	bc
	sub	b
	rr	h
	rr	l
	ld	h,a

	; check if the zero we found is not just dummy value after FAT

	ld	c,(ix + vdDataClus + 0)
	ld	b,(ix + vdDataClus + 1)
	inc	bc
	inc	bc
	or	a
	sbc	hl,bc
	jr	nc,FcFull
	add	hl,bc
	or	a
	ret

FcScan	ld	a,(hl)
	inc	l
	or	(hl)
	ret	z
	inc	l
	jr	nz,FcScan
	scf				; signal no zero was found
	ret

;-------------------------------------------------------------------------------
GetFreeClusterCount:
;-------------------------------------------------------------------------------
; Input:
; IX - address of volume descriptor
; HL - first free cluster to start search from
;-------------------------------------------------------------------------------
; Output:
; c = 0, IY - free cluster count
; c = 1 - read error
;-------------------------------------------------------------------------------

	; determine which sector of FAT to load
	; TODO: optimize so that count can start soewhere from the middle

	ld	iy,0

	ld	e,(ix + vdFatLBA + 0)
	ld	d,(ix + vdFatLBA + 1)
	ld	c,(ix + vdFatLBA + 2)
	ld	b,(ix + vdFatLBA + 3)
	ld	a,h			; only true for FAT16!
	call	BCDEplusA

	ld	a,(ix + vdFatSize)
	sub	h
	dec	a
	jr	z,FccLast

$$	push	af
	push	bc
	push	de
	call	ReadSectorCached
;	jr	c,Err

	ld	h,a
	ld	b,0
	call	FccCnt

	pop	de
	pop	bc
	pop	af
	call	IncBCDE
	dec	a
	jr	nz,$b

	; process last sector separately as it will probably contain dummy area

FccLast	call	ReadSectorCached
;	jr	c,Err

	ld	h,a
	ld	a,(ix + vdDataClus)
	add	a,2
	ld	b,a
	call	FccCnt
	ret

FccCnt	ld	de,0
	ld	l,e
$$	ld	a,(hl)
	inc	l
	or	(hl)
	inc	hl
	jr	nz,$ + 3
	inc	de
	djnz	$b
	add	iy,de
	ret



	org	1FE1h			; calculated to fill up space at end

;-------------------------------------------------------------------------------
CallBank:
;-------------------------------------------------------------------------------
; This routine doesn't set the MAPRAM bit explicitly because it is used to
; reset CONMEM and exit EEPROM even in cases when bank 3 content is not valid.
;-------------------------------------------------------------------------------

	ld	e,(hl)
	inc	hl	
	ld	d,(hl)
	inc	hl
	ex	(sp),hl
	call	PrepDE

PageOut	push	af
	ld	a,0			; 80h in the MAPRAM bank 3
PageMap	out	(DIV_CTRL),a
	pop	af
	ret

PrepDE	push	de
	ld	de,(SysTemp1)
	jr	PageOut

;-------------------------------------------------------------------------------
; ROM EXIT POINTS
;-------------------------------------------------------------------------------

	org	1FF7h

OutEI	ei
OutRET	ret
OutJPHL	jp	(hl)

	org	1FFEh

RomSign	db	0, 14			; ROM signature

; sector buffers used during initialisation
; (TODO: place them in main memory to preserve cache on reset?)

IdSector	equ	2000h
MbrSector	equ	2200h
BootSector	equ	2400h

; sector buffers used at runtime

SectorCache	equ	2000h		; sector cache (currently 8 sectors)
VolDesc		equ	3000h		; 256 bytes of volume descriptors
FileDesc	equ	3100h		; 256 bytes of file descriptors
FileName	equ	3200h		; 256 bytes reserved for filename
PathBuffer	equ	3300h		; 256 bytes reserved for complete path
SysVars		equ	3400h

CacheLBAs	equ	SysVars
CacheStamps	equ	CacheLBAs + 4 * MAX_CACHED_SECS
SysVarsCont	equ	CacheStamps + MAX_CACHED_SECS

		org	SysVarsCont

SysAY		dw	0, 0, 0, 0, 0, 0, 0, 0
SysNumber	dw	0, 0, 0, 0, 0	; can be stuffed into IM2 or stack area
SysError	db	0
Sys48K		db	0
SysErrorSP	dw	0
SysTimeOut	db	0

SysNumVolumes	db	0
SysCurrVolume	dw	0
SysBlockAddr	dw	0
SysBlockLng	dw	0
SysBlockSec	dw	0

SysCursor	dw	0		; current cursor position in screen
SysColour	db	0		; current colour
SysTemp1	dw	0, 0
SysTemp2	dw	0, 0
SysSign		dw	0		; signature to detect warm startup
SysBootOK	db	0

; cache state variables must be 256-byte aligned

DirEntry	equ	3F00h

; Area 3F20h..3FFFh is reserved for FATware stack.

FlashImage	equ	6000h

