├── _config.yml ├── about.bin ├── boot.bin ├── test.bin ├── mykernel.bin ├── out ├── boot.bin ├── test.bin ├── about.bin ├── mykernel.bin └── floppy_disk_image │ └── floppy.img ├── rudraOS.png ├── disinf.inc ├── programs ├── test.asm └── about.asm ├── .vscode └── tasks.json ├── boot └── boot.asm ├── README.md ├── kernel └── mykernel.asm ├── bpb.asm ├── LICENSE ├── Vagrantfile ├── disk.asm └── rosasm └── rosasm-std.inc /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /about.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/about.bin -------------------------------------------------------------------------------- /boot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/boot.bin -------------------------------------------------------------------------------- /test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/test.bin -------------------------------------------------------------------------------- /mykernel.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/mykernel.bin -------------------------------------------------------------------------------- /out/boot.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/out/boot.bin -------------------------------------------------------------------------------- /out/test.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/out/test.bin -------------------------------------------------------------------------------- /rudraOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/rudraOS.png -------------------------------------------------------------------------------- /out/about.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/out/about.bin -------------------------------------------------------------------------------- /out/mykernel.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/out/mykernel.bin -------------------------------------------------------------------------------- /disinf.inc: -------------------------------------------------------------------------------- 1 | os_name db 10, 13, "Sample rOSV", 0 2 | os_kernelversion db 10, 13, "Kernel 2.0" , 0 -------------------------------------------------------------------------------- /out/floppy_disk_image/floppy.img: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RudraSwat/rudraOS/HEAD/out/floppy_disk_image/floppy.img -------------------------------------------------------------------------------- /programs/test.asm: -------------------------------------------------------------------------------- 1 | ORG 32768 2 | 3 | mov si, about 4 | call lib_print_string 5 | ret 6 | 7 | about db 13, 10, "Test", 0 8 | 9 | %include "rosasm-std.inc" 10 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Compile rOSV", 6 | "type": "shell", 7 | "command": "sudo ./build", 8 | "group": { 9 | "kind": "build", 10 | "isDefault": true 11 | } 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /programs/about.asm: -------------------------------------------------------------------------------- 1 | ORG 32768 2 | 3 | mov si, about 4 | call lib_print_string 5 | ret 6 | 7 | about db 13, 10, "rudra S 2018 is an x86 operating system built from scratch in Assembly. Licensed by Rudra Saraswat of BlueFire, Inc. This OS has a shell named tash. The OS has 1 command and one executable.", 0 8 | 9 | %include "rosasm-std.inc" 10 | -------------------------------------------------------------------------------- /boot/boot.asm: -------------------------------------------------------------------------------- 1 | BITS 16 2 | 3 | jmp short start ; Jump past disk description section 4 | nop ; Pad out before disk description 5 | 6 | %include "bpb.asm" 7 | 8 | start: 9 | mov ax, 07C0h ; Where we're loaded 10 | mov ds, ax ; Data segment 11 | 12 | mov ax, 9000h ; Set up stack 13 | mov ss, ax 14 | mov sp, 0FFFFh ; Stack grows downwards! 15 | 16 | cld ; Clear direction flag 17 | 18 | mov si, kern_filename 19 | call load_file 20 | 21 | jmp 2000h:0000h ; Jump to loaded kernel 22 | 23 | kern_filename db "MYKERNELBIN" 24 | 25 | %include "disk.asm" 26 | 27 | times 510-($-$$) db 0 ; Pad to 510 bytes with zeros 28 | dw 0AA55h ; Boot signature 29 | 30 | buffer: ; Disk buffer begins 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rudraOS 2 | ### An x86 operating system built from scratch in Assembly 3 | This is an x86 operating system built from scratch in Assembly. Licensed by Rudra Saraswat of BlueFire, Inc. 4 | This OS has a shell named tash. The OS has 1 command and a few executables. 5 | 6 | ## Minimum Build Requirements 7 | ###### RAM - 1 GB (Recommended - 2 GBs or more) 8 | ###### Hard Disk Space - 1 GB (Estimated) 9 | ###### Processor - Any x86 or x64 based processor 10 | ###### Operating System - Debian, Ubuntu (or any other Ubuntu-based distro) 11 | 12 | ## New features 13 | ###### rOSVs (rudraOS Variations) have been introduced. 14 | ###### It has an about program that has specifications of the operating system. 15 | 16 | ![Figure 1-1](rudraOS.png "rudraOS 2018") 17 | -------------------------------------------------------------------------------- /kernel/mykernel.asm: -------------------------------------------------------------------------------- 1 | mov ah, 09h 2 | mov cx, 1000h 3 | mov al, 20h 4 | mov bl, 70 5 | mov ax, 2000h 6 | mov ds, ax 7 | mov es, ax 8 | mov si, start 9 | call lib_print_string 10 | mov si, os_name 11 | call lib_print_string 12 | mov si, os_kernelversion 13 | call lib_print_string 14 | 15 | loop: 16 | mov si, prompt 17 | call lib_print_string 18 | mov si, user_input 19 | call lib_input_string 20 | 21 | cmp byte [si], 0 22 | je loop 23 | cmp word [si], "ls" 24 | je list_files 25 | 26 | mov ax, si 27 | mov cx, 32768 28 | call lib_load_file 29 | jc load_fail 30 | 31 | call 32768 32 | jmp loop 33 | 34 | load_fail: 35 | mov si, load_fail_msg 36 | call lib_print_string 37 | jmp loop 38 | 39 | list_files: 40 | mov si, file_list 41 | call lib_get_file_list 42 | call lib_print_string 43 | jmp loop 44 | 45 | start db 13, 10, "rudraOS 2018", 0 46 | prompt db 13, 10, "rudraOS2.0 $ ", 0 47 | name db 13, 10, "What is your full name? ", 0 48 | load_fail_msg db 13, 10, "404 - No such executable or command", 0 49 | user_input times 256 db 0 50 | file_list times 1024 db 0 51 | 52 | %include "rosasm-std.inc" 53 | %include "disinf.inc" 54 | -------------------------------------------------------------------------------- /bpb.asm: -------------------------------------------------------------------------------- 1 | ; ------------------------------------------------------------------ 2 | ; Disk description table, to make it a valid floppy 3 | ; Values are those used by IBM for 1.44 MB, 3.5" diskette 4 | 5 | OEMLabel db "BOOTTEST" ; Disk label 6 | BytesPerSector dw 512 ; Bytes per sector 7 | SectorsPerCluster db 1 ; Sectors per cluster 8 | ReservedForBoot dw 1 ; Reserved sectors for boot record 9 | NumberOfFats db 2 ; Number of copies of the FAT 10 | RootDirEntries dw 224 ; Number of entries in root dir 11 | ; (224 * 32 = 7168 = 14 sectors to read) 12 | LogicalSectors dw 2880 ; Number of logical sectors 13 | MediumByte db 0F0h ; Medium descriptor byte 14 | SectorsPerFat dw 9 ; Sectors per FAT 15 | SectorsPerTrack dw 18 ; Sectors per track (36/cylinder) 16 | Sides dw 2 ; Number of sides/heads 17 | HiddenSectors dd 0 ; Number of hidden sectors 18 | LargeSectors dd 0 ; Number of LBA sectors 19 | DriveNo dw 0 ; Drive No: 0 20 | Signature db 41 ; Drive signature: 41 for floppy 21 | VolumeID dd 00000000h ; Volume ID: any number 22 | VolumeLabel db "BOOTTEST "; Volume label: any 11 chars 23 | FileSystem db "FAT12 " ; File system type: don't change! 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, BlueFire Inc. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure("2") do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://vagrantcloud.com/search. 15 | config.vm.box = "lvphu609/lucid32" 16 | config.vm.box_version = "2.2.3" 17 | 18 | # Disable automatic box update checking. If you disable this, then 19 | # boxes will only be checked for updates when the user runs 20 | # `vagrant box outdated`. This is not recommended. 21 | # config.vm.box_check_update = false 22 | 23 | # Create a forwarded port mapping which allows access to a specific port 24 | # within the machine from a port on the host machine. In the example below, 25 | # accessing "localhost:8080" will access port 80 on the guest machine. 26 | # NOTE: This will enable public access to the opened port 27 | # config.vm.network "forwarded_port", guest: 80, host: 8080 28 | 29 | # Create a forwarded port mapping which allows access to a specific port 30 | # within the machine from a port on the host machine and only allow access 31 | # via 127.0.0.1 to disable public access 32 | # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" 33 | 34 | # Create a private network, which allows host-only access to the machine 35 | # using a specific IP. 36 | # config.vm.network "private_network", ip: "192.168.33.10" 37 | 38 | # Create a public network, which generally matched to bridged network. 39 | # Bridged networks make the machine appear as another physical device on 40 | # your network. 41 | # config.vm.network "public_network" 42 | 43 | # Share an additional folder to the guest VM. The first argument is 44 | # the path on the host to the actual folder. The second argument is 45 | # the path on the guest to mount the folder. And the optional third 46 | # argument is a set of non-required options. 47 | # config.vm.synced_folder "../data", "/vagrant_data" 48 | 49 | # Provider-specific configuration so you can fine-tune various 50 | # backing providers for Vagrant. These expose provider-specific options. 51 | # Example for VirtualBox: 52 | # 53 | # config.vm.provider "virtualbox" do |vb| 54 | # # Display the VirtualBox GUI when booting the machine 55 | # vb.gui = true 56 | # 57 | # # Customize the amount of memory on the VM: 58 | # vb.memory = "1024" 59 | # end 60 | # 61 | # View the documentation for the provider you are using for more 62 | # information on available options. 63 | 64 | # Enable provisioning with a shell script. Additional provisioners such as 65 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 66 | # documentation for more information about their specific syntax and use. 67 | # config.vm.provision "shell", inline: <<-SHELL 68 | # apt-get update 69 | # apt-get install -y apache2 70 | # SHELL 71 | end 72 | -------------------------------------------------------------------------------- /disk.asm: -------------------------------------------------------------------------------- 1 | 2 | load_file: 3 | mov word [kern_filename_store], si ; Save kernel filename 4 | 5 | 6 | ; First, we need to load the root directory from the disk. Technical details, 7 | ; using values from the BPB: 8 | ; 9 | ; First root sector = ReservedForBoot + NumberOfFats * SectorsPerFat = logical 19 10 | ; Number of root sectors = RootDirEntries * 32 bytes/entry / 512 bytes/sector = 14 11 | ; Start of user data = (first root sector) + (number of root sectors) = logical 33 12 | 13 | mov ax, 19 ; Root directory starts at logical sector 19 14 | call l2hts ; Calculate head, track and sector settings in 15 | ; preparation for reading from floppy disk 16 | 17 | mov si, buffer ; Set ES:BX to point to our buffer (end of boot.asm) 18 | mov bx, ds 19 | mov es, bx 20 | mov bx, si 21 | 22 | mov ah, 2 ; Parameters for BIOS int 13h: read floppy sectors 23 | mov al, 14 ; And read 14 of them 24 | 25 | pusha ; Prepare to enter loop 26 | 27 | 28 | read_root_dir: 29 | popa ; In case registers are altered by int 13h 30 | pusha 31 | 32 | stc ; Set carry flag (a few BIOSes do not set properly on error) 33 | int 13h ; Read sectors using BIOS 34 | 35 | jnc search_dir ; If read went OK, skip ahead 36 | call reset_floppy ; Otherwise, reset floppy controller and try again 37 | jnc read_root_dir ; Floppy reset OK? 38 | 39 | jmp reboot ; If not, fatal double error 40 | 41 | 42 | search_dir: 43 | popa 44 | 45 | mov ax, ds ; Root dir is now in [buffer] 46 | mov es, ax ; Set DI to this info 47 | mov di, buffer 48 | 49 | mov cx, word [RootDirEntries] ; Search all (224) entries 50 | mov ax, 0 ; Searching at offset 0 51 | 52 | 53 | next_root_entry: 54 | xchg cx, dx ; Swap registers, as we use CX in the inner loop... 55 | 56 | mov word si, [kern_filename_store] ; Start searching for kernel filename 57 | mov cx, 11 58 | rep cmpsb ; Repeat compare string byte CX times 59 | je found_file_to_load ; Pointer DI will be at offset 11 60 | 61 | add ax, 32 ; Bump searched entries by 1 (32 bytes per entry) 62 | 63 | mov di, buffer ; Point to next entry 64 | add di, ax 65 | 66 | xchg dx, cx ; Get the original CX back 67 | loop next_root_entry 68 | 69 | mov si, file_not_found ; If kernel is not found, bail out 70 | call print_string 71 | jmp reboot 72 | 73 | 74 | found_file_to_load: ; Fetch cluster and load FAT into RAM 75 | mov ax, word [es:di+0Fh] ; Offset 11 + 15 = 26, contains 1st cluster 76 | mov word [cluster], ax 77 | 78 | mov ax, 1 ; Sector 1 = first sector of first FAT 79 | call l2hts 80 | 81 | mov di, buffer ; ES:BX points to our buffer 82 | mov bx, di 83 | 84 | mov ah, 2 ; int 13h params: read (FAT) sectors 85 | mov al, 9 ; All 9 sectors of 1st FAT 86 | 87 | pusha ; Prepare to enter loop 88 | 89 | 90 | read_fat: 91 | popa ; In case registers are altered by int 13h 92 | pusha 93 | 94 | stc 95 | int 13h ; Read sectors using the BIOS 96 | 97 | jnc read_fat_ok ; If read went OK, skip ahead 98 | call reset_floppy ; Otherwise, reset floppy controller and try again 99 | jnc read_fat ; Floppy reset OK? 100 | 101 | fatal_disk_error: 102 | mov si, disk_error ; If not, print error message and reboot 103 | call print_string 104 | jmp reboot ; Fatal double error 105 | 106 | 107 | read_fat_ok: 108 | popa 109 | 110 | mov ax, 2000h ; Segment where we'll load the kernel 111 | mov es, ax 112 | mov bx, 0 113 | 114 | mov ah, 2 ; int 13h floppy read params 115 | mov al, 1 116 | 117 | push ax ; Save in case we (or int calls) lose it 118 | 119 | 120 | ; Now we must load the FAT from the disk. Here's how we find out where it starts: 121 | ; FAT cluster 0 = media descriptor = 0F0h 122 | ; FAT cluster 1 = filler cluster = 0FFh 123 | ; Cluster start = ((cluster number) - 2) * SectorsPerCluster + (start of user) 124 | ; = (cluster number) + 31 125 | 126 | load_file_sector: 127 | mov ax, word [cluster] ; Convert sector to logical 128 | add ax, 31 129 | 130 | call l2hts ; Make appropriate params for int 13h 131 | 132 | mov ax, 2000h ; Set buffer past what we've already read 133 | mov es, ax 134 | mov bx, word [pointer] 135 | 136 | pop ax ; Save in case we (or int calls) lose it 137 | push ax 138 | 139 | stc 140 | int 13h 141 | 142 | jnc calculate_next_cluster ; If there's no error... 143 | 144 | call reset_floppy ; Otherwise, reset floppy and retry 145 | jmp load_file_sector 146 | 147 | 148 | ; In the FAT, cluster values are stored in 12 bits, so we have to 149 | ; do a bit of maths to work out whether we're dealing with a byte 150 | ; and 4 bits of the next byte -- or the last 4 bits of one byte 151 | ; and then the subsequent byte! 152 | 153 | calculate_next_cluster: 154 | mov ax, [cluster] 155 | mov dx, 0 156 | mov bx, 3 157 | mul bx 158 | mov bx, 2 159 | div bx ; DX = [cluster] mod 2 160 | mov si, buffer 161 | add si, ax ; AX = word in FAT for the 12 bit entry 162 | mov ax, word [ds:si] 163 | 164 | or dx, dx ; If DX = 0 [cluster] is even; if DX = 1 then it's odd 165 | 166 | jz even ; If [cluster] is even, drop last 4 bits of word 167 | ; with next cluster; if odd, drop first 4 bits 168 | 169 | odd: 170 | shr ax, 4 ; Shift out first 4 bits (they belong to another entry) 171 | jmp short next_cluster_cont 172 | 173 | 174 | even: 175 | and ax, 0FFFh ; Mask out final 4 bits 176 | 177 | 178 | next_cluster_cont: 179 | mov word [cluster], ax ; Store cluster 180 | 181 | cmp ax, 0FF8h ; FF8h = end of file marker in FAT12 182 | jae end 183 | 184 | add word [pointer], 512 ; Increase buffer pointer 1 sector length 185 | jmp load_file_sector 186 | 187 | 188 | end: ; We've got the file to load! 189 | pop ax ; Clean up the stack (AX was pushed earlier) 190 | mov dl, byte [bootdev] ; Provide kernel with boot device info 191 | 192 | jmp 2000h:0000h ; Jump to entry point of loaded kernel! 193 | 194 | 195 | ; ------------------------------------------------------------------ 196 | ; BOOTLOADER SUBROUTINES 197 | 198 | reboot: 199 | mov ax, 0 200 | int 16h ; Wait for keystroke 201 | mov ax, 0 202 | int 19h ; Reboot the system 203 | 204 | 205 | print_string: ; Output string in SI to screen 206 | pusha 207 | 208 | mov ah, 0Eh ; int 10h teletype function 209 | 210 | .repeat: 211 | lodsb ; Get char from string 212 | cmp al, 0 213 | je .done ; If char is zero, end of string 214 | int 10h ; Otherwise, print it 215 | jmp short .repeat 216 | 217 | .done: 218 | popa 219 | ret 220 | 221 | 222 | reset_floppy: ; IN: [bootdev] = boot device; OUT: carry set on error 223 | push ax 224 | push dx 225 | mov ax, 0 226 | mov dl, byte [bootdev] 227 | stc 228 | int 13h 229 | pop dx 230 | pop ax 231 | ret 232 | 233 | 234 | l2hts: ; Calculate head, track and sector settings for int 13h 235 | ; IN: logical sector in AX, OUT: correct registers for int 13h 236 | push bx 237 | push ax 238 | 239 | mov bx, ax ; Save logical sector 240 | 241 | mov dx, 0 ; First the sector 242 | div word [SectorsPerTrack] 243 | add dl, 01h ; Physical sectors start at 1 244 | mov cl, dl ; Sectors belong in CL for int 13h 245 | mov ax, bx 246 | 247 | mov dx, 0 ; Now calculate the head 248 | div word [SectorsPerTrack] 249 | mov dx, 0 250 | div word [Sides] 251 | mov dh, dl ; Head/side 252 | mov ch, al ; Track 253 | 254 | pop ax 255 | pop bx 256 | 257 | mov dl, byte [bootdev] ; Set correct device 258 | 259 | ret 260 | 261 | 262 | ; ------------------------------------------------------------------ 263 | ; STRINGS AND VARIABLES 264 | 265 | kern_filename_store dw 0 266 | 267 | disk_error db "Floppy error! Press any key...", 0 268 | file_not_found db "Kernel not found!", 0 269 | 270 | bootdev db 0 ; Boot device number 271 | cluster dw 0 ; Cluster of the file we want to load 272 | pointer dw 0 ; Pointer into Buffer, for loading kernel 273 | 274 | -------------------------------------------------------------------------------- /rosasm/rosasm-std.inc: -------------------------------------------------------------------------------- 1 | ; ------------------------------------------------------------------ 2 | ; lib_print_string -- Displays text 3 | ; IN: SI = message location (zero-terminated string) 4 | ; OUT: Nothing (registers preserved) 5 | 6 | lib_print_string: 7 | pusha 8 | 9 | mov ah, 0Eh ; int 10h teletype function 10 | 11 | .repeat: 12 | lodsb ; Get char from string 13 | cmp al, 0 14 | je .done ; If char is zero, end of string 15 | 16 | int 10h ; Otherwise, print it 17 | jmp .repeat ; And move on to next char 18 | 19 | .done: 20 | popa 21 | ret 22 | 23 | 24 | ; ------------------------------------------------------------------ 25 | ; lib_input_string -- Take string from keyboard entry 26 | ; IN/OUT: SI = location of string, other regs preserved 27 | ; (Location will contain up to 255 characters, zero-terminated) 28 | 29 | lib_input_string: 30 | pusha 31 | 32 | mov di, si ; DI is where we'll store input (buffer) 33 | mov cx, 0 ; Character received counter for backspace 34 | 35 | 36 | .more: ; Now onto string getting 37 | mov ax, 0 38 | mov ah, 10h ; BIOS call to wait for key 39 | int 16h 40 | 41 | cmp al, 13 ; If Enter key pressed, finish 42 | je .done 43 | 44 | cmp al, 8 ; Backspace pressed? 45 | je .backspace ; If not, skip following checks 46 | 47 | cmp al, ' ' ; In ASCII range (32 - 126)? 48 | jb .more ; Ignore most non-printing characters 49 | 50 | cmp al, '~' 51 | ja .more 52 | 53 | jmp .nobackspace 54 | 55 | 56 | .backspace: 57 | cmp cx, 0 ; Backspace at start of string? 58 | je .more ; Ignore it if so 59 | 60 | call lib_get_cursor_pos ; Backspace at start of screen line? 61 | cmp dl, 0 62 | je .backspace_linestart 63 | 64 | pusha 65 | mov ah, 0Eh ; If not, write space and move cursor back 66 | mov al, 8 67 | int 10h ; Backspace twice, to clear space 68 | mov al, 32 69 | int 10h 70 | mov al, 8 71 | int 10h 72 | popa 73 | 74 | dec di ; Character position will be overwritten by new 75 | ; character or terminator at end 76 | 77 | dec cx ; Step back counter 78 | 79 | jmp .more 80 | 81 | 82 | .backspace_linestart: 83 | dec dh ; Jump back to end of previous line 84 | mov dl, 79 85 | call lib_move_cursor 86 | 87 | mov al, ' ' ; Print space there 88 | mov ah, 0Eh 89 | int 10h 90 | 91 | mov dl, 79 ; And jump back before the space 92 | call lib_move_cursor 93 | 94 | dec di ; Step back position in string 95 | dec cx ; Step back counter 96 | 97 | jmp .more 98 | 99 | 100 | .nobackspace: 101 | pusha 102 | mov ah, 0Eh ; Output entered, printable character 103 | int 10h 104 | popa 105 | 106 | stosb ; Store character in designated buffer 107 | inc cx ; Characters processed += 1 108 | cmp cx, 254 ; Make sure we don't exhaust buffer 109 | jae near .done 110 | 111 | jmp near .more ; Still room for more 112 | 113 | 114 | .done: 115 | mov ax, 0 ; Zero-terminate string 116 | stosb 117 | 118 | mov ah, 0Eh ; Move to next line 119 | mov al, 13 120 | int 10h 121 | mov al, 10 122 | int 10h 123 | 124 | popa 125 | ret 126 | 127 | 128 | ; ------------------------------------------------------------------ 129 | ; lib_move_cursor -- Moves cursor in text mode 130 | ; IN: DH, DL = row, column; OUT: Nothing (registers preserved) 131 | 132 | lib_move_cursor: 133 | pusha 134 | 135 | mov bh, 0 136 | mov ah, 2 137 | int 10h ; BIOS interrupt to move cursor 138 | 139 | popa 140 | ret 141 | 142 | 143 | ; ------------------------------------------------------------------ 144 | ; lib_get_cursor_pos -- Return position of text cursor 145 | ; OUT: DH, DL = row, column 146 | 147 | lib_get_cursor_pos: 148 | pusha 149 | 150 | mov bh, 0 151 | mov ah, 3 152 | int 10h ; BIOS interrupt to get cursor position 153 | 154 | mov [.tmp], dx 155 | popa 156 | mov dx, [.tmp] 157 | ret 158 | 159 | 160 | .tmp dw 0 161 | 162 | 163 | ; ------------------------------------------------------------------ 164 | ; lib_string_uppercase -- Convert zero-terminated string to upper case 165 | ; IN/OUT: AX = string location 166 | 167 | lib_string_uppercase: 168 | pusha 169 | 170 | mov si, ax ; Use SI to access string 171 | 172 | .more: 173 | cmp byte [si], 0 ; Zero-termination of string? 174 | je .done ; If so, quit 175 | 176 | cmp byte [si], 'a' ; In the lower case A to Z range? 177 | jb .noatoz 178 | cmp byte [si], 'z' 179 | ja .noatoz 180 | 181 | sub byte [si], 20h ; If so, convert input char to upper case 182 | 183 | inc si 184 | jmp .more 185 | 186 | .noatoz: 187 | inc si 188 | jmp .more 189 | 190 | .done: 191 | popa 192 | ret 193 | 194 | 195 | ; ------------------------------------------------------------------ 196 | ; lib_string_length -- Return length of a string 197 | ; IN: AX = string location 198 | ; OUT AX = length (other regs preserved) 199 | 200 | lib_string_length: 201 | pusha 202 | 203 | mov bx, ax ; Move location of string to BX 204 | 205 | mov cx, 0 ; Counter 206 | 207 | .more: 208 | cmp byte [bx], 0 ; Zero (end of string) yet? 209 | je .done 210 | inc bx ; If not, keep adding 211 | inc cx 212 | jmp .more 213 | 214 | 215 | .done: 216 | mov word [.tmp_counter], cx ; Store count before restoring other registers 217 | popa 218 | 219 | mov ax, [.tmp_counter] ; Put count back into AX before returning 220 | ret 221 | 222 | 223 | .tmp_counter dw 0 224 | 225 | 226 | ; ------------------------------------------------------------------ 227 | ; lib_string_compare -- See if two strings match 228 | ; IN: SI = string one, DI = string two 229 | ; OUT: carry set if same, clear if different 230 | 231 | lib_string_compare: 232 | pusha 233 | 234 | .more: 235 | mov al, [si] ; Retrieve string contents 236 | mov bl, [di] 237 | 238 | cmp al, bl ; Compare characters at current location 239 | jne .not_same 240 | 241 | cmp al, 0 ; End of first string? Must also be end of second 242 | je .terminated 243 | 244 | inc si 245 | inc di 246 | jmp .more 247 | 248 | 249 | .not_same: ; If unequal lengths with same beginning, the byte 250 | popa ; comparison fails at shortest string terminator 251 | clc ; Clear carry flag 252 | ret 253 | 254 | 255 | .terminated: ; Both strings terminated at the same position 256 | popa 257 | stc ; Set carry flag 258 | ret 259 | 260 | 261 | ; ------------------------------------------------------------------ 262 | ; lib_get_file_list -- Generate comma-separated string of files on floppy 263 | ; IN/OUT: SI = location to store zero-terminated filename string 264 | 265 | lib_get_file_list: 266 | pusha 267 | 268 | mov word [.file_list_tmp], si 269 | 270 | mov eax, 0 ; Needed for some older BIOSes 271 | 272 | call disk_reset_floppy ; Just in case disk was changed 273 | 274 | mov ax, 19 ; Root dir starts at logical sector 19 275 | call disk_convert_l2hts 276 | 277 | mov si, disk_buffer ; ES:BX should point to our buffer 278 | mov bx, si 279 | 280 | mov ah, 2 ; Params for int 13h: read floppy sectors 281 | mov al, 14 ; And read 14 of them 282 | 283 | pusha ; Prepare to enter loop 284 | 285 | 286 | .read_root_dir: 287 | popa 288 | pusha 289 | 290 | stc 291 | int 13h ; Read sectors 292 | call disk_reset_floppy ; Check we've read them OK 293 | jnc .show_dir_init ; No errors, continue 294 | 295 | call disk_reset_floppy ; Error = reset controller and try again 296 | jnc .read_root_dir 297 | jmp .done ; Double error, exit 'dir' routine 298 | 299 | .show_dir_init: 300 | popa 301 | 302 | mov ax, 0 303 | mov si, disk_buffer ; Data reader from start of filenames 304 | 305 | mov word di, [.file_list_tmp] ; Name destination buffer 306 | 307 | 308 | .start_entry: 309 | mov al, [si+11] ; File attributes for entry 310 | cmp al, 0Fh ; Windows marker, skip it 311 | je .skip 312 | 313 | test al, 18h ; Is this a directory entry or volume label? 314 | jnz .skip ; Yes, ignore it 315 | 316 | mov al, [si] 317 | cmp al, 229 ; If we read 229 = deleted filename 318 | je .skip 319 | 320 | cmp al, 0 ; 1st byte = entry never used 321 | je .done 322 | 323 | 324 | mov cx, 1 ; Set char counter 325 | mov dx, si ; Beginning of possible entry 326 | 327 | .testdirentry: 328 | inc si 329 | mov al, [si] ; Test for most unusable characters 330 | cmp al, ' ' ; Windows sometimes puts 0 (UTF-8) or 0FFh 331 | jl .nxtdirentry 332 | cmp al, '~' 333 | ja .nxtdirentry 334 | 335 | inc cx 336 | cmp cx, 11 ; Done 11 char filename? 337 | je .gotfilename 338 | jmp .testdirentry 339 | 340 | 341 | .gotfilename: ; Got a filename that passes testing 342 | mov si, dx ; DX = where getting string 343 | 344 | mov cx, 0 345 | .loopy: 346 | mov byte al, [si] 347 | cmp al, ' ' 348 | je .ignore_space 349 | mov byte [di], al 350 | inc si 351 | inc di 352 | inc cx 353 | cmp cx, 8 354 | je .add_dot 355 | cmp cx, 11 356 | je .done_copy 357 | jmp .loopy 358 | 359 | .ignore_space: 360 | inc si 361 | inc cx 362 | cmp cx, 8 363 | je .add_dot 364 | jmp .loopy 365 | 366 | .add_dot: 367 | mov byte [di], '.' 368 | inc di 369 | jmp .loopy 370 | 371 | .done_copy: 372 | mov byte [di], ',' ; Use comma to separate filenames 373 | inc di 374 | 375 | .nxtdirentry: 376 | mov si, dx ; Start of entry, pretend to skip to next 377 | 378 | .skip: 379 | add si, 32 ; Shift to next 32 bytes (next filename) 380 | jmp .start_entry 381 | 382 | 383 | .done: 384 | dec di 385 | mov byte [di], 0 ; Zero-terminate string (gets rid of final comma) 386 | 387 | popa 388 | ret 389 | 390 | 391 | .file_list_tmp dw 0 392 | 393 | 394 | ; ------------------------------------------------------------------ 395 | ; lib_load_file -- Load file into RAM 396 | ; IN: AX = location of filename, CX = location in RAM to load file 397 | ; OUT: BX = file size (in bytes), carry set if file not found 398 | 399 | lib_load_file: 400 | call lib_string_uppercase 401 | call disk_filename_convert 402 | jc .root_problem 403 | 404 | mov [.filename_loc], ax ; Store filename location 405 | mov [.load_position], cx ; And where to load the file! 406 | 407 | mov eax, 0 ; Needed for some older BIOSes 408 | 409 | call disk_reset_floppy ; In case floppy has been changed 410 | jnc .floppy_ok ; Did the floppy reset OK? 411 | 412 | mov si, .err_msg_floppy_reset ; If not, bail out 413 | call lib_print_string 414 | jmp $ 415 | 416 | 417 | .floppy_ok: ; Ready to read first block of data 418 | mov ax, 19 ; Root dir starts at logical sector 19 419 | call disk_convert_l2hts 420 | 421 | mov si, disk_buffer ; ES:BX should point to our buffer 422 | mov bx, si 423 | 424 | mov ah, 2 ; Params for int 13h: read floppy sectors 425 | mov al, 14 ; 14 root directory sectors 426 | 427 | pusha ; Prepare to enter loop 428 | 429 | 430 | .read_root_dir: 431 | popa 432 | pusha 433 | 434 | stc ; A few BIOSes clear, but don't set properly 435 | int 13h ; Read sectors 436 | jnc .search_root_dir ; No errors = continue 437 | 438 | call disk_reset_floppy ; Problem = reset controller and try again 439 | jnc .read_root_dir 440 | 441 | popa 442 | jmp .root_problem ; Double error = exit 443 | 444 | .search_root_dir: 445 | popa 446 | 447 | mov cx, word 224 ; Search all entries in root dir 448 | mov bx, -32 ; Begin searching at offset 0 in root dir 449 | 450 | .next_root_entry: 451 | add bx, 32 ; Bump searched entries by 1 (offset + 32 bytes) 452 | mov di, disk_buffer ; Point root dir at next entry 453 | add di, bx 454 | 455 | mov al, [di] ; First character of name 456 | 457 | cmp al, 0 ; Last file name already checked? 458 | je .root_problem 459 | 460 | cmp al, 229 ; Was this file deleted? 461 | je .next_root_entry ; If yes, skip it 462 | 463 | mov al, [di+11] ; Get the attribute byte 464 | 465 | cmp al, 0Fh ; Is this a special Windows entry? 466 | je .next_root_entry 467 | 468 | test al, 18h ; Is this a directory entry or volume label? 469 | jnz .next_root_entry 470 | 471 | mov byte [di+11], 0 ; Add a terminator to directory name entry 472 | 473 | mov ax, di ; Convert root buffer name to upper case 474 | call lib_string_uppercase 475 | 476 | mov si, [.filename_loc] ; DS:SI = location of filename to load 477 | 478 | call lib_string_compare ; Current entry same as requested? 479 | jc .found_file_to_load 480 | 481 | loop .next_root_entry 482 | 483 | .root_problem: 484 | mov bx, 0 ; If file not found or major disk error, 485 | stc ; return with size = 0 and carry set 486 | ret 487 | 488 | 489 | .found_file_to_load: ; Now fetch cluster and load FAT into RAM 490 | mov ax, [di+28] ; Store file size to return to calling routine 491 | mov word [.file_size], ax 492 | 493 | cmp ax, 0 ; If the file size is zero, don't bother trying 494 | je .end ; to read more clusters 495 | 496 | mov ax, [di+26] ; Now fetch cluster and load FAT into RAM 497 | mov word [.cluster], ax 498 | 499 | mov ax, 1 ; Sector 1 = first sector of first FAT 500 | call disk_convert_l2hts 501 | 502 | mov di, disk_buffer ; ES:BX points to our buffer 503 | mov bx, di 504 | 505 | mov ah, 2 ; int 13h params: read sectors 506 | mov al, 9 ; And read 9 of them 507 | 508 | pusha 509 | 510 | .read_fat: 511 | popa ; In case registers altered by int 13h 512 | pusha 513 | 514 | stc 515 | int 13h 516 | jnc .read_fat_ok 517 | 518 | call disk_reset_floppy 519 | jnc .read_fat 520 | 521 | popa 522 | jmp .root_problem 523 | 524 | 525 | .read_fat_ok: 526 | popa 527 | 528 | 529 | .load_file_sector: 530 | mov ax, word [.cluster] ; Convert sector to logical 531 | add ax, 31 532 | 533 | call disk_convert_l2hts ; Make appropriate params for int 13h 534 | 535 | mov bx, [.load_position] 536 | 537 | 538 | mov ah, 02 ; AH = read sectors, AL = just read 1 539 | mov al, 01 540 | 541 | stc 542 | int 13h 543 | jnc .calculate_next_cluster ; If there's no error... 544 | 545 | call disk_reset_floppy ; Otherwise, reset floppy and retry 546 | jnc .load_file_sector 547 | 548 | mov si, .err_msg_floppy_reset ; Reset failed, bail out 549 | call lib_print_string 550 | jmp $ 551 | 552 | 553 | .calculate_next_cluster: 554 | mov ax, [.cluster] 555 | mov bx, 3 556 | mul bx 557 | mov bx, 2 558 | div bx ; DX = [CLUSTER] mod 2 559 | mov si, disk_buffer ; AX = word in FAT for the 12 bits 560 | add si, ax 561 | mov ax, word [ds:si] 562 | 563 | or dx, dx ; If DX = 0 [CLUSTER] = even, if DX = 1 then odd 564 | 565 | jz .even ; If [CLUSTER] = even, drop last 4 bits of word 566 | ; with next cluster; if odd, drop first 4 bits 567 | 568 | .odd: 569 | shr ax, 4 ; Shift out first 4 bits (belong to another entry) 570 | jmp .calculate_cluster_cont ; Onto next sector! 571 | 572 | .even: 573 | and ax, 0FFFh ; Mask out top (last) 4 bits 574 | 575 | .calculate_cluster_cont: 576 | mov word [.cluster], ax ; Store cluster 577 | 578 | cmp ax, 0FF8h 579 | jae .end 580 | 581 | add word [.load_position], 512 582 | jmp .load_file_sector ; Onto next sector! 583 | 584 | 585 | .end: 586 | mov bx, [.file_size] ; Get file size to pass back in BX 587 | clc ; Carry clear = good load 588 | ret 589 | 590 | 591 | .bootd db 0 ; Boot device number 592 | .cluster dw 0 ; Cluster of the file we want to load 593 | .pointer dw 0 ; Pointer into disk_buffer, for loading 'file2load' 594 | 595 | .filename_loc dw 0 ; Temporary store of filename location 596 | .load_position dw 0 ; Where we'll load the file 597 | .file_size dw 0 ; Size of the file 598 | 599 | .string_buff times 12 db 0 ; For size (integer) printing 600 | 601 | .err_msg_floppy_reset db 'lib_load_file: Floppy failed to reset', 0 602 | 603 | 604 | ; ================================================================== 605 | ; INTERNAL DISK ROUTINES 606 | ; ================================================================== 607 | ; ------------------------------------------------------------------ 608 | ; disk_filename_convert -- Change 'TEST.BIN' into 'TEST BIN' as per FAT12 609 | ; IN: AX = filename string 610 | ; OUT: AX = location of converted string (carry set if invalid) 611 | 612 | disk_filename_convert: 613 | pusha 614 | 615 | mov si, ax 616 | 617 | call lib_string_length 618 | cmp ax, 12 ; Filename too long? 619 | jg .failure ; Fail if so 620 | 621 | cmp ax, 0 622 | je .failure ; Similarly, fail if zero-char string 623 | 624 | mov dx, ax ; Store string length for now 625 | 626 | mov di, .dest_string 627 | 628 | mov cx, 0 629 | .copy_loop: 630 | lodsb 631 | cmp al, '.' 632 | je .extension_found 633 | stosb 634 | inc cx 635 | cmp cx, dx 636 | jg .failure ; No extension found = wrong 637 | jmp .copy_loop 638 | 639 | .extension_found: 640 | cmp cx, 0 641 | je .failure ; Fail if extension dot is first char 642 | 643 | cmp cx, 8 644 | je .do_extension ; Skip spaces if first bit is 8 chars 645 | 646 | ; Now it's time to pad out the rest of the first part of the filename 647 | ; with spaces, if necessary 648 | 649 | .add_spaces: 650 | mov byte [di], ' ' 651 | inc di 652 | inc cx 653 | cmp cx, 8 654 | jl .add_spaces 655 | 656 | ; Finally, copy over the extension 657 | .do_extension: 658 | lodsb ; 3 characters 659 | cmp al, 0 660 | je .failure 661 | stosb 662 | lodsb 663 | cmp al, 0 664 | je .failure 665 | stosb 666 | lodsb 667 | cmp al, 0 668 | je .failure 669 | stosb 670 | 671 | mov byte [di], 0 ; Zero-terminate filename 672 | 673 | popa 674 | mov ax, .dest_string 675 | clc ; Clear carry for success 676 | ret 677 | 678 | 679 | .failure: 680 | popa 681 | stc ; Set carry for failure 682 | ret 683 | 684 | 685 | .dest_string times 14 db 0 686 | 687 | 688 | ; -------------------------------------------------------------------------- 689 | ; Reset floppy disk 690 | 691 | disk_reset_floppy: 692 | push ax 693 | push dx 694 | mov ax, 0 695 | mov dl, [bootdev] 696 | stc 697 | int 13h 698 | pop dx 699 | pop ax 700 | ret 701 | 702 | 703 | ; -------------------------------------------------------------------------- 704 | ; disk_convert_l2hts -- Calculate head, track and sector for int 13h 705 | ; IN: logical sector in AX; OUT: correct registers for int 13h 706 | 707 | disk_convert_l2hts: 708 | push bx 709 | push ax 710 | 711 | mov bx, ax ; Save logical sector 712 | 713 | mov dx, 0 ; First the sector 714 | div word [SecsPerTrack] ; Sectors per track 715 | add dl, 01h ; Physical sectors start at 1 716 | mov cl, dl ; Sectors belong in CL for int 13h 717 | mov ax, bx 718 | 719 | mov dx, 0 ; Now calculate the head 720 | div word [SecsPerTrack] ; Sectors per track 721 | mov dx, 0 722 | div word [Sides] ; Floppy sides 723 | mov dh, dl ; Head/side 724 | mov ch, al ; Track 725 | 726 | pop ax 727 | pop bx 728 | 729 | mov dl, [bootdev] ; Set correct device 730 | ret 731 | 732 | Sides dw 2 733 | SecsPerTrack dw 18 734 | bootdev db 0 ; Boot device number 735 | 736 | disk_buffer equ 24576 737 | 738 | --------------------------------------------------------------------------------