1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-26 10:29:31 +00:00

The MSX analyser is now smart enough not to be definitive when it's uncertain.

The cartridge type has also migrated to being a property of the cartridge, prefiguring my intention to discard the static analyser union.
This commit is contained in:
Thomas Harte 2018-01-25 22:16:46 -05:00
parent f2519f4fd7
commit e025674eb2
6 changed files with 267 additions and 168 deletions

View File

@ -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<Segment> &segments, Type type) :
Storage::Cartridge::Cartridge(segments), type(type) {}
};
}
}
}
#endif /* Cartridge_hpp */

View File

@ -8,12 +8,43 @@
#include "StaticAnalyser.hpp"
#include "Cartridge.hpp"
#include "Tape.hpp"
#include "../Disassembler/Z80.hpp"
#include "../Disassembler/AddressMapper.hpp"
#include <algorithm>
static std::unique_ptr<Analyser::Static::Target> 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<Storage::Cartridge::Cartridge::Segment> output_segments;
if(segment.data.size() & 0x1fff) {
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::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<Analyser::Static::Target> 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<std::shared_ptr<Storage::Cartridge::Cartridge>>
MSXCartridgesFrom(const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges, Analyser::Static::Target &target) {
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> 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<std::unique_ptr<Analyser::Static::Target>> CartridgeTargetsFrom(
const std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
// No cartridges implies no targets.
if(cartridges.empty()) {
return {};
}
std::vector<std::unique_ptr<Analyser::Static::Target>> targets;
for(const auto &cartridge : cartridges) {
const auto &segments = cartridge->get_segments();
@ -57,154 +98,174 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
uint16_t init_address = static_cast<uint16_t>(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<uint8_t> 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<uint8_t> 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<uint16_t, Instruction> &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<uint16_t, Instruction> &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<uint16_t>(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<uint16_t>(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<uint16_t, int> 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<uint16_t>(instruction_pair.second.operand)]++;
}
}
// Sort possible cartridge types.
using Possibility = std::pair<Analyser::Static::MSXCartridgeType, int>;
std::vector<Possibility> 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<uint16_t, int> 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<uint16_t>(instruction_pair.second.operand)]++;
}
}
// Size down to a multiple of 8kb in size and apply the start address.
std::vector<Storage::Cartridge::Cartridge::Segment> output_segments;
if(segment.data.size() & 0x1fff) {
std::vector<uint8_t> truncated_data;
std::vector<uint8_t>::difference_type truncated_size = static_cast<std::vector<uint8_t>::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<float>(
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<float>( 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<float>( 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<float>( 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<float>( 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<std::unique_ptr<Target>> &destination) {
std::unique_ptr<Target> target(new Target);
// Append targets for any cartridges that look correct.
std::vector<std::unique_ptr<Target>> 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> target(new Target);
// Check tapes for loadable files.
for(const auto &tape : media.tapes) {

View File

@ -164,5 +164,11 @@ std::vector<std::unique_ptr<Target>> 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;
}

View File

@ -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;
};
};

View File

@ -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<uint16_t>(segment.start_address), std::min(segment.data.size(), 65536 - segment.start_address));
auto msx_cartridge = dynamic_cast<Analyser::Static::MSX::Cartridge *>(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()) {

View File

@ -644,6 +644,7 @@
/* Begin PBXFileReference section */
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
4B046DC31CFE651500E9E45E /* CRTMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CRTMachine.hpp; sourceTree = "<group>"; };
4B047075201ABC180047AB0D /* Cartridge.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
4B049CDC1DA3C82F00322067 /* BCDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BCDTest.swift; sourceTree = "<group>"; };
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 */,
);