feat: initial version
This commit is contained in:
parent
0a67fb21d1
commit
ee790e99d6
12 changed files with 798 additions and 0 deletions
34
hdrs/chip8.h
Normal file
34
hdrs/chip8.h
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#ifndef CHIP8_H
|
||||||
|
# define CHIP8_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "./memory.h"
|
||||||
|
#include "./graphics.h"
|
||||||
|
#include "./input.h"
|
||||||
|
|
||||||
|
#define FRAME_TIME_MS 1000 / 60
|
||||||
|
|
||||||
|
typedef struct s_chip8
|
||||||
|
{
|
||||||
|
t_memory memory;
|
||||||
|
t_display display;
|
||||||
|
t_keyboard keyboard;
|
||||||
|
|
||||||
|
uint8_t v[16];
|
||||||
|
uint16_t i;
|
||||||
|
uint16_t pc;
|
||||||
|
|
||||||
|
uint8_t delay_timer;
|
||||||
|
uint8_t sound_timer;
|
||||||
|
|
||||||
|
uint16_t stack[16];
|
||||||
|
uint8_t sp;
|
||||||
|
|
||||||
|
int running;
|
||||||
|
} t_chip8;
|
||||||
|
|
||||||
|
int chip8_load_rom(t_chip8 *emu, const char *path);
|
||||||
|
void chip8_init(t_chip8 *emu);
|
||||||
|
void chip8_run(t_chip8 *emu);
|
||||||
|
|
||||||
|
#endif
|
||||||
23
hdrs/cpu.h
Normal file
23
hdrs/cpu.h
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#ifndef CPU_H
|
||||||
|
# define CPU_H
|
||||||
|
|
||||||
|
#include "./chip8.h"
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct s_instruction
|
||||||
|
{
|
||||||
|
uint16_t opcode;
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t n;
|
||||||
|
uint8_t nn;
|
||||||
|
uint16_t nnn;
|
||||||
|
} t_instruction;
|
||||||
|
|
||||||
|
|
||||||
|
uint16_t cpu_fetch(t_chip8 *emu);
|
||||||
|
t_instruction cpu_decode(uint16_t opcode);
|
||||||
|
void cpu_execute(t_chip8 *emu, t_instruction instruction);
|
||||||
|
|
||||||
|
#endif
|
||||||
27
hdrs/font.h
Normal file
27
hdrs/font.h
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef FONT_H
|
||||||
|
#define FONT_H
|
||||||
|
|
||||||
|
#define FONTSET_SIZE 80
|
||||||
|
#define FONTSET_START (MEMORY_SIZE - FONTSET_SIZE)
|
||||||
|
|
||||||
|
static const unsigned char FONTSET[FONTSET_SIZE] = {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
29
hdrs/graphics.h
Normal file
29
hdrs/graphics.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#ifndef GRAPHICS_H
|
||||||
|
#define GRAPHICS_H
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct s_chip8;
|
||||||
|
typedef struct s_chip8 t_chip8;
|
||||||
|
|
||||||
|
#define SCREEN_WIDTH 64
|
||||||
|
#define SCREEN_HEIGHT 32
|
||||||
|
#define SCALE 12
|
||||||
|
|
||||||
|
typedef struct s_display
|
||||||
|
{
|
||||||
|
SDL_Window *window;
|
||||||
|
SDL_Renderer *renderer;
|
||||||
|
SDL_Texture *texture;
|
||||||
|
uint32_t pixels[SCREEN_WIDTH * SCREEN_HEIGHT];
|
||||||
|
} t_display;
|
||||||
|
|
||||||
|
int graphics_init(t_display *display);
|
||||||
|
void graphics_render(t_chip8 *emu);
|
||||||
|
void graphics_cleanup(t_display *display);
|
||||||
|
void graphics_draw_sprite(t_chip8 *emu, uint8_t x, uint8_t y, uint8_t n);
|
||||||
|
int graphics_display_ready();
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
||||||
26
hdrs/input.h
Normal file
26
hdrs/input.h
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#ifndef INPUT_H
|
||||||
|
#define INPUT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
#define KEY_COUNT 16
|
||||||
|
|
||||||
|
struct s_chip8;
|
||||||
|
typedef struct s_chip8 t_chip8;
|
||||||
|
|
||||||
|
typedef struct s_keyboard
|
||||||
|
{
|
||||||
|
uint8_t keyboard[KEY_COUNT];
|
||||||
|
uint8_t waiting_for_key;
|
||||||
|
uint8_t key_pressed;
|
||||||
|
uint8_t key_register;
|
||||||
|
} t_keyboard;
|
||||||
|
|
||||||
|
|
||||||
|
void input_update(SDL_Event *event, uint8_t *keyboard);
|
||||||
|
uint8_t map_sdl_to_chip8(SDL_Keycode key);
|
||||||
|
uint8_t get_pressed_key(t_chip8 *emu);
|
||||||
|
|
||||||
|
|
||||||
|
#endif // INPUT_H
|
||||||
22
hdrs/memory.h
Normal file
22
hdrs/memory.h
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef MEMORY_H
|
||||||
|
# define MEMORY_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "graphics.h"
|
||||||
|
|
||||||
|
#define MEMORY_SIZE 4096
|
||||||
|
|
||||||
|
typedef struct s_memory
|
||||||
|
{
|
||||||
|
uint8_t memory[MEMORY_SIZE];
|
||||||
|
uint8_t screen[SCREEN_WIDTH * SCREEN_HEIGHT];
|
||||||
|
} t_memory;
|
||||||
|
|
||||||
|
void memory_init(t_memory *mem);
|
||||||
|
int memory_load_rom(t_memory *mem, FILE *fp);
|
||||||
|
void memory_load_fontset(t_memory *mem);
|
||||||
|
void memory_clear_screen(t_memory *mem);
|
||||||
|
void memory_dump(t_memory *mem, const char *path);
|
||||||
|
|
||||||
|
#endif
|
||||||
119
srcs/chip8.c
Normal file
119
srcs/chip8.c
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#include "chip8.h"
|
||||||
|
#include "memory.h"
|
||||||
|
#include "cpu.h"
|
||||||
|
#include "input.h"
|
||||||
|
#include "libft.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
|
||||||
|
int chip8_load_rom(t_chip8 *emu, const char *path)
|
||||||
|
{
|
||||||
|
FILE *fp = fopen(path, "rb");
|
||||||
|
if (fp == NULL)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memory_load_rom(&emu->memory, fp) == 1)
|
||||||
|
{
|
||||||
|
fclose(fp);
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_init(t_chip8 *emu)
|
||||||
|
{
|
||||||
|
ft_memset(emu->keyboard.keyboard, 0, sizeof(emu->keyboard.keyboard));
|
||||||
|
|
||||||
|
memory_init(&emu->memory);
|
||||||
|
memory_load_fontset(&emu->memory);
|
||||||
|
|
||||||
|
memset(emu->v, 0, sizeof(emu->v));
|
||||||
|
memset(emu->stack, 0, sizeof(emu->stack));
|
||||||
|
|
||||||
|
emu->i = 0;
|
||||||
|
emu->pc = 0x200;
|
||||||
|
|
||||||
|
emu->delay_timer = 0;
|
||||||
|
emu->sound_timer = 0;
|
||||||
|
|
||||||
|
emu->sp = 0;
|
||||||
|
|
||||||
|
if (graphics_init(&emu->display) != 0)
|
||||||
|
{
|
||||||
|
printf("Failed to initialize graphics!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphics_display_ready() != 1)
|
||||||
|
{
|
||||||
|
printf("Graphics display failed to initialize properly!\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
emu->running = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void chip8_run(t_chip8 *emu)
|
||||||
|
{
|
||||||
|
uint32_t frame_start, frame_end, frame_duration;
|
||||||
|
SDL_Event event;
|
||||||
|
|
||||||
|
while (emu->running)
|
||||||
|
{
|
||||||
|
frame_start = SDL_GetTicks();
|
||||||
|
|
||||||
|
while (SDL_PollEvent(&event))
|
||||||
|
{
|
||||||
|
if (event.type == SDL_QUIT)
|
||||||
|
{
|
||||||
|
emu->running = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP)
|
||||||
|
{
|
||||||
|
input_update(&event, emu->keyboard.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emu->keyboard.waiting_for_key)
|
||||||
|
{
|
||||||
|
uint8_t key = get_pressed_key(emu);
|
||||||
|
if (key != 0xFF)
|
||||||
|
{
|
||||||
|
emu->v[emu->keyboard.key_register] = key;
|
||||||
|
emu->keyboard.waiting_for_key = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
uint16_t opcode = cpu_fetch(emu);
|
||||||
|
t_instruction instruction = cpu_decode(opcode);
|
||||||
|
cpu_execute(emu, instruction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emu->delay_timer > 0)
|
||||||
|
emu->delay_timer--;
|
||||||
|
if (emu->sound_timer > 0)
|
||||||
|
emu->sound_timer--;
|
||||||
|
|
||||||
|
graphics_render(emu);
|
||||||
|
|
||||||
|
frame_end = SDL_GetTicks();
|
||||||
|
frame_duration = frame_end - frame_start;
|
||||||
|
|
||||||
|
if (frame_duration < FRAME_TIME_MS)
|
||||||
|
{
|
||||||
|
SDL_Delay(FRAME_TIME_MS - frame_duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
257
srcs/cpu.c
Normal file
257
srcs/cpu.c
Normal file
|
|
@ -0,0 +1,257 @@
|
||||||
|
#include "input.h"
|
||||||
|
#include "cpu.h"
|
||||||
|
#include "font.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define OPCODE_SIZE 2
|
||||||
|
|
||||||
|
uint16_t cpu_fetch(t_chip8 *emu)
|
||||||
|
{
|
||||||
|
// Check if there is enough space in memory to fetch a 2-byte opcode
|
||||||
|
if (emu->pc + 1 >= MEMORY_SIZE)
|
||||||
|
{
|
||||||
|
printf("Error, program counter out of bounds! (pc = 0x%04X)\n", emu->pc);
|
||||||
|
graphics_cleanup(&emu->display);
|
||||||
|
emu->running = 0;
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t opcode = (emu->memory.memory[emu->pc] << 8) | emu->memory.memory[emu->pc + 1];
|
||||||
|
return opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
t_instruction cpu_decode(uint16_t opcode)
|
||||||
|
{
|
||||||
|
t_instruction instruction;
|
||||||
|
|
||||||
|
instruction.opcode = opcode;
|
||||||
|
instruction.type = (opcode & 0xF000) >> 12;
|
||||||
|
instruction.x = (opcode & 0x0F00) >> 8;
|
||||||
|
instruction.y = (opcode & 0x00F0) >> 4;
|
||||||
|
instruction.n = opcode & 0x000F;
|
||||||
|
instruction.nn = opcode & 0x00FF;
|
||||||
|
instruction.nnn = opcode & 0x0FFF;
|
||||||
|
|
||||||
|
return instruction;
|
||||||
|
}
|
||||||
|
void cpu_execute(t_chip8 *emu, t_instruction instruction)
|
||||||
|
{
|
||||||
|
uint8_t x = instruction.x;
|
||||||
|
uint8_t y = instruction.y;
|
||||||
|
uint8_t n = instruction.n;
|
||||||
|
uint8_t nn = instruction.nn;
|
||||||
|
uint16_t nnn = instruction.nnn;
|
||||||
|
|
||||||
|
switch (instruction.type)
|
||||||
|
{
|
||||||
|
// 0x0NNN: SYS addr (ignored, do nothing)
|
||||||
|
case 0x0:
|
||||||
|
// 0x00E0: CLS (Clear screen)
|
||||||
|
if (instruction.opcode == 0x00E0)
|
||||||
|
memory_clear_screen(&emu->memory);
|
||||||
|
// 0x00EE: RET (Return from subroutine)
|
||||||
|
else if (instruction.opcode == 0x00EE) {
|
||||||
|
if (emu->sp == 0)
|
||||||
|
{
|
||||||
|
printf("Stack underflow on RET!\n");
|
||||||
|
emu->running = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emu->pc = emu->stack[--emu->sp];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x1NNN: JP addr (Jump to address NNN)
|
||||||
|
case 0x1:
|
||||||
|
emu->pc = nnn;
|
||||||
|
return; // Do not increment PC after jump
|
||||||
|
|
||||||
|
// 0x2NNN: CALL addr (Call subroutine at NNN)
|
||||||
|
case 0x2:
|
||||||
|
if (emu->sp >= 16)
|
||||||
|
{
|
||||||
|
printf("Stack overflow!\n");
|
||||||
|
emu->running = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
emu->stack[emu->sp++] = emu->pc;
|
||||||
|
emu->pc = nnn;
|
||||||
|
return; // Do not increment PC after call
|
||||||
|
|
||||||
|
// 0x3XNN: SE Vx, byte (Skip if Vx == NN)
|
||||||
|
case 0x3:
|
||||||
|
if (emu->v[x] == nn)
|
||||||
|
emu->pc += OPCODE_SIZE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x4XNN: SNE Vx, byte (Skip if Vx != NN)
|
||||||
|
case 0x4:
|
||||||
|
if (emu->v[x] != nn)
|
||||||
|
emu->pc += OPCODE_SIZE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x5XY0: SE Vx, Vy (Skip if Vx == Vy)
|
||||||
|
case 0x5:
|
||||||
|
if (n == 0 && emu->v[x] == emu->v[y])
|
||||||
|
emu->pc += OPCODE_SIZE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x6XNN: LD Vx, byte (Load NN into Vx)
|
||||||
|
case 0x6:
|
||||||
|
emu->v[x] = nn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x7XNN: ADD Vx, byte (Add NN to Vx)
|
||||||
|
case 0x7:
|
||||||
|
emu->v[x] += nn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x8XYN: Arithmetic opcodes (switch on last nibble, 'n')
|
||||||
|
case 0x8:
|
||||||
|
switch (n)
|
||||||
|
{
|
||||||
|
// 0x8XY0: LD Vx, Vy (Set Vx = Vy)
|
||||||
|
case 0x0:
|
||||||
|
emu->v[x] = emu->v[y];
|
||||||
|
break;
|
||||||
|
// 0x8XY1: OR Vx, Vy (Set Vx = Vx OR Vy)
|
||||||
|
case 0x1:
|
||||||
|
emu->v[x] |= emu->v[y];
|
||||||
|
break;
|
||||||
|
// 0x8XY2: AND Vx, Vy (Set Vx = Vx AND Vy)
|
||||||
|
case 0x2:
|
||||||
|
emu->v[x] &= emu->v[y];
|
||||||
|
break;
|
||||||
|
// 0x8XY3: XOR Vx, Vy (Set Vx = Vx XOR Vy)
|
||||||
|
case 0x3:
|
||||||
|
emu->v[x] ^= emu->v[y];
|
||||||
|
break;
|
||||||
|
// 0x8XY4: ADD Vx, Vy (Set Vx = Vx + Vy, set VF = carry)
|
||||||
|
case 0x4:
|
||||||
|
emu->v[0xF] = (emu->v[x] + emu->v[y]) > 0xFF;
|
||||||
|
emu->v[x] += emu->v[y];
|
||||||
|
break;
|
||||||
|
// 0x8XY5: SUB Vx, Vy (Set Vx = Vx - Vy, set VF = NOT borrow)
|
||||||
|
case 0x5:
|
||||||
|
emu->v[0xF] = emu->v[x] >= emu->v[y];
|
||||||
|
emu->v[x] -= emu->v[y];
|
||||||
|
break;
|
||||||
|
// 0x8XY6: SHR Vx, Vy (Set Vx = Vx >> 1, set VF = LSB before shift)
|
||||||
|
case 0x6:
|
||||||
|
emu->v[0xF] = emu->v[x] & 1;
|
||||||
|
emu->v[x] >>= 1;
|
||||||
|
break;
|
||||||
|
// 0x8XY7: SUBN Vx, Vy (Set Vx = Vy - Vx, set VF = NOT borrow)
|
||||||
|
case 0x7:
|
||||||
|
emu->v[0xF] = emu->v[y] >= emu->v[x];
|
||||||
|
emu->v[x] = emu->v[y] - emu->v[x];
|
||||||
|
break;
|
||||||
|
// 0x8XYE: SHL Vx, Vy (Set Vx = Vx << 1, set VF = MSB before shift)
|
||||||
|
case 0xE:
|
||||||
|
emu->v[0xF] = (emu->v[x] >> 7) & 1;
|
||||||
|
emu->v[x] <<= 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("Unknown arithmetic opcode: 0x%04X\n", instruction.opcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x9XY0: SNE Vx, Vy (Skip if Vx != Vy)
|
||||||
|
case 0x9:
|
||||||
|
if (n == 0 && emu->v[x] != emu->v[y])
|
||||||
|
emu->pc += OPCODE_SIZE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0x0ANNN: LD I, addr (Set I = NNN)
|
||||||
|
case 0xA:
|
||||||
|
emu->i = nnn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0xBNNN: JP V0, addr (Jump to NNN + V0)
|
||||||
|
case 0xB:
|
||||||
|
emu->pc = nnn + emu->v[0];
|
||||||
|
return; // Do not increment PC after jump
|
||||||
|
|
||||||
|
// 0xCXNN: RND Vx, byte (Set Vx = random byte AND NN)
|
||||||
|
case 0xC:
|
||||||
|
emu->v[x] = (rand() & 0xFF) & nn;
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0xDXYN: DRW Vx, Vy, nibble (Draw sprite at (Vx, Vy), N rows)
|
||||||
|
// N=0 means 16 rows (wrapping)
|
||||||
|
case 0xD:
|
||||||
|
graphics_draw_sprite(emu, emu->v[x] % 64, emu->v[y] % 32, n);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0xEX9E: SKP Vx (Skip if key Vx is pressed)
|
||||||
|
// 0xEXA1: SKNP Vx (Skip if key Vx is not pressed)
|
||||||
|
case 0xE:
|
||||||
|
if (nn == 0x9E)
|
||||||
|
{
|
||||||
|
if (emu->keyboard.keyboard[emu->v[x] & 0xF])
|
||||||
|
emu->pc += OPCODE_SIZE;
|
||||||
|
}
|
||||||
|
else if (nn == 0xA1)
|
||||||
|
{
|
||||||
|
if (!emu->keyboard.keyboard[emu->v[x] & 0xF])
|
||||||
|
emu->pc += OPCODE_SIZE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// 0xFXNN: Various instructions (switch on NN)
|
||||||
|
case 0xF:
|
||||||
|
switch (nn)
|
||||||
|
{
|
||||||
|
// 0xFX07: LD Vx, DT (Set Vx = delay timer)
|
||||||
|
case 0x07:
|
||||||
|
emu->v[x] = emu->delay_timer;
|
||||||
|
break;
|
||||||
|
// 0xFX0A: LD Vx, K (Wait for key press, store in Vx)
|
||||||
|
case 0x0A:
|
||||||
|
emu->keyboard.waiting_for_key = 1;
|
||||||
|
emu->keyboard.key_register = x;
|
||||||
|
break;
|
||||||
|
// 0xFX15: LD DT, Vx (Set delay timer = Vx)
|
||||||
|
case 0x15:
|
||||||
|
emu->delay_timer = emu->v[x];
|
||||||
|
break;
|
||||||
|
// 0xFX18: LD ST, Vx (Set sound timer = Vx)
|
||||||
|
case 0x18:
|
||||||
|
emu->sound_timer = emu->v[x];
|
||||||
|
break;
|
||||||
|
// 0xFX1E: ADD I, Vx (Set I = I + Vx)
|
||||||
|
case 0x1E:
|
||||||
|
emu->i += emu->v[x];
|
||||||
|
break;
|
||||||
|
// 0xFX29: LD F, Vx (Set I = font sprite address for character in Vx)
|
||||||
|
case 0x29:
|
||||||
|
// emu->i = 0x50 + 5 * (emu->v[x] & 0xF);
|
||||||
|
emu->i = FONTSET_START + 5 * (emu->v[x] & 0xF);
|
||||||
|
break;
|
||||||
|
// 0xFX33: LD B, Vx (Store BCD representation of Vx at I, I+1, I+2)
|
||||||
|
case 0x33:
|
||||||
|
emu->memory.memory[emu->i] = (emu->v[x] / 100) % 10;
|
||||||
|
emu->memory.memory[emu->i + 1] = (emu->v[x] / 10) % 10;
|
||||||
|
emu->memory.memory[emu->i + 2] = emu->v[x] % 10;
|
||||||
|
break;
|
||||||
|
// 0xFX55: LD [I], Vx (Store V0-Vx in memory starting at I)
|
||||||
|
case 0x55:
|
||||||
|
for (int i = 0; i <= x; i++)
|
||||||
|
emu->memory.memory[emu->i + i] = emu->v[i];
|
||||||
|
break;
|
||||||
|
// 0xFX65: LD Vx, [I] (Load V0-Vx from memory starting at I)
|
||||||
|
case 0x65:
|
||||||
|
for (int i = 0; i <= x; i++)
|
||||||
|
emu->v[i] = emu->memory.memory[emu->i + i];
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("Unknown 0xF opcode: 0x%04X\n", instruction.opcode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
emu->pc += OPCODE_SIZE;
|
||||||
|
}
|
||||||
122
srcs/graphics.c
Normal file
122
srcs/graphics.c
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#include "graphics.h"
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "chip8.h"
|
||||||
|
|
||||||
|
int graphics_init(t_display *display)
|
||||||
|
{
|
||||||
|
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "SDL2 could not initiallize! e=%s\n", SDL_GetError());
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
display->window = SDL_CreateWindow("c8",
|
||||||
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
SDL_WINDOWPOS_UNDEFINED,
|
||||||
|
SCREEN_WIDTH * SCALE,
|
||||||
|
SCREEN_HEIGHT * SCALE,
|
||||||
|
SDL_WINDOW_SHOWN);
|
||||||
|
if (!display->window)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Window could not be created! e=%s\n", SDL_GetError());
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
display->renderer = SDL_CreateRenderer(display->window, -1, SDL_RENDERER_ACCELERATED);
|
||||||
|
if (!display->renderer)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Renderer could not be created! e=%s\n", SDL_GetError());
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
display->texture = SDL_CreateTexture(display->renderer, SDL_PIXELFORMAT_ARGB8888,
|
||||||
|
SDL_TEXTUREACCESS_STREAMING,
|
||||||
|
SCREEN_WIDTH,
|
||||||
|
SCREEN_HEIGHT);
|
||||||
|
if (!display->texture)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Texture could not be created! e=%s\n", SDL_GetError());
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void graphics_render(t_chip8 *emu)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++)
|
||||||
|
{
|
||||||
|
uint8_t pixel = emu->memory.screen[i];
|
||||||
|
emu->display.pixels[i] = pixel ? 0xFFFFFFFF : 0xFF000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_UpdateTexture(emu->display.texture, NULL, emu->display.pixels, SCREEN_WIDTH * sizeof(uint32_t));
|
||||||
|
|
||||||
|
SDL_RenderClear(emu->display.renderer);
|
||||||
|
SDL_RenderCopy(emu->display.renderer, emu->display.texture, NULL, NULL);
|
||||||
|
SDL_RenderPresent(emu->display.renderer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void graphics_cleanup(t_display *display)
|
||||||
|
{
|
||||||
|
SDL_DestroyTexture(display->texture);
|
||||||
|
SDL_DestroyRenderer(display->renderer);
|
||||||
|
SDL_DestroyWindow(display->window);
|
||||||
|
SDL_Quit();
|
||||||
|
}
|
||||||
|
void graphics_draw_sprite(t_chip8 *emu, uint8_t x, uint8_t y, uint8_t n)
|
||||||
|
{
|
||||||
|
uint8_t pixel;
|
||||||
|
uint16_t sprite_address = emu->i;
|
||||||
|
|
||||||
|
uint8_t collision = 0;
|
||||||
|
|
||||||
|
for (int row = 0; row < n; row++)
|
||||||
|
{
|
||||||
|
pixel = emu->memory.memory[sprite_address + row];
|
||||||
|
|
||||||
|
for (int col = 0; col < 8; col++)
|
||||||
|
{
|
||||||
|
if ((pixel & (0x80 >> col)) != 0)
|
||||||
|
{
|
||||||
|
uint16_t screen_x = (x + col) % SCREEN_WIDTH;
|
||||||
|
uint16_t screen_y = (y + row) % SCREEN_HEIGHT;
|
||||||
|
|
||||||
|
if (emu->memory.screen[screen_y * SCREEN_WIDTH + screen_x] == 1)
|
||||||
|
{
|
||||||
|
collision = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
emu->memory.screen[screen_y * SCREEN_WIDTH + screen_x] ^= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emu->v[0xF] = collision;
|
||||||
|
}
|
||||||
|
|
||||||
|
int graphics_display_ready()
|
||||||
|
{
|
||||||
|
SDL_Event event;
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
while (SDL_PollEvent(&event) != 0)
|
||||||
|
{
|
||||||
|
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SHOWN)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for window close event
|
||||||
|
if (event.type == SDL_QUIT)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
60
srcs/input.c
Normal file
60
srcs/input.c
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#include "input.h"
|
||||||
|
#include "chip8.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
void input_update(SDL_Event *event, uint8_t *keyboard)
|
||||||
|
{
|
||||||
|
uint8_t key = map_sdl_to_chip8(event->key.keysym.sym);
|
||||||
|
if (key != 0xFF)
|
||||||
|
{
|
||||||
|
if (event->type == SDL_KEYDOWN)
|
||||||
|
{
|
||||||
|
keyboard[key] = 1;
|
||||||
|
}
|
||||||
|
else if (event->type == SDL_KEYUP)
|
||||||
|
{
|
||||||
|
keyboard[key] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t map_sdl_to_chip8(SDL_Keycode key)
|
||||||
|
{
|
||||||
|
// Map to a numpad-like layout:
|
||||||
|
// 1 2 3 4 → 1 2 3 C
|
||||||
|
// Q W E R → 4 5 6 D
|
||||||
|
// A S D F → 7 8 9 E
|
||||||
|
// Z X C V → A 0 B F
|
||||||
|
switch (key)
|
||||||
|
{
|
||||||
|
case SDLK_1: return 0x1;
|
||||||
|
case SDLK_2: return 0x2;
|
||||||
|
case SDLK_3: return 0x3;
|
||||||
|
case SDLK_4: return 0xC;
|
||||||
|
case SDLK_q: return 0x4;
|
||||||
|
case SDLK_w: return 0x5;
|
||||||
|
case SDLK_e: return 0x6;
|
||||||
|
case SDLK_r: return 0xD;
|
||||||
|
case SDLK_a: return 0x7;
|
||||||
|
case SDLK_s: return 0x8;
|
||||||
|
case SDLK_d: return 0x9;
|
||||||
|
case SDLK_f: return 0xE;
|
||||||
|
case SDLK_z: return 0xA;
|
||||||
|
case SDLK_x: return 0x0;
|
||||||
|
case SDLK_c: return 0xB;
|
||||||
|
case SDLK_v: return 0xF;
|
||||||
|
default: return 0xFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t get_pressed_key(t_chip8 *emu)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
if (emu->keyboard.keyboard[i])
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return 0xFF; // No key pressed
|
||||||
|
}
|
||||||
25
srcs/main.c
Normal file
25
srcs/main.c
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "../hdrs/chip8.h"
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
if (argc != 2)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Usage: %s <rom.ch8>\n", argv[0]);
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
t_chip8 emu;
|
||||||
|
|
||||||
|
chip8_init(&emu);
|
||||||
|
|
||||||
|
if (chip8_load_rom(&emu, argv[1]) == 1)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "Failed to load rom: %s\n", argv[1]);
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
chip8_run(&emu);
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
54
srcs/memory.c
Normal file
54
srcs/memory.c
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#include "../hdrs/memory.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "../hdrs/font.h"
|
||||||
|
#include "libft.h"
|
||||||
|
|
||||||
|
void memory_init(t_memory *mem)
|
||||||
|
{
|
||||||
|
ft_memset(mem->memory, 0, MEMORY_SIZE);
|
||||||
|
}
|
||||||
|
int memory_load_rom(t_memory *mem, FILE *fp)
|
||||||
|
{
|
||||||
|
fseek(fp, 0, SEEK_END);
|
||||||
|
size_t rom_size = ftell(fp);
|
||||||
|
rewind(fp);
|
||||||
|
|
||||||
|
if (rom_size > (sizeof(mem->memory) - 0x200))
|
||||||
|
{
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(&mem->memory[0x200], 1, rom_size, fp) != rom_size)
|
||||||
|
{
|
||||||
|
return (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory_clear_screen(t_memory *mem)
|
||||||
|
{
|
||||||
|
ft_memset(mem->screen, 0, SCREEN_WIDTH * SCREEN_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void memory_load_fontset(t_memory *mem)
|
||||||
|
{
|
||||||
|
ft_memcpy(&mem->memory[MEMORY_SIZE - FONTSET_SIZE], FONTSET, FONTSET_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void memory_dump(t_memory *mem, const char *path)
|
||||||
|
{
|
||||||
|
FILE *fp = fopen(path, "wb");
|
||||||
|
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
printf("Failed to open: %s\n", path);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fwrite(mem->memory, 1, MEMORY_SIZE, fp);
|
||||||
|
fclose(fp);
|
||||||
|
printf("Memory dumped to: %s\n", path);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue