Disk: Drives, FAT16 | Index

Write Your Own OS from Scratch - Chapter 4. Disk

Disk: CD (Change Directory)

Disk

In disk.inc we will add the following structure.

struc DIRECTORY
    .name            resb        8
    .extension       resb        3
    .attribute       resb        1
    .reserved        resb        10
    .time            resw        1
    .date            resw        1
    .startcluster    resw        1
    .filesize        resd        1
endstruc
		

disk.inc

;=======================================================
; Disk - disk.inc
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================
struc BSDATA
    .jump            resb        3
    .oemname         resb        8
    .bytespersec     resw        1
    .secperclust     resb        1
    .ressectors      resw        1
    .fats            resb        1
    .rootdirents     resw        1
    .sectors         resw        1
    .media           resb        1
    .fatsecs         resw        1
    .secpertrack     resw        1
    .heads           resw        1
    .hiddensecs      resd        1
    .hugesectors     resd        1
    .drivenumber     resb        1
    .reserved1       resb        1
    .bootsignature   resb        1
    .volumeid        resd        1
    .volumelabel     resb        11
    .filesystype     resb        8
endstruc

struc PARTITION
    .status          resb        1
    .starthead       resb        1
    .startsecenc     resb        1
    .startcyl        resb        1
    .type            resb        1
    .endhead         resb        1
    .endsecenc       resb        1
    .endcyl          resb        1
    .startlba        resd        1
    .endlba          resd        1
endstruc

struc DIRECTORY
    .name            resb        8
    .extension       resb        3
    .attribute       resb        1
    .reserved        resb        10
    .time            resw        1
    .date            resw        1
    .startcluster    resw        1
    .filesize        resd        1
endstruc

DSKPART1             EQU         001BEh

		

We will start off by adding code that can read the offset of a file.

;-------------------------------------------------------
; Read offset sector
; Input: offset         AX   (5000)
; Input: startclust     CX
; Input: load location  ES:BX
; bytesperclust  =  secperclust * bytespersec   (2048)
; cluster = offset / bytesperclust   (2)
; clustoff =  offset % bytespercluster   (904)
; secinclust = clustoff / bytespersector = 1
; cluststartsec = walkthechan(startclust, cluster);
; LBA = (cluststartsec * secperclust + data) + secinclust
; calcchs
; readsec
;-------------------------------------------------------
dskreadoffsetsec:
    push   cx                                           ; Save startclust
    push   ax                                           ; Save offset
    mov    ax, [secperclust]
    mul    word [bytespersec]                           ; ax = bytesperclust
    mov    cx, ax                                       ; cx = bytesperclust
    xor    dx, dx
    pop    ax                                           ; restore offset
    div    cx                                           ; ax = cluster, dx = clustoff
    xchg   ax, dx                                       ; ax = clustoff, dx = cluster
    push   dx                                           ; save cluster
    xor    dx, dx
    div    word [bytespersec]                           ; ax = secinclust
    mov    dx, ax                                       ; dx = secinclust
    pop    cx                                           ; cx = cluster, restore cluster
    pop    ax                                           ; ax = startclust restore startclust
    push   dx
dskreadoffsetsecnextcl:                                 ; ax = cluststartsec
    mov    dx, ax                                       ; save our cluster
    or     cx, cx                                       ; check if our cluster is zero
    jz     dskreadoffsetsecthissec                      ;   if so this is our sector
    dec    cx                                           ; Next cluster
    call   dsknextcl                                 
    cmp    eax, 0FFFFFFFFh                              ; Check if we reached the end
    jnz    dskreadoffsetsecnextcl                       ;   If not next cluster
    or     cx, cx                                       ; Is our offset higher than end
    jnz    dskreadoffsetsecfail                         ;   If so fail
dskreadoffsetsecthissec:
    mov    ax, dx                                       ; restore our cluster
    dec    ax                                           ; adjust cluster
    dec    ax
    xor    dx, dx                                       ; Zero for multiply
    push   bx                                           ; save scratch address
    xor    bh, bh                                       ; Word multiply with byte
    mov    bl, [secperclust]
    mul    bx                                           ; multiply cluster by secperclust
    pop    bx
    add    ax, word [data]                              ; add data area
    pop    dx
    add    ax, dx                                       ; add secinclust
    xor    dx, dx
    call   dskcalcchs                                   ; Calculate chs
    call   dskreadsec                                   ; Read sector
    ret                                                 ; Return from subroutine
dskreadoffsetsecfail:
    pop    dx                                           ; Clear extra push
    stc                                                 ; Set carry for error
    ret                                                 ; Return from subroutine
		

Then we will change build match string do to an issue with the . and .. of change directory.

;-------------------------------------------------------
; Build match string
; Input:
;       es:di = Buffer
; Output:
;       matchname = Match Name
; fix this so first part is always spaces and last part is ???
;-------------------------------------------------------
dskbuildmatch:
	mov	cx, 11                                  ; 11 Characters
	mov	si, matchname                           ; Point to match string
dskbuildmatchnext:
	mov	al, [es:di]                             ; Get character from input buffer
	inc	di
	cmp	al, 0x0D                                ; If enter fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, 0x00                                ; If enter fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, ' '                                 ; If space fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, '.'                                 ; If . fill name portion only
	jz	dskbuildmatchfill8
	call	toup
	mov	[cs:si], al                             ; Otherwise write character to string
	inc	si
	loop	dskbuildmatchnext                       ; Get next character
        ret                                             ; Return from subroutine
dskbuildmatchfill:
	mov	al, '?'                                 ; Fill with question marks
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfill
	ret
dskbuildmatchfill8:
        cmp     cx, 11
        jz      dskbuildmatchspecial
	sub	cx, 0x03                                ; Adjust to just 8 characters
dskbuildmatchfillloop:
	mov	al, ' '                                 ; Fill with spaces
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfillloop
	add	cx, 0x03                                ; Restore 11 characters
	jmp     dskbuildmatchnext                       ; Continue match for extension
dskbuildmatchspecial:
        dec     di
dskbuildmatchspecialnext:
	mov	al, [es:di]                             ; Get character from input buffer
	inc	di
	cmp	al, 0x0D                                ; If enter fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, 0x00                                ; If enter fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, ' '                                 ; If space fill the rest of string
	jz	dskbuildmatchfill
	mov	[cs:si], al                             ; Otherwise write character to string
	inc	si
	loop	dskbuildmatchspecialnext
dskbuildmatchspecialfill:
	mov	al, ' '                                 ; Fill with question marks
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfill
	ret
		

