-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbios.asm
107 lines (106 loc) · 7.86 KB
/
bios.asm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
%ifndef QEMU
%ifndef V86
%error must select either QEMU or V86, or both
%endif
%endif
mov ah, 0xA0 ; set AX to start of screen buffer segment
mov ds, ax ; make DS point to screen buffer
mov es, ax ; same for ES
mov dx, 0x3C0 ; port 0x3C0 writes to the attribute address register
%ifdef V86
mov al, 0xF4 ; enable keyboard command
out 0x60, al ; sends the command to the i8042 controller
mov al, 0x7 ; use 0x7 both to choose text color and the DAC corresponding to it
out dx, al ; choose 0x7 ("white on black") text color
out dx, al ; set it to DAC index 7, in this case, black (this inverts colors, but it's OK)
%endif
%ifdef QEMU
mov al, 0x60 ; send 0x60 to the 8042 controller, and use later to set pallete address source bit
out 0x64, al ; command 0x60 write byte to controller configuration at byte 0
out 0x60, al ; write byte 0x60, disables internal clock
out dx, al ; "lock" color palette by setting the palette address source bit to 1 (the 0x40 is being ignored), necessary to initiate video
mov dl, 0xC4 ; port 0x3C4 writes to the sequencer registers
mov ax, 0x302 ; set the value of sequencer register 2 (the map mask register) to 3
out dx, ax ; enable DMA for the VGA segment
mov dl, 0xCE ; port 0x3CE writes to the graphics registers
mov ax, 0x1005 ; set the value of graphics register 5 (graphics mode register) to 0x10
out dx, ax ; store characters as color-value pairs, not with two matrices
mov ax, 0xFF08 ; set the value of graphics register 8 (byte mask) to 0xFF
out dx, ax ; don't mask the bytes when writing
%endif
%ifdef V86
mov dl, 0xC9 ; port 0x3C9 writes to the DAC data register
mov al, 0x1F ; store a 0x1F byte in the first DAC entry - not needed because we can use old AL but this looks better
times 3 out dx, al ; set rgb value of background to grey (rgb #1f1f1f)
%endif
mov dl, 0xB4 ; port 0x3B4 writes to the CRTC registers
mov ax, 0x2701 ; set the value of CRTC register 1 (horizontal display end) to 0x27
out dx, ax ; set the char count in each row to 0x27+1 i.e. 40
xchg si, ax ; arbitrary pointer to memory location where the initial position of the snake head is stored
mov ax, 0x4807 ; set the value of CTRC register 7 (the overflow register) to 0x48
out dx, ax ; setting bit 6 (0x40) sets vertical display end register's bit 9 to 1 which allows us not to set it, setting bit 3 (0x8) sets bit 8 of register index 0x15 (which we set for V86)
%ifdef V86
mov al, 0x2 ; write 0x48 into register index 0x02 (start horizontal blancking register)
out dx, ax ; disable blancking as 0x48 must be above the character clocks of a scan line as it's above the character clocks for the display
mov ax, 0x9015 ; write 0x190 into register index 0x15 (start vertical blanking register), the set 8 bit comes from the overflow register (index 0x07)
out dx, ax ; set display height to 0x10 (character height) times 25 lines
%endif
mov ax, 0xF09 ; set the value of CTRC register 9 (the minimum scan line register) to 0xF
out dx, ax ; set character height to 0xF+1 i.e. 16px
mov ch, 0x3B ; override initial CX so that in initial screen clearing the entire buffer will be cleared
start: ; reset game
mov ax, 0x720 ; fill the screen with word 0x720 (white on black space)
add ch, 0x5 ; add 0x500 to initial CX (0xFFFF) to write 0x4FF words (a little more then the screen)
xor di, di ; start writing at the start of the screen
rep stosw ; clear the screen
dec cx ; set CX to 0xFFFF again
mov di, [bx] ; reset head position, BX always points to a valid screen position containing 0x720 after setting video mode
lea sp, [bp+si] ; set stack pointer (tail) to current head pointer
.food: ; create new food item
%ifdef V86
push di ; save old DI before overwriting for randomization
.rand: ; lots of code to randomize food positions is better than initializing the PIT chip
xchg di, bx ; alternate BX between head position (not to iterate over the same food locations) and the end of the screen
dec bh ; decreasing BH for randomization ensures BX is still divisble by 2 and if the snake isn't filling all the possible options, below 0x7D0
xor [bx], cl ; place food item and check if position was empty by applying XOR with CL (assumed to be 0xFF)
jp .rand ; if position was occupied by snake or wall in food generation => try again, if we came from main loop PF=0
pop di ; restore actual head position
%else
in ax, 0x40 ; read 16 bit timer counter into AX for randomization
and bx, ax ; mask with BX to make divisible by 4 and less than or equal to screen size
xor [bx], cl ; place food item and check if position was empty by applying XOR with CL (assumed to be 0xFF)
%endif
.input: ; handle keyboard input
mov bx, 0x7D0 ; initialize BX to screen size (40x25x2 bytes)
jp .food ; if position was occupied by snake or wall in food generation => try again, if we came from main loop PF=0
.move: ; dummy label for jumping back to input evaluation
in al, 0x60 ; read scancode from keyboard controller - bit 7 is set in case key was released
%ifdef NONUMPAD
cmp al, 0xE0 ; if AL is the byte appended when using the keypad
je .move ; ignore it
%endif
imul ax, BYTE 0xA ; we want to map scancodes for arrow up (0x48/0xC8), left (0x4B/0xCB), right (0x4D/0xCD), down (0x50/0xD0) to movement offsets
aam 0x14 ; IMUL (AH is irrelevant here), AAM and AAD with some magic constants maps up => -80, left => -2, right => 2, down => 80
aad 0x44 ; using arithmetic instructions is more compact than checks and conditional jumps
cbw ; but causes weird snake movements though with other keys
add di, ax ; add offset to head position
cmp di, bx ; check if head crossed vertical edge by comparing against screen size in BX
lodsw ; load 0x2007 into AX from off-screen screen buffer and advance head pointer
adc [di], ah ; ADC head position with 0x20 to set snake character
jnp start ; if it already had snake or wall in it or if it crossed a vertical edge, PF=0 from ADC => game over
mov [bp+si], di ; store head position, use BP+SI to default to SS
jz .food ; if food was consumed, ZF=1 from ADC => generate new food
.wall: ; draw an invisible wall on the left side
mov [bx], cl ; store wall character
sub bx, BYTE 0x50 ; go one line backwards
jns .wall ; jump to draw the next wall
pop bx ; no food was consumed so pop tail position into BX
mov [bx], ah ; clear old tail position on screen
jnp .input ; loop to keyboard input, PF=0 from SUB
%ifdef V86
times ($$-$+0xFFFC) db 0x00 ; fill with zeros
nop ; this is only required because of a V86 bug (https://github.com/copy/v86/issues/1253)
jmp $$ ; so I'll ignore this section for now but will remove it when the bug is fixed
%else
times (0x10000+$$-$) db 0x0 ; fill the rest with zeros as the BIOS needs to be 0x10000 bytes
%endif