diff --git a/Analyser/Static/MSX/Cartridge.hpp b/Analyser/Static/MSX/Cartridge.hpp new file mode 100644 index 000000000..a2d4f8a71 --- /dev/null +++ b/Analyser/Static/MSX/Cartridge.hpp @@ -0,0 +1,40 @@ +// +// Cartridge.hpp +// Clock Signal +// +// Created by Thomas Harte on 25/01/2018. +// Copyright © 2018 Thomas Harte. All rights reserved. +// + +#ifndef Cartridge_hpp +#define Cartridge_hpp + +#include "../../../Storage/Cartridge/Cartridge.hpp" + +namespace Analyser { +namespace Static { +namespace MSX { + +/*! + Extends the base cartridge class by adding a (guess at) the banking scheme. +*/ +struct Cartridge: public ::Storage::Cartridge::Cartridge { + enum Type { + None, + Konami, + KonamiWithSCC, + ASCII8kb, + ASCII16kb, + FMPac + }; + const Type type; + + Cartridge(const std::vector &segments, Type type) : + Storage::Cartridge::Cartridge(segments), type(type) {} +}; + +} +} +} + +#endif /* Cartridge_hpp */ diff --git a/Analyser/Static/MSX/StaticAnalyser.cpp b/Analyser/Static/MSX/StaticAnalyser.cpp index 78f3d582f..de0d0ef76 100644 --- a/Analyser/Static/MSX/StaticAnalyser.cpp +++ b/Analyser/Static/MSX/StaticAnalyser.cpp @@ -8,12 +8,43 @@ #include "StaticAnalyser.hpp" +#include "Cartridge.hpp" #include "Tape.hpp" #include "../Disassembler/Z80.hpp" #include "../Disassembler/AddressMapper.hpp" #include +static std::unique_ptr CartridgeTarget( + const Storage::Cartridge::Cartridge::Segment &segment, + uint16_t start_address, + Analyser::Static::MSX::Cartridge::Type type, + float confidence) { + + // Size down to a multiple of 8kb in size and apply the start address. + std::vector output_segments; + if(segment.data.size() & 0x1fff) { + std::vector truncated_data; + std::vector::difference_type truncated_size = static_cast::difference_type>(segment.data.size()) & ~0x1fff; + truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); + output_segments.emplace_back(start_address, truncated_data); + } else { + output_segments.emplace_back(start_address, segment.data); + } + + std::unique_ptr target(new Analyser::Static::Target); + target->machine = Analyser::Machine::MSX; + target->confidence = confidence; + + if(type == Analyser::Static::MSX::Cartridge::Type::None) { + target->media.cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); + } else { + target->media.cartridges.emplace_back(new Analyser::Static::MSX::Cartridge(output_segments, type)); + } + + return target; +} + /* Expected standard cartridge format: @@ -23,11 +54,21 @@ DEFW device; pointer to expansion device handler, 0 if no such handler DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram DEFS 6,0 ; room reserved for future extensions -*/ -static std::vector> - MSXCartridgesFrom(const std::vector> &cartridges, Analyser::Static::Target &target) { - std::vector> msx_cartridges; + MSX cartridges often include banking hardware; those games were marketed as MegaROMs. The file + format that the MSX community has decided upon doesn't retain the type of hardware included, so + this analyser has to guess. + + (additional audio hardware is also sometimes included, but it's implied by the banking hardware) +*/ +static std::vector> CartridgeTargetsFrom( + const std::vector> &cartridges) { + // No cartridges implies no targets. + if(cartridges.empty()) { + return {}; + } + + std::vector> targets; for(const auto &cartridge : cartridges) { const auto &segments = cartridge->get_segments(); @@ -57,154 +98,174 @@ static std::vector> uint16_t init_address = static_cast(segment.data[2] | (segment.data[3] << 8)); // TODO: check for a rational init address? + // If this ROM is less than 48kb in size then it's an ordinary ROM. Just emplace it and move on. + if(data_size <= 0xc000) { + targets.emplace_back(CartridgeTarget(segment, start_address, Analyser::Static::MSX::Cartridge::Type::None, 1.0)); + continue; + } + // 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. - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::None; - if(data_size > 0xc000) { - std::vector first_16k; - first_16k.insert(first_16k.begin(), segment.data.begin(), segment.data.begin() + 8192); - Analyser::Static::Z80::Disassembly disassembly = - Analyser::Static::Z80::Disassemble( - first_16k, - Analyser::Static::Disassembler::OffsetMapper(start_address), - { init_address } - ); + std::vector first_8k; + first_8k.insert(first_8k.begin(), segment.data.begin(), segment.data.begin() + 8192); + Analyser::Static::Z80::Disassembly disassembly = + Analyser::Static::Z80::Disassemble( + first_8k, + Analyser::Static::Disassembler::OffsetMapper(start_address), + { init_address } + ); - // 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 = Analyser::Static::Z80::Instruction; - 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; +// // 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 = Analyser::Static::Z80::Instruction; + 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 = Analyser::Static::MSXCartridgeType::KonamiWithSCC; +// } +// break; +// case 0x6800: +// if(address >= 0x6000 && address < 0x6800) { +// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb; +// } +// break; +// case 0x7000: +// if(address >= 0x6000 && address < 0x8000) { +// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; +// } +// if(address >= 0x7000 && address < 0x7800) { +// is_ascii = true; +// } +// break; +// case 0x77ff: +// if(address >= 0x7000 && address < 0x7800) { +// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb; +// } +// break; +// case 0x7800: +// if(address >= 0xa000 && address < 0xc000) { +// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb; +// } +// break; +// case 0x8000: +// if(address >= 0x8000 && address < 0xa000) { +// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; +// } +// break; +// case 0x9000: +// if(address >= 0x8000 && address < 0xa000) { +// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; +// } +// break; +// case 0xa000: +// if(address >= 0xa000 && address < 0xc000) { +// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami; +// } +// break; +// case 0xb000: +// if(address >= 0xa000 && address < 0xc000) { +// target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; +// } +// break; +// } +// } +// +// iterator = next_iterator; - 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 = Analyser::Static::MSXCartridgeType::KonamiWithSCC; - } - break; - case 0x6800: - if(address >= 0x6000 && address < 0x6800) { - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb; - } - break; - case 0x7000: - if(address >= 0x6000 && address < 0x8000) { - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; - } - if(address >= 0x7000 && address < 0x7800) { - is_ascii = true; - } - break; - case 0x77ff: - if(address >= 0x7000 && address < 0x7800) { - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII16kb; - } - break; - case 0x7800: - if(address >= 0xa000 && address < 0xc000) { - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::ASCII8kb; - } - break; - case 0x8000: - if(address >= 0x8000 && address < 0xa000) { - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; - } - break; - case 0x9000: - if(address >= 0x8000 && address < 0xa000) { - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; - } - break; - case 0xa000: - if(address >= 0xa000 && address < 0xc000) { - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::Konami; - } - break; - case 0xb000: - if(address >= 0xa000 && address < 0xc000) { - target.msx.cartridge_type = Analyser::Static::MSXCartridgeType::KonamiWithSCC; - } - break; - } - } - - iterator = next_iterator; - } - - if(target.msx.cartridge_type == Analyser::Static::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)]++; - } - } - - // 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(Analyser::Static::MSXCartridgeType::ASCII8kb, address_counts[0x6000] + address_counts[0x6800] + address_counts[0x7000] + address_counts[0x7800])); - possibilities.push_back(std::make_pair(Analyser::Static::MSXCartridgeType::ASCII16kb, address_counts[0x6000] + address_counts[0x7000] + address_counts[0x77ff])); - if(!is_ascii) possibilities.push_back(std::make_pair(Analyser::Static::MSXCartridgeType::Konami, address_counts[0x6000] + address_counts[0x8000] + address_counts[0xa000])); - if(!is_ascii) possibilities.push_back(std::make_pair(Analyser::Static::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; + // 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)]++; } } - // Size down to a multiple of 8kb in size and apply the start address. - std::vector output_segments; - if(segment.data.size() & 0x1fff) { - std::vector truncated_data; - std::vector::difference_type truncated_size = static_cast::difference_type>(segment.data.size()) & ~0x1fff; - truncated_data.insert(truncated_data.begin(), segment.data.begin(), segment.data.begin() + truncated_size); - output_segments.emplace_back(start_address, truncated_data); - } else { - output_segments.emplace_back(start_address, segment.data); + // Weight confidences by number of observed hits. + float total_hits = + static_cast( + address_counts[0x6000] + address_counts[0x6800] + + address_counts[0x7000] + address_counts[0x7800] + + address_counts[0x77ff] + address_counts[0x8000] + + address_counts[0xa000] + address_counts[0x5000] + + address_counts[0x9000] + address_counts[0xb000] + ); + + targets.push_back(CartridgeTarget( + segment, + start_address, + Analyser::Static::MSX::Cartridge::ASCII8kb, + static_cast( address_counts[0x6000] + + address_counts[0x6800] + + address_counts[0x7000] + + address_counts[0x7800]) / total_hits)); + targets.push_back(CartridgeTarget( + segment, + start_address, + Analyser::Static::MSX::Cartridge::ASCII16kb, + static_cast( address_counts[0x6000] + + address_counts[0x7000] + + address_counts[0x77ff]) / total_hits)); + if(!is_ascii) { + targets.push_back(CartridgeTarget( + segment, + start_address, + Analyser::Static::MSX::Cartridge::Konami, + static_cast( address_counts[0x6000] + + address_counts[0x8000] + + address_counts[0xa000]) / total_hits)); + } + if(!is_ascii) { + targets.push_back(CartridgeTarget( + segment, + start_address, + Analyser::Static::MSX::Cartridge::KonamiWithSCC, + static_cast( address_counts[0x5000] + + address_counts[0x7000] + + address_counts[0x9000] + + address_counts[0xb000]) / total_hits)); } - msx_cartridges.emplace_back(new Storage::Cartridge::Cartridge(output_segments)); } - return msx_cartridges; + return targets; } void Analyser::Static::MSX::AddTargets(const Media &media, std::vector> &destination) { - std::unique_ptr target(new Target); + // Append targets for any cartridges that look correct. + std::vector> cartridge_targets = CartridgeTargetsFrom(media.cartridges); + std::move(cartridge_targets.begin(), cartridge_targets.end(), std::back_inserter(destination)); - // Obtain only those cartridges which it looks like an MSX would understand. - target->media.cartridges = MSXCartridgesFrom(media.cartridges, *target); + // Consider building a target for disks and/or tapes. + std::unique_ptr target(new Target); // Check tapes for loadable files. for(const auto &tape : media.tapes) { diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 6f59f8853..dab1e7705 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -164,5 +164,11 @@ std::vector> Analyser::Static::GetTargets(const char *fi } } + // Sort by initial confidence. Use a stable sort in case any of the machine-specific analysers + // picked their insertion order carefully. + std::stable_sort(targets.begin(), targets.end(), [](auto &a, auto &b) { + return a->confidence > b->confidence; + }); + return targets; } diff --git a/Analyser/Static/StaticAnalyser.hpp b/Analyser/Static/StaticAnalyser.hpp index 7cf4a738d..3a679c0a8 100644 --- a/Analyser/Static/StaticAnalyser.hpp +++ b/Analyser/Static/StaticAnalyser.hpp @@ -42,15 +42,6 @@ enum class Atari2600PagingModel { Pitfall2 }; -enum class MSXCartridgeType { - None, - Konami, - KonamiWithSCC, - ASCII8kb, - ASCII16kb, - FMPac -}; - enum class ZX8081MemoryModel { Unexpanded, SixteenKB, @@ -119,10 +110,6 @@ struct Target { struct { AmstradCPCModel model; } amstradcpc; - - struct { - MSXCartridgeType cartridge_type; - } msx; }; }; diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 40a697f06..2d550408b 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -12,6 +12,7 @@ #include "Keyboard.hpp" #include "ROMSlotHandler.hpp" +#include "../../Analyser/Static/MSX/Cartridge.hpp" #include "Cartridges/ASCII8kb.hpp" #include "Cartridges/ASCII16kb.hpp" #include "Cartridges/Konami.hpp" @@ -175,23 +176,6 @@ class ConcreteMachine: if(target.loading_command.length()) { type_string(target.loading_command); } - - // Attach the hardware necessary for a game cartridge, if any. - switch(target.msx.cartridge_type) { - default: break; - case Analyser::Static::MSXCartridgeType::Konami: - memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1)); - break; - case Analyser::Static::MSXCartridgeType::KonamiWithSCC: - memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_)); - break; - case Analyser::Static::MSXCartridgeType::ASCII8kb: - memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1)); - break; - case Analyser::Static::MSXCartridgeType::ASCII16kb: - memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1)); - break; - } } bool insert_media(const Analyser::Static::Media &media) override { @@ -199,6 +183,25 @@ class ConcreteMachine: const auto &segment = media.cartridges.front()->get_segments().front(); memory_slots_[1].source = segment.data; map(1, 0, static_cast(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address)); + + auto msx_cartridge = dynamic_cast(media.cartridges.front().get()); + if(msx_cartridge) { + switch(msx_cartridge->type) { + default: break; + case Analyser::Static::MSX::Cartridge::Konami: + memory_slots_[1].set_handler(new Cartridge::KonamiROMSlotHandler(*this, 1)); + break; + case Analyser::Static::MSX::Cartridge::KonamiWithSCC: + memory_slots_[1].set_handler(new Cartridge::KonamiWithSCCROMSlotHandler(*this, 1, scc_)); + break; + case Analyser::Static::MSX::Cartridge::ASCII8kb: + memory_slots_[1].set_handler(new Cartridge::ASCII8kbROMSlotHandler(*this, 1)); + break; + case Analyser::Static::MSX::Cartridge::ASCII16kb: + memory_slots_[1].set_handler(new Cartridge::ASCII16kbROMSlotHandler(*this, 1)); + break; + } + } } if(!media.tapes.empty()) { diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index bceb8e7dc..11f66c982 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -644,6 +644,7 @@ /* Begin PBXFileReference section */ 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = ""; }; 4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = ""; }; + 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 = ""; }; 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; }; @@ -2163,6 +2164,7 @@ children = ( 4B894513201967B4007DE474 /* StaticAnalyser.cpp */, 4B894512201967B4007DE474 /* Tape.cpp */, + 4B047075201ABC180047AB0D /* Cartridge.hpp */, 4B894510201967B4007DE474 /* StaticAnalyser.hpp */, 4B894511201967B4007DE474 /* Tape.hpp */, );