├── .gitignore ├── demo ├── dino.gif ├── hello_world.png └── screensaver.gif ├── flash.sh ├── start.sh ├── README.md └── src ├── bootstrap.asm ├── hello_world.asm ├── screensaver.asm └── dino.asm /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | test* -------------------------------------------------------------------------------- /demo/dino.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portasynthinca3/bootsect/HEAD/demo/dino.gif -------------------------------------------------------------------------------- /demo/hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portasynthinca3/bootsect/HEAD/demo/hello_world.png -------------------------------------------------------------------------------- /demo/screensaver.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/portasynthinca3/bootsect/HEAD/demo/screensaver.gif -------------------------------------------------------------------------------- /flash.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | rm -r build 4 | mkdir build 5 | nasm -f bin -o build/app.bin src/$1.asm 6 | nasm -f bin -o build/bootstrap.bin src/bootstrap.asm 7 | cat build/bootstrap.bin build/app.bin > build/boot.bin 8 | truncate -s 1474560 build/boot.bin 9 | sudo dd if=build/boot.bin of=$2 10 | -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | rm -r build 4 | mkdir build 5 | nasm -f bin -o build/boot.bin src/$1.asm 6 | ndisasm -b 16 build/boot.bin 7 | hexdump -C build/boot.bin 8 | truncate -s 1474560 build/boot.bin 9 | 10 | qemu-system-i386 \ 11 | -name guest="bootsect",debug-threads=on \ 12 | -machine q35,accel=kvm,usb=off,vmport=off,dump-guest-core=off \ 13 | -overcommit mem-lock=off \ 14 | -fda ./build/boot.bin \ 15 | -monitor stdio \ 16 | -display sdl \ 17 | -s 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boot sector application collection 2 | A collection of simple boot sector applications written in NASM for IBM 3 | PC-compatibles. 4 | 5 | # Why? 6 | I got a little bored with my current big project and wanted to refresh my 7 | assembly knowledge too. 8 | 9 | # Usage 10 | - `./start.sh [application name]` will assemble the app and run it in QEMU 11 | - `./flash.sh [application name] [block device]` will assemble the app and the 12 | bootstrap script and flash them to the specified block device 13 | 14 | # System requirements 15 | 80386 and a VGA-compatible graphics adapter (i.e. anything that was made in the 16 | last 30 years). 17 | 18 | # Application list 19 | 20 | ## Screensaver (`screensaver`) 21 | Displays a simple animation in 320x200 256-color mode 22 | 23 | ![demo](demo/screensaver.gif) 24 | 25 | ## Dino (`dino`) 26 | Built-in Chrome dino game in 320x200 256-color mode 27 | 28 | ![demo](demo/dino.gif) 29 | 30 | ## Hello, World! (`hello_world`) 31 | Displays "Hello, World!" in the 16 VGA colors. Originally written live for a 32 | low-level introductory lecture at [undef.space](https://undef.club). 33 | 34 | ![demo](demo/hello_world.png) 35 | -------------------------------------------------------------------------------- /src/bootstrap.asm: -------------------------------------------------------------------------------- 1 | ; Bootstrap script 2 | ; It is needed because real BIOSes expect a partition table 3 | ; and there's no way I'm fitting it into the main code. 4 | ; It is only theoretically required if you want to boot this on an actual PC. 5 | 6 | org 0x7c00 7 | use16 8 | 9 | bpb: 10 | jmp reloc 11 | reloc: 12 | ; relocate the loading code to 0x7a00 13 | push ds 14 | pop es 15 | mov cx, load_app.end - load_app 16 | mov di, 0x7a00 17 | mov si, load_app 18 | cld 19 | rep movsb 20 | ; jump to relocated code 21 | jmp 0x7a00 22 | 23 | load_app: 24 | ; make a BIOS function call to load LBA 1 to 0x7c00 25 | mov ah, 02h 26 | mov al, 1 27 | xor ch, ch 28 | mov cl, 2 29 | xor dh, dh 30 | push 0 31 | pop es 32 | mov bx, 0x7c00 33 | int 13h 34 | ; jump to loaded code 35 | jmp 0x0000:0x7c00 36 | .end: 37 | 38 | times 446 - ($-$$) db 0 ; zero padding 39 | mbr: 40 | ; active CHS type CHS LBA start LBA len 41 | .entry1: db 0x80, 0,0,1, 0x7F, 0,0,2, 1,0,0,0, 1,0,0,0 42 | .entry2: db 0x00, 0,0,0, 0x00, 0,0,0, 0,0,0,0, 0,0,0,0 43 | .entry3: db 0x00, 0,0,0, 0x00, 0,0,0, 0,0,0,0, 0,0,0,0 44 | .entry4: db 0x00, 0,0,0, 0x00, 0,0,0, 0,0,0,0, 0,0,0,0 45 | dw 0xAA55 ; boot sector signature -------------------------------------------------------------------------------- /src/hello_world.asm: -------------------------------------------------------------------------------- 1 | ; Prints "Hello, World!" in the 16 VGA colors, followed by 2 | ; "It's now safe to turn off your computer" in bright red. 3 | ; 4 | ; This code is in the public domain. 5 | ; Originally written live for a low-level introductory lecture at undef.space. 6 | 7 | 8 | ; Tell the assembler that this code is going to be located at 0x7C00 in the RAM. 9 | ; It ends up there thanks to the BIOS. 10 | org 0x7C00 11 | ; Tell the assembler that it should generate instructions for the x86-16 ISA. 12 | use16 13 | 14 | ; Three macros for convenience. The assembler will substitute the following 15 | ; names with their corresponding values. 16 | %define vga_buffer 0xb8000 17 | %define vga_width 80 18 | %define vga_height 25 19 | 20 | ; Execution starts at the top irregardless of the name of this label. 21 | entry: 22 | ; Set ES = vga_buffer 23 | ; Moving immediate values into segment registers is not allowed, so we have 24 | ; to use AX temporarily. Another option is to use the stack: 25 | ; push vga_buffer / 16 26 | ; pop es 27 | mov ax, vga_buffer / 16 ; Division performed at assembly time 28 | mov es, ax 29 | 30 | ; Clear the screen 31 | ; Every character is represented using two bytes: the ASCII code of the 32 | ; character itself and its attributes (background and foreground color). 33 | mov cx, vga_width * vga_height * 2 34 | xor di, di ; XORring a reguster with itself effectively resets it to zero. 35 | ; This is a tad more efficient than a `mov' of 0 because the 36 | ; instruction is one byte shorter. 37 | xor al, al 38 | rep stosb ; Sets CX bytes starting at ES:DI with AL 39 | 40 | ; Print hello in 16 different colors 41 | xor di, di ; DI is now outside of the framebuffer, return it to zero 42 | mov cx, 15 ; Start with color number 15 (bright white) 43 | .color_loop: 44 | mov si, hello ; Our "print" function as defined below accepts the 45 | ; pointer to the string in DS:SI. 46 | mov ah, cl ; It also accepts the color in AH. CL is the lower half of 47 | ; CX. Since the value of CX starts at 15 and only goes 48 | ; down until it reaches zero, truncating the upper half is 49 | ; fine. `mov ah, cx' is not fine since a 16-bit value 50 | ; cannot be copied into an 8-bit register. 51 | call print ; Call our "print" function (defined below). 52 | add di, (vga_width - (hello_end - hello)) * 2 ; Go to the next line 53 | loop .color_loop ; This decrements CX (the color index) and jumps to 54 | ; `.color_loop' if the value is still bigger than 0. 55 | ; `loop' is effectively equivalent to: 56 | ; dec cx 57 | ; jcxnz .color_loop ; jump if cx is not zero 58 | ; or: 59 | ; dec cx 60 | ; cmp cx, 0 61 | ; jne .color_loop 62 | 63 | ; Print the final message in red 64 | mov ah, 0x0c 65 | mov si, goodbye 66 | call print 67 | 68 | ; Stop 69 | cli ; Disable interrupts by clearing (resetting) the I flag. 70 | hlt ; Halt the processor until the next interrupt. Since interrupts are 71 | ; masked (the I flag is cleared), none will occur, and thus the 72 | ; processor is halted forever. An NMI (non-maskable-interrupt) may 73 | ; still occur, and such an occurence will lead to an unexpected result: 74 | ; the processor will execute "print" and proceed to go on to some random 75 | ; junk, interpreting whatever's in the memory as code. NMIs are a very 76 | ; rare occurence, usually triggered by fatal hardware errors. If one 77 | ; does occur, there's a problem that is more significant than the CPU 78 | ; executing random data as code. 79 | ; If the reader does, however, wish to make this code more robust, I am 80 | ; leaving this as an exercise for them. 81 | 82 | ; input: 83 | ; DS:SI = input string (null-terminated) 84 | ; AH = color 85 | ; DI = position in framebuffer 86 | ; output: 87 | ; none 88 | ; clobbers: 89 | ; AL, SI, DI 90 | print: 91 | .loop: 92 | lodsb ; Reads one byte at DS:SI into AL and increments SI. 93 | stosw ; Stores AX into a word at ES:DI and increments DI by two. 94 | ; Since x86 is little endian, AL ends up in memory just before 95 | ; AH, not the other way around. The VGA card thus ends up 96 | ; interpreting the stored value of AL as the character and AH 97 | ; as its attributes. 98 | cmp al, 0 ; Compare AL with 0 99 | jne .loop ; Continue looping if not 0 100 | ret ; Return to the caller 101 | 102 | ; Here we are defining a label. Upon encountering it in the code above, the 103 | ; assembler will substitute the label with its address in memory. 104 | ; `db' injects a byte or a sequence of bytes (in this case, the ASCII 105 | ; characters encoding a string and a zero) into the instruction stream. In 106 | ; theory, nothing is stopping the CPU from interpreting data as code; in 107 | ; practice, this is prevented here by ending the program with a `cli' and a 108 | ; `hlt'. If you're curious, this is what the string looks like when decoded as a 109 | ; sequence of x86-16 instructions: 110 | ; dec ax ; 'H' 111 | ; gs insb ; 'el' 112 | ; insb ; 'l' 113 | ; outsw ; 'o' 114 | ; sub al, 0x20 ; ', ' 115 | ; push di ; 'W' 116 | ; outsw ; 'o' 117 | ; jc 0x7cab ; 'rl' 118 | ; and fs:[bx+si], ax ; 'd!', 0 119 | hello: db "Hello, World!", 0 120 | hello_end: 121 | 122 | goodbye: db "It's now safe to turn off your computer", 0 123 | 124 | ; This line pads the size of this program to 510 bytes with zeroes. 125 | ; $ means "the address of this line" 126 | ; $$ means "the starting address of this program" 127 | ; ($-$$) thus means "the size of this program so far" 128 | ; `times N' repeats a statement N times 129 | times 510-($-$$) db 0 130 | 131 | ; This line injects the bytes 55, AA into the instruction steam (remember, x86 132 | ; is little endian). The BIOS expects to see these bytes at the end of the boot 133 | ; sector that it loads as a marker that it's actually bootable. 134 | dw 0xAA55 135 | 136 | ; The assembled binary is 512 bytes long, out of which: 137 | ; - 50 bytes are taken up by code; 138 | ; - 54 bytes are taken up by the two strings and their null terminators; 139 | ; - 406 bytes are taken up by the padding; 140 | ; - 2 bytes are taken up by the executable marker. 141 | ; This source file is 6285 bytes long, which is 12 times larger than the binary. 142 | -------------------------------------------------------------------------------- /src/screensaver.asm: -------------------------------------------------------------------------------- 1 | org 0x7c00 2 | use16 3 | 4 | %define clock_ms 0x7e00 ; in RAM right after the boot sector 5 | 6 | entry: 7 | jmp 0x000:0x7c05 8 | push 0 9 | pop ds 10 | 11 | ; set video mode 13h (https://ibm.retropc.se/video/bios_video_modes.html) 12 | xor ah, ah 13 | mov al, 13h 14 | int 10h 15 | ; set fs = VGA buffer 16 | push 0xa000 17 | pop fs 18 | 19 | ; draw sky 20 | mov cx, 131 21 | xor bx, bx 22 | mov dl, 0x35 23 | call fill_portion 24 | ; draw sea 25 | mov cx, 69 26 | mov bx, 131 27 | inc dl ; dl = 0x36 28 | call fill_portion 29 | 30 | ; draw foam 31 | mov si, foam_sprite 32 | mov dh, 128 33 | mov dl, 0xf 34 | xor bx, bx 35 | mov cx, 13 36 | .foam: 37 | call draw_sprite 38 | add bx, 24 39 | loop .foam 40 | ; draw the remainder 41 | mov cl, 1 42 | mov bx, 130 43 | call fill_portion 44 | 45 | ; draw fish 46 | mov cl, byte [fish_locations] 47 | xor di, di 48 | mov dl, 0x37 49 | .fish: 50 | mov si, word [fish_locations+di+1] 51 | mov bx, word [fish_locations+di+3] 52 | mov dh, byte [fish_locations+di+5] 53 | add di, 5 54 | call draw_sprite 55 | loop .fish 56 | 57 | ; set PIT channel 0 frequency (1kHz) 58 | mov ax, 1193182 / 1000 59 | out 0x40, al 60 | mov al, ah ; inshallah 61 | out 0x40, al 62 | ; set IRQ0 handler 63 | mov word [0x0022], 0 64 | mov word [0x0020], irq0 65 | 66 | jmp $ 67 | 68 | irq0: 69 | ; advance clock 70 | inc word [clock_ms] 71 | cmp word [clock_ms], 1000 72 | jb .no_overflow 73 | mov word [clock_ms], 0 74 | .no_overflow: 75 | mov ax, word [clock_ms] 76 | 77 | ; shift top foam row right on every 250th ms 78 | cmp ax, 250 79 | jne .nostr 80 | mov bx, 128 81 | call shift_row_right 82 | .nostr: 83 | ; shift top foam row left on every 750th ms 84 | cmp ax, 750 85 | jne .nostl 86 | mov bx, 128 87 | call shift_row_left 88 | .nostl: 89 | ; shift middle and bottom rows right on every 500th ms 90 | cmp ax, 500 91 | jne .nosbr 92 | mov bx, 131 93 | call shift_row_right 94 | mov bx, 129 95 | call shift_row_right 96 | .nosbr: 97 | ; shift middle and bottom rows left on every 999th ms 98 | cmp ax, 999 99 | jne .nosbl 100 | mov bx, 131 101 | call shift_row_left 102 | mov bx, 129 103 | call shift_row_left 104 | .nosbl: 105 | 106 | ; move top fish every 50 ms 107 | push ax 108 | mov bl, 50 109 | div bl 110 | cmp ah, 0 111 | jne .notfish 112 | mov bx, 138 113 | mov cx, 11 114 | call shift_rect_left 115 | .notfish: 116 | pop ax 117 | 118 | ; move bottom fish every 70 ms 119 | mov bl, 70 120 | div bl 121 | cmp ah, 0 122 | jne .nobfish 123 | mov bx, 157 124 | mov cx, 29 125 | call shift_rect_left 126 | .nobfish: 127 | 128 | ; EOI 129 | mov al, 0x20 130 | out 0x20, al 131 | iret 132 | 133 | ;description: 134 | ; fills a rectangle with the width of 320px starting at X=0 135 | ;input: 136 | ; CX = height 137 | ; BX = Y position 138 | ; DL = color 139 | fill_portion: 140 | ; save DX 141 | push dx 142 | ; calculate end 143 | mov ax, 320 144 | add bx, cx 145 | mul bx 146 | mov di, ax 147 | ; multiply CX by 320 148 | mov ax, cx 149 | mov dx, 320 150 | mul dx 151 | mov cx, ax 152 | ; restore DX 153 | pop dx 154 | .iter: 155 | dec di 156 | mov byte [fs:di], dl 157 | loop .iter 158 | ret 159 | 160 | ;description: 161 | ; shifts a row of pixels to the right 162 | ;input: 163 | ; BX = Y position 164 | shift_row_right: 165 | ; save regs 166 | pusha 167 | ; calculate end of line 168 | mov ax, 320 169 | inc bx 170 | mul bx 171 | mov di, ax 172 | mov cx, 319 173 | .iter: 174 | dec di 175 | mov al, byte [fs:di-1] 176 | mov byte [fs:di], al 177 | loop .iter 178 | ; restore regs 179 | popa 180 | ret 181 | 182 | ;description: 183 | ; shifts a row of pixels to the left 184 | ;input: 185 | ; BX = Y position 186 | shift_row_left: 187 | ; save regs 188 | pusha 189 | ; calculate start of line 190 | mov ax, 320 191 | mul bx 192 | mov di, ax 193 | mov cx, 319 194 | .iter: 195 | mov al, byte [fs:di+1] 196 | mov byte [fs:di], al 197 | inc di 198 | loop .iter 199 | ; wrap around 200 | mov al, byte [fs:di-319] 201 | mov byte [fs:di], al 202 | ; restore regs 203 | popa 204 | ret 205 | 206 | ;description: 207 | ; shifts a rectangle to the left 208 | ;input: 209 | ; BX = Y position 210 | ; CX = height 211 | shift_rect_left: 212 | .iter: 213 | call shift_row_left 214 | inc bx 215 | loop .iter 216 | ret 217 | 218 | ;description: 219 | ; draws a sprite 220 | ;input: 221 | ; DS:SI = sprite data 222 | ; BX = X coord 223 | ; DH = Y coord 224 | ; DL = foreground color 225 | draw_sprite: 226 | ; save regs 227 | pusha 228 | ; calculate vmem offset 229 | mov al, dh 230 | mov cl, 160 ; 231 | mul cl ; multiplication by 320 232 | shl ax, 1 ; 233 | mov di, ax 234 | add di, bx 235 | ; read height into cx 236 | mov al, byte [si] 237 | movzx cx, al 238 | and cl, 0xf 239 | ; keep width in al 240 | shr al, 4 241 | inc si 242 | push ax 243 | .row: 244 | pop ax 245 | push ax 246 | .chunk: 247 | ; read chunk 248 | mov ah, byte [si] 249 | mov bp, 8 250 | .pixel: 251 | test ah, 0x80 ; test leftmost pixel 252 | jz .bg_pixel 253 | mov byte [fs:di], dl ; foreground pixel 254 | .bg_pixel: 255 | shl ah, 1 256 | inc di 257 | dec bp 258 | jnz .pixel 259 | inc si 260 | dec al 261 | jnz .chunk 262 | ; go to second row 263 | pop ax 264 | add di, 320 265 | shl al, 3 266 | xor ah, ah 267 | sub di, ax 268 | shr al, 3 269 | push ax 270 | loop .row 271 | .return: 272 | pop ax 273 | ; restore regs 274 | popa 275 | ret 276 | 277 | ; sprites 278 | foam_sprite: 279 | db (3 << 4) | (4 << 0) ; width: 3 bytes (24px), height: 4 bytes (4 px) 280 | db 00011100b, 00000000b, 00000000b 281 | db 01111110b, 00000011b, 00001110b 282 | db 11111111b, 11111111b, 11111111b 283 | db 00000000b, 00011111b, 11000000b 284 | small_fish: 285 | db (2 << 4) | (6 << 0) 286 | db 00111110b, 00001101b 287 | db 01111111b, 11100111b 288 | db 11011111b, 11111110b 289 | db 11111111b, 11111100b 290 | db 01111111b, 11111000b 291 | db 00111111b, 11100000b 292 | coralfish: 293 | db (1 << 4) | (8 << 0) 294 | db 00010000b 295 | db 00110010b 296 | db 01110110b 297 | db 10111110b 298 | db 11111110b 299 | db 01110110b 300 | db 00110010b 301 | db 00010000b 302 | bird: 303 | db (1 << 4) | (3 << 0) 304 | db 00000000b 305 | db 00000000b 306 | db 00000000b 307 | 308 | fish_locations: 309 | db 13 ; length 310 | dw small_fish, 54, 311 | db 159 312 | dw small_fish, 121, 313 | db 143 314 | dw small_fish, 252, 315 | db 140 316 | dw small_fish, 169, 317 | db 157 318 | dw small_fish, 78, 319 | db 180 320 | dw small_fish, 152, 321 | db 176 322 | dw small_fish, 300, 323 | db 178 324 | dw coralfish, 92, 325 | db 157 326 | dw coralfish, 80, 327 | db 138 328 | dw coralfish, 217, 329 | db 160 330 | dw coralfish, 233, 331 | db 173 332 | dw coralfish, 160, 333 | db 160 334 | dw coralfish, 291, 335 | db 138 336 | 337 | times 510 - ($-$$) db 0 ; useless! all the code and data above fits in 510 bytes with no space to spare 338 | dw 0xAA55 ; boot sector signature -------------------------------------------------------------------------------- /src/dino.asm: -------------------------------------------------------------------------------- 1 | org 0x7c00 2 | use16 3 | 4 | %define var_base 0x7e00 ; in RAM right after the boot sector 5 | %define var(X) [bp+(X)] 6 | 7 | %define clock_ms 0x0000 8 | %define beep_start 0x0004 9 | %define cactus_test 0x0008 10 | %define randomness 0x000A 11 | %define dino_vel 0x000C 12 | %define next_cactus 0x000D 13 | 14 | %define bg_color 0x1e 15 | %define fg_color 0x18 16 | 17 | entry: 18 | ; archos bios is bad 19 | jmp 0x000:0x7c05 20 | push 0 21 | pop ds 22 | 23 | ; set video mode 13h (https://ibm.retropc.se/video/bios_video_modes.html) 24 | mov ax, 0x13 ; also sets AH=0 as function selector 25 | int 10h 26 | ; set es = VGA buffer 27 | push 0xa000 28 | pop es 29 | 30 | ; fill screen 31 | mov cx, 320 * 200 32 | ; xor di, di ; commenting this out is a dirty hack to shave off two bytes! 33 | mov al, bg_color 34 | rep stosb 35 | 36 | ; draw ground 37 | ; top line 38 | mov di, 158 * 320 39 | mov cx, 320 40 | mov al, fg_color 41 | rep stosb 42 | ; random dots 43 | mov cx, 320*7 44 | .dot: 45 | in al, 0x40 ; read PIT channel 0 for randomness 46 | and al, 0xee 47 | jz .black_dot 48 | mov al, bg_color 49 | jmp .dot_cont 50 | .black_dot: 51 | mov al, fg_color 52 | .dot_cont: 53 | stosb 54 | loop .dot 55 | 56 | ; initialize vars 57 | mov bp, var_base 58 | ; mov dword var(clock_ms), 0 ; commenting these out is a dirty hack! 59 | ; mov byte var(dino_vel), 0 ; 60 | 61 | ; read some randomness for the cactus rate selector 62 | ; mov cx, 0xff 63 | ; .rand_iter: 64 | ; in al, 0x40 65 | ; xor byte var(randomness), al 66 | ; in al, 0x40 67 | ; xor byte var(randomness+1), al 68 | ; loop .rand_iter 69 | 70 | ; set PIT channel 2 (PC speaker) frequency 71 | mov al, 0xb6 72 | out 0x43, al 73 | mov ax, 1193182 / 250 74 | out 0x42, al 75 | mov al, ah 76 | out 0x42, al 77 | 78 | ; set PIT channel 0 frequency 79 | ; mov al, 0b00110100 80 | ; out 0x43, al 81 | mov ax, 1193182 / 700 82 | out 0x40, al 83 | mov al, ah 84 | out 0x40, al 85 | ; set IRQ0 (PIT channel 0) handler 86 | push cs 87 | pop word [0x0022] 88 | mov word [0x0020], irq0 89 | ; configure PIC IRQ0 90 | ; in al, 0x21 91 | ; and al, ~(1 << 0) 92 | ; out 0x21, al 93 | 94 | jmp $ 95 | 96 | dino_y: dw 136 ; not a %define because it needs to be initalized 97 | 98 | irq0: 99 | ; stop the sound 20ms after it started 100 | ; mov eax, dword var(clock_ms) 101 | ; sub eax, 20 102 | ; cmp eax, dword var(beep_start) 103 | ; jl .nosoundstop 104 | ; xor al, al 105 | ; out 0x61, al 106 | ; .nosoundstop: 107 | 108 | ; clear dino at old position 109 | mov si, dino_sprite 110 | mov dl, bg_color 111 | mov bx, 20 112 | mov dh, byte [dino_y] 113 | call draw_sprite 114 | 115 | ; shift cacti and ground to the left 116 | test byte var(clock_ms), 3 117 | jnz .ground_cont 118 | ; shift cacti (no wrap-around) 119 | mov cx, 33 120 | mov bx, 125 121 | clc 122 | call shift_rect_left 123 | ; shift ground (wrap around) 124 | mov cx, 8 125 | mov bx, 158 126 | stc 127 | call shift_rect_left 128 | .ground_cont: 129 | 130 | ; draw new cacti 131 | mov eax, dword var(clock_ms) 132 | cmp eax, dword var(next_cactus) 133 | jl .cactus_cont 134 | mov si, cactus_sprite 135 | mov dl, fg_color 136 | mov bx, 304 137 | mov dh, 146 138 | call draw_sprite 139 | ; choose next cactus value 140 | ; ror word var(randomness), 2 141 | xor eax, eax 142 | 143 | 144 | 145 | mov al, byte var(randomness) 146 | and al, 3 147 | or al, 4 148 | shl ax, 7 149 | ; ax = (((byte)rnd & 0b11) | 0b100) << 7 = 0b0000001X_Y0000000 150 | add eax, dword var(clock_ms) 151 | mov dword var(next_cactus), eax 152 | .cactus_cont: 153 | 154 | ; update dino position 155 | test byte var(clock_ms), 15 156 | jnz .noupd 157 | ; update pos 158 | mov al, byte var(dino_vel) 159 | sub byte [dino_y], al 160 | cmp byte [dino_y], 136 161 | jae .nodown 162 | sub byte var(dino_vel), 1 163 | jmp .noupd 164 | .nodown: 165 | mov byte [dino_y], 136 166 | .noupd: 167 | 168 | ; check keypress 169 | mov ah, 1 170 | int 16h 171 | jz .nostroke 172 | ; remove from buffer 173 | xor ah, ah 174 | int 16h 175 | ; check dino pos 176 | cmp byte [dino_y], 136 177 | jne .nostroke 178 | ; make our dino jump up and play a sound 179 | mov al, 3 180 | out 0x61, al 181 | mov byte var(dino_vel), 8 182 | mov eax, dword var(clock_ms) 183 | mov dword var(beep_start), eax 184 | .nostroke: 185 | 186 | ; draw dino at new position 187 | mov dl, fg_color 188 | mov si, dino_sprite 189 | mov bx, 20 190 | mov dh, byte [dino_y] 191 | call draw_sprite 192 | 193 | ; check collision 194 | mov bx, word [dino_y] 195 | mov ax, 320 196 | mul bx 197 | mov bx, ax 198 | mov dl, fg_color 199 | cmp byte [es:bx+(15*320)+33], dl 200 | je $ 201 | cmp byte [es:bx+(18*320)+32], dl 202 | je $ 203 | 204 | ; advance clock 205 | inc dword var(clock_ms) 206 | 207 | ; EOI 208 | mov al, 0x20 209 | out 0x20, al 210 | iret 211 | 212 | ;description: 213 | ; shifts a row of pixels to the left 214 | ;input: 215 | ; BX = Y position 216 | ; CF = whether or not to wrap around (0 = false, 1 = true) 217 | shift_row_left: 218 | ; save regs 219 | push cx 220 | pushf ; save because we need to check CF and mul below affects it 221 | ; calculate start of line 222 | mov ax, 320 223 | mul bx 224 | mov di, ax 225 | mov cx, 319 226 | ; do the thing 227 | push ds 228 | push es 229 | pop ds 230 | lea si, [di+1] 231 | rep movsb 232 | pop ds 233 | ; wrap around (restore flags prematurely to check if we actually need to) 234 | popf 235 | jnc .nowrap 236 | mov al, byte [es:di-319] 237 | mov byte [es:di], al 238 | .nowrap: 239 | ; restore regs 240 | pop cx 241 | ret 242 | 243 | ;description: 244 | ; shifts a rectangle starting at X=0 with a width of 320 to the left 245 | ;input: 246 | ; BX = Y position 247 | ; CX = height 248 | ; DX = whether or not to wrap around (0 = false, 1..255 = true) 249 | shift_rect_left: 250 | .iter: 251 | call shift_row_left 252 | inc bx 253 | loop .iter 254 | ret 255 | 256 | ;description: 257 | ; draws a sprite 258 | ;input: 259 | ; DS:SI = sprite data 260 | ; BX = X coord 261 | ; DH = Y coord 262 | ; DL = foreground color 263 | draw_sprite: 264 | ; save regs 265 | pusha 266 | ; calculate vmem offset 267 | mov ax, 160 268 | mul dh 269 | shl ax, 1 270 | mov di, ax 271 | add di, bx 272 | ; read height into cx 273 | mov dh, byte [si] 274 | movzx cx, dh 275 | and cx, 0x1f 276 | add cl, 2 277 | ; keep width in dh 278 | shr dh, 5 279 | inc si 280 | .row: 281 | push dx 282 | .chunk: 283 | ; read chunk 284 | mov ah, byte [si] 285 | mov al, 8 286 | .pixel: 287 | test ah, 0x80 ; test leftmost pixel 288 | jz .bg_pixel 289 | mov byte [es:di], dl ; foreground pixel 290 | .bg_pixel: 291 | shl ah, 1 292 | inc di 293 | dec al 294 | jnz .pixel 295 | inc si 296 | dec dh 297 | jnz .chunk 298 | ; go to second row 299 | pop dx 300 | add di, 320 301 | movzx ax, dh 302 | shl ax, 3 303 | sub di, ax 304 | loop .row 305 | .return: 306 | ; restore regs 307 | popa 308 | ret 309 | 310 | cactus_sprite: ; 11 bytes 311 | db (1 << 5) | (10 << 0) 312 | db 00001000b 313 | db 00011010b 314 | db 10011011b 315 | db 11011011b 316 | db 11011011b 317 | db 11011011b 318 | db 11011111b 319 | db 11111110b 320 | db 01111000b 321 | db 00011000b 322 | db 00011000b 323 | db 00011000b 324 | dino_sprite: ; 67 bytes! MUHHHHH BLOATT!!!!! 325 | db (3 << 5) | (20 << 0) 326 | db 00000000b, 00011111b, 11100000b 327 | db 00000000b, 00111111b, 11110000b 328 | db 00000000b, 00110111b, 11110000b 329 | db 00000000b, 00111111b, 11110000b 330 | db 00000000b, 00111111b, 11110000b 331 | db 00000000b, 00111111b, 11110000b 332 | db 00000000b, 00111110b, 00000000b 333 | db 00000000b, 00111111b, 11000000b 334 | db 10000000b, 01111100b, 00000000b 335 | db 10000001b, 11111100b, 00000000b 336 | db 11000011b, 11111111b, 00000000b 337 | db 11100111b, 11111101b, 00000000b 338 | db 11111111b, 11111100b, 00000000b 339 | db 11111111b, 11111100b, 00000000b 340 | db 01111111b, 11111000b, 00000000b 341 | db 00111111b, 11111000b, 00000000b 342 | db 00011111b, 11110000b, 00000000b 343 | db 00001111b, 11110000b, 00000000b 344 | db 00000111b, 00110000b, 00000000b 345 | db 00000110b, 00100000b, 00000000b 346 | db 00000100b, 00100000b, 00000000b 347 | db 00000110b, 00110000b, 00000000b 348 | 349 | times 510 - ($-$$) db 0 ; zero padding 350 | dw 0xAA55 ; boot sector signature --------------------------------------------------------------------------------