a26: implement ELF executable cartridge

This commit is contained in:
ZengGengSen
2026-08-30 19:24:24 +01:00
committed by Luke Usher
parent 742cc93106
commit f0c946b55e
15 changed files with 1477 additions and 2 deletions
+16 -1
View File
@@ -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
View File
@@ -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>
+2
View File
@@ -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>
+1
View File
@@ -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"
+89
View File
@@ -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;
};
+1
View File
@@ -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);
+12
View File
@@ -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"
}
+235
View File
@@ -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>;
}
+216
View File
@@ -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;
}
+101
View File
@@ -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);
}
+187
View File
@@ -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;
}
+75
View File
@@ -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);
}
}
}
+153
View File
@@ -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);
}
+328
View File
@@ -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);
}
+60 -1
View File
@@ -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;