From 2cc1a2684a80004cfb55f97cc9e7c632b9927468 Mon Sep 17 00:00:00 2001 From: Thomas Harte Date: Fri, 29 Dec 2017 15:11:10 -0500 Subject: [PATCH] Introduces [over-]analysis of cassette contents prior to starting the MSX, and simplifies ROM checking. So a proper loading command is now known. --- StaticAnalyser/MSX/StaticAnalyser.cpp | 52 ++++---- StaticAnalyser/MSX/Tape.cpp | 163 ++++++++++++++++++++++++++ StaticAnalyser/MSX/Tape.hpp | 42 +++++++ 3 files changed, 232 insertions(+), 25 deletions(-) create mode 100644 StaticAnalyser/MSX/Tape.cpp create mode 100644 StaticAnalyser/MSX/Tape.hpp diff --git a/StaticAnalyser/MSX/StaticAnalyser.cpp b/StaticAnalyser/MSX/StaticAnalyser.cpp index a04756523..9fd288813 100644 --- a/StaticAnalyser/MSX/StaticAnalyser.cpp +++ b/StaticAnalyser/MSX/StaticAnalyser.cpp @@ -8,13 +8,17 @@ #include "StaticAnalyser.hpp" +#include "Tape.hpp" + /* -DEFB "AB" ; expansion ROM header -DEFW initcode ; start of the init code, 0 if no initcode -DEFW callstat; pointer to CALL statement handler, 0 if no such handler -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 + Expected standard cartridge format: + + DEFB "AB" ; expansion ROM header + DEFW initcode ; start of the init code, 0 if no initcode + DEFW callstat; pointer to CALL statement handler, 0 if no such handler + 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> MSXCartridgesFrom(const std::list> &cartridges) { @@ -29,31 +33,17 @@ static std::list> // Which must be a multiple of 16 kb in size. Storage::Cartridge::Cartridge::Segment segment = segments.front(); 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 // and consider swapping the image. // Check for the expansion ROM header and the reserved bytes. 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. - uint16_t start_address = 0xc000; - for(size_t c = 0; c < 8; c += 2) { - uint16_t code_pointer = static_cast(segment.data[2 + c] | segment.data[3 + c] << 8); - if(code_pointer) { - start_address = std::min(static_cast(code_pointer &~ 0x3fff), start_address); - } - } - - // That'll do then, but apply the detected start address. + // Apply the standard MSX start address. 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> void StaticAnalyser::MSX::AddTargets(const Media &media, std::list &destination) { Target target; + // Obtain only those cartridges which it looks like an MSX would understand. target.media.cartridges = MSXCartridgesFrom(media.cartridges); - // TODO: tape parsing. Be dumb for now. - target.media.tapes = media.tapes; + // Check tapes for loadable files. + for(const auto &tape : media.tapes) { + std::vector 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()) { target.machine = Target::MSX; diff --git a/StaticAnalyser/MSX/Tape.cpp b/StaticAnalyser/MSX/Tape.cpp new file mode 100644 index 000000000..5cfb4bc20 --- /dev/null +++ b/StaticAnalyser/MSX/Tape.cpp @@ -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 StaticAnalyser::MSX::GetFiles(const std::shared_ptr &tape) { + std::vector 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 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(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(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(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(byte); + } + if(c != sizeof(locations)) continue; + + file.starting_address = static_cast(locations[0] | (locations[1] << 8)); + end_address = static_cast(locations[2] | (locations[3] << 8)); + file.entry_address = static_cast(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(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(next_address_buffer[0])); + file.data.push_back(static_cast(next_address_buffer[1])); + + uint16_t next_address = static_cast(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(byte)); + } + if(found_error) break; + } + } + + return files; +} diff --git a/StaticAnalyser/MSX/Tape.hpp b/StaticAnalyser/MSX/Tape.hpp new file mode 100644 index 000000000..c7150852b --- /dev/null +++ b/StaticAnalyser/MSX/Tape.hpp @@ -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 +#include + +namespace StaticAnalyser { +namespace MSX { + +struct File { + std::string name; + enum Type { + Binary, + TokenisedBASIC, + ASCII + } type; + + std::vector 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 GetFiles(const std::shared_ptr &tape); + +} +} + +#endif /* StaticAnalyser_MSX_Tape_hpp */