1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00

Merge branch 'master' into Enterprise

This commit is contained in:
Thomas Harte 2021-06-10 21:41:57 -04:00
commit 8e0a6df03b
48 changed files with 1509 additions and 675 deletions

View File

@ -750,7 +750,7 @@ HalfCycles TMS9918::get_next_sequence_point() {
if(next_line_interrupt_row == -1) {
return generate_interrupts_ ?
half_cycles_before_internal_cycles(time_until_frame_interrupt) :
HalfCycles(-1);
HalfCycles::max();
}
// Figure out the number of internal cycles until the next line interrupt, which is the amount

View File

@ -787,45 +787,43 @@ template <bool has_fdc> class ConcreteMachine:
ay_.ay().set_port_handler(&key_state_);
// construct the list of necessary ROMs
const std::string machine_name = "AmstradCPC";
std::vector<ROMMachine::ROM> required_roms = {
ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd)
};
std::string model_number;
uint32_t crcs[2];
bool has_amsdos = false;
ROM::Name firmware, basic;
switch(target.model) {
default:
model_number = "6128";
has_128k_ = true;
crcs[0] = 0x0219bb74;
crcs[1] = 0xca6af63d;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
model_number = "464";
has_128k_ = false;
crcs[0] = 0x815752df;
crcs[1] = 0x7d9a3bac;
firmware = ROM::Name::CPC464Firmware;
basic = ROM::Name::CPC464BASIC;
break;
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
model_number = "664";
has_128k_ = false;
crcs[0] = 0x3f5a6dc4;
crcs[1] = 0x32fee492;
firmware = ROM::Name::CPC664Firmware;
basic = ROM::Name::CPC664BASIC;
has_amsdos = true;
break;
default:
firmware = ROM::Name::CPC6128Firmware;
basic = ROM::Name::CPC6128BASIC;
has_amsdos = true;
break;
}
required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]);
required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]);
// fetch and verify the ROMs
const auto roms = rom_fetcher(required_roms);
for(std::size_t index = 0; index < roms.size(); ++index) {
auto &data = roms[index];
if(!data) throw ROMMachine::Error::MissingROMs;
roms_[index] = std::move(*data);
roms_[index].resize(16384);
ROM::Request request = ROM::Request(firmware) && ROM::Request(basic);
if(has_amsdos) {
request = request && ROM::Request(ROM::Name::AMSDOS);
}
// Fetch and verify the ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
if(has_amsdos) {
roms_[ROMType::AMSDOS] = roms.find(ROM::Name::AMSDOS)->second;
}
roms_[ROMType::OS] = roms.find(firmware)->second;
roms_[ROMType::BASIC] = roms.find(basic)->second;
// Establish default memory map
upper_rom_is_paged_ = true;
upper_rom_ = ROMType::BASIC;

View File

@ -377,49 +377,55 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
// Pick the required ROMs.
using Target = Analyser::Static::AppleII::Target;
const std::string machine_name = "AppleII";
std::vector<ROMMachine::ROM> rom_descriptions;
size_t rom_size = 12*1024;
ROM::Name character, system;
switch(target.model) {
default:
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
character = ROM::Name::AppleIICharacter;
system = ROM::Name::AppleIIOriginal;
break;
case Target::Model::IIplus:
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
character = ROM::Name::AppleIICharacter;
system = ROM::Name::AppleIIPlus;
break;
case Target::Model::IIe:
rom_size += 3840;
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::IIe));
rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
character = ROM::Name::AppleIIeCharacter;
system = ROM::Name::AppleIIe;
break;
case Target::Model::EnhancedIIe:
rom_size += 3840;
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::EnhancedIIe));
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
character = ROM::Name::AppleIIEnhancedECharacter;
system = ROM::Name::AppleIIEnhancedE;
break;
}
const auto roms = rom_fetcher(rom_descriptions);
// Try to install a Disk II card now, before checking the ROM list,
// to make sure that Disk II dependencies have been communicated.
if(target.disk_controller != Target::DiskController::None) {
ROM::Request request = ROM::Request(character) && ROM::Request(system);
// Add the necessary Disk II requests if appropriate.
const bool has_disk_controller = target.disk_controller != Target::DiskController::None;
const bool is_sixteen_sector = target.disk_controller == Target::DiskController::SixteenSector;
if(has_disk_controller) {
// Apple recommended slot 6 for the (first) Disk II.
install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
request = request && DiskIICard::rom_request(is_sixteen_sector);
}
// Now, check and move the ROMs.
if(!roms[0] || !roms[1]) {
// Request, validate and install ROMs.
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(*roms[1]);
if(rom_.size() > rom_size) {
rom_.erase(rom_.begin(), rom_.end() - off_t(rom_size));
if(has_disk_controller) {
install_card(6, new Apple::II::DiskIICard(roms, is_sixteen_sector));
}
video_.set_character_rom(*roms[0]);
rom_ = std::move(roms.find(system)->second);
// The IIe and Enhanced IIe ROMs often distributed are oversized; trim if necessary.
if(system == ROM::Name::AppleIIe || system == ROM::Name::AppleIIEnhancedE) {
if(rom_.size() > 16128) {
rom_.erase(rom_.begin(), rom_.end() - off_t(16128));
}
}
video_.set_character_rom(roms.find(character)->second);
// Set up the default memory blocks. On a II or II+ these values will never change.
// On a IIe they'll be affected by selection of auxiliary RAM.
@ -781,12 +787,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
std::unique_ptr<Reflection::Struct> get_options() final {
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
options->output = get_video_signal_configurable();
options->use_square_pixels = video_.get_use_square_pixels();
return options;
}
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
const auto options = dynamic_cast<Options *>(str.get());
set_video_signal_configurable(options->output);
video_.set_use_square_pixels(options->use_square_pixels);
}
// MARK: MediaTarget

View File

@ -30,8 +30,11 @@ class Machine {
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
friend Configurable::DisplayOption<Options>;
public:
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
bool use_square_pixels = false;
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
if(needs_declare()) {
DeclareField(use_square_pixels);
declare_display_option();
limit_enum(&output, Configurable::Display::CompositeMonochrome, Configurable::Display::CompositeColour, -1);
}

View File

@ -10,27 +10,34 @@
using namespace Apple::II;
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
ROM::Request DiskIICard::rom_request(bool is_16_sector) {
if(is_16_sector) {
roms = rom_fetcher({
{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6},
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
});
return ROM::Request(ROM::Name::DiskIIBoot16Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
} else {
roms = rom_fetcher({
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
// {"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
});
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
return ROM::Request(ROM::Name::DiskIIBoot13Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
}
if(!roms[0] || !roms[1]) {
}
DiskIICard::DiskIICard(ROM::Map &map, bool is_16_sector) : diskii_(2045454) {
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
ROM::Map::iterator state_machine, boot;
if(is_16_sector) {
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
boot = map.find(ROM::Name::DiskIIBoot16Sector);
} else {
// TODO: see above re: 13-sector state machine.
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
boot = map.find(ROM::Name::DiskIIBoot13Sector);
}
if(state_machine == map.end() || boot == map.end()) {
throw ROMMachine::Error::MissingROMs;
}
boot_ = std::move(*roms[0]);
diskii_.set_state_machine(*roms[1]);
boot_ = std::move(boot->second);
diskii_.set_state_machine(state_machine->second);
set_select_constraints(None);
diskii_.set_clocking_hint_observer(this);
}

View File

@ -25,7 +25,8 @@ namespace II {
class DiskIICard: public Card, public ClockingHint::Observer {
public:
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
static ROM::Request rom_request(bool is_16_sector);
DiskIICard(ROM::Map &, bool is_16_sector);
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
void run_for(Cycles cycles, int stretches) final;

View File

@ -15,9 +15,8 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
is_iie_(is_iie) {
// Show only the centre 75% of the TV frame.
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
crt_.set_visible_area(Outputs::Display::Rect(0.118f, 0.122f, 0.77f, 0.77f));
set_use_square_pixels(use_square_pixels_);
// TODO: there seems to be some sort of bug whereby switching modes can cause
// a signal discontinuity that knocks phase out of whack. So it isn't safe to
@ -26,6 +25,41 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
// crt_.set_immediate_default_phase(0.5f);
}
void VideoBase::set_use_square_pixels(bool use_square_pixels) {
use_square_pixels_ = use_square_pixels;
// HYPER-UGLY HACK. See correlated hack in the Macintosh.
#ifdef __APPLE__
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.122f, 0.75f, 0.77f));
#else
if(use_square_pixels) {
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.112f, 0.75f, 0.73f));
} else {
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.12f, 0.75f, 0.77f));
}
#endif
if(use_square_pixels) {
// From what I can make out, many contemporary Apple II monitors were
// calibrated slightly to stretch the Apple II's display slightly wider
// than it should be per the NTSC standards, for approximately square
// pixels. This reproduces that.
// 243 lines and 52µs are visible.
// i.e. to be square, 1 pixel should be: (1/243 * 52) * (3/4) = 156/972 = 39/243 µs
// On an Apple II each pixel is actually 1/7µs.
// Therefore the adjusted aspect ratio should be (4/3) * (39/243)/(1/7) = (4/3) * 273/243 = 1092/729 = 343/243 ~= 1.412
crt_.set_aspect_ratio(343.0f / 243.0f);
} else {
// Standard NTSC aspect ratio.
crt_.set_aspect_ratio(4.0f / 3.0f);
}
}
bool VideoBase::get_use_square_pixels() {
return use_square_pixels_;
}
void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
crt_.set_scan_target(scan_target);
}

View File

@ -51,8 +51,14 @@ class VideoBase: public VideoSwitches<Cycles> {
/// Gets the type of output.
Outputs::Display::DisplayType get_display_type() const;
/// Sets whether the current CRT should be recalibrated away from normative NTSC
/// to produce square pixels in 40-column text mode.
void set_use_square_pixels(bool);
bool get_use_square_pixels();
protected:
Outputs::CRT::CRT crt_;
bool use_square_pixels_ = false;
// State affecting output video stream generation.
uint8_t *pixel_pointer_ = nullptr;

View File

@ -228,36 +228,6 @@ template <typename TimeUnit> class VideoSwitches {
return external_.annunciator_3;
}
enum class CharacterROM {
/// The ROM that shipped with both the Apple II and the II+.
II,
/// The ROM that shipped with the original IIe.
IIe,
/// The ROM that shipped with the Enhanced IIe.
EnhancedIIe,
/// The ROM that shipped with the IIgs.
IIgs
};
/// @returns A file-level description of @c rom.
static ROMMachine::ROM rom_description(CharacterROM rom) {
const std::string machine_name = "AppleII";
switch(rom) {
case CharacterROM::II:
return ROMMachine::ROM(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
case CharacterROM::IIe:
return ROMMachine::ROM(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
default: // To appease GCC.
case CharacterROM::EnhancedIIe:
return ROMMachine::ROM(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
case CharacterROM::IIgs:
return ROMMachine::ROM(machine_name, "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8);
}
}
/// Set the character ROM for this video output.
void set_character_rom(const std::vector<uint8_t> &rom) {
character_rom_ = rom;

View File

@ -189,31 +189,23 @@ class ConcreteMachine:
speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider));
using Target = Analyser::Static::AppleIIgs::Target;
std::vector<ROMMachine::ROM> rom_descriptions;
const std::string machine_name = "AppleIIgs";
ROM::Name system;
switch(target.model) {
case Target::Model::ROM00:
/* TODO */
case Target::Model::ROM01:
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0);
break;
case Target::Model::ROM03:
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29);
break;
case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break;
case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break;
default: system = ROM::Name::AppleIIgsROM03; break;
}
rom_descriptions.push_back(video_->rom_description(Video::Video::CharacterROM::EnhancedIIe));
constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter;
constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03;
// TODO: pick a different ADB ROM for earlier machine revisions?
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0);
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0] || !roms[1] || !roms[2]) {
ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = *roms[0];
video_->set_character_rom(*roms[1]);
adb_glu_->set_microcontroller_rom(*roms[2]);
rom_ = roms.find(system)->second;
video_->set_character_rom(roms.find(characters)->second);
adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second);
// Run only the currently-interesting self test.
// rom_[0x36402] = 2;

View File

