mirror of
https://github.com/TomHarte/CLK.git
synced 2024-09-04 06:29:47 +00:00
Merge pull request #865 from TomHarte/ADL
Electron: adds support for the ADL file format, and logic for AP6 and sideways RAM selection
This commit is contained in:
commit
d54085c7fd
@ -50,7 +50,10 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
|||||||
new_file.name = name;
|
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.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.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));
|
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);
|
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
|
||||||
@ -69,11 +72,16 @@ std::unique_ptr<Catalogue> 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);
|
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;
|
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;
|
return catalogue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Primary resource used: "Acorn 8-Bit ADFS Filesystem Structure";
|
||||||
|
http://mdfs.net/Docs/Comp/Disk/Format/ADFS
|
||||||
|
*/
|
||||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||||
auto catalogue = std::make_unique<Catalogue>();
|
auto catalogue = std::make_unique<Catalogue>();
|
||||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||||
@ -101,5 +109,73 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
|||||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
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;
|
return catalogue;
|
||||||
}
|
}
|
||||||
|
@ -19,19 +19,38 @@ namespace Acorn {
|
|||||||
|
|
||||||
struct File {
|
struct File {
|
||||||
std::string name;
|
std::string name;
|
||||||
uint32_t load_address;
|
uint32_t load_address = 0;
|
||||||
uint32_t execution_address;
|
uint32_t execution_address = 0;
|
||||||
bool is_protected;
|
|
||||||
|
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<uint8_t> data;
|
std::vector<uint8_t> 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 {
|
struct Chunk {
|
||||||
std::string name;
|
std::string name;
|
||||||
uint32_t load_address;
|
uint32_t load_address = 0;
|
||||||
uint32_t execution_address;
|
uint32_t execution_address = 0;
|
||||||
uint16_t block_number;
|
uint16_t block_number = 0;
|
||||||
uint16_t block_length;
|
uint16_t block_length = 0;
|
||||||
uint8_t block_flag;
|
uint32_t next_address = 0;
|
||||||
uint32_t next_address;
|
uint8_t block_flag = 0;
|
||||||
|
|
||||||
bool header_crc_matched;
|
bool header_crc_matched;
|
||||||
bool data_crc_matched;
|
bool data_crc_matched;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
#include "Tape.hpp"
|
#include "Tape.hpp"
|
||||||
#include "Target.hpp"
|
#include "Target.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace Analyser::Static::Acorn;
|
using namespace Analyser::Static::Acorn;
|
||||||
|
|
||||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||||
@ -77,8 +79,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
|||||||
if(!files.empty()) {
|
if(!files.empty()) {
|
||||||
bool is_basic = true;
|
bool is_basic = true;
|
||||||
|
|
||||||
// protected files are always for *RUNning only
|
// If a file is execute-only, that means *RUN.
|
||||||
if(files.front().is_protected) is_basic = false;
|
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,
|
// 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
|
// 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);
|
dfs_catalogue = GetDFSCatalogue(disk);
|
||||||
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
||||||
if(dfs_catalogue || adfs_catalogue) {
|
if(dfs_catalogue || adfs_catalogue) {
|
||||||
|
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
|
||||||
target->media.disks = media.disks;
|
target->media.disks = media.disks;
|
||||||
target->has_dfs = !!dfs_catalogue;
|
target->has_dfs = bool(dfs_catalogue);
|
||||||
target->has_adfs = !!adfs_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;
|
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||||
if(bootOption != Catalogue::BootOption::None)
|
if(bootOption != Catalogue::BootOption::None) {
|
||||||
target->should_shift_restart = true;
|
target->should_shift_restart = true;
|
||||||
else
|
} else {
|
||||||
target->loading_command = "*CAT\n";
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +109,12 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
|
|||||||
file->name = file->chunks.front().name;
|
file->name = file->chunks.front().name;
|
||||||
file->load_address = file->chunks.front().load_address;
|
file->load_address = file->chunks.front().load_address;
|
||||||
file->execution_address = file->chunks.front().execution_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
|
// copy all data into a single big block
|
||||||
for(File::Chunk chunk : file->chunks) {
|
for(File::Chunk chunk : file->chunks) {
|
||||||
|
@ -20,6 +20,8 @@ namespace Acorn {
|
|||||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||||
bool has_adfs = false;
|
bool has_adfs = false;
|
||||||
bool has_dfs = false;
|
bool has_dfs = false;
|
||||||
|
bool has_ap6_rom = false;
|
||||||
|
bool has_sideways_ram = false;
|
||||||
bool should_shift_restart = false;
|
bool should_shift_restart = false;
|
||||||
std::string loading_command;
|
std::string loading_command;
|
||||||
|
|
||||||
@ -27,6 +29,8 @@ struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<
|
|||||||
if(needs_declare()) {
|
if(needs_declare()) {
|
||||||
DeclareField(has_adfs);
|
DeclareField(has_adfs);
|
||||||
DeclareField(has_dfs);
|
DeclareField(has_dfs);
|
||||||
|
DeclareField(has_ap6_rom);
|
||||||
|
DeclareField(has_sideways_ram);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -98,6 +98,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
|||||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||||
|
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
|
||||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
|
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
|
||||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||||
|
@ -72,6 +72,10 @@ class ConcreteMachine:
|
|||||||
if(target.has_dfs) {
|
if(target.has_dfs) {
|
||||||
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
|
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);
|
const auto roms = rom_fetcher(required_roms);
|
||||||
|
|
||||||
for(const auto &rom: roms) {
|
for(const auto &rom: roms) {
|
||||||
@ -82,6 +86,15 @@ class ConcreteMachine:
|
|||||||
set_rom(ROM::BASIC, *roms[0], false);
|
set_rom(ROM::BASIC, *roms[0], false);
|
||||||
set_rom(ROM::OS, *roms[1], 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) {
|
if(target.has_dfs || target.has_adfs) {
|
||||||
plus3_ = std::make_unique<Plus3>();
|
plus3_ = std::make_unique<Plus3>();
|
||||||
|
|
||||||
@ -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);
|
insert_media(target.media);
|
||||||
|
|
||||||
if(!target.loading_command.empty()) {
|
if(!target.loading_command.empty()) {
|
||||||
@ -517,8 +542,20 @@ class ConcreteMachine:
|
|||||||
rom_ptr += size_to_copy;
|
rom_ptr += size_to_copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(int(slot) < 16)
|
if(int(slot) < 16) {
|
||||||
rom_inserted_[int(slot)] = true;
|
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.
|
// MARK: - Work deferral updates.
|
||||||
|
@ -4,13 +4,41 @@ Expected files:
|
|||||||
|
|
||||||
basic.rom
|
basic.rom
|
||||||
os.rom
|
os.rom
|
||||||
plus1.rom
|
DFS-1770-2.20.rom — used only if the user opens a DFS disk image
|
||||||
DFS-1770-2.20.rom
|
ADFS-E00_1.rom — used only if the user opens an ADFS disk image
|
||||||
ADFS-E00_1.rom
|
|
||||||
ADFS-E00_2.rom
|
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
|
Commands that trigger a request for the AP6v133 ROM:
|
||||||
ElectronExpansionRomPresAP2-v1.23.rom
|
* *AQRPAGE
|
||||||
os300.rom
|
* *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
|
@ -18,12 +18,13 @@ namespace {
|
|||||||
using namespace Storage::Disk;
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
|
AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
|
||||||
// very loose validation: the file needs to be a multiple of 256 bytes
|
// Check that the disk image contains a whole number of sector.
|
||||||
// and not ungainly large
|
|
||||||
if(file_.stats().st_size % off_t(128 << sector_size)) throw Error::InvalidFormat;
|
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;
|
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);
|
file_.seek(513, SEEK_SET);
|
||||||
uint8_t bytes[4];
|
uint8_t bytes[4];
|
||||||
file_.read(bytes, 4);
|
file_.read(bytes, 4);
|
||||||
@ -33,6 +34,10 @@ AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
|
|||||||
file_.read(bytes, 4);
|
file_.read(bytes, 4);
|
||||||
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw Error::InvalidFormat;
|
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);
|
set_geometry(sectors_per_track, sector_size, 0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,9 +46,9 @@ HeadPosition AcornADF::get_maximum_head_position() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int AcornADF::get_head_count() {
|
int AcornADF::get_head_count() {
|
||||||
return 1;
|
return head_count_;
|
||||||
}
|
}
|
||||||
|
|
||||||
long AcornADF::get_file_offset_for_position(Track::Address address) {
|
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;
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ class AcornADF: public MFMSectorDump {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
long get_file_offset_for_position(Track::Address address) final;
|
long get_file_offset_for_position(Track::Address address) final;
|
||||||
|
int head_count_ = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user