initialise a memory structure or smth
Signed-off-by: Amneesh Singh <natto@weirdnatto.in>
This commit is contained in:
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
@@ -1,15 +1,74 @@
|
||||
#include "emulator.hh"
|
||||
#include "memory.hh"
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
int
|
||||
main(int argc, const char* argv[]) {
|
||||
if (argc != 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <file>" << std::endl;
|
||||
return 1;
|
||||
auto usage = [argv]() {
|
||||
std::cerr << "Usage: " << argv[0] << " <file> [-b <bios>]" << std::endl;
|
||||
std::exit(EXIT_FAILURE);
|
||||
};
|
||||
|
||||
if (argc < 2)
|
||||
usage();
|
||||
|
||||
std::string rom_file, bios_file = "gba_bios.bin";
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
|
||||
if (arg == "-b") {
|
||||
if (++i < argc)
|
||||
bios_file = argv[i];
|
||||
else
|
||||
usage();
|
||||
} else {
|
||||
rom_file = arg;
|
||||
}
|
||||
}
|
||||
|
||||
if (rom_file.empty())
|
||||
usage();
|
||||
|
||||
try {
|
||||
emulator::run(argv[1]);
|
||||
std::ifstream ifile(rom_file, std::ios::in | std::ios::binary);
|
||||
std::vector<uint8_t> rom;
|
||||
std::array<uint8_t, Memory::BIOS_SIZE> bios;
|
||||
std::streampos bios_size;
|
||||
|
||||
if (!ifile.is_open()) {
|
||||
throw std::ios::failure("File not found", std::error_code());
|
||||
}
|
||||
|
||||
rom.assign(std::istreambuf_iterator<char>(ifile),
|
||||
std::istreambuf_iterator<char>());
|
||||
|
||||
ifile.close();
|
||||
|
||||
ifile.open(bios_file, std::ios::in | std::ios::binary);
|
||||
|
||||
if (!ifile.is_open()) {
|
||||
throw std::ios::failure("BIOS file not found", std::error_code());
|
||||
}
|
||||
|
||||
ifile.seekg(0, std::ios::end);
|
||||
bios_size = ifile.tellg();
|
||||
|
||||
if (bios_size != Memory::BIOS_SIZE) {
|
||||
throw std::ios::failure("BIOS file has invalid size",
|
||||
std::error_code());
|
||||
}
|
||||
|
||||
ifile.seekg(0, std::ios::beg);
|
||||
|
||||
ifile.read(reinterpret_cast<char*>(bios.data()), bios.size());
|
||||
|
||||
ifile.close();
|
||||
|
||||
Memory memory(std::move(bios), std::move(rom));
|
||||
} catch (const std::exception& e) {
|
||||
std::cerr << "Exception: " << e.what() << std::endl;
|
||||
return 1;
|
||||
|
@@ -1,16 +0,0 @@
|
||||
#ifndef EMULATOR_HH
|
||||
#define EMULATOR_HH
|
||||
|
||||
// Why do I have a public API? We will know that in the future
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace emulator {
|
||||
void
|
||||
run(std::string filepath);
|
||||
|
||||
void
|
||||
run(std::ifstream& ifile);
|
||||
}
|
||||
|
||||
#endif /* EMULATOR_HH */
|
1
include/header.hh
Symbolic link
1
include/header.hh
Symbolic link
@@ -0,0 +1 @@
|
||||
../src/header.hh
|
1
include/memory.hh
Symbolic link
1
include/memory.hh
Symbolic link
@@ -0,0 +1 @@
|
||||
../src/memory.hh
|
@@ -1,5 +1,6 @@
|
||||
headers = files(
|
||||
'emulator.hh'
|
||||
'memory.hh',
|
||||
'header.hh'
|
||||
)
|
||||
|
||||
install_headers(headers, subdir: meson.project_name(), preserve_path: true)
|
4
src/bus.cc
Normal file
4
src/bus.cc
Normal file
@@ -0,0 +1,4 @@
|
||||
#include "bus.hh"
|
||||
|
||||
Bus::Bus(Memory&& memory)
|
||||
: memory(std::move(memory)) {}
|
13
src/bus.hh
13
src/bus.hh
@@ -1,14 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
#include "memory.hh"
|
||||
|
||||
class Bus {
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
public:
|
||||
Bus() = default;
|
||||
Bus(std::istream& ifile)
|
||||
: data(std::istreambuf_iterator<char>(ifile),
|
||||
std::istreambuf_iterator<char>()) {}
|
||||
Bus(Memory&& memory);
|
||||
|
||||
private:
|
||||
Memory memory;
|
||||
};
|
||||
|
@@ -1,21 +1,3 @@
|
||||
enum class ArmInstructionFormat {
|
||||
DataProcessingAndFsrTransfer,
|
||||
Multiply,
|
||||
MultiplyLong,
|
||||
SingleDataSwap,
|
||||
BranchAndExchange,
|
||||
HalfwordDataTransferRegisterOffset,
|
||||
HalfwordDataTransferImmediateOffset,
|
||||
SingleDataTransfer,
|
||||
Undefined,
|
||||
BlockDataTransfer,
|
||||
Branch,
|
||||
CoprocessorDataTransfer,
|
||||
CoprocessorDataOperation,
|
||||
CoprocessorRegisterTransfer,
|
||||
SoftwareInterrupt
|
||||
};
|
||||
|
||||
enum class Condition {
|
||||
EQ = 0b0000,
|
||||
NE = 0b0001,
|
||||
@@ -53,7 +35,7 @@ enum class OpCode {
|
||||
MVN = 0b1111
|
||||
};
|
||||
|
||||
enum class Shift {
|
||||
enum class ShiftType {
|
||||
LSL = 0b00,
|
||||
LSR = 0b01,
|
||||
ASR = 0b10,
|
||||
|
@@ -3,6 +3,19 @@
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
|
||||
Cpu::Cpu(Bus bus)
|
||||
: gpr(0)
|
||||
, cpsr(0)
|
||||
, spsr(0)
|
||||
, bus(bus)
|
||||
, gpr_banked({ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 } })
|
||||
, spsr_banked({ 0, 0, 0, 0, 0 }) {
|
||||
cpsr.set_mode(Mode::System);
|
||||
cpsr.set_irq_disabled(true);
|
||||
cpsr.set_fiq_disabled(true);
|
||||
cpsr.set_state(State::Arm);
|
||||
}
|
||||
|
||||
/* change modes */
|
||||
void
|
||||
Cpu::chg_mode(Mode from, Mode to) {
|
||||
@@ -92,13 +105,13 @@ Cpu::chg_mode(Mode from, Mode to) {
|
||||
|
||||
// set register
|
||||
inline uint32_t&
|
||||
Cpu::operator[](size_t idx) {
|
||||
Cpu::operator[](uint8_t idx) {
|
||||
// avoid unneeded complexity like index checks
|
||||
return gpr[idx];
|
||||
}
|
||||
|
||||
// get register
|
||||
inline const uint32_t&
|
||||
Cpu::operator[](size_t idx) const {
|
||||
Cpu::operator[](uint8_t idx) const {
|
||||
return gpr[idx];
|
||||
}
|
||||
|
@@ -7,8 +7,11 @@
|
||||
|
||||
using std::size_t;
|
||||
|
||||
static constexpr size_t GPR_VISIBLE_COUNT = 16;
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(Bus bus);
|
||||
|
||||
private:
|
||||
static constexpr size_t GPR_FIQ_BANKED_FIRST = 8;
|
||||
static constexpr size_t GPR_FIQ_BANKED_COUNT = 7;
|
||||
|
||||
@@ -27,51 +30,34 @@ static constexpr size_t GPR_UND_BANKED_COUNT = 2;
|
||||
static constexpr size_t GPR_SYS_USR_BANKED_FIRST = 8;
|
||||
static constexpr size_t GPR_SYS_USR_BANKED_COUNT = 7;
|
||||
|
||||
struct _GprBanked {
|
||||
static constexpr size_t GPR_VISIBLE_COUNT = 16;
|
||||
|
||||
uint32_t gpr[GPR_VISIBLE_COUNT]; // general purpose registers
|
||||
Psr cpsr; // current program status register
|
||||
Psr spsr; // status program status register
|
||||
Bus bus;
|
||||
|
||||
struct {
|
||||
uint32_t fiq[GPR_FIQ_BANKED_COUNT];
|
||||
uint32_t svc[GPR_SVC_BANKED_COUNT];
|
||||
uint32_t abt[GPR_ABT_BANKED_COUNT];
|
||||
uint32_t irq[GPR_IRQ_BANKED_COUNT];
|
||||
uint32_t und[GPR_UND_BANKED_COUNT];
|
||||
|
||||
/* visible registers before the mode switch */
|
||||
// visible registers before the mode switch
|
||||
uint32_t old[GPR_SYS_USR_BANKED_COUNT];
|
||||
};
|
||||
typedef struct _GprBanked GprBanked;
|
||||
} gpr_banked; // banked general purpose registers
|
||||
|
||||
struct _SpsrBanked {
|
||||
struct {
|
||||
Psr fiq;
|
||||
Psr svc;
|
||||
Psr abt;
|
||||
Psr irq;
|
||||
Psr und;
|
||||
};
|
||||
typedef struct _SpsrBanked SpsrBanked;
|
||||
|
||||
class Cpu {
|
||||
uint32_t gpr[GPR_VISIBLE_COUNT]; // general purpose registers
|
||||
GprBanked gpr_banked; // banked general purpose registers
|
||||
SpsrBanked spsr_banked; // banked saved program status registers
|
||||
Psr cpsr; // current program status register
|
||||
Psr spsr; // status program status register
|
||||
Bus bus;
|
||||
} spsr_banked; // banked saved program status registers
|
||||
|
||||
void chg_mode(Mode from, Mode to);
|
||||
|
||||
uint32_t& operator[](size_t idx);
|
||||
const uint32_t& operator[](size_t idx) const;
|
||||
|
||||
public:
|
||||
Cpu(Bus bus)
|
||||
: gpr(0)
|
||||
, gpr_banked({ { 0 }, { 0 }, { 0 }, { 0 }, { 0 }, { 0 } })
|
||||
, spsr_banked({ 0, 0, 0, 0, 0 })
|
||||
, cpsr(0)
|
||||
, spsr(0)
|
||||
, bus(bus) {
|
||||
cpsr.set_mode(Mode::System);
|
||||
cpsr.set_irq_disabled(true);
|
||||
cpsr.set_fiq_disabled(true);
|
||||
cpsr.set_state(State::Arm);
|
||||
}
|
||||
uint32_t& operator[](uint8_t idx);
|
||||
const uint32_t& operator[](uint8_t idx) const;
|
||||
};
|
||||
|
@@ -1,5 +1,6 @@
|
||||
lib_sources += files(
|
||||
'cpu.cc'
|
||||
'cpu.cc',
|
||||
'psr.cc'
|
||||
)
|
||||
|
||||
subdir('arm')
|
49
src/cpu/psr.cc
Normal file
49
src/cpu/psr.cc
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "psr.hh"
|
||||
#include "util/bits.hh"
|
||||
|
||||
Psr::Psr(uint32_t raw) {
|
||||
psr = raw & PSR_CLEAR_RESERVED;
|
||||
}
|
||||
|
||||
Mode
|
||||
Psr::mode() const {
|
||||
return static_cast<Mode>(psr & ~PSR_CLEAR_MODE);
|
||||
}
|
||||
|
||||
void
|
||||
Psr::set_mode(Mode mode) {
|
||||
psr &= PSR_CLEAR_MODE;
|
||||
psr |= static_cast<uint32_t>(mode);
|
||||
}
|
||||
|
||||
bool
|
||||
Psr::state() const {
|
||||
return get_nth_bit(psr, 5);
|
||||
}
|
||||
|
||||
void
|
||||
Psr::set_state(State state) {
|
||||
chg_nth_bit(psr, 5, static_cast<bool>(state));
|
||||
}
|
||||
|
||||
#define GET_SET_NTH_BIT_FUNCTIONS(name, n) \
|
||||
bool Psr::name() const { \
|
||||
return get_nth_bit(psr, n); \
|
||||
} \
|
||||
void Psr::set_##name(bool val) { \
|
||||
chg_nth_bit(psr, n, val); \
|
||||
}
|
||||
|
||||
GET_SET_NTH_BIT_FUNCTIONS(fiq_disabled, 6)
|
||||
|
||||
GET_SET_NTH_BIT_FUNCTIONS(irq_disabled, 7)
|
||||
|
||||
GET_SET_NTH_BIT_FUNCTIONS(v, 28);
|
||||
|
||||
GET_SET_NTH_BIT_FUNCTIONS(c, 29);
|
||||
|
||||
GET_SET_NTH_BIT_FUNCTIONS(z, 30);
|
||||
|
||||
GET_SET_NTH_BIT_FUNCTIONS(n, 31);
|
||||
|
||||
#undef GET_SET_NTH_BIT_FUNCTIONS
|
@@ -1,55 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "bits.hh"
|
||||
#include "util/bits.hh"
|
||||
#include "utility.hh"
|
||||
#include <cstdint>
|
||||
|
||||
static constexpr uint32_t PSR_CLEAR_RESERVED = 0xf00000ff;
|
||||
static constexpr uint32_t PSR_CLEAR_MODE = 0x0b00000;
|
||||
|
||||
class Psr {
|
||||
uint32_t psr;
|
||||
|
||||
public:
|
||||
// clear the reserved bits i.e, [8:27]
|
||||
Psr(uint32_t raw) { psr = raw & PSR_CLEAR_RESERVED; }
|
||||
Psr(uint32_t raw);
|
||||
|
||||
// Mode : [4:0]
|
||||
Mode mode() const { return static_cast<Mode>(psr & ~PSR_CLEAR_MODE); }
|
||||
void set_mode(Mode mode) {
|
||||
psr &= PSR_CLEAR_MODE;
|
||||
psr |= static_cast<uint32_t>(mode);
|
||||
}
|
||||
Mode mode() const;
|
||||
void set_mode(Mode mode);
|
||||
|
||||
// State : [5]
|
||||
bool state() const { return get_nth_bit(psr, 5); }
|
||||
void set_state(State state) {
|
||||
chg_nth_bit(psr, 5, static_cast<bool>(state));
|
||||
}
|
||||
bool state() const;
|
||||
void set_state(State state);
|
||||
|
||||
#define GET_SET_NTH_BIT_FUNCTIONS(name, n) \
|
||||
bool name() const { return get_nth_bit(psr, n); } \
|
||||
void set_##name(bool val) { chg_nth_bit(psr, n, val); }
|
||||
#define GET_SET_NTH_BIT_FUNCTIONS(name) \
|
||||
bool name() const; \
|
||||
void set_##name(bool val);
|
||||
|
||||
// FIQ disable : [6]
|
||||
GET_SET_NTH_BIT_FUNCTIONS(fiq_disabled, 6)
|
||||
GET_SET_NTH_BIT_FUNCTIONS(fiq_disabled)
|
||||
|
||||
// IRQ disable : [7]
|
||||
GET_SET_NTH_BIT_FUNCTIONS(irq_disabled, 7)
|
||||
GET_SET_NTH_BIT_FUNCTIONS(irq_disabled)
|
||||
|
||||
// Reserved bits : [27:8]
|
||||
|
||||
// Overflow flag : [28]
|
||||
GET_SET_NTH_BIT_FUNCTIONS(v, 28);
|
||||
GET_SET_NTH_BIT_FUNCTIONS(v)
|
||||
|
||||
// Carry flag : [29]
|
||||
GET_SET_NTH_BIT_FUNCTIONS(c, 29);
|
||||
GET_SET_NTH_BIT_FUNCTIONS(c)
|
||||
|
||||
// Zero flag : [30]
|
||||
GET_SET_NTH_BIT_FUNCTIONS(z, 30);
|
||||
GET_SET_NTH_BIT_FUNCTIONS(z)
|
||||
|
||||
// Negative flag : [30]
|
||||
GET_SET_NTH_BIT_FUNCTIONS(n, 31);
|
||||
GET_SET_NTH_BIT_FUNCTIONS(n)
|
||||
|
||||
#undef GET_SET_NTH_BIT_FUNCTIONS
|
||||
|
||||
private:
|
||||
static constexpr uint32_t PSR_CLEAR_RESERVED = 0xf00000ff;
|
||||
static constexpr uint32_t PSR_CLEAR_MODE = 0x0b00000;
|
||||
|
||||
uint32_t psr;
|
||||
};
|
||||
|
@@ -1,24 +0,0 @@
|
||||
#include "emulator.hh"
|
||||
#include "bus.hh"
|
||||
#include "cpu/cpu.hh"
|
||||
#include <fstream>
|
||||
|
||||
namespace emulator {
|
||||
void
|
||||
run(std::ifstream& ifile) {
|
||||
Bus bus(ifile);
|
||||
Cpu cpu(bus);
|
||||
}
|
||||
|
||||
void
|
||||
run(std::string filepath) {
|
||||
std::ifstream ifile(filepath, std::ios::in | std::ios::binary);
|
||||
|
||||
if (!ifile.is_open()) {
|
||||
throw std::ios::failure("No such file exists", std::error_code());
|
||||
}
|
||||
|
||||
run(ifile);
|
||||
ifile.close();
|
||||
}
|
||||
}
|
44
src/header.hh
Normal file
44
src/header.hh
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
typedef struct {
|
||||
enum class UniqueCode {
|
||||
Old, // old games
|
||||
New, // new games
|
||||
Newer, // unused (newer games)
|
||||
Famicom, // NES
|
||||
YoshiKoro, // acceleration sensor
|
||||
Ereader, // dot code scanner
|
||||
Warioware, // rumble and z-axis gyro
|
||||
Boktai, // RTC and solar sensor
|
||||
DrillDozer, // rumble
|
||||
};
|
||||
|
||||
enum class I18n {
|
||||
Japan,
|
||||
Europe,
|
||||
French,
|
||||
Spanish,
|
||||
Usa,
|
||||
German,
|
||||
Italian
|
||||
};
|
||||
|
||||
enum class BootMode {
|
||||
Joybus,
|
||||
Normal,
|
||||
Multiplay
|
||||
};
|
||||
|
||||
uint32_t entrypoint;
|
||||
char title[12];
|
||||
UniqueCode unique_code;
|
||||
char title_code[2];
|
||||
I18n i18n;
|
||||
uint8_t version;
|
||||
BootMode multiboot;
|
||||
uint32_t multiboot_entrypoint;
|
||||
uint8_t slave_id;
|
||||
} Header;
|
223
src/memory.cc
Normal file
223
src/memory.cc
Normal file
@@ -0,0 +1,223 @@
|
||||
#include "memory.hh"
|
||||
#include "header.hh"
|
||||
#include "util/log.hh"
|
||||
#include "util/utils.hh"
|
||||
|
||||
Memory::Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
|
||||
std::vector<uint8_t>&& rom) noexcept
|
||||
: bios(std::move(bios))
|
||||
, board_wram(0)
|
||||
, chip_wram(0)
|
||||
, palette_ram(0)
|
||||
, vram(0)
|
||||
, oam_obj_attr(0)
|
||||
, rom(std::move(rom)) {
|
||||
std::string bios_hash = crypto::sha256(bios.data(), bios.size());
|
||||
std::string expected_hash =
|
||||
"fd2547724b505f487e6dcb29ec2ecff3af35a841a77ab2e85fd87350abd36570";
|
||||
|
||||
if (bios_hash != expected_hash) {
|
||||
log_warn("BIOS hash failed to match, run at your own risk",
|
||||
"\nExpected : ",
|
||||
expected_hash,
|
||||
"\nGot : ",
|
||||
bios_hash);
|
||||
}
|
||||
|
||||
parse_header();
|
||||
|
||||
log("Memory successfully initialised");
|
||||
};
|
||||
|
||||
#define MATCHES(area) address >= area##_START&& address <= area##_END
|
||||
|
||||
uint8_t
|
||||
Memory::read(size_t address) const {
|
||||
if (MATCHES(BIOS)) {
|
||||
return bios[address];
|
||||
} else if (MATCHES(BOARD_WRAM)) {
|
||||
return board_wram[address - BOARD_WRAM_START];
|
||||
} else if (MATCHES(CHIP_WRAM)) {
|
||||
return chip_wram[address - CHIP_WRAM_START];
|
||||
} else if (MATCHES(PALETTE_RAM)) {
|
||||
return palette_ram[address - PALETTE_RAM_START];
|
||||
} else if (MATCHES(VRAM)) {
|
||||
return vram[address - VRAM_START];
|
||||
} else if (MATCHES(OAM_OBJ_ATTR)) {
|
||||
return oam_obj_attr[address - OAM_OBJ_ATTR_START];
|
||||
} else if (MATCHES(ROM_0)) {
|
||||
return rom[address - ROM_0_START];
|
||||
} else if (MATCHES(ROM_1)) {
|
||||
return rom[address - ROM_1_START];
|
||||
} else if (MATCHES(ROM_2)) {
|
||||
return rom[address - ROM_2_START];
|
||||
} else {
|
||||
log_error("Invalid memory region accessed");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Memory::write(size_t address, uint8_t byte) {
|
||||
if (MATCHES(BIOS)) {
|
||||
bios[address] = byte;
|
||||
} else if (MATCHES(BOARD_WRAM)) {
|
||||
board_wram[address - BOARD_WRAM_START] = byte;
|
||||
} else if (MATCHES(CHIP_WRAM)) {
|
||||
chip_wram[address - CHIP_WRAM_START] = byte;
|
||||
} else if (MATCHES(PALETTE_RAM)) {
|
||||
palette_ram[address - PALETTE_RAM_START] = byte;
|
||||
} else if (MATCHES(VRAM)) {
|
||||
vram[address - VRAM_START] = byte;
|
||||
} else if (MATCHES(OAM_OBJ_ATTR)) {
|
||||
oam_obj_attr[address - OAM_OBJ_ATTR_START] = byte;
|
||||
} else if (MATCHES(ROM_0)) {
|
||||
rom[address - ROM_0_START] = byte;
|
||||
} else if (MATCHES(ROM_1)) {
|
||||
rom[address - ROM_1_START] = byte;
|
||||
} else if (MATCHES(ROM_2)) {
|
||||
rom[address - ROM_2_START] = byte;
|
||||
} else {
|
||||
log_error("Invalid memory region accessed");
|
||||
}
|
||||
}
|
||||
|
||||
#undef MATCHES
|
||||
|
||||
uint16_t
|
||||
Memory::read_halfword(size_t address) const {
|
||||
if (address & 0b01)
|
||||
log_warn("Reading a non aligned halfword address");
|
||||
|
||||
return read(address) | read(address + 1) << 8;
|
||||
}
|
||||
|
||||
void
|
||||
Memory::write_halfword(size_t address, uint16_t halfword) {
|
||||
if (address & 0b01)
|
||||
log_warn("Writing to a non aligned halfword address");
|
||||
|
||||
write(address, halfword & 0xFF);
|
||||
write(address + 1, halfword >> 8 & 0xFF);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Memory::read_word(size_t address) const {
|
||||
if (address & 0b11)
|
||||
log_warn("Reading a non aligned word address");
|
||||
|
||||
return read(address) | read(address + 1) << 8 | read(address + 2) << 16 |
|
||||
read(address + 3) << 24;
|
||||
}
|
||||
|
||||
void
|
||||
Memory::write_word(size_t address, uint32_t halfword) {
|
||||
if (address & 0b11)
|
||||
log_warn("Writing to a non aligned word address");
|
||||
|
||||
write(address, halfword & 0xFF);
|
||||
write(address + 1, halfword >> 8 & 0xFF);
|
||||
write(address + 2, halfword >> 16 & 0xFF);
|
||||
write(address + 3, halfword >> 24 & 0xFF);
|
||||
}
|
||||
|
||||
void
|
||||
Memory::parse_header() {
|
||||
// entrypoint
|
||||
header.entrypoint =
|
||||
rom[0x00] | rom[0x01] << 8 | rom[0x02] << 16 | rom[0x03] << 24;
|
||||
|
||||
// nintendo logo
|
||||
if (rom[0x9C] != 0x21)
|
||||
log_info("HEADER: BIOS debugger bits not set to 0");
|
||||
|
||||
// game info
|
||||
std::copy(&rom[0xA0], &rom[0xA0 + 12], header.title);
|
||||
|
||||
switch (rom[0xAC]) {
|
||||
case 'A':
|
||||
header.unique_code = Header::UniqueCode::Old;
|
||||
break;
|
||||
case 'B':
|
||||
header.unique_code = Header::UniqueCode::New;
|
||||
break;
|
||||
case 'C':
|
||||
header.unique_code = Header::UniqueCode::Newer;
|
||||
break;
|
||||
case 'F':
|
||||
header.unique_code = Header::UniqueCode::Famicom;
|
||||
break;
|
||||
case 'K':
|
||||
header.unique_code = Header::UniqueCode::YoshiKoro;
|
||||
break;
|
||||
case 'P':
|
||||
header.unique_code = Header::UniqueCode::Ereader;
|
||||
break;
|
||||
case 'R':
|
||||
header.unique_code = Header::UniqueCode::Warioware;
|
||||
break;
|
||||
case 'U':
|
||||
header.unique_code = Header::UniqueCode::Boktai;
|
||||
break;
|
||||
case 'V':
|
||||
header.unique_code = Header::UniqueCode::DrillDozer;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("HEADER: invalid unique code: ", rom[0xAC]);
|
||||
}
|
||||
|
||||
header.title_code[0] = rom[0xAD];
|
||||
header.title_code[1] = rom[0xAE];
|
||||
|
||||
switch (rom[0xAF]) {
|
||||
case 'J':
|
||||
header.i18n = Header::I18n::Japan;
|
||||
break;
|
||||
case 'P':
|
||||
header.i18n = Header::I18n::Europe;
|
||||
break;
|
||||
case 'F':
|
||||
header.i18n = Header::I18n::French;
|
||||
break;
|
||||
case 'S':
|
||||
header.i18n = Header::I18n::Spanish;
|
||||
break;
|
||||
case 'E':
|
||||
header.i18n = Header::I18n::Usa;
|
||||
break;
|
||||
case 'D':
|
||||
header.i18n = Header::I18n::German;
|
||||
break;
|
||||
case 'I':
|
||||
header.i18n = Header::I18n::Italian;
|
||||
break;
|
||||
|
||||
default:
|
||||
log_error("HEADER: invalid destination/language - ", rom[0xAF]);
|
||||
}
|
||||
|
||||
if (rom[0xB2] != 0x96)
|
||||
log_error("HEADER: invalid fixed byte at 0xB2");
|
||||
|
||||
for (size_t i = 0xB5; i < 0xBC; i++) {
|
||||
if (rom[i] != 0x00)
|
||||
log_error("HEADER: invalid fixed bytes at 0xB5");
|
||||
}
|
||||
|
||||
header.version = rom[0xBC];
|
||||
|
||||
// checksum
|
||||
{
|
||||
size_t i = 0xA0, chk = 0;
|
||||
while (i <= 0xBC)
|
||||
chk -= rom[i++];
|
||||
chk -= 0x19;
|
||||
chk &= 0xFF;
|
||||
|
||||
if (chk != rom[0xBD])
|
||||
log_error("HEADER: checksum does not match");
|
||||
}
|
||||
|
||||
// multiboot not required right now
|
||||
}
|
64
src/memory.hh
Normal file
64
src/memory.hh
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "header.hh"
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class Memory {
|
||||
public:
|
||||
static constexpr size_t BIOS_SIZE = 1024 * 16;
|
||||
|
||||
Memory(std::array<uint8_t, BIOS_SIZE>&& bios,
|
||||
std::vector<uint8_t>&& rom) noexcept;
|
||||
|
||||
uint8_t read(size_t address) const;
|
||||
void write(size_t address, uint8_t byte);
|
||||
|
||||
uint16_t read_halfword(size_t address) const;
|
||||
void write_halfword(size_t address, uint16_t halfword);
|
||||
|
||||
uint32_t read_word(size_t address) const;
|
||||
void write_word(size_t address, uint32_t word);
|
||||
|
||||
private:
|
||||
#define MEMORY_REGION(name, start, end) \
|
||||
static constexpr size_t name##_START = start; \
|
||||
static constexpr size_t name##_END = end;
|
||||
|
||||
#define DECL_MEMORY(name, ident, start, end) \
|
||||
MEMORY_REGION(name, start, end) \
|
||||
uint8_t ident[name##_END - name##_START + 1];
|
||||
|
||||
MEMORY_REGION(BIOS, 0x00000000, 0x00003FFF)
|
||||
std::array<uint8_t, BIOS_SIZE> bios;
|
||||
static_assert(BIOS_END - BIOS_START + 1 == BIOS_SIZE);
|
||||
|
||||
// board working RAM
|
||||
DECL_MEMORY(BOARD_WRAM, board_wram, 0x02000000, 0x0203FFFF)
|
||||
|
||||
// chip working RAM
|
||||
DECL_MEMORY(CHIP_WRAM, chip_wram, 0x03000000, 0x03007FFF)
|
||||
|
||||
// palette RAM
|
||||
DECL_MEMORY(PALETTE_RAM, palette_ram, 0x05000000, 0x050003FF)
|
||||
|
||||
// video RAM
|
||||
DECL_MEMORY(VRAM, vram, 0x06000000, 0x06017FFF)
|
||||
|
||||
// OAM OBJ attributes
|
||||
DECL_MEMORY(OAM_OBJ_ATTR, oam_obj_attr, 0x07000000, 0x070003FF)
|
||||
|
||||
#undef DECL_MEMORY
|
||||
|
||||
MEMORY_REGION(ROM_0, 0x08000000, 0x09FFFFFF)
|
||||
MEMORY_REGION(ROM_1, 0x0A000000, 0x0BFFFFFF)
|
||||
MEMORY_REGION(ROM_2, 0x0C000000, 0x0DFFFFFF)
|
||||
|
||||
#undef MEMORY_REGION
|
||||
|
||||
std::vector<uint8_t> rom;
|
||||
Header header;
|
||||
void parse_header();
|
||||
};
|
@@ -1,13 +1,14 @@
|
||||
lib_sources = files(
|
||||
'emulator.cc'
|
||||
'memory.cc',
|
||||
'bus.cc'
|
||||
)
|
||||
|
||||
subdir('util')
|
||||
subdir('cpu')
|
||||
|
||||
lib = library(
|
||||
meson.project_name(),
|
||||
lib_sources,
|
||||
include_directories: inc,
|
||||
install: true
|
||||
)
|
||||
|
||||
|
49
src/util/log.cc
Normal file
49
src/util/log.cc
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "log.hh"
|
||||
|
||||
namespace logging {
|
||||
|
||||
namespace ansi {
|
||||
static const char* RED = "\033[31m";
|
||||
static const char* YELLOW = "\033[33m";
|
||||
static const char* MAGENTA = "\033[35m";
|
||||
static const char* WHITE = "\033[37m";
|
||||
static const char* BOLD = "\033[1m";
|
||||
static const char* RESET = "\033[0m";
|
||||
}
|
||||
|
||||
Logger::Logger(std::ostream& os)
|
||||
: os(os){};
|
||||
|
||||
void
|
||||
Logger::set_level(Level level) {
|
||||
switch (level) {
|
||||
case Level::Debug:
|
||||
os << ansi::MAGENTA << ansi::BOLD << "[DEBUG] ";
|
||||
break;
|
||||
|
||||
case Level::Info:
|
||||
os << ansi::WHITE << "[INFO] ";
|
||||
break;
|
||||
|
||||
case Level::Warn:
|
||||
os << ansi::YELLOW << "[WARN] ";
|
||||
break;
|
||||
|
||||
case Level::Error:
|
||||
os << ansi::RED << ansi::BOLD << "[ERROR] ";
|
||||
break;
|
||||
|
||||
// unreachable
|
||||
default: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Logger::reset_level() {
|
||||
os << ansi::RESET;
|
||||
}
|
||||
}
|
||||
|
||||
// Global logger
|
||||
logging::Logger logger;
|
42
src/util/log.hh
Normal file
42
src/util/log.hh
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <source_location>
|
||||
#include <streambuf>
|
||||
|
||||
namespace logging {
|
||||
enum class Level {
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
};
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
Logger(std::ostream& os = std::clog);
|
||||
|
||||
template<typename... Args>
|
||||
void print(Level level, Args&&... args) {
|
||||
set_level(level);
|
||||
(os << ... << args);
|
||||
reset_level();
|
||||
os << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream& os;
|
||||
void set_level(Level level);
|
||||
void reset_level();
|
||||
};
|
||||
}
|
||||
|
||||
extern logging::Logger logger;
|
||||
|
||||
#define log_debug(...) logger.print(logging::Level::Debug, __VA_ARGS__)
|
||||
#define log_info(...) logger.print(logging::Level::Info, __VA_ARGS__)
|
||||
#define log_warn(...) logger.print(logging::Level::Warn, __VA_ARGS__)
|
||||
#define log_error(...) logger.print(logging::Level::Error, __VA_ARGS__)
|
||||
#define log(...) log_info(__VA_ARGS__)
|
||||
#define debug(value) log_debug(#value, " = ", value)
|
4
src/util/meson.build
Normal file
4
src/util/meson.build
Normal file
@@ -0,0 +1,4 @@
|
||||
lib_sources += files(
|
||||
'log.cc',
|
||||
'utils.cc'
|
||||
)
|
113
src/util/utils.cc
Normal file
113
src/util/utils.cc
Normal file
@@ -0,0 +1,113 @@
|
||||
#include "utils.hh"
|
||||
#include <bit>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace crypto {
|
||||
|
||||
using std::rotr;
|
||||
|
||||
std::string
|
||||
sha256(const uint8_t* data, const size_t size) {
|
||||
// Assuming 1 byte = 8 bits
|
||||
std::stringstream string;
|
||||
size_t k = 512 - (size * 8 + 65) % 512;
|
||||
size_t L = size + (65 + k) / 8;
|
||||
size_t i, j;
|
||||
bool c = 0;
|
||||
|
||||
static constexpr uint32_t K[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
||||
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
|
||||
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
|
||||
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
|
||||
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
|
||||
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
uint32_t h[8] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
|
||||
|
||||
for (i = 0; i < L; i += 64) {
|
||||
size_t n = (size > i ? size - i : 0);
|
||||
uint32_t h0[8];
|
||||
uint32_t w[64] = { 0 };
|
||||
|
||||
if (n == 64)
|
||||
c = 1;
|
||||
|
||||
if (n >= 64) {
|
||||
for (j = 0; j < 16; j++) {
|
||||
w[j] = data[i + j * 4] << 24 | data[i + j * 4 + 1] << 16 |
|
||||
data[i + j * 4 + 2] << 8 | data[i + j * 4 + 3];
|
||||
}
|
||||
} else {
|
||||
uint8_t cur[64] = { 0 };
|
||||
|
||||
if (n) {
|
||||
std::copy(data + i, data + size, cur);
|
||||
cur[n] = 0x80;
|
||||
} else if (c) {
|
||||
cur[n] = 0x80;
|
||||
}
|
||||
|
||||
if (n < 54) {
|
||||
for (j = 56; j < 64; j++) {
|
||||
cur[j] = (size * 8 >> ((63 - j) * 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 0; j < 16; j++) {
|
||||
w[j] = cur[j * 4] << 24 | cur[j * 4 + 1] << 16 |
|
||||
cur[j * 4 + 2] << 8 | cur[j * 4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
for (j = 16; j < 64; j++) {
|
||||
uint32_t s0 =
|
||||
rotr(w[j - 15], 7) ^ rotr(w[j - 15], 18) ^ (w[j - 15] >> 3);
|
||||
uint32_t s1 =
|
||||
rotr(w[j - 2], 17) ^ rotr(w[j - 2], 19) ^ (w[j - 2] >> 10);
|
||||
|
||||
w[j] = w[j - 16] + w[j - 7] + s0 + s1;
|
||||
}
|
||||
|
||||
std::copy(h, h + 8, h0);
|
||||
|
||||
for (j = 0; j < 64; j++) {
|
||||
uint32_t s1 = rotr(h0[4], 6) ^ rotr(h0[4], 11) ^ rotr(h0[4], 25);
|
||||
uint32_t ch = (h0[4] & h0[5]) ^ (~h0[4] & h0[6]);
|
||||
uint32_t t1 = h0[7] + s1 + ch + K[j] + w[j];
|
||||
uint32_t s0 = rotr(h0[0], 2) ^ rotr(h0[0], 13) ^ rotr(h0[0], 22);
|
||||
uint32_t maj = (h0[0] & h0[1]) ^ (h0[0] & h0[2]) ^ (h0[1] & h0[2]);
|
||||
uint32_t t2 = s0 + maj;
|
||||
|
||||
h0[7] = h0[6];
|
||||
h0[6] = h0[5];
|
||||
h0[5] = h0[4];
|
||||
h0[4] = h0[3] + t1;
|
||||
h0[3] = h0[2];
|
||||
h0[2] = h0[1];
|
||||
h0[1] = h0[0];
|
||||
h0[0] = t1 + t2;
|
||||
}
|
||||
|
||||
for (j = 0; j < 8; j++)
|
||||
h[j] += h0[j];
|
||||
}
|
||||
|
||||
string << std::hex << std::setfill('0');
|
||||
|
||||
for (j = 0; j < 8; j++)
|
||||
for (i = 0; i < 4; i++)
|
||||
string << std::setw(2) << ((h[j] >> (24 - i * 8)) & 0xFF);
|
||||
|
||||
return string.str();
|
||||
}
|
||||
}
|
10
src/util/utils.hh
Normal file
10
src/util/utils.hh
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
// Why I wrote this myself? I do not know
|
||||
// I will off myself 😹😹😹😹
|
||||
namespace crypto {
|
||||
std::string
|
||||
sha256(const uint8_t* data, const size_t size);
|
||||
}
|
Reference in New Issue
Block a user