mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-22 12:33:29 +00:00
Merge pull request #1353 from TomHarte/ArchmidesAnalysis
Add a through path for Archimedes disk images.
This commit is contained in:
commit
692a9da2e4
@ -17,6 +17,7 @@ enum class Machine {
|
||||
Atari2600,
|
||||
AtariST,
|
||||
Amiga,
|
||||
Archimedes,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
|
@ -87,37 +87,45 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
auto catalogue = std::make_unique<Catalogue>();
|
||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
||||
|
||||
const Storage::Encodings::MFM::Sector *free_space_map_second_half = parser.sector(0, 0, 1);
|
||||
// Grab the second half of the free-space map because it has the boot option in it.
|
||||
const Storage::Encodings::MFM::Sector *const free_space_map_second_half = parser.sector(0, 0, 1);
|
||||
if(!free_space_map_second_half) return nullptr;
|
||||
|
||||
const bool has_large_sectors = free_space_map_second_half->samples[0].size() == 1024;
|
||||
std::vector<uint8_t> root_directory;
|
||||
root_directory.reserve(5 * 256);
|
||||
for(uint8_t c = 2; c < 7; c++) {
|
||||
root_directory.reserve((has_large_sectors ? 5 : 8) * 256);
|
||||
|
||||
for(uint8_t c = 2; c < (has_large_sectors ? 4 : 7); c++) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.sector(0, 0, c);
|
||||
if(!sector) return nullptr;
|
||||
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
|
||||
}
|
||||
|
||||
// Quick sanity checks.
|
||||
if(root_directory[0x4cb]) 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);
|
||||
// Check for end of directory marker.
|
||||
if(root_directory[has_large_sectors ? 0x7d7 : 0x4cb]) return nullptr;
|
||||
|
||||
// Check for both directory identifiers.
|
||||
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[0x7fb], "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;
|
||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
if(!has_large_sectors) {
|
||||
// TODO: I don't know where the boot option rests with large sectors.
|
||||
switch(free_space_map_second_half->samples[0][0xfd]) {
|
||||
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; 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) {
|
||||
for(std::size_t file_offset = 0x005; file_offset < (has_large_sectors ? 0x7d7 : 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];
|
||||
char name[11]{};
|
||||
std::size_t c = 0;
|
||||
for(; c < 10; c++) {
|
||||
const char next = root_directory[file_offset + c] & 0x7f;
|
||||
|
@ -60,10 +60,11 @@ 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>();
|
||||
auto target8bit = std::make_unique<Target>();
|
||||
auto targetArchimedes = std::make_unique<Analyser::Static::Target>(Machine::Archimedes);
|
||||
|
||||
// Strip out inappropriate cartridges.
|
||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
// Copy appropriate cartridges to the 8-bit target.
|
||||
target8bit->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
|
||||
// If there are any tapes, attempt to get data from the first.
|
||||
if(!media.tapes.empty()) {
|
||||
@ -94,9 +95,9 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
|
||||
// Inspect first file. If it's protected or doesn't look like BASIC
|
||||
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
||||
target->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
target8bit->loading_command = is_basic ? "CHAIN\"\"\n" : "*RUN\n";
|
||||
|
||||
target->media.tapes = media.tapes;
|
||||
target8bit->media.tapes = media.tapes;
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,16 +113,16 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
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;
|
||||
target->has_dfs = bool(dfs_catalogue);
|
||||
target->has_pres_adfs = bool(adfs_catalogue);
|
||||
target8bit->media.disks = media.disks;
|
||||
target8bit->has_dfs = bool(dfs_catalogue);
|
||||
target8bit->has_pres_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) {
|
||||
target->should_shift_restart = true;
|
||||
target8bit->should_shift_restart = true;
|
||||
} else {
|
||||
target->loading_command = "*CAT\n";
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
}
|
||||
|
||||
// Check whether adding the AP6 ROM is justified.
|
||||
@ -137,39 +138,44 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
"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;
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if(adfs_catalogue) {
|
||||
targetArchimedes->media.disks = media.disks;
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the Acorn ADFS if a mass-storage device is attached;
|
||||
// unlike the Pres ADFS it retains SCSI logic.
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target->has_acorn_adfs = true;
|
||||
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target8bit->has_acorn_adfs = true;
|
||||
|
||||
// Assume some sort of later-era Acorn work is likely to happen;
|
||||
// so ensure *TYPE, etc are present.
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
target8bit->has_ap6_rom = true;
|
||||
target8bit->has_sideways_ram = true;
|
||||
|
||||
target->media.mass_storage_devices = media.mass_storage_devices;
|
||||
target8bit->media.mass_storage_devices = media.mass_storage_devices;
|
||||
|
||||
// Check for a boot option.
|
||||
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
|
||||
const auto sector = target8bit->media.mass_storage_devices.front()->get_block(1);
|
||||
if(sector[0xfd]) {
|
||||
target->should_shift_restart = true;
|
||||
target8bit->should_shift_restart = true;
|
||||
} else {
|
||||
target->loading_command = "*CAT\n";
|
||||
target8bit->loading_command = "*CAT\n";
|
||||
}
|
||||
}
|
||||
|
||||
TargetList targets;
|
||||
if(!target->media.empty()) {
|
||||
targets.push_back(std::move(target));
|
||||
if(!target8bit->media.empty()) {
|
||||
targets.push_back(std::move(target8bit));
|
||||
}
|
||||
if(!targetArchimedes->media.empty()) {
|
||||
targets.push_back(std::move(targetArchimedes));
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ std::unique_ptr<Machine::DynamicMachine> Machine::MachineForTarget(const Analyse
|
||||
|
||||
std::unique_ptr<Machine::DynamicMachine> machine;
|
||||
try {
|
||||
// TODO: add Archimedes below.
|
||||
#define BindD(name, m) case Analyser::Machine::m: machine = std::make_unique<Machine::TypedDynamicMachine<::name::Machine>>(name::Machine::m(target, rom_fetcher)); break;
|
||||
#define Bind(m) BindD(m, m)
|
||||
switch(target->machine) {
|
||||
@ -133,6 +134,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
|
||||
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
|
||||
case Analyser::Machine::AppleII: return "AppleII";
|
||||
case Analyser::Machine::AppleIIgs: return "AppleIIgs";
|
||||
case Analyser::Machine::Archimedes: return "Archimedes";
|
||||
case Analyser::Machine::Atari2600: return "Atari2600";
|
||||
case Analyser::Machine::AtariST: return "AtariST";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
@ -157,6 +159,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
|
||||
case Analyser::Machine::AmstradCPC: return "Amstrad CPC";
|
||||
case Analyser::Machine::AppleII: return "Apple II";
|
||||
case Analyser::Machine::AppleIIgs: return "Apple IIgs";
|
||||
case Analyser::Machine::Archimedes: return "Acorn Archimedes";
|
||||
case Analyser::Machine::Atari2600: return "Atari 2600";
|
||||
case Analyser::Machine::AtariST: return "Atari ST";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
@ -191,6 +194,7 @@ std::vector<std::string> Machine::AllMachines(Type type, bool long_names) {
|
||||
AddName(AmstradCPC);
|
||||
AddName(AppleII);
|
||||
AddName(AppleIIgs);
|
||||
AddName(Archimedes);
|
||||
AddName(AtariST);
|
||||
AddName(Electron);
|
||||
AddName(Enterprise);
|
||||
@ -245,6 +249,7 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
|
||||
Add(AmstradCPC);
|
||||
Add(AppleII);
|
||||
Add(AppleIIgs);
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Archimedes), new Analyser::Static::Target(Analyser::Machine::Archimedes)));
|
||||
Add(AtariST);
|
||||
AddMapped(Electron, Acorn);
|
||||
Add(Enterprise);
|
||||
|
@ -144,13 +144,21 @@ struct ActivityObserver: public Activity::Observer {
|
||||
ROM::Request missing_roms;
|
||||
_machine = Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error);
|
||||
if(!_machine) {
|
||||
const std::wstring description = missing_roms.description(0, L'•');
|
||||
static_assert(sizeof(wchar_t) == 4, "This code assumes wchar_t is UTF32");
|
||||
NSString *nativeString = [[NSString alloc]
|
||||
initWithBytes:description.data()
|
||||
length:description.size()*sizeof(wchar_t)
|
||||
encoding:NSUTF32LittleEndianStringEncoding];
|
||||
[missingROMs appendString:nativeString];
|
||||
switch(error) {
|
||||
case Machine::Error::MissingROM: {
|
||||
const std::wstring description = missing_roms.description(0, L'•');
|
||||
static_assert(sizeof(wchar_t) == 4, "This code assumes wchar_t is UTF32");
|
||||
NSString *nativeString = [[NSString alloc]
|
||||
initWithBytes:description.data()
|
||||
length:description.size()*sizeof(wchar_t)
|
||||
encoding:NSUTF32LittleEndianStringEncoding];
|
||||
[missingROMs appendString:nativeString];
|
||||
} break;
|
||||
|
||||
default:
|
||||
NSLog(@"Unhandled machine creation error %d", error);
|
||||
break;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
updater.performer.machine = _machine.get();
|
||||
|
Loading…
Reference in New Issue
Block a user