// // AcornAnalyser.cpp // Clock Signal // // Created by Thomas Harte on 29/08/2016. // Copyright © 2016 Thomas Harte. All rights reserved. // #include "StaticAnalyser.hpp" #include "Disk.hpp" #include "Tape.hpp" using namespace StaticAnalyser::Acorn; static std::list> AcornCartridgesFrom(const std::list> &cartridges) { std::list> acorn_cartridges; for(const auto &cartridge : cartridges) { const auto &segments = cartridge->get_segments(); // only one mapped item is allowed if(segments.size() != 1) continue; // which must be 16 kb in size Storage::Cartridge::Cartridge::Segment segment = segments.front(); if(segment.data.size() != 0x4000) continue; // is a copyright string present? uint8_t copyright_offset = segment.data[7]; if( segment.data[copyright_offset] != 0x00 || segment.data[copyright_offset+1] != 0x28 || segment.data[copyright_offset+2] != 0x43 || segment.data[copyright_offset+3] != 0x29 ) continue; // is the language entry point valid? if(!( (segment.data[0] == 0x00 && segment.data[1] == 0x00 && segment.data[2] == 0x00) || (segment.data[0] != 0x00 && segment.data[2] >= 0x80 && segment.data[2] < 0xc0) )) continue; // is the service entry point valid? if(!(segment.data[5] >= 0x80 && segment.data[5] < 0xc0)) continue; // probability of a random binary blob that isn't an Acorn ROM proceeding to here: // 1/(2^32) * // ( ((2^24)-1)/(2^24)*(1/4) + 1/(2^24) ) * // 1/4 // = something very improbable — around 1/16th of 1 in 2^32, but not exactly. acorn_cartridges.push_back(cartridge); } return acorn_cartridges; } void StaticAnalyser::Acorn::AddTargets(const Media &media, std::list &destination) { Target target; target.machine = Target::Electron; target.probability = 1.0; // TODO: a proper estimation target.acorn.has_dfs = false; target.acorn.has_adfs = false; target.acorn.should_shift_restart = false; // strip out inappropriate cartridges target.media.cartridges = AcornCartridgesFrom(media.cartridges); // if there are any tapes, attempt to get data from the first if(media.tapes.size() > 0) { std::shared_ptr tape = media.tapes.front(); std::list files = GetFiles(tape); tape->reset(); // continue if there are any files if(files.size()) { bool is_basic = true; // protected files are always for *RUNning only if(files.front().is_protected) is_basic = false; // check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code, // so that's also justification to *RUN std::size_t pointer = 0; uint8_t *data = &files.front().data[0]; std::size_t data_size = files.front().data.size(); while(1) { if(pointer >= data_size-1 || data[pointer] != 13) { is_basic = false; break; } if((data[pointer+1]&0x7f) == 0x7f) break; pointer += data[pointer+3]; } // Inspect first file. If it's protected or doesn't look like BASIC // then the loading command is *RUN. Otherwise it's CHAIN"". target.loadingCommand = is_basic ? "CHAIN\"\"\n" : "*RUN\n"; target.media.tapes = media.tapes; } } if(media.disks.size() > 0) { std::shared_ptr disk = media.disks.front(); std::unique_ptr dfs_catalogue, adfs_catalogue; dfs_catalogue = GetDFSCatalogue(disk); if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); if(dfs_catalogue || adfs_catalogue) { target.media.disks = media.disks; target.acorn.has_dfs = !!dfs_catalogue; target.acorn.has_adfs = !!adfs_catalogue; Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; if(bootOption != Catalogue::BootOption::None) target.acorn.should_shift_restart = true; else target.loadingCommand = "*CAT\n"; } } if(target.media.tapes.size() || target.media.disks.size() || target.media.cartridges.size()) destination.push_back(target); }