Then we add the following code to dsknextentry.

    mov    bx, 00500h                                  ; ES:BX = Scratch area
    mov    ax, [diroffset]                             ; AX = Offset
    mov    cx, [dircurrent]                            ; CX = Start cluster
    call   dskreadoffsetsec                            ; Read Offset Sec
    jc     dsknextentryerror
    mov    ax, [diroffset]                             ; sector offset = offset % 512
    div    word [bytespersec]
    mov    bx, dx
    add    bx, 00500h                                  ; adjust offset to scratch area
    cmp    [es:bx], byte 0                             ; check if entry is blank
    jz     dsknextentryerror                           ;   if so we reached the end
    cmp    [es:bx], byte 0E5h                          ; check if entry is deleted
    jz     dsknextentryfilenoshow                      ;   if so we reached the end
    cmp    [es:bx+11], byte 00Fh                       ; check if entry is Extended name
    jz     dsknextentryfilenoshow 
    mov    di, bx                                      ; Compare name
    call   dskcmpmatch
    jnc    dsknextentryexit                            ; If no carry found our directory
dsknextentryfilenoshow:
    add    [diroffset], word 32                        ; point to next dir entry
    jmp    dsknextentryfile                            ; Find next match
		

Then we will add the code to change directory.

; carry can only be returned by setting the flag in the stack
struc intcallparams
    .pbp      resw 1
    .pcall    resw 1
    .poffset  resw 1
    .psegment resw 1
    .flags    resw 1
endstruc
;-------------------------------------------------------
; Change Directory
; SERVICE 3BH
;-------------------------------------------------------
dskchangedir:
    push   bp                                           ; Get SP
    mov    bp, sp
    push   ax                                           ; Save registers
    push   ds
    push   es
    push   ds                                           ; Set ES:DI = DS:DX
    pop    es
    mov    di, dx                                       ; DI Points to string
    call   dskbuildmatch                                ; Build match string
    call   dskfirstentry                                ; Get first dir entry
    jc     dskchangedirerror                            ;   If fail we fail too
dskchangedirnext:
    test   [es:bx+DIRECTORY.attribute], byte 010h       ; Make sure it is a directory
    jnz    dskchangedirfound                            ; If so change directory
    call   dsknextentry                                 ; Find next entry
    jc     dskchangedirerror                            ;   If fail we fail too
    jmp    dskchangedirnext                             ; Check next entry for directory
dskchangedirfound:
    mov    ax, [es:bx+DIRECTORY.startcluster]           ; Set start cluster to current directory
    mov    [dircurrent], ax
    and    [ss:bp+intcallparams.flags], word 0FEh       ; Clear carry flag in stack
    pop    es                                           ; Restore registers
    pop    ds
    pop    ax
    pop    bp
    ret                                                 ; Return from subroutine
dskchangedirerror:
    or     [ss:bp+intcallparams.flags], word 001h       ; Set carry flag in stack
    pop    es                                           ; Restore registers
    pop    ds
    pop    ax
    pop    bp
    ret                                                 ; Return from subroutine
		

Then add the following code to change drive so when the drive changes we reset to root directory.

    mov    [dircurrent], word 0                         ; Set current dir to root
		

disk.asm

;=======================================================
; Disk - disk.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================

%include "disk.inc"

;-------------------------------------------------------
; SELECT DEFAULT DRIVE (compensate for entering 80h)
; SERVICE 0EH
;-------------------------------------------------------
dsksetmaindrive:
    cmp    dl, 002h                                     ; Check if input is less than 2
    jl     dsksetmaindrivedone                          ; If it is no conversion needed
    test   dl, 080h                                     ; Test if bit 7 is set
    jnz    dsksetmaindrivedone                          ; If it is no conversion needed
    add    dl, 07Eh                                     ; Drive 2+ start at 080h+
dsksetmaindrivedone:
    mov    [dskdrive], dl                               ; Set current drive
    call   dskloadboot                                  ; Load boot data and calculate
    mov    [dircurrent], word 0                         ; Set current dir to root
    mov    al, [dskmaxdrive]                            ; Return max drives
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; GET CURRENT DEFAULT DRIVE
; SERVICE 19H
;-------------------------------------------------------
dskgetmaindrive:
    mov    al, [dskdrive]                               ; Get current drive
    test   al, 80h                                      ; Test if it is a hard drive
    jz     dskgetmaindrivedone
    sub    al, 07Eh                                     ; Make hard drives start at #2
dskgetmaindrivedone:
    ret                                                 ; Return from subroutine


; carry can only be returned by setting the flag in the stack
struc intcallparams
    .pbp      resw 1
    .pcall    resw 1
    .poffset  resw 1
    .psegment resw 1
    .flags    resw 1
endstruc
;-------------------------------------------------------
; Change Directory
; SERVICE 3BH
;-------------------------------------------------------
dskchangedir:
    push   bp                                           ; Get SP
    mov    bp, sp
    push   ax                                           ; Save registers
    push   ds
    push   es
    push   ds                                           ; Set ES:DI = DS:DX
    pop    es
    mov    di, dx                                       ; DI Points to string
    call   dskbuildmatch                                ; Build match string
    call   dskfirstentry                                ; Get first dir entry
    jc     dskchangedirerror                            ;   If fail we fail too
dskchangedirnext:
    test   [es:bx+DIRECTORY.attribute], byte 010h       ; Make sure it is a directory
    jnz    dskchangedirfound                            ; If so change directory
    call   dsknextentry                                 ; Find next entry
    jc     dskchangedirerror                            ;   If fail we fail too
    jmp    dskchangedirnext                             ; Check next entry for directory
dskchangedirfound:
    mov    ax, [es:bx+DIRECTORY.startcluster]           ; Set start cluster to current directory
    mov    [dircurrent], ax
    and    [ss:bp+intcallparams.flags], word 0FEh       ; Clear carry flag in stack
    pop    es                                           ; Restore registers
    pop    ds
    pop    ax
    pop    bp
    ret                                                 ; Return from subroutine
dskchangedirerror:
    or     [ss:bp+intcallparams.flags], word 001h       ; Set carry flag in stack
    pop    es                                           ; Restore registers
    pop    ds
    pop    ax
    pop    bp
    ret                                                 ; Return from subroutine


;-------------------------------------------------------
; Get first directory match
; Input:
;       None
; Output:
;       ES:BX Points to directory entry
;-------------------------------------------------------
dskfirstentry:
    mov    [diroffset], word 0                          ; Clear offset for new read
    mov    [dsksectorload], word 0                      ; Clear any currently loaded sectors
                                                        ; drops into next entry
;-------------------------------------------------------
; Get next directory match
; Input:
;       None
; Output:
;       ES:BX Points to directory entry
; make this filter attributes
; write service wrapper
; fix name match routine
;-------------------------------------------------------
dsknextentry:
    push   ax
    push   cx
    push   dx
    push   di
    xor    bx, bx                                      ; es:bx = scratch area
    mov    es, bx
dsknextentrynext:
    cmp    [dircurrent], word 0                        ; check if root directory
    jnz    dsknextentryfile                            ; if not process as file
    mov    ax, 32                                      ; Calc size of root
    mul    word [rootdirents]
    mov    bx,[bytespersec]
    add    ax, bx
    cmp    [diroffset], ax                             ; check if we are past the end
    jge    dsknextentryerror                           ; we have reached the end
    jmp    dsknextentrydone
