emu.inc
rgimad_chip8_kolibri/emu.inc
; initalize the emulator
align 4
proc chip8_init stdcall
; destroys: nothing, mb flags
        push    eax ecx
        mov     word [P_C], 0x200
        mov     word [opcode], 0
        mov     word [I], 0
        mov     word [S_P], 0

        ;DEBUGF  DBG_INFO, "ESP = %x\n", esp
        stdcall _memset, memory, 0, MEM_SIZE
        stdcall _memset, V, 0, 16
        stdcall _memset, gfx, 0, GFX_SIZE
        stdcall _memset, stackmem, 0, 2*STACK_SIZE ; 2 = sizeof(dw)
        stdcall _memset, key, 0, KEY_SIZE
        ;DEBUGF  DBG_INFO, "ESP = %x\n", esp

        mcall   66, 1, 0 ; set ASCII keyboard mode

        xor     ecx, ecx
@@:
        cmp     ecx, 80
        jge     @f
        mov     al, byte [chip8_fontset + ecx]
        mov     byte [memory + FONTSET_ADDRESS + ecx], al
        inc     ecx
        jmp     @b
@@:
        mov     byte [chip8_draw_flag], 1
        mov     byte [delay_timer], 0
        mov     byte [sound_timer], 0
        stdcall _getseed
        stdcall _srand, eax

        stdcall _rand
        DEBUGF  DBG_INFO, "rand() = %u\n", eax
        ;stdcall _rand
        ;DEBUGF  DBG_INFO, "rand() = %u\n", eax
        ;stdcall _rand
        ;DEBUGF  DBG_INFO, "rand() = %u\n", eax
        ;stdcall _rand
        ;DEBUGF  DBG_INFO, "rand() = %u\n", eax
        ;stdcall _rand
        ;DEBUGF  DBG_INFO, "rand() = %u\n", eax

        ;mov     word [opcode], 0xBFAF
        ;movzx   eax, word [opcode]
        ;mov     eax, 0xABCDEF92
        ;stdcall unknown_opcode, eax
        ;DEBUGF  DBG_INFO, "testprint\n"
        pop     ecx eax
        ret
endp


; load game from file to memory
align 4
proc chip8_loadgame stdcall, struct_ptr: dword
; in: struct_ptr - pointer to structure for sysfn70
; out: ZF = 1 file loaded successfully
;      ZF = 0 error
; destroys: only flags
        push    eax ebx

        mov     eax, 70
        mov     ebx, [struct_ptr]
        int     0x40

        cmp     eax, 0
        je      @f
        cmp     eax, 6
        je      @f
        jmp     .load_fail
@@:
        mov     eax, 1
        jmp     .ret

.load_fail:
        xor     eax, eax
.ret:
        test    eax, eax
        pop     ebx eax
        ret
endp


