mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Merge branch 'master' into NIBSlipBits
This commit is contained in:
commit
0dfaca2a20
@ -20,10 +20,17 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
|||||||
CGA);
|
CGA);
|
||||||
VideoAdaptor adaptor = VideoAdaptor::CGA;
|
VideoAdaptor adaptor = VideoAdaptor::CGA;
|
||||||
|
|
||||||
|
ReflectableEnum(Speed,
|
||||||
|
ApproximatelyOriginal,
|
||||||
|
Fast);
|
||||||
|
Speed speed = Speed::Fast;
|
||||||
|
|
||||||
Target() : Analyser::Static::Target(Machine::PCCompatible) {
|
Target() : Analyser::Static::Target(Machine::PCCompatible) {
|
||||||
if(needs_declare()) {
|
if(needs_declare()) {
|
||||||
DeclareField(adaptor);
|
|
||||||
AnnounceEnum(VideoAdaptor);
|
AnnounceEnum(VideoAdaptor);
|
||||||
|
AnnounceEnum(Speed);
|
||||||
|
DeclareField(adaptor);
|
||||||
|
DeclareField(speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
|
||||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||||
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/IPF.hpp"
|
||||||
|
#include "../../Storage/Disk/DiskImage/Formats/IMD.hpp"
|
||||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||||
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
|
||||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||||
@ -182,6 +183,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
|||||||
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||||
Format("ima", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::PCCompatible) // IMG (MS-DOS style)
|
Format("ima", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::PCCompatible) // IMG (MS-DOS style)
|
||||||
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||||
|
Format("imd", result.disks, Disk::DiskImageHolder<Storage::Disk::IMD>, TargetPlatform::PCCompatible) // IMD
|
||||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||||
|
|
||||||
// Treat PC booter as a potential backup only if this doesn't parse as a FAT12.
|
// Treat PC booter as a potential backup only if this doesn't parse as a FAT12.
|
||||||
|
@ -47,12 +47,12 @@ enum class Status1: uint8_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
enum class Status2: uint8_t {
|
enum class Status2: uint8_t {
|
||||||
DeletedControlMark = 0x40,
|
DeletedControlMark = 0x40,
|
||||||
DataCRCError = 0x20,
|
DataCRCError = 0x20,
|
||||||
WrongCyinder = 0x10,
|
WrongCyinder = 0x10,
|
||||||
ScanEqualHit = 0x08,
|
ScanEqualHit = 0x08,
|
||||||
ScanNotSatisfied = 0x04,
|
ScanNotSatisfied = 0x04,
|
||||||
BadCylinder = 0x02,
|
BadCylinder = 0x02,
|
||||||
MissingDataAddressMark = 0x01,
|
MissingDataAddressMark = 0x01,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,41 +15,6 @@
|
|||||||
|
|
||||||
namespace PCCompatible {
|
namespace PCCompatible {
|
||||||
|
|
||||||
static constexpr uint8_t DarkCyan = 0b00'10'10;
|
|
||||||
static constexpr uint8_t DarkMagenta = 0b10'00'10;
|
|
||||||
static constexpr uint8_t DarkGrey = 0b10'10'10;
|
|
||||||
|
|
||||||
static constexpr uint8_t DarkGreen = 0b00'10'00;
|
|
||||||
static constexpr uint8_t DarkRed = 0b10'00'00;
|
|
||||||
static constexpr uint8_t DarkYellow = 0b10'10'00;
|
|
||||||
|
|
||||||
static constexpr uint8_t Brown = 0b10'01'00;
|
|
||||||
|
|
||||||
/// @returns @c Brown if @c source is @c DarkYellow; @c source otherwise.
|
|
||||||
constexpr uint8_t yellow_to_brown(uint8_t source) {
|
|
||||||
return (source == DarkYellow) ? Brown : source;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @returns The brightened (i.e. high intensity) version of @c source.
|
|
||||||
constexpr uint8_t bright(uint8_t source) {
|
|
||||||
return source | (source >> 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps the RGB TTL triplet @c source to an appropriate output colour.
|
|
||||||
constexpr uint8_t rgb(uint8_t source) {
|
|
||||||
return uint8_t(
|
|
||||||
((source & 0x01) << 1) |
|
|
||||||
((source & 0x02) << 2) |
|
|
||||||
((source & 0x04) << 3)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maps the RGBI value in @c source to an appropriate output colour, including potential yellow-to-brown conversion.
|
|
||||||
constexpr uint8_t rgbi(uint8_t source) {
|
|
||||||
const uint8_t result = rgb(source);
|
|
||||||
return (source & 0x10) ? bright(result) : yellow_to_brown(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
class CGA {
|
class CGA {
|
||||||
public:
|
public:
|
||||||
CGA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
|
CGA() : crtc_(Motorola::CRTC::Personality::HD6845S, outputter_) {}
|
||||||
@ -102,7 +67,7 @@ class CGA {
|
|||||||
// b1: 1 => positive edge from light pen has set trigger;
|
// b1: 1 => positive edge from light pen has set trigger;
|
||||||
// b0: 1 => safe to write to VRAM now without causing snow.
|
// b0: 1 => safe to write to VRAM now without causing snow.
|
||||||
(crtc_.get_bus_state().vsync ? 0b1001 : 0b0000) |
|
(crtc_.get_bus_state().vsync ? 0b1001 : 0b0000) |
|
||||||
(crtc_.get_bus_state().hsync ? 0b0001 : 0b0000) |
|
(crtc_.get_bus_state().display_enable ? 0b0000 : 0b0001) |
|
||||||
0b0100;
|
0b0100;
|
||||||
|
|
||||||
default: return 0xff;
|
default: return 0xff;
|
||||||
@ -113,6 +78,7 @@ class CGA {
|
|||||||
|
|
||||||
void set_display_type(Outputs::Display::DisplayType display_type) {
|
void set_display_type(Outputs::Display::DisplayType display_type) {
|
||||||
outputter_.crt.set_display_type(display_type);
|
outputter_.crt.set_display_type(display_type);
|
||||||
|
outputter_.set_is_composite(Outputs::Display::is_composite(display_type));
|
||||||
}
|
}
|
||||||
Outputs::Display::DisplayType get_display_type() const {
|
Outputs::Display::DisplayType get_display_type() const {
|
||||||
return outputter_.crt.get_display_type();
|
return outputter_.crt.get_display_type();
|
||||||
@ -165,6 +131,11 @@ class CGA {
|
|||||||
update_palette();
|
update_palette();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_is_composite(bool is_composite) {
|
||||||
|
is_composite_ = is_composite;
|
||||||
|
update_palette();
|
||||||
|
}
|
||||||
|
|
||||||
void set_colours(uint8_t value) {
|
void set_colours(uint8_t value) {
|
||||||
colours_ = value;
|
colours_ = value;
|
||||||
update_palette();
|
update_palette();
|
||||||
@ -207,6 +178,7 @@ class CGA {
|
|||||||
// Determine new output state.
|
// Determine new output state.
|
||||||
update_hsync(state.hsync);
|
update_hsync(state.hsync);
|
||||||
const OutputState new_state = implied_state(state);
|
const OutputState new_state = implied_state(state);
|
||||||
|
static constexpr uint8_t colour_phase = 200;
|
||||||
|
|
||||||
// Upon either a state change or just having accumulated too much local time...
|
// Upon either a state change or just having accumulated too much local time...
|
||||||
if(
|
if(
|
||||||
@ -219,7 +191,7 @@ class CGA {
|
|||||||
// (1) flush preexisting state.
|
// (1) flush preexisting state.
|
||||||
if(count) {
|
if(count) {
|
||||||
switch(output_state) {
|
switch(output_state) {
|
||||||
case OutputState::Sync: crt.output_sync(count * active_clock_divider); break;
|
case OutputState::Sync: crt.output_sync(count * active_clock_divider); break;
|
||||||
case OutputState::Border:
|
case OutputState::Border:
|
||||||
if(active_border_colour) {
|
if(active_border_colour) {
|
||||||
crt.output_blank(count * active_clock_divider);
|
crt.output_blank(count * active_clock_divider);
|
||||||
@ -227,8 +199,8 @@ class CGA {
|
|||||||
crt.output_level<uint8_t>(count * active_clock_divider, active_border_colour);
|
crt.output_level<uint8_t>(count * active_clock_divider, active_border_colour);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, 0); break;
|
case OutputState::ColourBurst: crt.output_colour_burst(count * active_clock_divider, colour_phase); break;
|
||||||
case OutputState::Pixels: flush_pixels(); break;
|
case OutputState::Pixels: flush_pixels(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,18 +307,21 @@ class CGA {
|
|||||||
|
|
||||||
// Apply blink or background intensity.
|
// Apply blink or background intensity.
|
||||||
if(control_ & 0x20) {
|
if(control_ & 0x20) {
|
||||||
|
// Set both colours to black if within a blink; otherwise consider a yellow-to-brown conversion.
|
||||||
if((attributes & 0x80) && (state.field_count & 16)) {
|
if((attributes & 0x80) && (state.field_count & 16)) {
|
||||||
std::swap(colours[0], colours[1]);
|
colours[0] = colours[1] = 0;
|
||||||
|
} else {
|
||||||
|
colours[0] = yellow_to_brown(colours[0]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(attributes & 0x80) {
|
if(attributes & 0x80) {
|
||||||
colours[0] = bright(colours[0]);
|
colours[0] = bright(colours[0]);
|
||||||
|
} else {
|
||||||
|
// Yellow to brown definitely doesn't apply if the colour has been brightened.
|
||||||
|
colours[0] = yellow_to_brown(colours[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Potentially remap dark yellow to brown.
|
|
||||||
colours[0] = yellow_to_brown(colours[0]);
|
|
||||||
|
|
||||||
// Draw according to ROM contents.
|
// Draw according to ROM contents.
|
||||||
pixel_pointer[0] = (row & 0x80) ? colours[1] : colours[0];
|
pixel_pointer[0] = (row & 0x80) ? colours[1] : colours[0];
|
||||||
pixel_pointer[1] = (row & 0x40) ? colours[1] : colours[0];
|
pixel_pointer[1] = (row & 0x40) ? colours[1] : colours[0];
|
||||||
@ -384,6 +359,7 @@ class CGA {
|
|||||||
int pixels_per_tick = 8;
|
int pixels_per_tick = 8;
|
||||||
uint8_t colours_ = 0;
|
uint8_t colours_ = 0;
|
||||||
uint8_t control_ = 0;
|
uint8_t control_ = 0;
|
||||||
|
bool is_composite_ = false;
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
Pixels640, Pixels320, Text,
|
Pixels640, Pixels320, Text,
|
||||||
} mode_ = Mode::Text;
|
} mode_ = Mode::Text;
|
||||||
@ -425,10 +401,48 @@ class CGA {
|
|||||||
border_colour = (mode_ != Mode::Pixels640) ? palette320[0] : 0;
|
border_colour = (mode_ != Mode::Pixels640) ? palette320[0] : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Named colours and mapping logic.
|
||||||
|
//
|
||||||
|
static constexpr uint8_t DarkCyan = 0b00'10'10;
|
||||||
|
static constexpr uint8_t DarkMagenta = 0b10'00'10;
|
||||||
|
static constexpr uint8_t DarkGrey = 0b10'10'10;
|
||||||
|
|
||||||
|
static constexpr uint8_t DarkGreen = 0b00'10'00;
|
||||||
|
static constexpr uint8_t DarkRed = 0b10'00'00;
|
||||||
|
static constexpr uint8_t DarkYellow = 0b10'10'00;
|
||||||
|
|
||||||
|
static constexpr uint8_t Brown = 0b10'01'00;
|
||||||
|
|
||||||
|
/// @returns @c Brown if @c source is @c DarkYellow and composite output is not enabled; @c source otherwise.
|
||||||
|
constexpr uint8_t yellow_to_brown(uint8_t source) {
|
||||||
|
return (source == DarkYellow && !is_composite_) ? Brown : source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @returns The brightened (i.e. high intensity) version of @c source.
|
||||||
|
constexpr uint8_t bright(uint8_t source) {
|
||||||
|
return source | (source >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps the RGB TTL triplet @c source to an appropriate output colour.
|
||||||
|
constexpr uint8_t rgb(uint8_t source) {
|
||||||
|
return uint8_t(
|
||||||
|
((source & 0x01) << 1) |
|
||||||
|
((source & 0x02) << 2) |
|
||||||
|
((source & 0x04) << 3)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps the RGBI value in @c source to an appropriate output colour, including potential yellow-to-brown conversion.
|
||||||
|
constexpr uint8_t rgbi(uint8_t source) {
|
||||||
|
const uint8_t result = rgb(source);
|
||||||
|
return (source & 0x10) ? bright(result) : yellow_to_brown(result);
|
||||||
|
}
|
||||||
} outputter_;
|
} outputter_;
|
||||||
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
|
Motorola::CRTC::CRTC6845<CRTCOutputter, Motorola::CRTC::CursorType::MDA> crtc_;
|
||||||
|
|
||||||
int full_clock_ = 0;
|
int full_clock_ = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ class i8237 {
|
|||||||
|
|
||||||
template <int address>
|
template <int address>
|
||||||
void write(uint8_t value) {
|
void write(uint8_t value) {
|
||||||
printf("DMA: Write %02x to %d\n", value, address);
|
// printf("DMA: Write %02x to %d\n", value, address);
|
||||||
|
|
||||||
switch(address) {
|
switch(address) {
|
||||||
default: {
|
default: {
|
||||||
@ -66,7 +66,7 @@ class i8237 {
|
|||||||
|
|
||||||
template <int address>
|
template <int address>
|
||||||
uint8_t read() {
|
uint8_t read() {
|
||||||
printf("DMA: Read %d\n", address);
|
// printf("DMA: Read %d\n", address);
|
||||||
switch(address) {
|
switch(address) {
|
||||||
default: {
|
default: {
|
||||||
constexpr int channel = (address >> 1) & 3;
|
constexpr int channel = (address >> 1) & 3;
|
||||||
@ -146,7 +146,7 @@ class i8237 {
|
|||||||
channel.transfer_complete = false;
|
channel.transfer_complete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("DMA: status is %02x\n", result);
|
// printf("DMA: status is %02x\n", result);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -157,19 +157,19 @@ class i8237 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void flip_flop_reset() {
|
void flip_flop_reset() {
|
||||||
printf("DMA: Flip flop reset\n");
|
// printf("DMA: Flip flop reset\n");
|
||||||
next_access_low_ = true;
|
next_access_low_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mask_reset() {
|
void mask_reset() {
|
||||||
printf("DMA: Mask reset\n");
|
// printf("DMA: Mask reset\n");
|
||||||
for(auto &channel : channels_) {
|
for(auto &channel : channels_) {
|
||||||
channel.mask = false;
|
channel.mask = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void master_reset() {
|
void master_reset() {
|
||||||
printf("DMA: Master reset\n");
|
// printf("DMA: Master reset\n");
|
||||||
flip_flop_reset();
|
flip_flop_reset();
|
||||||
for(auto &channel : channels_) {
|
for(auto &channel : channels_) {
|
||||||
channel.mask = true;
|
channel.mask = true;
|
||||||
@ -183,17 +183,17 @@ class i8237 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void set_reset_mask(uint8_t value) {
|
void set_reset_mask(uint8_t value) {
|
||||||
printf("DMA: Set/reset mask %02x\n", value);
|
// printf("DMA: Set/reset mask %02x\n", value);
|
||||||
channels_[value & 3].mask = value & 4;
|
channels_[value & 3].mask = value & 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_reset_request(uint8_t value) {
|
void set_reset_request(uint8_t value) {
|
||||||
printf("DMA: Set/reset request %02x\n", value);
|
// printf("DMA: Set/reset request %02x\n", value);
|
||||||
channels_[value & 3].request = value & 4;
|
channels_[value & 3].request = value & 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_mask(uint8_t value) {
|
void set_mask(uint8_t value) {
|
||||||
printf("DMA: Set mask %02x\n", value);
|
// printf("DMA: Set mask %02x\n", value);
|
||||||
channels_[0].mask = value & 1;
|
channels_[0].mask = value & 1;
|
||||||
channels_[1].mask = value & 2;
|
channels_[1].mask = value & 2;
|
||||||
channels_[2].mask = value & 4;
|
channels_[2].mask = value & 4;
|
||||||
@ -201,7 +201,7 @@ class i8237 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void set_mode(uint8_t value) {
|
void set_mode(uint8_t value) {
|
||||||
printf("DMA: Set mode %02x\n", value);
|
// printf("DMA: Set mode %02x\n", value);
|
||||||
channels_[value & 3].transfer = Channel::Transfer((value >> 2) & 3);
|
channels_[value & 3].transfer = Channel::Transfer((value >> 2) & 3);
|
||||||
channels_[value & 3].autoinitialise = value & 0x10;
|
channels_[value & 3].autoinitialise = value & 0x10;
|
||||||
channels_[value & 3].address_decrement = value & 0x20;
|
channels_[value & 3].address_decrement = value & 0x20;
|
||||||
@ -209,7 +209,7 @@ class i8237 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void set_command(uint8_t value) {
|
void set_command(uint8_t value) {
|
||||||
printf("DMA: Set command %02x\n", value);
|
// printf("DMA: Set command %02x\n", value);
|
||||||
enable_memory_to_memory_ = value & 0x01;
|
enable_memory_to_memory_ = value & 0x01;
|
||||||
enable_channel0_address_hold_ = value & 0x02;
|
enable_channel0_address_hold_ = value & 0x02;
|
||||||
enable_controller_ = value & 0x04;
|
enable_controller_ = value & 0x04;
|
||||||
|
@ -90,7 +90,7 @@ class MDA {
|
|||||||
// TODO: really this should be a Luminance8 and set an appropriate modal tint colour;
|
// TODO: really this should be a Luminance8 and set an appropriate modal tint colour;
|
||||||
// consider whether that's worth building into the scan target.
|
// consider whether that's worth building into the scan target.
|
||||||
{
|
{
|
||||||
// crt.set_visible_area(Outputs::Display::Rect(0.1072f, 0.1f, 0.842105263157895f, 0.842105263157895f));
|
crt.set_visible_area(Outputs::Display::Rect(0.028f, 0.025f, 0.98f, 0.98f));
|
||||||
crt.set_display_type(Outputs::Display::DisplayType::RGB);
|
crt.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,14 +113,36 @@ class FloppyController {
|
|||||||
printf("TODO: implement FDC command %d\n", uint8_t(decoder_.command()));
|
printf("TODO: implement FDC command %d\n", uint8_t(decoder_.command()));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Command::ReadData: {
|
case Command::WriteDeletedData:
|
||||||
printf("FDC: Read from drive %d / head %d / track %d of head %d / track %d / sector %d\n",
|
case Command::WriteData: {
|
||||||
decoder_.target().drive,
|
status_.begin(decoder_);
|
||||||
decoder_.target().head,
|
|
||||||
drives_[decoder_.target().drive].track,
|
// Just decline to write, for now.
|
||||||
decoder_.geometry().head,
|
status_.set(Intel::i8272::Status1::NotWriteable);
|
||||||
|
status_.set(Intel::i8272::Status0::BecameNotReady);
|
||||||
|
|
||||||
|
results_.serialise(
|
||||||
|
status_,
|
||||||
decoder_.geometry().cylinder,
|
decoder_.geometry().cylinder,
|
||||||
decoder_.geometry().sector);
|
decoder_.geometry().head,
|
||||||
|
decoder_.geometry().sector,
|
||||||
|
decoder_.geometry().size);
|
||||||
|
|
||||||
|
// TODO: what if head has changed?
|
||||||
|
drives_[decoder_.target().drive].status = decoder_.drive_head();
|
||||||
|
drives_[decoder_.target().drive].raised_interrupt = true;
|
||||||
|
pic_.apply_edge<6>(true);
|
||||||
|
} break;
|
||||||
|
|
||||||
|
case Command::ReadDeletedData:
|
||||||
|
case Command::ReadData: {
|
||||||
|
// printf("FDC: Read from drive %d / head %d / track %d of head %d / track %d / sector %d\n",
|
||||||
|
// decoder_.target().drive,
|
||||||
|
// decoder_.target().head,
|
||||||
|
// drives_[decoder_.target().drive].track,
|
||||||
|
// decoder_.geometry().head,
|
||||||
|
// decoder_.geometry().cylinder,
|
||||||
|
// decoder_.geometry().sector);
|
||||||
|
|
||||||
status_.begin(decoder_);
|
status_.begin(decoder_);
|
||||||
|
|
||||||
@ -131,11 +153,13 @@ class FloppyController {
|
|||||||
bool found_sector = false;
|
bool found_sector = false;
|
||||||
|
|
||||||
for(auto &pair: drives_[decoder_.target().drive].sectors(decoder_.target().head)) {
|
for(auto &pair: drives_[decoder_.target().drive].sectors(decoder_.target().head)) {
|
||||||
|
// TODO: I suspect that not all these fields are tested for equality.
|
||||||
if(
|
if(
|
||||||
(pair.second.address.track == target.cylinder) &&
|
(pair.second.address.track == target.cylinder) &&
|
||||||
(pair.second.address.sector == target.sector) &&
|
(pair.second.address.sector == target.sector) &&
|
||||||
(pair.second.address.side == target.head) &&
|
(pair.second.address.side == target.head) &&
|
||||||
(pair.second.size == target.size)
|
(pair.second.size == target.size) &&
|
||||||
|
(pair.second.is_deleted == (decoder_.command() == Command::ReadDeletedData))
|
||||||
) {
|
) {
|
||||||
found_sector = true;
|
found_sector = true;
|
||||||
bool wrote_in_full = true;
|
bool wrote_in_full = true;
|
||||||
@ -280,7 +304,7 @@ class FloppyController {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
void reset() {
|
void reset() {
|
||||||
printf("FDC reset\n");
|
// printf("FDC reset\n");
|
||||||
decoder_.clear();
|
decoder_.clear();
|
||||||
status_.reset();
|
status_.reset();
|
||||||
|
|
||||||
@ -884,7 +908,7 @@ class FlowController {
|
|||||||
bool halted_ = false;
|
bool halted_ = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <VideoAdaptor video>
|
template <VideoAdaptor video, bool turbo>
|
||||||
class ConcreteMachine:
|
class ConcreteMachine:
|
||||||
public Machine,
|
public Machine,
|
||||||
public MachineTypes::TimedMachine,
|
public MachineTypes::TimedMachine,
|
||||||
@ -951,9 +975,15 @@ class ConcreteMachine:
|
|||||||
// MARK: - TimedMachine.
|
// MARK: - TimedMachine.
|
||||||
void run_for(const Cycles duration) override {
|
void run_for(const Cycles duration) override {
|
||||||
const auto pit_ticks = duration.as_integral();
|
const auto pit_ticks = duration.as_integral();
|
||||||
cpu_divisor_ += pit_ticks;
|
|
||||||
int ticks = cpu_divisor_ / 3;
|
int ticks;
|
||||||
cpu_divisor_ %= 3;
|
if constexpr (!turbo) {
|
||||||
|
cpu_divisor_ += pit_ticks;
|
||||||
|
ticks = cpu_divisor_ / 3;
|
||||||
|
cpu_divisor_ %= 3;
|
||||||
|
} else {
|
||||||
|
ticks = pit_ticks;
|
||||||
|
}
|
||||||
|
|
||||||
while(ticks--) {
|
while(ticks--) {
|
||||||
//
|
//
|
||||||
@ -965,23 +995,30 @@ class ConcreteMachine:
|
|||||||
//
|
//
|
||||||
pit_.run_for(1);
|
pit_.run_for(1);
|
||||||
++speaker_.cycles_since_update;
|
++speaker_.cycles_since_update;
|
||||||
pit_.run_for(1);
|
|
||||||
++speaker_.cycles_since_update;
|
if constexpr (!turbo) {
|
||||||
pit_.run_for(1);
|
pit_.run_for(1);
|
||||||
++speaker_.cycles_since_update;
|
++speaker_.cycles_since_update;
|
||||||
|
pit_.run_for(1);
|
||||||
|
++speaker_.cycles_since_update;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Advance CRTC at a more approximate rate.
|
// Advance CRTC at a more approximate rate.
|
||||||
//
|
//
|
||||||
video_.run_for(Cycles(3));
|
video_.run_for(turbo ? Cycles(1) : Cycles(3));
|
||||||
|
|
||||||
|
//
|
||||||
|
// Give the keyboard a notification of passing time; it's very approximately clocked,
|
||||||
|
// really just including 'some' delays to avoid being instant.
|
||||||
|
//
|
||||||
|
keyboard_.run_for(Cycles(1));
|
||||||
|
|
||||||
//
|
//
|
||||||
// Perform one CPU instruction every three PIT cycles.
|
// Perform one CPU instruction every three PIT cycles.
|
||||||
// i.e. CPU instruction rate is 1/3 * ~1.19Mhz ~= 0.4 MIPS.
|
// i.e. CPU instruction rate is 1/3 * ~1.19Mhz ~= 0.4 MIPS.
|
||||||
//
|
//
|
||||||
|
|
||||||
keyboard_.run_for(Cycles(1));
|
|
||||||
|
|
||||||
// Query for interrupts and apply if pending.
|
// Query for interrupts and apply if pending.
|
||||||
if(pic_.pending() && context.flags.template flag<InstructionSet::x86::Flag::Interrupt>()) {
|
if(pic_.pending() && context.flags.template flag<InstructionSet::x86::Flag::Interrupt>()) {
|
||||||
// Regress the IP if a REP is in-progress so as to resume it later.
|
// Regress the IP if a REP is in-progress so as to resume it later.
|
||||||
@ -998,58 +1035,77 @@ class ConcreteMachine:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do nothing if halted.
|
// Do nothing if currently halted.
|
||||||
if(context.flow_controller.halted()) {
|
if(context.flow_controller.halted()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next thing to execute.
|
if constexpr (turbo) {
|
||||||
if(!context.flow_controller.should_repeat()) {
|
// There's no divider applied, so this makes for 2*PI = around 2.4 MIPS.
|
||||||
// Decode from the current IP.
|
// That's broadly 80286 speed, if MIPS were a valid measure.
|
||||||
decoded_ip_ = context.registers.ip();
|
perform_instruction();
|
||||||
const auto remainder = context.memory.next_code();
|
perform_instruction();
|
||||||
decoded = decoder.decode(remainder.first, remainder.second);
|
|
||||||
|
|
||||||
// If that didn't yield a whole instruction then the end of memory must have been hit;
|
|
||||||
// continue from the beginning.
|
|
||||||
if(decoded.first <= 0) {
|
|
||||||
const auto all = context.memory.all();
|
|
||||||
decoded = decoder.decode(all.first, all.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.registers.ip() += decoded.first;
|
|
||||||
} else {
|
} else {
|
||||||
context.flow_controller.begin_instruction();
|
// With the clock divider above, this makes for a net of PIT/3 = around 0.4 MIPS.
|
||||||
|
// i.e. a shade more than 8086 speed, if MIPS were meaningful.
|
||||||
|
perform_instruction();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if(decoded_ip_ >= 0x7c00 && decoded_ip_ < 0x7c00 + 1024) {
|
// Other inevitably broad and fuzzy and inconsistent MIPS counts for my own potential future play:
|
||||||
const auto next = to_string(decoded, InstructionSet::x86::Model::i8086);
|
//
|
||||||
// if(next != previous) {
|
// 80386 @ 20Mhz: 4–5 MIPS.
|
||||||
std::cout << std::hex << decoded_ip_ << " " << next;
|
// 80486 @ 66Mhz: 25 MIPS.
|
||||||
|
// Pentium @ 100Mhz: 188 MIPS.
|
||||||
if(decoded.second.operation() == InstructionSet::x86::Operation::INT) {
|
|
||||||
std::cout << " dl:" << std::hex << +context.registers.dl() << "; ";
|
|
||||||
std::cout << "ah:" << std::hex << +context.registers.ah() << "; ";
|
|
||||||
std::cout << "ch:" << std::hex << +context.registers.ch() << "; ";
|
|
||||||
std::cout << "cl:" << std::hex << +context.registers.cl() << "; ";
|
|
||||||
std::cout << "dh:" << std::hex << +context.registers.dh() << "; ";
|
|
||||||
std::cout << "es:" << std::hex << +context.registers.es() << "; ";
|
|
||||||
std::cout << "bx:" << std::hex << +context.registers.bx();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << std::endl;
|
|
||||||
// previous = next;
|
|
||||||
// }
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Execute it.
|
|
||||||
InstructionSet::x86::perform(
|
|
||||||
decoded.second,
|
|
||||||
context
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void perform_instruction() {
|
||||||
|
// Get the next thing to execute.
|
||||||
|
if(!context.flow_controller.should_repeat()) {
|
||||||
|
// Decode from the current IP.
|
||||||
|
decoded_ip_ = context.registers.ip();
|
||||||
|
const auto remainder = context.memory.next_code();
|
||||||
|
decoded = decoder.decode(remainder.first, remainder.second);
|
||||||
|
|
||||||
|
// If that didn't yield a whole instruction then the end of memory must have been hit;
|
||||||
|
// continue from the beginning.
|
||||||
|
if(decoded.first <= 0) {
|
||||||
|
const auto all = context.memory.all();
|
||||||
|
decoded = decoder.decode(all.first, all.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.registers.ip() += decoded.first;
|
||||||
|
} else {
|
||||||
|
context.flow_controller.begin_instruction();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if(decoded_ip_ >= 0x7c00 && decoded_ip_ < 0x7c00 + 1024) {
|
||||||
|
const auto next = to_string(decoded, InstructionSet::x86::Model::i8086);
|
||||||
|
// if(next != previous) {
|
||||||
|
std::cout << std::hex << decoded_ip_ << " " << next;
|
||||||
|
|
||||||
|
if(decoded.second.operation() == InstructionSet::x86::Operation::INT) {
|
||||||
|
std::cout << " dl:" << std::hex << +context.registers.dl() << "; ";
|
||||||
|
std::cout << "ah:" << std::hex << +context.registers.ah() << "; ";
|
||||||
|
std::cout << "ch:" << std::hex << +context.registers.ch() << "; ";
|
||||||
|
std::cout << "cl:" << std::hex << +context.registers.cl() << "; ";
|
||||||
|
std::cout << "dh:" << std::hex << +context.registers.dh() << "; ";
|
||||||
|
std::cout << "es:" << std::hex << +context.registers.es() << "; ";
|
||||||
|
std::cout << "bx:" << std::hex << +context.registers.bx();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << std::endl;
|
||||||
|
// previous = next;
|
||||||
|
// }
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// Execute it.
|
||||||
|
InstructionSet::x86::perform(
|
||||||
|
decoded.second,
|
||||||
|
context
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - ScanProducer.
|
// MARK: - ScanProducer.
|
||||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||||
video_.set_scan_target(scan_target);
|
video_.set_scan_target(scan_target);
|
||||||
@ -1109,10 +1165,9 @@ class ConcreteMachine:
|
|||||||
|
|
||||||
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
void set_display_type(Outputs::Display::DisplayType display_type) override {
|
||||||
video_.set_display_type(display_type);
|
video_.set_display_type(display_type);
|
||||||
ppi_handler_.hint_is_composite(
|
|
||||||
(display_type == Outputs::Display::DisplayType::CompositeColour) ||
|
// Give the PPI a shout-out in case it isn't too late to switch to CGA40.
|
||||||
(display_type == Outputs::Display::DisplayType::CompositeMonochrome)
|
ppi_handler_.hint_is_composite(Outputs::Display::is_composite(display_type));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Outputs::Display::DisplayType get_display_type() const override {
|
Outputs::Display::DisplayType get_display_type() const override {
|
||||||
@ -1181,10 +1236,18 @@ Machine *Machine::PCCompatible(const Analyser::Static::Target *target, const ROM
|
|||||||
using Target = Analyser::Static::PCCompatible::Target;
|
using Target = Analyser::Static::PCCompatible::Target;
|
||||||
const Target *const pc_target = dynamic_cast<const Target *>(target);
|
const Target *const pc_target = dynamic_cast<const Target *>(target);
|
||||||
|
|
||||||
switch(pc_target->adaptor) {
|
if(pc_target->speed == Target::Speed::Fast) {
|
||||||
case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<VideoAdaptor::MDA>(*pc_target, rom_fetcher);
|
switch(pc_target->adaptor) {
|
||||||
case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<VideoAdaptor::CGA>(*pc_target, rom_fetcher);
|
case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<VideoAdaptor::MDA, true>(*pc_target, rom_fetcher);
|
||||||
default: return nullptr;
|
case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<VideoAdaptor::CGA, true>(*pc_target, rom_fetcher);
|
||||||
|
default: return nullptr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch(pc_target->adaptor) {
|
||||||
|
case VideoAdaptor::MDA: return new PCCompatible::ConcreteMachine<VideoAdaptor::MDA, false>(*pc_target, rom_fetcher);
|
||||||
|
case VideoAdaptor::CGA: return new PCCompatible::ConcreteMachine<VideoAdaptor::CGA, false>(*pc_target, rom_fetcher);
|
||||||
|
default: return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,6 +37,8 @@
|
|||||||
42A5E8532ABBE16F00A0DD5D /* lax_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 42A5E8422ABBE16F00A0DD5D /* lax_test.bin */; };
|
42A5E8532ABBE16F00A0DD5D /* lax_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 42A5E8422ABBE16F00A0DD5D /* lax_test.bin */; };
|
||||||
42A5E8542ABBE16F00A0DD5D /* branch_backwards_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 42A5E8432ABBE16F00A0DD5D /* branch_backwards_test.bin */; };
|
42A5E8542ABBE16F00A0DD5D /* branch_backwards_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 42A5E8432ABBE16F00A0DD5D /* branch_backwards_test.bin */; };
|
||||||
42E5C3932AC46A7700DA093D /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42E5C3922AC46A7700DA093D /* Carbon.framework */; };
|
42E5C3932AC46A7700DA093D /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 42E5C3922AC46A7700DA093D /* Carbon.framework */; };
|
||||||
|
42EB81282B23AAC300429AF4 /* IMD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB81262B23AAC300429AF4 /* IMD.cpp */; };
|
||||||
|
42EB81292B23AAC300429AF4 /* IMD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB81262B23AAC300429AF4 /* IMD.cpp */; };
|
||||||
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; };
|
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; };
|
||||||
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
|
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
|
||||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
||||||
@ -1195,6 +1197,8 @@
|
|||||||
42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
|
42AD55312A0C4D5000ACE410 /* 68000Implementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 68000Implementation.hpp; sourceTree = "<group>"; };
|
||||||
42E5C3922AC46A7700DA093D /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
|
42E5C3922AC46A7700DA093D /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
|
||||||
42EB81252B21788200429AF4 /* RTC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RTC.hpp; sourceTree = "<group>"; };
|
42EB81252B21788200429AF4 /* RTC.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RTC.hpp; sourceTree = "<group>"; };
|
||||||
|
42EB81262B23AAC300429AF4 /* IMD.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IMD.cpp; sourceTree = "<group>"; };
|
||||||
|
42EB81272B23AAC300429AF4 /* IMD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IMD.hpp; sourceTree = "<group>"; };
|
||||||
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; };
|
4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; name = 65C02_extended_opcodes_test.bin; path = "Klaus Dormann/65C02_extended_opcodes_test.bin"; sourceTree = "<group>"; };
|
||||||
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
|
4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MemptrTests.swift; sourceTree = "<group>"; };
|
||||||
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
|
4B0333AD2094081A0050B93D /* AppleDSK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AppleDSK.cpp; sourceTree = "<group>"; };
|
||||||
@ -3064,6 +3068,7 @@
|
|||||||
4BEBFB4B2002C4BF000708CC /* FAT12.cpp */,
|
4BEBFB4B2002C4BF000708CC /* FAT12.cpp */,
|
||||||
4B4518931F75FD1B00926311 /* G64.cpp */,
|
4B4518931F75FD1B00926311 /* G64.cpp */,
|
||||||
4B4518951F75FD1B00926311 /* HFE.cpp */,
|
4B4518951F75FD1B00926311 /* HFE.cpp */,
|
||||||
|
42EB81262B23AAC300429AF4 /* IMD.cpp */,
|
||||||
4B5B372F2777C7FC0047F238 /* IPF.cpp */,
|
4B5B372F2777C7FC0047F238 /* IPF.cpp */,
|
||||||
4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */,
|
4BB4BFAE22A42F290069048D /* MacintoshIMG.cpp */,
|
||||||
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */,
|
4B58601C1F806AB200AEE2E3 /* MFMSectorDump.cpp */,
|
||||||
@ -3084,6 +3089,7 @@
|
|||||||
4BEBFB4C2002C4BF000708CC /* FAT12.hpp */,
|
4BEBFB4C2002C4BF000708CC /* FAT12.hpp */,
|
||||||
4B4518941F75FD1B00926311 /* G64.hpp */,
|
4B4518941F75FD1B00926311 /* G64.hpp */,
|
||||||
4B4518961F75FD1B00926311 /* HFE.hpp */,
|
4B4518961F75FD1B00926311 /* HFE.hpp */,
|
||||||
|
42EB81272B23AAC300429AF4 /* IMD.hpp */,
|
||||||
4B5B37302777C7FC0047F238 /* IPF.hpp */,
|
4B5B37302777C7FC0047F238 /* IPF.hpp */,
|
||||||
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */,
|
4BB4BFAF22A42F290069048D /* MacintoshIMG.hpp */,
|
||||||
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */,
|
4B58601D1F806AB200AEE2E3 /* MFMSectorDump.hpp */,
|
||||||
@ -5959,6 +5965,7 @@
|
|||||||
4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */,
|
4B051C912669C90B00CA44E8 /* ROMCatalogue.cpp in Sources */,
|
||||||
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
|
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
|
||||||
4B302185208A550100773308 /* DiskII.cpp in Sources */,
|
4B302185208A550100773308 /* DiskII.cpp in Sources */,
|
||||||
|
42EB81292B23AAC300429AF4 /* IMD.cpp in Sources */,
|
||||||
4B051CB1267C1CA200CA44E8 /* Keyboard.cpp in Sources */,
|
4B051CB1267C1CA200CA44E8 /* Keyboard.cpp in Sources */,
|
||||||
4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */,
|
4B0F1BB32602645900B85C66 /* StaticAnalyser.cpp in Sources */,
|
||||||
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
|
4B055A931FAE85B50060FFFF /* BinaryDump.cpp in Sources */,
|
||||||
@ -6057,6 +6064,7 @@
|
|||||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
|
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
|
||||||
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
|
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
|
||||||
4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */,
|
4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */,
|
||||||
|
42EB81282B23AAC300429AF4 /* IMD.cpp in Sources */,
|
||||||
4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */,
|
4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */,
|
||||||
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||||
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
||||||
|
@ -691,6 +691,26 @@
|
|||||||
<key>NSDocumentClass</key>
|
<key>NSDocumentClass</key>
|
||||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>imd</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>IMD disk image</string>
|
||||||
|
<key>CFBundleTypeOSTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>????</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Owner</string>
|
||||||
|
<key>LSTypeIsPackage</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSDocumentClass</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||||
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleTypeExtensions</key>
|
<key>CFBundleTypeExtensions</key>
|
||||||
<array>
|
<array>
|
||||||
|
@ -123,8 +123,9 @@ typedef NS_ENUM(NSInteger, CSMachineMSXRegion) {
|
|||||||
CSMachineMSXRegionJapanese,
|
CSMachineMSXRegionJapanese,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, CSPCCompatibleModel) {
|
typedef NS_ENUM(NSInteger, CSPCCompatibleSpeed) {
|
||||||
CSPCCompatibleModelTurboXT,
|
CSPCCompatibleSpeedOriginal,
|
||||||
|
CSPCCompatibleSpeedTurbo,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef NS_ENUM(NSInteger, CSPCCompatibleVideoAdaptor) {
|
typedef NS_ENUM(NSInteger, CSPCCompatibleVideoAdaptor) {
|
||||||
@ -152,7 +153,7 @@ typedef int Kilobytes;
|
|||||||
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540;
|
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540;
|
||||||
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
|
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
|
||||||
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
|
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
|
||||||
- (instancetype)initWithPCCompatibleModel:(CSPCCompatibleModel)model videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor;
|
- (instancetype)initWithPCCompatibleSpeed:(CSPCCompatibleSpeed)speed videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor;
|
||||||
|
|
||||||
@property(nonatomic, readonly, nullable) NSString *optionsNibName;
|
@property(nonatomic, readonly, nullable) NSString *optionsNibName;
|
||||||
@property(nonatomic, readonly) NSString *displayName;
|
@property(nonatomic, readonly) NSString *displayName;
|
||||||
|
@ -273,7 +273,7 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (instancetype)initWithPCCompatibleModel:(CSPCCompatibleModel)model videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor {
|
- (instancetype)initWithPCCompatibleSpeed:(CSPCCompatibleSpeed)speed videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if(self) {
|
if(self) {
|
||||||
using Target = Analyser::Static::PCCompatible::Target;
|
using Target = Analyser::Static::PCCompatible::Target;
|
||||||
@ -282,6 +282,10 @@
|
|||||||
case CSPCCompatibleVideoAdaptorMDA: target->adaptor = Target::VideoAdaptor::MDA; break;
|
case CSPCCompatibleVideoAdaptorMDA: target->adaptor = Target::VideoAdaptor::MDA; break;
|
||||||
case CSPCCompatibleVideoAdaptorCGA: target->adaptor = Target::VideoAdaptor::CGA; break;
|
case CSPCCompatibleVideoAdaptorCGA: target->adaptor = Target::VideoAdaptor::CGA; break;
|
||||||
}
|
}
|
||||||
|
switch(speed) {
|
||||||
|
case CSPCCompatibleSpeedOriginal: target->speed = Target::Speed::ApproximatelyOriginal; break;
|
||||||
|
case CSPCCompatibleSpeedTurbo: target->speed = Target::Speed::Fast; break;
|
||||||
|
}
|
||||||
_targets.push_back(std::move(target));
|
_targets.push_back(std::move(target));
|
||||||
}
|
}
|
||||||
return self;
|
return self;
|
||||||
|
@ -750,13 +750,39 @@ Gw
|
|||||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
</textFieldCell>
|
</textFieldCell>
|
||||||
</textField>
|
</textField>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8vF-eu-ClP">
|
||||||
|
<rect key="frame" x="18" y="168" width="47" height="16"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Speed:" id="qXc-wf-5jm">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ci6-TG-5tW">
|
||||||
|
<rect key="frame" x="68" y="162" width="146" height="25"/>
|
||||||
|
<popUpButtonCell key="cell" type="push" title="Similar to Original" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8086" imageScaling="axesIndependently" inset="2" selectedItem="R4W-s4-KFx" id="9i0-UG-B2c">
|
||||||
|
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||||
|
<font key="font" metaFont="menu"/>
|
||||||
|
<menu key="menu" id="KQw-8g-SrI">
|
||||||
|
<items>
|
||||||
|
<menuItem title="Similar to Original" state="on" tag="8086" id="R4W-s4-KFx"/>
|
||||||
|
<menuItem title="Turbo" tag="80286" id="LeD-N4-avf"/>
|
||||||
|
</items>
|
||||||
|
</menu>
|
||||||
|
</popUpButtonCell>
|
||||||
|
</popUpButton>
|
||||||
</subviews>
|
</subviews>
|
||||||
<constraints>
|
<constraints>
|
||||||
|
<constraint firstItem="Ci6-TG-5tW" firstAttribute="leading" secondItem="8vF-eu-ClP" secondAttribute="trailing" constant="8" symbolic="YES" id="0Sb-TO-UVM"/>
|
||||||
|
<constraint firstItem="Ci6-TG-5tW" firstAttribute="top" secondItem="stw-i3-ikG" secondAttribute="bottom" constant="10" symbolic="YES" id="4E9-4l-fat"/>
|
||||||
|
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Ci6-TG-5tW" secondAttribute="bottom" constant="20" symbolic="YES" id="9C6-PK-12w"/>
|
||||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="stw-i3-ikG" secondAttribute="trailing" constant="20" symbolic="YES" id="A0n-lr-LHf"/>
|
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="stw-i3-ikG" secondAttribute="trailing" constant="20" symbolic="YES" id="A0n-lr-LHf"/>
|
||||||
|
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Ci6-TG-5tW" secondAttribute="trailing" constant="20" symbolic="YES" id="G4D-zS-Q1t"/>
|
||||||
<constraint firstItem="uhf-1k-ibT" firstAttribute="leading" secondItem="gJD-vd-WWu" secondAttribute="leading" constant="20" symbolic="YES" id="JNr-Tg-gDV"/>
|
<constraint firstItem="uhf-1k-ibT" firstAttribute="leading" secondItem="gJD-vd-WWu" secondAttribute="leading" constant="20" symbolic="YES" id="JNr-Tg-gDV"/>
|
||||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="stw-i3-ikG" secondAttribute="bottom" constant="20" symbolic="YES" id="RSc-OQ-KNO"/>
|
|
||||||
<constraint firstItem="uhf-1k-ibT" firstAttribute="centerY" secondItem="stw-i3-ikG" secondAttribute="centerY" id="bfD-kx-eOc"/>
|
<constraint firstItem="uhf-1k-ibT" firstAttribute="centerY" secondItem="stw-i3-ikG" secondAttribute="centerY" id="bfD-kx-eOc"/>
|
||||||
<constraint firstItem="stw-i3-ikG" firstAttribute="leading" secondItem="uhf-1k-ibT" secondAttribute="trailing" constant="8" symbolic="YES" id="f6l-B2-nBw"/>
|
<constraint firstItem="stw-i3-ikG" firstAttribute="leading" secondItem="uhf-1k-ibT" secondAttribute="trailing" constant="8" symbolic="YES" id="f6l-B2-nBw"/>
|
||||||
|
<constraint firstItem="8vF-eu-ClP" firstAttribute="leading" secondItem="gJD-vd-WWu" secondAttribute="leading" constant="20" symbolic="YES" id="ftj-Es-CRQ"/>
|
||||||
|
<constraint firstItem="8vF-eu-ClP" firstAttribute="centerY" secondItem="Ci6-TG-5tW" secondAttribute="centerY" id="wUL-sD-fae"/>
|
||||||
<constraint firstItem="stw-i3-ikG" firstAttribute="top" secondItem="gJD-vd-WWu" secondAttribute="top" constant="20" symbolic="YES" id="wjD-TM-5CL"/>
|
<constraint firstItem="stw-i3-ikG" firstAttribute="top" secondItem="gJD-vd-WWu" secondAttribute="top" constant="20" symbolic="YES" id="wjD-TM-5CL"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
@ -1069,6 +1095,7 @@ Gw
|
|||||||
<outlet property="msxRegionButton" destination="LG6-mP-SeG" id="3a9-VG-6Wf"/>
|
<outlet property="msxRegionButton" destination="LG6-mP-SeG" id="3a9-VG-6Wf"/>
|
||||||
<outlet property="oricDiskInterfaceButton" destination="fYL-p6-wyn" id="aAt-wM-hRZ"/>
|
<outlet property="oricDiskInterfaceButton" destination="fYL-p6-wyn" id="aAt-wM-hRZ"/>
|
||||||
<outlet property="oricModelTypeButton" destination="ENP-hI-BVZ" id="n9i-Ym-miE"/>
|
<outlet property="oricModelTypeButton" destination="ENP-hI-BVZ" id="n9i-Ym-miE"/>
|
||||||
|
<outlet property="pcSpeedButton" destination="Ci6-TG-5tW" id="34Y-5H-5dN"/>
|
||||||
<outlet property="pcVideoAdaptorButton" destination="stw-i3-ikG" id="6VO-Q9-4kV"/>
|
<outlet property="pcVideoAdaptorButton" destination="stw-i3-ikG" id="6VO-Q9-4kV"/>
|
||||||
<outlet property="spectrumModelTypeButton" destination="gFZ-d4-WFv" id="tdX-Cv-Swe"/>
|
<outlet property="spectrumModelTypeButton" destination="gFZ-d4-WFv" id="tdX-Cv-Swe"/>
|
||||||
<outlet property="vic20HasC1540Button" destination="Lrf-gL-6EI" id="21g-dJ-mOo"/>
|
<outlet property="vic20HasC1540Button" destination="Lrf-gL-6EI" id="21g-dJ-mOo"/>
|
||||||
|
@ -65,6 +65,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
|||||||
|
|
||||||
// MARK: - PC compatible properties
|
// MARK: - PC compatible properties
|
||||||
@IBOutlet var pcVideoAdaptorButton: NSPopUpButton!
|
@IBOutlet var pcVideoAdaptorButton: NSPopUpButton!
|
||||||
|
@IBOutlet var pcSpeedButton: NSPopUpButton!
|
||||||
|
|
||||||
// MARK: - Spectrum properties
|
// MARK: - Spectrum properties
|
||||||
@IBOutlet var spectrumModelTypeButton: NSPopUpButton!
|
@IBOutlet var spectrumModelTypeButton: NSPopUpButton!
|
||||||
@ -155,6 +156,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
|||||||
|
|
||||||
// PC settings
|
// PC settings
|
||||||
pcVideoAdaptorButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.pcVideoAdaptor"))
|
pcVideoAdaptorButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.pcVideoAdaptor"))
|
||||||
|
pcSpeedButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.pcSpeed"))
|
||||||
|
|
||||||
// Spectrum settings
|
// Spectrum settings
|
||||||
spectrumModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.spectrumModel"))
|
spectrumModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.spectrumModel"))
|
||||||
@ -224,6 +226,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
|||||||
|
|
||||||
// PC settings
|
// PC settings
|
||||||
standardUserDefaults.set(pcVideoAdaptorButton.selectedTag(), forKey: "new.pcVideoAdaptor")
|
standardUserDefaults.set(pcVideoAdaptorButton.selectedTag(), forKey: "new.pcVideoAdaptor")
|
||||||
|
standardUserDefaults.set(pcSpeedButton.selectedTag(), forKey: "new.pcSpeed")
|
||||||
|
|
||||||
// Spectrum settings
|
// Spectrum settings
|
||||||
standardUserDefaults.set(spectrumModelTypeButton.selectedTag(), forKey: "new.spectrumModel")
|
standardUserDefaults.set(spectrumModelTypeButton.selectedTag(), forKey: "new.spectrumModel")
|
||||||
@ -416,7 +419,12 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
|||||||
case 1: videoAdaptor = .CGA
|
case 1: videoAdaptor = .CGA
|
||||||
default: break
|
default: break
|
||||||
}
|
}
|
||||||
return CSStaticAnalyser(pcCompatibleModel: .turboXT, videoAdaptor: videoAdaptor)
|
var speed: CSPCCompatibleSpeed = .original
|
||||||
|
switch pcSpeedButton.selectedTag() {
|
||||||
|
case 80286: speed = .turbo
|
||||||
|
default: break
|
||||||
|
}
|
||||||
|
return CSStaticAnalyser(pcCompatibleSpeed: speed, videoAdaptor: videoAdaptor)
|
||||||
|
|
||||||
case "spectrum":
|
case "spectrum":
|
||||||
var model: CSMachineSpectrumModel = .plus2a
|
var model: CSMachineSpectrumModel = .plus2a
|
||||||
|
@ -51,6 +51,10 @@ enum class DisplayType {
|
|||||||
CompositeMonochrome
|
CompositeMonochrome
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr bool is_composite(DisplayType type) {
|
||||||
|
return type == DisplayType::CompositeColour || type == DisplayType::CompositeMonochrome;
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
Enumerates the potential formats of input data.
|
Enumerates the potential formats of input data.
|
||||||
|
|
||||||
|
@ -24,19 +24,18 @@ namespace Storage::Disk {
|
|||||||
class CPCDSK: public DiskImage {
|
class CPCDSK: public DiskImage {
|
||||||
public:
|
public:
|
||||||
/*!
|
/*!
|
||||||
Construct an @c AcornADF containing content from the file with name @c file_name.
|
Construct a @c CPCDSK containing content from the file with name @c file_name.
|
||||||
|
|
||||||
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||||
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
|
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
|
||||||
*/
|
*/
|
||||||
CPCDSK(const std::string &file_name);
|
CPCDSK(const std::string &file_name);
|
||||||
|
|
||||||
// implemented to satisfy @c Disk
|
// DiskImage interface.
|
||||||
HeadPosition get_maximum_head_position() final;
|
HeadPosition get_maximum_head_position() final;
|
||||||
int get_head_count() final;
|
int get_head_count() final;
|
||||||
bool get_is_read_only() final;
|
bool get_is_read_only() final;
|
||||||
|
void set_tracks(const std::map<::Storage::Disk::Track::Address, std::shared_ptr<::Storage::Disk::Track>> &tracks) final;
|
||||||
void set_tracks(const std::map<Track::Address, std::shared_ptr<Track>> &tracks) final;
|
|
||||||
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final;
|
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
163
Storage/Disk/DiskImage/Formats/IMD.cpp
Normal file
163
Storage/Disk/DiskImage/Formats/IMD.cpp
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
//
|
||||||
|
// IMD.cpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 08/12/2023.
|
||||||
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "IMD.hpp"
|
||||||
|
|
||||||
|
#include "../../Encodings/MFM/Constants.hpp"
|
||||||
|
#include "../../Encodings/MFM/Encoder.hpp"
|
||||||
|
#include "../../Encodings/MFM/SegmentParser.hpp"
|
||||||
|
#include "../../Track/TrackSerialiser.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
using namespace Storage::Disk;
|
||||||
|
|
||||||
|
// Documentation source: https://oldcomputers-ddns.org/public/pub/manuals/imd.pdf
|
||||||
|
|
||||||
|
IMD::IMD(const std::string &file_name) : file_(file_name) {
|
||||||
|
// Check for signature.
|
||||||
|
if(!file_.check_signature("IMD")) {
|
||||||
|
throw Error::InvalidFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip rest of ASCII.
|
||||||
|
while(file_.get8() != 0x1a);
|
||||||
|
|
||||||
|
// Build track map.
|
||||||
|
while(true) {
|
||||||
|
const auto location = file_.tell();
|
||||||
|
|
||||||
|
// Skip mode.
|
||||||
|
file_.seek(1, SEEK_CUR);
|
||||||
|
|
||||||
|
// Grab relevant fields.
|
||||||
|
const uint8_t cylinder = file_.get8();
|
||||||
|
const uint8_t head = file_.get8();
|
||||||
|
const uint8_t sector_count = file_.get8();
|
||||||
|
const uint8_t sector_size = file_.get8();
|
||||||
|
if(file_.eof()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cylinders_ = std::max(cylinder, cylinders_);
|
||||||
|
heads_ = std::max(uint8_t(head & 1), heads_);
|
||||||
|
|
||||||
|
// Update head and cylinder extents, record sector location for later.
|
||||||
|
track_locations_.emplace(
|
||||||
|
Storage::Disk::Track::Address(head & 1, HeadPosition(cylinder)),
|
||||||
|
location);
|
||||||
|
|
||||||
|
// Skip sector numbers.
|
||||||
|
file_.seek(sector_count, SEEK_CUR);
|
||||||
|
|
||||||
|
// Skip cylinder map.
|
||||||
|
if(head & 0x80) {
|
||||||
|
file_.seek(sector_count, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip head map.
|
||||||
|
if(head & 0x40) {
|
||||||
|
file_.seek(sector_count, SEEK_CUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip sectors.
|
||||||
|
for(int c = 0; c < sector_count; c++) {
|
||||||
|
const uint8_t type = file_.get8();
|
||||||
|
switch(type) {
|
||||||
|
case 0x00: break; // Sector couldn't be read.
|
||||||
|
|
||||||
|
// Types with all sector data present.
|
||||||
|
case 0x01: case 0x03: case 0x05: case 0x07:
|
||||||
|
file_.seek(128 << sector_size, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Types with a single byte present.
|
||||||
|
case 0x02: case 0x04: case 0x06: case 0x08:
|
||||||
|
file_.seek(1, SEEK_CUR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Both heads_ and cylinders_ are now the maximum observed IDs, which
|
||||||
|
// are one less than the counts.
|
||||||
|
++ cylinders_;
|
||||||
|
++ heads_;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeadPosition IMD::get_maximum_head_position() {
|
||||||
|
return HeadPosition(cylinders_);
|
||||||
|
}
|
||||||
|
|
||||||
|
int IMD::get_head_count() {
|
||||||
|
return heads_ + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<::Storage::Disk::Track> IMD::get_track_at_position(::Storage::Disk::Track::Address address) {
|
||||||
|
auto location = track_locations_.find(address);
|
||||||
|
if(location == track_locations_.end()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seek to track, parse fully this time.
|
||||||
|
file_.seek(location->second, SEEK_SET);
|
||||||
|
|
||||||
|
const uint8_t mode = file_.get8();
|
||||||
|
const uint8_t cylinder = file_.get8();
|
||||||
|
const uint8_t head = file_.get8();
|
||||||
|
const uint8_t sector_count = file_.get8();
|
||||||
|
const uint8_t sector_size = file_.get8();
|
||||||
|
|
||||||
|
const std::vector<uint8_t> sector_ids = file_.read(sector_count);
|
||||||
|
const std::vector<uint8_t> cylinders = (head & 0x80) ? file_.read(sector_count) : std::vector<uint8_t>{};
|
||||||
|
const std::vector<uint8_t> heads = (head & 0x40) ? file_.read(sector_count) : std::vector<uint8_t>{};
|
||||||
|
|
||||||
|
std::vector<Storage::Encodings::MFM::Sector> sectors;
|
||||||
|
sectors.reserve(sector_count);
|
||||||
|
|
||||||
|
for(size_t c = 0; c < sector_count; c++) {
|
||||||
|
sectors.emplace_back();
|
||||||
|
Storage::Encodings::MFM::Sector §or = sectors.back();
|
||||||
|
|
||||||
|
// Set up sector address.
|
||||||
|
sector.address.track = cylinders.empty() ? cylinder : cylinders[c];
|
||||||
|
sector.address.side = heads.empty() ? head & 1 : heads[c];
|
||||||
|
sector.address.sector = sector_ids[c];
|
||||||
|
sector.size = sector_size;
|
||||||
|
|
||||||
|
const auto byte_size = size_t(128 << sector_size);
|
||||||
|
uint8_t type = file_.get8();
|
||||||
|
|
||||||
|
// Type 0: sector was present, but couldn't be read.
|
||||||
|
// Since body CRC errors are a separate item, just don't include a body at all.
|
||||||
|
if(!type) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement type to turn it into a bit field:
|
||||||
|
//
|
||||||
|
// b0 => is compressed within disk image;
|
||||||
|
// b1 => had deleted address mark;
|
||||||
|
// b2 => had data CRC error.
|
||||||
|
--type;
|
||||||
|
sector.is_deleted = type & 2;
|
||||||
|
sector.has_data_crc_error = type & 4;
|
||||||
|
if(type & 1) {
|
||||||
|
sector.samples.emplace_back(byte_size, file_.get8());
|
||||||
|
} else {
|
||||||
|
sector.samples.push_back(file_.read(byte_size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode also indicates data density, but I don't have a good strategy for reconciling that if
|
||||||
|
// it were to disagree with the density implied by the quantity of sectors. So a broad 'is it MFM' test is
|
||||||
|
// applied only.
|
||||||
|
return (mode >= 3) ?
|
||||||
|
Storage::Encodings::MFM::GetMFMTrackWithSectors(sectors) :
|
||||||
|
Storage::Encodings::MFM::GetFMTrackWithSectors(sectors);
|
||||||
|
}
|
45
Storage/Disk/DiskImage/Formats/IMD.hpp
Normal file
45
Storage/Disk/DiskImage/Formats/IMD.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// IMD.hpp
|
||||||
|
// Clock Signal
|
||||||
|
//
|
||||||
|
// Created by Thomas Harte on 08/12/2023.
|
||||||
|
// Copyright © 2023 Thomas Harte. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef IMD_hpp
|
||||||
|
#define IMD_hpp
|
||||||
|
|
||||||
|
#include "../DiskImage.hpp"
|
||||||
|
#include "../../../FileHolder.hpp"
|
||||||
|
|
||||||
|
namespace Storage::Disk {
|
||||||
|
|
||||||
|
/*!
|
||||||
|
Provides an @c DiskImage containing an IMD image, which is a collection of arbitrarily-numbered FM or MFM
|
||||||
|
sectors collected by track.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class IMD: public DiskImage {
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
Construct an @c IMD containing content from the file with name @c file_name.
|
||||||
|
|
||||||
|
@throws Storage::FileHolder::Error::CantOpen if this file can't be opened.
|
||||||
|
@throws Error::InvalidFormat if the file doesn't appear to contain an Acorn .ADF format image.
|
||||||
|
*/
|
||||||
|
IMD(const std::string &file_name);
|
||||||
|
|
||||||
|
// DiskImage interface.
|
||||||
|
HeadPosition get_maximum_head_position() final;
|
||||||
|
int get_head_count() final;
|
||||||
|
std::shared_ptr<::Storage::Disk::Track> get_track_at_position(::Storage::Disk::Track::Address address) final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FileHolder file_;
|
||||||
|
std::map<Storage::Disk::Track::Address, long> track_locations_;
|
||||||
|
uint8_t cylinders_ = 0, heads_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* IMD_hpp */
|
Loading…
Reference in New Issue
Block a user