diff --git a/Analyser/Static/Acorn/Disk.cpp b/Analyser/Static/Acorn/Disk.cpp index eada88f3c..220f6d5c9 100644 --- a/Analyser/Static/Acorn/Disk.cpp +++ b/Analyser/Static/Acorn/Disk.cpp @@ -50,7 +50,10 @@ std::unique_ptr Analyser::Static::Acorn::GetDFSCatalogue(const std::s new_file.name = name; new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14)); new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10)); - new_file.is_protected = names->samples[0][file_offset + 7] & 0x80; + if(names->samples[0][file_offset + 7] & 0x80) { + // File is locked; it may not be altered or deleted. + new_file.flags |= File::Flags::Locked; + } long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12)); int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8); @@ -69,11 +72,16 @@ std::unique_ptr Analyser::Static::Acorn::GetDFSCatalogue(const std::s new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector); data_length -= length_from_sector; } - if(!data_length) catalogue->files.push_back(new_file); + if(!data_length) catalogue->files.push_back(std::move(new_file)); } return catalogue; } + +/* + Primary resource used: "Acorn 8-Bit ADFS Filesystem Structure"; + http://mdfs.net/Docs/Comp/Disk/Format/ADFS +*/ std::unique_ptr Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr &disk) { auto catalogue = std::make_unique(); Storage::Encodings::MFM::Parser parser(true, disk); @@ -101,5 +109,73 @@ std::unique_ptr Analyser::Static::Acorn::GetADFSCatalogue(const std:: case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break; } + // Parse the root directory, at least. + for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) { + // Obtain the name, which will be at most ten characters long, and will + // be terminated by either a NULL character or a \r. + char name[11]; + std::size_t c = 0; + for(; c < 10; c++) { + const char next = root_directory[file_offset + c] & 0x7f; + name[c] = next; + if(next == '\0' || next == '\r') break; + } + name[c] = '\0'; + + // Skip if the name is empty. + if(name[0] == '\0') continue; + + // Populate a file then. + File new_file; + new_file.name = name; + new_file.flags = + (root_directory[file_offset + 0] & 0x80 ? File::Flags::Readable : 0) | + (root_directory[file_offset + 1] & 0x80 ? File::Flags::Writable : 0) | + (root_directory[file_offset + 2] & 0x80 ? File::Flags::Locked : 0) | + (root_directory[file_offset + 3] & 0x80 ? File::Flags::IsDirectory : 0) | + (root_directory[file_offset + 4] & 0x80 ? File::Flags::ExecuteOnly : 0) | + (root_directory[file_offset + 5] & 0x80 ? File::Flags::PubliclyReadable : 0) | + (root_directory[file_offset + 6] & 0x80 ? File::Flags::PubliclyWritable : 0) | + (root_directory[file_offset + 7] & 0x80 ? File::Flags::PubliclyExecuteOnly : 0) | + (root_directory[file_offset + 8] & 0x80 ? File::Flags::IsPrivate : 0); + + new_file.load_address = + (uint32_t(root_directory[file_offset + 0x0a]) << 0) | + (uint32_t(root_directory[file_offset + 0x0b]) << 8) | + (uint32_t(root_directory[file_offset + 0x0c]) << 16) | + (uint32_t(root_directory[file_offset + 0x0d]) << 24); + + new_file.execution_address = + (uint32_t(root_directory[file_offset + 0x0e]) << 0) | + (uint32_t(root_directory[file_offset + 0x0f]) << 8) | + (uint32_t(root_directory[file_offset + 0x10]) << 16) | + (uint32_t(root_directory[file_offset + 0x11]) << 24); + + new_file.sequence_number = root_directory[file_offset + 0x19]; + + const uint32_t size = + (uint32_t(root_directory[file_offset + 0x12]) << 0) | + (uint32_t(root_directory[file_offset + 0x13]) << 8) | + (uint32_t(root_directory[file_offset + 0x14]) << 16) | + (uint32_t(root_directory[file_offset + 0x15]) << 24); + + uint32_t start_sector = + (uint32_t(root_directory[file_offset + 0x16]) << 0) | + (uint32_t(root_directory[file_offset + 0x17]) << 8) | + (uint32_t(root_directory[file_offset + 0x18]) << 16); + + new_file.data.reserve(size); + while(new_file.data.size() < size) { + const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16); + if(!sector) break; + + const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size()); + new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector)); + ++start_sector; + } + + catalogue->files.push_back(std::move(new_file)); + } + return catalogue; } diff --git a/Analyser/Static/Acorn/File.hpp b/Analyser/Static/Acorn/File.hpp index 11df52237..f0a9d612e 100644 --- a/Analyser/Static/Acorn/File.hpp +++ b/Analyser/Static/Acorn/File.hpp @@ -19,19 +19,38 @@ namespace Acorn { struct File { std::string name; - uint32_t load_address; - uint32_t execution_address; - bool is_protected; + uint32_t load_address = 0; + uint32_t execution_address = 0; + + enum Flags: uint16_t { + Readable = 1 << 0, + Writable = 1 << 1, + Locked = 1 << 2, + IsDirectory = 1 << 3, + ExecuteOnly = 1 << 4, + PubliclyReadable = 1 << 5, + PubliclyWritable = 1 << 6, + PubliclyExecuteOnly = 1 << 7, + IsPrivate = 1 << 8, + }; + uint16_t flags = Flags::Readable | Flags::Readable | Flags::PubliclyReadable | Flags::PubliclyWritable; + uint8_t sequence_number = 0; + std::vector data; + /// Describes a single chunk of file data; these relate to the tape and ROM filing system. + /// The File-level fields contain a 'definitive' version of the load and execution addresses, + /// but both of those filing systems also store them per chunk. + /// + /// Similarly, the file-level data will contain the aggregate data of all chunks. struct Chunk { std::string name; - uint32_t load_address; - uint32_t execution_address; - uint16_t block_number; - uint16_t block_length; - uint8_t block_flag; - uint32_t next_address; + uint32_t load_address = 0; + uint32_t execution_address = 0; + uint16_t block_number = 0; + uint16_t block_length = 0; + uint32_t next_address = 0; + uint8_t block_flag = 0; bool header_crc_matched; bool data_crc_matched; diff --git a/Analyser/Static/Acorn/StaticAnalyser.cpp b/Analyser/Static/Acorn/StaticAnalyser.cpp index 51d8b394a..76d655cfc 100644 --- a/Analyser/Static/Acorn/StaticAnalyser.cpp +++ b/Analyser/Static/Acorn/StaticAnalyser.cpp @@ -12,6 +12,8 @@ #include "Tape.hpp" #include "Target.hpp" +#include + using namespace Analyser::Static::Acorn; static std::vector> @@ -77,8 +79,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me if(!files.empty()) { bool is_basic = true; - // protected files are always for *RUNning only - if(files.front().is_protected) is_basic = false; + // If a file is execute-only, that means *RUN. + if(files.front().flags & File::Flags::ExecuteOnly) 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 @@ -108,15 +110,37 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me dfs_catalogue = GetDFSCatalogue(disk); if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk); if(dfs_catalogue || adfs_catalogue) { + // Accept the disk and determine whether DFS or ADFS ROMs are implied. target->media.disks = media.disks; - target->has_dfs = !!dfs_catalogue; - target->has_adfs = !!adfs_catalogue; + target->has_dfs = bool(dfs_catalogue); + target->has_adfs = bool(adfs_catalogue); + // Check whether a simple shift+break will do for loading this disk. Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption; - if(bootOption != Catalogue::BootOption::None) + if(bootOption != Catalogue::BootOption::None) { target->should_shift_restart = true; - else + } else { target->loading_command = "*CAT\n"; + } + + // Check whether adding the AP6 ROM is justified. + // For now this is an incredibly dense text search; + // if any of the commands that aren't usually present + // on a stock Electron are here, add the AP6 ROM and + // some sideways RAM such that the SR commands are useful. + for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) { + for(const auto &command: { + "AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM", + "LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE", + "SRUNLOCK", "SRWIPE", "TUBE", "TYPE", "UNLOCK", "UNPLUG", "UROMS", + "VERIFY", "ZERO" + }) { + if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) { + target->has_ap6_rom = true; + target->has_sideways_ram = true; + } + } + } } } diff --git a/Analyser/Static/Acorn/Tape.cpp b/Analyser/Static/Acorn/Tape.cpp index 461f01960..c812d21ad 100644 --- a/Analyser/Static/Acorn/Tape.cpp +++ b/Analyser/Static/Acorn/Tape.cpp @@ -109,7 +109,12 @@ static std::unique_ptr GetNextFile(std::deque &chunks) { file->name = file->chunks.front().name; file->load_address = file->chunks.front().load_address; file->execution_address = file->chunks.front().execution_address; - file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check. + // 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; + } // copy all data into a single big block for(File::Chunk chunk : file->chunks) { diff --git a/Analyser/Static/Acorn/Target.hpp b/Analyser/Static/Acorn/Target.hpp index 1ef46c291..f415f4b30 100644 --- a/Analyser/Static/Acorn/Target.hpp +++ b/Analyser/Static/Acorn/Target.hpp @@ -20,6 +20,8 @@ namespace Acorn { struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl { bool has_adfs = false; bool has_dfs = false; + bool has_ap6_rom = false; + bool has_sideways_ram = false; bool should_shift_restart = false; std::string loading_command; @@ -27,6 +29,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl< if(needs_declare()) { DeclareField(has_adfs); DeclareField(has_dfs); + DeclareField(has_ap6_rom); + DeclareField(has_sideways_ram); } } }; diff --git a/Analyser/Static/StaticAnalyser.cpp b/Analyser/Static/StaticAnalyser.cpp index 27f4c3dee..9384620e9 100644 --- a/Analyser/Static/StaticAnalyser.cpp +++ b/Analyser/Static/StaticAnalyser.cpp @@ -111,6 +111,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform:: Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81 Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26 Format("adf", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // ADF + Format("adl", result.disks, Disk::DiskImageHolder, TargetPlatform::Acorn) // ADL Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump) Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index 53fb9b028..80bc3ccf7 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -72,6 +72,10 @@ class ConcreteMachine: if(target.has_dfs) { required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5); } + const size_t ap6_rom_position = required_roms.size(); + if(target.has_ap6_rom) { + required_roms.emplace_back(machine_name, "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfc); + } const auto roms = rom_fetcher(required_roms); for(const auto &rom: roms) { @@ -82,6 +86,15 @@ class ConcreteMachine: set_rom(ROM::BASIC, *roms[0], false); set_rom(ROM::OS, *roms[1], false); + /* + ROM slot mapping applied: + + * the keyboard and BASIC ROMs occupy slots 8, 9, 10 and 11; + * the DFS, if in use, occupies slot 1; + * the ADFS, if in use, occupies slots 4 and 5; + * the AP6, if in use, occupies slot 15; and + * if sideways RAM was asked for, all otherwise unused slots are populated with sideways RAM. + */ if(target.has_dfs || target.has_adfs) { plus3_ = std::make_unique(); @@ -94,6 +107,18 @@ class ConcreteMachine: } } + if(target.has_ap6_rom) { + set_rom(ROM::Slot15, *roms[ap6_rom_position], true); + } + + if(target.has_sideways_ram) { + for(int c = 0; c < 16; c++) { + if(rom_inserted_[c]) continue; + if(c >= int(ROM::Keyboard) && c < int(ROM::BASIC)+1) continue; + set_sideways_ram(ROM(c)); + } + } + insert_media(target.media); if(!target.loading_command.empty()) { @@ -517,8 +542,20 @@ class ConcreteMachine: rom_ptr += size_to_copy; } - if(int(slot) < 16) + if(int(slot) < 16) { rom_inserted_[int(slot)] = true; + } + } + + /*! + Enables @c slot as sideways RAM; ensures that it does not currently contain a valid ROM signature. + */ + void set_sideways_ram(ROM slot) { + std::memset(roms_[int(slot)], 0xff, 16*1024); + if(int(slot) < 16) { + rom_inserted_[int(slot)] = true; + rom_write_masks_[int(slot)] = true; + } } // MARK: - Work deferral updates. diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index c093a1acc..964397320 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -86,7 +86,7 @@ typedef int Kilobytes; - (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController; - (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize; - (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model; -- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs; +- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM; - (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model; - (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive; - (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index 37a4bf58f..67ea100cf 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -115,13 +115,15 @@ return self; } -- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs { +- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM { self = [super init]; if(self) { using Target = Analyser::Static::Acorn::Target; auto target = std::make_unique(); - target->has_dfs = !!dfs; - target->has_adfs = !!adfs; + target->has_dfs = dfs; + target->has_adfs = adfs; + target->has_ap6_rom = ap6; + target->has_sideways_ram = sidewaysRAM; _targets.push_back(std::move(target)); } return self; diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 92ce034e1..3b8df9258 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -1,8 +1,8 @@ - + - + @@ -18,7 +18,7 @@ - + @@ -254,31 +254,49 @@ Gw - + + + - + + + + + @@ -322,18 +340,18 @@ Gw - + - + @@ -347,7 +365,7 @@ Gw - + @@ -626,7 +644,9 @@ Gw + + diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift index d269c4b45..b84ea44a5 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift +++ b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift @@ -22,6 +22,8 @@ class MachinePicker: NSObject { // MARK: - Electron properties @IBOutlet var electronDFSButton: NSButton? @IBOutlet var electronADFSButton: NSButton? + @IBOutlet var electronAP6Button: NSButton? + @IBOutlet var electronSidewaysRAMButton: NSButton? // MARK: - CPC properties @IBOutlet var cpcModelTypeButton: NSPopUpButton? @@ -73,6 +75,8 @@ class MachinePicker: NSObject { // Electron settings electronDFSButton?.state = standardUserDefaults.bool(forKey: "new.electronDFS") ? .on : .off electronADFSButton?.state = standardUserDefaults.bool(forKey: "new.electronADFS") ? .on : .off + electronAP6Button?.state = standardUserDefaults.bool(forKey: "new.electronAP6") ? .on : .off + electronSidewaysRAMButton?.state = standardUserDefaults.bool(forKey: "new.electronSidewaysRAM") ? .on : .off // CPC settings cpcModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel")) @@ -118,6 +122,8 @@ class MachinePicker: NSObject { // Electron settings standardUserDefaults.set(electronDFSButton!.state == .on, forKey: "new.electronDFS") standardUserDefaults.set(electronADFSButton!.state == .on, forKey: "new.electronADFS") + standardUserDefaults.set(electronAP6Button!.state == .on, forKey: "new.electronAP6") + standardUserDefaults.set(electronSidewaysRAMButton!.state == .on, forKey: "new.electronSidewaysRAM") // CPC settings standardUserDefaults.set(cpcModelTypeButton!.selectedTag(), forKey: "new.cpcModel") @@ -152,7 +158,11 @@ class MachinePicker: NSObject { switch machineSelector!.selectedTabViewItem!.identifier as! String { case "electron": - return CSStaticAnalyser(electronDFS: electronDFSButton!.state == .on, adfs: electronADFSButton!.state == .on) + return CSStaticAnalyser( + electronDFS: electronDFSButton!.state == .on, + adfs: electronADFSButton!.state == .on, + ap6: electronAP6Button!.state == .on, + sidewaysRAM: electronSidewaysRAMButton!.state == .on) case "appleii": var model: CSMachineAppleIIModel = .appleII diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp index 549879668..091cbde9a 100644 --- a/OSBindings/Qt/mainwindow.cpp +++ b/OSBindings/Qt/mainwindow.cpp @@ -1185,6 +1185,8 @@ void MainWindow::start_electron() { target->has_dfs = ui->electronDFSCheckBox->isChecked(); target->has_adfs = ui->electronADFSCheckBox->isChecked(); + target->has_ap6_rom = ui->electronAP6CheckBox->isChecked(); + target->has_sideways_ram = ui->electronSidewaysRAMCheckBox->isChecked(); launchTarget(std::move(target)); } @@ -1323,6 +1325,8 @@ void MainWindow::launchTarget(std::unique_ptr &&target /* Electron. */ \ CheckBox(electronDFSCheckBox, "electron.hasDFS"); \ CheckBox(electronADFSCheckBox, "electron.hasADFS"); \ + CheckBox(electronAP6CheckBox, "electron.hasAP6"); \ + CheckBox(electronSidewaysRAMCheckBox, "electron.fillSidewaysRAM"); \ \ /* Macintosh. */ \ ComboBox(macintoshModelComboBox, "macintosh.model"); \ diff --git a/OSBindings/Qt/mainwindow.ui b/OSBindings/Qt/mainwindow.ui index 451bd1506..f3d2dea23 100644 --- a/OSBindings/Qt/mainwindow.ui +++ b/OSBindings/Qt/mainwindow.ui @@ -281,6 +281,20 @@ + + + + With Advanced Plus 6 Utility ROM + + + + + + + Fill unused ROM banks with sideways RAM + + + diff --git a/ROMImages/Electron/readme.txt b/ROMImages/Electron/readme.txt index accfc031e..995145ee9 100644 --- a/ROMImages/Electron/readme.txt +++ b/ROMImages/Electron/readme.txt @@ -4,13 +4,41 @@ Expected files: basic.rom os.rom -plus1.rom -DFS-1770-2.20.rom -ADFS-E00_1.rom +DFS-1770-2.20.rom — used only if the user opens a DFS disk image +ADFS-E00_1.rom — used only if the user opens an ADFS disk image ADFS-E00_2.rom +AP6v133.rom — used only if the user opens a disk image that makes use of any of the commands given below. -Likely to be desired in the future: +Possibly to be desired in the future: +* adfs.rom +* ElectronExpansionRomPresAP2-v1.23.rom +* os300.rom -adfs.rom -ElectronExpansionRomPresAP2-v1.23.rom -os300.rom +Commands that trigger a request for the AP6v133 ROM: +* *AQRPAGE +* *BUILD +* *DUMP +* *FORMAT +* *INSERT +* *LANG +* *LIST +* *LOADROM +* *LOCK +* *LROMS +* *RLOAD +* *ROMS +* *RSAVE +* *SAVEROM +* *SRLOAD +* *SRLOCK +* *SRPAGE +* *SRSAVE +* *SRUNLOCK +* *SRWIPE +* *TUBE +* *TYPE +* *UNLOCK +* *UNPLUG +* *UROMS +* *VERIFY +* *ZERO \ No newline at end of file diff --git a/Storage/Disk/DiskImage/Formats/AcornADF.cpp b/Storage/Disk/DiskImage/Formats/AcornADF.cpp index 7fc657273..e5b78e686 100644 --- a/Storage/Disk/DiskImage/Formats/AcornADF.cpp +++ b/Storage/Disk/DiskImage/Formats/AcornADF.cpp @@ -18,12 +18,13 @@ namespace { using namespace Storage::Disk; AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) { - // very loose validation: the file needs to be a multiple of 256 bytes - // and not ungainly large + // Check that the disk image contains a whole number of sector. if(file_.stats().st_size % off_t(128 << sector_size)) throw Error::InvalidFormat; + + // Check that the disk image is at least large enough to hold an ADFS catalogue. if(file_.stats().st_size < 7 * off_t(128 << sector_size)) throw Error::InvalidFormat; - // check that the initial directory's 'Hugo's are present + // Check that the initial directory's 'Hugo's are present. file_.seek(513, SEEK_SET); uint8_t bytes[4]; file_.read(bytes, 4); @@ -33,6 +34,10 @@ AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) { file_.read(bytes, 4); if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw Error::InvalidFormat; + // Pick a number of heads; treat this image as double sided if it's too large to be single-sided. + head_count_ = 1 + (file_.stats().st_size > sectors_per_track * off_t(128 << sector_size) * 80); + + // Announce disk geometry. set_geometry(sectors_per_track, sector_size, 0, true); } @@ -41,9 +46,9 @@ HeadPosition AcornADF::get_maximum_head_position() { } int AcornADF::get_head_count() { - return 1; + return head_count_; } long AcornADF::get_file_offset_for_position(Track::Address address) { - return address.position.as_int() * (128 << sector_size) * sectors_per_track; + return (address.position.as_int() * head_count_ + address.head) * (128 << sector_size) * sectors_per_track; } diff --git a/Storage/Disk/DiskImage/Formats/AcornADF.hpp b/Storage/Disk/DiskImage/Formats/AcornADF.hpp index c0d040d56..1988579f1 100644 --- a/Storage/Disk/DiskImage/Formats/AcornADF.hpp +++ b/Storage/Disk/DiskImage/Formats/AcornADF.hpp @@ -34,6 +34,7 @@ class AcornADF: public MFMSectorDump { private: long get_file_offset_for_position(Track::Address address) final; + int head_count_ = 1; }; }