Skip to content

Instantly share code, notes, and snippets.

@keyboardspecialist
Created September 22, 2023 23:51
Show Gist options
  • Save keyboardspecialist/d4a3829fe885374aa596aff8b0e6da96 to your computer and use it in GitHub Desktop.
Save keyboardspecialist/d4a3829fe885374aa596aff8b0e6da96 to your computer and use it in GitHub Desktop.
%define BOCHS_HD_IMAGE ;define this when booting in Bochs as a hard drive image
%define CONSOLE_WIDTH 0x4E
%define RTC_DIVIDER 0xF
%define PADDLE_WIDTH 0x08
%define PADDLE_SPEED 0x2
BITS 16 ;we are running in real mode, so 16 bit only
[org 0x7C00] ;set origin address to 0x7C00. This is where BIOS drops us off in RAM
boot:
cli ;halt interrupts
xor ax, ax
mov ds, ax ;set data segment register
mov gs, ax ;set general-use segment register
mov ax, 0B800h
mov es, ax ;VGA color text data segment
mov ax, 07E0h
mov ss, ax ;set stack segment register
mov bp, ax ;set stack base pointer. bottom of stack
mov sp, 9C00h ;set stack pointer. top of stack
mov ax, word int70h_handler ;setup 70h interrupt for RTC timer
mov [gs:70h*4], ax ;set interrupt offset
mov [gs:70h*4+2], ds ;set interrupt segment
mov ax, word int09h_handler ;setup 09h for keyboard
mov [gs:09h*4], ax ;set interrupt offset
mov [gs:09h*4+2], ds ;set interrupt segment
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Init Routine ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
init:
;fill with border chars
mov ax, 0x0FFE ;set ax to bg color
xor di, di ;clear di ;consider for space
mov cx, 80 * 25 ;set cx to size of console screen
rep stosw ;write ax to video memory cx times. mov [es:di++], ax
mov bx, 0x1901
xor ax, ax
call fill_area
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Draw Bricks ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov bx, 0x0501 ;start at row 1, col 1
mov ax, 0x03B2 ;char and attr to write
call fill_area
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; RTC Init ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
mov al, 8Bh ;select reg B, disable NMI
out 70h, al
in al, 71h ;read B
or al, 40h ;OR 0x40 to set flag for start
push ax
mov ax, 8Bh ;select B
out 70h, al
pop ax
out 71h, al ;set B register to start
mov al, 8Ah ;select reg A and disable NMI
out 70h, al
in al, 71h ;read reg A value
and al, 11110000b ;keep top nibble received
or al, RTC_DIVIDER ;OR in divider in low nibble
push ax
mov al, 8Ah
out 70h, al ;select A
pop ax
out 71h, al ;write value to A
in al, 0A1h ;clear PIC 2 mask to ensure that IRQ 8 fires
xor al, al
out 0A1h, al
sti
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
jmp main_loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
fill_area:
.iter:
mov cx, CONSOLE_WIDTH ;brick row counter
call vga_get_char_offset ;get video offset
rep stosw ;write cx num bricks
dec bh
cmp bh, 0 ;loop
jnz .iter
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Update Frame ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
update_frame:
mov al, byte [paddle_x] ;load paddle offsets
add al, byte [paddle_vec] ;add vector
cmp al, 1 ;test against left side
jge .testpaddlehigh ;bigger then test right side
mov al, 1 ;clip to 1
jmp .storepaddlex ;store it
.testpaddlehigh:
cmp al, CONSOLE_WIDTH - PADDLE_WIDTH - 1;compare adjusted values for right edge
jle .storepaddlex ;nope just store it
mov al, CONSOLE_WIDTH - PADDLE_WIDTH - 1;clip to right side
.storepaddlex:
mov byte [paddle_x], al ;store the new x value
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.checkballycollision:
mov bx, word [ball_x] ;load x,y at once
push bx ;save old position for later
add bh, byte [ball_vecy] ;add y pos + y vec
call vga_get_char_offset ;get video mem offset
mov ax, word [es:di] ;get char from memory offset
cmp al, 0 ;check if the cell is empty
je .storebally ;if it is, move there
neg byte [ball_vecy] ;if not, reverse direction
;this would be simpler with a paddle made of 1 char
cmp al, 205 ;check for paddle left side char
jz .paddlecollide ;if we hit, handle the collision
cmp al, 213 ;paddle middle char
jz .paddlecollide ;...
cmp al, 184 ;paddle right side char
jnz .ybrickcollision ;inverse test, if we hit just fall through
.paddlecollide:
mov dl, byte [ball_vecx] ;load ball x vec
cmp byte [paddle_vec], 0 ;check direction of paddle
jl .pvecn ;moving left ?
jg .pvec ;moving right ?
jmp .checkballxcollision ;no movement (only possible at start)
.pvecn: ;there's got to be a better way handling this
dec dl ;we are negative so decrement ball vec
cmp dl, -1 ;clip it to -1
jg .donepaddle ;if greater, finish and store
mov dl, -1 ;if not, set to -1
jmp .donepaddle ;store it
.pvec: ;paddle is positive
inc dl ;increment ball vec
cmp dl, 1 ;clipping to 1 this time
jl .donepaddle ;...
mov dl, 1 ;...
.donepaddle:
mov byte [ball_vecx], dl ;store new vector
jmp .checkballxcollision ;we hit something and reversed so just go on to check x direction
.ybrickcollision:
call brick_collider ;we possibly hit a brick so check and handle accordingly
jmp .checkballxcollision ;move on
.storebally:
mov byte [ball_y], bh ;cell was empty so we store the new y position
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.checkballxcollision:
pop bx ;restore old ball x,y in case it changed above
push bx ;push it back onto the stack for later
cmp byte [ball_vecx], 0 ;if we have no x vector we can't hit anything
jz .skiptodraw ;no vec, just go draw
add bl, byte [ball_vecx] ;add vector to x pos
call vga_get_char_offset ;get new video offset
mov ax, [es:di] ;retrieve char there
cmp al, 0 ;is it empty?
je .storeballx ;if it is, move there
neg byte [ball_vecx] ;nope, reverse direction
.xbrickcollision:
call brick_collider ;we can only hit bricks on this axis, so handle if we did
jmp .skiptodraw ;no store, just draw
.storeballx:
mov byte [ball_x], bl ;we hit nothing and had a vector, so store
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Clear Scr ;;;
.skiptodraw:
xor ax, ax ;clear paddle row for redraw
mov bx, 0x1701 ;row 23, col 1
mov cl, CONSOLE_WIDTH ;clear row
call vga_get_char_offset
rep stosw ;write blank chars to row
pop bx ;retrieve our original ball x,y
call vga_get_char_offset
mov [es:di], ax ;clear
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Draw Ball ;;;
mov bx, word [ball_x] ;get new ball x,y
mov ax, 0x0D07 ;ball char
call vga_get_char_offset
mov [es:di], ax ;write it
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Draw Paddle
mov bx, word [paddle_x] ;read as word to get row offset as well
mov ax, 0x07D5 ;left side char
call vga_get_char_offset
mov [es:di], ax ;write to video mem
add di, 2 ;increment video pointer
mov cl, PADDLE_WIDTH
mov al, 0xCD
rep stosw ;write middle bars to video mem
mov al, 0xB8
mov [es:di], ax ;write right side char
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
.update_end:
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Brick Collider ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
brick_collider: ;bx - bl - x, bh - y, al - char
cmp al, 178 ;chars 176-178 are brick chars. 178 - solid, 177 - 50%, 176 - 25%
jle .charlow ;if its less or equal, check low bound
jmp .skip ;greater, not a brick
.charlow:
cmp al, 176 ;check low bound
jl .skip ;lower, not a brick
push ax ;ax has our char and attr, save on stack
xor ax, ax ;clear ax
mov al, bl ;move ball x into al
mov dl, 3 ;set dl to 3. bricks are 3 wide. we need to divide ball x by 3 to get a brick offset
div dl ;do the divide
test ah, 0x3 ;check remainder. if its zero, we need to adjust or we will hit the brick to the right
jnz .noadjust ;not zero, dont adjust
dec al ;decrement so we collide with correct brick
.noadjust:
mul dl ;multiply our brick offset by brick size (3)
mov dl, al ;move offset into dl
;this gives us a brick offset that starts with the leftmost char of the brick
pop ax ;pop our char and attr off stack
push bx ;push our x/y onto stack so we don't clobber it
mov bl, dl ;vga_get_char_offset uses bx for x/y so transfer brick x offset to it
inc bl ;increment position since our bricks start at column 1
mov cl, 3 ;we need to draw 3 chars
dec al ;decrement to more damage brick char
cmp al, 176 ;if we fall below lowest brick char, zero it out, its destroyed
jge .draw ;nope, not destroyed, just draw
xor al, al ;zero out char
.draw:
call vga_get_char_offset ;get brick mem offset
rep stosw ;draw
pop bx ;restore original ball x,y into bx
.skip:
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
main_loop:
jmp main_loop
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; VGA Offset ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
vga_get_char_offset: ;bx parm. bl - x, bh - y. ret - offset in di
push ax ;push ax and dx onto stack
push dx ;we don't want to clobber them
xor dx, dx
mov al, bh ;put y value into al
mul byte [console_w] ;y * row_width. mul requires register or memory operand
mov dl, bl ;put x value into dl
add ax, dx ;add x + y_row offset
mov di, ax ;move to di for our return value
shl di, 1 ;offset * 2, chars occupy 2 bytes
pop dx ;restore ax and dx
pop ax
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; RTC Timer Handler ;;;;;;;;;;;;;;;;;;;;;;;;;;;
int70h_handler:
call update_frame
mov al, 0Ch ;select reg C
out 70h, al ;read RTC reg C. We have to do this to keep receiving interrupts
in al, 71h ;required dummy read. resets to reg D
mov al, 0x20 ;send EOI to allow interrupts continue
out 0xa0, al ;this int occurs on the slave PIC 2, so we need to notify it
out 0x20, al ;notify the master PIC 1, too
;this is fudged a bit since it doesn't reset. It'll trigger multiple times per 1 missed
;ball count reflects this
cmp byte [ball_y], 24 ;check if we missed the ball
jl .done ;nope, we're done
dec byte [balls_left] ;yep, decrement ball count
cmp byte [balls_left], 0 ;if we are at 0 we lose
jnz .done ;nope, we're done
.check:
in al, 64h ;out of balls, force reset. Read keyboard controller port
test al, 02h ;test system flag bit
jnz .check ;if its not clear, loop
mov al, 0FEh ;system reset command
out 64h, al ;send to controller
.done:
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;; Keyboard handler ;;;;;;;;;;;;;;;;;;;;;;;;;;;;
int09h_handler:
in al, 60h ;read the key pressed
cmp al, 0x4B ;left arrow key
jnz .testright ;if not pressed check right key
mov byte [paddle_vec], -PADDLE_SPEED ;set negative direction vector
jmp .done
.testright:
cmp al, 0x4D ;right arrow key
jnz .done ;not pressed, exit
mov byte [paddle_vec], PADDLE_SPEED ;set positive direction vector
;;;;;;;;;;;;;;;;;;;;;;;;;;; Stuff we gotta do to make the keyboard interrupt happy
.done:
in al, 61h ;get keyboard control line value
mov ah, al ;save for later
or al, 80h ;set enable keyboard bit
out 61h, al ;write to port
xchg ah, al ;swap control port value into al
out 61h, al ;write to port
mov al, 20h ;send EOI to allow interrupts to continue
out 20h, al
iret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
console_w db 80 ;console width
paddle_x db 36 ;current paddle x
db 23 ;hack to save 1 byte @ draw_paddle
paddle_vec db 0 ;paddle movement direction
ball_x db 40 ;current ball x
ball_y db 22 ;current ball y
ball_vecx db 0 ;vector in console chars
ball_vecy db -1 ;vector in console chars
block_cnt db 130 ;number of blocks left
balls_left db 0x20
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
times 510-($-$$) db 0 ; Pad remainder of boot sector with 0s
dw 0xAA55 ; The standard PC boot signature
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%ifdef BOCHS_HD_IMAGE
times 1024 * 1024 db 0 ;Bochs HD image padding. Set Cylinders/Sectors/Heads to 3
%endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment