diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj index ff9aee474..f9890f142 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj +++ b/OSBindings/Mac/Clock Signal.xcodeproj/project.pbxproj @@ -336,6 +336,7 @@ 4BC3B74F1CD194CC00F86E85 /* Shader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B74D1CD194CC00F86E85 /* Shader.cpp */; }; 4BC3B7521CD1956900F86E85 /* OutputShader.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC3B7501CD1956900F86E85 /* OutputShader.cpp */; }; 4BC5E4921D7ED365008CF980 /* CommodoreAnalyser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4901D7ED365008CF980 /* CommodoreAnalyser.cpp */; }; + 4BC5E4951D7EE0E0008CF980 /* Utilities.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */; }; 4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; }; 4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; }; 4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; }; @@ -764,6 +765,8 @@ 4BC3B7511CD1956900F86E85 /* OutputShader.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OutputShader.hpp; sourceTree = ""; }; 4BC5E4901D7ED365008CF980 /* CommodoreAnalyser.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommodoreAnalyser.cpp; path = ../../StaticAnalyser/Commodore/CommodoreAnalyser.cpp; sourceTree = ""; }; 4BC5E4911D7ED365008CF980 /* CommodoreAnalyser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CommodoreAnalyser.hpp; path = ../../StaticAnalyser/Commodore/CommodoreAnalyser.hpp; sourceTree = ""; }; + 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Utilities.cpp; path = ../../StaticAnalyser/Commodore/Utilities.cpp; sourceTree = ""; }; + 4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Utilities.hpp; path = ../../StaticAnalyser/Commodore/Utilities.hpp; sourceTree = ""; }; 4BC751B11D157E61006C31D9 /* 6522Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = 6522Tests.swift; sourceTree = ""; }; 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = ""; }; 4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = ""; }; @@ -1522,6 +1525,8 @@ 4BC5E4911D7ED365008CF980 /* CommodoreAnalyser.hpp */, 4BC830CF1D6E7C690000A26F /* Tape.cpp */, 4BC830D01D6E7C690000A26F /* Tape.hpp */, + 4BC5E4931D7EE0E0008CF980 /* Utilities.cpp */, + 4BC5E4941D7EE0E0008CF980 /* Utilities.hpp */, ); name = Commodore; sourceTree = ""; @@ -2024,6 +2029,7 @@ 4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */, 4BAB62AD1D3272D200DF5BA0 /* Disk.cpp in Sources */, 4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */, + 4BC5E4951D7EE0E0008CF980 /* Utilities.cpp in Sources */, 4BB697CE1D4BA44400248BDF /* CommodoreGCR.cpp in Sources */, 4BD14B111D74627C0088EAD6 /* AcornAnalyser.cpp in Sources */, 4BBF99151C8FBA6F0075DAFB /* CRTOpenGL.cpp in Sources */, diff --git a/StaticAnalyser/Commodore/Tape.cpp b/StaticAnalyser/Commodore/Tape.cpp index 870066666..3676a0210 100644 --- a/StaticAnalyser/Commodore/Tape.cpp +++ b/StaticAnalyser/Commodore/Tape.cpp @@ -8,8 +8,8 @@ #include "Tape.hpp" -#include #include "../TapeParser.hpp" +#include "Utilities.hpp" using namespace StaticAnalyser::Commodore; @@ -21,12 +21,114 @@ enum class SymbolType { One, Zero, Word, EndOfBlock, LeadIn }; +struct Header { + enum { + RelocatableProgram, + DataBlock, + NonRelocatableProgram, + SequenceHeader, + EndOfTape, + Unknown + } type; + + std::vector data; + std::wstring name; + std::vector raw_name; + uint16_t starting_address; + uint16_t ending_address; + bool parity_was_valid; + bool duplicate_matched; +}; + class CommodoreROMTapeParser: public StaticAnalyer::TapeParser { public: CommodoreROMTapeParser(const std::shared_ptr &tape) : TapeParser(tape), _wave_period(0.0f), - _previous_was_high(false) {} + _previous_was_high(false), + _parity_byte(0) {} + + /*! + Advances to the next header block on the tape, then consumes, parses, and returns it. + Returns @c nullptr if any wave-encoding level errors are encountered. + */ + std::unique_ptr
get_next_header() + { + std::unique_ptr
header(new Header); + reset_error_flag(); + + // find and proceed beyond lead-in tone + proceed_to_symbol(SymbolType::LeadIn); + + // look for landing zone + proceed_to_landing_zone(true); + reset_parity_byte(); + + // get header type + uint8_t header_type = get_next_byte(); + switch(header_type) + { + default: header->type = Header::Unknown; break; + case 0x01: header->type = Header::RelocatableProgram; break; + case 0x02: header->type = Header::DataBlock; break; + case 0x03: header->type = Header::NonRelocatableProgram; break; + case 0x04: header->type = Header::SequenceHeader; break; + case 0x05: header->type = Header::EndOfTape; break; + } + + // grab rest of data + header->data.reserve(191); + for(size_t c = 0; c < 191; c++) + { + header->data.push_back(get_next_byte()); + } + + uint8_t parity_byte = get_parity_byte(); + header->parity_was_valid = get_next_byte() == parity_byte; + + // check that the duplicate matches + proceed_to_landing_zone(false); + header->duplicate_matched = true; + if(get_next_byte() != header_type) header->duplicate_matched = false; + for(size_t c = 0; c < 191; c++) + { + if(header->data[c] != get_next_byte()) header->duplicate_matched = false; + } + if(get_next_byte() != parity_byte) header->duplicate_matched = false; + + // parse if this is not pure data + if(header->type != Header::DataBlock) + { + header->starting_address = (uint16_t)(header->data[0] | (header->data[1] << 8)); + header->ending_address = (uint16_t)(header->data[2] | (header->data[3] << 8)); + + for(size_t c = 0; c < 16; c++) + { + header->raw_name.push_back(header->data[4 + c]); + } + header->name = petscii_from_bytes(&header->raw_name[0], 16, false); + } + + if(get_error_flag()) return nullptr; + return header; + } + + std::unique_ptr> get_next_data() + { + std::unique_ptr> data(new std::vector); + + // find and proceed beyond lead-in tone to the next landing zone + proceed_to_symbol(SymbolType::LeadIn); + proceed_to_landing_zone(true); + + // accumulate until the next lead-in tone is hit +// while(!is_at_end()) +// { +// data->push_back(get_next_byte()); +// } + + return data; + } void spin() { @@ -45,6 +147,100 @@ class CommodoreROMTapeParser: public StaticAnalyer::TapeParser> 1) | (((next_symbol == SymbolType::One) ? 1 : 0) << 8); + } + + int check = byte_plus_parity; + check ^= (check >> 4); + check ^= (check >> 2); + check ^= (check >> 1); + if((check&1) == (byte_plus_parity >> 8)) _error_flag = true; + + add_parity_byte((uint8_t)byte_plus_parity); + return (uint8_t)byte_plus_parity; + } + + /*! + Returns the result of two consecutive @c get_next_byte calls, arranged in little-endian format. + */ + uint16_t get_next_short() + { + uint16_t value = get_next_byte(); + value |= get_next_byte() << 8; + return value; + } + + /*! + Per the contract with StaticAnalyser::TapeParser; sums time across pulses. If this pulse + indicates a high to low transition, inspects the time since the last transition, to produce + a long, medium, short or unrecognised wave period. + */ void process_pulse(Storage::Tape::Tape::Pulse pulse) { bool is_high = pulse.type == Storage::Tape::Tape::Pulse::High; @@ -64,6 +260,10 @@ class CommodoreROMTapeParser: public StaticAnalyer::TapeParser &waves) { if(waves.size() < 2) return; @@ -106,6 +306,7 @@ class CommodoreROMTapeParser: public StaticAnalyer::TapeParser StaticAnalyser::Commodore::GetFiles(const std::shared_ptr &tape) { CommodoreROMTapeParser parser(tape); + parser.get_next_header(); parser.spin(); std::list file_list; diff --git a/StaticAnalyser/Commodore/Tape.hpp b/StaticAnalyser/Commodore/Tape.hpp index 1d92db3d1..8ade612cb 100644 --- a/StaticAnalyser/Commodore/Tape.hpp +++ b/StaticAnalyser/Commodore/Tape.hpp @@ -16,6 +16,8 @@ namespace StaticAnalyser { namespace Commodore { struct File { + std::wstring name; + std::vector raw_name; uint16_t starting_address; uint16_t ending_address; enum { diff --git a/StaticAnalyser/Commodore/Utilities.cpp b/StaticAnalyser/Commodore/Utilities.cpp new file mode 100644 index 000000000..2f3a7e093 --- /dev/null +++ b/StaticAnalyser/Commodore/Utilities.cpp @@ -0,0 +1,63 @@ +// +// Utilities.cpp +// Clock Signal +// +// Created by Thomas Harte on 06/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#include "Utilities.hpp" + +std::wstring StaticAnalyser::Commodore::petscii_from_bytes(const uint8_t *string, int length, bool shifted) +{ + std::wstring result; + + wchar_t unshifted_characters[256] = + { + L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0', + L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', + L' ', L'!', L'"', L'#', L'$', L'%', L'&', L'\'', L'(', L')', L'*', L'+', L',', L'-', L'.', L'/', + L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'"', L';', L'<', L'=', L'>', L'?', + L'@', L'A', L'B', L'C', L'D', L'E', L'F', L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N', L'O', + L'P', L'Q', L'R', L'S', L'T', L'U', L'V', L'W', L'X', L'Y', L'Z', L'[', L'£', L']', L'↑', L'←', + L'─', L'♠', L'│', L'─', L'�', L'�', L'�', L'�', L'�', L'╮', L'╰', L'╯', L'�', L'╲', L'╱', L'�', + L'�', L'●', L'�', L'♥', L'�', L'╭', L'╳', L'○', L'♣', L'�', L'♦', L'┼', L'�', L'│', L'π', L'◥', + L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0', + L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', + L' ', L'▌', L'▄', L'▔', L'▁', L'▏', L'▒', L'▕', L'�', L'◤', L'�', L'├', L'▗', L'└', L'┐', L'▂', + L'┌', L'┴', L'┬', L'┤', L'▎', L'▍', L'�', L'�', L'�', L'▃', L'�', L'▖', L'▝', L'┘', L'▘', L'▚', + L'─', L'♠', L'│', L'─', L'�', L'�', L'�', L'�', L'�', L'╮', L'╰', L'╯', L'�', L'╲', L'╱', L'�', + L'�', L'●', L'�', L'♥', L'�', L'╭', L'╳', L'○', L'♣', L'�', L'♦', L'┼', L'�', L'│', L'π', L'◥', + L' ', L'▌', L'▄', L'▔', L'▁', L'▏', L'▒', L'▕', L'�', L'◤', L'�', L'├', L'▗', L'└', L'┐', L'▂', + L'┌', L'┴', L'┬', L'┤', L'▎', L'▍', L'�', L'�', L'�', L'▃', L'�', L'▖', L'▝', L'┘', L'▘', L'π', + }; + + wchar_t shifted_characters[256] = + { + L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0', + L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', + L' ', L'!', L'"', L'#', L'$', L'%', L'&', L'\'', L'(', L')', L'*', L'+', L',', L'-', L'.', L'/', + L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'"', L';', L'<', L'=', L'>', L'?', + L'@', L'a', L'b', L'c', L'd', L'e', L'f', L'g', L'h', L'i', L'j', L'k', L'l', L'm', L'n', L'o', + L'p', L'q', L'r', L's', L't', L'u', L'v', L'w', L'x', L'y', L'z', L'[', L'£', L']', L'↑', L'←', + L'─', L'A', L'B', L'C', L'D', L'E', L'F', L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N', L'O', + L'P', L'Q', L'R', L'S', L'T', L'U', L'V', L'W', L'X', L'Y', L'Z', L'┼', L'�', L'│', L'▒', L'◥', + L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\r', L'\0', L'\0', + L'\0', L'\0', L'\0', L'\0', L'\b', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', L'\0', + L' ', L'▌', L'▄', L'▔', L'▁', L'▏', L'▒', L'▕', L'�', L'�', L'�', L'├', L'▗', L'└', L'┐', L'▂', + L'┌', L'┴', L'┬', L'┤', L'▎', L'▍', L'�', L'�', L'�', L'▃', L'✓', L'▖', L'▝', L'┘', L'▘', L'▚', + L'─', L'A', L'B', L'C', L'D', L'E', L'F', L'G', L'H', L'I', L'J', L'K', L'L', L'M', L'N', L'O', + L'P', L'Q', L'R', L'S', L'T', L'U', L'V', L'W', L'X', L'Y', L'Z', L'┼', L'�', L'│', L'▒', L'�', + L' ', L'▌', L'▄', L'▔', L'▁', L'▏', L'▒', L'▕', L'�', L'�', L'�', L'├', L'▗', L'└', L'┐', L'▂', + L'┌', L'┴', L'┬', L'┤', L'▎', L'▍', L'�', L'�', L'�', L'▃', L'✓', L'▖', L'▝', L'┘', L'▘', L'▒', + }; + + wchar_t *table = shifted ? shifted_characters : unshifted_characters; + for(int c = 0; c < length; c++) + { + wchar_t next_character = table[string[c]]; + if(next_character) result.push_back(next_character); + } + + return result; +} diff --git a/StaticAnalyser/Commodore/Utilities.hpp b/StaticAnalyser/Commodore/Utilities.hpp new file mode 100644 index 000000000..1f4672424 --- /dev/null +++ b/StaticAnalyser/Commodore/Utilities.hpp @@ -0,0 +1,22 @@ +// +// Utilities.hpp +// Clock Signal +// +// Created by Thomas Harte on 06/09/2016. +// Copyright © 2016 Thomas Harte. All rights reserved. +// + +#ifndef Analyser_Commodore_Utilities_hpp +#define Analyser_Commodore_Utilities_hpp + +#include + +namespace StaticAnalyser { +namespace Commodore { + +std::wstring petscii_from_bytes(const uint8_t *string, int length, bool shifted); + +} +} + +#endif /* Utilities_hpp */