; Note: There needs to be some code addded so there is a return header
;  at the start of the binary segments!

RootSize         dw 0       ; Root Directory Size
RootStart        dw 0       ; Root Directory Start Location
DataStart        dw 0       ; Data Directory Start Location
FATsize          dw 0       ; FAT system size
FATstart         dw 0       ; FAT system start
RSDrive          db 0                 ; ReadSectors procedure drive variable.
FileAddress      dw 0       ; FAT address of file
FileAddressLBA   dw 0       ; LBA address of file
FileName         db '           '   ; Name of file (8 digits + 3 digit extension = 11)
FileNameTemplate db '           '   ; File name variable template (Used to reset the variable before each use)
TestName         db 'TEST.COM',0
Searcherr        db 'Unable to find file',13,10,0
BadClustererr    db 'Bad cluster',13,10,0
EmptyClustererr  db 'Empty cluster',13,10,0
FAT12ROOT_ADDR   dw 0
FAT12_ADDR       dw 0
FTEMP            dw 0

OEM_ID            db 'NXDOS8.0'      ; 8 char sys ID
BytesPerSector    dw 512             ; Sector size in bytes
SectorsPerCluster db 1               ; Sector per cluster
ReservedSectors   dw 4               ; Reserved sectors (This I think for this should become 2 for the boot strap, was 1)
TotalFATs         db 2               ; Number of fats
MaxRootEntries    dw 224             ; Root directory entries
TotalSectorsSmall dw 2880            ; Total Sectors
MediaDescriptor   db 0F0h            ; Format ID (FAT12 ID number)
SectorsPerFAT     dw 9               ; Sectors per FAT
SectorsPerTrack   dw 18              ; Sectors per track
NumHeads          dw 2               ; Number of heads (2 as double sided floppy)
HiddenSectors     dd 0               ; Special hidden sectors
TotalSectorsLarge dd 0               ; More sectors
DriveNumber       db 0               ; Drive Number (Primary Floppy is normally 0)
Flags             db 0               ; Reserved
Signature         db 41              ; Boot signature
VolumeID          dd 435101793       ; Volume serial number
VolumeLabel       db 'NO NAME    '   ; Volume label (11 bytes)
SystemID          db 'FAT12   '      ; File system (8 bytes)


; Procedure to change LBA address system into CHS address system
;  Description: Gets absolute values from logical values. (Logical = Consecutive sectors; Absolute = Sector:Track:Head)
;  Calculations done:
;   absolute sector = (LBA mod SPT) + 1
;   absolute head = (LBA / SPT) MOD Heads
;   absolute cylinder = (LBA / SPT) / Heads
;  Input: AX - LBA value (And the FAT table of course)
;  Output: AX - Sector; BX - Head; CX - Cylinder

LBAtoCHS proc far
 PUSH dx			; Save the value in dx
 XOR dx,dx			; Zero dx
 MOV bx, [SectorsPerTrack]	; Move into place STP (LBA all ready in place)
 DIV bx				; Make the divide (ax/bx -> ax,dx)
 inc dx				; Add one to the remainder (sector value)
 push dx			; Save the sector value on the stack

 XOR dx,dx			; Zero dx
 MOV bx, [NumHeads]		; Move NumHeads into place (NumTracks all ready in place)
 DIV bx				; Make the divide (ax/bx -> ax,dx)

 MOV cx,ax			; Move ax to cx (Cylinder)
 MOV bx,dx			; Move dx to bx (Head)
 POP ax				; Take the last value entered on the stack off.
 POP dx				; Restore dx, in case something was there to start with
 RET				; Return to the main function
LBAtoCHS endp

; FATtoLBA - Procedure to translate a FAT cluster address into an LBA address.
; LBA = (Cluster - 2) * sectors per cluster	| Also you have to add to this the datastart location
; Input: AX - FAT cluster ; FAT table (SectorsPerCluster) And data sector address
; Output: AX - LBA address

FATtoLBA proc far
        sub ax, 0002h                          ; Subtract 2 from ax (Not sure why yet)
	xor cx, cx				; Zero CX
	mov cl, BYTE [SectorsPerCluster]	; Move SPC to cl
	mul cx					; Multiply AX by CX (FAT*SectorsPerCluster)
	add ax, WORD [DataStart]		; Base data sector
	ret					; Return
FATtoLBA endp

