mirror of
https://github.com/TomHarte/CLK.git
synced 2024-09-08 18:55:14 +00:00
Merge pull request #645 from TomHarte/SCSI
Adds basic SCSI support, along with a Mac Plus to use it.
This commit is contained in:
commit
e470cf23d8
@ -10,10 +10,10 @@
|
||||
#include "Target.hpp"
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Macintosh::GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
// This analyser can comprehend disks and mass-storage devices only.
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty()) return {};
|
||||
|
||||
// If there is at least one disk, wave it through.
|
||||
// As there is at least one usable media image, wave it through.
|
||||
Analyser::Static::TargetList targets;
|
||||
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
|
@ -21,7 +21,7 @@ struct Target: public ::Analyser::Static::Target {
|
||||
MacPlus
|
||||
};
|
||||
|
||||
Model model = Model::Mac512ke;
|
||||
Model model = Model::MacPlus;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -46,6 +46,9 @@
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// Tapes
|
||||
#include "../../Storage/Tape/Formats/CAS.hpp"
|
||||
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
|
||||
@ -102,7 +105,8 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::CPCDSK>, TargetPlatform::AmstradCPC) // DSK (Amstrad CPC)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
@ -160,7 +164,7 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetList targets;
|
||||
|
||||
// Collect all disks, tapes and ROMs as can be extrapolated from this file, forming the
|
||||
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
|
||||
// union of all platforms this file might be a target for.
|
||||
TargetPlatform::IntType potential_platforms = 0;
|
||||
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||
|
@ -11,9 +11,10 @@
|
||||
|
||||
#include "../Machines.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/Cartridge/Cartridge.hpp"
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@ -29,9 +30,10 @@ struct Media {
|
||||
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
|
||||
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
|
||||
std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices;
|
||||
|
||||
bool empty() const {
|
||||
return disks.empty() && tapes.empty() && cartridges.empty();
|
||||
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
|
||||
}
|
||||
};
|
||||
|
||||
|
312
Components/5380/ncr5380.cpp
Normal file
312
Components/5380/ncr5380.cpp
Normal file
@ -0,0 +1,312 @@
|
||||
//
|
||||
// ncr5380.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ncr5380.hpp"
|
||||
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
using namespace NCR::NCR5380;
|
||||
using SCSI::Line;
|
||||
|
||||
NCR5380::NCR5380(SCSI::Bus &bus, int clock_rate) :
|
||||
bus_(bus),
|
||||
clock_rate_(clock_rate) {
|
||||
device_id_ = bus_.add_device();
|
||||
bus_.add_observer(this);
|
||||
}
|
||||
|
||||
void NCR5380::write(int address, uint8_t value, bool dma_acknowledge) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
// LOG("[SCSI 0] Set current SCSI bus state to " << PADHEX(2) << int(value));
|
||||
data_bus_ = value;
|
||||
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::Send) {
|
||||
// printf("w %02x\n", value);
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: {
|
||||
// LOG("[SCSI 1] Initiator command register set: " << PADHEX(2) << int(value));
|
||||
initiator_command_ = value;
|
||||
|
||||
bus_output_ &= ~(Line::Reset | Line::Acknowledge | Line::Busy | Line::SelectTarget | Line::Attention);
|
||||
if(value & 0x80) bus_output_ |= Line::Reset;
|
||||
if(value & 0x08) bus_output_ |= Line::Busy;
|
||||
if(value & 0x04) bus_output_ |= Line::SelectTarget;
|
||||
|
||||
/* bit 5 = differential enable if this were a 5381 */
|
||||
|
||||
test_mode_ = value & 0x40;
|
||||
assert_data_bus_ = value & 0x01;
|
||||
update_control_output();
|
||||
} break;
|
||||
|
||||
case 2:
|
||||
// LOG("[SCSI 2] Set mode: " << PADHEX(2) << int(value));
|
||||
mode_ = value;
|
||||
|
||||
// bit 7: 1 = use block mode DMA mode (if DMA mode is also enabled)
|
||||
// bit 6: 1 = be a SCSI target; 0 = be an initiator
|
||||
// bit 5: 1 = check parity
|
||||
// bit 4: 1 = generate an interrupt if parity checking is enabled and an error is found
|
||||
// bit 3: 1 = generate an interrupt when an EOP is received from the DMA controller
|
||||
// bit 2: 1 = generate an interrupt and reset low 6 bits of register 1 if an unexpected loss of Line::Busy occurs
|
||||
// bit 1: 1 = use DMA mode
|
||||
// bit 0: 1 = begin arbitration mode (device ID should be in register 0)
|
||||
arbitration_in_progress_ = false;
|
||||
switch(mode_ & 0x3) {
|
||||
case 0x0:
|
||||
bus_output_ &= ~SCSI::Line::Busy;
|
||||
dma_request_ = false;
|
||||
set_execution_state(ExecutionState::None);
|
||||
break;
|
||||
|
||||
case 0x1:
|
||||
arbitration_in_progress_ = true;
|
||||
set_execution_state(ExecutionState::WaitingForBusy);
|
||||
lost_arbitration_ = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_data_bus_ = false;
|
||||
set_execution_state(ExecutionState::PerformingDMA);
|
||||
bus_.update_observers();
|
||||
break;
|
||||
}
|
||||
update_control_output();
|
||||
break;
|
||||
|
||||
case 3: {
|
||||
// LOG("[SCSI 3] Set target command: " << PADHEX(2) << int(value));
|
||||
target_command_ = value;
|
||||
update_control_output();
|
||||
} break;
|
||||
|
||||
case 4:
|
||||
// LOG("[SCSI 4] Set select enabled: " << PADHEX(2) << int(value));
|
||||
break;
|
||||
|
||||
case 5:
|
||||
// LOG("[SCSI 5] Start DMA send: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::Send;
|
||||
break;
|
||||
|
||||
case 6:
|
||||
// LOG("[SCSI 6] Start DMA target receive: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::TargetReceive;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
// LOG("[SCSI 7] Start DMA initiator receive: " << PADHEX(2) << int(value));
|
||||
dma_operation_ = DMAOperation::InitiatorReceive;
|
||||
break;
|
||||
}
|
||||
|
||||
// Data is output only if the data bus is asserted.
|
||||
if(assert_data_bus_) {
|
||||
bus_output_ = (bus_output_ & ~SCSI::Line::Data) | data_bus_;
|
||||
} else {
|
||||
bus_output_ &= ~SCSI::Line::Data;
|
||||
}
|
||||
|
||||
// In test mode, still nothing is output. Otherwise throw out
|
||||
// the current value of bus_output_.
|
||||
if(test_mode_) {
|
||||
bus_.set_device_output(device_id_, SCSI::DefaultBusState);
|
||||
} else {
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t NCR5380::read(int address, bool dma_acknowledge) {
|
||||
switch(address & 7) {
|
||||
case 0:
|
||||
// LOG("[SCSI 0] Get current SCSI bus state: " << PADHEX(2) << (bus_.get_state() & 0xff));
|
||||
|
||||
if(dma_request_ && dma_operation_ == DMAOperation::InitiatorReceive) {
|
||||
dma_acknowledge_ = true;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
}
|
||||
return uint8_t(bus_.get_state());
|
||||
|
||||
case 1:
|
||||
// LOG("[SCSI 1] Initiator command register get: " << (arbitration_in_progress_ ? 'p' : '-') << (lost_arbitration_ ? 'l' : '-'));
|
||||
return
|
||||
// Bits repeated as they were set.
|
||||
(initiator_command_ & ~0x60) |
|
||||
|
||||
// Arbitration in progress.
|
||||
(arbitration_in_progress_ ? 0x40 : 0x00) |
|
||||
|
||||
// Lost arbitration.
|
||||
(lost_arbitration_ ? 0x20 : 0x00);
|
||||
|
||||
case 2:
|
||||
// LOG("[SCSI 2] Get mode");
|
||||
return mode_;
|
||||
|
||||
case 3:
|
||||
// LOG("[SCSI 3] Get target command");
|
||||
return target_command_;
|
||||
|
||||
case 4: {
|
||||
const auto bus_state = bus_.get_state();
|
||||
const uint8_t result =
|
||||
((bus_state & Line::Reset) ? 0x80 : 0x00) |
|
||||
((bus_state & Line::Busy) ? 0x40 : 0x00) |
|
||||
((bus_state & Line::Request) ? 0x20 : 0x00) |
|
||||
((bus_state & Line::Message) ? 0x10 : 0x00) |
|
||||
((bus_state & Line::Control) ? 0x08 : 0x00) |
|
||||
((bus_state & Line::Input) ? 0x04 : 0x00) |
|
||||
((bus_state & Line::SelectTarget) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Parity) ? 0x01 : 0x00);
|
||||
// LOG("[SCSI 4] Get current bus state: " << PADHEX(2) << int(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
case 5: {
|
||||
const auto bus_state = bus_.get_state();
|
||||
const bool phase_matches =
|
||||
(target_output() & (Line::Message | Line::Control | Line::Input)) ==
|
||||
(bus_state & (Line::Message | Line::Control | Line::Input));
|
||||
|
||||
const uint8_t result =
|
||||
/* b7 = end of DMA */
|
||||
((dma_request_ && state_ == ExecutionState::PerformingDMA) ? 0x40 : 0x00) |
|
||||
/* b5 = parity error */
|
||||
/* b4 = IRQ active */
|
||||
(phase_matches ? 0x08 : 0x00) |
|
||||
/* b2 = busy error */
|
||||
((bus_state & Line::Attention) ? 0x02 : 0x00) |
|
||||
((bus_state & Line::Acknowledge) ? 0x01 : 0x00);
|
||||
// LOG("[SCSI 5] Get bus and status: " << PADHEX(2) << int(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
case 6:
|
||||
// LOG("[SCSI 6] Get input data");
|
||||
return 0xff;
|
||||
|
||||
case 7:
|
||||
// LOG("[SCSI 7] Reset parity/interrupt");
|
||||
return 0xff;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SCSI::BusState NCR5380::target_output() {
|
||||
SCSI::BusState output = SCSI::DefaultBusState;
|
||||
if(target_command_ & 0x08) output |= Line::Request;
|
||||
if(target_command_ & 0x04) output |= Line::Message;
|
||||
if(target_command_ & 0x02) output |= Line::Control;
|
||||
if(target_command_ & 0x01) output |= Line::Input;
|
||||
return output;
|
||||
}
|
||||
|
||||
void NCR5380::update_control_output() {
|
||||
bus_output_ &= ~(Line::Request | Line::Message | Line::Control | Line::Input | Line::Acknowledge | Line::Attention);
|
||||
if(mode_ & 0x40) {
|
||||
// This is a target; C/D, I/O, /MSG and /REQ are signalled on the bus.
|
||||
bus_output_ |= target_output();
|
||||
} else {
|
||||
// This is an initiator; /ATN and /ACK are signalled on the bus.
|
||||
if(
|
||||
(initiator_command_ & 0x10) ||
|
||||
(state_ == ExecutionState::PerformingDMA && dma_acknowledge_)
|
||||
) bus_output_ |= Line::Acknowledge;
|
||||
if(initiator_command_ & 0x02) bus_output_ |= Line::Attention;
|
||||
}
|
||||
}
|
||||
|
||||
void NCR5380::scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) {
|
||||
switch(state_) {
|
||||
default: break;
|
||||
|
||||
/*
|
||||
Official documentation:
|
||||
|
||||
Arbitration is accomplished using a bus-free filter to continuously monitor BSY.
|
||||
If BSY remains inactive for at least 400 nsec then the SCSI bus is considered free
|
||||
and arbitration may begin. Arbitration will begin if the bus is free, SEL is inactive
|
||||
and the ARBITRATION bit (port 2, bit 0) is active. Once arbitration has begun
|
||||
(BSY asserted), an arbitration delay of 2.2 /Lsec must elapse before the data bus
|
||||
can be examined to deter- mine if arbitration has been won. This delay must be
|
||||
implemented in the controlling software driver.
|
||||
|
||||
Personal notes:
|
||||
|
||||
I'm discounting that "arbitratation is accomplished" opening, and assuming that what needs
|
||||
to happen is:
|
||||
|
||||
(i) wait for BSY to be inactive;
|
||||
(ii) count 400 nsec;
|
||||
(iii) check that BSY and SEL are inactive.
|
||||
*/
|
||||
|
||||
case ExecutionState::WaitingForBusy:
|
||||
if(!(new_state & SCSI::Line::Busy) || time_since_change < SCSI::DeskewDelay) return;
|
||||
state_ = ExecutionState::WatchingBusy;
|
||||
|
||||
case ExecutionState::WatchingBusy:
|
||||
if(!(new_state & SCSI::Line::Busy)) {
|
||||
lost_arbitration_ = true;
|
||||
set_execution_state(ExecutionState::None);
|
||||
}
|
||||
|
||||
// Check for having hit 400ns (more or less) since BSY was inactive.
|
||||
if(time_since_change >= SCSI::BusSettleDelay) {
|
||||
// arbitration_in_progress_ = false;
|
||||
if(new_state & SCSI::Line::SelectTarget) {
|
||||
lost_arbitration_ = true;
|
||||
set_execution_state(ExecutionState::None);
|
||||
} else {
|
||||
bus_output_ &= ~SCSI::Line::Busy;
|
||||
set_execution_state(ExecutionState::None);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO: there's a bug here, given that the dropping of Busy isn't communicated onward. */
|
||||
break;
|
||||
|
||||
case ExecutionState::PerformingDMA:
|
||||
if(time_since_change < SCSI::DeskewDelay) return;
|
||||
|
||||
// Signal a DMA request if the request line is active, i.e. meaningful data is
|
||||
// on the bus, and this device hasn't yet acknowledged it.
|
||||
switch(new_state & (SCSI::Line::Request | SCSI::Line::Acknowledge)) {
|
||||
case 0:
|
||||
dma_request_ = false;
|
||||
break;
|
||||
case SCSI::Line::Request:
|
||||
dma_request_ = true;
|
||||
break;
|
||||
case SCSI::Line::Request | SCSI::Line::Acknowledge:
|
||||
dma_request_ = false;
|
||||
break;
|
||||
case SCSI::Line::Acknowledge:
|
||||
dma_acknowledge_ = false;
|
||||
dma_request_ = false;
|
||||
update_control_output();
|
||||
bus_.set_device_output(device_id_, bus_output_);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void NCR5380::set_execution_state(ExecutionState state) {
|
||||
state_ = state;
|
||||
if(state != ExecutionState::PerformingDMA) dma_operation_ = DMAOperation::Ready;
|
||||
}
|
75
Components/5380/ncr5380.hpp
Normal file
75
Components/5380/ncr5380.hpp
Normal file
@ -0,0 +1,75 @@
|
||||
//
|
||||
// ncr5380.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ncr5380_hpp
|
||||
#define ncr5380_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
|
||||
|
||||
|
||||
namespace NCR {
|
||||
namespace NCR5380 {
|
||||
|
||||
/*!
|
||||
Models the NCR 5380, a SCSI interface chip.
|
||||
*/
|
||||
class NCR5380 final: public SCSI::Bus::Observer {
|
||||
public:
|
||||
NCR5380(SCSI::Bus &bus, int clock_rate);
|
||||
|
||||
/*! Writes @c value to @c address. */
|
||||
void write(int address, uint8_t value, bool dma_acknowledge = false);
|
||||
|
||||
/*! Reads from @c address. */
|
||||
uint8_t read(int address, bool dma_acknowledge = false);
|
||||
|
||||
private:
|
||||
SCSI::Bus &bus_;
|
||||
|
||||
const int clock_rate_;
|
||||
size_t device_id_;
|
||||
|
||||
SCSI::BusState bus_output_ = SCSI::DefaultBusState;
|
||||
SCSI::BusState expected_phase_ = SCSI::DefaultBusState;
|
||||
uint8_t mode_ = 0xff;
|
||||
uint8_t initiator_command_ = 0xff;
|
||||
uint8_t data_bus_ = 0xff;
|
||||
uint8_t target_command_ = 0xff;
|
||||
bool test_mode_ = false;
|
||||
bool assert_data_bus_ = false;
|
||||
bool dma_request_ = false;
|
||||
bool dma_acknowledge_ = false;
|
||||
|
||||
enum class ExecutionState {
|
||||
None,
|
||||
WaitingForBusy,
|
||||
WatchingBusy,
|
||||
PerformingDMA,
|
||||
} state_ = ExecutionState::None;
|
||||
enum class DMAOperation {
|
||||
Ready,
|
||||
Send,
|
||||
TargetReceive,
|
||||
InitiatorReceive
|
||||
} dma_operation_ = DMAOperation::Ready;
|
||||
bool lost_arbitration_ = false, arbitration_in_progress_ = false;
|
||||
|
||||
void set_execution_state(ExecutionState state);
|
||||
|
||||
SCSI::BusState target_output();
|
||||
void update_control_output();
|
||||
|
||||
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double time_since_change) final;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* ncr5380_hpp */
|
@ -39,7 +39,7 @@ class i8272: public Storage::Disk::MFMController {
|
||||
void set_dma_acknowledge(bool dack);
|
||||
void set_terminal_count(bool tc);
|
||||
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
|
||||
protected:
|
||||
virtual void select_drive(int number) = 0;
|
||||
|
@ -26,7 +26,7 @@ namespace Apple {
|
||||
/*!
|
||||
Provides an emulation of the Apple Disk II.
|
||||
*/
|
||||
class DiskII:
|
||||
class DiskII final:
|
||||
public Storage::Disk::Drive::EventDelegate,
|
||||
public ClockingHint::Source,
|
||||
public ClockingHint::Observer {
|
||||
@ -76,7 +76,7 @@ class DiskII:
|
||||
void set_disk(const std::shared_ptr<Storage::Disk::Disk> &disk, int drive);
|
||||
|
||||
// As per Sleeper.
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
|
||||
// The Disk II functions as a potential target for @c Activity::Sources.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
@ -26,15 +26,21 @@
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
#include "../../../ClockReceiver/JustInTime.hpp"
|
||||
#include "../../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
//#define LOG_TRACE
|
||||
|
||||
#include "../../../Components/5380/ncr5380.hpp"
|
||||
#include "../../../Components/6522/6522.hpp"
|
||||
#include "../../../Components/8530/z8530.hpp"
|
||||
#include "../../../Components/DiskII/IWM.hpp"
|
||||
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
|
||||
#include "../../../Processors/68000/68000.hpp"
|
||||
|
||||
#include "../../../Storage/MassStorage/SCSI/SCSI.hpp"
|
||||
#include "../../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
|
||||
#include "../../../Storage/MassStorage/Encodings/MacintoshVolume.hpp"
|
||||
|
||||
#include "../../../Analyser/Static/Macintosh/Target.hpp"
|
||||
|
||||
#include "../../Utility/MemoryPacker.hpp"
|
||||
@ -58,16 +64,20 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
public KeyboardMachine::MappedMachine,
|
||||
public Zilog::SCC::z8530::Delegate,
|
||||
public Activity::Source,
|
||||
public DriveSpeedAccumulator::Delegate {
|
||||
public DriveSpeedAccumulator::Delegate,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
|
||||
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
mc68000_(*this),
|
||||
iwm_(CLOCK_RATE),
|
||||
video_(ram_, audio_, drive_speed_accumulator_),
|
||||
video_(audio_, drive_speed_accumulator_),
|
||||
via_(via_port_handler_),
|
||||
via_port_handler_(*this, clock_, keyboard_, video_, audio_, iwm_, mouse_),
|
||||
scsi_bus_(CLOCK_RATE * 2),
|
||||
scsi_(scsi_bus_, CLOCK_RATE * 2),
|
||||
hard_drive_(scsi_bus_, 6 /* SCSI ID */),
|
||||
drives_{
|
||||
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke},
|
||||
{CLOCK_RATE, model >= Analyser::Static::Macintosh::Target::Model::Mac512ke}
|
||||
@ -94,7 +104,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
break;
|
||||
case Model::Mac512ke:
|
||||
case Model::MacPlus: {
|
||||
ram_size = 512*1024;
|
||||
ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024;
|
||||
rom_size = 128*1024;
|
||||
const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
|
||||
@ -102,7 +112,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
}
|
||||
ram_mask_ = (ram_size >> 1) - 1;
|
||||
rom_mask_ = (rom_size >> 1) - 1;
|
||||
video_.set_ram_mask(ram_mask_);
|
||||
ram_.resize(ram_size >> 1);
|
||||
video_.set_ram(ram_.data(), ram_mask_);
|
||||
|
||||
// Grab a copy of the ROM and convert it into big-endian data.
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
@ -113,7 +124,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
Memory::PackBigEndian16(*roms[0], rom_);
|
||||
|
||||
// Randomise memory contents.
|
||||
Memory::Fuzz(ram_, sizeof(ram_) / sizeof(*ram_));
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
// Attach the drives to the IWM.
|
||||
iwm_->set_drive(0, &drives_[0]);
|
||||
@ -127,6 +138,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
// Make sure interrupt changes from the SCC are observed.
|
||||
scc_.set_delegate(this);
|
||||
|
||||
// Also watch for changes in clocking requirement from the SCSI chip.
|
||||
if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
|
||||
scsi_bus_.set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
// The Mac runs at 7.8336mHz.
|
||||
set_clock_rate(double(CLOCK_RATE));
|
||||
audio_.speaker.set_input_rate(float(CLOCK_RATE) / 2.0f);
|
||||
@ -182,7 +198,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
// Grab the word-precision address being accessed.
|
||||
uint16_t *memory_base = nullptr;
|
||||
HalfCycles delay;
|
||||
switch(memory_map_[word_address >> 18]) {
|
||||
switch(memory_map_[word_address >> 16]) {
|
||||
default: assert(false);
|
||||
|
||||
case BusDevice::Unassigned:
|
||||
@ -232,6 +248,36 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
}
|
||||
} return delay;
|
||||
|
||||
case BusDevice::SCSI: {
|
||||
const int register_address = word_address >> 3;
|
||||
const bool dma_acknowledge = word_address & 0x100;
|
||||
|
||||
// Even accesses = read; odd = write.
|
||||
if(*cycle.address & 1) {
|
||||
// Odd access => this is a write. Data will be in the upper byte.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
scsi_.write(register_address, 0xff, dma_acknowledge);
|
||||
} else {
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge);
|
||||
} else {
|
||||
scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Even access => this is a read.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
const auto result = scsi_.read(register_address, dma_acknowledge);
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
// Data is loaded on the top part of the bus only.
|
||||
cycle.value->full = uint16_t((result << 8) | 0xff);
|
||||
} else {
|
||||
cycle.value->halves.low = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
} return delay;
|
||||
|
||||
case BusDevice::SCCReadResetPhase: {
|
||||
// Any word access here adjusts phase.
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
@ -280,7 +326,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
if(word_address > ram_mask_ - 0x6c80)
|
||||
update_video();
|
||||
|
||||
memory_base = ram_;
|
||||
memory_base = ram_.data();
|
||||
word_address &= ram_mask_;
|
||||
|
||||
// Apply a delay due to video contention if applicable; technically this is
|
||||
@ -348,12 +394,12 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
case Model::Mac128k:
|
||||
case Model::Mac512k:
|
||||
case Model::Mac512ke:
|
||||
populate_memory_map([rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
|
||||
populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
|
||||
// Addresses up to $80 0000 aren't affected by this bit.
|
||||
if(rom_is_overlay) {
|
||||
// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes.
|
||||
for(int c = 0; c <= 0x600000; c += 0x100000) {
|
||||
map_to(c, ((c >> 20)&1) ? BusDevice::ROM : BusDevice::Unassigned);
|
||||
for(int c = 0; c < 0x600000; c += 0x100000) {
|
||||
map_to(c + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM);
|
||||
}
|
||||
map_to(0x800000, BusDevice::RAM);
|
||||
} else {
|
||||
@ -365,19 +411,19 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
break;
|
||||
|
||||
case Model::MacPlus:
|
||||
populate_memory_map([rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
|
||||
populate_memory_map(0, [rom_is_overlay] (std::function<void(int target, BusDevice device)> map_to) {
|
||||
// Addresses up to $80 0000 aren't affected by this bit.
|
||||
if(rom_is_overlay) {
|
||||
map_to(0x100000, BusDevice::ROM);
|
||||
map_to(0x400000, BusDevice::Unassigned);
|
||||
map_to(0x500000, BusDevice::ROM);
|
||||
map_to(0x580000, BusDevice::Unassigned);
|
||||
for(int c = 0; c < 0x580000; c += 0x20000) {
|
||||
map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM);
|
||||
}
|
||||
map_to(0x600000, BusDevice::SCSI);
|
||||
map_to(0x800000, BusDevice::RAM);
|
||||
} else {
|
||||
map_to(0x400000, BusDevice::RAM);
|
||||
map_to(0x500000, BusDevice::ROM);
|
||||
map_to(0x580000, BusDevice::Unassigned);
|
||||
for(int c = 0x400000; c < 0x580000; c += 0x20000) {
|
||||
map_to(c + 0x20000, ((c & 0x100000) || (c & 0x20000)) ? BusDevice::Unassigned : BusDevice::ROM);
|
||||
}
|
||||
map_to(0x600000, BusDevice::SCSI);
|
||||
map_to(0x800000, BusDevice::Unassigned);
|
||||
}
|
||||
@ -396,16 +442,27 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
}
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) override {
|
||||
if(media.disks.empty())
|
||||
if(media.disks.empty() && media.mass_storage_devices.empty())
|
||||
return false;
|
||||
|
||||
// TODO: shouldn't allow disks to be replaced like this, as the Mac
|
||||
// uses software eject. Will need to expand messaging ability of
|
||||
// insert_media.
|
||||
if(!media.disks.empty()) {
|
||||
if(drives_[0].has_disk())
|
||||
drives_[1].set_disk(media.disks[0]);
|
||||
else
|
||||
drives_[0].set_disk(media.disks[0]);
|
||||
}
|
||||
|
||||
// TODO: allow this only at machine startup.
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
const auto volume = dynamic_cast<Storage::MassStorage::Encodings::Macintosh::Volume *>(media.mass_storage_devices.front().get());
|
||||
if(volume) {
|
||||
volume->set_drive_type(Storage::MassStorage::Encodings::Macintosh::DriveType::SCSI);
|
||||
}
|
||||
hard_drive_->set_storage(media.mass_storage_devices.front());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -446,6 +503,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
}
|
||||
|
||||
private:
|
||||
void set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference clocking) override {
|
||||
scsi_bus_is_clocked_ = scsi_bus_.preferred_clocking() != ClockingHint::Preference::None;
|
||||
}
|
||||
|
||||
void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) override {
|
||||
iwm_.flush();
|
||||
drives_[0].set_rotation_speed(speed);
|
||||
@ -533,6 +594,11 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, true);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::Two, false);
|
||||
}
|
||||
|
||||
// Update the SCSI if currently active.
|
||||
if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
|
||||
if(scsi_bus_is_clocked_) scsi_bus_.run_for(duration);
|
||||
}
|
||||
}
|
||||
|
||||
forceinline void update_video() {
|
||||
@ -672,6 +738,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
VIAPortHandler via_port_handler_;
|
||||
|
||||
Zilog::SCC::z8530 scc_;
|
||||
SCSI::Bus scsi_bus_;
|
||||
NCR::NCR5380::NCR5380 scsi_;
|
||||
SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_;
|
||||
bool scsi_bus_is_clocked_ = false;
|
||||
|
||||
HalfCycles via_clock_;
|
||||
HalfCycles real_time_clock_;
|
||||
@ -693,30 +763,20 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned
|
||||
};
|
||||
|
||||
/// Divides the 24-bit address space up into $80000 (i.e. 512kb) segments, recording
|
||||
/// Divides the 24-bit address space up into $20000 (i.e. 128kb) segments, recording
|
||||
/// which device is current mapped in each area. Keeping it in a table is a bit faster
|
||||
/// than the multi-level address inspection that is otherwise required, as well as
|
||||
/// simplifying slightly the handling of different models.
|
||||
///
|
||||
/// So: index with the top 5 bits of the 24-bit address.
|
||||
BusDevice memory_map_[32];
|
||||
/// So: index with the top 7 bits of the 24-bit address.
|
||||
BusDevice memory_map_[128];
|
||||
|
||||
void setup_memory_map() {
|
||||
// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true.
|
||||
// Apply the power-up memory map, i.e. assume that ROM_is_overlay_ = true;
|
||||
// start by calling into set_rom_is_overlay to seed everything up to $800000.
|
||||
set_rom_is_overlay(true);
|
||||
|
||||
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||
switch(model) {
|
||||
default: assert(false);
|
||||
|
||||
case Model::Mac128k:
|
||||
case Model::Mac512k:
|
||||
case Model::Mac512ke:
|
||||
populate_memory_map([] (std::function<void(int target, BusDevice device)> map_to) {
|
||||
// Up to $60 0000 mirrors of the ROM alternate with unassigned areas every $10 0000 byes.
|
||||
for(int c = 0; c <= 0x600000; c += 0x100000) {
|
||||
map_to(c, ((c >> 20)&1) ? BusDevice::ROM : BusDevice::Unassigned);
|
||||
}
|
||||
map_to(0x800000, BusDevice::RAM);
|
||||
populate_memory_map(0x800000, [] (std::function<void(int target, BusDevice device)> map_to) {
|
||||
map_to(0x900000, BusDevice::Unassigned);
|
||||
map_to(0xa00000, BusDevice::SCCReadResetPhase);
|
||||
map_to(0xb00000, BusDevice::Unassigned);
|
||||
@ -728,37 +788,14 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
map_to(0xf80000, BusDevice::PhaseRead);
|
||||
map_to(0x1000000, BusDevice::Unassigned);
|
||||
});
|
||||
break;
|
||||
|
||||
case Model::MacPlus:
|
||||
populate_memory_map([] (std::function<void(int target, BusDevice device)> map_to) {
|
||||
map_to(0x100000, BusDevice::ROM);
|
||||
map_to(0x400000, BusDevice::Unassigned);
|
||||
map_to(0x500000, BusDevice::ROM);
|
||||
map_to(0x580000, BusDevice::Unassigned);
|
||||
map_to(0x600000, BusDevice::SCSI);
|
||||
map_to(0x800000, BusDevice::RAM);
|
||||
map_to(0x900000, BusDevice::Unassigned);
|
||||
map_to(0xa00000, BusDevice::SCCReadResetPhase);
|
||||
map_to(0xb00000, BusDevice::Unassigned);
|
||||
map_to(0xc00000, BusDevice::SCCWrite);
|
||||
map_to(0xd00000, BusDevice::Unassigned);
|
||||
map_to(0xe00000, BusDevice::IWM);
|
||||
map_to(0xe80000, BusDevice::Unassigned);
|
||||
map_to(0xf00000, BusDevice::VIA);
|
||||
map_to(0xf80000, BusDevice::PhaseRead);
|
||||
map_to(0x1000000, BusDevice::Unassigned);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void populate_memory_map(std::function<void(std::function<void(int, BusDevice)>)> populator) {
|
||||
void populate_memory_map(int start_address, std::function<void(std::function<void(int, BusDevice)>)> populator) {
|
||||
// Define semantics for below; map_to will write from the current cursor position
|
||||
// to the supplied 24-bit address, setting a particular mapped device.
|
||||
int segment = 0;
|
||||
int segment = start_address >> 17;
|
||||
auto map_to = [&segment, this](int address, BusDevice device) {
|
||||
for(; segment < address >> 19; ++segment) {
|
||||
for(; segment < address >> 17; ++segment) {
|
||||
this->memory_map_[segment] = device;
|
||||
}
|
||||
};
|
||||
@ -768,8 +805,8 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
uint32_t ram_mask_ = 0;
|
||||
uint32_t rom_mask_ = 0;
|
||||
uint16_t rom_[64*1024];
|
||||
uint16_t ram_[256*1024];
|
||||
uint16_t rom_[64*1024]; // i.e. up to 128kb in size.
|
||||
std::vector<uint16_t> ram_;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -25,8 +25,15 @@ class RealTimeClock {
|
||||
public:
|
||||
RealTimeClock() {
|
||||
// TODO: this should persist, if possible, rather than
|
||||
// being randomly initialised.
|
||||
Memory::Fuzz(data_, sizeof(data_));
|
||||
// being default initialised.
|
||||
const uint8_t default_data[] = {
|
||||
0xa8, 0x00, 0x00, 0x00,
|
||||
0xcc, 0x0a, 0xcc, 0x0a,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x63, 0x00,
|
||||
0x03, 0x88, 0x00, 0x4c
|
||||
};
|
||||
memcpy(data_, default_data, sizeof(data_));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -33,11 +33,10 @@ const int sync_end = 38;
|
||||
// "The visible portion of a full-screen display consists of 342 horizontal scan lines...
|
||||
// During the vertical blanking interval, the turned-off beam ... traces out an additional 28 scan lines,"
|
||||
//
|
||||
Video::Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
|
||||
Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator) :
|
||||
audio_(audio),
|
||||
drive_speed_accumulator_(drive_speed_accumulator),
|
||||
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1),
|
||||
ram_(ram) {
|
||||
crt_(704, 1, 370, Outputs::Display::ColourSpace::YIQ, 1, 1, 6, false, Outputs::Display::InputDataType::Luminance1) {
|
||||
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
|
||||
@ -196,6 +195,7 @@ void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use
|
||||
use_alternate_audio_buffer_ = use_alternate_audio_buffer;
|
||||
}
|
||||
|
||||
void Video::set_ram_mask(uint32_t mask) {
|
||||
void Video::set_ram(uint16_t *ram, uint32_t mask) {
|
||||
ram_ = ram;
|
||||
ram_mask_ = mask;
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class Video {
|
||||
Constructs an instance of @c Video sourcing its pixel data from @c ram and
|
||||
providing audio and drive-speed bytes to @c audio and @c drive_speed_accumulator.
|
||||
*/
|
||||
Video(uint16_t *ram, DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator);
|
||||
Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulator);
|
||||
|
||||
/*!
|
||||
Sets the target device for video data.
|
||||
@ -47,10 +47,10 @@ class Video {
|
||||
void set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer);
|
||||
|
||||
/*!
|
||||
Provides a mask indicating which parts of the generated video and audio/drive addresses are
|
||||
Provides a base address and a mask indicating which parts of the generated video and audio/drive addresses are
|
||||
actually decoded, accessing *word-sized memory*; e.g. for a 128kb Macintosh this should be (1 << 16) - 1 = 0xffff.
|
||||
*/
|
||||
void set_ram_mask(uint32_t);
|
||||
void set_ram(uint16_t *ram, uint32_t mask);
|
||||
|
||||
/*!
|
||||
@returns @c true if the video is currently outputting a vertical sync, @c false otherwise.
|
||||
|
@ -26,7 +26,3 @@ void Memory::Fuzz(uint8_t *buffer, std::size_t size) {
|
||||
void Memory::Fuzz(uint16_t *buffer, std::size_t size) {
|
||||
Fuzz(reinterpret_cast<uint8_t *>(buffer), size * sizeof(uint16_t));
|
||||
}
|
||||
|
||||
void Memory::Fuzz(std::vector<uint8_t> &buffer) {
|
||||
Fuzz(buffer.data(), buffer.size());
|
||||
}
|
||||
|
@ -22,7 +22,9 @@ void Fuzz(uint8_t *buffer, std::size_t size);
|
||||
void Fuzz(uint16_t *buffer, std::size_t size);
|
||||
|
||||
/// Replaces all existing vector contents with random bytes.
|
||||
void Fuzz(std::vector<uint8_t> &buffer);
|
||||
template <typename T> void Fuzz(std::vector<T> &buffer) {
|
||||
Fuzz(reinterpret_cast<uint8_t *>(buffer.data()), buffer.size() * sizeof(buffer[0]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -216,12 +216,21 @@
|
||||
4B69FB441C4D941400B5F0AA /* TapeUEF.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB421C4D941400B5F0AA /* TapeUEF.cpp */; };
|
||||
4B69FB461C4D950F00B5F0AA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B69FB451C4D950F00B5F0AA /* libz.tbd */; };
|
||||
4B6A4C991F58F09E00E3F787 /* 6502Base.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */; };
|
||||
4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */; };
|
||||
4B6AAEAB230E40250078E864 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; };
|
||||
4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA7230E40250078E864 /* SCSI.cpp */; };
|
||||
4B6AAEAD230E40250078E864 /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA8230E40250078E864 /* Target.cpp */; };
|
||||
4B6AAEAE230E40250078E864 /* Target.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6AAEA8230E40250078E864 /* Target.cpp */; };
|
||||
4B6ED2F0208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; };
|
||||
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */; };
|
||||
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136841F78724F008B8ED9 /* Encoder.cpp */; };
|
||||
4B7136891F78725F008B8ED9 /* Shifter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7136871F78725F008B8ED9 /* Shifter.cpp */; };
|
||||
4B71368E1F788112008B8ED9 /* Parser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368C1F788112008B8ED9 /* Parser.cpp */; };
|
||||
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */; };
|
||||
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF802312FA9C00500CE8 /* HFV.cpp */; };
|
||||
4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF802312FA9C00500CE8 /* HFV.cpp */; };
|
||||
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */; };
|
||||
4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */; };
|
||||
4B7913CC1DFCD80E00175A82 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B7913CA1DFCD80E00175A82 /* Video.cpp */; };
|
||||
4B79A5011FC913C900EEDAD5 /* MSX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B79A4FF1FC913C900EEDAD5 /* MSX.cpp */; };
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */ = {isa = PBXBuildFile; fileRef = 4B79E4411E3AF38600141F11 /* cassette.png */; };
|
||||
@ -631,6 +640,8 @@
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC751B11D157E61006C31D9 /* 6522Tests.swift */; };
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */; };
|
||||
4BC76E6B1C98F43700E6EF73 /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */; };
|
||||
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
|
||||
4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */; };
|
||||
4BC91B831D1F160E00884B76 /* CommodoreTAP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */; };
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9DF4D1D04691600F44158 /* 6560.cpp */; };
|
||||
4BC9E1EE1D23449A003FCEE4 /* 6502InterruptTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */; };
|
||||
@ -669,6 +680,8 @@
|
||||
4BDA00E022E644AF00AC3CD0 /* CSROMReceiverView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */; };
|
||||
4BDA00E422E663B900AC3CD0 /* NSData+CRC32.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */; };
|
||||
4BDA00E622E699B000AC3CD0 /* CSMachine.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A53961D117D36003C6002 /* CSMachine.mm */; };
|
||||
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; };
|
||||
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */; };
|
||||
4BDB61EB2032806E0048AF91 /* CSAtari2600.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B2A539A1D117D36003C6002 /* CSAtari2600.mm */; };
|
||||
4BDB61EC203285AE0048AF91 /* Atari2600OptionsPanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8FE21F1DA19D7C0090D3CE /* Atari2600OptionsPanel.swift */; };
|
||||
4BDDBA991EF3451200347E61 /* Z80MachineCycleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */; };
|
||||
@ -953,6 +966,13 @@
|
||||
4B6A4C911F58F09E00E3F787 /* 6502AllRAM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502AllRAM.cpp; sourceTree = "<group>"; };
|
||||
4B6A4C921F58F09E00E3F787 /* 6502AllRAM.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = 6502AllRAM.hpp; sourceTree = "<group>"; };
|
||||
4B6A4C951F58F09E00E3F787 /* 6502Base.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = 6502Base.cpp; sourceTree = "<group>"; };
|
||||
4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MassStorageDevice.cpp; sourceTree = "<group>"; };
|
||||
4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MassStorageDevice.hpp; sourceTree = "<group>"; };
|
||||
4B6AAEA6230E40250078E864 /* Target.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4B6AAEA7230E40250078E864 /* SCSI.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SCSI.cpp; sourceTree = "<group>"; };
|
||||
4B6AAEA8230E40250078E864 /* Target.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Target.cpp; sourceTree = "<group>"; };
|
||||
4B6AAEA9230E40250078E864 /* SCSI.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SCSI.hpp; sourceTree = "<group>"; };
|
||||
4B6AAEAA230E40250078E864 /* TargetImplementation.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = TargetImplementation.hpp; sourceTree = "<group>"; };
|
||||
4B6ED2EE208E2F8A0047B343 /* WOZ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = WOZ.cpp; sourceTree = "<group>"; };
|
||||
4B6ED2EF208E2F8A0047B343 /* WOZ.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = WOZ.hpp; sourceTree = "<group>"; };
|
||||
4B7041271F92C26900735E45 /* JoystickMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = JoystickMachine.hpp; sourceTree = "<group>"; };
|
||||
@ -968,6 +988,10 @@
|
||||
4B71368D1F788112008B8ED9 /* Parser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Parser.hpp; sourceTree = "<group>"; };
|
||||
4B71368F1F789C93008B8ED9 /* SegmentParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SegmentParser.cpp; sourceTree = "<group>"; };
|
||||
4B7136901F789C93008B8ED9 /* SegmentParser.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SegmentParser.hpp; sourceTree = "<group>"; };
|
||||
4B74CF7F2312FA9C00500CE8 /* HFV.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = HFV.hpp; sourceTree = "<group>"; };
|
||||
4B74CF802312FA9C00500CE8 /* HFV.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HFV.cpp; sourceTree = "<group>"; };
|
||||
4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MacintoshVolume.cpp; path = Encodings/MacintoshVolume.cpp; sourceTree = "<group>"; };
|
||||
4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = MacintoshVolume.hpp; path = Encodings/MacintoshVolume.hpp; sourceTree = "<group>"; };
|
||||
4B77069C1EC904570053B588 /* Z80.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Z80.hpp; path = Z80/Z80.hpp; sourceTree = "<group>"; };
|
||||
4B770A961FE9EE770026DC70 /* CompoundSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CompoundSource.hpp; sourceTree = "<group>"; };
|
||||
4B7913CA1DFCD80E00175A82 /* Video.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Video.cpp; path = Electron/Video.cpp; sourceTree = "<group>"; };
|
||||
@ -1422,6 +1446,8 @@
|
||||
4BC76E671C98E31700E6EF73 /* FIRFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FIRFilter.cpp; sourceTree = "<group>"; };
|
||||
4BC76E681C98E31700E6EF73 /* FIRFilter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FIRFilter.hpp; sourceTree = "<group>"; };
|
||||
4BC76E6A1C98F43700E6EF73 /* Accelerate.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; };
|
||||
4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DirectAccessDevice.cpp; sourceTree = "<group>"; };
|
||||
4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DirectAccessDevice.hpp; sourceTree = "<group>"; };
|
||||
4BC91B811D1F160E00884B76 /* CommodoreTAP.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CommodoreTAP.cpp; sourceTree = "<group>"; };
|
||||
4BC91B821D1F160E00884B76 /* CommodoreTAP.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CommodoreTAP.hpp; sourceTree = "<group>"; };
|
||||
4BC9DF441D044FCA00F44158 /* ROMImages */ = {isa = PBXFileReference; lastKnownFileType = folder; name = ROMImages; path = ../../../../ROMImages; sourceTree = "<group>"; };
|
||||
@ -1481,6 +1507,8 @@
|
||||
4BDA00DF22E644AF00AC3CD0 /* CSROMReceiverView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSROMReceiverView.m; sourceTree = "<group>"; };
|
||||
4BDA00E222E663B900AC3CD0 /* NSData+CRC32.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+CRC32.m"; sourceTree = "<group>"; };
|
||||
4BDA00E322E663B900AC3CD0 /* NSData+CRC32.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+CRC32.h"; sourceTree = "<group>"; };
|
||||
4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ncr5380.cpp; sourceTree = "<group>"; };
|
||||
4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ncr5380.hpp; sourceTree = "<group>"; };
|
||||
4BDB3D8522833321002D3CEE /* Keyboard.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
|
||||
4BDCC5F81FB27A5E001220C5 /* ROMMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ROMMachine.hpp; sourceTree = "<group>"; };
|
||||
4BDDBA981EF3451200347E61 /* Z80MachineCycleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Z80MachineCycleTests.swift; sourceTree = "<group>"; };
|
||||
@ -2171,6 +2199,7 @@
|
||||
4BEE0A691D72496600532C7B /* Cartridge */,
|
||||
4B8805F81DCFF6CD003085B1 /* Data */,
|
||||
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
||||
4B6AAEA1230E3E1D0078E864 /* MassStorage */,
|
||||
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
||||
);
|
||||
name = Storage;
|
||||
@ -2234,6 +2263,33 @@
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B6AAEA1230E3E1D0078E864 /* MassStorage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */,
|
||||
4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */,
|
||||
4B74CF7E2312FA9C00500CE8 /* Formats */,
|
||||
4B6AAEA5230E40250078E864 /* SCSI */,
|
||||
4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */,
|
||||
4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */,
|
||||
);
|
||||
path = MassStorage;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B6AAEA5230E40250078E864 /* SCSI */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BC890D1230F86020025A55A /* DirectAccessDevice.cpp */,
|
||||
4B6AAEA7230E40250078E864 /* SCSI.cpp */,
|
||||
4B6AAEA8230E40250078E864 /* Target.cpp */,
|
||||
4BC890D2230F86020025A55A /* DirectAccessDevice.hpp */,
|
||||
4B6AAEA9230E40250078E864 /* SCSI.hpp */,
|
||||
4B6AAEA6230E40250078E864 /* Target.hpp */,
|
||||
4B6AAEAA230E40250078E864 /* TargetImplementation.hpp */,
|
||||
);
|
||||
path = SCSI;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B7136831F78724F008B8ED9 /* MFM */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -2252,6 +2308,15 @@
|
||||
path = Encodings/MFM;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B74CF7E2312FA9C00500CE8 /* Formats */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B74CF7F2312FA9C00500CE8 /* HFV.hpp */,
|
||||
4B74CF802312FA9C00500CE8 /* HFV.cpp */,
|
||||
);
|
||||
path = Formats;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B77069E1EC9045B0053B588 /* Z80 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -3111,6 +3176,7 @@
|
||||
4BC9DF4A1D04691600F44158 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BDACBE922FFA5B50045EF7E /* 5380 */,
|
||||
4BD468F81D8DF4290084958B /* 1770 */,
|
||||
4BC9DF4B1D04691600F44158 /* 6522 */,
|
||||
4B1E85791D174DEC001EF87D /* 6532 */,
|
||||
@ -3317,6 +3383,15 @@
|
||||
path = ROMRequester;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BDACBE922FFA5B50045EF7E /* 5380 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BDACBEA22FFA5D20045EF7E /* ncr5380.cpp */,
|
||||
4BDACBEB22FFA5D20045EF7E /* ncr5380.hpp */,
|
||||
);
|
||||
path = 5380;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -3938,6 +4013,7 @@
|
||||
4B8318B322D3E540006DB630 /* Audio.cpp in Sources */,
|
||||
4B055AAE1FAE85FD0060FFFF /* TrackSerialiser.cpp in Sources */,
|
||||
4B89452B201967B4007DE474 /* File.cpp in Sources */,
|
||||
4B6AAEAC230E40250078E864 /* SCSI.cpp in Sources */,
|
||||
4B055A981FAE85C50060FFFF /* Drive.cpp in Sources */,
|
||||
4BD424E62193B5830097291A /* Shader.cpp in Sources */,
|
||||
4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */,
|
||||
@ -3949,11 +4025,14 @@
|
||||
4B055AAC1FAE85FD0060FFFF /* PCMSegment.cpp in Sources */,
|
||||
4B055AB31FAE860F0060FFFF /* CSW.cpp in Sources */,
|
||||
4B89451D201967B4007DE474 /* Disk.cpp in Sources */,
|
||||
4BDACBED22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
|
||||
4B055ACF1FAE9B030060FFFF /* SoundGenerator.cpp in Sources */,
|
||||
4B894519201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
|
||||
4B055AEE1FAE9BBF0060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B055AED1FAE9BA20060FFFF /* Z80Storage.cpp in Sources */,
|
||||
4B1B88BC202E2EC100B67DFF /* MultiKeyboardMachine.cpp in Sources */,
|
||||
4BC890D4230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
||||
4B6AAEAE230E40250078E864 /* Target.cpp in Sources */,
|
||||
4BF437EF209D0F7E008CBD6B /* SegmentParser.cpp in Sources */,
|
||||
4B055AD11FAE9B030060FFFF /* Video.cpp in Sources */,
|
||||
4BB4BFBA22A4372F0069048D /* StaticAnalyser.cpp in Sources */,
|
||||
@ -3976,6 +4055,7 @@
|
||||
4B055AC91FAE9AFB0060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B055A991FAE85CB0060FFFF /* DiskController.cpp in Sources */,
|
||||
4B055ACC1FAE9B030060FFFF /* Electron.cpp in Sources */,
|
||||
4B74CF822312FA9C00500CE8 /* HFV.cpp in Sources */,
|
||||
4B8318B022D3E531006DB630 /* AppleII.cpp in Sources */,
|
||||
4B055AB11FAE86070060FFFF /* Tape.cpp in Sources */,
|
||||
4BFE7B881FC39D8900160B38 /* StandardOptions.cpp in Sources */,
|
||||
@ -4009,6 +4089,7 @@
|
||||
4B055ACD1FAE9B030060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B055AB21FAE860F0060FFFF /* CommodoreTAP.cpp in Sources */,
|
||||
4B055ADF1FAE9B4C0060FFFF /* IRQDelegatePortHandler.cpp in Sources */,
|
||||
4B74CF86231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
|
||||
4BD424E02193B5340097291A /* TextureTarget.cpp in Sources */,
|
||||
4B055AB51FAE860F0060FFFF /* TapePRG.cpp in Sources */,
|
||||
4B055AE01FAE9B660060FFFF /* CRT.cpp in Sources */,
|
||||
@ -4056,6 +4137,7 @@
|
||||
4B7A90E52041097C008514A2 /* ColecoVision.cpp in Sources */,
|
||||
4B2BFC5F1D613E0200BA3AA9 /* TapePRG.cpp in Sources */,
|
||||
4BC9DF4F1D04691600F44158 /* 6560.cpp in Sources */,
|
||||
4B6AAEAB230E40250078E864 /* SCSI.cpp in Sources */,
|
||||
4B59199C1DAC6C46005BB85C /* OricTAP.cpp in Sources */,
|
||||
4B9378E422A199C600973513 /* Audio.cpp in Sources */,
|
||||
4B89451E201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
@ -4065,6 +4147,7 @@
|
||||
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
|
||||
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
|
||||
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
|
||||
4B6AAEAD230E40250078E864 /* Target.cpp in Sources */,
|
||||
4B448E841F1C4C480009ABD6 /* PulseQueuedTape.cpp in Sources */,
|
||||
4B0E61071FF34737002A9DBD /* MSX.cpp in Sources */,
|
||||
4B4518A01F75FD1C00926311 /* CPCDSK.cpp in Sources */,
|
||||
@ -4104,8 +4187,10 @@
|
||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
|
||||
4B894518201967B4007DE474 /* ConfidenceCounter.cpp in Sources */,
|
||||
4BCE005A227CFFCA000CA200 /* Macintosh.cpp in Sources */,
|
||||
4B6AAEA4230E3E1D0078E864 /* MassStorageDevice.cpp in Sources */,
|
||||
4B89452E201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BD5D2682199148100DDF17D /* ScanTargetGLSLFragments.cpp in Sources */,
|
||||
4BC890D3230F86020025A55A /* DirectAccessDevice.cpp in Sources */,
|
||||
4B38F3481F2EC11D00D9235D /* AmstradCPC.cpp in Sources */,
|
||||
4B8FE2221DA19FB20090D3CE /* MachinePanel.swift in Sources */,
|
||||
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */,
|
||||
@ -4119,6 +4204,7 @@
|
||||
4BC76E691C98E31700E6EF73 /* FIRFilter.cpp in Sources */,
|
||||
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */,
|
||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */,
|
||||
4B74CF85231370BC00500CE8 /* MacintoshVolume.cpp in Sources */,
|
||||
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */,
|
||||
4B55CE5F1C3B7D960093A61B /* MachineDocument.swift in Sources */,
|
||||
4B2B3A4C1F9B8FA70062DABF /* MemoryFuzzer.cpp in Sources */,
|
||||
@ -4165,6 +4251,7 @@
|
||||
4BB4BFAD22A33DE50069048D /* DriveSpeedAccumulator.cpp in Sources */,
|
||||
4B2B3A4B1F9B8FA70062DABF /* Typer.cpp in Sources */,
|
||||
4B4518821F75E91A00926311 /* PCMSegment.cpp in Sources */,
|
||||
4B74CF812312FA9C00500CE8 /* HFV.cpp in Sources */,
|
||||
4B894522201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4B17B58B20A8A9D9007CCA8F /* StringSerialiser.cpp in Sources */,
|
||||
4BE7C9181E3D397100A5496D /* TIA.cpp in Sources */,
|
||||
@ -4180,6 +4267,7 @@
|
||||
4BBB70A4202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
||||
4B89453A201967B4007DE474 /* StaticAnalyser.cpp in Sources */,
|
||||
4BB697CB1D4B6D3E00248BDF /* TimedEventLoop.cpp in Sources */,
|
||||
4BDACBEC22FFA5D20045EF7E /* ncr5380.cpp in Sources */,
|
||||
4B54C0C21F8D91CD0050900F /* Keyboard.cpp in Sources */,
|
||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||
|
@ -21,6 +21,9 @@
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
Models a flopy disk.
|
||||
*/
|
||||
class Disk {
|
||||
public:
|
||||
virtual ~Disk() {}
|
||||
|
@ -138,7 +138,7 @@ class Drive: public ClockingHint::Source, public TimedEventLoop {
|
||||
void set_event_delegate(EventDelegate *);
|
||||
|
||||
// As per Sleeper.
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
|
||||
/// Adds an activity observer; it'll be notified of disk activity.
|
||||
/// The caller can specify whether to add an LED based on disk motor.
|
||||
|
301
Storage/MassStorage/Encodings/MacintoshVolume.cpp
Normal file
301
Storage/MassStorage/Encodings/MacintoshVolume.cpp
Normal file
@ -0,0 +1,301 @@
|
||||
//
|
||||
// MacintoshVolume.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MacintoshVolume.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
using namespace Storage::MassStorage::Encodings::Macintosh;
|
||||
|
||||
void Mapper::set_drive_type(DriveType drive_type, size_t number_of_blocks) {
|
||||
drive_type_ = drive_type;
|
||||
number_of_blocks_ = number_of_blocks;
|
||||
}
|
||||
|
||||
size_t Mapper::get_number_of_blocks() {
|
||||
return number_of_blocks_ + 0x60;
|
||||
}
|
||||
|
||||
ssize_t Mapper::to_source_address(size_t address) {
|
||||
return ssize_t(address) - 0x60;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> Mapper::convert_source_block(ssize_t source_address, std::vector<uint8_t> source_data) {
|
||||
// Addresses greater than or equal to zero map to the actual disk image.
|
||||
if(source_address >= 0) return source_data;
|
||||
|
||||
// Switch to mapping relative to 0, for personal sanity.
|
||||
source_address += 0x60;
|
||||
|
||||
// Block 0 is the device descriptor, which lists the total number of blocks,
|
||||
// and provides an offset to the driver.
|
||||
if(!source_address) {
|
||||
uint32_t total_device_blocks = uint32_t(number_of_blocks_ + 0x60);
|
||||
|
||||
/* The driver descriptor. */
|
||||
std::vector<uint8_t> driver_description = {
|
||||
0x45, 0x52, /* device signature */
|
||||
0x02, 0x00, /* block size, in bytes */
|
||||
|
||||
uint8_t(total_device_blocks >> 24),
|
||||
uint8_t(total_device_blocks >> 16),
|
||||
uint8_t(total_device_blocks >> 8),
|
||||
uint8_t(total_device_blocks),
|
||||
/* number of blocks on device */
|
||||
|
||||
0x00, 0x01, /* reserved (formerly: device type) */
|
||||
0x00, 0x01, /* reserved (formerly: device ID) */
|
||||
0x00, 0x00,
|
||||
0x00, 0x00, /* reserved ('sbData', no further explanation given) */
|
||||
|
||||
0x00, 0x01, /* number of device descriptor entries */
|
||||
0x00, 0x00,
|
||||
0x00, 0x40, /* first device descriptor's starting block */
|
||||
0x00, 0x0a, /* size of device driver */
|
||||
0x00, 0x01, /*
|
||||
More modern documentation: operating system (MacOS = 1)
|
||||
Inside Macintosh IV: system type (Mac Plus = 1)
|
||||
*/
|
||||
};
|
||||
driver_description.resize(512);
|
||||
|
||||
return driver_description;
|
||||
}
|
||||
|
||||
// Blocks 1, 2 and 3 contain parts of the partition map.
|
||||
if(source_address < 4) {
|
||||
struct Partition {
|
||||
const char *name, *type;
|
||||
uint32_t start_block, size;
|
||||
uint8_t status;
|
||||
} partitions[3] = {
|
||||
{ "MacOS", "Apple_HFS", 0x60, uint32_t(number_of_blocks_), 0xb7 },
|
||||
{ "Apple", "Apple_partition_map", 0x01, 0x3f, 0x37 },
|
||||
{ "Macintosh", "Apple_Driver", 0x40, 0x20, 0x7f },
|
||||
};
|
||||
|
||||
std::vector<uint8_t> partition(512);
|
||||
|
||||
// Fill in the fixed fields.
|
||||
partition[0] = 'P'; partition[1] = 'M'; /* Signature. */
|
||||
partition[7] = 3; /* Number of partitions. */
|
||||
|
||||
const Partition &details = partitions[source_address-1];
|
||||
|
||||
partition[8] = uint8_t(details.start_block >> 24);
|
||||
partition[9] = uint8_t(details.start_block >> 16);
|
||||
partition[10] = uint8_t(details.start_block >> 8);
|
||||
partition[11] = uint8_t(details.start_block);
|
||||
|
||||
partition[84] = partition[12] = uint8_t(details.size >> 24);
|
||||
partition[85] = partition[13] = uint8_t(details.size >> 16);
|
||||
partition[86] = partition[14] = uint8_t(details.size >> 8);
|
||||
partition[87] = partition[15] = uint8_t(details.size);
|
||||
|
||||
// 32 bytes are allocated for each of the following strings.
|
||||
memcpy(&partition[16], details.name, strlen(details.name));
|
||||
memcpy(&partition[48], details.type, strlen(details.type));
|
||||
|
||||
partition[91] = details.status;
|
||||
|
||||
// The third entry in this constructed partition map is the driver;
|
||||
// add some additional details.
|
||||
if(source_address == 3) {
|
||||
partition[98] = 0x13;
|
||||
partition[99] = 0x9e; /* This version of the driver code is 0x139e bytes long. */
|
||||
|
||||
partition[118] = 0x84;
|
||||
partition[119] = 0xb9; /* Driver checksum. */
|
||||
|
||||
memcpy(&partition[120], "68000", strlen("68000")); /* Driver is for the 68000. */
|
||||
|
||||
// Various non-zero values that Apple HD SC Tool wrote are below; they are
|
||||
// documented as reserved officially, so I don't know their meaning.
|
||||
partition[137] = 0x01;
|
||||
partition[138] = 0x06;
|
||||
partition[143] = 0x01;
|
||||
partition[147] = 0x02;
|
||||
partition[149] = 0x07;
|
||||
}
|
||||
|
||||
return partition;
|
||||
}
|
||||
|
||||
// The ten blocks starting at 0x40 contain the SCSI driver.
|
||||
if(source_address >= 0x40 && source_address < 0x40 + 10) {
|
||||
// This is the body of the SCSI driver.
|
||||
const uint8_t driver[] = {
|
||||
0x60, 0x00, 0x01, 0xba, 0x23, 0x24, 0x00, 0x01, 0x00, 0x27, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x13, 0x9e, 0x00, 0x00, 0xda, 0xe7, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56,
|
||||
0x00, 0x7c, 0x00, 0x68, 0x00, 0x72, 0x00, 0x5e, 0x07, 0x2e, 0x53, 0x43, 0x53, 0x49, 0x30, 0x30, 0x41, 0xbd, 0x30, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x1f, 0x60, 0x00,
|
||||
0x02, 0x12, 0x41, 0xfa, 0xff, 0xf4, 0x20, 0xaf, 0x00, 0x04, 0x4e, 0x75, 0x20, 0x3a, 0xff, 0xea, 0x4e, 0x75, 0x20, 0x0d, 0x2a, 0x7a, 0xff, 0xe2, 0x4e, 0x75, 0x41, 0xfa, 0xff, 0xd8, 0x30, 0xaf,
|
||||
0x00, 0x06, 0x4e, 0x75, 0x48, 0x7a, 0x00, 0x4e, 0x20, 0x1f, 0x4e, 0x75, 0x70, 0x00, 0x31, 0x40, 0x00, 0x10, 0x60, 0x2a, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x05, 0x4a, 0x60, 0x1c, 0x2f, 0x08,
|
||||
0x2f, 0x09, 0x4e, 0xba, 0x07, 0x82, 0x60, 0x12, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x08, 0xca, 0x60, 0x08, 0x2f, 0x08, 0x2f, 0x09, 0x4e, 0xba, 0x05, 0xf2, 0x22, 0x5f, 0x20, 0x5f, 0x4a, 0x40,
|
||||
0x67, 0x04, 0x31, 0xc0, 0x01, 0x42, 0x08, 0x28, 0x00, 0x09, 0x00, 0x06, 0x66, 0x04, 0x2f, 0x38, 0x08, 0xfc, 0x4e, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe,
|
||||
0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01, 0x88, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01,
|
||||
0x80, 0x00, 0x00, 0x01, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfe,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x53, 0x43, 0x53, 0x49, 0x20, 0x30, 0x00, 0x48, 0xe7, 0x3f, 0x3e,
|
||||
0x49, 0xfa, 0xfe, 0x50, 0x26, 0x14, 0x42, 0x94, 0x70, 0x00, 0x42, 0x42, 0x28, 0x3a, 0xfe, 0x40, 0x53, 0x44, 0x49, 0xfa, 0xfe, 0x2c, 0x14, 0x1c, 0xd0, 0x42, 0xe3, 0x58, 0x51, 0xcc, 0xff, 0xf8,
|
||||
0x4a, 0x40, 0x66, 0x02, 0x53, 0x40, 0x49, 0xfa, 0xfe, 0x2a, 0x28, 0x80, 0xb6, 0x80, 0x67, 0x0e, 0x49, 0xfa, 0xfe, 0x20, 0x38, 0x83, 0x4c, 0xdf, 0x7c, 0xfc, 0x70, 0xff, 0x4e, 0x75, 0x28, 0x48,
|
||||
0x41, 0xfa, 0xfd, 0xfe, 0x20, 0x3a, 0xfe, 0x08, 0xa0, 0x20, 0x70, 0x06, 0x22, 0x78, 0x01, 0x1c, 0xd2, 0xfc, 0x00, 0x9c, 0x24, 0x21, 0x67, 0x1e, 0x24, 0x42, 0x20, 0x52, 0x24, 0x50, 0x0c, 0xaa,
|
||||
0x41, 0xbd, 0x30, 0xb0, 0x00, 0x1a, 0x66, 0x0e, 0xb6, 0xaa, 0xff, 0xfc, 0x66, 0x08, 0x41, 0xfa, 0xfd, 0xd0, 0x4e, 0xea, 0x00, 0x26, 0x51, 0xc8, 0xff, 0xdc, 0x4a, 0x85, 0x6b, 0x14, 0x20, 0x78,
|
||||
0x02, 0xa6, 0x20, 0x50, 0xd0, 0xfc, 0x1f, 0x40, 0x2f, 0x38, 0x01, 0x18, 0xa0, 0x57, 0x21, 0xdf, 0x01, 0x18, 0x70, 0x20, 0xd0, 0x45, 0x38, 0x00, 0x46, 0x40, 0xa0, 0x3d, 0xe5, 0x44, 0x22, 0x78,
|
||||
0x01, 0x1c, 0x20, 0x71, 0x40, 0x00, 0x2c, 0x50, 0xa0, 0x29, 0x41, 0xfa, 0xfd, 0xaa, 0x2c, 0x88, 0x3d, 0x58, 0x00, 0x04, 0x2d, 0x58, 0x00, 0x22, 0x3d, 0x58, 0x00, 0x26, 0x08, 0xee, 0x00, 0x05,
|
||||
0x00, 0x05, 0x08, 0xae, 0x00, 0x06, 0x00, 0x05, 0x70, 0x01, 0x08, 0x05, 0x00, 0x1e, 0x67, 0x02, 0x70, 0x00, 0x2f, 0x00, 0x2f, 0x0e, 0x4e, 0xba, 0x00, 0x3c, 0x50, 0x8f, 0x4a, 0x40, 0x67, 0x02,
|
||||
0x70, 0xe9, 0x4c, 0xdf, 0x7c, 0xfc, 0x4e, 0x75, 0x2f, 0x02, 0x42, 0x67, 0x2f, 0x2f, 0x00, 0x0a, 0x3f, 0x3c, 0x00, 0x01, 0x3f, 0x3c, 0x00, 0x03, 0xa8, 0x95, 0x54, 0x8f, 0x24, 0x1f, 0x4e, 0x75,
|
||||
0x2f, 0x02, 0x42, 0x67, 0x2f, 0x2f, 0x00, 0x0a, 0x3f, 0x3c, 0x00, 0x04, 0xa8, 0x95, 0x54, 0x8f, 0x24, 0x1f, 0x4e, 0x75, 0x4e, 0x56, 0xfd, 0xf4, 0x48, 0xe7, 0x3e, 0x30, 0x26, 0x6e, 0x00, 0x08,
|
||||
0x24, 0x2e, 0x00, 0x0c, 0x4e, 0xba, 0xfd, 0x66, 0x4a, 0x80, 0x67, 0x0c, 0x4e, 0xba, 0xfd, 0x64, 0x2d, 0x40, 0xff, 0xf6, 0x60, 0x00, 0x00, 0x9a, 0x4e, 0xba, 0x0e, 0xde, 0x28, 0x00, 0x4e, 0xba,
|
||||
0x0e, 0xe8, 0x2f, 0x00, 0x4e, 0xba, 0x0e, 0xd8, 0x4e, 0xba, 0x0f, 0x92, 0x26, 0x00, 0x2f, 0x04, 0x4e, 0xba, 0x0e, 0xcc, 0x4a, 0x83, 0x50, 0x8f, 0x66, 0x06, 0x70, 0xff, 0x60, 0x00, 0x02, 0x9c,
|
||||
0x2f, 0x03, 0x4e, 0xba, 0x0f, 0x48, 0x2f, 0x03, 0x4e, 0xba, 0xfd, 0x18, 0x4e, 0xba, 0xfd, 0x24, 0x2d, 0x40, 0xff, 0xf6, 0x20, 0x38, 0x02, 0xae, 0x50, 0x80, 0x24, 0x40, 0x1b, 0x52, 0xfc, 0x70,
|
||||
0x50, 0x8f, 0x66, 0x06, 0x42, 0x2d, 0xfc, 0x74, 0x60, 0x14, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x74, 0x0c, 0x2d, 0x00, 0x03, 0xfc, 0x70, 0x65, 0x06, 0x1b, 0x7c, 0x00, 0x02, 0xfc, 0x70, 0x42, 0x6d,
|
||||
0xfc, 0x88, 0x42, 0xa7, 0x4e, 0xba, 0xfc, 0xf4, 0x42, 0x6d, 0xfc, 0x8c, 0x42, 0x2d, 0xfc, 0x78, 0x78, 0x00, 0x58, 0x8f, 0x60, 0x0e, 0x20, 0x04, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x42, 0x70,
|
||||
0x08, 0x00, 0x52, 0x84, 0x70, 0x08, 0xb0, 0x84, 0x62, 0x00, 0xff, 0xec, 0x42, 0x2d, 0xfc, 0x7c, 0x36, 0x2b, 0x00, 0x18, 0x46, 0x43, 0x04, 0x43, 0x00, 0x20, 0x72, 0x00, 0x32, 0x03, 0x20, 0x01,
|
||||
0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x45, 0xed, 0xfc, 0xa0, 0xd5, 0xc1, 0x4e, 0xba, 0xfc, 0x94, 0x27, 0x40, 0x00, 0x14, 0x42, 0xa7,
|
||||
0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x4e, 0xba, 0x07, 0x62, 0x42, 0x46, 0x42, 0x6e, 0xff, 0xfe, 0x7a, 0x01, 0x2d, 0x45, 0xff, 0xfa, 0x78, 0x01, 0x50, 0x8f, 0x60, 0x00, 0x01, 0x50, 0x48, 0x6e,
|
||||
0xfd, 0xf6, 0x2f, 0x04, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x4e, 0xba, 0x08, 0x58, 0x3a, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x67, 0x08, 0x0c, 0x45, 0xff, 0xfc,
|
||||
0x66, 0x00, 0x01, 0x28, 0x0c, 0x6e, 0x50, 0x4d, 0xfd, 0xf6, 0x66, 0x00, 0x01, 0x1e, 0x70, 0x01, 0xb0, 0x84, 0x66, 0x06, 0x2d, 0x6e, 0xfd, 0xfa, 0xff, 0xfa, 0x4a, 0x6e, 0xff, 0xfe, 0x66, 0x4e,
|
||||
0x41, 0xee, 0xfe, 0x26, 0x0c, 0x90, 0x41, 0x70, 0x70, 0x6c, 0x66, 0x42, 0x41, 0xee, 0xfe, 0x2a, 0x0c, 0x90, 0x65, 0x5f, 0x44, 0x72, 0x66, 0x36, 0x0c, 0xae, 0x00, 0x01, 0x06, 0x00, 0xfe, 0x7e,
|
||||
0x66, 0x2c, 0x72, 0x01, 0x3d, 0x41, 0xff, 0xfe, 0x70, 0x00, 0x30, 0x03, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x31, 0xae, 0xfe, 0x8c, 0x08, 0x00, 0x35, 0x6e, 0xfe, 0x84, 0x00, 0x2c, 0x15, 0x6e,
|
||||
0xfe, 0x89, 0x00, 0x25, 0x15, 0x6e, 0xfe, 0x8b, 0x00, 0x27, 0x60, 0x00, 0x00, 0xbe, 0x4a, 0x46, 0x66, 0x00, 0x00, 0xb8, 0x41, 0xee, 0xfe, 0x26, 0x0c, 0x90, 0x41, 0x70, 0x70, 0x6c, 0x66, 0x00,
|
||||
0x00, 0xaa, 0x41, 0xee, 0xfe, 0x2a, 0x0c, 0x90, 0x65, 0x5f, 0x48, 0x46, 0x66, 0x00, 0x00, 0x9c, 0x7c, 0x01, 0x7a, 0x08, 0x20, 0x78, 0x03, 0x0a, 0x60, 0x0e, 0x30, 0x28, 0x00, 0x06, 0xb0, 0x45,
|
||||
0x66, 0x04, 0x52, 0x45, 0x60, 0xee, 0x20, 0x50, 0x20, 0x08, 0x66, 0x00, 0xff, 0xee, 0x42, 0x52, 0x08, 0x2e, 0x00, 0x05, 0xfe, 0x51, 0x67, 0x04, 0x70, 0x00, 0x60, 0x06, 0x20, 0x3c, 0x00, 0x00,
|
||||
0x00, 0x80, 0x15, 0x40, 0x00, 0x02, 0x15, 0x7c, 0x00, 0x08, 0x00, 0x03, 0x15, 0x7c, 0x00, 0x01, 0x00, 0x04, 0x42, 0x2a, 0x00, 0x05, 0x35, 0x7c, 0x00, 0x01, 0x00, 0x0a, 0x42, 0xaa, 0x00, 0x06,
|
||||
0x42, 0x6a, 0x00, 0x10, 0x35, 0x6e, 0xfe, 0x4c, 0x00, 0x12, 0x20, 0x2e, 0xfe, 0x4a, 0x72, 0x10, 0xe2, 0xa8, 0x35, 0x40, 0x00, 0x14, 0x20, 0x2e, 0xfe, 0x46, 0xd0, 0xae, 0xfd, 0xfe, 0x25, 0x40,
|
||||
0x00, 0x18, 0x25, 0x40, 0x00, 0x1c, 0x25, 0x6e, 0xfe, 0x4a, 0x00, 0x20, 0x35, 0x45, 0x00, 0x16, 0x48, 0x6a, 0x00, 0x06, 0x70, 0x00, 0x30, 0x05, 0x2f, 0x00, 0x30, 0x2b, 0x00, 0x18, 0x48, 0xc0,
|
||||
0x2f, 0x00, 0x4e, 0xba, 0x0c, 0x98, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x84, 0xb8, 0xae, 0xff, 0xfa, 0x63, 0x00, 0xfe, 0xac, 0x4a, 0x6e, 0xff, 0xfe, 0x66, 0x06, 0x7a, 0xfe, 0x60, 0x00, 0x00, 0x6e,
|
||||
0x4a, 0x46, 0x66, 0x04, 0x42, 0x45, 0x60, 0x64, 0x70, 0x00, 0x30, 0x03, 0x41, 0xed, 0xfc, 0x80, 0x11, 0xbc, 0x00, 0x01, 0x08, 0x00, 0x70, 0x01, 0x12, 0x03, 0xe3, 0xa0, 0x36, 0x00, 0x4a, 0x82,
|
||||
0x67, 0x06, 0x30, 0x03, 0x81, 0x6d, 0xfc, 0x8c, 0x4a, 0x6d, 0xfc, 0x88, 0x66, 0x28, 0x3b, 0x7c, 0x00, 0x01, 0xfc, 0x64, 0x41, 0xfa, 0x04, 0x58, 0x2b, 0x48, 0xfc, 0x66, 0x3b, 0x7c, 0x00, 0x65,
|
||||
0xfc, 0x6a, 0x42, 0x6d, 0xfc, 0x6c, 0x48, 0x6d, 0xfc, 0x60, 0x4e, 0xba, 0x0c, 0xc0, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x7c, 0x58, 0x8f, 0x30, 0x03, 0x81, 0x6d, 0xfc, 0x88, 0x70, 0x00, 0x30, 0x2d,
|
||||
0xfc, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0xfa, 0xb4, 0x42, 0x45, 0x58, 0x8f, 0x2f, 0x2e, 0xff, 0xf6, 0x4e, 0xba, 0x0c, 0xe4, 0x30, 0x05, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xee, 0x0c, 0x7c, 0xfd, 0xd8,
|
||||
0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x38, 0x20, 0x24, 0x6f, 0x00, 0x14, 0x20, 0x2f, 0x00, 0x18, 0x4e, 0xba, 0xfa, 0x80, 0x28, 0x00, 0x34, 0x2a, 0x00, 0x18, 0x46, 0x42, 0x04, 0x42, 0x00, 0x20,
|
||||
0x70, 0x01, 0x12, 0x02, 0xe3, 0xa0, 0x46, 0x40, 0xc1, 0x6d, 0xfc, 0x88, 0x70, 0x00, 0x30, 0x2d, 0xfc, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0xfa, 0x64, 0x36, 0x2d, 0xfc, 0x88, 0x48, 0x78, 0x03, 0x08,
|
||||
0x32, 0x02, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0x48, 0x70, 0x18, 0x06, 0x4e, 0xba,
|
||||
0x0c, 0x04, 0x4a, 0x43, 0x4f, 0xef, 0x00, 0x0c, 0x66, 0x22, 0x4a, 0x2d, 0xfc, 0x7c, 0x67, 0x0a, 0x48, 0x6d, 0xfc, 0x60, 0x4e, 0xba, 0x0c, 0x20, 0x58, 0x8f, 0x4a, 0x2d, 0xfc, 0x78, 0x67, 0x0c,
|
||||
0x45, 0xfa, 0x04, 0x40, 0x2f, 0x0a, 0x4e, 0xba, 0xfc, 0x78, 0x58, 0x8f, 0x2f, 0x04, 0x4e, 0xba, 0x0c, 0x46, 0x4a, 0x43, 0x58, 0x8f, 0x66, 0x2a, 0x4e, 0xba, 0x0b, 0x7e, 0x24, 0x00, 0x4e, 0xba,
|
||||
0x0b, 0x88, 0x2f, 0x00, 0x4e, 0xba, 0x0b, 0x78, 0x4e, 0xba, 0xf9, 0xe2, 0x2f, 0x00, 0x4e, 0xba, 0x0c, 0x3c, 0x2f, 0x02, 0x4e, 0xba, 0x0b, 0x68, 0x42, 0xa7, 0x4e, 0xba, 0xf9, 0xc6, 0x4f, 0xef,
|
||||
0x00, 0x10, 0x70, 0x00, 0x4c, 0xdf, 0x04, 0x1c, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf0, 0x48, 0xe7, 0x3e, 0x38, 0x24, 0x2e, 0x00, 0x08, 0x24, 0x6e, 0x00, 0x0c, 0x4e, 0xba, 0xf9, 0xb6, 0x2d, 0x40,
|
||||
0xff, 0xf2, 0x28, 0x42, 0x3c, 0x2c, 0x00, 0x18, 0x46, 0x46, 0x04, 0x46, 0x00, 0x20, 0x32, 0x06, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40,
|
||||
0x42, 0x40, 0xd2, 0x80, 0x47, 0xed, 0xfc, 0xa0, 0xd7, 0xc1, 0x78, 0x00, 0x0c, 0x2a, 0x00, 0x03, 0x00, 0x07, 0x57, 0xc4, 0x44, 0x04, 0x28, 0x42, 0x2d, 0x6c, 0x00, 0x10, 0xff, 0xfc, 0x70, 0x09,
|
||||
0x26, 0x2e, 0xff, 0xfc, 0xe0, 0xa3, 0x2d, 0x43, 0xff, 0xfc, 0x2d, 0x6a, 0x00, 0x24, 0xff, 0xf8, 0x70, 0x09, 0x26, 0x2e, 0xff, 0xf8, 0xe0, 0xa3, 0x2d, 0x43, 0xff, 0xf8, 0x30, 0x2b, 0x00, 0x16,
|
||||
0xb0, 0x6a, 0x00, 0x16, 0x67, 0x06, 0x76, 0xc8, 0x60, 0x00, 0x00, 0xe4, 0x20, 0x2a, 0x00, 0x24, 0x02, 0x80, 0x00, 0x00, 0x01, 0xff, 0x66, 0x00, 0x00, 0x18, 0x4a, 0xab, 0x00, 0x1c, 0x67, 0x16,
|
||||
0x20, 0x2b, 0x00, 0x20, 0x22, 0x2e, 0xff, 0xf8, 0xd2, 0xae, 0xff, 0xfc, 0xb0, 0x81, 0x64, 0x06, 0x76, 0xce, 0x60, 0x00, 0x00, 0xba, 0x30, 0x06, 0x48, 0xc0, 0x41, 0xed, 0xfc, 0x80, 0x4a, 0x30,
|
||||
0x08, 0x00, 0x67, 0x20, 0x30, 0x06, 0x48, 0xc0, 0x41, 0xed, 0xfc, 0x80, 0x42, 0x30, 0x08, 0x00, 0x70, 0x00, 0x10, 0x2d, 0xfc, 0x74, 0x2f, 0x00, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba,
|
||||
0x03, 0xca, 0x50, 0x8f, 0x08, 0x2a, 0x00, 0x06, 0x00, 0x2d, 0x67, 0x0a, 0x4a, 0x44, 0x66, 0x06, 0x42, 0x43, 0x60, 0x00, 0x00, 0x6c, 0x42, 0x6e, 0xff, 0xf6, 0x7a, 0xff, 0x2f, 0x2a, 0x00, 0x20,
|
||||
0x20, 0x2b, 0x00, 0x1c, 0xd0, 0xae, 0xff, 0xfc, 0x2f, 0x00, 0x2f, 0x2e, 0xff, 0xf8, 0x30, 0x04, 0x48, 0xc0, 0x2f, 0x00, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba, 0x04, 0xae, 0x36, 0x00,
|
||||
0x4f, 0xef, 0x00, 0x14, 0x67, 0x3a, 0x53, 0x43, 0x66, 0x10, 0x2f, 0x2b, 0x00, 0x28, 0x30, 0x06, 0x48, 0xc0, 0x2f, 0x00, 0x4e, 0xba, 0x07, 0x42, 0x50, 0x8f, 0xba, 0xab, 0x00, 0x28, 0x66, 0x06,
|
||||
0x52, 0x6e, 0xff, 0xf6, 0x60, 0x08, 0x42, 0x6e, 0xff, 0xf6, 0x2a, 0x2b, 0x00, 0x28, 0x0c, 0x6e, 0x00, 0x04, 0xff, 0xf6, 0x6d, 0x00, 0xff, 0xa6, 0x76, 0xdc, 0x42, 0xaa, 0x00, 0x28, 0x60, 0x0e,
|
||||
0x20, 0x2a, 0x00, 0x24, 0x25, 0x40, 0x00, 0x28, 0x20, 0x42, 0xd1, 0xa8, 0x00, 0x10, 0x2f, 0x2e, 0xff, 0xf2, 0x4e, 0xba, 0x0a, 0xa2, 0x30, 0x03, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xee, 0x1c, 0x7c,
|
||||
0xff, 0xd0, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xfd, 0xfc, 0x48, 0xe7, 0x3c, 0x30, 0x26, 0x6e, 0x00, 0x08, 0x24, 0x6e, 0x00, 0x0c, 0x4e, 0xba, 0xf8, 0x3a, 0x2a, 0x00, 0x38, 0x2b, 0x00, 0x18,
|
||||
0x46, 0x44, 0x04, 0x44, 0x00, 0x20, 0x30, 0x04, 0x48, 0xc0, 0x24, 0x00, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x42, 0xc4, 0xfc, 0x00, 0x6c, 0x48, 0x42, 0x42, 0x42, 0xd0, 0x82, 0x41, 0xed, 0xfc, 0xa0,
|
||||
0xd1, 0xc0, 0x42, 0x43, 0x0c, 0x6a, 0x00, 0x41, 0x00, 0x1a, 0x66, 0x44, 0x4a, 0x2d, 0xfc, 0x78, 0x66, 0x34, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x9f, 0x4e, 0xba, 0x09, 0xda, 0x24, 0x00,
|
||||
0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x95, 0x4e, 0xba, 0x09, 0xcc, 0xb4, 0x80, 0x4f, 0xef, 0x00, 0x10, 0x67, 0x12, 0x45, 0xfa, 0x02, 0x0c, 0x2f, 0x0a, 0x4e, 0xba, 0xfa, 0x2c, 0x1b, 0x7c,
|
||||
0x00, 0x01, 0xfc, 0x78, 0x58, 0x8f, 0x02, 0x6b, 0x4f, 0xff, 0x00, 0x04, 0x60, 0x00, 0x00, 0xb2, 0x30, 0x28, 0x00, 0x16, 0xb0, 0x6a, 0x00, 0x16, 0x67, 0x06, 0x76, 0xc8, 0x60, 0x00, 0x00, 0xa2,
|
||||
0x30, 0x2a, 0x00, 0x1a, 0x0c, 0x40, 0x00, 0x07, 0x6d, 0x06, 0x6e, 0x1e, 0x60, 0x00, 0x00, 0x7e, 0x0c, 0x40, 0x00, 0x05, 0x6d, 0x00, 0x00, 0x88, 0x6e, 0x04, 0x60, 0x00, 0x00, 0x84, 0x0c, 0x40,
|
||||
0x00, 0x06, 0x66, 0x00, 0x00, 0x7a, 0x60, 0x00, 0x00, 0x78, 0x0c, 0x40, 0x00, 0x11, 0x6d, 0x00, 0x00, 0x6e, 0x6e, 0x02, 0x60, 0x08, 0x0c, 0x40, 0x00, 0x15, 0x66, 0x62, 0x60, 0x18, 0x43, 0xea,
|
||||
0x00, 0x1c, 0x0c, 0x51, 0x00, 0x01, 0x66, 0x06, 0x42, 0xa8, 0x00, 0x1c, 0x60, 0x52, 0x21, 0x68, 0x00, 0x18, 0x00, 0x1c, 0x60, 0x4a, 0x4e, 0xba, 0xf7, 0x6c, 0x2d, 0x40, 0xff, 0xfc, 0x20, 0x2e,
|
||||
0xff, 0xfc, 0x06, 0x80, 0x00, 0x00, 0x01, 0x00, 0x5c, 0x80, 0x32, 0x04, 0x48, 0xc1, 0x74, 0x30, 0xd2, 0x82, 0x26, 0x40, 0x16, 0x81, 0x48, 0x78, 0x00, 0x04, 0x48, 0x6e, 0xff, 0xfc, 0x48, 0x6a,
|
||||
0x00, 0x1c, 0x4e, 0xba, 0x08, 0x02, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x14, 0x70, 0x00, 0x30, 0x28, 0x00, 0x16, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x07, 0x4e, 0xba, 0x08, 0xdc, 0x50, 0x8f, 0x76, 0xef,
|
||||
0x2f, 0x05, 0x4e, 0xba, 0x09, 0x52, 0x34, 0x03, 0x48, 0xc2, 0x58, 0x8f, 0x20, 0x02, 0x4c, 0xee, 0x0c, 0x3c, 0xfd, 0xe4, 0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x30, 0x30, 0x26, 0x6f, 0x00, 0x14,
|
||||
0x24, 0x6f, 0x00, 0x18, 0x42, 0x42, 0x4e, 0xba, 0xf6, 0xea, 0x26, 0x00, 0x32, 0x2b, 0x00, 0x18, 0x46, 0x41, 0x04, 0x41, 0x00, 0x20, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40,
|
||||
0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0xd1, 0xc1, 0x30, 0x28, 0x00, 0x16, 0xb0, 0x6a, 0x00, 0x16, 0x67, 0x04, 0x74, 0xc8, 0x60, 0x1e, 0x30, 0x2a,
|
||||
0x00, 0x1a, 0x51, 0x40, 0x66, 0x14, 0x48, 0x78, 0x00, 0x16, 0x48, 0x50, 0x48, 0x6a, 0x00, 0x1c, 0x4e, 0xba, 0x07, 0x74, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x02, 0x74, 0xee, 0x2f, 0x03, 0x4e, 0xba,
|
||||
0x08, 0xd6, 0x30, 0x02, 0x48, 0xc0, 0x58, 0x8f, 0x4c, 0xdf, 0x0c, 0x0c, 0x4e, 0x75, 0x48, 0xe7, 0x38, 0x00, 0x4e, 0xba, 0xf6, 0x7e, 0x28, 0x00, 0x3b, 0x7c, 0x00, 0x65, 0xfc, 0x6a, 0x4a, 0x6d,
|
||||
0xfc, 0x8c, 0x67, 0x58, 0x36, 0x3c, 0x00, 0x80, 0x74, 0x07, 0x72, 0x00, 0x32, 0x2d, 0xfc, 0x8c, 0x30, 0x03, 0x48, 0xc0, 0xc2, 0x80, 0x67, 0x38, 0x32, 0x02, 0x48, 0xc1, 0x20, 0x01, 0xc2, 0xfc,
|
||||
0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0x70, 0x00, 0x30, 0x30, 0x18, 0x16, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x07, 0x4e, 0xba,
|
||||
0x07, 0xf6, 0x4a, 0x80, 0x50, 0x8f, 0x66, 0x08, 0x30, 0x03, 0x46, 0x40, 0xc1, 0x6d, 0xfc, 0x8c, 0x30, 0x03, 0xe2, 0x40, 0x36, 0x00, 0x53, 0x42, 0x6c, 0x00, 0xff, 0xb0, 0x4a, 0x2d, 0xfc, 0x74,
|
||||
0x66, 0x32, 0x22, 0x38, 0x02, 0xae, 0x20, 0x38, 0x0c, 0x54, 0x02, 0x80, 0x00, 0xff, 0xff, 0xff, 0xb2, 0x80, 0x63, 0x20, 0x1b, 0x7c, 0x00, 0x01, 0xfc, 0x74, 0x42, 0x42, 0x30, 0x02, 0x48, 0xc0,
|
||||
0x41, 0xed, 0xfc, 0x80, 0x11, 0xbc, 0x00, 0x01, 0x08, 0x00, 0x52, 0x42, 0x0c, 0x42, 0x00, 0x08, 0x6d, 0x00, 0xff, 0xea, 0x2f, 0x04, 0x4e, 0xba, 0x08, 0x1e, 0x58, 0x8f, 0x4c, 0xdf, 0x00, 0x1c,
|
||||
0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf8, 0x48, 0xe7, 0x3c, 0x00, 0x4e, 0xba, 0xf5, 0xc6, 0x2a, 0x00, 0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x1b, 0x48, 0x6e, 0xff, 0xfa, 0x4e, 0xba, 0x06, 0xa2,
|
||||
0x70, 0x01, 0x12, 0x2d, 0xfc, 0x70, 0xe3, 0xa0, 0x38, 0x00, 0x74, 0x00, 0x4f, 0xef, 0x00, 0x0c, 0x20, 0x02, 0xd0, 0x80, 0x41, 0xed, 0xfc, 0x90, 0x36, 0x30, 0x08, 0x00, 0x70, 0x00, 0x30, 0x04,
|
||||
0x72, 0x00, 0x32, 0x03, 0xc0, 0x81, 0x67, 0x46, 0x4a, 0x82, 0x67, 0x00, 0x00, 0x0e, 0x70, 0x00, 0x30, 0x03, 0x02, 0x80, 0x00, 0x00, 0x40, 0x00, 0x66, 0x34, 0x70, 0x00, 0x30, 0x03, 0x02, 0x80,
|
||||
0x00, 0x00, 0x80, 0x00, 0x67, 0x08, 0x1d, 0x7c, 0x00, 0x01, 0xff, 0xfb, 0x60, 0x04, 0x42, 0x2e, 0xff, 0xfb, 0x48, 0x78, 0x03, 0x84, 0x42, 0xa7, 0x42, 0xa7, 0x42, 0xa7, 0x2f, 0x02, 0x48, 0x78,
|
||||
0x00, 0x06, 0x48, 0x6e, 0xff, 0xfa, 0x4e, 0xba, 0x02, 0x70, 0x4f, 0xef, 0x00, 0x1c, 0x52, 0x82, 0x70, 0x08, 0xb0, 0x82, 0x6e, 0x00, 0xff, 0x9a, 0x2f, 0x05, 0x4e, 0xba, 0x07, 0x7a, 0x58, 0x8f,
|
||||
0x4c, 0xee, 0x00, 0x3c, 0xff, 0xe8, 0x4e, 0x5e, 0x4e, 0x75, 0x48, 0xe7, 0x30, 0x00, 0x22, 0x2f, 0x00, 0x0c, 0x26, 0x2f, 0x00, 0x10, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0xc0, 0xfc,
|
||||
0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x41, 0xed, 0xfc, 0xa0, 0xd1, 0xc1, 0x22, 0x48, 0x70, 0x01, 0xb0, 0x83, 0x66, 0x1a, 0x70, 0x00, 0x10, 0x29, 0x00, 0x27, 0x72, 0x01, 0x14, 0x2d,
|
||||
0xfc, 0x70, 0xe5, 0xa1, 0xc0, 0x81, 0x67, 0x08, 0x13, 0x7c, 0x00, 0x01, 0x00, 0x26, 0x60, 0x04, 0x42, 0x29, 0x00, 0x26, 0x33, 0x7c, 0x00, 0x01, 0x00, 0x30, 0x4a, 0x83, 0x67, 0x00, 0x00, 0x08,
|
||||
0x4a, 0x29, 0x00, 0x25, 0x66, 0x16, 0x42, 0x29, 0x00, 0x24, 0x23, 0x7c, 0x00, 0x00, 0x02, 0x00, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x3a, 0x60, 0x00, 0x00, 0xaa, 0x13, 0x69, 0x00, 0x25,
|
||||
0x00, 0x24, 0x70, 0x00, 0x10, 0x29, 0x00, 0x24, 0x0c, 0x40, 0x00, 0x01, 0x6d, 0x00, 0x00, 0x96, 0x6e, 0x02, 0x60, 0x08, 0x55, 0x40, 0x66, 0x00, 0x00, 0x8c, 0x60, 0x22, 0x23, 0x7c, 0x00, 0x00,
|
||||
0x02, 0x00, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x05, 0x00, 0x3a, 0x70, 0xf6, 0x23, 0x40, 0x00, 0x3c, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x44, 0x33, 0x7c, 0x00, 0x01, 0x00, 0x2e, 0x60, 0x66, 0x70, 0x00,
|
||||
0x30, 0x29, 0x00, 0x2c, 0x23, 0x40, 0x00, 0x36, 0x33, 0x7c, 0x00, 0x04, 0x00, 0x3a, 0x41, 0xe9, 0x00, 0x32, 0x23, 0x48, 0x00, 0x3c, 0x41, 0xe9, 0x00, 0x46, 0x23, 0x48, 0x00, 0x40, 0x33, 0x7c,
|
||||
0x00, 0x01, 0x00, 0x44, 0x20, 0x3c, 0x00, 0x00, 0x02, 0x00, 0x72, 0x00, 0x32, 0x29, 0x00, 0x2c, 0x90, 0x81, 0x23, 0x40, 0x00, 0x4a, 0x33, 0x7c, 0x00, 0x04, 0x00, 0x4e, 0x41, 0xe9, 0x00, 0x46,
|
||||
0x23, 0x48, 0x00, 0x50, 0x41, 0xe9, 0x00, 0x32, 0x23, 0x48, 0x00, 0x54, 0x33, 0x7c, 0x00, 0x05, 0x00, 0x58, 0x70, 0xd8, 0x23, 0x40, 0x00, 0x5a, 0x33, 0x7c, 0x00, 0x07, 0x00, 0x62, 0x33, 0x7c,
|
||||
0x00, 0x04, 0x00, 0x2e, 0x4c, 0xdf, 0x00, 0x0c, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf0, 0x48, 0xe7, 0x3c, 0x20, 0x38, 0x2e, 0x00, 0x0a, 0x36, 0x2e, 0x00, 0x0e, 0x24, 0x2e, 0x00, 0x10, 0x4a, 0x43,
|
||||
0x67, 0x04, 0x70, 0x0a, 0x60, 0x02, 0x70, 0x08, 0x48, 0x78, 0x00, 0x06, 0x2f, 0x00, 0x48, 0x6e, 0xff, 0xf2, 0x4e, 0xba, 0x04, 0xcc, 0x72, 0x00, 0x32, 0x04, 0x20, 0x01, 0xc2, 0xfc, 0x00, 0x6c,
|
||||
0x48, 0x40, 0xc0, 0xfc, 0x00, 0x6c, 0x48, 0x40, 0x42, 0x40, 0xd2, 0x80, 0x45, 0xed, 0xfc, 0xa0, 0xd5, 0xc1, 0x25, 0x6e, 0x00, 0x18, 0x00, 0x32, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x00, 0x00, 0xc8,
|
||||
0x20, 0x2e, 0x00, 0x14, 0x72, 0x10, 0xe2, 0xa8, 0x1d, 0x40, 0xff, 0xf3, 0x41, 0xee, 0xff, 0xf4, 0x30, 0xae, 0x00, 0x16, 0x4a, 0x2a, 0x00, 0x24, 0x66, 0x04, 0x7a, 0x01, 0x60, 0x28, 0x0c, 0x82,
|
||||
0x00, 0x00, 0x01, 0x00, 0x63, 0x08, 0x2a, 0x3c, 0x00, 0x00, 0x01, 0x00, 0x60, 0x02, 0x2a, 0x02, 0x72, 0x00, 0x32, 0x2a, 0x00, 0x2e, 0xd2, 0x81, 0x20, 0x01, 0xe5, 0x81, 0xd2, 0x80, 0x20, 0x05,
|
||||
0x2a, 0x00, 0x25, 0x80, 0x18, 0x36, 0x1d, 0x45, 0xff, 0xf6, 0x48, 0x78, 0x2a, 0x30, 0x70, 0x00, 0x10, 0x2a, 0x00, 0x26, 0x2f, 0x00, 0x70, 0x00, 0x30, 0x03, 0x2f, 0x00, 0x48, 0x6a, 0x00, 0x30,
|
||||
0x70, 0x00, 0x30, 0x04, 0x2f, 0x00, 0x48, 0x78, 0x00, 0x06, 0x48, 0x6e, 0xff, 0xf2, 0x4e, 0xba, 0x00, 0x68, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x1c, 0x67, 0x44, 0x0c, 0x40, 0x00, 0x02, 0x67, 0x04,
|
||||
0x48, 0xc0, 0x60, 0x4a, 0x48, 0x78, 0x00, 0x08, 0x48, 0x6e, 0xff, 0xf8, 0x70, 0x00, 0x30, 0x04, 0x2f, 0x00, 0x4e, 0xba, 0x01, 0x58, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x0c, 0x67, 0x04, 0x70, 0xf9,
|
||||
0x60, 0x2c, 0x48, 0x78, 0x00, 0x04, 0x48, 0x6e, 0xff, 0xfb, 0x48, 0x6a, 0x00, 0x28, 0x4e, 0xba, 0x03, 0xd6, 0x70, 0x00, 0x10, 0x2e, 0xff, 0xfa, 0x4f, 0xef, 0x00, 0x0c, 0x60, 0x10, 0x94, 0x85,
|
||||
0x20, 0x05, 0xd1, 0xae, 0x00, 0x14, 0x4a, 0x82, 0x62, 0x00, 0xff, 0x36, 0x70, 0x00, 0x4c, 0xee, 0x04, 0x3c, 0xff, 0xdc, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf8, 0x48, 0xe7, 0x3e, 0x00,
|
||||
0x26, 0x2e, 0x00, 0x08, 0x38, 0x2e, 0x00, 0x0e, 0x3a, 0x2e, 0x00, 0x12, 0x42, 0x46, 0x42, 0x6e, 0xff, 0xfe, 0x55, 0x8f, 0x4e, 0xba, 0x03, 0xcc, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x1e,
|
||||
0x52, 0x6e, 0xff, 0xfe, 0x0c, 0x6e, 0x00, 0x03, 0xff, 0xfe, 0x6f, 0x00, 0xff, 0xe6, 0x55, 0x8f, 0x4e, 0xba, 0x04, 0x00, 0x30, 0x1f, 0x08, 0x00, 0x00, 0x06, 0x67, 0xd2, 0x60, 0xf0, 0x55, 0x8f,
|
||||
0x3f, 0x05, 0x4e, 0xba, 0x03, 0xa8, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x06, 0x70, 0xfe, 0x60, 0x00, 0x00, 0xb0, 0x42, 0x6e, 0xff, 0xfe, 0x55, 0x8f, 0x2f, 0x03, 0x3f, 0x04, 0x4e, 0xba,
|
||||
0x03, 0x96, 0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x12, 0x52, 0x6e, 0xff, 0xfe, 0x0c, 0x6e, 0x00, 0x03, 0xff, 0xfe, 0x6f, 0x00, 0xff, 0xe2, 0x7c, 0xfd, 0x60, 0x52, 0x4a, 0xae, 0x00, 0x14,
|
||||
0x67, 0x4c, 0x4a, 0x6e, 0x00, 0x1e, 0x67, 0x24, 0x4a, 0x6e, 0x00, 0x1a, 0x67, 0x0c, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x92, 0x60, 0x0a, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14,
|
||||
0x4e, 0xba, 0x03, 0x7c, 0x30, 0x1f, 0x48, 0xc0, 0x2c, 0x00, 0x60, 0x22, 0x4a, 0x6e, 0x00, 0x1a, 0x67, 0x0c, 0x55, 0x8f, 0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x5a, 0x60, 0x0a, 0x55, 0x8f,
|
||||
0x2f, 0x2e, 0x00, 0x14, 0x4e, 0xba, 0x03, 0x44, 0x30, 0x1f, 0x48, 0xc0, 0x2c, 0x00, 0x55, 0x8f, 0x48, 0x6e, 0xff, 0xfa, 0x48, 0x6e, 0xff, 0xfc, 0x2f, 0x2e, 0x00, 0x20, 0x4e, 0xba, 0x03, 0x22,
|
||||
0x30, 0x1f, 0x48, 0xc0, 0x30, 0x00, 0x67, 0x04, 0x70, 0xfc, 0x60, 0x16, 0x4a, 0x46, 0x67, 0x0c, 0x0c, 0x6e, 0x00, 0x02, 0xff, 0xfa, 0x67, 0x04, 0x30, 0x06, 0x60, 0x04, 0x30, 0x2e, 0xff, 0xfa,
|
||||
0x48, 0xc0, 0x4c, 0xee, 0x00, 0x7c, 0xff, 0xe4, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xe4, 0x48, 0xe7, 0x38, 0x00, 0x36, 0x2e, 0x00, 0x0a, 0x28, 0x2e, 0x00, 0x0c, 0x34, 0x2e, 0x00, 0x12,
|
||||
0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x03, 0x48, 0x6e, 0xff, 0xe6, 0x4e, 0xba, 0x02, 0x92, 0x1d, 0x42, 0xff, 0xea, 0x3d, 0x7c, 0x00, 0x02, 0xff, 0xec, 0x2d, 0x44, 0xff, 0xee, 0x70, 0x00,
|
||||
0x30, 0x02, 0x2d, 0x40, 0xff, 0xf2, 0x3d, 0x7c, 0x00, 0x07, 0xff, 0xf6, 0x48, 0x78, 0x02, 0x58, 0x42, 0xa7, 0x42, 0xa7, 0x48, 0x6e, 0xff, 0xec, 0x30, 0x03, 0x48, 0xc0, 0x2f, 0x00, 0x48, 0x78,
|
||||
0x00, 0x06, 0x48, 0x6e, 0xff, 0xe6, 0x4e, 0xba, 0xfe, 0x90, 0x4f, 0xef, 0x00, 0x28, 0x4c, 0xee, 0x00, 0x1c, 0xff, 0xd8, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xf4, 0x48, 0xe7, 0x3e, 0x30,
|
||||
0x24, 0x2e, 0x00, 0x08, 0x26, 0x2e, 0x00, 0x0c, 0x4e, 0xba, 0x02, 0xce, 0x2d, 0x40, 0xff, 0xf6, 0x4e, 0xba, 0x02, 0xd6, 0x2f, 0x00, 0x4e, 0xba, 0x02, 0xc6, 0x48, 0x78, 0x04, 0x00, 0x4e, 0xba,
|
||||
0x02, 0xd8, 0x24, 0x40, 0xb4, 0xfc, 0x00, 0x00, 0x50, 0x8f, 0x66, 0x1e, 0x4e, 0xba, 0x02, 0xc2, 0x2f, 0x00, 0x4e, 0xba, 0x02, 0xaa, 0x48, 0x78, 0x04, 0x00, 0x4e, 0xba, 0x02, 0xbc, 0x24, 0x40,
|
||||
0xb4, 0xfc, 0x00, 0x00, 0x50, 0x8f, 0x67, 0x00, 0x01, 0x16, 0x2f, 0x0a, 0x4e, 0xba, 0x02, 0xbe, 0x26, 0x52, 0x20, 0x4b, 0x41, 0xe8, 0x02, 0x00, 0x2d, 0x48, 0xff, 0xfc, 0x42, 0x6e, 0xff, 0xfa,
|
||||
0x58, 0x8f, 0x42, 0x45, 0x42, 0x46, 0x42, 0x44, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0xd2, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14,
|
||||
0x67, 0x20, 0x53, 0x40, 0x66, 0x18, 0x52, 0x46, 0xba, 0x44, 0x66, 0x12, 0x48, 0x78, 0x02, 0x00, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x0b, 0x4e, 0xba, 0x01, 0x8e, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x45,
|
||||
0x60, 0x12, 0x48, 0x78, 0x02, 0x00, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x0b, 0x4e, 0xba, 0x01, 0x78, 0x4f, 0xef, 0x00, 0x0c, 0x52, 0x44, 0x0c, 0x44, 0x00, 0x0a, 0x6d, 0x00, 0xff, 0xac, 0x4a, 0x45,
|
||||
0x67, 0x00, 0x00, 0x9c, 0x4a, 0x46, 0x67, 0x00, 0x00, 0x96, 0x42, 0x44, 0x2f, 0x0b, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x01, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x6e, 0x30, 0x00,
|
||||
0x4f, 0xef, 0x00, 0x14, 0x66, 0x00, 0x00, 0x28, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x52, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14,
|
||||
0x66, 0x00, 0x00, 0x0c, 0x52, 0x44, 0x0c, 0x44, 0x00, 0x0a, 0x6c, 0x52, 0x60, 0xbe, 0x52, 0x6e, 0xff, 0xfa, 0x0c, 0x6e, 0x00, 0x02, 0xff, 0xfa, 0x6e, 0x00, 0x00, 0x44, 0x2f, 0x03, 0x2f, 0x02,
|
||||
0x4e, 0xba, 0x00, 0x56, 0x50, 0x8f, 0x2f, 0x0b, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x48, 0x78, 0x00, 0x01, 0x2f, 0x02, 0x4e, 0xba, 0xfc, 0x14, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x66, 0x00,
|
||||
0x00, 0x1e, 0x2f, 0x2e, 0xff, 0xfc, 0x2f, 0x03, 0x48, 0x78, 0x00, 0x01, 0x42, 0xa7, 0x2f, 0x02, 0x4e, 0xba, 0xfb, 0xf8, 0x30, 0x00, 0x4f, 0xef, 0x00, 0x14, 0x66, 0x00, 0xff, 0x06, 0x2f, 0x0a,
|
||||
0x4e, 0xba, 0x01, 0xa0, 0x2f, 0x2e, 0xff, 0xf6, 0x4e, 0xba, 0x01, 0x74, 0x50, 0x8f, 0x4c, 0xee, 0x0c, 0x7c, 0xff, 0xd8, 0x4e, 0x5e, 0x4e, 0x75, 0x4e, 0x56, 0xff, 0xd4, 0x48, 0xe7, 0x30, 0x00,
|
||||
0x34, 0x2e, 0x00, 0x0a, 0x26, 0x2e, 0x00, 0x0c, 0x48, 0x78, 0x00, 0x06, 0x48, 0x78, 0x00, 0x07, 0x48, 0x6e, 0xff, 0xde, 0x4e, 0xba, 0x00, 0xaa, 0x48, 0x78, 0x00, 0x08, 0x42, 0xa7, 0x48, 0x6e,
|
||||
0xff, 0xd6, 0x4e, 0xba, 0x00, 0x9c, 0x1d, 0x7c, 0x00, 0x04, 0xff, 0xd9, 0x3d, 0x7c, 0x00, 0x02, 0xff, 0xec, 0x41, 0xee, 0xff, 0xd6, 0x2d, 0x48, 0xff, 0xee, 0x70, 0x08, 0x2d, 0x40, 0xff, 0xf2,
|
||||
0x3d, 0x7c, 0x00, 0x07, 0xff, 0xf6, 0x41, 0xee, 0xff, 0xda, 0x20, 0x83, 0x48, 0x78, 0x0e, 0x10, 0x42, 0xa7, 0x48, 0x78, 0x00, 0x01, 0x48, 0x6e, 0xff, 0xec, 0x30, 0x02, 0x48, 0xc0, 0x2f, 0x00,
|
||||
0x48, 0x78, 0x00, 0x06, 0x48, 0x6e, 0xff, 0xde, 0x4e, 0xba, 0xfc, 0x8e, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x34, 0x67, 0x26, 0x48, 0x78, 0x00, 0x08, 0x48, 0x6e, 0xff, 0xe4, 0x30, 0x02, 0x48, 0xc0,
|
||||
0x2f, 0x00, 0x4e, 0xba, 0xfd, 0x88, 0x4a, 0x80, 0x4f, 0xef, 0x00, 0x0c, 0x67, 0x04, 0x70, 0xff, 0x60, 0x0a, 0x70, 0x00, 0x10, 0x2e, 0xff, 0xe6, 0x60, 0x02, 0x70, 0x00, 0x4c, 0xee, 0x00, 0x0c,
|
||||
0xff, 0xcc, 0x4e, 0x5e, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0x22, 0x6f, 0x00, 0x08, 0x20, 0x2f, 0x00, 0x0c, 0x60, 0x04, 0x10, 0xd9, 0x53, 0x80, 0x4a, 0x80, 0x66, 0x00, 0xff, 0xf8, 0x4e, 0x75,
|
||||
0x22, 0x6f, 0x00, 0x04, 0x10, 0x2f, 0x00, 0x0b, 0x32, 0x2f, 0x00, 0x0e, 0x48, 0xc1, 0x60, 0x06, 0x20, 0x41, 0xd1, 0xc9, 0x42, 0x10, 0x53, 0x81, 0x4a, 0x81, 0x6c, 0x00, 0xff, 0xf4, 0x12, 0x80,
|
||||
0x4e, 0x75, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x01, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x02, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x03, 0x2f, 0x08, 0xac, 0x15,
|
||||
0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x04, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x05, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x06, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f,
|
||||
0x3f, 0x3c, 0x00, 0x08, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x09, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x5f, 0x3f, 0x3c, 0x00, 0x0a, 0x2f, 0x08, 0xac, 0x15, 0x20, 0x6f, 0x00, 0x0c,
|
||||
0x30, 0x2f, 0x00, 0x0a, 0x48, 0x40, 0x30, 0x2f, 0x00, 0x06, 0xa0, 0x4e, 0x4e, 0x75, 0x70, 0x00, 0x31, 0xc0, 0x02, 0x20, 0x20, 0x08, 0x4e, 0x75, 0xa1, 0x1a, 0x4e, 0xfa, 0xff, 0xf4, 0x20, 0x6f,
|
||||
0x00, 0x04, 0xa0, 0x1b, 0x4e, 0xfa, 0xff, 0xea, 0x20, 0x78, 0x02, 0xa6, 0x4e, 0xfa, 0xff, 0xe0, 0x20, 0x78, 0x02, 0xaa, 0x4e, 0xfa, 0xff, 0xd8, 0x20, 0x2f, 0x00, 0x04, 0xa1, 0x22, 0x4e, 0xfa,
|
||||
0xff, 0xd0, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x23, 0x4e, 0xfa, 0xff, 0xc6, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x29, 0x4e, 0xfa, 0xff, 0xbc, 0x30, 0x6f, 0x00, 0x06, 0x20, 0x2f, 0x00, 0x08, 0xa0, 0x2f,
|
||||
0x48, 0xc0, 0x4e, 0x75, 0x22, 0x6f, 0x00, 0x08, 0x20, 0x6f, 0x00, 0x04, 0x2f, 0x02, 0xa9, 0x6e, 0x24, 0x1f, 0x48, 0xc0, 0x4e, 0x75, 0x30, 0x2f, 0x00, 0x06, 0x12, 0x2f, 0x00, 0x0b, 0x4a, 0x01,
|
||||
0x67, 0x04, 0xa7, 0x46, 0x60, 0x02, 0xa3, 0x46, 0x20, 0x08, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x33, 0x48, 0xc0, 0x4e, 0x75, 0x20, 0x6f, 0x00, 0x04, 0xa0, 0x34, 0x48, 0xc0, 0x4e, 0x75,
|
||||
0x4e, 0xba, 0x00, 0x58, 0x06, 0x80, 0x00, 0x00, 0x00, 0x20, 0x4e, 0x75, 0x2f, 0x0d, 0x20, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x66, 0x0c, 0x20, 0x6f, 0x00, 0x08, 0x70, 0x07, 0x20, 0xdd, 0x51, 0xc8,
|
||||
0xff, 0xfc, 0x2a, 0x6f, 0x00, 0x08, 0x4e, 0xba, 0x00, 0x3a, 0x2a, 0x5f, 0x4e, 0x75, 0x20, 0x0d, 0x2a, 0x6f, 0x00, 0x04, 0x4e, 0x75, 0x2a, 0x6f, 0x00, 0x04, 0x4e, 0x75, 0x4e, 0xba, 0xff, 0xc2,
|
||||
0xa1, 0x1e, 0x2f, 0x08, 0x4e, 0xba, 0x00, 0x14, 0xd0, 0x9f, 0x4e, 0x75, 0x4e, 0xba, 0x00, 0x0c, 0x20, 0x6f, 0x00, 0x04, 0x91, 0xc0, 0xa0, 0x1f, 0x4e, 0x75, 0x41, 0xfa, 0x00, 0xce, 0x20, 0x10,
|
||||
0x4e, 0x75, 0x48, 0xe7, 0xff, 0xf0, 0x42, 0x47, 0x41, 0xfa, 0x00, 0xc0, 0x22, 0x18, 0x6f, 0x00, 0x00, 0xa6, 0x2a, 0x18, 0x66, 0x04, 0x2a, 0x0d, 0x9a, 0x81, 0x26, 0x45, 0x24, 0x4b, 0xe2, 0x81,
|
||||
0x60, 0x02, 0x42, 0x5a, 0x51, 0xc9, 0xff, 0xfc, 0x30, 0x18, 0x3e, 0x18, 0x60, 0x00, 0x00, 0x84, 0x78, 0x00, 0x18, 0x18, 0x22, 0x04, 0x02, 0x01, 0x00, 0x0f, 0x08, 0x04, 0x00, 0x04, 0x67, 0x0e,
|
||||
0xe1, 0x41, 0x12, 0x18, 0x08, 0x81, 0x00, 0x0b, 0x67, 0x04, 0xe1, 0x81, 0x12, 0x18, 0x74, 0x01, 0x08, 0x04, 0x00, 0x07, 0x67, 0x16, 0x14, 0x18, 0x08, 0x82, 0x00, 0x07, 0x67, 0x0e, 0xe1, 0x42,
|
||||
0x14, 0x18, 0x08, 0x82, 0x00, 0x0e, 0x67, 0x04, 0xe1, 0x82, 0x14, 0x18, 0x7c, 0x02, 0x4e, 0xba, 0x00, 0x4c, 0x08, 0x85, 0x00, 0x0f, 0x67, 0x04, 0xe1, 0x85, 0x1a, 0x18, 0x43, 0xf3, 0x58, 0x00,
|
||||
0x24, 0x49, 0x08, 0x04, 0x00, 0x05, 0x67, 0x1c, 0x2c, 0x01, 0x4e, 0xba, 0x00, 0x30, 0x0c, 0x41, 0x00, 0x02, 0x67, 0x06, 0x6d, 0x08, 0xdb, 0x92, 0x60, 0x0e, 0xdb, 0x52, 0x60, 0x0a, 0xdb, 0x12,
|
||||
0x60, 0x06, 0x12, 0xd8, 0x51, 0xc9, 0xff, 0xfc, 0x08, 0x04, 0x00, 0x06, 0x67, 0x04, 0x26, 0x0d, 0xd7, 0x92, 0x51, 0xc8, 0xff, 0x7c, 0x4c, 0xdf, 0x0f, 0xff, 0x4e, 0x75, 0x7a, 0x00, 0x60, 0x04,
|
||||
0xe1, 0x8d, 0x1a, 0x18, 0x51, 0xce, 0xff, 0xfa, 0x4e, 0x75, 0x00, 0x00, 0x03, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x8a, 0x25, 0x41, 0x35, 0x69, 0x80, 0x00,
|
||||
0x03, 0x7e, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x19, 0x1a, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x53, 0x43, 0x53, 0x49, 0x20, 0x64, 0x72, 0x69, 0x76,
|
||||
0x65, 0x73, 0x2e, 0x2c, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x2e, 0x2e, 0x2e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74,
|
||||
0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x20, 0x55, 0x70, 0x64, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x72, 0x69, 0x76, 0x65, 0x72,
|
||||
};
|
||||
|
||||
const auto offset = (source_address - 0x40) * 512;
|
||||
return std::vector<uint8_t>(&driver[offset], &driver[offset + 512]);
|
||||
}
|
||||
|
||||
// Default: return an empty block.
|
||||
return std::vector<uint8_t>(512);
|
||||
}
|
87
Storage/MassStorage/Encodings/MacintoshVolume.hpp
Normal file
87
Storage/MassStorage/Encodings/MacintoshVolume.hpp
Normal file
@ -0,0 +1,87 @@
|
||||
//
|
||||
// MacintoshVolume.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MacintoshVolume_hpp
|
||||
#define MacintoshVolume_hpp
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Storage {
|
||||
namespace MassStorage {
|
||||
namespace Encodings {
|
||||
namespace Macintosh {
|
||||
|
||||
enum class DriveType {
|
||||
SCSI
|
||||
};
|
||||
|
||||
/*!
|
||||
On the Macintosh life is slightly complicated by Apple's
|
||||
decision to include device drivers on mass storage drives
|
||||
themselves — therefore a mass-storage device that is
|
||||
connected by SCSI will have different preliminary data on it
|
||||
than the same volume connected by ATA or as an HD20.
|
||||
|
||||
Mass storage devices that respond to @c Volume can be made
|
||||
to provide the proper whole-volume encoding necessary to
|
||||
impersonate different types of Macintosh drive.
|
||||
*/
|
||||
class Volume {
|
||||
public:
|
||||
virtual void set_drive_type(DriveType type) = 0;
|
||||
};
|
||||
|
||||
/*!
|
||||
A Mapper can used by a mass-storage device that knows the
|
||||
contents of an HFS or MFS partition to provide the conversion
|
||||
necessary to a particular type of drive.
|
||||
*/
|
||||
class Mapper {
|
||||
public:
|
||||
/*!
|
||||
Sets the drive type to map to and the number of blocks in the underlying partition.
|
||||
*/
|
||||
void set_drive_type(DriveType, size_t number_of_blocks);
|
||||
|
||||
/*!
|
||||
Maps from a mass-storage device address to an address
|
||||
in the underlying [H/M]FS partition.
|
||||
*/
|
||||
ssize_t to_source_address(size_t address);
|
||||
|
||||
/*!
|
||||
Converts from a source data block to one properly encoded for the drive type.
|
||||
|
||||
Expected usage:
|
||||
|
||||
const size_t source_address = mapper.to_source_address(unit_address);
|
||||
if(is_in_range_for_partition(source_address)) {
|
||||
return mapper.convert_source_block(source_address, get_block_contents(source_address));
|
||||
} else {
|
||||
return mapper.convert_source_block(source_address);
|
||||
}
|
||||
*/
|
||||
std::vector<uint8_t> convert_source_block(ssize_t source_address, std::vector<uint8_t> source_data = {});
|
||||
|
||||
/*!
|
||||
@returns The total number of blocks on the entire volume.
|
||||
*/
|
||||
size_t get_number_of_blocks();
|
||||
|
||||
private:
|
||||
DriveType drive_type_;
|
||||
size_t number_of_blocks_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MacintoshVolume_hpp */
|
56
Storage/MassStorage/Formats/HFV.cpp
Normal file
56
Storage/MassStorage/Formats/HFV.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
//
|
||||
// HFV.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "HFV.hpp"
|
||||
|
||||
using namespace Storage::MassStorage;
|
||||
|
||||
HFV::HFV(const std::string &file_name) : file_(file_name) {
|
||||
// Is the file a multiple of 512 bytes in size and larger than a floppy disk?
|
||||
const auto file_size = file_.stats().st_size;
|
||||
if(file_size & 511 || file_size <= 800*1024) throw std::exception();
|
||||
|
||||
// TODO: check filing system for MFS, HFS or HFS+.
|
||||
}
|
||||
|
||||
size_t HFV::get_block_size() {
|
||||
return 512;
|
||||
}
|
||||
|
||||
size_t HFV::get_number_of_blocks() {
|
||||
return mapper_.get_number_of_blocks();
|
||||
}
|
||||
|
||||
std::vector<uint8_t> HFV::get_block(size_t address) {
|
||||
const auto written = writes_.find(address);
|
||||
if(written != writes_.end()) return written->second;
|
||||
|
||||
const auto source_address = mapper_.to_source_address(address);
|
||||
if(source_address >= 0 && size_t(source_address)*get_block_size() < size_t(file_.stats().st_size)) {
|
||||
const long file_offset = long(get_block_size()) * long(source_address);
|
||||
file_.seek(file_offset, SEEK_SET);
|
||||
return mapper_.convert_source_block(source_address, file_.read(get_block_size()));
|
||||
} else {
|
||||
return mapper_.convert_source_block(source_address);
|
||||
}
|
||||
}
|
||||
|
||||
void HFV::set_block(size_t address, const std::vector<uint8_t> &contents) {
|
||||
const auto source_address = mapper_.to_source_address(address);
|
||||
if(source_address >= 0 && size_t(source_address)*get_block_size() < size_t(file_.stats().st_size)) {
|
||||
const long file_offset = long(get_block_size()) * long(source_address);
|
||||
file_.seek(file_offset, SEEK_SET);
|
||||
file_.write(contents);
|
||||
} else {
|
||||
writes_[address] = contents;
|
||||
}
|
||||
}
|
||||
|
||||
void HFV::set_drive_type(Encodings::Macintosh::DriveType drive_type) {
|
||||
mapper_.set_drive_type(drive_type, size_t(file_.stats().st_size) / get_block_size());
|
||||
}
|
54
Storage/MassStorage/Formats/HFV.hpp
Normal file
54
Storage/MassStorage/Formats/HFV.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
//
|
||||
// HFV.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef HFV_hpp
|
||||
#define HFV_hpp
|
||||
|
||||
#include "../MassStorageDevice.hpp"
|
||||
#include "../../FileHolder.hpp"
|
||||
#include "../Encodings/MacintoshVolume.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace Storage {
|
||||
namespace MassStorage {
|
||||
|
||||
/*!
|
||||
Provides a @c MassStorageDevice containing an HFV image, which is a sector dump of
|
||||
the HFS volume of a Macintosh drive that is not the correct size to be a floppy disk.
|
||||
*/
|
||||
class HFV: public MassStorageDevice, public Encodings::Macintosh::Volume {
|
||||
public:
|
||||
/*!
|
||||
Constructs an HFV with the contents of the file named @c file_name.
|
||||
Raises an exception if the file name doesn't appear to identify a valid
|
||||
Macintosh mass storage image.
|
||||
*/
|
||||
HFV(const std::string &file_name);
|
||||
|
||||
private:
|
||||
FileHolder file_;
|
||||
Encodings::Macintosh::Mapper mapper_;
|
||||
|
||||
/* MassStorageDevices overrides. */
|
||||
size_t get_block_size() final;
|
||||
size_t get_number_of_blocks() final;
|
||||
std::vector<uint8_t> get_block(size_t address) final;
|
||||
void set_block(size_t address, const std::vector<uint8_t> &) final;
|
||||
|
||||
/* Encodings::Macintosh::Volume overrides. */
|
||||
void set_drive_type(Encodings::Macintosh::DriveType) final;
|
||||
|
||||
std::map<size_t, std::vector<uint8_t>> writes_;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* HFV_hpp */
|
9
Storage/MassStorage/MassStorageDevice.cpp
Normal file
9
Storage/MassStorage/MassStorageDevice.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
//
|
||||
// MassStorageDevice.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "MassStorageDevice.hpp"
|
61
Storage/MassStorage/MassStorageDevice.hpp
Normal file
61
Storage/MassStorage/MassStorageDevice.hpp
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// MassStorageDevice.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MassStorageDevice_hpp
|
||||
#define MassStorageDevice_hpp
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace Storage {
|
||||
namespace MassStorage {
|
||||
|
||||
/*!
|
||||
A mass storage device is usually:
|
||||
|
||||
* large;
|
||||
* fixed; and
|
||||
* part of a class with a very wide array of potential speeds and timings.
|
||||
|
||||
Within this emulator, mass storage devices don't attempt to emulate
|
||||
any specific medium, they just offer block-based access to a
|
||||
linearly-addressed store.
|
||||
*/
|
||||
class MassStorageDevice {
|
||||
public:
|
||||
virtual ~MassStorageDevice() {}
|
||||
|
||||
/*!
|
||||
@returns The size of each individual block.
|
||||
*/
|
||||
virtual size_t get_block_size() = 0;
|
||||
|
||||
/*!
|
||||
Block addresses run from 0 to n. The total number of blocks, n,
|
||||
therefore provides the range of valid addresses.
|
||||
|
||||
@returns The total number of blocks on the device.
|
||||
*/
|
||||
virtual size_t get_number_of_blocks() = 0;
|
||||
|
||||
/*!
|
||||
@returns The current contents of the block at @c address.
|
||||
*/
|
||||
virtual std::vector<uint8_t> get_block(size_t address) = 0;
|
||||
|
||||
/*!
|
||||
Sets new contents for the block at @c address.
|
||||
*/
|
||||
virtual void set_block(size_t address, const std::vector<uint8_t> &) {}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MassStorageDevice_hpp */
|
86
Storage/MassStorage/SCSI/DirectAccessDevice.cpp
Normal file
86
Storage/MassStorage/SCSI/DirectAccessDevice.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
//
|
||||
// DirectAccessDevice.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "DirectAccessDevice.hpp"
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
using namespace SCSI;
|
||||
|
||||
void DirectAccessDevice::set_storage(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device) {
|
||||
device_ = device;
|
||||
}
|
||||
|
||||
bool DirectAccessDevice::read(const Target::CommandState &state, Target::Responder &responder) {
|
||||
if(!device_) return false;
|
||||
|
||||
const auto specs = state.read_write_specs();
|
||||
LOG("Read: " << specs.number_of_blocks << " from " << specs.address);
|
||||
|
||||
std::vector<uint8_t> output = device_->get_block(specs.address);
|
||||
for(uint32_t offset = 1; offset < specs.number_of_blocks; ++offset) {
|
||||
const auto next_block = device_->get_block(specs.address + offset);
|
||||
std::copy(next_block.begin(), next_block.end(), std::back_inserter(output));
|
||||
}
|
||||
|
||||
responder.send_data(std::move(output), [] (const Target::CommandState &state, Target::Responder &responder) {
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectAccessDevice::write(const Target::CommandState &state, Target::Responder &responder) {
|
||||
if(!device_) return false;
|
||||
|
||||
const auto specs = state.read_write_specs();
|
||||
|
||||
responder.receive_data(device_->get_block_size() * specs.number_of_blocks, [this, specs] (const Target::CommandState &state, Target::Responder &responder) {
|
||||
const auto received_data = state.received_data();
|
||||
const auto block_size = ssize_t(device_->get_block_size());
|
||||
for(uint32_t offset = 0; offset < specs.number_of_blocks; ++offset) {
|
||||
// TODO: clean up this gross inefficiency when std::span is standard.
|
||||
std::vector<uint8_t> sub_vector(received_data.begin() + ssize_t(offset)*block_size, received_data.begin() + ssize_t(offset+1)*block_size);
|
||||
this->device_->set_block(specs.address + offset, sub_vector);
|
||||
}
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DirectAccessDevice::read_capacity(const Target::CommandState &state, Target::Responder &responder) {
|
||||
const auto final_block = device_->get_number_of_blocks() - 1;
|
||||
const auto block_size = device_->get_block_size();
|
||||
std::vector<uint8_t> data = {
|
||||
uint8_t(final_block >> 24),
|
||||
uint8_t(final_block >> 16),
|
||||
uint8_t(final_block >> 8),
|
||||
uint8_t(final_block >> 0),
|
||||
|
||||
uint8_t(block_size >> 24),
|
||||
uint8_t(block_size >> 16),
|
||||
uint8_t(block_size >> 8),
|
||||
uint8_t(block_size >> 0),
|
||||
};
|
||||
|
||||
responder.send_data(std::move(data), [] (const Target::CommandState &state, Target::Responder &responder) {
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Target::Executor::Inquiry DirectAccessDevice::inquiry_values() {
|
||||
return Inquiry("Apple", "ProFile", "1"); // All just guesses.
|
||||
}
|
||||
|
||||
bool DirectAccessDevice::format_unit(const Target::CommandState &state, Target::Responder &responder) {
|
||||
// Formatting: immediate.
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
return true;
|
||||
}
|
40
Storage/MassStorage/SCSI/DirectAccessDevice.hpp
Normal file
40
Storage/MassStorage/SCSI/DirectAccessDevice.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
//
|
||||
// DirectAccessDevice.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SCSI_DirectAccessDevice_hpp
|
||||
#define SCSI_DirectAccessDevice_hpp
|
||||
|
||||
#include "Target.hpp"
|
||||
#include "../MassStorageDevice.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace SCSI {
|
||||
|
||||
class DirectAccessDevice: public Target::Executor {
|
||||
public:
|
||||
|
||||
/*!
|
||||
Sets the backing storage exposed by this direct-access device.
|
||||
*/
|
||||
void set_storage(const std::shared_ptr<Storage::MassStorage::MassStorageDevice> &device);
|
||||
|
||||
/* SCSI commands. */
|
||||
bool read(const Target::CommandState &, Target::Responder &);
|
||||
bool write(const Target::CommandState &, Target::Responder &);
|
||||
Inquiry inquiry_values();
|
||||
bool read_capacity(const Target::CommandState &, Target::Responder &);
|
||||
bool format_unit(const Target::CommandState &, Target::Responder &);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Storage::MassStorage::MassStorageDevice> device_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* SCSI_DirectAccessDevice_hpp */
|
104
Storage/MassStorage/SCSI/SCSI.cpp
Normal file
104
Storage/MassStorage/SCSI/SCSI.cpp
Normal file
@ -0,0 +1,104 @@
|
||||
//
|
||||
// SCSI.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "SCSI.hpp"
|
||||
|
||||
using namespace SCSI;
|
||||
|
||||
Bus::Bus(HalfCycles clock_rate) {
|
||||
cycles_to_time_ = 1.0 / double(clock_rate.as_int());
|
||||
|
||||
// NB: note that the dispatch times below are **ORDERED**
|
||||
// from least to greatest. Each box should contain the number
|
||||
// of whole clock periods it will take to get the the first
|
||||
// discrete moment after the required delay interval has been met.
|
||||
dispatch_times_[0] = 1 + int(CableSkew / cycles_to_time_);
|
||||
dispatch_times_[1] = 1 + int(DeskewDelay / cycles_to_time_);
|
||||
dispatch_times_[2] = 1 + int(BusFreeDelay / cycles_to_time_);
|
||||
dispatch_times_[3] = 1 + int(BusSettleDelay / cycles_to_time_);
|
||||
dispatch_times_[4] = 1 + int(BusClearDelay / cycles_to_time_);
|
||||
dispatch_times_[5] = 1 + int(BusSetDelay / cycles_to_time_);
|
||||
dispatch_times_[6] = 1 + int(ArbitrationDelay / cycles_to_time_);
|
||||
dispatch_times_[7] = 1 + int(ResetHoldTime / cycles_to_time_);
|
||||
}
|
||||
|
||||
size_t Bus::add_device() {
|
||||
const auto slot = device_states_.size();
|
||||
device_states_.push_back(DefaultBusState);
|
||||
return slot;
|
||||
}
|
||||
|
||||
void Bus::set_device_output(size_t device, BusState output) {
|
||||
if(device_states_[device] == output) return;
|
||||
device_states_[device] = output;
|
||||
|
||||
const auto previous_state = state_;
|
||||
state_ = DefaultBusState;
|
||||
for(auto state: device_states_) {
|
||||
state_ |= state;
|
||||
}
|
||||
if(state_ == previous_state) return;
|
||||
|
||||
// printf("SCSI bus: %02x %c%c%c%c%c%c%c%c%c%c\n",
|
||||
// state_ & 0xff,
|
||||
// (state_ & Line::Parity) ? 'p' : '-',
|
||||
// (state_ & Line::SelectTarget) ? 's' : '-',
|
||||
// (state_ & Line::Attention) ? 't' : '-',
|
||||
// (state_ & Line::Control) ? 'c' : '-',
|
||||
// (state_ & Line::Busy) ? 'b' : '-',
|
||||
// (state_ & Line::Acknowledge) ? 'a' : '-',
|
||||
// (state_ & Line::Reset) ? 'r' : '-',
|
||||
// (state_ & Line::Input) ? 'i' : '-',
|
||||
// (state_ & Line::Message) ? 'm' : '-',
|
||||
// (state_ & Line::Request) ? 'q' : '-'
|
||||
// );
|
||||
|
||||
bool was_asleep = preferred_clocking() == ClockingHint::Preference::None;
|
||||
dispatch_index_ = 0;
|
||||
time_in_state_ = HalfCycles(0);
|
||||
if(was_asleep) update_clocking_observer();
|
||||
}
|
||||
|
||||
BusState Bus::get_state() {
|
||||
return state_;
|
||||
}
|
||||
|
||||
void Bus::add_observer(Observer *observer) {
|
||||
observers_.push_back(observer);
|
||||
}
|
||||
|
||||
ClockingHint::Preference Bus::preferred_clocking() {
|
||||
return (dispatch_index_ < dispatch_times_.size()) ? ClockingHint::Preference::RealTime : ClockingHint::Preference::None;
|
||||
}
|
||||
|
||||
void Bus::update_observers() {
|
||||
const auto time_elapsed = double(time_in_state_.as_int()) * cycles_to_time_;
|
||||
for(auto &observer: observers_) {
|
||||
observer->scsi_bus_did_change(this, state_, time_elapsed);
|
||||
}
|
||||
}
|
||||
|
||||
void Bus::run_for(HalfCycles time) {
|
||||
if(dispatch_index_ < dispatch_times_.size()) {
|
||||
time_in_state_ += time;
|
||||
|
||||
const auto old_index = dispatch_index_;
|
||||
const auto time_as_int = time_in_state_.as_int();
|
||||
while(time_as_int >= dispatch_times_[dispatch_index_] && dispatch_index_ < dispatch_times_.size()) {
|
||||
++dispatch_index_;
|
||||
}
|
||||
|
||||
if(dispatch_index_ != old_index) {
|
||||
update_observers();
|
||||
}
|
||||
|
||||
if(preferred_clocking() == ClockingHint::Preference::None) {
|
||||
update_clocking_observer();
|
||||
}
|
||||
}
|
||||
}
|
156
Storage/MassStorage/SCSI/SCSI.hpp
Normal file
156
Storage/MassStorage/SCSI/SCSI.hpp
Normal file
@ -0,0 +1,156 @@
|
||||
//
|
||||
// SCSI.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SCSI_hpp
|
||||
#define SCSI_hpp
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
namespace SCSI {
|
||||
|
||||
typedef int BusState;
|
||||
|
||||
static const BusState DefaultBusState = 0;
|
||||
|
||||
/*!
|
||||
SCSI bus state is encoded entirely within an int.
|
||||
Bits correlate mostly but not exactly to the real SCSI bus.
|
||||
|
||||
TODO: validate levels below. The bus uses open collector logic,
|
||||
so active low needs to be respected.
|
||||
*/
|
||||
enum Line: BusState {
|
||||
/// Provides the value currently on the data lines.
|
||||
Data = 0xff,
|
||||
/// Parity of the data lines.
|
||||
Parity = 1 << 8,
|
||||
/// Set if the SEL line is currently selecting a target.
|
||||
/// Reset if it is selecting an initiator.
|
||||
SelectTarget = 1 << 9,
|
||||
/// Set to indicate an attention condition. Reset otherwise.
|
||||
Attention = 1 << 10,
|
||||
/// Set if control is on the bus. Reset if data is on the bus.
|
||||
Control = 1 << 11,
|
||||
/// Set if the bus is busy. Reset otherwise.
|
||||
Busy = 1 << 12,
|
||||
/// Set if acknowledging a data transfer request. Reset otherwise.
|
||||
Acknowledge = 1 << 13,
|
||||
/// Set if a bus reset is being requested. Reset otherwise.
|
||||
Reset = 1 << 14,
|
||||
/// Set if data is currently an input to the initiator. Reset if it is an output.
|
||||
Input = 1 << 15,
|
||||
/// Set during the message phase. Reset otherwise.
|
||||
Message = 1 << 16,
|
||||
/// Set if requesting a data transfer. Reset otherwise.
|
||||
Request = 1 << 17,
|
||||
};
|
||||
|
||||
#define us(x) (x) / 1000000.0
|
||||
#define ns(x) (x) / 1000000000.0
|
||||
|
||||
/// The minimum amount of time that reset must be held for.
|
||||
constexpr double ResetHoldTime = us(25.0);
|
||||
|
||||
/// The minimum amount of time a SCSI device must wait after asserting ::Busy
|
||||
/// until the data bus can be inspected to see whether arbitration has been won.
|
||||
constexpr double ArbitrationDelay = us(1.7);
|
||||
|
||||
/// The maximum amount of time a SCSI device can take from a detection that the
|
||||
/// bus is free until it asserts ::Busy and its ID for the purposes of arbitration.
|
||||
constexpr double BusSetDelay = us(1.1);
|
||||
|
||||
/// The maximum amount of time a SCSI device is permitted to take to stop driving
|
||||
/// all bus signals after: (i) the release of ::Busy ushering in a bus free phase;
|
||||
/// or (ii) some other device has asserted ::Select during an arbitration phase.
|
||||
constexpr double BusClearDelay = ns(650.0);
|
||||
|
||||
/// The minimum amount of time to wait for the bus to settle after changing
|
||||
/// "certain control signals". TODO: which?
|
||||
constexpr double BusSettleDelay = ns(450.0);
|
||||
|
||||
/// The minimum amount of time a SCSI must wait from detecting that the bus is free
|
||||
/// and asserting ::Busy if starting an arbitration phase.
|
||||
constexpr double BusFreeDelay = ns(100.0);
|
||||
|
||||
/// The minimum amount of time required for deskew of "certain signals". TODO: which?
|
||||
constexpr double DeskewDelay = ns(45.0);
|
||||
|
||||
/// The maximum amount of time that propagation of a SCSI bus signal can take between
|
||||
/// any two devices.
|
||||
constexpr double CableSkew = ns(10.0);
|
||||
|
||||
#undef ns
|
||||
#undef us
|
||||
|
||||
class Bus: public ClockingHint::Source {
|
||||
public:
|
||||
Bus(HalfCycles clock_rate);
|
||||
|
||||
/*!
|
||||
Adds a device to the bus, returning the index it should use
|
||||
to refer to itself in subsequent calls to set_device_output.
|
||||
*/
|
||||
size_t add_device();
|
||||
|
||||
/*!
|
||||
Sets the current output for @c device.
|
||||
*/
|
||||
void set_device_output(size_t device, BusState output);
|
||||
|
||||
/*!
|
||||
@returns the current state of the bus.
|
||||
*/
|
||||
BusState get_state();
|
||||
|
||||
struct Observer {
|
||||
/// Reports to an observer that the bus changed from a previous state to @c new_state,
|
||||
/// along with the time since that change was observed. The time is in seconds, and is
|
||||
/// intended for comparison with the various constants defined at namespace scope:
|
||||
/// ArbitrationDelay et al. Observers will be notified each time one of the thresholds
|
||||
/// defined by those constants is crossed.
|
||||
virtual void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) = 0;
|
||||
};
|
||||
/*!
|
||||
Adds an observer.
|
||||
*/
|
||||
void add_observer(Observer *);
|
||||
|
||||
/*!
|
||||
SCSI buses don't have a clock. But devices on the bus are concerned with time-based factors,
|
||||
and `run_for` is the way that time propagates within this emulator. So please permit this
|
||||
fiction.
|
||||
*/
|
||||
void run_for(HalfCycles);
|
||||
|
||||
/*!
|
||||
Forces a `scsi_bus_did_change` propagation now.
|
||||
*/
|
||||
void update_observers();
|
||||
|
||||
/// As per ClockingHint::Source.
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
|
||||
private:
|
||||
HalfCycles time_in_state_;
|
||||
double cycles_to_time_ = 1.0;
|
||||
size_t dispatch_index_ = 0;
|
||||
std::array<int, 8> dispatch_times_;
|
||||
|
||||
std::vector<BusState> device_states_;
|
||||
BusState state_ = DefaultBusState;
|
||||
std::vector<Observer *> observers_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* SCSI_hpp */
|
93
Storage/MassStorage/SCSI/Target.cpp
Normal file
93
Storage/MassStorage/SCSI/Target.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
//
|
||||
// Target.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Target.hpp"
|
||||
|
||||
using namespace SCSI::Target;
|
||||
|
||||
CommandState::CommandState(const std::vector<uint8_t> &data, const std::vector<uint8_t> &received) : data_(data), received_(received) {}
|
||||
|
||||
uint32_t CommandState::address() const {
|
||||
switch(data_.size()) {
|
||||
default: return 0;
|
||||
case 6:
|
||||
return
|
||||
(uint32_t(data_[1]) << 16) |
|
||||
(uint32_t(data_[2]) << 8) |
|
||||
uint32_t(data_[3]);
|
||||
case 10:
|
||||
case 12:
|
||||
return
|
||||
(uint32_t(data_[1]) << 24) |
|
||||
(uint32_t(data_[2]) << 16) |
|
||||
(uint32_t(data_[3]) << 8) |
|
||||
uint32_t(data_[4]);
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t CommandState::number_of_blocks() const {
|
||||
switch(data_.size()) {
|
||||
default: return 0;
|
||||
case 6:
|
||||
return uint16_t(data_[4]);
|
||||
case 10:
|
||||
return uint16_t((data_[7] << 8) | data_[8]);
|
||||
}
|
||||
}
|
||||
|
||||
CommandState::ReadWrite CommandState::read_write_specs() const {
|
||||
ReadWrite specs;
|
||||
|
||||
specs.address = address();
|
||||
specs.number_of_blocks = number_of_blocks();
|
||||
if(!specs.number_of_blocks && (data_.size() == 6)) {
|
||||
specs.number_of_blocks = 256;
|
||||
}
|
||||
|
||||
return specs;
|
||||
}
|
||||
|
||||
size_t CommandState::allocated_inquiry_bytes() const {
|
||||
// 0 means 256 bytes allocated for inquiry.
|
||||
return size_t(((data_[4] - 1) & 0xff) + 1);
|
||||
}
|
||||
|
||||
CommandState::ModeSense CommandState::mode_sense_specs() const {
|
||||
ModeSense specs;
|
||||
|
||||
specs.exclude_block_descriptors = (data_[1] & 0x08);
|
||||
specs.page_control_values = ModeSense::PageControlValues(data_[2] >> 5);
|
||||
specs.page_code = data_[2] & 0x3f;
|
||||
specs.subpage_code = data_[3];
|
||||
specs.allocated_bytes = number_of_blocks();
|
||||
|
||||
return specs;
|
||||
}
|
||||
|
||||
CommandState::ReadBuffer CommandState::read_buffer_specs() const {
|
||||
ReadBuffer specs;
|
||||
|
||||
specs.mode = ReadBuffer::Mode(data_[1]&7);
|
||||
if(specs.mode > ReadBuffer::Mode::Reserved) specs.mode = ReadBuffer::Mode::Reserved;
|
||||
specs.buffer_id = data_[2];
|
||||
specs.buffer_offset = uint32_t((data_[3] << 16) | (data_[4] << 8) | data_[5]);
|
||||
specs.buffer_length = uint32_t((data_[6] << 16) | (data_[7] << 8) | data_[8]);
|
||||
|
||||
return specs;
|
||||
}
|
||||
|
||||
CommandState::ModeSelect CommandState::mode_select_specs() const {
|
||||
ModeSelect specs;
|
||||
|
||||
specs.parameter_list_length = number_of_blocks();
|
||||
specs.content_is_vendor_specific = !(data_[1] & 0x10);
|
||||
specs.revert_to_default = (data_[1] & 0x02);
|
||||
specs.save_pages = (data_[1] & 0x01);
|
||||
|
||||
return specs;
|
||||
}
|
401
Storage/MassStorage/SCSI/Target.hpp
Normal file
401
Storage/MassStorage/SCSI/Target.hpp
Normal file
@ -0,0 +1,401 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef SCSI_Target_hpp
|
||||
#define SCSI_Target_hpp
|
||||
|
||||
#include "SCSI.hpp"
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
|
||||
namespace SCSI {
|
||||
namespace Target {
|
||||
|
||||
/*!
|
||||
Encapsulates the arguments supplied for a target SCSI command during
|
||||
the command phase plus any other data read since then.
|
||||
*/
|
||||
class CommandState {
|
||||
public:
|
||||
CommandState(const std::vector<uint8_t> &command, const std::vector<uint8_t> &received);
|
||||
|
||||
// For read and write commands.
|
||||
struct ReadWrite {
|
||||
uint32_t address, number_of_blocks;
|
||||
};
|
||||
ReadWrite read_write_specs() const;
|
||||
|
||||
// For inquiry commands.
|
||||
size_t allocated_inquiry_bytes() const;
|
||||
|
||||
// For mode sense commands.
|
||||
struct ModeSense {
|
||||
bool exclude_block_descriptors = false;
|
||||
enum class PageControlValues {
|
||||
Current = 0,
|
||||
Changeable = 1,
|
||||
Default = 2,
|
||||
Saved = 3
|
||||
} page_control_values = PageControlValues::Current;
|
||||
uint8_t page_code;
|
||||
uint8_t subpage_code;
|
||||
uint16_t allocated_bytes;
|
||||
};
|
||||
ModeSense mode_sense_specs() const;
|
||||
|
||||
struct ModeSelect {
|
||||
bool content_is_vendor_specific = true;
|
||||
bool revert_to_default = false;
|
||||
bool save_pages = false;
|
||||
uint16_t parameter_list_length = 0;
|
||||
};
|
||||
ModeSelect mode_select_specs() const;
|
||||
|
||||
struct ReadBuffer {
|
||||
enum class Mode {
|
||||
CombinedHeaderAndData = 0,
|
||||
VendorSpecific = 1,
|
||||
Data = 2,
|
||||
Descriptor = 3,
|
||||
Reserved = 4
|
||||
} mode = Mode::CombinedHeaderAndData;
|
||||
uint8_t buffer_id = 0;
|
||||
uint32_t buffer_offset = 0, buffer_length = 0;
|
||||
};
|
||||
ReadBuffer read_buffer_specs() const;
|
||||
|
||||
const std::vector<uint8_t> &received_data() const {
|
||||
return received_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t address() const;
|
||||
uint16_t number_of_blocks() const;
|
||||
const std::vector<uint8_t> &data_;
|
||||
const std::vector<uint8_t> &received_;
|
||||
};
|
||||
|
||||
/*!
|
||||
A Responder is supplied both (i) to the initial call-in to an Executor; and
|
||||
(ii) to all continuations provided by that Executor. It allows the next
|
||||
set of bus interactions to be dictated.
|
||||
*/
|
||||
struct Responder {
|
||||
using continuation = std::function<void(const CommandState &, Responder &)>;
|
||||
|
||||
enum class Status {
|
||||
Good = 0x00,
|
||||
CheckCondition = 0x02,
|
||||
ConditionMet = 0x04,
|
||||
Busy = 0x08,
|
||||
Intermediate = 0x10,
|
||||
IntermediateConditionMet = 0x14,
|
||||
ReservationConflict = 0x18,
|
||||
CommandTerminated = 0x22,
|
||||
TaskSetFull = 0x28,
|
||||
ACAActive = 0x30,
|
||||
TaskAborted = 0x40
|
||||
};
|
||||
|
||||
enum class Message {
|
||||
CommandComplete = 0x00
|
||||
};
|
||||
|
||||
/*!
|
||||
Causes the SCSI device to send @c data to the initiator and
|
||||
call @c next when done.
|
||||
*/
|
||||
virtual void send_data(std::vector<uint8_t> &&data, continuation next) = 0;
|
||||
/*!
|
||||
Causes the SCSI device to receive @c length bytes from the initiator and
|
||||
call @c next when done. The bytes will be accessible via the CommandInput object.
|
||||
*/
|
||||
virtual void receive_data(size_t length, continuation next) = 0;
|
||||
/*!
|
||||
Communicates the supplied status to the initiator.
|
||||
*/
|
||||
virtual void send_status(Status, continuation next) = 0;
|
||||
/*!
|
||||
Communicates the supplied message to the initiator.
|
||||
*/
|
||||
virtual void send_message(Message, continuation next) = 0;
|
||||
/*!
|
||||
Ends the SCSI command.
|
||||
*/
|
||||
virtual void end_command() = 0;
|
||||
/*!
|
||||
Terminates a SCSI command, sending the proper sequence of status and message phases.
|
||||
*/
|
||||
void terminate_command(Status status) {
|
||||
send_status(status, [] (const Target::CommandState &state, Target::Responder &responder) {
|
||||
responder.send_message(Target::Responder::Message::CommandComplete, [] (const Target::CommandState &state, Target::Responder &responder) {
|
||||
responder.end_command();
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
Executors contain device-specific logic; when the target has completed
|
||||
the command phase it will call the appropriate method on its executor,
|
||||
supplying it with the command's arguments.
|
||||
|
||||
If you implement a method, you should push a result and return @c true.
|
||||
Return @c false if you do not implement a method (or, just inherit from
|
||||
the basic executor below, and don't implement anything you don't support).
|
||||
*/
|
||||
struct Executor {
|
||||
/* Group 0 commands. */
|
||||
bool test_unit_ready(const CommandState &, Responder &responder) {
|
||||
/* "Returns zero status if addressed unit is powered on and ready. */
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
return true;
|
||||
}
|
||||
bool rezero_unit(const CommandState &, Responder &) { return false; }
|
||||
bool request_sense(const CommandState &, Responder &) { return false; }
|
||||
bool format_unit(const CommandState &, Responder &) { return false; }
|
||||
bool seek(const CommandState &, Responder &) { return false; }
|
||||
bool reserve_unit(const CommandState &, Responder &) { return false; }
|
||||
bool release_unit(const CommandState &, Responder &) { return false; }
|
||||
bool read_diagnostic(const CommandState &, Responder &) { return false; }
|
||||
bool write_diagnostic(const CommandState &, Responder &) { return false; }
|
||||
|
||||
/// Mode sense: the default implementation will call into the appropriate
|
||||
/// structured getter.
|
||||
bool mode_sense(const CommandState &state, Responder &responder) {
|
||||
const auto specs = state.mode_sense_specs();
|
||||
std::vector<uint8_t> response = {
|
||||
specs.page_code,
|
||||
uint8_t(specs.allocated_bytes)
|
||||
};
|
||||
switch(specs.page_code) {
|
||||
default:
|
||||
printf("Unknown mode sense page code %02x\n", specs.page_code);
|
||||
response.resize(specs.allocated_bytes);
|
||||
break;
|
||||
|
||||
case 0x30:
|
||||
response.resize(34);
|
||||
strcpy(reinterpret_cast<char *>(&response[14]), "APPLE COMPUTER, INC"); // This seems to be required to satisfy the Apple HD SC Utility.
|
||||
break;
|
||||
}
|
||||
|
||||
if(specs.allocated_bytes < response.size()) {
|
||||
response.resize(specs.allocated_bytes);
|
||||
}
|
||||
responder.send_data(std::move(response), [] (const Target::CommandState &state, Target::Responder &responder) {
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mode_select(const CommandState &state, Responder &responder) {
|
||||
const auto specs = state.mode_select_specs();
|
||||
|
||||
responder.receive_data(specs.parameter_list_length, [] (const Target::CommandState &state, Target::Responder &responder) {
|
||||
// TODO: parse data according to current sense mode.
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Inquiry: the default implementation will call the structured version and
|
||||
/// package appropriately.
|
||||
struct Inquiry {
|
||||
enum class DeviceType {
|
||||
DirectAccess = 0,
|
||||
SequentialAccess = 1,
|
||||
Printer = 2,
|
||||
Processor = 3,
|
||||
WriteOnceMultipleRead = 4,
|
||||
ReadOnlyDirectAccess = 5,
|
||||
Scanner = 6,
|
||||
OpticalMemory = 7,
|
||||
MediumChanger = 8,
|
||||
Communications = 9,
|
||||
} device_type = DeviceType::DirectAccess;
|
||||
bool is_removeable = false;
|
||||
uint8_t iso_standard = 0, ecma_standard = 0, ansi_standard = 0;
|
||||
bool supports_asynchronous_events = false;
|
||||
bool supports_terminate_io_process = false;
|
||||
bool supports_relative_addressing = false;
|
||||
bool supports_synchronous_transfer = true;
|
||||
bool supports_linked_commands = false;
|
||||
bool supports_command_queing = false;
|
||||
bool supports_soft_reset = false;
|
||||
char vendor_identifier[9] = "";
|
||||
char product_identifier[17] = "";
|
||||
char product_revision_level[5] = "";
|
||||
|
||||
Inquiry(const char *vendor, const char *product, const char *revision) {
|
||||
assert(strlen(vendor) <= 8);
|
||||
assert(strlen(product) <= 16);
|
||||
assert(strlen(revision) <= 4);
|
||||
strcpy(vendor_identifier, vendor);
|
||||
strcpy(product_identifier, product);
|
||||
strcpy(product_revision_level, revision);
|
||||
}
|
||||
Inquiry() = default;
|
||||
};
|
||||
Inquiry inquiry_values() {
|
||||
return Inquiry();
|
||||
}
|
||||
bool inquiry(const CommandState &state, Responder &responder) {
|
||||
const Inquiry inq = inquiry_values();
|
||||
|
||||
// Set up the easy fields.
|
||||
std::vector<uint8_t> response = {
|
||||
uint8_t(inq.device_type),
|
||||
uint8_t(inq.is_removeable ? 0x80 : 0x00),
|
||||
uint8_t((inq.iso_standard << 5) | (inq.ecma_standard << 3) | (inq.ansi_standard)),
|
||||
uint8_t((inq.supports_asynchronous_events ? 0x80 : 0x00) | (inq.supports_terminate_io_process ? 0x40 : 0x00) | 0x02),
|
||||
32, /* Additional length: 36 - 4. */
|
||||
0x00, /* Reserved. */
|
||||
0x00, /* Reserved. */
|
||||
uint8_t(
|
||||
(inq.supports_relative_addressing ? 0x80 : 0x00) |
|
||||
/* b6: supports 32-bit data; b5: supports 16-bit data. */
|
||||
(inq.supports_synchronous_transfer ? 0x10 : 0x00) |
|
||||
(inq.supports_linked_commands ? 0x08 : 0x00) |
|
||||
/* b3: reserved. */
|
||||
(inq.supports_command_queing ? 0x02 : 0x00) |
|
||||
(inq.supports_soft_reset ? 0x01 : 0x00)
|
||||
),
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Space for the vendor ID. */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* Space for the product ID. */
|
||||
0x00, 0x00, 0x00, 0x00 /* Space for the revision level. */
|
||||
};
|
||||
|
||||
auto copy_string = [] (uint8_t *destination, const char *source, size_t length) -> void {
|
||||
// Copy as much of the string as will fit, and pad with spaces.
|
||||
uint8_t *end = reinterpret_cast<uint8_t *>(stpncpy(reinterpret_cast<char *>(destination), source, length));
|
||||
while(end < destination + length) {
|
||||
*end = ' ';
|
||||
++end;
|
||||
}
|
||||
};
|
||||
copy_string(&response[8], inq.vendor_identifier, 8);
|
||||
copy_string(&response[16], inq.product_identifier, 16);
|
||||
copy_string(&response[32], inq.product_revision_level, 4);
|
||||
|
||||
// Truncate if requested.
|
||||
const auto allocated_bytes = state.allocated_inquiry_bytes();
|
||||
if(allocated_bytes < response.size()) {
|
||||
response.resize(allocated_bytes);
|
||||
}
|
||||
|
||||
responder.send_data(std::move(response), [] (const Target::CommandState &state, Target::Responder &responder) {
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Group 0/1 commands. */
|
||||
bool read(const CommandState &, Responder &) { return false; }
|
||||
bool write(const CommandState &, Responder &) { return false; }
|
||||
|
||||
/* Group 1 commands. */
|
||||
bool read_capacity(const CommandState &, Responder &) { return false; }
|
||||
bool write_and_verify(const CommandState &, Responder &) { return false; }
|
||||
bool verify(const CommandState &, Responder &) { return false; }
|
||||
bool search_data_equal(const CommandState &, Responder &) { return false; }
|
||||
bool search_data_high(const CommandState &, Responder &) { return false; }
|
||||
bool search_data_low(const CommandState &, Responder &) { return false; }
|
||||
bool read_buffer(const CommandState &state, Responder &responder) {
|
||||
// Since I have no idea what earthly function READ BUFFER is meant to allow,
|
||||
// the default implementation just returns an empty buffer of the requested size.
|
||||
const auto specs = state.read_buffer_specs();
|
||||
responder.send_data(std::vector<uint8_t>(specs.buffer_length), [] (const Target::CommandState &, Target::Responder &responder) {
|
||||
responder.terminate_command(Target::Responder::Status::Good);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Group 5 commands. */
|
||||
bool set_block_limits(const CommandState &, Responder &) { return false; }
|
||||
};
|
||||
|
||||
/*!
|
||||
A template for any SCSI target; provides the necessary bus glue to
|
||||
receive and respond to commands. Specific targets should be implemented
|
||||
as Executors.
|
||||
*/
|
||||
template <typename Executor> class Target: public Bus::Observer, public Responder {
|
||||
public:
|
||||
/*!
|
||||
Instantiates a target attached to @c bus,
|
||||
with SCSI ID @c scsi_id — a number in the range 0 to 7.
|
||||
|
||||
Received commands will be handed to the Executor to perform.
|
||||
*/
|
||||
Target(Bus &bus, int scsi_id);
|
||||
|
||||
inline Executor *operator->() {
|
||||
return &executor_;
|
||||
}
|
||||
|
||||
private:
|
||||
Executor executor_;
|
||||
|
||||
// Bus::Observer.
|
||||
void scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) final;
|
||||
|
||||
// Responder
|
||||
void send_data(std::vector<uint8_t> &&data, continuation next) final;
|
||||
void receive_data(size_t length, continuation next) final;
|
||||
void send_status(Status, continuation next) final;
|
||||
void send_message(Message, continuation next) final;
|
||||
void end_command() final;
|
||||
|
||||
// Instance storage.
|
||||
Bus &bus_;
|
||||
const BusState scsi_id_mask_;
|
||||
const size_t scsi_bus_device_id_;
|
||||
|
||||
enum class Phase {
|
||||
AwaitingSelection,
|
||||
Command,
|
||||
ReceivingData,
|
||||
SendingData,
|
||||
SendingStatus,
|
||||
SendingMessage
|
||||
} phase_ = Phase::AwaitingSelection;
|
||||
BusState bus_state_ = DefaultBusState;
|
||||
|
||||
void set_device_output(BusState state) {
|
||||
expected_control_state_ = state & (Line::Control | Line::Input | Line::Message);
|
||||
bus_.set_device_output(scsi_bus_device_id_, state);
|
||||
}
|
||||
BusState expected_control_state_ = DefaultBusState;
|
||||
|
||||
void begin_command(uint8_t first_byte);
|
||||
std::vector<uint8_t> command_;
|
||||
Status status_;
|
||||
Message message_;
|
||||
size_t command_pointer_ = 0;
|
||||
bool dispatch_command();
|
||||
|
||||
std::vector<uint8_t> data_;
|
||||
size_t data_pointer_ = 0;
|
||||
|
||||
continuation next_function_;
|
||||
};
|
||||
|
||||
#import "TargetImplementation.hpp"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* SCSI_Target_hpp */
|
280
Storage/MassStorage/SCSI/TargetImplementation.hpp
Normal file
280
Storage/MassStorage/SCSI/TargetImplementation.hpp
Normal file
@ -0,0 +1,280 @@
|
||||
//
|
||||
// TargetImplementation.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 19/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
template <typename Executor> Target<Executor>::Target(Bus &bus, int scsi_id) :
|
||||
bus_(bus),
|
||||
scsi_id_mask_(BusState(1 << scsi_id)),
|
||||
scsi_bus_device_id_(bus.add_device()) {
|
||||
bus.add_observer(this);
|
||||
}
|
||||
|
||||
template <typename Executor> void Target<Executor>::scsi_bus_did_change(Bus *, BusState new_state, double time_since_change) {
|
||||
/*
|
||||
"The target determines that it is selected when the SEL# signal
|
||||
and its SCSI ID bit are active and the BSY# and I#/O signals
|
||||
are false. It then asserts the signal within a selection abort
|
||||
time."
|
||||
*/
|
||||
|
||||
// Wait for deskew, at the very least.
|
||||
if(time_since_change < SCSI::DeskewDelay) return;
|
||||
|
||||
// A reset always takes precedence over anything else ongoing.
|
||||
if(new_state & Line::Reset) {
|
||||
phase_ = Phase::AwaitingSelection;
|
||||
bus_state_ = DefaultBusState;
|
||||
set_device_output(bus_state_);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(phase_) {
|
||||
/*
|
||||
While awaiting selection the SCSI target is passively watching the bus waiting for its ID
|
||||
to be set during a target selection. It will segue automatically from there to the command
|
||||
phase regardless of its executor.
|
||||
*/
|
||||
case Phase::AwaitingSelection:
|
||||
if(
|
||||
(new_state & scsi_id_mask_) &&
|
||||
((new_state & (Line::SelectTarget | Line::Busy | Line::Input)) == Line::SelectTarget)
|
||||
) {
|
||||
phase_ = Phase::Command;
|
||||
command_.resize(0);
|
||||
command_pointer_ = 0;
|
||||
bus_state_ |= Line::Busy; // Initiate the command phase: request a command byte.
|
||||
set_device_output(bus_state_);
|
||||
}
|
||||
break;
|
||||
|
||||
/*
|
||||
In the command phase, the target will stream an appropriate number of bytes for the command
|
||||
it is being offered, before giving the executor a chance to handle the command. If the target
|
||||
supports this command, it becomes responsible for the appropriate next phase transition. If it
|
||||
reports that it doesn't support that command, a suitable response is automatically dispatched.
|
||||
*/
|
||||
case Phase::Command:
|
||||
// Wait for select to be disabled before beginning the control phase proper.
|
||||
if((new_state & Line::SelectTarget)) return;
|
||||
|
||||
bus_state_ |= Line::Control;
|
||||
|
||||
switch(new_state & (Line::Request | Line::Acknowledge)) {
|
||||
// If request and acknowledge are both enabled, grab a byte and cancel the request.
|
||||
case Line::Request | Line::Acknowledge:
|
||||
bus_state_ &= ~Line::Request;
|
||||
|
||||
if(command_.empty()) {
|
||||
begin_command(uint8_t(new_state));
|
||||
|
||||
// TODO: if(command_.empty()) signal_error_somehow();
|
||||
} else {
|
||||
command_[command_pointer_] = uint8_t(new_state);
|
||||
++command_pointer_;
|
||||
if(command_pointer_ == command_.size()) {
|
||||
if(!dispatch_command()) {
|
||||
// This is just a guess for now; I don't know how SCSI
|
||||
// devices are supposed to respond if they don't support
|
||||
// a command.
|
||||
terminate_command(Responder::Status::TaskAborted);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// The reset of request has caused the initiator to reset acknowledge, so it is now
|
||||
// safe to request the next byte.
|
||||
case 0:
|
||||
bus_state_ |= Line::Request;
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
set_device_output(bus_state_);
|
||||
break;
|
||||
|
||||
case Phase::ReceivingData:
|
||||
switch(new_state & (Line::Request | Line::Acknowledge)) {
|
||||
case Line::Request | Line::Acknowledge:
|
||||
bus_state_ &= ~Line::Request;
|
||||
|
||||
data_[data_pointer_] = uint8_t(new_state);
|
||||
++data_pointer_;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
if(data_pointer_ == data_.size()) {
|
||||
next_function_(CommandState(command_, data_), *this);
|
||||
} else {
|
||||
bus_state_ |= Line::Request;
|
||||
}
|
||||
break;
|
||||
}
|
||||
set_device_output(bus_state_);
|
||||
break;
|
||||
|
||||
case Phase::SendingData:
|
||||
case Phase::SendingStatus:
|
||||
case Phase::SendingMessage:
|
||||
switch(new_state & (Line::Request | Line::Acknowledge)) {
|
||||
case Line::Request | Line::Acknowledge:
|
||||
bus_state_ &= ~(Line::Request | 0xff);
|
||||
|
||||
++data_pointer_;
|
||||
break;
|
||||
|
||||
case 0:
|
||||
if(
|
||||
(phase_ == Phase::SendingMessage && data_pointer_ == 1) ||
|
||||
(phase_ == Phase::SendingStatus && data_pointer_ == 1) ||
|
||||
(phase_ == Phase::SendingData && data_pointer_ == data_.size())
|
||||
) {
|
||||
next_function_(CommandState(command_, data_), *this);
|
||||
} else {
|
||||
bus_state_ |= Line::Request;
|
||||
bus_state_ &= ~0xff;
|
||||
|
||||
switch(phase_) {
|
||||
case Phase::SendingData: bus_state_ |= data_[data_pointer_]; break;
|
||||
case Phase::SendingStatus: bus_state_ |= BusState(status_); break;
|
||||
default:
|
||||
case Phase::SendingMessage: bus_state_ |= BusState(message_); break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
set_device_output(bus_state_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Executor> void Target<Executor>::begin_command(uint8_t first_byte) {
|
||||
// The logic below is valid for SCSI-1. TODO: other SCSIs.
|
||||
switch(first_byte >> 5) {
|
||||
default: break;
|
||||
case 0: command_.resize(6); break; // Group 0 commands: 6 bytes long.
|
||||
case 1: command_.resize(10); break; // Group 1 commands: 10 bytes long.
|
||||
case 5: command_.resize(12); break; // Group 5 commands: 12 bytes long.
|
||||
}
|
||||
|
||||
// Store the first byte if it was recognised.
|
||||
if(!command_.empty()) {
|
||||
command_[0] = first_byte;
|
||||
command_pointer_ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Executor> bool Target<Executor>::dispatch_command() {
|
||||
|
||||
CommandState arguments(command_, data_);
|
||||
|
||||
#define G0(x) x
|
||||
#define G1(x) (0x20|x)
|
||||
#define G5(x) (0xa0|x)
|
||||
|
||||
LOG("---Command " << PADHEX(2) << int(command_[0]) << "---");
|
||||
|
||||
switch(command_[0]) {
|
||||
default: return false;
|
||||
|
||||
case G0(0x00): return executor_.test_unit_ready(arguments, *this);
|
||||
case G0(0x01): return executor_.rezero_unit(arguments, *this);
|
||||
case G0(0x03): return executor_.request_sense(arguments, *this);
|
||||
case G0(0x04): return executor_.format_unit(arguments, *this);
|
||||
case G0(0x08): return executor_.read(arguments, *this);
|
||||
case G0(0x0a): return executor_.write(arguments, *this);
|
||||
case G0(0x0b): return executor_.seek(arguments, *this);
|
||||
case G0(0x12): return executor_.inquiry(arguments, *this);
|
||||
case G0(0x15): return executor_.mode_select(arguments, *this);
|
||||
case G0(0x16): return executor_.reserve_unit(arguments, *this);
|
||||
case G0(0x17): return executor_.release_unit(arguments, *this);
|
||||
case G0(0x1a): return executor_.mode_sense(arguments, *this);
|
||||
case G0(0x1c): return executor_.read_diagnostic(arguments, *this);
|
||||
case G0(0x1d): return executor_.write_diagnostic(arguments, *this);
|
||||
|
||||
case G1(0x05): return executor_.read_capacity(arguments, *this);
|
||||
case G1(0x08): return executor_.read(arguments, *this);
|
||||
case G1(0x0a): return executor_.write(arguments, *this);
|
||||
case G1(0x0e): return executor_.write_and_verify(arguments, *this);
|
||||
case G1(0x0f): return executor_.verify(arguments, *this);
|
||||
case G1(0x11): return executor_.search_data_equal(arguments, *this);
|
||||
case G1(0x10): return executor_.search_data_high(arguments, *this);
|
||||
case G1(0x12): return executor_.search_data_low(arguments, *this);
|
||||
case G1(0x1c): return executor_.read_buffer(arguments, *this);
|
||||
case G1(0x15): return executor_.mode_select(arguments, *this);
|
||||
|
||||
|
||||
case G5(0x09): return executor_.set_block_limits(arguments, *this);
|
||||
}
|
||||
|
||||
#undef G0
|
||||
#undef G1
|
||||
#undef G5
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename Executor> void Target<Executor>::send_data(std::vector<uint8_t> &&data, continuation next) {
|
||||
// Data out phase: control and message all reset, input set.
|
||||
bus_state_ &= ~(Line::Control | Line::Input | Line::Message);
|
||||
bus_state_ |= Line::Input;
|
||||
|
||||
phase_ = Phase::SendingData;
|
||||
next_function_ = next;
|
||||
data_ = std::move(data);
|
||||
|
||||
data_pointer_ = 0;
|
||||
set_device_output(bus_state_);
|
||||
}
|
||||
|
||||
template <typename Executor> void Target<Executor>::receive_data(size_t length, continuation next) {
|
||||
// Data out phase: control, input and message all reset.
|
||||
bus_state_ &= ~(Line::Control | Line::Input | Line::Message);
|
||||
|
||||
phase_ = Phase::ReceivingData;
|
||||
next_function_ = next;
|
||||
data_.resize(length);
|
||||
|
||||
data_pointer_ = 0;
|
||||
set_device_output(bus_state_);
|
||||
}
|
||||
|
||||
template <typename Executor> void Target<Executor>::send_status(Status status, continuation next) {
|
||||
// Status phase: message reset, control and input set.
|
||||
bus_state_ &= ~(Line::Control | Line::Input | Line::Message);
|
||||
bus_state_ |= Line::Input | Line::Control;
|
||||
|
||||
status_ = status;
|
||||
phase_ = Phase::SendingStatus;
|
||||
next_function_ = next;
|
||||
|
||||
data_pointer_ = 0;
|
||||
set_device_output(bus_state_);
|
||||
}
|
||||
|
||||
template <typename Executor> void Target<Executor>::send_message(Message message, continuation next) {
|
||||
// Message in phase: message, control and input set.
|
||||
bus_state_ |= Line::Message | Line::Control | Line::Input;
|
||||
|
||||
message_ = message;
|
||||
phase_ = Phase::SendingMessage;
|
||||
next_function_ = next;
|
||||
|
||||
data_pointer_ = 0;
|
||||
set_device_output(bus_state_);
|
||||
}
|
||||
|
||||
template <typename Executor> void Target<Executor>::end_command() {
|
||||
// TODO: was this a linked command?
|
||||
|
||||
// Release all bus lines and return to awaiting selection.
|
||||
phase_ = Phase::AwaitingSelection;
|
||||
bus_state_ = DefaultBusState;
|
||||
set_device_output(bus_state_);
|
||||
|
||||
LOG("---Done---");
|
||||
}
|
@ -8,6 +8,8 @@
|
||||
|
||||
#include "CSW.hpp"
|
||||
|
||||
#include "../../FileHolder.hpp"
|
||||
|
||||
#include <cassert>
|
||||
|
||||
using namespace Storage::Tape;
|
||||
|
@ -10,7 +10,6 @@
|
||||
#define CSW_hpp
|
||||
|
||||
#include "../Tape.hpp"
|
||||
#include "../../FileHolder.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
@ -145,7 +145,7 @@ class BinaryTapePlayer: public TapePlayer {
|
||||
};
|
||||
void set_delegate(Delegate *delegate);
|
||||
|
||||
ClockingHint::Preference preferred_clocking() override;
|
||||
ClockingHint::Preference preferred_clocking() final;
|
||||
|
||||
protected:
|
||||
Delegate *delegate_ = nullptr;
|
||||
|
Loading…
Reference in New Issue
Block a user