Initial commit
This commit is contained in:
389
libs/reader.hpp
Normal file
389
libs/reader.hpp
Normal file
@@ -0,0 +1,389 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <sys/types.h>
|
||||
|
||||
struct byte_reg {
|
||||
uint8_t reg;
|
||||
uint16_t byte;
|
||||
};
|
||||
|
||||
struct reg_reg {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
};
|
||||
|
||||
struct reg_reg_nibble {
|
||||
uint8_t x;
|
||||
uint8_t y;
|
||||
uint8_t nibble;
|
||||
};
|
||||
|
||||
// this definitely wont be confusing or difficult to implement at all, I am
|
||||
// perfect at writing good code and documentation and I would never write
|
||||
// something that is stupidly thought out or overly complicated
|
||||
enum instruction {
|
||||
// operand is a pointer to a uint8_t
|
||||
EXIT = 0,
|
||||
// operand is a pointer to a uint16_t
|
||||
SYS,
|
||||
// no operand
|
||||
CLS,
|
||||
// no operand
|
||||
RET,
|
||||
// operand is a pointer to a uint16_t
|
||||
JP,
|
||||
// operand is a pointer to a uint16_t
|
||||
CALL,
|
||||
// operand is a pointer to byte_reg
|
||||
SKIP_INSTRUCTION_BYTE,
|
||||
// operand is a pointer to byte_reg
|
||||
SKIP_INSTRUCTION_NE_BYTE,
|
||||
// operand is a pointer to reg_reg
|
||||
SKIP_INSTRUCTION_REG,
|
||||
// operand is a pointer to reg_byte
|
||||
LOAD_BYTE,
|
||||
// operand is a pointer to reg_byte
|
||||
ADD_BYTE,
|
||||
// operand is a pointer to reg_reg
|
||||
LOAD_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
OR_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
AND_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
XOR_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
ADD_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
SUB_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
SHR_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
SUBN_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
SHL_REG,
|
||||
// operand is a pointer to reg_reg
|
||||
SKIP_INSTRUCTION_NE_REG,
|
||||
// operand is a pointer to a uint16_t
|
||||
LOAD_I_BYTE,
|
||||
// operand is a pointer to a uint16_t
|
||||
JP_V0_BYTE,
|
||||
// operand is a pointer to a reg_byte
|
||||
RND,
|
||||
// operand is a pointer to a reg_reg_nibble
|
||||
DRW,
|
||||
// operand is a pointer to a uint8_t
|
||||
SKIP_PRESSED_REG,
|
||||
// operand is a pointer to a uint8_t
|
||||
SKIP_NOT_PRESSED_REG,
|
||||
// operand is a pointer to a uint8_t
|
||||
LD_REG_DT,
|
||||
// operand is a pointer to a uint8_t
|
||||
LD_REG_K,
|
||||
// operand is a pointer to a uint8_t
|
||||
LD_DT_REG,
|
||||
// operand is a pointer to a uint8_t
|
||||
LD_ST_REG,
|
||||
// operand is a pointer to a uint8_t
|
||||
ADD_I_REG,
|
||||
// operand is a pointer to a uint8_t
|
||||
LD_F_REG,
|
||||
// operand is a pointer to a uint8_t
|
||||
LD_B_REG,
|
||||
// operand is a pointer to a uint8_t
|
||||
LD_PTR_I_REG,
|
||||
// operand is a pointer to a uint8_t
|
||||
LD_REG_PTR_I,
|
||||
UNKNOWN_INSTRUCTION,
|
||||
};
|
||||
|
||||
class Bytecode {
|
||||
public:
|
||||
enum instruction instruction_type;
|
||||
// should be interpreted by a reader depending on the instruction type
|
||||
union {
|
||||
uint8_t byte;
|
||||
uint16_t word;
|
||||
struct byte_reg byte_reg;
|
||||
struct reg_reg reg_reg;
|
||||
struct reg_reg_nibble reg_reg_nibble;
|
||||
} operand;
|
||||
};
|
||||
|
||||
inline Bytecode parse(uint16_t opcode) {
|
||||
struct Bytecode bytecode;
|
||||
|
||||
switch (opcode & 0xF000) {
|
||||
case 0x0000: {
|
||||
if ((opcode & 0x00F0) == 0x0010) {
|
||||
// EXIT N 0x001N
|
||||
// Specific to emulators, not part of the original chip-8
|
||||
bytecode.instruction_type = EXIT;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (opcode & 0x00FF) {
|
||||
case 0x00E0: {
|
||||
// CLS 0x00E0
|
||||
// clears the screen
|
||||
bytecode.instruction_type = CLS;
|
||||
break;
|
||||
}
|
||||
case 0x00EE: {
|
||||
// RET 0x00EE
|
||||
bytecode.instruction_type = RET;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// SYS NNN
|
||||
//? NOTE: This is an outdated opcode, but it's still
|
||||
//? important for completeness. It's not clear what the
|
||||
//? difference is between it and the JMP NNN 0x1NNN opcode.
|
||||
bytecode.instruction_type = SYS;
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 0x1000: {
|
||||
bytecode.instruction_type = JP;
|
||||
break;
|
||||
}
|
||||
case 0x2000: {
|
||||
bytecode.instruction_type = CALL;
|
||||
break;
|
||||
}
|
||||
case 0x3000: {
|
||||
bytecode.instruction_type = SKIP_INSTRUCTION_BYTE;
|
||||
break;
|
||||
}
|
||||
case 0x4000: {
|
||||
bytecode.instruction_type = SKIP_INSTRUCTION_NE_BYTE;
|
||||
break;
|
||||
}
|
||||
case 0x5000: {
|
||||
bytecode.instruction_type = SKIP_INSTRUCTION_REG;
|
||||
break;
|
||||
}
|
||||
case 0x6000: {
|
||||
bytecode.instruction_type = LOAD_BYTE;
|
||||
break;
|
||||
}
|
||||
case 0x700: {
|
||||
bytecode.instruction_type = ADD_BYTE;
|
||||
break;
|
||||
}
|
||||
case 0x8000: {
|
||||
switch (opcode & 0x000F) {
|
||||
case 0x0000: {
|
||||
bytecode.instruction_type = LOAD_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0001: {
|
||||
bytecode.instruction_type = OR_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0002: {
|
||||
bytecode.instruction_type = AND_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0003: {
|
||||
bytecode.instruction_type = XOR_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0004: {
|
||||
bytecode.instruction_type = ADD_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0005: {
|
||||
bytecode.instruction_type = SUB_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0006: {
|
||||
// Set VX equal to VX bitshifted right 1. VF is set to the least
|
||||
// significant bit of VX prior to the shift. Originally this opcode
|
||||
// meant set VX equal to VY bitshifted right 1 but emulators and
|
||||
// software seem to ignore VY now. Note: This instruction was
|
||||
// originally undocumented but functional due to how the 8XXX
|
||||
// instructions were implemented on teh COSMAC VIP.
|
||||
bytecode.instruction_type = SHR_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0007: {
|
||||
bytecode.instruction_type = SUBN_REG;
|
||||
break;
|
||||
}
|
||||
case 0x000E: {
|
||||
bytecode.instruction_type = SHL_REG;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
bytecode.instruction_type = UNKNOWN_INSTRUCTION;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x9000: {
|
||||
bytecode.instruction_type = SKIP_INSTRUCTION_NE_REG;
|
||||
break;
|
||||
}
|
||||
case 0xA000: {
|
||||
bytecode.instruction_type = LOAD_I_BYTE;
|
||||
break;
|
||||
}
|
||||
case 0xB000: {
|
||||
bytecode.instruction_type = JP_V0_BYTE;
|
||||
break;
|
||||
}
|
||||
case 0xC000: {
|
||||
bytecode.instruction_type = RND;
|
||||
break;
|
||||
}
|
||||
case 0xD000: {
|
||||
bytecode.instruction_type = DRW;
|
||||
break;
|
||||
}
|
||||
case 0xE000: {
|
||||
switch (opcode & 0x00FF) {
|
||||
case 0x009E: {
|
||||
bytecode.instruction_type = SKIP_PRESSED_REG;
|
||||
break;
|
||||
}
|
||||
case 0x00A1: {
|
||||
bytecode.instruction_type = SKIP_NOT_PRESSED_REG;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
bytecode.instruction_type = UNKNOWN_INSTRUCTION;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xF000: {
|
||||
switch (opcode & 0x00FF) {
|
||||
case 0x0007: {
|
||||
bytecode.instruction_type = LD_REG_DT;
|
||||
break;
|
||||
}
|
||||
case 0x000A: {
|
||||
bytecode.instruction_type = LD_REG_K;
|
||||
break;
|
||||
}
|
||||
case 0x0015: {
|
||||
bytecode.instruction_type = LD_DT_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0018: {
|
||||
bytecode.instruction_type = LD_ST_REG;
|
||||
break;
|
||||
}
|
||||
case 0x001E: {
|
||||
bytecode.instruction_type = ADD_I_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0029: {
|
||||
bytecode.instruction_type = LD_F_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0033: {
|
||||
bytecode.instruction_type = LD_B_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0055: {
|
||||
bytecode.instruction_type = LD_PTR_I_REG;
|
||||
break;
|
||||
}
|
||||
case 0x0065: {
|
||||
bytecode.instruction_type = LD_REG_PTR_I;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
bytecode.instruction_type = UNKNOWN_INSTRUCTION;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
bytecode.instruction_type = UNKNOWN_INSTRUCTION;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (bytecode.instruction_type) {
|
||||
case UNKNOWN_INSTRUCTION:
|
||||
case RET:
|
||||
case CLS: {
|
||||
// no operand
|
||||
break;
|
||||
}
|
||||
case EXIT: {
|
||||
bytecode.operand.byte = opcode & 0x000F;
|
||||
break;
|
||||
}
|
||||
case SKIP_PRESSED_REG:
|
||||
case SKIP_NOT_PRESSED_REG:
|
||||
case LD_REG_DT:
|
||||
case LD_REG_K:
|
||||
case LD_DT_REG:
|
||||
case LD_ST_REG:
|
||||
case ADD_I_REG:
|
||||
case LD_F_REG:
|
||||
case LD_B_REG:
|
||||
case LD_PTR_I_REG:
|
||||
case LD_REG_PTR_I: {
|
||||
bytecode.operand.byte = (uint8_t)((opcode & 0x0F00) >> 8);
|
||||
break;
|
||||
}
|
||||
case SYS:
|
||||
case JP:
|
||||
case CALL:
|
||||
case LOAD_I_BYTE:
|
||||
case JP_V0_BYTE: {
|
||||
bytecode.operand.word = (uint16_t)(opcode & 0x0FFF);
|
||||
break;
|
||||
}
|
||||
case SKIP_INSTRUCTION_BYTE:
|
||||
case SKIP_INSTRUCTION_NE_BYTE:
|
||||
case LOAD_BYTE:
|
||||
case ADD_BYTE:
|
||||
case RND: {
|
||||
bytecode.operand.byte_reg = (struct byte_reg){
|
||||
.reg = (uint8_t)((opcode & 0x0F00) >> 8),
|
||||
.byte = (uint16_t)(opcode & 0x00FF),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case SKIP_INSTRUCTION_REG:
|
||||
case LOAD_REG:
|
||||
case OR_REG:
|
||||
case AND_REG:
|
||||
case XOR_REG:
|
||||
case ADD_REG:
|
||||
case SUB_REG:
|
||||
case SHR_REG:
|
||||
case SUBN_REG:
|
||||
case SHL_REG:
|
||||
case SKIP_INSTRUCTION_NE_REG: {
|
||||
bytecode.operand.reg_reg = (struct reg_reg){
|
||||
.x = (uint8_t)((opcode & 0x0F00) >> 8),
|
||||
.y = (uint8_t)((opcode & 0x00F0) >> 4),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case DRW: {
|
||||
bytecode.operand.reg_reg_nibble = (struct reg_reg_nibble){
|
||||
.x = (uint8_t)((opcode & 0x0F00) >> 8),
|
||||
.y = (uint8_t)((opcode & 0x00F0) >> 4),
|
||||
.nibble = (uint8_t)(opcode & 0x000F),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bytecode;
|
||||
}
|
||||
Reference in New Issue
Block a user