├── .DS_Store ├── LICENSE ├── Makefile ├── README.md ├── Screenshots ├── .DS_Store ├── Pong-Chip8 Mac.png └── linux-screenshot.png ├── beep.wav ├── c8games ├── .DS_Store ├── 15PUZZLE ├── BLINKY ├── BLITZ ├── BRIX ├── CONNECT4 ├── GUESS ├── HIDDEN ├── INVADERS ├── KALEID ├── MAZE ├── MERLIN ├── MISSILE ├── PONG ├── PONG2 ├── PUZZLE ├── SYZYGY ├── TANK ├── TETRIS ├── TICTAC ├── UFO ├── VBRIX ├── VERS ├── WIPEOFF ├── breakout.ch8 ├── connnect4.ch8 ├── invaders.ch8 ├── maze.ch8 ├── pong.ch8 ├── tetris.ch8 └── tictac.ch8 ├── ch8 ├── ch8.c ├── ch8.dSYM └── Contents │ ├── Info.plist │ └── Resources │ └── DWARF │ └── ch8 ├── cpu.c ├── cpu.h ├── dbg.h └── opcodes.h /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Casey Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-Wall -g -I/System/Library/Frameworks/GLUT.framework/Headers -I/System/Library/Frameworks/SDL2_mixer.framework/Headers 2 | LDFLAGS=-framework OpenGL -framework GLUT -framework SDL2_mixer 3 | 4 | 5 | build: ch8 6 | 7 | ch8: ch8.c cpu.c 8 | 9 | clean: 10 | rm -f main 11 | rm -f cpu 12 | rm -f ch8 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chip-Emul8 2 |

My first little emulator of a Chip-8

3 | 4 | 5 |

The Idea

6 |

So I first decided to code this Chip-8 emulator around a week ago as I've really wanted to get into emulation and as far as I was aware - writing one is the best way to learn about it. And I can definitely agree. I learnt a lot when going through this and searching the net for info, how to do certain things, using libraries etc and it has all been a great experience. The plan was that this would introduce me to the basics of emulation so I can progress to coding a Gameboy DMG-01 emulator... eventually.

7 | 8 |

What I Did

9 |