FAT12_CALC proc far
; Procedure which recalculates the root directory size, location and data location.
;  Input: FAT table variables
;  Output: [RootSize], [RootStart], [DataStart]
FAT12LocCalcs:
	; Root Dir Size:
	; ([MaxRootEntries] * 32 bytes per entry) / [BytesPerSector]
	xor ax, ax			; Zero registers
	xor cx, cx
	mov ax,[MaxRootEntries] 	; Move value to register to work on. ax=Arithmatic
	mov cx, 32      		; Move value to multiply into register
	mul cx          		; Multiply
	div WORD [BytesPerSector]    	; Divide
	mov [RootSize], ax      	; Put the value into a nice storage area for a bit


	; Root Dir Start Location
	; RootStart = Reserved sectors + fat sectors
	; So RootStart = [ReservedSectors] + ([TotalFATs]*[SectorsPerFAT])
	xor ax,ax			; Zero AX for next calculation
	mov al, BYTE [TotalFATs]        ; Load up number of FAT tables (/info) into AL
	mul WORD [SectorsPerFAT]        ; Multiply AL by the number of sectors per FAT table (/info)
	add ax, WORD [ReservedSectors]  ; Add to the FAT total (AX) the number of reserved sectors
	mov [RootStart], ax             ; Put the start of the root address into RootStart variable


	; Now add to the total the root directory so giving the start of the data
	; DataStart = Reserved Sectors + FAT sectors + Root size
	mov cx,[RootSize]               ; Mov the root size into CX
	add ax, cx			; Add ax (RootStart) to cx (RootSize)
	mov [DataStart], ax             ; Move the answer into DataStart
      ret
FAT12_CALC endp

FAT12_CALC2 proc far
; Works out the start and size of the FAT system.	
;  Input - FAT table variables
;  Output - [FATsize], [FATstart]
FATCalcs:
	; Work out size of FAT
	; [TotalFATs] * [SectorsPerFAT] = [FATsize]
	xor ax,ax		; Zero AX
	mov al, BYTE [TotalFATs]; Move TotalFAT's into position
	mul WORD [SectorsPerFAT]; Multiply by SectorsPerFAT
	mov WORD [FATsize], ax	; Move into memory variable

	; Work out start of FAT
	; [ReservedSectors] = [FATstart]
	xor ax,ax			; Zero AX
	mov ax, WORD [ReservedSectors]	; Move ReservedSectors into ax (This is the FATstart location)
	mov [FATstart], ax		; Move to variable
        ret
FAT12_CALC2 endp

NextCluster proc far
; Next cluster procedure
; * (Multiply by 3 * Divide result by 2) or get 1.5 by (Divide by 2 * Add 1) * Read WORD at resulting address * Even keep low 12 bytes * Odd shift right by 4 bits
; Input: AX - CurrentCluster
; Output: AX - NextCluster
        mov  cx, ax                      ; Copy current cluster
        mov  dx, ax                      ; Ditto again
        shr  dx, 0001h                  ; Divide dx by 2
        add  cx, dx                      ; CX = 1.5
        mov  bx, FAT12_ADDR  
        add  bx, cx                      ; Add the calculated offset to the FAT location (Index into FAT) bx = FAT+calculated offset
        mov  dx, WORD [bx]               ; Read two bytes from FAT (a word)
	; Odd even test
        test ax, 0001h                 ; Test to see if the cluster was odd or even (Seems to be the old cluster rather than the just calculated value!)
        jnz  OddCluster                 ; If not a zero ending cluster:
EvenCluster:
        and  dx, 0fffh          ; Mask out the top 4 bits (0000111111111111b)
        jmp  Done               ; Carry on to next section
OddCluster:  
        shr  dx, 0004h          ; So shift right by 4 bits. (1111111111110000b -> 0000111111111111b)
Done:
        mov  ax, dx              ; Move result to ax
        ret                     ; Return
NextCluster endp

df_init proc far
     call FAT12_CALC                   ; Work out the root directory location
     call FAT12_CALC2                  ; Load the root directory
     mov  ax, cs:[RootSize]
     mov  bx, 16
     xor  dx, dx
     div  bx
     inc  ax
     mov  cx, ax
     ; mov  cx, ((FAT12_ROOT_SIZE / PARAGRAPH)+1)
     call AllocMemBlock
     jnc  fatok
faterror:
          _panic
          ret
fatok:
     mov  ax, es
     mov  cs:[FAT12ROOT_ADDR], ax

     mov  ax, cs:[FATSize]
     mov  bx, 16
     xor  dx, dx
     div  bx
     inc  ax
     mov  cx, ax
;     mov  cx, ((FAT12_SIZE / PARAGRAPH)+1)
     call AllocMemBlock
     jc   faterror
     mov  ax, es
     mov  cs:[FAT12_ADDR], ax
     ;
     ; Procedure to read the root directory into memory
LoadRoot:
     mov  bx, FAT12ROOT_ADDR
     mov  cx, cs:[RootSize]
     mov  dx, cs:[RootStart]
     mov  al, [DriveNumber]
     int  25h                  
     ; Procedure to load the FAT into memory.
