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:
commit
45628ba9df
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user