; emulate one cycle
align 4
proc chip8_emulatecycle stdcall
; destroys: ?
        locals
          x     db ?
          y     db ?
          n     db ?
          kk    db ?
          nnn   dw ?
        endl
        ; fetch:
        movzx   ecx, word [P_C]
        movzx   ax, byte [memory + ecx]
        shl     ax, 8
        movzx   bx, byte [memory + 1 + ecx]
        or      ax, bx
        mov     word [opcode], ax
        DEBUGF  DBG_INFO, "opcode = 0x%x, ax = 0x%x\n", [opcode]:4, ax

        shr     ax, 8
        and     ax, 0x000F
        mov     byte [x], al

        mov     ax, word [opcode]
        shr     ax, 4
        and     ax, 0x000F
        mov     byte [y], al

        mov     ax, word [opcode]
        and     ax, 0x000F
        mov     byte [n], al

        mov     ax, word [opcode]
        and     ax, 0x00FF
        mov     byte [kk], al

        mov     ax, word [opcode]
        and     ax, 0x0FFF
        mov     word [nnn], ax

        ; DEBUGF  DBG_INFO, "P_C: 0x%x Op: 0x%x\n",  [P_C],  [opcode]:4 ; was word word
        ; TODO test this and watch values of x, y, n, kk, nnn

        ; decode & execute
        ; sw1
        mov     ax, word [opcode]
        and     ax, 0xF000

        cmp     ax, 0x0000
        je      .sw1_case_0000

        cmp     ax, 0x1000
        je      .sw1_case_1000

        cmp     ax, 0x2000
        je      .sw1_case_2000

        cmp     ax, 0x3000
        je      .sw1_case_3000

        cmp     ax, 0x4000
        je      .sw1_case_4000

        cmp     ax, 0x5000
        je      .sw1_case_5000

        cmp     ax, 0x6000
        je      .sw1_case_6000

        cmp     ax, 0x7000
        je      .sw1_case_7000

        cmp     ax, 0x8000
        je      .sw1_case_8000

        cmp     ax, 0x9000
        je      .sw1_case_9000

        cmp     ax, 0xA000
        je      .sw1_case_A000

        cmp     ax, 0xB000
        je      .sw1_case_B000

        cmp     ax, 0xC000
        je      .sw1_case_C000

        cmp     ax, 0xD000
        je      .sw1_case_D000

        cmp     ax, 0xE000
        je      .sw1_case_E000

        cmp     ax, 0xF000
        je      .sw1_case_F000

        jmp     .sw1_default

.sw1_case_0000:
        ; sw2
        cmp    byte [kk], 0xE0 
        je     .sw2_case_E0

        cmp    byte [kk], 0xEE
        je     .sw2_case_EE

        jmp    .sw2_default

        .sw2_case_E0: ; clear the screen
            stdcall _memset, gfx, 0, GFX_SIZE
            mov     byte [chip8_draw_flag], 1
            add     word [P_C], 2
            jmp     .sw2_end

        .sw2_case_EE: ; TODO check!!; ret
            dec     word [S_P]
            movzx   ecx, word [S_P]
            mov     ax, word [stackmem + ecx*2] 
            mov     word [P_C], ax
            jmp     .sw2_end

        .sw2_default:
            movzx   eax, word [opcode]
            stdcall unknown_opcode, eax
        .sw2_end:
        jmp     .sw1_end

.sw1_case_1000: ; TODO check; 1nnn: jump to address nnn
        mov     ax, word [nnn]
        mov     word [P_C], ax
        jmp     .sw1_end

.sw1_case_2000: ; TODO check; 2nnn: call address nnn
        mov     ax, word [P_C]
        add     ax, 2
        movzx   ecx, word [S_P]
        mov     word [stackmem + ecx*2], ax   
        inc     word [S_P]
        mov     ax, word [nnn]
        mov     word [P_C], ax
        jmp     .sw1_end

.sw1_case_3000: ; 3xkk: skip next instr if V[x] = kk
        movzx   ecx, byte [x]
        mov     al, byte [V + ecx]
        mov     bl, byte [kk]
        mov     cx, 2
        cmp     al, bl
        jne     @f
        mov     cx, 4
@@:
        add     word [P_C], cx
        jmp     .sw1_end

.sw1_case_4000: ; 4xkk: skip next instr if V[x] != kk
        movzx   ecx, byte [x]
        mov     al, byte [V + ecx]
        mov     bl, byte [kk]
        mov     cx, 2
        cmp     al, bl
        je     @f
        mov     cx, 4
@@:
        add     word [P_C], cx
        jmp     .sw1_end

.sw1_case_5000: ; 5xy0: skip next instr if V[x] == V[y]
        movzx   ecx, byte [x]
        mov     al, byte [V + ecx]
        movzx   ecx, byte [y]
        mov     bl, byte [V + ecx]
        mov     cx, 2
        cmp     al, bl
        jne     @f
        mov     cx, 4
@@:
        add     word [P_C], cx
        jmp     .sw1_end

