mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-25 03:32:01 +00:00
Merge branch 'master' into Enterprise
This commit is contained in:
commit
8e0a6df03b
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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_);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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_);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
482
Machines/Utility/ROMCatalogue.cpp
Normal file
482
Machines/Utility/ROMCatalogue.cpp
Normal 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;
|
||||
|
||||
}
|
||||
}
|
269
Machines/Utility/ROMCatalogue.hpp
Normal file
269
Machines/Utility/ROMCatalogue.hpp
Normal 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 */
|
@ -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 */,
|
||||
|
@ -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 = ""/Users/thomasharte/Desktop/Soft/Apple II/WOZs/Prince of Persia side A.woz""
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--rompath=~/ROMs"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--has-disk-drive"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Macintosh/MusicWorks 0.42.image""
|
||||
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 = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/ColecoVision/Galaxian (1983)(Atari).col""
|
||||
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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"/>
|
||||
|
@ -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)")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 *);
|
||||
|
@ -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;
|
||||
|
18
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAppleII.h
Normal file
18
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAppleII.h
Normal 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
|
57
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAppleII.mm
Normal file
57
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAppleII.mm
Normal 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
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user