@ -96,25 +96,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
using Model = Analyser::Static::Macintosh::Target::Model;
const std::string machine_name = "Macintosh";
uint32_t ram_size, rom_size;
std::vector<ROMMachine::ROM> rom_descriptions;
ROM::Name rom_name;
switch(model) {
default:
case Model::Mac128k:
ram_size = 128*1024;
rom_size = 64*1024;
rom_descriptions.emplace_back(machine_name, "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28);
rom_name = ROM::Name::Macintosh128k;
break;
case Model::Mac512k:
ram_size = 512*1024;
rom_size = 64*1024;
rom_descriptions.emplace_back(machine_name, "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d);
rom_name = ROM::Name::Macintosh512k;
break;
case Model::Mac512ke:
case Model::MacPlus: {
ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024;
rom_size = 128*1024;
const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
rom_name = ROM::Name::MacintoshPlus;
} break;
}
ram_mask_ = ram_size - 1;
@ -123,12 +122,12 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1);
// Grab a copy of the ROM and convert it into big-endian data.
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0]) {
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
roms[0]->resize(rom_size);
Memory::PackBigEndian16(*roms[0], rom_);
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
// Randomise memory contents.
Memory::Fuzz(ram_);

View File

@ -74,15 +74,13 @@ class ConcreteMachine:
video_->set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size());
Memory::Fuzz(ram_);
std::vector<ROMMachine::ROM> rom_descriptions = {
{"AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64}
// {"AtariST", "the UK TOS 1.04 ROM", "tos104.img", 192*1024, 0xa50d1d43}
};
const auto roms = rom_fetcher(rom_descriptions);
if(!roms[0]) {
constexpr ROM::Name rom_name = ROM::Name::AtariSTTOS100;
ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
Memory::PackBigEndian16(*roms[0], rom_);
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
// Set up basic memory map.
memory_map_[0] = BusDevice::MostlyRAM;

View File

@ -127,15 +127,13 @@ class ConcreteMachine:
joysticks_.emplace_back(new Joystick);
joysticks_.emplace_back(new Joystick);
const auto roms = rom_fetcher(
{ {"ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3} });
if(!roms[0]) {
constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS;
const ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
bios_ = *roms[0];
bios_.resize(8192);
bios_ = roms.find(rom_name)->second;
if(!target.media.cartridges.empty()) {
const auto &segment = target.media.cartridges.front()->get_segments().front();

View File

@ -41,7 +41,8 @@ namespace C1540 {
*/
class Machine final: public MachineBase {
public:
Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
static ROM::Request rom_request(Personality personality);
Machine(Personality personality, const ROM::Map &roms);
/*!
Sets the serial bus to which this drive should attach itself.

View File

@ -16,7 +16,15 @@
using namespace Commodore::C1540;
MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
ROM::Request Machine::rom_request(Personality personality) {
switch(personality) {
default:
case Personality::C1540: return ROM::Request(ROM::Name::Commodore1540);
case Personality::C1541: return ROM::Request(ROM::Name::Commodore1541);
}
}
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
Storage::Disk::Controller(1000000),
m6502_(*this),
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
@ -39,28 +47,22 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &
emplace_drive(1000000, 300, 2);
set_drive(1);
std::string device_name;
uint32_t crc = 0;
ROM::Name rom_name;
switch(personality) {
case Personality::C1540:
device_name = "1540";
crc = 0x718d42b1;
break;
case Personality::C1541:
device_name = "1541";
crc = 0xfb760019;
break;
default:
case Personality::C1540: rom_name = ROM::Name::Commodore1540; break;
case Personality::C1541: rom_name = ROM::Name::Commodore1541; break;
}
auto roms = rom_fetcher({ {"Commodore1540", "the " + device_name + " ROM", device_name + ".bin", 16*1024, crc} });
if(!roms[0]) {
auto rom = roms.find(rom_name);
if(rom == roms.end()) {
throw ROMMachine::Error::MissingROMs;
}
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
std::memcpy(rom_, roms.find(rom_name)->second.data(), std::min(sizeof(rom_), roms.find(rom_name)->second.size()));
}
Machine::Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
MachineBase(personality, rom_fetcher) {}
Machine::Machine(Personality personality, const ROM::Map &roms) :
MachineBase(personality, roms) {}
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);

View File

@ -127,7 +127,7 @@ class MachineBase:
public Storage::Disk::Controller {
public:
MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
MachineBase(Personality personality, const ROM::Map &roms);
// to satisfy CPU::MOS6502::Processor
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);

View File

@ -316,53 +316,48 @@ class ConcreteMachine:
// Install a joystick.
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
const std::string machine_name = "Vic20";
std::vector<ROMMachine::ROM> rom_names = {
{machine_name, "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1}
};
ROM::Request request(ROM::Name::Vic20BASIC);
ROM::Name kernel, character;
switch(target.region) {
default:
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
rom_names.emplace_back(machine_name, "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4);
character = ROM::Name::Vic20EnglishCharacters;
kernel = ROM::Name::Vic20EnglishPALKernel;
break;
case Analyser::Static::Commodore::Target::Region::American:
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
rom_names.emplace_back(machine_name, "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174);
character = ROM::Name::Vic20EnglishCharacters;
kernel = ROM::Name::Vic20EnglishNTSCKernel;
break;
case Analyser::Static::Commodore::Target::Region::Danish:
rom_names.emplace_back(machine_name, "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454);
rom_names.emplace_back(machine_name, "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16);
character = ROM::Name::Vic20DanishCharacters;
kernel = ROM::Name::Vic20DanishKernel;
break;
case Analyser::Static::Commodore::Target::Region::Japanese:
rom_names.emplace_back(machine_name, "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4b);
rom_names.emplace_back(machine_name, "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7);
character = ROM::Name::Vic20JapaneseCharacters;
kernel = ROM::Name::Vic20JapaneseKernel;
break;
case Analyser::Static::Commodore::Target::Region::Swedish:
rom_names.emplace_back(machine_name, "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551d);
rom_names.emplace_back(machine_name, "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662);
character = ROM::Name::Vic20SwedishCharacters;
kernel = ROM::Name::Vic20SwedishKernel;
break;
}
const auto roms = rom_fetcher(rom_names);
if(target.has_c1540) {
request = request && Commodore::C1540::Machine::rom_request(Commodore::C1540::Personality::C1540);
}
request = request && ROM::Request(character) && ROM::Request(kernel);
for(const auto &rom: roms) {
if(!rom) {
throw ROMMachine::Error::MissingROMs;
}
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
basic_rom_ = std::move(*roms[0]);
character_rom_ = std::move(*roms[1]);
kernel_rom_ = std::move(*roms[2]);
// Characters ROMs should be 4kb.
character_rom_.resize(4096);
// Kernel ROMs and the BASIC ROM should be 8kb.
kernel_rom_.resize(8192);
basic_rom_ = std::move(roms.find(ROM::Name::Vic20BASIC)->second);
character_rom_ = std::move(roms.find(character)->second);
kernel_rom_ = std::move(roms.find(kernel)->second);
if(target.has_c1540) {
// construct the 1540
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, rom_fetcher);
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, roms);
// attach it to the serial bus
c1540_->set_serial_bus(serial_bus_);

View File

@ -69,37 +69,25 @@ template <bool has_scsi_bus> class ConcreteMachine:
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
speaker_.set_high_frequency_cutoff(6000);
const std::string machine_name = "Electron";
std::vector<ROMMachine::ROM> required_roms = {
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
};
const size_t pres_adfs_rom_position = required_roms.size();
::ROM::Request request = ::ROM::Request(::ROM::Name::AcornBASICII) && ::ROM::Request(::ROM::Name::AcornElectronMOS100);
if(target.has_pres_adfs) {
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993);
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e);
request = request && ::ROM::Request(::ROM::Name::PRESADFSSlot1) && ::ROM::Request(::ROM::Name::PRESADFSSlot2);
}
const size_t acorn_adfs_rom_position = required_roms.size();
if(target.has_acorn_adfs) {
required_roms.emplace_back(machine_name, "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6);
request = request && ::ROM::Request(::ROM::Name::AcornADFS);
}
const size_t dfs_rom_position = required_roms.size();
if(target.has_dfs) {
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
request = request && ::ROM::Request(::ROM::Name::Acorn1770DFS);
}
const size_t ap6_rom_position = required_roms.size();
if(target.has_ap6_rom) {
required_roms.emplace_back(machine_name, "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfc);
request = request && ::ROM::Request(::ROM::Name::PRESAdvancedPlus6);
}
const auto roms = rom_fetcher(required_roms);
for(const auto &rom: roms) {
if(!rom) {
throw ROMMachine::Error::MissingROMs;
}
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
set_rom(ROM::BASIC, *roms[0], false);
set_rom(ROM::OS, *roms[1], false);
set_rom(ROM::BASIC, roms.find(::ROM::Name::AcornBASICII)->second, false);
set_rom(ROM::OS, roms.find(::ROM::Name::AcornElectronMOS100)->second, false);
/*
ROM slot mapping applied:
@ -115,19 +103,18 @@ template <bool has_scsi_bus> class ConcreteMachine:
plus3_ = std::make_unique<Plus3>();
if(target.has_dfs) {
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
set_rom(ROM::Slot0, roms.find(::ROM::Name::Acorn1770DFS)->second, true);
}
if(target.has_pres_adfs) {
set_rom(ROM::Slot4, *roms[pres_adfs_rom_position], true);
set_rom(ROM::Slot5, *roms[pres_adfs_rom_position+1], true);
set_rom(ROM::Slot4, roms.find(::ROM::Name::PRESADFSSlot1)->second, true);
set_rom(ROM::Slot5, roms.find(::ROM::Name::PRESADFSSlot2)->second, true);
}
if(target.has_acorn_adfs) {
set_rom(ROM::Slot6, *roms[acorn_adfs_rom_position], true);
set_rom(ROM::Slot6, roms.find(::ROM::Name::AcornADFS)->second, true);
}
}
if(target.has_ap6_rom) {
set_rom(ROM::Slot15, *roms[ap6_rom_position], true);
set_rom(ROM::Slot15, roms.find(::ROM::Name::PRESAdvancedPlus6)->second, true);
}
if(target.has_sideways_ram) {

View File

@ -169,19 +169,22 @@ class ConcreteMachine:
// Install the proper TV standard and select an ideal BIOS name.
const std::string machine_name = "MSX";
std::vector<ROMMachine::ROM> required_roms = {
{machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3}
};
ROM::Request bios_request = ROM::Request(ROM::Name::MSXGenericBIOS);
// std::vector<ROMMachine::ROM> required_roms = {
// {machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u}
// };
bool is_ntsc = true;
uint8_t character_generator = 1; /* 0 = Japan, 1 = USA, etc, 2 = USSR */
uint8_t date_format = 1; /* 0 = Y/M/D, 1 = M/D/Y, 2 = D/M/Y */
uint8_t keyboard = 1; /* 0 = Japan, 1 = USA, 2 = France, 3 = UK, 4 = Germany, 5 = USSR, 6 = Spain */
ROM::Name regional_bios_name;
// TODO: CRCs below are incomplete, at best.
switch(target.region) {
default:
case Target::Region::Japan:
required_roms.emplace_back(machine_name, "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390);
regional_bios_name = ROM::Name::MSXJapaneseBIOS;
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
is_ntsc = true;
@ -189,7 +192,7 @@ class ConcreteMachine:
date_format = 0;
break;
case Target::Region::USA:
required_roms.emplace_back(machine_name, "an American MSX BIOS", "msx-american.rom", 32*1024, 0);
regional_bios_name = ROM::Name::MSXAmericanBIOS;
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
is_ntsc = true;
@ -197,7 +200,7 @@ class ConcreteMachine:
date_format = 1;
break;
case Target::Region::Europe:
required_roms.emplace_back(machine_name, "a European MSX BIOS", "msx-european.rom", 32*1024, 0);
regional_bios_name = ROM::Name::MSXEuropeanBIOS;
vdp_->set_tv_standard(TI::TMS::TVStandard::PAL);
is_ntsc = false;
@ -205,27 +208,30 @@ class ConcreteMachine:
date_format = 2;
break;
}
bios_request = bios_request || ROM::Request(regional_bios_name);
// Fetch the necessary ROMs; try the region-specific ROM first,
// but failing that fall back on patching the main one.
size_t disk_index = 0;
ROM::Request request;
if(target.has_disk_drive) {
disk_index = required_roms.size();
required_roms.emplace_back(machine_name, "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61df);
request = ROM::Request(ROM::Name::MSXDOS) && bios_request;
} else {
request = bios_request;
}
const auto roms = rom_fetcher(required_roms);
if((!roms[0] && !roms[1]) || (target.has_disk_drive && !roms[2])) {
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
// Figure out which BIOS to use, either a specific one or the generic
// one appropriately patched.
if(roms[1]) {
memory_slots_[0].source = std::move(*roms[1]);
const auto regional_bios = roms.find(regional_bios_name);
if(regional_bios != roms.end()) {
memory_slots_[0].source = std::move(regional_bios->second);
memory_slots_[0].source.resize(32768);
} else {
memory_slots_[0].source = std::move(*roms[0]);
memory_slots_[0].source = std::move(roms.find(ROM::Name::MSXGenericBIOS)->second);
memory_slots_[0].source.resize(32768);
memory_slots_[0].source[0x2b] = uint8_t(
@ -252,7 +258,7 @@ class ConcreteMachine:
// Add a disk cartridge if any disks were supplied.
if(target.has_disk_drive) {
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
memory_slots_[2].source = std::move(*roms[disk_index]);
memory_slots_[2].source = std::move(roms.find(ROM::Name::MSXDOS)->second);
memory_slots_[2].source.resize(16384);
map(2, 0, 0x4000, 0x2000);

View File

@ -141,23 +141,20 @@ class ConcreteMachine:
// 0072ed54 = US/European BIOS 1.3
// 48d44a13 = Japanese BIOS 2.1
const bool is_japanese = target.region == Target::Region::Japan;
const auto roms = rom_fetcher(
{ {"MasterSystem",
is_japanese ? "the Japanese Master System BIOS" : "the European/US Master System BIOS",
is_japanese ? "japanese-bios.sms" : "bios.sms",
8*1024,
{ is_japanese ? 0x48d44a13u : 0x0072ed54u }
} }
);
if(!roms[0]) {
const ROM::Name bios_name = is_japanese ? ROM::Name::MasterSystemJapaneseBIOS : ROM::Name::MasterSystemWesternBIOS;
ROM::Request request(bios_name, true);
auto roms = rom_fetcher(request);
request.validate(roms);
const auto rom = roms.find(bios_name);
if(rom == roms.end()) {
// No BIOS found; attempt to boot as though it has already disabled itself.
has_bios_ = false;
memory_control_ |= 0x08;
std::cerr << "No BIOS found; attempting to start cartridge directly" << std::endl;
} else {
has_bios_ = true;
roms[0]->resize(8*1024);
memcpy(&bios_, roms[0]->data(), roms[0]->size());
memcpy(&bios_, rom->second.data(), std::min(sizeof(bios_), rom->second.size()));
}
page_cartridge();

View File

@ -304,73 +304,64 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
ram_[c] |= 0x40;
}
const std::string machine_name = "Oric";
std::vector<ROMMachine::ROM> rom_names = { {machine_name, "the Oric colour ROM", "colour.rom", 128, 0xd50fca65} };
::ROM::Request request = ::ROM::Request(::ROM::Name::OricColourROM, true);
::ROM::Name basic;
switch(target.rom) {
case Analyser::Static::Oric::Target::ROM::BASIC10:
rom_names.emplace_back(machine_name, "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4);
break;
case Analyser::Static::Oric::Target::ROM::BASIC11:
rom_names.emplace_back(machine_name, "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92bef);
break;
case Analyser::Static::Oric::Target::ROM::Pravetz:
rom_names.emplace_back(machine_name, "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502);
break;
case Analyser::Static::Oric::Target::ROM::BASIC10: basic = ::ROM::Name::OricBASIC10; break;
default:
case Analyser::Static::Oric::Target::ROM::BASIC11: basic = ::ROM::Name::OricBASIC11; break;
case Analyser::Static::Oric::Target::ROM::Pravetz: basic = ::ROM::Name::OricPravetzBASIC; break;
}
size_t diskii_state_machine_index = 0;
request = request && ::ROM::Request(basic);
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
rom_names.emplace_back(machine_name, "the Oric Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34);
request = request && ::ROM::Request(::ROM::Name::OricByteDrive500);
break;
case DiskInterface::Jasmin:
rom_names.emplace_back(machine_name, "the Oric Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89);
request = request && ::ROM::Request(::ROM::Name::OricJasmin);
break;
case DiskInterface::Microdisc:
rom_names.emplace_back(machine_name, "the Oric Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9c);
request = request && ::ROM::Request(::ROM::Name::OricMicrodisc);
break;
case DiskInterface::Pravetz:
rom_names.emplace_back(machine_name, "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06);
// These ROM details are coupled with those in the DiskIICard.
diskii_state_machine_index = rom_names.size();
rom_names.push_back({"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 }});
request = request && ::ROM::Request(::ROM::Name::Oric8DOSBoot) && ::ROM::Request(::ROM::Name::DiskIIStateMachine16Sector);
break;
}
const auto roms = rom_fetcher(rom_names);
for(std::size_t index = 0; index < roms.size(); ++index) {
if(!roms[index]) {
throw ROMMachine::Error::MissingROMs;
}
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
video_->set_colour_rom(*roms[0]);
rom_ = std::move(*roms[1]);
// The colour ROM is optional; an alternative composite encoding can be used if
// it is absent.
const auto colour_rom = roms.find(::ROM::Name::OricColourROM);
if(colour_rom != roms.end()) {
video_->set_colour_rom(colour_rom->second);
}
rom_ = std::move(roms.find(basic)->second);
switch(disk_interface) {
default: break;
case DiskInterface::BD500:
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(8192);
disk_rom_ = std::move(roms.find(::ROM::Name::OricByteDrive500)->second);
break;
case DiskInterface::Jasmin:
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(2048);
disk_rom_ = std::move(roms.find(::ROM::Name::OricJasmin)->second);
break;
case DiskInterface::Microdisc:
disk_rom_ = std::move(*roms[2]);
disk_rom_.resize(8192);
disk_rom_ = std::move(roms.find(::ROM::Name::OricMicrodisc)->second);
break;
case DiskInterface::Pravetz: {
pravetz_rom_ = std::move(*roms[2]);
pravetz_rom_ = std::move(roms.find(::ROM::Name::Oric8DOSBoot)->second);
pravetz_rom_.resize(512);
diskii_->set_state_machine(*roms[diskii_state_machine_index]);
diskii_->set_state_machine(roms.find(::ROM::Name::DiskIIStateMachine16Sector)->second);
} break;
}
rom_.resize(16384);
paged_rom_ = rom_.data();
switch(target.disk_interface) {

View File

@ -54,7 +54,7 @@ void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
#ifdef SUPPLY_COMPOSITE
const auto data_type =
(display_type == Outputs::Display::DisplayType::RGB) ?
(!has_colour_rom_ || display_type == Outputs::Display::DisplayType::RGB) ?
Outputs::Display::InputDataType::Red1Green1Blue1 :
Outputs::Display::InputDataType::PhaseLinkedLuminance8;
#else
@ -80,6 +80,7 @@ Outputs::Display::ScanStatus VideoOutput::get_scaled_scan_status() const {
}
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
has_colour_rom_ = true;
for(std::size_t c = 0; c < 8; c++) {
colour_forms_[c] = 0;

View File

@ -37,6 +37,7 @@ class VideoOutput {
Outputs::CRT::CRT crt_;
Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_;
bool crt_is_60Hz_ = false;
bool has_colour_rom_ = false;
void update_crt_frequency();

View File

@ -10,38 +10,16 @@
#define ROMMachine_hpp
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "Utility/ROMCatalogue.hpp"
namespace ROMMachine {
/*!
Describes a ROM image; this term is used in this emulator strictly in the sense of firmware
system software that is an inherent part of a machine.
*/
struct ROM {
/// The machine with which this ROM is associated, in a form that is safe for using as
/// part of a file name.
std::string machine_name;
/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket
/// clause, etc, e.g. "the Electron MOS 1.0".
std::string descriptive_name;
/// An idiomatic file name for this ROM, e.g. "os10.rom".
std::string file_name;
/// The expected size of this ROM in bytes, e.g. 32768.
size_t size = 0;
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
/// to exclude ROMs where the user's intent is otherwise clear.
std::vector<uint32_t> crc32s;
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, uint32_t crc32) :
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s({crc32}) {}
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, std::initializer_list<uint32_t> crc32s) :
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s(crc32s) {}
};
/*!
Defines the signature for a function that must be supplied by the host environment in order to give machines
a route for fetching any system ROMs they might need.
@ -50,7 +28,7 @@ struct ROM {
return a vector of unique_ptrs that either contain the contents of the ROM from @c names that corresponds by
index, or else are @c nullptr.
*/
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::vector<ROM> &roms)> ROMFetcher;
typedef std::function<ROM::Map(const ROM::Request &request)> ROMFetcher;
enum class Error {
MissingROMs

View File

@ -74,15 +74,13 @@ template<bool is_zx81> class ConcreteMachine:
speaker_.set_input_rate(float(ZX8081ClockRate) / 2.0f);
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
const auto roms =
use_zx81_rom ?
rom_fetcher({ {"ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6eb} }) :
rom_fetcher({ {"ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597} });
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
rom_ = std::move(*roms[0]);
rom_.resize(use_zx81_rom ? 8192 : 4096);
const ROM::Name rom_name = use_zx81_rom ? ROM::Name::ZX81 : ROM::Name::ZX80;
const ROM::Request request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
rom_ = std::move(roms.find(rom_name)->second);
rom_mask_ = uint16_t(rom_.size() - 1);

View File

@ -146,33 +146,24 @@ template<Model model> class ConcreteMachine:
set_clock_rate(clock_rate());
speaker_.set_input_rate(float(clock_rate()) / 2.0f);
// With only the +2a and +3 currently supported, the +3 ROM is always
// the one required.
std::vector<ROMMachine::ROM> rom_names;
const std::string machine = "ZXSpectrum";
ROM::Name rom_name;
switch(model) {
case Model::SixteenK:
case Model::FortyEightK:
rom_names.emplace_back(machine, "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531f);
break;
case Model::OneTwoEightK:
rom_names.emplace_back(machine, "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995);
break;
case Model::Plus2:
rom_names.emplace_back(machine, "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dc);
break;
case Model::FortyEightK: rom_name = ROM::Name::Spectrum48k; break;
case Model::OneTwoEightK: rom_name = ROM::Name::Spectrum128k; break;
case Model::Plus2: rom_name = ROM::Name::SpecrumPlus2; break;
case Model::Plus2a:
case Model::Plus3: {
const std::initializer_list<uint32_t> crc32s = { 0x96e3c17a, 0xbe0d9ec4 };
rom_names.emplace_back(machine, "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crc32s);
} break;
case Model::Plus3: rom_name = ROM::Name::SpectrumPlus3; break;
// TODO: possibly accept the +3 ROM in multiple parts?
}
const auto roms = rom_fetcher(rom_names);
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size()));
const auto request = ROM::Request(rom_name);
auto roms = rom_fetcher(request);
if(!request.validate(roms)) {
throw ROMMachine::Error::MissingROMs;
}
const auto &rom = roms.find(rom_name)->second;
memcpy(rom_.data(), rom.data(), std::min(rom_.size(), rom.size()));
// Register for sleeping notifications.
tape_player_.set_clocking_hint_observer(this);

View File

@ -0,0 +1,482 @@
//
// ROMCatalogue.cpp
// Clock Signal
//
// Created by Thomas Harte on 01/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#include "ROMCatalogue.hpp"
#include <algorithm>
#include <cassert>
#include <codecvt>
#include <iomanip>
#include <locale>
#include <sstream>
using namespace ROM;
namespace {
constexpr Name MaxName = Name::SpectrumPlus3;
}
Request::Request(Name name, bool optional) {
node.name = name;
node.is_optional = optional;
}
Request Request::append(Node::Type type, const Request &rhs) {
// Start with the easiest case: this is already an appropriate
// request, and so is the new thing.
if(node.type == type && rhs.node.type == type) {
Request new_request = *this;
new_request.node.children.insert(new_request.node.children.end(), rhs.node.children.begin(), rhs.node.children.end());
new_request.node.sort();
return new_request;
}
// Possibly: left is appropriate request and rhs is just one more thing?
if(node.type == type && rhs.node.type == Node::Type::One) {
Request new_request = *this;
new_request.node.children.push_back(rhs.node);
new_request.node.sort();
return new_request;
}
// Or: right is appropriate request and this is just one more thing?
if(rhs.node.type == type && node.type == Node::Type::One) {
Request new_request = rhs;
new_request.node.children.push_back(node);
new_request.node.sort();
return new_request;
}
// Otherwise create a new parent node.
Request parent;
parent.node.type = type;
parent.node.children.push_back(this->node);
parent.node.children.push_back(rhs.node);
return parent;
}
Request Request::operator &&(const Request &rhs) {
return append(Node::Type::All, rhs);
}
Request Request::operator ||(const Request &rhs) {
return append(Node::Type::Any, rhs);
}
bool Request::validate(Map &map) const {
return node.validate(map);
}
std::vector<ROM::Description> Request::all_descriptions() const {
std::vector<Description> result;
node.add_descriptions(result);
return result;
}
void Request::Node::add_descriptions(std::vector<Description> &result) const {
if(type == Type::One) {
result.push_back(name);
return;
}
for(const auto &child: children) {
child.add_descriptions(result);
}
}
Request Request::subtract(const ROM::Map &map) const {
Request copy(*this);
if(copy.node.subtract(map)) {
copy.node.name = Name::None;
copy.node.type = Node::Type::One;
}
return copy;
}
bool Request::Node::subtract(const ROM::Map &map) {
switch(type) {
case Type::One:
return map.find(name) != map.end();
default: {
bool has_all = true;
bool has_any = false;
auto iterator = children.begin();
while(iterator != children.end()) {
const bool did_subtract = iterator->subtract(map);
has_all &= did_subtract;
has_any |= did_subtract;
if(did_subtract) {
iterator = children.erase(iterator);
} else {
++iterator;
}
}
return (type == Type::All && has_all) || (type == Type::Any && has_any);
}
}
}
bool Request::empty() {
return node.type == Node::Type::One && node.name == Name::None;
}
bool Request::Node::validate(Map &map) const {
// Leaf nodes are easy: check that the named ROM is present,
// unless it's optional, in which case it is always valid.
//
// If it is present, make sure it's the proper size.
if(type == Type::One) {
auto rom = map.find(name);
if(rom == map.end()) {
return is_optional;
}
const Description description(name);
rom->second.resize(description.size);
return true;
}
// This is a collection node then. Check for both any or all
// simultaneously, since all nodes will need to be visited
// regardless of any/all in order to ensure proper sizing.
bool has_all = true;
bool has_any = false;
for(const auto &child: children) {
const bool is_valid = child.validate(map);
has_all &= is_valid;
has_any |= is_valid;
}
return (type == Type::Any && has_any) || (type == Type::All && has_all);
}
void Request::visit(
const std::function<void(ListType, size_t)> &enter_list,
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType, const ROM::Description &, bool, size_t)> &add_item
) const {
node.visit(enter_list, exit_list, add_item);
}
void Request::visit(
const std::function<void(LineItem, ListType, int, const ROM::Description *, bool, size_t)> &add_item
) const {
int indentation_level = 0;
node.visit(
[&indentation_level, &add_item] (ROM::Request::ListType type, size_t size) {
add_item(LineItem::NewList, type, indentation_level, nullptr, false, size);
++indentation_level;
},
[&indentation_level] {
--indentation_level;
},
[&indentation_level, &add_item] (ROM::Request::ListType type, const ROM::Description &rom, bool is_optional, size_t remaining) {
add_item(LineItem::Description, type, indentation_level, &rom, is_optional, remaining);
}
);
}
void Request::Node::visit(
const std::function<void(ListType, size_t)> &enter_list,
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
) const {
switch(type) {
case Type::One:
enter_list(ListType::Single, 1);
add_item(ROM::Request::ListType::Any, Description(name), is_optional, 0);
exit_list();
break;
case Type::Any:
case Type::All: {
const ListType list_type = type == Type::Any ? ListType::Any : ListType::All;
enter_list(list_type, children.size());
for(size_t index = 0; index < children.size(); index++) {
auto &child = children[index];
if(child.type == Type::One) {
add_item(list_type, Description(child.name), child.is_optional, children.size() - 1 - index);
} else {
child.visit(enter_list, exit_list, add_item);
}
}
exit_list();
} break;
}
}
std::vector<Description> ROM::all_descriptions() {
std::vector<Description> result;
for(int name = 1; name <= MaxName; name++) {
result.push_back(Description(ROM::Name(name)));
}
return result;
}
std::optional<Description> Description::from_crc(uint32_t crc32) {
for(int name = 1; name <= MaxName; name++) {
const Description candidate = Description(ROM::Name(name));
const auto found_crc = std::find(candidate.crc32s.begin(), candidate.crc32s.end(), crc32);
if(found_crc != candidate.crc32s.end()) {
return candidate;
}
}
return std::nullopt;
}
std::string Description::description(int flags) const {
std::stringstream output;
// If there are no CRCs, don't output them.
if(crc32s.empty()) flags &= ~ DescriptionFlag::CRC;
// Print the file name(s) and the descriptive name.
if(flags & DescriptionFlag::Filename) {
flags &= ~DescriptionFlag::Filename;
output << machine_name << '/';
if(file_names.size() == 1) {
output << file_names[0];
} else {
output << "{";
bool is_first = true;
for(const auto &file_name: file_names) {
if(!is_first) output << " or ";
output << file_name;
is_first = false;
}
output << "}";
}
output << " (" << descriptive_name;
if(!flags) {
output << ")";
return output.str();
}
output << "; ";
} else {
output << descriptive_name;
if(!flags) {
return output.str();
}
output << " (";
}
// Print the size.
if(flags & DescriptionFlag::Size) {
flags &= ~DescriptionFlag::Size;
output << size << " bytes";
if(!flags) {
output << ")";
return output.str();
}
output << "; ";
}
// Print the CRC(s).
if(flags & DescriptionFlag::CRC) {
flags &= ~DescriptionFlag::CRC;
output << ((crc32s.size() > 1) ? "usual crc32s: " : "usual crc32: ");
bool is_first = true;
for(const auto crc32: crc32s) {
if(!is_first) output << ", ";
is_first = false;
output << std::hex << std::setfill('0') << std::setw(8) << crc32;
}
if(!flags) {
output << ")";
return output.str();
}
}
return output.str();
}
std::wstring Request::description(int description_flags, wchar_t bullet_point) {
std::wstringstream output;
std::wstring_convert<std::codecvt_utf8<wchar_t>> wstring_converter;
visit(
[&output, description_flags, bullet_point, &wstring_converter] (ROM::Request::LineItem item, ROM::Request::ListType type, int indentation_level, const ROM::Description *description, bool is_optional, size_t remaining) {
if(indentation_level) {
output << std::endl;
for(int c = 0; c < indentation_level; c++) output << '\t';
output << bullet_point << ' ';
}
switch(item) {
case ROM::Request::LineItem::NewList:
if(remaining > 1) {
if(!indentation_level) output << " ";
switch(type) {
default:
case ROM::Request::ListType::All: output << "all of:"; break;
case ROM::Request::ListType::Any:
if(remaining == 2) {
output << "either of:";
} else {
output << "any of:";
}
break;
}
} else {
output << ":";
}
break;
case ROM::Request::LineItem::Description:
if(is_optional) output << "optionally, ";
output << wstring_converter.from_bytes(description->description(description_flags));
if(remaining) {
output << ";";
if(remaining == 1) {
output << ((type == ROM::Request::ListType::All) ? " and" : " or");
}
} else {
output << ".";
}
break;
}
}
);
return output.str();
}
Description::Description(Name name) {
switch(name) {
default: assert(false); break;
case Name::AMSDOS: *this = Description(name, "AmstradCPC", "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecdu); break;
case Name::CPC464Firmware: *this = Description(name, "AmstradCPC", "the CPC 464 firmware", "os464.rom", 16*1024, 0x815752dfu); break;
case Name::CPC464BASIC: *this = Description(name, "AmstradCPC", "the CPC 464 BASIC ROM", "basic464.rom", 16*1024, 0x7d9a3bacu); break;
case Name::CPC664Firmware: *this = Description(name, "AmstradCPC", "the CPC 664 firmware", "os664.rom", 16*1024, 0x3f5a6dc4u); break;
case Name::CPC664BASIC: *this = Description(name, "AmstradCPC", "the CPC 664 BASIC ROM", "basic664.rom", 16*1024, 0x32fee492u); break;
case Name::CPC6128Firmware: *this = Description(name, "AmstradCPC", "the CPC 6128 firmware", "os6128.rom", 16*1024, 0x0219bb74u); break;
case Name::CPC6128BASIC: *this = Description(name, "AmstradCPC", "the CPC 6128 BASIC ROM", "basic6128.rom", 16*1024, 0xca6af63du); break;
case Name::AppleIIEnhancedE: *this = Description(name, "AppleII", "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942u); break;
case Name::AppleIIe: *this = Description(name, "AppleII", "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18du); break;
case Name::AppleIIPlus: *this = Description(name, "AppleII", "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26u); break;
case Name::AppleIIOriginal: *this = Description(name, "AppleII", "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588u); break;
case Name::AppleIICharacter: *this = Description(name, "AppleII", "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6u); break;
case Name::AppleIIeCharacter: *this = Description(name, "AppleII", "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1u); break;
case Name::AppleIIEnhancedECharacter:
*this = Description(name, "AppleII", "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014du);
break;
case Name::AppleIIgsROM00: /* TODO */
case Name::AppleIIgsROM01: *this = Description(name, "AppleIIgs", "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0u); break;
case Name::AppleIIgsROM03: *this = Description(name, "AppleIIgs", "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29u); break;
case Name::AppleIIgsCharacter: *this = Description(name, "AppleIIgs", "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8u); break;
case AppleIIgsMicrocontrollerROM03:
*this = Description(name, "AppleIIgs", "the Apple IIgs ROM03 ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0u);
break;
case Name::DiskIIBoot16Sector:
*this = Description(name, "DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6u);
break;
case Name::DiskIIStateMachine16Sector:
*this = Description(name, "DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, std::initializer_list<uint32_t>{ 0x9796a238, 0xb72a2c70 } );
break;
case Name::DiskIIBoot13Sector:
*this = Description(name, "DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ffu);
break;
case Name::DiskIIStateMachine13Sector:
*this = Description(name, "DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620u);
break;
case Name::Macintosh128k: *this = Description(name, "Macintosh", "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28u); break;
case Name::Macintosh512k: *this = Description(name, "Macintosh", "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d); break;
case Name::MacintoshPlus: {
const std::initializer_list<uint32_t> crcs = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
*this = Description(name, "Macintosh", "the Macintosh Plus ROM", "macplus.rom", 128*1024, crcs);
} break;
case Name::AtariSTTOS100: *this = Description(name, "AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64u); break;
case Name::AtariSTTOS104: *this = Description(name, "AtariST", "the UK TOS 1.04 ROM", "tos104.img", 192*1024, 0xa50d1d43u); break;
case Name::AtariSTEmuTOS192: *this = Description(name, "AtariST", "the UK EmuTOS 1.92 ROM", "etos192uk.img", 192*1024, 0xfc3b9e61u); break;
case Name::ColecoVisionBIOS:
*this = Description(name, "ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3u);
break;
case Name::ZX80: *this = Description(name, "ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597u); break;
case Name::ZX81: *this = Description(name, "ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6ebu); break;
case Name::Spectrum48k: *this = Description(name, "ZXSpectrum", "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531fu); break;
case Name::Spectrum128k: *this = Description(name, "ZXSpectrum", "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995u); break;
case Name::SpecrumPlus2: *this = Description(name, "ZXSpectrum", "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dcu); break;
case Name::SpectrumPlus3: {
const std::initializer_list<uint32_t> crcs = { 0x96e3c17a, 0xbe0d9ec4 };
*this = Description(name, "ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crcs);
} break;
case Name::AcornBASICII: *this = Description(name, "Electron", "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781u); break;
case Name::PRESADFSSlot1: *this = Description(name, "Electron", "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993u); break;
case Name::PRESADFSSlot2: *this = Description(name, "Electron", "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0eu); break;
case Name::AcornADFS: *this = Description(name, "Electron", "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6u); break;
case Name::Acorn1770DFS: *this = Description(name, "Electron", "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5u); break;
case Name::PRESAdvancedPlus6:
*this = Description(name, "Electron", "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfcu);
break;
case Name::AcornElectronMOS100:
*this = Description(name, "Electron", "the Electron MOS ROM v1.00", "os.rom", 16*1024, 0xbf63fb1fu);
break;
case Name::MasterSystemJapaneseBIOS: *this = Description(name, "MasterSystem", "the Japanese Master System BIOS", "japanese-bios.sms", 8*1024, 0x48d44a13u); break;
case Name::MasterSystemWesternBIOS: *this = Description(name, "MasterSystem", "the European/US Master System BIOS", "bios.sms", 8*1024, 0x0072ed54u); break;
case Name::Commodore1540: *this = Description(name, "Commodore1540", "the 1540 ROM", "1540.bin", 16*1024, 0x718d42b1u); break;
case Name::Commodore1541: *this = Description(name, "Commodore1540", "the 1541 ROM", "1541.bin", 16*1024, 0xfb760019); break;
case Name::Vic20BASIC: *this = Description(name, "Vic20", "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1u); break;
case Name::Vic20EnglishCharacters: *this = Description(name, "Vic20", "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6u); break;
case Name::Vic20EnglishPALKernel: *this = Description(name, "Vic20", "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4u); break;
case Name::Vic20EnglishNTSCKernel: *this = Description(name, "Vic20", "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174u); break;
case Name::Vic20DanishCharacters: *this = Description(name, "Vic20", "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454u); break;
case Name::Vic20DanishKernel: *this = Description(name, "Vic20", "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16u); break;
case Name::Vic20JapaneseCharacters: *this = Description(name, "Vic20", "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4bu); break;
case Name::Vic20JapaneseKernel: *this = Description(name, "Vic20", "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7u); break;
case Name::Vic20SwedishCharacters: *this = Description(name, "Vic20", "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551du); break;
case Name::Vic20SwedishKernel: *this = Description(name, "Vic20", "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662u); break;
case Name::OricColourROM: *this = Description(name, "Oric", "the Oric colour ROM", "colour.rom", 128, 0xd50fca65u); break;
case Name::OricBASIC10: *this = Description(name, "Oric", "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4u); break;
case Name::OricBASIC11: *this = Description(name, "Oric", "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92befu); break;
case Name::OricPravetzBASIC: *this = Description(name, "Oric", "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502u); break;
case Name::OricByteDrive500: *this = Description(name, "Oric", "the Oric Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34u); break;
case Name::OricJasmin: *this = Description(name, "Oric", "the Oric Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89u); break;
case Name::OricMicrodisc: *this = Description(name, "Oric", "the Oric Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9cu); break;
case Name::Oric8DOSBoot: *this = Description(name, "Oric", "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06u); break;
case Name::MSXGenericBIOS: *this = Description(name, "MSX", "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u); break;
case Name::MSXJapaneseBIOS: *this = Description(name, "MSX", "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390u); break;
case Name::MSXAmericanBIOS: *this = Description(name, "MSX", "an American MSX BIOS", "msx-american.rom", 32*1024, 0u); break;
case Name::MSXEuropeanBIOS: *this = Description(name, "MSX", "a European MSX BIOS", "msx-european.rom", 32*1024, 0u); break;
case Name::MSXDOS: *this = Description(name, "MSX", "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61dfu); break;
case Name::SinclairQLJS:
*this = Description(name, "SinclairQL", "the Sinclair QL 'JS' ROM", "js.rom", 48*1024, 0x0f95aab5u);
break;
}
}

View File

@ -0,0 +1,269 @@
//
// ROMCatalogue.hpp
// Clock Signal
//
// Created by Thomas Harte on 01/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#ifndef ROMCatalogue_hpp
#define ROMCatalogue_hpp
#include <algorithm>
#include <functional>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>
namespace ROM {
enum Name {
None,
// Acorn.
AcornBASICII,
AcornElectronMOS100,
PRESADFSSlot1,
PRESADFSSlot2,
AcornADFS,
PRESAdvancedPlus6,
Acorn1770DFS,
// Amstrad CPC.
AMSDOS,
CPC464Firmware, CPC464BASIC,
CPC664Firmware, CPC664BASIC,
CPC6128Firmware, CPC6128BASIC,
// Apple II.
AppleIIOriginal,
AppleIIPlus,
AppleIICharacter,
AppleIIe,
AppleIIeCharacter,
AppleIIEnhancedE,
AppleIIEnhancedECharacter,
// Apple IIgs.
AppleIIgsROM00,
AppleIIgsROM01,
AppleIIgsROM03,
AppleIIgsMicrocontrollerROM03,
AppleIIgsCharacter,
// Atari ST.
AtariSTTOS100,
AtariSTTOS104,
AtariSTEmuTOS192,
// ColecoVision.
ColecoVisionBIOS,
// Commodore 1540/1541.
Commodore1540,
Commodore1541,
// Disk II.
DiskIIStateMachine16Sector,
DiskIIBoot16Sector,
DiskIIStateMachine13Sector,
DiskIIBoot13Sector,
// Macintosh.
Macintosh128k,
Macintosh512k,
MacintoshPlus,
// Master System.
MasterSystemJapaneseBIOS,
MasterSystemWesternBIOS,
// MSX.
MSXGenericBIOS,
MSXJapaneseBIOS,
MSXAmericanBIOS,
MSXEuropeanBIOS,
MSXDOS,
// Oric.
OricColourROM,
OricBASIC10,
OricBASIC11,
OricPravetzBASIC,
OricByteDrive500,
OricJasmin,
OricMicrodisc,
Oric8DOSBoot,
// Sinclair QL.
SinclairQLJS,
// Vic-20.
Vic20BASIC,
Vic20EnglishCharacters,
Vic20EnglishPALKernel,
Vic20EnglishNTSCKernel,
Vic20DanishCharacters,
Vic20DanishKernel,
Vic20JapaneseCharacters,
Vic20JapaneseKernel,
Vic20SwedishCharacters,
Vic20SwedishKernel,
// ZX80/81.
ZX80,
ZX81,
// ZX Spectrum.
Spectrum48k,
Spectrum128k,
SpecrumPlus2,
SpectrumPlus3,
};
using Map = std::map<ROM::Name, std::vector<uint8_t>>;
struct Description {
/// The ROM's enum name.
Name name = Name::None;
/// The machine with which this ROM is associated, in a form that is safe for using as
/// part of a file name.
std::string machine_name;
/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket
/// clause, etc, e.g. "the Electron MOS 1.0".
std::string descriptive_name;
/// All idiomatic file name for this ROM, e.g. "os10.rom".
std::vector<std::string> file_names;
/// The expected size of this ROM in bytes, e.g. 32768.
size_t size = 0;
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
/// to exclude ROMs where the user's intent is otherwise clear.
std::set<uint32_t> crc32s;
/// Constructs the @c Description that correlates to @c name.
Description(Name name);
/// Constructs the @c Description that correlates to @c crc32, if any.
static std::optional<Description> from_crc(uint32_t crc32);
enum DescriptionFlag {
Size = 1 << 0,
CRC = 1 << 1,
Filename = 1 << 2,
};
/// Provides a single-line of text describing this ROM, including the usual base text
/// plus all the fields provided as @c flags .
std::string description(int flags) const;
private:
template <typename FileNameT, typename CRC32T> Description(
Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = uint32_t(0)
) : name{name}, machine_name{machine_name}, descriptive_name{descriptive_name}, file_names{file_names}, size{size}, crc32s{crc32s} {
// Slightly lazy: deal with the case where the constructor wasn't provided with any
// CRCs by spotting that the set has exactly one member, which has value 0. The alternative
// would be to provide a partial specialisation that never put anything into the set.
if(this->crc32s.size() == 1 && !*this->crc32s.begin()) {
this->crc32s.clear();
}
}
};
/// @returns a vector of all possible instances of ROM::Description — i.e. descriptions of every ROM
/// currently known to the ROM catalogue.
std::vector<Description> all_descriptions();
struct Request {
Request(Name name, bool optional = false);
Request() {}
/// Forms the request that would be satisfied by @c this plus the right-hand side.
Request operator &&(const Request &);
/// Forms the request that would be satisfied by either @c this or the right-hand side.
Request operator ||(const Request &);
/// Inspects the ROMMap to ensure that it satisfies this @c Request.
/// @c returns @c true if the request is satisfied; @c false otherwise.
///
/// All ROMs in the map will be resized to their idiomatic sizes.
bool validate(Map &) const;
/// Returns a flattened array of all @c ROM::Descriptions that relate to anything
/// anywhere in this ROM request.
std::vector<Description> all_descriptions() const;
/// @returns @c true if this request is empty, i.e. would be satisfied with no ROMs; @c false otherwise.
bool empty();
/// @returns what remains of this ROM request given that everything in @c map has been found.
Request subtract(const ROM::Map &map) const;
enum class ListType {
Any, All, Single
};
void visit(
const std::function<void(ListType, size_t size)> &enter_list,
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
) const;
enum class LineItem {
NewList, Description
};
void visit(
const std::function<void(LineItem, ListType, int level, const ROM::Description *, bool is_optional, size_t remaining)> &add_item
) const;
/// @returns a full bullet-pointed list of the requirements of this request, including
/// appropriate conjuntives. This text is intended to be glued to the end of an opening
/// portion of a sentence, e.g. "Please supply" + request.description(0, L'*').
std::wstring description(int description_flags, wchar_t bullet_point);
private:
struct Node {
enum class Type {
Any, All, One
};
Type type = Type::One;
Name name = Name::None;
/// @c true if this ROM is optional for machine startup. Generally indicates something
/// that would make emulation more accurate, but not sufficiently so to make it
/// a necessity.
bool is_optional = false;
std::vector<Node> children;
void add_descriptions(std::vector<Description> &) const;
bool validate(Map &) const;
void visit(
const std::function<void(ListType, size_t)> &enter_list,
const std::function<void(void)> &exit_list,
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
) const;
bool subtract(const ROM::Map &map);
void sort() {
// Don't do a full sort, but move anything optional to the back.
// This makes them print more nicely; it's a human-facing tweak only.
ssize_t index = ssize_t(children.size() - 1);
bool has_seen_non_optional = false;
while(index >= 0) {
has_seen_non_optional |= !children[size_t(index)].is_optional;
if(children[size_t(index)].is_optional && has_seen_non_optional) {
std::rotate(children.begin() + index, children.begin() + index + 1, children.end());
}
--index;
}
}
};
Node node;
Request append(Node::Type type, const Request &rhs);
};
}
#endif /* ROMCatalogue_hpp */

View File

@ -12,6 +12,11 @@
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B0333B02094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B049CDC1DA3C82F00322067 /* BCDTest.swift */; };
4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; };
4B051C922669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */; };
4B051C93266D9D6900CA44E8 /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
4B051C95266EF50200CA44E8 /* AppleIIOptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C94266EF50200CA44E8 /* AppleIIOptionsPanel.swift */; };
4B051C97266EF5F600CA44E8 /* CSAppleII.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B051C96266EF5F600CA44E8 /* CSAppleII.mm */; };
4B05401E219D1618001BF69C /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B05401D219D1618001BF69C /* ScanTarget.cpp */; };
4B05401F219D1618001BF69C /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B05401D219D1618001BF69C /* ScanTarget.cpp */; };
4B055A7A1FAE78A00060FFFF /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; };
@ -566,7 +571,6 @@
4B9F11CA2272433900701480 /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
4B9F11CC22729B3600701480 /* OPCLOGR2.BIN in Resources */ = {isa = PBXBuildFile; fileRef = 4B9F11CB22729B3500701480 /* OPCLOGR2.BIN */; };
4BA0F68E1EEA0E8400E9489E /* ZX8081.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BA0F68C1EEA0E8400E9489E /* ZX8081.cpp */; };
4BA3189422E7A4CA00D18CFA /* ROMImages in Resources */ = {isa = PBXBuildFile; fileRef = 4BC9DF441D044FCA00F44158 /* ROMImages */; };
4BA61EB01D91515900B3C876 /* NSData+StdVector.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */; };
4BA91E1D216D85BA00F79557 /* MasterSystemVDPTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BA91E1C216D85BA00F79557 /* MasterSystemVDPTests.mm */; };
4BAD13441FF709C700FD114A /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0E61051FF34737002A9DBD /* MSX.cpp */; };
@ -1019,6 +1023,11 @@
4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = "<group>"; };
4B04B65622A58CB40006AB58 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ROMCatalogue.cpp; sourceTree = "<group>"; };
4B051C5926670A9300CA44E8 /* ROMCatalogue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMCatalogue.hpp; sourceTree = "<group>"; };
4B051C94266EF50200CA44E8 /* AppleIIOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleIIOptionsPanel.swift; sourceTree = "<group>"; };
4B051C96266EF5F600CA44E8 /* CSAppleII.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAppleII.mm; sourceTree = "<group>"; };
4B051C98266EF60500CA44E8 /* CSAppleII.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSAppleII.h; sourceTree = "<group>"; };
4B05401D219D1618001BF69C /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
4B055A6A1FAE763F0060FFFF /* Clock Signal Kiosk */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "Clock Signal Kiosk"; sourceTree = BUILT_PRODUCTS_DIR; };
4B055A771FAE78210060FFFF /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = ../../../../Library/Frameworks/SDL2.framework; sourceTree = SOURCE_ROOT; };
@ -2401,8 +2410,10 @@
4B2A53981D117D36003C6002 /* Wrappers */ = {
isa = PBXGroup;
children = (
4B051C98266EF60500CA44E8 /* CSAppleII.h */,
4B2A53991D117D36003C6002 /* CSAtari2600.h */,
4B14978D1EE4B4D200CE2596 /* CSZX8081.h */,
4B051C96266EF5F600CA44E8 /* CSAppleII.mm */,
4B2A539A1D117D36003C6002 /* CSAtari2600.mm */,
4B14978E1EE4B4D200CE2596 /* CSZX8081.mm */,
);
@ -2415,11 +2426,13 @@
4B055ABE1FAE98000060FFFF /* MachineForTarget.cpp */,
4B2B3A481F9B8FA70062DABF /* MemoryFuzzer.cpp */,
4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */,
4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */,
4B17B58920A8A9D9007CCA8F /* StringSerialiser.cpp */,
4B2B3A471F9B8FA70062DABF /* Typer.cpp */,
4B055ABF1FAE98000060FFFF /* MachineForTarget.hpp */,
4B2B3A491F9B8FA70062DABF /* MemoryFuzzer.hpp */,
4BCE005C227D30CC000CA200 /* MemoryPacker.hpp */,
4B051C5926670A9300CA44E8 /* ROMCatalogue.hpp */,
4B17B58A20A8A9D9007CCA8F /* StringSerialiser.hpp */,
4B79A4FE1FC9082300EEDAD5 /* TypedDynamicMachine.hpp */,
4B2B3A4A1F9B8FA70062DABF /* Typer.hpp */,
@ -2771,6 +2784,7 @@
4B55CE551C3B7D360093A61B /* Documents */ = {
isa = PBXGroup;
children = (
4B051C94266EF50200CA44E8 /* AppleIIOptionsPanel.swift */,
4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */,
4B55CE5E1C3B7D960093A61B /* MachineDocument.swift */,
4B8FE2211DA19FB20090D3CE /* MachinePanel.swift */,
@ -4762,7 +4776,7 @@
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */,
4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */,
4BA3189422E7A4CA00D18CFA /* ROMImages in Resources */,
4B051C93266D9D6900CA44E8 /* ROMImages in Resources */,
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */,
4B1497981EE4B97F00CE2596 /* ZX8081Options.xib in Resources */,
@ -5321,6 +5335,7 @@
4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */,
4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
4B0F1C242605996900B85C66 /* ZXSpectrumTAP.cpp in Sources */,
4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */,
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
4B302185208A550100773308 /* DiskII.cpp in Sources */,
4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */,
@ -5395,11 +5410,13 @@
4B4518861F75E91A00926311 /* MFMDiskController.cpp in Sources */,
4B0ACC2C23775819008902D0 /* IntelligentKeyboard.cpp in Sources */,
4B92E26A234AE35100CD6D1B /* MFP68901.cpp in Sources */,
4B051C97266EF5F600CA44E8 /* CSAppleII.mm in Sources */,
4B0ACC2A23775819008902D0 /* Video.cpp in Sources */,
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */,
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
4B051C95266EF50200CA44E8 /* AppleIIOptionsPanel.swift in Sources */,
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
@ -5537,6 +5554,7 @@
4B15A9FC208249BB005E6C8D /* StaticAnalyser.cpp in Sources */,
4B5FADC01DE3BF2B00AEC565 /* Microdisc.cpp in Sources */,
4B0F1BDA2602FF9800B85C66 /* Video.cpp in Sources */,
4B051C922669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */,
4B54C0C81F8D91E50050900F /* Keyboard.cpp in Sources */,
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */,
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */,

View File

@ -56,6 +56,18 @@
argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Desktop/Soft/Apple II/WOZs/Prince of Persia side A.woz&quot;"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--rompath=~/ROMs"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--has-disk-drive"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Macintosh/MusicWorks 0.42.image&quot;"
isEnabled = "NO">
@ -70,11 +82,11 @@
</CommandLineArgument>
<CommandLineArgument
argument = "--volume=0.001"
isEnabled = "NO">
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "--new=amstradcpc"
isEnabled = "YES">
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/ColecoVision/Galaxian (1983)(Atari).col&quot;"
@ -116,6 +128,14 @@
argument = "--model=cpc6128"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--new=appleii"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--use-square-pixels"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
</LaunchAction>
<ProfileAction

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -13,17 +13,27 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="AppleIIOptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="80" y="150" width="200" height="61"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="contentRect" x="80" y="150" width="200" height="159"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" id="tpZ-0B-QQu">
<rect key="frame" x="0.0" y="0.0" width="200" height="61"/>
<rect key="frame" x="0.0" y="0.0" width="200" height="84"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kDb-7g-cVx">
<rect key="frame" x="18" y="47" width="162" height="18"/>
<buttonCell key="cell" type="check" title="Use Square Pixels" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="h9q-Wb-em8">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="optionDidChange:" target="ZW7-Bw-4RP" id="pNS-aK-0no"/>
</connections>
</button>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ex3-VM-58z">
<rect key="frame" x="18" y="17" width="165" height="25"/>
<rect key="frame" x="17" y="16" width="167" height="25"/>
<popUpButtonCell key="cell" type="push" title="Colour" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="gOu-dv-tre" id="u3N-Je-c2L">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
@ -40,16 +50,20 @@
</popUpButton>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="ex3-VM-58z" secondAttribute="bottom" constant="20" id="4ZS-q5-TJL"/>
<constraint firstAttribute="bottom" secondItem="ex3-VM-58z" secondAttribute="bottom" constant="20" symbolic="YES" id="4ZS-q5-TJL"/>
<constraint firstItem="ex3-VM-58z" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="8Pj-Ns-TrJ"/>
<constraint firstAttribute="trailing" secondItem="kDb-7g-cVx" secondAttribute="trailing" constant="20" symbolic="YES" id="KHa-of-eY7"/>
<constraint firstItem="kDb-7g-cVx" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" symbolic="YES" id="OcZ-Xa-394"/>
<constraint firstAttribute="trailing" secondItem="ex3-VM-58z" secondAttribute="trailing" constant="20" id="QWA-lY-ugz"/>
<constraint firstItem="ex3-VM-58z" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="szw-WO-3tS"/>
<constraint firstItem="ex3-VM-58z" firstAttribute="top" secondItem="kDb-7g-cVx" secondAttribute="bottom" constant="8" id="jDW-N8-c4V"/>
<constraint firstItem="kDb-7g-cVx" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" symbolic="YES" id="wdj-PF-zxO"/>
</constraints>
</view>
<connections>
<outlet property="displayTypeButton" destination="ex3-VM-58z" id="lmZ-aN-lcj"/>
<outlet property="squarePixelButton" destination="kDb-7g-cVx" id="zbx-o8-7yC"/>
</connections>
<point key="canvasLocation" x="-161" y="38.5"/>
<point key="canvasLocation" x="-161" y="104.5"/>
</window>
</objects>
</document>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -13,17 +13,17 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="80" y="150" width="200" height="121"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" id="aQh-Pm-DEo">
<rect key="frame" x="0.0" y="0.0" width="200" height="121"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rQO-uD-fwn">
<rect key="frame" x="14" y="73" width="86" height="32"/>
<rect key="frame" x="13" y="74" width="88" height="32"/>
<buttonCell key="cell" type="push" title="Reset" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="l3H-0m-aK0">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@ -33,7 +33,7 @@
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="3qw-C1-NYW">
<rect key="frame" x="18" y="58" width="164" height="18"/>
<rect key="frame" x="18" y="58" width="162" height="18"/>
<buttonCell key="cell" type="check" title="Black and White" bezelStyle="regularSquare" imagePosition="left" inset="2" id="UP7-mf-IKo">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -43,7 +43,7 @@
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="Xbc-cw-Sc2">
<rect key="frame" x="18" y="38" width="164" height="18"/>
<rect key="frame" x="18" y="36" width="162" height="18"/>
<buttonCell key="cell" type="check" title="Left Player Difficulty" bezelStyle="regularSquare" imagePosition="left" inset="2" id="wlJ-8s-PEh">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -53,7 +53,7 @@
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="kPV-Tm-TTc">
<rect key="frame" x="18" y="18" width="164" height="18"/>
<rect key="frame" x="18" y="14" width="162" height="18"/>
<buttonCell key="cell" type="check" title="Right Player Difficulty" bezelStyle="regularSquare" imagePosition="left" inset="2" id="F05-cA-66S">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@ -63,7 +63,7 @@
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nt7-8K-xY9">
<rect key="frame" x="100" y="73" width="86" height="32"/>
<rect key="frame" x="99" y="74" width="88" height="32"/>
<buttonCell key="cell" type="push" title="Select" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8Na-Z1-EXS">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>

View File

@ -137,22 +137,22 @@ class MachineDocument:
volumeSlider.floatValue = userDefaultsVolume()
}
private var missingROMs: [CSMissingROM] = []
private var missingROMs: String = ""
func configureAs(_ analysis: CSStaticAnalyser) {
self.machineDescription = analysis
actionLock.lock()
drawLock.lock()
let missingROMs = NSMutableArray()
let missingROMs = NSMutableString()
if let machine = CSMachine(analyser: analysis, missingROMs: missingROMs) {
setRomRequesterIsVisible(false)
self.machine = machine
machine.setVolume(userDefaultsVolume())
setupMachineOutput()
} else {
// Store the selected machine and list of missing ROMs, and
// show the missing ROMs dialogue.
self.missingROMs = missingROMs.map({$0 as! CSMissingROM})
self.missingROMs = missingROMs as String
requestRoms()
}
@ -413,23 +413,42 @@ class MachineDocument:
@IBOutlet var romReceiverErrorField: NSTextField?
@IBOutlet var romReceiverView: CSROMReceiverView?
private var romRequestBaseText = ""
private func setRomRequesterIsVisible(_ visible : Bool) {
if !visible && self.romRequesterPanel == nil {
return;
}
if self.romRequesterPanel!.isVisible == visible {
return
}
if visible {
self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil)
} else {
self.windowControllers[0].window?.endSheet(self.romRequesterPanel!)
}
}
func requestRoms() {
// Don't act yet if there's no window controller yet.
if self.windowControllers.count == 0 {
return
}
// Load the ROM requester dialogue.
Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil)
self.romReceiverView!.delegate = self
self.romRequestBaseText = romRequesterText!.stringValue
romReceiverErrorField?.alphaValue = 0.0
// Load the ROM requester dialogue if it's not already loaded.
if self.romRequesterPanel == nil {
Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil)
self.romReceiverView!.delegate = self
self.romRequestBaseText = romRequesterText!.stringValue
romReceiverErrorField?.alphaValue = 0.0
}
// Populate the current absentee list.
populateMissingRomList()
// Show the thing.
self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil)
setRomRequesterIsVisible(true)
}
@IBAction func cancelRequestROMs(_ sender: NSButton?) {
@ -437,89 +456,17 @@ class MachineDocument:
}
func populateMissingRomList() {
// Fill in the missing details; first build a list of all the individual
// line items.
var requestLines: [String] = []
for missingROM in self.missingROMs {
if let descriptiveName = missingROM.descriptiveName {
requestLines.append("" + descriptiveName)
} else {
requestLines.append("" + missingROM.fileName)
}
}
// Suffix everything up to the penultimate line with a semicolon;
// the penultimate line with a semicolon and a conjunctive; the final
// line with a full stop.
for x in 0 ..< requestLines.count {
if x < requestLines.count - 2 {
requestLines[x].append(";")
} else if x < requestLines.count - 1 {
requestLines[x].append("; and")
} else {
requestLines[x].append(".")
}
}
romRequesterText!.stringValue = self.romRequestBaseText + requestLines.joined(separator: "\n")
romRequesterText!.stringValue = self.romRequestBaseText + self.missingROMs
}
func romReceiverView(_ view: CSROMReceiverView, didReceiveFileAt URL: URL) {
// Test whether the file identified matches any of the currently missing ROMs.
// If so then remove that ROM from the missing list and update the request screen.
// If no ROMs are still missing, start the machine.
do {
let fileData = try Data(contentsOf: URL)
var didInstallRom = false
// Try to match by size first, CRC second. Accept that some ROMs may have
// some additional appended data. Arbitrarily allow them to be up to 10kb
// too large.
var index = 0
for missingROM in self.missingROMs {
if fileData.count >= missingROM.size && fileData.count < missingROM.size + 10*1024 {
// Trim to size.
let trimmedData = fileData[0 ..< missingROM.size]
// Get CRC.
if missingROM.crc32s.contains( (trimmedData as NSData).crc32 ) {
// This ROM matches; copy it into the application library,
// strike it from the missing ROM list and decide how to
// proceed.
let fileManager = FileManager.default
let targetPath = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
.appendingPathComponent("ROMImages")
.appendingPathComponent(missingROM.machineName)
let targetFile = targetPath
.appendingPathComponent(missingROM.fileName)
do {
try fileManager.createDirectory(atPath: targetPath.path, withIntermediateDirectories: true, attributes: nil)
try trimmedData.write(to: targetFile)
} catch let error {
showRomReceiverError(error: "Couldn't write to application support directory: \(error)")
}
self.missingROMs.remove(at: index)
didInstallRom = true
break
}
}
index = index + 1
}
if didInstallRom {
if self.missingROMs.count == 0 {
self.windowControllers[0].window?.endSheet(self.romRequesterPanel!)
configureAs(self.machineDescription!)
} else {
populateMissingRomList()
}
} else {
showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)")
}
} catch let error {
showRomReceiverError(error: "Couldn't read file at \(URL.absoluteString): \(error)")
if CSMachine.attemptInstallROM(URL) {
configureAs(self.machineDescription!)
} else {
showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)")
}
}

View File

@ -33,20 +33,15 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
CSMachineKeyboardInputModeJoystick,
};
@interface CSMissingROM: NSObject
@property (nonatomic, readonly, nonnull) NSString *machineName;
@property (nonatomic, readonly, nonnull) NSString *fileName;
@property (nonatomic, readonly, nullable) NSString *descriptiveName;
@property (nonatomic, readonly) NSUInteger size;
@property (nonatomic, readonly, nonnull) NSArray<NSNumber *> *crc32s;
@end
// Deliberately low; to ensure CSMachine has been declared as an @class already.
#import "CSAtari2600.h"
#import "CSZX8081.h"
#import "CSAppleII.h"
@interface CSMachine : NSObject
+ (BOOL)attemptInstallROM:(nonnull NSURL *)url;
- (nonnull instancetype)init NS_UNAVAILABLE;
/*!
@ -56,7 +51,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
@param missingROMs An array that is filled with a list of ROMs that the machine requested but which
were not found; populated only if this `init` has failed.
*/
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableString *)missingROMs NS_DESIGNATED_INITIALIZER;
- (float)idealSamplingRateFromRange:(NSRange)range;
@property (readonly, getter=isStereo) BOOL stereo;
@ -109,5 +104,6 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
@property (nonatomic, readonly, nullable) CSAtari2600 *atari2600;
@property (nonatomic, readonly, nullable) CSZX8081 *zx8081;
@property (nonatomic, readonly, nullable) CSAppleII *appleII;
@end

View File

@ -31,6 +31,8 @@
#include <atomic>
#include <bitset>
#include <codecvt>
#include <locale>
@interface CSMachine() <CSScanTargetViewDisplayLinkDelegate>
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
@ -74,28 +76,6 @@ struct ActivityObserver: public Activity::Observer {
__unsafe_unretained CSMachine *machine;
};
@interface CSMissingROM (/*Mutability*/)
@property (nonatomic, nonnull, copy) NSString *machineName;
@property (nonatomic, nonnull, copy) NSString *fileName;
@property (nonatomic, nullable, copy) NSString *descriptiveName;
@property (nonatomic, readwrite) NSUInteger size;
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
@end
@implementation CSMissingROM
@synthesize machineName=_machineName;
@synthesize fileName=_fileName;
@synthesize descriptiveName=_descriptiveName;
@synthesize size=_size;
@synthesize crc32s=_crc32s;
- (NSString *)description {
return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s];
}
@end
@implementation CSMachine {
SpeakerDelegate _speakerDelegate;
ActivityObserver _activityObserver;
@ -126,35 +106,18 @@ struct ActivityObserver: public Activity::Observer {
NSMutableArray<dispatch_block_t> *_inputEvents;
}
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableString *)missingROMs {
self = [super init];
if(self) {
_analyser = result;
Machine::Error error;
std::vector<ROMMachine::ROM> missing_roms;
ROM::Request missing_roms;
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error));
if(!_machine) {
for(const auto &missing_rom : missing_roms) {
CSMissingROM *rom = [[CSMissingROM alloc] init];
// Copy/convert the primitive fields.
rom.machineName = @(missing_rom.machine_name.c_str());
rom.fileName = @(missing_rom.file_name.c_str());
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : @(missing_rom.descriptive_name.c_str());
rom.size = missing_rom.size;
// Convert the CRC list.
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] initWithCapacity:missing_rom.crc32s.size()];
for(const auto &crc : missing_rom.crc32s) {
[crc32s addObject:@(crc)];
}
rom.crc32s = crc32s;
// Add to the missing list.
[missingROMs addObject:rom];
}
std::wstring_convert<std::codecvt_utf8<wchar_t>> wstring_converter;
const std::wstring description = missing_roms.description(0, L'•');
[missingROMs appendString:[NSString stringWithUTF8String:wstring_converter.to_bytes(description).c_str()]];
return nil;
}
@ -614,6 +577,10 @@ struct ActivityObserver: public Activity::Observer {
return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self];
}
- (CSAppleII *)appleII {
return [[CSAppleII alloc] initWithAppleII:_machine->raw_pointer() owner:self];
}
#pragma mark - Input device queries
- (BOOL)hasJoystick {
@ -783,4 +750,8 @@ struct ActivityObserver: public Activity::Observer {
_timer = nil;
}
+ (BOOL)attemptInstallROM:(NSURL *)url {
return CSInstallROM(url);
}
@end

View File

@ -8,4 +8,5 @@
#include "ROMMachine.hpp"
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms = nullptr);
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing = nullptr);
BOOL CSInstallROM(NSURL *);

View File

@ -11,47 +11,96 @@
#import "NSBundle+DataResource.h"
#import "NSData+StdVector.h"
#import "NSData+CRC32.h"
#include <string>
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms) {
return [missing_roms] (const std::vector<ROMMachine::ROM> &roms) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
namespace {
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
for(const auto &rom: roms) {
NSData *fileData;
NSString *const subdirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:rom.machine_name.c_str()]];
NSString *directoryFor(const ROM::Description &description) {
return [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:description.machine_name.c_str()]];
}
// Check for this file first within the application support directories.
for(NSURL *supportURL in supportURLs) {
NSURL *const fullURL = [[supportURL URLByAppendingPathComponent:subdirectory]
URLByAppendingPathComponent:[NSString stringWithUTF8String:rom.file_name.c_str()]];
fileData = [NSData dataWithContentsOfURL:fullURL];
if(fileData) break;
NSArray<NSURL *> *urlsFor(const ROM::Description &description, const std::string &file_name) {
NSMutableArray<NSURL *> *const urls = [[NSMutableArray alloc] init];
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
NSString *const subdirectory = directoryFor(description);
for(NSURL *supportURL in supportURLs) {
[urls addObject:[[supportURL URLByAppendingPathComponent:subdirectory]
URLByAppendingPathComponent:[NSString stringWithUTF8String:file_name.c_str()]]];
}
return urls;
}
}
BOOL CSInstallROM(NSURL *url) {
NSData *const data = [NSData dataWithContentsOfURL:url];
if(!data) return NO;
// Try for a direct CRC match.
std::optional<ROM::Description> target_description;
target_description = ROM::Description::from_crc(uint32_t(data.crc32.integerValue));
// See whether there's an acceptable trimming that creates a CRC match.
if(!target_description) {
const std::vector<ROM::Description> descriptions = ROM::all_descriptions();
for(const auto &description: descriptions) {
if(description.size > data.length) continue;
NSData *const trimmedData = [data subdataWithRange:NSMakeRange(0, description.size)];
if(description.crc32s.find(uint32_t(trimmedData.crc32.unsignedIntValue)) != description.crc32s.end()) {
target_description = description;
break;
}
}
}
// Failing that, check inside the application bundle.
if(!fileData) {
fileData = [[NSBundle mainBundle]
dataForResource:[NSString stringWithUTF8String:rom.file_name.c_str()]
withExtension:nil
subdirectory:subdirectory];
}
// If no destination was found, stop.
if(!target_description) {
return NO;
}
// Store an appropriate result, accumulating a list of the missing if requested.
if(!fileData) {
results.emplace_back(nullptr);
// Copy the data to its destination and report success.
NSURL *const targetURL = [urlsFor(*target_description, target_description->file_names[0]) firstObject];
[[NSFileManager defaultManager] createDirectoryAtPath:targetURL.URLByDeletingLastPathComponent.path withIntermediateDirectories:YES attributes:nil error:nil];
[data writeToURL:targetURL atomically:NO];
if(missing_roms) {
missing_roms->push_back(rom);
return YES;
}
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing) {
return [missing] (const ROM::Request &roms) -> ROM::Map {
ROM::Map results;
for(const auto &description: roms.all_descriptions()) {
for(const auto &file_name: description.file_names) {
NSData *fileData;
// Check for this file first within the application support directories.
for(NSURL *fileURL in urlsFor(description, file_name)) {
fileData = [NSData dataWithContentsOfURL:fileURL];
if(fileData) break;
}
// Failing that, check inside the application bundle.
if(!fileData) {
fileData = [[NSBundle mainBundle]
dataForResource:[NSString stringWithUTF8String:file_name.c_str()]
withExtension:nil
subdirectory:directoryFor(description)];
}
// Store an appropriate result.
if(fileData) {
results[description.name] = fileData.stdVector8;
}
}
else {
auto data = std::make_unique<std::vector<std::uint8_t>>();
*data = fileData.stdVector8;
results.emplace_back(std::move(data));
}
}
if(missing) {
*missing = roms.subtract(results);
}
return results;

View File

@ -0,0 +1,18 @@
//
// CSAppleII.h
// Clock Signal
//
// Created by Thomas Harte on 07/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
@class CSAppleII;
#import "CSMachine.h"
@interface CSAppleII : NSObject
- (instancetype)initWithAppleII:(void *)appleII owner:(CSMachine *)machine;
@property (nonatomic, assign) BOOL useSquarePixels;
@end

View File

@ -0,0 +1,57 @@
//
// CSAppleII.m
// Clock Signal
//
// Created by Thomas Harte on 07/06/2021.
// Copyright © 2021 Thomas Harte. All rights reserved.
//
#import "CSAppleII.h"
#include "AppleII.hpp"
@implementation CSAppleII {
Apple::II::Machine *_appleII;
__weak CSMachine *_machine;
}
- (instancetype)initWithAppleII:(void *)appleII owner:(CSMachine *)machine {
self = [super init];
if(self) {
_appleII = (Apple::II::Machine *)appleII;
_machine = machine;
}
return self;
}
#pragma mark - Options
- (void)setUseSquarePixels:(BOOL)useSquarePixels {
Configurable::Device *const configurable = dynamic_cast<Configurable::Device *>(_appleII);
#ifndef NDEBUG
assert(configurable);
#endif
@synchronized(_machine) {
auto options = configurable->get_options();
#ifndef NDEBUG
assert(dynamic_cast<Apple::II::Machine::Options *>(options.get()));
#endif
auto appleii_configurable = static_cast<Apple::II::Machine::Options *>(options.get());
appleii_configurable->use_square_pixels = useSquarePixels;
configurable->set_options(options);
}
}
- (BOOL)useSquarePixels {
Configurable::Device *const configurable = dynamic_cast<Configurable::Device *>(_appleII);
@synchronized(_machine) {
auto options = configurable->get_options();
auto appleii_configurable = dynamic_cast<Apple::II::Machine::Options *>(options.get());
return appleii_configurable->use_square_pixels ? YES : NO;
}
}
@end

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -20,18 +20,16 @@
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ" customClass="CSROMReceiverView">
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav">
<rect key="frame" x="18" y="154" width="444" height="96"/>
<rect key="frame" x="18" y="186" width="444" height="64"/>
<textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia">
<font key="font" usesAppearanceFont="YES"/>
<string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.Please drag and drop the following over this text:
</string>
<string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.Please drag and drop over this text</string>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>

View File

@ -35,7 +35,8 @@ class VanillaSerialPort: public Commodore::Serial::Port {
_serialPort = std::make_shared<VanillaSerialPort>();
auto rom_fetcher = CSROMFetcher();
_c1540 = std::make_unique<Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, rom_fetcher);
auto roms = rom_fetcher(Commodore::C1540::Machine::rom_request(Commodore::C1540::Personality::C1540));
_c1540 = std::make_unique<Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, roms);
_c1540->set_serial_bus(_serialBus);
Commodore::Serial::AttachPortAndBus(_serialPort, _serialBus);
}

View File

@ -103,21 +103,20 @@ class EmuTOS: public ComparativeBusHandler {
std::unique_ptr<EmuTOS> _machine;
}
- (void)testImage:(NSString *)image trace:(NSString *)trace length:(int)length {
const std::vector<ROMMachine::ROM> rom_names = {{"AtariST", "", image.UTF8String, 0, 0 }};
const auto roms = CSROMFetcher()(rom_names);
- (void)testImage:(ROM::Name)name trace:(NSString *)trace length:(int)length {
const auto roms = CSROMFetcher()(ROM::Request(name));
NSString *const traceLocation = [[NSBundle bundleForClass:[self class]] pathForResource:trace ofType:@"trace.txt.gz"];
_machine = std::make_unique<EmuTOS>(*roms[0], traceLocation.fileSystemRepresentation);
_machine = std::make_unique<EmuTOS>(roms.find(name)->second, traceLocation.fileSystemRepresentation);
_machine->run_for(HalfCycles(length));
}
- (void)testEmuTOSStartup {
[self testImage:@"etos192uk.img" trace:@"etos192uk" length:313490];
[self testImage:ROM::Name::AtariSTEmuTOS192 trace:@"etos192uk" length:313490];
// TODO: assert that machine is now STOPped.
}
- (void)testTOSStartup {
[self testImage:@"tos100.img" trace:@"tos100" length:54011091];
[self testImage:ROM::Name::AtariSTTOS100 trace:@"tos100" length:54011091];
}
@end

View File

@ -101,10 +101,11 @@ class QL: public ComparativeBusHandler {
Tests the progression of Clock Signal's 68000 through the Sinclair QL's ROM against a known-good trace.
*/
- (void)testStartup {
const std::vector<ROMMachine::ROM> rom_names = {{"SinclairQL", "", "js.rom", 0, 0 }};
const auto roms = CSROMFetcher()(rom_names);
constexpr ROM::Name rom_name = ROM::Name::SinclairQLJS;
ROM::Request request(rom_name);
const auto roms = CSROMFetcher()(request);
NSString *const traceLocation = [[NSBundle bundleForClass:[self class]] pathForResource:@"qltrace" ofType:@".txt.gz"];
_machine = std::make_unique<QL>(*roms[0], traceLocation.UTF8String);
_machine = std::make_unique<QL>(roms.find(rom_name)->second, traceLocation.UTF8String);
// This is how many cycles it takes to exhaust the supplied trace file.
_machine->run_for(HalfCycles(23923180));

View File

@ -24,8 +24,10 @@ struct ContentionAnalysis {
HalfCycles pattern[8];
};
template <Sinclair::ZXSpectrum::VideoTiming video_timing> ContentionAnalysis analyse() {
Sinclair::ZXSpectrum::Video<video_timing> video;
using Timing = Sinclair::ZXSpectrum::Video::Timing;
template <Timing video_timing> ContentionAnalysis analyse() {
Sinclair::ZXSpectrum::Video::Video<video_timing> video;
ContentionAnalysis analysis;
// Advance to the start of the first interrupt.
@ -82,7 +84,7 @@ template <Sinclair::ZXSpectrum::VideoTiming video_timing> ContentionAnalysis ana
}
- (void)test48k {
const auto analysis = analyse<Sinclair::ZXSpectrum::VideoTiming::FortyEightK>();
const auto analysis = analyse<Timing::FortyEightK>();
// Check time from interrupt.
XCTAssertEqual(analysis.time_after_interrupt, 14335*2);
@ -105,7 +107,7 @@ template <Sinclair::ZXSpectrum::VideoTiming video_timing> ContentionAnalysis ana
}
- (void)test128k {
const auto analysis = analyse<Sinclair::ZXSpectrum::VideoTiming::OneTwoEightK>();
const auto analysis = analyse<Timing::OneTwoEightK>();
// Check time from interrupt.
XCTAssertEqual(analysis.time_after_interrupt, 14361*2);
@ -128,7 +130,7 @@ template <Sinclair::ZXSpectrum::VideoTiming video_timing> ContentionAnalysis ana
}
- (void)testPlus3 {
const auto analysis = analyse<Sinclair::ZXSpectrum::VideoTiming::Plus3>();
const auto analysis = analyse<Timing::Plus3>();
// Check time from interrupt.
XCTAssertEqual(analysis.time_after_interrupt, 14361*2);

View File

@ -251,33 +251,33 @@ void MainWindow::tile(const QMainWindow *previous) {
void MainWindow::launchMachine() {
const QStringList appDataLocations = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation);
missingRoms.clear();
ROMMachine::ROMFetcher rom_fetcher = [&appDataLocations, this]
(const std::vector<ROMMachine::ROM> &roms) -> std::vector<std::unique_ptr<std::vector<uint8_t>>> {
std::vector<std::unique_ptr<std::vector<uint8_t>>> results;
(const ROM::Request &roms) -> ROM::Map {
ROM::Map results;
for(const auto &rom: roms) {
FILE *file = nullptr;
for(const auto &path: appDataLocations) {
const std::string source = path.toStdString() + "/ROMImages/" + rom.machine_name + "/" + rom.file_name;
const std::string nativeSource = QDir::toNativeSeparators(QString::fromStdString(source)).toStdString();
for(const auto &description: roms.all_descriptions()) {
for(const auto &file_name: description.file_names) {
FILE *file = nullptr;
for(const auto &path: appDataLocations) {
const std::string source = path.toStdString() + "/ROMImages/" + description.machine_name + "/" + file_name;
const std::string nativeSource = QDir::toNativeSeparators(QString::fromStdString(source)).toStdString();
file = fopen(nativeSource.c_str(), "rb");
if(file) break;
}
file = fopen(nativeSource.c_str(), "rb");
if(file) break;
}
if(file) {
auto data = fileContentsAndClose(file);
if(data) {
results.push_back(std::move(data));
continue;
if(file) {
auto data = fileContentsAndClose(file);
if(data) {
results[description.name] = *data;
continue;
}
}
}
results.push_back(nullptr);
missingRoms.push_back(rom);
}
missingRoms = roms.subtract(results);
return results;
};
Machine::Error error;
@ -291,22 +291,7 @@ void MainWindow::launchMachine() {
// Populate request text.
QString requestText = romRequestBaseText;
size_t index = 0;
for(const auto &rom: missingRoms) {
requestText += "";
requestText += rom.descriptive_name.c_str();
++index;
if(index == missingRoms.size()) {
requestText += ".\n";
continue;
}
if(index == missingRoms.size() - 1) {
requestText += "; and\n";
continue;
}
requestText += ";\n";
}
requestText += QString::fromWCharArray(missingRoms.description(0, L'').c_str());
ui->missingROMsBox->setPlainText(requestText);
} break;
}
@ -418,7 +403,7 @@ void MainWindow::launchMachine() {
break;
case Analyser::Machine::AppleII:
addDisplayMenu(settingsPrefix, "Colour", "Monochrome", "", "");
addAppleIIMenu();
break;
case Analyser::Machine::Atari2600:
@ -686,6 +671,41 @@ void MainWindow::toggleAtari2600Switch(Atari2600Switch toggleSwitch) {
});
}
void MainWindow::addAppleIIMenu() {
// Add the standard display settings.
addDisplayMenu("appleII", "Colour", "Monochrome", "", "");
// Add an additional tick box, for square pixels.
QAction *const squarePixelsAction = new QAction(tr("Square Pixels"));
squarePixelsAction->setCheckable(true);
connect(squarePixelsAction, &QAction::triggered, this, [=] {
std::lock_guard lock_guard(machineMutex);
// Apply the new setting to the machine.
setAppleIISquarePixels(squarePixelsAction->isChecked());
// Also store it.
Settings settings;
settings.setValue("appleII.squarePixels", squarePixelsAction->isChecked());
});
displayMenu->addAction(squarePixelsAction);
// Establish initial selection.
Settings settings;
const bool useSquarePixels = settings.value("appleII.squarePixels").toBool();
squarePixelsAction->setChecked(useSquarePixels);
setAppleIISquarePixels(useSquarePixels);
}
void MainWindow::setAppleIISquarePixels(bool squarePixels) {
Configurable::Device *const configurable = machine->configurable_device();
auto options = configurable->get_options();
auto appleii_options = static_cast<Apple::II::Machine::Options *>(options.get());
appleii_options->use_square_pixels = squarePixels;
configurable->set_options(options);
}
void MainWindow::speaker_did_complete_samples(Outputs::Speaker::Speaker *, const std::vector<int16_t> &buffer) {
audioBuffer.write(buffer);
}
@ -736,28 +756,23 @@ void MainWindow::dropEvent(QDropEvent* event) {
CRC::CRC32 generator;
const uint32_t crc = generator.compute_crc(*contents);
bool wasUsed = false;
for(const auto &rom: missingRoms) {
if(std::find(rom.crc32s.begin(), rom.crc32s.end(), crc) != rom.crc32s.end()) {
foundROM = true;
std::optional<ROM::Description> target_rom = ROM::Description::from_crc(crc);
if(target_rom) {
// Ensure the destination folder exists.
const std::string path = appDataLocation + "/ROMImages/" + target_rom->machine_name;
const QDir dir(QString::fromStdString(path));
if (!dir.exists())
dir.mkpath(".");
// Ensure the destination folder exists.
const std::string path = appDataLocation + "/ROMImages/" + rom.machine_name;
const QDir dir(QString::fromStdString(path));
if (!dir.exists())
dir.mkpath(".");
// Write into place.
const std::string destination = QDir::toNativeSeparators(QString::fromStdString(path+ "/" + target_rom->file_names[0])).toStdString();
FILE *const target = fopen(destination.c_str(), "wb");
fwrite(contents->data(), 1, contents->size(), target);
fclose(target);
// Write into place.
const std::string destination = QDir::toNativeSeparators(QString::fromStdString(path+ "/" + rom.file_name)).toStdString();
FILE *const target = fopen(destination.c_str(), "wb");
fwrite(contents->data(), 1, contents->size(), target);
fclose(target);
wasUsed = true;
}
}
if(!wasUsed) {
// Note that at least one meaningful ROM was supplied.
foundROM = true;
} else {
if(!unusedRoms.isEmpty()) unusedRoms += ", ";
unusedRoms += url.fileName();
}

View File

@ -20,8 +20,9 @@
#include "../../Activity/Observer.hpp"
// There are machine-specific controls for the following:
#include "../../Machines/Sinclair/ZX8081/ZX8081.hpp"
#include "../../Machines/Apple/AppleII/AppleII.hpp"
#include "../../Machines/Atari/2600/Atari2600.hpp"
#include "../../Machines/Sinclair/ZX8081/ZX8081.hpp"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
@ -60,7 +61,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
void launchMachine();
QString romRequestBaseText;
std::vector<ROMMachine::ROM> missingRoms;
ROM::Request missingRoms;
// File drag and drop is supported.
void dragEnterEvent(QDragEnterEvent* event) override;
@ -137,6 +138,9 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
void addAtari2600Menu();
void toggleAtari2600Switch(Atari2600Switch toggleSwitch);
void addAppleIIMenu();
void setAppleIISquarePixels(bool);
void setWindowTitle();
bool mouseIsCaptured = false;

View File

@ -860,9 +860,7 @@
<property name="plainText">
<string>Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop the following over this window:
</string>
Please drag and drop over this window</string>
</property>
</widget>
</item>

View File

@ -694,11 +694,10 @@ int main(int argc, char *argv[]) {
// /usr/local/share/CLK/[system];
// /usr/share/CLK/[system]; or
// [user-supplied path]/[system]
std::vector<ROMMachine::ROM> requested_roms;
ROMMachine::ROMFetcher rom_fetcher = [&requested_roms, &arguments]
(const std::vector<ROMMachine::ROM> &roms) -> std::vector<std::unique_ptr<std::vector<uint8_t>>> {
requested_roms.insert(requested_roms.end(), roms.begin(), roms.end());
ROM::Request missing_roms;
std::vector<std::string> checked_paths;
ROMMachine::ROMFetcher rom_fetcher = [&missing_roms, &arguments, &checked_paths]
(const ROM::Request &roms) -> ROM::Map {
std::vector<std::string> paths = {
"/usr/local/share/CLK/",
"/usr/share/CLK/"
@ -706,41 +705,56 @@ int main(int argc, char *argv[]) {
const auto rompath = arguments.selections.find("rompath");
if(rompath != arguments.selections.end()) {
if(rompath->second.back() != '/') {
paths.push_back(rompath->second + "/");
} else {
paths.push_back(rompath->second);
std::string path = rompath->second;
// Ensure the path ends in a slash.
if(path.back() != '/') {
path += '/';
}
// If ~ is present, expand it to %HOME%.
const size_t tilde_position = path.find("~");
if(tilde_position != std::string::npos) {
path.replace(tilde_position, 1, getenv("HOME"));
}
paths.push_back(path);
}
ROM::Map results;
for(const auto &description: roms.all_descriptions()) {
for(const auto &file_name: description.file_names) {
FILE *file = nullptr;
std::vector<std::string> rom_checked_paths;
for(const auto &path: paths) {
std::string local_path = path + description.machine_name + "/" + file_name;
file = std::fopen(local_path.c_str(), "rb");
rom_checked_paths.push_back(local_path);
if(file) break;
}
if(!file) {
std::copy(rom_checked_paths.begin(), rom_checked_paths.end(), std::back_inserter(checked_paths));
continue;
}
std::vector<uint8_t> data;
std::fseek(file, 0, SEEK_END);
data.resize(std::ftell(file));
std::fseek(file, 0, SEEK_SET);
std::size_t read = fread(data.data(), 1, data.size(), file);
std::fclose(file);
if(read == data.size()) {
results[description.name] = std::move(data);
} else {
std::copy(rom_checked_paths.begin(), rom_checked_paths.end(), std::back_inserter(checked_paths));
}
}
}
std::vector<std::unique_ptr<std::vector<uint8_t>>> results;
for(const auto &rom: roms) {
FILE *file = nullptr;
for(const auto &path: paths) {
std::string local_path = path + rom.machine_name + "/" + rom.file_name;
file = std::fopen(local_path.c_str(), "rb");
if(file) break;
}
if(!file) {
results.emplace_back(nullptr);
continue;
}
auto data = std::make_unique<std::vector<uint8_t>>();
std::fseek(file, 0, SEEK_END);
data->resize(std::ftell(file));
std::fseek(file, 0, SEEK_SET);
std::size_t read = fread(data->data(), 1, data->size(), file);
std::fclose(file);
if(read == data->size())
results.emplace_back(std::move(data));
else
results.emplace_back(nullptr);
}
missing_roms = roms.subtract(results);
return results;
};
@ -758,24 +772,22 @@ int main(int argc, char *argv[]) {
if(!machine) {
switch(error) {
default: break;
case ::Machine::Error::MissingROM:
std::cerr << "Could not find system ROMs; please install to /usr/local/share/CLK/ or /usr/share/CLK/, or provide a --rompath." << std::endl;
std::cerr << "One or more of the following was needed but not found:" << std::endl;
for(const auto &rom: requested_roms) {
std::cerr << rom.machine_name << '/' << rom.file_name << " (";
if(!rom.descriptive_name.empty()) {
std::cerr << rom.descriptive_name << "; ";
}
std::cerr << "usual crc32s: ";
bool is_first = true;
for(const auto crc32: rom.crc32s) {
if(!is_first) std::cerr << ", ";
is_first = false;
std::cerr << std::hex << std::setfill('0') << std::setw(8) << crc32;
}
std::cerr << ")" << std::endl;
case ::Machine::Error::MissingROM: {
std::cerr << "Could not find system ROMs; please install to /usr/local/share/CLK/ or /usr/share/CLK/, or provide a --rompath, e.g. --rompath=~/ROMs." << std::endl;
std::cerr << "Needed — but didn't find —";
using DescriptionFlag = ROM::Description::DescriptionFlag;
std::wcerr << missing_roms.description(DescriptionFlag::Filename | DescriptionFlag::CRC, L'*');
std::cerr << std::endl << std::endl << "Searched unsuccessfully: ";
bool is_first = true;
for(const auto &path: checked_paths) {
if(!is_first) std::cerr << "; ";
std::cerr << path;
is_first = false;
}
break;
std::cerr << std::endl;
} break;
}
return EXIT_FAILURE;