.sw1_case_6000: ; 6xkk: set V[x] = kk
        movzx   ecx, byte [x]
        mov     bl, byte [kk]
        mov     byte [V + ecx], bl
        add     word [P_C], 2
        jmp     .sw1_end

.sw1_case_7000: ; 7xkk: set V[x] = V[x] + kk
        movzx   ecx, byte [x]
        mov     bl, byte [kk]
        add     byte [V + ecx], bl
        add     word [P_C], 2
        jmp     .sw1_end

.sw1_case_8000: ; 8xyn: Arithmetic stuff
        ; sw3
        cmp     byte [n], 0x0
        je      .sw3_case_0

        cmp     byte [n], 0x1
        je      .sw3_case_1

        cmp     byte [n], 0x2
        je      .sw3_case_2

        cmp     byte [n], 0x3
        je      .sw3_case_3

        cmp     byte [n], 0x4
        je      .sw3_case_4

        cmp     byte [n], 0x5
        je      .sw3_case_5

        cmp     byte [n], 0x6
        je      .sw3_case_6

        cmp     byte [n], 0x7
        je      .sw3_case_7

        cmp     byte [n], 0xE
        je      .sw3_case_E

        jmp     .sw3_default

        .sw3_case_0: ; V[x] = V[y]
            movzx   ecx, byte [x]
            movzx   edx, byte [y]
            mov     al, byte [V + edx]
            mov     byte [V + ecx], al
            jmp     .sw3_end

        .sw3_case_1: ; V[x] = V[x] | V[y]
            movzx   ecx, byte [x]
            movzx   edx, byte [y]
            mov     al, byte [V + ecx]
            or      al, byte [V + edx]
            mov     byte [V + ecx], al
            jmp     .sw3_end

        .sw3_case_2: ; V[x] = V[x] & V[y]
            movzx   ecx, byte [x]
            movzx   edx, byte [y]
            mov     al, byte [V + ecx]
            and      al, byte [V + edx]
            mov     byte [V + ecx], al
            jmp     .sw3_end

        .sw3_case_3: ; V[x] = V[x] ^ V[y]
            movzx   ecx, byte [x]
            movzx   edx, byte [y]
            mov     al, byte [V + ecx]
            xor     al, byte [V + edx]
            mov     byte [V + ecx], al
            jmp     .sw3_end

        .sw3_case_4: ; V[x] = V[x] + V[y]; if carry, move 1 to V[0xF]
            movzx   ecx, byte [x]
            movzx   edx, byte [y]
            movzx   ax, byte [V + ecx]
            movzx   bx, byte [V + edx]
            add     ax, bx
            mov     byte [V + ecx], al

            xor     cl, cl 
            cmp     ax, 255
            jbe     @f
            inc     cl
        @@:
            mov     byte [V + 0xF], cl 
            jmp     .sw3_end

        .sw3_case_5: ;TODO check; V[x] = V[x] - V[y]; if no borrow, move 1 to V[0xF]
            movzx   ecx, byte [x]
            movzx   edx, byte [y]
            mov     al, byte [V + ecx]
            mov     bl, byte [V + edx]
            sub     al, bl
            mov     byte [V + ecx], al

            xor     cl, cl
            cmp     al, bl
            jbe     @f
            inc     cl
        @@:
            mov     byte [V + 0xF], cl
            jmp     .sw3_end

        .sw3_case_6: ; TODO check; V[x] = V[x] SHR 1 ; V[0xF] = least-significant bit of V[x] before shift
            movzx   ecx, byte [x]
            mov     al, byte [V + ecx]
            and     al, 0x01
            mov     byte [V + 0xF], al
            shr     byte [V + ecx], 1
            jmp     .sw3_end

        .sw3_case_7: ; TODO check; V[x] = V[y] - V[x]; if no borrow, move 1 to V[0xF]
            movzx   ecx, byte [y]
            movzx   edx, byte [x]
            mov     al, byte [V + ecx]
            mov     bl, byte [V + edx]
            sub     al, bl
            mov     byte [V + ecx], al

            xor     cl, cl
            cmp     al, bl
            jbe     @f
            inc     cl
        @@:
            mov     byte [V + 0xF], cl
            jmp     .sw3_end

        .sw3_case_E: ; TODO check; V[0xF] = most-significant bit of V[x] before shift
            movzx   ecx, byte [x]
            mov     al, byte [V + ecx]
            shr     al, 7
            and     al, 0x01
            mov     byte [V + 0xF], al
            shl     byte [V + ecx], 1
            jmp     .sw3_end

        .sw3_default:
            movzx   eax, word [opcode]
            stdcall unknown_opcode, eax

        .sw3_end:
        add     word [P_C], 2
        jmp     .sw1_end

