#include <emulator.h>
int emulator_initialise(Emulator* emulator)
{
const uint8_t font[] =
{
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
};
srand(time(NULL));
//zero out the memory
memset(emulator, 0, sizeof(Emulator));
memcpy(emulator->memory + FONT_LOAD_LOCATION, font, sizeof(font));
emulator->is_on = 1;
emulator->keys[0].value = 'x'; //O
emulator->keys[1].value = '1'; //1
emulator->keys[2].value = '2'; //2
emulator->keys[3].value = '3'; //3
emulator->keys[4].value = 'q'; //4
emulator->keys[5].value = 'w'; //5
emulator->keys[6].value = 'e'; //6
emulator->keys[7].value = 'a'; //7
emulator->keys[8].value = 's'; //8
emulator->keys[9].value = 'd'; //9
emulator->keys[10].value = 'z'; //D
emulator->keys[11].value = 'c'; //B
emulator->keys[12].value = '4'; //C
emulator->keys[13].value = 'r'; //D
emulator->keys[14].value = 'f'; //E
emulator->keys[15].value = 'v'; //F
pthread_create(&emulator->timers_thread, NULL, &emulator_timers_thread, emulator);
return 0;
}
int emulator_deinitialise(Emulator* emulator)
{
//join timers thread
emulator->is_on = 0;
pthread_join(emulator->timers_thread, NULL);
return 0;
}
int emulator_load_rom(Emulator* emulator, char* rom_name)
{
printf("load rom!: %s\n", rom_name);
FILE* rom = fopen(rom_name, "r");
if(rom == NULL)
{
perror("no rom file!\n");
return 1;
}
struct stat st;
fstat(fileno(rom), &st);
//rom loaded after 0x200 into memory
int bytes_read = fread(emulator->memory + GAME_LOAD_LOCATION, 1, st.st_size, rom);
fclose(rom);
if(bytes_read != st.st_size)
{
perror("doesnt cuadrar\n");
return 2;
}
emulator->pc = GAME_LOAD_LOCATION;
return 0;
}
int emulator_handle_key_press(Emulator* emulator, uint8_t key)
{
for(size_t i = 0; i < (sizeof(emulator->keys) / sizeof(emulator->keys[0])); ++i)
{
if(emulator->keys[i].value == key)
{
dbgprintf("KEY ACTIVATED: %c-%d!\n", key, key);
emulator->keys[i].activated = 1;
break;
}
}
return 0;
}
int emulator_handle_key_release(Emulator* emulator, uint8_t key)
{
for(size_t i = 0; i < (sizeof(emulator->keys) / sizeof(emulator->keys[0])); ++i)
{
if(emulator->keys[i].value == key)
{
dbgprintf("KEY RELEASE: %c-%d!\n", key, key);
emulator->keys[i].activated = 0;
break;
}
}
return 0;
}
int emulator_tick(Emulator* emulator)
{
uint16_t* pc = &emulator->pc;
uint16_t instr = (emulator->memory[*pc] << 8) | emulator->memory[*pc + 1];
uint16_t instr_pc = *pc;
emulator_step(emulator);
uint8_t first_nibble = (instr >> 12) & 0xf;
uint8_t X = (instr & 0x0F00) >> 8; //second_nibble
uint8_t Y = (instr & 0x00F0) >> 4; //third_nibble
uint8_t N = (instr & 0x000F); //fourth_nibble
uint8_t NN = (instr & 0x00FF); //second_byte
uint16_t NNN = (instr & 0x0FFF); //last three nibbles
dbgprintf("instr: 0x%x [0x%x]\n", instr, instr_pc);
dbgprintf("A: 0x%x\nX: 0x%x\nY: 0x%x\nN: 0x%x\nNN: 0x%x\nNNN: 0x%x\n", first_nibble, X, Y, N, NN, NNN);
switch(first_nibble)
{
case 0x0:
switch(NNN)
{
case 0x0E0: //00E0: Clear screen
dbgprintf("CLEAR SCREEN!\n");
memset(emulator->display, 0, sizeof(emulator->display));
emulator->draw_flag = 1;
break;
case 0x0EE: //00EE: Return from subroutine
//pop callee pc from stack and set current pc to it
emulator->pc = emulator->stack[--emulator->sp];
break;
default:
printf("DEFAULT: Instr: 0x%x\n", instr);
assert(0);
}
break;
case 0x1:
dbgprintf("JUMP! (0x%x)\n", NNN);
emulator->pc = NNN;
#ifdef STOP_ON_INFINITE_LOOP
if(NNN == instr_pc) //infinite loop
{
printf("INFINITE LOOP DETECTED! EXITING...\n");
emulator_dump_registers(emulator);
sleep(2);
//getchar(); //block
emulator->is_on = 0;
}
#endif
break;
case 0x2:
dbgprintf("CALL SUBROUTINE! (0x%x)\n", NNN);
//push pc to stack and increment sp
emulator->stack[emulator->sp++] = emulator->pc;
emulator->pc = NNN;
break;
case 0x3:
dbgprintf("SKIP INSTR IF REGISTER VX == NN!\n");
if(emulator->regs.V[X] == NN)
{
dbgprintf("SKIPPED COZ THEY WERE EQUAL!\n");
emulator_step(emulator);
}
break;
case 0x4:
dbgprintf("SKIP INSTR IF REGISTER VX != NN!\n");
if(emulator->regs.V[X] != NN)
{
dbgprintf("SKIPPED COZ THEY WERE NOT EQUAL!\n");
emulator_step(emulator);
}
break;
case 0x5:
dbgprintf("SKIP INSTR IF REGISTER VX == VY!\n");
if(emulator->regs.V[X] == emulator->regs.V[Y])
{
dbgprintf("SKIPPED COZ THEY WERE EQUAL!\n");
emulator_step(emulator);
}
break;
case 0x6:
dbgprintf("SET REGISTER VX! (0x%x)\n", NN);
emulator->regs.V[X] = NN;
break;
case 0x7:
dbgprintf("ADD VALUE TO REGISTER VX! (0x%x)\n", NN);
emulator->regs.V[X] += NN;
break;
case 0x8:
switch(N)
{
case 0x0:
dbgprintf("SET VX TO VY!\n");
emulator->regs.V[X] = emulator->regs.V[Y];
break;
case 0x1:
emulator->regs.V[X] |= emulator->regs.V[Y];
break;
case 0x2:
emulator->regs.V[X] &= emulator->regs.V[Y];
break;
case 0x3:
emulator->regs.V[X] ^= emulator->regs.V[Y];
break;
case 0x4:
dbgprintf("ADD VY TO VX!\n");
emulator->regs.V[X] += emulator->regs.V[Y];
if(emulator->regs.V[Y] > emulator->regs.V[X])
emulator->regs.VF = 1; //carry
else
emulator->regs.VF = 0;
break;
case 0x5:
if(emulator->regs.V[X] > emulator->regs.V[Y])
emulator->regs.VF = 1;
else
emulator->regs.VF = 0;
emulator->regs.V[X] -= emulator->regs.V[Y];
break;
case 0x6:
emulator->regs.VF = emulator->regs.V[X] & 0x1;
emulator->regs.V[X] >>= 1;
break;
case 0x7:
if(emulator->regs.V[Y] > emulator->regs.V[X])
emulator->regs.VF = 1;
else
emulator->regs.VF = 0;
emulator->regs.V[X] = emulator->regs.V[Y] - emulator->regs.V[X];
break;
case 0xE:
emulator->regs.VF = emulator->regs.V[X] >> 7;
emulator->regs.V[X] <<= 1;
break;
default:
printf("DEFAULT: Instr: 0x%x\n", instr);
assert(0);
}
break;
case 0x9:
dbgprintf("SKIP INSTR IF REGISTER VX != VY!\n");
if(emulator->regs.V[X] != emulator->regs.V[Y])
{
dbgprintf("SKIPPED COZ THEY WERE NOT EQUAL!\n");
emulator_step(emulator);
}
break;
case 0xA:
dbgprintf("SET INDEX REGISTER I! (0x%x)\n", NNN);
emulator->regs.I = NNN;
break;
case 0xB:
printf("TODO: Instr: 0x%x\n", instr);
assert(0);
break;
case 0xC:
emulator->regs.V[X] = rand() & NN;
break;
case 0xD:
dbgprintf("DRAW!\n");
int x = emulator->regs.V[X] % 64;
int y = emulator->regs.V[Y] % 32;
emulator->regs.VF = 0;
for(int row = 0; row < N; ++row)
{
uint8_t pixels = emulator->memory[emulator->regs.I + row];
for(int offset = 0; offset < 8; ++offset)
{
if((pixels & (0x80 >> offset)) != 0)
{
if(emulator->display[x + offset][y + row] == 1)
emulator->regs.V[0xF] = 1;
emulator->display[x + offset][y + row] ^= 1;
}
}
}
emulator->draw_flag = 1;
break;
case 0xE:
switch(NN)
{
case 0xA1: //EX9E
//skip if key not pressed
if(emulator->keys[emulator->regs.V[X]].activated == 0)
*pc += 2;
break;
case 0x9E: //EX9E
//skip if key pressed
if(emulator->keys[emulator->regs.V[X]].activated == 1)
*pc += 2;
break;
default:
printf("DEFAULT: Instr: 0x%x\n", instr);
assert(0);
}
break;
case 0xF:
switch(NN)
{
case 0x1E: //FX1E
dbgprintf("ADD V[X] to the I register!\n");
if(emulator->regs.I + emulator->regs.V[X] > 0xFFF)
emulator->regs.VF = 1;
else
emulator->regs.VF = 0;
emulator->regs.I += emulator->regs.V[X];
break;
case 0x55: //FX55
for (int i = 0; i <= X; ++i)
emulator->memory[emulator->regs.I + i] = emulator->regs.V[i];
COSMAC_VIP(emulator->regs.I += X + 1;)
break;
case 0x65: //FX65
for (int i = 0; i <= X; ++i)
emulator->regs.V[i] = emulator->memory[emulator->regs.I + i];
COSMAC_VIP(emulator->regs.I += X + 1;)
break;
case 0x15: //FX15
dbgprintf("SET THE DELAY TIMER TO V[X]!\n");
emulator->delay_timer = emulator->regs.V[X];
break;
case 0x07: //FX07
dbgprintf("SET V[X] TO THE DELAY TIMER!\n");
emulator->regs.V[X] = emulator->delay_timer;
break;
case 0x18: //FX18
dbgprintf("SET THE SOUND TIMER TO V[X]!\n");
emulator->sound_timer = emulator->regs.V[X];
break;
case 0x33: //FX33
{
uint8_t number = emulator->regs.V[X];
for(uint8_t offset = 2; number > 0; --offset)
{
emulator->memory[emulator->regs.I + offset] = number % 10; //last digit
number /= 10;
}
break;
}
case 0x0A:
{
uint8_t key_pressed = 0;
for(uint8_t i = 0; i < (sizeof(emulator->keys) / sizeof(emulator->keys[0])); ++i)
{
if(emulator->keys[i].activated == 1)
{
emulator->regs.V[X] = i;
key_pressed = 1;
break;
}
}
if(!key_pressed)
*pc -= 2; //block (loop)
}
break;
case 0x29:
emulator->regs.I = emulator->regs.V[X] * 0x5 + FONT_LOAD_LOCATION;
break;
default:
printf("DEFAULT: Instr: 0x%x\n", instr);
assert(0);
}
break;
default:
printf("DEFAULT!: Instr: 0x%x -- %d\n", instr, N);
assert(0);
}
dbgprintf("\n");
if(DEBUG)
emulator_dump_registers(emulator);
return 0;
}
void emulator_step(Emulator* emulator)
{
emulator->pc += 2;
}
void* emulator_timers_thread(Emulator* emulator)
{
printf("STARTED TIMERS THREAD\n");
while(emulator->is_on)
{
if(emulator->delay_timer > 0)
{
dbgprintf("lower timer!\n");
--emulator->delay_timer;
}
if(emulator->sound_timer > 0)
--emulator->sound_timer;
if(!DEBUG)
usleep(1000000 / TIMERS_THREAD_FREQUENCY);
}
return NULL;
}
void emulator_dump_registers(Emulator* emulator)
{
printf("REGISTERS: \n");
printf("\tV0: 0x%x - %d\n", emulator->regs.V0, emulator->regs.V0);
printf("\tV1: 0x%x - %d\n", emulator->regs.V1, emulator->regs.V1);
printf("\tV2: 0x%x - %d\n", emulator->regs.V2, emulator->regs.V2);
printf("\tV3: 0x%x - %d\n", emulator->regs.V3, emulator->regs.V3);
printf("\tV4: 0x%x - %d\n", emulator->regs.V4, emulator->regs.V4);
printf("\tV5: 0x%x - %d\n", emulator->regs.V5, emulator->regs.V5);
printf("\tV6: 0x%x - %d\n", emulator->regs.V6, emulator->regs.V6);
printf("\tV7: 0x%x - %d\n", emulator->regs.V7, emulator->regs.V7);
printf("\tV8: 0x%x - %d\n", emulator->regs.V8, emulator->regs.V8);
printf("\tV9: 0x%x - %d\n", emulator->regs.V9, emulator->regs.V9);
printf("\tVA: 0x%x - %d\n", emulator->regs.VA, emulator->regs.VA);
printf("\tVB: 0x%x - %d\n", emulator->regs.VB, emulator->regs.VB);
printf("\tVC: 0x%x - %d\n", emulator->regs.VC, emulator->regs.VC);
printf("\tVD: 0x%x - %d\n", emulator->regs.VD, emulator->regs.VD);
printf("\tVE: 0x%x - %d\n", emulator->regs.VE, emulator->regs.VE);
printf("\tVF: 0x%x - %d\n", emulator->regs.VF, emulator->regs.VF);
printf("\tI: 0x%x - %d\n", emulator->regs.I, emulator->regs.I);
}