mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
155 Commits
2019-08-01
...
2019-11-05
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
015f2101f8 | ||
|
|
35f1a7ab10 | ||
|
|
f4556ef6b0 | ||
|
|
4266264449 | ||
|
|
1aba1db62c | ||
|
|
0fc191c87d | ||
|
|
dc4a0e4e3b | ||
|
|
3794d94b68 | ||
|
|
d4077afd30 | ||
|
|
95c45b5515 | ||
|
|
684644420a | ||
|
|
735586f5f8 | ||
|
|
ddae086661 | ||
|
|
9c7aa5f3fc | ||
|
|
418cd07e17 | ||
|
|
e8bc254f3f | ||
|
|
3c146a3fb2 | ||
|
|
b609ce6fcb | ||
|
|
929475d31e | ||
|
|
f14d98452e | ||
|
|
9d17d48bca | ||
|
|
4ac3839185 | ||
|
|
c089d1cd09 | ||
|
|
cb85ec25cc | ||
|
|
fbf95ec2b8 | ||
|
|
6adca98f34 | ||
|
|
48f4d8b875 | ||
|
|
7758f9d0a9 | ||
|
|
7112f0336c | ||
|
|
298694a881 | ||
|
|
7ff4594f09 | ||
|
|
e8bd538182 | ||
|
|
8489e8650f | ||
|
|
114f81941e | ||
|
|
077c7d767f | ||
|
|
8f88addf9f | ||
|
|
f28c124039 | ||
|
|
a416bc0058 | ||
|
|
e78b1dcf3c | ||
|
|
8a14f5d814 | ||
|
|
e5f983fbac | ||
|
|
3e639e96e7 | ||
|
|
61993f0687 | ||
|
|
5f16fa8c08 | ||
|
|
dcea9c9ab2 | ||
|
|
e7bf0799b6 | ||
|
|
e760421f6f | ||
|
|
8ea4c17315 | ||
|
|
2e24da4614 | ||
|
|
e46601872b | ||
|
|
6d0e41b760 | ||
|
|
5a82df837d | ||
|
|
776b819a5a | ||
|
|
1783f6c84b | ||
|
|
2ef2c73efe | ||
|
|
55e003ccc1 | ||
|
|
3d54d55dbb | ||
|
|
72c0a631f7 | ||
|
|
1608a90d5d | ||
|
|
4f8a45a6ce | ||
|
|
4f0f1dcf18 | ||
|
|
839e51d92d | ||
|
|
e470cf23d8 | ||
|
|
8d4a96683a | ||
|
|
f53411a319 | ||
|
|
128a1da626 | ||
|
|
962275c22a | ||
|
|
3002ac8a4a | ||
|
|
ff43674638 | ||
|
|
2f6c366668 | ||
|
|
2ce1f0a3b1 | ||
|
|
210129c3a1 | ||
|
|
934901447a | ||
|
|
960b289e70 | ||
|
|
243e40cd79 | ||
|
|
c849188016 | ||
|
|
87e8dade2f | ||
|
|
6fc5b4e825 | ||
|
|
00ce7f8ae0 | ||
|
|
6e0e9afe2f | ||
|
|
cb0d994827 | ||
|
|
bee782234a | ||
|
|
64dad35026 | ||
|
|
cbd1a8cf78 | ||
|
|
a4ab0afce3 | ||
|
|
1c7e0f3c9d | ||
|
|
318cdb41ea | ||
|
|
2f8e31bc8b | ||
|
|
310c722cc0 | ||
|
|
25956bd90f | ||
|
|
1a60ced61b | ||
|
|
081316c071 | ||
|
|
eafbc12cc1 | ||
|
|
ca08716c52 | ||
|
|
30cef1ee22 | ||
|
|
5598802439 | ||
|
|
1c6720b0db | ||
|
|
404b088199 | ||
|
|
7d61df238a | ||
|
|
c86db12f1c | ||
|
|
ce2e85af8b | ||
|
|
2d82855f26 | ||
|
|
faec516a2c | ||
|
|
8e274ec5d0 | ||
|
|
bb1a0a0b76 | ||
|
|
252650808d | ||
|
|
e3d9254555 | ||
|
|
90cf99b626 | ||
|
|
955e909e61 | ||
|
|
8339e2044c | ||
|
|
0e0c789b02 | ||
|
|
7e001c1d03 | ||
|
|
9047932b81 | ||
|
|
f668e4a54c | ||
|
|
ce1c96d68c | ||
|
|
0f67e490e8 | ||
|
|
895c315fa5 | ||
|
|
a90a74a512 | ||
|
|
3e1286cbef | ||
|
|
949c1e1668 | ||
|
|
bbd4e4d3dc | ||
|
|
4c5f596533 | ||
|
|
4859d3781b | ||
|
|
bac0461f7f | ||
|
|
f26a200d78 | ||
|
|
28ccb7b54e | ||
|
|
b6e4c8209b | ||
|
|
16548f0765 | ||
|
|
6a80832140 | ||
|
|
c6cf0e914b | ||
|
|
35b1a55c12 | ||
|
|
e3794c0c0e | ||
|
|
f88dc23c71 | ||
|
|
0e293e4983 | ||
|
|
e334abfe20 | ||
|
|
fd2fbe0e59 | ||
|
|
330b27d085 | ||
|
|
478f2533b5 | ||
|
|
b96972a4b9 | ||
|
|
f2b083f4de | ||
|
|
80f6d665d9 | ||
|
|
a07488cf1b | ||
|
|
d67c5145c0 | ||
|
|
5e76d593af | ||
|
|
83393e8e91 | ||
|
|
e08a64d455 | ||
|
|
b93f9b3973 | ||
|
|
9c517d07d4 | ||
|
|
f45de5b87a | ||
|
|
011d76175c | ||
|
|
96005261c7 | ||
|
|
c8177af45a | ||
|
|
97eff5b16d | ||
|
|
917520fb1e | ||
|
|
335dda3d55 |
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
Machines that accumulate HalfCycle time but supply to a Cycle-counted device may supply a
|
||||
separate @c TargetTimeScale at template declaration.
|
||||
*/
|
||||
template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
|
||||
template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class JustInTimeActor {
|
||||
public:
|
||||
/// Constructs a new JustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> JustInTimeActor(Args&&... args) : object_(std::forward<Args>(args)...) {}
|
||||
@@ -60,7 +60,7 @@ template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale>
|
||||
Any time the amount of accumulated time crosses a threshold provided at construction time,
|
||||
the object will be updated on the AsyncTaskQueue.
|
||||
*/
|
||||
template <class T, class LocalTimeScale, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
|
||||
template <class T, class LocalTimeScale = HalfCycles, class TargetTimeScale = LocalTimeScale> class AsyncJustInTimeActor {
|
||||
public:
|
||||
/// Constructs a new AsyncJustInTimeActor using the same construction arguments as the included object.
|
||||
template<typename... Args> AsyncJustInTimeActor(TargetTimeScale threshold, Args&&... args) :
|
||||
|
||||
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 */
|
||||
@@ -24,7 +24,7 @@ class BusHandler {
|
||||
virtual void set_interrupt(bool irq) {}
|
||||
};
|
||||
|
||||
class i8272: public Storage::Disk::MFMController {
|
||||
class i8272 : public Storage::Disk::MFMController {
|
||||
public:
|
||||
i8272(BusHandler &bus_handler, Cycles clock_rate);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -100,7 +100,7 @@ class Base {
|
||||
// (though, in practice, it won't happen until the next
|
||||
// external slot after this number of cycles after the
|
||||
// device has requested the read or write).
|
||||
return 7;
|
||||
return 6;
|
||||
}
|
||||
|
||||
// Holds the main status register.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -235,23 +235,32 @@ void IWM::run_for(const Cycles cycles) {
|
||||
// Activity otherwise depends on mode and motor state.
|
||||
int integer_cycles = cycles.as_int();
|
||||
switch(shift_mode_) {
|
||||
case ShiftMode::Reading:
|
||||
case ShiftMode::Reading: {
|
||||
// Per the IWM patent, column 7, around line 35 onwards: "The expected time
|
||||
// is widened by approximately one-half an interval before and after the
|
||||
// expected time since the data is not precisely spaced when read due to
|
||||
// variations in drive speed and other external factors". The error_margin
|
||||
// here implements the 'after' part of that contract.
|
||||
const auto error_margin = Cycles(bit_length_.as_int() >> 1);
|
||||
|
||||
if(drive_is_rotating_[active_drive_]) {
|
||||
while(integer_cycles--) {
|
||||
drives_[active_drive_]->run_for(Cycles(1));
|
||||
++cycles_since_shift_;
|
||||
if(cycles_since_shift_ == bit_length_ + Cycles(2)) {
|
||||
if(cycles_since_shift_ == bit_length_ + error_margin) {
|
||||
propose_shift(0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
while(cycles_since_shift_ + integer_cycles >= bit_length_ + Cycles(2)) {
|
||||
while(cycles_since_shift_ + integer_cycles >= bit_length_ + error_margin) {
|
||||
const auto run_length = bit_length_ + error_margin - cycles_since_shift_;
|
||||
integer_cycles -= run_length.as_int();
|
||||
cycles_since_shift_ += run_length;
|
||||
propose_shift(0);
|
||||
integer_cycles -= bit_length_.as_int() + 2 - cycles_since_shift_.as_int();
|
||||
}
|
||||
cycles_since_shift_ += Cycles(integer_cycles);
|
||||
}
|
||||
break;
|
||||
} break;
|
||||
|
||||
case ShiftMode::Writing:
|
||||
if(drives_[active_drive_]->is_writing()) {
|
||||
@@ -351,12 +360,28 @@ void IWM::propose_shift(uint8_t bit) {
|
||||
// TODO: synchronous mode.
|
||||
|
||||
// LOG("Shifting input");
|
||||
|
||||
// See above for text from the IWM patent, column 7, around line 35 onwards.
|
||||
// The error_margin here implements the 'before' part of that contract.
|
||||
//
|
||||
// Basic effective logic: if at least 1 is fozund in the bit_length_ cycles centred
|
||||
// on the current expected bit delivery time as implied by cycles_since_shift_,
|
||||
// shift in a 1 and start a new window wherever the first found 1 was.
|
||||
//
|
||||
// If no 1s are found, shift in a 0 and don't alter expectations as to window placement.
|
||||
const auto error_margin = Cycles(bit_length_.as_int() >> 1);
|
||||
if(bit && cycles_since_shift_ < error_margin) return;
|
||||
|
||||
shift_register_ = uint8_t((shift_register_ << 1) | bit);
|
||||
if(shift_register_ & 0x80) {
|
||||
data_register_ = shift_register_;
|
||||
shift_register_ = 0;
|
||||
}
|
||||
cycles_since_shift_ = Cycles(0);
|
||||
|
||||
if(bit)
|
||||
cycles_since_shift_ = Cycles(0);
|
||||
else
|
||||
cycles_since_shift_ -= bit_length_;
|
||||
}
|
||||
|
||||
void IWM::set_drive(int slot, IWMDrive *drive) {
|
||||
@@ -374,3 +399,8 @@ void IWM::set_component_prefers_clocking(ClockingHint::Source *component, Clocki
|
||||
drive_is_rotating_[1] = is_rotating;
|
||||
}
|
||||
}
|
||||
|
||||
void IWM::set_activity_observer(Activity::Observer *observer) {
|
||||
if(drives_[0]) drives_[0]->set_activity_observer(observer, "Internal Floppy", true);
|
||||
if(drives_[1]) drives_[1]->set_activity_observer(observer, "External Floppy", true);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,11 @@
|
||||
#ifndef IWM_hpp
|
||||
#define IWM_hpp
|
||||
|
||||
#include "../../Activity/Observer.hpp"
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../ClockReceiver/ClockingHintSource.hpp"
|
||||
|
||||
#include "../../Storage/Disk/Drive.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
@@ -67,6 +70,10 @@ class IWM:
|
||||
/// Connects a drive to the IWM.
|
||||
void set_drive(int slot, IWMDrive *drive);
|
||||
|
||||
/// Registers the currently-connected drives as @c Activity::Sources ;
|
||||
/// the first will be declared 'Internal', the second 'External'.
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
private:
|
||||
// Storage::Disk::Drive::EventDelegate.
|
||||
void process_event(const Storage::Disk::Drive::Event &event) override;
|
||||
|
||||
@@ -71,7 +71,9 @@ void DoubleDensityDrive::set_rotation_speed(float revolutions_per_minute) {
|
||||
|
||||
// MARK: - Control input/output.
|
||||
|
||||
void DoubleDensityDrive::set_enabled(bool) {
|
||||
void DoubleDensityDrive::set_enabled(bool enabled) {
|
||||
// Disabling a drive also stops its motor.
|
||||
if(!enabled) set_motor_on(false);
|
||||
}
|
||||
|
||||
void DoubleDensityDrive::set_control_lines(int lines) {
|
||||
|
||||
@@ -21,9 +21,9 @@ void append_bool(Configurable::SelectionSet &selection_set, const std::string &n
|
||||
Enquires for a Boolean selection for option @c name from @c selections_by_option, storing it to @c result if found.
|
||||
*/
|
||||
bool get_bool(const Configurable::SelectionSet &selections_by_option, const std::string &name, bool &result) {
|
||||
auto quickload = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, "quickload");
|
||||
if(!quickload) return false;
|
||||
result = quickload->value;
|
||||
auto selection = Configurable::selection<Configurable::BooleanSelection>(selections_by_option, name);
|
||||
if(!selection) return false;
|
||||
result = selection->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ std::vector<std::unique_ptr<Configurable::Option>> Configurable::standard_option
|
||||
options.emplace_back(new Configurable::ListOption("Display", "display", display_options));
|
||||
}
|
||||
if(mask & AutomaticTapeMotorControl) options.emplace_back(new Configurable::BooleanOption("Automatic Tape Motor Control", "autotapemotor"));
|
||||
if(mask & QuickBoot) options.emplace_back(new Configurable::BooleanOption("Boot Quickly", "quickboot"));
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -66,6 +67,10 @@ void Configurable::append_display_selection(Configurable::SelectionSet &selectio
|
||||
selection_set["display"] = std::unique_ptr<Configurable::Selection>(new Configurable::ListSelection(string_selection));
|
||||
}
|
||||
|
||||
void Configurable::append_quick_boot_selection(Configurable::SelectionSet &selection_set, bool selection) {
|
||||
append_bool(selection_set, "quickboot", selection);
|
||||
}
|
||||
|
||||
// MARK: - Selection parsers
|
||||
bool Configurable::get_quick_load_tape(const Configurable::SelectionSet &selections_by_option, bool &result) {
|
||||
return get_bool(selections_by_option, "quickload", result);
|
||||
@@ -97,3 +102,7 @@ bool Configurable::get_display(const Configurable::SelectionSet &selections_by_o
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Configurable::get_quick_boot(const Configurable::SelectionSet &selections_by_option, bool &result) {
|
||||
return get_bool(selections_by_option, "quickboot", result);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ enum StandardOptions {
|
||||
DisplayCompositeColour = (1 << 2),
|
||||
DisplayCompositeMonochrome = (1 << 3),
|
||||
QuickLoadTape = (1 << 4),
|
||||
AutomaticTapeMotorControl = (1 << 5)
|
||||
AutomaticTapeMotorControl = (1 << 5),
|
||||
QuickBoot = (1 << 6),
|
||||
};
|
||||
|
||||
enum class Display {
|
||||
@@ -49,6 +50,11 @@ void append_automatic_tape_motor_control_selection(SelectionSet &selection_set,
|
||||
*/
|
||||
void append_display_selection(SelectionSet &selection_set, Display selection);
|
||||
|
||||
/*!
|
||||
Appends to @c selection_set a selection of @c selection for QuickBoot.
|
||||
*/
|
||||
void append_quick_boot_selection(SelectionSet &selection_set, bool selection);
|
||||
|
||||
/*!
|
||||
Attempts to discern a QuickLoadTape selection from @c selections_by_option.
|
||||
|
||||
@@ -76,6 +82,15 @@ bool get_automatic_tape_motor_control_selection(const SelectionSet &selections_b
|
||||
*/
|
||||
bool get_display(const SelectionSet &selections_by_option, Display &result);
|
||||
|
||||
/*!
|
||||
Attempts to QuickBoot a QuickLoadTape selection from @c selections_by_option.
|
||||
|
||||
@param selections_by_option The user selections.
|
||||
@param result The location to which the selection will be stored if found.
|
||||
@returns @c true if a selection is found; @c false otherwise.
|
||||
*/
|
||||
bool get_quick_boot(const SelectionSet &selections_by_option, bool &result);
|
||||
|
||||
}
|
||||
|
||||
#endif /* StandardOptions_hpp */
|
||||
|
||||
@@ -10,13 +10,14 @@
|
||||
|
||||
using namespace Inputs;
|
||||
|
||||
Keyboard::Keyboard() {
|
||||
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {
|
||||
for(int k = 0; k < int(Key::Help); ++k) {
|
||||
observed_keys_.insert(Key(k));
|
||||
}
|
||||
}
|
||||
|
||||
Keyboard::Keyboard(const std::set<Key> &observed_keys) : observed_keys_(observed_keys), is_exclusive_(false) {}
|
||||
Keyboard::Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers) :
|
||||
observed_keys_(observed_keys), essential_modifiers_(essential_modifiers), is_exclusive_(false) {}
|
||||
|
||||
void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
|
||||
std::size_t key_offset = static_cast<std::size_t>(key);
|
||||
@@ -28,6 +29,10 @@ void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
|
||||
if(delegate_) delegate_->keyboard_did_change_key(this, key, is_pressed);
|
||||
}
|
||||
|
||||
const std::set<Inputs::Keyboard::Key> &Keyboard::get_essential_modifiers() {
|
||||
return essential_modifiers_;
|
||||
}
|
||||
|
||||
void Keyboard::reset_all_keys() {
|
||||
std::fill(key_states_.begin(), key_states_.end(), false);
|
||||
if(delegate_) delegate_->reset_all_keys(this);
|
||||
|
||||
@@ -23,8 +23,8 @@ class Keyboard {
|
||||
public:
|
||||
enum class Key {
|
||||
Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
|
||||
BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace,
|
||||
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, BackSlash,
|
||||
BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, Backspace,
|
||||
Tab, Q, W, E, R, T, Y, U, I, O, P, OpenSquareBracket, CloseSquareBracket, Backslash,
|
||||
CapsLock, A, S, D, F, G, H, J, K, L, Semicolon, Quote, Hash, Enter,
|
||||
LeftShift, Z, X, C, V, B, N, M, Comma, FullStop, ForwardSlash, RightShift,
|
||||
LeftControl, LeftOption, LeftMeta, Space, RightMeta, RightOption, RightControl,
|
||||
@@ -39,10 +39,10 @@ class Keyboard {
|
||||
};
|
||||
|
||||
/// Constructs a Keyboard that declares itself to observe all keys.
|
||||
Keyboard();
|
||||
Keyboard(const std::set<Key> &essential_modifiers = {});
|
||||
|
||||
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
|
||||
Keyboard(const std::set<Key> &observed_keys);
|
||||
Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers);
|
||||
|
||||
// Host interface.
|
||||
virtual void set_key_pressed(Key key, char value, bool is_pressed);
|
||||
@@ -51,10 +51,18 @@ class Keyboard {
|
||||
/// @returns a set of all Keys that this keyboard responds to.
|
||||
virtual const std::set<Key> &observed_keys();
|
||||
|
||||
/*
|
||||
/// @returns the list of modifiers that this keyboard considers 'essential' (i.e. both mapped and highly used).
|
||||
virtual const std::set<Inputs::Keyboard::Key> &get_essential_modifiers();
|
||||
|
||||
/*!
|
||||
@returns @c true if this keyboard, on its original machine, looked
|
||||
like a complete keyboard — i.e. if a user would expect this keyboard
|
||||
to be the only thing a real keyboard maps to.
|
||||
|
||||
So this would be true of something like the Amstrad CPC, which has a full
|
||||
keyboard, but it would be false of something like the Sega Master System
|
||||
which has some buttons that you'd expect an emulator to map to its host
|
||||
keyboard but which does not offer a full keyboard.
|
||||
*/
|
||||
virtual bool is_exclusive();
|
||||
|
||||
@@ -68,6 +76,7 @@ class Keyboard {
|
||||
|
||||
private:
|
||||
std::set<Key> observed_keys_;
|
||||
std::set<Key> essential_modifiers_;
|
||||
std::vector<bool> key_states_;
|
||||
Delegate *delegate_ = nullptr;
|
||||
bool is_exclusive_ = true;
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace AmstradCPC {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
return Configurable::standard_options(
|
||||
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour)
|
||||
Configurable::StandardOptions(Configurable::DisplayRGB | Configurable::DisplayCompositeColour)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ class AYDeferrer {
|
||||
*/
|
||||
class CRTCBusHandler {
|
||||
public:
|
||||
CRTCBusHandler(uint8_t *ram, InterruptTimer &interrupt_timer) :
|
||||
CRTCBusHandler(const uint8_t *ram, InterruptTimer &interrupt_timer) :
|
||||
crt_(1024, 1, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2),
|
||||
ram_(ram),
|
||||
interrupt_timer_(interrupt_timer) {
|
||||
@@ -222,9 +222,9 @@ class CRTCBusHandler {
|
||||
if(cycles_) {
|
||||
switch(previous_output_mode_) {
|
||||
default:
|
||||
case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break;
|
||||
case OutputMode::Blank: crt_.output_blank(cycles_ * 16); break;
|
||||
case OutputMode::Sync: crt_.output_sync(cycles_ * 16); break;
|
||||
case OutputMode::Border: output_border(cycles_); break;
|
||||
case OutputMode::Border: output_border(cycles_); break;
|
||||
case OutputMode::ColourBurst: crt_.output_default_colour_burst(cycles_ * 16); break;
|
||||
case OutputMode::Pixels:
|
||||
crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
|
||||
@@ -249,44 +249,46 @@ class CRTCBusHandler {
|
||||
// the CPC shuffles output lines as:
|
||||
// MA13 MA12 RA2 RA1 RA0 MA9 MA8 MA7 MA6 MA5 MA4 MA3 MA2 MA1 MA0 CCLK
|
||||
// ... so form the real access address.
|
||||
uint16_t address =
|
||||
static_cast<uint16_t>(
|
||||
const uint16_t address =
|
||||
uint16_t(
|
||||
((state.refresh_address & 0x3ff) << 1) |
|
||||
((state.row_address & 0x7) << 11) |
|
||||
((state.refresh_address & 0x3000) << 2)
|
||||
);
|
||||
|
||||
// fetch two bytes and translate into pixels
|
||||
// Fetch two bytes and translate into pixels. Guaranteed: the mode can change only at
|
||||
// hsync, so there's no risk of pixel_pointer_ overrunning 320 output pixels without
|
||||
// exactly reaching 320 output pixels.
|
||||
switch(mode_) {
|
||||
case 0:
|
||||
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode0_output_[ram_[address]];
|
||||
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode0_output_[ram_[address+1]];
|
||||
pixel_pointer_ += 4;
|
||||
pixel_pointer_ += 2 * sizeof(uint16_t);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
reinterpret_cast<uint32_t *>(pixel_pointer_)[0] = mode1_output_[ram_[address]];
|
||||
reinterpret_cast<uint32_t *>(pixel_pointer_)[1] = mode1_output_[ram_[address+1]];
|
||||
pixel_pointer_ += 8;
|
||||
pixel_pointer_ += 2 * sizeof(uint32_t);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
reinterpret_cast<uint64_t *>(pixel_pointer_)[0] = mode2_output_[ram_[address]];
|
||||
reinterpret_cast<uint64_t *>(pixel_pointer_)[1] = mode2_output_[ram_[address+1]];
|
||||
pixel_pointer_ += 16;
|
||||
pixel_pointer_ += 2 * sizeof(uint64_t);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
reinterpret_cast<uint16_t *>(pixel_pointer_)[0] = mode3_output_[ram_[address]];
|
||||
reinterpret_cast<uint16_t *>(pixel_pointer_)[1] = mode3_output_[ram_[address+1]];
|
||||
pixel_pointer_ += 4;
|
||||
pixel_pointer_ += 2 * sizeof(uint16_t);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// flush the current buffer pixel if full; the CRTC allows many different display
|
||||
// Flush the current buffer pixel if full; the CRTC allows many different display
|
||||
// widths so it's not necessarily possible to predict the correct number in advance
|
||||
// and using the upper bound could lead to inefficient behaviour
|
||||
// and using the upper bound could lead to inefficient behaviour.
|
||||
if(pixel_pointer_ == pixel_data_ + 320) {
|
||||
crt_.output_data(cycles_ * 16, size_t(cycles_ * 16 / pixel_divider_));
|
||||
pixel_pointer_ = pixel_data_ = nullptr;
|
||||
@@ -369,9 +371,17 @@ class CRTCBusHandler {
|
||||
|
||||
private:
|
||||
void output_border(int length) {
|
||||
uint8_t *colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1));
|
||||
if(colour_pointer) *colour_pointer = border_;
|
||||
crt_.output_level(length * 16);
|
||||
assert(length >= 0);
|
||||
|
||||
// A black border can be output via crt_.output_blank for a minor performance
|
||||
// win; otherwise paint whatever the border colour really is.
|
||||
if(border_) {
|
||||
uint8_t *const colour_pointer = static_cast<uint8_t *>(crt_.begin_data(1));
|
||||
if(colour_pointer) *colour_pointer = border_;
|
||||
crt_.output_level(length * 16);
|
||||
} else {
|
||||
crt_.output_blank(length * 16);
|
||||
}
|
||||
}
|
||||
|
||||
#define Mode0Colour0(c) ((c & 0x80) >> 7) | ((c & 0x20) >> 3) | ((c & 0x08) >> 2) | ((c & 0x02) << 2)
|
||||
@@ -387,16 +397,16 @@ class CRTCBusHandler {
|
||||
|
||||
void establish_palette_hits() {
|
||||
for(int c = 0; c < 256; c++) {
|
||||
mode0_palette_hits_[Mode0Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode0_palette_hits_[Mode0Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode0_palette_hits_[Mode0Colour0(c)].push_back(uint8_t(c));
|
||||
mode0_palette_hits_[Mode0Colour1(c)].push_back(uint8_t(c));
|
||||
|
||||
mode1_palette_hits_[Mode1Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode1_palette_hits_[Mode1Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode1_palette_hits_[Mode1Colour2(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode1_palette_hits_[Mode1Colour3(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode1_palette_hits_[Mode1Colour0(c)].push_back(uint8_t(c));
|
||||
mode1_palette_hits_[Mode1Colour1(c)].push_back(uint8_t(c));
|
||||
mode1_palette_hits_[Mode1Colour2(c)].push_back(uint8_t(c));
|
||||
mode1_palette_hits_[Mode1Colour3(c)].push_back(uint8_t(c));
|
||||
|
||||
mode3_palette_hits_[Mode3Colour0(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode3_palette_hits_[Mode3Colour1(c)].push_back(static_cast<uint8_t>(c));
|
||||
mode3_palette_hits_[Mode3Colour0(c)].push_back(uint8_t(c));
|
||||
mode3_palette_hits_[Mode3Colour1(c)].push_back(uint8_t(c));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +416,7 @@ class CRTCBusHandler {
|
||||
// Mode 0: abcdefgh -> [gcea] [hdfb]
|
||||
for(int c = 0; c < 256; c++) {
|
||||
// prepare mode 0
|
||||
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
|
||||
uint8_t *const mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
|
||||
mode0_pixels[0] = palette_[Mode0Colour0(c)];
|
||||
mode0_pixels[1] = palette_[Mode0Colour1(c)];
|
||||
}
|
||||
@@ -415,7 +425,7 @@ class CRTCBusHandler {
|
||||
case 1:
|
||||
for(int c = 0; c < 256; c++) {
|
||||
// prepare mode 1
|
||||
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
|
||||
uint8_t *const mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
|
||||
mode1_pixels[0] = palette_[Mode1Colour0(c)];
|
||||
mode1_pixels[1] = palette_[Mode1Colour1(c)];
|
||||
mode1_pixels[2] = palette_[Mode1Colour2(c)];
|
||||
@@ -426,7 +436,7 @@ class CRTCBusHandler {
|
||||
case 2:
|
||||
for(int c = 0; c < 256; c++) {
|
||||
// prepare mode 2
|
||||
uint8_t *mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
|
||||
uint8_t *const mode2_pixels = reinterpret_cast<uint8_t *>(&mode2_output_[c]);
|
||||
mode2_pixels[0] = palette_[((c & 0x80) >> 7)];
|
||||
mode2_pixels[1] = palette_[((c & 0x40) >> 6)];
|
||||
mode2_pixels[2] = palette_[((c & 0x20) >> 5)];
|
||||
@@ -441,7 +451,7 @@ class CRTCBusHandler {
|
||||
case 3:
|
||||
for(int c = 0; c < 256; c++) {
|
||||
// prepare mode 3
|
||||
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
|
||||
uint8_t *const mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
|
||||
mode3_pixels[0] = palette_[Mode3Colour0(c)];
|
||||
mode3_pixels[1] = palette_[Mode3Colour1(c)];
|
||||
}
|
||||
@@ -453,7 +463,7 @@ class CRTCBusHandler {
|
||||
switch(mode_) {
|
||||
case 0: {
|
||||
for(uint8_t c : mode0_palette_hits_[pen]) {
|
||||
uint8_t *mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
|
||||
uint8_t *const mode0_pixels = reinterpret_cast<uint8_t *>(&mode0_output_[c]);
|
||||
mode0_pixels[0] = palette_[Mode0Colour0(c)];
|
||||
mode0_pixels[1] = palette_[Mode0Colour1(c)];
|
||||
}
|
||||
@@ -461,7 +471,7 @@ class CRTCBusHandler {
|
||||
case 1:
|
||||
if(pen > 3) return;
|
||||
for(uint8_t c : mode1_palette_hits_[pen]) {
|
||||
uint8_t *mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
|
||||
uint8_t *const mode1_pixels = reinterpret_cast<uint8_t *>(&mode1_output_[c]);
|
||||
mode1_pixels[0] = palette_[Mode1Colour0(c)];
|
||||
mode1_pixels[1] = palette_[Mode1Colour1(c)];
|
||||
mode1_pixels[2] = palette_[Mode1Colour2(c)];
|
||||
@@ -478,7 +488,7 @@ class CRTCBusHandler {
|
||||
if(pen > 3) return;
|
||||
// Same argument applies here as to case 1, as the unused bits aren't masked out.
|
||||
for(uint8_t c : mode3_palette_hits_[pen]) {
|
||||
uint8_t *mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
|
||||
uint8_t *const mode3_pixels = reinterpret_cast<uint8_t *>(&mode3_output_[c]);
|
||||
mode3_pixels[0] = palette_[Mode3Colour0(c)];
|
||||
mode3_pixels[1] = palette_[Mode3Colour1(c)];
|
||||
}
|
||||
@@ -528,7 +538,7 @@ class CRTCBusHandler {
|
||||
Outputs::CRT::CRT crt_;
|
||||
uint8_t *pixel_data_ = nullptr, *pixel_pointer_ = nullptr;
|
||||
|
||||
uint8_t *ram_ = nullptr;
|
||||
const uint8_t *const ram_ = nullptr;
|
||||
|
||||
int next_mode_ = 2, mode_ = 2;
|
||||
|
||||
@@ -564,7 +574,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
|
||||
Sets the row currently being reported to the AY.
|
||||
*/
|
||||
void set_row(int row) {
|
||||
row_ = static_cast<size_t>(row);
|
||||
row_ = size_t(row);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -583,7 +593,7 @@ class KeyboardState: public GI::AY38910::PortHandler {
|
||||
*/
|
||||
void set_is_pressed(bool is_pressed, int line, int key) {
|
||||
int mask = 1 << key;
|
||||
assert(static_cast<size_t>(line) < sizeof(rows_));
|
||||
assert(size_t(line) < sizeof(rows_));
|
||||
if(is_pressed) rows_[line] &= ~mask; else rows_[line] |= mask;
|
||||
}
|
||||
|
||||
@@ -816,8 +826,8 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
auto &data = roms[index];
|
||||
if(!data) throw ROMMachine::Error::MissingROMs;
|
||||
roms_[static_cast<int>(index)] = std::move(*data);
|
||||
roms_[static_cast<int>(index)].resize(16384);
|
||||
roms_[int(index)] = std::move(*data);
|
||||
roms_[int(index)].resize(16384);
|
||||
}
|
||||
|
||||
// Establish default memory map
|
||||
|
||||
@@ -31,12 +31,12 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
BIND(F11, KeyRightSquareBracket);
|
||||
BIND(F12, KeyClear);
|
||||
|
||||
BIND(Hyphen, KeyMinus); BIND(Equals, KeyCaret); BIND(BackSpace, KeyDelete);
|
||||
BIND(Hyphen, KeyMinus); BIND(Equals, KeyCaret); BIND(Backspace, KeyDelete);
|
||||
BIND(Tab, KeyTab);
|
||||
|
||||
BIND(OpenSquareBracket, KeyAt);
|
||||
BIND(CloseSquareBracket, KeyLeftSquareBracket);
|
||||
BIND(BackSlash, KeyBackSlash);
|
||||
BIND(Backslash, KeyBackSlash);
|
||||
|
||||
BIND(CapsLock, KeyCapsLock);
|
||||
BIND(Semicolon, KeyColon);
|
||||
|
||||
@@ -84,8 +84,10 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
speaker_.run_for(audio_queue_, cycles_since_audio_update_.divide(Cycles(audio_divider)));
|
||||
}
|
||||
void update_just_in_time_cards() {
|
||||
for(const auto &card : just_in_time_cards_) {
|
||||
card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
|
||||
if(cycles_since_card_update_ > Cycles(0)) {
|
||||
for(const auto &card : just_in_time_cards_) {
|
||||
card->run_for(cycles_since_card_update_, stretched_cycles_since_card_update_);
|
||||
}
|
||||
}
|
||||
cycles_since_card_update_ = 0;
|
||||
stretched_cycles_since_card_update_ = 0;
|
||||
@@ -124,19 +126,25 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
pick_card_messaging_group(card);
|
||||
}
|
||||
|
||||
bool is_every_cycle_card(Apple::II::Card *card) {
|
||||
bool is_every_cycle_card(const Apple::II::Card *card) {
|
||||
return !card->get_select_constraints();
|
||||
}
|
||||
|
||||
bool card_lists_are_dirty_ = true;
|
||||
bool card_became_just_in_time_ = false;
|
||||
void pick_card_messaging_group(Apple::II::Card *card) {
|
||||
// Simplify to a card being either just-in-time or realtime.
|
||||
// Don't worry about exactly what it's watching,
|
||||
const bool is_every_cycle = is_every_cycle_card(card);
|
||||
std::vector<Apple::II::Card *> &intended = is_every_cycle ? every_cycle_cards_ : just_in_time_cards_;
|
||||
std::vector<Apple::II::Card *> &undesired = is_every_cycle ? just_in_time_cards_ : every_cycle_cards_;
|
||||
|
||||
// If the card is already in the proper group, stop.
|
||||
if(std::find(intended.begin(), intended.end(), card) != intended.end()) return;
|
||||
auto old_membership = std::find(undesired.begin(), undesired.end(), card);
|
||||
if(old_membership != undesired.end()) undesired.erase(old_membership);
|
||||
intended.push_back(card);
|
||||
|
||||
// Otherwise, mark the sets as dirty. It isn't safe to transition the card here,
|
||||
// as the main loop may be part way through iterating the two lists.
|
||||
card_lists_are_dirty_ = true;
|
||||
card_became_just_in_time_ |= !is_every_cycle;
|
||||
}
|
||||
|
||||
void card_did_change_select_constraints(Apple::II::Card *card) override {
|
||||
@@ -753,6 +761,31 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
// Update the card lists if any mutations are due.
|
||||
if(card_lists_are_dirty_) {
|
||||
card_lists_are_dirty_ = false;
|
||||
|
||||
// There's only one counter of time since update
|
||||
// for just-in-time cards. If something new is
|
||||
// transitioning, that needs to be zeroed.
|
||||
if(card_became_just_in_time_) {
|
||||
card_became_just_in_time_ = false;
|
||||
update_just_in_time_cards();
|
||||
}
|
||||
|
||||
// Clear the two lists and repopulate.
|
||||
every_cycle_cards_.clear();
|
||||
just_in_time_cards_.clear();
|
||||
for(const auto &card: cards_) {
|
||||
if(!card) continue;
|
||||
if(is_every_cycle_card(card.get())) {
|
||||
every_cycle_cards_.push_back(card.get());
|
||||
} else {
|
||||
just_in_time_cards_.push_back(card.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update analogue charge level.
|
||||
analogue_charge_ = std::min(analogue_charge_ + 1.0f / 2820.0f, 1.1f);
|
||||
|
||||
@@ -795,7 +828,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
case Key::Right: value = 0x15; break;
|
||||
case Key::Down: value = 0x0a; break;
|
||||
case Key::Up: value = 0x0b; break;
|
||||
case Key::BackSpace: value = 0x7f; break;
|
||||
case Key::Backspace: value = 0x7f; break;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +83,8 @@ class Card {
|
||||
will receive a perform_bus_operation every cycle. To reduce the number of
|
||||
virtual method calls, they **will not** receive run_for. run_for will propagate
|
||||
only to cards that register for IO and/or Device accesses only.
|
||||
|
||||
|
||||
*/
|
||||
int get_select_constraints() {
|
||||
int get_select_constraints() const {
|
||||
return select_constraints_;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ void DiskIICard::set_activity_observer(Activity::Observer *observer) {
|
||||
|
||||
void DiskIICard::set_component_prefers_clocking(ClockingHint::Source *component, ClockingHint::Preference preference) {
|
||||
diskii_clocking_preference_ = preference;
|
||||
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : 0);
|
||||
set_select_constraints((preference != ClockingHint::Preference::RealTime) ? (IO | Device) : None);
|
||||
}
|
||||
|
||||
Storage::Disk::Drive &DiskIICard::get_drive(int drive) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
void DriveSpeedAccumulator::post_sample(uint8_t sample) {
|
||||
if(!number_of_drives_) return;
|
||||
if(!delegate_) return;
|
||||
|
||||
// An Euler-esque approximation is used here: just collect all
|
||||
// the samples until there is a certain small quantity of them,
|
||||
@@ -50,14 +50,7 @@ void DriveSpeedAccumulator::post_sample(uint8_t sample) {
|
||||
const float normalised_sum = float(sum) / float(samples_.size());
|
||||
const float rotation_speed = (normalised_sum * 27.08f) - 259.0f;
|
||||
|
||||
for(int c = 0; c < number_of_drives_; ++c) {
|
||||
drives_[c]->set_rotation_speed(rotation_speed);
|
||||
}
|
||||
// printf("RPM: %0.2f (%d sum)\n", rotation_speed, sum);
|
||||
delegate_->drive_speed_accumulator_set_drive_speed(this, rotation_speed);
|
||||
}
|
||||
}
|
||||
|
||||
void DriveSpeedAccumulator::add_drive(Apple::Macintosh::DoubleDensityDrive *drive) {
|
||||
drives_[number_of_drives_] = drive;
|
||||
++number_of_drives_;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../../Components/DiskII/MacintoshDoubleDensityDrive.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
@@ -25,18 +23,20 @@ class DriveSpeedAccumulator {
|
||||
*/
|
||||
void post_sample(uint8_t sample);
|
||||
|
||||
struct Delegate {
|
||||
virtual void drive_speed_accumulator_set_drive_speed(DriveSpeedAccumulator *, float speed) = 0;
|
||||
};
|
||||
/*!
|
||||
Adds a connected drive. Up to two of these
|
||||
can be supplied. Only Macintosh DoubleDensityDrives
|
||||
are supported.
|
||||
Sets the delegate to receive drive speed changes.
|
||||
*/
|
||||
void add_drive(Apple::Macintosh::DoubleDensityDrive *drive);
|
||||
void set_delegate(Delegate *delegate) {
|
||||
delegate_ = delegate;;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 20> samples_;
|
||||
std::size_t sample_pointer_ = 0;
|
||||
Apple::Macintosh::DoubleDensityDrive *drives_[2] = {nullptr, nullptr};
|
||||
int number_of_drives_ = 0;
|
||||
Delegate *delegate_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
88
Machines/Apple/Macintosh/Keyboard.cpp
Normal file
88
Machines/Apple/Macintosh/Keyboard.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/08/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
using Key = Inputs::Keyboard::Key;
|
||||
using MacKey = Apple::Macintosh::Key;
|
||||
switch(key) {
|
||||
default: return KeyboardMachine::MappedMachine::KeyNotMapped;
|
||||
|
||||
#define Bind(x, y) case Key::x: return uint16_t(y)
|
||||
|
||||
Bind(BackTick, MacKey::BackTick);
|
||||
Bind(k1, MacKey::k1); Bind(k2, MacKey::k2); Bind(k3, MacKey::k3);
|
||||
Bind(k4, MacKey::k4); Bind(k5, MacKey::k5); Bind(k6, MacKey::k6);
|
||||
Bind(k7, MacKey::k7); Bind(k8, MacKey::k8); Bind(k9, MacKey::k9);
|
||||
Bind(k0, MacKey::k0);
|
||||
Bind(Hyphen, MacKey::Hyphen);
|
||||
Bind(Equals, MacKey::Equals);
|
||||
Bind(Backspace, MacKey::Backspace);
|
||||
|
||||
Bind(Tab, MacKey::Tab);
|
||||
Bind(Q, MacKey::Q); Bind(W, MacKey::W); Bind(E, MacKey::E); Bind(R, MacKey::R);
|
||||
Bind(T, MacKey::T); Bind(Y, MacKey::Y); Bind(U, MacKey::U); Bind(I, MacKey::I);
|
||||
Bind(O, MacKey::O); Bind(P, MacKey::P);
|
||||
Bind(OpenSquareBracket, MacKey::OpenSquareBracket);
|
||||
Bind(CloseSquareBracket, MacKey::CloseSquareBracket);
|
||||
|
||||
Bind(CapsLock, MacKey::CapsLock);
|
||||
Bind(A, MacKey::A); Bind(S, MacKey::S); Bind(D, MacKey::D); Bind(F, MacKey::F);
|
||||
Bind(G, MacKey::G); Bind(H, MacKey::H); Bind(J, MacKey::J); Bind(K, MacKey::K);
|
||||
Bind(L, MacKey::L);
|
||||
Bind(Semicolon, MacKey::Semicolon);
|
||||
Bind(Quote, MacKey::Quote);
|
||||
Bind(Enter, MacKey::Return);
|
||||
|
||||
Bind(LeftShift, MacKey::Shift);
|
||||
Bind(Z, MacKey::Z); Bind(X, MacKey::X); Bind(C, MacKey::C); Bind(V, MacKey::V);
|
||||
Bind(B, MacKey::B); Bind(N, MacKey::N); Bind(M, MacKey::M);
|
||||
Bind(Comma, MacKey::Comma);
|
||||
Bind(FullStop, MacKey::FullStop);
|
||||
Bind(ForwardSlash, MacKey::ForwardSlash);
|
||||
Bind(RightShift, MacKey::Shift);
|
||||
|
||||
Bind(Left, MacKey::Left);
|
||||
Bind(Right, MacKey::Right);
|
||||
Bind(Up, MacKey::Up);
|
||||
Bind(Down, MacKey::Down);
|
||||
|
||||
Bind(LeftOption, MacKey::Option);
|
||||
Bind(RightOption, MacKey::Option);
|
||||
Bind(LeftMeta, MacKey::Command);
|
||||
Bind(RightMeta, MacKey::Command);
|
||||
|
||||
Bind(Space, MacKey::Space);
|
||||
Bind(Backslash, MacKey::Backslash);
|
||||
|
||||
Bind(KeyPadDelete, MacKey::KeyPadDelete);
|
||||
Bind(KeyPadEquals, MacKey::KeyPadEquals);
|
||||
Bind(KeyPadSlash, MacKey::KeyPadSlash);
|
||||
Bind(KeyPadAsterisk, MacKey::KeyPadAsterisk);
|
||||
Bind(KeyPadMinus, MacKey::KeyPadMinus);
|
||||
Bind(KeyPadPlus, MacKey::KeyPadPlus);
|
||||
Bind(KeyPadEnter, MacKey::KeyPadEnter);
|
||||
Bind(KeyPadDecimalPoint, MacKey::KeyPadDecimalPoint);
|
||||
|
||||
Bind(KeyPad9, MacKey::KeyPad9);
|
||||
Bind(KeyPad8, MacKey::KeyPad8);
|
||||
Bind(KeyPad7, MacKey::KeyPad7);
|
||||
Bind(KeyPad6, MacKey::KeyPad6);
|
||||
Bind(KeyPad5, MacKey::KeyPad5);
|
||||
Bind(KeyPad4, MacKey::KeyPad4);
|
||||
Bind(KeyPad3, MacKey::KeyPad3);
|
||||
Bind(KeyPad2, MacKey::KeyPad2);
|
||||
Bind(KeyPad1, MacKey::KeyPad1);
|
||||
Bind(KeyPad0, MacKey::KeyPad0);
|
||||
|
||||
#undef Bind
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
#define Apple_Macintosh_Keyboard_hpp
|
||||
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
@@ -17,6 +18,72 @@
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
static const uint16_t KeypadMask = 0x100;
|
||||
|
||||
/*!
|
||||
Defines the keycodes that could be passed directly to a Macintosh via set_key_pressed.
|
||||
*/
|
||||
enum class Key: uint16_t {
|
||||
/*
|
||||
See p284 of the Apple Guide to the Macintosh Family Hardware
|
||||
for documentation of the mapping below.
|
||||
*/
|
||||
BackTick = 0x65,
|
||||
k1 = 0x25, k2 = 0x27, k3 = 0x29, k4 = 0x2b, k5 = 0x2f,
|
||||
k6 = 0x2d, k7 = 0x35, k8 = 0x39, k9 = 0x33, k0 = 0x3b,
|
||||
|
||||
Hyphen = 0x37,
|
||||
Equals = 0x31,
|
||||
Backspace = 0x67,
|
||||
Tab = 0x61,
|
||||
|
||||
Q = 0x19, W = 0x1b, E = 0x1d, R = 0x1f, T = 0x23, Y = 0x21, U = 0x41, I = 0x45, O = 0x3f, P = 0x47,
|
||||
A = 0x01, S = 0x03, D = 0x05, F = 0x07, G = 0x0b, H = 0x09, J = 0x4d, K = 0x51, L = 0x4b,
|
||||
Z = 0x0d, X = 0x0f, C = 0x11, V = 0x13, B = 0x17, N = 0x5b, M = 0x5d,
|
||||
|
||||
OpenSquareBracket = 0x43,
|
||||
CloseSquareBracket = 0x3d,
|
||||
Semicolon = 0x53,
|
||||
Quote = 0x4f,
|
||||
Comma = 0x57,
|
||||
FullStop = 0x5f,
|
||||
ForwardSlash = 0x59,
|
||||
|
||||
CapsLock = 0x73,
|
||||
Shift = 0x71,
|
||||
Option = 0x75,
|
||||
Command = 0x6f,
|
||||
|
||||
Space = 0x63,
|
||||
Backslash = 0x55,
|
||||
Return = 0x49,
|
||||
|
||||
Left = KeypadMask | 0x0d,
|
||||
Right = KeypadMask | 0x05,
|
||||
Up = KeypadMask | 0x1b,
|
||||
Down = KeypadMask | 0x11,
|
||||
|
||||
KeyPadDelete = KeypadMask | 0x0f,
|
||||
KeyPadEquals = KeypadMask | 0x11,
|
||||
KeyPadSlash = KeypadMask | 0x1b,
|
||||
KeyPadAsterisk = KeypadMask | 0x05,
|
||||
KeyPadMinus = KeypadMask | 0x1d,
|
||||
KeyPadPlus = KeypadMask | 0x0d,
|
||||
KeyPadEnter = KeypadMask | 0x19,
|
||||
KeyPadDecimalPoint = KeypadMask | 0x03,
|
||||
|
||||
KeyPad9 = KeypadMask | 0x39,
|
||||
KeyPad8 = KeypadMask | 0x37,
|
||||
KeyPad7 = KeypadMask | 0x33,
|
||||
KeyPad6 = KeypadMask | 0x31,
|
||||
KeyPad5 = KeypadMask | 0x2f,
|
||||
KeyPad4 = KeypadMask | 0x2d,
|
||||
KeyPad3 = KeypadMask | 0x2b,
|
||||
KeyPad2 = KeypadMask | 0x29,
|
||||
KeyPad1 = KeypadMask | 0x27,
|
||||
KeyPad0 = KeypadMask | 0x25
|
||||
};
|
||||
|
||||
class Keyboard {
|
||||
public:
|
||||
void set_input(bool data) {
|
||||
@@ -147,14 +214,16 @@ class Keyboard {
|
||||
|
||||
// Keys on the keypad are preceded by a $79 keycode; in the internal naming scheme
|
||||
// they are indicated by having bit 8 set. So add the $79 prefix if required.
|
||||
if(key & 0x100) {
|
||||
if(key & KeypadMask) {
|
||||
key_queue_.insert(key_queue_.begin(), 0x79);
|
||||
}
|
||||
key_queue_.insert(key_queue_.begin(), (is_pressed ? 0x00 : 0x80) | uint8_t(key));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/// Performs the pre-ADB Apple keyboard protocol command @c command, returning
|
||||
/// the proper result if the command were to terminate now. So, it treats inquiry
|
||||
/// and instant as the same command.
|
||||
int perform_command(int command) {
|
||||
switch(command) {
|
||||
case 0x10: // Inquiry.
|
||||
@@ -180,22 +249,41 @@ class Keyboard {
|
||||
return 0x7b; // No key transition.
|
||||
}
|
||||
|
||||
/// Maintains the current operating mode — a record of what the
|
||||
/// keyboard is doing now.
|
||||
enum class Mode {
|
||||
/// The keyboard is waiting to begin a transaction.
|
||||
Waiting,
|
||||
/// The keyboard is currently clocking in a new command.
|
||||
AcceptingCommand,
|
||||
/// The keyboard is waiting for the computer to indicate that it is ready for a response.
|
||||
AwaitingEndOfCommand,
|
||||
/// The keyboard is in the process of performing the command it most-recently received.
|
||||
/// If the command was an 'inquiry', this state may persist for a non-neglibible period of time.
|
||||
PerformingCommand,
|
||||
/// The keyboard is currently shifting a response back to the computer.
|
||||
SendingResponse,
|
||||
PerformingCommand
|
||||
} mode_ = Mode::Waiting;
|
||||
|
||||
/// Holds a count of progress through the current @c Mode. Exact meaning depends on mode.
|
||||
int phase_ = 0;
|
||||
/// Holds the most-recently-received command; the command is shifted into here as it is received
|
||||
/// so this may not be valid prior to Mode::PerformingCommand.
|
||||
int command_ = 0;
|
||||
/// Populated during PerformingCommand as the response to the most-recently-received command, this
|
||||
/// is then shifted out to teh host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
|
||||
/// but not afterwards.
|
||||
int response_ = 0;
|
||||
|
||||
/// The current state of the serial connection's data input.
|
||||
bool data_input_ = false;
|
||||
/// The current clock output from this keyboard.
|
||||
bool clock_output_ = false;
|
||||
|
||||
// TODO: improve this very, very simple implementation.
|
||||
/// Guards multithread access to key_queue_.
|
||||
std::mutex key_queue_mutex_;
|
||||
/// A FIFO queue for key events, in the form they'd be communicated to the Macintosh,
|
||||
/// with the newest events towards the front.
|
||||
std::vector<uint8_t> key_queue_;
|
||||
};
|
||||
|
||||
@@ -203,89 +291,7 @@ class Keyboard {
|
||||
Provides a mapping from idiomatic PC keys to Macintosh keys.
|
||||
*/
|
||||
class KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) override {
|
||||
using Key = Inputs::Keyboard::Key;
|
||||
switch(key) {
|
||||
default: return KeyboardMachine::MappedMachine::KeyNotMapped;
|
||||
|
||||
/*
|
||||
See p284 of the Apple Guide to the Macintosh Family Hardware
|
||||
for documentation of the mapping below.
|
||||
*/
|
||||
|
||||
case Key::BackTick: return 0x65;
|
||||
case Key::k1: return 0x25;
|
||||
case Key::k2: return 0x27;
|
||||
case Key::k3: return 0x29;
|
||||
case Key::k4: return 0x2b;
|
||||
case Key::k5: return 0x2f;
|
||||
case Key::k6: return 0x2d;
|
||||
case Key::k7: return 0x35;
|
||||
case Key::k8: return 0x39;
|
||||
case Key::k9: return 0x33;
|
||||
case Key::k0: return 0x3b;
|
||||
case Key::Hyphen: return 0x37;
|
||||
case Key::Equals: return 0x31;
|
||||
case Key::BackSpace: return 0x67;
|
||||
|
||||
case Key::Tab: return 0x61;
|
||||
case Key::Q: return 0x19;
|
||||
case Key::W: return 0x1b;
|
||||
case Key::E: return 0x1d;
|
||||
case Key::R: return 0x1f;
|
||||
case Key::T: return 0x23;
|
||||
case Key::Y: return 0x21;
|
||||
case Key::U: return 0x41;
|
||||
case Key::I: return 0x45;
|
||||
case Key::O: return 0x3f;
|
||||
case Key::P: return 0x47;
|
||||
case Key::OpenSquareBracket: return 0x43;
|
||||
case Key::CloseSquareBracket: return 0x3d;
|
||||
|
||||
case Key::CapsLock: return 0x73;
|
||||
case Key::A: return 0x01;
|
||||
case Key::S: return 0x03;
|
||||
case Key::D: return 0x05;
|
||||
case Key::F: return 0x07;
|
||||
case Key::G: return 0x0b;
|
||||
case Key::H: return 0x09;
|
||||
case Key::J: return 0x4d;
|
||||
case Key::K: return 0x51;
|
||||
case Key::L: return 0x4b;
|
||||
case Key::Semicolon: return 0x53;
|
||||
case Key::Quote: return 0x4f;
|
||||
case Key::Enter: return 0x49;
|
||||
|
||||
case Key::LeftShift: return 0x71;
|
||||
case Key::Z: return 0x0d;
|
||||
case Key::X: return 0x0f;
|
||||
case Key::C: return 0x11;
|
||||
case Key::V: return 0x13;
|
||||
case Key::B: return 0x17;
|
||||
case Key::N: return 0x5b;
|
||||
case Key::M: return 0x5d;
|
||||
case Key::Comma: return 0x57;
|
||||
case Key::FullStop: return 0x5f;
|
||||
case Key::ForwardSlash: return 0x59;
|
||||
case Key::RightShift: return 0x71;
|
||||
|
||||
case Key::Left: return 0x100 | 0x0d;
|
||||
case Key::Right: return 0x100 | 0x05;
|
||||
case Key::Up: return 0x100 | 0x1b;
|
||||
case Key::Down: return 0x100 | 0x11;
|
||||
|
||||
case Key::LeftOption:
|
||||
case Key::RightOption: return 0x75;
|
||||
case Key::LeftMeta:
|
||||
case Key::RightMeta: return 0x6f;
|
||||
|
||||
case Key::Space: return 0x63;
|
||||
case Key::BackSlash: return 0x55;
|
||||
|
||||
/* TODO: the numeric keypad. */
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) final;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "RealTimeClock.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "../../../Activity/Source.hpp"
|
||||
#include "../../CRTMachine.hpp"
|
||||
#include "../../KeyboardMachine.hpp"
|
||||
#include "../../MediaTarget.hpp"
|
||||
@@ -25,15 +26,22 @@
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
#include "../../../ClockReceiver/JustInTime.hpp"
|
||||
#include "../../../ClockReceiver/ClockingHintSource.hpp"
|
||||
#include "../../../Configurable/StandardOptions.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"
|
||||
@@ -48,6 +56,12 @@ const int CLOCK_RATE = 7833600;
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
|
||||
return Configurable::standard_options(
|
||||
static_cast<Configurable::StandardOptions>(Configurable::QuickBoot)
|
||||
);
|
||||
}
|
||||
|
||||
template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachine:
|
||||
public Machine,
|
||||
public CRTMachine::Machine,
|
||||
@@ -55,16 +69,28 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
public MouseMachine::Machine,
|
||||
public CPU::MC68000::BusHandler,
|
||||
public KeyboardMachine::MappedMachine,
|
||||
public Zilog::SCC::z8530::Delegate {
|
||||
public Zilog::SCC::z8530::Delegate,
|
||||
public Activity::Source,
|
||||
public Configurable::Device,
|
||||
public DriveSpeedAccumulator::Delegate,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
using Target = Analyser::Static::Macintosh::Target;
|
||||
|
||||
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
KeyboardMachine::MappedMachine({
|
||||
Inputs::Keyboard::Key::LeftShift, Inputs::Keyboard::Key::RightShift,
|
||||
Inputs::Keyboard::Key::LeftOption, Inputs::Keyboard::Key::RightOption,
|
||||
Inputs::Keyboard::Key::LeftMeta, Inputs::Keyboard::Key::RightMeta,
|
||||
}),
|
||||
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_),
|
||||
via_port_handler_(*this, clock_, keyboard_, 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}
|
||||
@@ -91,7 +117,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);
|
||||
@@ -99,7 +125,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);
|
||||
@@ -110,25 +137,34 @@ 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_.iwm.set_drive(0, &drives_[0]);
|
||||
iwm_.iwm.set_drive(1, &drives_[1]);
|
||||
iwm_->set_drive(0, &drives_[0]);
|
||||
iwm_->set_drive(1, &drives_[1]);
|
||||
|
||||
// If they are 400kb drives, also attach them to the drive-speed accumulator.
|
||||
if(!drives_[0].is_800k()) drive_speed_accumulator_.add_drive(&drives_[0]);
|
||||
if(!drives_[1].is_800k()) drive_speed_accumulator_.add_drive(&drives_[1]);
|
||||
if(!drives_[0].is_800k() || !drives_[1].is_800k()) {
|
||||
drive_speed_accumulator_.set_delegate(this);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// Insert any supplied media.
|
||||
insert_media(target.media);
|
||||
|
||||
// Set the immutables of the memory map.
|
||||
setup_memory_map();
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -149,96 +185,45 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
using Microcycle = CPU::MC68000::Microcycle;
|
||||
|
||||
HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
||||
// TODO: pick a delay if this is a video-clashing memory fetch.
|
||||
HalfCycles delay(0);
|
||||
|
||||
time_since_video_update_ += cycle.length;
|
||||
iwm_.time_since_update += cycle.length;
|
||||
|
||||
// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
|
||||
// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
|
||||
// may occur here in order to provide VSYNC at a proper moment.
|
||||
// Possibly route vsync.
|
||||
if(time_since_video_update_ < time_until_video_event_) {
|
||||
via_clock_ += cycle.length;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
} else {
|
||||
auto via_time_base = time_since_video_update_ - cycle.length;
|
||||
auto via_cycles_outstanding = cycle.length;
|
||||
while(time_until_video_event_ < time_since_video_update_) {
|
||||
const auto via_cycles = time_until_video_event_ - via_time_base;
|
||||
via_time_base = HalfCycles(0);
|
||||
via_cycles_outstanding -= via_cycles;
|
||||
|
||||
via_clock_ += via_cycles;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
|
||||
video_.run_for(time_until_video_event_);
|
||||
time_since_video_update_ -= time_until_video_event_;
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
||||
}
|
||||
|
||||
via_clock_ += via_cycles_outstanding;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
}
|
||||
|
||||
// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
|
||||
// Its clock and data lines are connected to the VIA.
|
||||
keyboard_clock_ += cycle.length;
|
||||
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
|
||||
if(keyboard_ticks > HalfCycles(0)) {
|
||||
keyboard_.run_for(keyboard_ticks);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
|
||||
}
|
||||
|
||||
// Feed mouse inputs within at most 1250 cycles of each other.
|
||||
if(mouse_.has_steps()) {
|
||||
time_since_mouse_update_ += cycle.length;
|
||||
const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
|
||||
if(mouse_ticks > HalfCycles(0)) {
|
||||
mouse_.prepare_step();
|
||||
scc_.set_dcd(0, mouse_.get_channel(1) & 1);
|
||||
scc_.set_dcd(1, mouse_.get_channel(0) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
|
||||
// anything connected.
|
||||
|
||||
// Consider updating the real-time clock.
|
||||
real_time_clock_ += cycle.length;
|
||||
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int();
|
||||
while(ticks--) {
|
||||
clock_.update();
|
||||
// TODO: leave a delay between toggling the input rather than using this coupled hack.
|
||||
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);
|
||||
}
|
||||
forceinline HalfCycles perform_bus_operation(const Microcycle &cycle, int is_supervisor) {
|
||||
// Advance time.
|
||||
advance_time(cycle.length);
|
||||
|
||||
// A null cycle leaves nothing else to do.
|
||||
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return delay;
|
||||
if(!(cycle.operation & (Microcycle::NewAddress | Microcycle::SameAddress))) return HalfCycles(0);
|
||||
|
||||
auto word_address = cycle.active_operation_word_address();
|
||||
// Grab the value on the address bus, at word precision.
|
||||
uint32_t word_address = cycle.active_operation_word_address();
|
||||
|
||||
// Everything above E0 0000 is signalled as being on the peripheral bus.
|
||||
mc68000_.set_is_peripheral_address(word_address >= 0x700000);
|
||||
|
||||
// All code below deals only with reads and writes — cycles in which a
|
||||
// data select is active. So quit now if this is not the active part of
|
||||
// a read or write.
|
||||
if(!cycle.data_select_active()) return delay;
|
||||
// a read or write.
|
||||
//
|
||||
// The 68000 uses 6800-style autovectored interrupts, so the mere act of
|
||||
// having set VPA above deals with those given that the generated address
|
||||
// for interrupt acknowledge cycles always has all bits set except the
|
||||
// lowest explicit address lines.
|
||||
if(!cycle.data_select_active() || (cycle.operation & Microcycle::InterruptAcknowledge)) return HalfCycles(0);
|
||||
|
||||
// Check whether this access maps into the IO area; if so then
|
||||
// apply more complicated decoding logic.
|
||||
if(word_address >= 0x400000) {
|
||||
const int register_address = word_address >> 8;
|
||||
// Grab the word-precision address being accessed.
|
||||
uint16_t *memory_base = nullptr;
|
||||
HalfCycles delay;
|
||||
switch(memory_map_[word_address >> 16]) {
|
||||
default: assert(false);
|
||||
|
||||
case BusDevice::Unassigned:
|
||||
fill_unmapped(cycle);
|
||||
return delay;
|
||||
|
||||
case BusDevice::VIA: {
|
||||
if(*cycle.address & 1) {
|
||||
fill_unmapped(cycle);
|
||||
} else {
|
||||
const int register_address = word_address >> 8;
|
||||
|
||||
switch(word_address & 0x78f000) {
|
||||
case 0x70f000:
|
||||
// VIA accesses are via address 0xefe1fe + register*512,
|
||||
// which at word precision is 0x77f0ff + register*256.
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
@@ -246,116 +231,139 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
} else {
|
||||
via_.set_register(register_address, cycle.value->halves.low);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x68f000:
|
||||
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
||||
}
|
||||
} return delay;
|
||||
|
||||
case BusDevice::PhaseRead: {
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = phase_ & 7;
|
||||
}
|
||||
|
||||
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
||||
} return delay;
|
||||
|
||||
case BusDevice::IWM: {
|
||||
if(*cycle.address & 1) {
|
||||
const int register_address = word_address >> 8;
|
||||
|
||||
// The IWM; this is a purely polled device, so can be run on demand.
|
||||
iwm_.flush();
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = iwm_.iwm.read(register_address);
|
||||
cycle.value->halves.low = iwm_->read(register_address);
|
||||
} else {
|
||||
iwm_.iwm.write(register_address, cycle.value->halves.low);
|
||||
iwm_->write(register_address, cycle.value->halves.low);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x780000:
|
||||
// Phase read.
|
||||
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
||||
} else {
|
||||
fill_unmapped(cycle);
|
||||
}
|
||||
} 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) {
|
||||
cycle.value->halves.low = phase_ & 7;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x480000: case 0x48f000:
|
||||
case 0x580000: case 0x58f000:
|
||||
// Any word access here adjusts phase.
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
++phase_;
|
||||
scsi_.write(register_address, 0xff, dma_acknowledge);
|
||||
} else {
|
||||
if(word_address < 0x500000) {
|
||||
// A0 = 1 => reset; A0 = 0 => read.
|
||||
if(*cycle.address & 1) {
|
||||
scc_.reset();
|
||||
} else {
|
||||
const auto read = scc_.read(int(word_address));
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = read;
|
||||
}
|
||||
}
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
scsi_.write(register_address, cycle.value->halves.high, dma_acknowledge);
|
||||
} else {
|
||||
if(*cycle.address & 1) {
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
scc_.write(int(word_address), 0xff);
|
||||
} else {
|
||||
scc_.write(int(word_address), cycle.value->halves.low);
|
||||
}
|
||||
}
|
||||
scsi_.write(register_address, cycle.value->halves.low, dma_acknowledge);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
LOG("Unrecognised read " << PADHEX(6) << (*cycle.address & 0xffffff));
|
||||
cycle.value->halves.low = 0x00;
|
||||
} else {
|
||||
LOG("Unrecognised write %06x" << PADHEX(6) << (*cycle.address & 0xffffff));
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(cycle.operation & Microcycle::SelectWord) cycle.value->halves.high = 0xff;
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
// Having reached here, this is a RAM or ROM access.
|
||||
|
||||
// When ROM overlay is enabled, the ROM begins at both $000000 and $400000,
|
||||
// and RAM is available at $600000.
|
||||
//
|
||||
// Otherwise RAM is mapped at $000000 and ROM from $400000.
|
||||
uint16_t *memory_base;
|
||||
if(
|
||||
(!ROM_is_overlay_ && word_address < 0x200000) ||
|
||||
(ROM_is_overlay_ && word_address >= 0x300000)
|
||||
) {
|
||||
memory_base = ram_;
|
||||
word_address &= ram_mask_;
|
||||
|
||||
// This is coupled with the Macintosh implementation of video; the magic
|
||||
// constant should probably be factored into the Video class.
|
||||
// It embodies knowledge of the fact that video (and audio) will always
|
||||
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
|
||||
// (And that ram_mask_ = ram size - 1).
|
||||
// if(word_address > ram_mask_ - 0x6c80)
|
||||
update_video();
|
||||
} else {
|
||||
memory_base = rom_;
|
||||
word_address &= rom_mask_;
|
||||
|
||||
// Writes to ROM have no effect, and it doesn't mirror above 0x60000.
|
||||
if(!(cycle.operation & Microcycle::Read)) return delay;
|
||||
if(word_address >= 0x300000) {
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
cycle.value->full = 0xffff;
|
||||
} else {
|
||||
cycle.value->halves.low = 0xff;
|
||||
// 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;
|
||||
}
|
||||
} return delay;
|
||||
|
||||
case BusDevice::SCCReadResetPhase: {
|
||||
// Any word access here adjusts phase.
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
adjust_phase();
|
||||
} else {
|
||||
// A0 = 1 => reset; A0 = 0 => read.
|
||||
if(*cycle.address & 1) {
|
||||
scc_.reset();
|
||||
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = 0xff;
|
||||
}
|
||||
} else {
|
||||
const auto read = scc_.read(int(word_address));
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
cycle.value->halves.low = read;
|
||||
}
|
||||
}
|
||||
}
|
||||
} return delay;
|
||||
|
||||
case BusDevice::SCCWrite: {
|
||||
// Any word access here adjusts phase.
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
adjust_phase();
|
||||
} else {
|
||||
if(*cycle.address & 1) {
|
||||
if(cycle.operation & Microcycle::Read) {
|
||||
scc_.write(int(word_address), 0xff);
|
||||
cycle.value->halves.low = 0xff;
|
||||
} else {
|
||||
scc_.write(int(word_address), cycle.value->halves.low);
|
||||
}
|
||||
} else {
|
||||
fill_unmapped(cycle);
|
||||
}
|
||||
}
|
||||
} return delay;
|
||||
|
||||
case BusDevice::RAM: {
|
||||
// This is coupled with the Macintosh implementation of video; the magic
|
||||
// constant should probably be factored into the Video class.
|
||||
// It embodies knowledge of the fact that video (and audio) will always
|
||||
// be fetched from the final $d900 bytes (i.e. $6c80 words) of memory.
|
||||
// (And that ram_mask_ = ram size - 1).
|
||||
if(word_address > ram_mask_ - 0x6c80)
|
||||
update_video();
|
||||
|
||||
memory_base = ram_.data();
|
||||
word_address &= ram_mask_;
|
||||
|
||||
// Apply a delay due to video contention if applicable; scheme applied:
|
||||
// only every other access slot is available during the period of video
|
||||
// output. I believe this to be correct for the 128k, 512k and Plus.
|
||||
// More research to do on other models.
|
||||
if(video_is_outputting() && ram_subcycle_ < 8) {
|
||||
delay = HalfCycles(8 - ram_subcycle_);
|
||||
advance_time(delay);
|
||||
}
|
||||
} break;
|
||||
|
||||
case BusDevice::ROM: {
|
||||
if(!(cycle.operation & Microcycle::Read)) return delay;
|
||||
memory_base = rom_;
|
||||
word_address &= rom_mask_;
|
||||
} break;
|
||||
}
|
||||
|
||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read | Microcycle::InterruptAcknowledge)) {
|
||||
// If control has fallen through to here, the access is either a read from ROM, or a read or write to RAM.
|
||||
switch(cycle.operation & (Microcycle::SelectWord | Microcycle::SelectByte | Microcycle::Read)) {
|
||||
default:
|
||||
break;
|
||||
|
||||
// Catches the deliberation set of operation to 0 above.
|
||||
case 0: break;
|
||||
|
||||
case Microcycle::InterruptAcknowledge | Microcycle::SelectByte:
|
||||
// The Macintosh uses autovectored interrupts.
|
||||
mc68000_.set_is_peripheral_address(true);
|
||||
break;
|
||||
|
||||
case Microcycle::SelectWord | Microcycle::Read:
|
||||
cycle.value->full = memory_base[word_address];
|
||||
break;
|
||||
@@ -373,17 +381,6 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
Normal memory map:
|
||||
|
||||
000000: RAM
|
||||
400000: ROM
|
||||
9FFFF8+: SCC read operations
|
||||
BFFFF8+: SCC write operations
|
||||
DFE1FF+: IWM
|
||||
EFE1FE+: VIA
|
||||
*/
|
||||
|
||||
return delay;
|
||||
}
|
||||
|
||||
@@ -404,6 +401,48 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
void set_rom_is_overlay(bool rom_is_overlay) {
|
||||
ROM_is_overlay_ = rom_is_overlay;
|
||||
|
||||
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||
switch(model) {
|
||||
case Model::Mac128k:
|
||||
case Model::Mac512k:
|
||||
case Model::Mac512ke:
|
||||
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 + 0x100000, (c & 0x100000) ? BusDevice::Unassigned : BusDevice::ROM);
|
||||
}
|
||||
map_to(0x800000, BusDevice::RAM);
|
||||
} else {
|
||||
map_to(0x400000, BusDevice::RAM);
|
||||
map_to(0x500000, BusDevice::ROM);
|
||||
map_to(0x800000, BusDevice::Unassigned);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case Model::MacPlus:
|
||||
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) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool video_is_outputting() {
|
||||
@@ -416,16 +455,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(drives_[0].has_disk())
|
||||
drives_[1].set_disk(media.disks[0]);
|
||||
else
|
||||
drives_[0].set_disk(media.disks[0]);
|
||||
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;
|
||||
}
|
||||
@@ -460,8 +510,144 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) override {
|
||||
iwm_->set_activity_observer(observer);
|
||||
|
||||
if(model == Analyser::Static::Macintosh::Target::Model::MacPlus) {
|
||||
scsi_bus_.set_activity_observer(observer);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
|
||||
return Apple::Macintosh::get_options();
|
||||
}
|
||||
|
||||
void set_selections(const Configurable::SelectionSet &selections_by_option) override {
|
||||
bool quick_boot;
|
||||
if(Configurable::get_quick_boot(selections_by_option, quick_boot)) {
|
||||
if(quick_boot) {
|
||||
// Cf. Big Mess o' Wires' disassembly of the Mac Plus ROM, and the
|
||||
// test at $E00. TODO: adapt as(/if?) necessary for other Macs.
|
||||
ram_[0x02ae >> 1] = 0x40;
|
||||
ram_[0x02b0 >> 1] = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_accurate_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_boot_selection(selection_set, false);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
Configurable::SelectionSet get_user_friendly_selections() override {
|
||||
Configurable::SelectionSet selection_set;
|
||||
Configurable::append_quick_boot_selection(selection_set, true);
|
||||
return selection_set;
|
||||
}
|
||||
|
||||
private:
|
||||
void update_video() {
|
||||
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);
|
||||
drives_[1].set_rotation_speed(speed);
|
||||
}
|
||||
|
||||
forceinline void adjust_phase() {
|
||||
++phase_;
|
||||
}
|
||||
|
||||
forceinline void fill_unmapped(const Microcycle &cycle) {
|
||||
if(!(cycle.operation & Microcycle::Read)) return;
|
||||
if(cycle.operation & Microcycle::SelectWord) {
|
||||
cycle.value->full = 0xffff;
|
||||
} else {
|
||||
cycle.value->halves.low = 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
/// Advances all non-CPU components by @c duration half cycles.
|
||||
forceinline void advance_time(HalfCycles duration) {
|
||||
time_since_video_update_ += duration;
|
||||
iwm_ += duration;
|
||||
ram_subcycle_ = (ram_subcycle_ + duration.as_int()) & 15;
|
||||
|
||||
// The VIA runs at one-tenth of the 68000's clock speed, in sync with the E clock.
|
||||
// See: Guide to the Macintosh Hardware Family p149 (PDF p188). Some extra division
|
||||
// may occur here in order to provide VSYNC at a proper moment.
|
||||
// Possibly route vsync.
|
||||
if(time_since_video_update_ < time_until_video_event_) {
|
||||
via_clock_ += duration;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
} else {
|
||||
auto via_time_base = time_since_video_update_ - duration;
|
||||
auto via_cycles_outstanding = duration;
|
||||
while(time_until_video_event_ < time_since_video_update_) {
|
||||
const auto via_cycles = time_until_video_event_ - via_time_base;
|
||||
via_time_base = HalfCycles(0);
|
||||
via_cycles_outstanding -= via_cycles;
|
||||
|
||||
via_clock_ += via_cycles;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
|
||||
video_.run_for(time_until_video_event_);
|
||||
time_since_video_update_ -= time_until_video_event_;
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !video_.vsync());
|
||||
}
|
||||
|
||||
via_clock_ += via_cycles_outstanding;
|
||||
via_.run_for(via_clock_.divide(HalfCycles(10)));
|
||||
}
|
||||
|
||||
// The keyboard also has a clock, albeit a very slow one — 100,000 cycles/second.
|
||||
// Its clock and data lines are connected to the VIA.
|
||||
keyboard_clock_ += duration;
|
||||
const auto keyboard_ticks = keyboard_clock_.divide(HalfCycles(CLOCK_RATE / 100000));
|
||||
if(keyboard_ticks > HalfCycles(0)) {
|
||||
keyboard_.run_for(keyboard_ticks);
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::Two, keyboard_.get_data());
|
||||
via_.set_control_line_input(MOS::MOS6522::Port::B, MOS::MOS6522::Line::One, keyboard_.get_clock());
|
||||
}
|
||||
|
||||
// Feed mouse inputs within at most 1250 cycles of each other.
|
||||
if(mouse_.has_steps()) {
|
||||
time_since_mouse_update_ += duration;
|
||||
const auto mouse_ticks = time_since_mouse_update_.divide(HalfCycles(2500));
|
||||
if(mouse_ticks > HalfCycles(0)) {
|
||||
mouse_.prepare_step();
|
||||
scc_.set_dcd(0, mouse_.get_channel(1) & 1);
|
||||
scc_.set_dcd(1, mouse_.get_channel(0) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: SCC should be clocked at a divide-by-two, if and when it actually has
|
||||
// anything connected.
|
||||
|
||||
// Consider updating the real-time clock.
|
||||
real_time_clock_ += duration;
|
||||
auto ticks = real_time_clock_.divide_cycles(Cycles(CLOCK_RATE)).as_int();
|
||||
while(ticks--) {
|
||||
clock_.update();
|
||||
// TODO: leave a delay between toggling the input rather than using this coupled hack.
|
||||
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() {
|
||||
video_.run_for(time_since_video_update_.flush<HalfCycles>());
|
||||
time_until_video_event_ = video_.get_next_sequence_point();
|
||||
}
|
||||
@@ -470,21 +656,10 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
return mouse_;
|
||||
}
|
||||
|
||||
struct IWM {
|
||||
IWM(int clock_rate) : iwm(clock_rate) {}
|
||||
|
||||
HalfCycles time_since_update;
|
||||
Apple::IWM iwm;
|
||||
|
||||
void flush() {
|
||||
iwm.run_for(time_since_update.flush<Cycles>());
|
||||
}
|
||||
};
|
||||
|
||||
class VIAPortHandler: public MOS::MOS6522::PortHandler {
|
||||
public:
|
||||
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, Video &video, DeferredAudio &audio, IWM &iwm, Inputs::QuadratureMouse &mouse) :
|
||||
machine_(machine), clock_(clock), keyboard_(keyboard), video_(video), audio_(audio), iwm_(iwm), mouse_(mouse) {}
|
||||
VIAPortHandler(ConcreteMachine &machine, RealTimeClock &clock, Keyboard &keyboard, DeferredAudio &audio, JustInTimeActor<IWM, HalfCycles, Cycles> &iwm, Inputs::QuadratureMouse &mouse) :
|
||||
machine_(machine), clock_(clock), keyboard_(keyboard), audio_(audio), iwm_(iwm), mouse_(mouse) {}
|
||||
|
||||
using Port = MOS::MOS6522::Port;
|
||||
using Line = MOS::MOS6522::Line;
|
||||
@@ -505,8 +680,7 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
b3: 0 = use alternate sound buffer, 1 = use ordinary sound buffer
|
||||
b2–b0: audio output volume
|
||||
*/
|
||||
iwm_.flush();
|
||||
iwm_.iwm.set_select(!!(value & 0x20));
|
||||
iwm_->set_select(!!(value & 0x20));
|
||||
|
||||
machine_.set_use_alternate_buffers(!(value & 0x40), !(value&0x08));
|
||||
machine_.set_rom_is_overlay(!!(value & 0x10));
|
||||
@@ -589,16 +763,15 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
ConcreteMachine &machine_;
|
||||
RealTimeClock &clock_;
|
||||
Keyboard &keyboard_;
|
||||
Video &video_;
|
||||
DeferredAudio &audio_;
|
||||
IWM &iwm_;
|
||||
JustInTimeActor<IWM, HalfCycles, Cycles> &iwm_;
|
||||
Inputs::QuadratureMouse &mouse_;
|
||||
};
|
||||
|
||||
CPU::MC68000::Processor<ConcreteMachine, true> mc68000_;
|
||||
|
||||
DriveSpeedAccumulator drive_speed_accumulator_;
|
||||
IWM iwm_;
|
||||
JustInTimeActor<IWM, HalfCycles, Cycles> iwm_;
|
||||
|
||||
DeferredAudio audio_;
|
||||
Video video_;
|
||||
@@ -610,6 +783,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_;
|
||||
@@ -620,16 +797,61 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
|
||||
bool ROM_is_overlay_ = true;
|
||||
int phase_ = 1;
|
||||
int ram_subcycle_ = 0;
|
||||
|
||||
DoubleDensityDrive drives_[2];
|
||||
Inputs::QuadratureMouse mouse_;
|
||||
|
||||
Apple::Macintosh::KeyboardMapper keyboard_mapper_;
|
||||
|
||||
enum class BusDevice {
|
||||
RAM, ROM, VIA, IWM, SCCWrite, SCCReadResetPhase, SCSI, PhaseRead, Unassigned
|
||||
};
|
||||
|
||||
/// 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 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;
|
||||
// start by calling into set_rom_is_overlay to seed everything up to $800000.
|
||||
set_rom_is_overlay(true);
|
||||
|
||||
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);
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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 = start_address >> 17;
|
||||
auto map_to = [&segment, this](int address, BusDevice device) {
|
||||
for(; segment < address >> 17; ++segment) {
|
||||
this->memory_map_[segment] = device;
|
||||
}
|
||||
};
|
||||
|
||||
populator(map_to);
|
||||
}
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
#ifndef Macintosh_hpp
|
||||
#define Macintosh_hpp
|
||||
|
||||
#include "../../../Configurable/Configurable.hpp"
|
||||
#include "../../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../ROMMachine.hpp"
|
||||
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
std::vector<std::unique_ptr<Configurable::Option>> get_options();
|
||||
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
@@ -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_));
|
||||
}
|
||||
|
||||
/*!
|
||||
|
||||
@@ -12,16 +12,6 @@
|
||||
|
||||
using namespace Apple::Macintosh;
|
||||
|
||||
namespace {
|
||||
|
||||
const HalfCycles line_length(704);
|
||||
const int number_of_lines = 370;
|
||||
const HalfCycles frame_length(line_length * HalfCycles(number_of_lines));
|
||||
const int sync_start = 36;
|
||||
const int sync_end = 38;
|
||||
|
||||
}
|
||||
|
||||
// Re: CRT timings, see the Apple Guide to the Macintosh Hardware Family,
|
||||
// bottom of page 400:
|
||||
//
|
||||
@@ -33,11 +23,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));
|
||||
@@ -184,18 +173,12 @@ HalfCycles Video::get_next_sequence_point() {
|
||||
}
|
||||
}
|
||||
|
||||
bool Video::is_outputting(HalfCycles offset) {
|
||||
const auto offset_position = frame_position_ + offset % frame_length;
|
||||
const int column = (offset_position % line_length).as_int() >> 4;
|
||||
const int line = (offset_position / line_length).as_int();
|
||||
return line < 342 && column < 32;
|
||||
}
|
||||
|
||||
void Video::set_use_alternate_buffers(bool use_alternate_screen_buffer, bool use_alternate_audio_buffer) {
|
||||
use_alternate_screen_buffer_ = use_alternate_screen_buffer;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@
|
||||
namespace Apple {
|
||||
namespace Macintosh {
|
||||
|
||||
static const HalfCycles line_length(704);
|
||||
static const int number_of_lines = 370;
|
||||
static const HalfCycles frame_length(line_length * HalfCycles(number_of_lines));
|
||||
static const int sync_start = 36;
|
||||
static const int sync_end = 38;
|
||||
|
||||
/*!
|
||||
Models the 68000-era Macintosh video hardware, producing a 512x348 pixel image,
|
||||
within a total scanning area of 370 lines, at 352 cycles per line.
|
||||
@@ -29,7 +35,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 +53,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.
|
||||
@@ -61,7 +67,12 @@ class Video {
|
||||
@returns @c true if in @c offset half cycles from now, the video will be outputting pixels;
|
||||
@c false otherwise.
|
||||
*/
|
||||
bool is_outputting(HalfCycles offset = HalfCycles(0));
|
||||
bool is_outputting(HalfCycles offset = HalfCycles(0)) {
|
||||
const auto offset_position = frame_position_ + offset % frame_length;
|
||||
const int column = (offset_position % line_length).as_int() >> 4;
|
||||
const int line = (offset_position / line_length).as_int();
|
||||
return line < 342 && column < 32;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the amount of time until there is next a transition on the
|
||||
|
||||
@@ -34,7 +34,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
BIND(OpenSquareBracket, KeyAt);
|
||||
BIND(CloseSquareBracket, KeyAsterisk);
|
||||
|
||||
BIND(BackSlash, KeyRestore);
|
||||
BIND(Backslash, KeyRestore);
|
||||
BIND(Hash, KeyUp);
|
||||
BIND(F10, KeyUp);
|
||||
|
||||
@@ -59,7 +59,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
|
||||
BIND(Enter, KeyReturn);
|
||||
BIND(Space, KeySpace);
|
||||
BIND(BackSpace, KeyDelete);
|
||||
BIND(Backspace, KeyDelete);
|
||||
|
||||
BIND(Escape, KeyRunStop);
|
||||
BIND(F1, KeyF1);
|
||||
|
||||
@@ -41,7 +41,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
BIND(LeftShift, KeyShift); BIND(RightShift, KeyShift);
|
||||
|
||||
BIND(Hyphen, KeyMinus);
|
||||
BIND(Delete, KeyDelete); BIND(BackSpace, KeyDelete);
|
||||
BIND(Delete, KeyDelete); BIND(Backspace, KeyDelete);
|
||||
BIND(Enter, KeyReturn); BIND(KeyPadEnter, KeyReturn);
|
||||
|
||||
BIND(KeyPad0, Key0); BIND(KeyPad1, Key1); BIND(KeyPad2, Key2); BIND(KeyPad3, Key3); BIND(KeyPad4, Key4);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
using namespace KeyboardMachine;
|
||||
|
||||
MappedMachine::MappedMachine() {
|
||||
MappedMachine::MappedMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers) : keyboard_(essential_modifiers) {
|
||||
keyboard_.set_delegate(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <set>
|
||||
|
||||
#include "../Inputs/Keyboard.hpp"
|
||||
|
||||
@@ -56,7 +57,7 @@ class Machine: public KeyActions {
|
||||
*/
|
||||
class MappedMachine: public Inputs::Keyboard::Delegate, public Machine {
|
||||
public:
|
||||
MappedMachine();
|
||||
MappedMachine(const std::set<Inputs::Keyboard::Key> &essential_modifiers = {});
|
||||
|
||||
/*!
|
||||
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
|
||||
|
||||
@@ -47,12 +47,12 @@ uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
BIND(FullStop, KeyFullStop);
|
||||
BIND(Comma, KeyComma);
|
||||
BIND(ForwardSlash, KeyForwardSlash);
|
||||
BIND(BackSlash, KeyBackSlash);
|
||||
BIND(Backslash, KeyBackSlash);
|
||||
BIND(BackTick, KeyGrave);
|
||||
|
||||
BIND(Enter, KeyEnter);
|
||||
BIND(Space, KeySpace);
|
||||
BIND(BackSpace, KeyBackspace);
|
||||
BIND(Backspace, KeyBackspace);
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ class ConcreteMachine:
|
||||
audio_queue_,
|
||||
sn76489_divider),
|
||||
speaker_(sn76489_),
|
||||
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}) {
|
||||
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}, {}) {
|
||||
// Pick the clock rate based on the region.
|
||||
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
|
||||
speaker_.set_input_rate(static_cast<float>(clock_rate / sn76489_divider));
|
||||
|
||||
@@ -26,10 +26,10 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
|
||||
|
||||
BIND(Left, KeyLeft); BIND(Right, KeyRight); BIND(Up, KeyUp); BIND(Down, KeyDown);
|
||||
|
||||
BIND(Hyphen, KeyMinus); BIND(Equals, KeyEquals); BIND(BackSlash, KeyBackSlash);
|
||||
BIND(Hyphen, KeyMinus); BIND(Equals, KeyEquals); BIND(Backslash, KeyBackSlash);
|
||||
BIND(OpenSquareBracket, KeyOpenSquare); BIND(CloseSquareBracket, KeyCloseSquare);
|
||||
|
||||
BIND(BackSpace, KeyDelete); BIND(Delete, KeyDelete);
|
||||
BIND(Backspace, KeyDelete); BIND(Delete, KeyDelete);
|
||||
|
||||
BIND(Semicolon, KeySemiColon); BIND(Quote, KeyQuote);
|
||||
BIND(Comma, KeyComma); BIND(FullStop, KeyFullStop); BIND(ForwardSlash, KeyForwardSlash);
|
||||
|
||||
@@ -106,6 +106,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Electron";
|
||||
case Analyser::Machine::Macintosh: return "Macintosh";
|
||||
case Analyser::Machine::MasterSystem: return "MasterSystem";
|
||||
case Analyser::Machine::MSX: return "MSX";
|
||||
case Analyser::Machine::Oric: return "Oric";
|
||||
case Analyser::Machine::Vic20: return "Vic20";
|
||||
@@ -123,6 +124,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Acorn Electron";
|
||||
case Analyser::Machine::Macintosh: return "Apple Macintosh";
|
||||
case Analyser::Machine::MasterSystem: return "Sega Master System";
|
||||
case Analyser::Machine::MSX: return "MSX";
|
||||
case Analyser::Machine::Oric: return "Oric";
|
||||
case Analyser::Machine::Vic20: return "Vic 20";
|
||||
@@ -139,6 +141,8 @@ std::map<std::string, std::vector<std::unique_ptr<Configurable::Option>>> Machin
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::AppleII), Apple::II::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::ColecoVision), Coleco::Vision::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Electron), Electron::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Macintosh), Apple::Macintosh::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MasterSystem), Sega::MasterSystem::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::MSX), MSX::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Oric), Oric::get_options()));
|
||||
options.emplace(std::make_pair(LongNameForTargetMachine(Analyser::Machine::Vic20), Commodore::Vic20::get_options()));
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
4B4518A31F75FD1C00926311 /* HFE.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518951F75FD1B00926311 /* HFE.cpp */; };
|
||||
4B4518A41F75FD1C00926311 /* OricMFMDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518971F75FD1B00926311 /* OricMFMDSK.cpp */; };
|
||||
4B4518A51F75FD1C00926311 /* SSD.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4518991F75FD1B00926311 /* SSD.cpp */; };
|
||||
4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */; };
|
||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */; };
|
||||
4B4B1A3C200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; };
|
||||
4B4B1A3D200198CA00A0F866 /* KonamiSCC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */; };
|
||||
@@ -210,16 +211,27 @@
|
||||
4B622AE5222E0AD5008B59F2 /* DisplayMetrics.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B622AE3222E0AD5008B59F2 /* DisplayMetrics.cpp */; };
|
||||
4B643F3A1D77AD1900D431D6 /* CSStaticAnalyser.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */; };
|
||||
4B643F3F1D77B88000D431D6 /* DocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B643F3E1D77B88000D431D6 /* DocumentController.swift */; };
|
||||
4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B65085F22F4CF8D009C1100 /* Keyboard.cpp */; };
|
||||
4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B65085F22F4CF8D009C1100 /* Keyboard.cpp */; };
|
||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */; };
|
||||
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 */; };
|
||||
@@ -629,6 +641,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 */; };
|
||||
@@ -642,6 +656,7 @@
|
||||
4BCE005D227D30CC000CA200 /* MemoryPacker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005B227D30CC000CA200 /* MemoryPacker.cpp */; };
|
||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
||||
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; };
|
||||
4BD191F42191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||
4BD388882239E198002D14B5 /* 68000Tests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BD388872239E198002D14B5 /* 68000Tests.mm */; };
|
||||
@@ -667,6 +682,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 */; };
|
||||
@@ -897,6 +914,7 @@
|
||||
4B45189A1F75FD1B00926311 /* SSD.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SSD.hpp; sourceTree = "<group>"; };
|
||||
4B4518A71F76004200926311 /* TapeParser.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = TapeParser.hpp; path = Parsers/TapeParser.hpp; sourceTree = "<group>"; };
|
||||
4B4518A81F76022000926311 /* DiskImageImplementation.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DiskImageImplementation.hpp; sourceTree = "<group>"; };
|
||||
4B49F0A823346F7A0045E6A6 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/MacintoshOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4B4A762E1DB1A3FA007AAE2E /* AY38910.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AY38910.cpp; path = AY38910/AY38910.cpp; sourceTree = "<group>"; };
|
||||
4B4A762F1DB1A3FA007AAE2E /* AY38910.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = AY38910.hpp; path = AY38910/AY38910.hpp; sourceTree = "<group>"; };
|
||||
4B4B1A3A200198C900A0F866 /* KonamiSCC.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = KonamiSCC.cpp; sourceTree = "<group>"; };
|
||||
@@ -940,6 +958,7 @@
|
||||
4B643F391D77AD1900D431D6 /* CSStaticAnalyser.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = CSStaticAnalyser.mm; path = StaticAnalyser/CSStaticAnalyser.mm; sourceTree = "<group>"; };
|
||||
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CSMachine+Target.h"; sourceTree = "<group>"; };
|
||||
4B643F3E1D77B88000D431D6 /* DocumentController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DocumentController.swift; sourceTree = "<group>"; };
|
||||
4B65085F22F4CF8D009C1100 /* Keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
|
||||
4B698D1A1FE768A100696C91 /* SampleSource.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = SampleSource.hpp; sourceTree = "<group>"; };
|
||||
4B69FB3B1C4D908A00B5F0AA /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Tape.cpp; sourceTree = "<group>"; };
|
||||
4B69FB3C1C4D908A00B5F0AA /* Tape.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Tape.hpp; sourceTree = "<group>"; };
|
||||
@@ -950,6 +969,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>"; };
|
||||
@@ -965,6 +991,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>"; };
|
||||
@@ -1066,6 +1096,7 @@
|
||||
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TestRunner68000.hpp; sourceTree = "<group>"; };
|
||||
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000BitwiseTests.mm; sourceTree = "<group>"; };
|
||||
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000ArithmeticTests.mm; sourceTree = "<group>"; };
|
||||
4B911A9B2337D8AB00A2BB1D /* CSApplication.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CSApplication.h; sourceTree = "<group>"; };
|
||||
4B92294222B04A3D00A1458F /* MouseMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MouseMachine.hpp; sourceTree = "<group>"; };
|
||||
4B92294422B04ACB00A1458F /* Mouse.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Mouse.hpp; sourceTree = "<group>"; };
|
||||
4B92294A22B064FD00A1458F /* QuadratureMouse.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = QuadratureMouse.hpp; sourceTree = "<group>"; };
|
||||
@@ -1403,7 +1434,6 @@
|
||||
4BBB70A3202011C2002FE009 /* MultiMediaTarget.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMediaTarget.cpp; sourceTree = "<group>"; };
|
||||
4BBB70A6202014E2002FE009 /* MultiCRTMachine.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MultiCRTMachine.cpp; sourceTree = "<group>"; };
|
||||
4BBB70A7202014E2002FE009 /* MultiCRTMachine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MultiCRTMachine.hpp; sourceTree = "<group>"; };
|
||||
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSFastLoading.h; sourceTree = "<group>"; };
|
||||
4BBC951C1F368D83008F4C34 /* i8272.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = i8272.cpp; path = 8272/i8272.cpp; sourceTree = "<group>"; };
|
||||
4BBC951D1F368D83008F4C34 /* i8272.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = i8272.hpp; path = 8272/i8272.hpp; sourceTree = "<group>"; };
|
||||
4BBF49AE1ED2880200AB3669 /* FUSETests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FUSETests.swift; sourceTree = "<group>"; };
|
||||
@@ -1419,6 +1449,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>"; };
|
||||
@@ -1447,6 +1479,7 @@
|
||||
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Oric.hpp; path = Oric/Oric.hpp; sourceTree = "<group>"; };
|
||||
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
4BD0692B22828A2D00D2A54F /* RealTimeClock.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RealTimeClock.hpp; sourceTree = "<group>"; };
|
||||
4BD0FBC2233706A200148981 /* CSApplication.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CSApplication.m; sourceTree = "<group>"; };
|
||||
4BD191D9219113B80042E144 /* OpenGL.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = OpenGL.hpp; sourceTree = "<group>"; };
|
||||
4BD191F22191180E0042E144 /* ScanTarget.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ScanTarget.cpp; sourceTree = "<group>"; };
|
||||
4BD191F32191180E0042E144 /* ScanTarget.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ScanTarget.hpp; sourceTree = "<group>"; };
|
||||
@@ -1478,6 +1511,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>"; };
|
||||
@@ -1738,7 +1773,6 @@
|
||||
4B2A53921D117D36003C6002 /* Machine */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBC34241D2208B100FFC9DF /* CSFastLoading.h */,
|
||||
4B2A53951D117D36003C6002 /* CSMachine.h */,
|
||||
4B643F3C1D77AE5C00D431D6 /* CSMachine+Target.h */,
|
||||
4B2A53971D117D36003C6002 /* KeyCodes.h */,
|
||||
@@ -2103,6 +2137,7 @@
|
||||
4B8FE2131DA19D5F0090D3CE /* Atari2600Options.xib */,
|
||||
4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */,
|
||||
4B8FE2151DA19D5F0090D3CE /* MachineDocument.xib */,
|
||||
4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */,
|
||||
4B2A332B1DB86821002876E3 /* OricOptions.xib */,
|
||||
4B8FE2171DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib */,
|
||||
4BD61662206B2AC700236112 /* QuickLoadOptions.xib */,
|
||||
@@ -2168,6 +2203,7 @@
|
||||
4BEE0A691D72496600532C7B /* Cartridge */,
|
||||
4B8805F81DCFF6CD003085B1 /* Data */,
|
||||
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
||||
4B6AAEA1230E3E1D0078E864 /* MassStorage */,
|
||||
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
||||
);
|
||||
name = Storage;
|
||||
@@ -2231,6 +2267,33 @@
|
||||
path = Implementation;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B6AAEA1230E3E1D0078E864 /* MassStorage */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4B74CF83231370BC00500CE8 /* MacintoshVolume.cpp */,
|
||||
4B6AAEA2230E3E1D0078E864 /* MassStorageDevice.cpp */,
|
||||
4B74CF84231370BC00500CE8 /* MacintoshVolume.hpp */,
|
||||
4B6AAEA3230E3E1D0078E864 /* MassStorageDevice.hpp */,
|
||||
4B74CF7E2312FA9C00500CE8 /* Formats */,
|
||||
4B6AAEA5230E40250078E864 /* SCSI */,
|
||||
);
|
||||
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 = (
|
||||
@@ -2249,6 +2312,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 = (
|
||||
@@ -2934,22 +3006,24 @@
|
||||
4BB73EA01B587A5100552FC2 /* Clock Signal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
||||
4BB73ECF1B587A6700552FC2 /* Clock Signal.entitlements */,
|
||||
4B1414501B58848C00E04248 /* ClockSignal-Bridging-Header.h */,
|
||||
4B911A9B2337D8AB00A2BB1D /* CSApplication.h */,
|
||||
4BD0FBC2233706A200148981 /* CSApplication.m */,
|
||||
4BB73EAD1B587A5100552FC2 /* Info.plist */,
|
||||
4BB73EA11B587A5100552FC2 /* AppDelegate.swift */,
|
||||
4BB73EA81B587A5100552FC2 /* Assets.xcassets */,
|
||||
4B2A538F1D117D36003C6002 /* Audio */,
|
||||
4B643F3D1D77B88000D431D6 /* Document Controller */,
|
||||
4B55CE551C3B7D360093A61B /* Documents */,
|
||||
4BBFE83B21015D9C00BF1C40 /* Joystick Manager */,
|
||||
4B2A53921D117D36003C6002 /* Machine */,
|
||||
4B55DD7F20DF06680043F2E5 /* MachinePicker */,
|
||||
4BB73EAA1B587A5100552FC2 /* MainMenu.xib */,
|
||||
4BE5F85A1C3E1C2500C43F01 /* Resources */,
|
||||
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
||||
4BD5F1961D1352A000631CD1 /* Updater */,
|
||||
4B55CE5A1C3B7D6F0093A61B /* Views */,
|
||||
4BDA00DB22E60EE900AC3CD0 /* ROMRequester */,
|
||||
);
|
||||
path = "Clock Signal";
|
||||
sourceTree = "<group>";
|
||||
@@ -3108,6 +3182,7 @@
|
||||
4BC9DF4A1D04691600F44158 /* Components */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BDACBE922FFA5B50045EF7E /* 5380 */,
|
||||
4BD468F81D8DF4290084958B /* 1770 */,
|
||||
4BC9DF4B1D04691600F44158 /* 6522 */,
|
||||
4B1E85791D174DEC001EF87D /* 6532 */,
|
||||
@@ -3182,6 +3257,7 @@
|
||||
children = (
|
||||
4B9378E222A199C600973513 /* Audio.cpp */,
|
||||
4BB4BFAC22A33DE50069048D /* DriveSpeedAccumulator.cpp */,
|
||||
4B65085F22F4CF8D009C1100 /* Keyboard.cpp */,
|
||||
4BCE0058227CFFCA000CA200 /* Macintosh.cpp */,
|
||||
4BCE005E227D39AB000CA200 /* Video.cpp */,
|
||||
4B9378E322A199C600973513 /* Audio.hpp */,
|
||||
@@ -3313,6 +3389,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 = (
|
||||
@@ -3503,7 +3588,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 1030;
|
||||
LastUpgradeCheck = 1100;
|
||||
ORGANIZATIONNAME = "Thomas Harte";
|
||||
TargetAttributes = {
|
||||
4B055A691FAE763F0060FFFF = {
|
||||
@@ -3572,6 +3657,7 @@
|
||||
4B79E4441E3AF38600141F11 /* cassette.png in Resources */,
|
||||
4BB73EAC1B587A5100552FC2 /* MainMenu.xib in Resources */,
|
||||
4B8FE21D1DA19D5F0090D3CE /* QuickLoadCompositeOptions.xib in Resources */,
|
||||
4B49F0A923346F7A0045E6A6 /* MacintoshOptions.xib in Resources */,
|
||||
4BA3189422E7A4CA00D18CFA /* ROMImages in Resources */,
|
||||
4B79E4461E3AF38600141F11 /* floppy525.png in Resources */,
|
||||
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */,
|
||||
@@ -3900,6 +3986,7 @@
|
||||
4B055AA71FAE85EF0060FFFF /* SegmentParser.cpp in Sources */,
|
||||
4BB0A65E204500A900FB3688 /* StaticAnalyser.cpp in Sources */,
|
||||
4B055AC11FAE98DC0060FFFF /* MachineForTarget.cpp in Sources */,
|
||||
4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */,
|
||||
4BBB70A9202014E2002FE009 /* MultiCRTMachine.cpp in Sources */,
|
||||
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */,
|
||||
4B055AD81FAE9B180060FFFF /* Video.cpp in Sources */,
|
||||
@@ -3933,6 +4020,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 */,
|
||||
@@ -3944,11 +4032,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 */,
|
||||
@@ -3971,6 +4062,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 */,
|
||||
@@ -4004,6 +4096,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 */,
|
||||
@@ -4051,6 +4144,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 */,
|
||||
@@ -4060,6 +4154,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 */,
|
||||
@@ -4099,8 +4194,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 */,
|
||||
@@ -4114,6 +4211,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 */,
|
||||
@@ -4160,6 +4258,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 */,
|
||||
@@ -4170,11 +4269,14 @@
|
||||
4B69FB3D1C4D908A00B5F0AA /* Tape.cpp in Sources */,
|
||||
4B4518841F75E91A00926311 /* UnformattedTrack.cpp in Sources */,
|
||||
4B55CE5D1C3B7D6F0093A61B /* CSOpenGLView.m in Sources */,
|
||||
4B65086022F4CF8D009C1100 /* Keyboard.cpp in Sources */,
|
||||
4B894528201967B4007DE474 /* Disk.cpp in Sources */,
|
||||
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 */,
|
||||
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */,
|
||||
4BBC951E1F368D83008F4C34 /* i8272.cpp in Sources */,
|
||||
4B89449520194CB3007DE474 /* MachineForTarget.cpp in Sources */,
|
||||
4B4A76301DB1A3FA007AAE2E /* AY38910.cpp in Sources */,
|
||||
@@ -4332,6 +4434,14 @@
|
||||
name = OricOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B49F0A723346F7A0045E6A6 /* MacintoshOptions.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
4B49F0A823346F7A0045E6A6 /* Base */,
|
||||
);
|
||||
name = MacintoshOptions.xib;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4B55DD8120DF06680043F2E5 /* MachinePicker.xib */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
@@ -4407,6 +4517,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -4426,6 +4537,7 @@
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -4555,6 +4667,7 @@
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -4596,6 +4709,7 @@
|
||||
CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES;
|
||||
CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "-";
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "self:Clock Signal.xcodeproj">
|
||||
</FileRef>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1030"
|
||||
LastUpgradeVersion = "1100"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,9 +26,18 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
disableMainThreadChecker = "YES"
|
||||
codeCoverageEnabled = "YES"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
codeCoverageEnabled = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
|
||||
BuildableName = "Clock Signal.app"
|
||||
BlueprintName = "Clock Signal"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -56,17 +65,6 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "4BB73E9D1B587A5100552FC2"
|
||||
BuildableName = "Clock Signal.app"
|
||||
BlueprintName = "Clock Signal"
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -78,8 +76,7 @@
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
stopOnEveryThreadSanitizerIssue = "YES"
|
||||
stopOnEveryUBSanitizerIssue = "YES"
|
||||
migratedStopOnEveryIssue = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "NO">
|
||||
<BuildableProductRunnable
|
||||
@@ -92,8 +89,6 @@
|
||||
ReferencedContainer = "container:Clock Signal.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -12,15 +12,26 @@ import Cocoa
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
// Insert code here to initialize your application
|
||||
// Insert code here to initialize your application.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
// Insert code here to tear down your application
|
||||
// Insert code here to tear down your application.
|
||||
}
|
||||
|
||||
// decline to open a new file unless the user explicitly requests it
|
||||
private var hasShownOpenDocument = false
|
||||
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
|
||||
// Decline to show the 'New...' selector by default; the 'Open...'
|
||||
// dialogue has already been shown if this application was started
|
||||
// without a file.
|
||||
//
|
||||
// Obiter: I dislike it when other applications do this for me, but it
|
||||
// seems to be the new norm, and I've had user feedback that showing
|
||||
// nothing is confusing. So here it is.
|
||||
if !hasShownOpenDocument {
|
||||
NSDocumentController.shared.openDocument(self)
|
||||
hasShownOpenDocument = true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
49
OSBindings/Mac/Clock Signal/Base.lproj/MacintoshOptions.xib
Normal file
49
OSBindings/Mac/Clock Signal/Base.lproj/MacintoshOptions.xib
Normal file
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="MachineDocument" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
<outlet property="optionsPanel" destination="ZW7-Bw-4RP" id="JpE-wG-zRR"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="54"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="54"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="zPG-yW-4Gy">
|
||||
<rect key="frame" x="18" y="18" width="164" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Start Quickly" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="alI-Mw-35c">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="setFastBooting:" target="ZW7-Bw-4RP" id="AgA-2q-qUU"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="zPG-yW-4Gy" secondAttribute="bottom" constant="20" id="7u0-BP-FXG"/>
|
||||
<constraint firstAttribute="trailing" secondItem="zPG-yW-4Gy" secondAttribute="trailing" constant="20" id="Mtb-hf-4ap"/>
|
||||
<constraint firstItem="zPG-yW-4Gy" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="imk-5k-8nm"/>
|
||||
<constraint firstItem="zPG-yW-4Gy" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="jAt-iF-uaT"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="fastBootingButton" destination="zPG-yW-4Gy" id="3Mq-l2-NEp"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-50" y="2"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
33
OSBindings/Mac/Clock Signal/CSApplication.h
Normal file
33
OSBindings/Mac/Clock Signal/CSApplication.h
Normal file
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// CSApplication.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/09/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef CSApplication_h
|
||||
#define CSApplication_h
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
@class CSApplication;
|
||||
|
||||
@protocol CSApplicationEventDelegate
|
||||
- (BOOL)application:(nonnull CSApplication *)application shouldSendEvent:(nonnull NSEvent *)event;
|
||||
@end
|
||||
|
||||
/*!
|
||||
CSApplication differs from NSApplication in only one regard: it supports an eventDelegate.
|
||||
|
||||
If conected, an eventDelegate will be offered all application events prior to their propagation
|
||||
into the application proper. It may opt to remove those events from the queue. This primarily
|
||||
provides a way to divert things like the command key that will otherwise trigger menu
|
||||
shortcuts, for periods when it is appropriate to do so.
|
||||
*/
|
||||
@interface CSApplication: NSApplication
|
||||
@property(nonatomic, weak, nullable) id<CSApplicationEventDelegate> eventDelegate;
|
||||
@end
|
||||
|
||||
|
||||
#endif /* CSApplication_h */
|
||||
20
OSBindings/Mac/Clock Signal/CSApplication.m
Normal file
20
OSBindings/Mac/Clock Signal/CSApplication.m
Normal file
@@ -0,0 +1,20 @@
|
||||
//
|
||||
// Application.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 21/09/2019.
|
||||
// Copyright © 2019 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSApplication.h"
|
||||
|
||||
@implementation CSApplication
|
||||
|
||||
- (void)sendEvent:(NSEvent *)event {
|
||||
// Send the event unless an event delegate says otherwise.
|
||||
if(!self.eventDelegate || [self.eventDelegate application:self shouldSendEvent:event]) {
|
||||
[super sendEvent:event];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -3,7 +3,6 @@
|
||||
//
|
||||
|
||||
#import "CSMachine.h"
|
||||
#import "CSFastLoading.h"
|
||||
|
||||
#import "CSAtari2600.h"
|
||||
#import "CSZX8081.h"
|
||||
|
||||
@@ -154,6 +154,11 @@ class MachineDocument:
|
||||
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to know that the window
|
||||
// is visible, though it's a little premature.
|
||||
func windowDidUpdate(_ notification: Notification) {
|
||||
// Grab the regular window title, if it's not already stored.
|
||||
if self.unadornedWindowTitle.count == 0 {
|
||||
self.unadornedWindowTitle = self.windowControllers[0].window!.title
|
||||
}
|
||||
|
||||
// If an interaction mode is not yet in effect, pick the proper one and display the relevant thing.
|
||||
if self.interactionMode == .notStarted {
|
||||
// If a full machine exists, just continue showing it.
|
||||
@@ -204,8 +209,10 @@ class MachineDocument:
|
||||
openGLView.delegate = self
|
||||
openGLView.responderDelegate = self
|
||||
|
||||
// If this machine has a mouse, enable mouse capture.
|
||||
// If this machine has a mouse, enable mouse capture; also indicate whether usurption
|
||||
// of the command key is desired.
|
||||
openGLView.shouldCaptureMouse = machine.hasMouse
|
||||
openGLView.shouldUsurpCommand = machine.shouldUsurpCommand
|
||||
|
||||
setupAudioQueueClockRate()
|
||||
|
||||
@@ -613,7 +620,17 @@ class MachineDocument:
|
||||
try! pngData?.write(to: url)
|
||||
}
|
||||
|
||||
// MARK: Activity display.
|
||||
// MARK: - Window Title Updates.
|
||||
private var unadornedWindowTitle = ""
|
||||
func openGLViewDidCaptureMouse(_ view: CSOpenGLView) {
|
||||
self.windowControllers[0].window?.title = self.unadornedWindowTitle + " (press ⌘+control to release mouse)"
|
||||
}
|
||||
|
||||
func openGLViewDidReleaseMouse(_ view: CSOpenGLView) {
|
||||
self.windowControllers[0].window?.title = self.unadornedWindowTitle
|
||||
}
|
||||
|
||||
// MARK: - Activity Display.
|
||||
|
||||
private class LED {
|
||||
let levelIndicator: NSLevelIndicator
|
||||
|
||||
@@ -16,18 +16,29 @@ class MachinePanel: NSPanel {
|
||||
return "\(self.machine.userDefaultsPrefix).\(key)"
|
||||
}
|
||||
|
||||
// MARK: Fast Loading
|
||||
var fastLoadingUserDefaultsKey: String {
|
||||
return prefixedUserDefaultsKey("fastLoading")
|
||||
}
|
||||
@IBOutlet var fastLoadingButton: NSButton?
|
||||
@IBAction func setFastLoading(_ sender: NSButton!) {
|
||||
if let fastLoadingMachine = machine as? CSFastLoading {
|
||||
let useFastLoadingHack = sender.state == .on
|
||||
fastLoadingMachine.useFastLoadingHack = useFastLoadingHack
|
||||
UserDefaults.standard.set(useFastLoadingHack, forKey: fastLoadingUserDefaultsKey)
|
||||
}
|
||||
let useFastLoadingHack = sender.state == .on
|
||||
machine.useFastLoadingHack = useFastLoadingHack
|
||||
UserDefaults.standard.set(useFastLoadingHack, forKey: fastLoadingUserDefaultsKey)
|
||||
}
|
||||
|
||||
// MARK: Quick Boot
|
||||
var bootQuicklyUserDefaultsKey: String {
|
||||
return prefixedUserDefaultsKey("bootQuickly")
|
||||
}
|
||||
@IBOutlet var fastBootingButton: NSButton?
|
||||
@IBAction func setFastBooting(_ sender: NSButton!) {
|
||||
let useQuickBootingHack = sender.state == .on
|
||||
machine.useQuickBootingHack = useQuickBootingHack
|
||||
UserDefaults.standard.set(useQuickBootingHack, forKey: bootQuicklyUserDefaultsKey)
|
||||
}
|
||||
|
||||
// MARK: Display-Type Selection
|
||||
fileprivate func signalForTag(tag: Int) -> CSMachineVideoSignal {
|
||||
switch tag {
|
||||
case 1: return .composite
|
||||
@@ -49,17 +60,25 @@ class MachinePanel: NSPanel {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Restoring user defaults
|
||||
func establishStoredOptions() {
|
||||
let standardUserDefaults = UserDefaults.standard
|
||||
standardUserDefaults.register(defaults: [
|
||||
fastLoadingUserDefaultsKey: true,
|
||||
bootQuicklyUserDefaultsKey: true,
|
||||
displayTypeUserDefaultsKey: 0
|
||||
])
|
||||
|
||||
if let fastLoadingMachine = machine as? CSFastLoading {
|
||||
if let fastLoadingButton = self.fastLoadingButton {
|
||||
let useFastLoadingHack = standardUserDefaults.bool(forKey: self.fastLoadingUserDefaultsKey)
|
||||
fastLoadingMachine.useFastLoadingHack = useFastLoadingHack
|
||||
self.fastLoadingButton?.state = useFastLoadingHack ? .on : .off
|
||||
machine.useFastLoadingHack = useFastLoadingHack
|
||||
fastLoadingButton.state = useFastLoadingHack ? .on : .off
|
||||
}
|
||||
|
||||
if let fastBootingButton = self.fastBootingButton {
|
||||
let bootQuickly = standardUserDefaults.bool(forKey: self.bootQuicklyUserDefaultsKey)
|
||||
machine.useQuickBootingHack = bootQuickly
|
||||
fastBootingButton.state = bootQuickly ? .on : .off
|
||||
}
|
||||
|
||||
if let displayTypeButton = self.displayTypeButton {
|
||||
|
||||
@@ -554,6 +554,6 @@
|
||||
<key>NSMainNibFile</key>
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<string>CSApplication</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
//
|
||||
// CSFastLoading.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 05/06/2016.
|
||||
// Copyright 2016 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
@protocol CSFastLoading <NSObject>
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@end
|
||||
@@ -9,7 +9,6 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "CSAudioQueue.h"
|
||||
#import "CSFastLoading.h"
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSStaticAnalyser.h"
|
||||
#import "CSJoystickManager.h"
|
||||
@@ -86,6 +85,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
@property (nonatomic, assign) BOOL useFastLoadingHack;
|
||||
@property (nonatomic, assign) CSMachineVideoSignal videoSignal;
|
||||
@property (nonatomic, assign) BOOL useAutomaticTapeMotorControl;
|
||||
@property (nonatomic, assign) BOOL useQuickBootingHack;
|
||||
|
||||
@property (nonatomic, readonly) BOOL canInsertMedia;
|
||||
|
||||
@@ -93,6 +93,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
|
||||
// Input control.
|
||||
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
|
||||
@property (nonatomic, readonly) BOOL shouldUsurpCommand;
|
||||
@property (nonatomic, readonly) BOOL hasJoystick;
|
||||
@property (nonatomic, readonly) BOOL hasMouse;
|
||||
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
#include "../../../../Outputs/OpenGL/ScanTarget.hpp"
|
||||
#include "../../../../Outputs/OpenGL/Screenshot.hpp"
|
||||
|
||||
@interface CSMachine() <CSFastLoading>
|
||||
@interface CSMachine()
|
||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
|
||||
- (void)addLED:(NSString *)led;
|
||||
@@ -131,7 +131,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"%@/%@, %@ bytes, CRCs: %@", _fileName, _descriptiveName, @(_size), _crc32s];
|
||||
return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -415,7 +415,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
BIND(VK_ANSI_Quote, Quote); BIND(VK_ANSI_Grave, BackTick);
|
||||
|
||||
BIND(VK_ANSI_Semicolon, Semicolon);
|
||||
BIND(VK_ANSI_Backslash, BackSlash); BIND(VK_ANSI_Slash, ForwardSlash);
|
||||
BIND(VK_ANSI_Backslash, Backslash); BIND(VK_ANSI_Slash, ForwardSlash);
|
||||
BIND(VK_ANSI_Comma, Comma); BIND(VK_ANSI_Period, FullStop);
|
||||
|
||||
BIND(VK_ANSI_KeypadDecimal, KeyPadDecimalPoint); BIND(VK_ANSI_KeypadEquals, KeyPadEquals);
|
||||
@@ -424,7 +424,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
BIND(VK_ANSI_KeypadClear, KeyPadDelete); BIND(VK_ANSI_KeypadEnter, KeyPadEnter);
|
||||
|
||||
BIND(VK_Return, Enter); BIND(VK_Tab, Tab);
|
||||
BIND(VK_Space, Space); BIND(VK_Delete, BackSpace);
|
||||
BIND(VK_Space, Space); BIND(VK_Delete, Backspace);
|
||||
BIND(VK_Control, LeftControl); BIND(VK_Option, LeftOption);
|
||||
BIND(VK_Command, LeftMeta); BIND(VK_Shift, LeftShift);
|
||||
BIND(VK_RightControl, RightControl); BIND(VK_RightOption, RightOption);
|
||||
@@ -624,6 +624,19 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setUseQuickBootingHack:(BOOL)useQuickBootingHack {
|
||||
Configurable::Device *configurable_device = _machine->configurable_device();
|
||||
if(!configurable_device) return;
|
||||
|
||||
@synchronized(self) {
|
||||
_useQuickBootingHack = useQuickBootingHack;
|
||||
|
||||
Configurable::SelectionSet selection_set;
|
||||
append_quick_boot_selection(selection_set, useQuickBootingHack ? true : false);
|
||||
configurable_device->set_selections(selection_set);
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)userDefaultsPrefix {
|
||||
// Assumes that the first machine in the targets list is the source of user defaults.
|
||||
std::string name = Machine::ShortNameForTargetMachine(_analyser.targets.front()->machine);
|
||||
@@ -658,6 +671,14 @@ struct ActivityObserver: public Activity::Observer {
|
||||
return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive();
|
||||
}
|
||||
|
||||
- (BOOL)shouldUsurpCommand {
|
||||
if(!_machine->keyboard_machine()) return NO;
|
||||
|
||||
const auto essential_modifiers = _machine->keyboard_machine()->get_keyboard().get_essential_modifiers();
|
||||
return essential_modifiers.find(Inputs::Keyboard::Key::LeftMeta) != essential_modifiers.end() ||
|
||||
essential_modifiers.find(Inputs::Keyboard::Key::RightMeta) != essential_modifiers.end();
|
||||
}
|
||||
|
||||
#pragma mark - Activity observation
|
||||
|
||||
- (void)addLED:(NSString *)led {
|
||||
|
||||
@@ -219,6 +219,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
case Analyser::Machine::Atari2600: return @"Atari2600Options";
|
||||
case Analyser::Machine::ColecoVision: return @"CompositeOptions";
|
||||
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
|
||||
case Analyser::Machine::Macintosh: return @"MacintoshOptions";
|
||||
case Analyser::Machine::MasterSystem: return @"CompositeOptions";
|
||||
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
|
||||
case Analyser::Machine::Oric: return @"OricOptions";
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -50,7 +50,7 @@ Gw
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="9YM-5x-pc0">
|
||||
<rect key="frame" x="20" y="14" width="398" height="34"/>
|
||||
<rect key="frame" x="20" y="15" width="398" height="32"/>
|
||||
<textFieldCell key="cell" allowsUndo="NO" sendsActionOnEndEditing="YES" id="xTm-Oy-oz5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="title">If you use File -> Open... to select a disk, tape or cartridge directly, the emulator will select and configure a machine for you.</string>
|
||||
@@ -68,7 +68,7 @@ Gw
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
|
||||
<rect key="frame" x="15" y="72" width="46" height="17"/>
|
||||
<rect key="frame" x="15" y="73" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -76,7 +76,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
|
||||
<rect key="frame" x="15" y="41" width="96" height="17"/>
|
||||
<rect key="frame" x="15" y="42" width="96" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -148,7 +148,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
|
||||
<rect key="frame" x="15" y="72" width="46" height="17"/>
|
||||
<rect key="frame" x="15" y="73" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -202,20 +202,35 @@ Gw
|
||||
<rect key="frame" x="10" y="33" width="604" height="94"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="F6e-UC-25u">
|
||||
<rect key="frame" x="15" y="74" width="574" height="17"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="At present Clock Signal emulates only the Macintosh 512ke." id="IGV-Yp-6Af">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
|
||||
<rect key="frame" x="15" y="73" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="h9r-i6-66j">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xa6-NA-JY5">
|
||||
<rect key="frame" x="65" y="67" width="74" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Plus" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="proportionallyDown" inset="2" selectedItem="R6T-hg-rOF" id="1Kb-Q2-BGM">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="ofy-j9-YnU">
|
||||
<items>
|
||||
<menuItem title="512ke" tag="2" id="WCG-6u-ANQ"/>
|
||||
<menuItem title="Plus" state="on" tag="3" id="R6T-hg-rOF"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="F6e-UC-25u" secondAttribute="trailing" constant="17" id="42z-hS-aPq"/>
|
||||
<constraint firstItem="F6e-UC-25u" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="bIg-C3-xdz"/>
|
||||
<constraint firstItem="F6e-UC-25u" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="3" id="cxs-OP-oH5"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="F6e-UC-25u" secondAttribute="bottom" constant="17" id="vpF-ER-pmD"/>
|
||||
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="3" id="3hY-Ca-mnR"/>
|
||||
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="17" id="5s6-87-VT6"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="3" id="KYf-GJ-Y7k"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="trailing" constant="17" id="LZ5-xH-fU0"/>
|
||||
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="centerY" secondItem="xa6-NA-JY5" secondAttribute="centerY" id="Wa5-KX-3Me"/>
|
||||
<constraint firstItem="xa6-NA-JY5" firstAttribute="leading" secondItem="ZOY-4E-Cfl" secondAttribute="trailing" constant="8" id="ktS-sr-F8L"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
@@ -246,7 +261,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
|
||||
<rect key="frame" x="15" y="72" width="50" height="17"/>
|
||||
<rect key="frame" x="15" y="73" width="50" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="x4m-eh-Nif">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -273,7 +288,7 @@ Gw
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
|
||||
<rect key="frame" x="15" y="72" width="46" height="17"/>
|
||||
<rect key="frame" x="15" y="73" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Xm1-7x-YVl">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -309,7 +324,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
|
||||
<rect key="frame" x="15" y="41" width="92" height="17"/>
|
||||
<rect key="frame" x="15" y="42" width="92" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk interface:" id="SFK-hS-tFC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -368,7 +383,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
|
||||
<rect key="frame" x="15" y="72" width="50" height="17"/>
|
||||
<rect key="frame" x="15" y="73" width="50" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="F3g-Ya-ypU">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -376,7 +391,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
|
||||
<rect key="frame" x="15" y="41" width="87" height="17"/>
|
||||
<rect key="frame" x="15" y="42" width="87" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="a4I-vG-yCp">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -429,7 +444,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
|
||||
<rect key="frame" x="15" y="72" width="87" height="17"/>
|
||||
<rect key="frame" x="15" y="73" width="87" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -477,7 +492,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
|
||||
<rect key="frame" x="15" y="72" width="87" height="17"/>
|
||||
<rect key="frame" x="15" y="73" width="87" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -523,6 +538,7 @@ Gw
|
||||
<outlet property="electronADFSButton" destination="945-wU-JOH" id="Fjm-W8-kvh"/>
|
||||
<outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/>
|
||||
<outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/>
|
||||
<outlet property="macintoshModelTypeButton" destination="xa6-NA-JY5" id="2jX-PY-v2z"/>
|
||||
<outlet property="msxHasDiskDriveButton" destination="8xT-Pr-8SE" id="zGH-GA-9QF"/>
|
||||
<outlet property="msxRegionButton" destination="LG6-mP-SeG" id="3a9-VG-6Wf"/>
|
||||
<outlet property="oricDiskInterfaceButton" destination="fYL-p6-wyn" id="aAt-wM-hRZ"/>
|
||||
|
||||
@@ -22,6 +22,9 @@ class MachinePicker: NSObject {
|
||||
// MARK: - CPC properties
|
||||
@IBOutlet var cpcModelTypeButton: NSPopUpButton?
|
||||
|
||||
// MARK: - Macintosh properties
|
||||
@IBOutlet var macintoshModelTypeButton: NSPopUpButton?
|
||||
|
||||
// MARK: - MSX properties
|
||||
@IBOutlet var msxRegionButton: NSPopUpButton?
|
||||
@IBOutlet var msxHasDiskDriveButton: NSButton?
|
||||
@@ -62,6 +65,9 @@ class MachinePicker: NSObject {
|
||||
// CPC settings
|
||||
cpcModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
|
||||
|
||||
// Macintosh settings
|
||||
macintoshModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel"))
|
||||
|
||||
// MSX settings
|
||||
msxRegionButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.msxRegion"))
|
||||
msxHasDiskDriveButton?.state = standardUserDefaults.bool(forKey: "new.msxDiskDrive") ? .on : .off
|
||||
@@ -100,6 +106,9 @@ class MachinePicker: NSObject {
|
||||
// CPC settings
|
||||
standardUserDefaults.set(cpcModelTypeButton!.selectedTag(), forKey: "new.cpcModel")
|
||||
|
||||
// Macintosh settings
|
||||
standardUserDefaults.set(macintoshModelTypeButton!.selectedTag(), forKey: "new.macintoshModel")
|
||||
|
||||
// MSX settings
|
||||
standardUserDefaults.set(msxRegionButton!.selectedTag(), forKey: "new.msxRegion")
|
||||
standardUserDefaults.set(msxHasDiskDriveButton?.state == .on, forKey: "new.msxDiskDrive")
|
||||
@@ -158,7 +167,13 @@ class MachinePicker: NSObject {
|
||||
}
|
||||
|
||||
case "mac":
|
||||
return CSStaticAnalyser(macintoshModel: .model512ke)
|
||||
switch macintoshModelTypeButton!.selectedItem!.tag {
|
||||
case 0: return CSStaticAnalyser(macintoshModel: .model128k)
|
||||
case 1: return CSStaticAnalyser(macintoshModel: .model512k)
|
||||
case 2: return CSStaticAnalyser(macintoshModel: .model512ke)
|
||||
case 3: fallthrough
|
||||
default: return CSStaticAnalyser(macintoshModel: .modelPlus)
|
||||
}
|
||||
|
||||
case "msx":
|
||||
let hasDiskDrive = msxHasDiskDriveButton!.state == .on
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14868" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14868"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -25,11 +25,11 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav">
|
||||
<rect key="frame" x="18" y="148" width="444" height="102"/>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav">
|
||||
<rect key="frame" x="18" y="154" width="444" height="96"/>
|
||||
<textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop the following over this view:
|
||||
<string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop the following over this text:
|
||||
|
||||
</string>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -50,7 +50,7 @@ DQ
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Bia-0m-GxK">
|
||||
<rect key="frame" x="20" y="61" width="442" height="17"/>
|
||||
<rect key="frame" x="20" y="61" width="442" height="16"/>
|
||||
<textFieldCell key="cell" allowsUndo="NO" alignment="center" title="Multiline Label" drawsBackground="YES" id="8jl-xs-LjP">
|
||||
<font key="font" metaFont="message"/>
|
||||
<color key="textColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -70,6 +70,7 @@ DQ
|
||||
<constraint firstItem="5qG-I3-Qav" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="qeC-Rh-hmr"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="139" y="147"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
|
||||
@@ -37,6 +37,19 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
*/
|
||||
- (void)openGLView:(nonnull CSOpenGLView *)view didReceiveFileAtURL:(nonnull NSURL *)URL;
|
||||
|
||||
/*!
|
||||
Announces 'capture' of the mouse — i.e. that the view is now preventing the mouse from exiting
|
||||
the window, in order to forward continuous mouse motion.
|
||||
@param view The view making the announcement.
|
||||
*/
|
||||
- (void)openGLViewDidCaptureMouse:(nonnull CSOpenGLView *)view;
|
||||
|
||||
/*!
|
||||
Announces that the mouse is no longer captured.
|
||||
@param view The view making the announcement.
|
||||
*/
|
||||
- (void)openGLViewDidReleaseMouse:(nonnull CSOpenGLView *)view;
|
||||
|
||||
@end
|
||||
|
||||
@protocol CSOpenGLViewResponderDelegate <NSObject>
|
||||
@@ -97,8 +110,22 @@ typedef NS_ENUM(NSInteger, CSOpenGLViewRedrawEvent) {
|
||||
@property (atomic, weak, nullable) id <CSOpenGLViewDelegate> delegate;
|
||||
@property (nonatomic, weak, nullable) id <CSOpenGLViewResponderDelegate> responderDelegate;
|
||||
|
||||
/// Determines whether the view offers mouse capturing — i.e. if the user clicks on the view then
|
||||
/// then the system cursor is disabled and the mouse events defined by CSOpenGLViewResponderDelegate
|
||||
/// are forwarded, unless and until the user releases the mouse using the control+command shortcut.
|
||||
@property (nonatomic, assign) BOOL shouldCaptureMouse;
|
||||
|
||||
/// Determines whether the CSOpenGLViewResponderDelegate of this window expects to use the command
|
||||
/// key as though it were any other key — i.e. all command combinations should be forwarded to the delegate,
|
||||
/// not being allowed to trigger regular application shortcuts such as command+q or command+h.
|
||||
///
|
||||
/// How the view respects this will depend on other state; if this view is one that captures the mouse then it
|
||||
/// will usurp command only while the mouse is captured.
|
||||
///
|
||||
/// TODO: what's smart behaviour if this view doesn't capture the mouse? Probably
|
||||
/// force a similar capturing behaviour?
|
||||
@property (nonatomic, assign) BOOL shouldUsurpCommand;
|
||||
|
||||
/*!
|
||||
Ends the timer tracking time; should be called prior to giving up the last owning reference
|
||||
to ensure that any retain cycles implied by the timer are resolved.
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
//
|
||||
|
||||
#import "CSOpenGLView.h"
|
||||
#import "CSApplication.h"
|
||||
@import CoreVideo;
|
||||
@import GLKit;
|
||||
|
||||
@interface CSOpenGLView () <NSDraggingDestination>
|
||||
@interface CSOpenGLView () <NSDraggingDestination, CSApplicationEventDelegate>
|
||||
@end
|
||||
|
||||
@implementation CSOpenGLView {
|
||||
@@ -139,23 +140,32 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)keyDown:(NSEvent *)theEvent {
|
||||
[self.responderDelegate keyDown:theEvent];
|
||||
- (void)keyDown:(NSEvent *)event {
|
||||
[self.responderDelegate keyDown:event];
|
||||
}
|
||||
|
||||
- (void)keyUp:(NSEvent *)theEvent {
|
||||
[self.responderDelegate keyUp:theEvent];
|
||||
- (void)keyUp:(NSEvent *)event {
|
||||
[self.responderDelegate keyUp:event];
|
||||
}
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)theEvent {
|
||||
[self.responderDelegate flagsChanged:theEvent];
|
||||
|
||||
- (void)flagsChanged:(NSEvent *)event {
|
||||
// Release the mouse upon a control + command.
|
||||
if(_mouseIsCaptured &&
|
||||
theEvent.modifierFlags & NSEventModifierFlagControl &&
|
||||
theEvent.modifierFlags & NSEventModifierFlagCommand) {
|
||||
event.modifierFlags & NSEventModifierFlagControl &&
|
||||
event.modifierFlags & NSEventModifierFlagCommand) {
|
||||
[self releaseMouse];
|
||||
}
|
||||
|
||||
[self.responderDelegate flagsChanged:event];
|
||||
}
|
||||
|
||||
- (BOOL)application:(nonnull CSApplication *)application shouldSendEvent:(nonnull NSEvent *)event {
|
||||
switch(event.type) {
|
||||
default: return YES;
|
||||
case NSEventTypeKeyUp: [self keyUp:event]; return NO;
|
||||
case NSEventTypeKeyDown: [self keyDown:event]; return NO;
|
||||
case NSEventTypeFlagsChanged: [self flagsChanged:event]; return NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)paste:(id)sender {
|
||||
@@ -223,6 +233,8 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
_mouseIsCaptured = NO;
|
||||
CGAssociateMouseAndMouseCursorPosition(true);
|
||||
[NSCursor unhide];
|
||||
[self.delegate openGLViewDidReleaseMouse:self];
|
||||
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = nil;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +293,10 @@ static CVReturn DisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeSt
|
||||
_mouseIsCaptured = YES;
|
||||
[NSCursor hide];
|
||||
CGAssociateMouseAndMouseCursorPosition(false);
|
||||
[self.delegate openGLViewDidCaptureMouse:self];
|
||||
if(self.shouldUsurpCommand) {
|
||||
((CSApplication *)[NSApplication sharedApplication]).eventDelegate = self;
|
||||
}
|
||||
|
||||
// Don't report the first click to the delegate; treat that as merely
|
||||
// an invitation to capture the cursor.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,8 @@
|
||||
|
||||
- (void)setUp {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
_video.reset(new Apple::Macintosh::Video(_ram, _dummy_audio, _dummy_drive_speed_accumulator));
|
||||
_video.reset(new Apple::Macintosh::Video(_dummy_audio, _dummy_drive_speed_accumulator));
|
||||
_video->set_ram(_ram, sizeof(_ram) - 1);
|
||||
}
|
||||
|
||||
- (void)testPrediction {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import glob
|
||||
import sys
|
||||
|
||||
# establish UTF-8 encoding
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf-8')
|
||||
# establish UTF-8 encoding for Python 2
|
||||
if sys.version_info < (3, 0):
|
||||
reload(sys)
|
||||
sys.setdefaultencoding('utf-8')
|
||||
|
||||
# create build environment
|
||||
env = Environment()
|
||||
@@ -35,6 +36,7 @@ SOURCES += glob.glob('../../Analyser/Static/Sega/*.cpp')
|
||||
SOURCES += glob.glob('../../Analyser/Static/ZX8081/*.cpp')
|
||||
|
||||
SOURCES += glob.glob('../../Components/1770/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/5380/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/6522/Implementation/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/6560/*.cpp')
|
||||
SOURCES += glob.glob('../../Components/8272/*.cpp')
|
||||
@@ -96,6 +98,10 @@ SOURCES += glob.glob('../../Storage/Disk/Encodings/MFM/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Disk/Parsers/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Disk/Track/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Disk/Data/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/MassStorage/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/MassStorage/Encodings/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/MassStorage/Formats/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/MassStorage/SCSI/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Tape/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Tape/Formats/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Tape/Parsers/*.cpp')
|
||||
|
||||
@@ -189,11 +189,11 @@ bool KeyboardKeyForSDLScancode(SDL_Keycode scancode, Inputs::Keyboard::Key &key)
|
||||
|
||||
BIND(PRINTSCREEN, PrintScreen) BIND(SCROLLLOCK, ScrollLock) BIND(PAUSE, Pause)
|
||||
|
||||
BIND(GRAVE, BackTick) BIND(MINUS, Hyphen) BIND(EQUALS, Equals) BIND(BACKSPACE, BackSpace)
|
||||
BIND(GRAVE, BackTick) BIND(MINUS, Hyphen) BIND(EQUALS, Equals) BIND(BACKSPACE, Backspace)
|
||||
|
||||
BIND(TAB, Tab)
|
||||
BIND(LEFTBRACKET, OpenSquareBracket) BIND(RIGHTBRACKET, CloseSquareBracket)
|
||||
BIND(BACKSLASH, BackSlash)
|
||||
BIND(BACKSLASH, Backslash)
|
||||
|
||||
BIND(CAPSLOCK, CapsLock) BIND(SEMICOLON, Semicolon)
|
||||
BIND(APOSTROPHE, Quote) BIND(RETURN, Enter)
|
||||
@@ -302,6 +302,32 @@ std::string system_get(const char *command) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
Maintains a communicative window title.
|
||||
*/
|
||||
class DynamicWindowTitler {
|
||||
public:
|
||||
DynamicWindowTitler(SDL_Window *window) : window_(window), file_name_(SDL_GetWindowTitle(window)) {}
|
||||
|
||||
std::string window_title() {
|
||||
if(!mouse_is_captured_) return file_name_;
|
||||
return file_name_ + " (press control+escape to release mouse)";
|
||||
}
|
||||
|
||||
void set_mouse_is_captured(bool is_captured) {
|
||||
mouse_is_captured_ = is_captured;
|
||||
update_window_title();
|
||||
}
|
||||
|
||||
private:
|
||||
void update_window_title() {
|
||||
SDL_SetWindowTitle(window_, window_title().c_str());
|
||||
}
|
||||
bool mouse_is_captured_ = false;
|
||||
SDL_Window *window_ = nullptr;
|
||||
const std::string file_name_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@@ -351,7 +377,7 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
// Determine the machine for the supplied file.
|
||||
Analyser::Static::TargetList targets = Analyser::Static::GetTargets(arguments.file_name);
|
||||
const auto targets = Analyser::Static::GetTargets(arguments.file_name);
|
||||
if(targets.empty()) {
|
||||
std::cerr << "Cannot open " << arguments.file_name << "; no target machine found" << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
@@ -459,6 +485,8 @@ int main(int argc, char *argv[]) {
|
||||
400, 300,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
|
||||
|
||||
DynamicWindowTitler window_titler(window);
|
||||
|
||||
SDL_GLContext gl_context = nullptr;
|
||||
if(window) {
|
||||
gl_context = SDL_GL_CreateContext(window);
|
||||
@@ -628,6 +656,7 @@ int main(int argc, char *argv[]) {
|
||||
// Use ctrl+escape to release the mouse (if captured).
|
||||
if(event.key.keysym.sym == SDLK_ESCAPE && (SDL_GetModState()&KMOD_CTRL)) {
|
||||
SDL_SetRelativeMouseMode(SDL_FALSE);
|
||||
window_titler.set_mouse_is_captured(false);
|
||||
}
|
||||
|
||||
// Capture ctrl+shift+d as a take-a-screenshot command.
|
||||
@@ -734,6 +763,7 @@ int main(int argc, char *argv[]) {
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
if(uses_mouse && !SDL_GetRelativeMouseMode()) {
|
||||
SDL_SetRelativeMouseMode(SDL_TRUE);
|
||||
window_titler.set_mouse_is_captured(true);
|
||||
break;
|
||||
}
|
||||
case SDL_MOUSEBUTTONUP: {
|
||||
|
||||
@@ -406,6 +406,10 @@ void CRT::set_immediate_default_phase(float phase) {
|
||||
}
|
||||
|
||||
void CRT::output_data(int number_of_cycles, size_t number_of_samples) {
|
||||
#ifndef NDEBUG
|
||||
assert(number_of_samples > 0 && number_of_samples <= allocated_data_length_);
|
||||
allocated_data_length_ = std::numeric_limits<size_t>::min();
|
||||
#endif
|
||||
scan_target_->end_data(number_of_samples);
|
||||
Scan scan;
|
||||
scan.type = Scan::Type::Data;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define CRT_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
|
||||
#include "../ScanTarget.hpp"
|
||||
@@ -83,6 +84,10 @@ class CRT {
|
||||
Outputs::Display::ScanTarget::Modals scan_target_modals_;
|
||||
static const uint8_t DefaultAmplitude = 80;
|
||||
|
||||
#ifndef NDEBUG
|
||||
size_t allocated_data_length_ = std::numeric_limits<size_t>::min();
|
||||
#endif
|
||||
|
||||
public:
|
||||
/*! Constructs the CRT with a specified clock rate, height and colour subcarrier frequency.
|
||||
The requested number of buffers, each with the requested number of bytes per pixel,
|
||||
@@ -221,6 +226,9 @@ class CRT {
|
||||
@returns A pointer to the allocated area if room is available; @c nullptr otherwise.
|
||||
*/
|
||||
inline uint8_t *begin_data(std::size_t required_length, std::size_t required_alignment = 1) {
|
||||
#ifndef NDEBUG
|
||||
allocated_data_length_ = required_length;
|
||||
#endif
|
||||
return scan_target_->begin_data(required_length, required_alignment);
|
||||
}
|
||||
|
||||
|
||||
@@ -174,8 +174,10 @@ void ScanTarget::end_scan() {
|
||||
}
|
||||
|
||||
uint8_t *ScanTarget::begin_data(size_t required_length, size_t required_alignment) {
|
||||
assert(required_alignment);
|
||||
|
||||
if(allocation_has_failed_) return nullptr;
|
||||
if(!write_area_texture_.size()) {
|
||||
if(write_area_texture_.empty()) {
|
||||
allocation_has_failed_ = true;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -227,8 +229,10 @@ void ScanTarget::end_data(size_t actual_length) {
|
||||
data_type_size_);
|
||||
|
||||
// The write area was allocated in the knowledge that there's sufficient
|
||||
// distance left on the current line, so there's no need to worry about carry.
|
||||
// distance left on the current line, but there's a risk of exactly filling
|
||||
// the final line, in which case this should wrap back to 0.
|
||||
write_pointers_.write_area += actual_length + 1;
|
||||
write_pointers_.write_area %= write_area_texture_.size();
|
||||
|
||||
// Also bookend the end.
|
||||
memcpy(
|
||||
|
||||
@@ -123,8 +123,8 @@ class ScanTarget: public Outputs::Display::ScanTarget {
|
||||
/// A pointer to the first thing not yet submitted for display.
|
||||
std::atomic<PointerSet> read_pointers_;
|
||||
|
||||
// Maintains a buffer of the most recent 3072 scans.
|
||||
std::array<Scan, 3072> scan_buffer_;
|
||||
/// Maintains a buffer of the most recent scans.
|
||||
std::array<Scan, 16384> scan_buffer_;
|
||||
|
||||
// Maintains a list of composite scan buffer coordinates; the Line struct
|
||||
// is transported to the GPU in its entirety; the LineMetadatas live in CPU
|
||||
|
||||
@@ -188,6 +188,12 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
|
||||
case BusStep::Action::AdvancePrefetch:
|
||||
prefetch_queue_.halves.high = prefetch_queue_.halves.low;
|
||||
|
||||
// During prefetch advance seems to be the only time the interrupt inputs are sampled;
|
||||
// TODO: determine whether this really happens on *every* advance.
|
||||
if(bus_interrupt_level_ > interrupt_level_) {
|
||||
pending_interrupt_level_ = bus_interrupt_level_;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -199,6 +205,7 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
// If an interrupt (TODO: or reset) has finally arrived that will be serviced,
|
||||
// exit the STOP.
|
||||
if(bus_interrupt_level_ > interrupt_level_) {
|
||||
pending_interrupt_level_ = bus_interrupt_level_;
|
||||
execution_state_ = ExecutionState::BeginInterrupt;
|
||||
continue;
|
||||
}
|
||||
@@ -234,12 +241,6 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
continue;
|
||||
|
||||
case ExecutionState::BeginInterrupt:
|
||||
#ifdef LOG_TRACE
|
||||
// should_log = true;
|
||||
if(should_log) {
|
||||
printf("\n\nInterrupt\n\n");
|
||||
}
|
||||
#endif
|
||||
active_program_ = nullptr;
|
||||
active_micro_op_ = interrupt_micro_ops_;
|
||||
execution_state_ = ExecutionState::Executing;
|
||||
@@ -260,7 +261,7 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
// Either the micro-operations for this instruction have been exhausted, or
|
||||
// no instruction was ongoing. Either way, do a standard instruction operation.
|
||||
|
||||
if(bus_interrupt_level_ > interrupt_level_) {
|
||||
if(pending_interrupt_level_) {
|
||||
execution_state_ = ExecutionState::BeginInterrupt;
|
||||
break;
|
||||
}
|
||||
@@ -1055,51 +1056,56 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
break;
|
||||
}
|
||||
|
||||
int32_t dividend = int32_t(destination()->full);
|
||||
int32_t divisor = s_extend16(source()->halves.low.full);
|
||||
const int64_t quotient = int64_t(dividend) / int64_t(divisor);
|
||||
const int32_t signed_dividend = int32_t(destination()->full);
|
||||
const int32_t signed_divisor = s_extend16(source()->halves.low.full);
|
||||
const auto result_sign =
|
||||
( (0 <= signed_dividend) - (signed_dividend < 0) ) *
|
||||
( (0 <= signed_divisor) - (signed_divisor < 0) );
|
||||
|
||||
const uint32_t dividend = uint32_t(abs(signed_dividend));
|
||||
const uint32_t divisor = uint32_t(abs(signed_divisor));
|
||||
|
||||
int cycles_expended = 12; // Covers the nn nnn n to get beyond the sign test.
|
||||
if(dividend < 0) {
|
||||
if(signed_dividend < 0) {
|
||||
cycles_expended += 2; // An additional microycle applies if the dividend is negative.
|
||||
}
|
||||
|
||||
// Check for overflow. If it exists, work here is already done.
|
||||
if(quotient > 32767 || quotient < -32768) {
|
||||
const auto quotient = dividend / divisor;
|
||||
if(quotient > 32767) {
|
||||
overflow_flag_ = 1;
|
||||
set_next_microcycle_length(HalfCycles(3*2*2));
|
||||
set_next_microcycle_length(HalfCycles(6*2*2));
|
||||
|
||||
// These are officially undefined for results that overflow, so the below is a guess.
|
||||
zero_result_ = decltype(zero_result_)(divisor & 0xffff);
|
||||
zero_result_ = decltype(zero_result_)(dividend);
|
||||
negative_flag_ = zero_result_ & 0x8000;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
zero_result_ = decltype(zero_result_)(quotient);
|
||||
const uint16_t remainder = uint16_t(signed_dividend % signed_divisor);
|
||||
const int signed_quotient = result_sign*int(quotient);
|
||||
destination()->halves.high.full = remainder;
|
||||
destination()->halves.low.full = uint16_t(signed_quotient);
|
||||
|
||||
zero_result_ = decltype(zero_result_)(signed_quotient);
|
||||
negative_flag_ = zero_result_ & 0x8000;
|
||||
overflow_flag_ = 0;
|
||||
|
||||
// TODO: check sign rules here; am I necessarily giving the remainder the correct sign?
|
||||
// (and, if not, am I counting it in the correct direction?)
|
||||
const uint16_t remainder = uint16_t(dividend % divisor);
|
||||
destination()->halves.high.full = remainder;
|
||||
destination()->halves.low.full = uint16_t(quotient);
|
||||
|
||||
// Algorithm here: there is a fixed three-microcycle cost per bit set
|
||||
// in the unsigned quotient; there is an additional microcycle for
|
||||
// every bit that is set. Also, since the possibility of overflow
|
||||
// was already dealt with, it's now a smaller number.
|
||||
int positive_quotient_bits = int(abs(quotient)) & 0xfffe;
|
||||
// Algorithm here: there is a fixed cost per unset bit
|
||||
// in the first 15 bits of the unsigned quotient.
|
||||
auto positive_quotient_bits = ~quotient & 0xfffe;
|
||||
convert_to_bit_count_16(positive_quotient_bits);
|
||||
cycles_expended += 2 * positive_quotient_bits;
|
||||
|
||||
// There's then no way to terminate the loop that isn't at least six cycles long.
|
||||
cycles_expended += 6;
|
||||
// There's then no way to terminate the loop that isn't at least ten cycles long;
|
||||
// there's also a fixed overhead per bit. The two together add up to the 104 below.
|
||||
cycles_expended += 104;
|
||||
|
||||
if(divisor < 0) {
|
||||
// This picks up at 'No more bits' in yacht.txt's diagram.
|
||||
if(signed_divisor < 0) {
|
||||
cycles_expended += 2;
|
||||
} else if(dividend < 0) {
|
||||
} else if(signed_dividend < 0) {
|
||||
cycles_expended += 4;
|
||||
}
|
||||
set_next_microcycle_length(HalfCycles(cycles_expended * 2));
|
||||
@@ -1960,10 +1966,13 @@ template <class T, bool dtack_is_implicit, bool signal_will_perform> void Proces
|
||||
|
||||
// Mutate neessary internal state — effective_address_[0] is exposed
|
||||
// on the data bus as the accepted interrupt number during the interrupt
|
||||
// acknowledge cycle, with the low bit set since a real 68000 uses the lower
|
||||
// data strobe to collect the corresponding vector byte.
|
||||
accepted_interrupt_level_ = interrupt_level_ = bus_interrupt_level_;
|
||||
effective_address_[0].full = 1 | uint32_t(accepted_interrupt_level_ << 1);
|
||||
// acknowledge cycle, with all other bits set, including the low bit as
|
||||
// a real 68000 uses the lower data strobe to collect the corresponding vector byte.
|
||||
//
|
||||
// Cf. M68000 8-/16-/32-BIT MICROPROCESSORS USER'S MANUAL 5.1.4.
|
||||
accepted_interrupt_level_ = interrupt_level_ = pending_interrupt_level_;
|
||||
pending_interrupt_level_ = 0;
|
||||
effective_address_[0].full = 0xfffffff1 | uint32_t(accepted_interrupt_level_ << 1);
|
||||
|
||||
// Recede the program counter to where it would have been were there no
|
||||
// prefetch; that's where the reading stream should pick up upon RTE.
|
||||
|
||||
@@ -62,6 +62,11 @@ class ProcessorStorage {
|
||||
bool bus_acknowledge_ = false;
|
||||
bool halt_ = false;
|
||||
|
||||
// Holds the interrupt level that should be serviced at the next instruction
|
||||
// dispatch, if any.
|
||||
int pending_interrupt_level_ = 0;
|
||||
// Holds the interrupt level that is currently being serviced.
|
||||
// TODO: surely this doesn't need to be distinct from the pending_interrupt_level_?
|
||||
int accepted_interrupt_level_ = 0;
|
||||
bool is_starting_interrupt_ = false;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Z80 {
|
||||
/*
|
||||
The list of registers that can be accessed via @c set_value_of_register and @c set_value_of_register.
|
||||
*/
|
||||
enum Register {
|
||||
enum class Register {
|
||||
ProgramCounter,
|
||||
StackPointer,
|
||||
|
||||
|
||||
@@ -18,13 +18,12 @@ It currently contains emulations of the:
|
||||
* Atari 2600;
|
||||
* ColecoVision;
|
||||
* Commodore Vic-20 (and Commodore 1540/1);
|
||||
* Macintosh 512ke and Plus;
|
||||
* MSX 1;
|
||||
* Oric 1/Atmos;
|
||||
* Sega Master System; and
|
||||
* Sinclair ZX80/81.
|
||||
|
||||
Work is in progress on the Macintosh 512ke; it is presently very experimental.
|
||||
|
||||
## Single-click Loading
|
||||
|
||||
Through the combination of static analysis and runtime analysis, CLK seeks to be able automatically to select and configure the appropriate machine to run any provided disk, tape or ROM; to issue any commands necessary to run the software contained on the disk, tape or ROM; and to provide accelerated loading where feasible.
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
namespace Storage {
|
||||
namespace Disk {
|
||||
|
||||
/*!
|
||||
Models a flopy disk.
|
||||
*/
|
||||
class Disk {
|
||||
public:
|
||||
virtual ~Disk() {}
|
||||
|
||||
@@ -20,8 +20,8 @@ using namespace Storage::Disk;
|
||||
|
||||
Drive::Drive(int input_clock_rate, int revolutions_per_minute, int number_of_heads):
|
||||
Storage::TimedEventLoop(input_clock_rate),
|
||||
rotational_multiplier_(60.0f / float(revolutions_per_minute)),
|
||||
available_heads_(number_of_heads) {
|
||||
set_rotation_speed(revolutions_per_minute);
|
||||
|
||||
const auto seed = static_cast<std::default_random_engine::result_type>(std::chrono::system_clock::now().time_since_epoch().count());
|
||||
std::default_random_engine randomiser(seed);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
89
Storage/MassStorage/Encodings/MacintoshVolume.hpp
Normal file
89
Storage/MassStorage/Encodings/MacintoshVolume.hpp
Normal file
@@ -0,0 +1,89 @@
|
||||
//
|
||||
// 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>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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 */
|
||||
113
Storage/MassStorage/SCSI/SCSI.cpp
Normal file
113
Storage/MassStorage/SCSI/SCSI.cpp
Normal file
@@ -0,0 +1,113 @@
|
||||
//
|
||||
// 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;
|
||||
|
||||
if(activity_observer_ && (state_^previous_state)&SCSI::Line::Busy) {
|
||||
activity_observer_->set_led_status("SCSI", state_&SCSI::Line::Busy);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
void Bus::set_activity_observer(Activity::Observer *observer) {
|
||||
activity_observer_ = observer;
|
||||
activity_observer_->register_led("SCSI");
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
162
Storage/MassStorage/SCSI/SCSI.hpp
Normal file
162
Storage/MassStorage/SCSI/SCSI.hpp
Normal file
@@ -0,0 +1,162 @@
|
||||
//
|
||||
// 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"
|
||||
#include "../../../Activity/Source.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 Activity::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;
|
||||
|
||||
// Fulfilling public Activity::Source.
|
||||
void set_activity_observer(Activity::Observer *observer) 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_;
|
||||
|
||||
Activity::Observer *activity_observer_ = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#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;
|
||||
}
|
||||
402
Storage/MassStorage/SCSI/Target.hpp
Normal file
402
Storage/MassStorage/SCSI/Target.hpp
Normal file
@@ -0,0 +1,402 @@
|
||||
//
|
||||
// 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 <cassert>
|
||||
#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_;
|
||||
};
|
||||
|
||||
#include "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>
|
||||
|
||||
@@ -128,7 +128,7 @@ class TapePlayer: public TimedEventLoop, public ClockingHint::Source {
|
||||
|
||||
They can also provide a delegate to be notified upon any change in the input level.
|
||||
*/
|
||||
class BinaryTapePlayer: public TapePlayer {
|
||||
class BinaryTapePlayer : public TapePlayer {
|
||||
public:
|
||||
BinaryTapePlayer(int input_clock_rate);
|
||||
void set_motor_control(bool enabled);
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user