I started where most people probably would: how to code a chip 8 emulator search in Google. I found the multigesture post linked at the bottom and I got going. I modelled the CPU, added the emulators basic functions and it was all good. I then tweaked around with some of the modelling for example using the header so I could model the registers as uint8_t rather than unsigned char. These little things just made it a bit easier to understand and keep track of everything. After that I went on to begin decoding the opcodes using the Cowgod's technical reference linked down below (as best as I could) and then took a look at a couple of the other emulators down below as reference on some of the instructions I was unsure about. When I finished writing them for the first time and had fixed the small syntax errors etc. I ran the program and it was fine! I couldn't believe that it actually worked (wasn't what I was expecting but then again there are only 36 instructions). I went on to looking at how I was going to draw the graphics. From what I had looked up I was either going to use SDL2 or GLUT. After playing round with both I decided to go with GLUT - primarily because it ships with OSX but also because it just seemed a bit easier to setup and initialise. To draw the graphics on screen was fairly simple as the Chip-8 has monochrome graphics and a 64 x 32 pixel display. Just iterate through my screen array and draw the pixels. On first run the picture was a mess. So I spent around an hour going back through my opcodes optimising some and correcting others and now that is all done so here we have the Chip-Emul8.

10 | 11 |

Plans

12 |

The only thing I have planned for now is to implement sounds, that's it.

13 | 14 | 15 |

Screenshots

16 |

Mac

17 | 18 | 19 |

Linux

20 | 21 | 22 |

How To Use It

23 |

Currently I've only tested this on Mac and I believe you have to download GLUT on other OSs. First off, download the repo and open it up. Navigate to the directory and run

24 |

Mac

25 |

FYI: If you have updated to sierra, some of the GLUT library has been fully deprecated and so will no longer compile.

26 | ```make build``` 27 |

After that, to use the emulator type

28 | ```./ch8 ./``` 29 |

Controls... I'm not exactly sure as to the controls for all the games but the keys will be one of: 1, 2, 3, 4, q, w, e, r, t, y, a, s, d, f, z, x, c, v. To quit the emulator press esc.

30 | 31 |

Windows

32 |

To be done..

33 | 34 |

Linux

35 |

Exact same as Mac.

36 | 37 |
Feel free to use the code so long as you give credit to me.
38 | 39 |

Here are some useful links:

40 | 41 |

About the CHIP-8:

42 |

https://en.wikipedia.org/wiki/CHIP-8#Virtual_machine_description

43 | 44 |

Chip-8 Opcodes:

45 |

http://devernay.free.fr/hacks/chip8/C8TECH10.HTM

46 | 47 |

Writing an emulator:

48 |

http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/

49 |

http://mattmik.com/files/chip8/mastering/chip8.html

50 |

http://emulator101.com/

51 |

http://fms.komkon.org/EMUL8/HOWTO.html

52 | 53 |

ROMS:

54 |

http://www.zophar.net/

55 | 56 |

Reading in files as byte arrays:

57 |

http://www.linuxquestions.org/questions/programming-9/c-howto-read-binary-file-into-buffer-172985/

58 | 59 |

Some other good emulators:

60 |

https://github.com/eshyong/Chip-8-Emulator

61 |

https://github.com/bryangarza/chip8

62 |

https://github.com/prophittcorey/iC8

63 | 64 | -------------------------------------------------------------------------------- /Screenshots/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/Screenshots/.DS_Store -------------------------------------------------------------------------------- /Screenshots/Pong-Chip8 Mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/Screenshots/Pong-Chip8 Mac.png -------------------------------------------------------------------------------- /Screenshots/linux-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/Screenshots/linux-screenshot.png -------------------------------------------------------------------------------- /beep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/beep.wav -------------------------------------------------------------------------------- /c8games/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/.DS_Store -------------------------------------------------------------------------------- /c8games/15PUZZLE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/15PUZZLE -------------------------------------------------------------------------------- /c8games/BLINKY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/BLINKY -------------------------------------------------------------------------------- /c8games/BLITZ: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/BLITZ -------------------------------------------------------------------------------- /c8games/BRIX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/BRIX -------------------------------------------------------------------------------- /c8games/CONNECT4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/CONNECT4 -------------------------------------------------------------------------------- /c8games/GUESS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/GUESS -------------------------------------------------------------------------------- /c8games/HIDDEN: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/HIDDEN -------------------------------------------------------------------------------- /c8games/INVADERS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/INVADERS -------------------------------------------------------------------------------- /c8games/KALEID: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/KALEID -------------------------------------------------------------------------------- /c8games/MAZE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/MAZE -------------------------------------------------------------------------------- /c8games/MERLIN: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/MERLIN -------------------------------------------------------------------------------- /c8games/MISSILE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/MISSILE -------------------------------------------------------------------------------- /c8games/PONG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/PONG -------------------------------------------------------------------------------- /c8games/PONG2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/PONG2 -------------------------------------------------------------------------------- /c8games/PUZZLE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/PUZZLE -------------------------------------------------------------------------------- /c8games/SYZYGY: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/SYZYGY -------------------------------------------------------------------------------- /c8games/TANK: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/TANK -------------------------------------------------------------------------------- /c8games/TETRIS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/TETRIS -------------------------------------------------------------------------------- /c8games/TICTAC: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/TICTAC -------------------------------------------------------------------------------- /c8games/UFO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/UFO -------------------------------------------------------------------------------- /c8games/VBRIX: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/VBRIX -------------------------------------------------------------------------------- /c8games/VERS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/VERS -------------------------------------------------------------------------------- /c8games/WIPEOFF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/WIPEOFF -------------------------------------------------------------------------------- /c8games/breakout.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/breakout.ch8 -------------------------------------------------------------------------------- /c8games/connnect4.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/connnect4.ch8 -------------------------------------------------------------------------------- /c8games/invaders.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/invaders.ch8 -------------------------------------------------------------------------------- /c8games/maze.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/maze.ch8 -------------------------------------------------------------------------------- /c8games/pong.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/pong.ch8 -------------------------------------------------------------------------------- /c8games/tetris.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/tetris.ch8 -------------------------------------------------------------------------------- /c8games/tictac.ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/c8games/tictac.ch8 -------------------------------------------------------------------------------- /ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/ch8 -------------------------------------------------------------------------------- /ch8.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "cpu.h" 5 | #include "dbg.h" 6 | 7 | 8 | 9 | chip8 cpu; 10 | 11 | void initGL(); //initialise GL 12 | 13 | void draw_square(float x_coord, float y_coord); //draw pixel 14 | 15 | void render(); //update render 16 | 17 | void idle(); //idling function 18 | 19 | void handle_key_press(unsigned char key, int x, int y); 20 | 21 | void handle_key_release(unsigned char key, int x, int y); 22 | 23 | int main(int argc, char const *argv[]) 24 | { 25 | 26 | const char * game = argv[1]; 27 | if(!game) { 28 | log_info("Usage: ./ch8 ./"); 29 | exit(1); 30 | } 31 | 32 | initialise_cpu(&cpu); 33 | load_rom(&cpu, game); 34 | 35 | 36 | glutInit(&argc, argv); //initialise glut 37 | 38 | //initialise display and window 39 | 40 | glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE); 41 | glutInitWindowPosition(100, 100); 42 | glutInitWindowSize(640, 320); 43 | glutCreateWindow("Chip-Emul8"); 44 | 45 | initGL(); 46 | 47 | //handle key presses and releases 48 | glutKeyboardFunc(handle_key_press); 49 | glutKeyboardUpFunc(handle_key_release); 50 | 51 | //draw function 52 | glutDisplayFunc(render); 53 | 54 | //idle function (to redraw) 55 | glutIdleFunc(idle); 56 | 57 | glutMainLoop(); 58 | 59 | return 0; 60 | } 61 | 62 | void handle_key_press(unsigned char key, int x, int y) { 63 | unsigned char input; 64 | switch(key) { 65 | case 27: 66 | log_info("Exiting emulator..."); 67 | exit(0); 68 | case '1': 69 | // only certain key values are passed to the chip 70 | input = 0x0001; 71 | break; 72 | case '2': 73 | input = 0x0002; 74 | break; 75 | case '3': 76 | input = 0x0003; 77 | break; 78 | case '4': 79 | input = 0x000C; 80 | break; 81 | case 'q': 82 | input = 0x0004; 83 | break; 84 | case 'w': 85 | input = 0x0005; 86 | break; 87 | case 'e': 88 | input = 0x0006; 89 | break; 90 | case 'r': 91 | input = 0x000D; 92 | break; 93 | case 'a': 94 | input = 0x0007; 95 | break; 96 | case 's': 97 | input = 0x0008; 98 | break; 99 | case 'd': 100 | input = 0x0009; 101 | break; 102 | case 'f': 103 | input = 0x000E; 104 | break; 105 | case 'z': 106 | input = 0x000A; 107 | break; 108 | case 'x': 109 | input = 0x0000; 110 | break; 111 | case 'c': 112 | input = 0x000B; 113 | break; 114 | case 'v': 115 | input = 0x000F; 116 | break; 117 | default: 118 | break; 119 | } 120 | handle_input(&cpu, input); 121 | 122 | 123 | } 124 | 125 | void handle_key_release(unsigned char key, int x, int y) { 126 | handle_input(&cpu, 0x00FF); 127 | } 128 | 129 | void draw_square(float x_coord, float y_coord) { 130 | glBegin(GL_QUADS); 131 | glColor3f(1.0f, 1.0f, 1.0f); 132 | glVertex2f(x_coord, y_coord); 133 | glVertex2f(x_coord + 10, y_coord); 134 | glVertex2f(x_coord + 10, y_coord + 10); 135 | glVertex2f(x_coord, y_coord + 10); 136 | glEnd(); 137 | } 138 | 139 | void render() { 140 | glClear(GL_COLOR_BUFFER_BIT); //clears screen 141 | 142 | glMatrixMode(GL_MODELVIEW); 143 | 144 | glLoadIdentity(); 145 | 146 | emulate_cycle(&cpu); 147 | 148 | int i, j; 149 | for(i = 0; i < 640; i++) { 150 | for(j = 0; j < 320; j++) { 151 | if(cpu.gfx[i][j] == 1) { 152 | draw_square((float)(i * 10), (float) (j * 10)); 153 | } 154 | } 155 | } 156 | 157 | glutSwapBuffers(); //smooth animation 158 | } 159 | 160 | void idle() { 161 | glutPostRedisplay(); //calls to redraw screen 162 | } 163 | 164 | void initGL() { 165 | // sets up GLUT window for 2D drawing 166 | glMatrixMode(GL_PROJECTION); 167 | glLoadIdentity(); 168 | gluOrtho2D(0.0, 640, 320, 0.0); 169 | 170 | glMatrixMode(GL_MODELVIEW); 171 | glLoadIdentity(); 172 | 173 | // clears screen color 174 | glClearColor(0.f, 0.f, 0.f, 1.f); 175 | } 176 | 177 | 178 | -------------------------------------------------------------------------------- /ch8.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.ch8 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /ch8.dSYM/Contents/Resources/DWARF/ch8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shnupta/Chip-Emul8/71a8f562ebb3dd1040924d53447720f3ee69c5c6/ch8.dSYM/Contents/Resources/DWARF/ch8 -------------------------------------------------------------------------------- /cpu.c: -------------------------------------------------------------------------------- 1 | #include "dbg.h" 2 | #include "cpu.h" 3 | #include "opcodes.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Mix_Music * beep_sound = NULL; 10 | Mix_Chunk * beep_scratch = NULL; 11 | 12 | unsigned char font_set[80] = 13 | { 14 | 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 15 | 0x20, 0x60, 0x20, 0x20, 0x70, // 1 16 | 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 17 | 0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3 18 | 0x90, 0x90, 0xF0, 0x10, 0x10, // 4 19 | 0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5 20 | 0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6 21 | 0xF0, 0x10, 0x20, 0x40, 0x40, // 7 22 | 0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8 23 | 0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9 24 | 0xF0, 0x90, 0xF0, 0x90, 0x90, // A 25 | 0xE0, 0x90, 0xE0, 0x90, 0xE0, // B 26 | 0xF0, 0x80, 0x80, 0x80, 0xF0, // C 27 | 0xE0, 0x90, 0x90, 0x90, 0xE0, // D 28 | 0xF0, 0x80, 0xF0, 0x80, 0xF0, // E 29 | 0xF0, 0x80, 0xF0, 0x80, 0x80 // F 30 | }; 31 | 32 | 33 | void initialise_cpu(chip8 * cpu) { 34 | memset(cpu->memory, 0, 4096); //reset memory 35 | memset(cpu->V, 0, 16); //reset registers 36 | memset(cpu->stack, 0, 16); //reset stack 37 | memset(cpu->keys, 0, 16); //reset keys 38 | //defaults 39 | cpu->I = 0; 40 | cpu->pc = 0x200; 41 | cpu->sp = 0; 42 | cpu->delay_timer = 0; 43 | cpu->sound_timer = 0; 44 | cpu->opcode = 0; 45 | 46 | // load fonts 47 | for (int i = 0; i < 80; i++) { 48 | cpu->memory[i] = font_set[i]; 49 | } 50 | 51 | clear_screen(cpu); 52 | 53 | srand(time(NULL)); //reset random seed 54 | 55 | 56 | 57 | if(Mix_OpenAudio(441000, MIX_DEFAULT_FORMAT, 2, 2048) < 0) { 58 | log_err("SDL_mixer error."); 59 | } 60 | 61 | 62 | beep_sound = Mix_LoadMUS("beep.wav"); 63 | 64 | if(beep_sound == NULL) { 65 | log_err("Loading of sound failed."); 66 | } 67 | 68 | 69 | 70 | } 71 | 72 | 73 | bool load_rom(chip8 * cpu, const char *rom_name) { 74 | FILE * file = fopen(rom_name, "rb"); //open file in binary mode 75 | if(!file) { 76 | log_err("File does not exist."); 77 | return false; 78 | } 79 | 80 | fseek(file, 0, SEEK_END); 81 | unsigned long buffer_size = ftell(file); 82 | rewind(file); 83 | 84 | log_info("Read %lu bytes from %s", buffer_size, rom_name); 85 | 86 | char *buffer = (char *) malloc((buffer_size + 1) * sizeof(char)); //allocate memory for buffer 87 | fread(buffer, buffer_size, 1, file); 88 | 89 | for(int i = 0; i < buffer_size; i++) { 90 | cpu->memory[512 + i] = buffer[i]; 91 | } 92 | 93 | return true; 94 | 95 | } 96 | 97 | 98 | void emulate_cycle(chip8 * cpu) { 99 | 100 | update_timers(cpu); 101 | //fetch opcode 102 | cpu->opcode = cpu->memory[cpu->pc] << 8 | cpu->memory[cpu->pc + 1]; 103 | 104 | decode_opcode(cpu); 105 | 106 | 107 | } 108 | 109 | void clear_screen(chip8 * cpu) { 110 | memset(cpu->gfx, 0, 64 * 32); 111 | 112 | cpu->draw_flag = true; 113 | 114 | } 115 | 116 | void update_timers(chip8 * cpu) { 117 | if(cpu->delay_timer > 0) { 118 | --cpu->delay_timer; 119 | } 120 | 121 | if(cpu->sound_timer > 0) { 122 | if(cpu->sound_timer == 1) { 123 | printf("BEEP\n"); 124 | Mix_PlayMusic(beep_sound, -1); 125 | --cpu->sound_timer; 126 | } 127 | } 128 | } 129 | 130 | void handle_input(chip8 * cpu, unsigned char key) { 131 | cpu->key = key; 132 | } 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /cpu.h: -------------------------------------------------------------------------------- 1 | #ifndef _CPU_H 2 | #define _CPU_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct { 9 | uint16_t opcode; //an opcode is two bytes 10 | 11 | uint8_t memory[4096]; //has 4K memory 12 | 13 | uint8_t V[16]; //general purpose regsters, VF doubles as a a carry flag 14 | 15 | uint16_t I; //index register 16 | uint16_t pc; //program counter 17 | 18 | uint8_t gfx[64][32]; //pixels of screen 19 | 20 | uint8_t delay_timer; 21 | uint8_t sound_timer; 22 | 23 | uint16_t stack[16]; 24 | uint16_t sp; 25 | 26 | uint8_t keys[16]; //hex keypad 27 | 28 | unsigned char key; 29 | 30 | bool draw_flag; 31 | 32 | } chip8; 33 | 34 | //0x000-0x1FF - Chip 8 interpreter (contains font set in emu) 35 | //0x050-0x0A0 - Used for the built in 4x5 pixel font set (0-F) 36 | //0x200-0xFFF - Program ROM and work RAM 37 | 38 | 39 | void initialise_cpu(chip8 * cpu); 40 | bool load_rom(chip8 * cpu, const char *name); 41 | void emulate_cycle(chip8 * cpu); 42 | void clear_screen(chip8 *cpu); 43 | void update_timers(chip8 * cpu); 44 | 45 | #endif -------------------------------------------------------------------------------- /dbg.h: -------------------------------------------------------------------------------- 1 | #ifndef __dbg_h__ 2 | #define __dbg_h__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef NDEBUG 9 | #define debug(M, ...) 10 | #else 11 | #define debug(M, ...) fprintf(stderr, "DEBUG function:%s %s: %d: " M "\n", __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) 12 | #endif 13 | 14 | #define clean_errno() (errno == 0 ? "None" : strerror(errno)) 15 | 16 | #define log_err(M, ...) fprintf(stderr, "[ERROR] (function:%s %s:%d: errno: %s) " M "\n", __FUNCTION__, __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__) 17 | 18 | #define log_warn(M, ...) fprintf(stderr, "[WARN] (function:%s %s:%d: errno: %s) " M "\n", __FUNCTION__, __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__) 19 | 20 | #define log_info(M, ...) fprintf(stderr, "[INFO] (function:%s %s:%d) " M "\n", __FUNCTION__, __FILE__, __LINE__, ##__VA_ARGS__) 21 | 22 | #define check(A, M, ...) if(!(A)) { log_err(M, ##__VA_ARGS__); errno=0; goto error; } 23 | 24 | #define sentinel(M, ...) {log_err(M, ##__VA_ARGS__); errno=0; goto error; } 25 | 26 | #define check_mem(A) check((A), "Out of memory.") 27 | 28 | #define check_debug(A, M, ...) if(!(A)) { debug(M, ##__VA_ARGS__); errno=0; goto error; } 29 | 30 | #endif -------------------------------------------------------------------------------- /opcodes.h: -------------------------------------------------------------------------------- 1 | #ifndef _opcodes_h 2 | #define _opcodes_h 3 | #include 4 | #include 5 | 6 | //instructions site: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#00E0 7 | 8 | #define X(x) ((x & 0x0F00) >> 8) 9 | #define Y(x) ((x & 0x00F0) >> 4) 10 | #define N(x) (x & 0x000F) 11 | #define KK(x) (x & 0x00FF) 12 | #define NNN(x) (x & 0x0FFF) 13 | 14 | 15 | void decode_opcode(chip8 * cpu); 16 | 17 | void decode_opcode(chip8 * cpu) { 18 | switch(cpu->opcode & 0xF000) { 19 | case 0x0000: //0nnn 20 | switch(cpu->opcode) { 21 | case 0x00E0: //00E0: CLS - clear the display 22 | clear_screen(cpu); 23 | cpu->pc += 2; 24 | break; 25 | 26 | case 0x00EE: //00EE: RET - return from a subroutine, sets PC = stack[sp] then sp-- 27 | cpu->sp -= 1; 28 | cpu->pc = cpu->stack[cpu->sp]; 29 | cpu->pc += 2; 30 | 31 | 32 | break; 33 | 34 | default: 35 | log_err("Unknown opcode: %X", cpu->opcode); 36 | cpu->pc += 2; 37 | break; 38 | 39 | } 40 | break; 41 | 42 | case 0x1000: //1nnn: JP addr - jump to location nnn 43 | cpu->pc = NNN(cpu->opcode); 44 | break; 45 | 46 | case 0x2000: //2nnn: CALL addr - call subroutine at nnn 47 | //add error checking to prevent stck overflow 48 | 49 | 50 | cpu->stack[cpu->sp] = cpu->pc; 51 | cpu->sp += 1; 52 | cpu->pc = NNN(cpu->opcode); 53 | break; 54 | 55 | case 0x3000: //3xkk: SE Vx, byte - skip next instruction if Vx == kk 56 | cpu->pc += cpu->V[X(cpu->opcode)] == KK(cpu->opcode) ? 4 : 2; //Ternary truth test, read X ? Y : Z as "if X then Y else Z". 57 | break; 58 | 59 | case 0x4000: //4XKK: SNE Vx, byte - skip next instruction if Vx != kk 60 | cpu->pc += cpu->V[X(cpu->opcode)] != KK(cpu->opcode) ? 4 : 2; 61 | break; 62 | 63 | case 0x5000: //5XY0: SE Vx, Vy - skips next instrution if Vx == Vy 64 | cpu->pc += cpu->V[X(cpu->opcode)] == cpu->V[Y(cpu->opcode)] ? 4 : 2; 65 | break; 66 | 67 | case 0x6000: //6XKK: LD Vx, byte - set V == KK 68 | cpu->V[X(cpu->opcode)] = KK(cpu->opcode); 69 | cpu->pc += 2; 70 | break; 71 | 72 | case 0x7000: //7XKK: ADD Vx, byte - set Vx = Vx + kk 73 | cpu->V[X(cpu->opcode)] += KK(cpu->opcode); 74 | cpu->pc += 2; 75 | break; 76 | 77 | case 0x8000: 78 | switch(cpu->opcode & 0x000F) { 79 | case 0x0000: //8XY0: LD Vx, Vy - set Vx = Vy 80 | cpu->V[X(cpu->opcode)] = cpu->V[Y(cpu->opcode)]; 81 | cpu->pc += 2; 82 | break; 83 | 84 | case 0x0001: //8XY1: OR Vx, Vy - set Vx = Vx Or Vy 85 | cpu->V[X(cpu->opcode)] |= cpu->V[Y(cpu->opcode)]; 86 | cpu->pc += 2; 87 | break; 88 | 89 | case 0x0002: //8XY2: AND Vx, Vy - set Vx = Vx AND Vy 90 | cpu->V[X(cpu->opcode)] &= cpu->V[Y(cpu->opcode)]; 91 | cpu->pc += 2; 92 | break; 93 | 94 | case 0x0003: //8XY3: XOR Vx, Vy - set Vx = Vx XOR Vy 95 | cpu->V[X(cpu->opcode)] ^= cpu->V[Y(cpu->opcode)]; 96 | cpu->pc += 2; 97 | break; 98 | 99 | 100 | case 0x0004: //8XY4: ADD Vx, Vy - set Vx = Vx + Vy, set VF = carry 101 | { 102 | int vx = cpu->V[X(cpu->opcode)]; 103 | int vy = cpu->V[Y(cpu->opcode)]; 104 | 105 | int result = vx + vy; 106 | 107 | cpu->V[0xF] = result > 255 ? 1 : 0; //VF is doubled as a carry flag register 108 | 109 | //only the lower 8 bits are stored ad kept in Vx 110 | cpu->V[X(cpu->opcode)] = result &0xFF; 111 | 112 | cpu->pc += 2; 113 | } 114 | break; 115 | 116 | case 0x0005: //8XY5: SUB Vx, Vy - set Vx = Vx - Vy, set VF = NOT borrow 117 | { 118 | int vx = cpu->V[X(cpu->opcode)]; 119 | int vy = cpu->V[Y(cpu->opcode)]; 120 | 121 | cpu->V[0xF] = vx < vy ? 0 : 1; 122 | 123 | cpu->V[X(cpu->opcode)] = vx -vy; 124 | 125 | cpu->pc += 2; 126 | } 127 | break; 128 | 129 | 130 | case 0x0006: //8XY6: SHR Vx {, Vy} - Vx = Vx SHR 1. 131 | cpu->V[0xF] = cpu->V[X(cpu->opcode)] & 0x01; 132 | cpu->V[X(cpu->opcode)] >>= 1; 133 | cpu->pc += 2; 134 | break; 135 | 136 | case 0x0007: //8XY7: SUBN Vx, Vy - set Vx = Vy - Vx, set VF NOT borrow 137 | { 138 | int vx = cpu->V[X(cpu->opcode)]; 139 | int vy = cpu->V[Y(cpu->opcode)]; 140 | 141 | cpu->V[0xF] = vx > vy ? 0 : 1; 142 | 143 | cpu->V[X(cpu->opcode)] = vy - vx; 144 | 145 | cpu->pc += 2; 146 | } 147 | break; 148 | 149 | 150 | case 0x000E: //8XYE: SHL Vx {, Vy} - set Vx = Vx SHL 1 151 | cpu->V[0xF] = (cpu->V[X(cpu->opcode)] & 0x80) >> 7; 152 | cpu->V[X(cpu->opcode)] <<= 1; 153 | cpu->pc += 2; 154 | break; 155 | 156 | default: 157 | log_err("Unknown opcode: %X", cpu->opcode); 158 | cpu->pc += 2; 159 | } 160 | break; 161 | 162 | case 0x9000: //9XY0: SNE Vx,Vy - skip next instruction if Vx != VY 163 | cpu->pc += cpu->V[X(cpu->opcode)] != cpu->V[Y(cpu->opcode)] ? 4 : 2; 164 | break; 165 | 166 | case 0xA000: //ANNN: LD I, addr set = NNN 167 | cpu->I = NNN(cpu->opcode); 168 | cpu->pc += 2; 169 | break; 170 | 171 | case 0xB000: //BNNN: JP v0, addr - jump to location nnn + v0 172 | cpu->pc = NNN(cpu->opcode) + cpu->V[0x0]; 173 | break; 174 | 175 | case 0xC000: //CXKK - RNDVx, byte - set Vx = random byte AND KK 176 | cpu->V[X(cpu->opcode)] = (rand() % 256) & KK(cpu->opcode); 177 | cpu->pc += 2; 178 | break; 179 | 180 | case 0xD000: //DXYN: DRW Vx, Vy, nibble - display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. 181 | { 182 | int height = cpu->opcode &0x000F; 183 | int x_coord = cpu->V[X(cpu->opcode)]; 184 | int y_coord = cpu->V[Y(cpu->opcode)]; 185 | 186 | // because the sprite is represented by hexadecimal numbers 187 | // bitwise operators are necessary to obtain each pixel 188 | int ands[8] = { 128, 64, 32, 16, 8, 4, 2, 1 }; 189 | 190 | cpu->V[0xF] = 0; 191 | 192 | for(int i = 0; i < height; i++) { 193 | for(int j = 0; j < 8; j++) { 194 | if(x_coord + j == 64) { 195 | x_coord = -j; 196 | } 197 | if(y_coord + i == 32) { 198 | y_coord = -i; 199 | } 200 | 201 | if(cpu->gfx[x_coord + j][y_coord + i] == 1 && ((cpu->memory[cpu->I + i] & ands[j]) >> (8 - j - 1)) == 1) { 202 | cpu->V[0xF] = 1; 203 | } 204 | 205 | cpu->gfx[x_coord + j][y_coord + i] = cpu->gfx[x_coord + j][y_coord + i] ^ ((cpu->memory[cpu->I + i] & ands[j]) >> (8 - j - 1)); 206 | } 207 | x_coord = cpu->V[X(cpu->opcode)]; 208 | y_coord = cpu->V[Y(cpu->opcode)]; 209 | } 210 | 211 | cpu->draw_flag = true; 212 | cpu->pc += 2; 213 | } 214 | break; 215 | 216 | case 0xE000: 217 | switch(cpu->opcode & 0x00FF) { 218 | case 0x009E: //EX9E: SKP Vx - skip next instruction if key with the value of Vx is pressed. 219 | cpu->pc += cpu->key == cpu->V[X(cpu->opcode)] ? 4 : 2; 220 | break; 221 | 222 | case 0x00A1: //EXA1: SKNP Vx - Skip next instruction if key with the value of Vx is not pressed. 223 | cpu->pc += cpu->key != cpu->V[X(cpu->opcode)] ? 4 : 2; 224 | break; 225 | 226 | default: 227 | log_err("Unknown opcode %X", cpu->opcode); 228 | exit(1); 229 | } 230 | break; 231 | 232 | 233 | case 0xF000: 234 | switch(cpu->opcode & 0x00FF) { 235 | case 0x0007: //FX07: LD Vx, DT - Set Vx = delay timer value. 236 | cpu->V[X(cpu->opcode)] = cpu->delay_timer; 237 | cpu->pc += 2; 238 | break; 239 | 240 | case 0x000A: //Ld Vx, K - Wait for a key press, store the value of the key in Vx. 241 | { 242 | if(cpu->key == 0) { 243 | return; 244 | } else { 245 | cpu->V[X(cpu->opcode)] = cpu->key; 246 | } 247 | cpu->pc += 2; 248 | } 249 | break; 250 | 251 | case 0x0015: //FX15: LD T, Vx - set delay timer = Vx 252 | cpu->delay_timer = cpu->V[X(cpu->opcode)]; 253 | cpu->pc += 2; 254 | break; 255 | 256 | case 0x0018: //FX18: LD ST, Vx - set sound timer = Vx 257 | cpu->sound_timer = cpu->V[X(cpu->opcode)]; 258 | cpu->pc += 2; 259 | break; 260 | 261 | case 0x001E: //FX1E: ADD I, Vx - set I = I + Vx 262 | 263 | cpu->I += cpu->V[X(cpu->opcode)]; 264 | cpu->pc += 2; 265 | break; 266 | 267 | case 0x0029: //FX29: LD , Vx - set I = location of sprite for digit Vx. 268 | cpu->I = cpu->V[X(cpu->opcode)] * 5; 269 | cpu->pc += 2; 270 | break; 271 | 272 | case 0x0033: //FX33: LD B, Vx - Store BCD representation of Vx in memory locations I, I+1, and I+2. 273 | cpu->memory[cpu->I] = cpu->V[X(cpu->opcode)] / 100; //store the hundreds value of vx in memory location I 274 | cpu->memory[cpu->I + 1] = (cpu->V[X(cpu->opcode)] % 10) % 10; //store the tens value of vx in memory location I + 1 275 | cpu->memory[cpu->I + 2] = cpu->V[X(cpu->opcode)] % 10; //store the units value of vx in memory location I + 2 276 | cpu->pc += 2; 277 | break; 278 | 279 | case 0x0055: //FX55: LD [I], Vx - Store registers V0 through Vx in memory starting at location I. 280 | for (int i = 0; i < X(cpu->opcode) + 1; i++) { 281 | cpu->memory[cpu->I + i] = cpu->V[i]; 282 | } 283 | cpu->I = cpu->I + X(cpu->opcode) + 1; 284 | cpu->pc += 2; 285 | break; 286 | 287 | case 0x0065: //FX6: Ld Vx, [I] - Read registers V0 through Vx from memory starting at location I. 288 | for (int i = 0; i < X(cpu->opcode) + 1; i++) { 289 | cpu->V[i] = cpu->memory[cpu->I + i]; 290 | } 291 | cpu->I = cpu->I + X(cpu->opcode) + 1; 292 | cpu->pc += 2; 293 | break; 294 | 295 | default: 296 | log_err("Unknown opcode %X", cpu->opcode); 297 | exit(1); 298 | 299 | 300 | } 301 | break; 302 | 303 | default: 304 | log_err("Unknown opcode %X", cpu->opcode); 305 | exit(1); 306 | 307 | } 308 | } 309 | 310 | 311 | #endif --------------------------------------------------------------------------------