dsknextentryfile:
    mov    bx, 00500h                                  ; ES:BX = Scratch area
    mov    ax, [diroffset]                             ; AX = Offset
    mov    cx, [dircurrent]                            ; CX = Start cluster
    call   dskreadoffsetsec                            ; Read Offset Sec
    jc     dsknextentryerror
    mov    ax, [diroffset]                             ; sector offset = offset % 512
    div    word [bytespersec]
    mov    bx, dx
    add    bx, 00500h                                  ; adjust offset to scratch area
    cmp    [es:bx], byte 0                             ; check if entry is blank
    jz     dsknextentryerror                           ;   if so we reached the end
    cmp    [es:bx], byte 0E5h                          ; check if entry is deleted
    jz     dsknextentryfilenoshow                      ;   if so we reached the end
    cmp    [es:bx+11], byte 00Fh                       ; check if entry is Extended name
    jz     dsknextentryfilenoshow 
    mov    di, bx                                      ; Compare name
    call   dskcmpmatch
    jnc    dsknextentryexit                            ; If no carry found our directory
dsknextentryfilenoshow:
    add    [diroffset], word 32                        ; point to next dir entry
    jmp    dsknextentryfile                            ; Find next match
dsknextentrydone:
    mov    ax, [diroffset]                             ; read sector = diroffset / BYTES PER SECTOR
    xor    dx, dx
    div    word [bytespersec]                           
    push   dx                                          ; dx should equal index into sector
    add    ax, [root]                                  ; add directory base
    cmp    [dsksectorload], ax                         ; Check if this sector is already in scratch
    jz     dsknextentryskprd                           ;    If it is don't load it again
    mov    [dsksectorload], ax                         ; Set current loaded FAT sector
    mov    bx, 00500h
    xor    dx, dx
    call   dskcalcchs                                  ; Calc chs
    jc     dsknextentryerrorpop
    call   dskreadsec                                  ; read sector
    jc     dsknextentryerrorpop
dsknextentryskprd:
    pop    bx                                          ; restore index       
    add    bx, 00500h
    cmp    [es:bx], byte 0                             ; check if entry is blank
    jz     dsknextentryerror                           ; if so we reached the end
    cmp    [es:bx], byte 0E5h                          ; check if entry is deleted
    jz     dsknextentrynoshow                          ; if so we reached the end
    cmp    [es:bx+11], byte 00Fh                       ; check if entry is Extended name
    jz     dsknextentrynoshow                          ; if so we reached the end
    ; attribute mask
    mov    di, bx
    call   dskcmpmatch                                 ; see if it matches our match string
    jnc    dsknextentryexit
dsknextentrynoshow:
    add    [diroffset], word 32                        ; point to next dir entry
    jmp    dsknextentrynext
dsknextentryexit:
    ; copy data to dta
    add    [diroffset], word 32                        ; point to next dir entry
    pop    di                                          ; Restore registers
    pop    dx
    pop    cx
    pop    ax
    clc                                                ; Clear carry for success
    ret                                                ; Return from subroutine
dsknextentryerrorpop:
    pop    dx                                          ; Clear the extra push
dsknextentryerror:
    pop    di                                          ; Restore registers
    pop    dx
    pop    cx
    pop    ax
    stc                                                ; Set carry for error
    ret                                                ; Return from subroutine

;-------------------------------------------------------
; Build blank match string
; Input:
;       es:di = Buffer
; Output:
;       matchname = Match Name
;-------------------------------------------------------
dskmatchblank:
    push   cx                                           ; Save registers
    push   si
    mov    cx,    11                                    ; 11 Characters
    mov    si, matchname                                ; Point to matchname
dskmatchblanknext:
    mov    [ds:si], byte '?'                            ; Fill with '?'
    inc    si
    loop   dskmatchblanknext
    pop    si                                           ; Restore registers
    pop    cx
    ret                                                 ; Return from subroutine

matchname:        db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

;-------------------------------------------------------
; Build match string
; Input:
;       es:di = Buffer
; Output:
;       matchname = Match Name
; fix this so first part is always spaces and last part is ???
;-------------------------------------------------------
dskbuildmatch:
	mov	cx, 11                                  ; 11 Characters
	mov	si, matchname                           ; Point to match string
dskbuildmatchnext:
	mov	al, [es:di]                             ; Get character from input buffer
	inc	di
	cmp	al, 0x0D                                ; If enter fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, 0x00                                ; If enter fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, ' '                                 ; If space fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, '.'                                 ; If . fill name portion only
	jz	dskbuildmatchfill8
	call	toup
	mov	[cs:si], al                             ; Otherwise write character to string
	inc	si
	loop	dskbuildmatchnext                       ; Get next character
        ret                                             ; Return from subroutine
dskbuildmatchfill:
	mov	al, '?'                                 ; Fill with question marks
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfill
	ret
dskbuildmatchfill8:
        cmp     cx, 11
        jz      dskbuildmatchspecial
	sub	cx, 0x03                                ; Adjust to just 8 characters
dskbuildmatchfillloop:
	mov	al, ' '                                 ; Fill with spaces
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfillloop
	add	cx, 0x03                                ; Restore 11 characters
	jmp     dskbuildmatchnext                       ; Continue match for extension
dskbuildmatchspecial:
        dec     di
dskbuildmatchspecialnext:
	mov	al, [es:di]                             ; Get character from input buffer
	inc	di
	cmp	al, 0x0D                                ; If enter fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, 0x00                                ; If enter fill the rest of string
	jz	dskbuildmatchfill
	cmp	al, ' '                                 ; If space fill the rest of string
	jz	dskbuildmatchfill
	mov	[cs:si], al                             ; Otherwise write character to string
	inc	si
	loop	dskbuildmatchspecialnext
dskbuildmatchspecialfill:
	mov	al, ' '                                 ; Fill with question marks
	mov	[cs:si], al
	inc	si
	loop	dskbuildmatchfill
	ret
        

;-------------------------------------------------------
; Compare match string
; Input:
;       es:di = Directory entry
; Output:
;       Carry Set = not match
;       Carry Clear = match
;-------------------------------------------------------
dskcmpmatch:
    mov    si, matchname                                 ; Point to match name                            
    mov    cx, 11                                        ; 11 Characters
dskcmpmatchnext:
    mov    al, [si]                                      ; Read match name
    inc    si                                            ; Next match caracter
    cmp    al, '?'                                       ; If ? auto match
    jz     dskcmpmatchauto
    cmp    [es:di], al                                   ; Else check if character matches
    jnz    dskcmpmatchfail
dskcmpmatchauto:
    inc    di                                            ; Next source character
    loop   dskcmpmatchnext                               ; Next character match
    clc                                                  ; Clear carry for success
    ret                                                  ; Return from subroutine
