1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-24 12:30:17 +00:00

Introduces [over-]analysis of cassette contents prior to starting the MSX, and simplifies ROM checking.

So a proper loading command is now known.
This commit is contained in:
Thomas Harte 2017-12-29 15:11:10 -05:00
parent 98a9d57c0b
commit 2cc1a2684a
3 changed files with 232 additions and 25 deletions

View File

@ -8,13 +8,17 @@
#include "StaticAnalyser.hpp" #include "StaticAnalyser.hpp"
#include "Tape.hpp"
/* /*
DEFB "AB" ; expansion ROM header Expected standard cartridge format:
DEFW initcode ; start of the init code, 0 if no initcode
DEFW callstat; pointer to CALL statement handler, 0 if no such handler DEFB "AB" ; expansion ROM header
DEFW device; pointer to expansion device handler, 0 if no such handler DEFW initcode ; start of the init code, 0 if no initcode
DEFW basic ; pointer to the start of a tokenized basicprogram, 0 if no basicprogram DEFW callstat; pointer to CALL statement handler, 0 if no such handler
DEFS 6,0 ; room reserved for future extensions 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::list<std::shared_ptr<Storage::Cartridge::Cartridge>> static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
MSXCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) { MSXCartridgesFrom(const std::list<std::shared_ptr<Storage::Cartridge::Cartridge>> &cartridges) {
@ -29,31 +33,17 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
// Which must be a multiple of 16 kb in size. // Which must be a multiple of 16 kb in size.
Storage::Cartridge::Cartridge::Segment segment = segments.front(); Storage::Cartridge::Cartridge::Segment segment = segments.front();
const size_t data_size = segment.data.size(); const size_t data_size = segment.data.size();
if(data_size < 0x4000 || data_size & 0x3fff) continue; 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 // Check for a ROM header at address 0; TODO: if it's not found then try 0x4000
// and consider swapping the image. // and consider swapping the image.
// Check for the expansion ROM header and the reserved bytes. // Check for the expansion ROM header and the reserved bytes.
if(segment.data[0] != 0x41 || segment.data[1] != 0x42) continue; if(segment.data[0] != 0x41 || segment.data[1] != 0x42) continue;
bool all_zeroes = true;
for(size_t c = 0; c < 6; ++c) {
if(segment.data[10 + c] != 0) all_zeroes = false;
}
if(!all_zeroes) continue;
// Pick a paging address based on the four pointers. // Apply the standard MSX start address.
uint16_t start_address = 0xc000;
for(size_t c = 0; c < 8; c += 2) {
uint16_t code_pointer = static_cast<uint16_t>(segment.data[2 + c] | segment.data[3 + c] << 8);
if(code_pointer) {
start_address = std::min(static_cast<uint16_t>(code_pointer &~ 0x3fff), start_address);
}
}
// That'll do then, but apply the detected start address.
msx_cartridges.emplace_back(new Storage::Cartridge::Cartridge({ msx_cartridges.emplace_back(new Storage::Cartridge::Cartridge({
Storage::Cartridge::Cartridge::Segment(start_address, segment.data) Storage::Cartridge::Cartridge::Segment(0x4000, segment.data)
})); }));
} }
@ -63,10 +53,22 @@ static std::list<std::shared_ptr<Storage::Cartridge::Cartridge>>
void StaticAnalyser::MSX::AddTargets(const Media &media, std::list<Target> &destination) { void StaticAnalyser::MSX::AddTargets(const Media &media, std::list<Target> &destination) {
Target target; Target target;
// Obtain only those cartridges which it looks like an MSX would understand.
target.media.cartridges = MSXCartridgesFrom(media.cartridges); target.media.cartridges = MSXCartridgesFrom(media.cartridges);
// TODO: tape parsing. Be dumb for now. // Check tapes for loadable files.
target.media.tapes = media.tapes; for(const auto &tape : media.tapes) {
std::vector<File> files_on_tape = GetFiles(tape);
if(!files_on_tape.empty()) {
switch(files_on_tape.front().type) {
case File::Type::ASCII: target.loadingCommand = "RUN\"CAS:\n"; break;
case File::Type::TokenisedBASIC: target.loadingCommand = "CLOAD\nRUN\n"; break;
case File::Type::Binary: target.loadingCommand = "BLOAD\"CAS:\",R\n"; break;
default: break;
}
target.media.tapes.push_back(tape);
}
}
if(!target.media.empty()) { if(!target.media.empty()) {
target.machine = Target::MSX; target.machine = Target::MSX;

163
StaticAnalyser/MSX/Tape.cpp Normal file
View File

@ -0,0 +1,163 @@
//
// Tape.cpp
// Clock Signal
//
// Created by Thomas Harte on 25/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#include "Tape.hpp"
#include "../../Storage/Tape/Parsers/MSX.hpp"
using namespace StaticAnalyser::MSX;
File::File(File &&rhs) :
name(std::move(rhs.name)),
type(rhs.type),
data(std::move(rhs.data)),
starting_address(rhs.starting_address),
entry_address(rhs.entry_address) {}
File::File() :
type(Type::Binary),
starting_address(0),
entry_address(0) {} // For the sake of initialising in a defined state.
std::vector<File> StaticAnalyser::MSX::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
std::vector<File> files;
Storage::Tape::BinaryTapePlayer tape_player(1000000);
tape_player.set_motor_control(true);
tape_player.set_tape(tape);
using Parser = Storage::Tape::MSX::Parser;
// Get all recognisable files from the tape.
while(!tape->is_at_end()) {
// Try to locate and measure a header.
std::unique_ptr<Parser::FileSpeed> file_speed = Parser::find_header(tape_player);
if(!file_speed) continue;
// Check whether what follows is a recognisable file type.
uint8_t header[10] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
for(std::size_t c = 0; c < sizeof(header); ++c) {
int next_byte = Parser::get_byte(*file_speed, tape_player);
if(next_byte == -1) break;
header[c] = static_cast<uint8_t>(next_byte);
}
bool bytes_are_same = true;
for(std::size_t c = 1; c < sizeof(header); ++c)
bytes_are_same &= (header[c] == header[0]);
if(!bytes_are_same) continue;
if(header[0] != 0xd0 && header[0] != 0xd3 && header[0] != 0xea) continue;
File file;
// Determine file type from information already collected.
switch(header[0]) {
case 0xd0: file.type = File::Type::Binary; break;
case 0xd3: file.type = File::Type::TokenisedBASIC; break;
case 0xea: file.type = File::Type::ASCII; break;
default: break; // Unreachable.
}
// Read file name.
char name[7];
for(std::size_t c = 1; c < 6; ++c)
name[c] = static_cast<char>(Parser::get_byte(*file_speed, tape_player));
name[6] = '\0';
file.name = name;
// ASCII: Read 256-byte segments until one ends with an end-of-file character.
if(file.type == File::Type::ASCII) {
while(true) {
file_speed = Parser::find_header(tape_player);
if(!file_speed) break;
int c = 256;
while(c--) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) break;
file.data.push_back(static_cast<uint8_t>(byte));
}
if(c != -1) break;
if(file.data.back() == 0x1a) {
files.push_back(std::move(file));
break;
}
}
continue;
}
// Read a single additional segment, using the information at the begging to determine length.
file_speed = Parser::find_header(tape_player);
if(!file_speed) continue;
// Binary: read start address, end address, entry address, then that many bytes.
if(file.type == File::Type::Binary) {
uint8_t locations[6];
uint16_t end_address;
std::size_t c;
for(c = 0; c < sizeof(locations); ++c) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) break;
locations[c] = static_cast<uint8_t>(byte);
}
if(c != sizeof(locations)) continue;
file.starting_address = static_cast<uint16_t>(locations[0] | (locations[1] << 8));
end_address = static_cast<uint16_t>(locations[2] | (locations[3] << 8));
file.entry_address = static_cast<uint16_t>(locations[4] | (locations[5] << 8));
if(end_address < file.starting_address) continue;
std::size_t length = end_address - file.starting_address;
while(length--) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) continue;
file.data.push_back(static_cast<uint8_t>(byte));
}
files.push_back(std::move(file));
continue;
}
// Tokenised BASIC, then: keep following 'next line' links from a hypothetical start of
// 0x8001, until finding the final line.
uint16_t current_address = 0x8001;
while(current_address) {
int next_address_buffer[2];
next_address_buffer[0] = Parser::get_byte(*file_speed, tape_player);
next_address_buffer[1] = Parser::get_byte(*file_speed, tape_player);
if(next_address_buffer[0] == -1 || next_address_buffer[1] == -1) break;
file.data.push_back(static_cast<uint8_t>(next_address_buffer[0]));
file.data.push_back(static_cast<uint8_t>(next_address_buffer[1]));
uint16_t next_address = static_cast<uint16_t>(next_address_buffer[0] | (next_address_buffer[1] << 8));
if(!next_address) {
files.push_back(std::move(file));
break;
}
if(next_address < current_address+2) break;
// This line makes sense, so push it all in.
std::size_t length = next_address - current_address - 2;
current_address = next_address;
bool found_error = false;
while(length--) {
int byte = Parser::get_byte(*file_speed, tape_player);
if(byte == -1) {
found_error = true;
break;
}
file.data.push_back(static_cast<uint8_t>(byte));
}
if(found_error) break;
}
}
return files;
}

View File

@ -0,0 +1,42 @@
//
// Tape.hpp
// Clock Signal
//
// Created by Thomas Harte on 25/12/2017.
// Copyright © 2017 Thomas Harte. All rights reserved.
//
#ifndef StaticAnalyser_MSX_Tape_hpp
#define StaticAnalyser_MSX_Tape_hpp
#include "../../Storage/Tape/Tape.hpp"
#include <string>
#include <vector>
namespace StaticAnalyser {
namespace MSX {
struct File {
std::string name;
enum Type {
Binary,
TokenisedBASIC,
ASCII
} type;
std::vector<uint8_t> data;
uint16_t starting_address; // Provided only for Type::Binary files.
uint16_t entry_address; // Provided only for Type::Binary files.
File(File &&rhs);
File();
};
std::vector<File> GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape);
}
}
#endif /* StaticAnalyser_MSX_Tape_hpp */