LoadFAT:
     mov al, cs:[DriveNumber]   ; Drive to load from
     mov dx, cs:[FATstart]      ; Start of FAT table
     mov cx, cs:[FATsize]       ; FAT table size
     mov bx, FAT12_ADDR
     int 25h   

TxtConListFiles:
	call FileList		; List the files
     ret
df_init endp

df_deinit proc far
     mov  ax, FAT12ROOT_ADDR
     call Free_Block
     mov  ax, FAT12_ADDR
     call Free_Block
     ret
df_deinit endp

FileList proc far
; Procedure to list the files in the root directory
; Will I need to compare the root table with the FAT table as a filter? (Will add extra time to complete if so)
	; Browse the root directory for the binary image
	; Loop to start of each entry to check the name
	; Input: FAT table (And some immediate values)
	; Output: Print output to text console (Eventually a version will be made which returns the values in a memory location or variable)
	; Preset values:
	; - Initial values
	mov cx, WORD [MaxRootEntries]	; Load the loop counter
        mov di, 1000h                  ; First root entry (Offset)

	; - List loop
ListLoop:
	push cx				; Save entry counter value
					; -Filter out non alphabet characters!
        cmp byte ptr [di], 41h             ; The characters below Cap Letters in the Table
        jb FilteredEntry               ; Jump to filtered entry. (Not a viable character)
        cmp byte ptr [di], 5Ah             ; The characters above Cap Letters in Table.
        ja FilteredEntry               ; Jump if above to filtered entry.
					; Finalise this code later so that the filter keeps in other viable filename characters.
					; At later date set up extra filter code here which cross references the entries with the FAT table data.

        mov cx, 000Bh                  ; Eleven character name (Num of times for rep to repeat) - Name length counter!
	; -*- The actual name displaying/processing code.
        NameLoop:                       ; Add the characters to a memory variable.(Or screen or something)

	; Display the data
        mov al, [DI]			; Move the ASCII code to correct register
        call teletype

	inc di				; Move to next character
        loop NameLoop                  ; Loop 11 times (CX value)
        _newline

	; Setup ready for next loop
        sub di,0bh                      ; Subtract the amount added by file name pointer. (Want to combine with below instruction at some point!)
        add di,0020h                   ; Add 32 to value in DI to move to next entry
	pop cx				; Restore the root entry counter value
        loop ListLoop                  ; Loop round to next entry
	ret				; End procedure

	; -*- Process of filtered entries (aka discarding them)
FilteredEntry:
        add di, 0020h                  ; Add 32 to the value in DI (Next root entry block start)
	pop cx				; Restore cx. (Number of entries)
        loop ListLoop                  ; Loop round too next entry
	ret				; End procedure

	; -*- End of the list command section - Currently non used, will be used to return list in variable or similar.
ListEnd:    
        ret 
FileList endp

FileSearch proc far
; Procedure to search the root directory for a file,
;  once located it should return the files address.
	; Browse the root directory for the binary image
	; Loop to start of each entry to check the name
	; Input: FAT table (And some immediate values)
	; Output: DI - Contains the address of the kernel image (Root Entry Wise)
	; Preset values:
	mov cx, WORD [MaxRootEntries]	; Load the loop counter
        mov di, 1000h                  ; First root entry (Offset)
	.SearchLoop:
	push cx				; Save counter value
        mov cx, 000Bh                  ; Eleven character name (Num of times for rep to repeat)
        mov si, offset FileName                ; Kernel image to find (Load into SI the string to compare to DI)
	push di				; Save DI (Modified by cmpsb command)
	rep cmpsb			; Repeat internally the compare string block instruction (DS:SI to ES:DI) CX times
	pop di				; Restore DI
	pop cx				; Restore the counter value
        je FoundFile                   ; If equal jump to load kernel.
        add di, 0020h                  ; Add 32 to the value in DI (Next FAT block start)
        loop SearchLoop                ; Loop dec's cx by one and jmps, unless cx == 0 then it stops looping.

        jmp SearchError                ; Jump to the search error message

	; Error message and quit section
SearchError:
        _print Searcherr, 0  ; Move the message address

	xor dx,dx		; Error code

	ret			; Return

FoundFile:
	; Save the Files address in the FAT table
        mov     dx, WORD [di + 001Ah]
        mov     WORD [FileAddress], dx	; file’s first cluster

	xor dx,dx		; Error/Success code
        mov dx, 01h

	ret				; Return
FileSearch endp

SearchFile proc far
	call FileSearch		; Search for the file
        xor  bx, bx               ; Return code
        cmp  dx, bx               ; <- Failed
        je   SearchEnd
        mov  dx, 01h             ; <- Worked