dskcmpmatchfail:
    stc                                                  ; Set carry for fail
    ret                                                  ; Return from subroutine

;-------------------------------------------------------
; FAT Chain Loader
; Input:
;     AX = First Cluster
;     ES:BX = Buffer
; Output:
;     Loaded buffer
;-------------------------------------------------------
dskchainload:
    mov    [dsksectorload], word 0
dskchainloadnext:
    cmp    eax, 0FFFFFFFFh                               ; Last cluster found
    jz     dskchainloaddone                             ; We are done
    call   dskreaddatacluster                           ; Read cluster from data area
    jc     dskchainloadfailpop
    call   dsknextcl                                    ; Get next cluster
    jc     dskchainloadfail
    jmp    dskchainloadnext                             ; Loop until last cluster
dskchainloaddone:
    clc                                                 ; Carry clear for success
    ret                                                 ; Return from subroutine
dskchainloadfailpop:
    pop    ax                                           ; Clear extra push
dskchainloadfail:
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Read offset sector
; Input: offset         AX   (5000)
; Input: startclust     CX
; Input: load location  ES:BX
; bytesperclust  =  secperclust * bytespersec   (2048)
; cluster = offset / bytesperclust   (2)
; clustoff =  offset % bytespercluster   (904)
; secinclust = clustoff / bytespersector = 1
; cluststartsec = walkthechan(startclust, cluster);
; LBA = (cluststartsec * secperclust + data) + secinclust
; calcchs
; readsec
;-------------------------------------------------------
dskreadoffsetsec:
    push   cx                                           ; Save startclust
    push   ax                                           ; Save offset
    mov    ax, [secperclust]
    mul    word [bytespersec]                           ; ax = bytesperclust
    mov    cx, ax                                       ; cx = bytesperclust
    xor    dx, dx
    pop    ax                                           ; restore offset
    div    cx                                           ; ax = cluster, dx = clustoff
    xchg   ax, dx                                       ; ax = clustoff, dx = cluster
    push   dx                                           ; save cluster
    xor    dx, dx
    div    word [bytespersec]                           ; ax = secinclust
    mov    dx, ax                                       ; dx = secinclust
    pop    cx                                           ; cx = cluster, restore cluster
    pop    ax                                           ; ax = startclust restore startclust
    push   dx
dskreadoffsetsecnextcl:                                 ; ax = cluststartsec
    mov    dx, ax                                       ; save our cluster
    or     cx, cx                                       ; check if our cluster is zero
    jz     dskreadoffsetsecthissec                      ;   if so this is our sector
    dec    cx                                           ; Next cluster
    call   dsknextcl                                 
    cmp    eax, 0FFFFFFFFh                              ; Check if we reached the end
    jnz    dskreadoffsetsecnextcl                       ;   If not next cluster
    or     cx, cx                                       ; Is our offset higher than end
    jnz    dskreadoffsetsecfail                         ;   If so fail
dskreadoffsetsecthissec:
    mov    ax, dx                                       ; restore our cluster
    dec    ax                                           ; adjust cluster
    dec    ax
    xor    dx, dx                                       ; Zero for multiply
    push   bx                                           ; save scratch address
    xor    bh, bh                                       ; Word multiply with byte
    mov    bl, [secperclust]
    mul    bx                                           ; multiply cluster by secperclust
    pop    bx
    add    ax, word [data]                              ; add data area
    pop    dx
    add    ax, dx                                       ; add secinclust
    xor    dx, dx
    call   dskcalcchs                                   ; Calculate chs
    call   dskreadsec                                   ; Read sector
    ret                                                 ; Return from subroutine
dskreadoffsetsecfail:
    pop    dx                                           ; Clear extra push
    stc                                                 ; Set carry for error
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Read Data Cluster  
; Input:
;     AX = Cluster start
;     ES:BX = Buffer
; Output:
;     Loaded buffer
;-------------------------------------------------------
dskreaddatacluster:
    push   ax                                           ; Save registers
    push   cx
    push   dx
    xor    ch, ch                                       ; Clear ch
    mov    cl, [secperclust]                            ; CX = Sectors per cluster
;dskreadclusternext:
    push   bx                                           ; Save load location
    dec    ax                                           ; Subtract 2 because FAT starts at 2
    dec    ax
    mov    bl, [secperclust]
    xor    bh, bh
    mul    bx
    pop    bx                                           ; Restore load location
    add    ax, [data]                                   ; Add the data start cluster
dskreadclusternext:
    push   ax
    push   cx                                           ; Save count read will destroy it
    xor    dx, dx                                       ; Clear for calcchs
    call   dskcalcchs                                   ; Calculate Cylinder Head Sector
    jc     dskreaddataclusterfail                       ;   If error exit routine
    call   dskreadsec                                   ; Read sector into buffer
    jc     dskreaddataclusterfail                       ;   If error exit routine
    pop    cx                                           ; Restore counter
    pop    ax                                           ; Restore cluster start
    add    bx, word [bytespersec]                       ; Add bytes per sec for next read
    inc    ax                                           ; Increment sector
    loop   dskreadclusternext                           ; Get next sector of cluster
    pop    dx                                           ; Restore registers
    pop    cx
    pop    ax
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
dskreaddataclusterfail:
    pop    cx                                           ; Restore registers
    pop    ax
    pop    dx                                           ; Restore registers
    pop    cx
    pop    ax
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Get Next Cluster
; Input:
;     AX = Current Cluster
; Output:
;     AX = Next Cluster
;-------------------------------------------------------
dsknextcl:
    cmp   [filesystype+3], word 03233h                    ; Check fat 32
    jnz    dsknextclnot32
    call   dsknextcl32                                    ; Call fat nextcl32
    jc     dsknextclfail
    jmp    dsknextcldone                                  ; Done
dsknextclnot32:
    cmp    [filesystype+3], word 03631h 		  ; Check fat 16
    jnz    dsknextclnot16
    call   dsknextcl16                                    ; Call fat nextcl16
    jc     dsknextclfail
    cmp    ax,  0FFFFh                                    ; Check end of chain
    jnz    dsknextcldone
    mov    eax, 0FFFFFFFFh                                ; Translate to fat32 end of chain
    jmp    dsknextcldone
dsknextclnot16:
    cmp    [filesystype+3], word 03231h                   ; Check fat 12
    jnz    dsknextclnot12
    call   dsknextcl12                                    ; Call fat nextcl12
    jc     dsknextclfail
    cmp    ax, 000FFFh                                    ; Check end of chain
    jnz    dsknextcldone
    mov    eax, 0FFFFFFFFh                                ; Translate to fat32 end of chain
    jmp    dsknextcldone
dsknextclnot12:
    jmp    dsknextclfail                                  ; Not a fat partition
dsknextcldone:
    clc                                                   ; Clear carry for succes
    ret                                                   ; Return from subroutine
dsknextclfail:
    stc                                                   ; Set carry for fail
    ret                                                   ; Return from subroutine

