2016-08-30 01:10:38 +00:00
|
|
|
//
|
|
|
|
// Tape.cpp
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 29/08/2016.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
2016-08-30 01:10:38 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#include "Tape.hpp"
|
|
|
|
|
2016-09-01 12:35:28 +00:00
|
|
|
#include <deque>
|
|
|
|
|
2020-01-20 04:14:35 +00:00
|
|
|
#include "../../../Numeric/CRC.hpp"
|
2018-01-25 02:48:44 +00:00
|
|
|
#include "../../../Storage/Tape/Parsers/Acorn.hpp"
|
|
|
|
|
|
|
|
using namespace Analyser::Static::Acorn;
|
2016-08-30 01:10:38 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
static std::unique_ptr<File::Chunk> GetNextChunk(const std::shared_ptr<Storage::Tape::Tape> &tape, Storage::Tape::Acorn::Parser &parser) {
|
2019-12-22 04:52:04 +00:00
|
|
|
auto new_chunk = std::make_unique<File::Chunk>();
|
2016-08-30 01:53:06 +00:00
|
|
|
int shift_register = 0;
|
|
|
|
|
2024-01-23 02:33:39 +00:00
|
|
|
// TODO: move this into the parser
|
|
|
|
const auto shift = [&] {
|
|
|
|
shift_register = (shift_register >> 1) | (parser.get_next_bit(tape) << 9);
|
|
|
|
};
|
2016-08-30 01:53:06 +00:00
|
|
|
|
|
|
|
// find next area of high tone
|
2017-03-26 18:34:47 +00:00
|
|
|
while(!tape->is_at_end() && (shift_register != 0x3ff)) {
|
2016-08-30 01:53:06 +00:00
|
|
|
shift();
|
|
|
|
}
|
|
|
|
|
|
|
|
// find next 0x2a (swallowing stop bit)
|
2017-03-26 18:34:47 +00:00
|
|
|
while(!tape->is_at_end() && (shift_register != 0x254)) {
|
2016-08-30 01:53:06 +00:00
|
|
|
shift();
|
|
|
|
}
|
|
|
|
|
2016-08-30 11:38:08 +00:00
|
|
|
parser.reset_crc();
|
|
|
|
parser.reset_error_flag();
|
|
|
|
|
2016-08-30 01:53:06 +00:00
|
|
|
// read out name
|
2016-09-05 22:15:15 +00:00
|
|
|
char name[11];
|
2017-11-11 20:28:40 +00:00
|
|
|
std::size_t name_ptr = 0;
|
2017-03-26 18:34:47 +00:00
|
|
|
while(!tape->is_at_end() && name_ptr < sizeof(name)) {
|
2020-05-10 03:00:39 +00:00
|
|
|
name[name_ptr] = char(parser.get_next_byte(tape));
|
2016-08-30 01:53:06 +00:00
|
|
|
if(!name[name_ptr]) break;
|
2020-05-10 03:42:42 +00:00
|
|
|
++name_ptr;
|
2016-08-30 01:53:06 +00:00
|
|
|
}
|
2016-09-05 22:28:43 +00:00
|
|
|
name[sizeof(name)-1] = '\0';
|
2016-08-31 23:57:09 +00:00
|
|
|
new_chunk->name = name;
|
2016-08-30 01:53:06 +00:00
|
|
|
|
2016-08-30 11:38:08 +00:00
|
|
|
// addresses
|
2020-05-10 03:00:39 +00:00
|
|
|
new_chunk->load_address = uint32_t(parser.get_next_word(tape));
|
|
|
|
new_chunk->execution_address = uint32_t(parser.get_next_word(tape));
|
|
|
|
new_chunk->block_number = uint16_t(parser.get_next_short(tape));
|
|
|
|
new_chunk->block_length = uint16_t(parser.get_next_short(tape));
|
|
|
|
new_chunk->block_flag = uint8_t(parser.get_next_byte(tape));
|
|
|
|
new_chunk->next_address = uint32_t(parser.get_next_word(tape));
|
2016-08-30 11:38:08 +00:00
|
|
|
|
2016-08-30 12:13:40 +00:00
|
|
|
uint16_t calculated_header_crc = parser.get_crc();
|
2020-05-10 03:00:39 +00:00
|
|
|
uint16_t stored_header_crc = uint16_t(parser.get_next_short(tape));
|
|
|
|
stored_header_crc = uint16_t((stored_header_crc >> 8) | (stored_header_crc << 8));
|
2016-08-30 12:13:40 +00:00
|
|
|
new_chunk->header_crc_matched = stored_header_crc == calculated_header_crc;
|
2016-08-30 11:38:08 +00:00
|
|
|
|
2017-07-14 01:26:45 +00:00
|
|
|
if(!new_chunk->header_crc_matched) return nullptr;
|
|
|
|
|
2016-08-30 12:13:40 +00:00
|
|
|
parser.reset_crc();
|
|
|
|
new_chunk->data.reserve(new_chunk->block_length);
|
2017-03-26 18:34:47 +00:00
|
|
|
for(int c = 0; c < new_chunk->block_length; c++) {
|
2020-05-10 03:00:39 +00:00
|
|
|
new_chunk->data.push_back(uint8_t(parser.get_next_byte(tape)));
|
2016-08-30 12:13:40 +00:00
|
|
|
}
|
2016-08-30 11:38:08 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
if(new_chunk->block_length && !(new_chunk->block_flag&0x40)) {
|
2016-08-30 12:13:40 +00:00
|
|
|
uint16_t calculated_data_crc = parser.get_crc();
|
2020-05-10 03:00:39 +00:00
|
|
|
uint16_t stored_data_crc = uint16_t(parser.get_next_short(tape));
|
|
|
|
stored_data_crc = uint16_t((stored_data_crc >> 8) | (stored_data_crc << 8));
|
2016-08-30 12:13:40 +00:00
|
|
|
new_chunk->data_crc_matched = stored_data_crc == calculated_data_crc;
|
2017-03-26 18:34:47 +00:00
|
|
|
} else {
|
2016-08-30 12:13:40 +00:00
|
|
|
new_chunk->data_crc_matched = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return parser.get_error_flag() ? nullptr : std::move(new_chunk);
|
2016-08-30 11:38:08 +00:00
|
|
|
}
|
|
|
|
|
2017-07-22 00:57:48 +00:00
|
|
|
static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
|
2016-08-31 23:57:09 +00:00
|
|
|
// find next chunk with a block number of 0
|
2017-03-26 18:34:47 +00:00
|
|
|
while(chunks.size() && chunks.front().block_number) {
|
2016-09-01 12:35:28 +00:00
|
|
|
chunks.pop_front();
|
2016-08-31 23:57:09 +00:00
|
|
|
}
|
|
|
|
|
2016-09-01 12:35:28 +00:00
|
|
|
if(!chunks.size()) return nullptr;
|
2016-09-01 00:24:13 +00:00
|
|
|
|
2016-08-31 23:57:09 +00:00
|
|
|
// accumulate chunks for as long as block number is sequential and the end-of-file bit isn't set
|
2019-12-22 04:52:04 +00:00
|
|
|
auto file = std::make_unique<File>();
|
2016-09-01 12:35:28 +00:00
|
|
|
|
2016-09-05 21:53:57 +00:00
|
|
|
uint16_t block_number = 0;
|
2016-09-01 12:35:28 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
while(chunks.size()) {
|
2016-09-05 21:53:57 +00:00
|
|
|
if(chunks.front().block_number != block_number) return nullptr;
|
2016-09-01 12:35:28 +00:00
|
|
|
|
|
|
|
bool was_last = chunks.front().block_flag & 0x80;
|
|
|
|
file->chunks.push_back(chunks.front());
|
|
|
|
chunks.pop_front();
|
|
|
|
block_number++;
|
|
|
|
|
|
|
|
if(was_last) break;
|
2016-08-31 23:57:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// accumulate total data, copy flags appropriately
|
|
|
|
file->name = file->chunks.front().name;
|
|
|
|
file->load_address = file->chunks.front().load_address;
|
|
|
|
file->execution_address = file->chunks.front().execution_address;
|
2021-01-31 04:10:59 +00:00
|
|
|
// I think the final chunk's flags are the ones that count; TODO: check.
|
|
|
|
if(file->chunks.back().block_flag & 0x01) {
|
|
|
|
// File is locked, which in more generalised terms means it is
|
|
|
|
// for execution only.
|
|
|
|
file->flags |= File::Flags::ExecuteOnly;
|
|
|
|
}
|
2016-08-31 23:57:09 +00:00
|
|
|
|
|
|
|
// copy all data into a single big block
|
2017-03-26 18:34:47 +00:00
|
|
|
for(File::Chunk chunk : file->chunks) {
|
2016-08-31 23:57:09 +00:00
|
|
|
file->data.insert(file->data.end(), chunk.data.begin(), chunk.data.end());
|
|
|
|
}
|
|
|
|
|
|
|
|
return file;
|
|
|
|
}
|
|
|
|
|
2018-01-25 02:48:44 +00:00
|
|
|
std::vector<File> Analyser::Static::Acorn::GetFiles(const std::shared_ptr<Storage::Tape::Tape> &tape) {
|
2016-11-06 21:13:13 +00:00
|
|
|
Storage::Tape::Acorn::Parser parser;
|
2016-08-30 11:38:08 +00:00
|
|
|
|
2016-09-01 12:35:28 +00:00
|
|
|
// populate chunk list
|
|
|
|
std::deque<File::Chunk> chunk_list;
|
2017-03-26 18:34:47 +00:00
|
|
|
while(!tape->is_at_end()) {
|
2016-11-06 20:25:18 +00:00
|
|
|
std::unique_ptr<File::Chunk> chunk = GetNextChunk(tape, parser);
|
2017-03-26 18:34:47 +00:00
|
|
|
if(chunk) {
|
2016-09-01 12:35:28 +00:00
|
|
|
chunk_list.push_back(*chunk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// decompose into file list
|
2018-01-24 03:18:16 +00:00
|
|
|
std::vector<File> file_list;
|
2016-09-01 12:35:28 +00:00
|
|
|
|
2017-03-26 18:34:47 +00:00
|
|
|
while(chunk_list.size()) {
|
2016-09-01 12:35:28 +00:00
|
|
|
std::unique_ptr<File> next_file = GetNextFile(chunk_list);
|
2017-03-26 18:34:47 +00:00
|
|
|
if(next_file) {
|
2016-08-31 23:57:09 +00:00
|
|
|
file_list.push_back(*next_file);
|
|
|
|
}
|
|
|
|
}
|
2016-08-30 11:38:08 +00:00
|
|
|
|
2016-08-31 23:57:09 +00:00
|
|
|
return file_list;
|
2016-08-30 01:10:38 +00:00
|
|
|
}
|