.sw1_case_9000: ; TODO check; 9xy0: skip instruction if V[x] != V[y]
        movzx   ecx, byte [x]
        mov     al, byte [V + ecx]
        movzx   ecx, byte [y]
        mov     bl, byte [V + ecx]
        mov     cx, 2
        cmp     al, bl
        je      @f
        mov     cx, 4
@@:
        add     word [P_C], cx
        jmp     .sw1_end

.sw1_case_A000: ; Annn: set I to address nnn
        mov     ax, word [nnn]
        mov     word [I], ax
        add     word [P_C], 2
        jmp     .sw1_end

.sw1_case_B000: ; Bnnn: jump to location nnn + V[0]
        mov     ax, word [nnn]
        movzx   bx, byte [V]
        add     ax, bx
        mov     word [P_C], ax
        jmp     .sw1_end

.sw1_case_C000: ; TODO check; Cxkk: V[x] = random byte AND kk
        stdcall _rand
        and     al, byte [kk]
        movzx   ecx, byte [x]
        mov     byte [V + ecx], al
        add     word [P_C], 2
        jmp     .sw1_end

.sw1_case_D000: ; TODO check; Dxyn: Display an n-byte sprite starting at memory location I at (Vx, Vy) on the screen, VF = collision
        movzx   ecx, byte [x]
        movzx   eax, byte [V + ecx]
        movzx   ecx, byte [y]
        movzx   ebx, byte [V + ecx]
        movzx   ecx, byte [n]
        stdcall chip8_draw_sprite, eax, ebx, ecx
        mov     byte [chip8_draw_flag], 1
        add     word [P_C], 2
        jmp     .sw1_end

.sw1_case_E000: ; TODO check; key-pressed events
        cmp    byte [kk], 0x9E
        je     .sw5_case_9E

        cmp    byte [kk], 0xA1
        je     .sw5_case_A1

        jmp    .sw5_default

        .sw5_case_9E: ; skip next instruction if key V[X] is pressed
            movzx   ecx, byte [x]
            movzx   edx, byte [V + ecx]
            mov     bl, byte [key + edx]
            mov     ax, 2
            cmp     bl, 1
            jne     .sw5_case_9E_endcheck
            mov     ax, 4
            mov     byte [key + edx], 0 ; release pressed key
            .sw5_case_9E_endcheck:
            add     word [P_C], ax
            jmp     .sw5_end

        .sw5_case_A1: ; skip next instruction if key V[X] is NOT pressed
            movzx   ecx, byte [x]
            movzx   edx, byte [V + ecx]
            mov     bl, byte [key + edx]
            mov     ax, 4
            cmp     bl, 1
            jne     .sw5_case_A1_endcheck
            mov     ax, 2
            .sw5_case_A1_endcheck:
            mov     byte [key + edx], 0 ; release pressed key
            add     word [P_C], ax
            jmp     .sw5_end

        .sw5_default:
            movzx   eax, word [opcode]
            stdcall unknown_opcode, eax
        .sw5_end:
        jmp     .sw1_end

