module assembler; import std.stdio : writefln; struct Assembler { /* Instruction Glossary: 0x0nnn - SYS addr 0x00E0 - CLS 0x00EE - RET 0x1nnn - JP addr 0x2nnn - CALL addr 0x3xkk - SE Vx, byte 0x4xkk - SNE Vx, byte 0x5xy0 - SE Vx, Vy 0x6xkk - LD Vx, byte 0x7xkk - ADD Vx, byte 0x8xy0 - LD Vx, Vy 0x8xy1 - OR Vx, Vy 0x8xy2 - AND Vx, Vy 0x8xy3 - XOR Vx, Vy 0x8xy4 - ADD Vx, Vy 0x8xy5 - SUB Vx, Vy 0x8xy6 - SHR Vx {, Vy} 0x8xy7 - SUBN Vx, Vy 0x8xyE - SHL Vx {, Vy} 0x9xy0 - SNE Vx, Vy 0xAnnn - LD I, addr 0xBnnn - JP V0, addr 0xCxkk - RND Vx, byte 0xDxyn - DRW Vx, Vy, nibble 0xEx9E - SKP Vx 0xExA1 - SKNP Vx 0xFx07 - LD Vx, DT 0xFx0A - LD Vx, K 0xFx15 - LD DT, Vx 0xFx18 - LD ST, Vx 0xFx1E - ADD I, Vx 0xFx29 - LD F, Vx 0xFx33 - LD B, Vx 0xFx55 - LD [I], Vx 0xFx65 - LD Vx, [I] */ enum OpCode : const (char)* { UNK = "UNK", // UNKNOWN CLS = "CLS", // CLS RET = "RET", // RET CALL = "CALL", // CALL addr ADD = "ADD", // ADD Vx, Vy // ADD Vx, byte // ADD I, Vx SUB = "SUB", // SUB Vx, Vy // SUB Vx, byte SUBN = "SUBN", // SUBN Vx, Vy SE = "SE", // SE Vx, Vy // SE Vx, byte SNE = "SNE", // SNE Vx, byte SKP = "SKP", // SKP Vx SKNP = "SKNP", // SKNP Vx SHL = "SHL", // SHL Vx {, Vy} SHR = "SHR", // SHR Vx {, Vy} XOR = "XOR", // XOR Vx, Vy AND = "AND", // AND Vx, Vy OR = "OR", // OR Vx, Vy LD = "LD", // LD Vx, DT // LD DT, Vx // LD ST, Vx // LD Vx, K // LD [I], Vx // LD Vx, [I] RND = "RND", // RND Vx, byte, JP = "JP", // JP addr // JP V0, addr DRW = "DRW", // DRW Vx, Vy, nibble SYS = "SYS" // SYS addr | probably unused? but still } enum Argument { Nil, Vx, Vy, V0, I, I_addr, DT, ST, K, B, F, beit, nibble, addr, } struct Instruction { @property { ubyte vx() { return (v_ & 0x0F00) >> 8; } ubyte vy() { return (v_ & 0x00F0) >> 4; } ubyte n() { return (v_ & 0x000F); } ubyte nn() { return (v_ & 0x00FF); } ushort nnn() { return (v_ & 0x0FFF); } } private { ushort v_; // raw value OpCode op_; Argument a1_; Argument a2_; Argument a3_; } void* extract(Argument a) { final switch (a) with (Argument) { case Vx: return cast(void*)(vx()); case Vy: return cast(void*)(vy()); case V0: return cast(void*)(0); case I: return cast(void*)("I".ptr); case I_addr: return cast(void*)("[I]".ptr); case DT: return cast(void*)("DT".ptr); case ST: return cast(void*)("ST".ptr); case B: return cast(void*)("B".ptr); case F: return cast(void*)("F".ptr); case K: return cast(void*)("K".ptr); case nibble: return cast(void*)(n()); case beit: return cast(void*)(nn()); case addr: return cast(void*)(nnn()); case Nil: return cast(void*)(0); } } @property OpCode op() { return op_; } void* a1(out Argument a) { a = a1_; return extract(a1_); } void* a2(out Argument a) { a = a2_; return extract(a2_); } void* a3(out Argument a) { a = a3_; return extract(a3_); } @property int args() { int args = 0; args += (a1_ != Argument.Nil); args += (a2_ != Argument.Nil); args += (a3_ != Argument.Nil); return args; } this(ushort v, OpCode op, Argument a) { v_ = v; op_ = op; a1_ = a; } this(ushort v, OpCode op, Argument a1, Argument a2) { v_ = v; op_ = op; a1_ = a1; a2_ = a2; } this(ushort v, OpCode op, Argument a1, Argument a2, Argument a3) { v_ = v; op_ = op; a1_ = a1; a2_ = a2; a3_ = a3; } this(ushort v, OpCode op) { v_ = v; op_ = op; } } import std.array; private { Appender!(ubyte[]) assembled_data; Appender!string dissassembled_data; } @disable this(this); ubyte[] assemble(const char* input) { return []; } // assemble ubyte[] assemble(const char[] input) { return []; } // assemble static Instruction[] disassemble(ubyte[] instructions) { import std.range : chunks; auto instr_output = Appender!(Instruction[])(); foreach (ref ubyte[] i; instructions.chunks(2)) { if (i.length != 2) break; ushort opcode = i[0] << 8 | i[1]; switch (opcode & 0xF000) { case 0x0000: switch (opcode & 0x0FFF) { case 0x00E0: // 0x00E0 Clears the screen. | CLS instr_output ~= Instruction(opcode, OpCode.CLS); break; case 0x00EE: // 0x00EE Returns from a subroutine. | RET instr_output ~= Instruction(opcode, OpCode.RET); break; default: // 0x0NNN Calls RCA 1802 program at address NNN. Not necessary for most ROMs. | SYS addr //assert(0, "0x0NNN RCA 1802 program opcode not implemented!"); // instr_output ~= Instruction(opcode, OpCode.SYS, Argument.addr); instr_output ~= Instruction(opcode, OpCode.UNK); break; } break; case 0x1000: // 0x1NNN Jumps to address NNN. | JP addr instr_output ~= Instruction(opcode, OpCode.JP, Argument.addr); break; case 0x2000: // 0x2NNN Calls subroutine at NNN. | CALL addr instr_output ~= Instruction(opcode, OpCode.CALL, Argument.addr); break; case 0x3000: // 0x3XNN Skips the next instruction if VX equals NN. | SE Vx, byte instr_output ~= Instruction(opcode, OpCode.SE, Argument.Vx, Argument.beit); break; case 0x4000: // 0x4XNN Skips the next instruction if VX doesn't equal NN. | SNE Vx, byte instr_output ~= Instruction(opcode, OpCode.SNE, Argument.Vx, Argument.beit); break; case 0x5000: // 0x5XYO Skips the next instruction if VX equals VY. | SE Vx, Vy instr_output ~= Instruction(opcode, OpCode.SE, Argument.Vx, Argument.Vy); break; case 0x6000: // 0x6XNN Sets VX to NN. | LD Vx, byte instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.beit); break; case 0x7000: // 0x7XNN Adds NN to VX. | ADD Vx, byte instr_output ~= Instruction(opcode, OpCode.ADD, Argument.Vx, Argument.beit); break; case 0x8000: switch (opcode) { case 0x0000: // 0x8XY0 Sets VX to the value of VY. | LD Vx, Vy instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.Vy); break; case 0x0001: // 0x8XY1 Sets VX to VX or VY. | OR Vx, Vy instr_output ~= Instruction(opcode, OpCode.OR, Argument.Vx, Argument.Vy); break; case 0x0002: // 0x8XY2 Sets VX to VX and VY. | AND Vx, Vy instr_output ~= Instruction(opcode, OpCode.AND, Argument.Vx, Argument.Vy); break; case 0x0003: // 0x8XY3 Sets VX to VX xor VY. | XOR Vx, Vy instr_output ~= Instruction(opcode, OpCode.XOR, Argument.Vx, Argument.Vy); 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. | ADD Vx, Vy instr_output ~= Instruction(opcode, OpCode.ADD, Argument.Vx, Argument.Vy); 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. | SUB Vx, Vy instr_output ~= Instruction(opcode, OpCode.SUB, Argument.Vx, Argument.Vy); 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. | SHR Vx {, Vy} instr_output ~= Instruction(opcode, OpCode.SHR, Argument.Vx); // FIXME? what about Vy here actually? 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. | SUBN Vx, Vy instr_output ~= Instruction(opcode, OpCode.SUBN, Argument.Vx, Argument.Vy); 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. | SHL Vx {, Vy} instr_output ~= Instruction(opcode, OpCode.SHL, Argument.Vx); // FIXME? what about Vy here actually? break; default: // unhandled for some reason instr_output ~= Instruction(opcode, OpCode.UNK); writefln("unknown opcode: 0x%x", opcode); break; } break; case 0x9000: // 0x9XYO Skips the next instruction if VX doesn't equal VY. | SNE Vx, Vy instr_output ~= Instruction(opcode, OpCode.SNE, Argument.Vx, Argument.Vy); break; case 0xA000: // 0xANNN Sets I to the address NNN. | LD I, addr instr_output ~= Instruction(opcode, OpCode.LD, Argument.I, Argument.addr); break; case 0xB000: // 0xBNNN Jumps to the address NNN plus V0. | JP V0, addr instr_output ~= Instruction(opcode, OpCode.JP, Argument.V0, Argument.addr); break; case 0xC000: // 0xCXNN Sets VX to the result of a bitwise and operation on a random number and NN. | RND Vx, byte instr_output ~= Instruction(opcode, OpCode.RND, Argument.Vx, Argument.beit); break; // 0xDXYN | DRW Vx, Vy, nibble // 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: instr_output ~= Instruction(opcode, OpCode.DRW, Argument.Vx, Argument.Vy, Argument.beit); break; case 0xE000: switch (opcode & 0x000F) { case 0x000E: // 0xEX9E Skips the next instruction if the key stored in VX is pressed. | SKP Vx instr_output ~= Instruction(opcode, OpCode.SKP, Argument.Vx); break; case 0x0001: // 0xEXA1 Skips the next instruction if the key stored in VX isn't pressed. | SKNP Vx instr_output ~= Instruction(opcode, OpCode.SKNP, Argument.Vx); break; default: // unhandled for some reason instr_output ~= Instruction(opcode, OpCode.UNK); writefln("unknown opcode: 0x%x", opcode); break; } break; case 0xF000: switch (opcode & 0x00FF) { case 0x0007: // 0xFX07 Sets VX to the value of the delay timer. | LD Vx, DT instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.DT); break; case 0x000A: // 0xFX0A A key press is awaited, and then stored in VX. | LD Vx, K instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.K); break; case 0x0015: // 0xFX15 Sets the delay timer to VX. | LD DT, Vx instr_output ~= Instruction(opcode, OpCode.LD, Argument.DT, Argument.Vx); break; case 0x0018: // 0xFX18 Sets the sound timer to VX. | LD ST, Vx instr_output ~= Instruction(opcode, OpCode.LD, Argument.ST, Argument.Vx); break; case 0x001E: // 0xFX1E Adds VX to I. | ADD I, Vx instr_output ~= Instruction(opcode, OpCode.ADD, Argument.I, Argument.DT); break; case 0x0029: // 0xFX29 Sets I to the location of the sprite for the character in VX. | LD F, Vx instr_output ~= Instruction(opcode, OpCode.LD, Argument.F, Argument.DT); 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: // 0xFX33 ??? FIXME? | LD B, Vx instr_output ~= Instruction(opcode, OpCode.LD, Argument.B, Argument.Vx); break; case 0x0055: // 0xFX55 Stores V0 to VX in memory starting at address I. | LD [I], Vx instr_output ~= Instruction(opcode, OpCode.LD, Argument.I_addr, Argument.DT); break; case 0x0065: // 0xFX65 Fills V0 to VX with values from memory starting at address I. | LD Vx, [I] instr_output ~= Instruction(opcode, OpCode.LD, Argument.Vx, Argument.I_addr); break; default: // unhandled for some reason instr_output ~= Instruction(opcode, OpCode.UNK); writefln("unknown opcode: 0x%x", opcode); break; } break; default: instr_output ~= Instruction(opcode, OpCode.UNK); writefln("unknown opcode: 0x%x", opcode); } } return instr_output.data; } // dissassemble } // Assembler