Files
CLK/Analyser/Static/Thomson/StaticAnalyser.cpp
T

136 lines
3.6 KiB
C++

//
// StaticAnalyser.cpp
// Clock Signal
//
// Created by Thomas Harte on 23/03/2026.
// Copyright © 2026 Thomas Harte. All rights reserved.
//
#include "StaticAnalyser.hpp"
#include "Target.hpp"
#include "Storage/Tape/Parsers/ThomsonMO.hpp"
namespace {
using CartridgeList = std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>;
CartridgeList validated(const CartridgeList &cartridges) {
//
// This is an adhoc, empirical take on detecting cartridge validity.
// For the MO series it looks like:
//
// (1) the final two bytes of the cartridge are an entry vector, so should be in
// the range [b000, f000); and
// (2) a cartridge name begins 32 bytes before the end of the cartridge, and is at
// most 20 bytes long including a terminator of value 0x04.
//
CartridgeList result;
for(const auto &cartridge: cartridges) {
if(cartridge->segments().empty()) {
continue;
}
auto &front = cartridge->segments().front().data;
if(front.size() & 16383) {
continue;
}
const auto vector = (front[16382] << 8) | front[16383];
if(vector < 0xb000 || vector > 0xefe0) {
continue;
}
const bool has_valid_string = [&]() {
for(size_t c = 0; c < 20; c++) {
const auto next = front[16384 - 32 + c];
if(next & 0x80) return false;
if(next == 0x04) return true;
}
return false;
} ();
if(!has_valid_string) {
continue;
}
result.push_back(cartridge);
}
return result;
}
static constexpr bool DefaultMO6 = true;
}
Analyser::Static::TargetList Analyser::Static::Thomson::GetTargets(
const Media &media,
const std::string &,
TargetPlatform::IntType,
const bool is_confident
) {
using MOTarget = Analyser::Static::Thomson::MOTarget;
TargetList destination;
auto target = std::make_unique<MOTarget>();
if(DefaultMO6) {
target->model = MOTarget::Model::MO6v3;
}
if(!media.tapes.empty()) {
Storage::Tape::Thomson::MO::Parser parser;
auto &tape = media.tapes.front();
const auto serialiser = tape->serialiser();
const auto first = parser.block(*serialiser);
if(first && first->checksum_valid()) {
target->media.tapes = media.tapes;
// Cf. https://pulkomandy.tk/wiki/doku.php?id=documentations:monitor:tape.format for leader block format;
// my parser provides block type separately and length implicitly so the type at offset 0xd is at 0xb
// in ->data.
static constexpr size_t TypeOffset = 0xb;
if(!first->type && first->data.size() > TypeOffset) {
const auto command = [&]() -> std::wstring {
if(!first->data[TypeOffset]) { // File type; 0 = BASIC, 1 = DATA; 2 = binary.
return L"RUN\"";
} else {
return L"LOADM\"\",,R";
}
};
// TODO: determine whether BASIC 1 or BASIC 128 is appropriate.
if(DefaultMO6) {
target->loading_command = std::wstring(L"2 ") + command() + L"\n";
} else {
target->loading_command = command() + L"\n";
}
}
}
}
// Currently: accept all floppy disks and all cartridges.
if(!media.disks.empty()) {
target->floppy = MOTarget::Floppy::CD90_640;
target->media.disks = media.disks;
if(DefaultMO6) {
target->loading_command = L"2";
}
}
if(!media.cartridges.empty()) {
target->media.cartridges = is_confident ? media.cartridges : validated(media.cartridges);
// Upon survey, it seemed that there are no catridges that require or that do anything additional on an MO6.
// So switch to an MO5 for faster launch.
if(!target->media.cartridges.empty()) {
target->model = MOTarget::Model::MO5v11;
}
}
if(!target->media.empty()) {
destination.push_back(std::move(target));
}
return destination;
}