.sw1_case_F000: ; misc
        cmp    byte [kk], 0x07
        je     .sw4_case_07

        cmp    byte [kk], 0x0A
        je     .sw4_case_0A

        cmp    byte [kk], 0x15
        je     .sw4_case_15

        cmp    byte [kk], 0x18
        je     .sw4_case_18

        cmp    byte [kk], 0x1E
        je     .sw4_case_1E

        cmp    byte [kk], 0x29
        je     .sw4_case_29

        cmp    byte [kk], 0x33
        je     .sw4_case_33

        cmp    byte [kk], 0x55
        je     .sw4_case_55

        cmp    byte [kk], 0x65
        je     .sw4_case_65

        jmp    .sw4_default

        .sw4_case_07: ; TODO check; V[X] = delay timer
            mov     al, byte [delay_timer]
            movzx   ecx, byte [x]
            mov     byte [V + ecx], al
            add     word [P_C], 2
            jmp     .sw4_end

        .sw4_case_0A: ; TODO check; wait for key instruction
            ;.sw4_case_0A_loop:
                ;mcall 2
                ;stdcall  keyboard_update, eax
                xor  ecx, ecx
                .sw4_case_0A_loop2:
                    cmp  ecx, KEY_SIZE
                    jae  .sw4_case_0A_loop_end ;

                    cmp  byte [key + ecx], 1
                    jne  .sw4_case_0A_loop2_endcheck

                    movzx edx, byte [x]
                    mov   byte [V + edx], cl
                    mov  byte [key + ecx], 0 ; release pressed key
                    ;jmp  .sw4_case_0A_loop_end
                    add     word [P_C], 2
                    jmp     .sw4_end

                    .sw4_case_0A_loop2_endcheck:
                    inc  ecx
                    jmp  .sw4_case_0A_loop2

            ;jmp  .sw4_case_0A_loop
            .sw4_case_0A_loop_end:
            ;add     word [P_C], 2
            jmp     .sw4_end

        .sw4_case_15: ; TODO check; delay_timer = V[X]
            movzx   ecx, byte [x]
            mov     al, byte [V + ecx]
            mov     byte [delay_timer], al
            add     word [P_C], 2
            jmp     .sw4_end

        .sw4_case_18: ; TODO check; sound_timer = V[X]
            movzx   ecx, byte [x]
            mov     al, byte [V + ecx]
            mov     byte [sound_timer], al
            add     word [P_C], 2
            jmp     .sw4_end

        .sw4_case_1E: ; I = I + V[X]
            ; V[0xF] = (I + V[x] > 0xfff) ? 1 : 0; (TODO?? (no this line in other chip8 emulators))
            movzx   ecx, byte [x]
            movzx   ax, byte [V + ecx]
            add     word [I], ax
            add     word [P_C], 2
            jmp     .sw4_end

        .sw4_case_29: ; TODO check; I = location of font for character V[X]
            movzx   ecx, byte [x]
            movzx   ax, byte [V + ecx]
            mov     bx, FONTSET_BYTES_PER_CHAR
            mul     bx
            mov     word [I], ax
            add     word [P_C], 2
            jmp     .sw4_end

        .sw4_case_33: ; TODO check; Store BCD for V[X] starting at address I
            movzx   ecx, byte [x]
            movzx   ebx, byte [V + ecx]

            stdcall mod_div, ebx, 1000, 100
            movzx   ecx, word [I]
            mov     byte [memory + ecx], al

            stdcall mod_div, ebx, 100, 10
            mov     byte [memory + ecx + 1], al

            stdcall mod_div, ebx, 10, 1
            mov     byte [memory + ecx + 2], al

            add     word [P_C], 2
            jmp     .sw4_end

        .sw4_case_55: ; TODO check; Copy sprite from registers 0 to X into memory at address I
            movzx   edx, word [I]
            xor     ecx, ecx
            .for_sw4_1:
                movzx  eax, byte [x]
                cmp    ecx, eax
                ja     .for_sw4_1_end

                mov    al, byte [V + ecx]
                mov    byte [memory + ecx + edx], al
                
                inc    ecx
                jmp    .for_sw4_1
            .for_sw4_1_end:
            movzx   ax, byte [x] 
            inc     ax
            add     word [I], ax

            add     word [P_C], 2
            jmp     .sw4_end

        .sw4_case_65: ; TODO check; Copy sprite from memory at address X into registers 0 to I
            movzx   edx, word [I]
            xor     ecx, ecx
            .for_sw4_2:
                movzx  eax, byte [x]
                cmp    ecx, eax
                ja     .for_sw4_2_end

                mov    al, byte [memory + ecx + edx]
                mov    byte [V + ecx], al

                inc    ecx
                jmp    .for_sw4_2
            .for_sw4_2_end:
            movzx   ax, byte [x] 
            inc     ax
            add     word [I], ax

            add     word [P_C], 2
            jmp     .sw4_end

        .sw4_default:
            movzx   eax, word [opcode]
            stdcall unknown_opcode, eax
        .sw4_end:
        jmp     .sw1_end

