diff --git a/data/Tupfile.lua b/data/Tupfile.lua index 6b2419b6e3..55e1d37884 100644 --- a/data/Tupfile.lua +++ b/data/Tupfile.lua @@ -201,6 +201,9 @@ extra_files = { {"kolibrios/emul/e80/keyboard.png", PROGS .. "/emulator/e80/trunk/keyboard.png"}, {"kolibrios/emul/fceu/fceu", PROGS .. "/emulator/fceu/fceu"}, {"kolibrios/emul/fceu/FCEU ReadMe.txt", PROGS .. "/emulator/fceu/FCEU ReadMe.txt"}, + {"kolibrios/emul/chip8/chip8", PROGS .. "/emulator/chip8/chip8"}, + {"kolibrios/emul/chip8/readme.txt", PROGS .. "/emulator/chip8/readme.txt"}, + {"kolibrios/emul/chip8/roms/", PROGS .. "/emulator/chip8/roms/*"}, {"kolibrios/emul/kwine/kwine", PROGS .. "/emulator/kwine/bin/kwine"}, {"kolibrios/emul/kwine/lib/", PROGS .. "/emulator/kwine/bin/lib/*"}, {"kolibrios/emul/uarm/", "common/emul/uarm/*"}, diff --git a/programs/emulator/chip8/Tupfile.lua b/programs/emulator/chip8/Tupfile.lua new file mode 100644 index 0000000000..3f985bdfde --- /dev/null +++ b/programs/emulator/chip8/Tupfile.lua @@ -0,0 +1,6 @@ +if tup.getconfig("NO_FASM") ~= "" then return end +HELPERDIR = (tup.getconfig("HELPERDIR") == "") and "../../.." or tup.getconfig("HELPERDIR") +tup.include(HELPERDIR .. "/use_fasm.lua") + +tup.rule("chip8.asm", FASM .. " %f %o " .. tup.getconfig("KPACK_CMD"), "chip8") + \ No newline at end of file diff --git a/programs/emulator/chip8/build.sh b/programs/emulator/chip8/build.sh new file mode 100755 index 0000000000..495ecf3168 --- /dev/null +++ b/programs/emulator/chip8/build.sh @@ -0,0 +1 @@ +fasm chip8.asm chip8 diff --git a/programs/emulator/chip8/chip8.asm b/programs/emulator/chip8/chip8.asm new file mode 100644 index 0000000000..ca997e46b9 --- /dev/null +++ b/programs/emulator/chip8/chip8.asm @@ -0,0 +1,114 @@ +; Author: rgimad (2021) +; License: GNU GPL v2 + +format binary as "" +use32 +org 0 +db 'MENUET01' ; signature +dd 1 ; header version +dd start ; entry point +dd _i_end ; end of image +dd _mem ; required memory size +dd _stacktop ; address of stack top +dd cmdline ; buffer for command line arguments +dd 0 ; buffer for path + +include 'constants.inc' + +; application's entry point +align 4 +start: + + stdcall chip8_init ; initialize + + DEBUGF DBG_INFO, "app started, args = %s\n", cmdline + DEBUGF DBG_INFO, "MAX_GAME_SIZE = %x = %u\n", MAX_GAME_SIZE, MAX_GAME_SIZE + +; xor ecx, ecx +; @@: +; mov al, byte [chip8_fontset + ecx] +; DEBUGF DBG_INFO, "%x ", al +; cmp ecx, 79 +; je @f +; inc ecx +; jmp @b +; @@: + + mov dword [fread_struct.filename], cmdline + stdcall chip8_loadgame, fread_struct + jz .file_not_found + + DEBUGF DBG_INFO, "file was read. bytes: %x %x %x..\n", [memory + 0x200], [memory + 0x200 + 4], [memory + 0x200 + 8] + + ; mov byte [gfx + 5], 1 + ; mov byte [gfx + 64], 1 + ; mov byte [gfx + 65], 1 + ; mov byte [gfx + 64*2 + 3], 1 + +.event_loop: + mcall 23, CLOCK_RATE ; wait for event with CLOCK_RATE timeout + ;DEBUGF DBG_INFO, "evenp loop iter i\n" + + cmp eax, 1 + je .event_redraw + + cmp eax, 2 + je .event_key + + cmp eax, 3 + je .event_button + + jmp .event_default + + .event_redraw: + stdcall draw_main_window + jmp .event_default + + .event_key: + mcall 2 + stdcall keyboard_update, eax + DEBUGF DBG_INFO, "key scancode %x\n", eax:4 + jmp .event_default + + .event_button: + mcall 17 + cmp ah, 1 + jne .event_default + mcall -1 + + .event_default: + stdcall chip8_emulatecycle + cmp byte [chip8_draw_flag], 0 + jz @f + + ; mov byte [gfx + 64*2 + 3], 1 ; DBG + + stdcall draw_screen + mov byte [chip8_draw_flag], 0 + @@: + stdcall chip8_tick + jmp .event_loop + +.file_not_found: + DEBUGF DBG_ERR, "Unable to open game file! eax = %u\n", eax + jmp .exit + +.exit: + mov eax, -1 + int 0x40 + +;;;;;;;;;;;;;;;;;;;;;;; + + +include 'gui.inc' + +include 'emu.inc' + +include 'utils.inc' + +include 'data.inc' + rb 4096 ; reserve for main thread stack +align 16 +_stacktop: + +_mem: diff --git a/programs/emulator/chip8/constants.inc b/programs/emulator/chip8/constants.inc new file mode 100644 index 0000000000..7245a375bd --- /dev/null +++ b/programs/emulator/chip8/constants.inc @@ -0,0 +1,29 @@ +; constants for formatted debug +__DEBUG__ = 1 ; 0 - disable debug output / 1 - enable debug output +__DEBUG_LEVEL__ = DBG_ERR ; set the debug level + +DBG_ALL = 0 ; all messages +DBG_INFO = 1 ; info and errors +DBG_ERR = 2 ; only errors + +; emulator constants +MAX_GAME_SIZE = 0x1000 - 0x200 +FONTSET_ADDRESS = 0x00 +FONTSET_BYTES_PER_CHAR = 5 +MEM_SIZE = 4096 +STACK_SIZE = 16 +KEY_SIZE = 16 +GFX_ROWS = 32 +GFX_COLS = 64 +GFX_SIZE = GFX_ROWS * GFX_COLS +GFX_PIX_SIZE = 10 +CLOCK_RATE = 1 ; in 10^-2 seconds + + +include '../../macros.inc' +purge mov, add, sub + +include '../../debug-fdo.inc' +include '../../proc32.inc' + +;========================================= \ No newline at end of file diff --git a/programs/emulator/chip8/data.inc b/programs/emulator/chip8/data.inc new file mode 100644 index 0000000000..c0df58a45c --- /dev/null +++ b/programs/emulator/chip8/data.inc @@ -0,0 +1,57 @@ +;========================================= +; initialized data + include_debug_strings ; for debug-fdo + + chip8_fontset db \ + 0xF0, 0x90, 0x90, 0x90, 0xF0, \ ; 0 + 0x20, 0x60, 0x20, 0x20, 0x70, \ ; 1 + 0xF0, 0x10, 0xF0, 0x80, 0xF0, \ ; 2 + 0xF0, 0x10, 0xF0, 0x10, 0xF0, \ ; 3 + 0x90, 0x90, 0xF0, 0x10, 0x10, \ ; 4 + 0xF0, 0x80, 0xF0, 0x10, 0xF0, \ ; 5 + 0xF0, 0x80, 0xF0, 0x90, 0xF0, \ ; 6 + 0xF0, 0x10, 0x20, 0x40, 0x40, \ ; 7 + 0xF0, 0x90, 0xF0, 0x90, 0xF0, \ ; 8 + 0xF0, 0x90, 0xF0, 0x10, 0xF0, \ ; 9 + 0xF0, 0x90, 0xF0, 0x90, 0x90, \ ; A + 0xE0, 0x90, 0xE0, 0x90, 0xE0, \ ; B + 0xF0, 0x80, 0x80, 0x80, 0xF0, \ ; C + 0xE0, 0x90, 0x90, 0x90, 0xE0, \ ; D + 0xF0, 0x80, 0xF0, 0x80, 0xF0, \ ; E + 0xF0, 0x80, 0xF0, 0x80, 0x80 ; F + + opcode dw 0 ; operation code + V db 16 dup(0) ; 16 8-bit registers + I dw 0 ; additional register (usually used for storing addresses) + P_C dw 0 ; program counter + S_P dw 0 ; stack pointer + delay_timer db 0 + sound_timer db 0 + stackmem dw STACK_SIZE dup(0) ; stack memory + key db KEY_SIZE dup (0) ; keyboard + chip8_draw_flag db 0 + next_rand dd 1 + + align 4 + fread_struct: + .subfunction dd 0 ; + 0 + .offset_low dd 0 ; + 4 + .offset_high dd 0 ; + 8 + .size dd MAX_GAME_SIZE ; + 12 + .buffer dd memory + 0x200 ; + 16 + db 0 ; + 20 + .filename: dd 0 ; + 24 + + sys_colors system_colors + main_window_title db 'CHIP-8 Emulator',0 + +;========================================= +align 16 +_i_end: +; uninitialized data + cmdline rb 1024 ; reserve for command line arguments + + memory rb MEM_SIZE + gfx rb GFX_SIZE + + ;tmp_buf rb 128 diff --git a/programs/emulator/chip8/emu.inc b/programs/emulator/chip8/emu.inc new file mode 100644 index 0000000000..b7703b10f7 --- /dev/null +++ b/programs/emulator/chip8/emu.inc @@ -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 diff --git a/programs/emulator/chip8/gui.inc b/programs/emulator/chip8/gui.inc new file mode 100644 index 0000000000..bc9624b25c --- /dev/null +++ b/programs/emulator/chip8/gui.inc @@ -0,0 +1,65 @@ +; draw main window +align 4 +proc draw_main_window stdcall + mcall 12, 1 ; notify about draw beginning + mcall 48, 3, sys_colors, sizeof.system_colors + + mov edx, [sys_colors.work] ; background color + or edx, 0x74000000 ; window type + ; DEBUGF DBG_INFO, "mainwindow w, h = %u, %u", GFX_COLS*GFX_PIX_SIZE + 8, GFX_ROWS*GFX_PIX_SIZE + 28 + mcall 0, <50, GFX_COLS*GFX_PIX_SIZE + 8>, <50, GFX_ROWS*GFX_PIX_SIZE + 28>, , , main_window_title ; + + stdcall draw_screen + + mcall 12, 2 ; notify about draw ending + ret +endp + +; draw screen +align 4 +proc draw_screen stdcall + pushad + locals + row_ind dd ? + col_ind dd ? + color dd ? + endl + + xor esi, esi +.loop1: + cmp esi, GFX_SIZE + jae .loop1_end + xor edx, edx + mov eax, esi + mov ecx, GFX_COLS + div ecx ; eax = row index, edx = col index + mov dword [row_ind], eax + mov dword [col_ind], edx + mov dword [color], 0x80FFFFFF ; white + cmp byte [esi + gfx], 0 ; check if cell is 0 or not 0 + jne @f + mov dword [color], 0x80000000 ; black + @@: + mov ebx, dword [col_ind] + imul ebx, GFX_PIX_SIZE + ;add ebx, WINDOW_BORDER + shl ebx, 16 + add ebx, GFX_PIX_SIZE + + mov ecx, dword [row_ind] + imul ecx, GFX_PIX_SIZE + ;add ecx, WINDOW_BORDER + shl ecx, 16 + add ecx, GFX_PIX_SIZE + + mov eax, 13 + mov edx, dword [color] + int 0x40 + + inc esi + jmp .loop1 + +.loop1_end: + popad + ret +endp \ No newline at end of file diff --git a/programs/emulator/chip8/readme.txt b/programs/emulator/chip8/readme.txt new file mode 100644 index 0000000000..611a50d013 --- /dev/null +++ b/programs/emulator/chip8/readme.txt @@ -0,0 +1,12 @@ +CHIP-8 Emulator for KolibriOS. +Author: rgimad +Year: 2021 + +How to use: + +Open shell in current directory (in Eolite Ctrl+G). +Type for example: + +chip8 roms/ibm.chip8 + +P.S. ROMs from https://github.com/dmatlack/chip8/tree/master/roms diff --git a/programs/emulator/chip8/roms/chip8.ch8 b/programs/emulator/chip8/roms/chip8.ch8 new file mode 100644 index 0000000000..74ab4bf916 Binary files /dev/null and b/programs/emulator/chip8/roms/chip8.ch8 differ diff --git a/programs/emulator/chip8/roms/clock.ch8 b/programs/emulator/chip8/roms/clock.ch8 new file mode 100644 index 0000000000..ec137bd137 Binary files /dev/null and b/programs/emulator/chip8/roms/clock.ch8 differ diff --git a/programs/emulator/chip8/roms/ibm.ch8 b/programs/emulator/chip8/roms/ibm.ch8 new file mode 100644 index 0000000000..113338e670 Binary files /dev/null and b/programs/emulator/chip8/roms/ibm.ch8 differ diff --git a/programs/emulator/chip8/roms/invaders1.ch8 b/programs/emulator/chip8/roms/invaders1.ch8 new file mode 100644 index 0000000000..3ada8dfe93 Binary files /dev/null and b/programs/emulator/chip8/roms/invaders1.ch8 differ diff --git a/programs/emulator/chip8/roms/keytest.ch8 b/programs/emulator/chip8/roms/keytest.ch8 new file mode 100644 index 0000000000..c60558ee71 Binary files /dev/null and b/programs/emulator/chip8/roms/keytest.ch8 differ diff --git a/programs/emulator/chip8/roms/life.ch8 b/programs/emulator/chip8/roms/life.ch8 new file mode 100644 index 0000000000..d35039a653 Binary files /dev/null and b/programs/emulator/chip8/roms/life.ch8 differ diff --git a/programs/emulator/chip8/roms/maze_alt.ch8 b/programs/emulator/chip8/roms/maze_alt.ch8 new file mode 100644 index 0000000000..0dca981949 Binary files /dev/null and b/programs/emulator/chip8/roms/maze_alt.ch8 differ diff --git a/programs/emulator/chip8/roms/pong1.ch8 b/programs/emulator/chip8/roms/pong1.ch8 new file mode 100644 index 0000000000..e371e91c0f Binary files /dev/null and b/programs/emulator/chip8/roms/pong1.ch8 differ diff --git a/programs/emulator/chip8/roms/pong2.ch8 b/programs/emulator/chip8/roms/pong2.ch8 new file mode 100644 index 0000000000..65d63106cc Binary files /dev/null and b/programs/emulator/chip8/roms/pong2.ch8 differ diff --git a/programs/emulator/chip8/roms/pong3.ch8 b/programs/emulator/chip8/roms/pong3.ch8 new file mode 100644 index 0000000000..295ce91c67 Binary files /dev/null and b/programs/emulator/chip8/roms/pong3.ch8 differ diff --git a/programs/emulator/chip8/roms/rnd.ch8 b/programs/emulator/chip8/roms/rnd.ch8 new file mode 100644 index 0000000000..c9f79e4c31 Binary files /dev/null and b/programs/emulator/chip8/roms/rnd.ch8 differ diff --git a/programs/emulator/chip8/roms/sqrt.ch8 b/programs/emulator/chip8/roms/sqrt.ch8 new file mode 100644 index 0000000000..56485517d0 Binary files /dev/null and b/programs/emulator/chip8/roms/sqrt.ch8 differ diff --git a/programs/emulator/chip8/roms/test_opcode.ch8 b/programs/emulator/chip8/roms/test_opcode.ch8 new file mode 100644 index 0000000000..f540f69ecf Binary files /dev/null and b/programs/emulator/chip8/roms/test_opcode.ch8 differ diff --git a/programs/emulator/chip8/roms/tetris1.ch8 b/programs/emulator/chip8/roms/tetris1.ch8 new file mode 100644 index 0000000000..9f5e0874db Binary files /dev/null and b/programs/emulator/chip8/roms/tetris1.ch8 differ diff --git a/programs/emulator/chip8/utils.inc b/programs/emulator/chip8/utils.inc new file mode 100644 index 0000000000..a58a6ff685 --- /dev/null +++ b/programs/emulator/chip8/utils.inc @@ -0,0 +1,112 @@ +; note: proc that defines without stdcall, call using "call" +; note: by stdcall convention callee is responsible for cleaning up the stack, +; arguments are pushed onto the stack in right-to-left order +align 4 +proc _memset stdcall, dest:dword, val:byte, cnt:dword ; doesnt clobber any registers + ;DEBUGF DBG_INFO, "memset(%x, %u, %u)\n", [dest], [val], [cnt] + push eax ecx edi + mov edi, dword [dest] + mov al, byte [val] + mov ecx, dword [cnt] + rep stosb + pop edi ecx eax + ret +endp + +align 4 +proc _srand stdcall, seed:dword + push eax + ;DEBUGF DBG_INFO, "_srand: next_rand = %u\n", [next_rand] + mov eax, dword [seed] + mov dword [next_rand], eax + pop eax + ret +endp + +align 4 +proc _rand stdcall + push ebx edx + ;DEBUGF DBG_INFO, "_rand: next_rand = %u\n", [next_rand] + mov eax, dword [next_rand] + mov ebx, 214013 + mul ebx + add eax, 2531011 + mov dword [next_rand], eax + shr eax, 16 + and eax, 0x7fff + pop edx ebx + ret +endp + +align 4 +proc _getseed stdcall + push edx + rdtsc + xor eax, edx + pop edx + ret +endp + +; calculate (num % num_mod) / num_div +align 4 +proc mod_div stdcall, num: dword, num_mod: dword, num_div:dword ; TODO check +; in: num - number +; num_mod - first divisor +; num_div - second divisor +; out: eax - the result +; destroys: mb flags + push ecx edx + xor edx, edx + mov eax, dword [num] + mov ecx, dword [num_mod] + div ecx + + mov eax, edx + xor edx, edx + mov ecx, dword [num_div] + div ecx + pop edx ecx + ret +endp + +; update key map; TODO impl; +align 4 +proc keyboard_update stdcall, _code: dword + push eax ebx ecx edx + mov eax, dword [_code] + + mov edx, eax + and edx, 0xF000 + + mov ebx, eax + and ebx, 0x0F00 + shr ebx, 8 + + xor ecx, ecx + cmp edx, 0x6000 + jne .if1_end + mov ecx, 0x9 +.if1_end: + add ebx, ecx + ; DEBUGF DBG_INFO, "keynum %x\n", ebx + cmp ebx, KEY_SIZE + jae .ret + DEBUGF DBG_INFO, "keynum %x\n", ebx + mov byte [key + ebx], 1 + +.ret: + pop edx ecx ebx eax + ret +endp + +; ; get seconds count +; align 4 +; proc get_clock stdcall +; ; out: eax - time from system start in 10^-2 secs +; push ebx +; mov eax, 26 +; mov ebx, 9 +; int 0x40 +; pop ebx +; ret +; endp