SearchEnd:
	ret			; Return
SearchFile endp

LoadFile proc far
; Procedure to load the file into memory
;  Input: AX-Start segment, CX-Start offset
;	  [FileAddress], FAT table variables

	; The output location: (AX:CX -> ES:BX)
	push es		; Save es
	mov bx, ax	; Destination location
	mov es, bx	; Segment
	mov bx, cx	; Offset
	push bx		; Save bx

LoadFileImage:
	; !*! FAT -> LBA conversion
	xor ax,ax
        mov     ax, [FileAddress]		; cluster to read
	pop bx

        call FATtoLBA                        ; convert cluster to LBA

        mov [FileAddressLBA], ax

	; !*! Move to correct int registers:
	mov ax, [FileAddressLBA]
	mov 	dx, ax				; Start location

        xor     cx, cx
        mov     cl, BYTE [SectorsPerCluster]	; sectors to read

	xor 	ax,ax
	mov	al, [DriveNumber]		; Drive number (from FAT table)

        ;pop     bx				; buffer to read into

        int 25h                                ; Read sectors interrupt

	xor ax,ax
	mov al, BYTE [SectorsPerCluster]
	mov cx, [BytesPerSector]
	mul cx
	add bx, ax

        push    bx

     	; !*! compute next cluster
	xor ax,ax
	mov ax, WORD [FileAddress]	; Current Location
	call NextCluster		; Work out the next cluster
	mov WORD [FileAddress], ax	; The new cluster value is stored in the variable.

	; !*! Test to see what value the next cluster contains.
	cmp ax,0000h		; Free cluster (Empty)
        je EmptyError          ; Error message
	cmp ax,0ff7h		; Is it a bad cluster?
        je BadClusterError     ; Error message

	; !*! Test to see if this is the end of the cluster chain:
        cmp ax,0fffh           ; End of chain? (0fff)
        je FileLoaded          ; Jump to end of procedure. (Finished)

        jmp LoadFileImage      ; Loop back round to start
BadClusterError:
        _print BadClustererr, 0
        pop bx                          ; Empty stack
        pop es
        xor dx,dx                       ; Error code (0)
        ret                             ; Return
EmptyError:
        _print EmptyClustererr, 0
        pop bx                          ; Empty stack
        pop es
        xor dx,dx                       ; Error code (0)
        ret                             ; Return
FileLoaded:
        pop bx                          ; Empty stack
        pop es
        xor dx, dx                       ; Error code (1)
        mov dx, 01h
       ret
LoadFile endp

MakePSP proc far
; Load the return stuff to the start of the segment.
;  (allows the applications to be ended with a ret if small enough)
        ; AX = segment start
	pusha					; Save segment register
	push es					; Save all general registers
        xor  cx, cx                               ; Zero cx
        mov  cx, WORD ptr [HeaderStuff]              ; Move instructions into a register
        mov  es, ax                               ; Setup data segment
        xor  bx, bx                               ; Setup the offset location
        mov  es:[bx], cx                         ; Move to memory
        pop  es                                  ; Restore all general registers
	popa					; Restore segment register
	ret					; Return
HeaderStuff:
        int 20                                  ; Program terminate function
MakePSP endp

RunFile proc far
	call FileSearch		; Search for file address
        xor  bx, bx               ; Set compare values
        cmp  dx, bx
        je   RunEnd              ; Check error and jump if needed
	; Found
        ; calc FATSIZE and location / loadfat
        mov  cx, ((65534 / PARAGRAPH)+1)
        call AllocMemBlock
        jc   runend
             mov  ax, es
             mov  cs:[FTEMP], ax
             mov  cx, 0100h
             ; 5000:0100h 
             call MakePSP         ; Setup return stuff
             call LoadFile           ; Load file
             xor  bx, bx               ; Set compare values
             cmp  dx, bx
             je   RunEnd              ; Check error and jump if needed
	; Found
        mov ax, cs:[FTEMP]
        mov cx, 0100h
        mov   ds, ax
        mov   es, ax
	; Value below is required in order to allow a program to 'ret' back to the OS using code at the start of the .com file.
        xor   bx, bx               ; Zero BX
        push  bx                   ; Push return offset location
        push  ax                   ; Push new segment location
        push  cx                   ; Push new offset location
        xor   ax, ax               ; Zero all the general registers
        xor   cx, cx       ; <- In MS-DOS not zero
        xor   dx, dx
        xor   di, di
        xor   si, si
        xor   bp, bp       ; <- Unsure of value in MS-DOS
        retf               ; Far return (takes CS and IP from the stack)
RunEnd:
        ret
RunFile endp

