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.
CHIP8Emulator/main.cpp

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;
}