mirror of
https://github.com/ares-emulator/ares.git
synced 2026-10-12 02:11:04 +00:00
a26: implement ELF executable cartridge
This commit is contained in:
+16
-1
@@ -1,4 +1,4 @@
|
||||
ares_components(arm7tdmi m24c mos6502)
|
||||
ares_components(arm7tdmi armv6m m24c mos6502)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
@@ -10,6 +10,7 @@ ares_add_sources(
|
||||
controller/controller.cpp
|
||||
cartridge/cartridge.cpp
|
||||
cpu/cpu.cpp
|
||||
elf/elf.cpp
|
||||
harmony/harmony.cpp
|
||||
video/video.cpp
|
||||
tia/tia.cpp
|
||||
@@ -44,11 +45,25 @@ ares_add_sources(
|
||||
cartridge/board/chetiry.cpp
|
||||
cartridge/board/fa2.cpp
|
||||
cartridge/board/dpc-plus.cpp
|
||||
cartridge/board/elf.cpp
|
||||
cartridge/board/linear.cpp
|
||||
cartridge/board/parker-bros.cpp
|
||||
cartridge/board/tigervision.cpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
a26
|
||||
INCLUDED #
|
||||
elf/elf.hpp
|
||||
elf/queue.cpp
|
||||
elf/parser.cpp
|
||||
elf/linker.cpp
|
||||
elf/memory.cpp
|
||||
elf/vcslib.cpp
|
||||
elf/runtime.cpp
|
||||
)
|
||||
|
||||
ares_add_sources(
|
||||
CORE #
|
||||
a26
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <a26/cartridge/cartridge.cpp>
|
||||
#include <a26/controller/controller.cpp>
|
||||
#include <a26/cpu/cpu.cpp>
|
||||
#include <a26/elf/elf.cpp>
|
||||
#include <a26/harmony/harmony.cpp>
|
||||
#include <a26/video/video.cpp>
|
||||
#include <a26/riot/riot.cpp>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <component/processor/mos6502/mos6502.hpp>
|
||||
#include <component/processor/arm7tdmi/arm7tdmi.hpp>
|
||||
#include <component/processor/armv6m/armv6m.hpp>
|
||||
#include <component/eeprom/m24c/m24c.hpp>
|
||||
|
||||
namespace ares::Atari2600 {
|
||||
@@ -21,6 +22,7 @@ namespace ares::Atari2600 {
|
||||
#include <a26/persistent-memory.hpp>
|
||||
#include <a26/controller/controller.hpp>
|
||||
#include <a26/system/system.hpp>
|
||||
#include <a26/elf/elf.hpp>
|
||||
#include <a26/harmony/harmony.hpp>
|
||||
#include <a26/cartridge/cartridge.hpp>
|
||||
#include <a26/cpu/cpu.hpp>
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Board {
|
||||
#include "bus.cpp"
|
||||
#include "chetiry.cpp"
|
||||
#include "fa2.cpp"
|
||||
#include "elf.cpp"
|
||||
#include "cpuwiz-4ksc.cpp"
|
||||
#include "three-e.cpp"
|
||||
#include "three-ex.cpp"
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
struct ELFCartridge : Interface {
|
||||
ELFCartridge(Cartridge& cartridge) : Interface(cartridge) {}
|
||||
|
||||
auto fail(string reason) -> void {
|
||||
runtime.unload();
|
||||
publishedError = std::move(reason);
|
||||
ready = false;
|
||||
cartridge.node->setAttribute("error", publishedError);
|
||||
}
|
||||
|
||||
auto clearError() -> void {
|
||||
publishedError = {};
|
||||
cartridge.node->setAttribute("error");
|
||||
}
|
||||
|
||||
auto publishRuntimeError() -> bool {
|
||||
if(!runtime.faulted()) return false;
|
||||
auto error = runtime.error();
|
||||
if(error != publishedError) {
|
||||
publishedError = std::move(error);
|
||||
cartridge.node->setAttribute("error", publishedError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto access(n16 address, n8 value) -> n8 {
|
||||
if(!ready || publishRuntimeError()) return value;
|
||||
return runtime.access(address, value);
|
||||
}
|
||||
|
||||
auto load() -> void override {
|
||||
image.clear();
|
||||
object = {};
|
||||
linker = {};
|
||||
ready = false;
|
||||
clearError();
|
||||
if(auto fp = pak->read("program.elf")) {
|
||||
if(fp->size() > ELF::MaximumImageSize) return fail("ELF image exceeds 16 MiB");
|
||||
image.resize(fp->size());
|
||||
fp->read({image.data(), image.size()});
|
||||
} else {
|
||||
return fail("ELF package is missing program.elf");
|
||||
}
|
||||
if(!object.parse(image)) return fail(object.error());
|
||||
if(!linker.link(object, ELF::externalSymbols(Region::NTSC()))) return fail(linker.error());
|
||||
ready = true;
|
||||
}
|
||||
|
||||
auto unload() -> void override {
|
||||
runtime.unload();
|
||||
image.clear();
|
||||
object = {};
|
||||
linker = {};
|
||||
ready = false;
|
||||
publishedError = {};
|
||||
}
|
||||
|
||||
auto power(bool) -> void override {
|
||||
if(!ready) return;
|
||||
if(!linker.link(object, ELF::externalSymbols(Region::NTSC()))) return fail(linker.error());
|
||||
runtime.power(linker.result(), Region::NTSC());
|
||||
clearError();
|
||||
publishRuntimeError();
|
||||
}
|
||||
|
||||
auto read(n16 address, n8 value) -> n8 override {
|
||||
return access(address, value);
|
||||
}
|
||||
|
||||
auto write(n16 address, n8 value) -> n8 override {
|
||||
return access(address, value);
|
||||
}
|
||||
|
||||
auto serialize(serializer& s) -> void override {
|
||||
if(!ready) return;
|
||||
runtime.serialize(s);
|
||||
if(s.reading()) {
|
||||
clearError();
|
||||
publishRuntimeError();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<u8> image;
|
||||
ELF::Object object;
|
||||
ELF::Linker linker;
|
||||
ELF::Runtime runtime;
|
||||
bool ready = false;
|
||||
string publishedError;
|
||||
};
|
||||
@@ -49,6 +49,7 @@ auto Cartridge::connect() -> void {
|
||||
if(information.board == "BUS") board = std::make_unique<Board::BUS>(*this);
|
||||
if(information.board == "Chetiry") board = std::make_unique<Board::Chetiry>(*this);
|
||||
if(information.board == "FA2") board = std::make_unique<Board::FA2>(*this);
|
||||
if(information.board == "ELF") board = std::make_unique<Board::ELFCartridge>(*this);
|
||||
if(information.board == "4KSC") board = std::make_unique<Board::CPUWiz4KSC>(*this);
|
||||
if(information.board == "3E") board = std::make_unique<Board::ThreeE>(*this);
|
||||
if(information.board == "3EX") board = std::make_unique<Board::ThreeEX>(*this);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
#include <a26/a26.hpp>
|
||||
|
||||
namespace ares::Atari2600::ELF {
|
||||
|
||||
#include "queue.cpp"
|
||||
#include "parser.cpp"
|
||||
#include "linker.cpp"
|
||||
#include "memory.cpp"
|
||||
#include "vcslib.cpp"
|
||||
#include "runtime.cpp"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
namespace ELF {
|
||||
|
||||
static constexpr u32 MaximumImageSize = 16_MiB;
|
||||
|
||||
struct BusQueue {
|
||||
static constexpr u32 Capacity = 16384;
|
||||
|
||||
struct Transaction {
|
||||
u16 address = 0;
|
||||
u16 mask = 0x1fff;
|
||||
u8 data = 0;
|
||||
u64 timestamp = 0;
|
||||
bool yield = false;
|
||||
};
|
||||
|
||||
auto reset() -> void;
|
||||
auto size() const -> u32 { return count; }
|
||||
auto empty() const -> bool { return !count; }
|
||||
auto setTimestamp(u64 value) -> BusQueue& { timestamp = value; return *this; }
|
||||
auto setNextAddress(u16 value) -> BusQueue& { nextAddress = value & 0x1fff; return *this; }
|
||||
auto getNextAddress() const -> u16 { return nextAddress; }
|
||||
auto inject(u8 data) -> bool;
|
||||
auto injectAt(u16 address, u8 data) -> bool;
|
||||
auto stuff(u16 address, u8 data) -> bool;
|
||||
auto release(u16 address, u16 mask = 0x1fff) -> bool;
|
||||
auto consume(u16 address, u64 timestamp, Transaction& result) -> bool;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
private:
|
||||
auto push(Transaction) -> bool;
|
||||
|
||||
Transaction queue[Capacity] = {};
|
||||
u32 head = 0;
|
||||
u32 count = 0;
|
||||
u16 nextAddress = 0;
|
||||
u64 timestamp = 0;
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
SHT_NULL = 0,
|
||||
SHT_PROGBITS = 1,
|
||||
SHT_SYMTAB = 2,
|
||||
SHT_STRTAB = 3,
|
||||
SHT_RELA = 4,
|
||||
SHT_NOBITS = 8,
|
||||
SHT_REL = 9,
|
||||
SHT_INIT_ARRAY = 14,
|
||||
SHT_PREINIT_ARRAY = 16,
|
||||
SHF_WRITE = 1,
|
||||
SHF_ALLOC = 2,
|
||||
SHF_EXECINSTR = 4,
|
||||
SHN_UNDEF = 0,
|
||||
SHN_ABS = 0xfff1,
|
||||
R_ARM_ABS32 = 2,
|
||||
R_ARM_REL32 = 3,
|
||||
R_ARM_THM_CALL = 10,
|
||||
R_ARM_THM_JUMP24 = 30,
|
||||
R_ARM_TARGET1 = 38,
|
||||
};
|
||||
|
||||
struct Section {
|
||||
string name;
|
||||
u32 nameOffset = 0;
|
||||
u32 type = 0;
|
||||
u32 flags = 0;
|
||||
u32 address = 0;
|
||||
u32 offset = 0;
|
||||
u32 size = 0;
|
||||
u32 link = 0;
|
||||
u32 info = 0;
|
||||
u32 alignment = 1;
|
||||
u32 entrySize = 0;
|
||||
};
|
||||
|
||||
struct Symbol {
|
||||
string name;
|
||||
u32 nameOffset = 0;
|
||||
u32 value = 0;
|
||||
u32 size = 0;
|
||||
u8 bind = 0;
|
||||
u8 type = 0;
|
||||
u8 visibility = 0;
|
||||
u16 section = 0;
|
||||
};
|
||||
|
||||
struct Relocation {
|
||||
u32 offset = 0;
|
||||
u32 symbol = 0;
|
||||
u8 type = 0;
|
||||
i32 addend = 0;
|
||||
bool explicitAddend = false;
|
||||
};
|
||||
|
||||
struct Object {
|
||||
auto parse(std::span<const u8> source) -> bool;
|
||||
auto valid() const -> bool { return parsed; }
|
||||
auto error() const -> const string& { return failure; }
|
||||
auto bytes(u32 offset, u32 size) const -> std::span<const u8>;
|
||||
|
||||
std::vector<u8> image;
|
||||
std::vector<Section> sections;
|
||||
std::vector<Symbol> symbols;
|
||||
std::vector<std::vector<Relocation>> relocations;
|
||||
string failure;
|
||||
bool parsed = false;
|
||||
};
|
||||
|
||||
struct Linker {
|
||||
enum class Segment : u32 { Text, Data, Rodata };
|
||||
|
||||
struct External {
|
||||
string name;
|
||||
u32 value = 0;
|
||||
};
|
||||
|
||||
struct Placement {
|
||||
bool allocated = false;
|
||||
Segment segment = Segment::Text;
|
||||
u32 offset = 0;
|
||||
};
|
||||
|
||||
struct Result {
|
||||
std::vector<u8> text;
|
||||
std::vector<u8> data;
|
||||
std::vector<u8> rodata;
|
||||
std::vector<u32> preinit;
|
||||
std::vector<u32> init;
|
||||
std::vector<Placement> placements;
|
||||
std::vector<u32> relocatedSymbols;
|
||||
u32 entrypoint = 0;
|
||||
};
|
||||
|
||||
auto link(const Object&, std::span<const External>) -> bool;
|
||||
auto error() const -> const string& { return failure; }
|
||||
auto result() const -> const Result& { return linked; }
|
||||
|
||||
static constexpr u32 TextBase = 0x0010'0000;
|
||||
static constexpr u32 TextCapacity = 1_MiB;
|
||||
static constexpr u32 DataBase = 0x0020'0000;
|
||||
static constexpr u32 DataCapacity = 512_KiB;
|
||||
static constexpr u32 RodataBase = 0x0030'0000;
|
||||
static constexpr u32 RodataCapacity = 512_KiB;
|
||||
|
||||
private:
|
||||
Result linked;
|
||||
string failure;
|
||||
};
|
||||
|
||||
struct Memory {
|
||||
static constexpr u32 StubBase = 0x0000'1000;
|
||||
static constexpr u32 StackBase = 0x0000'4000;
|
||||
static constexpr u32 StackSize = 32_KiB;
|
||||
static constexpr u32 TablesBase = 0x0040'0000;
|
||||
static constexpr u32 TablesSize = 4_KiB;
|
||||
static constexpr u32 PeripheralBase = 0xf000'0000;
|
||||
|
||||
auto power(const Linker::Result&) -> void;
|
||||
auto unload() -> void;
|
||||
auto get(u32 mode, n32 address, n32& value) -> ARMv6M::Fault;
|
||||
auto set(u32 mode, n32 address, n32 value) -> ARMv6M::Fault;
|
||||
auto writeWord(u32 address, u32 value) -> bool;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
std::vector<u8> stack;
|
||||
std::vector<u8> text;
|
||||
std::vector<u8> data;
|
||||
std::vector<u8> rodata;
|
||||
std::vector<u8> tables;
|
||||
u32 peripheral[4] = {};
|
||||
};
|
||||
|
||||
struct VCSLib {
|
||||
static constexpr u32 StubCount = 38;
|
||||
|
||||
auto power() -> void;
|
||||
auto access(n16 address, n8 value, Memory&) -> n8;
|
||||
auto execute(u32 address, ARMv6M&, Memory&) -> ARMv6M::Fault;
|
||||
auto stalled() const -> bool;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
BusQueue queue;
|
||||
bool busDriven = false;
|
||||
u8 busValue = 0;
|
||||
u16 currentAddress = 0;
|
||||
u8 currentData = 0;
|
||||
bool waiting = false;
|
||||
u16 waitingAddress = 0;
|
||||
u8 stuffMaskA = 0;
|
||||
u8 stuffMaskX = 0;
|
||||
u8 stuffMaskY = 0;
|
||||
u32 randomState = 1;
|
||||
u64 busAccessCounter = 0;
|
||||
|
||||
private:
|
||||
auto bootstrap() -> void;
|
||||
auto copyOverblank() -> void;
|
||||
auto write5(u16 address, u8 value) -> void;
|
||||
};
|
||||
|
||||
struct Runtime : ARMv6M, Thread {
|
||||
enum class Stage : u32 { Preinit, Init, Main };
|
||||
enum class Failure : u32 { None, Instruction, MainReturn, PreinitEntry, InitEntry, MainEntry };
|
||||
|
||||
auto power(const Linker::Result&, bool ntsc) -> void;
|
||||
auto unload() -> void;
|
||||
auto main() -> void;
|
||||
auto access(n16 address, n8 value) -> n8;
|
||||
auto invoke(u32 address, u32 stack, u32 argument0) -> Fault;
|
||||
auto startNext() -> void;
|
||||
auto fail(Failure, u32 address = 0) -> void;
|
||||
auto error() const -> string;
|
||||
auto faulted() const -> bool { return failure != Failure::None; }
|
||||
|
||||
auto step(u32 clocks) -> void override;
|
||||
auto get(u32 mode, n32 address, n32& value) -> Fault override;
|
||||
auto getDebugger(u32 mode, n32 address, n32& value) -> Fault override;
|
||||
auto set(u32 mode, n32 address, n32 value) -> Fault override;
|
||||
auto serialize(serializer&) -> void;
|
||||
|
||||
Memory memory;
|
||||
VCSLib vcs;
|
||||
std::vector<u32> preinit;
|
||||
std::vector<u32> init;
|
||||
u32 entrypoint = 0;
|
||||
u32 systemType = 0;
|
||||
Stage stage = Stage::Preinit;
|
||||
u32 initIndex = 0;
|
||||
bool ready = false;
|
||||
Failure failure = Failure::None;
|
||||
u32 failureAddress = 0;
|
||||
};
|
||||
|
||||
auto externalSymbols(bool ntsc) -> std::vector<Linker::External>;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
namespace {
|
||||
|
||||
auto alignTo(u32 value, u32 alignment, u32& output) -> bool {
|
||||
alignment = max(1u, alignment);
|
||||
auto mask = alignment - 1;
|
||||
if(value > ~0u - mask) return false;
|
||||
output = value + mask & ~mask;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto get32(const std::vector<u8>& bytes, u32 offset, u32& value) -> bool {
|
||||
if(offset > bytes.size() || 4 > bytes.size() - offset) return false;
|
||||
value = (u32)bytes[offset] | (u32)bytes[offset + 1] << 8
|
||||
| (u32)bytes[offset + 2] << 16 | (u32)bytes[offset + 3] << 24;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto put32(std::vector<u8>& bytes, u32 offset, u32 value) -> bool {
|
||||
if(offset > bytes.size() || 4 > bytes.size() - offset) return false;
|
||||
for(u32 byte = 0; byte < 4; byte++) bytes[offset + byte] = value >> byte * 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto get16(const std::vector<u8>& bytes, u32 offset, u16& value) -> bool {
|
||||
if(offset > bytes.size() || 2 > bytes.size() - offset) return false;
|
||||
value = bytes[offset] | bytes[offset + 1] << 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto put16(std::vector<u8>& bytes, u32 offset, u16 value) -> bool {
|
||||
if(offset > bytes.size() || 2 > bytes.size() - offset) return false;
|
||||
bytes[offset] = value;
|
||||
bytes[offset + 1] = value >> 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto signExtend(u32 value, u32 bits) -> i32 {
|
||||
auto shift = 32 - bits;
|
||||
return (i32)(value << shift) >> shift;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto Linker::link(const Object& object, std::span<const External> externals) -> bool {
|
||||
Result candidate;
|
||||
failure = {};
|
||||
auto fail = [&](const char* reason) { failure = reason; return false; };
|
||||
if(!object.valid()) return fail("cannot link an invalid ELF object");
|
||||
candidate.placements.resize(object.sections.size());
|
||||
|
||||
auto segmentFor = [](const Section& section) -> Segment {
|
||||
if(section.flags & SHF_EXECINSTR) return Segment::Text;
|
||||
if(section.flags & SHF_WRITE) return Segment::Data;
|
||||
return Segment::Rodata;
|
||||
};
|
||||
auto segment = [&](Segment type) -> std::vector<u8>& {
|
||||
if(type == Segment::Text) return candidate.text;
|
||||
if(type == Segment::Data) return candidate.data;
|
||||
return candidate.rodata;
|
||||
};
|
||||
auto base = [](Segment type) -> u32 {
|
||||
if(type == Segment::Text) return TextBase;
|
||||
if(type == Segment::Data) return DataBase;
|
||||
return RodataBase;
|
||||
};
|
||||
auto capacity = [](Segment type) -> u32 {
|
||||
if(type == Segment::Text) return TextCapacity;
|
||||
if(type == Segment::Data) return DataCapacity;
|
||||
return RodataCapacity;
|
||||
};
|
||||
|
||||
for(u32 index = 0; index < object.sections.size(); index++) {
|
||||
auto& section = object.sections[index];
|
||||
if(!(section.flags & SHF_ALLOC)) continue;
|
||||
if(section.type != SHT_PROGBITS && section.type != SHT_NOBITS
|
||||
&& section.type != SHT_INIT_ARRAY && section.type != SHT_PREINIT_ARRAY) continue;
|
||||
auto type = segmentFor(section);
|
||||
auto& output = segment(type);
|
||||
u32 offset = 0;
|
||||
if(!alignTo(output.size(), section.alignment, offset)) return fail("section layout overflow");
|
||||
if(offset > capacity(type) || section.size > capacity(type) - offset) {
|
||||
return fail("linked segment exceeds fixed capacity");
|
||||
}
|
||||
output.resize(offset + section.size, 0);
|
||||
if(section.type != SHT_NOBITS) {
|
||||
auto source = object.bytes(section.offset, section.size);
|
||||
if(source.size() != section.size) return fail("section copy exceeds ELF image");
|
||||
std::copy(source.begin(), source.end(), output.begin() + offset);
|
||||
}
|
||||
candidate.placements[index] = {true, type, offset};
|
||||
}
|
||||
|
||||
candidate.relocatedSymbols.resize(object.symbols.size());
|
||||
for(u32 index = 0; index < object.symbols.size(); index++) {
|
||||
auto& symbol = object.symbols[index];
|
||||
u32 value = 0;
|
||||
if(symbol.section == SHN_UNDEF) {
|
||||
bool resolved = !symbol.name;
|
||||
for(auto& external : externals) if(external.name == symbol.name) {
|
||||
value = external.value;
|
||||
resolved = true;
|
||||
break;
|
||||
}
|
||||
if(!resolved && symbol.bind == 2) resolved = true; //ELF weak undefined symbol.
|
||||
if(!resolved) { failure = {"undefined ELF symbol: ", symbol.name}; return false; }
|
||||
} else if(symbol.section == SHN_ABS) {
|
||||
value = symbol.value;
|
||||
} else {
|
||||
auto& placement = candidate.placements[symbol.section];
|
||||
auto& section = object.sections[symbol.section];
|
||||
auto symbolOffset = symbol.type == 2 ? symbol.value & ~1u : symbol.value;
|
||||
if(!placement.allocated || symbolOffset > section.size) return fail("symbol refers outside an allocated section");
|
||||
value = base(placement.segment) + placement.offset;
|
||||
if(symbol.type != 3) value += symbol.value; //STT_SECTION names the section base.
|
||||
}
|
||||
if(symbol.type == 2) value |= 1; //STT_FUNC is a Thumb entrypoint.
|
||||
candidate.relocatedSymbols[index] = value;
|
||||
if(symbol.name == "elf_main") {
|
||||
if(symbol.section == SHN_UNDEF || symbol.section == SHN_ABS || symbol.type != 2) {
|
||||
return fail("elf_main is not an allocated function");
|
||||
}
|
||||
auto& placement = candidate.placements[symbol.section];
|
||||
auto& section = object.sections[symbol.section];
|
||||
if(placement.segment != Segment::Text || (symbol.value & ~1u) >= section.size) {
|
||||
return fail("elf_main is outside executable text");
|
||||
}
|
||||
candidate.entrypoint = value;
|
||||
}
|
||||
}
|
||||
if(!candidate.entrypoint) return fail("required elf_main symbol is missing");
|
||||
|
||||
for(u32 sectionIndex = 0; sectionIndex < object.relocations.size(); sectionIndex++) {
|
||||
auto& placement = candidate.placements[sectionIndex];
|
||||
if(object.relocations[sectionIndex].empty()) continue;
|
||||
if(!placement.allocated) return fail("relocation targets an unallocated section");
|
||||
auto& targetSection = object.sections[sectionIndex];
|
||||
auto& output = segment(placement.segment);
|
||||
auto sectionAddress = base(placement.segment) + placement.offset;
|
||||
for(auto& relocation : object.relocations[sectionIndex]) {
|
||||
if(relocation.symbol >= candidate.relocatedSymbols.size()) return fail("relocation symbol is out of range");
|
||||
if(relocation.offset > targetSection.size || 4 > targetSection.size - relocation.offset) {
|
||||
return fail("relocation exceeds its target section");
|
||||
}
|
||||
auto location = placement.offset + relocation.offset;
|
||||
auto place = sectionAddress + relocation.offset;
|
||||
auto symbol = candidate.relocatedSymbols[relocation.symbol];
|
||||
if(relocation.type == R_ARM_ABS32 || relocation.type == R_ARM_TARGET1 || relocation.type == R_ARM_REL32) {
|
||||
if(relocation.offset & 3) return fail("word relocation is not aligned");
|
||||
u32 encoded = 0;
|
||||
if(!get32(output, location, encoded)) return fail("word relocation exceeds its target section");
|
||||
auto addend = relocation.explicitAddend ? relocation.addend : (i32)encoded;
|
||||
auto value = relocation.type == R_ARM_REL32 ? symbol + addend - place : symbol + addend;
|
||||
if(!put32(output, location, value)) return fail("word relocation write failed");
|
||||
continue;
|
||||
}
|
||||
if(relocation.type == R_ARM_THM_CALL || relocation.type == R_ARM_THM_JUMP24) {
|
||||
if(relocation.offset & 1) return fail("Thumb relocation is not aligned");
|
||||
u16 first = 0, second = 0;
|
||||
if(!get16(output, location, first) || !get16(output, location + 2, second)) {
|
||||
return fail("Thumb relocation exceeds its target section");
|
||||
}
|
||||
i32 addend = relocation.addend;
|
||||
if(!relocation.explicitAddend) {
|
||||
auto sign = first >> 10 & 1;
|
||||
auto j1 = second >> 13 & 1;
|
||||
auto j2 = second >> 11 & 1;
|
||||
auto i1 = !(j1 ^ sign);
|
||||
auto i2 = !(j2 ^ sign);
|
||||
auto encoded = sign << 24 | i1 << 23 | i2 << 22
|
||||
| (first & 0x03ff) << 12 | (second & 0x07ff) << 1;
|
||||
addend = signExtend(encoded, 25);
|
||||
}
|
||||
auto displacement = (i64)(symbol & ~1u) + addend - place;
|
||||
if(relocation.explicitAddend) displacement -= 4;
|
||||
if(displacement & 1 || displacement < -0x0100'0000ll || displacement > 0x00ff'fffell) {
|
||||
return fail("Thumb branch relocation is out of range");
|
||||
}
|
||||
auto encoded = (u32)displacement & 0x01ff'ffff;
|
||||
auto sign = encoded >> 24 & 1;
|
||||
auto i1 = encoded >> 23 & 1;
|
||||
auto i2 = encoded >> 22 & 1;
|
||||
auto j1 = !(i1 ^ sign);
|
||||
auto j2 = !(i2 ^ sign);
|
||||
first = first & 0xf800 | sign << 10 | encoded >> 12 & 0x03ff;
|
||||
second = second & 0xd000 | j1 << 13 | j2 << 11 | encoded >> 1 & 0x07ff;
|
||||
if(!put16(output, location, first) || !put16(output, location + 2, second)) {
|
||||
return fail("Thumb branch relocation write failed");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return fail("unsupported ARM relocation type");
|
||||
}
|
||||
}
|
||||
|
||||
auto copyArray = [&](u32 type, std::vector<u32>& destination) -> bool {
|
||||
for(u32 index = 0; index < object.sections.size(); index++) if(object.sections[index].type == type) {
|
||||
auto& placement = candidate.placements[index];
|
||||
if(!placement.allocated || object.sections[index].size % 4) return false;
|
||||
for(auto& relocation : object.relocations[index]) {
|
||||
if(relocation.type != R_ARM_ABS32 && relocation.type != R_ARM_TARGET1) return false;
|
||||
}
|
||||
auto& output = segment(placement.segment);
|
||||
for(u32 offset = 0; offset < object.sections[index].size; offset += 4) {
|
||||
u32 value = 0;
|
||||
if(!get32(output, placement.offset + offset, value)) return false;
|
||||
destination.push_back(value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if(!copyArray(SHT_PREINIT_ARRAY, candidate.preinit) || !copyArray(SHT_INIT_ARRAY, candidate.init)) {
|
||||
return fail("invalid initialization array");
|
||||
}
|
||||
linked = std::move(candidate);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
auto Memory::power(const Linker::Result& linked) -> void {
|
||||
text = linked.text;
|
||||
rodata = linked.rodata;
|
||||
stack.assign(StackSize, 0);
|
||||
data.assign(Linker::DataCapacity, 0);
|
||||
std::copy(linked.data.begin(), linked.data.end(), data.begin());
|
||||
tables.assign(TablesSize, 0);
|
||||
for(auto& value : peripheral) value = 0;
|
||||
|
||||
static const u8 ntscRows[16] = {
|
||||
0x00,0x20,0x20,0x30,0x40,0x50,0x60,0x70,0x80,0x90,0xa0,0xb0,0xc0,0xd0,0x20,0x20
|
||||
};
|
||||
static const u8 palRows[16] = {
|
||||
0x00,0x20,0x20,0x40,0x60,0x80,0xa0,0xc0,0xd0,0xb0,0x90,0x70,0x50,0x30,0x20,0x20
|
||||
};
|
||||
for(u32 value = 0; value < 256; value++) {
|
||||
tables[value] = ntscRows[value >> 4] | (value & 15);
|
||||
tables[256 + value] = palRows[value >> 4] | (value & 15);
|
||||
u8 reversed = 0;
|
||||
for(u32 bit = 0; bit < 8; bit++) reversed |= (value >> bit & 1) << (7 - bit);
|
||||
tables[512 + value] = reversed;
|
||||
}
|
||||
}
|
||||
|
||||
auto Memory::unload() -> void {
|
||||
stack.clear();
|
||||
text.clear();
|
||||
data.clear();
|
||||
rodata.clear();
|
||||
tables.clear();
|
||||
for(auto& value : peripheral) value = 0;
|
||||
}
|
||||
|
||||
auto Memory::get(u32 mode, n32 address, n32& value) -> ARMv6M::Fault {
|
||||
using enum ARMv6M::Fault;
|
||||
auto location = (u32)address;
|
||||
auto size = mode & ARMv6M::Byte ? 1u : mode & ARMv6M::Half ? 2u : 4u;
|
||||
auto readFrom = [&](const std::vector<u8>& source, u32 offset) -> ARMv6M::Fault {
|
||||
if(offset > source.size() || size > source.size() - offset) return Read;
|
||||
value = 0;
|
||||
for(u32 byte = 0; byte < size; byte++) value |= (u32)source[offset + byte] << byte * 8;
|
||||
return None;
|
||||
};
|
||||
|
||||
if(location >= StackBase && location - StackBase < StackSize) return readFrom(stack, location - StackBase);
|
||||
if(location >= Linker::TextBase && location - Linker::TextBase < text.size()) {
|
||||
return readFrom(text, location - Linker::TextBase);
|
||||
}
|
||||
if(location >= Linker::DataBase && location - Linker::DataBase < data.size()) {
|
||||
return readFrom(data, location - Linker::DataBase);
|
||||
}
|
||||
if(location >= Linker::RodataBase && location - Linker::RodataBase < rodata.size()) {
|
||||
return readFrom(rodata, location - Linker::RodataBase);
|
||||
}
|
||||
if(location >= TablesBase && location - TablesBase < tables.size()) return readFrom(tables, location - TablesBase);
|
||||
if(location >= PeripheralBase && location - PeripheralBase + size <= sizeof(peripheral)) {
|
||||
u8 bytes[sizeof(peripheral)] = {};
|
||||
for(u32 index = 0; index < std::size(peripheral); index++) {
|
||||
for(u32 byte = 0; byte < 4; byte++) bytes[index * 4 + byte] = peripheral[index] >> byte * 8;
|
||||
}
|
||||
value = 0;
|
||||
auto offset = location - PeripheralBase;
|
||||
for(u32 byte = 0; byte < size; byte++) value |= (u32)bytes[offset + byte] << byte * 8;
|
||||
return None;
|
||||
}
|
||||
return Read;
|
||||
}
|
||||
|
||||
auto Memory::set(u32 mode, n32 address, n32 value) -> ARMv6M::Fault {
|
||||
using enum ARMv6M::Fault;
|
||||
auto location = (u32)address;
|
||||
auto size = mode & ARMv6M::Byte ? 1u : mode & ARMv6M::Half ? 2u : 4u;
|
||||
auto writeTo = [&](std::vector<u8>& target, u32 offset) -> ARMv6M::Fault {
|
||||
if(offset > target.size() || size > target.size() - offset) return Write;
|
||||
for(u32 byte = 0; byte < size; byte++) target[offset + byte] = value >> byte * 8;
|
||||
return None;
|
||||
};
|
||||
|
||||
if(location >= StackBase && location - StackBase < StackSize) return writeTo(stack, location - StackBase);
|
||||
if(location >= Linker::DataBase && location - Linker::DataBase < data.size()) {
|
||||
return writeTo(data, location - Linker::DataBase);
|
||||
}
|
||||
if(location >= PeripheralBase + 8 && location - PeripheralBase + size <= sizeof(peripheral)) {
|
||||
auto index = (location - PeripheralBase) >> 2;
|
||||
auto shift = (location & 3) * 8;
|
||||
auto mask = size == 4 ? ~0u : ((1u << size * 8) - 1) << shift;
|
||||
peripheral[index] = peripheral[index] & ~mask | ((u32)value << shift & mask);
|
||||
return None;
|
||||
}
|
||||
return Write;
|
||||
}
|
||||
|
||||
auto Memory::writeWord(u32 address, u32 value) -> bool {
|
||||
return set(ARMv6M::Store | ARMv6M::Word, address, value) == ARMv6M::Fault::None;
|
||||
}
|
||||
|
||||
auto Memory::serialize(serializer& s) -> void {
|
||||
for(auto& value : stack) s(value);
|
||||
for(auto& value : data) s(value);
|
||||
for(auto& value : peripheral) s(value);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
namespace {
|
||||
|
||||
auto read16(std::span<const u8> data, u32 offset, u16& value) -> bool {
|
||||
if(offset > data.size() || 2 > data.size() - offset) return false;
|
||||
value = (u16)data[offset] | (u16)data[offset + 1] << 8;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto read32(std::span<const u8> data, u32 offset, u32& value) -> bool {
|
||||
if(offset > data.size() || 4 > data.size() - offset) return false;
|
||||
value = (u32)data[offset] | (u32)data[offset + 1] << 8
|
||||
| (u32)data[offset + 2] << 16 | (u32)data[offset + 3] << 24;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto bounded(u64 offset, u64 size, u64 capacity) -> bool {
|
||||
return offset <= capacity && size <= capacity - offset;
|
||||
}
|
||||
|
||||
auto stringAt(std::span<const u8> table, u32 offset, string& output) -> bool {
|
||||
if(offset >= table.size()) return false;
|
||||
auto end = offset;
|
||||
while(end < table.size() && table[end]) end++;
|
||||
if(end == table.size()) return false;
|
||||
output = string{string_view{(const char*)table.data() + offset, end - offset}};
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
auto Object::bytes(u32 offset, u32 size) const -> std::span<const u8> {
|
||||
if(!bounded(offset, size, image.size())) return {};
|
||||
return {image.data() + offset, size};
|
||||
}
|
||||
|
||||
auto Object::parse(std::span<const u8> source) -> bool {
|
||||
image.clear();
|
||||
sections.clear();
|
||||
symbols.clear();
|
||||
relocations.clear();
|
||||
failure = {};
|
||||
parsed = false;
|
||||
auto fail = [&](const char* reason) { failure = reason; return false; };
|
||||
|
||||
if(source.size() < 52) return fail("ELF header is truncated");
|
||||
if(source.size() > MaximumImageSize) return fail("ELF image exceeds 16 MiB");
|
||||
if(source[0] != 0x7f || source[1] != 'E' || source[2] != 'L' || source[3] != 'F') return fail("bad ELF magic");
|
||||
if(source[4] != 1) return fail("ELF is not 32-bit");
|
||||
if(source[5] != 1) return fail("ELF is not little-endian");
|
||||
if(source[6] != 1) return fail("bad ELF identification version");
|
||||
|
||||
u16 type = 0, machine = 0, headerSize = 0, sectionSize = 0, sectionCount = 0, namesIndex = 0;
|
||||
u32 version = 0, sectionOffset = 0;
|
||||
if(!read16(source, 0x10, type) || !read16(source, 0x12, machine)
|
||||
|| !read32(source, 0x14, version) || !read32(source, 0x20, sectionOffset)
|
||||
|| !read16(source, 0x28, headerSize) || !read16(source, 0x2e, sectionSize)
|
||||
|| !read16(source, 0x30, sectionCount) || !read16(source, 0x32, namesIndex)) {
|
||||
return fail("ELF header fields are truncated");
|
||||
}
|
||||
if(type != 1) return fail("ELF is not relocatable");
|
||||
if(machine != 40) return fail("ELF machine is not ARM");
|
||||
if(version != 1 || headerSize != 52) return fail("unsupported ELF header version or size");
|
||||
if(sectionSize != 40 || !sectionCount || sectionCount > 4096) return fail("invalid ELF section table shape");
|
||||
if(namesIndex >= sectionCount) return fail("section-name table index is out of range");
|
||||
if(sectionOffset < 52) return fail("section table overlaps the ELF header");
|
||||
if(!bounded(sectionOffset, (u64)sectionSize * sectionCount, source.size())) {
|
||||
return fail("section table exceeds ELF image");
|
||||
}
|
||||
|
||||
image.assign(source.begin(), source.end());
|
||||
sections.resize(sectionCount);
|
||||
relocations.resize(sectionCount);
|
||||
for(u32 index = 0; index < sectionCount; index++) {
|
||||
auto offset = sectionOffset + index * sectionSize;
|
||||
auto& section = sections[index];
|
||||
if(!read32(source, offset + 0x00, section.nameOffset)
|
||||
|| !read32(source, offset + 0x04, section.type)
|
||||
|| !read32(source, offset + 0x08, section.flags)
|
||||
|| !read32(source, offset + 0x0c, section.address)
|
||||
|| !read32(source, offset + 0x10, section.offset)
|
||||
|| !read32(source, offset + 0x14, section.size)
|
||||
|| !read32(source, offset + 0x18, section.link)
|
||||
|| !read32(source, offset + 0x1c, section.info)
|
||||
|| !read32(source, offset + 0x20, section.alignment)
|
||||
|| !read32(source, offset + 0x24, section.entrySize)) return fail("section header is truncated");
|
||||
if(section.type != SHT_NOBITS && !bounded(section.offset, section.size, source.size())) {
|
||||
return fail("section exceeds ELF image");
|
||||
}
|
||||
if(section.alignment && (section.alignment & (section.alignment - 1))) {
|
||||
return fail("section alignment is not a power of two");
|
||||
}
|
||||
if(section.alignment > 1_MiB) return fail("section alignment is excessive");
|
||||
}
|
||||
if(sections[0].type != SHT_NULL || sections[0].size) return fail("ELF section zero is not null");
|
||||
|
||||
//Each file-backed section must own a distinct byte range. Aliasing a
|
||||
//string/symbol/relocation payload makes later validation order-dependent
|
||||
//and is never emitted by the supported relocatable-object toolchain.
|
||||
for(u32 first = 1; first < sections.size(); first++) {
|
||||
auto& lhs = sections[first];
|
||||
if(lhs.type == SHT_NOBITS || !lhs.size) continue;
|
||||
if(lhs.offset < 52) return fail("section overlaps the ELF header");
|
||||
auto sectionTableEnd = (u64)sectionOffset + (u64)sectionSize * sectionCount;
|
||||
auto lhsEnd = (u64)lhs.offset + lhs.size;
|
||||
if(lhs.offset < sectionTableEnd && sectionOffset < lhsEnd) return fail("section overlaps the section table");
|
||||
for(u32 second = first + 1; second < sections.size(); second++) {
|
||||
auto& rhs = sections[second];
|
||||
if(rhs.type == SHT_NOBITS || !rhs.size) continue;
|
||||
auto rhsEnd = (u64)rhs.offset + rhs.size;
|
||||
if(lhs.offset < rhsEnd && rhs.offset < lhsEnd) return fail("file-backed ELF sections overlap");
|
||||
}
|
||||
}
|
||||
|
||||
auto& names = sections[namesIndex];
|
||||
if(names.type != SHT_STRTAB) return fail("section-name table has the wrong type");
|
||||
auto nameBytes = bytes(names.offset, names.size);
|
||||
for(auto& section : sections) {
|
||||
if(!stringAt(nameBytes, section.nameOffset, section.name)) {
|
||||
return fail("section name is out of bounds or unterminated");
|
||||
}
|
||||
}
|
||||
|
||||
u32 symbolSection = ~0u;
|
||||
for(u32 index = 0; index < sections.size(); index++) if(sections[index].type == SHT_SYMTAB) {
|
||||
if(symbolSection != ~0u) return fail("multiple symbol tables are unsupported");
|
||||
symbolSection = index;
|
||||
}
|
||||
if(symbolSection != ~0u) {
|
||||
auto& table = sections[symbolSection];
|
||||
if(table.entrySize != 16 || table.size % 16) return fail("invalid symbol table entry size");
|
||||
if(table.link >= sections.size() || sections[table.link].type != SHT_STRTAB) {
|
||||
return fail("symbol string table is invalid");
|
||||
}
|
||||
auto strings = bytes(sections[table.link].offset, sections[table.link].size);
|
||||
auto count = table.size / 16;
|
||||
if(count > 65536) return fail("symbol table is excessive");
|
||||
symbols.resize(count);
|
||||
for(u32 index = 0; index < count; index++) {
|
||||
auto offset = table.offset + index * 16;
|
||||
auto& symbol = symbols[index];
|
||||
if(!read32(source, offset + 0x00, symbol.nameOffset)
|
||||
|| !read32(source, offset + 0x04, symbol.value)
|
||||
|| !read32(source, offset + 0x08, symbol.size)
|
||||
|| !read16(source, offset + 0x0e, symbol.section)) return fail("symbol entry is truncated");
|
||||
symbol.bind = source[offset + 0x0c] >> 4;
|
||||
symbol.type = source[offset + 0x0c] & 15;
|
||||
symbol.visibility = source[offset + 0x0d] & 3;
|
||||
if(symbol.type == 3 && symbol.section < sections.size()) symbol.name = sections[symbol.section].name;
|
||||
else if(!stringAt(strings, symbol.nameOffset, symbol.name)) {
|
||||
return fail("symbol name is out of bounds or unterminated");
|
||||
}
|
||||
if(symbol.section != SHN_UNDEF && symbol.section != SHN_ABS && symbol.section >= sections.size()) {
|
||||
return fail("symbol section is out of range");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(u32 sectionIndex = 0; sectionIndex < sections.size(); sectionIndex++) {
|
||||
auto& table = sections[sectionIndex];
|
||||
if(table.type != SHT_REL && table.type != SHT_RELA) continue;
|
||||
auto entrySize = table.type == SHT_REL ? 8u : 12u;
|
||||
if(table.entrySize != entrySize || table.size % entrySize) return fail("invalid relocation table entry size");
|
||||
if(table.link != symbolSection || table.info >= sections.size()) {
|
||||
return fail("relocation table link or target is invalid");
|
||||
}
|
||||
auto count = table.size / entrySize;
|
||||
if(count > 262144) return fail("relocation table is excessive");
|
||||
auto& output = relocations[table.info];
|
||||
for(u32 index = 0; index < count; index++) {
|
||||
auto offset = table.offset + index * entrySize;
|
||||
u32 info = 0, addend = 0;
|
||||
Relocation relocation;
|
||||
if(!read32(source, offset, relocation.offset) || !read32(source, offset + 4, info)) {
|
||||
return fail("relocation entry is truncated");
|
||||
}
|
||||
if(table.type == SHT_RELA && !read32(source, offset + 8, addend)) return fail("relocation addend is truncated");
|
||||
relocation.symbol = info >> 8;
|
||||
relocation.type = info;
|
||||
relocation.explicitAddend = table.type == SHT_RELA;
|
||||
relocation.addend = (i32)addend;
|
||||
if(relocation.symbol >= symbols.size()) return fail("relocation symbol is out of range");
|
||||
output.push_back(relocation);
|
||||
}
|
||||
}
|
||||
parsed = true;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
auto BusQueue::reset() -> void {
|
||||
head = 0;
|
||||
count = 0;
|
||||
nextAddress = 0;
|
||||
timestamp = 0;
|
||||
}
|
||||
|
||||
auto BusQueue::push(Transaction transaction) -> bool {
|
||||
if(count) {
|
||||
auto& previous = queue[(head + count - 1) % Capacity];
|
||||
if(previous.address == transaction.address && previous.mask == transaction.mask) {
|
||||
previous = transaction;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(count == Capacity) return false;
|
||||
queue[(head + count++) % Capacity] = transaction;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto BusQueue::inject(u8 data) -> bool {
|
||||
return injectAt(nextAddress, data);
|
||||
}
|
||||
|
||||
auto BusQueue::injectAt(u16 address, u8 data) -> bool {
|
||||
auto result = push({(u16)(address & 0x1fff), 0x1fff, data, timestamp, false});
|
||||
if(result) nextAddress = (address + 1) & 0x1fff;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto BusQueue::stuff(u16 address, u8 data) -> bool {
|
||||
return push({(u16)(address & 0x1fff), 0x1fff, data, timestamp, false});
|
||||
}
|
||||
|
||||
auto BusQueue::release(u16 address, u16 mask) -> bool {
|
||||
mask &= 0x1fff;
|
||||
return push({(u16)(address & mask), mask, 0, timestamp, true});
|
||||
}
|
||||
|
||||
auto BusQueue::consume(u16 address, u64 now, Transaction& result) -> bool {
|
||||
if(!count) return false;
|
||||
auto& next = queue[head];
|
||||
if((address & next.mask & 0x1fff) != next.address || next.timestamp > now) return false;
|
||||
result = next;
|
||||
head = (head + 1) % Capacity;
|
||||
count--;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto BusQueue::serialize(serializer& s) -> void {
|
||||
s(count);
|
||||
s(nextAddress);
|
||||
s(timestamp);
|
||||
if(s.writing()) {
|
||||
for(u32 index = 0; index < count; index++) {
|
||||
auto& transaction = queue[(head + index) % Capacity];
|
||||
s(transaction.address);
|
||||
s(transaction.mask);
|
||||
s(transaction.data);
|
||||
s(transaction.timestamp);
|
||||
s(transaction.yield);
|
||||
}
|
||||
} else {
|
||||
head = 0;
|
||||
count = min(count, Capacity);
|
||||
for(u32 index = 0; index < count; index++) {
|
||||
auto& transaction = queue[index];
|
||||
s(transaction.address);
|
||||
s(transaction.mask);
|
||||
s(transaction.data);
|
||||
s(transaction.timestamp);
|
||||
s(transaction.yield);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
auto Runtime::power(const Linker::Result& linked, bool ntsc) -> void {
|
||||
Thread::create(300'000'000, std::bind_front(&Runtime::main, this));
|
||||
ARMv6M::power();
|
||||
memory.power(linked);
|
||||
vcs.power();
|
||||
preinit = linked.preinit;
|
||||
init = linked.init;
|
||||
entrypoint = linked.entrypoint;
|
||||
systemType = ntsc ? 0 : 1;
|
||||
stage = Stage::Preinit;
|
||||
initIndex = 0;
|
||||
ready = true;
|
||||
failure = Failure::None;
|
||||
failureAddress = 0;
|
||||
startNext();
|
||||
}
|
||||
|
||||
auto Runtime::unload() -> void {
|
||||
Thread::destroy();
|
||||
memory.unload();
|
||||
preinit.clear();
|
||||
init.clear();
|
||||
entrypoint = 0;
|
||||
systemType = 0;
|
||||
ready = false;
|
||||
failure = Failure::None;
|
||||
failureAddress = 0;
|
||||
}
|
||||
|
||||
auto Runtime::main() -> void {
|
||||
auto wait = [&] {
|
||||
setClock(max(clock(), cpu.clock()));
|
||||
step(1);
|
||||
};
|
||||
if(!ready || faulted()) return wait();
|
||||
if(vcs.queue.size() >= 10) return wait();
|
||||
if(vcs.stalled()) return wait();
|
||||
|
||||
auto error = instruction();
|
||||
if(error == Fault::Suspend) return wait();
|
||||
if(error == Fault::Return) {
|
||||
if(stage == Stage::Main) return fail(Failure::MainReturn);
|
||||
return startNext();
|
||||
}
|
||||
if(error != Fault::None) return fail(Failure::Instruction, r(15));
|
||||
}
|
||||
|
||||
auto Runtime::access(n16 address, n8 value) -> n8 {
|
||||
if(!ready || faulted()) return value;
|
||||
return vcs.access(address, value, memory);
|
||||
}
|
||||
|
||||
auto Runtime::invoke(u32 address, u32 stack, u32 argument0) -> Fault {
|
||||
if(!(address & 1)) return Fault::Alignment;
|
||||
r(0) = argument0;
|
||||
r(13) = stack & ~3u;
|
||||
r(14) = 0xffff'ffff;
|
||||
r(15) = address & ~1u;
|
||||
return Fault::None;
|
||||
}
|
||||
|
||||
auto Runtime::startNext() -> void {
|
||||
constexpr u32 stackTop = Memory::StackBase + Memory::StackSize;
|
||||
if(stage == Stage::Preinit && initIndex >= preinit.size()) { stage = Stage::Init; initIndex = 0; }
|
||||
if(stage == Stage::Preinit) {
|
||||
if(auto error = invoke(preinit[initIndex++], stackTop, stackTop); error != Fault::None) {
|
||||
return fail(Failure::PreinitEntry, preinit[initIndex - 1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(stage == Stage::Init && initIndex >= init.size()) { stage = Stage::Main; initIndex = 0; }
|
||||
if(stage == Stage::Init) {
|
||||
if(auto error = invoke(init[initIndex++], stackTop, stackTop); error != Fault::None) {
|
||||
return fail(Failure::InitEntry, init[initIndex - 1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto pointer = stackTop - 12;
|
||||
memory.writeWord(pointer + 0, systemType);
|
||||
memory.writeWord(pointer + 4, (u32)frequency());
|
||||
memory.writeWord(pointer + 8, 0);
|
||||
if(auto error = invoke(entrypoint, pointer, pointer); error != Fault::None) {
|
||||
return fail(Failure::MainEntry, entrypoint);
|
||||
}
|
||||
}
|
||||
|
||||
auto Runtime::fail(Failure reason, u32 address) -> void {
|
||||
failure = reason;
|
||||
failureAddress = address;
|
||||
}
|
||||
|
||||
auto Runtime::error() const -> string {
|
||||
if(failure == Failure::Instruction) return {"ELF ARM runtime fault at 0x", hex(failureAddress)};
|
||||
if(failure == Failure::MainReturn) return "ELF elf_main returned unexpectedly";
|
||||
if(failure == Failure::PreinitEntry) return "ELF preinit entrypoint is not Thumb code";
|
||||
if(failure == Failure::InitEntry) return "ELF init entrypoint is not Thumb code";
|
||||
if(failure == Failure::MainEntry) return "ELF elf_main entrypoint is not Thumb code";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto Runtime::step(u32 clocks) -> void {
|
||||
Thread::step(clocks);
|
||||
Thread::synchronize(cpu);
|
||||
}
|
||||
|
||||
auto Runtime::get(u32 mode, n32 address, n32& value) -> Fault {
|
||||
auto location = (u32)address;
|
||||
if(mode & Prefetch) {
|
||||
if(location & 1) return Fault::Fetch;
|
||||
if(location >= Memory::StubBase && location < Memory::StubBase + VCSLib::StubCount * 4 && !(location & 3)) {
|
||||
if(auto error = vcs.execute(location, *this, memory); error != Fault::None) return error;
|
||||
value = 0x4770; //BX LR
|
||||
return Fault::None;
|
||||
}
|
||||
if(location < Linker::TextBase || location - Linker::TextBase + 2 > memory.text.size()) return Fault::Fetch;
|
||||
}
|
||||
auto error = memory.get(mode, address, value);
|
||||
if(mode & Prefetch && error == Fault::Read) return Fault::Fetch;
|
||||
return error;
|
||||
}
|
||||
|
||||
auto Runtime::getDebugger(u32 mode, n32 address, n32& value) -> Fault {
|
||||
auto location = (u32)address;
|
||||
if(mode & Half && location >= Memory::StubBase
|
||||
&& location < Memory::StubBase + VCSLib::StubCount * 4 && !(location & 3)) {
|
||||
value = 0x4770; //BX LR
|
||||
return Fault::None;
|
||||
}
|
||||
if(mode & Prefetch) {
|
||||
if(location & 1) return Fault::Fetch;
|
||||
if(location < Linker::TextBase || location - Linker::TextBase + 2 > memory.text.size()) return Fault::Fetch;
|
||||
}
|
||||
auto error = memory.get(mode, address, value);
|
||||
if(mode & Prefetch && error == Fault::Read) return Fault::Fetch;
|
||||
return error;
|
||||
}
|
||||
|
||||
auto Runtime::set(u32 mode, n32 address, n32 value) -> Fault {
|
||||
return memory.set(mode, address, value);
|
||||
}
|
||||
|
||||
auto Runtime::serialize(serializer& s) -> void {
|
||||
ARMv6M::serialize(s);
|
||||
Thread::serialize(s);
|
||||
memory.serialize(s);
|
||||
vcs.serialize(s);
|
||||
s((u32&)stage);
|
||||
s(initIndex);
|
||||
s(systemType);
|
||||
s((u32&)failure);
|
||||
s(failureAddress);
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
auto externalSymbols(bool ntsc) -> std::vector<Linker::External> {
|
||||
static const char* names[] = {
|
||||
"memset", "memcpy", "vcsLdaForBusStuff2", "vcsLdxForBusStuff2", "vcsLdyForBusStuff2",
|
||||
"vcsWrite3", "vcsJmp3", "vcsNop2", "vcsNop2n", "vcsWrite5", "vcsWrite6", "vcsLda2",
|
||||
"vcsLdx2", "vcsLdy2", "vcsSax3", "vcsSta3", "vcsStx3", "vcsSty3", "vcsSta4", "vcsStx4",
|
||||
"vcsSty4", "vcsCopyOverblankToRiotRam", "vcsStartOverblank", "vcsEndOverblank", "vcsRead4",
|
||||
"randint", "vcsTxs2", "vcsJsr6", "vcsPha3", "vcsPhp3", "vcsPla4", "vcsPlp4", "vcsPla4Ex",
|
||||
"vcsPlp4Ex", "vcsJmpToRam3", "vcsWaitForAddress", "injectDmaData", "vcsWrite4"
|
||||
};
|
||||
|
||||
std::vector<Linker::External> symbols;
|
||||
symbols.push_back({"ADDR_IDR", Memory::PeripheralBase + 1});
|
||||
symbols.push_back({"DATA_IDR", Memory::PeripheralBase + 5});
|
||||
symbols.push_back({"DATA_ODR", Memory::PeripheralBase + 9});
|
||||
symbols.push_back({"DATA_MODER", Memory::PeripheralBase + 13});
|
||||
for(u32 index = 0; index < std::size(names); index++) {
|
||||
symbols.push_back({names[index], Memory::StubBase + index * 4 + 1});
|
||||
}
|
||||
symbols.push_back({"ColorLookup", Memory::TablesBase + (ntsc ? 0 : 256)});
|
||||
symbols.push_back({"ReverseByte", Memory::TablesBase + 512});
|
||||
return symbols;
|
||||
}
|
||||
|
||||
auto VCSLib::power() -> void {
|
||||
queue.reset();
|
||||
busDriven = false;
|
||||
busValue = 0;
|
||||
currentAddress = 0;
|
||||
currentData = 0;
|
||||
waiting = false;
|
||||
waitingAddress = 0;
|
||||
stuffMaskA = 0;
|
||||
stuffMaskX = 0;
|
||||
stuffMaskY = 0;
|
||||
randomState = 1;
|
||||
busAccessCounter = 0;
|
||||
bootstrap();
|
||||
}
|
||||
|
||||
auto VCSLib::access(n16 address, n8 value, Memory& memory) -> n8 {
|
||||
auto timestamp = ++busAccessCounter;
|
||||
BusQueue::Transaction transaction;
|
||||
if(queue.consume(address, timestamp, transaction)) {
|
||||
busDriven = !transaction.yield;
|
||||
busValue = transaction.data;
|
||||
}
|
||||
if(busDriven) value = busValue;
|
||||
currentAddress = address & 0x1fff;
|
||||
currentData = value;
|
||||
memory.peripheral[0] = currentAddress;
|
||||
memory.peripheral[1] = currentData;
|
||||
return value;
|
||||
}
|
||||
|
||||
auto VCSLib::stalled() const -> bool {
|
||||
return waiting && (!queue.empty() || currentAddress != waitingAddress);
|
||||
}
|
||||
|
||||
auto VCSLib::execute(u32 address, ARMv6M& arm, Memory& memory) -> ARMv6M::Fault {
|
||||
using enum ARMv6M::Fault;
|
||||
if(queue.size() >= 10) return Suspend;
|
||||
queue.setTimestamp(busAccessCounter);
|
||||
auto argument = (u32)arm.r(0);
|
||||
auto value = (u8)arm.r(1);
|
||||
stuffMaskA = memory.peripheral[2];
|
||||
stuffMaskX = memory.peripheral[2] >> 8;
|
||||
stuffMaskY = memory.peripheral[2] >> 16;
|
||||
|
||||
switch(address) {
|
||||
case Memory::StubBase + 0: {
|
||||
auto target = (u32)arm.r(0), size = (u32)arm.r(2);
|
||||
for(u32 offset = 0; offset < size; offset++) {
|
||||
if(auto error = memory.set(ARMv6M::Store | ARMv6M::Byte, target + offset, value); error != None) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
arm.r(0) = target;
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 4: {
|
||||
auto target = (u32)arm.r(0), source = (u32)arm.r(1), size = (u32)arm.r(2);
|
||||
for(u32 offset = 0; offset < size; offset++) {
|
||||
n32 byte = 0;
|
||||
if(auto error = memory.get(ARMv6M::Load | ARMv6M::Byte, source + offset, byte); error != None) return error;
|
||||
if(auto error = memory.set(ARMv6M::Store | ARMv6M::Byte, target + offset, byte); error != None) return error;
|
||||
}
|
||||
arm.r(0) = target;
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 8: {
|
||||
queue.inject(0xa9);
|
||||
queue.inject(stuffMaskA);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 12: {
|
||||
queue.inject(0xa2);
|
||||
queue.inject(stuffMaskX);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 16: {
|
||||
queue.inject(0xa0);
|
||||
queue.inject(stuffMaskY);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 20: {
|
||||
queue.inject(0x85);
|
||||
queue.inject(argument);
|
||||
queue.stuff(argument, value);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 24: {
|
||||
queue.inject(0x4c);
|
||||
queue.inject(0);
|
||||
queue.inject(0x10);
|
||||
queue.setNextAddress(0x1000);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 28: {
|
||||
queue.inject(0xea);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 32: {
|
||||
if(argument) {
|
||||
queue.inject(0xea);
|
||||
queue.setNextAddress(queue.getNextAddress() + argument - 1);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 36: {
|
||||
write5(argument, value);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 40: {
|
||||
queue.inject(0xa9);
|
||||
queue.inject(value);
|
||||
queue.inject(0x8d);
|
||||
queue.inject(argument);
|
||||
queue.inject(argument >> 8);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 44: {
|
||||
queue.inject(0xa9);
|
||||
queue.inject(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 48: {
|
||||
queue.inject(0xa2);
|
||||
queue.inject(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 52: {
|
||||
queue.inject(0xa0);
|
||||
queue.inject(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 56: {
|
||||
queue.inject(0x87);
|
||||
queue.inject(argument);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 60: {
|
||||
queue.inject(0x85);
|
||||
queue.inject(argument);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 64: {
|
||||
queue.inject(0x86);
|
||||
queue.inject(argument);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 68: {
|
||||
queue.inject(0x84);
|
||||
queue.inject(argument);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 72: {
|
||||
queue.inject(0x8d);
|
||||
queue.inject(argument);
|
||||
queue.inject(argument >> 8);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 76: {
|
||||
queue.inject(0x8e);
|
||||
queue.inject(argument);
|
||||
queue.inject(argument >> 8);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 80: {
|
||||
queue.inject(0x8c);
|
||||
queue.inject(argument);
|
||||
queue.inject(argument >> 8);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 84: {
|
||||
copyOverblank();
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 88: {
|
||||
queue.inject(0x4c);
|
||||
queue.inject(0x80);
|
||||
queue.inject(0);
|
||||
queue.release(0x80);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 92: {
|
||||
queue.injectAt(0x1fff, 0);
|
||||
queue.release(0xac);
|
||||
queue.setNextAddress(0x1000);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 96: {
|
||||
if(waiting) {
|
||||
if(stalled()) return Suspend;
|
||||
waiting = false;
|
||||
arm.r(0) = currentData;
|
||||
return None;
|
||||
}
|
||||
waiting = true;
|
||||
waitingAddress = argument & 0x1fff;
|
||||
queue.inject(0xad);
|
||||
queue.inject(argument);
|
||||
queue.inject(argument >> 8);
|
||||
queue.release(argument);
|
||||
return Suspend;
|
||||
}
|
||||
case Memory::StubBase + 100: {
|
||||
randomState = randomState * 1664525 + 1013904223;
|
||||
arm.r(0) = randomState;
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 104: {
|
||||
queue.inject(0x9a);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 108: {
|
||||
queue.inject(0x20);
|
||||
queue.inject(argument);
|
||||
queue.release(0, 0x1000);
|
||||
queue.inject(argument >> 8);
|
||||
queue.setNextAddress(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 112: {
|
||||
queue.inject(0x48);
|
||||
queue.release(0, 0x1000);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 116: {
|
||||
queue.inject(0x08);
|
||||
queue.release(0, 0x1000);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 120: {
|
||||
queue.inject(0x68);
|
||||
queue.release(0, 0x1000);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 124: {
|
||||
queue.inject(0x28);
|
||||
queue.release(0, 0x1000);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 136: {
|
||||
queue.inject(0x4c);
|
||||
queue.inject(argument);
|
||||
queue.inject(argument >> 8);
|
||||
queue.release(argument);
|
||||
return None;
|
||||
}
|
||||
case Memory::StubBase + 148: {
|
||||
queue.inject(0x8d);
|
||||
queue.inject(argument);
|
||||
queue.inject(argument >> 8);
|
||||
queue.stuff(argument, value);
|
||||
return None;
|
||||
}
|
||||
default: {
|
||||
return Undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto VCSLib::bootstrap() -> void {
|
||||
queue.injectAt(0x1ffc, 0x00);
|
||||
queue.inject(0x10);
|
||||
queue.setNextAddress(0x1000);
|
||||
copyOverblank();
|
||||
queue.inject(0x4c); queue.inject(0x80); queue.inject(0x00); queue.release(0x0080);
|
||||
queue.injectAt(0x1fff, 0x00); queue.release(0x00ac); queue.setNextAddress(0x1000);
|
||||
queue.inject(0xea);
|
||||
queue.setNextAddress(queue.getNextAddress() + 1023);
|
||||
}
|
||||
|
||||
auto VCSLib::write5(u16 address, u8 value) -> void {
|
||||
queue.inject(0xa9); queue.inject(value); queue.inject(0x85); queue.inject(address); queue.release(address);
|
||||
}
|
||||
|
||||
auto VCSLib::copyOverblank() -> void {
|
||||
static const u8 program[] = {
|
||||
0xa0,0x00,0xa5,0xe0,0x85,0x02,0x85,0x2d,0x98,0x18,0x6a,0xaa,0xb5,0xe0,0x90,0x04,
|
||||
0x4a,0x4a,0x4a,0x4a,0xc8,0xc0,0x1d,0xd0,0x04,0xa2,0x02,0x86,0x00,0xc0,0x20,0xd0,
|
||||
0x04,0xa2,0x00,0x86,0x00,0xc0,0x3f,0xd0,0xdb,0xae,0xff,0xff,0xd0,0xfb,0x4c,0x00,0x10
|
||||
};
|
||||
for(u32 index = 0; index < std::size(program); index++) write5(0x80 + index, program[index]);
|
||||
}
|
||||
|
||||
auto VCSLib::serialize(serializer& s) -> void {
|
||||
queue.serialize(s);
|
||||
s(busDriven);
|
||||
s(busValue);
|
||||
s(currentAddress);
|
||||
s(currentData);
|
||||
s(waiting);
|
||||
s(waitingAddress);
|
||||
s(stuffMaskA);
|
||||
s(stuffMaskX);
|
||||
s(stuffMaskY);
|
||||
s(randomState);
|
||||
s(busAccessCounter);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
struct Atari2600 : Cartridge {
|
||||
auto name() -> string override { return "Atari 2600"; }
|
||||
auto extensions() -> std::vector<string> override { return {"a26", "bin"}; }
|
||||
auto extensions() -> std::vector<string> override { return {"a26", "bin", "elf"}; }
|
||||
auto load(string location) -> LoadResult override;
|
||||
auto save(string location) -> bool override;
|
||||
|
||||
@@ -61,9 +61,49 @@ private:
|
||||
auto hasRepeatedRamWindow(std::vector<u8>& rom) -> bool;
|
||||
auto hasSaraRamLayout(std::vector<u8>& rom) -> bool;
|
||||
auto normalizeWDSW(std::vector<u8>& rom) -> void;
|
||||
auto isELF(std::span<const u8> image) const -> bool;
|
||||
};
|
||||
|
||||
auto Atari2600::load(string location) -> LoadResult {
|
||||
auto directoryELF = directory::exists(location) && file::exists({location, "program.elf"});
|
||||
auto standaloneELF = !directory::exists(location) && location.iendsWith(".elf");
|
||||
if(!directory::exists(location) && file::size(location) >= 4) {
|
||||
u8 magic[4] = {};
|
||||
if(auto fp = file::open(location, file::mode::read)) fp.read(magic);
|
||||
standaloneELF |= magic[0] == 0x7f && magic[1] == 'E' && magic[2] == 'L' && magic[3] == 'F';
|
||||
}
|
||||
if(directoryELF || standaloneELF) {
|
||||
auto elfLocation = directoryELF ? string{location, "program.elf"} : location;
|
||||
if(file::size(elfLocation) > 16_MiB) return invalidROM;
|
||||
std::vector<u8> image;
|
||||
if(directoryELF) append(image, elfLocation);
|
||||
else image = Cartridge::read(location);
|
||||
if(!isELF(image)) return invalidROM;
|
||||
|
||||
this->sha256 = Hash::SHA256(image).digest();
|
||||
this->location = location;
|
||||
string region = "NTSC";
|
||||
if(location.ifind("(Europe)") || location.ifind("(PAL)")) region = "PAL";
|
||||
manifest = "game\n";
|
||||
manifest += {" name: ", Medium::name(location), "\n"};
|
||||
manifest += {" title: ", Medium::name(location), "\n"};
|
||||
manifest += {" region: ", region, "\n"};
|
||||
manifest += {" sha256: ", sha256, "\n"};
|
||||
manifest += " board: ELF\n";
|
||||
manifest += " memory\n";
|
||||
manifest += " type: Executable\n";
|
||||
manifest += {" size: 0x", hex(image.size()), "\n"};
|
||||
manifest += " content: Program\n";
|
||||
|
||||
pak = std::make_shared<vfs::directory>();
|
||||
pak->setAttribute("title", Medium::name(location));
|
||||
pak->setAttribute("region", region);
|
||||
pak->setAttribute("board", "ELF");
|
||||
pak->append("manifest.bml", manifest);
|
||||
pak->append("program.elf", image);
|
||||
return successful;
|
||||
}
|
||||
|
||||
std::vector<u8> rom;
|
||||
if(directory::exists(location)) {
|
||||
append(rom, {location, "program.rom"});
|
||||
@@ -559,6 +599,25 @@ auto Atari2600::normalizeWDSW(std::vector<u8>& rom) -> void {
|
||||
std::swap_ranges(rom.begin() + 0x0800, rom.begin() + 0x0c00, rom.begin() + 0x0c00);
|
||||
}
|
||||
|
||||
auto Atari2600::isELF(std::span<const u8> image) const -> bool {
|
||||
if(image.size() < 52 || image.size() > 16_MiB) return false;
|
||||
if(image[0] != 0x7f || image[1] != 'E' || image[2] != 'L' || image[3] != 'F') return false;
|
||||
if(image[4] != 1 || image[5] != 1 || image[6] != 1) return false;
|
||||
auto read16 = [&](u32 offset) { return (u16)image[offset] | (u16)image[offset + 1] << 8; };
|
||||
auto read32 = [&](u32 offset) {
|
||||
return (u32)image[offset] | (u32)image[offset + 1] << 8
|
||||
| (u32)image[offset + 2] << 16 | (u32)image[offset + 3] << 24;
|
||||
};
|
||||
if(read16(0x10) != 1 || read16(0x12) != 40 || read32(0x14) != 1) return false;
|
||||
if(read16(0x28) != 52 || read16(0x2e) != 40) return false;
|
||||
auto sectionOffset = read32(0x20);
|
||||
auto sectionCount = read16(0x30);
|
||||
auto namesIndex = read16(0x32);
|
||||
if(!sectionCount || sectionCount > 4096 || namesIndex >= sectionCount) return false;
|
||||
auto sectionBytes = (u64)sectionCount * 40;
|
||||
return sectionOffset <= image.size() && sectionBytes <= image.size() - sectionOffset;
|
||||
}
|
||||
|
||||
auto Atari2600::match(std::vector<u8>& rom, std::vector<u8> pattern, u8 target_matches) -> bool {
|
||||
u8 matches = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user