;-------------------------------------------------------
; Get Next Cluster (FAT16)
; Input:
;     AX = Current Cluster
; Output:
;     AX = Next Cluster
;-------------------------------------------------------
dsknextcl32:
    push   cx                                           ; Save cx
    mov    cl, 2
    shl    ax, cl                                       ; Multiply by 4
    pop    cx
    jmp    dsknextcl16read                              ; Rest is the same as nectcl16
dsknextcl16:
    shl    ax, 1                                        ; Multiply by 2
dsknextcl16read:
    call   dskfatread                                   ; Read first byte of fat12
    jc     dsknextcl16error                             ;   error exit routine
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
dsknextcl16error:
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Get Next Cluster (FAT12)
; Input:
;     AX = Current Cluster
; Output:
;     AX = Next Cluster
;-------------------------------------------------------
dsknextcl12:
    push   cx                                           ; Save register
    push   ax                                           ; Save a copy of input cluster
    mov    cx, ax                                       ; FAT index = n*3/2
    shl    ax, 1                                        ; Multiply by 2
    add    ax, cx                                       ; Add 1
    shr    ax, 1                                        ; Divide by 2
    push   ax                                           ; Save input byte to read
    call   dskfatread                                   ; Read first byte of fat12
    jc     dsknextcl12errorpop                          ;   error exit routine
    mov    cl, al                                       ; Save byte read
    pop    ax                                           ; Restore input byte to read
    inc    ax                                           ; Get next byte
    call   dskfatread                                   ; Read second byte of fat12
    jc     dsknextcl12error                             ;   error exit routine
    xchg   ah, al                                       ; Make second byte high
    mov    al, cl                                       ; Make first byte low
    pop    cx                                           ; Restore the saved input cluster
    test   cx, 0x0001                                   ; Check if it was odd or even
    jz     dsknextcl12done                              ; If even do nothing
    mov    cl, 4                                        ; If odd we need to shift by 4
    shr    ax, cl
dsknextcl12done:
    and    ax, 0x0FFF                                   ; Clear top 4 bits we only need 12 bits
    pop    cx                                           ; Restore register
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
dsknextcl12errorpop:
    pop    ax                                           ; We had an extra push clear it
dsknextcl12error:
    pop    ax                                           ; Restore input cluster
    pop    cx                                           ; Restore register
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Get byte from fat
; Input:
;     AX = Byte to read
; Output:
;     AL = Byte read
;-------------------------------------------------------
dskfatread:
    push   bx                                           ; Save registers
    push   cx
    push   es
    xor    bx, bx                                       ; Set ES to 0000h
    mov    es, bx
    mov    bx, 00500h                                   ; Read into scratch area
    xor    dx, dx                                       ; Clear for divide
    div    word [bytespersec]                           ; Sector of file = FAT index / bytes per sector 
    ;inc    ax                                           ; FAT starts at sector 2 advance ax
    add    ax, [dskfatlba]
    cmp    [dsksectorload], ax                          ; Check if this sector is already in scratch
    jz     dskfatread12noload                           ;    If it is don't load it again
    mov    [dsksectorload], ax                          ; Set current loaded FAT sector
    push   dx                                           ; Save FAT index MOD bytes per sector
    xor    dx, dx                                       ; Clear for divide in calcchs
    call   dskcalcchs                                      ; Calculate Cylinder Head Sector
    jc     dskfatread12error                            ;    If error exit routine
    call   dskreadsec                                      ; Read sector
    jc     dskfatread12error                            ;    If error exit routine
    pop    dx                                           ; Restore FAT index MOD bytes per sector
dskfatread12noload:
    add    bx, dx                                       ; Byte to read = file index % bytes per sector + scratch
    mov    ax, [es:bx]                                  ; Get byte

    pop    es                                           ; Restore registers
    pop    cx
    pop    bx
    clc                                                 ; Carry clear on success
    ret                                                 ; Return from subroutine
dskfatread12error:
    pop    dx                                           ; Restore FAT index MOD bytes per sector
    pop    es                                           ; Restore registers
    pop    cx
    pop    bx
    stc                                                 ; Set carry on fail
    ret                                                 ; Return from subroutine


;-------------------------------------------------------
; Load Boot Sector
; ES:BX = Scratch
;-------------------------------------------------------
dskloadboot:
    push   ax                                           ; Save registers
    push   bx
    push   cx
    push   dx
    push   es
    xor    ax, ax                                       ; Point to scratch area
    mov    es, ax
    mov    bx, 00500h
    mov    [dskbootlba], word 0                         ; Set normal boot lba
    mov    [dskbootlba+2], word 0
    mov    [dskfatlba], word 1                          ; Set normal fat lba
    mov    [dskfatlba+2], word 0
    mov    [dskcylinder], word 0                        ; Read boot sector
    mov    [dsksector], byte 1
    mov    [dskhead], byte 0
    call   dskreadsec
    jc     dskloadbootfail
    test   [dskdrive], byte 080h                        ; If hard disk use partition info
    jz     dskloadbootdone
    mov    ax, [es:bx+DSKPART1+PARTITION.startlba]      ; Adjust boot lba to start of partition
    mov    [dskbootlba], ax
    inc    ax                                           ; Adjust fat lba 1 lba past boot
    mov    [dskfatlba], ax
    mov    ax, [es:bx+DSKPART1+PARTITION.startlba+2]
    mov    [dskbootlba+2], ax
    adc    ax, 0
    mov    [dskfatlba+2], ax
    mov    ch, [es:bx+DSKPART1+PARTITION.startcyl]      ; Read real boot sector
    mov    cl, [es:bx+DSKPART1+PARTITION.startsecenc]
    mov    dh, [es:bx+DSKPART1+PARTITION.starthead]
    mov    dl, [dskdrive]
    mov    ax, 00201h
    int    013h
    jc     dskloadbootfail
dskloadbootdone:
    call   dskcopyboot                                  ; Copy boot sector header
    pop    es                                           ; Restore registers
    pop    dx
    pop    cx
    pop    bx
    pop    ax
    clc                                                 ; Clear carry for success
    ret                                                 ; Return from subroutine