.sw1_default:
        movzx   eax, word [opcode]
        stdcall unknown_opcode, eax

.sw1_end:
        ret
endp


; tick the timers
align 4
proc chip8_tick stdcall
; destroys: flags
        cmp     byte [delay_timer], 0
        jz      @f
        dec     byte [delay_timer]
@@:
        cmp     byte [sound_timer], 0
        jz      .ret
        dec     byte [sound_timer]
        cmp     byte [sound_timer], 0
        jnz     @f
        DEBUGF  DBG_INFO, "BEEP!\n"
@@:
.ret:
        ret
endp


; print unknown opcode error & exit
align 4
proc unknown_opcode stdcall, op:word
        DEBUGF DBG_ERR, "Error: unknown opcode 0x%x\n", [op]:4
        mov     eax, -1
        int     0x40
        ret
endp

; draw sprite from memory to gfx; TODO check;
; if collision then V[0xF] = 1
align 4
proc chip8_draw_sprite stdcall, col: dword, row: dword, n: dword
        locals
          byte_index  dd ?
          bit_index   dd ?
          pixelp      dd ?
          temp        dd ?
          _byte       db ?
          _bit        db ?
        endl
        
        DEBUGF  DBG_INFO, "draw_sprite x = %u, y = %u, n = %u\n", [col], [row], [n]

        movzx   eax, word [I]
        mov     ebx, dword [memory + eax]
        mov     ecx, dword [memory + eax + 4]
        DEBUGF  DBG_INFO, "I = %x, at I: %x, at I + 4: %x\n", eax, ebx, ecx

        mov     byte [V + 0xF], 0
        mov     dword [byte_index], 0
.for1:
        movzx   eax, byte [n] 
        cmp     dword [byte_index], eax
        jae     .for1_end
        
        movzx   ecx, word [I]
        add     ecx, dword [byte_index]
        mov     al, byte [memory + ecx]
        mov     byte [_byte], al
        mov     dword [bit_index], 0
.for2:
        cmp     dword [bit_index], 8
        jae     .for2_end

        mov     ecx, dword [bit_index]
        mov     al, byte [_byte]
        shr     al, cl
        and     al, 1
        mov     byte [_bit], al

        mov     eax, dword [row]
        add     eax, dword [byte_index]
        imul    eax, GFX_COLS
        add     eax, dword [col]
        add     eax, 7
        sub     eax, dword [bit_index]
        add     eax, gfx
        mov     dword [pixelp], eax

        ; DEBUGF  DBG_INFO, "gfx = %x, pixelp = %x\n", gfx, edx

        cmp     byte [_bit], 1
        jne     .if2_end

        mov     eax, dword [pixelp]
        cmp     byte [eax], 1
        jne     .if2_end

        mov     byte [V + 0xF], 1
.if2_end:

        mov     ebx, dword [pixelp]
        mov     al, byte [ebx]
        xor     al, byte [_bit]
        mov     byte [ebx], al

        inc     dword [bit_index]
        jmp     .for2
.for2_end:

        inc     dword [byte_index]
        jmp     .for1
.for1_end:

        ret
endp
