diff --git a/Components/9918/9918.cpp b/Components/9918/9918.cpp index b6f786b50..aef510c59 100644 --- a/Components/9918/9918.cpp +++ b/Components/9918/9918.cpp @@ -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 diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index 32c0647a3..3b1b43bae 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -787,45 +787,43 @@ template class ConcreteMachine: ay_.ay().set_port_handler(&key_state_); // construct the list of necessary ROMs - const std::string machine_name = "AmstradCPC"; - std::vector 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; diff --git a/Machines/Apple/AppleII/AppleII.cpp b/Machines/Apple/AppleII/AppleII.cpp index 06044b807..80436626d 100644 --- a/Machines/Apple/AppleII/AppleII.cpp +++ b/Machines/Apple/AppleII/AppleII.cpp @@ -377,49 +377,55 @@ template class ConcreteMachine: // Pick the required ROMs. using Target = Analyser::Static::AppleII::Target; - const std::string machine_name = "AppleII"; - std::vector 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 class ConcreteMachine: std::unique_ptr get_options() final { auto options = std::make_unique(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 &str) { const auto options = dynamic_cast(str.get()); set_video_signal_configurable(options->output); + video_.set_use_square_pixels(options->use_square_pixels); } // MARK: MediaTarget diff --git a/Machines/Apple/AppleII/AppleII.hpp b/Machines/Apple/AppleII/AppleII.hpp index e373da4b8..97d348578 100644 --- a/Machines/Apple/AppleII/AppleII.hpp +++ b/Machines/Apple/AppleII/AppleII.hpp @@ -30,8 +30,11 @@ class Machine { class Options: public Reflection::StructImpl, public Configurable::DisplayOption { friend Configurable::DisplayOption; public: - Options(Configurable::OptionsType) : Configurable::DisplayOption(Configurable::Display::CompositeColour) { + bool use_square_pixels = false; + + Options(Configurable::OptionsType) : Configurable::DisplayOption(Configurable::Display::CompositeColour) { if(needs_declare()) { + DeclareField(use_square_pixels); declare_display_option(); limit_enum(&output, Configurable::Display::CompositeMonochrome, Configurable::Display::CompositeColour, -1); } diff --git a/Machines/Apple/AppleII/DiskIICard.cpp b/Machines/Apple/AppleII/DiskIICard.cpp index 7f0bd7a60..08addefcc 100644 --- a/Machines/Apple/AppleII/DiskIICard.cpp +++ b/Machines/Apple/AppleII/DiskIICard.cpp @@ -10,27 +10,34 @@ using namespace Apple::II; -DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) { - std::vector>> 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>> 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); } diff --git a/Machines/Apple/AppleII/DiskIICard.hpp b/Machines/Apple/AppleII/DiskIICard.hpp index 76815f0cf..979d31b07 100644 --- a/Machines/Apple/AppleII/DiskIICard.hpp +++ b/Machines/Apple/AppleII/DiskIICard.hpp @@ -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; diff --git a/Machines/Apple/AppleII/Video.cpp b/Machines/Apple/AppleII/Video.cpp index 7b0df2baa..ae358c0ab 100644 --- a/Machines/Apple/AppleII/Video.cpp +++ b/Machines/Apple/AppleII/Video.cpp @@ -15,9 +15,8 @@ VideoBase::VideoBase(bool is_iie, std::function &&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 &&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); } diff --git a/Machines/Apple/AppleII/Video.hpp b/Machines/Apple/AppleII/Video.hpp index afd332efc..ba414c83e 100644 --- a/Machines/Apple/AppleII/Video.hpp +++ b/Machines/Apple/AppleII/Video.hpp @@ -51,8 +51,14 @@ class VideoBase: public VideoSwitches { /// 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; diff --git a/Machines/Apple/AppleII/VideoSwitches.hpp b/Machines/Apple/AppleII/VideoSwitches.hpp index faad5df88..7852516f4 100644 --- a/Machines/Apple/AppleII/VideoSwitches.hpp +++ b/Machines/Apple/AppleII/VideoSwitches.hpp @@ -228,36 +228,6 @@ template 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 &rom) { character_rom_ = rom; diff --git a/Machines/Apple/AppleIIgs/AppleIIgs.cpp b/Machines/Apple/AppleIIgs/AppleIIgs.cpp index 218da7d9e..e778457ca 100644 --- a/Machines/Apple/AppleIIgs/AppleIIgs.cpp +++ b/Machines/Apple/AppleIIgs/AppleIIgs.cpp @@ -189,31 +189,23 @@ class ConcreteMachine: speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider)); using Target = Analyser::Static::AppleIIgs::Target; - std::vector 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; diff --git a/Machines/Apple/Macintosh/Macintosh.cpp b/Machines/Apple/Macintosh/Macintosh.cpp index 1d8ca3b35..4903055f3 100644 --- a/Machines/Apple/Macintosh/Macintosh.cpp +++ b/Machines/Apple/Macintosh/Macintosh.cpp @@ -96,25 +96,24 @@ template class ConcreteMachin using Model = Analyser::Static::Macintosh::Target::Model; const std::string machine_name = "Macintosh"; uint32_t ram_size, rom_size; - std::vector 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 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 class ConcreteMachin video_.set_ram(reinterpret_cast(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_); diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index 1c3f496e0..e3c656f58 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -74,15 +74,13 @@ class ConcreteMachine: video_->set_ram(reinterpret_cast(ram_.data()), ram_.size()); Memory::Fuzz(ram_); - std::vector 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; diff --git a/Machines/ColecoVision/ColecoVision.cpp b/Machines/ColecoVision/ColecoVision.cpp index 1523e3542..922229554 100644 --- a/Machines/ColecoVision/ColecoVision.cpp +++ b/Machines/ColecoVision/ColecoVision.cpp @@ -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(); diff --git a/Machines/Commodore/1540/C1540.hpp b/Machines/Commodore/1540/C1540.hpp index 1b747b1ef..b04334423 100644 --- a/Machines/Commodore/1540/C1540.hpp +++ b/Machines/Commodore/1540/C1540.hpp @@ -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. diff --git a/Machines/Commodore/1540/Implementation/C1540.cpp b/Machines/Commodore/1540/Implementation/C1540.cpp index 6a60aa59f..6ec47298d 100644 --- a/Machines/Commodore/1540/Implementation/C1540.cpp +++ b/Machines/Commodore/1540/Implementation/C1540.cpp @@ -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); diff --git a/Machines/Commodore/1540/Implementation/C1540Base.hpp b/Machines/Commodore/1540/Implementation/C1540Base.hpp index 5190bed02..f0f50f8b3 100644 --- a/Machines/Commodore/1540/Implementation/C1540Base.hpp +++ b/Machines/Commodore/1540/Implementation/C1540Base.hpp @@ -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); diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 70ec12dab..49432e44d 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -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 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_); diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index e76566a2d..7590d8dc6 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -69,37 +69,25 @@ template 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 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 class ConcreteMachine: plus3_ = std::make_unique(); 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) { diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 15b2bf9ef..69c05a350 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -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 required_roms = { - {machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3} - }; + ROM::Request bios_request = ROM::Request(ROM::Name::MSXGenericBIOS); +// std::vector 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); diff --git a/Machines/MasterSystem/MasterSystem.cpp b/Machines/MasterSystem/MasterSystem.cpp index 562f48967..5b81fbfc8 100644 --- a/Machines/MasterSystem/MasterSystem.cpp +++ b/Machines/MasterSystem/MasterSystem.cpp @@ -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(); diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index 016c23cc6..49d16c7f5 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -304,73 +304,64 @@ template 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) { diff --git a/Machines/Oric/Video.cpp b/Machines/Oric/Video.cpp index 30fe2f805..b14aab6b1 100644 --- a/Machines/Oric/Video.cpp +++ b/Machines/Oric/Video.cpp @@ -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 &rom) { + has_colour_rom_ = true; for(std::size_t c = 0; c < 8; c++) { colour_forms_[c] = 0; diff --git a/Machines/Oric/Video.hpp b/Machines/Oric/Video.hpp index 59181526d..bfd4df537 100644 --- a/Machines/Oric/Video.hpp +++ b/Machines/Oric/Video.hpp @@ -37,6 +37,7 @@ class VideoOutput { Outputs::CRT::CRT crt_; Outputs::CRT::CRTFrequencyMismatchWarner frequency_mismatch_warner_; bool crt_is_60Hz_ = false; + bool has_colour_rom_ = false; void update_crt_frequency(); diff --git a/Machines/ROMMachine.hpp b/Machines/ROMMachine.hpp index b1b9cb818..f28d89268 100644 --- a/Machines/ROMMachine.hpp +++ b/Machines/ROMMachine.hpp @@ -10,38 +10,16 @@ #define ROMMachine_hpp #include +#include #include +#include #include #include +#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 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 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>>(const std::vector &roms)> ROMFetcher; +typedef std::function ROMFetcher; enum class Error { MissingROMs diff --git a/Machines/Sinclair/ZX8081/ZX8081.cpp b/Machines/Sinclair/ZX8081/ZX8081.cpp index 74eae2a16..e61f5bc41 100644 --- a/Machines/Sinclair/ZX8081/ZX8081.cpp +++ b/Machines/Sinclair/ZX8081/ZX8081.cpp @@ -74,15 +74,13 @@ template 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); diff --git a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp index 88bf2a60b..5c648c53b 100644 --- a/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp +++ b/Machines/Sinclair/ZXSpectrum/ZXSpectrum.cpp @@ -146,33 +146,24 @@ template 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 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 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); diff --git a/Machines/Utility/ROMCatalogue.cpp b/Machines/Utility/ROMCatalogue.cpp new file mode 100644 index 000000000..7a12069ff --- /dev/null +++ b/Machines/Utility/ROMCatalogue.cpp @@ -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 +#include +#include +#include +#include +#include + +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 Request::all_descriptions() const { + std::vector result; + node.add_descriptions(result); + return result; +} + +void Request::Node::add_descriptions(std::vector &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 &enter_list, + const std::function &exit_list, + const std::function &add_item +) const { + node.visit(enter_list, exit_list, add_item); +} + +void Request::visit( + const std::function &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 &enter_list, + const std::function &exit_list, + const std::function &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 ROM::all_descriptions() { + std::vector result; + for(int name = 1; name <= MaxName; name++) { + result.push_back(Description(ROM::Name(name))); + } + return result; +} + +std::optional 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> 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{ 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 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 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; + + } +} diff --git a/Machines/Utility/ROMCatalogue.hpp b/Machines/Utility/ROMCatalogue.hpp new file mode 100644 index 000000000..88ec7ca5b --- /dev/null +++ b/Machines/Utility/ROMCatalogue.hpp @@ -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 +#include +#include +#include +#include +#include +#include + +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>; + +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 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 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 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 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 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 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 &enter_list, + const std::function &exit_list, + const std::function &add_item + ) const; + + enum class LineItem { + NewList, Description + }; + void visit( + const std::function &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 children; + + void add_descriptions(std::vector &) const; + bool validate(Map &) const; + void visit( + const std::function &enter_list, + const std::function &exit_list, + const std::function &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 */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index bde5d0985..60a7ae8c2 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = ""; }; 4B04B65622A58CB40006AB58 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = ""; }; + 4B051C5826670A9300CA44E8 /* ROMCatalogue.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ROMCatalogue.cpp; sourceTree = ""; }; + 4B051C5926670A9300CA44E8 /* ROMCatalogue.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMCatalogue.hpp; sourceTree = ""; }; + 4B051C94266EF50200CA44E8 /* AppleIIOptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppleIIOptionsPanel.swift; sourceTree = ""; }; + 4B051C96266EF5F600CA44E8 /* CSAppleII.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSAppleII.mm; sourceTree = ""; }; + 4B051C98266EF60500CA44E8 /* CSAppleII.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSAppleII.h; sourceTree = ""; }; 4B05401D219D1618001BF69C /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = ""; }; 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 */, diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index 02b9d9828..263537208 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -56,6 +56,18 @@ argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK" isEnabled = "NO"> + + + + + + @@ -70,11 +82,11 @@ + isEnabled = "YES"> + isEnabled = "NO"> + + + + - + - + @@ -13,17 +13,27 @@ - + - - + + - + + - + @@ -40,16 +50,20 @@ - + + + - + + + - + diff --git a/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Options.xib b/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Options.xib index 2e6e00fb0..41c6e7572 100644 --- a/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Options.xib +++ b/OSBindings/Mac/Clock Signal/Base.lproj/Atari2600Options.xib @@ -1,8 +1,8 @@ - + - + @@ -13,17 +13,17 @@ - + - +