dskloadbootfail:
    pop    es                                           ; Restore registers
    pop    dx
    pop    cx
    pop    bx
    pop    ax
    stc                                                 ; Set carry for fail
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Copy Boot
;-------------------------------------------------------
dskcopyboot:
    push   ds                                           ; Save registers
    push   es
    push   si
    push   di
    xor    ax, ax                                       ; DS = 0
    mov    ds, ax
    mov    ax, 00070h                                   ; ES = DEMOOS disk header
    mov    es, ax
    mov    di, oemname                                  ; Copy disk header to oemname
    mov    si, 0503h                                    ; From oemname of disk header

    mov    cx, track-oemname                            ; Header size
    rep    movsb                                        ; Do copy

    push   cs                                           ; Set DS local
    pop    ds

    push    bx
    mov     al, [fats]                                  ; Calculate root location
    mul     word [fatsecs]
    add     ax, [hiddensecs]
    adc     dx, [hiddensecs+2]
    add     ax, [ressectors]
    adc     dx, byte 0                                  ; if carry increment modulous
    mov     [root], ax                                  ; Save result in root
    mov     [rootmod], dx                               ; Save modulous in rootmod
    mov     [data], ax                                  ; Seve result in data we will just add
    mov     [datamod], dx                               ; Save modulous in datamod

    mov     ax, 32					; Calculate data location
    mul     word [rootdirents]
    mov     bx,[bytespersec]
    add     ax, bx
    dec     ax
    div     bx
    add     [data], ax					; save result in data
    adc     word [datamod], byte 0                      ; save modulous in datamod
    pop     bx

    pop    di                                           ; Restore registers
    pop    si
    pop    es
    pop    ds
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Calculate CHS (limit of 33.5MB)
; Input:
;     AX:DX = Logical Sector
; Output:
;     sector
;     head
;     track/cylinder
;-------------------------------------------------------
; sector = (lba % sectors per track) + 1
; head = lba / sectors per track % number of heads
; track = lba / sectors per track / number of heads
dskcalcchs:
    cmp     dx, [secpertrack]                           ; Check within track
    jnc     dskcalcchsfail                              ; if not fail
    div    word [secpertrack]                           ; LBA / secpertrack
    inc     dl                                          ; LBA % secpertrack + 1
    mov     [dsksector], dl                             ; Save in sector
    xor     dx, dx                                      ; Clear the modulous
    div     word [heads]                                ; lba / secpertrack / heads
    mov     [dskhead], dl                               ; lba / secpertrack % heads
    mov     [dskcylinder], ax                           ; lba / secpertrack / heads
    clc                                                 ; Clear carry to report success
    ret                                                 ; Return from subroutine
dskcalcchsfail:
    stc                                                 ; Set carry to report failure
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Read Sector
; Input:
;    bsTrack = Track
;    bsSector = Sector
;    bsHead = Head
;    ES:BX = Location to load sector at
; Output:
;       AX, BX, CX, DX are destroyed
;-------------------------------------------------------
dskreadsec:
    mov     dx, [dskcylinder]	                        ; Get the track
    mov     cl, 006h                                    ; Rotate the bits 8 and 9 into position
    shl     dh, cl
    or      dh, [dsksector]                             ; Get the sector with out changing high bits
    mov     cx, dx                                      ; Place in the correct register
    xchg    ch, cl                                      ; Swap bytes to complete the process
    mov     dl, [dskdrive]                              ; Drive number
    mov     dh, [dskhead]                               ; Current Head
    mov     ax, 00201h                                  ; BIOS Disk Read 1 Sector
    int     013h
    ret

;-------------------------------------------------------
; Date Output
; Input:
;       ax = Date
; Output:
;       none
;-------------------------------------------------------
dskdateout:
    push   ax                                           ; Save registers
    push   cx
    push   ax                                           ; Save date
    mov    cl, 5                                        ; Shift month into position
    shr    ax, cl
    and    ax, 00Fh                                     ; Clear day year info
    mov    cx, 2                                        ; Print month
    mov    dh, 0
    mov    dl, '0'
    call   decfmtout
    mov    ah, 002h                                     ; Print -
    mov    dl, '-'
    int    021h
    pop    ax                                           ; Restore date
    push   ax                                           ; Resave date
    and    ax, 01Fh                                     ; Clear month year info
    mov    cx, 2                                        ; Print Day
    mov    dh, 0
    mov    dl, '0'
    call   decfmtout
    mov    ah, 002h                                     ; Print -
    mov    dl, '-'
    int    021h    
    pop    ax
    mov    cl, 9                                        ; Shift year into position
    shr    ax, cl
    and    ax, 03Fh                                     ; Clear day month info
    add    ax, 80                                       ; Add 1980
    cmp    ax, 100                                      ; If over 100 fix
    jl     diskdateoutyear
    sub    ax, 100
diskdateoutyear:
    mov    cx, 2                                        ; Print year
    mov    dh, 0
    mov    dl, '0'
    call   decfmtout
    pop    cx                                           ; Restore registers
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Time Output
; Input:
;       ax = Time
; Output:
;       none
;-------------------------------------------------------
dsktimeout:
    push   cx                                           ; Save registers
    push   dx
    push   ax                                           ; Save time
    mov    cl, 11                                       ; Shift hour into position
    shr    ax, cl
    and    ax, 01Fh                                     ; Clear minute second info
    mov    cx, 2                                        ; Print hour
    mov    dh, 0
    mov    dl, '0'
    call   decfmtout
    mov    ah, 002h                                     ; Print :
    mov    dl, ':'
    int    021h
    pop    ax                                           ; Restore time
    push   ax                                           ; Resave time
    mov    cl, 5                                        ; Shift minute into position
    shr    ax, cl
    and    ax, 03Fh                                     ; Clear hour second info
    mov    cx, 2                                        ; Print minute
    mov    dh, 0
    mov    dl, '0'
    call   decfmtout
    pop    ax                                           ; Restore time
    pop    dx                                           ; Restore registers
    pop    cx
    ret                                                 ; Return from interrupt

dskbootlba:             dd   0
dskfatlba:              dd   0
dskcylinder             dw   0
dsksector               db   0
dskhead                 db   0
dsksectorload:          dw   0
dskdrive:               db   0
dskmaxdrive:            db   16
diroffset:		dw   0
dircurrent:		dw   0
dskdtaseg:              dw   0
dskdtaoff:              dw   0
		

Services

In inthook in int.asm add the following code:

    cmp    ah, 03Bh                                     ; Service 3Bh: changedir
    jnz    inthookdone3B
    call   dskchangedir
    jmp    inthookdone
inthookdone3B:
		

int.asm

;=======================================================
; Service - int.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================


struc inthookparams
    .pbp      resw 1
    .poffset  resw 1
    .psegment resw 1
    .flags    resw 1
endstruc
;-------------------------------------------------------
; Interrupt Hook
;-------------------------------------------------------
inthook:
    cmp    ah, 002h                                     ; Service 02h: stdout
    jnz    inthookdone02
    call   stdout
    jmp    inthookdone
inthookdone02:
    cmp    ah, 008h                                     ; Service 08h: stdin
    jnz    inthookdone08
    call   stdin
    jmp    inthookdone
inthookdone08:
    cmp    ah, 009h                                     ; Service 09h: strout
    jnz    inthookdone09
    call   strout
    jmp    inthookdone
inthookdone09:
    cmp    ah, 00Ah                                     ; Service 0Ah: strin
    jnz    inthookdone0A
    call   strin
    jmp    inthookdone
inthookdone0A:
    cmp    ah, 00Eh                                     ; Service 0Eh: setmaindrive
    jnz    inthookdone0E
    call   dsksetmaindrive
    jmp    inthookdone
