It's my first time writing x86-16 assembly. I'm writing a bootloader game, but I'm struggling with iterating through level data.
I'm attempting to iterate through a 2d array - each byte of this array is split into 2-bit chunks. Therefore, I have a 3-layered loop. The video output that I'm getting shows the characters in positions that they aren't meant to be able to reach.
I tested my calculation of the position and I believe it to be sound, which leads me to believe the registers are being corrupted by another operation I'm performing.
Here is the algorithm that I am using:
; Draw map
; Loop setup will have
; ax - Y pos counter
; bx - X pos counter
; cx - Bit pair counter
; di - Offset (map_ptr + bp)
pusha
mov si, [map_ptr] ; Storing map data
xor di, di ; Clearing offset to 0
xor ax, ax ; Starting loop
.loop_y:
xor bx, bx ; Reset X for each Y
.loop_x:
xor cx, cx ; Reset bit pair counter for each X
.loop_bits:
; Move cursor
xor dx, dx
movzx dx, bl
imul dx, dx, e_MAP_TILE_PER_BYTE
add dx, cx
mov dh, al
call s_move_cursor
; Get data
push cx
push si
add si, di
movzx dx, byte [si] ; Move full byte into dl (dl = byte)
imul cx, cx, 2 ; Calculate shift amount
shl dx, cl ; Shift tile data into dh
and dh, 00000011b ; Mask only the last tile, now dh = tile type
pop si
pop cx
; Print character
push ax
push bx
cmp dh, e_BLOCK_WALL
je .draw_wall
cmp dh, e_BLOCK_LAVA
je .draw_lava
cmp dh, e_BLOCK_END
je .draw_end
jmp .draw_none
.draw_wall:
mov al, e_WALL_CHR
mov bl, e_WALL_COL
jmp .draw_start
.draw_lava:
mov al, e_LAVA_CHR
mov bl, e_LAVA_COL
jmp .draw_start
.draw_end:
mov al, e_END_CHR
mov bl, e_END_COL
jmp .draw_start
.draw_start:
call s_print
.draw_none:
pop bx
pop ax
.loop_bits_end:
inc cx
cmp cx, e_MAP_TILE_PER_BYTE
jne .loop_bits
.loop_x_end:
inc di ; Increment offset by 1 for next access
inc bx
cmp bx, e_MAP_MAX_LENGTH
jne .loop_x
.loop_y_end:
inc ax
cmp ax, e_MAP_MAX_HEIGHT
jne .loop_y
; fall through naturally if ended
popa
There is some other information that is useful
; Preprocessor constants
e_MAP_MAX_LENGTH equ 2 ; Map size (Split into half-nibbles for 8 cells)
e_MAP_MAX_HEIGHT equ 7
e_MAP_TILE_PER_BYTE equ 4
e_MAP_BSIZE equ e_MAP_MAX_LENGTH * e_MAP_MAX_HEIGHT * e_MAP_TILE_PER_BYTE
; Maps
map_i db e_MAP_BSIZE dup (0x55) ; Cells are 1byte=4cells
map_ii db e_MAP_BSIZE dup (0x55) ; Cells are 1byte=4cells
map_iii db e_MAP_BSIZE dup (0x55) ; Cells are 1byte=4cells
map_iv db e_MAP_BSIZE dup (0x55) ; Cells are 1byte=4cells
; Data and variables
map_ptr dw map_i
I believe my push/pop structure would make these subroutines safe, but just so everything is clear here they are:
; SUBROUTINE
; dx - positional information
s_move_cursor:
push ax
push bx
mov ah, 02h ; Call interrupt to move cursor
mov bh, 0 ; Page number
int 10h
pop ax
pop bx
ret
; SUBROUTINE
; al - character
; bl - style
s_print:
push ax
push bx
push cx
mov bh, 0 ; Page number
mov cx, 1 ; Repeat count
mov ah, 09h ; Call interrupt to print
int 10h
pop cx
pop bx
pop ax
ret
I expected this function to print a full wall of #'s. Instead all printed characters are #'s, however they are in incorrect positions.
Stepping through each iteration in this with gdb resulted in me seeing the registers take up the incorrect values (almost as if they are iterating too early?). I can't see how an early iteration could happen in my code though, instead I suspect an instruction or subroutine is corrupting the register with data.
Although you say that you think that your subroutines are fine, I believe it is there that the trouble starts.
Whatever it is that you push
to the stack, it must come off in the reverse order. That is not the case in the s_move_cursor subroutine:
push ax push bx mov ah, 02h ; Call interrupt to move cursor mov bh, 0 ; Page number int 10h pop ax pop bx ret
The pop ax
must come after the pop bx
.