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:
parent
98a9d57c0b
commit
2cc1a2684a
@ -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
163
StaticAnalyser/MSX/Tape.cpp
Normal 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;
|
||||||
|
}
|
42
StaticAnalyser/MSX/Tape.hpp
Normal file
42
StaticAnalyser/MSX/Tape.hpp
Normal 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 */
|
Loading…
Reference in New Issue
Block a user