inthookdone0E:
    cmp    ah, 019h                                     ; Service 19h: getmaindrive
    jnz    inthookdone19
    call   dskgetmaindrive
    jmp    inthookdone
inthookdone19:
    cmp    ah, 025h                                     ; Service 25h: setint
    jnz    inthookdone25
    call   intset
    jmp    inthookdone
inthookdone25:
    cmp    ah, 031h                                    ; Service 31h: TSR
    jnz    inthookdone31
    jmp    tsr;
inthookdone31:
    cmp    ah, 035h                                     ; Service 35h: getint
    jnz    inthookdone35
    call   intget
    jmp    inthookdone
inthookdone35:
    cmp    ah, 03Bh                                     ; Service 3Bh: changedir
    jnz    inthookdone3B
    call   dskchangedir
    jmp    inthookdone
inthookdone3B:
    cmp    ah, 048h                                     ; Service 48h: alloc
    jnz    inthookdone48
    call   memalloc
    jmp    inthookdone
inthookdone48:
    cmp    ah, 049h                                     ; Service 49h: free
    jnz    inthookdone49
    call   memfree
    jmp    inthookdone
inthookdone49:
    cmp    ah, 04Ah                                     ; Service 4Ah: resize
    jnz    inthookdone4A
    call   memresize
    jmp    inthookdone
inthookdone4A:
    cmp    ah, 0x4C                                     ; Service 4Ch: exit program
    jnz    inthookdone4C
    jmp    exitprogram
inthookdone4C:
    push   bp
    mov    bp, sp
    or     [ss:bp+inthookparams.flags], word 001h         ; set carry flag in stack
    pop    bp
inthookdone:
    iret


;-------------------------------------------------------
; Exit program
; Input:
;       SP+2 = owner (return address from interrupt call)
; Output:
;       None
;-------------------------------------------------------
exitprogram:
    mov    bp, sp                                      ; Copy stack pointer
    mov    ax, [ss:bp+0x02]                            ; Get memory owner
    mov    es, ax                                      ; Free program memory
    mov    ah, 049h
    int    021h
    xor    ax, ax                                      ; SS = 0
    mov    ss, ax
    mov    sp, 07C00h                                  ; Reset SP to 7C00
    push   cs                                          ; DS = CS
    pop    ds
    push   cs                                          ; ES = CS
    pop    es
    jmp    main                                        ; Restart command prompt

;-------------------------------------------------------
; Terminate and stay resident
; Input:
;       SP+2 = owner (return address from interrupt call)
; Output:
;       None
;-------------------------------------------------------
tsr:
    mov    bp, sp                                      ; Copy stack pointer
    mov    ax, [ss:bp+0x02]                            ; Get memory owner
    mov    es, ax                                      ; Free program memory
    mov    bx, dx
    mov    ah, 04Ah
    int    021h
    xor    ax, ax                                      ; SS = 0
    mov    ss, ax
    mov    sp, 07C00h                                  ; Reset SP to 7C00
    push   cs                                          ; DS = CS
    pop    ds
    push   cs                                          ; ES = CS
    pop    es
    jmp    main                                        ; Restart command prompt

;-------------------------------------------------------
; Interrupt Set
; Input:
;       AL = Interrupt Number
;       DS = Segment of Interrupt
;       DX = Offset of Interrupt
; Output:
;       None
;-------------------------------------------------------
intset:
    push   ax                                           ; Save registers
    push   cx
    push   di
    push   es
    xor    ah, ah                                       ; DI = INT << 2
    mov    cl, 2                                        ; Essentially multiply by 4
    shl    ax, cl
    mov    di, ax                                       ; di is the address of our int
    xor    ax, ax                                       ; ES = 0 The int table is at 00000h
    mov    es, ax
    mov    ax, dx                                       ; Store offset
    stosw
    mov    ax, ds                                       ; Store segment
    stosw
    pop    es                                           ; Restore registers
    pop    di
    pop    cx
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Interrupt Get
; Input:
;       AL = Interrupt Number
; Output:
;       ES = Segment of Interrupt
;       BX = Offset of Interrupt
;-------------------------------------------------------
intget:
    push   ax                                           ; Save registers
    push   cx
    push   si
    push   ds
    xor    ah, ah                                       ; DI = INT << 2
    mov    cl, 2                                        ; Essentially multiply by 4
    shl    ax, cl
    mov    si, ax                                       ; di is the address of our int
    xor    ax, ax                                       ; DS = 0 The int table is at 00000h
    mov    ds, ax
    lodsw
    mov    bx, ax                                       ; Get offset
    lodsw
    mov    es, ax                                       ; Get segment
    pop    ds                                           ; Restore registers
    pop    si
    pop    cx
    pop    ax
    ret                                                 ; Return from subroutine

;-------------------------------------------------------
; Interrupt Initialize
; Input:
;       None
; Output:
;       DX, AX are destroyped
;-------------------------------------------------------
intinit:
    mov    dx, inthook                                  ; Offset to our service routine
                                                        ;    DS should already be the
                                                        ;    correct segment
    mov    al, 021h                                     ; Service 21h
    call   intset                                       ; Set Interrupt 21h
    ret                                                 ; Return from subroutine
		

DEMO OS

We will add the string to match for cd

cdstr:          db      "CD", 0
		

Then the code to handle the CD command.

;-------------------------------------------------------
; Change Directory
; Input:
;       Command Line Directory
; Output:
;       None
;-------------------------------------------------------
changedir:
    push   cs
    pop    ds
    mov    si, buffer+2
changedirnextenter:
    lodsb
    cmp    al, 00Dh
    jz     changedirfoundenter
    jmp    changedirnextenter
changedirfoundenter:
    dec    si
    mov    [si], byte 0
    mov    dx, buffer+5
    mov    ah, 03Bh             ; Change directory
    int    021h
    jnc    changedirdone        ; why is this not triggering!
    mov    ah, 09h
    mov    dx, nodir
    int    021h
changedirdone:
    ret
		

Then to main we will add the check for CD.

    mov    si, cdstr                                    ; Point to command text
    call   cmpcmd                                       ; Compare
    jc     cmdncd                                       ; Carry set if not equal
    call   changedir                                    ; Call function
    jmp    main                                         ; We're done checking
cmdncd:
		

And lets not forget to update the version line by changing:

version:        db      00Dh, 00Ah, "DEMO OS V0.16"     ; Message to be printed
                db      00Dh, 00Ah, '$'
		

to

version:        db      00Dh, 00Ah, "DEMO OS V0.17"     ; Message to be printed
                db      00Dh, 00Ah, '$'
		

Modify the disk image

Create the new directory structure

