0,0 → 1,779 |
; 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 ; set scancode 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 eax, byte [V + ecx] |
mov bl, byte [key + eax] |
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 |