chipd8/source/app.d

1047 lines
21 KiB
D
Raw Permalink Normal View History

import std.stdio;
import derelict.sdl2.sdl;
2018-06-22 23:54:39 +01:00
import derelict.imgui.imgui;
2018-10-03 23:36:40 +01:00
import gl : PixelBuffer;
2018-06-22 23:54:39 +01:00
import window;
import imgui;
struct Chip8Status {
2018-10-05 00:29:02 +01:00
struct Assembly {
import assembler;
alias OpCode = Assembler.OpCode;
alias Instr = Assembler.Instruction;
alias Arg = Assembler.Argument;
enum ViewMode {
Memory,
Disassembly
}
2018-10-05 15:16:21 +01:00
enum DisassemblyMode {
Linear,
Recursive,
Dynamic
}
2018-10-05 00:29:02 +01:00
private {
Chip8* emu_;
ViewMode mode_;
int range_start_;
int range_end_;
// disassembly state
2018-10-05 15:16:21 +01:00
DisassemblyMode ass_mode_;
2018-10-05 00:29:02 +01:00
Instr[] disassembly_;
}
void initialize(Chip8* emu) {
2018-10-05 15:16:21 +01:00
mode_ = ViewMode.Disassembly;
2018-10-05 00:29:02 +01:00
emu_ = emu;
} // initialize
static const (char)* formatQual(Arg a, bool with_comma = false) {
if (with_comma) {
switch (a) with (Arg) {
2018-10-05 15:16:21 +01:00
case Vx, Vy, V0: return "V%hu,";
case nibble: return "%01X,";
case beit: return "0x%02X,";
case addr: return "0x%03X,";
default: return "%s,";
2018-10-05 00:29:02 +01:00
}
} else {
switch (a) with (Arg) {
case Vx, Vy, V0: return "V%hu";
case nibble: return "0x%01X";
case beit: return "0x%02X";
case addr: return "0x%03X";
default: return "%s";
}
}
2018-10-05 15:16:21 +01:00
} // formatQual
2018-10-05 00:29:02 +01:00
void disassemble() {
ubyte[] instrs = emu_.ram[range_start_ .. range_end_];
disassembly_ = Assembler.disassemble(instrs);
2018-10-05 15:16:21 +01:00
} // disassemble
2018-10-05 00:29:02 +01:00
void setRange(ushort start, ushort end) {
range_start_ = start;
range_end_ = end;
2018-10-05 15:16:21 +01:00
} // setRange
void reset() {
disassembly_ = null;
} // reset
2018-10-05 00:29:02 +01:00
void draw() {
import std.range : chunks;
2018-10-05 15:16:21 +01:00
igBegin("Emulator Assembly");
igRadioButton("Memory View", cast(int*)&mode_, cast(int)ViewMode.Memory); igSameLine();
igRadioButton("Disassembly View", cast(int*)&mode_, cast(int)ViewMode.Disassembly);
2018-10-05 00:29:02 +01:00
if (mode_ == ViewMode.Memory) {
} else if (mode_ == ViewMode.Disassembly) {
2018-10-05 15:16:21 +01:00
igInputInt("Range Start ", &range_start_);
igInputInt("Range End ", &range_end_);
igNewLine();
igText("Disassembly Mode");
igRadioButton("Linear", cast(int*)&ass_mode_, cast(int)DisassemblyMode.Linear); igSameLine();
igRadioButton("Recursive", cast(int*)&ass_mode_, cast(int)DisassemblyMode.Recursive); igSameLine();
igRadioButton("Dynamic", cast(int*)&ass_mode_, cast(int)DisassemblyMode.Dynamic);
2018-10-05 00:29:02 +01:00
if (igButton("Disassemble")) {
2018-10-05 15:16:21 +01:00
final switch (ass_mode_) with (DisassemblyMode) {
case Linear:
disassemble();
break;
case Recursive:
break;
case Dynamic:
break;
}
2018-10-05 00:29:02 +01:00
}
2018-10-05 15:16:21 +01:00
2018-10-05 00:29:02 +01:00
}
if (range_start_ < 0) range_start_ = 0;
if (range_start_ > ushort.max) range_start_ = 0;
if (range_end_ < range_start_) range_end_ = 0;
if (range_end_ > ushort.max) range_end_ = 0;
if (mode_ == ViewMode.Memory) {
/*
foreach (ref ubyte[] b; emu_.ram[0x200 + range_start_ .. range_end_].chunks(2)) {
ushort opcode = (*(cast(ushort*)(b.ptr)));
igText("0x%04X", opcode);
}
*/
} else if (mode_ == ViewMode.Disassembly) {
2018-10-05 15:16:21 +01:00
int cur_offset = range_start_;
2018-10-05 00:29:02 +01:00
foreach (ref instr; disassembly_) {
2018-10-05 15:16:21 +01:00
if (emu_.cpu.pc == cur_offset) {
igTextColored(ImColor(0, 255, 0), "0x%03X |", cur_offset);
cur_offset += 2;
igSameLine();
} else {
igText("0x%03X |", cur_offset);
cur_offset += 2;
igSameLine();
}
2018-10-05 00:29:02 +01:00
switch (instr.args) {
case 3:
igText("%s", instr.op);
igSameLine();
Arg a1_arg;
auto a1 = instr.a1(a1_arg);
2018-10-05 15:16:21 +01:00
igText(formatQual(a1_arg, true), a1);
2018-10-05 00:29:02 +01:00
igSameLine();
Arg a2_arg;
auto a2 = instr.a2(a2_arg);
2018-10-05 15:16:21 +01:00
igText(formatQual(a2_arg, true), a2);
2018-10-05 00:29:02 +01:00
igSameLine();
Arg a3_arg;
auto a3 = instr.a3(a3_arg);
igText(formatQual(a3_arg), a3);
break;
case 2:
igText("%s", instr.op);
igSameLine();
Arg a1_arg;
auto a1 = instr.a1(a1_arg);
2018-10-05 15:16:21 +01:00
igText(formatQual(a1_arg, true), a1);
2018-10-05 00:29:02 +01:00
igSameLine();
Arg a2_arg;
auto a2 = instr.a2(a2_arg);
igText(formatQual(a2_arg), a2);
break;
case 1:
igText("%s", instr.op);
igSameLine();
Arg a1_arg;
auto a1 = instr.a1(a1_arg);
igText(formatQual(a1_arg), a1);
break;
case 0:
igText("%s", instr.op);
break;
default:
break;
}
}
}
igEnd();
} // draw
} // Assembly
2018-09-30 23:11:30 +01:00
// emu ptr
Emulator* run_;
Chip8* emu_;
2018-10-05 00:29:02 +01:00
// assembly/disassembly
Assembly ass_;
2018-09-30 23:11:30 +01:00
// loaded program
2018-10-05 15:54:38 +01:00
const(char)*[string] string_cache;
2018-09-30 23:11:30 +01:00
const (char)* loaded_program;
2018-10-05 15:54:38 +01:00
string loaded_program_str;
2018-09-30 23:11:30 +01:00
// mem editor
// MemoryEditor mem_editor_;
// state
bool status_menu_ = true;
int stack_cur_ = -1;
void initialize(Emulator* run, Chip8* emu) {
this.run_ = run;
this.emu_ = emu;
2018-10-05 00:29:02 +01:00
ass_.initialize(emu);
} // initialize
alias Callback = float delegate(int idx, const char** out_text);
static extern(C) bool doCallback(void* ptr, int idx, const (char**) out_text) {
auto callback = *(cast(Callback*) ptr);
callback(idx, out_text);
return true;
} // doCallback;
void getStackFrame(int idx, const (char**) out_text) {
static char[32] frame_text;
import core.stdc.stdio : sprintf;
sprintf(frame_text.ptr, "0x%04X", emu_.stack[idx]);
auto p = frame_text.ptr;
auto op = cast(char**)out_text;
*op = p;
} // getStackFrame
2018-09-30 17:22:59 +01:00
void resetShortcut() {
2018-09-30 23:11:30 +01:00
loaded_program = null;
2018-09-30 17:22:59 +01:00
emu_.reset();
2018-10-05 15:16:21 +01:00
ass_.reset();
2018-09-30 17:22:59 +01:00
} // resetShortcut
void loadShortcut() {
import std.file : read;
2018-10-05 15:54:38 +01:00
auto buf = read(loaded_program_str);
emu_.load(0x200, buf); // do ze load yes, will copy all the data in
2018-10-05 00:29:02 +01:00
ass_.setRange(cast(ushort)0x200, cast(ushort)(0x200 + cast(ushort)buf.length));
2018-10-05 15:16:21 +01:00
ass_.reset();
} // loadShortcut
void saveShortcut() {
} // saveShortcut
void debugShortcut() {
status_menu_ = !status_menu_;
} // debugShortcut
void redrawShortcut() {
emu_.draw_flag = true;
} // redrawShortcut
void toggleRunShortcut() {
emu_.run_flag = !emu_.run_flag;
} // toggleRunShortcut
void stepShortcut() {
emu_.step();
} // stepShortcut
void quitShortcut() {
run_.quit();
} // quitShortcut
void draw() {
2018-10-05 00:29:02 +01:00
ass_.draw();
if (!status_menu_) return;
if (igBeginMainMenuBar()) {
if (igBeginMenu("Menu")) {
2018-09-30 17:22:59 +01:00
if (igMenuItem("Reset", "CTRL+R")) {
resetShortcut();
}
2018-10-05 15:54:38 +01:00
if (igBeginMenu("Load")) {
import std.file;
auto dir_iterator = dirEntries("", "*.ch8", SpanMode.breadth);
foreach (DirEntry e; dir_iterator) {
import std.string : toStringz;
const (char)* c_str;
if (!(e.name in string_cache)) c_str = toStringz(e.name);
else c_str = string_cache[e.name];
if (igMenuItem(c_str)) {
loaded_program_str = e.name;
loaded_program = c_str;
loadShortcut();
}
}
igEndMenu();
}
if (igMenuItem("Debug", "CTRL+D")) {
debugShortcut();
}
if (igMenuItem("Quit", "CTRL+Q")) {
quitShortcut();
}
igEndMenu();
}
igEndMainMenuBar();
}
{
import std.range : chunks;
igBegin("Emulator Status");
igBeginChild("General");
2018-09-30 23:11:30 +01:00
if (!loaded_program) {
igText("Loaded Program: none");
} else {
igText("Loaded Program: %s", loaded_program);
}
igText("Opcode: 0x%04X", emu_.cpu.opcode);
igSameLine();
2018-09-30 23:11:30 +01:00
igText("| PC: 0x%04X (%hu)", emu_.cpu.pc, emu_.cpu.pc);
2018-09-30 21:25:40 +01:00
// igDragInt("##pc", cast(int*)&emu_.cpu.pc, 0.5f, 0, emu_.ram.length);
igText("Registers (v0 - vF)");
igColumns(4, null, false);
2018-09-30 23:11:30 +01:00
igIndent();
auto n = 0;
foreach (ref chunk; emu_.cpu.v[].chunks(4)) {
igText("v%0X 0x%02X ", n, chunk[0]);
igText("v%0X 0x%02X ", n+1, chunk[1]);
igText("v%0X 0x%02X ", n+2, chunk[2]);
igText("v%0X 0x%02X ", n+3, chunk[3]);
igNextColumn();
n += 4;
}
igColumns(1, null, false);
2018-09-30 23:11:30 +01:00
igUnindent();
igText("Index Register: 0x%04X", emu_.cpu.i);
igText("Delay Timer: 0x%04X", emu_.cpu.delay_timer);
igText("Sound Timer: 0x%04X", emu_.cpu.sound_timer);
2018-09-30 23:11:30 +01:00
if (igButton("Reload")) {
resetShortcut();
loadShortcut();
}
igSameLine();
if (igButton("Reset")) {
resetShortcut();
}
if (igButton("Step")) {
emu_.step();
}
igSameLine();
if (igButton((emu_.run_flag) ? "Stop" : "Run")) {
emu_.run_flag = !emu_.run_flag;
}
igText("Stack");
auto d = &getStackFrame;
igPushItemWidth(-1);
igListBox2("", &stack_cur_, &doCallback, cast(void*)&d, emu_.stack.length, 16);
igPopItemWidth();
igEndChild();
igEnd();
}
// mem_editor_.draw("Emulator Memory", emu_.ram[]);
} // draw
2018-06-23 01:15:25 +01:00
void handleEvent(ref SDL_Event ev) {
switch (ev.type) with (SDL_EventType) {
case SDL_KEYDOWN:
if ((ev.key.keysym.mod & KMOD_CTRL) != 0) {
switch (ev.key.keysym.scancode) with (SDL_EventType) {
2018-09-30 17:22:59 +01:00
case SDL_SCANCODE_R: resetShortcut(); break;
2018-06-23 01:15:25 +01:00
case SDL_SCANCODE_L: loadShortcut(); break;
case SDL_SCANCODE_D: debugShortcut(); break;
case SDL_SCANCODE_G: redrawShortcut(); break;
2018-09-30 17:22:59 +01:00
case SDL_SCANCODE_T: toggleRunShortcut(); break;
case SDL_SCANCODE_S: stepShortcut(); break;
2018-09-30 20:12:37 +01:00
case SDL_SCANCODE_Q: quitShortcut(); break;
2018-06-23 01:15:25 +01:00
default: break;
}
} else {
if (ev.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
quitShortcut();
}
}
2018-09-30 20:12:37 +01:00
break;
2018-06-23 01:15:25 +01:00
default:
break;
}
} // handleEvent
} // Chip8Status
2018-09-30 20:42:26 +01:00
pure
@property {
2018-10-03 23:36:40 +01:00
ubyte x(in ushort oc) {
2018-09-30 20:42:26 +01:00
return (oc & 0x0F00) >> 8;
}
2018-10-03 23:36:40 +01:00
ubyte y(in ushort oc) {
2018-09-30 20:42:26 +01:00
return (oc & 0x00F0) >> 4;
}
2018-10-03 23:36:40 +01:00
ubyte n(in ushort oc) {
2018-09-30 20:42:26 +01:00
return oc & 0x000F;
}
2018-10-03 23:36:40 +01:00
ubyte nn(in ushort oc) {
2018-09-30 20:42:26 +01:00
return (oc & 0x00FF);
}
2018-10-03 23:36:40 +01:00
ushort nnn(in ushort oc) {
2018-09-30 20:42:26 +01:00
return (oc & 0x0FFF);
}
}
struct Chip8 {
2018-10-05 15:16:21 +01:00
ubyte[80] chip8_fontset = [
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
];
alias OpCode = ushort;
alias ProgramCounter = ushort;
alias Memory = ubyte[4096];
alias Stack = ushort[16];
alias Register = ubyte;
alias Registers = Register[16];
alias IndexRegister = ushort;
alias KeyPad = ubyte[16];
struct CPU {
OpCode opcode = 0; //current opcode
ProgramCounter pc = 0x200;
Registers v; //16 general purpose registers
IndexRegister i;
Register delay_timer;
Register sound_timer;
} // CPU
CPU cpu;
Memory ram;
Stack stack;
ubyte sp;
KeyPad kp;
ubyte[64*32] screen_buf;
bool run_flag;
bool draw_flag;
2018-09-30 21:35:23 +01:00
bool reset_flag;
void load(size_t offset, in void[] data) {
assert(offset + data.length < ram.length);
ram[offset .. offset + data.length] = cast(ubyte[])data[];
2018-10-05 15:16:21 +01:00
loadFont(); // again
} // load
2018-10-05 15:16:21 +01:00
void loadFont() {
foreach (i, b; chip8_fontset) {
ram[i] = b;
}
} // loadFont
2018-09-30 17:22:59 +01:00
void reset() {
2018-09-30 21:35:23 +01:00
reset_flag = true;
2018-09-30 17:22:59 +01:00
cpu = cpu.init;
2018-09-30 18:58:46 +01:00
stack = stack.init;
2018-09-30 20:12:37 +01:00
ram[0x200 .. $ - 1] = 0;
cpu.pc = 0x200;
2018-09-30 18:58:46 +01:00
sp = sp.init;
run_flag = run_flag.init;
draw_flag = draw_flag.init;
2018-09-30 17:22:59 +01:00
screen_buf = screen_buf.init;
} // reset
void step() {
2018-09-30 21:35:23 +01:00
// reset if we go OOB
if (cpu.pc >= 0xFFF) {
reset();
return;
}
cpu.opcode = ram[cpu.pc] << 8 | ram[cpu.pc + 1];
2018-09-30 21:25:40 +01:00
ushort pc_target = cast(ProgramCounter)(cpu.pc + 2u);
2018-09-30 21:25:40 +01:00
// writefln("opcode: 0x%X, pc: 0x%X : %d", cpu.opcode, cpu.pc, cpu.pc);
switch (cpu.opcode & 0xF000) with (cpu) {
case 0x0000:
switch (cpu.opcode & 0x0FFF) {
case 0x00E0: // 0x00E0 Clears the screen.
2018-09-30 18:06:30 +01:00
screen_buf[0..$] = 0;
draw_flag = true;
break;
case 0x00EE: // 0x00EE Returns from a subroutine.
pc_target = stack[--sp];
break;
default: // 0x0NNN Calls RCA 1802 program at address NNN. Not necessary for most ROMs.
//assert(0, "0x0NNN RCA 1802 program opcode not implemented!");
break;
}
break;
case 0x1000: // 0x1NNN Jumps to address NNN.
2018-09-30 20:42:26 +01:00
pc_target = cpu.opcode.nnn;
break;
case 0x2000: // 0x2NNN Calls subroutine at NNN.
stack[sp++] = cpu.pc;
2018-09-30 20:42:26 +01:00
pc_target = cpu.opcode.nnn;
break;
case 0x3000: // 0x3XNN Skips the next instruction if VX equals NN.
2018-09-30 20:42:26 +01:00
if (cpu.v[cpu.opcode.x] == (cpu.opcode.nn)) {
2018-09-30 21:25:40 +01:00
pc_target += 2u;
}
break;
case 0x4000: // 0x4XNN Skips the next instruction if VX doesn't equal NN.
2018-09-30 20:42:26 +01:00
if (cpu.v[cpu.opcode.x] != (cpu.opcode.nn)) {
2018-09-30 21:25:40 +01:00
pc_target += 2u;
}
break;
case 0x5000: // 0x5XYO Skips the next instruction if VX equals VY.
2018-09-30 20:42:26 +01:00
if (cpu.v[cpu.opcode.x] == cpu.v[cpu.opcode.y]) {
2018-09-30 21:25:40 +01:00
pc_target += 2u;
}
break;
case 0x6000: // 0x6XNN Sets VX to NN.
2018-09-30 20:42:26 +01:00
cpu.v[cpu.opcode.x] = cpu.opcode.nn;
break;
case 0x7000: // 0x7XNN Adds NN to VX.
2018-09-30 20:42:26 +01:00
cpu.v[cpu.opcode.x] += cpu.opcode.nn;
break;
case 0x8000:
2018-10-03 23:36:40 +01:00
immutable ubyte x = cpu.opcode.x;
immutable ubyte y = cpu.opcode.y;
2018-09-30 20:42:26 +01:00
switch (cpu.opcode.n) {
case 0x0000: // 0x8XY0 Sets VX to the value of VY.
cpu.v[x] = cpu.v[y];
break;
case 0x0001: // 0x8XY1 Sets VX to VX or VY.
cpu.v[x] = cpu.v[x] | cpu.v[y];
break;
case 0x0002: // 0x8XY2 Sets VX to VX and VY.
cpu.v[x] = cpu.v[x] & cpu.v[y];
break;
case 0x0003: // 0x8XY3 Sets VX to VX xor VY.
cpu.v[x] = cpu.v[x] ^ cpu.v[y];
break;
case 0x0004: // 0x8XY4 Adds VY to VX. VF is set to 1 when there's a carry, and to 0 when there isn't.
2018-10-03 23:36:40 +01:00
immutable ubyte vx = cpu.v[x];
immutable ubyte vy = cpu.v[y];
2018-09-30 18:58:46 +01:00
if (cast(ushort)vx + cast(ushort)vy > 255) {
cpu.v[0xF] = 1;
} else {
cpu.v[0xF] = 0;
}
cpu.v[x] += cpu.v[y];
break;
case 0x0005: // 0x8XY5 VY is subtracted from VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
2018-10-03 23:36:40 +01:00
immutable ubyte vx = cpu.v[x];
immutable ubyte vy = cpu.v[y];
2018-09-30 18:58:46 +01:00
if (vx > vy) {
cpu.v[0xF] = 1;
} else {
cpu.v[0xF] = 0;
}
cpu.v[x] -= cpu.v[y];
break;
case 0x0006: // 0x8XY6 Shifts VX right by one. VF is set to the value of the least significant bit of VX before the shift.
2018-10-03 23:36:40 +01:00
immutable ubyte vx = cpu.v[x];
2018-09-30 21:25:40 +01:00
cpu.v[0xF] = vx & 0x1;
cpu.v[x] >>= 1;
break;
case 0x0007: // 0x8XY7 Sets VX to VY minus VX. VF is set to 0 when there's a borrow, and 1 when there isn't.
2018-10-03 23:36:40 +01:00
immutable ubyte vx = cpu.v[x];
immutable ubyte vy = cpu.v[y];
2018-09-30 18:58:46 +01:00
if (vy > vx) {
cpu.v[0xF] = 1;
} else {
cpu.v[0xF] = 0;
}
2018-09-30 21:25:40 +01:00
cpu.v[x] = cast(Register)(vy - vx);
break;
case 0x000E: // 0x8XYE Shifts VX left by one. VF is set to the value of the most significant bit of VX before the shift.
2018-10-03 23:36:40 +01:00
immutable ubyte vx = cpu.v[x];
2018-09-30 21:25:40 +01:00
cpu.v[0xF] = vx >> 7;
cpu.v[x] <<= 1;
break;
2018-09-30 17:22:59 +01:00
default: // unhandled for some reason
writefln("unknown opcode: 0x%x", cpu.opcode);
break;
}
break;
case 0x9000: // 0x9XYO Skips the next instruction if VX doesn't equal VY.
2018-09-30 20:42:26 +01:00
if (cpu.v[cpu.opcode.x] != cpu.v[cpu.opcode.y]) {
2018-09-30 21:25:40 +01:00
pc_target += 2u; // do skip yes
}
break;
case 0xA000: // 0xANNN Sets I to the address NNN.
2018-09-30 20:42:26 +01:00
cpu.i = cpu.opcode.nnn;
break;
case 0xB000: // 0xBNNN Jumps to the address NNN plus V0.
2018-09-30 20:42:26 +01:00
pc_target = cast(ProgramCounter)(cpu.opcode.nnn + cpu.v[0x0]);
break;
case 0xC000: // 0xCXNN Sets VX to the result of a bitwise and operation on a random number and NN.
import std.random : uniform;
2018-09-30 21:25:40 +01:00
ubyte x = cpu.opcode.x;
2018-10-05 15:16:21 +01:00
cpu.v[x] = uniform(Register.min, Register.max) & x;
break;
// 0xDXYN
// Sprites stored in memory at location in index register (I), 8bits wide.
// Wraps around the screen. If when drawn, clears a pixel, register VF is set to 1 otherwise it is zero.
// All drawing is XOR drawing (i.e. it toggles the screen pixels).
// Sprites are drawn starting at position VX, VY. N is the number of 8bit rows that need to be drawn.
// If N is greater than 1, second line continues at position VX, VY+1, and so on.
case 0xD000:
2018-10-03 23:36:40 +01:00
immutable ProgramCounter spr_addr = cpu.i;
immutable ubyte x = cpu.opcode.x;
immutable ubyte y = cpu.opcode.y;
immutable ubyte n = cpu.opcode.n;
foreach(int row; 0 .. n) {
2018-10-03 23:36:40 +01:00
immutable ushort pixel = ram[spr_addr + row];
foreach (int col; 0 .. 8) {
2018-09-30 21:25:40 +01:00
if ((pixel & (0x80 >> col)) != 0) {
ubyte x_off = cast(ubyte)((x + col) % 64);
ubyte y_off = cast(ubyte)((y + row) % 32);
ushort offset = x_off + (y_off * 64);
2018-09-30 20:12:37 +01:00
if (screen_buf[offset] == 1) {
cpu.v[0xF] = 1;
}
2018-09-30 20:12:37 +01:00
screen_buf[offset] ^= 1;
}
}
}
draw_flag = true;
break;
case 0xE000:
2018-09-30 21:25:40 +01:00
ubyte x = cpu.opcode.x;
ubyte key = cpu.v[x];
switch (cpu.opcode & 0x000F) {
case 0x000E: // 0xEX9E Skips the next instruction if the key stored in VX is pressed.
writefln("0xEXA1: skip instruction if VX not pressed: %x", key);
break;
case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed.
writefln("0xEXA1: skip instruction if VX not pressed: %x", key);
2018-10-05 15:16:21 +01:00
pc_target += 2;
break;
default: //unhandled for some reason
writefln("unknown opcode: 0x%x", cpu.opcode);
break;
}
break;
case 0xF000:
switch (cpu.opcode & 0x00FF) {
case 0x0007: // 0xFX07 Sets VX to the value of the delay timer.
2018-09-30 20:42:26 +01:00
cpu.v[cpu.opcode.x] = cpu.delay_timer;
break;
case 0x000A: // 0xFX0A A key press is awaited, and then stored in VX.
break;
case 0x0015: // 0xFX15 Sets the delay timer to VX.
2018-09-30 20:42:26 +01:00
cpu.delay_timer = cpu.v[cpu.opcode.x];
break;
case 0x0018: // 0xFX18 Sets the sound timer to VX.
2018-09-30 20:42:26 +01:00
cpu.sound_timer = cpu.v[cpu.opcode.x];
break;
case 0x001E: // 0xFX1E Adds VX to I.
2018-09-30 21:25:40 +01:00
if (cpu.i + cpu.v[cpu.opcode.x] > 0xFF) {
cpu.v[0xF] = 1;
} else {
cpu.v[0xF] = 0;
}
2018-09-30 20:42:26 +01:00
cpu.i += cpu.v[cpu.opcode.x];
break;
case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX.
2018-10-03 23:36:40 +01:00
immutable ubyte vx = cpu.v[cpu.opcode.x];
2018-10-05 15:16:21 +01:00
// immutable ushort char_addr = 0x200 + (vx * 40); // base of char sprites + value of vx * bits per character
immutable ushort char_addr = vx * 0x05;
cpu.i = char_addr;
break;
// 0xFX33 Stores the Binary-coded decimal representation of VX,
// with the most significant of three digits at the address in I,
// the middle digit at I plus 1, and the least significant digit at I plus 2.
case 0x0033:
2018-10-03 23:36:40 +01:00
immutable ubyte vx = cpu.v[cpu.opcode.x];
ram[cpu.i] = vx / 100;
ram[cpu.i + 1] = (vx / 10) % 10;
ram[cpu.i + 2] = (vx % 100) % 10;
break;
case 0x0055: // 0xFX55 Stores V0 to VX in memory starting at address I.
2018-09-30 21:25:40 +01:00
IndexRegister addr = cpu.i;
foreach (reg; cpu.v) {
ram[addr++] = reg;
}
break;
case 0x0065: // 0xFX65 Fills V0 to VX with values from memory starting at address I.
2018-09-30 21:25:40 +01:00
IndexRegister addr = cpu.i;
foreach (ref reg; cpu.v) {
reg = ram[addr++];
}
break;
2018-09-30 18:58:46 +01:00
default: // unhandled for some reason
writefln("unknown opcode: 0x%x", cpu.opcode);
break;
}
break;
default:
writefln("unknown opcode: 0x%x", cpu.opcode);
}
/* now update pc and timer registers. */
cpu.pc = pc_target;
if (cpu.delay_timer != 0u) {
--cpu.delay_timer;
}
if (cpu.sound_timer != 0u) {
2018-09-30 17:22:59 +01:00
if (cpu.sound_timer == 1u) {
writefln("beep!");
}
--cpu.sound_timer;
}
} // step
void handleEvent(ref SDL_Event ev) {
2018-06-22 23:54:39 +01:00
} // handleEvent
2018-06-22 23:54:39 +01:00
void tick() {
if (run_flag) {
step();
}
} // tick
} // Emulator
void loadLibs() {
DerelictSDL2.load();
2018-06-22 23:54:39 +01:00
DerelictImgui.load();
}
void initLibs() {
SDL_Init(SDL_INIT_VIDEO);
SDL_GL_LoadLibrary(null);
}
void setupImgui() {
}
void main() {
loadLibs();
initLibs();
Emulator emu;
emu.create();
emu.run();
}
struct Emulator {
bool running;
// debug
Chip8Status status;
Window window;
Imgui imgui;
Chip8 emu;
2018-09-30 17:22:59 +01:00
// drawing
PixelBuffer buf;
void create() {
// create window
2018-06-23 01:15:25 +01:00
window.createWindow(960, 768);
// setup imgui
imgui.initialize();
imgui.createDeviceObjects();
// setup debug ui
status.initialize(&this, &emu);
2018-09-30 18:06:30 +01:00
// debug data
2018-09-30 18:58:46 +01:00
emu.screen_buf[0] = 1;
emu.screen_buf[64 - 1] = 1;
emu.screen_buf[64*32 - 64] = 1;
emu.screen_buf[64*32 - 1] = 1;
2018-09-30 17:22:59 +01:00
2018-09-30 20:12:37 +01:00
// set up pixel buffer to poke at
2018-09-30 18:06:30 +01:00
buf.create(emu.screen_buf.ptr, 64, 32);
2018-09-30 17:22:59 +01:00
}
void handleEvents() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
2018-06-22 23:54:39 +01:00
imgui.handleEvent(event);
2018-06-23 01:15:25 +01:00
status.handleEvent(event);
emu.handleEvent(event);
2018-06-22 23:54:39 +01:00
switch (event.type) with (SDL_EventType) {
case SDL_QUIT: {
running = false;
break;
}
default: {
break;
}
}
2018-06-22 23:54:39 +01:00
}
} // handleEvents
void run() {
running = true;
while (running) {
handleEvents();
tick();
draw();
}
}
void tick() {
emu.tick();
}
void draw() {
window.renderClear(0x428bca);
2018-09-30 17:22:59 +01:00
int w, h;
window.windowSize(w, h);
2018-09-30 21:35:23 +01:00
if (emu.draw_flag || emu.reset_flag) {
2018-09-30 18:06:30 +01:00
buf.update(emu.screen_buf);
2018-10-03 23:36:40 +01:00
emu.reset_flag = false;
2018-09-30 20:12:37 +01:00
emu.draw_flag = false;
2018-09-30 18:06:30 +01:00
}
2018-09-30 17:22:59 +01:00
buf.draw(w, h);
2018-09-30 18:58:46 +01:00
imgui.newFrame(window);
status.draw();
imgui.endFrame();
window.renderPresent();
}
void quit() {
running = false;
}
}