From d92e499c0dcc383f7d798f790c162f3387dc32e4 Mon Sep 17 00:00:00 2001 From: flytrapdev <68333938+flytrapdev@users.noreply.github.com> Date: Sun, 24 Oct 2021 15:51:35 +0200 Subject: [PATCH] Add files via upload --- chip8.cpp | 1215 +++++++++++++++++++++++++++++++++++++++++++++++++++++ chip8.hpp | 118 ++++++ main.cpp | 431 +++++++++++++++++++ 3 files changed, 1764 insertions(+) create mode 100644 chip8.cpp create mode 100644 chip8.hpp create mode 100644 main.cpp diff --git a/chip8.cpp b/chip8.cpp new file mode 100644 index 0000000..d44569c --- /dev/null +++ b/chip8.cpp @@ -0,0 +1,1215 @@ +/** +MIT License + +Copyright (c) 2021 Matthieu Le Gallic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "chip8.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Chip8::Chip8() { + //ROM loaded + loaded = false; + + //CHIP extensions quirks + fxQuirk = false; //FX55 FX65 SCHIP behavior + shiftQuirk = false; //SCHIP shift behavior + hiresQuirk = true; //Clear screen when changing resolutions + wrapQuirk = false; //Sprites do not wrap around by default + + //Default palette + palette[0][0] = 0x00; + palette[0][1] = 0x00; + palette[0][2] = 0x00; + palette[1][0] = 0x54; + palette[1][1] = 0x54; + palette[1][2] = 0x54; + palette[2][0] = 0xa8; + palette[2][1] = 0xa8; + palette[2][2] = 0xa8; + palette[3][0] = 0xfc; + palette[3][1] = 0xfc; + palette[3][2] = 0xfc; + + initialize(); +}; + +//Initialize CHIP-8 +void Chip8::initialize() { + pc = 0x200; //Program counter + I = 0; //Index register + sp = 0; //Stack pointer + opcode = 0; //Current opcode + + hiRes = false; + drawFlag = true; + + //Stop flag used by SUPERCHIP + stopped = false; + + //Clear graphics bit planes + memset(gfx[0], false, SCHIP_WH); + memset(gfx[1], false, SCHIP_WH); + + bitPlane = 1; + + delayTimer = 0; + soundTimer = 0; + + memset(keys, false, 16); + memset(v, false, 16); + + //Font set + uint8_t fontSet[180] = { + 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 + + // Hi-res font (0-9) (SCHIP) + 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, + 0x18, 0x38, 0x58, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, + 0x3E, 0x7F, 0xC3, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFF, 0xFF, + 0x3C, 0x7E, 0xC3, 0x03, 0x0E, 0x0E, 0x03, 0xC3, 0x7E, 0x3C, + 0x06, 0x0E, 0x1E, 0x36, 0x66, 0xC6, 0xFF, 0xFF, 0x06, 0x06, + 0xFF, 0xFF, 0xC0, 0xC0, 0xFC, 0xFE, 0x03, 0xC3, 0x7E, 0x3C, + 0x3E, 0x7C, 0xC0, 0xC0, 0xFC, 0xFE, 0xC3, 0xC3, 0x7E, 0x3C, + 0xFF, 0xFF, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x60, + 0x3C, 0x7E, 0xC3, 0xC3, 0x7E, 0x7E, 0xC3, 0xC3, 0x7E, 0x3C, + 0x3C, 0x7E, 0xC3, 0xC3, 0x7F, 0x3F, 0x03, 0x03, 0x3E, 0x7C + }; + + memcpy(memory, fontSet, 180); +}; + +//Print unknown opcode error +void Chip8::unknownOpcode(uint16_t opcode) { + std::cout << "Unknown opcode 0x" << std::hex << std::setfill('0') << std::setw(4) << opcode << std::endl; +}; + +//Load program +uint8_t Chip8::loadROM(std::string filename) { + + std::cout << "Loading ROM " << filename << std::endl; + + int fd = open(filename.c_str(), O_RDONLY | O_BINARY); + + if(fd == -1) { + std::cout << "Could not read file " << filename << std::endl; + return -1; + } + + int len; + int pos = 0; + + uint8_t byte; + + len = read(fd, &byte, 1); + + while(len > 0) { + memory[0x200 + pos] = byte; + len = read(fd, &byte, 1); + pos ++; + } + + close(fd); + + std::cout << "Loaded : " << pos << " bytes" << std::endl; + + loaded = true; + + return 0; + +}; + +//Load palette file +uint8_t Chip8::loadPalette(std::string filename) { + + std::cout << "Loading palette file " << filename << std::endl; + + std::ifstream file(filename.c_str()); + + if(!file.is_open()) { + std::cout << "Could not read palette file " << filename << std::endl; + return -1; + } + + uint8_t colorCount = 0; + uint32_t color = 0; + + std::string line; + + //Read line by line + while(std::getline(file, line) && colorCount < 4) { + + if(line.size() != 0) { + std::stringstream sstream; + sstream << std::hex << std::uppercase << line; + + sstream >> color; + + palette[colorCount][0] = (color & 0xff0000) >> 16; + palette[colorCount][1] = (color & 0x00ff00) >> 8; + palette[colorCount][2] = (color & 0x0000ff); + + colorCount ++; + } + } + + + return 0; +} + +//Check pressed keys +uint8_t Chip8::checkKeys() { + + for(int i = 0 ; i < 16 ; i++) + if(keys[i]) + return i; + + return 255; +}; + +//Read next byte and increment pc +uint8_t Chip8::nextByte() { + uint8_t byte = memory[pc]; + pc ++; + + return byte; +} + +//Read next 2 bytes and increment pc +uint16_t Chip8::nextWord() { + uint16_t word = (memory[pc] << 8) | memory[pc + 1]; + pc += 2; + + return word; +} + +//Skip next instruction +void Chip8::skipNextInstruction() { + + //XO-CHIP has double-length F0000 NNNN instruction + if(nextWord() == 0xF000) + pc += 2; +} + +//Decrement delay and sound timers +void Chip8::updateTimers() { + if(delayTimer > 0) + delayTimer --; + + if(soundTimer > 0) + soundTimer --; +} + +//Scroll left +void Chip8::scrollLeft(uint8_t pixels) { + + for(uint8_t y0 = 0 ; y0 < SCHIP_H ; y0 ++) { + if((bitPlane & 0x1) != 0) { + memmove(gfx[0] + y0*SCHIP_W, gfx[0] + pixels + y0*SCHIP_W, SCHIP_W - pixels); + memset(gfx[0] + SCHIP_W - pixels + y0*SCHIP_W, false, pixels); + } + + if((bitPlane & 0x2) != 0) { + memmove(gfx[1] + y0*SCHIP_W, gfx[1] + pixels + y0*SCHIP_W, SCHIP_W - pixels); + memset(gfx[1] + SCHIP_W - pixels + y0*SCHIP_W, false, pixels); + } + } + + drawFlag = true; +} + +//Scroll right +void Chip8::scrollRight(uint8_t pixels) { + + for(uint8_t y0 = 0 ; y0 < SCHIP_H ; y0 ++) { + if((bitPlane & 0x1) != 0) { + memmove(gfx[0] + pixels + y0*SCHIP_W, gfx[0] + y0*SCHIP_W, SCHIP_W - pixels); + memset(gfx[0] + y0*SCHIP_W, false, pixels); + } + + if((bitPlane & 0x2) != 0) { + memmove(gfx[1] + pixels + y0*SCHIP_W, gfx[1] + y0*SCHIP_W, SCHIP_W - pixels); + memset(gfx[1] + y0*SCHIP_W, false, pixels); + } + } + + drawFlag = true; +} + +//Scroll down +void Chip8::scrollDown(uint8_t pixels) { + + if((bitPlane & 0x1) != 0) { + memmove(gfx[0] + SCHIP_W * pixels, gfx[0], SCHIP_W * (SCHIP_H - pixels)); + memset(gfx[0], false, SCHIP_W * pixels); + } + + if((bitPlane & 0x2) != 0) { + memmove(gfx[1] + SCHIP_W * pixels, gfx[1], SCHIP_W * (SCHIP_H - pixels)); + memset(gfx[1], false, SCHIP_W * pixels); + } + + + drawFlag = true; +} + +//Scroll up +void Chip8::scrollUp(uint8_t pixels) { + + if((bitPlane & 0x1) != 0) { + memmove(gfx[0], gfx[0] + SCHIP_W * pixels, SCHIP_W * (SCHIP_H - pixels)); + memset(gfx[0] + SCHIP_W * (SCHIP_H - pixels), false, SCHIP_W * pixels); + } + + if((bitPlane & 0x2) != 0) { + memmove(gfx[1], gfx[1] + SCHIP_W * pixels, SCHIP_W * (SCHIP_H - pixels)); + memset(gfx[1] + SCHIP_W * (SCHIP_H - pixels), false, SCHIP_W * pixels); + } + + drawFlag = true; +} + +//Draw pixel to the gfx buffer +void Chip8::pixel(uint8_t x, uint8_t y, uint8_t sprPlane) { + + for(uint8_t plane = 0 ; plane < 2 ; plane ++) { + + //XO-CHIP bitplanes + if((sprPlane & (plane+1)) != 0) { + + //Memory location + uint16_t addr = x + SCHIP_W * y; + + //Collision flag + if(gfx[plane][addr]) + v[0xF] = 1; + + //VRAM + gfx[plane][addr] ^= 1; + } + } +} + +//Emulate CHIP-8 instruction +void Chip8::emulateInstruction() { + + opcode = nextWord(); + + switch(opcode & 0xF000) { + + case 0x0000:{ + + if((opcode & 0x00F0) == 0x00C0) { + //0x00CN + //(SCHIP) Scroll down by N pixels + scrollDown(opcode & 0x000F); + } + else if ((opcode & 0x00F0) == 0x00D0) { + //0x00DN + //(XO-CHIP) Scroll up by N pixels + scrollUp(opcode & 0x000F); + } + else + switch(opcode & 0x00FF) { + + case 0x00E0: { + //0x00E0 + //Clear screen + if((bitPlane & 0x1) != 0) + memset(gfx[0], false, SCHIP_WH); + + if((bitPlane & 0x2) != 0) + memset(gfx[1], false, SCHIP_WH); + + drawFlag = true; + break; + } + + case 0x00EE: { + //0x00EE + //Return + sp --; + pc = stck[sp]; + break; + } + + case 0x00FB: { + //0x00FB + //(SCHIP) Scroll right by 4 pixels + scrollRight(4); + break; + } + + case 0x00FC: { + //0x00FC + //(SCHIP) Scroll left by 4 pixels + scrollLeft(4); + break; + } + + case 0x00FD: { + //0x00FD + //(SCHIP) Stop + stopped = true; + pc -= 2; + break; + } + + case 0x00FE: { + //0x00FE + //(SCHIP) disable hi-res mode + //TODO clears the screen in XO-CHIP + memset(gfx, false, SCHIP_WH * 2); + + hiRes = false; + drawFlag = true; + break; + } + + case 0x00FF: { + //0x00FF + //(SCHIP) enable hi-res mode + //TODO clears the screen in XO-CHIP + memset(gfx, false, SCHIP_WH * 2); + + hiRes = true; + drawFlag = true; + break; + } + + default: { + //Unknown opcode + unknownOpcode(opcode); + break; + } + + } + + break; + } + + case 0x1000: { + //0x1NNN + //Jump to location NNN + pc = opcode & 0xFFF; + break; + } + + case 0x2000: { + //0x2NNN + //Call location NNN + stck[sp] = pc; + sp ++; + pc = (opcode & 0xFFF); + break; + } + + case 0x3000: { + //0x3XNN + //Skip next instruction if VX == NN + if((opcode & 0x00FF) == v[(opcode & 0x0F00) >> 8]) + skipNextInstruction(); + + break; + } + + case 0x4000: { + //0x4XNN + //Skip next instruction if VX != NN + if((opcode & 0x00FF) != v[(opcode & 0x0F00) >> 8]) + skipNextInstruction(); + + break; + } + + case 0x5000: { + + if((opcode & 0x000F) == 0x0000) { + //0x5XY0 + //Skip next instruction if VX == VY + if(v[(opcode & 0x0F00) >> 8] == v[(opcode & 0x00F0) >> 4]) + skipNextInstruction(); + } + else if((opcode & 0x000F) == 0x0002) { + //0x5XY2 + //(XO-CHIP) Save VX..VY to memory at location I + uint8_t x = ((opcode & 0x0F00) >> 8); + uint8_t y = ((opcode & 0x00F0) >> 4); + + if(y >= x) + memcpy(memory + I, v + x, 1 + y - x); + else { + //Reverse + for(uint8_t i = 0 ; i <= x - y ; i ++) + memory[I + i] = v[x - i]; + } + + } + else if((opcode & 0x000F) == 0x0003) { + //0x5XY3 + //(XO-CHIP) Load VX..VY from memory at location I + uint8_t x = ((opcode & 0x0F00) >> 8); + uint8_t y = ((opcode & 0x00F0) >> 4); + + if(y >= x) + memcpy(v + x, memory + I, 1 + y - x); + else { + //Reverse + for(uint8_t i = 0 ; i <= x - y ; i ++) + v[x - i] = memory[I + i]; + } + + } + + break; + } + + case 0x6000: { + //0x6XNN + //Load NN into VX + v[(opcode & 0x0F00) >> 8] = opcode & 0x00FF; + break; + } + + case 0x7000: { + //0x7XNN + //Add NN to VX + v[(opcode & 0x0F00) >> 8] += (opcode & 0x00FF); + break; + } + + case 0x8000: { + uint8_t x = (opcode & 0x0F00) >> 8; + uint8_t y = (opcode & 0x00F0) >> 4; + + switch(opcode & 0x000F) { + + case 0x0000: { + //0x8XY0 + //Set VX = VY + v[x] = v[y]; + break; + } + + case 0x0001: { + //0x8XY1 + //Set VX = VX OR VY + v[x] |= v[y]; + break; + } + + case 0x0002: { + //0x8XY2 + //Set VX = VX AND VY + v[x] &= v[y]; + break; + } + + case 0x0003: { + //0x8XY3 + //Set VX = VX XOR VY + v[x] ^= v[y]; + break; + } + + case 0x0004: { + //0x8XY4 + //Set VX = VX + VY + //Set VF = carry + uint8_t carry = ((v[x] + v[y]) > 0xFF) ? 1 : 0; + v[x] += v[y]; + v[0xF] = carry; + break; + } + + case 0x0005: { + //0x8XY5 + //Set VX = VX - VY + //Set VF = carry + uint8_t carry = (v[y] > v[x]) ? 0 : 1; + v[x] -= v[y]; + v[0xF] = carry; + break; + } + + case 0x0006: { + //0x8XY6 + //Rotate VX right + //Set VF = least significant bit of VX + + //Rotate VY right + //Set VF = least significant bit of VY + + if(shiftQuirk) { + uint8_t carry = v[x] & 0x01; + v[x] >>= 1; + v[0xF] = carry; + } + else { + uint8_t carry = v[y] & 0x01; + v[y] >>= 1; + v[x] = v[y]; + v[0xF] = carry; + } + + break; + } + + case 0x0007: { + //0x8XY7 + //Set VX = VY - VX + //Set VF = Not borrow + uint8_t carry = (v[x] > v[y]) ? 0 : 1; + v[x] = v[y] - v[x]; + v[0xF] = carry; + + break; + } + + case 0x000E: { + //0x8XYE + //Rotate VX left + //Set VF = most significant bit of VX + + //Rotate VY left + //Set VF = most significant bit of VY + + if(shiftQuirk) { + uint8_t carry = v[x] >> 7; + v[x] <<= 1; + v[0xF] = carry; + } + else { + uint8_t carry = v[x] >> 7; + v[y] <<= 1; + v[x] = v[y]; + v[0xF] = carry; + } + + break; + } + + default: { + //Unknown opcode + unknownOpcode(opcode); + break; + } + + } + + break; + } + + case 0x9000: { + //0x9XY0 + //Skip next instruction if VX != VY + if(v[(opcode & 0x0F00) >> 8] != v[(opcode & 0x00F0) >> 4]) + skipNextInstruction(); + break; + } + + case 0xA000: { + //0xANNN + //Set I = NNN + I = (opcode & 0xFFF); + break; + } + + case 0xB000: { + //0xBNNN + //Jump to address NNN + V0 + pc = ((opcode & 0xFFF) + v[0]); + break; + } + + case 0xC000: { + //0xCNNN + //Set VX = Random (0 -> 255) AND NN + v[(opcode & 0x0F00) >> 8] = opcode & (rand() & 0xFF); + break; + } + + case 0xD000: { + //0xDXYN + //Draw sprite + + //Dot size on screen + uint8_t pSize = hiRes ? 1 : 2; + + //Opcode parameters + uint8_t x = v[(opcode & 0x0F00) >> 8]; + uint8_t y = v[(opcode & 0x00F0) >> 4]; + uint8_t n = opcode & 0x000F; + + //Pixel coordinates + uint8_t x0, y0; + + //Bit mask + uint16_t mask; + + //Collision flag + v[0xF] = 0; + + uint8_t dX, dY; + + uint8_t height, memHeight; + uint8_t sprPlane = bitPlane; + + //Octo quirk : DXY0 draws 16x16 sprite even in loRes mode + if(n == 0) { + + //16x16 sprite + height = 16; + memHeight = 16; + + //XO-CHIP multicolor sprite + if(bitPlane == 3) { + memHeight = 32; + sprPlane = 1; + } + + for(dY = 0 ; dY < memHeight ; dY ++) { + + y0 = ((y + (dY % height)) * pSize) % SCHIP_H; + + //Multicolor sprites + if(dY >= height) + sprPlane = 2; + + for(dX = 0 ; dX < 16 ; dX ++) { + + mask = 0x8000 >> dX; + x0 = ((x + dX) * pSize) % SCHIP_W; + + //Sprites don't wrap around the screen in XOCHIP mode + if(wrapQuirk || (((x + dX) * pSize < SCHIP_W) && (((y + (dY % height)) * pSize < SCHIP_H)))) { + + if(((memory[I + 2*dY] << 8 | memory[I + 2*dY + 1]) & mask) != 0) { + + pixel(x0, y0, sprPlane); + + if(!hiRes) { + pixel(x0 + 1, y0, sprPlane); + pixel(x0, y0 + 1, sprPlane); + pixel(x0 + 1, y0 + 1, sprPlane); + } + } + } + } + + } + + } + else { + //Regular sprite drawing + + height = n; + memHeight = n; + + if(bitPlane == 3) { + memHeight = n * 2; + sprPlane = 1; + } + + //Regular sprite + for(dY = 0 ; dY < memHeight ; dY ++) { + + y0 = ((y + (dY % height)) * pSize) % SCHIP_H; + + if(dY >= height) + sprPlane = 2; + + for(dX = 0 ; dX < 8 ; dX ++) { + + mask = 0x80 >> dX; + x0 = ((x + dX) * pSize) % SCHIP_W; + + if(wrapQuirk || (((x + dX) * pSize < SCHIP_W) && (((y + (dY % height)) * pSize < SCHIP_H)))) { + if((memory[I + dY] & mask) != 0) { + + pixel(x0, y0, sprPlane); + + if(!hiRes) { + pixel(x0 + 1, y0, sprPlane); + pixel(x0, y0 + 1, sprPlane); + pixel(x0 + 1, y0 + 1, sprPlane); + } + } + } + + } + + } + + } + + drawFlag = true; + break; + } + + case 0xE000: { + uint8_t key = v[(opcode & 0x0F00) >> 8] & 0xF; + switch(opcode & 0x00FF) { + + case 0x009E: { + //0xEX9E + //Skip next instruction if key VX is pressed + if(keys[key]) + skipNextInstruction(); + + break; + } + + case 0x00A1: { + //0xEXA1 + //Skip next instruction if key VX is not pressed + if(!keys[key]) + skipNextInstruction(); + + break; + } + + default: { + //Unknown opcode + unknownOpcode(opcode); + break; + } + } + break; + } + + case 0xF000: { + uint16_t x = (opcode & 0x0F00) >> 8; + uint16_t opcodeLast = opcode & 0x00FF; + + switch(opcodeLast) { + + case 0x0000: { + //0xF000 NNNN + //(XO-CHIP) Load NNNN into I + I = nextWord(); + break; + } + + case 0x0001: { + //0xFN01 + //(XO-CHIP) bitplane N select + bitPlane = (opcode & 0x0F00) >> 8; + break; + } + + case 0x0002: { + //0xF002 + //(XO-CHIP) Store to audio buffer + for(uint8_t i = 0 ; i < 16 ; i++) + audioBuffer[i] = memory[I + i]; + break; + } + + case 0x0007: { + //0xFX07 + //Set VX = delay timer + v[x] = delayTimer; + break; + } + + case 0x000A: { + //0xFX0A + //Wait for key press then store key into Vx + uint8_t key = checkKeys(); + + if(key < 16) + v[x] = key; + else + pc -= 2; + + break; + } + + case 0x0015: { + //0xFX15 + //Set delay timer = VX + delayTimer = v[x]; + break; + } + + case 0x0018: { + //0xFX18 + //Set sound timer = VX + soundTimer = v[x]; + break; + } + + case 0x001E: { + //0xFX1E + //Set I = I + VX + uint8_t carry = (I + v[x] > 0xFFF)? 1 : 0; + + I += v[x]; + v[0xF] = carry; + + break; + } + + case 0x0029: { + //0xFX29 + //Set I to the location of sprite for digit VX + I = (v[x] * 5); + break; + } + + case 0x0030: { + //0xFX30 + //(SCHIP) Set I to the location of hi-res sprite for digit VX + I = (80 + v[x] * 10); + break; + } + + case 0x0033: { + //0xFX33 + //Store BCD representation of VX to I, I+1, I+2 + uint8_t n = v[x]; + memory[I] = n / 100; + memory[(I + 1) & 0xFFF] = (n / 10) % 10; + memory[(I + 2) & 0xFFF] = (n % 100) % 10; + break; + } + + case 0x0055: { + //0xFX55 + //Store V0..VX into memory at location I + memcpy(memory + I, v, x + 1); + + if(!fxQuirk) + I += x + 1; + + break; + } + + case 0x0065: { + //0xFX65 + //Store memory at location I into V0..VX + memcpy(v, memory + I, x + 1); + + if(!fxQuirk) + I += x + 1; + + break; + } + + case 0x0075: { + //0xFX75 + //(SCHIP) Store V0..VX into user flags + memcpy(userFlags, v, x + 1); + + break; + } + + case 0x0085: { + //0xFX85 + //(SCHIP) Store user flags into V0..VX + memcpy(v, userFlags, x + 1); + + break; + } + + default : { + //Unknown opcode + unknownOpcode(opcode); + break; + } + } + break; + } + + default: { + //Unknown opcode + unknownOpcode(opcode); + break; + } + + } + +} + +void Chip8::printInstruction(uint16_t op, uint16_t p) { + + std::cout << std::hex << std::setfill('0') << std::setw(4) << p; + std::cout << ": "; + + switch(op & 0xF000) { + + case 0x0000 : { + + if((op & 0x00F0) == 0x00C0) + std::cout << "SCD " << std::dec << (int) (opcode & 0x000F) << std::endl; + else if ((op & 0x00F0) == 0x00D0) + std::cout << "SCU " << std::dec << (int) (opcode & 0x000F) << std::endl; + else switch(op & 0x00FF) { + case 0x00E0 : { + std::cout << "CLS" << std::endl; + break; + } + case 0x00EE : { + std::cout << "RET" << std::endl; + break; + } + case 0x00FB: { + std::cout << "SCR 4" << std::endl; + break; + } + + case 0x00FC: { + std::cout << "SCL 4" << std::endl; + break; + } + + case 0x00FD: { + std::cout << "EXIT" << std::endl; + break; + } + + case 0x00FE: { + std::cout << "LORES" << std::endl; + break; + } + + case 0x00FF: { + std::cout << "HIRES" << std::endl; + break; + } + + } + break; + } + case 0x1000 : { + std::cout << "JP " << std::hex << (int)(op & 0x0FFF) << std::endl; + break; + } + case 0x2000 : { + std::cout << "CALL " << std::hex << (int)(op & 0x0FFF) << std::endl; + break; + } + case 0x3000 : { + std::cout << "SE V" << std::hex << (int)((op & 0x0F00) >> 8) << ", "; + std::cout << std::setw(2) << std::hex << (int)(op & 0x00FF) << std::endl; + std::setw(0); + break; + } + case 0x4000 : { + std::cout << "SNE V" << std::hex << (int)((op & 0x0F00) >> 8) << ", "; + std::cout << std::setw(2) << std::hex << (int)(op & 0x00FF) << std::endl; + std::setw(0); + break; + } + case 0x5000 : { + switch(opcode & 0x000F) { + case 0x0000 : { + std::cout << "SE V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x0002 : { + std::cout << "SAVE V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x0003 : { + std::cout << "LOAD V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + } + break; + } + case 0x6000 : { + std::cout << "LD V" << std::hex << (int)((op & 0x0F00) >> 8) << ", " << (int)(op & 0x00FF) << std::endl; + break; + } + case 0x7000 : { + std::cout << "ADD V" << std::hex << (int)((op & 0x0F00) >> 8) << ", " << (int)(op & 0x00FF) << std::endl; + break; + } + case 0x8000 : { + switch(op & 0x000F) { + case 0x0 : { + std::cout << "LD V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x1 : { + std::cout << "OR V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x2 : { + std::cout << "AND V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x3 : { + std::cout << "XOR V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x4 : { + std::cout << "ADD V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x5 : { + std::cout << "SUB V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x6 : { + std::cout << "SHR V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0x7 : { + std::cout << "SUBN V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0xE : { + std::cout << "SHL V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + } + + break; + } + case 0x9000 : { + std::cout << "SNE V" << std::hex << (int)((op & 0x0F00) >> 8) << ", V" << (int)((op & 0x00F0) >> 4) << std::endl; + break; + } + case 0xA000 : { + std::cout << "LD I, " << std::hex << (int)(op & 0x0FFF) << std::endl; + break; + } + case 0xB000 : { + std::cout << "JP V0, " << std::hex << (int)(op & 0x0FFF) << std::endl; + break; + } + case 0xC000 : { + std::cout << "RAND V" << std::hex << (int)((op & 0x0F00) >> 8) << ", " << (int)(op & 0x00FF) << std::endl; + break; + } + case 0xD000 : { + std::cout << "DRAW V" << std::hex << (int)((op&0x0F00)>>8) << ", V" << (int)((op&0x00F0)>>4) << ", " << (int)(op&0x000F) << std::endl; + break; + } + case 0xE000 : { + switch(op & 0x00FF) { + case 0x009E : { + std::cout << "SKP V" << std::hex << (int)((op&0x0F00)>>8) << std::endl; + break; + } + case 0x00A1 : { + std::cout << "SKNP V" << std::hex << (int)((op&0x0F00)>>8) << std::endl; + break; + } + } + break; + } + case 0xF000 : { + switch(op & 0x00FF) { + case 0x00 : { + std::cout << "JP " << std::hex << (int) ((memory[pc - 1] << 8) | memory[pc - 2]) << std::endl; + break; + } + case 0x01 : { + std::cout << "PLANE " << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x02 : { + std::cout << "AUDIO" << std::endl; + break; + } + case 0x07 : { + std::cout << "LD V" << (int)((op & 0x0F00) >> 8) << ", DT" << std::endl; + break; + } + case 0x0A : { + std::cout << "LD V" << (int)((op & 0x0F00) >> 8) << ", K" << std::endl; + break; + } + case 0x15 : { + std::cout << "LD DT, V" << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x18 : { + std::cout << "LD ST, V" << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x1E : { + std::cout << "ADD I, V" << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x29 : { + std::cout << "LD I, CHAR V" << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x30 : { + std::cout << "LD I, HIRES CHAR V" << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x33 : { + std::cout << "LD [I], BCD V" << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x55 : { + std::cout << "LD [I], V0..V" << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x65 : { + std::cout << "LD V0..V" << (int)((op & 0x0F00) >> 8) << ", [I]" << std::endl; + break; + } + case 0x75 : { + std::cout << "LD [I], V0..V" << (int)((op & 0x0F00) >> 8) << std::endl; + break; + } + case 0x85 : { + std::cout << "LD V0..V" << (int)((op & 0x0F00) >> 8) << ", [I]" << std::endl; + break; + } + } + break; + } + } +} diff --git a/chip8.hpp b/chip8.hpp new file mode 100644 index 0000000..1302827 --- /dev/null +++ b/chip8.hpp @@ -0,0 +1,118 @@ +/** +MIT License + +Copyright (c) 2021 Matthieu Le Gallic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef CHIP8_HPP_INCLUDED +#define CHIP8_HPP_INCLUDED + +#include + +#define CHIP_W 64 +#define CHIP_H 32 +#define CHIP_WH 2048 +#define SCHIP_W 128 +#define SCHIP_H 64 +#define SCHIP_WH 8192 + +class Chip8 { + +public: + + //Opcode + uint16_t opcode; + + //Memory + uint8_t memory[65535]; + + //Registers + uint8_t v[16]; + uint16_t I; + uint16_t pc; + + //Graphics bitplanes + bool gfx[2][SCHIP_WH]; + uint8_t bitPlane; + + //Color palette + uint8_t palette[4][3]; + + //Timers + uint8_t delayTimer; + uint8_t soundTimer; + + //XO-Chip audio buffer + uint8_t audioBuffer[16]; + + //Stack + uint16_t stck[16]; + uint8_t sp; + + //SCHIP user flags + uint8_t userFlags[8]; + + //Keys + bool keys[16]; + + //SCHIP hi-res mode + bool hiRes; + + //Draw flag + bool drawFlag; + + //Interpreter stopped + bool stopped; + + //ROM loaded + bool loaded; + + //CHIP-8 font sprites + uint8_t *fontSet; + + //CHIP extensions quirks + bool fxQuirk; //FX55 FX65 behavior + bool shiftQuirk; //Shift instructions behavior + bool hiresQuirk; //Clear screen on resolution change (SCHIP and XOCHIP only) + bool wrapQuirk; //Sprites wrap around screen boundariess + + Chip8(); + void initialize(); + void unknownOpcode(uint16_t); + uint8_t loadROM(std::string); + uint8_t loadPalette(std::string); + uint8_t checkKeys(); + void updateTimers(); + uint8_t nextByte(); + uint16_t nextWord(); + void skipNextInstruction(); + void scrollLeft(uint8_t); + void scrollRight(uint8_t); + void scrollUp(uint8_t); + void scrollDown(uint8_t); + void pixel(uint8_t, uint8_t, uint8_t); + void emulateInstruction(); + void printInstruction(uint16_t, uint16_t); + + +}; + +#endif // CHIP8_HPP_INCLUDED diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..65e73cc --- /dev/null +++ b/main.cpp @@ -0,0 +1,431 @@ +/** +MIT License + +Copyright (c) 2021 Matthieu Le Gallic + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chip8.hpp" + +#define CYCLES_STEP 50 +#define CYCLES_DEFAULT 600 + +#define ARG_CYCLES "-c" +#define ARG_MACHINE "-m" +#define ARG_KEYBOARD "-k" +#define ARG_PALETTE "-p" +#define ARGLEN 2 +#define ARG_AUTO "auto" +#define ARG_CHIP8 "chip8" +#define ARG_SCHIP "schip" +#define ARG_XOCHIP "xochip" +#define ARG_QWERTY "qwerty" +#define ARG_AZERTY "azerty" + +#define MACHINE_AUTO 0 +#define MACHINE_CHIP8 1 +#define MACHINE_SCHIP 2 +#define MACHINE_XOCHIP 3 + +#define KB_QWERTY 0 +#define KB_AZERTY 1 + +#define KB_DEFAULT KB_QWERTY +#define MACHINE_DEFAULT MACHINE_AUTO + +using namespace std; + +int main(int argc, char** argv) +{ + SDL_Keycode keyBindings[] = { + //QWERTY + SDLK_x, SDLK_1, SDLK_2, SDLK_3, + SDLK_q, SDLK_w, SDLK_e, SDLK_a, + SDLK_s, SDLK_d, SDLK_z, SDLK_c, + SDLK_4, SDLK_r, SDLK_f, SDLK_v, + + //AZERTY + SDLK_x, SDLK_1, SDLK_2, SDLK_3, + SDLK_a, SDLK_z, SDLK_e, SDLK_q, + SDLK_s, SDLK_d, SDLK_w, SDLK_c, + SDLK_4, SDLK_r, SDLK_f, SDLK_v + }; + + //Emulation properties + int cycles = CYCLES_DEFAULT; // CHIP-8 cycles per second + uint8_t keySet = KB_DEFAULT; // 0-QWERTY 1-AZERTY + bool paused = false; // Emulation paused + int machine = MACHINE_DEFAULT;// 0: auto 1: chip8 2:schip 3:xochip + + //Display argument help + if(argc < 2) { + cout << "usage: chip-8 rom_file [options]" << endl; + cout << " options :" << endl; + cout << " -k [azerty qwerty] keyboard layout" << endl; + cout << " -m [auto chip8 schip xochip] machine type" << endl; + cout << " -c cycles instructions per second" << endl; + + return 0; + } + + //Create CHIP-8 instance + Chip8* chip8 = new Chip8(); + + //Read command line arguments + int i; + for(i = 1 ; i < argc ; i++) { + //Number of cycles + if(strncmp(ARG_CYCLES, argv[i], ARGLEN) == 0) { + int newCycles; + + if(argc <= i+1) { + cout << "ERROR : cycles value not provided" << endl; + return 1; + } + + if(sscanf(argv[i+1], "%d", &newCycles) != 1) { + cout << "ERROR : cycles argument must be an integer number" << endl; + return 1; + } + else if(newCycles <= 0) { + cout << "ERROR : cycles argument must be greater than 0" << endl; + return 1; + } + else { + cycles = newCycles; + } + } + + //Emulated machine + if(strncmp(ARG_MACHINE, argv[i], ARGLEN) == 0) { + char *values[] = {(char*)ARG_AUTO, (char*)ARG_CHIP8, (char*)ARG_SCHIP, (char*)ARG_XOCHIP}; + int machines[] = {MACHINE_AUTO, MACHINE_CHIP8, MACHINE_SCHIP, MACHINE_XOCHIP}; + bool machineFound = false; + + if(argc <= i+1) { + cout << "ERROR : machine type not provided" << endl; + return 1; + } + + for(int j = 0 ; j < 4 ; j++) { + if(strncmp(values[j], argv[i+1], strlen(values[j])) == 0) { + machine = machines[j]; + machineFound = true; + break; + } + } + + if(!machineFound){ + cout << "Unknown machine type " << argv[i+1] << endl; + return 1; + } + } + + //Keyboard layout + if(strncmp(ARG_KEYBOARD, argv[i], ARGLEN) == 0) { + char *values[] = {(char*)ARG_QWERTY, (char*)ARG_AZERTY}; + int keysets[] = {0, 1}; + bool keysetFound = false; + + if(argc <= i+1) { + cout << "ERROR : keyboard layout not provided" << endl; + return 1; + } + + for(int j = 0 ; j < 2 ; j++) { + if(strncmp(values[j], argv[i+1], strlen(values[j])) == 0) { + keySet = keysets[j]; + keysetFound = true; + break; + } + } + + if(!keysetFound){ + cout << "Unknown keyboard layout " << argv[i+1] << endl; + return 1; + } + } + + if(strncmp(ARG_PALETTE, argv[i], ARGLEN) == 0) { + if(argc <= i+1) { + cout << "ERROR : palette file not provided" << endl; + return 1; + } + + if(chip8->loadPalette(argv[i+1]) != 0) { + return 1; + } + } + } + + switch(machine) { + default: break; + + case MACHINE_AUTO : + case MACHINE_CHIP8 : { + //CHIP-8 + chip8->fxQuirk = false; + chip8->shiftQuirk = false; + chip8->wrapQuirk = true; + break; + } + + case MACHINE_SCHIP : { + //SCHIP + chip8->fxQuirk = true; + chip8->shiftQuirk = true; + chip8->hiresQuirk = false; + chip8->wrapQuirk = true; + break; + } + + case MACHINE_XOCHIP : { + //XO-CHIP + chip8->fxQuirk = false; + chip8->shiftQuirk = false; + chip8->hiresQuirk = true; + chip8->wrapQuirk = false; + break; + } + } + + + bool running = false; + + //Load ROM + if(chip8->loadROM(argv[1]) == 0) { + running = true; + } else { + return 1; + } + + //Window title + char cyclesBuff[256]; + snprintf(cyclesBuff, 256, "%i", cycles); + + string title = "CHIP-8 Interpreter - " + (string)cyclesBuff + " instructions per second"; + + cout << "Program started" << endl; + + srand(time(NULL)); + + if(SDL_Init(SDL_INIT_EVERYTHING) != 0) { + cout << "Error initializing SDL" << endl; + return 1; + } + + SDL_Window* window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1024, 512, SDL_WINDOW_OPENGL); + SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0); + SDL_Event event; + SDL_Rect rect; + + //Get window size + SDL_GetWindowSize(window, &(rect.w), &(rect.h)); + + rect.w /= SCHIP_W; + rect.h /= SCHIP_H; + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + SDL_RenderPresent(renderer); + + Uint32 lastTime, currentTime; + + if(window == NULL) { + cout << "Could not initialize window" << endl; + return 1; + } + + lastTime = SDL_GetTicks(); + + + while(running) { + + //Poll events + while(SDL_PollEvent(&event)) { + + switch(event.type){ + + case SDL_QUIT: { + running = false; + break; + } + + case SDL_KEYDOWN: { + unsigned short i; + + for(i = 0 ; i < 16 ; i++) { + if(event.key.keysym.sym == keyBindings[i + 16*keySet]) + chip8 -> keys[i] = true; + } + + switch(event.key.keysym.sym) { + + case SDLK_F2: { + chip8 -> initialize(); + break; + } + + case SDLK_F5: { + if(cycles > CYCLES_STEP) + cycles -= CYCLES_STEP; + + snprintf(cyclesBuff, 256, "%i", cycles); + title = "CHIP-8 Interpreter - " + (string)cyclesBuff + " instructions per second"; + SDL_SetWindowTitle(window, title.c_str()); + break; + } + + case SDLK_F6: { + cycles += CYCLES_STEP; + + snprintf(cyclesBuff, 256, "%i", cycles); + title = "CHIP-8 Interpreter - " + (string)cyclesBuff + " instructions per second"; + SDL_SetWindowTitle(window, title.c_str()); + break; + } + + + case SDLK_p: { + paused ^= 1; + break; + } + + case SDLK_o: { + uint16_t pc = chip8->pc; + chip8->emulateInstruction(); + chip8->printInstruction(chip8->opcode, pc); + break; + } + + + default : break; + } + + if(event.key.keysym.sym == SDLK_ESCAPE) + running = false; + + break; + } + + case SDL_KEYUP: { + unsigned short i; + + for(i = 0 ; i < 16 ; i++) { + if(event.key.keysym.sym == keyBindings[i + 16*keySet]) + chip8->keys[i] = false; + } + + break; + } + + default : + break; + } + } + + //Emulate cycles + if(!paused and !chip8->stopped){ + + for(int i = 0 ; i < cycles / 60 ; i++) + chip8 -> emulateInstruction(); + + } + + //Update Chip-8 timers + chip8 -> updateTimers(); + + + //Update display + if(chip8 -> drawFlag) { + + //Clear surface + SDL_SetRenderDrawColor(renderer, chip8->palette[0][0], chip8->palette[0][1], chip8->palette[0][2], 255); + SDL_RenderClear(renderer); + + //Color (XO-CHIP) + uint8_t col; + + //Memory location + uint16_t memLoc = 0; + + //Draw pixels + for(uint8_t y = 0 ; y < SCHIP_H ; y++) { + + for(uint8_t x = 0 ; x < SCHIP_W ; x++) { + + if(chip8->gfx[0][memLoc] != 0 || chip8->gfx[1][memLoc] != 0) { + + //Use full palette on XOCHIP, only two colors on other machines + if(machine == MACHINE_AUTO || machine == MACHINE_XOCHIP) + col = ((chip8->gfx[1][memLoc] << 1) + chip8->gfx[0][memLoc]); + else + col = ((chip8->gfx[1][memLoc] << 1) + chip8->gfx[0][memLoc] > 0) ? 3 : 0; + + SDL_SetRenderDrawColor(renderer, chip8->palette[col][0], chip8->palette[col][1], chip8->palette[col][2], 255); + + rect.x = x * rect.w; + rect.y = y * rect.h; + + SDL_RenderFillRect(renderer, &rect); + } + + + + memLoc ++; + + } + + } + + //Update display + SDL_RenderPresent(renderer); + + chip8 -> drawFlag = false; + } + + + //60fps delay + currentTime = SDL_GetTicks(); + + if(currentTime - lastTime < 1000/60) { + SDL_Delay(1000/60 - (currentTime - lastTime)); + } + + lastTime = currentTime; + + } + + SDL_DestroyWindow(window); + SDL_Quit(); + + + return 0; +}