;-------------------------------------------------------
; UTILS Directory
;-------------------------------------------------------

                times ((040h-2+021h)*512)-($-$$) db 0x00
                db      ".          "                   ; Filename
                db      010h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                dw      0A232h                          ; Written Time
                dw      05277h                          ; Written Date
                dw      00040h                          ; First Cluster Address Low
                dd      0                               ; File Size

                db      "..         "                   ; Filename
                db      010h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                dw      0A232h                          ; Written Time
                dw      05277h                          ; Written Date
                dw      00000h                          ; First Cluster Address Low
                dd      0                               ; File Size
		

Move the hello program directory entry to the end of the utils directory.

                db      "HELLO   COM"                   ; Filename
                db      020h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                db      0C0h, 032h, 0BFh, 01Ch          ; Written Date/Time
                dw      00034h                          ; First Cluster Address Low
                dd      helloend-hellostart             ; File Size
		

Add the UTILS directory entry to root.

                db      "UTILS      "                   ; Filename
                db      010h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                db      0C0h, 032h, 0BFh, 01Ch          ; Written Date/Time
                dw      00040h                          ; First Cluster Address Low
                dd      0                               ; File Size
		

Add the utils fat entry to the fat table

                times (040h*3/2)+512-($-$$) db 0       ; Fat for UTILS directory 040h is Cluster
                FAT12 040h, 512                        ; 040h Cluster, filesize
		

osdisk.asm

;=======================================================
; Disk Image - osdisk.asm
; -----------------------------------------------------
; Written in NASM
; Written by Marcus Kelly
; -----------------------------------------------------
; Copyright 2021 Marcus Kelly
;=======================================================

;-------------------------------------------------------
; FAT12 Generator
; Usage:
;      FAT12 Cluster, Filesize
;-------------------------------------------------------
%macro FAT12 2		                                ; FAT STARTBLOCK FILESIZE
    %assign i %1                                        ; FAT Block Number
    %assign j %2                                        ; File Size
    %assign k 512                                       ; Cluster Size
    %assign m j
    %assign j 0                                         ; j = j mod k
    %rep 500                                            ; repeate 100 times or m is less than 512
        %assign j j+1                                   ; j ++
        %assign m m-k                                   ; subtract sector size
        %if m0                                     ; if m is still greater than zero increment j
                %assign j j+1
            %endif
            %exitrep                                    ; we are done with our divide
        %endif
    %endrep
    %assign l i+j-1                                     ; l = FAT Number + Number of FAT Blocks for file
    %assign j j*3/2                                     ; Position in FAT
    %rep j                                              ; Numver of blocks to write
        %if i=l
            %assign x 0FFFh                             ; Last FAT Block for File 0xFFF
            %if i&1                                     ; If odd
                db     ((x<<4)&0F0h)+(i>>8)
                db     (x>>4)&0FFh)
            %else                                       ; If even
                db     x&0FFh
                db     (x>>8)&00Fh
                db     000h
            %endif
            %exitrep
        %endif
        %if i&1                                         ; If odd
            db     ((i+1<<4)&0xF0)+(i>>8)
            db     (i+1>>4)&0xFF)
        %else
            db     i+1&0xFF
        %endif
        %assign i i+1                                   ; increment i
    %endrep
%endmacro


;-------------------------------------------------------
; Boot Sector
;-------------------------------------------------------
incbin "boot.bin"                                       ; include our boot.bin file as binary

;-------------------------------------------------------
; FAT Table 1
;-------------------------------------------------------
                db      0F0h, 0FFh, 0FFh

                times (002h*3/2)+512-($-$$) db 0       ; Fat for demoos.bin 002h is Cluster
                FAT12 002h, 4152                       ; 002h Cluster, file size (MAX 8K)

                times (034h*3/2)+512-($-$$) db 0       ; Fat for hello.com 034h is Cluster
                FAT12 034h, 30                         ; 034h Cluster, filesize

                times (040h*3/2)+512-($-$$) db 0       ; Fat for UTILS directory 040h is Cluster
                FAT12 040h, 512                        ; 040h Cluster, filesize

;-------------------------------------------------------
; Root Directory
;-------------------------------------------------------
                times 9728-($-$$) db 0                  ; Place our file at 9728

                db      "DEMOOS  BIN"                   ; Filename
                db      021h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                db      0C0h, 032h, 0BFh, 01Ch          ; Written Date/Time
                dw      00002h                          ; First Cluster Address Low
                dd      demoosend-demoosstart           ; File Size

                db      "UTILS      "                   ; Filename
                db      010h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                db      0C0h, 032h, 0BFh, 01Ch          ; Written Date/Time
                dw      00040h                          ; First Cluster Address Low
                dd      0                               ; File Size

;-------------------------------------------------------
; Data Area
;-------------------------------------------------------
                times 16896-($-$$) db 0                 ; (32 * 224 + 512 - 1) / 512 = 14
demoosstart:
incbin "demoos.bin"                                     ; 14 * 512 + 9728 = 16896
demoosend:

                times ((034h-2+021h)*512)-($-$$) db 0x00
hellostart:                                             ; Cluster 34h
incbin "hello.com"                                      ; hello.com                                        
helloend:


;-------------------------------------------------------
; UTILS Directory
;-------------------------------------------------------

                times ((040h-2+021h)*512)-($-$$) db 0x00
                db      ".          "                   ; Filename
                db      010h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                dw      0A232h                          ; Written Time
                dw      05277h                          ; Written Date
                dw      00040h                          ; First Cluster Address Low
                dd      0                               ; File Size

                db      "..         "                   ; Filename
                db      010h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                dw      0A232h                          ; Written Time
                dw      05277h                          ; Written Date
                dw      00000h                          ; First Cluster Address Low
                dd      0                               ; File Size

                db      "HELLO   COM"                   ; Filename
                db      020h				; Attributes
                db      0                               ; Reserved
                db      0, 0, 0, 0, 0                   ; Reserved
                db      0, 0                            ; Access Date
                dw      0                               ; First Cluster Address High
                db      0C0h, 032h, 0BFh, 01Ch          ; Written Date/Time
                dw      00034h                          ; First Cluster Address Low
                dd      helloend-hellostart             ; File Size

		

;-------------------------------------------------------
; Correct Disk Size
;-------------------------------------------------------
                times 1474560-($-$$) db 0               ; Make file size 1,474,560 bytes long
                                                        ;   This is the size of a floppy disk

		

Now lets build our disk. If you are running the previous tutorial in VirtualBox Remember to remove the disk or close virtual box before assembling the disk image.

> nasm demoos.asm -o demoos.bin
> nasm osdisk.asm -o osdisk.img
		

Your osdisk.img can now be loaded with VirtualBox. The result should be as follows:

DEMO OS V0.17

A:\> dir

 Volume in drive A is DEMO OS DSK
 Volume Serial Number is 2394-C726

DEMOOS  BIN
UTILS            <DIR>

A:\>cd utils

A:\>dir

 Volume in drive A is DEMO OS DSK
 Volume Serial Number is 2394-C726

HELLO   COM
		
Disk: Drives, FAT16 | Index

Support This Project On Patreon

Copyright © 2021 Marcus Kelly