generated from sigonasr2/CPlusPlusProjectTemplate
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
681 lines
21 KiB
681 lines
21 KiB
#include "pixelGameEngine.h"
|
|
#include "Polygon.h"
|
|
#define OLC_PGEX_SPLASHSCREEN
|
|
#include "splash.h"
|
|
#include <ios>
|
|
#include <stack>
|
|
#include <bitset>
|
|
#include <random>
|
|
|
|
using namespace olc;
|
|
|
|
bool USE_DEBUG_DISPLAY=false;
|
|
int EMULATOR_SCREEN_WIDTH = 64;
|
|
int EMULATOR_SCREEN_HEIGHT = 32;
|
|
int EMULATOR_PIXEL_SIZE=5;
|
|
|
|
class Chip8Emulator : public olc::PixelGameEngine
|
|
{
|
|
public:
|
|
//SplashScreen s;
|
|
Chip8Emulator()
|
|
{
|
|
sAppName = "CHIP-8!";
|
|
}
|
|
|
|
public:
|
|
std::array<uint8_t,4096>memory={
|
|
0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,
|
|
//Font starts at 0x50
|
|
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
|
|
};
|
|
std::array<bool,2048>display;
|
|
std::array<uint8_t,2048>screen;
|
|
std::list<uint8_t>stack;
|
|
float pulse=0;
|
|
uint8_t delay_timer,sound_timer;
|
|
uint16_t pc=0x200;
|
|
uint16_t index; //One 16-bit index register called “I” which is used to point at locations in memory
|
|
std::array<uint8_t,16>reg;
|
|
std::random_device rd; //Will be used to obtain a seed for the random number engine
|
|
std::mt19937 gen; //Standard mersenne_twister_engine seeded with rd()
|
|
std::uniform_int_distribution<> distrib;
|
|
std::array<Key,16>keymap{X,K1,K2,K3,Q,W,E,A,S,D,Z,C,K4,R,F,V};
|
|
bool USE_ORIGINAL_CHIP8_SET=true; //True means use the original CHIP-8 spec (COSMAC VIP emulation). Set to false to use CHIP-48 spec.
|
|
bool PAUSED=true;
|
|
long instructionCount=0;
|
|
uint16_t watchMemoryAddress=0;
|
|
|
|
std::string Display8(int number){
|
|
std::bitset<8>numb(number);
|
|
return numb.to_string();
|
|
}
|
|
std::string Display16(int number){
|
|
std::bitset<16>numb(number);
|
|
return numb.to_string();
|
|
}
|
|
|
|
void advanceTimers(){
|
|
if (delay_timer>0){
|
|
delay_timer--;
|
|
}
|
|
if (sound_timer>0){
|
|
sound_timer--;
|
|
}
|
|
}
|
|
|
|
bool OnUserCreate() override
|
|
{
|
|
gen=std::mt19937(rd());
|
|
distrib=std::uniform_int_distribution<>(0,255);
|
|
for (int i=0;i<display.size();i++){
|
|
display[i]=0;
|
|
}
|
|
std::ifstream file("ChipWar.ch8",std::ios_base::binary);
|
|
uint16_t counter=0x200;
|
|
while (file.good()){
|
|
int val = file.get();
|
|
if (val!=-1){
|
|
memory[counter++]=val;
|
|
}
|
|
}
|
|
/*memory[0x200]=0x00;
|
|
memory[0x201]=0xE0;
|
|
memory[0x202]=0xA0;
|
|
memory[0x203]=0x50;
|
|
memory[0x204]=0x60;
|
|
memory[0x205]=0x04;
|
|
memory[0x206]=0xD0;
|
|
memory[0x207]=0x05;
|
|
memory[0x208]=0xD0;
|
|
memory[0x209]=0x05;
|
|
memory[0x210]=0x12;
|
|
memory[0x211]=0x10;*/
|
|
//0x200 program start
|
|
return true;
|
|
}
|
|
|
|
void OnTextEntryComplete(const std::string &sText) override{
|
|
watchMemoryAddress=std::stoul(sText.c_str(),nullptr,16);
|
|
}
|
|
|
|
bool OnUserUpdate(float fElapsedTime) override
|
|
{
|
|
if (!USE_DEBUG_DISPLAY||!PAUSED){
|
|
pulse+=fElapsedTime;
|
|
if (pulse>=1/60.f){
|
|
advanceTimers();
|
|
pulse-=1/60.f;
|
|
|
|
for (int i=0;i<10;i++){
|
|
RunInstruction();
|
|
}
|
|
DrawDisplay();
|
|
}
|
|
} else {
|
|
if (GetKey(G).bPressed){
|
|
TextEntryEnable(true);
|
|
}
|
|
if (!IsTextEntryEnabled()){
|
|
if (GetKey(OEM_6).bPressed){
|
|
RunInstruction();
|
|
instructionCount++;
|
|
if (instructionCount%10==0){
|
|
advanceTimers(); //After 10 instructions, 1/60th of a second has passed.
|
|
}
|
|
DrawDisplay();
|
|
} else
|
|
if (GetKey(SPACE).bHeld){
|
|
RunInstruction();
|
|
instructionCount++;
|
|
if (instructionCount%10==0){
|
|
advanceTimers(); //After 10 instructions, 1/60th of a second has passed.
|
|
}
|
|
DrawDisplay();
|
|
}
|
|
}
|
|
}
|
|
if (IsTextEntryEnabled()){
|
|
DrawStringDecal({2,2},"Goto Memory Address: "+TextEntryGetString());
|
|
}
|
|
|
|
std::stringstream s;
|
|
s<<"PC: 0x"<<std::setfill('0')<< std::setw(4)<<std::hex<<pc;
|
|
std::string ss=s.str();
|
|
std::transform(ss.begin()+6,ss.end(),ss.begin()+6,::toupper);
|
|
DrawStringDecal(vi2d{8,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+16},ss);
|
|
std::stringstream instruction;
|
|
instruction<<"0x"<<std::setfill('0')<< std::setw(2)<<std::hex<<(int)memory[pc]<<std::setw(2)<<(int)memory[pc+1]<<" "<<GetInstructionDescription(memory[pc]<<8|memory[pc+1]);
|
|
ss=instruction.str();
|
|
std::transform(ss.begin()+2,ss.end(),ss.begin()+2,::toupper);
|
|
DrawStringDecal(vi2d{12,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+26},ss);
|
|
|
|
for (int i=0;i<21;i++){
|
|
uint16_t tmp=pc+(i-10)*2;
|
|
if (tmp<memory.size()){
|
|
std::stringstream tmp_s;
|
|
tmp_s<<"0x"<<std::setfill('0')<< std::setw(4)<<std::hex<<tmp<<" "<<std::setw(2)<<(int)memory[tmp]<<std::setw(2)<<(int)memory[tmp+1]<<" "<<GetInstructionDescription(memory[tmp]<<8|memory[tmp+1]);
|
|
std::string ss=tmp_s.str();
|
|
std::transform(ss.begin()+2,ss.end(),ss.begin()+2,::toupper);
|
|
DrawStringDecal(vi2d{14+EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_WIDTH,8+i*10},ss,(tmp==pc)?YELLOW:WHITE);
|
|
}
|
|
}
|
|
|
|
DrawStringDecal(vi2d{8,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+44},"REG");
|
|
for (int i=0;i<16;i++){
|
|
std::stringstream s;
|
|
s<<"V"+std::to_string(i)<<std::right<<std::setfill(' ')<<std::setw((i>=10)?3:4)<<(int)reg[i];
|
|
DrawStringDecal(vi2d{12+i/8*64,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+54+i%8*10},s.str());
|
|
}
|
|
|
|
std::stringstream index_s;
|
|
index_s<<"0x"<<std::hex<<std::setfill('0')<<std::setw(4)<<index;
|
|
DrawStringDecal(vi2d{8,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+64+10*8},"INDEX: "+index_s.str());
|
|
DrawStringDecal(vi2d{8,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+84+10*8},"STACK: ");
|
|
|
|
int offsetX=0;
|
|
bool first=true;
|
|
uint8_t prevVal;
|
|
bool firstVal=true;
|
|
for (uint8_t&val:stack){
|
|
if (first) {
|
|
first=false;
|
|
prevVal=val;
|
|
} else {
|
|
first=true;
|
|
std::stringstream hexStack;
|
|
hexStack<<"0x"<<std::hex<<std::setfill('0')<<std::setw(4)<<(val<<8|prevVal);
|
|
std::string ss=hexStack.str();
|
|
std::transform(ss.begin()+2,ss.end(),ss.begin()+2,::toupper);
|
|
DrawStringDecal(vi2d{12+offsetX,EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_HEIGHT+94+10*8},((!firstVal)?" <- ":"")+ss);
|
|
if (firstVal){
|
|
offsetX+=6*8;
|
|
} else {
|
|
offsetX+=10*8;
|
|
}
|
|
firstVal=false;
|
|
}
|
|
}
|
|
|
|
std::stringstream hexMemoryWatch;
|
|
hexMemoryWatch<<std::hex<<std::setfill('0')<<std::setw(4)<<watchMemoryAddress;
|
|
DrawStringDecal(vi2d{14+EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_WIDTH,18+21*10},"Memory Region [0x"+hexMemoryWatch.str()+"]:");
|
|
for (int j=0;j<12;j++){
|
|
std::stringstream memj;
|
|
memj<<std::hex<<std::setfill('0')<<std::setw(4)<<watchMemoryAddress+16*j;
|
|
DrawStringDecal(vi2d{14+EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_WIDTH,18+21*10+j*10+10},"0x"+memj.str());
|
|
for (int i=0;i<16;i++){
|
|
uint16_t mem = i+j*16+watchMemoryAddress;
|
|
if (mem<memory.size()){
|
|
std::stringstream hexval;
|
|
hexval<<std::hex<<std::setfill('0')<<std::setw(2)<<(int)memory[mem];
|
|
std::string sss=hexval.str();
|
|
std::transform(sss.begin(),sss.end(),sss.begin(),::toupper);
|
|
DrawStringDecal(vi2d{14+EMULATOR_PIXEL_SIZE*EMULATOR_SCREEN_WIDTH+8*8+i*3*8,18+21*10+j*10+10},sss);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OnUserDestroy()override{
|
|
return true;
|
|
}
|
|
|
|
void DrawDisplay(){
|
|
if (USE_DEBUG_DISPLAY){
|
|
Clear(VERY_DARK_BLUE);
|
|
for (int x=0;x<EMULATOR_SCREEN_WIDTH;x++){
|
|
for (int y=0;y<EMULATOR_SCREEN_HEIGHT;y++){
|
|
if (display[y*EMULATOR_SCREEN_WIDTH+x]){
|
|
screen[y*EMULATOR_SCREEN_WIDTH+x]=255;
|
|
FillRect(vi2d{x*EMULATOR_PIXEL_SIZE,y*EMULATOR_PIXEL_SIZE}+vi2d{8,8},vi2d{EMULATOR_PIXEL_SIZE,EMULATOR_PIXEL_SIZE});
|
|
} else {
|
|
screen[y*EMULATOR_SCREEN_WIDTH+x]=std::max(0,screen[y*EMULATOR_SCREEN_WIDTH+x]-36);
|
|
FillRect(vi2d{x*EMULATOR_PIXEL_SIZE,y*EMULATOR_PIXEL_SIZE}+vi2d{8,8},vi2d{EMULATOR_PIXEL_SIZE,EMULATOR_PIXEL_SIZE},{screen[y*EMULATOR_SCREEN_WIDTH+x],screen[y*EMULATOR_SCREEN_WIDTH+x],screen[y*EMULATOR_SCREEN_WIDTH+x]});
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
Clear(VERY_DARK_BLUE);
|
|
for (int x=0;x<EMULATOR_SCREEN_WIDTH;x++){
|
|
for (int y=0;y<EMULATOR_SCREEN_HEIGHT;y++){
|
|
if (display[y*EMULATOR_SCREEN_WIDTH+x]){
|
|
screen[y*EMULATOR_SCREEN_WIDTH+x]=255;
|
|
Draw({x,y},WHITE);
|
|
} else {
|
|
screen[y*EMULATOR_SCREEN_WIDTH+x]=std::max(0,screen[y*EMULATOR_SCREEN_WIDTH+x]-36);
|
|
Draw({x,y},{screen[y*EMULATOR_SCREEN_WIDTH+x],screen[y*EMULATOR_SCREEN_WIDTH+x],screen[y*EMULATOR_SCREEN_WIDTH+x]});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string GetInstructionDescription(uint16_t opcode){
|
|
uint8_t nibble1 = opcode>>12;
|
|
uint8_t X = opcode>>8&0xF;
|
|
uint8_t Y = opcode>>4&0xF;
|
|
uint8_t N = opcode&0xF;
|
|
uint8_t NN = opcode&0x00FF;
|
|
uint16_t NNN = opcode&0x0FFF;
|
|
switch(nibble1){
|
|
case 0x0:{
|
|
switch (NNN){
|
|
case 0x0E0:{ //Clear screen.
|
|
return "CLEAR SCREEN";
|
|
}break;
|
|
case 0x0EE:{ //Return from subroutine.
|
|
return "<<<RETURN";
|
|
}break;
|
|
}
|
|
}break;
|
|
case 0x1:{ //Jump.
|
|
std::stringstream jump;
|
|
jump<<"JUMP PC=0x"<<std::hex<<std::setfill('0')<<std::setw(4)<<NNN;
|
|
return jump.str();
|
|
}break;
|
|
case 0x2:{ //calls the subroutine at memory location NNN
|
|
std::stringstream call;
|
|
call<<"CALL ("<<std::hex<<std::setfill('0')<<std::setw(4)<<NNN<<")";
|
|
return call.str();
|
|
}break;
|
|
case 0x3:{//If register X is equal to NN, skip instruction.
|
|
return "IF V"+std::to_string(X)+"!="+std::to_string(NN);
|
|
}break;
|
|
case 0x4:{//If register X is not equal to NN, skip instruction.
|
|
return "IF V"+std::to_string(X)+"=="+std::to_string(NN);
|
|
}break;
|
|
case 0x5:{//If register X is equal to register Y, skip instruction.
|
|
return "IF V"+std::to_string(X)+"!=V"+std::to_string(Y);
|
|
}break;
|
|
case 0x6:{ //Set the register X to NN.
|
|
return "SET V"+std::to_string(X)+"="+std::to_string(NN);
|
|
}break;
|
|
case 0x7:{ //Add w/no carry
|
|
return "ADD_NC V"+std::to_string(X)+"+="+std::to_string(NN);
|
|
}break;
|
|
case 0x8:{
|
|
switch(N){
|
|
case 0x0:{//VX is set to the value of VY.
|
|
return "SET V"+std::to_string(X)+"=V"+std::to_string(Y);
|
|
}break;
|
|
case 0x1:{//Binary OR VX w/VY.
|
|
return " OR V"+std::to_string(X)+"|=V"+std::to_string(Y);
|
|
}break;
|
|
case 0x2:{//Binary AND VX w/VY.
|
|
return " AND V"+std::to_string(X)+"&=V"+std::to_string(Y);
|
|
}break;
|
|
case 0x3:{//Logical XOR VX w/VY.
|
|
reg[X]^=reg[Y];
|
|
return " XOR V"+std::to_string(X)+"^=V"+std::to_string(Y);
|
|
}break;
|
|
case 0x4:{//Add VY to VX, set carry bit if overflow occurs.
|
|
return "ADD V"+std::to_string(X)+"+=V"+std::to_string(Y);
|
|
}break;
|
|
case 0x5:{// sets VX to the result of VX - VY.
|
|
return "SUBTRACT V"+std::to_string(X)+"-=V"+std::to_string(Y);
|
|
}break;
|
|
case 0x6:{//Shift Right
|
|
return "RSHIFT V"+std::to_string(X)+">>1";
|
|
}break;
|
|
case 0x7:{//sets VX to the result of VY - VX. It's a reverse subtraction.
|
|
return "SUBTRACT V"+std::to_string(X)+"=V"+std::to_string(Y)+"-V"+std::to_string(X);
|
|
}break;
|
|
case 0xE:{//Shift Left
|
|
return "LSHIFT V"+std::to_string(X)+"<<1";
|
|
}break;
|
|
}
|
|
}break;
|
|
case 0x9:{//If register X is not equal to register Y, skip instruction.
|
|
return "IF V"+std::to_string(X)+"==V"+std::to_string(Y);
|
|
}break;
|
|
case 0xA:{ //This sets the index register I to the value NNN.
|
|
std::stringstream set;
|
|
set<<"SET I=0x"<<std::hex<<std::setfill('0')<<std::setw(4)<<NNN;
|
|
return set.str();
|
|
}break;
|
|
case 0xB:{//Jump w/Offset
|
|
std::stringstream jump;
|
|
jump<<"JUMP_OFFSET PC"<<"=0x"<<std::hex<<std::setfill('0')<<std::setw(4)<<NNN<<"+[V0]";
|
|
return jump.str();
|
|
}break;
|
|
case 0xC:{//Random number from 0 to NN.
|
|
return "RAND (0-"+std::to_string(NN)+")";
|
|
}break;
|
|
case 0xD:{ //Display
|
|
return "DISPLAY X:[V"+std::to_string(X)+"],Y:[V"+std::to_string(Y)+"] H:"+std::to_string(N);
|
|
}break;
|
|
case 0xE:{
|
|
switch (NN){
|
|
case 0x9E:{//EX9E: Skip an instruction if key in VX is pressed.
|
|
return "IF KEY_UNHELD [V"+std::to_string(X)+"]";
|
|
}break;
|
|
case 0xA1:{//EXA1: Skip an instruction if key in VX is not pressed.
|
|
return "IF KEY_HELD [V"+std::to_string(X)+"]";
|
|
}break;
|
|
}
|
|
}break;
|
|
case 0xF:{
|
|
switch (NN){
|
|
case 0x07:{//sets VX to the current value of the delay timer
|
|
return "GET DELAY_TIMER -> V"+std::to_string(X);
|
|
}break;
|
|
case 0x15:{//sets the delay timer to the value in VX
|
|
return "SET DELAY_TIMER = [V"+std::to_string(X)+"]";
|
|
}break;
|
|
case 0x18:{//sets the sound timer to the value in VX
|
|
return "SET SOUND_TIMER = [V"+std::to_string(X)+"]";
|
|
}break;
|
|
case 0x1E:{//The index register I will get the value in VX added to it.
|
|
return "ADD I+=[V"+std::to_string(X)+"]";
|
|
}break;
|
|
case 0x0A:{//This instruction “blocks”; it stops executing instructions and waits for key input
|
|
return "WAIT KEY_INPUT";
|
|
}break;
|
|
case 0x29:{//The index register I is set to the address of the hexadecimal character in VX.
|
|
return "SET I = FONT([V"+std::to_string(X)+"])";
|
|
}break;
|
|
case 0x33:{//Binary-coded decimal conversion
|
|
return "SET [I~I+3] = BCD([V"+std::to_string(X)+"])";
|
|
}break;
|
|
case 0x55:{//Stores registers from V0 to VX in memory pointed by index.
|
|
return "STORE V0~V"+std::to_string(X)+" in I~I+"+std::to_string(X);
|
|
}break;
|
|
case 0x65:{//Retrieves registers from V0 to VX in memory pointed by index.
|
|
return "LOAD V0~V"+std::to_string(X)+" in I~I+"+std::to_string(X);
|
|
}break;
|
|
}
|
|
}break;
|
|
}
|
|
return "???";
|
|
}
|
|
|
|
void RunInstruction(){
|
|
//FETCH
|
|
uint16_t opcode = memory[pc]<<8|memory[pc+1];
|
|
pc+=2;
|
|
//std::cout<<"Next instruction is 0x"<<std::hex<<pc<<std::endl;
|
|
|
|
//DECODE
|
|
uint8_t nibble1 = opcode>>12;
|
|
uint8_t X = opcode>>8&0xF;
|
|
uint8_t Y = opcode>>4&0xF;
|
|
uint8_t N = opcode&0xF;
|
|
uint8_t NN = opcode&0x00FF;
|
|
uint16_t NNN = opcode&0x0FFF;
|
|
//std::cout<<"Opcode 0x"<<std::hex<<opcode<<std::dec<<": "<<Display8(nibble1)<<", "<<Display8(X)<<"/"<<Display8(Y)<<"/"<<Display8(N)<<"/"<<Display8(NN)<<"/"<<Display16(NNN)<<std::endl;
|
|
switch(nibble1){
|
|
case 0x0:{
|
|
switch (NNN){
|
|
case 0x0E0:{ //Clear screen.
|
|
for (int i=0;i<display.size();i++){
|
|
display[i]=0;
|
|
}
|
|
}break;
|
|
case 0x0EE:{ //Return from subroutine.
|
|
uint8_t byte1,byte2;
|
|
byte1=stack.front();
|
|
stack.pop_front();
|
|
byte2=stack.front();
|
|
stack.pop_front();
|
|
pc=byte2<<8|byte1;
|
|
//std::cout<<"Exiting subroutine to 0x"<<std::hex<<pc<<std::endl;
|
|
}break;
|
|
}
|
|
}break;
|
|
case 0x1:{ //Jump.
|
|
pc=NNN;
|
|
}break;
|
|
case 0x2:{ //calls the subroutine at memory location NNN
|
|
stack.push_front(pc>>8);
|
|
stack.push_front(pc&0xFF);
|
|
//std::cout<<"Pushed 0x"<<std::hex<<pc<<" onto stack"<<std::endl;
|
|
pc=NNN;
|
|
//std::cout<<"Entering subroutine @ 0x"<<std::hex<<NNN<<std::endl;
|
|
}break;
|
|
case 0x3:{//If register X is equal to NN, skip instruction.
|
|
if(reg[X]==NN){
|
|
pc+=2;
|
|
}
|
|
}break;
|
|
case 0x4:{//If register X is not equal to NN, skip instruction.
|
|
if(reg[X]!=NN){
|
|
pc+=2;
|
|
}
|
|
}break;
|
|
case 0x5:{//If register X is equal to register Y, skip instruction.
|
|
if(reg[X]==reg[Y]){
|
|
pc+=2;
|
|
}
|
|
}break;
|
|
case 0x6:{ //Set the register X to NN.
|
|
reg[X]=NN;
|
|
//std::cout<<(int)reg[X]<<std::endl;
|
|
}break;
|
|
case 0x7:{ //Add w/no carry
|
|
reg[X]+=NN;
|
|
//std::cout<<(int)reg[X]<<std::endl;
|
|
}break;
|
|
case 0x8:{
|
|
switch(N){
|
|
case 0x0:{//VX is set to the value of VY.
|
|
reg[X]=reg[Y];
|
|
}break;
|
|
case 0x1:{//Binary OR VX w/VY.
|
|
reg[X]|=reg[Y];
|
|
}break;
|
|
case 0x2:{//Binary AND VX w/VY.
|
|
reg[X]&=reg[Y];
|
|
}break;
|
|
case 0x3:{//Logical XOR VX w/VY.
|
|
reg[X]^=reg[Y];
|
|
}break;
|
|
case 0x4:{//Add VY to VX, set carry bit if overflow occurs.
|
|
bool carryFlag=0;
|
|
if ((int)reg[X]+(int)reg[Y]>255){
|
|
carryFlag=1;
|
|
}
|
|
reg[X]+=reg[Y];
|
|
reg[0xF]=carryFlag;
|
|
}break;
|
|
case 0x5:{// sets VX to the result of VX - VY.
|
|
bool carryFlag=0;
|
|
if (reg[X]>reg[Y]){
|
|
//reg[0xF]=1;
|
|
carryFlag=1;
|
|
}
|
|
reg[X]-=reg[Y];
|
|
reg[0xF]=carryFlag;
|
|
}break;
|
|
case 0x6:{//Shift Right
|
|
if (USE_ORIGINAL_CHIP8_SET){
|
|
/*
|
|
In the CHIP-8 interpreter for the original COSMAC VIP, this instruction did the following: It put the value of VY into VX, and then shifted the value in VX 1 bit to the right (8XY6) or left (8XYE). VY was not affected, but the flag register VF would be set to the bit that was shifted out.
|
|
|
|
However, starting with CHIP-48 and SUPER-CHIP in the early 1990s, these instructions were changed so that they shifted VX in place, and ignored the Y completely.
|
|
*/
|
|
reg[X]=reg[Y];
|
|
}
|
|
bool carryFlag=reg[X]&0x1;
|
|
reg[X]>>=1;
|
|
reg[0xF]=carryFlag;
|
|
}break;
|
|
case 0x7:{//sets VX to the result of VY - VX. It's a reverse subtraction.
|
|
//reg[0xF]=0;
|
|
bool carryFlag=false;
|
|
if (reg[Y]>reg[X]){
|
|
//reg[0xF]=1;
|
|
carryFlag=true;
|
|
}
|
|
reg[X]=reg[Y]-reg[X];
|
|
reg[0xF]=carryFlag;
|
|
}break;
|
|
case 0xE:{//Shift Left
|
|
//std::cout<<"Y is: "<<(int)Y<<std::endl;
|
|
if (USE_ORIGINAL_CHIP8_SET){
|
|
/*
|
|
In the CHIP-8 interpreter for the original COSMAC VIP, this instruction did the following: It put the value of VY into VX, and then shifted the value in VX 1 bit to the right (8XY6) or left (8XYE). VY was not affected, but the flag register VF would be set to the bit that was shifted out.
|
|
|
|
However, starting with CHIP-48 and SUPER-CHIP in the early 1990s, these instructions were changed so that they shifted VX in place, and ignored the Y completely.
|
|
*/
|
|
reg[X]=reg[Y];
|
|
}
|
|
bool carryFlag=(reg[X]&0x80)>>7;
|
|
reg[X]<<=1;
|
|
reg[0xF]=carryFlag;
|
|
}break;
|
|
}
|
|
}break;
|
|
case 0x9:{//If register X is not equal to register Y, skip instruction.
|
|
if(reg[X]!=reg[Y]){
|
|
pc+=2;
|
|
}
|
|
}break;
|
|
case 0xA:{ //This sets the index register I to the value NNN.
|
|
index=NNN;
|
|
}break;
|
|
case 0xB:{//Jump w/Offset
|
|
pc=NNN+reg[0x0];
|
|
}break;
|
|
case 0xC:{//Random number from 0 to NN.
|
|
reg[X]=distrib(gen)&NN;
|
|
}break;
|
|
case 0xD:{ //Display
|
|
uint8_t start_X=reg[X]%EMULATOR_SCREEN_WIDTH;
|
|
uint8_t start_Y=reg[Y]%EMULATOR_SCREEN_HEIGHT;
|
|
reg[0xF]=0;
|
|
for (uint8_t y=0;y<N;y++){
|
|
uint16_t spriteRow=memory[index+y];
|
|
//std::cout<<spriteRow<<std::endl;
|
|
for (uint8_t x=0;x<8;x++){
|
|
if (start_X+x<EMULATOR_SCREEN_WIDTH&&start_Y+y<EMULATOR_SCREEN_HEIGHT){
|
|
//Memory loc: 0x50
|
|
//0xF0,0x90,0x90,0x90,0xF0
|
|
bool pixel = spriteRow>>(8-x-1)&0x1;
|
|
if (display[(start_Y+y)*EMULATOR_SCREEN_WIDTH+(start_X+x)]&&pixel){
|
|
//std::cout<<"Double on."<<std::endl;
|
|
reg[0xF]=0;
|
|
}
|
|
display[(start_Y+y)*EMULATOR_SCREEN_WIDTH+(start_X+x)]=display[(start_Y+y)*EMULATOR_SCREEN_WIDTH+(start_X+x)]^pixel;
|
|
}
|
|
}
|
|
}
|
|
}break;
|
|
case 0xE:{
|
|
switch (NN){
|
|
case 0x9E:{//EX9E: Skip an instruction if key in VX is pressed.
|
|
if (GetKey(keymap[reg[X]]).bHeld){
|
|
pc+=2;
|
|
}
|
|
}break;
|
|
case 0xA1:{//EXA1: Skip an instruction if key in VX is not pressed.
|
|
if (!GetKey(keymap[reg[X]]).bHeld){
|
|
pc+=2;
|
|
}
|
|
}break;
|
|
}
|
|
}break;
|
|
case 0xF:{
|
|
switch (NN){
|
|
case 0x07:{//sets VX to the current value of the delay timer
|
|
reg[X]=delay_timer;
|
|
}break;
|
|
case 0x15:{//sets the delay timer to the value in VX
|
|
delay_timer=reg[X];
|
|
}break;
|
|
case 0x18:{//sets the sound timer to the value in VX
|
|
sound_timer=reg[X];
|
|
}break;
|
|
case 0x1E:{//The index register I will get the value in VX added to it.
|
|
//reg[0xF]=0;
|
|
bool carryFlag=false;
|
|
if (index+reg[X]>=0x1000){
|
|
//reg[0xF]=1;
|
|
carryFlag=true;
|
|
}
|
|
index+=reg[X];
|
|
reg[0xF]=carryFlag;
|
|
}break;
|
|
case 0x0A:{//This instruction “blocks”; it stops executing instructions and waits for key input
|
|
for (int i=0;i<keymap.size();i++){
|
|
if (GetKey(keymap[i]).bHeld){
|
|
reg[X]=i;
|
|
goto pass;
|
|
}
|
|
}
|
|
pc-=2;
|
|
pass:;
|
|
}break;
|
|
case 0x29:{//The index register I is set to the address of the hexadecimal character in VX.
|
|
index=0x50+(reg[X]&0xF)*5;
|
|
}break;
|
|
case 0x33:{//Binary-coded decimal conversion
|
|
uint8_t numb=reg[X];//156
|
|
memory[index+2]=numb%10;
|
|
numb/=10;
|
|
memory[index+1]=numb%10;
|
|
numb/=10;
|
|
memory[index]=numb%10;
|
|
}break;
|
|
case 0x55:{//Stores registers from V0 to VX in memory pointed by index.
|
|
for (int i=0;i<=X;i++){
|
|
memory[index+i]=reg[i];
|
|
}
|
|
if (USE_ORIGINAL_CHIP8_SET){
|
|
/*The original CHIP-8 interpreter for the COSMAC VIP actually incremented the I register while it worked. Each time it stored or loaded one register, it incremented I. After the instruction was finished, I would be set to the new value I + X + 1.*/
|
|
index+=X+1;
|
|
}
|
|
}break;
|
|
case 0x65:{//Retrieves registers from V0 to VX in memory pointed by index.
|
|
for (int i=0;i<=X;i++){
|
|
reg[i]=memory[index+i];
|
|
}
|
|
if (USE_ORIGINAL_CHIP8_SET){
|
|
/*The original CHIP-8 interpreter for the COSMAC VIP actually incremented the I register while it worked. Each time it stored or loaded one register, it incremented I. After the instruction was finished, I would be set to the new value I + X + 1.*/
|
|
index+=X+1;
|
|
}
|
|
}break;
|
|
}
|
|
}break;
|
|
}
|
|
}
|
|
};
|
|
|
|
int main()
|
|
{
|
|
Chip8Emulator demo;
|
|
if (USE_DEBUG_DISPLAY){
|
|
if (demo.Construct(800, 360, 2, 2))
|
|
demo.Start();
|
|
} else {
|
|
if (demo.Construct(64, 32, 10, 10))
|
|
demo.Start();
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|