mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-25 16:31:42 +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,
|
Atari2600,
|
||||||
AtariST,
|
AtariST,
|
||||||
Amiga,
|
Amiga,
|
||||||
|
Archimedes,
|
||||||
ColecoVision,
|
ColecoVision,
|
||||||
Electron,
|
Electron,
|
||||||
Enterprise,
|
Enterprise,
|
||||||
|
@ -87,37 +87,45 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
|||||||
auto catalogue = std::make_unique<Catalogue>();
|
auto catalogue = std::make_unique<Catalogue>();
|
||||||
Storage::Encodings::MFM::Parser parser(Storage::Encodings::MFM::Density::Double, disk);
|
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;
|
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;
|
std::vector<uint8_t> root_directory;
|
||||||
root_directory.reserve(5 * 256);
|
root_directory.reserve((has_large_sectors ? 5 : 8) * 256);
|
||||||
for(uint8_t c = 2; c < 7; c++) {
|
|
||||||
|
for(uint8_t c = 2; c < (has_large_sectors ? 4 : 7); c++) {
|
||||||
const Storage::Encodings::MFM::Sector *const sector = parser.sector(0, 0, c);
|
const Storage::Encodings::MFM::Sector *const sector = parser.sector(0, 0, c);
|
||||||
if(!sector) return nullptr;
|
if(!sector) return nullptr;
|
||||||
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
|
root_directory.insert(root_directory.end(), sector->samples[0].begin(), sector->samples[0].end());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick sanity checks.
|
// Check for end of directory marker.
|
||||||
if(root_directory[0x4cb]) return nullptr;
|
if(root_directory[has_large_sectors ? 0x7d7 : 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 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) {
|
if(!catalogue->is_hugo && !is_nick) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(free_space_map_second_half->samples[0][0xfd]) {
|
if(!has_large_sectors) {
|
||||||
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
// TODO: I don't know where the boot option rests with large sectors.
|
||||||
case 1: catalogue->bootOption = Catalogue::BootOption::LoadBOOT; break;
|
switch(free_space_map_second_half->samples[0][0xfd]) {
|
||||||
case 2: catalogue->bootOption = Catalogue::BootOption::RunBOOT; break;
|
default: catalogue->bootOption = Catalogue::BootOption::None; break;
|
||||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; 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.
|
// 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
|
// Obtain the name, which will be at most ten characters long, and will
|
||||||
// be terminated by either a NULL character or a \r.
|
// be terminated by either a NULL character or a \r.
|
||||||
char name[11];
|
char name[11]{};
|
||||||
std::size_t c = 0;
|
std::size_t c = 0;
|
||||||
for(; c < 10; c++) {
|
for(; c < 10; c++) {
|
||||||
const char next = root_directory[file_offset + c] & 0x7f;
|
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) {
|
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.
|
// Copy appropriate cartridges to the 8-bit target.
|
||||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
target8bit->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()) {
|
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
|
// Inspect first file. If it's protected or doesn't look like BASIC
|
||||||
// then the loading command is *RUN. Otherwise it's CHAIN"".
|
// 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)) {
|
if(dfs_catalogue || (adfs_catalogue && adfs_catalogue->is_hugo)) {
|
||||||
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
|
// 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.
|
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
|
||||||
target->media.disks = media.disks;
|
target8bit->media.disks = media.disks;
|
||||||
target->has_dfs = bool(dfs_catalogue);
|
target8bit->has_dfs = bool(dfs_catalogue);
|
||||||
target->has_pres_adfs = bool(adfs_catalogue);
|
target8bit->has_pres_adfs = bool(adfs_catalogue);
|
||||||
|
|
||||||
// Check whether a simple shift+break will do for loading this disk.
|
// 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;
|
target8bit->should_shift_restart = true;
|
||||||
} else {
|
} else {
|
||||||
target->loading_command = "*CAT\n";
|
target8bit->loading_command = "*CAT\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether adding the AP6 ROM is justified.
|
// Check whether adding the AP6 ROM is justified.
|
||||||
@ -137,39 +138,44 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
|||||||
"VERIFY", "ZERO"
|
"VERIFY", "ZERO"
|
||||||
}) {
|
}) {
|
||||||
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
||||||
target->has_ap6_rom = true;
|
target8bit->has_ap6_rom = true;
|
||||||
target->has_sideways_ram = 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;
|
// Enable the Acorn ADFS if a mass-storage device is attached;
|
||||||
// unlike the Pres ADFS it retains SCSI logic.
|
// unlike the Pres ADFS it retains SCSI logic.
|
||||||
if(!media.mass_storage_devices.empty()) {
|
if(!media.mass_storage_devices.empty()) {
|
||||||
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
target8bit->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||||
target->has_acorn_adfs = true;
|
target8bit->has_acorn_adfs = true;
|
||||||
|
|
||||||
// Assume some sort of later-era Acorn work is likely to happen;
|
// Assume some sort of later-era Acorn work is likely to happen;
|
||||||
// so ensure *TYPE, etc are present.
|
// so ensure *TYPE, etc are present.
|
||||||
target->has_ap6_rom = true;
|
target8bit->has_ap6_rom = true;
|
||||||
target->has_sideways_ram = 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.
|
// 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]) {
|
if(sector[0xfd]) {
|
||||||
target->should_shift_restart = true;
|
target8bit->should_shift_restart = true;
|
||||||
} else {
|
} else {
|
||||||
target->loading_command = "*CAT\n";
|
target8bit->loading_command = "*CAT\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TargetList targets;
|
TargetList targets;
|
||||||
if(!target->media.empty()) {
|
if(!target8bit->media.empty()) {
|
||||||
targets.push_back(std::move(target));
|
targets.push_back(std::move(target8bit));
|
||||||
|
}
|
||||||
|
if(!targetArchimedes->media.empty()) {
|
||||||
|
targets.push_back(std::move(targetArchimedes));
|
||||||
}
|
}
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,7 @@ std::unique_ptr<Machine::DynamicMachine> Machine::MachineForTarget(const Analyse
|
|||||||
|
|
||||||
std::unique_ptr<Machine::DynamicMachine> machine;
|
std::unique_ptr<Machine::DynamicMachine> machine;
|
||||||
try {
|
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 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)
|
#define Bind(m) BindD(m, m)
|
||||||
switch(target->machine) {
|
switch(target->machine) {
|
||||||
@ -133,6 +134,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
|
|||||||
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
|
case Analyser::Machine::AmstradCPC: return "AmstradCPC";
|
||||||
case Analyser::Machine::AppleII: return "AppleII";
|
case Analyser::Machine::AppleII: return "AppleII";
|
||||||
case Analyser::Machine::AppleIIgs: return "AppleIIgs";
|
case Analyser::Machine::AppleIIgs: return "AppleIIgs";
|
||||||
|
case Analyser::Machine::Archimedes: return "Archimedes";
|
||||||
case Analyser::Machine::Atari2600: return "Atari2600";
|
case Analyser::Machine::Atari2600: return "Atari2600";
|
||||||
case Analyser::Machine::AtariST: return "AtariST";
|
case Analyser::Machine::AtariST: return "AtariST";
|
||||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
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::AmstradCPC: return "Amstrad CPC";
|
||||||
case Analyser::Machine::AppleII: return "Apple II";
|
case Analyser::Machine::AppleII: return "Apple II";
|
||||||
case Analyser::Machine::AppleIIgs: return "Apple IIgs";
|
case Analyser::Machine::AppleIIgs: return "Apple IIgs";
|
||||||
|
case Analyser::Machine::Archimedes: return "Acorn Archimedes";
|
||||||
case Analyser::Machine::Atari2600: return "Atari 2600";
|
case Analyser::Machine::Atari2600: return "Atari 2600";
|
||||||
case Analyser::Machine::AtariST: return "Atari ST";
|
case Analyser::Machine::AtariST: return "Atari ST";
|
||||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||||
@ -191,6 +194,7 @@ std::vector<std::string> Machine::AllMachines(Type type, bool long_names) {
|
|||||||
AddName(AmstradCPC);
|
AddName(AmstradCPC);
|
||||||
AddName(AppleII);
|
AddName(AppleII);
|
||||||
AddName(AppleIIgs);
|
AddName(AppleIIgs);
|
||||||
|
AddName(Archimedes);
|
||||||
AddName(AtariST);
|
AddName(AtariST);
|
||||||
AddName(Electron);
|
AddName(Electron);
|
||||||
AddName(Enterprise);
|
AddName(Enterprise);
|
||||||
@ -245,6 +249,7 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
|
|||||||
Add(AmstradCPC);
|
Add(AmstradCPC);
|
||||||
Add(AppleII);
|
Add(AppleII);
|
||||||
Add(AppleIIgs);
|
Add(AppleIIgs);
|
||||||
|
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Archimedes), new Analyser::Static::Target(Analyser::Machine::Archimedes)));
|
||||||
Add(AtariST);
|
Add(AtariST);
|
||||||
AddMapped(Electron, Acorn);
|
AddMapped(Electron, Acorn);
|
||||||
Add(Enterprise);
|
Add(Enterprise);
|
||||||
|
@ -144,13 +144,21 @@ struct ActivityObserver: public Activity::Observer {
|
|||||||
ROM::Request missing_roms;
|
ROM::Request missing_roms;
|
||||||
_machine = Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error);
|
_machine = Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error);
|
||||||
if(!_machine) {
|
if(!_machine) {
|
||||||
const std::wstring description = missing_roms.description(0, L'•');
|
switch(error) {
|
||||||
static_assert(sizeof(wchar_t) == 4, "This code assumes wchar_t is UTF32");
|
case Machine::Error::MissingROM: {
|
||||||
NSString *nativeString = [[NSString alloc]
|
const std::wstring description = missing_roms.description(0, L'•');
|
||||||
initWithBytes:description.data()
|
static_assert(sizeof(wchar_t) == 4, "This code assumes wchar_t is UTF32");
|
||||||
length:description.size()*sizeof(wchar_t)
|
NSString *nativeString = [[NSString alloc]
|
||||||
encoding:NSUTF32LittleEndianStringEncoding];
|
initWithBytes:description.data()
|
||||||
[missingROMs appendString:nativeString];
|
length:description.size()*sizeof(wchar_t)
|
||||||
|
encoding:NSUTF32LittleEndianStringEncoding];
|
||||||
|
[missingROMs appendString:nativeString];
|
||||||
|
} break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
NSLog(@"Unhandled machine creation error %d", error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
updater.performer.machine = _machine.get();
|
updater.performer.machine = _machine.get();
|
||||||
|
Loading…
Reference in New Issue
Block a user