The following code is a complete mess and completely incomplete.
It is a Chip 8 emulator, it is hard coded to load pong.ch8 from the same dir as the xex. Sound and controls are not implemented. (Well controls are, but not properly).
If anyone wants to take it and complete it with sound, controls and a proper UI then go for it (IMG:
style_emoticons/default/smile.gif)
Otherwise, perhaps some of the below will help others learn... idk.... but I'm going to try GB now.
Here's a screenshot of pong running:
(IMG:
http://i18.photobucket.com/albums/b145/Powerslayer/IMG_2065.jpg)
CODE
#include
#include
#include
#include
#include
#include
#include
#include
#include "AtgApp.h"
#include "AtgFont.h"
#include "AtgInput.h"
#include "AtgResource.h"
#include "AtgUtil.h"
#include "AtgSimpleShaders.h"
#include "AtgDebugDraw.h"
using namespace std;
typedef unsigned char u8;
typedef unsigned char byte;
typedef unsigned short u16;
u8 V[16] = {0x0}; // 16 8-bit registers V0 - VF
u16 I = 0x0; // Memory address register
u8 SP = 0x0; // Stack pointer is used to point to the topmost level of the stack.
u16 Stack[16] = {0x0}; // The stack is an array of 16 16-bit values, used to store the address that the interpreter shoud return to when finished with a subroutine
u16 PC = 0x000; // Program counter starting at mem address 0x200 because on the original console the OS took up the first 200 bytes
u8 Memory[0xFFF] = {0x0}; // 4KB of memory
u16 Opcode = 0x0; // Current instruction
byte dtime; // Delay timer
byte stime; // Sound timer
byte keyval; // Key press
byte vx, yline, data, vxval, vyval; // Screen data
byte screen[2049]; // New screen to display
byte old_screen[2049]; // Old screen just displayed
int count = 0;
bool end = false;
bool debugEnabled = false;
IDirect3DTexture9* display;
D3DLOCKED_RECT rect;
byte font[80]={ 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
};
void debugLog(char* output)
{
if (debugEnabled)
{
ofstream writeLog;
writeLog.open("game:\\debug.log",ofstream::app);
if (writeLog.is_open())
{
writeLog.write(output,strlen(output));
writeLog.write("\n",1);
}
writeLog.close();
}
}
// Controls //
byte check_keys(bool wait)
{
bool done = false;
byte keyval = 0;
while(!done)
{
ATG::GAMEPAD* pGamepad = ATG::Input::GetMergedInput();
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_A)
{
keyval = 1;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_B)
{
keyval = 5;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_X)
{
keyval = 3;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_Y)
{
keyval = 7;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_DPAD_UP)
{
keyval = 8;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_DPAD_DOWN)
{
keyval = 2;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_DPAD_LEFT)
{
keyval = 4;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
{
keyval = 6;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_START)
{
keyval = 9;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_BACK)
{
keyval = 0xA;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_LEFT_THUMB)
{
keyval = 0xB;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_RIGHT_THUMB)
{
keyval = 0xC;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_LEFT_SHOULDER)
{
keyval = 0xD;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER)
{
keyval = 0xE;
done = true;
}
if( pGamepad->wPressedButtons & XINPUT_GAMEPAD_BIGBUTTON)
{
keyval = 0xF;
done = true;
}
if(wait == false)
{
done = true;
}
}
return keyval;
}
// CPU //
void cpu(){
Opcode = ((Memory[PC]<<8) + Memory[PC+1]);
switch (Opcode&0xF000)
{
case 0x0000:
switch(Opcode&0x00FF)
{
case 0x00E0:
debugLog("00E0\n");
for(int i=0; i <2047; i++ ){
screen = 0;
}
PC += 2;
break;
case 0x00EE:
debugLog("00EE\n");
SP -= 1;
PC = Stack[SP];
PC += 2;
break;
case 0X00FF:
debugLog("00FF\n");
PC+=2;
break;
default:
debugLog("Opcode not found\n");
debugLog((char*)PC);
end = true;
break;
}
break;
case 0x1000:
debugLog("1NNN\n");
PC = (Opcode&0x0FFF);
break;
case 0x2000:
debugLog("2NNN\n");
Stack[SP] = PC;
SP += 1;
PC = (Opcode&0x0FFF);
break;
case 0x3000:
debugLog("3XNN\n");
if(V[((Opcode&0x0F00)>>
] == (Opcode&0x00FF))
{
PC += 4;
}
else
{
PC +=2;
}
break;
case 0x4000:
debugLog("4XNN\n");
if(V[((Opcode&0x0F00)>>
] != (Opcode&0x00FF))
{
PC += 4;
}
else
{
PC +=2;
}
break;
case 0x5000:
debugLog("5XY0\n");
if(V[((Opcode&0x0F00)>>
] == V[((Opcode&0x00F0)>>4)])
{
PC += 4;
}
else
{
PC +=2;
}
break;
case 0x6000:
debugLog("6XNN\n");
V[((Opcode&0x0F00)>>
] = (Opcode&0x00FF);
PC+=2;
break;
case 0x7000:
debugLog("7XNN\n");
V[((Opcode&0x0F00)>>
]=(V[((Opcode&0x0F00)>>
]+(Opcode&0x00FF));
PC+=2;
break;
case 0x8000:
switch (Opcode&0x000F){
case 0x0:
debugLog("8XY0\n");
V[((Opcode&0x0F00)>>
]=V[((Opcode&0x00F0)>>4)];
PC+=2;
break;
case 0x1:
debugLog("8XY1\n");
V[((Opcode&0x0F00)>>
]=(V[((Opcode&0x0F00)>>
]|V[((Opcode&0x00F0)>>4)]);
PC+=2;
break;
case 0x2:
debugLog("8XY2\n");
V[((Opcode&0x0F00)>>
]=(V[((Opcode&0x0F00)>>
]&V[((Opcode&0x00F0)>>4)]);
PC+=2;
break;
case 0x3:
debugLog("8XY3\n");
V[((Opcode&0x0F00)>>
]=(V[((Opcode&0x0F00)>>
]^V[((Opcode&0x00F0)>>4)]);
PC+=2;
break;
case 0x4:
debugLog("8XY4\n");
if((V[((Opcode&0x00F0)>>4)]+V[((Opcode&0x0F00)>>
]) > 0xFF)
{
V[0xF] = 0x01;
}
else
{
V[0xF] = 0x00;
}
V[((Opcode&0x0F00)>>
]+=V[((Opcode&0x00F0)>>4)];
PC+=2;
break;
case 0x5:
debugLog("8XY5\n");
if (V[((Opcode&0x00F0)>>4)]>(V[((Opcode&0x0F00)>>
]))
{
V[0xF]=00;
}
else
{
V[0xF]=1;
}
V[((Opcode&0x0F00)>>
]-=V[((Opcode&0x00F0)>>4)];
PC+=2;
break;
case 0x6:
debugLog("8XY6\n");
V[0xF]=(V[((Opcode&0x0F00)>>
]&0x1);
V[((Opcode&0x0F00)>>
]>>=1;
PC+=2;
break;
case 0x7:
debugLog("8XY7\n");
if(V[((Opcode&0x00F0)>>4)] < V[((Opcode&0x0F00)>>
])
{
V[0xF] = 0x00;
}
else
{
V[0xF] = 0x01;
}
V[((Opcode&0x0F00)>>
] = V[((Opcode&0x00F0)>>4)] - V[((Opcode&0x0F00)>>
];
PC+=2;
break;
case 0xE:
debugLog("8XYE\n");
V[0xF] = ((V[((Opcode&0x0F00)>>
]&0x80)>>7);
V[((Opcode&0x0F00)>>
] <<=1;
PC+=2;
break;
default:
debugLog("Opcode not found\n");
debugLog((char*)PC);
end = true;
break;
}
break;
case 0x9000:
debugLog("9XY0\n");
if (V[((Opcode&0x0F00)>>
] != V[((Opcode&0x00F0)>>4)])
{
PC += 4;
}
else
{
PC += 2;
}
break;
case 0xA000:
debugLog("ANNN\n");
I = (Opcode&0x0FFF);
PC += 2;
break;
case 0xB000:
debugLog("BNNN\n");
PC = (Opcode&0x0FFF) + V[0x0];
break;
case 0xC000:
debugLog("CXNN\n");
V[((Opcode&0x0F00)>>
] = (rand()&(Opcode&0x00FF));
PC += 2;
break;
case 0xD000:
debugLog("DXYN\n");
vxval = V[(Opcode&0x0F00)>>8];
vyval = V[(Opcode&0x00F0)>>4];
if ((Opcode&0x000F)==0)
for(yline=0; yline<16; yline++ ){
data = Memory[I+yline];
for(vx=0; vx<16; vx++){
if((data&(0x80>>vx))!=0){
if(screen[ vx + vxval + ((vyval+yline) * 64)] == 1)
V[15] = 1;
screen[ vx + vxval + ((vyval+yline) * 64)] ^= 1;
}
}
}
else{
for (yline=0;yline<(Opcode&0x000F);yline++){
data = (Memory[I+yline]);
for(vx=0;vx<8;vx++){
if ((data&(0x80>>vx))!=0){
if ( screen[ vx + vxval + (( vyval + yline) * 64)]==1)
V[15]=1;
screen[ vx + vxval + ( (vyval + yline) * 64)]^=1;
}
}
}
}
PC+=2;
for(yline=0; yline<32; yline++)
for(vx=0; vx<64; vx++)
old_screen[vx + (yline *64)] = screen[vx + (yline *64)];
break;
case 0xE000:
switch (Opcode&0x00FF){
case 0x9E:
debugLog("EX9E\n");
if(V[((Opcode&0x0F00)>>
] == check_keys(false))
{
PC +=4;
} else
{
PC +=2;
}
break;
case 0xA1:
debugLog("EXA1\n");
if(V[((Opcode&0x0F00)>>
] != check_keys(false))
{
PC +=4;
} else
{
PC +=2;
}
break;
}
break;
case 0xF000:
switch (Opcode&0x00FF){
case 0x07:
debugLog("FX07\n");
V[((Opcode&0x0F00)>>
] = dtime;
PC+=2;
break;
case 0x0A:
debugLog("FX0A\n");
keyval=check_keys(true);
V[((Opcode&0x0F00)>>
]=keyval;
PC+=2;
break;
case 0x15:
debugLog("FX15\n");
dtime = V[((Opcode&0x0F00)>>
];
PC+=2;
break;
case 0x18:
debugLog("FX18\n");
stime = V[((Opcode&0x0F00)>>
];
PC+=2;
break;
case 0x1E:
debugLog("FX1E\n");
I += V[((Opcode&0x0F00)>>
];
PC+=2;
break;
case 0x29:
debugLog("FX29\n");
I = (V[((Opcode&0x0F00)>>
]*5);
PC+=2;
break;
case 0x30:
debugLog("FX30\n");
I = (V[((Opcode&0x0F00)>>
]*10);
PC+=2;
break;
case 0x33:
debugLog("FX33\n");
Memory = (V[((Opcode&0x0F00)>>
]/100);
Memory[I+1] = ((V[((Opcode&0x0F00)>>
]/10)%10);
Memory[I+2] = ((V[((Opcode&0x0F00)>>
]%100)%10);
PC+=2;
break;
case 0x55:
debugLog("FX55\n");
for(int i=0; i<=((Opcode&0x0F00)>>
; i++ ){
Memory[I+i] = V;
}
PC+=2;
break;
case 0x65:
debugLog("FX65\n");
for(int i=0; i<=((Opcode&0x0F00)>>
; i++ ){
V = Memory[I+i];
}
PC+=2;
break;
default:
debugLog("Opcode not found\n");
debugLog((char*)PC);
end = true;
break;
}
break;
default:
debugLog("Opcode not found\n");
debugLog((char*)PC);
end = true;
break;
}
}
class Sample : public ATG::Application
{
ATG::Font m_Font;
DWORD m_dwFirstUserIndex;
XINPUT_STATE m_InputState;
private:
virtual HRESULT Initialize();
virtual HRESULT Update();
virtual HRESULT Render();
};
void DrawTextureScaled( LPDIRECT3DTEXTURE9 curTexture, int x, int y, int w, int h )
{
D3DRECT Rect;
Rect.x1 = x;
Rect.y1 = y;
Rect.x2 = x + w;
Rect.y2 = y + h;
ATG::DebugDraw::DrawScreenSpaceTexturedRect( Rect, curTexture, 0 );
}
HRESULT Sample::Initialize()
{
for(PC=0; PC < 80; PC++ ){
Memory[0x000 + PC] = font[PC];
}
PC = 0x200;
ifstream inFile;
inFile.open("game:\\pong.ch8",ifstream::binary);
if(inFile.is_open()){debugLog("pong open");}
inFile.read((char*)&Memory[0x200],0xFFF);
inFile.close();
int x, y;
for(y=0; y<32; y++)
for(x=0; x<64; x++)
{
old_screen[x + (y *64)] = 0;
screen[x + (y *64)] = 0;
}
if( FAILED( m_Font.Create( "game:\\Media\\Fonts\\Arial_16.xpr" ) ) )
return ATGAPPERR_MEDIANOTFOUND;
m_Font.SetWindow( ATG::GetTitleSafeArea() );
ATG::SimpleShaders::Initialize( NULL, NULL );
m_pd3dDevice ->CreateTexture(64, 32, 1, 0, D3DFMT_LIN_A8R8G8B8, D3DPOOL_DEFAULT, &display, 0);
return S_OK;
}
HRESULT Sample::Update()
{
display->LockRect(0,&rect,NULL, NULL);
BYTE* pByte =(BYTE*)rect.pBits;
for (DWORD y=0;y<32;++y)
{
for (DWORD x=0;x<64;++x)
{
DWORD index = (x*4)+(y*rect.Pitch);
pByte[index] = (BYTE)(screen[(y*64) + x] == 0 ? 0x00 : 0xFF);
pByte[index+1] = (BYTE)(screen[(y*64) + x] == 0 ? 0x00 : 0xFF);
pByte[index+2] = (BYTE)(screen[(y*64) + x] == 0 ? 0x00 : 0xFF);
pByte[index+3] = (BYTE)(screen[(y*64) + x] == 0 ? 0x00 : 0xFF);
}
}
display->UnlockRect(0);
cpu();
if (dtime>0)dtime-=1;
if (stime>0)stime-=1;
return S_OK;
}
HRESULT Sample::Render()
{
ATG::RenderBackground( 0xFFFFFFFF, 0x00000000 );
DrawTextureScaled(display,20,20,64*10,32*10);
m_pd3dDevice->Present( NULL, NULL, NULL, NULL );
return S_OK;
}
VOID __cdecl main()
{
Sample atgApp;
ATG::GetVideoSettings( &atgApp.m_d3dpp.BackBufferWidth, &atgApp.m_d3dpp.BackBufferHeight );
atgApp.Run();
}