diff --git a/.gitignore b/.gitignore index 060311938..8cb481017 100644 --- a/.gitignore +++ b/.gitignore @@ -18,9 +18,10 @@ DerivedData *.xcuserstate .DS_Store -# Exclude system ROMs +# Exclude system ROMs and unit test ROMs ROMImages/* -OSBindings/Mac/Clock SignalTests/Atari\ ROMs +OSBindings/Mac/Clock SignalTests/Atari ROMs +OSBindings/Mac/Clock SignalTests/MSX ROMs # Exclude intermediate build products *.o diff --git a/Machines/MSX/Cartridges/ASCII16kb.hpp b/Machines/MSX/Cartridges/ASCII16kb.hpp new file mode 100644 index 000000000..37aa6a70b --- /dev/null +++ b/Machines/MSX/Cartridges/ASCII16kb.hpp @@ -0,0 +1,42 @@ +// +// ASCII16kb.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef ASCII16kb_hpp +#define ASCII16kb_hpp + +#include "../ROMSlotHandler.hpp" + +namespace MSX { +namespace Cartridge { + +class ASCII16kbROMSlotHandler: public ROMSlotHandler { + public: + ASCII16kbROMSlotHandler(MSX::MemoryMap &map, int slot) : + map_(map), slot_(slot) {} + + void write(uint16_t address, uint8_t value) { + switch(address >> 11) { + default: break; + case 0xc: + map_.map(slot_, value * 8192, 0x4000, 0x4000); + break; + case 0xe: + map_.map(slot_, value * 8192, 0x8000, 0x4000); + break; + } + } + + private: + MSX::MemoryMap &map_; + int slot_; +}; + +} +} + +#endif /* ASCII16kb_hpp */ diff --git a/Machines/MSX/Cartridges/ASCII8kb.hpp b/Machines/MSX/Cartridges/ASCII8kb.hpp new file mode 100644 index 000000000..ab5c515e2 --- /dev/null +++ b/Machines/MSX/Cartridges/ASCII8kb.hpp @@ -0,0 +1,48 @@ +// +// ASCII8kb.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef ASCII8kb_hpp +#define ASCII8kb_hpp + +#include "../ROMSlotHandler.hpp" + +namespace MSX { +namespace Cartridge { + +class ASCII8kbROMSlotHandler: public ROMSlotHandler { + public: + ASCII8kbROMSlotHandler(MSX::MemoryMap &map, int slot) : + map_(map), slot_(slot) {} + + void write(uint16_t address, uint8_t value) { + switch(address >> 11) { + default: break; + case 0xc: + map_.map(slot_, value * 8192, 0x4000, 0x2000); + break; + case 0xd: + map_.map(slot_, value * 8192, 0x6000, 0x2000); + break; + case 0xe: + map_.map(slot_, value * 8192, 0x8000, 0x2000); + break; + case 0xf: + map_.map(slot_, value * 8192, 0xa000, 0x2000); + break; + } + } + + private: + MSX::MemoryMap &map_; + int slot_; +}; + +} +} + +#endif /* ASCII8kb_hpp */ diff --git a/Machines/MSX/Cartridges/Konami.hpp b/Machines/MSX/Cartridges/Konami.hpp new file mode 100644 index 000000000..296f00a97 --- /dev/null +++ b/Machines/MSX/Cartridges/Konami.hpp @@ -0,0 +1,45 @@ +// +// Konami.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef Konami_hpp +#define Konami_hpp + +#include "../ROMSlotHandler.hpp" + +namespace MSX { +namespace Cartridge { + +class KonamiROMSlotHandler: public ROMSlotHandler { + public: + KonamiROMSlotHandler(MSX::MemoryMap &map, int slot) : + map_(map), slot_(slot) {} + + void write(uint16_t address, uint8_t value) { + switch(address >> 13) { + default: break; + case 3: + map_.map(slot_, value * 8192, 0x6000, 0x2000); + break; + case 4: + map_.map(slot_, value * 8192, 0x8000, 0x2000); + break; + case 5: + map_.map(slot_, value * 8192, 0xa000, 0x2000); + break; + } + } + + private: + MSX::MemoryMap &map_; + int slot_; +}; + +} +} + +#endif /* Konami_hpp */ diff --git a/Machines/MSX/Cartridges/KonamiWithSCC.hpp b/Machines/MSX/Cartridges/KonamiWithSCC.hpp new file mode 100644 index 000000000..f57d33249 --- /dev/null +++ b/Machines/MSX/Cartridges/KonamiWithSCC.hpp @@ -0,0 +1,48 @@ +// +// KonamiWithSCC.hpp +// Clock Signal +// +// Created by Thomas Harte on 04/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef KonamiWithSCC_hpp +#define KonamiWithSCC_hpp + +#include "../ROMSlotHandler.hpp" + +namespace MSX { +namespace Cartridge { + +class KonamiWithSCCROMSlotHandler: public ROMSlotHandler { + public: + KonamiWithSCCROMSlotHandler(MSX::MemoryMap &map, int slot) : + map_(map), slot_(slot) {} + + void write(uint16_t address, uint8_t value) { + switch(address >> 11) { + default: break; + case 0x0a: + map_.map(slot_, value * 8192, 0x4000, 0x2000); + break; + case 0x0e: + map_.map(slot_, value * 8192, 0x6000, 0x2000); + break; + case 0x12: + map_.map(slot_, value * 8192, 0x8000, 0x2000); + break; + case 0x16: + map_.map(slot_, value * 8192, 0xa000, 0x2000); + break; + } + } + + private: + MSX::MemoryMap &map_; + int slot_; +}; + +} +} + +#endif /* KonamiWithSCC_hpp */ diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 14ff65f82..5ba77f305 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -9,6 +9,12 @@ #include "MSX.hpp" #include "Keyboard.hpp" +#include "ROMSlotHandler.hpp" + +#include "Cartridges/ASCII8kb.hpp" +#include "Cartridges/ASCII16kb.hpp" +#include "Cartridges/Konami.hpp" +#include "Cartridges/KonamiWithSCC.hpp" #include "../../Processors/Z80/Z80.hpp" @@ -106,7 +112,8 @@ class ConcreteMachine: public CRTMachine::Machine, public ConfigurationTarget::Machine, public KeyboardMachine::Machine, - public Configurable::Device { + public Configurable::Device, + public MemoryMap { public: ConcreteMachine(): z80_(*this), @@ -152,18 +159,30 @@ class ConcreteMachine: if(target.loading_command.length()) { type_string(target.loading_command); } + + switch(target.msx.cartridge_type) { + default: break; + case StaticAnalyser::MSXCartridgeType::Konami: + memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1)); + break; + case StaticAnalyser::MSXCartridgeType::KonamiWithSCC: + // TODO: enable an SCC. + memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1)); + break; + case StaticAnalyser::MSXCartridgeType::ASCII8kb: + memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1)); + break; + case StaticAnalyser::MSXCartridgeType::ASCII16kb: + memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1)); + break; + } } bool insert_media(const StaticAnalyser::Media &media) override { if(!media.cartridges.empty()) { const auto &segment = media.cartridges.front()->get_segments().front(); - cartridge_ = segment.data; - - // TODO: should clear other page 1 pointers, should allow for paging cartridges, etc. - size_t base = segment.start_address >> 14; - for(size_t c = 0; c < cartridge_.size(); c += 16384) { - memory_slots_[1].read_pointers[(c >> 14) + base] = cartridge_.data() + c; - } + memory_slots_[1].source = segment.data; + map(1, 0, static_cast(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address)); } if(!media.tapes.empty()) { @@ -177,14 +196,47 @@ class ConcreteMachine: input_text_ += string; } + // MARK: MSX::MemoryMap + void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) override { + assert(!(destination_address & 8191)); + assert(!(length & 8191)); + assert(static_cast(destination_address) + length <= 65536); + + for(std::size_t c = 0; c < (length >> 13); ++c) { + if(memory_slots_[slot].wrapping_strategy == ROMSlotHandler::WrappingStrategy::Repeat) source_address %= memory_slots_[slot].source.size(); + memory_slots_[slot].read_pointers[(destination_address >> 13) + c] = + (source_address < memory_slots_[slot].source.size()) ? &memory_slots_[slot].source[source_address] : unpopulated_; + source_address += 8192; + } + + page_memory(paged_memory_); + } + + void unmap(int slot, uint16_t destination_address, std::size_t length) override { + assert(!(destination_address & 8191)); + assert(!(length & 8191)); + assert(static_cast(destination_address) + length <= 65536); + + for(std::size_t c = 0; c < (length >> 13); ++c) { + memory_slots_[slot].read_pointers[(destination_address >> 13) + c] = nullptr; + } + + page_memory(paged_memory_); + } + + // MARK: Ordinary paging. void page_memory(uint8_t value) { - for(size_t c = 0; c < 4; ++c) { + paged_memory_ = value; + for(std::size_t c = 0; c < 8; c += 2) { read_pointers_[c] = memory_slots_[value & 3].read_pointers[c]; write_pointers_[c] = memory_slots_[value & 3].write_pointers[c]; + read_pointers_[c+1] = memory_slots_[value & 3].read_pointers[c+1]; + write_pointers_[c+1] = memory_slots_[value & 3].write_pointers[c+1]; value >>= 2; } } + // MARK: Z80::BusHandler HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) { if(time_until_interrupt_ > 0) { time_until_interrupt_ -= cycle.length; @@ -196,7 +248,7 @@ class ConcreteMachine: uint16_t address = cycle.address ? *cycle.address : 0x0000; switch(cycle.operation) { case CPU::Z80::PartialMachineCycle::ReadOpcode: - if(use_fast_tape_) { + if(use_fast_tape_ && tape_player_.has_tape()) { if(address == 0x1a63) { // TAPION @@ -251,12 +303,24 @@ class ConcreteMachine: } } case CPU::Z80::PartialMachineCycle::Read: - *cycle.value = read_pointers_[address >> 14][address & 16383]; + if(read_pointers_[address >> 13]) { + *cycle.value = read_pointers_[address >> 13][address & 8191]; + } else { + int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; + memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); + *cycle.value = memory_slots_[slot_hit].handler->read(address); + } break; - case CPU::Z80::PartialMachineCycle::Write: - write_pointers_[address >> 14][address & 16383] = *cycle.value; - break; + case CPU::Z80::PartialMachineCycle::Write: { + write_pointers_[address >> 13][address & 8191] = *cycle.value; + + int slot_hit = (paged_memory_ >> ((address >> 14) * 2)) & 3; + if(memory_slots_[slot_hit].handler) { + memory_slots_[slot_hit].handler->run_for(memory_slots_[slot_hit].cycles_since_update.flush()); + memory_slots_[slot_hit].handler->write(address, *cycle.value); + } + } break; case CPU::Z80::PartialMachineCycle::Input: switch(address & 0xff) { @@ -354,6 +418,10 @@ class ConcreteMachine: HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0); time_since_vdp_update_ += cycle.length + addition; time_since_ay_update_ += cycle.length + addition; + memory_slots_[0].cycles_since_update += cycle.length + addition; + memory_slots_[1].cycles_since_update += cycle.length + addition; + memory_slots_[2].cycles_since_update += cycle.length + addition; + memory_slots_[3].cycles_since_update += cycle.length + addition; return addition; } @@ -373,26 +441,21 @@ class ConcreteMachine: if(!roms[0]) return false; - rom_ = std::move(*roms[0]); - rom_.resize(32768); + memory_slots_[0].source = std::move(*roms[0]); + memory_slots_[0].source.resize(32768); - for(size_t c = 0; c < 4; ++c) { + for(size_t c = 0; c < 8; ++c) { for(size_t slot = 0; slot < 3; ++slot) { memory_slots_[slot].read_pointers[c] = unpopulated_; memory_slots_[slot].write_pointers[c] = scratch_; } memory_slots_[3].read_pointers[c] = - memory_slots_[3].write_pointers[c] = &ram_[c * 16384]; + memory_slots_[3].write_pointers[c] = &ram_[c * 8192]; } - memory_slots_[0].read_pointers[0] = rom_.data(); - memory_slots_[0].read_pointers[1] = &rom_[16384]; - - for(size_t c = 0; c < 4; ++c) { - read_pointers_[c] = memory_slots_[0].read_pointers[c]; - write_pointers_[c] = memory_slots_[0].write_pointers[c]; - } + map(0, 0, 0, 32768); + page_memory(0); return true; } @@ -514,19 +577,28 @@ class ConcreteMachine: i8255PortHandler i8255_port_handler_; AYPortHandler ay_port_handler_; - uint8_t *read_pointers_[4]; - uint8_t *write_pointers_[4]; + uint8_t paged_memory_ = 0; + uint8_t *read_pointers_[8]; + uint8_t *write_pointers_[8]; struct MemorySlots { - uint8_t *read_pointers[4]; - uint8_t *write_pointers[4]; + uint8_t *read_pointers[8]; + uint8_t *write_pointers[8]; + + void set_handler(ROMSlotHandler *slot_handler) { + handler.reset(slot_handler); + wrapping_strategy = handler->wrapping_strategy(); + } + + std::unique_ptr handler; + std::vector source; + HalfCycles cycles_since_update; + ROMSlotHandler::WrappingStrategy wrapping_strategy = ROMSlotHandler::WrappingStrategy::Repeat; } memory_slots_[4]; uint8_t ram_[65536]; - uint8_t scratch_[16384]; - uint8_t unpopulated_[16384]; - std::vector rom_; - std::vector cartridge_; + uint8_t scratch_[8192]; + uint8_t unpopulated_[8192]; HalfCycles time_since_vdp_update_; HalfCycles time_since_ay_update_; diff --git a/Machines/MSX/ROMSlotHandler.hpp b/Machines/MSX/ROMSlotHandler.hpp new file mode 100644 index 000000000..857d1a6aa --- /dev/null +++ b/Machines/MSX/ROMSlotHandler.hpp @@ -0,0 +1,70 @@ +// +// ROMSlotHandler.hpp +// Clock Signal +// +// Created by Thomas Harte on 03/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef ROMSlotHandler_hpp +#define ROMSlotHandler_hpp + +#include "../../ClockReceiver/ClockReceiver.hpp" + +#include +#include + +/* + Design assumptions: + + - to-ROM writes and paging events are 'rare', so virtual call costs aren't worrisome; + - ROM type variety is sufficiently slender that most of it can be built into the MSX. + + Part of the motivation is also that the MSX has four logical slots, the ROM, RAM plus two + things plugged in. So even if the base class were templated to remove the virtual call, + there'd just be a switch on what to call. +*/ +namespace MSX { + +class MemoryMap { + public: + /*! + Maps source data from the ROM's source to the given address range. + */ + virtual void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) = 0; + + /*! + Unmaps source data from the given address range; the slot handler's read function will be used + to respond to queries in that range. + */ + virtual void unmap(int slot, uint16_t destination_address, std::size_t length) = 0; +}; + +class ROMSlotHandler { + public: + /*! Advances time by @c half_cycles. */ + virtual void run_for(HalfCycles half_cycles) {} + + /*! Announces an attempt to write @c value to @c address. */ + virtual void write(uint16_t address, uint8_t value) = 0; + + /*! Seeks the result of a read at @c address; this is used only if the area is unmapped. */ + virtual uint8_t read(uint16_t address) { return 0xff; } + + enum class WrappingStrategy { + /// Repeat causes all accesses to be modulo the size of the ROM. + Repeat, + /// Empty causes all out-of-bounds accesses to read a vacant bus. + Empty + }; + /*! + Returns the wrapping strategy to apply to mapping requests from this ROM slot. + */ + virtual WrappingStrategy wrapping_strategy() const { + return WrappingStrategy::Repeat; + } +}; + +} + +#endif /* ROMSlotHandler_hpp */ diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index f9e560b36..3ac05131c 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -267,6 +267,10 @@ 4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */; }; 4B95FA9D1F11893B0008E395 /* ZX8081OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */; }; 4B96F7221D75119A0058BB2D /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B96F7201D75119A0058BB2D /* Tape.cpp */; }; + 4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; }; + 4B98A05F1FFAD62400ADF63B /* CSROMFetcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */; }; + 4B98A0611FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */; }; + 4B98A1CE1FFADEC500ADF63B /* MSX ROMs in Resources */ = {isa = PBXBuildFile; fileRef = 4B98A1CD1FFADEC400ADF63B /* MSX ROMs */; }; 4B9C9D751FF81CC00030A129 /* Z80.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9C9D731FF81CC00030A129 /* Z80.cpp */; }; 4B9C9D781FF81ED30030A129 /* AddressMapper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B9C9D761FF81ED30030A129 /* AddressMapper.cpp */; }; 4B9CCDA11DA279CA0098B625 /* Vic20OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B9CCDA01DA279CA0098B625 /* Vic20OptionsPanel.swift */; }; @@ -673,6 +677,10 @@ 4B1497971EE4B97F00CE2596 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/ZX8081Options.xib"; sourceTree = SOURCE_ROOT; }; 4B1558BE1F844ECD006E9A97 /* BitReverse.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = BitReverse.cpp; path = Data/BitReverse.cpp; sourceTree = ""; }; 4B1558BF1F844ECD006E9A97 /* BitReverse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = BitReverse.hpp; path = Data/BitReverse.hpp; sourceTree = ""; }; + 4B1667F61FFF1E2400A16032 /* Konami.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Konami.hpp; path = MSX/Cartridges/Konami.hpp; sourceTree = ""; }; + 4B1667F91FFF215E00A16032 /* ASCII16kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ASCII16kb.hpp; path = MSX/Cartridges/ASCII16kb.hpp; sourceTree = ""; }; + 4B1667FA1FFF215E00A16032 /* ASCII8kb.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ASCII8kb.hpp; path = MSX/Cartridges/ASCII8kb.hpp; sourceTree = ""; }; + 4B1667FB1FFF215F00A16032 /* KonamiWithSCC.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = KonamiWithSCC.hpp; path = MSX/Cartridges/KonamiWithSCC.hpp; sourceTree = ""; }; 4B1BA0881FD4967700CB4ADA /* CSMSX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSMSX.h; sourceTree = ""; }; 4B1BA0891FD4967800CB4ADA /* CSMSX.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CSMSX.mm; sourceTree = ""; }; 4B1BA08C1FD498B000CB4ADA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MSXOptions.xib"; sourceTree = SOURCE_ROOT; }; @@ -840,6 +848,7 @@ 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = ""; }; 4B7041271F92C26900735E45 /* JoystickMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JoystickMachine.hpp; sourceTree = ""; }; 4B70412A1F92C2A700735E45 /* Joystick.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Joystick.hpp; sourceTree = ""; }; + 4B70EF6A1FFDCDF400A3494E /* ROMSlotHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = ROMSlotHandler.hpp; path = MSX/ROMSlotHandler.hpp; sourceTree = ""; }; 4B7136841F78724F008B8ED9 /* Encoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Encoder.cpp; sourceTree = ""; }; 4B7136851F78724F008B8ED9 /* Encoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Encoder.hpp; sourceTree = ""; }; 4B7136871F78725F008B8ED9 /* Shifter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Shifter.cpp; sourceTree = ""; }; @@ -901,6 +910,10 @@ 4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081OptionsPanel.swift; sourceTree = ""; }; 4B96F7201D75119A0058BB2D /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = ../../StaticAnalyser/Acorn/Tape.cpp; sourceTree = ""; }; 4B96F7211D75119A0058BB2D /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Tape.hpp; path = ../../StaticAnalyser/Acorn/Tape.hpp; sourceTree = ""; }; + 4B98A05C1FFAD3F600ADF63B /* CSROMFetcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CSROMFetcher.hpp; sourceTree = ""; }; + 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSROMFetcher.mm; sourceTree = ""; }; + 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSXStaticAnalyserTests.mm; sourceTree = ""; }; + 4B98A1CD1FFADEC400ADF63B /* MSX ROMs */ = {isa = PBXFileReference; lastKnownFileType = folder; path = "MSX ROMs"; sourceTree = ""; }; 4B9C9D731FF81CC00030A129 /* Z80.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Z80.cpp; path = ../../StaticAnalyser/Disassembler/Z80.cpp; sourceTree = ""; }; 4B9C9D741FF81CC00030A129 /* Z80.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = ../../StaticAnalyser/Disassembler/Z80.hpp; sourceTree = ""; }; 4B9C9D761FF81ED30030A129 /* AddressMapper.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AddressMapper.cpp; path = ../../StaticAnalyser/Disassembler/AddressMapper.cpp; sourceTree = ""; }; @@ -1421,6 +1434,7 @@ 4B1414631B588A1100E04248 /* Test Binaries */ = { isa = PBXGroup; children = ( + 4B98A1CD1FFADEC400ADF63B /* MSX ROMs */, 4B9252CD1E74D28200B76AF1 /* Atari ROMs */, 4B44EBF81DC9898E00A7820C /* BCDTEST_beeb */, 4B44EBF61DC9883B00A7820C /* 6502_functional_test.bin */, @@ -1454,6 +1468,17 @@ name = ZX8081; sourceTree = ""; }; + 4B1667F81FFF1E2900A16032 /* Cartridges */ = { + isa = PBXGroup; + children = ( + 4B1667FA1FFF215E00A16032 /* ASCII8kb.hpp */, + 4B1667F91FFF215E00A16032 /* ASCII16kb.hpp */, + 4B1667FB1FFF215F00A16032 /* KonamiWithSCC.hpp */, + 4B1667F61FFF1E2400A16032 /* Konami.hpp */, + ); + name = Cartridges; + sourceTree = ""; + }; 4B1E85791D174DEC001EF87D /* 6532 */ = { isa = PBXGroup; children = ( @@ -1501,9 +1526,11 @@ 4B2A53951D117D36003C6002 /* CSMachine.h */, 4B2A53941D117D36003C6002 /* CSMachine+Subclassing.h */, 4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */, + 4B98A05C1FFAD3F600ADF63B /* CSROMFetcher.hpp */, 4B2A53971D117D36003C6002 /* KeyCodes.h */, 4B8FE2251DA1DE2D0090D3CE /* NSBundle+DataResource.h */, 4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */, + 4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */, 4B8FE2261DA1DE2D0090D3CE /* NSBundle+DataResource.m */, 4B2A53961D117D36003C6002 /* CSMachine.mm */, 4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */, @@ -1973,10 +2000,12 @@ 4B79A4FC1FC8FF9800EEDAD5 /* MSX */ = { isa = PBXGroup; children = ( - 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */, - 4B79A5001FC913C900EEDAD5 /* MSX.hpp */, 4B12C0EB1FCFA98D005BFD93 /* Keyboard.cpp */, + 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */, 4B12C0EC1FCFA98D005BFD93 /* Keyboard.hpp */, + 4B79A5001FC913C900EEDAD5 /* MSX.hpp */, + 4B70EF6A1FFDCDF400A3494E /* ROMSlotHandler.hpp */, + 4B1667F81FFF1E2900A16032 /* Cartridges */, ); name = MSX; sourceTree = ""; @@ -2431,6 +2460,7 @@ 4BB73EB51B587A5100552FC2 /* Clock SignalTests */ = { isa = PBXGroup; children = ( + 4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */, 4B5073091DDFCFDF00C48FBD /* ArrayBuilderTests.mm */, 4B924E981E74D22700B76AF1 /* AtariStaticAnalyserTests.mm */, 4BB2A9AE1E13367E001A5C23 /* CRCTests.mm */, @@ -3087,6 +3117,7 @@ 4B9252CE1E74D28200B76AF1 /* Atari ROMs in Resources */, 4BB2997D1B587D8400A49093 /* ldxay in Resources */, 4BB299D71B587D8400A49093 /* staax in Resources */, + 4B98A1CE1FFADEC500ADF63B /* MSX ROMs in Resources */, 4BB2990C1B587D8400A49093 /* asoax in Resources */, 4BB299191B587D8400A49093 /* bita in Resources */, 4BB2992A1B587D8400A49093 /* cia2ta in Resources */, @@ -3480,6 +3511,7 @@ 4B4518871F75E91A00926311 /* DigitalPhaseLockedLoop.cpp in Sources */, 4B2A53A31D117D36003C6002 /* CSVic20.mm in Sources */, 4B2A53A21D117D36003C6002 /* CSElectron.mm in Sources */, + 4B98A05E1FFAD3F600ADF63B /* CSROMFetcher.mm in Sources */, 4B8FE2201DA19D7C0090D3CE /* Atari2600OptionsPanel.swift in Sources */, 4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */, 4B2E2D9A1C3A06EC00138695 /* Atari2600.cpp in Sources */, @@ -3527,6 +3559,7 @@ files = ( 4B1E85811D176468001EF87D /* 6532Tests.swift in Sources */, 4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */, + 4B98A05F1FFAD62400ADF63B /* CSROMFetcher.mm in Sources */, 4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */, 4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */, 4B924E991E74D22700B76AF1 /* AtariStaticAnalyserTests.mm in Sources */, @@ -3543,6 +3576,7 @@ 4BB73EB71B587A5100552FC2 /* AllSuiteATests.swift in Sources */, 4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */, 4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */, + 4B98A0611FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm in Sources */, 4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */, 4B3BA0CF1D318B44005DD7A7 /* MOS6522Bridge.mm in Sources */, 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */, diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index d44d54994..ae790259c 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -10,6 +10,8 @@ #import "CSMachine+Subclassing.h" #import "CSMachine+Target.h" +#include "CSROMFetcher.hpp" + #include "ConfigurationTarget.hpp" #include "JoystickMachine.hpp" #include "KeyboardMachine.hpp" @@ -74,23 +76,7 @@ struct MachineDelegate: CRTMachine::Machine::Delegate, public LockProtectedDeleg _speakerDelegate.machineAccessLock = _delegateMachineAccessLock; _machine->crt_machine()->set_delegate(&_machineDelegate); - _machine->crt_machine()->set_rom_fetcher( [] (const std::string &machine, const std::vector &names) -> std::vector>> { - NSString *subDirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:machine.c_str()]]; - std::vector>> results; - for(auto &name: names) { - NSData *fileData = [[NSBundle mainBundle] dataForResource:[NSString stringWithUTF8String:name.c_str()] withExtension:nil subdirectory:subDirectory]; - - if(!fileData) - results.emplace_back(nullptr); - else { - std::unique_ptr> data(new std::vector); - *data = fileData.stdVector8; - results.emplace_back(std::move(data)); - } - } - - return results; - }); + CSApplyROMFetcher(*_machine->crt_machine()); } return self; } diff --git a/OSBindings/Mac/Clock Signal/Machine/CSROMFetcher.hpp b/OSBindings/Mac/Clock Signal/Machine/CSROMFetcher.hpp new file mode 100644 index 000000000..0580df92e --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/CSROMFetcher.hpp @@ -0,0 +1,11 @@ +// +// ROMFetcher.h +// Clock Signal +// +// Created by Thomas Harte on 01/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#include "ROMMachine.hpp" + +void CSApplyROMFetcher(ROMMachine::Machine &rom_machine); diff --git a/OSBindings/Mac/Clock Signal/Machine/CSROMFetcher.mm b/OSBindings/Mac/Clock Signal/Machine/CSROMFetcher.mm new file mode 100644 index 000000000..b6e995c7c --- /dev/null +++ b/OSBindings/Mac/Clock Signal/Machine/CSROMFetcher.mm @@ -0,0 +1,35 @@ +// +// CSROMFetcher.m +// Clock Signal +// +// Created by Thomas Harte on 01/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#import +#include "CSROMFetcher.hpp" + +#import "NSBundle+DataResource.h" +#import "NSData+StdVector.h" + +#include + +void CSApplyROMFetcher(ROMMachine::Machine &rom_machine) { + rom_machine.set_rom_fetcher( [] (const std::string &machine, const std::vector &names) -> std::vector>> { + NSString *subDirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:machine.c_str()]]; + std::vector>> results; + for(auto &name: names) { + NSData *fileData = [[NSBundle mainBundle] dataForResource:[NSString stringWithUTF8String:name.c_str()] withExtension:nil subdirectory:subDirectory]; + + if(!fileData) + results.emplace_back(nullptr); + else { + std::unique_ptr> data(new std::vector); + *data = fileData.stdVector8; + results.emplace_back(std::move(data)); + } + } + + return results; + }); +} diff --git a/OSBindings/Mac/Clock Signal/Machine/NSBundle+DataResource.m b/OSBindings/Mac/Clock Signal/Machine/NSBundle+DataResource.m index 46c9e4035..fb3d6cf74 100644 --- a/OSBindings/Mac/Clock Signal/Machine/NSBundle+DataResource.m +++ b/OSBindings/Mac/Clock Signal/Machine/NSBundle+DataResource.m @@ -10,8 +10,7 @@ @implementation NSBundle (DataResource) -- (NSData *)dataForResource:(NSString *)resource withExtension:(NSString *)extension subdirectory:(NSString *)subdirectory -{ +- (NSData *)dataForResource:(NSString *)resource withExtension:(NSString *)extension subdirectory:(NSString *)subdirectory { NSURL *url = [self URLForResource:resource withExtension:extension subdirectory:subdirectory]; if(!url) return nil; return [NSData dataWithContentsOfURL:url]; diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/C1540Bridge.h b/OSBindings/Mac/Clock SignalTests/Bridges/C1540Bridge.h index c1e6dd963..8a5bed004 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/C1540Bridge.h +++ b/OSBindings/Mac/Clock SignalTests/Bridges/C1540Bridge.h @@ -15,6 +15,5 @@ @property (nonatomic) BOOL clockLine; - (void)runForCycles:(NSUInteger)numberOfCycles; -- (void)setROM:(NSData *)ROM; @end diff --git a/OSBindings/Mac/Clock SignalTests/Bridges/C1540Bridge.mm b/OSBindings/Mac/Clock SignalTests/Bridges/C1540Bridge.mm index 1cc7e0a2e..7cd2f495a 100644 --- a/OSBindings/Mac/Clock SignalTests/Bridges/C1540Bridge.mm +++ b/OSBindings/Mac/Clock SignalTests/Bridges/C1540Bridge.mm @@ -9,6 +9,7 @@ #import "C1540Bridge.h" #include "C1540.hpp" #include "NSData+StdVector.h" +#include "CSROMFetcher.hpp" class VanillaSerialPort: public Commodore::Serial::Port { public: @@ -20,7 +21,7 @@ class VanillaSerialPort: public Commodore::Serial::Port { }; @implementation C1540Bridge { - Commodore::C1540::Machine _c1540; + std::unique_ptr _c1540; std::shared_ptr _serialBus; std::shared_ptr _serialPort; } @@ -31,18 +32,16 @@ class VanillaSerialPort: public Commodore::Serial::Port { _serialBus.reset(new ::Commodore::Serial::Bus); _serialPort.reset(new VanillaSerialPort); - _c1540.set_serial_bus(_serialBus); + _c1540.reset(new Commodore::C1540::Machine(Commodore::C1540::Machine::C1540)); + CSApplyROMFetcher(*_c1540); + _c1540->set_serial_bus(_serialBus); Commodore::Serial::AttachPortAndBus(_serialPort, _serialBus); } return self; } -- (void)setROM:(NSData *)ROM { - _c1540.set_rom(ROM.stdVector8); -} - - (void)runForCycles:(NSUInteger)numberOfCycles { - _c1540.run_for(Cycles((int)numberOfCycles)); + _c1540->run_for(Cycles((int)numberOfCycles)); } - (void)setAttentionLine:(BOOL)attentionLine { diff --git a/OSBindings/Mac/Clock SignalTests/C1540Tests.swift b/OSBindings/Mac/Clock SignalTests/C1540Tests.swift index 90a05d9c6..6cb81b4a2 100644 --- a/OSBindings/Mac/Clock SignalTests/C1540Tests.swift +++ b/OSBindings/Mac/Clock SignalTests/C1540Tests.swift @@ -12,12 +12,6 @@ class C1540Tests: XCTestCase { fileprivate func with1540(_ action: (C1540Bridge) -> ()) { let bridge = C1540Bridge() - - if let path = Bundle.main.path(forResource: "1541", ofType: "bin", inDirectory: "ROMImages/Commodore1540") { - let data = try? Data(contentsOf: URL(fileURLWithPath: path)) - bridge.setROM(data) - } - action(bridge) } diff --git a/OSBindings/Mac/Clock SignalTests/MSX ROMs/readme.txt b/OSBindings/Mac/Clock SignalTests/MSX ROMs/readme.txt new file mode 100644 index 000000000..d12ed5463 --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/MSX ROMs/readme.txt @@ -0,0 +1,3 @@ +This folder is intended to contain commercial MSX ROM images; these are used to unit test the MSX static analyser. + +Those tested are primarily from the Internet Archive’s version of the TOSEC collection. \ No newline at end of file diff --git a/OSBindings/Mac/Clock SignalTests/MSXStaticAnalyserTests.mm b/OSBindings/Mac/Clock SignalTests/MSXStaticAnalyserTests.mm new file mode 100644 index 000000000..5a628a09d --- /dev/null +++ b/OSBindings/Mac/Clock SignalTests/MSXStaticAnalyserTests.mm @@ -0,0 +1,230 @@ +// +// AtariStaticAnalyserTests.m +// Clock Signal +// +// Created by Thomas Harte on 11/03/2017. +// Copyright © 2017 Thomas Harte. All rights reserved. +// + +#import + +#import +#include "../../../StaticAnalyser/StaticAnalyser.hpp" + +@interface MSXROMRecord : NSObject +@property(nonatomic, readonly) StaticAnalyser::MSXCartridgeType cartridgeType; ++ (instancetype)recordWithCartridgeType:(StaticAnalyser::MSXCartridgeType)cartridgeType; +@end + +@implementation MSXROMRecord ++ (instancetype)recordWithCartridgeType:(StaticAnalyser::MSXCartridgeType)cartridgeType { + MSXROMRecord *record = [[MSXROMRecord alloc] init]; + record->_cartridgeType = cartridgeType; + return record; +} +@end + +#define Record(sha, type) sha : [MSXROMRecord recordWithCartridgeType:StaticAnalyser::MSXCartridgeType::type], +static NSDictionary *romRecordsBySHA1 = @{ + Record(@"da397e783d677d1a78fff222d9d6cb48b915dada", ASCII8kb) // 1942 (1986)(ASCII)(JP).rom + Record(@"0733cd627467a866846e15caf1770a5594eaf4cc", ASCII8kb) // 1942 (1986)(ASCII)(JP)[a].rom + Record(@"ba07b1b585386f887d4c7e457210b3fce819709a", Konami) // 1942 (1987)(Zemina)(KR).rom + Record(@"dd1e87a16e5fb38d9d729ef7edc6da21146a99fe", Konami) // 1942 (1987)(Zemina)(KR)[a].rom + Record(@"6bde4e6761286a2909858ecef04155e17072996e", ASCII8kb) // A Life M36 Planet - MotherBrain has Been Aliving (1987)(Pixel)(JP).rom + Record(@"937464eb371c68add2236bcef91d24a8ce7c4ed1", KonamiWithSCC) // A1 Spirit - The Way to Formula 1 (1987)(Konami)(JP).rom + Record(@"2639792df6f7c7cfaffc2616b0e1849f18897ace", ASCII16kb) // Aliens. Alien 2 (1987)(Square)(JP).rom + Record(@"0d9c472cf7687b86f3fe2e5af6545a26a0efd5fc", ASCII16kb) // Aliens. Alien 2 (1987)(Square)(JP)[a2].rom + Record(@"5380e913d8dac23470446844cab21f6921101af8", ASCII16kb) // Aliens. Alien 2 (1987)(Square)(JP)[a].rom + Record(@"db33011d006201b3bd3bbc4c7c952da2990f36e4", KonamiWithSCC) // Animal Land Murder Case (1987)(Enix)(JP).rom + Record(@"495876c504bdc4de24860ea15b25dda8f3b06c49", ASCII8kb) // Animal Land Murder Case (1987)(Enix)(JP)[a].rom + Record(@"72815a7213f899a454bcf76733834ba499d35cd8", ASCII16kb) // Astro Marine Corps (1989)(Dinamic Software)(ES).rom + Record(@"709fb35338f21897e275237cc4c5615d0a5c2753", ASCII8kb) // Batman (1986)(Pack In Video)(JP).rom + Record(@"3739d841abfef971db76ba10915b19b9df833476", ASCII8kb) // Batman (1986)(Pack In Video)(JP)[a].rom + Record(@"2588b9ade775b93f03fd4b17fd3f78ba70b556d6", ASCII16kb) // Black Onyx II, The - Search for the Fire Crystal (1986)(ASCII)(JP).rom + Record(@"482cd650220e6931f85ee8532c61dac561365e30", ASCII8kb) // Bomber King (1988)(Hudson Soft)(JP).rom + Record(@"16c3ced0fb2e360bc7c43d372a0a30eb6bd3963d", ASCII16kb) // Borfesu. Bolu Fez and Five Evil Spirits (1987)(XTalSoft)(JP).rom + Record(@"16c692a2c9babfdadd8408d2f0f8fae3a8d96fd5", ASCII8kb) // Cosmic Soldier 2 - Psychic War (1987)(Kogado)(JP).rom + Record(@"a731d3d3b5badf33c7602febd32cc4e6ec98c646", Konami) // Craze (1988)(Heart Soft)(JP).rom + Record(@"9e0312e72f30a20f556b64fe37dbbfe0d4471823", ASCII16kb) // Craze (1988)(Heart Soft)(JP)[a].rom + Record(@"25b28cfe8d6d51f619d182c774f6ceb05b577eeb", ASCII16kb) // Cross Blaim (1986)(dB-Soft)(JP).rom + Record(@"bb902e82a2bdda61101a9b3646462adecdd18c8d", FMPac) // Cross Blaim (1986)(dB-Soft)(JP)[b].rom + Record(@"1833ffc252d43d3d8239e57d5ac2c015b4367988", Konami) // Daiva Story 4 - Asura's Bloodfeud (1987)(T&E Soft)(JP).rom + Record(@"2418c0302abbce8b0f8556b63169c60a849f60ee", ASCII8kb) // Daiva Story 4 - Asura's Bloodfeud (1987)(T&E Soft)(JP)[a].rom + Record(@"e6419519c2d3247ea395e4feaa494a2e23e469ce", ASCII8kb) // Deep Dungeon (1988)(Scaptrust)(JP).rom + Record(@"d82135a5e28b750c44995af116db890a15f6428a", ASCII8kb) // Deep Dungeon II (1988)(Scaptrust)(JP).rom + Record(@"63d4e39c59f24f880809caa534d7a46ae83f4c9f", ASCII16kb) // Demon Crystal Saga II - Knither Special (1987)(Radio Wave Newspaper Publisher)(JP).rom + Record(@"d5b164797bc969b55c1a6f4006a4535c3fb03cf0", ASCII8kb) // Demonia (1986)(Microids)(GB)(fr).rom + Record(@"7e9a9ce7c18206b325830e9cdcbb27179118de96", Konami) // Dragon Quest (1986)(Enix)(JP).rom + Record(@"7b94a728a5945a53d518c18994e1e09a09ec3c1b", ASCII8kb) // Dragon Quest (1986)(Enix)(JP)[a].rom + Record(@"3df7c19f739d74d6efdfd8151343e5a55d4ac842", ASCII8kb) // Dragon Quest II (1988)(Enix)(JP).rom + Record(@"68691348a29ce59046f993e9abaf3c8651bdda3c", Konami) // Dragon Quest II (1988)(Enix)(JP)[a2].rom + Record(@"d7b46aece68c924e09f07e3df45711f337d35d6a", ASCII8kb) // Dragon Quest II (1988)(Enix)(JP)[a].rom + Record(@"6c1814c70d69a50ec60e39ef281f0b8cd7bf8598", ASCII8kb) // Dragon Slayer 2 - Xanadu (1987)(Falcom)(JP).rom + Record(@"fcdbc5e15dd6b973e0f1112f4599dad985f48042", Konami) // Dragon Slayer 2 - Xanadu (1987)(Zemina)(KR).rom + Record(@"2f0db48fbcf3444f52b9c7c76ba9c4bd38bc2a15", ASCII16kb) // Dragon Slayer 3 - Romancia. Dragon Slayer Jr (1987)(Falcom)(JP).rom + Record(@"a52c37c1f16ba13d3f39bb5403c82c0187cbff51", ASCII16kb) // Dragon Slayer 3 - Romancia. Dragon Slayer Jr (1987)(Falcom)(JP)[b].rom + Record(@"f100a76117e95ab0335e89a901e47d844bbc0ab6", ASCII8kb) // Dragon Slayer 4 - Drasle Family (1987)(Falcom)(JP).rom + Record(@"d1fbdbdf2e830139584d7dc796806aa3327720dd", ASCII16kb) // Dungeon Hunter (1989)(ASCII)(JP).rom + Record(@"2799d221d5486d48226bbbd3941207e1fc7c985e", ASCII16kb) // Dustin (1987)(Dinamic Software)(ES).rom + Record(@"d7109cf20a22558f923c833ff0b4e2311340acb1", ASCII16kb) // Dynamite Bowl (1988)(Toshiba-EMI)(JP).rom + Record(@"e92baa5fdfb2715e68700024d964098ef35704d9", ASCII16kb) // Eggerland Mystery 2. Meikyushinwa. Labyrinth Myth (1986)(HAL Laboratory)(JP).rom + Record(@"98b7a6ac44b82ccfc45eb51595e2905adabac1c7", ASCII16kb) // Eggerland Mystery 2. Meikyushinwa. Labyrinth Myth (1986)(HAL Laboratory)(JP)[a].rom + Record(@"cf344e0f58fd918c089c4d4575caec8843944ff6", ASCII16kb) // Eggerland Mystery 2. Meikyushinwa. Labyrinth Myth (1986)(HAL Laboratory)(JP)[h THEVMA].rom + Record(@"ff72a1788d47f9876d9fabd720f6a289fb409090", KonamiWithSCC) // F-1 Spirit - The Way to Formula 1 (1987)(Konami)(JP)[a][RC-752].rom + Record(@"42fbb18722df3e34e5b0f935a2dc0ce0d85099e9", KonamiWithSCC) // F-1 Spirit - The Way to Formula 1 (1987)(Konami)(JP)[RC-752].rom + Record(@"3880b064dcb851ca221ff67e435137a7cf1141f8", ASCII8kb) // F-1 Spirit - The Way to Formula 1 (1987)(Konami)(JP)[SCC][RC-752].rom + Record(@"6e5acdfb1c1610257a7aabf3d5aa858866dbcf2e", KonamiWithSCC) // F-1 Spirit - The Way to Formula 1 (1987)(Zemina)(KR)[RC-752].rom + Record(@"84566b5f37ab4f04a2e5b950c5beecbd27b88ea0", Konami) // Fairyland Story, The (1987)(Hot-B)(JP).rom + Record(@"e11afc03db4e1d03976d02796b29da9c65d4ff3d", ASCII8kb) // Fairyland Story, The (1987)(Hot-B)(JP)[a2].rom + Record(@"2bb837a9051277ba574d8351a9f91f9e57033074", ASCII8kb) // Fairyland Story, The (1987)(Hot-B)(JP)[a].rom + Record(@"048737f995eecb1dd8dd341d750efd005267796f", ASCII8kb) // Fantasy Zone (1986)(Pony Canyon)(JP).rom + Record(@"442a39b196f19a22fc7fb8f14bf17386a292b60e", Konami) // Fantasy Zone (1987)(Zemina)(KR).rom + Record(@"a553c3f204c105c23b227a1e5aeb290671ccdbeb", ASCII8kb) // Final Zone Wolf (1986)(Telenet Japan)(JP).rom + Record(@"90ea059c57f011a4fb33a558e868ee639882fe5e", Konami) // Final Zone Wolf (1986)(Zemina)(KR).rom + Record(@"2bd311a4baf59cb85b839cca1f55b7462aa96952", Konami) // Flight Simulator (1986)(subLOGIC)(JP).rom + Record(@"453eac7568d5d04c8cf7da86f2e8dc79777343da", ASCII8kb) // Flight Simulator (1986)(subLOGIC)(JP)[a].rom + Record(@"abe52920e895c148851649ecb9c029fdc41a275f", ASCII16kb) // Gall Force - Defense of Chaos (1986)(Sony)(JP).rom + Record(@"e8ab46785e75e19dac497c4a82ac2f5b37ac0580", ASCII16kb) // Gall Force - Defense of Chaos (1986)(Sony)(JP)[a2].rom + Record(@"3425eea336140da03d7d7f09a94fd928d70e2212", ASCII16kb) // Gall Force - Defense of Chaos (1986)(Sony)(JP)[a].rom + Record(@"89073c052b0fe29b6de077c8bdf5373474081edf", ASCII8kb) // Gambler Jikichushinpa (1988)(Game Arts)(JP).rom + Record(@"12b3c31f0fd10ff5823dcc8bf6dfeb785a8af2f7", FMPac) // Genghis Khan (1986)(Koei)(JP).rom + Record(@"0413bb3aeacb0c28429b8c85b42796dbe48bef6d", KonamiWithSCC) // Gofer no Yabou Episode II. Nemesis 3 - The Eve of Destruction (1988)(Konami)(JP)[a2][RC-764].rom + Record(@"7393f677e0fae5fc83071c6b74756117b7d75e2d", KonamiWithSCC) // Gofer no Yabou Episode II. Nemesis 3 - The Eve of Destruction (1988)(Konami)(JP)[a][RC-764].rom + Record(@"40a0f35dccc7572ae53bcd4be70abfe477d49bc9", KonamiWithSCC) // Gofer no Yabou Episode II. Nemesis 3 - The Eve of Destruction (1988)(Konami)(JP)[o][b][RC-764].rom + Record(@"5692e41b3a4c5e767cf290fd6c24942d0fd7b2e3", KonamiWithSCC) // Gofer no Yabou Episode II. Nemesis 3 - The Eve of Destruction (1988)(Konami)(JP)[RC-764].rom + Record(@"f416b424fb913ca067fe75279c924a20fac5c6a1", ASCII8kb) // Gofer no Yabou Episode II. Nemesis 3 - The Eve of Destruction (1988)(Konami)(JP)[SCC][RC-764].rom + Record(@"520739caa1e0aac1b8eabc4305556aa75f3f5a3b", KonamiWithSCC) // Gofer no Yabou Episode II. Nemesis 3 - The Eve of Destruction (1988)(Konami)(JP)[t][RC-764].rom + Record(@"7a4126934f9e68c34bf00dd3d9a9e753c05ee73f", ASCII16kb) // Golvellius (1987)(Compile)(JP)[a].rom + Record(@"91505fccdcc43230550d101967010bed27f9b573", Konami) // Golvellius (1987)(Compile)(JP)[o].rom + Record(@"930df58762e7b5bcf2d362462f03335bad732398", ASCII16kb) // Golvellius (1987)(Compile).rom + Record(@"4c2f015685a17db7a8c3893e868e0a84a8dbf1e5", KonamiWithSCC) // Gradius 2. Nemesis 2 (1987)(Konami)(beta)[a][RC-751].rom + Record(@"6844758ff5c2c410115d4d7cf12498c42e931732", KonamiWithSCC) // Gradius 2. Nemesis 2 (1987)(Konami)(beta)[RC-751].rom + Record(@"d63e20369f98487767810a0c57603bef6a2a07e5", KonamiWithSCC) // Gradius 2. Nemesis 2 (1987)(Konami)(JP)[a][RC-751].rom + Record(@"c66483cd0d83292e4f2b54a3e89bd96b8bf9abb2", KonamiWithSCC) // Gradius 2. Nemesis 2 (1987)(Konami)(JP)[penguin version][RC-751].rom + Record(@"ab30cdeaacbdf14e6366d43d881338178fc665cb", KonamiWithSCC) // Gradius 2. Nemesis 2 (1987)(Konami)(JP)[RC-751].rom + Record(@"fd7b23a4f1c2058b966b5ddd52cf7ae44a0eebb0", ASCII8kb) // Gradius 2. Nemesis 2 (1987)(Konami)(JP)[[SCC]][RC-751].rom + Record(@"4127844955388f812e33437f618936dc98944c0a", KonamiWithSCC) // Gradius 2. Nemesis 2 (1987)(Konami)(JP)[t][RC-751].rom + Record(@"50efb7040339632cf8bddbc1d3eaae1fb2e2188f", ASCII8kb) // Gradius. Nemesis (1986)(Konami)(JP)[a][RC-742].rom + Record(@"f0e4168ea18188fca2581526c2503223b9a28581", Konami) // Gradius. Nemesis (1986)(Konami)(JP)[a][SCC][RC-742].rom + Record(@"e31ac6520e912c27ce96431a1dfb112bf71cb7b9", Konami) // Gradius. Nemesis (1986)(Konami)(JP)[SCC][RC-742].rom + Record(@"98748c364e7bff50cf073c1a421ebe5b5d8b7025", Konami) // Gradius. Nemesis (1986)(Konami)(JP)[t][SCC][RC-742].rom + Record(@"44b23a175d04ca21236a5fef18600b01b12aaf4d", ASCII8kb) // Haja no Fuin (1987)(Kogado)(JP).rom + Record(@"a3de07612da7986387a4f5c41bbbc7e3b244e077", ASCII16kb) // Harry Fox MSX Special (1986)(Micro Cabin)(JP).rom + Record(@"3626b5dd3188ec2a16e102d05c79f8f242fbd892", FMPac) // Harry Fox Yki no Maoh (1985)(Micro Cabin)(JP).rom + Record(@"26a7b0118d158e9bd3ea947fbe68f57b54e0b847", ASCII16kb) // Head over Heels (1987)(Ocean Software)(GB).rom + Record(@"da4b44c734029f60388b7cea4ab97c3d5c6a09e9", ASCII16kb) // Hydlide II - Shine of Darkness (1986)(T&E Soft)(JP).rom + Record(@"a3537934a4d9dfbf27aca5aaf42e0f18e4975366", ASCII16kb) // Hydlide II - Shine of Darkness (1986)(T&E Soft)(JP)[a].rom + Record(@"b18d36cc60d0e3b325138bb98472b685cca89f90", ASCII16kb) // Hydlide II - Shine of Darkness (1986)(T&E Soft)(JP)[b].rom + Record(@"74e9ea381e2fed07d989d1056002de5737125aaf", ASCII8kb) // Hydlide III - The Space Memories (1987)(T&E Soft)(JP).rom + Record(@"5d2062ecc7176ca2b100724c2a17f877878d721d", Konami) // Hydlide III - The Space Memories (1987)(Zemina)(KR).rom + Record(@"842009e0f7d0e977e47be7a56fe60707f477ed93", Konami) // Jagur (1987)(Hudson Soft)(JP).rom + Record(@"6fefbe448674b6ea846d0c6b9c8a0d57a11aa410", ASCII16kb) // Jagur (1987)(Hudson Soft)(JP)[a2].rom + Record(@"1aeae1180471e9a6e8e866993031b881a341f921", ASCII16kb) // Jagur (1987)(Hudson Soft)(JP)[a3].rom + Record(@"3b6200f88561a59ae5d2b3e94515b89cdb96044b", Konami) // Jagur (1987)(Hudson Soft)(JP)[a].rom + Record(@"1c462c3629d43297a006ba9055b39a2dccba9f6c", ASCII8kb) // Karuizawa Kidnapping Guidance, The (1986)(Enix)(JP).rom + Record(@"46062d3393c49884f84c2dc437ff27854e9d2e49", ASCII16kb) // King's Knight (1986)(Square)(JP).rom + Record(@"122f659250a0ae10ce0be0dde626dd3e384affa7", ASCII16kb) // King's Knight (1986)(Square)(JP)[a2].rom + Record(@"a2ca7e6e216f8b450eb8db10a4120f0353275b6b", ASCII16kb) // King's Knight (1986)(Square)(JP)[a3].rom + Record(@"438bbb3367db4938b3d90fa9d2cfb1f08c072bb7", Konami) // King's Knight (1986)(Square)(JP)[a].rom + Record(@"cfd872d005b7bd4cdd6e06c4c0162191f0b0415d", KonamiWithSCC) // King's Valley II - The Seal of El Giza (1988)(Konami)(JP)[RC-760].rom + Record(@"f3306e6f25d111da21ce66db3404f5f48acb25a1", ASCII8kb) // King's Valley II - The Seal of El Giza (1988)(Konami)(JP)[SCC][RC-760].rom + Record(@"ee60e88ae409ddd93d4259b79586dacb2e5ee372", ASCII8kb) // Knightmare II - The Maze of Galious (1987)(Konami)(JP)[a][RC-749].rom + Record(@"4d51d3c5036311392b173a576bc7d91dc9fed6cb", Konami) // Knightmare II - The Maze of Galious (1987)(Konami)(JP)[RC-749].rom + Record(@"7a786959d8fc0b518b35341422f096dd6019468d", Konami) // Knightmare II - The Maze of Galious (1987)(Zemina)(KR)[RC-749].rom + Record(@"f999e0187023413d839a67337d0595e150b2398f", ASCII8kb) // Knightmare III - Shalom (1987)(Konami)(JP)[a2][RC-754].rom + Record(@"3b6f140c99cba5bfa510200df49d1e573d687e4d", Konami) // Knightmare III - Shalom (1987)(Konami)(JP)[a3][RC-754].rom + Record(@"240e15fb5d918aa821f226002772dc800d9f20a4", ASCII8kb) // Knightmare III - Shalom (1987)(Konami)(JP)[a][RC-754].rom + Record(@"25f5adeca8a2ddb754d25eb18cff0d84e5b003bc", Konami) // Knightmare III - Shalom (1987)(Konami)(JP)[RC-754].rom + Record(@"8c609b8bee4245a1bb81e37d888ac5efb66533cf", Konami) // Knightmare III - Shalom (1987)(Konami)(JP)[tr pt R. Bittencourt][RC-754].rom + Record(@"6a98d5787dd0e76f04283ce0aec55d45ba81565d", Konami) // Legendly Knight (1988)(Topia)(KR).rom + Record(@"2cea9cdd501d77f2b6bd78ae2ae9a63aba64cfed", ASCII8kb) // Legendly Knight (1988)(Topia)(KR)[a].rom + Record(@"0442dd29ea0f7b78c9cd5849ec7ef5bb21ec0bf5", ASCII16kb) // Light Corridor, The (1990)(Infogrames)(FR).rom + Record(@"3243a5cc562451de527917e9ca85657286b2852f", ASCII16kb) // Magunam. Kiki Ippatsu. Magnum Prohibition 1931 (1988)(Toshiba-EMI)(JP).rom + Record(@"9c5c1c40ec30c34b1b436cf8cc494c0b509e81fc", ASCII16kb) // Magunam. Kiki Ippatsu. Magnum Prohibition 1931 (1988)(Toshiba-EMI)(JP)[a].rom + Record(@"f7dd6841d280cbffa9f0f2da7af3549f23270ddb", ASCII8kb) // Marchen Veil (1986)(System Sacom)(JP).rom + Record(@"0cb11c766bd357d203879bd6bee041a4690cc3df", ASCII16kb) // Meikyuu no Tobira. Gate of Labyrinth (1987)(Radio Wave Newspaper Publisher)(JP).rom + Record(@"7abf89652396a648a84ae06e6dabc09735a75798", ASCII8kb) // Mirai. Future (1987)(Xain)(JP).rom + Record(@"7341efc039394ec159feebcfaa9d4a61ebf08a18", Konami) // Mitsume ga Tooru. The Three-Eyed One Comes Here (1989)(Natsume)(JP).rom + Record(@"176ec8e65a9fdbf59edc245b9e8388cc94195db9", ASCII8kb) // Mitsume ga Tooru. The Three-Eyed One Comes Here (1989)(Natsume)(JP)[a2].rom + Record(@"0ec71916791e05d207d7fe0a461a79a76eab52c5", Konami) // Mitsume ga Tooru. The Three-Eyed One Comes Here (1989)(Natsume)(JP)[a].rom + Record(@"03b42b77b1a7412f1d7bd0998cf8f2f003f77d0a", Konami) // Monogatari Megami Tensei. Digital Devil Story (1987)(Telenet Japan)(JP).rom + Record(@"7dc7f7e3966943280f34836656a7d1bd3ace67cd", ASCII8kb) // Monogatari Megami Tensei. Digital Devil Story (1987)(Telenet Japan)(JP)[a].rom + Record(@"e6de8bbd60123444de2a90928853985ceb0b4cbf", FMPac) // Nobunaga no Yabou - Zenkoku Han (1987)(Koei)(JP).rom + Record(@"75d3b72d9ceeaa55c76223d935629a30ae4124d6", KonamiWithSCC) // Parodius - Tako Saves Earth (1988)(Konami)(JP)[a][RC-759].rom + Record(@"7c066cb763f7a4fec0474b5a09e3ef43bbf9248b", ASCII8kb) // Parodius - Tako Saves Earth (1988)(Konami)(JP)[a][SCC][RC-759].rom + Record(@"2220363ae56ef707ab2471fcdb36f4816ad1d32c", KonamiWithSCC) // Parodius - Tako Saves Earth (1988)(Konami)(JP)[RC-759].rom + Record(@"0b02dda5316a318a7f75a811aa54200ddd7abc30", ASCII8kb) // Parodius - Tako Saves Earth (1988)(Konami)(JP)[SCC][RC-759].rom + Record(@"2111cec4b5ea698d772bb80664f6be690b47391c", KonamiWithSCC) // Parodius - Tako Saves Earth (1988)(Konami)(JP)[t][RC-759].rom + Record(@"c95f8edb24edca9f5d38c26d0e8a34c6b61efb0c", ASCII16kb) // Pinball Blaster (1988)(Eurosoft)(NL).rom + Record(@"37bd4680a36c3a1a078e2bc47b631d858d9296b8", FMPac) // R-Type (1988)(IREM)(JP).rom + Record(@"9c886fff02779267041efe45dadefc5fd7f4b9a2", FMPac) // R-Type v2 (1988)(IREM)(JP).rom + Record(@"0b379610cb7085005a56b24a0890b79dd5a7e817", FMPac) // R-Type v2 (1988)(IREM)(JP)[a].rom + Record(@"74ae85d44cb8ef1bae428c90200cb74be6d56d3a", ASCII8kb) // Relics (1986)(Bothtec)(JP).rom + Record(@"46c98a3143f5a80b2090311a770f9b73000881c0", ASCII16kb) // Robowres 2001 (1987)(Micronet)(JP).rom + Record(@"0d459788b6c464b50cbc2436e67a2cef248e0c4a", KonamiWithSCC) // Salamander - Operation X (1987)(Konami)(JP)[RC-758].rom + Record(@"0d76a069726fec7326541f75b809b8b72148ed3a", ASCII8kb) // Salamander - Operation X (1987)(Konami)(JP)[SCC][RC-758].rom + Record(@"655b15e8fd81866492bcf2b1d6609211b30efce1", KonamiWithSCC) // Salamander - Operation X (1987)(Konami)(JP)[t][RC-758].rom + Record(@"d30c17109fa1c4a81e39dca57b79464b0aa0b7c2", KonamiWithSCC) // Salamander - Operation X (1988)(Zemina)(KR)[RC-758].rom + Record(@"96e2a7163c755fbea77abfd9d09a798687a5a993", Konami) // Sangokushi. Romance of Three Kingdoms (1986)(Koei)(JP).rom + Record(@"3f741ba2ab08c5e9fb658882b36b8e3d01682f58", ASCII8kb) // Sangokushi. Romance of Three Kingdoms (1986)(Koei)(JP)[a2].rom + Record(@"7ad40ae512bbf4ba688467ba23e16354b8421d0a", Konami) // Sangokushi. Romance of Three Kingdoms (1986)(Koei)(JP)[a3].rom + Record(@"14ff6fe464362c6b7dbb47b2ecda3a8c5f05ef79", ASCII8kb) // Sangokushi. Romance of Three Kingdoms (1986)(Koei)(JP)[a].rom + Record(@"2b255c3e5615b2f5e2365419a35e4115a060e93c", ASCII8kb) // Senjyo no Ookami. Wolf's Battlefield. Commando (1987)(ASCII)(JP).rom + Record(@"5a12439f74ca5d1c2664f01ff6a8302d3ce907a8", ASCII8kb) // Sofia (1988)(Radio Wave Newspaper Publisher)(JP).rom + Record(@"3caeec19423a960d98e4719b301a3d276339e5ae", ASCII8kb) // Sofia (1988)(Radio Wave Newspaper Publisher)(JP)[o].rom + Record(@"9bafce699964f4aabc6b60c71a71c9ff5b0cc82d", ASCII8kb) // Super Boy III (1991)(Zemina)(KR).rom + Record(@"2dfbca8f5cc3a9e9d151382ebe0da410f5393eaf", ASCII8kb) // Super Laydock - Mission Striker (1987)(T&E Soft)(JP).rom + Record(@"244399d67d7851f3daa9bb87a14f5b8ef6d8c160", Konami) // Super Laydock - Mission Striker (1988)(Zemina)(KR).rom + Record(@"0d2f86dbb70f4b4a4e4dc1bc95df232f48856037", FMPac) // Super Pierrot (1988)(Nidecom)(JP).rom + Record(@"16351e6a7d7fc38c23b54ee15ac6f0275621ba96", ASCII8kb) // Syougun. Shigun (1987)(Nippon Dexter)(JP).rom + Record(@"d147f2cac0600527ce49bfffc865c54eb783e5e5", ASCII16kb) // Toobin' (1989)(Domark)(GB).rom + Record(@"bfe8046f8ccc6d6016d7752c04f0654420ef81e7", ASCII16kb) // Tumego 120 (1987)(Champion Soft)(JP).rom + Record(@"2b10234debd2a6a9a02e0750ba6563768bc4a2f3", ASCII8kb) // Valis - The Fantasm Soldier (1986)(Telenet Japan)(JP).rom + Record(@"dccdb2d18c70a94e48b3ec5e0cb986c5d708bbc9", Konami) // Valis - The Fantasm Soldier (1987)(Zemina)(KR).rom + Record(@"4340a2580c949d498d2c8e71699fff860214e9ea", ASCII16kb) // Vaxol (1987)(Heart Soft)(JP).rom + Record(@"818d91505ad39bba2eaf7f4857c7d41e95fcb233", ASCII8kb) // Wing Man 2 (1987)(Enix)(JP).rom + Record(@"b6a5552effcee708b665fa74e5ce7b0fa2541c03", Konami) // Young Sherlock - The Legacy of Doyle (1985)(Pack In Video)(JP).rom + Record(@"97e173dac64dbde7d6a60de7606cba0c860813db", ASCII8kb) // Young Sherlock - The Legacy of Doyle (1985)(Pack In Video)(JP)[a].rom + Record(@"d0706fd10e418eba2929d515cc0994f49376a63f", Konami) // Yume Tairiku Adventure. Penguin Adventure (1986)(Konami)(JP)[a2][RC-743].rom + Record(@"d53e0c8bcd98820afe820f756af35cc97911bfe4", Konami) // Yume Tairiku Adventure. Penguin Adventure (1986)(Konami)(JP)[a][RC-743].rom + Record(@"898bda19f882c6d1dffdb2173db84a97dc21f7d6", Konami) // Yume Tairiku Adventure. Penguin Adventure (1986)(Konami)(JP)[cr Screen][RC-743].rom + Record(@"fa6c059e14092d023b1f9f2df28e482f966287db", ASCII8kb) // Yume Tairiku Adventure. Penguin Adventure (1986)(Konami)(JP)[RC-743].rom + Record(@"d3d411c8b7891aef9c59cbc20bb4fa3ff0ca03ea", Konami) // Yume Tairiku Adventure. Penguin Adventure (1987)(Zemina)(KR)[RC-743].rom +}; +#undef Record + +@interface MSXStaticAnalyserTests : XCTestCase +@end + +@implementation MSXStaticAnalyserTests + +- (void)testROMs { + NSString *basePath = [[[NSBundle bundleForClass:[self class]] resourcePath] stringByAppendingPathComponent:@"MSX ROMs"]; + for(NSString *testFile in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:basePath error:nil]) { + NSString *fullPath = [basePath stringByAppendingPathComponent:testFile]; + + // get a SHA1 for the file + NSData *fileData = [NSData dataWithContentsOfFile:fullPath]; + uint8_t sha1Bytes[CC_SHA1_DIGEST_LENGTH]; + CC_SHA1([fileData bytes], (CC_LONG)[fileData length], sha1Bytes); + NSMutableString *sha1 = [[NSMutableString alloc] init]; + for(int c = 0; c < CC_SHA1_DIGEST_LENGTH; c++) [sha1 appendFormat:@"%02x", sha1Bytes[c]]; + + // get an analysis of the file + std::list targets = StaticAnalyser::GetTargets([fullPath UTF8String]); + + // grab the ROM record + MSXROMRecord *romRecord = romRecordsBySHA1[sha1]; + if(!romRecord) { + continue; + } + + // assert equality + XCTAssert(!targets.empty(), "%@ should be recognised as an MSX file", testFile); + if(!targets.empty()) { + XCTAssert(targets.front().msx.cartridge_type == romRecord.cartridgeType, @"%@; should be %d, is %d", testFile, romRecord.cartridgeType, targets.front().msx.cartridge_type); + } + } +} + +@end diff --git a/StaticAnalyser/MSX/StaticAnalyser.cpp b/StaticAnalyser/MSX/StaticAnalyser.cpp index 3006b7b6c..22d8d9aab 100644 --- a/StaticAnalyser/MSX/StaticAnalyser.cpp +++ b/StaticAnalyser/MSX/StaticAnalyser.cpp @@ -39,55 +39,154 @@ static std::list> const size_t data_size = segment.data.size(); if(data_size < 0x2000 || data_size & 0x3fff) continue; - // Check for a ROM header at address 0; TODO: if it's not found then try 0x4000 - // and consider swapping the image. + // Check for a ROM header at address 0; if it's not found then try 0x4000 + // and adjust the start address; + uint16_t start_address = 0; + bool found_start = false; + if(segment.data[0] == 0x41 && segment.data[1] == 0x42) { + start_address = 0x4000; + found_start = true; + } else if(segment.data.size() >= 0x8000 && segment.data[0x4000] == 0x41 && segment.data[0x4001] == 0x42) { + start_address = 0; + found_start = true; + } - // Check for the expansion ROM header and the reserved bytes. - if(segment.data[0] != 0x41 || segment.data[1] != 0x42) continue; + // Reject cartridge if the ROM header wasn't found. + if(!found_start) continue; uint16_t init_address = static_cast(segment.data[2] | (segment.data[3] << 8)); // TODO: check for a rational init address? - // If this ROM is greater than 32kb in size then some sort of MegaROM scheme must + // If this ROM is greater than 48kb in size then some sort of MegaROM scheme must // be at play; disassemble to try to figure it out. - if(data_size > 0x4000) { - std::vector first_segment; - first_segment.insert(first_segment.begin(), segment.data.begin(), segment.data.begin() + 32768); + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::None; + if(data_size > 0xc000) { + std::vector first_16k; + first_16k.insert(first_16k.begin(), segment.data.begin(), segment.data.begin() + 8192); StaticAnalyser::Z80::Disassembly disassembly = StaticAnalyser::Z80::Disassemble( - first_segment, - StaticAnalyser::Disassembler::OffsetMapper(0x4000), + first_16k, + StaticAnalyser::Disassembler::OffsetMapper(start_address), { init_address } ); - // Look for LD (nnnn), A instructions, and collate those addresses. + // Look for a indirect store followed by an unconditional JP or CALL into another + // segment, that's a fairly explicit sign where found. using Instruction = StaticAnalyser::Z80::Instruction; - std::map address_counts; - for(const auto &instruction_pair : disassembly.instructions_by_address) { - if( instruction_pair.second.operation == Instruction::Operation::LD && - instruction_pair.second.destination == Instruction::Location::Operand_Indirect && - instruction_pair.second.source == Instruction::Location::A) { - address_counts[static_cast(instruction_pair.second.operand)]++; + std::map &instructions = disassembly.instructions_by_address; + bool is_ascii = false; + auto iterator = instructions.begin(); + while(iterator != instructions.end()) { + auto next_iterator = iterator; + next_iterator++; + if(next_iterator == instructions.end()) break; + + if( iterator->second.operation == Instruction::Operation::LD && + iterator->second.destination == Instruction::Location::Operand_Indirect && + ( + iterator->second.operand == 0x5000 || + iterator->second.operand == 0x6000 || + iterator->second.operand == 0x6800 || + iterator->second.operand == 0x7000 || + iterator->second.operand == 0x77ff || + iterator->second.operand == 0x7800 || + iterator->second.operand == 0x8000 || + iterator->second.operand == 0x9000 || + iterator->second.operand == 0xa000 + ) && + ( + next_iterator->second.operation == Instruction::Operation::CALL || + next_iterator->second.operation == Instruction::Operation::JP + ) && + ((next_iterator->second.operand >> 13) != (0x4000 >> 13)) + ) { + const uint16_t address = static_cast(next_iterator->second.operand); + switch(iterator->second.operand) { + case 0x6000: + if(address >= 0x6000 && address < 0x8000) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::KonamiWithSCC; + } + break; + case 0x6800: + if(address >= 0x6000 && address < 0x6800) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::ASCII8kb; + } + break; + case 0x7000: + if(address >= 0x6000 && address < 0x8000) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::KonamiWithSCC; + } + if(address >= 0x7000 && address < 0x7800) { + is_ascii = true; + } + break; + case 0x77ff: + if(address >= 0x7000 && address < 0x7800) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::ASCII16kb; + } + break; + case 0x7800: + if(address >= 0xa000 && address < 0xc000) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::ASCII8kb; + } + break; + case 0x8000: + if(address >= 0x8000 && address < 0xa000) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::KonamiWithSCC; + } + break; + case 0x9000: + if(address >= 0x8000 && address < 0xa000) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::KonamiWithSCC; + } + break; + case 0xa000: + if(address >= 0xa000 && address < 0xc000) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::Konami; + } + break; + case 0xb000: + if(address >= 0xa000 && address < 0xc000) { + target.msx.cartridge_type = StaticAnalyser::MSXCartridgeType::KonamiWithSCC; + } + break; + } } + + iterator = next_iterator; } - // Sort possible cartridge types. - using Possibility = std::pair; - std::vector possibilities; - possibilities.push_back(std::make_pair(StaticAnalyser::MSXCartridgeType::Konami, address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000])); - possibilities.push_back(std::make_pair(StaticAnalyser::MSXCartridgeType::KonamiWithSCC, address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000])); - possibilities.push_back(std::make_pair(StaticAnalyser::MSXCartridgeType::ASCII8kb, address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800])); - possibilities.push_back(std::make_pair(StaticAnalyser::MSXCartridgeType::ASCII16kb, address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff])); - std::sort(possibilities.begin(), possibilities.end(), [](const Possibility &a, const Possibility &b) { - return a.second > b.second; - }); + if(target.msx.cartridge_type == StaticAnalyser::MSXCartridgeType::None) { + // Look for LD (nnnn), A instructions, and collate those addresses. + std::map address_counts; + for(const auto &instruction_pair : instructions) { + if( instruction_pair.second.operation == Instruction::Operation::LD && + instruction_pair.second.destination == Instruction::Location::Operand_Indirect && + instruction_pair.second.source == Instruction::Location::A) { + address_counts[static_cast(instruction_pair.second.operand)]++; + } + } - target.msx.paging_model = possibilities[0].first; + // Sort possible cartridge types. + using Possibility = std::pair; + std::vector possibilities; + // Add to list in order of declining probability, so that stable_sort below prefers + // the more likely option in a tie. + possibilities.push_back(std::make_pair(StaticAnalyser::MSXCartridgeType::ASCII8kb, address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800])); + possibilities.push_back(std::make_pair(StaticAnalyser::MSXCartridgeType::ASCII16kb, address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff])); + if(!is_ascii) possibilities.push_back(std::make_pair(StaticAnalyser::MSXCartridgeType::Konami, address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000])); + if(!is_ascii) possibilities.push_back(std::make_pair(StaticAnalyser::MSXCartridgeType::KonamiWithSCC, address_counts[0x5000] + address_counts[0x7000] + address_counts[0x9000] + address_counts[0xb000])); + std::stable_sort(possibilities.begin(), possibilities.end(), [](const Possibility &a, const Possibility &b) { + return a.second > b.second; + }); + + target.msx.cartridge_type = possibilities[0].first; + } } // Apply the standard MSX start address. msx_cartridges.emplace_back(new Storage::Cartridge::Cartridge({ - Storage::Cartridge::Cartridge::Segment(0x4000, segment.data) + Storage::Cartridge::Cartridge::Segment(start_address, segment.data) })); } diff --git a/StaticAnalyser/StaticAnalyser.hpp b/StaticAnalyser/StaticAnalyser.hpp index 693c4a9cf..b4bcee655 100644 --- a/StaticAnalyser/StaticAnalyser.hpp +++ b/StaticAnalyser/StaticAnalyser.hpp @@ -46,7 +46,7 @@ enum class MSXCartridgeType { KonamiWithSCC, ASCII8kb, ASCII16kb, - RType + FMPac }; enum class ZX8081MemoryModel { @@ -124,7 +124,7 @@ struct Target { } amstradcpc; struct { - MSXCartridgeType paging_model; + MSXCartridgeType cartridge_type; } msx; };