flat assembler
Message board for the users of flat assembler.
![]() |
Author |
|
windwakr 31 Aug 2010, 01:48
Simple Chip-8 I wrote in a couple of hours out of boredom.
VERY VERY ugly code, but it seems to work. There are some 'public domain' programs for it available here, just add a '.c8' extension to the end of them: http://www.zophar.net/pdroms/chip8.html The Chip-8 controls are mapped to: 1234 qwer asdf zxcv Emulator controls are: Space: Pause the emu Escape: Exit the emu Plus and Minus on the numpad: Increase/decrease the instructions per second. Control: When emu is paused, control allows you to load a new ROM. Emu remains paused. I'm sorry if my coding style causes you to go blind. ![]() The emu is probably very buggy Code: ;Simple Chip-8 emulator ;By windwakr ;Made at the end of August 2010 ;Some of the basic timer/gdi framework borrowed from Alphonso: ;http://board.flatassembler.net/topic.php?t=9174 format PE GUI 4.0 include 'win32ax.inc' entry start section '.code' code readable executable start: invoke QueryPerformanceCounter,qpc ;For PRNG. invoke GetProcessHeap ;Allocate the Chip-8's memory mov [hHeap],eax invoke HeapAlloc,[hHeap],0,0xFFF mov [hMem],eax stdcall loadrom,1 xor edx,edx ;Figure out how many Instructions to run per frame mov eax,[IPS] mov ebx,60 div ebx mov [IPF],eax ;Just normal window junk invoke LoadIcon,0,IDI_APPLICATION mov [wc.hIcon],eax invoke LoadCursor,0,IDC_ARROW mov [wc.hCursor],eax invoke GetModuleHandle,0 mov [hInstance],eax mov [wc.hInstance],eax invoke RegisterClass,wc invoke CreateDIBSection,0,BMPinfo,0,BitValues,0,0 mov [hBmp],eax invoke CreateWindowEx,0,_class,_title,WS_MINIMIZEBOX+WS_SYSMENU+WS_POPUP+WS_CAPTION+WS_VISIBLE,0,0,640,320,NULL,NULL,[hInstance],NULL mov [mainhwnd],eax ;The client area needs to be 640 by 320, not the window invoke GetClientRect,[mainhwnd],crect invoke GetWindowRect,[mainhwnd],wrect mov eax,[wrect.right] sub eax,[wrect.left] sub eax,[crect.right] add eax,640 mov ebx,[wrect.bottom] sub ebx,[wrect.top] sub ebx,[crect.bottom] add ebx,320 invoke MoveWindow,[mainhwnd],[wrect.left],[wrect.top],eax,ebx,FALSE invoke UpdateWindow,[mainhwnd] invoke CreateCompatibleDC,0 mov [hdcMem],eax invoke SelectObject,[hdcMem],[hBmp] mov [hbmOld],eax cinvoke wsprintf,buf,fmt,[IPS] invoke SetWindowText,[mainhwnd],buf invoke ShowWindow,[mainhwnd],SW_SHOWNORMAL invoke UpdateWindow,[mainhwnd] ;;;;;;; ;Clear the screen stdcall CPUclrscrn ;;;;;;; ;Set the timer to go off 60 times a second. invoke SetTimer,[mainhwnd],1,16,CPUproc mov [hTimer],eax msg_loop: invoke GetMessage,msg,NULL,0,0 or eax,eax jz end_loop invoke TranslateMessage,msg invoke DispatchMessage,msg jmp msg_loop end_loop: invoke KillTimer,[mainhwnd],[hTimer] invoke SelectObject,[hdcMem],[hbmOld] invoke DeleteObject,[hbmOld] invoke DeleteDC,[hdcMem] invoke HeapFree,[hHeap],0,[hMem] invoke ExitProcess,[msg.wParam] ;Firsttime variable tells it to close the program if it's the first time this is called and it fails. Else, we'll just keep emulating the open program. proc loadrom stdcall firsttime:DWORD ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;Ugly file selecting code... invoke GetOpenFileName, ofn cmp eax,0 jne .2 cmp [firsttime],1 jne .1 invoke MessageBox,NULL,'Error selecting file, closing down.','ERROR',MB_OK invoke ExitProcess,0 .1: mov eax,1 ret .2: stdcall CPUreset ;Reset the Chip-8 CPU invoke CreateFile,filebuf,GENERIC_READ,0,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0 cmp eax,INVALID_HANDLE_VALUE jne @f invoke MessageBox,NULL,'Error opening file, closing down.','ERROR',MB_OK invoke ExitProcess,0 @@: mov [hFile],eax invoke GetFileSize,[hFile],0 mov [nSize],eax mov eax,[hMem] add eax,0x200 invoke ReadFile,[hFile],eax,[nSize],bytesread,0 invoke CloseHandle,[hFile] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; mov eax,0 ret endp proc WindowProc uses ebx esi edi, hwnd,wmsg,wparam,lparam cmp [wmsg],WM_CLOSE je .wmclose cmp [wmsg],WM_PAINT je .wmpaint cmp [wmsg],WM_KEYDOWN je .wmkeydown cmp [wmsg],WM_KEYUP je .wmkeyup cmp [wmsg],WM_ERASEBKGND jne .defwndproc mov eax,1 jmp .finish .defwndproc: invoke DefWindowProc,[hwnd],[wmsg],[wparam],[lparam] jmp .finish .wmpaint: invoke BeginPaint,[hwnd],PaintS mov [hDC],eax invoke BitBlt,[hDC],0,0,640,320,[hdcMem],0,0,SRCCOPY invoke EndPaint,[hwnd],PaintS mov eax,1 jmp .finish ;Ugly keypress code, viewer beware.... .wmkeydown: pushad cmp [wparam],VK_ESCAPE je .wmclose cmp [wparam],VK_SPACE jne .notspace xor [paused],1 .updatetitle: cmp [paused],1 jne @f cinvoke wsprintf,buf,fmt_paused,[IPS] invoke SetWindowText,[mainhwnd],buf jmp .donekeys @@: cinvoke wsprintf,buf,fmt,[IPS] invoke SetWindowText,[mainhwnd],buf jmp .donekeys .notspace: cmp [wparam],VK_ADD jne .notadd cmp [IPS],10000 je .donekeys add [IPS],100 xor edx,edx mov eax,[IPS] mov ebx,60 div ebx mov [IPF],eax jmp .updatetitle .notadd: cmp [wparam],VK_SUBTRACT jne .notsubtract cmp [IPS],0 je .donekeys sub [IPS],100 xor edx,edx mov eax,[IPS] mov ebx,60 div ebx mov [IPF],eax jmp .updatetitle .notsubtract: xor ecx,ecx mov edi,keys mov esi,keycodes @@: lodsb movzx eax,al cmp [wparam],eax jne .notkey add edi,ecx mov al,1 stosb jmp .donekeys .notkey: inc ecx cmp ecx,16 jne @b cmp [wparam],VK_CONTROL jne .donekeys cmp [paused],1 jne .donekeys stdcall loadrom,0 cmp eax,1 je .donekeys stdcall CPUclrscrn .donekeys: popad xor eax,eax jmp .finish .wmkeyup: pushad xor ecx,ecx mov edi,keys mov esi,keycodes @@: lodsb movzx eax,al cmp [wparam],eax jne .notkey1 add edi,ecx mov al,0 stosb jmp .donekeys1 .notkey1: inc ecx cmp ecx,16 jne @b .donekeys1: popad xor eax,eax jmp .finish .wmclose: invoke PostQuitMessage,0 xor eax,eax jmp .finish .finish: ret endp proc CPUreset stdcall mov [CI],0 mov [CPC],0x200 mov [CDelay],0 mov [CSound],0 mov dword[CR],0 mov dword[CR+4],0 mov dword[CR+8],0 mov dword[CR+12],0 mov [curstck],endstack mov ecx,0xFFF shr 2 ;Zero out the memory mov edi,[hMem] mov eax,0 rep stosd mov edi,[hMem] mov esi,font mov ecx,80 @@: lodsb stosb loop @b ret endp ;The timer proc proc CPUproc uses ebx esi edi, hwnd,tmsg,event,dtime cmp [paused],1 je @f stdcall CPUdectimers stdcall CPUexecute,[IPF] ;Execute the number of instructions specified by IPF(instructions per frame) @@: invoke InvalidateRect,[mainhwnd],0,0 xor eax,eax ret endp proc CPUdectimers stdcall cmp [CDelay],0 je @f dec [CDelay] @@: cmp [CSound],0 je @f dec [CSound] stdcall CPUbeep @@: ret endp proc CPUbeep stdcall ;Empty.....for now..... ret endp ;Will document this later..... proc CPUexecute stdcall number:DWORD locals ;-.decode0 to save a word for each one jmptbl dw .decode0-.decode0, .opcode1NNN-.decode0, .opcode2NNN-.decode0, .opcode3XNN-.decode0, .opcode4XNN-.decode0, .opcode5XY0-.decode0,\ .opcode6XNN-.decode0, .opcode7XNN-.decode0, .decode8-.decode0, .opcode9XY0-.decode0, .opcodeANNN-.decode0, .opcodeBNNN-.decode0,\ .opcodeCXNN-.decode0, .opcodeDXYN-.decode0, .decodeE-.decode0, .decodeF-.decode0 num dd 0 endl mov ebx,[number] mov [num],ebx .loop: cmp [num],0 je .done mov edi,[hMem] mov ebx,[CPC] add edi,ebx xor ebx,ebx mov bh,byte[edi] or bl,byte[edi+1] add [CPC],2 mov eax,ebx and eax,0xF000 shr eax,12 lea eax,[eax*2+jmptbl] movzx eax,word[eax] add eax,.decode0 jmp eax .decode0: mov eax,ebx and eax,0xF cmp al,0x0 je .opcode00E0 cmp al,0xE je .opcode00EE jmp .next .decode8: mov eax,ebx and eax,0xF cmp al,0x0 je .opcode8XY0 cmp al,0x1 je .opcode8XY1 cmp al,0x2 je .opcode8XY2 cmp al,0x3 je .opcode8XY3 cmp al,0x4 je .opcode8XY4 cmp al,0x5 je .opcode8XY5 cmp al,0x6 je .opcode8XY6 cmp al,0x7 je .opcode8XY7 cmp al,0xE je .opcode8XYE jmp .next .decodeE: mov eax,ebx and eax,0xF cmp al,0x1 je .opcodeEXA1 cmp al,0xE je .opcodeEX9E jmp .next .decodeF: mov eax,ebx and eax,0xFF cmp al,0x07 je .opcodeFX07 cmp al,0x0A je .opcodeFX0A cmp al,0x15 je .opcodeFX15 cmp al,0x18 je .opcodeFX18 cmp al,0x1E je .opcodeFX1E cmp al,0x29 je .opcodeFX29 cmp al,0x33 je .opcodeFX33 cmp al,0x55 je .opcodeFX55 cmp al,0x65 je .opcodeFX65 jmp .next .opcode00E0: stdcall CPUclrscrn jmp .next .opcode00EE: mov edi,[curstck] mov eax,[edi] mov [CPC],eax add [curstck],4 jmp .next .opcode1NNN: and ebx,0xFFF mov [CPC],ebx jmp .next .opcode2NNN: sub [curstck],4 mov edi,[curstck] mov eax,[CPC] mov [edi],eax mov eax,ebx and eax,0xFFF mov [CPC],eax jmp .next .opcode3XNN: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR movzx eax,byte[edi] and ebx,0x00FF cmp eax,ebx jne @f add [CPC],2 @@: jmp .next .opcode4XNN: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR movzx eax,byte[edi] and ebx,0x00FF cmp eax,ebx je @f add [CPC],2 @@: jmp .next .opcode5XY0: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR mov al,byte[edi] and ebx,0x00F0 shr ebx,4 add ebx,CR cmp al,byte[ebx] jne @f add [CPC],2 @@: jmp .next .opcode6XNN: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR and ebx,0x00FF mov byte[edi],bl jmp .next .opcode7XNN: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR and ebx,0x00FF add byte[edi],bl jmp .next .opcode8XY0: mov edi,ebx and edi,0x00F0 shr edi,4 add edi,CR mov al,byte[edi] and ebx,0x0F00 shr ebx,8 add ebx,CR mov edi,ebx mov byte[edi],al jmp .next .opcode8XY1: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR mov al,byte[edi] and ebx,0x00F0 shr ebx,4 add ebx,CR or al,byte[ebx] mov byte[edi],al jmp .next .opcode8XY2: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR mov al,byte[edi] and ebx,0x00F0 shr ebx,4 add ebx,CR and al,byte[ebx] mov byte[edi],al jmp .next .opcode8XY3: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR mov al,byte[edi] and ebx,0x00F0 shr ebx,4 add ebx,CR xor al,byte[ebx] mov byte[edi],al jmp .next .opcode8XY4: mov edi,ebx and ebx,0x00F0 shr ebx,4 add ebx,CR mov al,byte[ebx] and edi,0x0F00 shr edi,8 add edi,CR add byte[edi],al jnc @f mov edi,0xF add edi,CR mov byte[edi],1 @@: jmp .next .opcode8XY5: mov edi,ebx and ebx,0x00F0 shr ebx,4 add ebx,CR mov al,byte[ebx] and edi,0x0F00 shr edi,8 add edi,CR sub byte[edi],al jc @f mov edi,0xF add edi,CR mov byte[edi],1 jmp .next @@: mov edi,0xF add edi,CR mov byte[edi],0 jmp .next .opcode8XY6: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR mov al,byte[edi] shr al,1 mov ah,0 rcl ah,1 mov byte[edi],al mov edi,CR add edi,0xF cmp ah,1 je @f mov byte[edi],0 jmp .next @@: mov byte[edi],1 jmp .next .opcode8XY7: mov edi,ebx and edi,0x00F0 shr edi,4 add edi,CR mov al,byte[edi] and ebx,0x0F00 shr ebx,8 add ebx,CR sub al,byte[ebx] mov byte[ebx],al jnc @f mov edi,0xF add edi,CR mov byte[edi],0 jmp .next @@: mov edi,0xF add edi,CR mov byte[edi],1 jmp .next .opcode8XYE: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR mov al,byte[edi] shl al,1 mov ah,0 rcl ah,1 mov byte[edi],al mov edi,CR add edi,0xF cmp ah,1 je @f mov byte[edi],0 jmp .next @@: mov byte[edi],1 jmp .next .opcode9XY0: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR mov al,byte[edi] and ebx,0x00F0 shr ebx,4 add ebx,CR cmp al,byte[ebx] je @f add [CPC],2 @@: jmp .next .opcodeANNN: and ebx,0x0FFF mov [CI],ebx jmp .next .opcodeBNNN: and ebx,0x0FFF movzx eax,byte[CR] add ebx,eax mov [CPC],ebx jmp .next .opcodeCXNN: mov eax,dword[qpc] imul eax or eax,0x5 add dword[qpc],eax mov edi,ebx and ebx,0x00FF and eax,ebx and edi,0x0F00 shr edi,8 add edi,CR mov byte[edi],al jmp .next .opcodeDXYN: mov edi,ebx and edi,0x0F00 shr edi,8 add edi,CR mov ecx,ebx and ecx,0x000F mov [sprheight],ecx and ebx,0x00F0 shr ebx,4 add ebx,CR mov ecx,10 movzx eax,byte[edi] mul ecx mov [coordx],eax movzx eax,byte[ebx] mul ecx mov [coordy],eax mov edi,CR add edi,0xF mov byte[edi],0 mov [yline],0 .oloop: mov edi,[hMem] add edi,[CI] add edi,[yline] movzx eax,byte[edi] mov [sprdata],eax mov [xline],0 mov [mask],7 .iloop: mov ecx,[mask] mov eax,1 shl eax,cl and eax,[sprdata] jz .nextloop mov ecx,10 mov eax,[yline] mul ecx add eax,[coordy] mov ebx,[xline] xchg eax,ebx mul ecx xchg eax,ebx add ebx,[coordx] mov [color],0 mov edi,[BitValues] mov ecx,640 mul ecx add eax,ebx mov ecx,4 mul ecx add edi,eax movzx eax,byte[edi] cmp eax,0 jne @f mov [color],0x00FFFFFF mov ebx,CR add ebx,0xF mov byte[ebx],1 @@: mov eax,[color] mov ebx,0 .ooloop: mov ecx,0 .iiloop: mov eax,[color] stosd inc ecx cmp ecx,10 jnz .iiloop add edi,2520 inc ebx cmp ebx,10 jnz .ooloop .nextloop: inc [xline] dec [mask] cmp [xline],8 jne .iloop inc [yline] mov eax,[sprheight] cmp [yline],eax jne .oloop jmp .next .opcodeEX9E: and ebx,0x0F00 shr ebx,8 add ebx,CR movzx eax,byte[ebx] mov ebx,keys add ebx,eax cmp byte[ebx],1 jne @f add [CPC],2 @@: jmp .next .opcodeEXA1: and ebx,0x0F00 shr ebx,8 add ebx,CR movzx eax,byte[ebx] mov ebx,keys add ebx,eax cmp byte[ebx],1 je @f add [CPC],2 @@: jmp .next .opcodeFX07: and ebx,0x0F00 shr ebx,8 add ebx,CR mov al,[CDelay] mov byte[ebx],al jmp .next .opcodeFX0A: mov ecx,15 mov edi,keys @@: cmp byte[edi],1 je .keypressed inc edi dec ecx jnz @b sub [CPC],2 jmp .next .keypressed: sub edi,keys mov eax,edi and ebx,0x0F00 shr ebx,8 add ebx,CR mov byte[ebx],al jmp .next .opcodeFX15: and ebx,0x0F00 shr ebx,8 add ebx,CR mov al,byte[ebx] mov [CDelay],al jmp .next .opcodeFX18: and ebx,0x0F00 shr ebx,8 add ebx,CR mov al,byte[ebx] mov [CSound],al jmp .next .opcodeFX1E: mov edi,CR add edi,0xF mov byte[edi],0 and ebx,0x0F00 shr ebx,8 add ebx,CR movzx eax,byte[ebx] add [CI],eax cmp [CI],0xFFF jna @f mov byte[edi],1 @@: jmp .next .opcodeFX29: and ebx,0x0F00 shr ebx,8 add ebx,CR movzx eax,byte[ebx] mov bl,5 mul bl mov [CI],eax jmp .next .opcodeFX33: and ebx,0x0F00 shr ebx,8 add ebx,CR movzx eax,byte[ebx] mov ebx,eax mov cl,100 div cl mov edi,[hMem] add edi,[CI] stosb mov eax,ebx mov cl,10 div cl mov [edi+1],ah movzx eax,al div cl mov al,ah stosb jmp .next .opcodeFX55: mov edi,[hMem] add edi,[CI] mov esi,CR and ebx,0x0F00 shr ebx,8 xor ecx,ecx @@: lodsb stosb inc ecx cmp ecx,ebx jna @b ;add [CI],ebx ;These two lines of code SHOULD NOT be commented, but it oddly messes up when they aren't. ;inc [CI] ;Must be some bug elsewhere in the emulation... jmp .next .opcodeFX65: mov esi,[hMem] add esi,[CI] mov edi,CR and ebx,0x0F00 shr ebx,8 xor ecx,ecx @@: lodsb stosb inc ecx cmp ecx,ebx jna @b ;add [CI],ebx ;These two lines of code SHOULD NOT be commented, but it oddly messes up when they aren't. ;inc [CI] ;Must be some bug elsewhere in the emulation... jmp .next .next: dec [num] jmp .loop .done: ret endp proc CPUclrscrn mov edi,[BitValues] mov ecx,640*320 mov eax,0x00FFFFFF rep stosd ret endp section '.data' data readable writeable hFile dd 0 nSize dd 0 bytesread dd 0 hBmp dd 0 hDC dd 0 hdcMem dd 0 hbmOld dd 0 hTimer dd 0 hHeap dd 0 mainhwnd dd 0 hInstance dd 0 BitValues dd 0 _title db 'Simple Chip-8 emulator',0 _class db 'CHIP8EMU',0 wc WNDCLASS 0,WindowProc,0,0,NULL,NULL,NULL,7,NULL,_class msg MSG PaintS PAINTSTRUCT wrect RECT crect RECT BMPinfo BITMAPINFOHEADER sizeof.BITMAPINFOHEADER,640,-320,1,32,BI_RGB,0,0,0,0,0 CI dd 0 ;Address pointer CPC dd 0 ;Program counter CR db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;The 16 registers CDelay db 0 ;Delay counter CSound db 0 ;Beep counter hMem dd 0 keys db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ;The 16 keys keycodes db 0x58,0x31,0x32,0x33,0x51,0x57,0x45,0x41,0x53,0x44,0x5A,0x43,0x34,0x52,0x46,0x56 paused dd 0 ;Is it paused? font db 0xF0, 0x90, 0x90, 0x90, 0xF0, 0x20, 0x60, 0x20, 0x20, 0x70, 0xF0, 0x10, 0xF0, 0x80, 0xF0,\ ;Font 'borrowed' from some Chip-8 emu I found 0xF0, 0x10, 0xF0, 0x10, 0xF0, 0x90, 0x90, 0xF0, 0x10, 0x10, 0xF0, 0x80, 0xF0, 0x10, 0xF0,\ 0xF0, 0x80, 0xF0, 0x90, 0xF0, 0xF0, 0x10, 0x20, 0x40, 0x40, 0xF0, 0x90, 0xF0, 0x90, 0xF0,\ 0xF0, 0x90, 0xF0, 0x10, 0xF0, 0xF0, 0x90, 0xF0, 0x90, 0x90, 0xE0, 0x90, 0xE0, 0x90, 0xE0,\ 0xF0, 0x80, 0x80, 0x80, 0xF0, 0xE0, 0x90, 0x90, 0x90, 0xE0, 0xF0, 0x80, 0xF0, 0x80, 0xF0,\ 0xF0, 0x80, 0xF0, 0x80, 0x80 IPS dd 600 IPF dd 0 yline dd 0 xline dd 0 sprdata dd 0 sprx dd 0 spry dd 0 coordx dd 0 coordy dd 0 sprheight dd 0 color dd 0 mask dd 0 ofn OPENFILENAME sizeof.OPENFILENAME,NULL,NULL,filefilter,NULL,NULL,NULL,filebuf,256,NULL,NULL,NULL,NULL,OFN_FILEMUSTEXIST + OFN_PATHMUSTEXIST,NULL,NULL,NULL,NULL,NULL,NULL filefilter db 'Chip-8 files',0,'*.c8',0,0 align 4 qpc: dq 0 curstck dd 0 stck rd 16 endstack = $-4 fmt db 'Simple Chip-8 emulator IPS: %hu',0 fmt_paused db 'PAUSED IPS: %hu',0 buf rb 200 filebuf rb 260 section '.idata' import data readable writeable library kernel32,'KERNEL32.DLL',\ user32,'USER32.DLL',\ gdi32,'GDI32.DLL',\ comdlg32,'Comdlg32.dll' include 'api\kernel32.inc' include 'api\user32.inc' include 'api\gdi32.inc' include 'api\comdlg32.inc' EDIT: Oops, bug in BCD instruction, should be fixed now. EDIT2: Simple jump table, as suggested below. Multi-instruction numbers still have to get further decoding, though. Last edited by windwakr on 31 Aug 2010, 14:30; edited 2 times in total |
|||
![]() |
|
windwakr 31 Aug 2010, 14:29
What painting error are you seeing?
|
|||
![]() |
|
bitRAKE 31 Aug 2010, 15:45
If I force the title bar to redraw then it is corrected:
|
||||||||||
![]() |
|
bitRAKE 31 Aug 2010, 16:49
AdjustWindowRectEx and WM_ERASEBKGND painting:
|
|||||||||||
![]() |
|
Coty 02 Sep 2010, 10:57
Awesome! i have always wanted to build my own emu, mabey i will get to it someday...
thanks for sharing *bows down* |
|||
![]() |
|
mindcooler 02 Sep 2010, 14:02
|
|||
![]() |
|
flaith 04 Sep 2010, 20:27
i really like those retro things, thanks for sharing
![]() _________________ Je suis sur de 'rien', mais je ne suis pas sur du 'tout'. |
|||
![]() |
|
rugxulo 09 Sep 2010, 20:00
Congrats, windwakr, nice to see you programming something cool, as usual.
![]() |
|||
![]() |
|
< Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.