wok-tiny view bootmine/stuff/mine.asm @ rev 186

Add bootlife
author Pascal Bellard <pascal.bellard@slitaz.org>
date Sun Feb 04 18:02:38 2024 +0000 (3 months ago)
parents 61c76233911e
children
line source
1 bits 16
3 %if 1
4 %define UNVEIL_ON_GAME_OVER +16
5 %define CGA_DISPLAY +6
6 %define MDA_SUPPORT +74
7 %define DOS_QUIT +12
8 %define UNDO_FLAG +10
9 %define EXPLOSION +4
10 ;%define LAZY_CHECK -4 cylinder board
11 ;%define USE_RDTSC -15 need a 586+
12 %else
13 %define USE_RDTSC -15 need a 586+
14 %endif
16 cpu 8086
17 org 0x7c00
19 ;; Constants
21 ;; Boot sector size in bytes
22 %assign BootSector.Size 512
24 ;; Words in 16 bit x86 are 2 bytes
25 %assign WordSize 2
27 ;; This is the value to store in segment register to access the VGA text buffer.
28 ;; In 16 bit x86, segmented memory accesses are of the form:
29 ;;
30 ;; (segment register) * 0x10 + (offset register)
31 ;;
32 ;; The VGA text buffer is at 0xb80000, so if 0xb800 is stored in a segment
33 ;; register, then memory access instructions will be relative to the VGA text
34 ;; buffer, allowing easier access. For example, trying to access the nth byte of
35 ;; memory will *actually* access the nth byte of the text buffer.
36 %ifdef MDA_SUPPORT
37 %assign TextBuf.Seg 0xb000
38 %else
39 %assign TextBuf.Seg 0xb800
40 %endif
42 ;; Dimensions of text buffer
43 %assign TextBuf.Width 40
44 %assign TextBuf.Height 25
45 %assign TextBuf.Size (TextBuf.Width * TextBuf.Height)
47 ;; Macro to get the index of a text buffer cell from coordinates
48 %define TextBuf.Index(y, x) ((y) * TextBuf.Width * 2 + (x) * 2)
50 ;; Keyboard scan codes
51 ;; http://www.delorie.com/djgpp/doc/rbinter/it/06/0.html
52 %assign Key.ScanCode.Space 0x39
53 %assign Key.ScanCode.Up 0x48
54 %assign Key.ScanCode.Down 0x50
55 %assign Key.ScanCode.Left 0x4b
56 %assign Key.ScanCode.Right 0x4d
57 %assign Key.ScanCode.Enter 0x1c
58 %assign Key.ScanCode.QuitGame 0x01
60 ;; This is a convenience macro for creating VGA characters. VGA characters are
61 ;; 16 bit words, with the lower byte as the ASCII value and the upper byte
62 ;; holding the foreground and background colors.
63 %define VgaChar(color, ascii) (((color) << 8) | (ascii))
65 ;; VGA colors to use for game items
66 ;; https://wiki.osdev.org/Text_UI#Colours
67 %assign Color.Veiled 0x77
68 %assign Color.Unveiled 0xf0
69 %assign Color.Cursor 0x00
70 %assign Color.Flag 0xcc
71 %assign Color.GameWinText 0x20
72 %assign Color.GameOverText 0xc0
74 %ifdef MDA_SUPPORT
75 %assign Mda.InverseAttr 0x70 ; 0x[7F][08] Bit 3: High intensity.
76 %assign Mda.CursorAttr 0x09 ; 0x?[19] Bits 0-2: 1 => underline, other values => no underline.
77 %assign Mda.HiddenAttr 0x00 ; 0x[08][08] Bit 7: Blink.
78 %assign Mda.Veiled 0xdb ; 0xb1
79 %assign Mda.Flag 0x02
80 %assign Mda.Cursor 0xb1 ; 0x16
81 %assign Mda.Screen.Offset 0x8000
82 %assign Mda.Buffer 0x8000
83 %define Mda.Attr(cga, mda) (((cga) << 8) | (mda))
84 %endif
86 ;; This value is used to calculate bomb frequency. The probability that any
87 ;; given cell is a bomb is (1/2)^n, where n = "number of ones in the binary
88 ;; representation of BombFreq".
89 ;;
90 ;; In other words, when BombFreq=-1, every cell is a bomb, and appending a one
91 ;; halves the amount of bombs.
92 %assign BombFreq 0b111
93 %assign Ascii.Bomb '*'
95 ;; BootMine is supported as both a DOS game and a boot sector game :)
97 ;; Entry point: set up graphics and run game
98 BootMine:
99 cld
100 sti ;allow interrupts
102 ; VGA text mode 0x00
103 ; 320x200 pixel resolution
104 ; 40x25 text resolution
105 ; 16 colors
106 ; http://www.delorie.com/djgpp/doc/rbinter/id/74/0.html
107 xor ax, ax
108 int 0x10
110 %ifdef CGA_DISPLAY
111 ; Toggle intensity/blinking bit
112 ; http://www.techhelpmanual.com/140-int_10h_1003h__select_foreground_blink_or_bold_background.html
113 xor bx, bx ;blinking off
114 mov ds, bx
115 mov ax, 0x1003 ;toggle intensity/blinking bit (Jr, PS, TANDY 1000, EGA, VGA)
116 int 0x10
117 mov si, 0x463
118 lodsw ;get port address for 6845 video controller chip
119 add al, 4
120 xchg ax, dx
121 and byte [si], 0xdf ;mask value by 1101 1111 (to clear bit 5)
122 lodsb ;get new value of Mode Select Register
123 out dx, al ;disable blink (set for bold background)
124 %else
125 ; Disable blinking text
126 ; https://www.reddit.com/r/osdev/comments/70fcig/blinking_text/dn2t6u8?utm_source=share&utm_medium=web2x
127 ; Read I/O Address 0x03DA to reset index/data flip-flop
128 mov dx, 0x03DA
129 in al, dx
130 ; Write index 0x30 to 0x03C0 to set register index to 0x30
131 mov dl, 0xC0
132 mov al, 0x30
133 out dx, al
134 ; Read from 0x03C1 to get register contents
135 inc dx
136 in al, dx
137 ; Unset Bit 3 to disable Blink
138 and al, 0xF7
139 ; Write to 0x03C0 to update register with changed value
140 dec dx
141 out dx, al
142 %endif
144 ; Disable VGA text mode cursor
145 ; https://wiki.osdev.org/Text_Mode_Cursor#Disabling_the_Cursor
146 mov ah, 0x01
147 mov ch, 0x3f
148 int 0x10
150 ;; Run game (the game is restarted by jumping here)
151 RunGame:
152 push cs
153 pop ss
154 mov sp, TextBuf.Seg
156 ; Load VGA text buffer segment into segment registers
157 mov es, sp
158 mov ds, sp
160 %ifndef USE_RDTSC
161 ; Initialyze the simple pseudo-random number generator
162 ; seed = set_system_time()
163 mov ah, 0
164 int 0x1a
165 %endif
167 ;; Set all cells of game map to veiled '0' cells
168 ZeroTextBuf:
169 %ifdef MDA_SUPPORT
170 %ifdef UNVEIL_ON_GAME_OVER
171 call FillScreen
172 %else
173 mov ax, VgaChar(Color.Veiled, '0')
174 mov di, Mda.Screen.Offset
175 mov bx, TextBuf.Width - Mda.Screen.Offset
176 mov bp, TextBuf.Height
177 .1:
178 mov cx, TextBuf.Width
179 .2:
180 mov [cs:di + Mda.Buffer - Mda.Screen.Offset], ax
181 mov byte [bx + di], Mda.Veiled
182 stosw
183 loop .2
184 add bx, TextBuf.Width * 2
185 dec bp
186 jnz .1
187 %endif
188 %else
189 mov ax, VgaChar(Color.Veiled, '0')
190 mov cx, TextBuf.Size
191 xor di, di
192 rep stosw
193 %endif
195 ;; Populate text buffer with mines and digits
196 ;;
197 ;; This is done with a single triple-nested loop. The nested loops iterate over
198 ;; y coordinates, then x coordinates, then over the 8 adjacent cells at (y, x).
199 ;;
200 ;; Inside the 2nd loop level is bomb generation logic. Digit incrementing logic
201 ;; is in the 3rd loop level.
202 ;;
203 ;; Note that the coordinates on the outside border are skipped to avoid bounds
204 ;; checking logic.
205 PopulateTextBuf:
206 ; Iterate over y coordinates. ch = 0 form ZeroTextBuf
207 mov cl, TextBuf.Height - 2
209 .LoopY:
210 mov di, TextBuf.Width - 2
212 .LoopX:
214 ; The register al holds a boolean that is 1 if the current cell is a bomb, 0
215 ; otherwise.
216 %ifndef USE_RDTSC
217 ; It is calculated by bitwise and-ing the result of the simple pseudo-random number
218 ; generator seed = ((seed + LARGE_PRIME1) * LARGE_PRIME2) % LARGE_PRIME3
219 ;
220 ; al = ! (prng() & BombFreq)
221 %assign Prime1 32749
222 %assign Prime2 65519
223 %assign Prime3 65521
224 xchg ax, dx
225 add ax, Prime1
226 mov si, Prime2
227 mul si
228 inc si
229 inc si
230 div si
231 test dl, cl
232 %else
233 ; It is calculated by bitwise and-ing the result of rdtsc. (rdtsc returns the
234 ; amount of CPU cycles since boot, which works okay as a cheap random number
235 ; generator, and it's apparently supported on all x86 CPUs since the Pentium line)
236 ;
237 ; al = ! (rdtsc() & BombFreq)
238 cpu 686
239 rdtsc
240 cpu 8086
241 test al, BombFreq
242 %endif
244 mov ax, VgaChar(Color.Veiled, Ascii.Bomb)
246 ; If this cell isn't a bomb, then skip marking it as a bomb
247 jnz .Iterated
249 ; si = &TextBuf[y][x]
250 call GetTextBufIndex
252 ; Mark the current cell as a bomb
253 ; Iterate over adjacent cells (directions)
254 call AdjacentCells
255 .Iterated:
257 ; Decrement x coordinate loop counter and continue if nonzero
258 dec di
259 jnz .LoopX
261 ; Decrement y coordinate loop counter and continue if nonzero
262 loop .LoopY
263 ; cl and di are zeroed from the PopulateTextBuf loops above
265 ;; Done populating the text buffer
267 ; Set the initial cursor color for game loop. The dh register is now used to
268 ; store the saved cell color that the cursor is on, since the cursor
269 ; overwrites the cell color with the cursor color.
270 xchg ax, dx
272 ;; Main loop to process key presses and update state
273 GameLoop:
275 ;; Detect win (a win occurs when every veiled cell is a mine)
276 DetectWin:
277 mov bx, TextBuf.Size
278 ; if (char != Ascii.Bomb && (color == Color.Veiled || color == Color.Flag)) {
279 ; break; // Didn't win yet :(
280 ; }
281 ; Use si register as cell pointer for win detection
282 %ifdef MDA_SUPPORT
283 mov si, Mda.Buffer
284 .Loop:
285 cs lodsw
286 %else
287 xor si, si
288 .Loop:
289 lodsw
290 %endif
291 cmp al, Ascii.Bomb
292 je .Continue
293 cmp ah, Color.Unveiled
294 jb .Break
295 .Continue:
296 dec bx
297 jne .Loop
298 ; If loop completes without breaking, then we win! :)
300 ;; Show game win screen
301 ;;GameWin:
302 mov ah, Color.GameWinText
303 call GameEndHelper
304 db 'GAME WIN'
306 .Break:
307 WaitRestart:
308 ;; if bx == 0: Wait for restart key to be pressed, then restart game
309 ; Get keystroke
310 ; ah = BIOS scan code
311 ; al = ASCII character
312 ; http://www.delorie.com/djgpp/doc/rbinter/id/63/17.html
313 mov ah, 0
314 int 0x16
315 %ifdef DOS_QUIT
316 dec ah
317 je Quit
318 %endif
319 dec bx
320 js RunGame
322 ; cl = y coord
323 ; di = x coord
325 ; si = cell pointer
326 call GetTextBufIndex
327 ; Apply saved cell color
328 %ifdef MDA_SUPPORT
329 call SetColor
330 %else
331 mov byte [si + 1], dh
332 %endif
334 ; Didn't win yet
335 xchg al, ah
337 ;; Process key press. This is an if-else chain that runs code depending on the
338 ;; key pressed.
339 CmpUp:
340 ; Move cursor up
341 dec cx
342 %ifdef DOS_QUIT
343 sub al, Key.ScanCode.Up-1
344 %else
345 sub al, Key.ScanCode.Up
346 %endif
347 je WrapCursor
348 inc cx
349 CmpDown:
350 ; Move cursor down
351 inc cx
352 sub al, Key.ScanCode.Down - Key.ScanCode.Up
353 je WrapCursor
354 dec cx
355 CmpLeft:
356 ; Move cursor left
357 dec di
358 sub al, Key.ScanCode.Left - Key.ScanCode.Down
359 je WrapCursor
360 inc di
361 CmpRight:
362 ; Move cursor right
363 inc di
364 sub al, Key.ScanCode.Right - Key.ScanCode.Left
365 je WrapCursor
366 dec di
367 CmpEnter:
368 sub al, Key.ScanCode.Enter - Key.ScanCode.Right
369 jne CmpSpace
370 ; Place flag by coloring current cell
371 %ifdef MDA_SUPPORT
372 %ifdef UNDO_FLAG
373 cmp byte [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], Color.Unveiled
374 jae GameLoop
375 xor byte [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], Color.Flag ^ Color.Veiled
376 mov dx, [bp + si + Mda.Buffer - Mda.Screen.Offset]
377 %else
378 mov dh, Color.Flag
379 %endif
380 call SetColor
381 %else
382 %ifdef UNDO_FLAG
383 inc si
384 cmp byte [si], Color.Unveiled
385 jae GameLoop
386 xor byte [si], Color.Flag ^ Color.Veiled
387 mov dx, [si]
388 %else
389 mov dh, Color.Flag
390 mov [si + 1], dh
391 %endif
392 %endif
393 ; jmp GameLoop
394 CmpSpace:
395 sub al, Key.ScanCode.Space - Key.ScanCode.Enter
396 jne GameLoop
398 ;; If the player pressed space, clear the current cell
399 ClearCell:
400 %ifdef EXPLOSION
401 %ifdef MDA_SUPPORT
402 mov al, [bp + si + Mda.Buffer - Mda.Screen.Offset]
403 %else
404 mov al, [si]
405 %endif
406 cmp al, Ascii.Bomb
407 %else
408 %ifdef MDA_SUPPORT
409 cmp byte [bp + si + Mda.Buffer - Mda.Screen.Offset], Ascii.Bomb
410 %else
411 cmp byte [si], Ascii.Bomb
412 %endif
413 %endif
414 je GameOver
416 ; If cell is empty, run flood fill algorithm
417 call Flood
418 %ifdef MDA_SUPPORT
419 GetCharAndColor:
420 mov dx, [bp + si + Mda.Buffer - Mda.Screen.Offset]
421 %endif
422 jmpGameLoop:
423 jmp GameLoop
425 ;; Helper code for GameWin and GameOver; print a string in the center of the
426 ;; text buffer, then wait for game to be restarted.
427 GameEndHelper:
428 pop si
429 %ifdef MDA_SUPPORT
430 mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - (GameOverStr.End - GameOverStr) / 2) + Mda.Screen.Offset
431 %else
432 mov di, TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2 - (GameOverStr.End - GameOverStr) / 2)
433 %endif
434 xor bx, bx
435 .String:
436 cs lodsb
437 cmp al, 'Z'
438 ja WaitRestart
439 %ifdef MDA_SUPPORT
440 mov [di + TextBuf.Index(TextBuf.Height / 2, TextBuf.Width / 2) - Mda.Screen.Offset], al
441 %endif
442 stosw
443 jmp .String
445 %ifdef DOS_QUIT
446 Quit:
447 mov al,0x03 ; Restore text mode
448 int 0x10
449 int 0x20
450 int 0x19
451 %endif
453 GameOver:
454 ; If cell is bomb, game over :(
455 %ifdef EXPLOSION
456 call UnveilCell
457 %endif
459 ;; Show game over screen
461 %ifdef UNVEIL_ON_GAME_OVER
462 %ifdef MDA_SUPPORT
463 call UnveilBomb
464 %else
465 mov cx, TextBuf.Size
466 xor si, si
467 .Loop:
468 ; Load VGA character into ax
469 lodsw
470 cmp al, Ascii.Bomb
471 jne .Next
472 and byte [si-1], Color.Unveiled
473 .Next:
474 loop .Loop
475 %endif
476 %endif
477 mov ah, Color.GameOverText
478 call GameEndHelper
479 GameOverStr:
480 db 'GAME OVER'
481 GameOverStr.End:
483 ;; Set y and x coordinates of cursor to zero if they are out of bounds
484 ResetCursor:
485 xchg ax, cx
486 xor di, di
488 WrapCursor:
489 ; Get text buffer index (it changed)
490 call GetTextBufIndex
491 jae ResetCursor
493 ; Draw cursor by changing cell to the cursor color, but save current color for
494 ; restoring in the next iteration of the game loop.
495 %ifdef MDA_SUPPORT
496 mov dx, Mda.Attr(Color.Cursor, Mda.Cursor)
497 call SetScreenColor
498 jmp GetCharAndColor
499 %else
500 mov dh, Color.Cursor
501 xchg byte [si + 1], dh
502 jmp jmpGameLoop
503 %endif
505 %ifdef UNVEIL_ON_GAME_OVER
506 %ifdef MDA_SUPPORT
507 FillScreen:
508 mov cx, Mda.Veiled
509 UnveilBomb:
510 mov si, Mda.Buffer
511 mov bp, TextBuf.Height
512 mov bx, 0 - TextBuf.Width + Mda.Buffer
513 .LoopLine:
514 mov di, TextBuf.Width
515 add bx, 2*TextBuf.Width
516 .Loop:
517 mov ax, VgaChar(Color.Veiled, '0')
518 or cl, cl
519 js .Fill
520 ; Load VGA character into ax
521 mov ax,[cs:si]
522 cmp al, Ascii.Bomb
523 jne .Next
524 and ah, Color.Unveiled
525 mov cl, al
526 .Fill:
527 mov [si], ax
528 mov [cs:si], ax
529 mov [bx + si + Mda.Screen.Offset - Mda.Buffer], cl
530 .Next:
531 lodsw
532 dec di
533 jnz .Loop
534 dec bp
535 jnz .LoopLine
536 ret
537 %endif
538 %endif
540 ;; Unveil a cell so it is visible on the screen
541 ;;
542 ;; Parameters:
543 ;; * si - cell pointer in text buffer
544 ;; * al - cell ASCII value
545 ;; Returns:
546 ;; * dh - written VGA color code
547 UnveilCell:
548 ; TLDR: Use xor magic to make the cells colored.
549 ;
550 ; We have three cases to consider:
551 ;
552 ; Case 1: the cell is a digit from 1-8
553 ;
554 ; The ASCII values '1', '2', '3', ..., '8' are 0x31, 0x32, 0x33, ..., 0x38. In
555 ; other words, the upper nibble is always 0x3 and the lower nibble is the
556 ; digit. We want the VGA color code to be `Color.Unveiled | digit`. For
557 ; example, the color of a '3' cell would be 0xf3.
558 ;
559 ; We can accomplish this with the formula `cell_value ^ '0' ^ Color.Unveiled`.
560 ; Xor-ing by '0' (0x30), clears the upper nibble of the cell value, leaving
561 ; just the digit value. Xor-ing again by Color.Unveiled sets the upper nibble
562 ; to 0xf, leading to the value `Color.Unveiled | digit`.
563 ;
564 ; Since xor is associative, this can be done in one operation, by xor-ing the
565 ; cell value by ('0' ^ Color.Unveiled).
566 ;
567 ; Case 2: the cell is a bomb
568 ;
569 ; We don't really care about this case as long as the bomb is visible against
570 ; the background. The bomb turns out to be bright yellow, oh well.
571 %ifdef MDA_SUPPORT
572 mov dl, al
573 mov [si], al
574 %endif
575 xor al, '0' ^ Color.Unveiled
576 mov dh, al
577 %ifdef MDA_SUPPORT
578 SetColor:
579 mov [bp + si + Mda.Buffer - Mda.Screen.Offset + 1], dh
580 cmp dh, Color.Flag
581 ja .NotVeiled
582 mov dl, Mda.Veiled
583 jne .NotVeiled
584 mov dl, Mda.Flag
585 .NotVeiled:
586 SetScreenColor:
587 mov [bx + si], dl
588 %endif
589 mov [si + 1], dh
590 ret
592 ;; Flood fill empty cells
593 ;;
594 ;; Parameters:
595 ;; * cl - cell y coordinate
596 ;; * di - cell x coordinate
597 ;; * si - cell pointer in text buffer
598 FloodStep:
599 %ifdef MDA_SUPPORT
600 mov al, [bp + si + Mda.Buffer - Mda.Screen.Offset]
601 %else
602 mov al, [si]
603 %endif
604 cmp al, '0'
606 ; Base case: nonempty cell unveiled and stop recursion
607 ja UnveilCell
609 mov ax, VgaChar(Color.Unveiled, ' ')
611 ; Base case: we visited this cell already or bomb
612 jb Ret
614 ; Body: mark cell as visited and empty
615 %ifdef MDA_SUPPORT
616 mov [si], ax
617 mov [bx + si], al
618 AdjacentCells:
619 mov [bp + si + Mda.Buffer - Mda.Screen.Offset], ax
620 %else
621 AdjacentCells:
622 mov [si], ax
623 %endif
625 ; Recursive case: flood adjacent cells
627 ; Flood left-row
628 dec di
629 call UpAndFloodRow
631 ; Flood center-row
632 call FloodNextRow
634 ; Flood right-row
635 call FloodNextRow
637 ; re-center
638 dec cx
639 dec di
641 ;; Compute the text buffer index from y and x coordinates
642 ;;
643 ;; si = &TextBuf[cl = y][di = x]
644 ;;
645 ;; This computes the equivalent of the TextBuf.Index(y, x) macro, but at runtime
646 ;;
647 ;; Parameters:
648 ;; * cl - y coordinate
649 ;; * di - x coordinate
650 ;; Returns:
651 ;; * si - text buffer index
652 ;; * carry - x and y are valid coordinates
653 GetTextBufIndex:
654 xchg ax, si
655 mov al, TextBuf.Width * 2
656 imul cl
657 xchg ax, si
658 %ifdef MDA_SUPPORT
659 lea bx, [si + TextBuf.Width - Mda.Screen.Offset]
660 lea si, [bx + di - TextBuf.Width]
661 add si, di
662 %ifdef LAZY_CHECK
663 cmp si, TextBuf.Size * 2 + Mda.Screen.Offset
664 %endif
665 %else
666 add si, di
667 add si, di
668 %ifdef LAZY_CHECK
669 cmp si, TextBuf.Size * 2
670 %endif
671 %endif
672 %ifndef LAZY_CHECK
673 ; Base case: bounds check y
674 cmp cl, TextBuf.Height
675 jae Ret
676 ; Base case: bounds check x
677 cmp di, TextBuf.Width
678 %endif
679 Ret:
680 ret
682 FloodNextRow:
683 inc di
684 dec cx
685 UpAndFloodRow:
686 dec cx
687 FloodRow:
688 call Flood
689 call FloodDown
690 FloodDown:
691 inc cx
692 Flood:
693 ; si = &TextBuf[y][x]
694 call GetTextBufIndex
695 jae Ret
697 cmp al, Ascii.Bomb
698 jne FloodStep
699 Counter:
700 ; If adjacent cell is a bomb, skip digit incrementing
701 %ifdef MDA_SUPPORT
702 cmp [bp + si + Mda.Buffer - Mda.Screen.Offset], al
703 %else
704 cmp [si], al
705 %endif
706 je .IsMine
707 ; The adjacent cell is a 0-7 digit and not a bomb. Add 1 to the cell.
708 ; This gradually accumulates to the
709 ; amount of neighboring bombs and represents the number cells in the
710 ; minesweeper game.
711 %ifdef MDA_SUPPORT
712 inc byte [bp + si + Mda.Buffer - Mda.Screen.Offset]
713 %else
714 inc byte [si]
715 %endif
716 .IsMine:
717 ret
719 ;; Print program size at build time
720 %assign CodeSize $ - $$
721 %assign Size $ - BootMine
722 %warning Code is Size bytes
724 CodeEnd:
725 ; Pad to size of boot sector, minus the size of a word for the boot sector
726 ; magic value. If the code is too big to fit in a boot sector, the `times`
727 ; directive uses a negative value, causing a build error.
728 times (BootSector.Size - WordSize) - CodeSize db 0
730 ; Boot sector magic
731 dw 0xaa55