1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-12-24 12:30:17 +00:00

Merge pull request #1337 from TomHarte/ArchimedesADFs

Add some support for Archimedes ADF files.
This commit is contained in:
Thomas Harte 2024-02-24 15:32:08 -05:00 committed by GitHub
commit 45628ba9df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 124 additions and 43 deletions

View File

@ -13,6 +13,7 @@
#include "../../../Numeric/CRC.hpp"
#include <algorithm>
#include <cstring>
using namespace Analyser::Static::Acorn;
@ -99,8 +100,11 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
// Quick sanity checks.
if(root_directory[0x4cb]) return nullptr;
if(root_directory[1] != 'H' || root_directory[2] != 'u' || root_directory[3] != 'g' || root_directory[4] != 'o') return nullptr;
if(root_directory[0x4FB] != 'H' || root_directory[0x4FC] != 'u' || root_directory[0x4FD] != 'g' || root_directory[0x4FE] != 'o') return nullptr;
catalogue->is_hugo = !memcmp(&root_directory[1], "Hugo", 4) && !memcmp(&root_directory[0x4FB], "Hugo", 4);
const bool is_nick = !memcmp(&root_directory[1], "Nick", 4) && !memcmp(&root_directory[0x4FB], "Nick", 4);
if(!catalogue->is_hugo && !is_nick) {
return nullptr;
}
switch(free_space_map_second_half->samples[0][0xfd]) {
default: catalogue->bootOption = Catalogue::BootOption::None; break;

View File

@ -15,6 +15,7 @@ namespace Analyser::Static::Acorn {
/// Describes a DFS- or ADFS-format catalogue(/directory): the list of files available and the catalogue's boot option.
struct Catalogue {
bool is_hugo = false;
std::string name;
std::vector<File> files;
enum class BootOption {

View File

@ -62,10 +62,10 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
auto target = std::make_unique<Target>();
// strip out inappropriate cartridges
// Strip out inappropriate cartridges.
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
// if there are any tapes, attempt to get data from the first
// If there are any tapes, attempt to get data from the first.
if(!media.tapes.empty()) {
std::shared_ptr<Storage::Tape::Tape> tape = media.tapes.front();
std::vector<File> files = GetFiles(tape);
@ -101,11 +101,15 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
}
if(!media.disks.empty()) {
// TODO: below requires an [8-bit compatible] 'Hugo' ADFS catalogue, disallowing
// [Archimedes-exclusive] 'Nick' catalogues.
//
// Would be better to form the appropriate target in the latter case.
std::shared_ptr<Storage::Disk::Disk> disk = media.disks.front();
std::unique_ptr<Catalogue> dfs_catalogue, adfs_catalogue;
dfs_catalogue = GetDFSCatalogue(disk);
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
if(dfs_catalogue || adfs_catalogue) {
if(dfs_catalogue || (adfs_catalogue && adfs_catalogue->is_hugo)) {
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
target->media.disks = media.disks;

View File

@ -10,36 +10,105 @@
#include "Utility/ImplicitSectors.hpp"
namespace {
constexpr int sectors_per_track = 16;
constexpr int sector_size = 1;
}
#include <cstring>
using namespace Storage::Disk;
AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
// Check that the disk image contains a whole number of sector.
using sizeT = decltype(file_.stats().st_size);
if(file_.stats().st_size % sizeT(128 << sector_size)) throw Error::InvalidFormat;
const auto size = file_.stats().st_size;
if(size < 1024) throw Error::InvalidFormat;
// Definitely true: a directory signature of 'Hugo' can be read by both 8-bit
// machines and the Archimedes. 'Nick' can be read only by the Archimedes.
//
// https://mdfs.net/Docs/Comp/Disk/Format/ADFS then falsely states that:
//
// The type of ADFS filesystem can be determined by looking for the "Hugo"/
// "Nick" identifier that marks the start of the root directory 512 bytes into
// the filesystem and 1024 bytes in.
//
// In terms of .ADF files:
//
// all 8-bit files seem to have 'Hugo' at offset 513;
// ADFS-D (early Arc, late BBC Master) has 'Nick' or 'Hugo' at 1025; but
// ADFS-E (most Arc) has 'Hugo' at 2049.
//
// Even allowing for the document having failed to account for the directory ID,
// I can't reconcile that 2049 offset with being 1024 bytes into the file system.
//
// That document claims that ADFS-D and ADFS-E are logically interleaved but
// https://github.com/android444/fluxengine/blob/master/doc/disk-acornadfs.md
// states that:
//
// Acorn logical block numbering goes all the way up side 0 and then all the
// way up side 1. However, FluxEngine uses traditional disk images with alternating
// sides, with the blocks from track 0 side 0 then track 0 side 1 then track 1 side 0 etc.
// Most Acorn emulators will use both formats, but they might require nudging as the side
// order can't be reliably autodetected.
//
// So then .ADF files might be track-interleaved and might not be.
const auto has_identifier = [&](long location, bool permit_hugo, bool permit_nick) -> bool {
file_.seek(location, SEEK_SET);
uint8_t bytes[4];
file_.read(bytes, 4);
return
(permit_hugo && !memcmp(bytes, "Hugo", 4)) ||
(permit_nick && !memcmp(bytes, "Nick", 4));
};
const auto file_size = file_.stats().st_size;
Encodings::MFM::Density density = Encodings::MFM::Density::Double;
if(has_identifier(513, true, false)) {
// One of:
//
// ADFS-S: 1 side, 40 tracks, 16 sectors, 256 bytes = 160K old map, old dir
// ADFS-M: 1 side, 80 tracks, 16 sectors, 256 bytes = 320K old map, old dir
// ADFS-L: 2 sides, 80 tracks, 16 sectors, 256 bytes = 640K old map, old dir
head_count_ = file_.stats().st_size > 80*16*256 ? 2 : 1;
sector_size_ = 1;
sectors_per_track_ = 16;
} else if(has_identifier(1025, true, true)) {
// ADFS-D: 80 tracks, 2 sides, 5 sectors, 1024 bytes = 800K old map, new dir
head_count_ = 2;
sector_size_ = 3;
sectors_per_track_ = 5;
} else if(has_identifier(2049, false, true)) {
// One of:
//
// ADFS-E: 80 tracks, 2 sides, 5 sectors, 1024 bytes = 800K new map, new dir
// ADFS-F: 80 tracks, 2 sides, 10 sectors, 1024 bytes = 1600K new map, new dir
// ADFS-G: 80 tracks, 2 sides, 20 sectors, 1024 bytes = 3200K new map, new dir
head_count_ = 2;
sector_size_ = 3;
if(file_size > 80*2*10*1024) {
sectors_per_track_ = 20;
density = Encodings::MFM::Density::High; // Or, presumably, higher than high?
} else if(file_size > 80*2*10*1024) {
sectors_per_track_ = 10;
density = Encodings::MFM::Density::High;
} else {
sectors_per_track_ = 5;
}
} else {
throw Error::InvalidFormat;
}
// TODO: possibly this image is side-interleaved if sectors per track is less than 16.
// Figure that out.
// Check that the disk image is at least large enough to hold an ADFS catalogue.
if(file_.stats().st_size < 7 * sizeT(128 << sector_size)) throw Error::InvalidFormat;
// Check that the initial directory's 'Hugo's are present.
file_.seek(513, SEEK_SET);
uint8_t bytes[4];
file_.read(bytes, 4);
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw Error::InvalidFormat;
file_.seek(0x6fb, SEEK_SET);
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 * sizeT(128 << sector_size) * 80);
if(file_.stats().st_size < 7 * sizeT(128 << sector_size_)) throw Error::InvalidFormat;
// Announce disk geometry.
set_geometry(sectors_per_track, sector_size, 0, Encodings::MFM::Density::Double);
set_geometry(sectors_per_track_, sector_size_, 0, density);
}
HeadPosition AcornADF::get_maximum_head_position() {
@ -51,5 +120,5 @@ int AcornADF::get_head_count() {
}
long AcornADF::get_file_offset_for_position(Track::Address address) {
return (address.position.as_int() * head_count_ + address.head) * (128 << sector_size) * sectors_per_track;
return (address.position.as_int() * head_count_ + address.head) * (128 << sector_size_) * sectors_per_track_;
}

View File

@ -33,6 +33,8 @@ class AcornADF: public MFMSectorDump {
private:
long get_file_offset_for_position(Track::Address address) final;
int head_count_ = 1;
uint8_t sector_size_ = 1;
int sectors_per_track_ = 16;
};
}

View File

@ -23,24 +23,25 @@ enum Type: IntType {
AcornAtom = 1 << 5,
AcornElectron = 1 << 6,
Amiga = 1 << 7,
BBCMaster = 1 << 8,
BBCModelA = 1 << 9,
BBCModelB = 1 << 10,
Coleco = 1 << 11,
Commodore = 1 << 12,
DiskII = 1 << 13,
Enterprise = 1 << 14,
Sega = 1 << 15,
Macintosh = 1 << 16,
MSX = 1 << 17,
Oric = 1 << 18,
ZX80 = 1 << 19,
ZX81 = 1 << 20,
ZXSpectrum = 1 << 21,
PCCompatible = 1 << 22,
FAT12 = 1 << 23,
Archimedes = 1 << 8,
BBCMaster = 1 << 9,
BBCModelA = 1 << 10,
BBCModelB = 1 << 11,
Coleco = 1 << 12,
Commodore = 1 << 13,
DiskII = 1 << 14,
Enterprise = 1 << 15,
Sega = 1 << 16,
Macintosh = 1 << 17,
MSX = 1 << 18,
Oric = 1 << 19,
ZX80 = 1 << 20,
ZX81 = 1 << 21,
ZXSpectrum = 1 << 22,
PCCompatible = 1 << 23,
FAT12 = 1 << 24,
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB,
Acorn = AcornAtom | AcornElectron | BBCMaster | BBCModelA | BBCModelB | Archimedes,
ZX8081 = ZX80 | ZX81,
AllCartridge = Atari2600 | AcornElectron | Coleco | MSX,
AllDisk = Acorn | AmstradCPC | Commodore | Oric | MSX | ZXSpectrum | Macintosh | AtariST | DiskII | Amiga | PCCompatible | FAT12,