mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
117 Commits
2020-11-13
...
2021-02-02
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d2490b774 | ||
|
|
1e041f1adf | ||
|
|
4fdf01a1a8 | ||
|
|
beb514b231 | ||
|
|
f57e897085 | ||
|
|
2a8e8a4982 | ||
|
|
9f202d4238 | ||
|
|
1a40cc048e | ||
|
|
53514c7fdc | ||
|
|
274b3c7d24 | ||
|
|
07df7572b3 | ||
|
|
906b6ccdb7 | ||
|
|
f1ba040dd8 | ||
|
|
8db289e229 | ||
|
|
8142487d57 | ||
|
|
2860be7068 | ||
|
|
7e720e754b | ||
|
|
41a618c957 | ||
|
|
3d85e6bb97 | ||
|
|
d54085c7fd | ||
|
|
0bb8bdf938 | ||
|
|
865058b8d6 | ||
|
|
b6bc0a21fb | ||
|
|
8311ac4a7c | ||
|
|
4636d8dfb7 | ||
|
|
ac95e4d758 | ||
|
|
4bd6ffa9e4 | ||
|
|
9c2c918760 | ||
|
|
47d20699d8 | ||
|
|
e8ce70dccb | ||
|
|
fa4938f29c | ||
|
|
ddb4bb1421 | ||
|
|
ca94e9038e | ||
|
|
2c72a77a25 | ||
|
|
8c0e06e645 | ||
|
|
a24ae727a7 | ||
|
|
5058a8b96a | ||
|
|
762ecab3aa | ||
|
|
9ba5b7c1d4 | ||
|
|
5f807b6e47 | ||
|
|
718f950071 | ||
|
|
68fe16a092 | ||
|
|
97a64db5e0 | ||
|
|
86577b772b | ||
|
|
306df7554e | ||
|
|
30c2c0f050 | ||
|
|
205649cac2 | ||
|
|
fd49b72e31 | ||
|
|
995904993d | ||
|
|
17cbba85fc | ||
|
|
9d7d45338f | ||
|
|
3b55d3f158 | ||
|
|
fda2293d6b | ||
|
|
d4095b1b3b | ||
|
|
ed41154338 | ||
|
|
38bca5f0f0 | ||
|
|
a8738b533a | ||
|
|
29cf96c703 | ||
|
|
782dc3d046 | ||
|
|
0ae217f51d | ||
|
|
adcb2e03e8 | ||
|
|
11b6c1d4b5 | ||
|
|
367cb1789d | ||
|
|
adf1484ecc | ||
|
|
5401ff6c78 | ||
|
|
eb8d0eefd5 | ||
|
|
c934e22cee | ||
|
|
1a3effc692 | ||
|
|
32c942d154 | ||
|
|
9c5dc0ed29 | ||
|
|
290972cedf | ||
|
|
dc9d370952 | ||
|
|
a41be61f99 | ||
|
|
3d1783ddae | ||
|
|
8151c8e409 | ||
|
|
0ef42f93ff | ||
|
|
d318ab4e70 | ||
|
|
ebfa35c2c7 | ||
|
|
db50b0fe23 | ||
|
|
233a69a1d8 | ||
|
|
3749b7b776 | ||
|
|
ed63e7ea75 | ||
|
|
3d79b11f92 | ||
|
|
dfe4e49110 | ||
|
|
c5c56f9d05 | ||
|
|
9f0129cab8 | ||
|
|
5a48e50355 | ||
|
|
86283b1815 | ||
|
|
a38d964f62 | ||
|
|
2f86d5ebaf | ||
|
|
b589d6e3ef | ||
|
|
db8b265e80 | ||
|
|
8560b38ffa | ||
|
|
94eb17db0c | ||
|
|
9577c8e27f | ||
|
|
32ccce3040 | ||
|
|
ab3fcb3ea0 | ||
|
|
9610672615 | ||
|
|
5ee9630624 | ||
|
|
1b3836eb1c | ||
|
|
1302a046e9 | ||
|
|
33dec3c220 | ||
|
|
7c29c3a944 | ||
|
|
c9ca1fc7a0 | ||
|
|
a965c8de9f | ||
|
|
0b4b271e3d | ||
|
|
5fc6dd1a4d | ||
|
|
79ef026b93 | ||
|
|
a4ab5b0b49 | ||
|
|
3207183f05 | ||
|
|
e803f993b7 | ||
|
|
5dbc87caf0 | ||
|
|
4862ccc947 | ||
|
|
e1ecf66485 | ||
|
|
2c71ba0744 | ||
|
|
a7aeb779e9 | ||
|
|
e72cfbf447 |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://www.amazon.com/hz/wishlist/ls/8WPVFLQQDPTA']
|
||||
# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
@@ -50,7 +50,10 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
new_file.name = name;
|
||||
new_file.load_address = uint32_t(details->samples[0][file_offset] | (details->samples[0][file_offset+1] << 8) | ((details->samples[0][file_offset+6]&0x0c) << 14));
|
||||
new_file.execution_address = uint32_t(details->samples[0][file_offset+2] | (details->samples[0][file_offset+3] << 8) | ((details->samples[0][file_offset+6]&0xc0) << 10));
|
||||
new_file.is_protected = names->samples[0][file_offset + 7] & 0x80;
|
||||
if(names->samples[0][file_offset + 7] & 0x80) {
|
||||
// File is locked; it may not be altered or deleted.
|
||||
new_file.flags |= File::Flags::Locked;
|
||||
}
|
||||
|
||||
long data_length = long(details->samples[0][file_offset+4] | (details->samples[0][file_offset+5] << 8) | ((details->samples[0][file_offset+6]&0x30) << 12));
|
||||
int start_sector = details->samples[0][file_offset+7] | ((details->samples[0][file_offset+6]&0x03) << 8);
|
||||
@@ -69,11 +72,16 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetDFSCatalogue(const std::s
|
||||
new_file.data.insert(new_file.data.end(), next_sector->samples[0].begin(), next_sector->samples[0].begin() + length_from_sector);
|
||||
data_length -= length_from_sector;
|
||||
}
|
||||
if(!data_length) catalogue->files.push_back(new_file);
|
||||
if(!data_length) catalogue->files.push_back(std::move(new_file));
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
|
||||
/*
|
||||
Primary resource used: "Acorn 8-Bit ADFS Filesystem Structure";
|
||||
http://mdfs.net/Docs/Comp/Disk/Format/ADFS
|
||||
*/
|
||||
std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
auto catalogue = std::make_unique<Catalogue>();
|
||||
Storage::Encodings::MFM::Parser parser(true, disk);
|
||||
@@ -101,5 +109,73 @@ std::unique_ptr<Catalogue> Analyser::Static::Acorn::GetADFSCatalogue(const std::
|
||||
case 3: catalogue->bootOption = Catalogue::BootOption::ExecBOOT; break;
|
||||
}
|
||||
|
||||
// Parse the root directory, at least.
|
||||
for(std::size_t file_offset = 0x005; file_offset < 0x4cb; file_offset += 0x1a) {
|
||||
// Obtain the name, which will be at most ten characters long, and will
|
||||
// be terminated by either a NULL character or a \r.
|
||||
char name[11];
|
||||
std::size_t c = 0;
|
||||
for(; c < 10; c++) {
|
||||
const char next = root_directory[file_offset + c] & 0x7f;
|
||||
name[c] = next;
|
||||
if(next == '\0' || next == '\r') break;
|
||||
}
|
||||
name[c] = '\0';
|
||||
|
||||
// Skip if the name is empty.
|
||||
if(name[0] == '\0') continue;
|
||||
|
||||
// Populate a file then.
|
||||
File new_file;
|
||||
new_file.name = name;
|
||||
new_file.flags =
|
||||
(root_directory[file_offset + 0] & 0x80 ? File::Flags::Readable : 0) |
|
||||
(root_directory[file_offset + 1] & 0x80 ? File::Flags::Writable : 0) |
|
||||
(root_directory[file_offset + 2] & 0x80 ? File::Flags::Locked : 0) |
|
||||
(root_directory[file_offset + 3] & 0x80 ? File::Flags::IsDirectory : 0) |
|
||||
(root_directory[file_offset + 4] & 0x80 ? File::Flags::ExecuteOnly : 0) |
|
||||
(root_directory[file_offset + 5] & 0x80 ? File::Flags::PubliclyReadable : 0) |
|
||||
(root_directory[file_offset + 6] & 0x80 ? File::Flags::PubliclyWritable : 0) |
|
||||
(root_directory[file_offset + 7] & 0x80 ? File::Flags::PubliclyExecuteOnly : 0) |
|
||||
(root_directory[file_offset + 8] & 0x80 ? File::Flags::IsPrivate : 0);
|
||||
|
||||
new_file.load_address =
|
||||
(uint32_t(root_directory[file_offset + 0x0a]) << 0) |
|
||||
(uint32_t(root_directory[file_offset + 0x0b]) << 8) |
|
||||
(uint32_t(root_directory[file_offset + 0x0c]) << 16) |
|
||||
(uint32_t(root_directory[file_offset + 0x0d]) << 24);
|
||||
|
||||
new_file.execution_address =
|
||||
(uint32_t(root_directory[file_offset + 0x0e]) << 0) |
|
||||
(uint32_t(root_directory[file_offset + 0x0f]) << 8) |
|
||||
(uint32_t(root_directory[file_offset + 0x10]) << 16) |
|
||||
(uint32_t(root_directory[file_offset + 0x11]) << 24);
|
||||
|
||||
new_file.sequence_number = root_directory[file_offset + 0x19];
|
||||
|
||||
const uint32_t size =
|
||||
(uint32_t(root_directory[file_offset + 0x12]) << 0) |
|
||||
(uint32_t(root_directory[file_offset + 0x13]) << 8) |
|
||||
(uint32_t(root_directory[file_offset + 0x14]) << 16) |
|
||||
(uint32_t(root_directory[file_offset + 0x15]) << 24);
|
||||
|
||||
uint32_t start_sector =
|
||||
(uint32_t(root_directory[file_offset + 0x16]) << 0) |
|
||||
(uint32_t(root_directory[file_offset + 0x17]) << 8) |
|
||||
(uint32_t(root_directory[file_offset + 0x18]) << 16);
|
||||
|
||||
new_file.data.reserve(size);
|
||||
while(new_file.data.size() < size) {
|
||||
const Storage::Encodings::MFM::Sector *const sector = parser.get_sector(start_sector / (80 * 16), (start_sector / 16) % 80, start_sector % 16);
|
||||
if(!sector) break;
|
||||
|
||||
const auto length_from_sector = std::min(size - new_file.data.size(), sector->samples[0].size());
|
||||
new_file.data.insert(new_file.data.end(), sector->samples[0].begin(), sector->samples[0].begin() + ssize_t(length_from_sector));
|
||||
++start_sector;
|
||||
}
|
||||
|
||||
catalogue->files.push_back(std::move(new_file));
|
||||
}
|
||||
|
||||
return catalogue;
|
||||
}
|
||||
|
||||
@@ -19,19 +19,38 @@ namespace Acorn {
|
||||
|
||||
struct File {
|
||||
std::string name;
|
||||
uint32_t load_address;
|
||||
uint32_t execution_address;
|
||||
bool is_protected;
|
||||
uint32_t load_address = 0;
|
||||
uint32_t execution_address = 0;
|
||||
|
||||
enum Flags: uint16_t {
|
||||
Readable = 1 << 0,
|
||||
Writable = 1 << 1,
|
||||
Locked = 1 << 2,
|
||||
IsDirectory = 1 << 3,
|
||||
ExecuteOnly = 1 << 4,
|
||||
PubliclyReadable = 1 << 5,
|
||||
PubliclyWritable = 1 << 6,
|
||||
PubliclyExecuteOnly = 1 << 7,
|
||||
IsPrivate = 1 << 8,
|
||||
};
|
||||
uint16_t flags = Flags::Readable | Flags::Readable | Flags::PubliclyReadable | Flags::PubliclyWritable;
|
||||
uint8_t sequence_number = 0;
|
||||
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
/// Describes a single chunk of file data; these relate to the tape and ROM filing system.
|
||||
/// The File-level fields contain a 'definitive' version of the load and execution addresses,
|
||||
/// but both of those filing systems also store them per chunk.
|
||||
///
|
||||
/// Similarly, the file-level data will contain the aggregate data of all chunks.
|
||||
struct Chunk {
|
||||
std::string name;
|
||||
uint32_t load_address;
|
||||
uint32_t execution_address;
|
||||
uint16_t block_number;
|
||||
uint16_t block_length;
|
||||
uint8_t block_flag;
|
||||
uint32_t next_address;
|
||||
uint32_t load_address = 0;
|
||||
uint32_t execution_address = 0;
|
||||
uint16_t block_number = 0;
|
||||
uint16_t block_length = 0;
|
||||
uint32_t next_address = 0;
|
||||
uint8_t block_flag = 0;
|
||||
|
||||
bool header_crc_matched;
|
||||
bool data_crc_matched;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Analyser::Static::Acorn;
|
||||
|
||||
static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
@@ -59,10 +61,6 @@ static std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>>
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
auto target = std::make_unique<Target>();
|
||||
target->confidence = 0.5; // TODO: a proper estimation
|
||||
target->has_dfs = false;
|
||||
target->has_adfs = false;
|
||||
target->should_shift_restart = false;
|
||||
|
||||
// strip out inappropriate cartridges
|
||||
target->media.cartridges = AcornCartridgesFrom(media.cartridges);
|
||||
@@ -77,8 +75,8 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
if(!files.empty()) {
|
||||
bool is_basic = true;
|
||||
|
||||
// protected files are always for *RUNning only
|
||||
if(files.front().is_protected) is_basic = false;
|
||||
// If a file is execute-only, that means *RUN.
|
||||
if(files.front().flags & File::Flags::ExecuteOnly) is_basic = false;
|
||||
|
||||
// check also for a continuous threading of BASIC lines; if none then this probably isn't BASIC code,
|
||||
// so that's also justification to *RUN
|
||||
@@ -108,15 +106,60 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(const Media &me
|
||||
dfs_catalogue = GetDFSCatalogue(disk);
|
||||
if(dfs_catalogue == nullptr) adfs_catalogue = GetADFSCatalogue(disk);
|
||||
if(dfs_catalogue || adfs_catalogue) {
|
||||
// Accept the disk and determine whether DFS or ADFS ROMs are implied.
|
||||
// Use the Pres ADFS if using an ADFS, as it leaves Page at &EOO.
|
||||
target->media.disks = media.disks;
|
||||
target->has_dfs = !!dfs_catalogue;
|
||||
target->has_adfs = !!adfs_catalogue;
|
||||
target->has_dfs = bool(dfs_catalogue);
|
||||
target->has_pres_adfs = bool(adfs_catalogue);
|
||||
|
||||
// Check whether a simple shift+break will do for loading this disk.
|
||||
Catalogue::BootOption bootOption = (dfs_catalogue ?: adfs_catalogue)->bootOption;
|
||||
if(bootOption != Catalogue::BootOption::None)
|
||||
if(bootOption != Catalogue::BootOption::None) {
|
||||
target->should_shift_restart = true;
|
||||
else
|
||||
} else {
|
||||
target->loading_command = "*CAT\n";
|
||||
}
|
||||
|
||||
// Check whether adding the AP6 ROM is justified.
|
||||
// For now this is an incredibly dense text search;
|
||||
// if any of the commands that aren't usually present
|
||||
// on a stock Electron are here, add the AP6 ROM and
|
||||
// some sideways RAM such that the SR commands are useful.
|
||||
for(const auto &file: dfs_catalogue ? dfs_catalogue->files : adfs_catalogue->files) {
|
||||
for(const auto &command: {
|
||||
"AQRPAGE", "BUILD", "DUMP", "FORMAT", "INSERT", "LANG", "LIST", "LOADROM",
|
||||
"LOCK", "LROMS", "RLOAD", "ROMS", "RSAVE", "SAVEROM", "SRLOAD", "SRPAGE",
|
||||
"SRUNLOCK", "SRWIPE", "TUBE", "TYPE", "UNLOCK", "UNPLUG", "UROMS",
|
||||
"VERIFY", "ZERO"
|
||||
}) {
|
||||
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enable the Acorn ADFS if a mass-storage device is attached;
|
||||
// unlike the Pres ADFS it retains SCSI logic.
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
target->has_pres_adfs = false; // To override a floppy selection, if one was made.
|
||||
target->has_acorn_adfs = true;
|
||||
|
||||
// Assume some sort of later-era Acorn work is likely to happen;
|
||||
// so ensure *TYPE, etc are present.
|
||||
target->has_ap6_rom = true;
|
||||
target->has_sideways_ram = true;
|
||||
|
||||
target->media.mass_storage_devices = media.mass_storage_devices;
|
||||
|
||||
// Check for a boot option.
|
||||
const auto sector = target->media.mass_storage_devices.front()->get_block(1);
|
||||
if(sector[0xfd]) {
|
||||
target->should_shift_restart = true;
|
||||
} else {
|
||||
target->loading_command = "*CAT\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,12 @@ static std::unique_ptr<File> GetNextFile(std::deque<File::Chunk> &chunks) {
|
||||
file->name = file->chunks.front().name;
|
||||
file->load_address = file->chunks.front().load_address;
|
||||
file->execution_address = file->chunks.front().execution_address;
|
||||
file->is_protected = !!(file->chunks.back().block_flag & 0x01); // I think the last flags are the ones that count; TODO: check.
|
||||
// I think the final chunk's flags are the ones that count; TODO: check.
|
||||
if(file->chunks.back().block_flag & 0x01) {
|
||||
// File is locked, which in more generalised terms means it is
|
||||
// for execution only.
|
||||
file->flags |= File::Flags::ExecuteOnly;
|
||||
}
|
||||
|
||||
// copy all data into a single big block
|
||||
for(File::Chunk chunk : file->chunks) {
|
||||
|
||||
@@ -18,15 +18,21 @@ namespace Static {
|
||||
namespace Acorn {
|
||||
|
||||
struct Target: public ::Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
bool has_adfs = false;
|
||||
bool has_acorn_adfs = false;
|
||||
bool has_pres_adfs = false;
|
||||
bool has_dfs = false;
|
||||
bool has_ap6_rom = false;
|
||||
bool has_sideways_ram = false;
|
||||
bool should_shift_restart = false;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Electron) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(has_adfs);
|
||||
DeclareField(has_pres_adfs);
|
||||
DeclareField(has_acorn_adfs);
|
||||
DeclareField(has_dfs);
|
||||
DeclareField(has_ap6_rom);
|
||||
DeclareField(has_sideways_ram);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "../../Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// Tapes
|
||||
@@ -98,12 +99,14 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
Format("81", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // 81
|
||||
Format("a26", result.cartridges, Cartridge::BinaryDump, TargetPlatform::Atari2600) // A26
|
||||
Format("adf", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADF
|
||||
Format("adl", result.disks, Disk::DiskImageHolder<Storage::Disk::AcornADF>, TargetPlatform::Acorn) // ADL
|
||||
Format("bin", result.cartridges, Cartridge::BinaryDump, TargetPlatform::AllCartridge) // BIN (cartridge dump)
|
||||
Format("cas", result.tapes, Tape::CAS, TargetPlatform::MSX) // CAS
|
||||
Format("cdt", result.tapes, Tape::TZX, TargetPlatform::AmstradCPC) // CDT
|
||||
Format("col", result.cartridges, Cartridge::BinaryDump, TargetPlatform::ColecoVision) // COL
|
||||
Format("csw", result.tapes, Tape::CSW, TargetPlatform::AllTape) // CSW
|
||||
Format("d64", result.disks, Disk::DiskImageHolder<Storage::Disk::D64>, TargetPlatform::Commodore) // D64
|
||||
Format("dat", result.mass_storage_devices, MassStorage::DAT, TargetPlatform::Acorn) // DAT
|
||||
Format("dmk", result.disks, Disk::DiskImageHolder<Storage::Disk::DMK>, TargetPlatform::MSX) // DMK
|
||||
Format("do", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DO
|
||||
Format("dsd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // DSD
|
||||
|
||||
@@ -180,29 +180,40 @@ void DiskII::set_state_machine(const std::vector<uint8_t> &state_machine) {
|
||||
if(state_machine[0] != 0x18) {
|
||||
for(size_t source_address = 0; source_address < 256; ++source_address) {
|
||||
// Remap into Beneath Apple Pro-DOS address form.
|
||||
size_t destination_address =
|
||||
((source_address&0x80) ? 0x10 : 0x00) |
|
||||
((source_address&0x01) ? 0x20 : 0x00) |
|
||||
((source_address&0x40) ? 0x40 : 0x00) |
|
||||
const size_t destination_address =
|
||||
((source_address&0x20) ? 0x80 : 0x00) |
|
||||
((source_address&0x10) ? 0x01 : 0x00) |
|
||||
((source_address&0x40) ? 0x40 : 0x00) |
|
||||
((source_address&0x01) ? 0x20 : 0x00) |
|
||||
((source_address&0x80) ? 0x10 : 0x00) |
|
||||
((source_address&0x08) ? 0x08 : 0x00) |
|
||||
((source_address&0x04) ? 0x04 : 0x00) |
|
||||
((source_address&0x02) ? 0x02 : 0x00);
|
||||
uint8_t source_value = state_machine[source_address];
|
||||
((source_address&0x02) ? 0x02 : 0x00) |
|
||||
((source_address&0x10) ? 0x01 : 0x00);
|
||||
|
||||
source_value =
|
||||
// Store.
|
||||
const uint8_t source_value = state_machine[source_address];
|
||||
state_machine_[destination_address] =
|
||||
((source_value & 0x80) ? 0x10 : 0x0) |
|
||||
((source_value & 0x40) ? 0x20 : 0x0) |
|
||||
((source_value & 0x20) ? 0x40 : 0x0) |
|
||||
((source_value & 0x10) ? 0x80 : 0x0) |
|
||||
(source_value & 0x0f);
|
||||
|
||||
// Store.
|
||||
state_machine_[destination_address] = source_value;
|
||||
}
|
||||
} else {
|
||||
memcpy(&state_machine_[0], &state_machine[0], 128);
|
||||
for(size_t source_address = 0; source_address < 256; ++source_address) {
|
||||
// Reshuffle ordering of bytes only, to retain indexing by the high nibble.
|
||||
const size_t destination_address =
|
||||
((source_address&0x80) ? 0x80 : 0x00) |
|
||||
((source_address&0x40) ? 0x40 : 0x00) |
|
||||
((source_address&0x01) ? 0x20 : 0x00) |
|
||||
((source_address&0x20) ? 0x10 : 0x00) |
|
||||
((source_address&0x08) ? 0x08 : 0x00) |
|
||||
((source_address&0x04) ? 0x04 : 0x00) |
|
||||
((source_address&0x02) ? 0x02 : 0x00) |
|
||||
((source_address&0x10) ? 0x01 : 0x00);
|
||||
|
||||
state_machine_[destination_address] = state_machine[source_address];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
347
InstructionSets/PowerPC/Decoder.cpp
Normal file
347
InstructionSets/PowerPC/Decoder.cpp
Normal file
@@ -0,0 +1,347 @@
|
||||
//
|
||||
// Decoder.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/30/20.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Decoder.hpp"
|
||||
|
||||
using namespace CPU::Decoder::PowerPC;
|
||||
|
||||
Decoder::Decoder(Model model) : model_(model) {}
|
||||
|
||||
Instruction Decoder::decode(uint32_t opcode) {
|
||||
// Quick bluffer's guide to PowerPC instruction encoding:
|
||||
//
|
||||
// There is a six-bit field at the very top of the instruction.
|
||||
// Sometimes that fully identifies an instruction, but usually
|
||||
// it doesn't.
|
||||
//
|
||||
// There is an addition 9- or 10-bit field starting one bit above
|
||||
// least significant that disambiguates the rest. Strictly speaking
|
||||
// it's a 10-bit field, but the mnemonics for many instructions treat
|
||||
// it as a 9-bit field with a flag at the top.
|
||||
//
|
||||
// I've decided to hew directly to the mnemonics.
|
||||
//
|
||||
// Various opcodes in the 1995 documentation define reserved bits,
|
||||
// which are given the nominal value of 0. It does not give a formal
|
||||
// definition of a reserved bit. As a result this code does not
|
||||
// currently check the value of reserved bits. That may need to change
|
||||
// if/when I add support for extended instruction sets.
|
||||
|
||||
#define Bind(mask, operation) case mask: return Instruction(Operation::operation, opcode);
|
||||
#define BindSupervisor(mask, operation) case mask: return Instruction(Operation::operation, opcode, true);
|
||||
#define BindConditional(condition, mask, operation) \
|
||||
case mask: \
|
||||
if(condition()) return Instruction(Operation::operation, opcode); \
|
||||
return Instruction(opcode);
|
||||
#define BindSupervisorConditional(condition, mask, operation) \
|
||||
case mask: \
|
||||
if(condition()) return Instruction(Operation::operation, opcode, true); \
|
||||
return Instruction(opcode);
|
||||
|
||||
#define Six(x) (unsigned(x) << 26)
|
||||
#define SixTen(x, y) (Six(x) | ((y) << 1))
|
||||
|
||||
// First pass: weed out all those instructions identified entirely by the
|
||||
// top six bits.
|
||||
switch(opcode & Six(0b111111)) {
|
||||
default: break;
|
||||
|
||||
BindConditional(is64bit, Six(0b000010), tdi);
|
||||
|
||||
Bind(Six(0b000011), twi);
|
||||
Bind(Six(0b000111), mulli);
|
||||
Bind(Six(0b001000), subfic);
|
||||
Bind(Six(0b001100), addic); Bind(Six(0b001101), addic_);
|
||||
Bind(Six(0b001110), addi); Bind(Six(0b001111), addis);
|
||||
case Six(0b010000): {
|
||||
// This might be a bcx, but check for a valid bo field.
|
||||
switch((opcode >> 21) & 0x1f) {
|
||||
case 0: case 1: case 2: case 3: case 4: case 5:
|
||||
case 8: case 9: case 10: case 11: case 12: case 13:
|
||||
case 16: case 17: case 18: case 19: case 20:
|
||||
return Instruction(Operation::bcx, opcode);
|
||||
|
||||
default: return Instruction(opcode);
|
||||
}
|
||||
} break;
|
||||
Bind(Six(0b010010), bx);
|
||||
Bind(Six(0b010100), rlwimix);
|
||||
Bind(Six(0b010101), rlwinmx);
|
||||
Bind(Six(0b010111), rlwnmx);
|
||||
|
||||
Bind(Six(0b011000), ori); Bind(Six(0b011001), oris);
|
||||
Bind(Six(0b011010), xori); Bind(Six(0b011011), xoris);
|
||||
Bind(Six(0b011100), andi_); Bind(Six(0b011101), andis_);
|
||||
Bind(Six(0b100000), lwz); Bind(Six(0b100001), lwzu);
|
||||
Bind(Six(0b100010), lbz); Bind(Six(0b100011), lbzu);
|
||||
Bind(Six(0b100100), stw); Bind(Six(0b100101), stwu);
|
||||
Bind(Six(0b100110), stb); Bind(Six(0b100111), stbu);
|
||||
Bind(Six(0b101000), lhz); Bind(Six(0b101001), lhzu);
|
||||
Bind(Six(0b101010), lha); Bind(Six(0b101011), lhau);
|
||||
Bind(Six(0b101100), sth); Bind(Six(0b101101), sthu);
|
||||
Bind(Six(0b101110), lmw); Bind(Six(0b101111), stmw);
|
||||
Bind(Six(0b110000), lfs); Bind(Six(0b110001), lfsu);
|
||||
Bind(Six(0b110010), lfd); Bind(Six(0b110011), lfdu);
|
||||
Bind(Six(0b110100), stfs); Bind(Six(0b110101), stfsu);
|
||||
Bind(Six(0b110110), stfd); Bind(Six(0b110111), stfdu);
|
||||
|
||||
BindConditional(is601, Six(9), dozi);
|
||||
BindConditional(is601, Six(22), rlmix);
|
||||
|
||||
Bind(Six(0b001010), cmpli); Bind(Six(0b001011), cmpi);
|
||||
}
|
||||
|
||||
// Second pass: all those with a top six bits and a bottom nine or ten.
|
||||
switch(opcode & SixTen(0b111111, 0b1111111111)) {
|
||||
default: break;
|
||||
|
||||
// 64-bit instructions.
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000001001), mulhdux); BindConditional(is64bit, SixTen(0b011111, 0b1000001001), mulhdux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000010101), ldx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000011011), sldx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000110101), ldux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0000111010), cntlzdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0001000100), td);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0001001001), mulhdx); BindConditional(is64bit, SixTen(0b011111, 0b1001001001), mulhdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0001010100), ldarx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0010010101), stdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0010110101), stdux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0011101001), mulld); BindConditional(is64bit, SixTen(0b011111, 0b1011101001), mulld);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0101010101), lwax);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0101110101), lwaux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1100111011), sradix); BindConditional(is64bit, SixTen(0b011111, 0b1100111010), sradix);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0110110010), slbie);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0111001001), divdux); BindConditional(is64bit, SixTen(0b011111, 0b1111001001), divdux);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b0111101001), divdx); BindConditional(is64bit, SixTen(0b011111, 0b1111101001), divdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1000011011), srdx);
|
||||
BindConditional(is64bit, SixTen(0b011111, 0b1100011010), sradx);
|
||||
BindConditional(is64bit, SixTen(0b111111, 0b1111011010), extsw);
|
||||
|
||||
// Power instructions; these are all taken from the MPC601 manual rather than
|
||||
// the PowerPC Programmer's Reference Guide, hence the decimal encoding of the
|
||||
// ten-bit field.
|
||||
BindConditional(is601, SixTen(0b011111, 360), absx); BindConditional(is601, SixTen(0b011111, 512 + 360), absx);
|
||||
BindConditional(is601, SixTen(0b011111, 531), clcs);
|
||||
BindConditional(is601, SixTen(0b011111, 331), divx); BindConditional(is601, SixTen(0b011111, 512 + 331), divx);
|
||||
BindConditional(is601, SixTen(0b011111, 363), divsx); BindConditional(is601, SixTen(0b011111, 512 + 363), divsx);
|
||||
BindConditional(is601, SixTen(0b011111, 264), dozx); BindConditional(is601, SixTen(0b011111, 512 + 264), dozx);
|
||||
BindConditional(is601, SixTen(0b011111, 277), lscbxx);
|
||||
BindConditional(is601, SixTen(0b011111, 29), maskgx);
|
||||
BindConditional(is601, SixTen(0b011111, 541), maskirx);
|
||||
BindConditional(is601, SixTen(0b011111, 107), mulx); BindConditional(is601, SixTen(0b011111, 512 + 107), mulx);
|
||||
BindConditional(is601, SixTen(0b011111, 488), nabsx); BindConditional(is601, SixTen(0b011111, 512 + 488), nabsx);
|
||||
BindConditional(is601, SixTen(0b011111, 537), rribx);
|
||||
BindConditional(is601, SixTen(0b011111, 153), slex);
|
||||
BindConditional(is601, SixTen(0b011111, 217), sleqx);
|
||||
BindConditional(is601, SixTen(0b011111, 184), sliqx);
|
||||
BindConditional(is601, SixTen(0b011111, 248), slliqx);
|
||||
BindConditional(is601, SixTen(0b011111, 216), sllqx);
|
||||
BindConditional(is601, SixTen(0b011111, 152), slqx);
|
||||
BindConditional(is601, SixTen(0b011111, 952), sraiqx);
|
||||
BindConditional(is601, SixTen(0b011111, 920), sraqx);
|
||||
BindConditional(is601, SixTen(0b011111, 665), srex);
|
||||
BindConditional(is601, SixTen(0b011111, 921), sreax);
|
||||
BindConditional(is601, SixTen(0b011111, 729), sreqx);
|
||||
BindConditional(is601, SixTen(0b011111, 696), sriqx);
|
||||
BindConditional(is601, SixTen(0b011111, 760), srliqx);
|
||||
BindConditional(is601, SixTen(0b011111, 728), srlqx);
|
||||
BindConditional(is601, SixTen(0b011111, 664), srqx);
|
||||
|
||||
// 32-bit instructions.
|
||||
Bind(SixTen(0b010011, 0b0000000000), mcrf);
|
||||
Bind(SixTen(0b010011, 0b0000010000), bclrx);
|
||||
Bind(SixTen(0b010011, 0b0000100001), crnor);
|
||||
Bind(SixTen(0b010011, 0b0000110010), rfi);
|
||||
Bind(SixTen(0b010011, 0b0010000001), crandc);
|
||||
Bind(SixTen(0b010011, 0b0010010110), isync);
|
||||
Bind(SixTen(0b010011, 0b0011000001), crxor);
|
||||
Bind(SixTen(0b010011, 0b0011100001), crnand);
|
||||
Bind(SixTen(0b010011, 0b0100000001), crand);
|
||||
Bind(SixTen(0b010011, 0b0100100001), creqv);
|
||||
Bind(SixTen(0b010011, 0b0110100001), crorc);
|
||||
Bind(SixTen(0b010011, 0b0111000001), cror);
|
||||
Bind(SixTen(0b010011, 0b1000010000), bcctrx);
|
||||
Bind(SixTen(0b011111, 0b0000000000), cmp);
|
||||
Bind(SixTen(0b011111, 0b0000000100), tw);
|
||||
Bind(SixTen(0b011111, 0b0000001000), subfcx); Bind(SixTen(0b011111, 0b1000001000), subfcx);
|
||||
Bind(SixTen(0b011111, 0b0000001010), addcx); Bind(SixTen(0b011111, 0b1000001010), addcx);
|
||||
Bind(SixTen(0b011111, 0b0000001011), mulhwux); Bind(SixTen(0b011111, 0b1000001011), mulhwux);
|
||||
Bind(SixTen(0b011111, 0b0000010011), mfcr);
|
||||
Bind(SixTen(0b011111, 0b0000010100), lwarx);
|
||||
Bind(SixTen(0b011111, 0b0000010111), lwzx);
|
||||
Bind(SixTen(0b011111, 0b0000011000), slwx);
|
||||
Bind(SixTen(0b011111, 0b0000011010), cntlzwx);
|
||||
Bind(SixTen(0b011111, 0b0000011100), andx);
|
||||
Bind(SixTen(0b011111, 0b0000100000), cmpl);
|
||||
Bind(SixTen(0b011111, 0b0000101000), subfx); Bind(SixTen(0b011111, 0b1000101000), subfx);
|
||||
Bind(SixTen(0b011111, 0b0000110110), dcbst);
|
||||
Bind(SixTen(0b011111, 0b0000110111), lwzux);
|
||||
Bind(SixTen(0b011111, 0b0000111100), andcx);
|
||||
Bind(SixTen(0b011111, 0b0001001011), mulhwx); Bind(SixTen(0b011111, 0b1001001011), mulhwx);
|
||||
Bind(SixTen(0b011111, 0b0001010011), mfmsr);
|
||||
Bind(SixTen(0b011111, 0b0001010110), dcbf);
|
||||
Bind(SixTen(0b011111, 0b0001010111), lbzx);
|
||||
Bind(SixTen(0b011111, 0b0001101000), negx); Bind(SixTen(0b011111, 0b1001101000), negx);
|
||||
Bind(SixTen(0b011111, 0b0001110111), lbzux);
|
||||
Bind(SixTen(0b011111, 0b0001111100), norx);
|
||||
Bind(SixTen(0b011111, 0b0010001000), subfex); Bind(SixTen(0b011111, 0b1010001000), subfex);
|
||||
Bind(SixTen(0b011111, 0b0010001010), addex); Bind(SixTen(0b011111, 0b1010001010), addex);
|
||||
Bind(SixTen(0b011111, 0b0010010000), mtcrf);
|
||||
Bind(SixTen(0b011111, 0b0010010010), mtmsr);
|
||||
Bind(SixTen(0b011111, 0b0010010111), stwx);
|
||||
Bind(SixTen(0b011111, 0b0010110111), stwux);
|
||||
Bind(SixTen(0b011111, 0b0011001000), subfzex); Bind(SixTen(0b011111, 0b1011001000), subfzex);
|
||||
Bind(SixTen(0b011111, 0b0011001010), addzex); Bind(SixTen(0b011111, 0b1011001010), addzex);
|
||||
Bind(SixTen(0b011111, 0b0011010111), stbx);
|
||||
Bind(SixTen(0b011111, 0b0011101000), subfmex); Bind(SixTen(0b011111, 0b1011101000), subfmex);
|
||||
Bind(SixTen(0b011111, 0b0011101010), addmex); Bind(SixTen(0b011111, 0b1011101010), addmex);
|
||||
Bind(SixTen(0b011111, 0b0011101011), mullwx); Bind(SixTen(0b011111, 0b1011101011), mullwx);
|
||||
Bind(SixTen(0b011111, 0b0011110110), dcbtst);
|
||||
Bind(SixTen(0b011111, 0b0011110111), stbux);
|
||||
Bind(SixTen(0b011111, 0b0100001010), addx); Bind(SixTen(0b011111, 0b1100001010), addx);
|
||||
Bind(SixTen(0b011111, 0b0100010110), dcbt);
|
||||
Bind(SixTen(0b011111, 0b0100010111), lhzx);
|
||||
Bind(SixTen(0b011111, 0b0100011100), eqvx);
|
||||
Bind(SixTen(0b011111, 0b0100110110), eciwx);
|
||||
Bind(SixTen(0b011111, 0b0100110111), lhzux);
|
||||
Bind(SixTen(0b011111, 0b0100111100), xorx);
|
||||
Bind(SixTen(0b011111, 0b0101010111), lhax);
|
||||
Bind(SixTen(0b011111, 0b0101110011), mftb);
|
||||
Bind(SixTen(0b011111, 0b0101110111), lhaux);
|
||||
Bind(SixTen(0b011111, 0b0110010111), sthx);
|
||||
Bind(SixTen(0b011111, 0b0110011100), orcx);
|
||||
Bind(SixTen(0b011111, 0b0110110110), ecowx);
|
||||
Bind(SixTen(0b011111, 0b0110110111), sthux);
|
||||
Bind(SixTen(0b011111, 0b0110111100), orx);
|
||||
Bind(SixTen(0b011111, 0b0111001011), divwux); Bind(SixTen(0b011111, 0b1111001011), divwux);
|
||||
Bind(SixTen(0b011111, 0b0111010110), dcbi);
|
||||
Bind(SixTen(0b011111, 0b0111011100), nandx);
|
||||
Bind(SixTen(0b011111, 0b0111101011), divwx); Bind(SixTen(0b011111, 0b1111101011), divwx);
|
||||
Bind(SixTen(0b011111, 0b1000000000), mcrxr);
|
||||
Bind(SixTen(0b011111, 0b1000010101), lswx);
|
||||
Bind(SixTen(0b011111, 0b1000010110), lwbrx);
|
||||
Bind(SixTen(0b011111, 0b1000010111), lfsx);
|
||||
Bind(SixTen(0b011111, 0b1000011000), srwx);
|
||||
Bind(SixTen(0b011111, 0b1000110111), lfsux);
|
||||
Bind(SixTen(0b011111, 0b1001010101), lswi);
|
||||
Bind(SixTen(0b011111, 0b1001010110), sync);
|
||||
Bind(SixTen(0b011111, 0b1001010111), lfdx);
|
||||
Bind(SixTen(0b011111, 0b1001110111), lfdux);
|
||||
Bind(SixTen(0b011111, 0b1010010101), stswx);
|
||||
Bind(SixTen(0b011111, 0b1010010110), stwbrx);
|
||||
Bind(SixTen(0b011111, 0b1010010111), stfsx);
|
||||
Bind(SixTen(0b011111, 0b1010110111), stfsux);
|
||||
Bind(SixTen(0b011111, 0b1011010101), stswi);
|
||||
Bind(SixTen(0b011111, 0b1011010111), stfdx);
|
||||
Bind(SixTen(0b011111, 0b1011110111), stfdux);
|
||||
Bind(SixTen(0b011111, 0b1100010110), lhbrx);
|
||||
Bind(SixTen(0b011111, 0b1100011000), srawx);
|
||||
Bind(SixTen(0b011111, 0b1100111000), srawix);
|
||||
Bind(SixTen(0b011111, 0b1101010110), eieio);
|
||||
Bind(SixTen(0b011111, 0b1110010110), sthbrx);
|
||||
Bind(SixTen(0b011111, 0b1110011010), extshx);
|
||||
Bind(SixTen(0b011111, 0b1110111010), extsbx);
|
||||
Bind(SixTen(0b011111, 0b1111010110), icbi);
|
||||
Bind(SixTen(0b011111, 0b1111010111), stfiwx);
|
||||
Bind(SixTen(0b011111, 0b1111110110), dcbz);
|
||||
Bind(SixTen(0b111111, 0b0000000000), fcmpu);
|
||||
Bind(SixTen(0b111111, 0b0000001100), frspx);
|
||||
Bind(SixTen(0b111111, 0b0000001110), fctiwx);
|
||||
Bind(SixTen(0b111111, 0b0000001111), fctiwzx);
|
||||
Bind(SixTen(0b111111, 0b0000100000), fcmpo);
|
||||
Bind(SixTen(0b111111, 0b0000100110), mtfsb1x);
|
||||
Bind(SixTen(0b111111, 0b0000101000), fnegx);
|
||||
Bind(SixTen(0b111111, 0b0001000000), mcrfs);
|
||||
Bind(SixTen(0b111111, 0b0001000110), mtfsb0x);
|
||||
Bind(SixTen(0b111111, 0b0001001000), fmrx);
|
||||
Bind(SixTen(0b111111, 0b0010000110), mtfsfix);
|
||||
Bind(SixTen(0b111111, 0b0010001000), fnabsx);
|
||||
Bind(SixTen(0b111111, 0b0100001000), fabsx);
|
||||
Bind(SixTen(0b111111, 0b1001000111), mffsx);
|
||||
Bind(SixTen(0b111111, 0b1011000111), mtfsfx);
|
||||
Bind(SixTen(0b111111, 0b1100101110), fctidx);
|
||||
Bind(SixTen(0b111111, 0b1100101111), fctidzx);
|
||||
Bind(SixTen(0b111111, 0b1101001110), fcfidx);
|
||||
|
||||
Bind(SixTen(0b011111, 0b0101010011), mfspr); // Flagged as "supervisor and user"?
|
||||
Bind(SixTen(0b011111, 0b0111010011), mtspr); // Flagged as "supervisor and user"?
|
||||
|
||||
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b0011010010), mtsr);
|
||||
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b0011110010), mtsrin);
|
||||
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b1001010011), mfsr);
|
||||
BindSupervisorConditional(is32bit, SixTen(0b011111, 0b1010010011), mfsrin);
|
||||
|
||||
BindSupervisorConditional(is64bit, SixTen(0b011111, 0b0111110010), slbia); // optional
|
||||
|
||||
// The following are all optional; should I record that?
|
||||
BindSupervisor(SixTen(0b011111, 0b0100110010), tlbie);
|
||||
BindSupervisor(SixTen(0b011111, 0b0101110010), tlbia);
|
||||
BindSupervisor(SixTen(0b011111, 0b1000110110), tlbsync);
|
||||
}
|
||||
|
||||
// Third pass: like six-ten except that the top five of the final ten
|
||||
// are reserved (i.e. ignored here).
|
||||
switch(opcode & SixTen(0b111111, 0b11111)) {
|
||||
default: break;
|
||||
|
||||
Bind(SixTen(0b111011, 0b10010), fdivsx);
|
||||
Bind(SixTen(0b111011, 0b10100), fsubsx);
|
||||
Bind(SixTen(0b111011, 0b10101), faddsx);
|
||||
Bind(SixTen(0b111011, 0b11001), fmulsx);
|
||||
Bind(SixTen(0b111011, 0b11100), fmsubsx);
|
||||
Bind(SixTen(0b111011, 0b11101), fmaddsx);
|
||||
Bind(SixTen(0b111011, 0b11110), fnmsubsx);
|
||||
Bind(SixTen(0b111011, 0b11111), fnmaddsx);
|
||||
|
||||
Bind(SixTen(0b111111, 0b10010), fdivx);
|
||||
Bind(SixTen(0b111111, 0b10100), fsubx);
|
||||
Bind(SixTen(0b111111, 0b10101), faddx);
|
||||
Bind(SixTen(0b111111, 0b11001), fmulx);
|
||||
Bind(SixTen(0b111111, 0b11100), fmsubx);
|
||||
Bind(SixTen(0b111111, 0b11101), fmaddx);
|
||||
Bind(SixTen(0b111111, 0b11110), fnmsubx);
|
||||
Bind(SixTen(0b111111, 0b11111), fnmaddx);
|
||||
|
||||
BindConditional(is64bit, SixTen(0b111011, 0b10110), fsqrtsx);
|
||||
BindConditional(is64bit, SixTen(0b111011, 0b11000), fresx);
|
||||
|
||||
// Optional...
|
||||
Bind(SixTen(0b111111, 0b10110), fsqrtx);
|
||||
Bind(SixTen(0b111111, 0b10111), fselx);
|
||||
Bind(SixTen(0b111111, 0b11010), frsqrtex);
|
||||
}
|
||||
|
||||
// stwcx. and stdcx.
|
||||
switch(opcode & 0b111111'00'00000000'000'111111111'1){
|
||||
case 0b011111'00'00000000'00000'0010010110'1: return Instruction(Operation::stwcx_, opcode);
|
||||
case 0b011111'00'00000000'00000'0011010110'1:
|
||||
if(is64bit()) return Instruction(Operation::stdcx_, opcode);
|
||||
return Instruction(opcode);
|
||||
}
|
||||
|
||||
// std and stdu
|
||||
switch(opcode & 0b111111'00'00000000'00000000'000000'11){
|
||||
case 0b111110'00'00000000'00000000'000000'00: return Instruction(Operation::std, opcode);
|
||||
case 0b111110'00'00000000'00000000'000000'01:
|
||||
if(is64bit()) return Instruction(Operation::stdu, opcode);
|
||||
return Instruction(opcode);
|
||||
}
|
||||
|
||||
// sc
|
||||
if((opcode & 0b111111'00'00000000'00000000'000000'1'0) == 0b010001'00'00000000'00000000'000000'1'0) {
|
||||
return Instruction(Operation::sc, opcode);
|
||||
}
|
||||
|
||||
#undef Six
|
||||
#undef SixTen
|
||||
|
||||
#undef Bind
|
||||
#undef BindConditional
|
||||
|
||||
return Instruction(opcode);
|
||||
}
|
||||
58
InstructionSets/PowerPC/Decoder.hpp
Normal file
58
InstructionSets/PowerPC/Decoder.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
//
|
||||
// Decoder.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 12/30/20.
|
||||
// Copyright © 2020 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_PowerPC_Decoder_hpp
|
||||
#define InstructionSets_PowerPC_Decoder_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
namespace CPU {
|
||||
namespace Decoder {
|
||||
namespace PowerPC {
|
||||
|
||||
enum class Model {
|
||||
/// i.e. 32-bit, with POWER carry-over instructions.
|
||||
MPC601,
|
||||
/// i.e. 32-bit, no POWER instructions.
|
||||
MPC603,
|
||||
/// i.e. 64-bit.
|
||||
MPC620,
|
||||
};
|
||||
|
||||
/*!
|
||||
Implements PowerPC instruction decoding.
|
||||
|
||||
This is an experimental implementation; it has not yet undergone significant testing.
|
||||
*/
|
||||
struct Decoder {
|
||||
public:
|
||||
Decoder(Model model);
|
||||
|
||||
Instruction decode(uint32_t opcode);
|
||||
|
||||
private:
|
||||
Model model_;
|
||||
|
||||
bool is64bit() const {
|
||||
return model_ == Model::MPC620;
|
||||
}
|
||||
|
||||
bool is32bit() const {
|
||||
return !is64bit();
|
||||
}
|
||||
|
||||
bool is601() const {
|
||||
return model_ == Model::MPC601;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_PowerPC_Decoder_hpp */
|
||||
192
InstructionSets/PowerPC/Instruction.hpp
Normal file
192
InstructionSets/PowerPC/Instruction.hpp
Normal file
@@ -0,0 +1,192 @@
|
||||
//
|
||||
// Instruction.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 1/15/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_PowerPC_Instruction_h
|
||||
#define InstructionSets_PowerPC_Instruction_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace CPU {
|
||||
namespace Decoder {
|
||||
namespace PowerPC {
|
||||
|
||||
enum class Operation: uint8_t {
|
||||
Undefined,
|
||||
|
||||
// These 601-exclusive instructions; a lot of them are carry-overs
|
||||
// from POWER.
|
||||
absx, clcs, divx, divsx, dozx, dozi, lscbxx, maskgx, maskirx, mulx,
|
||||
nabsx, rlmix, rribx, slex, sleqx, sliqx, slliqx, sllqx, slqx,
|
||||
sraiqx, sraqx, srex, sreax, sreqx, sriqx, srliqx, srlqx, srqx,
|
||||
|
||||
// 32- and 64-bit PowerPC instructions.
|
||||
addx, addcx, addex, addi, addic, addic_, addis, addmex, addzex, andx,
|
||||
andcx, andi_, andis_, bx, bcx, bcctrx, bclrx, cmp, cmpi, cmpl, cmpli,
|
||||
cntlzwx, crand, crandc, creqv, crnand, crnor, cror, crorc, crxor, dcbf,
|
||||
dcbst, dcbt, dcbtst, dcbz, divwx, divwux, eciwx, ecowx, eieio, eqvx,
|
||||
extsbx, extshx, fabsx, faddx, faddsx, fcmpo, fcmpu, fctiwx, fctiwzx,
|
||||
fdivx, fdivsx, fmaddx, fmaddsx, fmrx, fmsubx, fmsubsx, fmulx, fmulsx,
|
||||
fnabsx, fnegx, fnmaddx, fnmaddsx, fnmsubx, fnmsubsx, frspx, fsubx, fsubsx,
|
||||
icbi, isync, lbz, lbzu, lbzux, lbzx, lfd, lfdu, lfdux, lfdx, lfs, lfsu,
|
||||
lfsux, lfsx, lha, lhau, lhaux, lhax, lhbrx, lhz, lhzu, lhzux, lhzx, lmw,
|
||||
lswi, lswx, lwarx, lwbrx, lwz, lwzu, lwzux, lwzx, mcrf, mcrfs, mcrxr,
|
||||
mfcr, mffsx, mfmsr, mfspr, mfsr, mfsrin, mtcrf, mtfsb0x, mtfsb1x, mtfsfx,
|
||||
mtfsfix, mtmsr, mtspr, mtsr, mtsrin, mulhwx, mulhwux, mulli, mullwx,
|
||||
nandx, negx, norx, orx, orcx, ori, oris, rfi, rlwimix, rlwinmx, rlwnmx,
|
||||
sc, slwx, srawx, srawix, srwx, stb, stbu, stbux, stbx, stfd, stfdu,
|
||||
stfdux, stfdx, stfs, stfsu, stfsux, stfsx, sth, sthbrx, sthu, sthux, sthx,
|
||||
stmw, stswi, stswx, stw, stwbrx, stwcx_, stwu, stwux, stwx, subfx, subfcx,
|
||||
subfex, subfic, subfmex, subfzex, sync, tw, twi, xorx, xori, xoris, mftb,
|
||||
|
||||
// 32-bit, supervisor level.
|
||||
dcbi,
|
||||
|
||||
// Supervisor, optional.
|
||||
tlbia, tlbie, tlbsync,
|
||||
|
||||
// Optional.
|
||||
fresx, frsqrtex, fselx, fsqrtx, slbia, slbie, stfiwx,
|
||||
|
||||
// 64-bit only PowerPC instructions.
|
||||
cntlzdx, divdx, divdux, extswx, fcfidx, fctidx, fctidzx, tdi, mulhdux,
|
||||
ldx, sldx, ldux, td, mulhdx, ldarx, stdx, stdux, mulld, lwax, lwaux,
|
||||
sradix, srdx, sradx, extsw, fsqrtsx, std, stdu, stdcx_,
|
||||
};
|
||||
|
||||
/*!
|
||||
Holds a decoded PowerPC instruction.
|
||||
|
||||
Implementation note: because the PowerPC encoding is particularly straightforward,
|
||||
only the operation has been decoded ahead of time; all other fields are decoded on-demand.
|
||||
|
||||
It would be possible to partition the ordering of Operations into user followed by supervisor,
|
||||
eliminating the storage necessary for a flag, but it wouldn't save anything due to alignment.
|
||||
*/
|
||||
struct Instruction {
|
||||
Operation operation = Operation::Undefined;
|
||||
bool is_supervisor = false;
|
||||
uint32_t opcode = 0;
|
||||
|
||||
Instruction() noexcept {}
|
||||
Instruction(uint32_t opcode) noexcept : opcode(opcode) {}
|
||||
Instruction(Operation operation, uint32_t opcode, bool is_supervisor = false) noexcept : operation(operation), is_supervisor(is_supervisor), opcode(opcode) {}
|
||||
|
||||
// Instruction fields are decoded below; naming is a compromise between
|
||||
// Motorola's documentation and IBM's.
|
||||
//
|
||||
// I've dutifully implemented various synonyms with unique entry points,
|
||||
// in order to capture that information here rather than thrusting it upon
|
||||
// the reader of whatever implementation may follow.
|
||||
|
||||
// Currently omitted: OPCD and XO, which I think are unnecessary given that
|
||||
// full decoding has already occurred.
|
||||
|
||||
/// Immediate field used to specify an unsigned 16-bit integer.
|
||||
uint16_t uimm() const { return uint16_t(opcode & 0xffff); }
|
||||
/// Immediate field used to specify a signed 16-bit integer.
|
||||
int16_t simm() const { return int16_t(opcode & 0xffff); }
|
||||
/// Immediate field used to specify a signed 16-bit integer.
|
||||
int16_t d() const { return int16_t(opcode & 0xffff); }
|
||||
/// Immediate field used to specify a signed 14-bit integer [64-bit only].
|
||||
int16_t ds() const { return int16_t(opcode & 0xfffc); }
|
||||
/// Immediate field used as data to be placed into a field in the floating point status and condition register.
|
||||
int32_t imm() const { return (opcode >> 12) & 0xf; }
|
||||
|
||||
/// Specifies the conditions on which to trap.
|
||||
int32_t to() const { return (opcode >> 21) & 0x1f; }
|
||||
|
||||
/// Register source A or destination.
|
||||
uint32_t rA() const { return (opcode >> 16) & 0x1f; }
|
||||
/// Register source B.
|
||||
uint32_t rB() const { return (opcode >> 11) & 0x1f; }
|
||||
/// Register destination.
|
||||
uint32_t rD() const { return (opcode >> 21) & 0x1f; }
|
||||
/// Register source.
|
||||
uint32_t rS() const { return (opcode >> 21) & 0x1f; }
|
||||
|
||||
/// Floating point register source A.
|
||||
uint32_t frA() const { return (opcode >> 16) & 0x1f; }
|
||||
/// Floating point register source B.
|
||||
uint32_t frB() const { return (opcode >> 11) & 0x1f; }
|
||||
/// Floating point register source C.
|
||||
uint32_t frC() const { return (opcode >> 6) & 0x1f; }
|
||||
/// Floating point register source.
|
||||
uint32_t frS() const { return (opcode >> 21) & 0x1f; }
|
||||
/// Floating point register destination.
|
||||
uint32_t frD() const { return (opcode >> 21) & 0x1f; }
|
||||
|
||||
/// Branch conditional options.
|
||||
uint32_t bo() const { return (opcode >> 21) & 0x1f; }
|
||||
/// Source condition register bit for branch conditionals.
|
||||
uint32_t bi() const { return (opcode >> 16) & 0x1f; }
|
||||
/// Branch displacement; provided as already sign extended.
|
||||
int16_t bd() const { return int16_t(opcode & 0xfffc); }
|
||||
|
||||
/// Specifies the first 1 bit of a 32/64-bit mask for rotate operations.
|
||||
uint32_t mb() const { return (opcode >> 6) & 0x1f; }
|
||||
/// Specifies the first 1 bit of a 32/64-bit mask for rotate operations.
|
||||
uint32_t me() const { return (opcode >> 1) & 0x1f; }
|
||||
|
||||
/// Condition register source bit A.
|
||||
uint32_t crbA() const { return (opcode >> 16) & 0x1f; }
|
||||
/// Condition register source bit B.
|
||||
uint32_t crbB() const { return (opcode >> 11) & 0x1f; }
|
||||
/// Condition register (or floating point status & condition register) destination bit.
|
||||
uint32_t crbD() const { return (opcode >> 21) & 0x1f; }
|
||||
|
||||
/// Condition register (or floating point status & condition register) destination field.
|
||||
uint32_t crfD() const { return (opcode >> 23) & 0x07; }
|
||||
/// Condition register (or floating point status & condition register) source field.
|
||||
uint32_t crfS() const { return (opcode >> 18) & 0x07; }
|
||||
|
||||
/// Mask identifying fields to be updated by mtcrf.
|
||||
uint32_t crm() const { return (opcode >> 12) & 0xff; }
|
||||
|
||||
/// Mask identifying fields to be updated by mtfsf.
|
||||
uint32_t fm() const { return (opcode >> 17) & 0xff; }
|
||||
|
||||
/// Specifies the number of bytes to move in an immediate string load or store.
|
||||
uint32_t nb() const { return (opcode >> 11) & 0x1f; }
|
||||
|
||||
/// Specifies a shift amount.
|
||||
/// TODO: possibly bit 30 is also used in 64-bit mode, find out.
|
||||
uint32_t sh() const { return (opcode >> 11) & 0x1f; }
|
||||
|
||||
/// Specifies one of the 16 segment registers [32-bit only].
|
||||
uint32_t sr() const { return (opcode >> 16) & 0xf; }
|
||||
|
||||
/// A 24-bit signed number; provided as already sign extended.
|
||||
int32_t li() const {
|
||||
constexpr uint32_t extensions[2] = {
|
||||
0x0000'0000,
|
||||
0xfc00'0000
|
||||
};
|
||||
const uint32_t value = (opcode & 0x03ff'fffc) | extensions[(opcode >> 25) & 1];
|
||||
return int32_t(value);
|
||||
}
|
||||
|
||||
/// Absolute address bit; @c 0 or @c non-0.
|
||||
uint32_t aa() const { return opcode & 0x02; }
|
||||
/// Link bit; @c 0 or @c non-0.
|
||||
uint32_t lk() const { return opcode & 0x01; }
|
||||
/// Record bit; @c 0 or @c non-0.
|
||||
uint32_t rc() const { return opcode & 0x01; }
|
||||
/// Whether to compare 32-bit or 64-bit numbers [for 64-bit implementations only]; @c 0 or @c non-0.
|
||||
uint32_t l() const { return opcode & 0x200000; }
|
||||
/// Enables setting of OV and SO in the XER; @c 0 or @c non-0.
|
||||
uint32_t oe() const { return opcode & 0x800; }
|
||||
};
|
||||
|
||||
// Sanity check on Instruction size.
|
||||
static_assert(sizeof(Instruction) <= 8);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_PowerPC_Instruction_h */
|
||||
54
InstructionSets/README.md
Normal file
54
InstructionSets/README.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Instruction Sets
|
||||
|
||||
## Decoders
|
||||
|
||||
A decoder extracts fully-decoded instructions from a data stream for its associated architecture.
|
||||
|
||||
The meaning of 'fully-decoded' is flexible but it means that a caller can easily discern at least:
|
||||
* the operation in use;
|
||||
* its addressing mode; and
|
||||
* relevant registers.
|
||||
|
||||
It may be assumed that callers will have access to the original data stream for immediate values, if it is sensible to do so.
|
||||
|
||||
In deciding what to expose, what to store ahead of time and what to obtain just-in-time a decoder should have an eye on two principal consumers:
|
||||
1. disassemblers; and
|
||||
2. instruction executors.
|
||||
|
||||
It may also be reasonable to make allowances for bus-centric CPU emulators, but those will be tightly coupled to specific decoders so no general rules need apply.
|
||||
|
||||
Disassemblers are likely to decode an instruction, output it, and then immediately forget about it.
|
||||
|
||||
Instruction executors may opt to cache decoded instructions to reduce recurrent costs, but will always be dealing with an actual instruction stream. The chance of caching means that decoded instructions should seek to be small. If helpful then a decoder might prefer to return a `std::pair` or similar of ephemeral information and stuff that it is meaningful to store.
|
||||
|
||||
## Likely Interfaces
|
||||
|
||||
These examples assume that the processor itself doesn't hold any state that affects instruction parsing. Whether processors with such state offer more than one decoder or take state as an argument will be a question of measure and effect.
|
||||
|
||||
### Fixed-size instruction words
|
||||
|
||||
If the instructions are a fixed size, the decoder can provide what is functionally a simple lookup, whether implemented as such or not:
|
||||
|
||||
Instruction decode(word_type instruction) { ... }
|
||||
|
||||
For now I have preferred not to make this a simple constructor on `Instruction` because I'm reserving the option of switching to an ephemeral/permanent split in what's returned. More consideration needs to be applied here.
|
||||
|
||||
### Variable-size instruction words
|
||||
|
||||
If instructions are a variable size, the decoder should maintain internal state such that it can be provided with fragments of instructions until a full decoding has occurred — this avoids an assumption that all source bytes will always be laid out linearly in memory.
|
||||
|
||||
A sample interface:
|
||||
|
||||
std::pair<int, Instruction> decode(word_type *stream, size_t length) { ... }
|
||||
|
||||
In this sample the returned pair provides an `int` size that is one of:
|
||||
* a positive number, indicating a completed decoding that consumed that many `word_type`s; or
|
||||
* a negative number, indicating the [negatived] minimum number of `word_type`s that the caller should try to get hold of before calling `decode` again.
|
||||
|
||||
A caller is permitted to react in any way it prefers to negative numbers; they're a hint potentially to reduce calling overhead only. A size of `0` would be taken to have the same meaning as a size of `-1`.
|
||||
|
||||
## Tying Decoders into Instruction Executors
|
||||
|
||||
It is assumed that disassemblers and bus-centric CPU emulators have limited generic functionality; for executors it is assumed that a processor-specific instruction fetcher and a dispatcher will be provided to couple with the decoder.
|
||||
|
||||
Therefore decoders should adopt whatever interface is most natural; the expected uses information above is to provide a motivation for the scope of responsibilities and hints as to likely performance objectives only. Beyond requiring that decoded instructions be a tangible struct or class, it is not intended to be prescriptive as to form or interface.
|
||||
625
InstructionSets/x86/Decoder.cpp
Normal file
625
InstructionSets/x86/Decoder.cpp
Normal file
@@ -0,0 +1,625 @@
|
||||
//
|
||||
// x86.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 1/1/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Decoder.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <utility>
|
||||
|
||||
using namespace CPU::Decoder::x86;
|
||||
|
||||
// Only 8086 is suppoted for now.
|
||||
Decoder::Decoder(Model) {}
|
||||
|
||||
std::pair<int, CPU::Decoder::x86::Instruction> Decoder::decode(const uint8_t *source, size_t length) {
|
||||
const uint8_t *const end = source + length;
|
||||
|
||||
// MARK: - Prefixes (if present) and the opcode.
|
||||
|
||||
/// Helper macro for those that follow.
|
||||
#define SetOpSrcDestSize(op, src, dest, size) \
|
||||
operation_ = Operation::op; \
|
||||
source_ = Source::src; \
|
||||
destination_ = Source::dest; \
|
||||
operation_size_ = size
|
||||
|
||||
/// Covers anything which is complete as soon as the opcode is encountered.
|
||||
#define Complete(op, src, dest, size) \
|
||||
SetOpSrcDestSize(op, src, dest, size); \
|
||||
phase_ = Phase::ReadyToPost
|
||||
|
||||
/// Handles instructions of the form rr, kk and rr, jjkk, i.e. a destination register plus an operand.
|
||||
#define RegData(op, dest, size) \
|
||||
SetOpSrcDestSize(op, DirectAddress, dest, size); \
|
||||
source_ = Source::Immediate; \
|
||||
operand_size_ = size; \
|
||||
phase_ = Phase::AwaitingDisplacementOrOperand
|
||||
|
||||
/// Handles instructions of the form Ax, jjkk where the latter is implicitly an address.
|
||||
#define RegAddr(op, dest, op_size, addr_size) \
|
||||
SetOpSrcDestSize(op, DirectAddress, dest, op_size); \
|
||||
operand_size_ = addr_size; \
|
||||
phase_ = Phase::AwaitingDisplacementOrOperand
|
||||
|
||||
/// Handles instructions of the form jjkk, Ax where the former is implicitly an address.
|
||||
#define AddrReg(op, source, op_size, addr_size) \
|
||||
SetOpSrcDestSize(op, source, DirectAddress, op_size); \
|
||||
operand_size_ = addr_size; \
|
||||
destination_ = Source::DirectAddress; \
|
||||
phase_ = Phase::AwaitingDisplacementOrOperand
|
||||
|
||||
/// Covers both `mem/reg, reg` and `reg, mem/reg`.
|
||||
#define MemRegReg(op, format, size) \
|
||||
operation_ = Operation::op; \
|
||||
phase_ = Phase::ModRegRM; \
|
||||
modregrm_format_ = ModRegRMFormat::format; \
|
||||
operand_size_ = 0; \
|
||||
operation_size_ = size
|
||||
|
||||
/// Handles JO, JNO, JB, etc — jumps with a single byte displacement.
|
||||
#define Jump(op) \
|
||||
operation_ = Operation::op; \
|
||||
phase_ = Phase::AwaitingDisplacementOrOperand; \
|
||||
displacement_size_ = 1
|
||||
|
||||
/// Handles far CALL and far JMP — fixed four byte operand operations.
|
||||
#define Far(op) \
|
||||
operation_ = Operation::op; \
|
||||
phase_ = Phase::AwaitingDisplacementOrOperand; \
|
||||
operand_size_ = 4; \
|
||||
|
||||
while(phase_ == Phase::Instruction && source != end) {
|
||||
// Retain the instruction byte, in case additional decoding is deferred
|
||||
// to the ModRegRM byte.
|
||||
instr_ = *source;
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
switch(instr_) {
|
||||
default: {
|
||||
const auto result = std::make_pair(consumed_, Instruction());
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
#define PartialBlock(start, operation) \
|
||||
case start + 0x00: MemRegReg(operation, MemReg_Reg, 1); break; \
|
||||
case start + 0x01: MemRegReg(operation, MemReg_Reg, 2); break; \
|
||||
case start + 0x02: MemRegReg(operation, Reg_MemReg, 1); break; \
|
||||
case start + 0x03: MemRegReg(operation, Reg_MemReg, 2); break; \
|
||||
case start + 0x04: RegData(operation, AL, 1); break; \
|
||||
case start + 0x05: RegData(operation, AX, 2)
|
||||
|
||||
PartialBlock(0x00, ADD); break;
|
||||
case 0x06: Complete(PUSH, ES, None, 2); break;
|
||||
case 0x07: Complete(POP, None, ES, 2); break;
|
||||
|
||||
PartialBlock(0x08, OR); break;
|
||||
case 0x0e: Complete(PUSH, CS, None, 2); break;
|
||||
|
||||
PartialBlock(0x10, ADC); break;
|
||||
case 0x16: Complete(PUSH, SS, None, 2); break;
|
||||
case 0x17: Complete(POP, None, SS, 2); break;
|
||||
|
||||
PartialBlock(0x18, SBB); break;
|
||||
case 0x1e: Complete(PUSH, DS, None, 2); break;
|
||||
case 0x1f: Complete(POP, None, DS, 2); break;
|
||||
|
||||
PartialBlock(0x20, AND); break;
|
||||
case 0x26: segment_override_ = Source::ES; break;
|
||||
case 0x27: Complete(DAA, AL, AL, 1); break;
|
||||
|
||||
PartialBlock(0x28, SUB); break;
|
||||
case 0x2e: segment_override_ = Source::CS; break;
|
||||
case 0x2f: Complete(DAS, AL, AL, 1); break;
|
||||
|
||||
PartialBlock(0x30, XOR); break;
|
||||
case 0x36: segment_override_ = Source::SS; break;
|
||||
case 0x37: Complete(AAA, AL, AX, 1); break;
|
||||
|
||||
PartialBlock(0x38, CMP); break;
|
||||
case 0x3e: segment_override_ = Source::DS; break;
|
||||
case 0x3f: Complete(AAS, AL, AX, 1); break;
|
||||
|
||||
#undef PartialBlock
|
||||
|
||||
#define RegisterBlock(start, operation) \
|
||||
case start + 0x00: Complete(operation, AX, AX, 2); break; \
|
||||
case start + 0x01: Complete(operation, CX, CX, 2); break; \
|
||||
case start + 0x02: Complete(operation, DX, DX, 2); break; \
|
||||
case start + 0x03: Complete(operation, BX, BX, 2); break; \
|
||||
case start + 0x04: Complete(operation, SP, SP, 2); break; \
|
||||
case start + 0x05: Complete(operation, BP, BP, 2); break; \
|
||||
case start + 0x06: Complete(operation, SI, SI, 2); break; \
|
||||
case start + 0x07: Complete(operation, DI, DI, 2)
|
||||
|
||||
RegisterBlock(0x40, INC); break;
|
||||
RegisterBlock(0x48, DEC); break;
|
||||
RegisterBlock(0x50, PUSH); break;
|
||||
RegisterBlock(0x58, POP); break;
|
||||
|
||||
#undef RegisterBlock
|
||||
|
||||
// 0x60–0x6f: not used.
|
||||
|
||||
case 0x70: Jump(JO); break;
|
||||
case 0x71: Jump(JNO); break;
|
||||
case 0x72: Jump(JB); break;
|
||||
case 0x73: Jump(JNB); break;
|
||||
case 0x74: Jump(JE); break;
|
||||
case 0x75: Jump(JNE); break;
|
||||
case 0x76: Jump(JBE); break;
|
||||
case 0x77: Jump(JNBE); break;
|
||||
case 0x78: Jump(JS); break;
|
||||
case 0x79: Jump(JNS); break;
|
||||
case 0x7a: Jump(JP); break;
|
||||
case 0x7b: Jump(JNP); break;
|
||||
case 0x7c: Jump(JL); break;
|
||||
case 0x7d: Jump(JNL); break;
|
||||
case 0x7e: Jump(JLE); break;
|
||||
case 0x7f: Jump(JNLE); break;
|
||||
|
||||
case 0x80: MemRegReg(Invalid, MemRegADD_to_CMP, 1); break;
|
||||
case 0x81: MemRegReg(Invalid, MemRegADD_to_CMP, 2); break;
|
||||
case 0x82: MemRegReg(Invalid, MemRegADC_to_CMP, 1); break;
|
||||
case 0x83: MemRegReg(Invalid, MemRegADC_to_CMP, 2); break;
|
||||
|
||||
case 0x84: MemRegReg(TEST, MemReg_Reg, 1); break;
|
||||
case 0x85: MemRegReg(TEST, MemReg_Reg, 2); break;
|
||||
case 0x86: MemRegReg(XCHG, Reg_MemReg, 1); break;
|
||||
case 0x87: MemRegReg(XCHG, Reg_MemReg, 2); break;
|
||||
case 0x88: MemRegReg(MOV, MemReg_Reg, 1); break;
|
||||
case 0x89: MemRegReg(MOV, MemReg_Reg, 2); break;
|
||||
case 0x8a: MemRegReg(MOV, Reg_MemReg, 1); break;
|
||||
case 0x8b: MemRegReg(MOV, Reg_MemReg, 2); break;
|
||||
// 0x8c: not used.
|
||||
case 0x8d: MemRegReg(LEA, Reg_MemReg, 2); break;
|
||||
case 0x8e: MemRegReg(MOV, SegReg, 2); break;
|
||||
case 0x8f: MemRegReg(POP, MemRegPOP, 2); break;
|
||||
|
||||
case 0x90: Complete(NOP, None, None, 0); break; // Or XCHG AX, AX?
|
||||
case 0x91: Complete(XCHG, AX, CX, 2); break;
|
||||
case 0x92: Complete(XCHG, AX, DX, 2); break;
|
||||
case 0x93: Complete(XCHG, AX, BX, 2); break;
|
||||
case 0x94: Complete(XCHG, AX, SP, 2); break;
|
||||
case 0x95: Complete(XCHG, AX, BP, 2); break;
|
||||
case 0x96: Complete(XCHG, AX, SI, 2); break;
|
||||
case 0x97: Complete(XCHG, AX, DI, 2); break;
|
||||
|
||||
case 0x98: Complete(CBW, AL, AH, 1); break;
|
||||
case 0x99: Complete(CWD, AX, DX, 2); break;
|
||||
case 0x9a: Far(CALLF); break;
|
||||
case 0x9b: Complete(WAIT, None, None, 0); break;
|
||||
case 0x9c: Complete(PUSHF, None, None, 2); break;
|
||||
case 0x9d: Complete(POPF, None, None, 2); break;
|
||||
case 0x9e: Complete(SAHF, None, None, 1); break;
|
||||
case 0x9f: Complete(LAHF, None, None, 1); break;
|
||||
|
||||
case 0xa0: RegAddr(MOV, AL, 1, 1); break;
|
||||
case 0xa1: RegAddr(MOV, AX, 2, 2); break;
|
||||
case 0xa2: AddrReg(MOV, AL, 1, 1); break;
|
||||
case 0xa3: AddrReg(MOV, AX, 2, 2); break;
|
||||
|
||||
case 0xa4: Complete(MOVS, None, None, 1); break;
|
||||
case 0xa5: Complete(MOVS, None, None, 2); break;
|
||||
case 0xa6: Complete(CMPS, None, None, 1); break;
|
||||
case 0xa7: Complete(CMPS, None, None, 2); break;
|
||||
case 0xa8: RegData(TEST, AL, 1); break;
|
||||
case 0xa9: RegData(TEST, AX, 2); break;
|
||||
case 0xaa: Complete(STOS, None, None, 1); break;
|
||||
case 0xab: Complete(STOS, None, None, 2); break;
|
||||
case 0xac: Complete(LODS, None, None, 1); break;
|
||||
case 0xad: Complete(LODS, None, None, 2); break;
|
||||
case 0xae: Complete(SCAS, None, None, 1); break;
|
||||
case 0xaf: Complete(SCAS, None, None, 2); break;
|
||||
|
||||
case 0xb0: RegData(MOV, AL, 1); break;
|
||||
case 0xb1: RegData(MOV, CL, 1); break;
|
||||
case 0xb2: RegData(MOV, DL, 1); break;
|
||||
case 0xb3: RegData(MOV, BL, 1); break;
|
||||
case 0xb4: RegData(MOV, AH, 1); break;
|
||||
case 0xb5: RegData(MOV, CH, 1); break;
|
||||
case 0xb6: RegData(MOV, DH, 1); break;
|
||||
case 0xb7: RegData(MOV, BH, 1); break;
|
||||
case 0xb8: RegData(MOV, AX, 2); break;
|
||||
case 0xb9: RegData(MOV, CX, 2); break;
|
||||
case 0xba: RegData(MOV, DX, 2); break;
|
||||
case 0xbb: RegData(MOV, BX, 2); break;
|
||||
case 0xbc: RegData(MOV, SP, 2); break;
|
||||
case 0xbd: RegData(MOV, BP, 2); break;
|
||||
case 0xbe: RegData(MOV, SI, 2); break;
|
||||
case 0xbf: RegData(MOV, DI, 2); break;
|
||||
|
||||
case 0xc2: RegData(RETN, None, 2); break;
|
||||
case 0xc3: Complete(RETN, None, None, 2); break;
|
||||
case 0xc4: MemRegReg(LES, Reg_MemReg, 2); break;
|
||||
case 0xc5: MemRegReg(LDS, Reg_MemReg, 2); break;
|
||||
case 0xc6: MemRegReg(MOV, MemRegMOV, 1); break;
|
||||
case 0xc7: MemRegReg(MOV, MemRegMOV, 2); break;
|
||||
|
||||
case 0xca: RegData(RETF, None, 2); break;
|
||||
case 0xcb: Complete(RETF, None, None, 4); break;
|
||||
|
||||
case 0xcc: Complete(INT3, None, None, 0); break;
|
||||
case 0xcd: RegData(INT, None, 1); break;
|
||||
case 0xce: Complete(INTO, None, None, 0); break;
|
||||
case 0xcf: Complete(IRET, None, None, 0); break;
|
||||
|
||||
case 0xd0: case 0xd1:
|
||||
phase_ = Phase::ModRegRM;
|
||||
modregrm_format_ = ModRegRMFormat::MemRegROL_to_SAR;
|
||||
operation_size_ = 1 + (instr_ & 1);
|
||||
source_ = Source::Immediate;
|
||||
operand_ = 1;
|
||||
break;
|
||||
case 0xd2: case 0xd3:
|
||||
phase_ = Phase::ModRegRM;
|
||||
modregrm_format_ = ModRegRMFormat::MemRegROL_to_SAR;
|
||||
operation_size_ = 1 + (instr_ & 1);
|
||||
source_ = Source::CL;
|
||||
break;
|
||||
case 0xd4: RegData(AAM, AX, 1); break;
|
||||
case 0xd5: RegData(AAD, AX, 1); break;
|
||||
|
||||
case 0xd7: Complete(XLAT, None, None, 1); break;
|
||||
|
||||
case 0xd8: MemRegReg(ESC, MemReg_Reg, 0); break;
|
||||
case 0xd9: MemRegReg(ESC, MemReg_Reg, 0); break;
|
||||
case 0xda: MemRegReg(ESC, MemReg_Reg, 0); break;
|
||||
case 0xdb: MemRegReg(ESC, MemReg_Reg, 0); break;
|
||||
case 0xdc: MemRegReg(ESC, MemReg_Reg, 0); break;
|
||||
case 0xdd: MemRegReg(ESC, MemReg_Reg, 0); break;
|
||||
case 0xde: MemRegReg(ESC, MemReg_Reg, 0); break;
|
||||
case 0xdf: MemRegReg(ESC, MemReg_Reg, 0); break;
|
||||
|
||||
case 0xe0: Jump(LOOPNE); break;
|
||||
case 0xe1: Jump(LOOPE); break;
|
||||
case 0xe2: Jump(LOOP); break;
|
||||
case 0xe3: Jump(JPCX); break;
|
||||
|
||||
case 0xe4: RegAddr(IN, AL, 1, 1); break;
|
||||
case 0xe5: RegAddr(IN, AX, 2, 1); break;
|
||||
case 0xe6: AddrReg(OUT, AL, 1, 1); break;
|
||||
case 0xe7: AddrReg(OUT, AX, 2, 1); break;
|
||||
|
||||
case 0xe8: RegData(CALLD, None, 2); break;
|
||||
case 0xe9: RegData(JMPN, None, 2); break;
|
||||
case 0xea: Far(JMPF); break;
|
||||
case 0xeb: Jump(JMPN); break;
|
||||
|
||||
case 0xec: Complete(IN, DX, AL, 1); break;
|
||||
case 0xed: Complete(IN, DX, AX, 1); break;
|
||||
case 0xee: Complete(OUT, AL, DX, 1); break;
|
||||
case 0xef: Complete(OUT, AX, DX, 2); break;
|
||||
|
||||
case 0xf4: Complete(HLT, None, None, 1); break;
|
||||
case 0xf5: Complete(CMC, None, None, 1); break;
|
||||
case 0xf6: MemRegReg(Invalid, MemRegTEST_to_IDIV, 1); break;
|
||||
case 0xf7: MemRegReg(Invalid, MemRegTEST_to_IDIV, 2); break;
|
||||
|
||||
case 0xf8: Complete(CLC, None, None, 1); break;
|
||||
case 0xf9: Complete(STC, None, None, 1); break;
|
||||
case 0xfa: Complete(CLI, None, None, 1); break;
|
||||
case 0xfb: Complete(STI, None, None, 1); break;
|
||||
case 0xfc: Complete(CLD, None, None, 1); break;
|
||||
case 0xfd: Complete(STD, None, None, 1); break;
|
||||
|
||||
case 0xfe: MemRegReg(Invalid, MemRegINC_DEC, 1); break;
|
||||
case 0xff: MemRegReg(Invalid, MemRegINC_to_PUSH, 1); break;
|
||||
|
||||
// Other prefix bytes.
|
||||
case 0xf0: lock_ = true; break;
|
||||
case 0xf2: repetition_ = Repetition::RepNE; break;
|
||||
case 0xf3: repetition_ = Repetition::RepE; break;
|
||||
}
|
||||
}
|
||||
|
||||
#undef Far
|
||||
#undef Jump
|
||||
#undef MemRegReg
|
||||
#undef AddrReg
|
||||
#undef RegAddr
|
||||
#undef RegData
|
||||
#undef Complete
|
||||
#undef SetOpSrcDestSize
|
||||
|
||||
// MARK: - ModRegRM byte, if any.
|
||||
|
||||
if(phase_ == Phase::ModRegRM && source != end) {
|
||||
const uint8_t mod = *source >> 6; // i.e. mode.
|
||||
const uint8_t reg = (*source >> 3) & 7; // i.e. register.
|
||||
const uint8_t rm = *source & 7; // i.e. register/memory.
|
||||
++source;
|
||||
++consumed_;
|
||||
|
||||
Source memreg;
|
||||
constexpr Source reg_table[3][8] = {
|
||||
{},
|
||||
{
|
||||
Source::AL, Source::CL, Source::DL, Source::BL,
|
||||
Source::AH, Source::CH, Source::DH, Source::BH,
|
||||
}, {
|
||||
Source::AX, Source::CX, Source::DX, Source::BX,
|
||||
Source::SP, Source::BP, Source::SI, Source::DI,
|
||||
}
|
||||
};
|
||||
switch(mod) {
|
||||
case 0: {
|
||||
constexpr Source rm_table[8] = {
|
||||
Source::IndBXPlusSI, Source::IndBXPlusDI,
|
||||
Source::IndBPPlusSI, Source::IndBPPlusDI,
|
||||
Source::IndSI, Source::IndDI,
|
||||
Source::DirectAddress, Source::IndBX,
|
||||
};
|
||||
memreg = rm_table[rm];
|
||||
} break;
|
||||
|
||||
default: {
|
||||
constexpr Source rm_table[8] = {
|
||||
Source::IndBXPlusSI, Source::IndBXPlusDI,
|
||||
Source::IndBPPlusSI, Source::IndBPPlusDI,
|
||||
Source::IndSI, Source::IndDI,
|
||||
Source::IndBP, Source::IndBX,
|
||||
};
|
||||
memreg = rm_table[rm];
|
||||
|
||||
displacement_size_ = 1 + (mod == 2);
|
||||
} break;
|
||||
|
||||
// Other operand is just a register.
|
||||
case 3:
|
||||
memreg = reg_table[operation_size_][rm];
|
||||
|
||||
// LES and LDS accept a memory argument only, not a register.
|
||||
if(operation_ == Operation::LES || operation_ == Operation::LDS) {
|
||||
const auto result = std::make_pair(consumed_, Instruction());
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch(modregrm_format_) {
|
||||
case ModRegRMFormat::Reg_MemReg:
|
||||
case ModRegRMFormat::MemReg_Reg: {
|
||||
if(modregrm_format_ == ModRegRMFormat::Reg_MemReg) {
|
||||
source_ = memreg;
|
||||
destination_ = reg_table[operation_size_][reg];
|
||||
} else {
|
||||
source_ = reg_table[operation_size_][reg];
|
||||
destination_ = memreg;
|
||||
}
|
||||
} break;
|
||||
|
||||
case ModRegRMFormat::MemRegTEST_to_IDIV:
|
||||
source_ = destination_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: {
|
||||
const auto result = std::make_pair(consumed_, Instruction());
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
case 0: operation_ = Operation::TEST; break;
|
||||
case 2: operation_ = Operation::NOT; break;
|
||||
case 3: operation_ = Operation::NEG; break;
|
||||
case 4: operation_ = Operation::MUL; break;
|
||||
case 5: operation_ = Operation::IMUL; break;
|
||||
case 6: operation_ = Operation::DIV; break;
|
||||
case 7: operation_ = Operation::IDIV; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::SegReg: {
|
||||
source_ = memreg;
|
||||
|
||||
constexpr Source seg_table[4] = {
|
||||
Source::ES, Source::CS,
|
||||
Source::SS, Source::DS,
|
||||
};
|
||||
|
||||
if(reg & 4) {
|
||||
const auto result = std::make_pair(consumed_, Instruction());
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
destination_ = seg_table[reg];
|
||||
} break;
|
||||
|
||||
case ModRegRMFormat::MemRegROL_to_SAR:
|
||||
destination_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: {
|
||||
const auto result = std::make_pair(consumed_, Instruction());
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
case 0: operation_ = Operation::ROL; break;
|
||||
case 2: operation_ = Operation::ROR; break;
|
||||
case 3: operation_ = Operation::RCL; break;
|
||||
case 4: operation_ = Operation::RCR; break;
|
||||
case 5: operation_ = Operation::SAL; break;
|
||||
case 6: operation_ = Operation::SHR; break;
|
||||
case 7: operation_ = Operation::SAR; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegINC_DEC:
|
||||
source_ = destination_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: {
|
||||
const auto result = std::make_pair(consumed_, Instruction());
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
case 0: operation_ = Operation::INC; break;
|
||||
case 1: operation_ = Operation::DEC; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegINC_to_PUSH:
|
||||
source_ = destination_ = memreg;
|
||||
|
||||
switch(reg) {
|
||||
default: {
|
||||
const auto result = std::make_pair(consumed_, Instruction());
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
case 0: operation_ = Operation::INC; break;
|
||||
case 1: operation_ = Operation::DEC; break;
|
||||
case 2: operation_ = Operation::CALLN; break;
|
||||
case 3:
|
||||
operation_ = Operation::CALLF;
|
||||
operand_size_ = 4;
|
||||
source_ = Source::Immediate;
|
||||
break;
|
||||
case 4: operation_ = Operation::JMPN; break;
|
||||
case 5:
|
||||
operation_ = Operation::JMPF;
|
||||
operand_size_ = 4;
|
||||
source_ = Source::Immediate;
|
||||
break;
|
||||
case 6: operation_ = Operation::PUSH; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegPOP:
|
||||
source_ = destination_ = memreg;
|
||||
|
||||
if(reg != 0) {
|
||||
reset_parsing();
|
||||
return std::make_pair(consumed_, Instruction());
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegMOV:
|
||||
source_ = Source::Immediate;
|
||||
destination_ = memreg;
|
||||
operand_size_ = operation_size_;
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegADD_to_CMP:
|
||||
destination_ = memreg;
|
||||
operand_size_ = operation_size_;
|
||||
|
||||
switch(reg) {
|
||||
default: operation_ = Operation::ADD; break;
|
||||
case 1: operation_ = Operation::OR; break;
|
||||
case 2: operation_ = Operation::ADC; break;
|
||||
case 3: operation_ = Operation::SBB; break;
|
||||
case 4: operation_ = Operation::AND; break;
|
||||
case 5: operation_ = Operation::SUB; break;
|
||||
case 6: operation_ = Operation::XOR; break;
|
||||
case 7: operation_ = Operation::CMP; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ModRegRMFormat::MemRegADC_to_CMP:
|
||||
destination_ = memreg;
|
||||
source_ = Source::Immediate;
|
||||
operand_size_ = 1; // ... and always 1; it'll be sign extended if
|
||||
// the operation requires it.
|
||||
|
||||
switch(reg) {
|
||||
default: {
|
||||
const auto result = std::make_pair(consumed_, Instruction());
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
case 0: operation_ = Operation::ADD; break;
|
||||
case 2: operation_ = Operation::ADC; break;
|
||||
case 3: operation_ = Operation::SBB; break;
|
||||
case 5: operation_ = Operation::SUB; break;
|
||||
case 7: operation_ = Operation::CMP; break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: assert(false);
|
||||
}
|
||||
|
||||
phase_ = (displacement_size_ + operand_size_) ? Phase::AwaitingDisplacementOrOperand : Phase::ReadyToPost;
|
||||
}
|
||||
|
||||
// MARK: - Displacement and operand.
|
||||
|
||||
if(phase_ == Phase::AwaitingDisplacementOrOperand && source != end) {
|
||||
const int required_bytes = displacement_size_ + operand_size_;
|
||||
|
||||
const int outstanding_bytes = required_bytes - operand_bytes_;
|
||||
const int bytes_to_consume = std::min(int(end - source), outstanding_bytes);
|
||||
|
||||
// TODO: I can surely do better than this?
|
||||
for(int c = 0; c < bytes_to_consume; c++) {
|
||||
inward_data_ = (inward_data_ >> 8) | (uint64_t(source[0]) << 56);
|
||||
++source;
|
||||
}
|
||||
|
||||
consumed_ += bytes_to_consume;
|
||||
operand_bytes_ += bytes_to_consume;
|
||||
|
||||
if(bytes_to_consume == outstanding_bytes) {
|
||||
phase_ = Phase::ReadyToPost;
|
||||
|
||||
switch(operand_size_) {
|
||||
default: operand_ = 0; break;
|
||||
case 1:
|
||||
operand_ = inward_data_ >> 56; inward_data_ <<= 8;
|
||||
|
||||
// Sign extend if a single byte operand is feeding a two-byte instruction.
|
||||
if(operation_size_ == 2 && operation_ != Operation::IN && operation_ != Operation::OUT) {
|
||||
operand_ |= (operand_ & 0x80) ? 0xff00 : 0x0000;
|
||||
}
|
||||
break;
|
||||
case 4: displacement_size_ = 2; [[fallthrough]];
|
||||
case 2: operand_ = inward_data_ >> 48; inward_data_ <<= 16; break;
|
||||
break;
|
||||
}
|
||||
switch(displacement_size_) {
|
||||
default: displacement_ = 0; break;
|
||||
case 1: displacement_ = int8_t(inward_data_ >> 56); break;
|
||||
case 2: displacement_ = int16_t(inward_data_ >> 48); break;
|
||||
}
|
||||
} else {
|
||||
// Provide a genuine measure of further bytes required.
|
||||
return std::make_pair(-(outstanding_bytes - bytes_to_consume), Instruction());
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Check for completion.
|
||||
|
||||
if(phase_ == Phase::ReadyToPost) {
|
||||
const auto result = std::make_pair(
|
||||
consumed_,
|
||||
Instruction(
|
||||
operation_,
|
||||
source_,
|
||||
destination_,
|
||||
lock_,
|
||||
segment_override_,
|
||||
repetition_,
|
||||
Size(operation_size_),
|
||||
displacement_,
|
||||
operand_)
|
||||
);
|
||||
reset_parsing();
|
||||
return result;
|
||||
}
|
||||
|
||||
// i.e. not done yet.
|
||||
return std::make_pair(0, Instruction());
|
||||
}
|
||||
156
InstructionSets/x86/Decoder.hpp
Normal file
156
InstructionSets/x86/Decoder.hpp
Normal file
@@ -0,0 +1,156 @@
|
||||
//
|
||||
// Decoder.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 1/1/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_x86_Decoder_hpp
|
||||
#define InstructionSets_x86_Decoder_hpp
|
||||
|
||||
#include "Instruction.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace CPU {
|
||||
namespace Decoder {
|
||||
namespace x86 {
|
||||
|
||||
enum class Model {
|
||||
i8086,
|
||||
};
|
||||
|
||||
/*!
|
||||
Implements Intel x86 instruction decoding.
|
||||
|
||||
This is an experimental implementation; it has not yet undergone significant testing.
|
||||
*/
|
||||
struct Decoder {
|
||||
public:
|
||||
Decoder(Model model);
|
||||
|
||||
/*!
|
||||
@returns an @c Instruction plus a size; a positive size to indicate successful decoding; a
|
||||
negative size specifies the [negatived] number of further bytes the caller should ideally
|
||||
collect before calling again. The caller is free to call with fewer, but may not get a decoded
|
||||
instruction in response, and the decoder may still not be able to complete decoding
|
||||
even if given that number of bytes.
|
||||
*/
|
||||
std::pair<int, Instruction> decode(const uint8_t *source, size_t length);
|
||||
|
||||
private:
|
||||
enum class Phase {
|
||||
/// Captures all prefixes and continues until an instruction byte is encountered.
|
||||
Instruction,
|
||||
/// Receives a ModRegRM byte and either populates the source_ and dest_ fields appropriately
|
||||
/// or completes decoding of the instruction, as per the instruction format.
|
||||
ModRegRM,
|
||||
/// Waits for sufficiently many bytes to pass for the required displacement and operand to be captured.
|
||||
/// Cf. displacement_size_ and operand_size_.
|
||||
AwaitingDisplacementOrOperand,
|
||||
/// Forms and returns an Instruction, and resets parsing state.
|
||||
ReadyToPost
|
||||
} phase_ = Phase::Instruction;
|
||||
|
||||
/// During the ModRegRM phase, format dictates interpretation of the ModRegRM byte.
|
||||
///
|
||||
/// During the ReadyToPost phase, format determines how transiently-recorded fields
|
||||
/// are packaged into an Instruction.
|
||||
enum class ModRegRMFormat: uint8_t {
|
||||
// Parse the ModRegRM for mode, register and register/memory fields
|
||||
// and populate the source_ and destination_ fields appropriate.
|
||||
MemReg_Reg,
|
||||
Reg_MemReg,
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// source_ and destination_ fields with the result. Use the 'register'
|
||||
// field to pick an operation from the TEST/NOT/NEG/MUL/IMUL/DIV/IDIV group.
|
||||
MemRegTEST_to_IDIV,
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// source_ and destination_ fields with the result. Use the 'register'
|
||||
// field to check for the POP operation.
|
||||
MemRegPOP,
|
||||
|
||||
// Parse for mode and register/memory fields, populating both
|
||||
// the destination_ field with the result and setting source_ to Immediate.
|
||||
// Use the 'register' field to check for the MOV operation.
|
||||
MemRegMOV,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// destination_ field with the result. Use the 'register' field
|
||||
// to pick an operation from the ROL/ROR/RCL/RCR/SAL/SHR/SAR group.
|
||||
MemRegROL_to_SAR,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// destination_ field with the result. Use the 'register' field
|
||||
// to pick an operation from the ADD/OR/ADC/SBB/AND/SUB/XOR/CMP group and
|
||||
// waits for an operand equal to the operation size.
|
||||
MemRegADD_to_CMP,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ field with the result. Fills destination_ with a segment
|
||||
// register based on the reg field.
|
||||
SegReg,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ and destination_ fields with the result. Uses the
|
||||
// 'register' field to pick INC or DEC.
|
||||
MemRegINC_DEC,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ and destination_ fields with the result. Uses the
|
||||
// 'register' field to pick from INC/DEC/CALL/JMP/PUSH, altering
|
||||
// the source to ::Immediate and setting an operand size if necessary.
|
||||
MemRegINC_to_PUSH,
|
||||
|
||||
// Parse for mode and register/memory fields, populating the
|
||||
// source_ and destination_ fields with the result. Uses the
|
||||
// 'register' field to pick from ADD/ADC/SBB/SUB/CMP, altering
|
||||
// the source to ::Immediate and setting an appropriate operand size.
|
||||
MemRegADC_to_CMP,
|
||||
} modregrm_format_ = ModRegRMFormat::MemReg_Reg;
|
||||
|
||||
// Ephemeral decoding state.
|
||||
Operation operation_ = Operation::Invalid;
|
||||
uint8_t instr_ = 0x00; // TODO: is this desired, versus loading more context into ModRegRMFormat?
|
||||
int consumed_ = 0, operand_bytes_ = 0;
|
||||
|
||||
// Source and destination locations.
|
||||
Source source_ = Source::None;
|
||||
Source destination_ = Source::None;
|
||||
|
||||
// Immediate fields.
|
||||
int16_t displacement_ = 0;
|
||||
uint16_t operand_ = 0;
|
||||
uint64_t inward_data_ = 0;
|
||||
|
||||
// Facts about the instruction.
|
||||
int displacement_size_ = 0; // i.e. size of in-stream displacement, if any.
|
||||
int operand_size_ = 0; // i.e. size of in-stream operand, if any.
|
||||
int operation_size_ = 0; // i.e. size of data manipulated by the operation.
|
||||
|
||||
// Prefix capture fields.
|
||||
Repetition repetition_ = Repetition::None;
|
||||
bool lock_ = false;
|
||||
Source segment_override_ = Source::None;
|
||||
|
||||
/// Resets size capture and all fields with default values.
|
||||
void reset_parsing() {
|
||||
consumed_ = operand_bytes_ = 0;
|
||||
displacement_size_ = operand_size_ = 0;
|
||||
displacement_ = operand_ = 0;
|
||||
lock_ = false;
|
||||
segment_override_ = Source::None;
|
||||
repetition_ = Repetition::None;
|
||||
phase_ = Phase::Instruction;
|
||||
source_ = destination_ = Source::None;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_x86_Decoder_hpp */
|
||||
305
InstructionSets/x86/Instruction.hpp
Normal file
305
InstructionSets/x86/Instruction.hpp
Normal file
@@ -0,0 +1,305 @@
|
||||
//
|
||||
// Instruction.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 1/15/21.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef InstructionSets_x86_Instruction_h
|
||||
#define InstructionSets_x86_Instruction_h
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace CPU {
|
||||
namespace Decoder {
|
||||
namespace x86 {
|
||||
|
||||
/*
|
||||
Operations are documented below to establish expectations as to which
|
||||
instruction fields will be meaningful for each; this is a work-in-progress
|
||||
and may currently contain errors in the opcode descriptions — especially
|
||||
where implicit register dependencies are afoot.
|
||||
*/
|
||||
enum class Operation: uint8_t {
|
||||
Invalid,
|
||||
|
||||
/// ASCII adjust after addition; source will be AL and destination will be AX.
|
||||
AAA,
|
||||
/// ASCII adjust before division; destination will be AX and source will be a multiplier.
|
||||
AAD,
|
||||
/// ASCII adjust after multiplication; destination will be AX and source will be a divider.
|
||||
AAM,
|
||||
/// ASCII adjust after subtraction; source will be AL and destination will be AX.
|
||||
AAS,
|
||||
/// Decimal adjust after addition; source and destination will be AL.
|
||||
DAA,
|
||||
/// Decimal adjust after subtraction; source and destination will be AL.
|
||||
DAS,
|
||||
|
||||
/// Convert byte into word; source will be AL, destination will be AH.
|
||||
CBW,
|
||||
/// Convert word to double word; source will be AX and destination will be DX.
|
||||
CWD,
|
||||
|
||||
/// Escape, for a coprocessor; perform the bus cycles necessary to read the source and destination and perform a NOP.
|
||||
ESC,
|
||||
|
||||
/// Stops the processor until the next interrupt is fired.
|
||||
HLT,
|
||||
/// Waits until the WAIT input is asserted; if an interrupt occurs then it is serviced but returns to the WAIT.
|
||||
WAIT,
|
||||
|
||||
/// Add with carry; source, destination, operand and displacement will be populated appropriately.
|
||||
ADC,
|
||||
/// Add; source, destination, operand and displacement will be populated appropriately.
|
||||
ADD,
|
||||
/// Subtract with borrow; source, destination, operand and displacement will be populated appropriately.
|
||||
SBB,
|
||||
/// Subtract; source, destination, operand and displacement will be populated appropriately.
|
||||
SUB,
|
||||
/// Unsigned multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
MUL,
|
||||
/// Signed multiply; multiplies the source value by AX or AL, storing the result in DX:AX or AX.
|
||||
IMUL,
|
||||
/// Unsigned divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
|
||||
DIV,
|
||||
/// Signed divide; divide the source value by AX or AL, storing the quotient in AL and the remainder in AH.
|
||||
IDIV,
|
||||
|
||||
/// Increment; source, destination, operand and displacement will be populated appropriately.
|
||||
INC,
|
||||
/// Decrement; source, destination, operand and displacement will be populated appropriately.
|
||||
DEC,
|
||||
|
||||
/// Reads from the port specified by source to the destination.
|
||||
IN,
|
||||
/// Writes from the port specified by destination from the source.
|
||||
OUT,
|
||||
|
||||
// Various jumps; see the displacement to calculate targets.
|
||||
JO, JNO, JB, JNB, JE, JNE, JBE, JNBE,
|
||||
JS, JNS, JP, JNP, JL, JNL, JLE, JNLE,
|
||||
|
||||
/// Far call; see the segment() and offset() fields.
|
||||
CALLF,
|
||||
/// Displacement call; followed by a 16-bit operand providing a call offset.
|
||||
CALLD,
|
||||
/// Near call.
|
||||
CALLN,
|
||||
/// Return from interrupt.
|
||||
IRET,
|
||||
/// Near return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
|
||||
RETF,
|
||||
/// Far return; if source is not ::None then it will be an ::Immediate indicating how many additional bytes to remove from the stack.
|
||||
RETN,
|
||||
/// Near jump; if an operand is not ::None then it gives an absolute destination; otherwise see the displacement.
|
||||
JMPN,
|
||||
/// Far jump to the indicated segment and offset.
|
||||
JMPF,
|
||||
/// Relative jump performed only if CX = 0; see the displacement.
|
||||
JPCX,
|
||||
/// Generates a software interrupt of the level stated in the operand.
|
||||
INT,
|
||||
/// Generates a software interrupt of level 3.
|
||||
INT3,
|
||||
/// Generates a software interrupt of level 4 if overflow is set.
|
||||
INTO,
|
||||
|
||||
/// Load status flags to AH.
|
||||
LAHF,
|
||||
/// Load status flags from AH.
|
||||
SAHF,
|
||||
/// Load a segment and offset from the source into DS and the destination.
|
||||
LDS,
|
||||
/// Load a segment and offset from the source into ES and the destination.
|
||||
LES,
|
||||
/// Computes the effective address of the source and loads it into the destination.
|
||||
LEA,
|
||||
|
||||
/// Compare [bytes or words, per operation size]; source and destination implied to be DS:[SI] and ES:[DI].
|
||||
CMPS,
|
||||
/// Load string; reads from DS:SI into AL or AX, subject to segment override.
|
||||
LODS,
|
||||
/// Move string; moves a byte or word from DS:SI to ES:DI. If a segment override is provided, it overrides the the source.
|
||||
MOVS,
|
||||
/// Scan string; reads a byte or word from DS:SI and compares it to AL or AX.
|
||||
SCAS,
|
||||
/// Store string; store AL or AX to ES:DI.
|
||||
STOS,
|
||||
|
||||
// Perform a possibly-conditional loop, decrementing CX. See the displacement.
|
||||
LOOP, LOOPE, LOOPNE,
|
||||
|
||||
/// Loads the destination with the source.
|
||||
MOV,
|
||||
/// Negatives; source and destination point to the same thing, to negative.
|
||||
NEG,
|
||||
/// Logical NOT; source and destination point to the same thing, to negative.
|
||||
NOT,
|
||||
/// Logical AND; source, destination, operand and displacement will be populated appropriately.
|
||||
AND,
|
||||
/// Logical OR of source onto destination.
|
||||
OR,
|
||||
/// Logical XOR of source onto destination.
|
||||
XOR,
|
||||
/// NOP; no further fields.
|
||||
NOP,
|
||||
/// POP from the stack to destination.
|
||||
POP,
|
||||
/// POP from the stack to the flags register.
|
||||
POPF,
|
||||
/// PUSH the source to the stack.
|
||||
PUSH,
|
||||
/// PUSH the flags register to the stack.
|
||||
PUSHF,
|
||||
/// Rotate the destination left through carry the number of bits indicated by source.
|
||||
RCL,
|
||||
/// Rotate the destination right through carry the number of bits indicated by source.
|
||||
RCR,
|
||||
/// Rotate the destination left the number of bits indicated by source.
|
||||
ROL,
|
||||
/// Rotate the destination right the number of bits indicated by source.
|
||||
ROR,
|
||||
/// Arithmetic shift left the destination by the number of bits indicated by source.
|
||||
SAL,
|
||||
/// Arithmetic shift right the destination by the number of bits indicated by source.
|
||||
SAR,
|
||||
/// Logical shift right the destination by the number of bits indicated by source.
|
||||
SHR,
|
||||
|
||||
/// Clear carry flag; no source or destination provided.
|
||||
CLC,
|
||||
/// Clear direction flag; no source or destination provided.
|
||||
CLD,
|
||||
/// Clear interrupt flag; no source or destination provided.
|
||||
CLI,
|
||||
/// Set carry flag.
|
||||
STC,
|
||||
/// Set decimal flag.
|
||||
STD,
|
||||
/// Set interrupt flag.
|
||||
STI,
|
||||
/// Complement carry flag; no source or destination provided.
|
||||
CMC,
|
||||
|
||||
/// Compare; source, destination, operand and displacement will be populated appropriately.
|
||||
CMP,
|
||||
/// Sets flags based on the result of a logical AND of source and destination.
|
||||
TEST,
|
||||
|
||||
/// Exchanges the contents of the source and destination.
|
||||
XCHG,
|
||||
|
||||
/// Load AL with DS:[AL+BX].
|
||||
XLAT,
|
||||
};
|
||||
|
||||
enum class Size: uint8_t {
|
||||
Implied = 0,
|
||||
Byte = 1,
|
||||
Word = 2,
|
||||
DWord = 4,
|
||||
};
|
||||
|
||||
enum class Source: uint8_t {
|
||||
None,
|
||||
CS, DS, ES, SS,
|
||||
|
||||
AL, AH, AX,
|
||||
BL, BH, BX,
|
||||
CL, CH, CX,
|
||||
DL, DH, DX,
|
||||
|
||||
SI, DI,
|
||||
BP, SP,
|
||||
|
||||
IndBXPlusSI,
|
||||
IndBXPlusDI,
|
||||
IndBPPlusSI,
|
||||
IndBPPlusDI,
|
||||
IndSI,
|
||||
IndDI,
|
||||
DirectAddress,
|
||||
IndBP,
|
||||
IndBX,
|
||||
|
||||
Immediate
|
||||
};
|
||||
|
||||
enum class Repetition: uint8_t {
|
||||
None, RepE, RepNE
|
||||
};
|
||||
|
||||
class Instruction {
|
||||
public:
|
||||
Operation operation = Operation::Invalid;
|
||||
|
||||
bool operator ==(const Instruction &rhs) const {
|
||||
return
|
||||
repetition_size_ == rhs.repetition_size_ &&
|
||||
sources_ == rhs.sources_ &&
|
||||
displacement_ == rhs.displacement_ &&
|
||||
operand_ == rhs.operand_;
|
||||
}
|
||||
|
||||
private:
|
||||
// b0, b1: a Repetition;
|
||||
// b2+: operation size.
|
||||
uint8_t repetition_size_ = 0;
|
||||
|
||||
// b0–b5: source;
|
||||
// b6–b11: destination;
|
||||
// b12–b14: segment override;
|
||||
// b15: lock.
|
||||
uint16_t sources_ = 0;
|
||||
|
||||
// Unpackable fields.
|
||||
int16_t displacement_ = 0;
|
||||
uint16_t operand_ = 0; // ... or used to store a segment for far operations.
|
||||
|
||||
public:
|
||||
Source source() const { return Source(sources_ & 0x3f); }
|
||||
Source destination() const { return Source((sources_ >> 6) & 0x3f); }
|
||||
bool lock() const { return sources_ & 0x8000; }
|
||||
Source segment_override() const { return Source((sources_ >> 12) & 7); }
|
||||
|
||||
Repetition repetition() const { return Repetition(repetition_size_ & 3); }
|
||||
Size operation_size() const { return Size(repetition_size_ >> 2); }
|
||||
|
||||
uint16_t segment() const { return uint16_t(operand_); }
|
||||
uint16_t offset() const { return uint16_t(displacement_); }
|
||||
|
||||
int16_t displacement() const { return displacement_; }
|
||||
uint16_t operand() const { return operand_; }
|
||||
|
||||
Instruction() noexcept {}
|
||||
Instruction(
|
||||
Operation operation,
|
||||
Source source,
|
||||
Source destination,
|
||||
bool lock,
|
||||
Source segment_override,
|
||||
Repetition repetition,
|
||||
Size operation_size,
|
||||
int16_t displacement,
|
||||
uint16_t operand) noexcept :
|
||||
operation(operation),
|
||||
repetition_size_(uint8_t((int(operation_size) << 2) | int(repetition))),
|
||||
sources_(uint16_t(
|
||||
int(source) |
|
||||
(int(destination) << 6) |
|
||||
(int(segment_override) << 12) |
|
||||
(int(lock) << 15)
|
||||
)),
|
||||
displacement_(displacement),
|
||||
operand_(operand) {}
|
||||
};
|
||||
|
||||
static_assert(sizeof(Instruction) <= 8);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* InstructionSets_x86_Instruction_h */
|
||||
@@ -1089,7 +1089,7 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
|
||||
}
|
||||
|
||||
HalfCycles get_typer_delay() const final {
|
||||
HalfCycles get_typer_delay(const std::string &) const final {
|
||||
return z80_.get_is_resetting() ? Cycles(3'400'000) : Cycles(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -804,38 +804,62 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
void reset_all_keys(Inputs::Keyboard *) final {
|
||||
void reset_all_keys() final {
|
||||
open_apple_is_pressed_ = closed_apple_is_pressed_ = key_is_down_ = false;
|
||||
}
|
||||
|
||||
bool prefers_logical_input() final {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_key_pressed(Key key, char value, bool is_pressed) final {
|
||||
// If no ASCII value is supplied, look for a few special cases.
|
||||
switch(key) {
|
||||
default: break;
|
||||
case Key::F12:
|
||||
m6502_.set_reset_line(is_pressed);
|
||||
return true;
|
||||
case Key::Left: value = 0x08; break;
|
||||
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::Enter: value = 0x0d; break;
|
||||
case Key::Tab: value = '\t'; break;
|
||||
case Key::Escape: value = 0x1b; break;
|
||||
|
||||
case Key::LeftOption:
|
||||
case Key::RightMeta:
|
||||
open_apple_is_pressed_ = is_pressed;
|
||||
return true;
|
||||
|
||||
case Key::RightOption:
|
||||
case Key::LeftMeta:
|
||||
closed_apple_is_pressed_ = is_pressed;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no ASCII value is supplied, look for a few special cases.
|
||||
if(!value) {
|
||||
switch(key) {
|
||||
case Key::Left: value = 0x08; break;
|
||||
case Key::Right: value = 0x15; break;
|
||||
case Key::Down: value = 0x0a; break;
|
||||
case Key::Up: value = 0x0b; break;
|
||||
case Key::Backspace: value = 0x7f; break;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
case Key::F1: case Key::F2: case Key::F3: case Key::F4:
|
||||
case Key::F5: case Key::F6: case Key::F7: case Key::F8:
|
||||
case Key::F9: case Key::F10: case Key::F11: case Key::F12:
|
||||
case Key::PrintScreen:
|
||||
case Key::ScrollLock:
|
||||
case Key::Pause:
|
||||
case Key::Insert:
|
||||
case Key::Home:
|
||||
case Key::PageUp:
|
||||
case Key::PageDown:
|
||||
case Key::End:
|
||||
// Accept a bunch non-symbolic other keys, as
|
||||
// reset, in the hope that the user can find
|
||||
// at least one usable key.
|
||||
m6502_.set_reset_line(is_pressed);
|
||||
return true;
|
||||
|
||||
// Prior to the IIe, the keyboard could produce uppercase only.
|
||||
if(!is_iie()) value = char(toupper(value));
|
||||
default:
|
||||
if(!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prior to the IIe, the keyboard could produce uppercase only.
|
||||
if(!is_iie()) value = char(toupper(value));
|
||||
break;
|
||||
}
|
||||
|
||||
if(is_pressed) {
|
||||
keyboard_input_ = uint8_t(value | 0x80);
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Processors/6502/6502.hpp"
|
||||
|
||||
#include "../../Storage/MassStorage/SCSI/SCSI.hpp"
|
||||
#include "../../Storage/MassStorage/SCSI/DirectAccessDevice.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include "../Utility/Typer.hpp"
|
||||
@@ -31,7 +34,7 @@
|
||||
|
||||
namespace Electron {
|
||||
|
||||
class ConcreteMachine:
|
||||
template <bool has_scsi_bus> class ConcreteMachine:
|
||||
public Machine,
|
||||
public MachineTypes::TimedMachine,
|
||||
public MachineTypes::ScanProducer,
|
||||
@@ -42,10 +45,15 @@ class ConcreteMachine:
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Tape::Delegate,
|
||||
public Utility::TypeRecipient<CharacterMapper>,
|
||||
public Activity::Source {
|
||||
public Activity::Source,
|
||||
public SCSI::Bus::Observer,
|
||||
public ClockingHint::Observer {
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
m6502_(*this),
|
||||
scsi_bus_(4'000'000),
|
||||
hard_drive_(scsi_bus_, 0),
|
||||
scsi_device_(scsi_bus_.add_device()),
|
||||
video_output_(ram_),
|
||||
sound_generator_(audio_queue_),
|
||||
speaker_(sound_generator_) {
|
||||
@@ -64,14 +72,23 @@ class ConcreteMachine:
|
||||
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
|
||||
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
|
||||
};
|
||||
if(target.has_adfs) {
|
||||
const size_t pres_adfs_rom_position = required_roms.size();
|
||||
if(target.has_pres_adfs) {
|
||||
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993);
|
||||
required_roms.emplace_back(machine_name, "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0e);
|
||||
}
|
||||
const size_t acorn_adfs_rom_position = required_roms.size();
|
||||
if(target.has_acorn_adfs) {
|
||||
required_roms.emplace_back(machine_name, "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6);
|
||||
}
|
||||
const size_t dfs_rom_position = required_roms.size();
|
||||
if(target.has_dfs) {
|
||||
required_roms.emplace_back(machine_name, "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5);
|
||||
}
|
||||
const size_t ap6_rom_position = required_roms.size();
|
||||
if(target.has_ap6_rom) {
|
||||
required_roms.emplace_back(machine_name, "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfc);
|
||||
}
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
@@ -82,15 +99,40 @@ class ConcreteMachine:
|
||||
set_rom(ROM::BASIC, *roms[0], false);
|
||||
set_rom(ROM::OS, *roms[1], false);
|
||||
|
||||
if(target.has_dfs || target.has_adfs) {
|
||||
/*
|
||||
ROM slot mapping applied:
|
||||
|
||||
* the keyboard and BASIC ROMs occupy slots 8, 9, 10 and 11;
|
||||
* the DFS, if in use, occupies slot 1;
|
||||
* the Pres ADFS, if in use, occupies slots 4 and 5;
|
||||
* the Acorn ADFS, if in use, occupies slot 6;
|
||||
* the AP6, if in use, occupies slot 15; and
|
||||
* if sideways RAM was asked for, all otherwise unused slots are populated with sideways RAM.
|
||||
*/
|
||||
if(target.has_dfs || target.has_acorn_adfs || target.has_pres_adfs) {
|
||||
plus3_ = std::make_unique<Plus3>();
|
||||
|
||||
if(target.has_dfs) {
|
||||
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
|
||||
}
|
||||
if(target.has_adfs) {
|
||||
set_rom(ROM::Slot4, *roms[2], true);
|
||||
set_rom(ROM::Slot5, *roms[3], true);
|
||||
if(target.has_pres_adfs) {
|
||||
set_rom(ROM::Slot4, *roms[pres_adfs_rom_position], true);
|
||||
set_rom(ROM::Slot5, *roms[pres_adfs_rom_position+1], true);
|
||||
}
|
||||
if(target.has_acorn_adfs) {
|
||||
set_rom(ROM::Slot6, *roms[acorn_adfs_rom_position], true);
|
||||
}
|
||||
}
|
||||
|
||||
if(target.has_ap6_rom) {
|
||||
set_rom(ROM::Slot15, *roms[ap6_rom_position], true);
|
||||
}
|
||||
|
||||
if(target.has_sideways_ram) {
|
||||
for(int c = 0; c < 16; c++) {
|
||||
if(rom_inserted_[c]) continue;
|
||||
if(c >= int(ROM::Keyboard) && c < int(ROM::BASIC)+1) continue;
|
||||
set_sideways_ram(ROM(c));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +145,11 @@ class ConcreteMachine:
|
||||
if(target.should_shift_restart) {
|
||||
shift_restart_counter_ = 1000000;
|
||||
}
|
||||
|
||||
if(has_scsi_bus) {
|
||||
scsi_bus_.add_observer(this);
|
||||
scsi_bus_.set_clocking_hint_observer(this);
|
||||
}
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -168,7 +215,12 @@ class ConcreteMachine:
|
||||
set_rom(slot, cartridge->get_segments().front().data, false);
|
||||
}
|
||||
|
||||
return !media.tapes.empty() || !media.disks.empty() || !media.cartridges.empty();
|
||||
// TODO: allow this only at machine startup?
|
||||
if(!media.mass_storage_devices.empty()) {
|
||||
hard_drive_->set_storage(media.mass_storage_devices.front());
|
||||
}
|
||||
|
||||
return !media.empty();
|
||||
}
|
||||
|
||||
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
@@ -290,7 +342,73 @@ class ConcreteMachine:
|
||||
plus3_->set_control_register(*value);
|
||||
} else *value = 1;
|
||||
}
|
||||
|
||||
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
|
||||
scsi_acknowledge_ = true;
|
||||
if(!isReadOperation(operation)) {
|
||||
scsi_data_ = *value;
|
||||
push_scsi_output();
|
||||
} else {
|
||||
*value = SCSI::data_lines(scsi_bus_.get_state());
|
||||
push_scsi_output();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xfc03:
|
||||
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
|
||||
scsi_interrupt_state_ = false;
|
||||
scsi_interrupt_mask_ = *value & 1;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
break;
|
||||
case 0xfc01:
|
||||
if(has_scsi_bus && (address&0x00f0) == 0x0040 && isReadOperation(operation)) {
|
||||
// Status byte is:
|
||||
//
|
||||
// b7: SCSI C/D
|
||||
// b6: SCSI I/O
|
||||
// b5: SCSI REQ
|
||||
// b4: interrupt flag
|
||||
// b3: 0
|
||||
// b2: 0
|
||||
// b1: SCSI BSY
|
||||
// b0: SCSI MSG
|
||||
const auto state = scsi_bus_.get_state();
|
||||
*value =
|
||||
(state & SCSI::Line::Control ? 0x80 : 0x00) |
|
||||
(state & SCSI::Line::Input ? 0x40 : 0x00) |
|
||||
(state & SCSI::Line::Request ? 0x20 : 0x00) |
|
||||
((scsi_interrupt_state_ && scsi_interrupt_mask_) ? 0x10 : 0x00) |
|
||||
(state & SCSI::Line::Busy ? 0x02 : 0x00) |
|
||||
(state & SCSI::Line::Message ? 0x01 : 0x00);
|
||||
|
||||
// Empirical guess: this is also the trigger to affect busy/request/acknowledge
|
||||
// signalling. Maybe?
|
||||
if(scsi_select_ && scsi_bus_.get_state() & SCSI::Line::Busy) {
|
||||
scsi_select_ = false;
|
||||
push_scsi_output();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 0xfc02:
|
||||
if(has_scsi_bus && (address&0x00f0) == 0x0040) {
|
||||
scsi_select_ = true;
|
||||
push_scsi_output();
|
||||
}
|
||||
break;
|
||||
|
||||
// SCSI locations:
|
||||
//
|
||||
// fc40: data, read and write
|
||||
// fc41: status read
|
||||
// fc42: select write
|
||||
// fc43: interrupt latch
|
||||
//
|
||||
//
|
||||
// Interrupt latch is:
|
||||
//
|
||||
// b0: enable or disable IRQ on REQ
|
||||
// (and, possibly, writing to the latch acknowledges?)
|
||||
|
||||
default:
|
||||
if(address >= 0xc000) {
|
||||
@@ -389,6 +507,12 @@ class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (has_scsi_bus) {
|
||||
if(scsi_is_clocked_) {
|
||||
scsi_bus_.run_for(Cycles(int(cycles)));
|
||||
}
|
||||
}
|
||||
|
||||
return Cycles(int(cycles));
|
||||
}
|
||||
|
||||
@@ -422,13 +546,44 @@ class ConcreteMachine:
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
void scsi_bus_did_change(SCSI::Bus *, SCSI::BusState new_state, double) final {
|
||||
// Release acknowledge when request is released.
|
||||
if(scsi_acknowledge_ && !(new_state & SCSI::Line::Request)) {
|
||||
scsi_acknowledge_ = false;
|
||||
push_scsi_output();
|
||||
}
|
||||
|
||||
// Output occurs only while SCSI::Line::Input is inactive; therefore a change
|
||||
// in that line affects what's on the bus.
|
||||
if(((new_state^previous_bus_state_)&SCSI::Line::Input)) {
|
||||
push_scsi_output();
|
||||
}
|
||||
|
||||
scsi_interrupt_state_ |= (new_state^previous_bus_state_)&new_state & SCSI::Line::Request;
|
||||
previous_bus_state_ = new_state;
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference preference) final {
|
||||
scsi_is_clocked_ = preference != ClockingHint::Preference::None;
|
||||
}
|
||||
|
||||
void tape_did_change_interrupt_status(Tape *) final {
|
||||
interrupt_status_ = (interrupt_status_ & ~(Interrupt::TransmitDataEmpty | Interrupt::ReceiveDataFull | Interrupt::HighToneDetect)) | tape_.get_interrupt_status();
|
||||
evaluate_interrupts();
|
||||
}
|
||||
|
||||
HalfCycles get_typer_delay() const final {
|
||||
return m6502_.get_is_resetting() ? Cycles(750'000) : Cycles(0);
|
||||
HalfCycles get_typer_delay(const std::string &text) const final {
|
||||
if(!m6502_.get_is_resetting()) {
|
||||
return Cycles(0);
|
||||
}
|
||||
|
||||
// Add a longer delay for a command at reset that involves pressing a modifier;
|
||||
// empirically this seems to be a requirement, in order to avoid a collision with
|
||||
// the system's built-in modifier-at-startup test (e.g. to perform shift+break).
|
||||
CharacterMapper test_mapper;
|
||||
const uint16_t *const sequence = test_mapper.sequence_for_character(text[0]);
|
||||
return is_modifier(Key(sequence[0])) ? Cycles(1'000'000) : Cycles(750'000);
|
||||
}
|
||||
|
||||
HalfCycles get_typer_frequency() const final {
|
||||
@@ -469,10 +624,14 @@ class ConcreteMachine:
|
||||
if(activity_observer_) {
|
||||
activity_observer_->register_led(caps_led);
|
||||
activity_observer_->set_led_status(caps_led, caps_led_state_);
|
||||
}
|
||||
|
||||
if(plus3_) {
|
||||
plus3_->set_activity_observer(observer);
|
||||
}
|
||||
if(plus3_) {
|
||||
plus3_->set_activity_observer(observer);
|
||||
}
|
||||
|
||||
if(has_scsi_bus) {
|
||||
scsi_bus_.set_activity_observer(observer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,8 +676,20 @@ class ConcreteMachine:
|
||||
rom_ptr += size_to_copy;
|
||||
}
|
||||
|
||||
if(int(slot) < 16)
|
||||
if(int(slot) < 16) {
|
||||
rom_inserted_[int(slot)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Enables @c slot as sideways RAM; ensures that it does not currently contain a valid ROM signature.
|
||||
*/
|
||||
void set_sideways_ram(ROM slot) {
|
||||
std::memset(roms_[int(slot)], 0xff, 16*1024);
|
||||
if(int(slot) < 16) {
|
||||
rom_inserted_[int(slot)] = true;
|
||||
rom_write_masks_[int(slot)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Work deferral updates.
|
||||
@@ -554,7 +725,12 @@ class ConcreteMachine:
|
||||
} else {
|
||||
interrupt_status_ &= ~1;
|
||||
}
|
||||
m6502_.set_irq_line(interrupt_status_ & 1);
|
||||
|
||||
if constexpr (has_scsi_bus) {
|
||||
m6502_.set_irq_line((scsi_interrupt_state_ && scsi_interrupt_mask_) | (interrupt_status_ & 1));
|
||||
} else {
|
||||
m6502_.set_irq_line(interrupt_status_ & 1);
|
||||
}
|
||||
}
|
||||
|
||||
CPU::MOS6502::Processor<CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
@@ -598,6 +774,25 @@ class ConcreteMachine:
|
||||
bool is_holding_shift_ = false;
|
||||
int shift_restart_counter_ = 0;
|
||||
|
||||
// Hard drive.
|
||||
SCSI::Bus scsi_bus_;
|
||||
SCSI::Target::Target<SCSI::DirectAccessDevice> hard_drive_;
|
||||
SCSI::BusState previous_bus_state_ = SCSI::DefaultBusState;
|
||||
const size_t scsi_device_ = 0;
|
||||
uint8_t scsi_data_ = 0;
|
||||
bool scsi_select_ = false;
|
||||
bool scsi_acknowledge_ = false;
|
||||
bool scsi_is_clocked_ = false;
|
||||
bool scsi_interrupt_state_ = false;
|
||||
bool scsi_interrupt_mask_ = false;
|
||||
void push_scsi_output() {
|
||||
scsi_bus_.set_device_output(scsi_device_,
|
||||
(scsi_bus_.get_state()&SCSI::Line::Input ? 0 : scsi_data_) |
|
||||
(scsi_select_ ? SCSI::Line::SelectTarget : 0) |
|
||||
(scsi_acknowledge_ ? SCSI::Line::Acknowledge : 0)
|
||||
);
|
||||
}
|
||||
|
||||
// Outputs
|
||||
VideoOutput video_output_;
|
||||
|
||||
@@ -620,7 +815,12 @@ using namespace Electron;
|
||||
Machine *Machine::Electron(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Acorn::Target;
|
||||
const Target *const acorn_target = dynamic_cast<const Target *>(target);
|
||||
return new Electron::ConcreteMachine(*acorn_target, rom_fetcher);
|
||||
|
||||
if(acorn_target->media.mass_storage_devices.empty()) {
|
||||
return new Electron::ConcreteMachine<false>(*acorn_target, rom_fetcher);
|
||||
} else {
|
||||
return new Electron::ConcreteMachine<true>(*acorn_target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
|
||||
@@ -16,6 +16,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
|
||||
default: break;
|
||||
|
||||
BIND(BackTick, KeyCopy);
|
||||
BIND(Backslash, KeyCopy);
|
||||
|
||||
BIND(k0, Key0); BIND(k1, Key1); BIND(k2, Key2); BIND(k3, Key3); BIND(k4, Key4);
|
||||
BIND(k5, Key5); BIND(k6, Key6); BIND(k7, Key7); BIND(k8, Key8); BIND(k9, Key9);
|
||||
|
||||
@@ -31,11 +31,15 @@ enum Key: uint16_t {
|
||||
KeyShift = 0x00d0 | 0x08, KeyControl = 0x00d0 | 0x04, KeyFunc = 0x00d0 | 0x02, KeyEscape = 0x00d0 | 0x01,
|
||||
|
||||
// Virtual keys.
|
||||
KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
|
||||
KeyF1 = 0xfff0, KeyF2, KeyF3, KeyF4, KeyF5, KeyF6, KeyF7, KeyF8, KeyF9, KeyF0,
|
||||
|
||||
KeyBreak = 0xfffd,
|
||||
};
|
||||
|
||||
constexpr bool is_modifier(Key key) {
|
||||
return (key == KeyShift) || (key == KeyControl) || (key == KeyFunc);
|
||||
}
|
||||
|
||||
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
|
||||
};
|
||||
|
||||
@@ -32,6 +32,11 @@ struct KeyActions {
|
||||
Instructs that all keys should now be treated as released.
|
||||
*/
|
||||
virtual void clear_all_keys() {}
|
||||
|
||||
/*!
|
||||
Indicates whether a machine most naturally accepts logical rather than physical input.
|
||||
*/
|
||||
virtual bool prefers_logical_input() { return false; }
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -413,7 +413,7 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
|
||||
// to satisfy CPU::MOS6502::BusHandler
|
||||
forceinline Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value) {
|
||||
if(address > ram_top_) {
|
||||
if(isReadOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
||||
if(!isWriteOperation(operation)) *value = paged_rom_[address - ram_top_ - 1];
|
||||
|
||||
// 024D = 0 => fast; otherwise slow
|
||||
// E6C9 = read byte: return byte in A
|
||||
@@ -432,39 +432,39 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
|
||||
} else {
|
||||
if((address & 0xff00) == 0x0300) {
|
||||
if(address < 0x0310 || (disk_interface == DiskInterface::None)) {
|
||||
if(isReadOperation(operation)) *value = via_.read(address);
|
||||
if(!isWriteOperation(operation)) *value = via_.read(address);
|
||||
else via_.write(address, *value);
|
||||
} else {
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::BD500:
|
||||
if(isReadOperation(operation)) *value = bd500_.read(address);
|
||||
if(!isWriteOperation(operation)) *value = bd500_.read(address);
|
||||
else bd500_.write(address, *value);
|
||||
break;
|
||||
case DiskInterface::Jasmin:
|
||||
if(address >= 0x3f4) {
|
||||
if(isReadOperation(operation)) *value = jasmin_.read(address);
|
||||
if(!isWriteOperation(operation)) *value = jasmin_.read(address);
|
||||
else jasmin_.write(address, *value);
|
||||
}
|
||||
break;
|
||||
case DiskInterface::Microdisc:
|
||||
switch(address) {
|
||||
case 0x0310: case 0x0311: case 0x0312: case 0x0313:
|
||||
if(isReadOperation(operation)) *value = microdisc_.read(address);
|
||||
if(!isWriteOperation(operation)) *value = microdisc_.read(address);
|
||||
else microdisc_.write(address, *value);
|
||||
break;
|
||||
case 0x314: case 0x315: case 0x316: case 0x317:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_interrupt_request_register();
|
||||
if(!isWriteOperation(operation)) *value = microdisc_.get_interrupt_request_register();
|
||||
else microdisc_.set_control_register(*value);
|
||||
break;
|
||||
case 0x318: case 0x319: case 0x31a: case 0x31b:
|
||||
if(isReadOperation(operation)) *value = microdisc_.get_data_request_register();
|
||||
if(!isWriteOperation(operation)) *value = microdisc_.get_data_request_register();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DiskInterface::Pravetz:
|
||||
if(address >= 0x0320) {
|
||||
if(isReadOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)];
|
||||
if(!isWriteOperation(operation)) *value = pravetz_rom_[pravetz_rom_base_pointer_ + (address & 0xff)];
|
||||
else {
|
||||
switch(address) {
|
||||
case 0x380: case 0x381: case 0x382: case 0x383:
|
||||
@@ -476,13 +476,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
|
||||
} else {
|
||||
flush_diskii();
|
||||
const int disk_value = diskii_.read_address(address);
|
||||
if(isReadOperation(operation) && disk_value != diskii_.DidNotLoad) *value = uint8_t(disk_value);
|
||||
if(!isWriteOperation(operation) && disk_value != diskii_.DidNotLoad) *value = uint8_t(disk_value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(isReadOperation(operation))
|
||||
if(!isWriteOperation(operation))
|
||||
*value = ram_[address];
|
||||
else {
|
||||
if(address >= 0x9800 && address <= 0xc000) update_video();
|
||||
|
||||
@@ -109,7 +109,7 @@ class TypeRecipient: public Typer::Delegate {
|
||||
/// Attaches a typer to this class that will type @c string using @c character_mapper as a source.
|
||||
void add_typer(const std::string &string) {
|
||||
if(!typer_) {
|
||||
typer_ = std::make_unique<Typer>(string, get_typer_delay(), get_typer_frequency(), character_mapper, this);
|
||||
typer_ = std::make_unique<Typer>(string, get_typer_delay(string), get_typer_frequency(), character_mapper, this);
|
||||
} else {
|
||||
typer_->append(string);
|
||||
}
|
||||
@@ -137,7 +137,7 @@ class TypeRecipient: public Typer::Delegate {
|
||||
typer_ = nullptr;
|
||||
}
|
||||
|
||||
virtual HalfCycles get_typer_delay() const { return HalfCycles(0); }
|
||||
virtual HalfCycles get_typer_delay(const std::string &) const { return HalfCycles(0); }
|
||||
virtual HalfCycles get_typer_frequency() const { return HalfCycles(0); }
|
||||
std::unique_ptr<Typer> typer_;
|
||||
|
||||
|
||||
@@ -387,7 +387,7 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
}
|
||||
|
||||
// MARK: - Typer timing
|
||||
HalfCycles get_typer_delay() const final {
|
||||
HalfCycles get_typer_delay(const std::string &) const final {
|
||||
return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@
|
||||
4B3BA0D01D318B44005DD7A7 /* MOS6532Bridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CB1D318B44005DD7A7 /* MOS6532Bridge.mm */; };
|
||||
4B3BA0D11D318B44005DD7A7 /* TestMachine6502.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */; };
|
||||
4B3BF5B01F146265005B6C36 /* CSW.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3BF5AE1F146264005B6C36 /* CSW.cpp */; };
|
||||
4B3F76B925A1635300178AEC /* PowerPCDecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */; };
|
||||
4B3FCC40201EC24200960631 /* MultiMachine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FCC3F201EC24200960631 /* MultiMachine.cpp */; };
|
||||
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */; };
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B448E7F1F1C45A00009ABD6 /* TZX.cpp */; };
|
||||
@@ -849,6 +850,7 @@
|
||||
4BE211DE253E4E4800435408 /* 65C02_no_Rockwell_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4BE211DD253E4E4800435408 /* 65C02_no_Rockwell_test.bin */; };
|
||||
4BE34438238389E10058E78F /* AtariSTVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE34437238389E10058E78F /* AtariSTVideoTests.mm */; };
|
||||
4BE76CF922641ED400ACD6FA /* QLTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE76CF822641ED300ACD6FA /* QLTests.mm */; };
|
||||
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BE8EB6425C750B50040BC40 /* DAT.cpp */; };
|
||||
4BE90FFD22D5864800FB464D /* MacintoshVideoTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */; };
|
||||
4BE9A6B11EDE293000CBCB47 /* zexdoc.com in Resources */ = {isa = PBXBuildFile; fileRef = 4BE9A6B01EDE293000CBCB47 /* zexdoc.com */; };
|
||||
4BEA525E1DF33323007E74F2 /* Tape.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEA525D1DF33323007E74F2 /* Tape.cpp */; };
|
||||
@@ -857,11 +859,20 @@
|
||||
4BEBFB4E2002C4BF000708CC /* MSXDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4B2002C4BF000708CC /* MSXDSK.cpp */; };
|
||||
4BEBFB512002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
|
||||
4BEBFB522002DB30000708CC /* DiskROM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEBFB4F2002DB30000708CC /* DiskROM.cpp */; };
|
||||
4BEDA3BA25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B425B25563000C2DBD /* Decoder.cpp */; };
|
||||
4BEDA3BB25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B425B25563000C2DBD /* Decoder.cpp */; };
|
||||
4BEDA3BC25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B425B25563000C2DBD /* Decoder.cpp */; };
|
||||
4BEDA3BD25B25563000C2DBD /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B625B25563000C2DBD /* README.md */; };
|
||||
4BEDA3BE25B25563000C2DBD /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B625B25563000C2DBD /* README.md */; };
|
||||
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B925B25563000C2DBD /* Decoder.cpp */; };
|
||||
4BEDA3C025B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B925B25563000C2DBD /* Decoder.cpp */; };
|
||||
4BEDA3C125B25563000C2DBD /* Decoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEDA3B925B25563000C2DBD /* Decoder.cpp */; };
|
||||
4BEE0A6F1D72496600532C7B /* Cartridge.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6A1D72496600532C7B /* Cartridge.cpp */; };
|
||||
4BEE0A701D72496600532C7B /* PRG.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE0A6D1D72496600532C7B /* PRG.cpp */; };
|
||||
4BEE149A227FC0EA00133682 /* IWM.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE1498227FC0EA00133682 /* IWM.cpp */; };
|
||||
4BEE1EC022B5E236000A26A6 /* MacGCRTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */; };
|
||||
4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD67DCE209BF27B00AB2146 /* Encoder.cpp */; };
|
||||
4BEE4BD425A26E2B00011BD2 /* x86DecoderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */; };
|
||||
4BEEE6BD20DC72EB003723BF /* CompositeOptions.xib in Resources */ = {isa = PBXBuildFile; fileRef = 4BEEE6BB20DC72EA003723BF /* CompositeOptions.xib */; };
|
||||
4BEF6AAA1D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */; };
|
||||
4BEF6AAC1D35D1C400E73575 /* DPLLTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BEF6AAB1D35D1C400E73575 /* DPLLTests.swift */; };
|
||||
@@ -1074,6 +1085,7 @@
|
||||
4B3BA0CD1D318B44005DD7A7 /* TestMachine6502.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = TestMachine6502.mm; sourceTree = "<group>"; };
|
||||
4B3BF5AE1F146264005B6C36 /* CSW.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSW.cpp; sourceTree = "<group>"; };
|
||||
4B3BF5AF1F146264005B6C36 /* CSW.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = CSW.hpp; sourceTree = "<group>"; };
|
||||
4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PowerPCDecoderTests.mm; sourceTree = "<group>"; };
|
||||
4B3FCC3E201EC24200960631 /* MultiMachine.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = MultiMachine.hpp; sourceTree = "<group>"; };
|
||||
4B3FCC3F201EC24200960631 /* MultiMachine.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MultiMachine.cpp; sourceTree = "<group>"; };
|
||||
4B3FE75C1F3CF68B00448EE4 /* CPM.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CPM.cpp; path = Parsers/CPM.cpp; sourceTree = "<group>"; };
|
||||
@@ -1332,7 +1344,6 @@
|
||||
4B9378E322A199C600973513 /* Audio.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Audio.hpp; sourceTree = "<group>"; };
|
||||
4B95FA9C1F11893B0008E395 /* ZX8081OptionsPanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZX8081OptionsPanel.swift; sourceTree = "<group>"; };
|
||||
4B961408222760E0001A7BF2 /* Screenshot.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Screenshot.hpp; sourceTree = "<group>"; };
|
||||
4B97ADC722C6FD9B00A22A41 /* 68000ArithmeticTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = 68000ArithmeticTests.mm; path = "/Users/thomasharte/Projects/CLK/OSBindings/Mac/Clock SignalTests/68000ArithmeticTests.mm"; sourceTree = "<absolute>"; };
|
||||
4B98A05C1FFAD3F600ADF63B /* CSROMFetcher.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CSROMFetcher.hpp; sourceTree = "<group>"; };
|
||||
4B98A05D1FFAD3F600ADF63B /* CSROMFetcher.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = CSROMFetcher.mm; sourceTree = "<group>"; };
|
||||
4B98A0601FFADCDE00ADF63B /* MSXStaticAnalyserTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MSXStaticAnalyserTests.mm; sourceTree = "<group>"; };
|
||||
@@ -1778,6 +1789,8 @@
|
||||
4BE34437238389E10058E78F /* AtariSTVideoTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AtariSTVideoTests.mm; sourceTree = "<group>"; };
|
||||
4BE76CF822641ED300ACD6FA /* QLTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = QLTests.mm; sourceTree = "<group>"; };
|
||||
4BE845201F2FF7F100A5EA22 /* CRTC6845.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = CRTC6845.hpp; path = 6845/CRTC6845.hpp; sourceTree = "<group>"; };
|
||||
4BE8EB6425C750B50040BC40 /* DAT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DAT.cpp; sourceTree = "<group>"; };
|
||||
4BE8EB6525C750B50040BC40 /* DAT.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = DAT.hpp; sourceTree = "<group>"; };
|
||||
4BE90FFC22D5864800FB464D /* MacintoshVideoTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacintoshVideoTests.mm; sourceTree = "<group>"; };
|
||||
4BE9A6B01EDE293000CBCB47 /* zexdoc.com */ = {isa = PBXFileReference; lastKnownFileType = file; name = zexdoc.com; path = Zexall/zexdoc.com; sourceTree = "<group>"; };
|
||||
4BEA525D1DF33323007E74F2 /* Tape.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Tape.cpp; path = Electron/Tape.cpp; sourceTree = "<group>"; };
|
||||
@@ -1789,6 +1802,13 @@
|
||||
4BEBFB4C2002C4BF000708CC /* MSXDSK.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MSXDSK.hpp; sourceTree = "<group>"; };
|
||||
4BEBFB4F2002DB30000708CC /* DiskROM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DiskROM.cpp; path = MSX/DiskROM.cpp; sourceTree = "<group>"; };
|
||||
4BEBFB502002DB30000708CC /* DiskROM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = DiskROM.hpp; path = MSX/DiskROM.hpp; sourceTree = "<group>"; };
|
||||
4BEDA3B425B25563000C2DBD /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = "<group>"; };
|
||||
4BEDA3B525B25563000C2DBD /* Decoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Decoder.hpp; sourceTree = "<group>"; };
|
||||
4BEDA3B625B25563000C2DBD /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
4BEDA3B825B25563000C2DBD /* Decoder.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Decoder.hpp; sourceTree = "<group>"; };
|
||||
4BEDA3B925B25563000C2DBD /* Decoder.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Decoder.cpp; sourceTree = "<group>"; };
|
||||
4BEDA3D225B257F2000C2DBD /* Instruction.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Instruction.hpp; sourceTree = "<group>"; };
|
||||
4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Instruction.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6A1D72496600532C7B /* Cartridge.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Cartridge.cpp; sourceTree = "<group>"; };
|
||||
4BEE0A6B1D72496600532C7B /* Cartridge.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Cartridge.hpp; sourceTree = "<group>"; };
|
||||
4BEE0A6D1D72496600532C7B /* PRG.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PRG.cpp; sourceTree = "<group>"; };
|
||||
@@ -1796,6 +1816,7 @@
|
||||
4BEE1498227FC0EA00133682 /* IWM.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = IWM.cpp; sourceTree = "<group>"; };
|
||||
4BEE1499227FC0EA00133682 /* IWM.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = IWM.hpp; sourceTree = "<group>"; };
|
||||
4BEE1EBF22B5E236000A26A6 /* MacGCRTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MacGCRTests.mm; sourceTree = "<group>"; };
|
||||
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = x86DecoderTests.mm; sourceTree = "<group>"; };
|
||||
4BEEE6BC20DC72EA003723BF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = "Clock Signal/Base.lproj/CompositeOptions.xib"; sourceTree = SOURCE_ROOT; };
|
||||
4BEF6AA81D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigitalPhaseLockedLoopBridge.h; sourceTree = "<group>"; };
|
||||
4BEF6AA91D35CE9E00E73575 /* DigitalPhaseLockedLoopBridge.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DigitalPhaseLockedLoopBridge.mm; sourceTree = "<group>"; };
|
||||
@@ -2715,6 +2736,8 @@
|
||||
children = (
|
||||
4B74CF7F2312FA9C00500CE8 /* HFV.hpp */,
|
||||
4B74CF802312FA9C00500CE8 /* HFV.cpp */,
|
||||
4BE8EB6425C750B50040BC40 /* DAT.cpp */,
|
||||
4BE8EB6525C750B50040BC40 /* DAT.hpp */,
|
||||
);
|
||||
path = Formats;
|
||||
sourceTree = "<group>";
|
||||
@@ -3385,6 +3408,7 @@
|
||||
4B31B88E1FBFBCD800C140D5 /* Configurable */,
|
||||
4B055A761FAE78210060FFFF /* Frameworks */,
|
||||
4B86E2581F8C628F006FAA45 /* Inputs */,
|
||||
4BEDA3B225B25563000C2DBD /* InstructionSets */,
|
||||
4BB73EDC1B587CA500552FC2 /* Machines */,
|
||||
4B7BA03C23D55E7900B98D9E /* Numeric */,
|
||||
4B366DFD1B5C165F0026627B /* Outputs */,
|
||||
@@ -3442,7 +3466,7 @@
|
||||
children = (
|
||||
4B85322922778E4200F26553 /* Comparative68000.hpp */,
|
||||
4B90467222C6FA31000E2074 /* TestRunner68000.hpp */,
|
||||
4B97ADC722C6FD9B00A22A41 /* 68000ArithmeticTests.mm */,
|
||||
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
|
||||
4B9D0C4A22C7D70900DE1AD3 /* 68000BCDTests.mm */,
|
||||
4B90467322C6FADD000E2074 /* 68000BitwiseTests.mm */,
|
||||
4B680CE123A5553100451D43 /* 68000ComparativeTests.mm */,
|
||||
@@ -3461,9 +3485,11 @@
|
||||
4BC0CB272446BC7B00A79DBB /* OPLTests.mm */,
|
||||
4B121F9A1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm */,
|
||||
4BD4A8CF1E077FD20020D856 /* PCMTrackTests.mm */,
|
||||
4B3F76B825A1635300178AEC /* PowerPCDecoderTests.mm */,
|
||||
4BE76CF822641ED300ACD6FA /* QLTests.mm */,
|
||||
4B2AF8681E513FC20027EE29 /* TIATests.mm */,
|
||||
4B1D08051E0F7A1100763741 /* TimeTests.mm */,
|
||||
4BEE4BD325A26E2B00011BD2 /* x86DecoderTests.mm */,
|
||||
4BB73EB81B587A5100552FC2 /* Info.plist */,
|
||||
4BC9E1ED1D23449A003FCEE4 /* 6502InterruptTests.swift */,
|
||||
4B92EAC91B7C112B00246143 /* 6502TimingTests.swift */,
|
||||
@@ -3485,7 +3511,6 @@
|
||||
4BFCA12A1ECBE7C400AC40C1 /* ZexallTests.swift */,
|
||||
4B3BA0C41D318B44005DD7A7 /* Bridges */,
|
||||
4B1414631B588A1100E04248 /* Test Binaries */,
|
||||
4B90467522C6FD6E000E2074 /* 68000ArithmeticTests.mm */,
|
||||
);
|
||||
path = "Clock SignalTests";
|
||||
sourceTree = "<group>";
|
||||
@@ -3907,6 +3932,37 @@
|
||||
name = Zexall;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEDA3B225B25563000C2DBD /* InstructionSets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BEDA3B625B25563000C2DBD /* README.md */,
|
||||
4BEDA3B325B25563000C2DBD /* PowerPC */,
|
||||
4BEDA3B725B25563000C2DBD /* x86 */,
|
||||
);
|
||||
name = InstructionSets;
|
||||
path = ../../InstructionSets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEDA3B325B25563000C2DBD /* PowerPC */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BEDA3B425B25563000C2DBD /* Decoder.cpp */,
|
||||
4BEDA3B525B25563000C2DBD /* Decoder.hpp */,
|
||||
4BEDA3D225B257F2000C2DBD /* Instruction.hpp */,
|
||||
);
|
||||
path = PowerPC;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEDA3B725B25563000C2DBD /* x86 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BEDA3B925B25563000C2DBD /* Decoder.cpp */,
|
||||
4BEDA3B825B25563000C2DBD /* Decoder.hpp */,
|
||||
4BEDA3DB25B2588F000C2DBD /* Instruction.hpp */,
|
||||
);
|
||||
path = x86;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BEE0A691D72496600532C7B /* Cartridge */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -4137,6 +4193,7 @@
|
||||
4B2C45421E3C3896002A2389 /* cartridge.png in Resources */,
|
||||
4BB73EA91B587A5100552FC2 /* Assets.xcassets in Resources */,
|
||||
4B79E4451E3AF38600141F11 /* floppy35.png in Resources */,
|
||||
4BEDA3BD25B25563000C2DBD /* README.md in Resources */,
|
||||
4B55DD8420DF06680043F2E5 /* MachinePicker.xib in Resources */,
|
||||
4BDA00DA22E60EE300AC3CD0 /* ROMRequester.xib in Resources */,
|
||||
4BC5FC3020CDDDEF00410AA0 /* AppleIIOptions.xib in Resources */,
|
||||
@@ -4173,6 +4230,7 @@
|
||||
4BB2994E1B587D8400A49093 /* dexn in Resources */,
|
||||
4BB299971B587D8400A49093 /* nopa in Resources */,
|
||||
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */,
|
||||
4BEDA3BE25B25563000C2DBD /* README.md in Resources */,
|
||||
4BFCA1291ECBE7A700AC40C1 /* zexall.com in Resources */,
|
||||
4BB299521B587D8400A49093 /* eoray in Resources */,
|
||||
4BB299411B587D8400A49093 /* cpyb in Resources */,
|
||||
@@ -4607,6 +4665,7 @@
|
||||
4BD67DD1209BF27B00AB2146 /* Encoder.cpp in Sources */,
|
||||
4B89451F201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B055AA81FAE85EF0060FFFF /* Shifter.cpp in Sources */,
|
||||
4BEDA3BC25B25563000C2DBD /* Decoder.cpp in Sources */,
|
||||
4B8318B422D3E546006DB630 /* DriveSpeedAccumulator.cpp in Sources */,
|
||||
4B055AC81FAE9AFB0060FFFF /* C1540.cpp in Sources */,
|
||||
4B055A8F1FAE85A90060FFFF /* FileHolder.cpp in Sources */,
|
||||
@@ -4641,6 +4700,7 @@
|
||||
4B0F94FF208C1A1600FE41D9 /* NIB.cpp in Sources */,
|
||||
4B0E04EB1FC9E78800F43484 /* CAS.cpp in Sources */,
|
||||
4BB0A65D2045009000FB3688 /* ColecoVision.cpp in Sources */,
|
||||
4BEDA3C125B25563000C2DBD /* Decoder.cpp in Sources */,
|
||||
4BB0A65C2044FD3000FB3688 /* SN76489.cpp in Sources */,
|
||||
4B595FAE2086DFBA0083CAA8 /* AudioToggle.cpp in Sources */,
|
||||
4B055AB91FAE86170060FFFF /* Acorn.cpp in Sources */,
|
||||
@@ -4713,7 +4773,9 @@
|
||||
4B54C0BF1F8D8F450050900F /* Keyboard.cpp in Sources */,
|
||||
4B3FE75E1F3CF68B00448EE4 /* CPM.cpp in Sources */,
|
||||
4B2BFDB21DAEF5FF001A68B8 /* Video.cpp in Sources */,
|
||||
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
||||
4BAE49582032881E004BE78E /* CSZX8081.mm in Sources */,
|
||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */,
|
||||
@@ -4749,6 +4811,7 @@
|
||||
4B45189F1F75FD1C00926311 /* AcornADF.cpp in Sources */,
|
||||
4B7BA03023C2B19C00B98D9E /* Jasmin.cpp in Sources */,
|
||||
4B7136911F789C93008B8ED9 /* SegmentParser.cpp in Sources */,
|
||||
4BEDA3BA25B25563000C2DBD /* Decoder.cpp in Sources */,
|
||||
4B4518A21F75FD1C00926311 /* G64.cpp in Sources */,
|
||||
4B89452C201967B4007DE474 /* Tape.cpp in Sources */,
|
||||
4B448E811F1C45A00009ABD6 /* TZX.cpp in Sources */,
|
||||
@@ -4877,6 +4940,7 @@
|
||||
4B778F4223A5F1A70000D260 /* MemoryFuzzer.cpp in Sources */,
|
||||
4B778F0123A5EBA00000D260 /* MacintoshIMG.cpp in Sources */,
|
||||
4BFF1D3D2235C3C100838EA1 /* EmuTOSTests.mm in Sources */,
|
||||
4B3F76B925A1635300178AEC /* PowerPCDecoderTests.mm in Sources */,
|
||||
4B778F0A23A5EC150000D260 /* TapePRG.cpp in Sources */,
|
||||
4B778F0823A5EC150000D260 /* CSW.cpp in Sources */,
|
||||
4B778F5323A5F23F0000D260 /* SerialBus.cpp in Sources */,
|
||||
@@ -4933,6 +4997,7 @@
|
||||
4B778F4823A5F1E70000D260 /* StaticAnalyser.cpp in Sources */,
|
||||
4B92EACA1B7C112B00246143 /* 6502TimingTests.swift in Sources */,
|
||||
4B778F2823A5EEF80000D260 /* Cartridge.cpp in Sources */,
|
||||
4BEDA3C025B25563000C2DBD /* Decoder.cpp in Sources */,
|
||||
4B778F4C23A5F2090000D260 /* StaticAnalyser.cpp in Sources */,
|
||||
4B778F2623A5EE350000D260 /* Acorn.cpp in Sources */,
|
||||
4B778F5B23A5F2DE0000D260 /* Tape.cpp in Sources */,
|
||||
@@ -4943,6 +5008,7 @@
|
||||
4BEE1EC122B5E2FD000A26A6 /* Encoder.cpp in Sources */,
|
||||
4B121F9B1E06293F00BFDA12 /* PCMSegmentEventSourceTests.mm in Sources */,
|
||||
4B778EFF23A5EB940000D260 /* D64.cpp in Sources */,
|
||||
4BEDA3BB25B25563000C2DBD /* Decoder.cpp in Sources */,
|
||||
4B778F2423A5EDEE0000D260 /* PRG.cpp in Sources */,
|
||||
4B778F5A23A5F2D50000D260 /* 6502.cpp in Sources */,
|
||||
4B778F6223A5F35F0000D260 /* File.cpp in Sources */,
|
||||
@@ -5001,6 +5067,7 @@
|
||||
4B778F2123A5EDD50000D260 /* TrackSerialiser.cpp in Sources */,
|
||||
4B049CDD1DA3C82F00322067 /* BCDTest.swift in Sources */,
|
||||
4B778F3923A5F11C0000D260 /* Shifter.cpp in Sources */,
|
||||
4BEE4BD425A26E2B00011BD2 /* x86DecoderTests.mm in Sources */,
|
||||
4B778F3623A5F1040000D260 /* Target.cpp in Sources */,
|
||||
4B1D08061E0F7A1100763741 /* TimeTests.mm in Sources */,
|
||||
4B778F3D23A5F1750000D260 /* ncr5380.cpp in Sources */,
|
||||
@@ -5175,7 +5242,7 @@
|
||||
"$(USER_LIBRARY_DIR)/Frameworks",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -5198,7 +5265,8 @@
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GCC_OPTIMIZATION_LEVEL = 2;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = NDEBUG;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.10;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
@@ -5252,7 +5320,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@@ -5305,7 +5373,7 @@
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_PARAMETER = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
@@ -5327,7 +5395,6 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = DV3346VVUN;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -5373,7 +5440,6 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = DV3346VVUN;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
@@ -5416,7 +5482,6 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
INFOPLIST_FILE = "Clock SignalTests/Info.plist";
|
||||
@@ -5440,7 +5505,6 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Clock Signal/Clock Signal.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = 2;
|
||||
@@ -5458,7 +5522,6 @@
|
||||
4BB73ECD1B587A5100552FC2 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||
INFOPLIST_FILE = "Clock SignalUITests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
@@ -5473,7 +5536,6 @@
|
||||
4BB73ECE1B587A5100552FC2 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
DEVELOPMENT_TEAM = CP2SKEB3XT;
|
||||
INFOPLIST_FILE = "Clock SignalUITests/Info.plist";
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks";
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
disableMainThreadChecker = "YES"
|
||||
@@ -58,8 +58,16 @@
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Macintosh/MusicWorks 0.42.image""
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "/Users/thomasharte/Library/Mobile\ Documents/com\~apple\~CloudDocs/Desktop/Soft/Apple\ II/Keplermatik.dsk"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "/Users/thomasharte/Library/Mobile\ Documents/com\~apple\~CloudDocs/Desktop/Soft/Apple\ II/WOZs/Prince\ of\ Persia\ side\ A.woz"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--volume=0.001"
|
||||
isEnabled = "NO">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097.2"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -21,15 +21,15 @@
|
||||
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="80" y="250" width="600" height="450"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="3840" height="2137"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
|
||||
<value key="minSize" type="size" width="228" height="171"/>
|
||||
<view key="contentView" id="gIp-Ho-8D9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<openGLView hidden="YES" wantsLayer="YES" useAuxiliaryDepthBufferStencil="NO" allowOffline="YES" wantsBestResolutionOpenGLSurface="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSScanTargetView">
|
||||
<view hidden="YES" wantsLayer="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DEG-fq-cjd" customClass="CSScanTargetView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="450"/>
|
||||
</openGLView>
|
||||
</view>
|
||||
<box hidden="YES" boxType="custom" cornerRadius="4" title="Box" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4ap-Gi-2AO">
|
||||
<rect key="frame" x="150" y="20" width="300" height="48"/>
|
||||
<view key="contentView" id="gwO-Ty-LCX">
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
@class CSApplication;
|
||||
|
||||
@protocol CSApplicationEventDelegate
|
||||
@protocol CSApplicationEventDelegate <NSObject>
|
||||
- (BOOL)application:(nonnull CSApplication *)application shouldSendEvent:(nonnull NSEvent *)event;
|
||||
@end
|
||||
|
||||
|
||||
@@ -88,14 +88,30 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
override func close() {
|
||||
// Close any dangling sheets.
|
||||
//
|
||||
// Be warned: in 11.0 at least, if there are any panels then posting the endSheet request
|
||||
// will defer the close(), and close() will be called again at the end of that animation.
|
||||
//
|
||||
// So: MAKE SURE IT'S SAFE TO ENTER THIS FUNCTION TWICE. Hence the non-assumption here about
|
||||
// any windows still existing.
|
||||
if let window = self.windowControllers.first?.window {
|
||||
for sheet in window.sheets {
|
||||
window.endSheet(sheet)
|
||||
}
|
||||
}
|
||||
|
||||
// Stop the machine, if any.
|
||||
machine?.stop()
|
||||
|
||||
// Dismiss panels.
|
||||
activityPanel?.setIsVisible(false)
|
||||
activityPanel = nil
|
||||
|
||||
optionsPanel?.setIsVisible(false)
|
||||
optionsPanel = nil
|
||||
|
||||
// End the update cycle.
|
||||
actionLock.lock()
|
||||
drawLock.lock()
|
||||
machine = nil
|
||||
@@ -103,6 +119,7 @@ class MachineDocument:
|
||||
actionLock.unlock()
|
||||
drawLock.unlock()
|
||||
|
||||
// Let the document controller do its thing.
|
||||
super.close()
|
||||
}
|
||||
|
||||
@@ -129,10 +146,7 @@ class MachineDocument:
|
||||
} else {
|
||||
// Store the selected machine and list of missing ROMs, and
|
||||
// show the missing ROMs dialogue.
|
||||
self.missingROMs = []
|
||||
for untypedMissingROM in missingROMs {
|
||||
self.missingROMs.append(untypedMissingROM as! CSMissingROM)
|
||||
}
|
||||
self.missingROMs = missingROMs.map({$0 as! CSMissingROM})
|
||||
|
||||
requestRoms()
|
||||
}
|
||||
@@ -144,35 +158,37 @@ class MachineDocument:
|
||||
private var interactionMode: InteractionMode = .notStarted
|
||||
|
||||
// Attempting to show a sheet before the window is visible (such as when the NIB is loaded) results in
|
||||
// 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.
|
||||
// a sheet mysteriously floating on its own. For now, use windowDidUpdate as a proxy to check whether
|
||||
// the window is visible.
|
||||
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.
|
||||
if self.machine != nil {
|
||||
self.interactionMode = .showingMachine
|
||||
setupMachineOutput()
|
||||
return
|
||||
if self.windowControllers.count > 0, let window = self.windowControllers[0].window, window.isVisible {
|
||||
// Grab the regular window title, if it's not already stored.
|
||||
if self.unadornedWindowTitle.count == 0 {
|
||||
self.unadornedWindowTitle = window.title
|
||||
}
|
||||
|
||||
// If a machine has been picked but is not showing, there must be ROMs missing.
|
||||
if self.machineDescription != nil {
|
||||
self.interactionMode = .showingROMRequester
|
||||
requestRoms()
|
||||
return
|
||||
}
|
||||
// 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.
|
||||
if self.machine != nil {
|
||||
self.interactionMode = .showingMachine
|
||||
setupMachineOutput()
|
||||
return
|
||||
}
|
||||
|
||||
// If a machine hasn't even been picked yet, show the machine picker.
|
||||
self.interactionMode = .showingMachinePicker
|
||||
Bundle.main.loadNibNamed("MachinePicker", owner: self, topLevelObjects: nil)
|
||||
self.machinePicker?.establishStoredOptions()
|
||||
self.windowControllers[0].window?.beginSheet(self.machinePickerPanel!, completionHandler: nil)
|
||||
// If a machine has been picked but is not showing, there must be ROMs missing.
|
||||
if self.machineDescription != nil {
|
||||
self.interactionMode = .showingROMRequester
|
||||
requestRoms()
|
||||
return
|
||||
}
|
||||
|
||||
// If a machine hasn't even been picked yet, show the machine picker.
|
||||
self.interactionMode = .showingMachinePicker
|
||||
Bundle.main.loadNibNamed("MachinePicker", owner: self, topLevelObjects: nil)
|
||||
self.machinePicker?.establishStoredOptions()
|
||||
window.beginSheet(self.machinePickerPanel!, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +249,7 @@ class MachineDocument:
|
||||
// TODO: this needs to be threadsafe. FIX!
|
||||
let maximumSamplingRate = CSAudioQueue.preferredSamplingRate()
|
||||
let selectedSamplingRate = Float64(self.machine.idealSamplingRate(from: NSRange(location: 0, length: NSInteger(maximumSamplingRate))))
|
||||
let isStereo = self.machine.isStereo()
|
||||
let isStereo = self.machine.isStereo
|
||||
if selectedSamplingRate > 0 {
|
||||
// [Re]create the audio queue only if necessary.
|
||||
if self.audioQueue == nil || self.audioQueue.samplingRate != selectedSamplingRate {
|
||||
@@ -265,9 +281,7 @@ class MachineDocument:
|
||||
/// Delegate message to receive drag and drop files.
|
||||
final func scanTargetView(_ view: CSScanTargetView, didReceiveFileAt URL: URL) {
|
||||
let mediaSet = CSMediaSet(fileAt: URL)
|
||||
if let mediaSet = mediaSet {
|
||||
mediaSet.apply(to: self.machine)
|
||||
}
|
||||
mediaSet.apply(to: self.machine)
|
||||
}
|
||||
|
||||
/// Action for the insert menu command; displays an NSOpenPanel and then segues into the same process
|
||||
@@ -279,9 +293,7 @@ class MachineDocument:
|
||||
if response == .OK {
|
||||
for url in openPanel.urls {
|
||||
let mediaSet = CSMediaSet(fileAt: url)
|
||||
if let mediaSet = mediaSet {
|
||||
mediaSet.apply(to: self.machine)
|
||||
}
|
||||
mediaSet.apply(to: self.machine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,6 +552,26 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>dat</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Electron/BBC Hard Disk Image</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
||||
@@ -10,17 +10,35 @@
|
||||
|
||||
@import IOKit;
|
||||
#include <IOKit/hid/IOHIDLib.h>
|
||||
@import GameController;
|
||||
|
||||
#pragma mark - CSJoystickButton
|
||||
|
||||
@implementation CSJoystickButton {
|
||||
@package
|
||||
bool _isPressed;
|
||||
}
|
||||
|
||||
- (instancetype)initWithIndex:(NSInteger)index {
|
||||
if (self = [super init]) {
|
||||
_index = index;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface CSIOJoystickButton: CSJoystickButton
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSIOJoystickButton {
|
||||
IOHIDElementRef _element;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(IOHIDElementRef)element index:(NSInteger)index {
|
||||
self = [super init];
|
||||
self = [super initWithIndex:index];
|
||||
if(self) {
|
||||
_index = index;
|
||||
_element = (IOHIDElementRef)CFRetain(element);
|
||||
}
|
||||
return self;
|
||||
@@ -31,7 +49,7 @@
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
|
||||
return [NSString stringWithFormat:@"<CSIOJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
|
||||
}
|
||||
|
||||
- (IOHIDElementRef)element {
|
||||
@@ -47,14 +65,33 @@
|
||||
#pragma mark - CSJoystickAxis
|
||||
|
||||
@implementation CSJoystickAxis {
|
||||
@package
|
||||
float _position;
|
||||
}
|
||||
|
||||
- (instancetype)initWithType:(CSJoystickAxisType)type
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_type = type;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface CSIOJoystickAxis: CSJoystickAxis
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSIOJoystickAxis {
|
||||
IOHIDElementRef _element;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElement:(IOHIDElementRef)element type:(CSJoystickAxisType)type {
|
||||
self = [super init];
|
||||
self = [super initWithType:type];
|
||||
if(self) {
|
||||
_element = (IOHIDElementRef)CFRetain(element);
|
||||
_type = type;
|
||||
_position = 0.5f;
|
||||
}
|
||||
return self;
|
||||
@@ -65,7 +102,7 @@
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
|
||||
return [NSString stringWithFormat:@"<CSIOJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
|
||||
}
|
||||
|
||||
- (IOHIDElementRef)element {
|
||||
@@ -81,6 +118,18 @@
|
||||
#pragma mark - CSJoystickHat
|
||||
|
||||
@implementation CSJoystickHat {
|
||||
@package
|
||||
CSJoystickHatDirection _direction;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface CSIOJoystickHat: CSJoystickHat
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation CSIOJoystickHat {
|
||||
IOHIDElementRef _element;
|
||||
}
|
||||
|
||||
@@ -97,7 +146,7 @@
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSJoystickHat: %p>; direction %ld", self, (long)self.direction];
|
||||
return [NSString stringWithFormat:@"<CSIOJoystickHat: %p>; direction %ld", self, (long)self.direction];
|
||||
}
|
||||
|
||||
- (IOHIDElementRef)element {
|
||||
@@ -113,6 +162,24 @@
|
||||
#pragma mark - CSJoystick
|
||||
|
||||
@implementation CSJoystick {
|
||||
@package
|
||||
NSArray<CSJoystickButton *> *_buttons;
|
||||
NSArray<CSJoystickAxis *> *_axes;
|
||||
NSArray<CSJoystickHat *> *_hats;
|
||||
}
|
||||
|
||||
- (void)update
|
||||
{
|
||||
//subclass!
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface CSIOJoystick: CSJoystick
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSIOJoystick {
|
||||
IOHIDDeviceRef _device;
|
||||
}
|
||||
|
||||
@@ -142,12 +209,12 @@
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
|
||||
return [NSString stringWithFormat:@"<CSIOJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
|
||||
}
|
||||
|
||||
- (void)update {
|
||||
// Update buttons.
|
||||
for(CSJoystickButton *button in _buttons) {
|
||||
for(CSIOJoystickButton *button in _buttons) {
|
||||
IOHIDValueRef value;
|
||||
if(IOHIDDeviceGetValue(_device, button.element, &value) == kIOReturnSuccess) {
|
||||
// Some pressure-sensitive buttons return values greater than 1 for hard presses,
|
||||
@@ -157,7 +224,7 @@
|
||||
}
|
||||
|
||||
// Update hats.
|
||||
for(CSJoystickHat *hat in _hats) {
|
||||
for(CSIOJoystickHat *hat in _hats) {
|
||||
IOHIDValueRef value;
|
||||
if(IOHIDDeviceGetValue(_device, hat.element, &value) == kIOReturnSuccess) {
|
||||
// Hats report a direction, which is either one of eight or one of four.
|
||||
@@ -181,7 +248,7 @@
|
||||
}
|
||||
|
||||
// Update axes.
|
||||
for(CSJoystickAxis *axis in _axes) {
|
||||
for(CSIOJoystickAxis *axis in _axes) {
|
||||
IOHIDValueRef value;
|
||||
if(IOHIDDeviceGetValue(_device, axis.element, &value) == kIOReturnSuccess) {
|
||||
const CFIndex integerValue = IOHIDValueGetIntegerValue(value) - IOHIDElementGetLogicalMin(axis.element);
|
||||
@@ -197,11 +264,157 @@
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - GameController subclasses
|
||||
|
||||
API_AVAILABLE(macos(11.0))
|
||||
@interface CSGCJoystickHat: CSJoystickHat
|
||||
@property (readonly, strong) GCDeviceDirectionPad *directionPad;
|
||||
@end
|
||||
|
||||
API_AVAILABLE(macos(11.0))
|
||||
@interface CSGCJoystickAxis: CSJoystickAxis
|
||||
@property (readonly, strong) GCDeviceAxisInput *axis;
|
||||
@end
|
||||
|
||||
API_AVAILABLE(macos(11.0))
|
||||
@interface CSGCJoystickButton: CSJoystickButton
|
||||
@property (readonly, strong) GCDeviceButtonInput *button;
|
||||
@end
|
||||
|
||||
API_AVAILABLE(macos(11.0))
|
||||
@interface CSGCJoystick: CSJoystick
|
||||
@property (readonly, strong) GCController *device;
|
||||
@end
|
||||
|
||||
@implementation CSGCJoystickHat
|
||||
|
||||
- (instancetype)initWithDirectionPad:(GCDeviceDirectionPad*)dPad {
|
||||
if (self = [super init]) {
|
||||
_directionPad = dPad;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSGCJoystickHat: %p>; direction %ld", self, (long)self.direction];
|
||||
}
|
||||
|
||||
- (void)setDirection:(CSJoystickHatDirection)direction {
|
||||
_direction = direction;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSGCJoystickAxis
|
||||
|
||||
- (instancetype)initWithAxis:(GCDeviceAxisInput*)element type:(CSJoystickAxisType)type {
|
||||
self = [super initWithType:type];
|
||||
if(self) {
|
||||
_axis = element;
|
||||
_position = 0.5f;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSGCJoystickAxis: %p>; type %d, value %0.2f", self, (int)self.type, self.position];
|
||||
}
|
||||
|
||||
- (void)setPosition:(float)position {
|
||||
_position = position;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSGCJoystickButton
|
||||
|
||||
- (instancetype)initWithButton:(GCDeviceButtonInput*)element index:(NSInteger)index {
|
||||
self = [super initWithIndex:index];
|
||||
if(self) {
|
||||
_button = element;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSGCJoystickButton: %p>; button %ld, %@", self, (long)self.index, self.isPressed ? @"pressed" : @"released"];
|
||||
}
|
||||
|
||||
- (void)setIsPressed:(bool)isPressed {
|
||||
_isPressed = isPressed;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSGCJoystick
|
||||
|
||||
- (instancetype)initWithButtons:(NSArray<CSJoystickButton *> *)buttons
|
||||
axes:(NSArray<CSJoystickAxis *> *)axes
|
||||
hats:(NSArray<CSJoystickHat *> *)hats
|
||||
device:(GCController*)device {
|
||||
if (self = [super init]) {
|
||||
// Sort buttons by index.
|
||||
_buttons = [buttons sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"index" ascending:YES]]];
|
||||
|
||||
// Sort axes by enum value.
|
||||
_axes = [axes sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"type" ascending:YES]]];
|
||||
|
||||
// Hats have no guaranteed ordering.
|
||||
_hats = hats;
|
||||
|
||||
// Keep hold of the device.
|
||||
_device = device;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)update {
|
||||
// Update buttons.
|
||||
for(CSGCJoystickButton *button in _buttons) {
|
||||
// This assumes that the values provided by GCDeviceButtonInput are
|
||||
// digital. This might not always be the case.
|
||||
button.isPressed = button.button.pressed;
|
||||
}
|
||||
for(CSGCJoystickAxis *axis in _axes) {
|
||||
float val = axis.axis.value;
|
||||
val += 1;
|
||||
val /= 2;
|
||||
axis.position = val;
|
||||
}
|
||||
for(CSGCJoystickHat *hat in _hats) {
|
||||
// This assumes that the values provided by GCDeviceDirectionPad are
|
||||
// digital. this might not always be the case.
|
||||
CSJoystickHatDirection hatDir = 0;
|
||||
if (hat.directionPad.down.pressed) {
|
||||
hatDir |= CSJoystickHatDirectionDown;
|
||||
}
|
||||
if (hat.directionPad.up.pressed) {
|
||||
hatDir |= CSJoystickHatDirectionUp;
|
||||
}
|
||||
if (hat.directionPad.left.pressed) {
|
||||
hatDir |= CSJoystickHatDirectionLeft;
|
||||
}
|
||||
if (hat.directionPad.right.pressed) {
|
||||
hatDir |= CSJoystickHatDirectionRight;
|
||||
}
|
||||
// There shouldn't be any conflicting directions.
|
||||
hat.direction = hatDir;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"<CSGCJoystick: %p>; buttons %@, axes %@, hats %@", self, self.buttons, self.axes, self.hats];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - CSJoystickManager
|
||||
|
||||
@interface CSJoystickManager ()
|
||||
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
||||
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender;
|
||||
- (void)controllerDidConnect:(NSNotification *)note API_AVAILABLE(macos(11.0));
|
||||
- (void)controllerDidDisconnect:(NSNotification *)note API_AVAILABLE(macos(11.0));
|
||||
@end
|
||||
|
||||
static void DeviceMatched(void *context, IOReturn result, void *sender, IOHIDDeviceRef device) {
|
||||
@@ -235,6 +448,10 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
|
||||
IOHIDManagerRegisterDeviceMatchingCallback(_hidManager, DeviceMatched, (__bridge void *)self);
|
||||
IOHIDManagerRegisterDeviceRemovalCallback(_hidManager, DeviceRemoved, (__bridge void *)self);
|
||||
IOHIDManagerScheduleWithRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
|
||||
if (@available(macOS 11.0, *)) {
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidConnect:) name:GCControllerDidConnectNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(controllerDidDisconnect:) name:GCControllerDidDisconnectNotification object:nil];
|
||||
}
|
||||
|
||||
if(IOHIDManagerOpen(_hidManager, kIOHIDOptionsTypeNone) != kIOReturnSuccess) {
|
||||
NSLog(@"Failed to open HID manager");
|
||||
@@ -246,15 +463,80 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)controllerDidConnect:(NSNotification *)note {
|
||||
GCController *controller = note.object;
|
||||
|
||||
// Double check this joystick isn't already known.
|
||||
for(CSGCJoystick *joystick in _joysticks) {
|
||||
if (![joystick isKindOfClass:[CSGCJoystick class]]) {
|
||||
continue;
|
||||
}
|
||||
if([joystick.device isEqual:controller]) return;
|
||||
}
|
||||
|
||||
// Prepare to collate a list of buttons, axes and hats for the new device.
|
||||
NSMutableArray<CSJoystickButton *> *buttons = [[NSMutableArray alloc] init];
|
||||
NSMutableArray<CSJoystickAxis *> *axes = [[NSMutableArray alloc] init];
|
||||
NSMutableArray<CSJoystickHat *> *hats = [[NSMutableArray alloc] init];
|
||||
|
||||
if (controller.extendedGamepad) {
|
||||
GCExtendedGamepad *gp = controller.extendedGamepad;
|
||||
// Let's go a b x y
|
||||
// 1 2 3 4
|
||||
[buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonA index:1]];
|
||||
[buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonB index:2]];
|
||||
[buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonX index:3]];
|
||||
[buttons addObject:[[CSGCJoystickButton alloc] initWithButton:gp.buttonY index:4]];
|
||||
|
||||
[hats addObject:[[CSGCJoystickHat alloc] initWithDirectionPad:gp.dpad]];
|
||||
|
||||
[axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.leftThumbstick.xAxis type:CSJoystickAxisTypeX]];
|
||||
[axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.leftThumbstick.yAxis type:CSJoystickAxisTypeY]];
|
||||
[axes addObject:[[CSGCJoystickAxis alloc] initWithAxis:gp.rightThumbstick.xAxis type:CSJoystickAxisTypeZ]];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add this joystick to the list.
|
||||
[_joysticks addObject:[[CSGCJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:controller]];
|
||||
}
|
||||
|
||||
- (void)controllerDidDisconnect:(NSNotification *)note {
|
||||
GCController *controller = note.object;
|
||||
|
||||
// If this joystick was recorded, remove it.
|
||||
for(CSGCJoystick *joystick in [_joysticks copy]) {
|
||||
if (![joystick isKindOfClass:[CSGCJoystick class]]) {
|
||||
continue;
|
||||
}
|
||||
if([joystick.device isEqual:controller]) {
|
||||
[_joysticks removeObject:joystick];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
IOHIDManagerUnscheduleFromRunLoop(_hidManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
|
||||
IOHIDManagerClose(_hidManager, kIOHIDOptionsTypeNone);
|
||||
CFRelease(_hidManager);
|
||||
if (@available(macOS 11.0, *)) {
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidConnectNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] removeObserver:self name:GCControllerDidDisconnectNotification object:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)deviceMatched:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
if ([GCController supportsHIDDevice:device]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Double check this joystick isn't already known.
|
||||
for(CSJoystick *joystick in _joysticks) {
|
||||
for(CSIOJoystick *joystick in _joysticks) {
|
||||
if (![joystick isKindOfClass:[CSIOJoystick class]]) {
|
||||
continue;
|
||||
}
|
||||
if(joystick.device == device) return;
|
||||
}
|
||||
|
||||
@@ -279,7 +561,7 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
|
||||
case kIOHIDElementTypeInput_Button: {
|
||||
// Add a button; pretty easy stuff. 'Usage' provides a button index.
|
||||
const uint32_t usage = IOHIDElementGetUsage(element);
|
||||
[buttons addObject:[[CSJoystickButton alloc] initWithElement:element index:usage]];
|
||||
[buttons addObject:[[CSIOJoystickButton alloc] initWithElement:element index:usage]];
|
||||
} break;
|
||||
|
||||
case kIOHIDElementTypeInput_Misc:
|
||||
@@ -296,25 +578,28 @@ static void DeviceRemoved(void *context, IOReturn result, void *sender, IOHIDDev
|
||||
|
||||
// A hatswitch is a multi-directional control all of its own.
|
||||
case kHIDUsage_GD_Hatswitch:
|
||||
[hats addObject:[[CSJoystickHat alloc] initWithElement:element]];
|
||||
[hats addObject:[[CSIOJoystickHat alloc] initWithElement:element]];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add the axis; if it was a hat switch or unrecognised then the code doesn't
|
||||
// reach here.
|
||||
[axes addObject:[[CSJoystickAxis alloc] initWithElement:element type:axisType]];
|
||||
[axes addObject:[[CSIOJoystickAxis alloc] initWithElement:element type:axisType]];
|
||||
} break;
|
||||
}
|
||||
}
|
||||
CFRelease(elements);
|
||||
|
||||
// Add this joystick to the list.
|
||||
[_joysticks addObject:[[CSJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]];
|
||||
[_joysticks addObject:[[CSIOJoystick alloc] initWithButtons:buttons axes:axes hats:hats device:device]];
|
||||
}
|
||||
|
||||
- (void)deviceRemoved:(IOHIDDeviceRef)device result:(IOReturn)result sender:(void *)sender {
|
||||
// If this joystick was recorded, remove it.
|
||||
for(CSJoystick *joystick in [_joysticks copy]) {
|
||||
for(CSIOJoystick *joystick in [_joysticks copy]) {
|
||||
if (![joystick isKindOfClass:[CSIOJoystick class]]) {
|
||||
continue;
|
||||
}
|
||||
if(joystick.device == device) {
|
||||
[_joysticks removeObject:joystick];
|
||||
return;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#import "CSStaticAnalyser.h"
|
||||
|
||||
@class CSMachine;
|
||||
@protocol CSMachineDelegate
|
||||
@protocol CSMachineDelegate <NSObject>
|
||||
- (void)machineSpeakerDidChangeInputClock:(nonnull CSMachine *)machine;
|
||||
- (void)machine:(nonnull CSMachine *)machine led:(nonnull NSString *)led didChangeToLit:(BOOL)isLit;
|
||||
- (void)machine:(nonnull CSMachine *)machine ledShouldBlink:(nonnull NSString *)led;
|
||||
@@ -59,7 +59,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range;
|
||||
- (BOOL)isStereo;
|
||||
@property (readonly, getter=isStereo) BOOL stereo;
|
||||
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize stereo:(BOOL)stereo;
|
||||
|
||||
- (void)setView:(nullable CSScanTargetView *)view aspectRatio:(float)aspectRatio;
|
||||
|
||||
@@ -74,7 +74,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
__unsafe_unretained CSMachine *machine;
|
||||
};
|
||||
|
||||
@interface CSMissingROM (Mutability)
|
||||
@interface CSMissingROM (/*Mutability*/)
|
||||
@property (nonatomic, nonnull, copy) NSString *machineName;
|
||||
@property (nonatomic, nonnull, copy) NSString *fileName;
|
||||
@property (nonatomic, nullable, copy) NSString *descriptiveName;
|
||||
@@ -82,53 +82,13 @@ struct ActivityObserver: public Activity::Observer {
|
||||
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
|
||||
@end
|
||||
|
||||
@implementation CSMissingROM {
|
||||
NSString *_machineName;
|
||||
NSString *_fileName;
|
||||
NSString *_descriptiveName;
|
||||
NSUInteger _size;
|
||||
NSArray<NSNumber *> *_crc32s;
|
||||
}
|
||||
@implementation CSMissingROM
|
||||
|
||||
- (NSString *)machineName {
|
||||
return _machineName;
|
||||
}
|
||||
|
||||
- (void)setMachineName:(NSString *)machineName {
|
||||
_machineName = [machineName copy];
|
||||
}
|
||||
|
||||
- (NSString *)fileName {
|
||||
return _fileName;
|
||||
}
|
||||
|
||||
- (void)setFileName:(NSString *)fileName {
|
||||
_fileName = [fileName copy];
|
||||
}
|
||||
|
||||
- (NSString *)descriptiveName {
|
||||
return _descriptiveName;
|
||||
}
|
||||
|
||||
- (void)setDescriptiveName:(NSString *)descriptiveName {
|
||||
_descriptiveName = [descriptiveName copy];
|
||||
}
|
||||
|
||||
- (NSUInteger)size {
|
||||
return _size;
|
||||
}
|
||||
|
||||
- (void)setSize:(NSUInteger)size {
|
||||
_size = size;
|
||||
}
|
||||
|
||||
- (NSArray<NSNumber *> *)crc32s {
|
||||
return _crc32s;
|
||||
}
|
||||
|
||||
- (void)setCrc32s:(NSArray<NSNumber *> *)crc32s {
|
||||
_crc32s = [crc32s copy];
|
||||
}
|
||||
@synthesize machineName=_machineName;
|
||||
@synthesize fileName=_fileName;
|
||||
@synthesize descriptiveName=_descriptiveName;
|
||||
@synthesize size=_size;
|
||||
@synthesize crc32s=_crc32s;
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s];
|
||||
@@ -179,17 +139,17 @@ struct ActivityObserver: public Activity::Observer {
|
||||
CSMissingROM *rom = [[CSMissingROM alloc] init];
|
||||
|
||||
// Copy/convert the primitive fields.
|
||||
rom.machineName = [NSString stringWithUTF8String:missing_rom.machine_name.c_str()];
|
||||
rom.fileName = [NSString stringWithUTF8String:missing_rom.file_name.c_str()];
|
||||
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : [NSString stringWithUTF8String:missing_rom.descriptive_name.c_str()];
|
||||
rom.machineName = @(missing_rom.machine_name.c_str());
|
||||
rom.fileName = @(missing_rom.file_name.c_str());
|
||||
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : @(missing_rom.descriptive_name.c_str());
|
||||
rom.size = missing_rom.size;
|
||||
|
||||
// Convert the CRC list.
|
||||
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] init];
|
||||
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] initWithCapacity:missing_rom.crc32s.size()];
|
||||
for(const auto &crc : missing_rom.crc32s) {
|
||||
[crc32s addObject:@(crc)];
|
||||
}
|
||||
rom.crc32s = [crc32s copy];
|
||||
rom.crc32s = crc32s;
|
||||
|
||||
// Add to the missing list.
|
||||
[missingROMs addObject:rom];
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#import "CSStaticAnalyser.h"
|
||||
|
||||
@interface CSStaticAnalyser (ResultVector)
|
||||
@interface CSStaticAnalyser ()
|
||||
|
||||
- (Analyser::Static::TargetList &)targets;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CSMachine;
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineAppleIIModel) {
|
||||
@@ -72,9 +74,9 @@ typedef int Kilobytes;
|
||||
|
||||
@interface CSStaticAnalyser : NSObject
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url;
|
||||
- (nullable instancetype)initWithFileAtURL:(NSURL *)url;
|
||||
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs;
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM;
|
||||
- (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model;
|
||||
- (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive;
|
||||
- (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface;
|
||||
@@ -85,7 +87,7 @@ typedef int Kilobytes;
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
|
||||
- (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model;
|
||||
|
||||
@property(nonatomic, readonly) NSString *optionsPanelNibName;
|
||||
@property(nonatomic, readonly, nullable) NSString *optionsPanelNibName;
|
||||
@property(nonatomic, readonly) NSString *displayName;
|
||||
|
||||
@end
|
||||
@@ -96,3 +98,5 @@ typedef int Kilobytes;
|
||||
- (void)applyToMachine:(CSMachine *)machine;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
|
||||
// TODO: could this better be supplied by the analyser? A hypothetical file format might
|
||||
// provide a better name for it contents than the file name?
|
||||
_displayName = [[url pathComponents] lastObject];
|
||||
_displayName = [url.lastPathComponent copy];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -93,13 +93,15 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs {
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Acorn::Target;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->has_dfs = !!dfs;
|
||||
target->has_adfs = !!adfs;
|
||||
target->has_dfs = dfs;
|
||||
target->has_pres_adfs = adfs;
|
||||
target->has_ap6_rom = ap6;
|
||||
target->has_sideways_ram = sidewaysRAM;
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17506" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17506"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -18,7 +18,7 @@
|
||||
<windowStyleMask key="styleMask" titled="YES" documentModal="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="725" height="205"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
|
||||
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="725" height="205"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
@@ -189,31 +189,49 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
|
||||
<view key="view" id="SRc-2D-95G">
|
||||
<rect key="frame" x="10" y="33" width="674" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="679" height="95"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
|
||||
<rect key="frame" x="15" y="75" width="164" height="18"/>
|
||||
<rect key="frame" x="15" y="75" width="168" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="With Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tpW-5C-xKp">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="945-wU-JOH">
|
||||
<rect key="frame" x="15" y="55" width="228" height="18"/>
|
||||
<rect key="frame" x="15" y="53" width="232" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="With Advanced Disk Filing System" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="S0c-Jg-7Pu">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cG2-Ph-S3Z">
|
||||
<rect key="frame" x="15" y="31" width="231" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="With Advanced Plus 6 Utility ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="yjF-XS-zx6">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lzo-8g-o4S">
|
||||
<rect key="frame" x="15" y="9" width="284" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Fill unused ROM banks with sideways RAM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="JEz-eK-uWp">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="945-wU-JOH" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="17" id="1iM-70-oZq"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="945-wU-JOH" secondAttribute="bottom" constant="17" id="7ZL-rR-aaz"/>
|
||||
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="top" secondItem="945-wU-JOH" secondAttribute="bottom" constant="6" symbolic="YES" id="E9b-RP-9vj"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="8" id="FM6-AA-Vhf"/>
|
||||
<constraint firstItem="JqM-IK-FMP" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="17" id="NfY-dE-aJw"/>
|
||||
<constraint firstItem="lzo-8g-o4S" firstAttribute="top" secondItem="cG2-Ph-S3Z" secondAttribute="bottom" constant="6" symbolic="YES" id="S45-42-Gtv"/>
|
||||
<constraint firstItem="lzo-8g-o4S" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="17" id="b5a-SX-2ty"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="945-wU-JOH" secondAttribute="trailing" constant="20" id="dmY-PV-ap4"/>
|
||||
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="3" id="ggl-QH-mV4"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="JqM-IK-FMP" secondAttribute="trailing" constant="20" id="mvO-UZ-BtT"/>
|
||||
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="17" id="npw-IZ-6xU"/>
|
||||
<constraint firstItem="945-wU-JOH" firstAttribute="top" secondItem="JqM-IK-FMP" secondAttribute="bottom" constant="6" id="pes-xi-zkv"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -257,18 +275,18 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="MSX" identifier="msx" id="6SR-DY-zdI">
|
||||
<view key="view" id="mWD-An-tR7">
|
||||
<rect key="frame" x="10" y="33" width="674" height="94"/>
|
||||
<rect key="frame" x="10" y="33" width="679" height="95"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
|
||||
<rect key="frame" x="15" y="48" width="124" height="18"/>
|
||||
<rect key="frame" x="15" y="49" width="128" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Attach disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="CB3-nA-VTM">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LG6-mP-SeG">
|
||||
<rect key="frame" x="69" y="67" width="145" height="25"/>
|
||||
<rect key="frame" x="68" y="68" width="146" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="xAh-Ch-tby" id="yR4-yv-Lvu">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -282,7 +300,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
|
||||
<rect key="frame" x="15" y="73" width="50" height="16"/>
|
||||
<rect key="frame" x="15" y="74" 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"/>
|
||||
@@ -551,7 +569,7 @@ Gw
|
||||
<constraint firstItem="VUb-QG-x7c" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" id="zT3-Ea-QQJ"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="34" y="88.5"/>
|
||||
<point key="canvasLocation" x="33.5" y="88.5"/>
|
||||
</window>
|
||||
<customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
@@ -559,7 +577,9 @@ Gw
|
||||
<outlet property="appleIIModelButton" destination="jli-ac-Sij" id="Jm3-f7-C17"/>
|
||||
<outlet property="cpcModelTypeButton" destination="00d-sg-Krh" id="VyV-b1-A6x"/>
|
||||
<outlet property="electronADFSButton" destination="945-wU-JOH" id="Fjm-W8-kvh"/>
|
||||
<outlet property="electronAP6Button" destination="cG2-Ph-S3Z" id="vkq-1J-KBG"/>
|
||||
<outlet property="electronDFSButton" destination="JqM-IK-FMP" id="C80-1k-TdQ"/>
|
||||
<outlet property="electronSidewaysRAMButton" destination="lzo-8g-o4S" id="LtS-wv-tMf"/>
|
||||
<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"/>
|
||||
|
||||
@@ -18,6 +18,8 @@ class MachinePicker: NSObject {
|
||||
// MARK: - Electron properties
|
||||
@IBOutlet var electronDFSButton: NSButton?
|
||||
@IBOutlet var electronADFSButton: NSButton?
|
||||
@IBOutlet var electronAP6Button: NSButton?
|
||||
@IBOutlet var electronSidewaysRAMButton: NSButton?
|
||||
|
||||
// MARK: - CPC properties
|
||||
@IBOutlet var cpcModelTypeButton: NSPopUpButton?
|
||||
@@ -51,7 +53,11 @@ class MachinePicker: NSObject {
|
||||
|
||||
// Machine type
|
||||
if let machineIdentifier = standardUserDefaults.string(forKey: "new.machine") {
|
||||
machineSelector?.selectTabViewItem(withIdentifier: machineIdentifier as Any)
|
||||
// If I've changed my mind about visible tabs between versions, there may not be one that corresponds
|
||||
// to the stored identifier. Make sure not to raise an NSRangeException in that scenario.
|
||||
if let index = machineSelector?.indexOfTabViewItem(withIdentifier: machineIdentifier as Any), index != NSNotFound {
|
||||
machineSelector?.selectTabViewItem(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
// Apple II settings
|
||||
@@ -61,6 +67,8 @@ class MachinePicker: NSObject {
|
||||
// Electron settings
|
||||
electronDFSButton?.state = standardUserDefaults.bool(forKey: "new.electronDFS") ? .on : .off
|
||||
electronADFSButton?.state = standardUserDefaults.bool(forKey: "new.electronADFS") ? .on : .off
|
||||
electronAP6Button?.state = standardUserDefaults.bool(forKey: "new.electronAP6") ? .on : .off
|
||||
electronSidewaysRAMButton?.state = standardUserDefaults.bool(forKey: "new.electronSidewaysRAM") ? .on : .off
|
||||
|
||||
// CPC settings
|
||||
cpcModelTypeButton?.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
|
||||
@@ -102,6 +110,8 @@ class MachinePicker: NSObject {
|
||||
// Electron settings
|
||||
standardUserDefaults.set(electronDFSButton!.state == .on, forKey: "new.electronDFS")
|
||||
standardUserDefaults.set(electronADFSButton!.state == .on, forKey: "new.electronADFS")
|
||||
standardUserDefaults.set(electronAP6Button!.state == .on, forKey: "new.electronAP6")
|
||||
standardUserDefaults.set(electronSidewaysRAMButton!.state == .on, forKey: "new.electronSidewaysRAM")
|
||||
|
||||
// CPC settings
|
||||
standardUserDefaults.set(cpcModelTypeButton!.selectedTag(), forKey: "new.cpcModel")
|
||||
@@ -136,7 +146,11 @@ class MachinePicker: NSObject {
|
||||
|
||||
switch machineSelector!.selectedTabViewItem!.identifier as! String {
|
||||
case "electron":
|
||||
return CSStaticAnalyser(electronDFS: electronDFSButton!.state == .on, adfs: electronADFSButton!.state == .on)!
|
||||
return CSStaticAnalyser(
|
||||
electronDFS: electronDFSButton!.state == .on,
|
||||
adfs: electronADFSButton!.state == .on,
|
||||
ap6: electronAP6Button!.state == .on,
|
||||
sidewaysRAM: electronSidewaysRAMButton!.state == .on)
|
||||
|
||||
case "appleii":
|
||||
var model: CSMachineAppleIIModel = .appleII
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
@class CSROMReceiverView;
|
||||
|
||||
@protocol CSROMReciverViewDelegate
|
||||
@protocol CSROMReciverViewDelegate <NSObject>
|
||||
/*!
|
||||
Announces receipt of a file by drag and drop to the delegate.
|
||||
@param view The view making the request.
|
||||
|
||||
@@ -396,8 +396,13 @@ using BufferingScanTarget = Outputs::Display::BufferingScanTarget;
|
||||
}
|
||||
|
||||
- (void)updateSizeBuffersToSize:(CGSize)size {
|
||||
const NSUInteger frameBufferWidth = NSUInteger(size.width * _view.layer.contentsScale) * (_isUsingSupersampling ? 2 : 1);
|
||||
const NSUInteger frameBufferHeight = NSUInteger(size.height * _view.layer.contentsScale) * (_isUsingSupersampling ? 2 : 1);
|
||||
// Anecdotally, the size provided here, which ultimately is from _view.drawableSize,
|
||||
// already factors in Retina-style scaling.
|
||||
//
|
||||
// 16384 has been the maximum texture size in all Mac versions of Metal so far, and
|
||||
// I haven't yet found a way to query it dynamically. So it's hard-coded.
|
||||
const NSUInteger frameBufferWidth = MIN(NSUInteger(size.width) * (_isUsingSupersampling ? 2 : 1), 16384);
|
||||
const NSUInteger frameBufferHeight = MIN(NSUInteger(size.height) * (_isUsingSupersampling ? 2 : 1), 16384);
|
||||
|
||||
// Generate a framebuffer and a stencil.
|
||||
MTLTextureDescriptor *const textureDescriptor = [MTLTextureDescriptor
|
||||
|
||||
390
OSBindings/Mac/Clock SignalTests/PowerPCDecoderTests.mm
Normal file
390
OSBindings/Mac/Clock SignalTests/PowerPCDecoderTests.mm
Normal file
@@ -0,0 +1,390 @@
|
||||
//
|
||||
// PowerPCDecoderTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 02/01/2021.
|
||||
// Copyright 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include "../../../InstructionSets/PowerPC/Decoder.hpp"
|
||||
|
||||
namespace {
|
||||
using Operation = CPU::Decoder::PowerPC::Operation;
|
||||
using Instruction = CPU::Decoder::PowerPC::Instruction;
|
||||
}
|
||||
|
||||
@interface PowerPCDecoderTests : XCTestCase
|
||||
@end
|
||||
|
||||
/*!
|
||||
Tests PowerPC decoding by throwing a bunch of randomly-generated
|
||||
word streams and checking that the result matches what I got from a
|
||||
disassembler elsewhere.
|
||||
*/
|
||||
@implementation PowerPCDecoderTests {
|
||||
Instruction instructions[32];
|
||||
}
|
||||
|
||||
// MARK: - Specific instruction asserts.
|
||||
|
||||
- (void)assertUndefined:(Instruction &)instruction {
|
||||
XCTAssertEqual(instruction.operation, Operation::Undefined);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation rD:(int)rD rA:(int)rA simm:(int)simm {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.rD(), rD);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.simm(), simm);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation rD:(int)rD rA:(int)rA d:(int)d {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.rD(), rD);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.d(), d);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation rA:(int)rA rS:(int)rS uimm:(int)uimm {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.rS(), rS);
|
||||
XCTAssertEqual(instruction.uimm(), uimm);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation rS:(int)rS rA:(int)rA d:(int)d {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.rS(), rS);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.d(), d);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation frD:(int)frD rA:(int)rA d:(int)d {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.frD(), frD);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.d(), d);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation frS:(int)frS rA:(int)rA d:(int)d {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.frS(), frS);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.d(), d);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation li:(uint32_t)li lk:(BOOL)lk aa:(BOOL)aa {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.li(), li);
|
||||
XCTAssertEqual(!!instruction.lk(), lk);
|
||||
XCTAssertEqual(!!instruction.aa(), aa);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation bo:(int)bo bi:(int)bi bd:(int)bd lk:(BOOL)lk aa:(BOOL)aa {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.bo(), bo);
|
||||
XCTAssertEqual(instruction.bi(), bi);
|
||||
XCTAssertEqual(instruction.bd(), bd);
|
||||
XCTAssertEqual(!!instruction.lk(), lk);
|
||||
XCTAssertEqual(!!instruction.aa(), aa);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation rA:(int)rA rS:(int)rS rB:(int)rB mb:(int)mb me:(int)me rc:(BOOL)rc {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.rS(), rS);
|
||||
XCTAssertEqual(instruction.rB(), rB);
|
||||
XCTAssertEqual(instruction.mb(), mb);
|
||||
XCTAssertEqual(instruction.me(), me);
|
||||
XCTAssertEqual(!!instruction.rc(), rc);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation to:(int)to rA:(int)rA simm:(int)simm {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.to(), to);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.simm(), simm);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation crfD:(int)crfD l:(BOOL)l rA:(int)rA uimm:(int)uimm {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.crfD(), crfD);
|
||||
XCTAssertEqual(!!instruction.l(), l);
|
||||
XCTAssertEqual(instruction.rA(), rA);
|
||||
XCTAssertEqual(instruction.uimm(), uimm);
|
||||
}
|
||||
|
||||
// MARK: - Decoder
|
||||
|
||||
- (void)decode:(const uint32_t *)stream {
|
||||
CPU::Decoder::PowerPC::Decoder decoder(CPU::Decoder::PowerPC::Model::MPC601);
|
||||
for(int c = 0; c < 32; c++) {
|
||||
instructions[c] = decoder.decode(stream[c]);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tests
|
||||
|
||||
- (void)testSequence1 {
|
||||
const uint32_t sequence[] = {
|
||||
0x32eeefa5, 0xc2ee0786, 0x80ce552c, 0x88d5f02a,
|
||||
0xf8c2e801, 0xe83d5cdf, 0x7fa51fbb, 0xaacea8b0,
|
||||
0x7d4d4d21, 0x1314cf89, 0x47e0014b, 0xdf67566d,
|
||||
0xfb29a33e, 0x312cbf53, 0x706f1a15, 0xa87b7011,
|
||||
0x2107090c, 0xce04935e, 0x642e464a, 0x931e8eba,
|
||||
0xee396d5b, 0x4901183d, 0x31ccaa9a, 0x42b61a86,
|
||||
0x1ed4751d, 0x86af76e4, 0x151405a9, 0xca0ac015,
|
||||
0x60dd1f9d, 0xecff44f6, 0xf2c1110e, 0x9aa6653b,
|
||||
};
|
||||
[self decode:sequence];
|
||||
|
||||
// addic r23,r14,-4187
|
||||
// lfs f23,1926(r14)
|
||||
// lwz r6,21804(r14)
|
||||
// lbz r6,-4054(r21)
|
||||
[self assert:instructions[0] operation:Operation::addic rD:23 rA:14 simm:-4187];
|
||||
[self assert:instructions[1] operation:Operation::lfs frD:23 rA:14 d:1926];
|
||||
[self assert:instructions[2] operation:Operation::lwz rD:6 rA:14 d:21804];
|
||||
[self assert:instructions[3] operation:Operation::lbz rD:6 rA:21 d:-4054];
|
||||
|
||||
// .long 0xf8c2e801
|
||||
// .long 0xe83d5cdf
|
||||
// .long 0x7fa51fbb
|
||||
// lha r22,-22352(r14)
|
||||
[self assertUndefined:instructions[4]];
|
||||
[self assertUndefined:instructions[5]];
|
||||
[self assertUndefined:instructions[6]];
|
||||
[self assert:instructions[7] operation:Operation::lha rD:22 rA:14 d:-22352];
|
||||
|
||||
// .long 0x7d4d4d21
|
||||
// .long 0x1314cf89
|
||||
// .long 0x47e0014b
|
||||
[self assertUndefined:instructions[8]];
|
||||
[self assertUndefined:instructions[9]];
|
||||
// CLK decodes this as sc because it ignores reserved bits; the disassembler
|
||||
// I used checks the reserved bits. For now: don't test.
|
||||
// XCTAssertEqual(instructions[10].operation, Operation::Undefined);
|
||||
|
||||
// stfdu f27,22125(r7)
|
||||
// .long 0xfb29a33e
|
||||
// addic r9,r12,-16557
|
||||
// andi. r15,r3,6677
|
||||
[self assert:instructions[11] operation:Operation::stfdu frS:27 rA:7 d:22125];
|
||||
[self assertUndefined:instructions[12]];
|
||||
[self assert:instructions[13] operation:Operation::addic rD:9 rA:12 simm:-16557];
|
||||
[self assert:instructions[14] operation:Operation::andi_ rA:15 rS:3 uimm:6677];
|
||||
|
||||
// lha r3,28689(r27)
|
||||
// subfic r8,r7,2316
|
||||
// lfdu f16,-27810(r4)
|
||||
// oris r14,r1,17994
|
||||
[self assert:instructions[15] operation:Operation::lha rD:3 rA:27 d:28689];
|
||||
[self assert:instructions[16] operation:Operation::subfic rD:8 rA:7 simm:2316];
|
||||
[self assert:instructions[17] operation:Operation::lfdu frD:16 rA:4 d:-27810];
|
||||
[self assert:instructions[18] operation:Operation::oris rA:14 rS:1 uimm:17994];
|
||||
|
||||
// stw r24,-28998(r30)
|
||||
// .long 0xee396d5b
|
||||
// bl 0x01011890 [disassmebled at address 0x54]
|
||||
// addic r14,r12,-21862
|
||||
[self assert:instructions[19] operation:Operation::stw rS:24 rA:30 d:-28998];
|
||||
[self assertUndefined:instructions[20]];
|
||||
[self assert:instructions[21] operation:Operation::bx li:0x01011890 - 0x54 lk:TRUE aa:FALSE];
|
||||
[self assert:instructions[22] operation:Operation::addic rD:14 rA:12 simm:-21862];
|
||||
|
||||
// .long 0x42b61a86 [10101]
|
||||
// mulli r22,r20,29981
|
||||
// lwzu r21,30436(r15)
|
||||
// .long 0x151405a9
|
||||
[self assertUndefined:instructions[23]];
|
||||
[self assert:instructions[24] operation:Operation::mulli rD:22 rA:20 simm:29981];
|
||||
[self assert:instructions[25] operation:Operation::lwzu rD:21 rA:15 d:30436];
|
||||
[self assertUndefined:instructions[26]];
|
||||
|
||||
// lfd f16,-16363(r10)
|
||||
// ori r29,r6,8093
|
||||
// .long 0xecff44f6
|
||||
// .long 0xf2c1110e
|
||||
// stb r21,25915(r6)
|
||||
[self assert:instructions[27] operation:Operation::lfd frD:16 rA:10 d:-16363];
|
||||
[self assert:instructions[28] operation:Operation::ori rA:29 rS:6 uimm:8093];
|
||||
[self assertUndefined:instructions[29]];
|
||||
[self assertUndefined:instructions[30]];
|
||||
[self assert:instructions[31] operation:Operation::stb rS:21 rA:6 d:25915];
|
||||
}
|
||||
|
||||
- (void)testSequence2 {
|
||||
const uint32_t sequence[] = {
|
||||
0x90252dae, 0x7429ee14, 0x618935bc, 0xd6c94af0,
|
||||
0xba1d295f, 0x649e3869, 0x6def742c, 0x5c64cdce,
|
||||
0x762d59ee, 0x565c8189, 0xc7c59f81, 0xce1157fd,
|
||||
0xc86aef59, 0x81325882, 0x1336fad6, 0xe1ddfa2b,
|
||||
0x18c60357, 0x4c122cb5, 0xccb1f749, 0xdbdcebc3,
|
||||
0x0fc60187, 0x117eb911, 0x80334c43, 0xe65371e8,
|
||||
0xa047c94d, 0xe671dd0b, 0xe07992bb, 0x6a332fe8,
|
||||
0xfc361c6b, 0x5e8b5a28, 0xb2b64a22, 0x045dd156,
|
||||
};
|
||||
[self decode:sequence];
|
||||
|
||||
// stw r1,11694(r5)
|
||||
// andis. r9,r1,60948
|
||||
// ori r9,r12,13756
|
||||
// stfsu f22,19184(r9)
|
||||
[self assert:instructions[0] operation:Operation::stw rS:1 rA:5 d:11694];
|
||||
[self assert:instructions[1] operation:Operation::andis_ rA:9 rS:1 uimm:60948];
|
||||
[self assert:instructions[2] operation:Operation::ori rA:9 rS:12 uimm:13756];
|
||||
[self assert:instructions[3] operation:Operation::stfsu frS:22 rA:9 d:19184];
|
||||
|
||||
// lmw r16,10591(r29)
|
||||
// oris r30,r4,14441
|
||||
// xoris r15,r15,29740
|
||||
// rlwnm r4,r3,r25,23,7
|
||||
[self assert:instructions[4] operation:Operation::lmw rD:16 rA:29 d:10591];
|
||||
[self assert:instructions[5] operation:Operation::oris rA:30 rS:4 uimm:14441];
|
||||
[self assert:instructions[6] operation:Operation::xoris rA:15 rS:15 uimm:29740];
|
||||
[self assert:instructions[7] operation:Operation::rlwnmx rA:4 rS:3 rB:25 mb:23 me:7 rc:FALSE];
|
||||
|
||||
// andis. r13,r17,23022
|
||||
// rlwinm. r28,r18,16,6,4
|
||||
// lfsu f30,-24703(r5)
|
||||
// lfdu f16,22525(r17)
|
||||
[self assert:instructions[8] operation:Operation::andis_ rA:13 rS:17 uimm:23022];
|
||||
[self assert:instructions[9] operation:Operation::rlwinmx rA:28 rS:18 rB:16 mb:6 me:4 rc:TRUE];
|
||||
[self assert:instructions[10] operation:Operation::lfsu frD:30 rA:5 d:-24703];
|
||||
[self assert:instructions[11] operation:Operation::lfdu frD:16 rA:17 d:22525];
|
||||
|
||||
// lfd f3,-4263(r10)
|
||||
// lwz r9,22658(r18)
|
||||
// .long 0x1336fad6
|
||||
// .long 0xe1ddfa2b
|
||||
[self assert:instructions[12] operation:Operation::lfd frD:3 rA:10 d:-4263];
|
||||
[self assert:instructions[13] operation:Operation::lwz rD:9 rA:18 d:22658];
|
||||
[self assertUndefined:instructions[14]];
|
||||
[self assertUndefined:instructions[15]];
|
||||
|
||||
// .long 0x18c60357
|
||||
// .long 0x4c122cb5
|
||||
// lfdu f5,-2231(r17)
|
||||
// stfd f30,-5181(r28)
|
||||
[self assertUndefined:instructions[16]];
|
||||
[self assertUndefined:instructions[17]];
|
||||
[self assert:instructions[18] operation:Operation::lfdu frD:5 rA:17 d:-2231];
|
||||
[self assert:instructions[19] operation:Operation::stfd frD:30 rA:28 d:-5181];
|
||||
|
||||
// twi 30,r6,391
|
||||
// .long 0x117eb911
|
||||
// lwz r1,19523(r19)
|
||||
// .long 0xe65371e8
|
||||
[self assert:instructions[20] operation:Operation::twi to:30 rA:6 simm:391];
|
||||
[self assertUndefined:instructions[21]];
|
||||
[self assert:instructions[22] operation:Operation::lwz rD:1 rA:19 d:19523];
|
||||
[self assertUndefined:instructions[23]];
|
||||
|
||||
// lhz r2,-14003(r7)
|
||||
// .long 0xe671dd0b
|
||||
// .long 0xe07992bb
|
||||
// xori r19,r17,12264
|
||||
[self assert:instructions[24] operation:Operation::lhz rD:2 rA:7 d:-14003];
|
||||
[self assertUndefined:instructions[25]];
|
||||
[self assertUndefined:instructions[26]];
|
||||
[self assert:instructions[27] operation:Operation::xori rA:19 rS:17 uimm:12264];
|
||||
|
||||
// .long 0xfc361c6b
|
||||
// rlwnm r11,r20,r11,8,20
|
||||
// sth r21,18978(r22)
|
||||
// .long 0x45dd156
|
||||
// [self assertUndefined:instructions[28]]; // Disabled due to reserved field; I'm decoding this as faddx.
|
||||
[self assert:instructions[29] operation:Operation::rlwnmx rA:11 rS:20 rB:11 mb:8 me:20 rc:FALSE];
|
||||
[self assert:instructions[30] operation:Operation::sth rS:21 rA:22 d:18978];
|
||||
[self assertUndefined:instructions[31]];
|
||||
}
|
||||
|
||||
- (void)testSequence3 {
|
||||
const uint32_t sequence[] = {
|
||||
0xbcaf3520, 0xfa9df12d, 0xc631efca, 0xa3e7f409,
|
||||
0x3ddca273, 0x3cfb234d, 0x551dc325, 0x8c1a0f37,
|
||||
0x5b3ca99b, 0xce08cc1e, 0x7b1dfd3a, 0xf19aee7c,
|
||||
0x52c852e9, 0xc681c0c1, 0xd3b1fda5, 0xe2b401cb,
|
||||
0x433cb83d, 0x54412f41, 0x532d624a, 0x0b3117c5,
|
||||
0x988144ba, 0xc7a96ad0, 0x28331474, 0x5620c367,
|
||||
0xab0a2607, 0xe826acf4, 0x41969154, 0x6471d09f,
|
||||
0x6a25f04f, 0x4a15996d, 0x272c96ef, 0xab3171a9,
|
||||
};
|
||||
[self decode:sequence];
|
||||
|
||||
// stmw r5,13600(r15)
|
||||
// .long 0xfa9df12d
|
||||
// lfsu f17,-4150(r17)
|
||||
// lhz r31,-3063(r7)
|
||||
[self assert:instructions[0] operation:Operation::stmw rS:5 rA:15 d:13600];
|
||||
[self assertUndefined:instructions[1]];
|
||||
[self assert:instructions[2] operation:Operation::lfsu frD:17 rA:17 d:-4150];
|
||||
[self assert:instructions[3] operation:Operation::lhz rD:31 rA:7 d:-3063];
|
||||
|
||||
// addis r14,r28,-23949
|
||||
// addis r7,r27,9037
|
||||
// rlwinm. r29,r8,24,12,18
|
||||
// lbzu r0,3895(r26)
|
||||
[self assert:instructions[4] operation:Operation::addis rD:14 rA:28 simm:-23949];
|
||||
[self assert:instructions[5] operation:Operation::addis rD:7 rA:27 simm:9037];
|
||||
[self assert:instructions[6] operation:Operation::rlwinmx rA:29 rS:8 rB:24 mb:12 me:18 rc:TRUE];
|
||||
[self assert:instructions[7] operation:Operation::lbzu rD:0 rA:26 d:3895];
|
||||
|
||||
// rlmi. r28,r25,r21,6,13
|
||||
// lfdu f16,-13282(r8)
|
||||
// .long 0x7b1dfd3a
|
||||
// .long 0xf19aee7c
|
||||
[self assert:instructions[8] operation:Operation::rlmix rA:28 rS:25 rB:21 mb:6 me:13 rc:TRUE];
|
||||
[self assert:instructions[9] operation:Operation::lfdu frD:16 rA:8 d:-13282];
|
||||
[self assertUndefined:instructions[10]];
|
||||
[self assertUndefined:instructions[11]];
|
||||
|
||||
// rlwimi. r8,r22,10,11,20
|
||||
// lfsu f20,-16191(r1)
|
||||
// stfs f29,-603(r17)
|
||||
// .long 0xe2b401cb
|
||||
[self assert:instructions[12] operation:Operation::rlwimix rA:8 rS:22 rB:10 mb:11 me:20 rc:TRUE];
|
||||
[self assert:instructions[13] operation:Operation::lfsu frD:20 rA:1 d:-16191];
|
||||
[self assert:instructions[14] operation:Operation::stfs frS:29 rA:17 d:-603];
|
||||
[self assertUndefined:instructions[15]];
|
||||
|
||||
// .long 0x433cb83d
|
||||
// rlwinm. r1,r2,5,29,0
|
||||
// rlwimi r13,r25,12,9,5
|
||||
// .long 0xb3117c5
|
||||
[self assertUndefined:instructions[16]];
|
||||
[self assert:instructions[17] operation:Operation::rlwinmx rA:1 rS:2 rB:5 mb:29 me:0 rc:TRUE];
|
||||
[self assert:instructions[18] operation:Operation::rlwimix rA:13 rS:25 rB:12 mb:9 me:5 rc:FALSE];
|
||||
[self assertUndefined:instructions[19]];
|
||||
|
||||
// stb r4,17594(r1)
|
||||
// lfsu f29,27344(r9)
|
||||
// cmpli cr0,1,r19,5236
|
||||
// rlwinm. r0,r17,24,13,19
|
||||
[self assert:instructions[20] operation:Operation::stb rS:4 rA:1 d:17594];
|
||||
[self assert:instructions[21] operation:Operation::lfsu frD:29 rA:9 d:27344];
|
||||
[self assert:instructions[22] operation:Operation::cmpli crfD:0 l:TRUE rA:19 uimm:5236];
|
||||
[self assert:instructions[23] operation:Operation::rlwinmx rA:0 rS:17 rB:24 mb:13 me:19 rc:TRUE];
|
||||
|
||||
// lha r24,9735(r10)
|
||||
// .long 0xe826acf4
|
||||
// beq+ cr5,0xffffffffffff91bc [at address 0x68]
|
||||
// oris r17,r3,53407
|
||||
[self assert:instructions[24] operation:Operation::lha rD:24 rA:10 d:9735];
|
||||
[self assertUndefined:instructions[25]];
|
||||
[self assert:instructions[26] operation:Operation::bcx bo:12 bi:22 bd:0xffff91bc - 0x68 lk:FALSE aa:FALSE];
|
||||
[self assert:instructions[27] operation:Operation::oris rA:17 rS:3 uimm:53407];
|
||||
|
||||
// xori r5,r17,61519
|
||||
// bl 0xfffffffffe1599e0 [at address 0x74]
|
||||
// dozi r25,r12,-26897
|
||||
// lha r25,29097(r17)
|
||||
[self assert:instructions[28] operation:Operation::xori rA:5 rS:17 uimm:61519];
|
||||
[self assert:instructions[29] operation:Operation::bx li:0xfe1599e0 - 0x74 lk:TRUE aa:FALSE];
|
||||
[self assert:instructions[30] operation:Operation::dozi rD:25 rA:12 simm:-26897];
|
||||
[self assert:instructions[31] operation:Operation::lha rD:25 rA:17 d:29097];
|
||||
}
|
||||
|
||||
@end
|
||||
336
OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm
Normal file
336
OSBindings/Mac/Clock SignalTests/x86DecoderTests.mm
Normal file
@@ -0,0 +1,336 @@
|
||||
//
|
||||
// x86DecoderTests.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/01/2021.
|
||||
// Copyright 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <vector>
|
||||
#include "../../../InstructionSets/x86/Decoder.hpp"
|
||||
|
||||
namespace {
|
||||
using Operation = CPU::Decoder::x86::Operation;
|
||||
using Instruction = CPU::Decoder::x86::Instruction;
|
||||
using Source = CPU::Decoder::x86::Source;
|
||||
using Size = CPU::Decoder::x86::Size;
|
||||
}
|
||||
|
||||
@interface x86DecoderTests : XCTestCase
|
||||
@end
|
||||
|
||||
/*!
|
||||
Tests 8086 decoding by throwing a bunch of randomly-generated
|
||||
word streams and checking that the result matches what I got from a
|
||||
disassembler elsewhere.
|
||||
*/
|
||||
@implementation x86DecoderTests {
|
||||
std::vector<Instruction> instructions;
|
||||
}
|
||||
|
||||
// MARK: - Specific instruction asserts.
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source destination:(Source)destination displacement:(int16_t)displacement {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
|
||||
XCTAssertEqual(instruction.source(), source);
|
||||
XCTAssertEqual(instruction.destination(), destination);
|
||||
XCTAssertEqual(instruction.displacement(), displacement);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source destination:(Source)destination displacement:(int16_t)displacement operand:(uint16_t)operand {
|
||||
[self assert:instruction operation:operation size:size source:source destination:destination displacement:displacement];
|
||||
XCTAssertEqual(instruction.operand(), operand);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source destination:(Source)destination operand:(uint16_t)operand {
|
||||
[self assert:instruction operation:operation size:size source:source destination:destination displacement:0 operand:operand];
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source destination:(Source)destination {
|
||||
[self assert:instruction operation:operation size:size source:source destination:destination displacement:0];
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size source:(Source)source {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
|
||||
XCTAssertEqual(instruction.source(), source);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size destination:(Source)destination {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
|
||||
XCTAssertEqual(instruction.destination(), destination);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation size:(int)size operand:(uint16_t)operand destination:(Source)destination {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.operation_size(), CPU::Decoder::x86::Size(size));
|
||||
XCTAssertEqual(instruction.destination(), destination);
|
||||
XCTAssertEqual(instruction.source(), Source::Immediate);
|
||||
XCTAssertEqual(instruction.operand(), operand);
|
||||
XCTAssertEqual(instruction.displacement(), 0);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation displacement:(int16_t)displacement {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.displacement(), displacement);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation operand:(uint16_t)operand {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.operand(), operand);
|
||||
XCTAssertEqual(instruction.displacement(), 0);
|
||||
}
|
||||
|
||||
- (void)assert:(Instruction &)instruction operation:(Operation)operation segment:(uint16_t)segment offset:(uint16_t)offset {
|
||||
XCTAssertEqual(instruction.operation, operation);
|
||||
XCTAssertEqual(instruction.segment(), segment);
|
||||
XCTAssertEqual(instruction.offset(), offset);
|
||||
}
|
||||
|
||||
// MARK: - Decoder
|
||||
|
||||
- (void)decode:(const std::initializer_list<uint8_t> &)stream {
|
||||
// Decode by offering up all data at once.
|
||||
CPU::Decoder::x86::Decoder decoder(CPU::Decoder::x86::Model::i8086);
|
||||
instructions.clear();
|
||||
const uint8_t *byte = stream.begin();
|
||||
while(byte != stream.end()) {
|
||||
const auto [size, next] = decoder.decode(byte, stream.end() - byte);
|
||||
if(size <= 0) break;
|
||||
instructions.push_back(next);
|
||||
byte += size;
|
||||
}
|
||||
|
||||
// Grab a byte-at-a-time decoding and check that it matches the previous.
|
||||
{
|
||||
CPU::Decoder::x86::Decoder decoder(CPU::Decoder::x86::Model::i8086);
|
||||
|
||||
auto previous_instruction = instructions.begin();
|
||||
for(auto item: stream) {
|
||||
const auto [size, next] = decoder.decode(&item, 1);
|
||||
if(size > 0) {
|
||||
XCTAssert(next == *previous_instruction);
|
||||
++previous_instruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Tests
|
||||
|
||||
- (void)testSequence1 {
|
||||
// Sequences the Online Disassembler believes to exist but The 8086 Book does not:
|
||||
//
|
||||
// 0x6a 0x65 push $65
|
||||
// 0x65 0x6d gs insw (%dx),%es:(%di)
|
||||
// 0x67 0x61 addr32 popa
|
||||
// 0x6c insb (%dx), %es:(%di)
|
||||
// 0xc9 leave
|
||||
//
|
||||
[self decode:{
|
||||
0x2d, 0x77, 0xea, 0x72, 0xfc, 0x4b, 0xb5, 0x28, 0xc3, 0xca, 0x26, 0x48, /* 0x65, 0x6d, */ 0x7b, 0x9f,
|
||||
0xc2, 0x65, 0x42, 0x4e, 0xef, 0x70, 0x20, 0x94, 0xc4, 0xd4, 0x93, 0x43, 0x3c, 0x8e, /* 0x6a, 0x65, */
|
||||
0x1a, 0x78, 0x45, 0x10, 0x7f, 0x3c, 0x19, 0x5a, 0x16, 0x31, 0x64, 0x2c, 0xe7, 0xc6, 0x7d, 0xb0,
|
||||
0xb5, 0x49, /* 0x67, 0x61, */ 0xba, 0xc0, 0xcb, 0x14, 0x7e, 0x71, 0xd0, 0x50, 0x78, 0x3d, 0x03, 0x1d,
|
||||
0xe5, 0xc9, 0x97, 0xc3, 0x9b, 0xe6, 0xd3, /* 0x6c, */ 0x58, 0x4d, 0x76, 0x80, 0x44, 0xd6, 0x9f, 0xa5,
|
||||
0xbd, 0xa1, 0x12, 0xc5, 0x29, /* 0xc9, */ 0x9e, 0xd8, 0xf3, 0xcf, 0x92, 0x39, 0x5d, 0x90, 0x15, 0xc3,
|
||||
0xb8, 0xad, 0xe8, 0xc8, 0x16, 0x4a, 0xb0, 0x9e, 0xf9, 0xbf, 0x56, 0xea, 0x4e, 0xfd, 0xe4, 0x5a,
|
||||
0x23, 0xaa, 0x2c, 0x5b, 0x2a, 0xd2, 0xf7, 0x5f, 0x18, 0x86, 0x90, 0x25, 0x64, 0xb7, 0xc3
|
||||
}];
|
||||
|
||||
// 63 instructions are expected.
|
||||
XCTAssertEqual(instructions.size(), 63);
|
||||
|
||||
// sub $0xea77,%ax
|
||||
// jb 0x00000001
|
||||
// dec %bx
|
||||
// mov $0x28,%ch
|
||||
[self assert:instructions[0] operation:Operation::SUB size:2 operand:0xea77 destination:Source::AX];
|
||||
[self assert:instructions[1] operation:Operation::JB displacement:0xfffc];
|
||||
[self assert:instructions[2] operation:Operation::DEC size:2 source:Source::BX destination:Source::BX];
|
||||
[self assert:instructions[3] operation:Operation::MOV size:1 operand:0x28 destination:Source::CH];
|
||||
|
||||
// ret
|
||||
// lret $0x4826
|
||||
// [[ omitted: gs insw (%dx),%es:(%di) ]]
|
||||
// jnp 0xffffffaf
|
||||
// ret $0x4265
|
||||
[self assert:instructions[4] operation:Operation::RETN];
|
||||
[self assert:instructions[5] operation:Operation::RETF operand:0x4826];
|
||||
[self assert:instructions[6] operation:Operation::JNP displacement:0xff9f];
|
||||
[self assert:instructions[7] operation:Operation::RETN operand:0x4265];
|
||||
|
||||
// dec %si
|
||||
// out %ax,(%dx)
|
||||
// jo 0x00000037
|
||||
// xchg %ax,%sp
|
||||
[self assert:instructions[8] operation:Operation::DEC size:2 source:Source::SI destination:Source::SI];
|
||||
[self assert:instructions[9] operation:Operation::OUT size:2 source:Source::AX destination:Source::DX];
|
||||
[self assert:instructions[10] operation:Operation::JO displacement:0x20];
|
||||
[self assert:instructions[11] operation:Operation::XCHG size:2 source:Source::AX destination:Source::SP];
|
||||
|
||||
// ODA has:
|
||||
// c4 (bad)
|
||||
// d4 93 aam $0x93
|
||||
//
|
||||
// That assumes that upon discovering that the d4 doesn't make a valid LES,
|
||||
// it can become an instruction byte. I'm not persuaded. So I'm taking:
|
||||
//
|
||||
// c4 d4 (bad)
|
||||
// 93 XCHG AX, BX
|
||||
[self assert:instructions[12] operation:Operation::Invalid];
|
||||
[self assert:instructions[13] operation:Operation::XCHG size:2 source:Source::AX destination:Source::BX];
|
||||
|
||||
// inc %bx
|
||||
// cmp $0x8e,%al
|
||||
// [[ omitted: push $0x65 ]]
|
||||
// sbb 0x45(%bx,%si),%bh
|
||||
// adc %bh,0x3c(%bx)
|
||||
[self assert:instructions[14] operation:Operation::INC size:2 source:Source::BX destination:Source::BX];
|
||||
[self assert:instructions[15] operation:Operation::CMP size:1 operand:0x8e destination:Source::AL];
|
||||
[self assert:instructions[16] operation:Operation::SBB size:1 source:Source::IndBXPlusSI destination:Source::BH displacement:0x45];
|
||||
[self assert:instructions[17] operation:Operation::ADC size:1 source:Source::BH destination:Source::IndBX displacement:0x3c];
|
||||
|
||||
// sbb %bx,0x16(%bp,%si)
|
||||
// xor %sp,0x2c(%si)
|
||||
// out %ax,$0xc6
|
||||
// jge 0xffffffe0
|
||||
[self assert:instructions[18] operation:Operation::SBB size:2 source:Source::BX destination:Source::IndBPPlusSI displacement:0x16];
|
||||
[self assert:instructions[19] operation:Operation::XOR size:2 source:Source::SP destination:Source::IndSI displacement:0x2c];
|
||||
[self assert:instructions[20] operation:Operation::OUT size:2 source:Source::AX destination:Source::DirectAddress operand:0xc6];
|
||||
[self assert:instructions[21] operation:Operation::JNL displacement:0xffb0];
|
||||
|
||||
// mov $0x49,%ch
|
||||
// [[ omitted: addr32 popa ]]
|
||||
// mov $0xcbc0,%dx
|
||||
// adc $0x7e,%al
|
||||
// jno 0x0000000b
|
||||
[self assert:instructions[22] operation:Operation::MOV size:1 operand:0x49 destination:Source::CH];
|
||||
[self assert:instructions[23] operation:Operation::MOV size:2 operand:0xcbc0 destination:Source::DX];
|
||||
[self assert:instructions[24] operation:Operation::ADC size:1 operand:0x7e destination:Source::AL];
|
||||
[self assert:instructions[25] operation:Operation::JNO displacement:0xffd0];
|
||||
|
||||
// push %ax
|
||||
// js 0x0000007b
|
||||
// add (%di),%bx
|
||||
// in $0xc9,%ax
|
||||
[self assert:instructions[26] operation:Operation::PUSH size:2 source:Source::AX];
|
||||
[self assert:instructions[27] operation:Operation::JS displacement:0x3d];
|
||||
[self assert:instructions[28] operation:Operation::ADD size:2 source:Source::IndDI destination:Source::BX];
|
||||
[self assert:instructions[29] operation:Operation::IN size:2 source:Source::DirectAddress destination:Source::AX operand:0xc9];
|
||||
|
||||
// xchg %ax,%di
|
||||
// ret
|
||||
// fwait
|
||||
// out %al,$0xd3
|
||||
[self assert:instructions[30] operation:Operation::XCHG size:2 source:Source::AX destination:Source::DI];
|
||||
[self assert:instructions[31] operation:Operation::RETN];
|
||||
[self assert:instructions[32] operation:Operation::WAIT];
|
||||
[self assert:instructions[33] operation:Operation::OUT size:1 source:Source::AL destination:Source::DirectAddress operand:0xd3];
|
||||
|
||||
// [[ omitted: insb (%dx),%es:(%di) ]]
|
||||
// pop %ax
|
||||
// dec %bp
|
||||
// jbe 0xffffffcc
|
||||
// inc %sp
|
||||
[self assert:instructions[34] operation:Operation::POP size:2 destination:Source::AX];
|
||||
[self assert:instructions[35] operation:Operation::DEC size:2 source:Source::BP destination:Source::BP];
|
||||
[self assert:instructions[36] operation:Operation::JBE displacement:0xff80];
|
||||
[self assert:instructions[37] operation:Operation::INC size:2 source:Source::SP destination:Source::SP];
|
||||
|
||||
// (bad)
|
||||
// lahf
|
||||
// movsw %ds:(%si),%es:(%di)
|
||||
// mov $0x12a1,%bp
|
||||
[self assert:instructions[38] operation:Operation::Invalid];
|
||||
[self assert:instructions[39] operation:Operation::LAHF];
|
||||
[self assert:instructions[40] operation:Operation::MOVS size:2];
|
||||
[self assert:instructions[41] operation:Operation::MOV size:2 operand:0x12a1 destination:Source::BP];
|
||||
|
||||
// lds (%bx,%di),%bp
|
||||
// [[ omitted: leave ]]
|
||||
// sahf
|
||||
// fdiv %st(3),%st
|
||||
// iret
|
||||
[self assert:instructions[42] operation:Operation::LDS size:2];
|
||||
[self assert:instructions[43] operation:Operation::SAHF];
|
||||
[self assert:instructions[44] operation:Operation::ESC];
|
||||
[self assert:instructions[45] operation:Operation::IRET];
|
||||
|
||||
// xchg %ax,%dx
|
||||
// cmp %bx,-0x70(%di)
|
||||
// adc $0xb8c3,%ax
|
||||
// lods %ds:(%si),%ax
|
||||
[self assert:instructions[46] operation:Operation::XCHG size:2 source:Source::AX destination:Source::DX];
|
||||
[self assert:instructions[47] operation:Operation::CMP size:2 source:Source::BX destination:Source::IndDI displacement:0xff90];
|
||||
[self assert:instructions[48] operation:Operation::ADC size:2 operand:0xb8c3 destination:Source::AX];
|
||||
[self assert:instructions[49] operation:Operation::LODS size:2];
|
||||
|
||||
// call 0x0000172d
|
||||
// dec %dx
|
||||
// mov $0x9e,%al
|
||||
// stc
|
||||
[self assert:instructions[50] operation:Operation::CALLD operand:0x16c8];
|
||||
[self assert:instructions[51] operation:Operation::DEC size:2 source:Source::DX destination:Source::DX];
|
||||
[self assert:instructions[52] operation:Operation::MOV size:1 operand:0x9e destination:Source::AL];
|
||||
[self assert:instructions[53] operation:Operation::STC];
|
||||
|
||||
// mov $0xea56,%di
|
||||
// dec %si
|
||||
// std
|
||||
// in $0x5a,%al
|
||||
[self assert:instructions[54] operation:Operation::MOV size:2 operand:0xea56 destination:Source::DI];
|
||||
[self assert:instructions[55] operation:Operation::DEC size:2 source:Source::SI destination:Source::SI];
|
||||
[self assert:instructions[56] operation:Operation::STD];
|
||||
[self assert:instructions[57] operation:Operation::IN size:1 source:Source::DirectAddress destination:Source::AL operand:0x5a];
|
||||
|
||||
// and 0x5b2c(%bp,%si),%bp
|
||||
// sub %dl,%dl
|
||||
// negw 0x18(%bx)
|
||||
// xchg %dl,0x6425(%bx,%si)
|
||||
[self assert:instructions[58] operation:Operation::AND size:2 source:Source::IndBPPlusSI destination:Source::BP displacement:0x5b2c];
|
||||
[self assert:instructions[59] operation:Operation::SUB size:1 source:Source::DL destination:Source::DL];
|
||||
[self assert:instructions[60] operation:Operation::NEG size:2 source:Source::IndBX destination:Source::IndBX displacement:0x18];
|
||||
[self assert:instructions[61] operation:Operation::XCHG size:1 source:Source::IndBXPlusSI destination:Source::DL displacement:0x6425];
|
||||
|
||||
// mov $0xc3,%bh
|
||||
[self assert:instructions[62] operation:Operation::MOV size:1 operand:0xc3 destination:Source::BH];
|
||||
}
|
||||
|
||||
- (void)test83 {
|
||||
[self decode:{
|
||||
0x83, 0x10, 0x80, // adcw $0xff80,(%bx,%si)
|
||||
0x83, 0x3b, 0x04, // cmpw $0x4,(%bp,%di)
|
||||
0x83, 0x2f, 0x09, // subw $0x9,(%bx)
|
||||
}];
|
||||
|
||||
XCTAssertEqual(instructions.size(), 3);
|
||||
[self assert:instructions[0] operation:Operation::ADC size:2 source:Source::Immediate destination:Source::IndBXPlusSI operand:0xff80];
|
||||
[self assert:instructions[1] operation:Operation::CMP size:2 source:Source::Immediate destination:Source::IndBPPlusDI operand:0x4];
|
||||
[self assert:instructions[2] operation:Operation::SUB size:2 source:Source::Immediate destination:Source::IndBX operand:0x9];
|
||||
}
|
||||
|
||||
- (void)testFar {
|
||||
[self decode:{
|
||||
0x9a, 0x12, 0x34, 0x56, 0x78, // lcall 0x7856, 0x3412
|
||||
}];
|
||||
|
||||
XCTAssertEqual(instructions.size(), 1);
|
||||
[self assert:instructions[0] operation:Operation::CALLF segment:0x7856 offset:0x3412];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1020,7 +1020,7 @@ bool MainWindow::processEvent(QKeyEvent *event) {
|
||||
const auto keyboardMachine = machine->keyboard_machine();
|
||||
if(!keyboardMachine) return true;
|
||||
|
||||
auto keyboard = keyboardMachine->get_keyboard();
|
||||
auto &keyboard = keyboardMachine->get_keyboard();
|
||||
keyboard.set_key_pressed(*key, event->text().size() ? event->text()[0].toLatin1() : '\0', isPressed);
|
||||
if(keyboard.is_exclusive() || keyboard.observed_keys().find(*key) != keyboard.observed_keys().end()) {
|
||||
return false;
|
||||
@@ -1163,7 +1163,9 @@ void MainWindow::start_electron() {
|
||||
auto target = std::make_unique<Target>();
|
||||
|
||||
target->has_dfs = ui->electronDFSCheckBox->isChecked();
|
||||
target->has_adfs = ui->electronADFSCheckBox->isChecked();
|
||||
target->has_pres_adfs = ui->electronADFSCheckBox->isChecked();
|
||||
target->has_ap6_rom = ui->electronAP6CheckBox->isChecked();
|
||||
target->has_sideways_ram = ui->electronSidewaysRAMCheckBox->isChecked();
|
||||
|
||||
launchTarget(std::move(target));
|
||||
}
|
||||
@@ -1298,6 +1300,8 @@ void MainWindow::launchTarget(std::unique_ptr<Analyser::Static::Target> &&target
|
||||
/* Electron. */ \
|
||||
CheckBox(electronDFSCheckBox, "electron.hasDFS"); \
|
||||
CheckBox(electronADFSCheckBox, "electron.hasADFS"); \
|
||||
CheckBox(electronAP6CheckBox, "electron.hasAP6"); \
|
||||
CheckBox(electronSidewaysRAMCheckBox, "electron.fillSidewaysRAM"); \
|
||||
\
|
||||
/* Macintosh. */ \
|
||||
ComboBox(macintoshModelComboBox, "macintosh.model"); \
|
||||
|
||||
@@ -201,6 +201,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="electronAP6CheckBox">
|
||||
<property name="text">
|
||||
<string>With Advanced Plus 6 Utility ROM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="electronSidewaysRAMCheckBox">
|
||||
<property name="text">
|
||||
<string>Fill unused ROM banks with sideways RAM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
|
||||
@@ -783,8 +783,10 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether a 'logical' keyboard has been requested.
|
||||
const bool logical_keyboard = arguments.selections.find("logical-keyboard") != arguments.selections.end();
|
||||
// Check whether a 'logical' keyboard has been requested, or the machine would prefer one anyway.
|
||||
const bool logical_keyboard =
|
||||
(arguments.selections.find("logical-keyboard") != arguments.selections.end()) ||
|
||||
(machine->keyboard_machine() && machine->keyboard_machine()->prefers_logical_input());
|
||||
if(logical_keyboard) {
|
||||
SDL_StartTextInput();
|
||||
}
|
||||
@@ -1162,13 +1164,8 @@ int main(int argc, char *argv[]) {
|
||||
} else {
|
||||
// This is a slightly terrible way of obtaining a symbol for the key, e.g. for letters it will always return
|
||||
// the capital letter version, at least empirically. But it'll have to do for now.
|
||||
//
|
||||
// TODO: ideally have a keyboard machine declare whether it wants either key events or text input? But that
|
||||
// doesn't match machines like the IIe that, to some extent, expose both. So then eliding as attempted above,
|
||||
// and keeping ephemeral track of which symbols have been tied to which keys for the benefit of future key up
|
||||
// events is probably the way forward?
|
||||
const char *key_name = SDL_GetKeyName(keypress.keycode);
|
||||
if(keyboard_machine->get_keyboard().set_key_pressed(key, key_name[0], keypress.is_down)) {
|
||||
if(keyboard_machine->get_keyboard().set_key_pressed(key, (strlen(key_name) == 1) ? key_name[0] : 0, keypress.is_down)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,8 +194,9 @@ std::vector<std::string> ScanTarget::bindings(ShaderType type) const {
|
||||
std::string ScanTarget::sampling_function() const {
|
||||
std::string fragment_shader;
|
||||
const auto modals = BufferingScanTarget::modals();
|
||||
const bool is_svideo = modals.display_type == DisplayType::SVideo;
|
||||
|
||||
if(modals.display_type == DisplayType::SVideo) {
|
||||
if(is_svideo) {
|
||||
fragment_shader +=
|
||||
"vec2 svideo_sample(vec2 coordinate, float angle) {";
|
||||
} else {
|
||||
@@ -203,7 +204,6 @@ std::string ScanTarget::sampling_function() const {
|
||||
"float composite_sample(vec2 coordinate, float angle) {";
|
||||
}
|
||||
|
||||
const bool is_svideo = modals.display_type == DisplayType::SVideo;
|
||||
switch(modals.input_data_type) {
|
||||
case InputDataType::Luminance1:
|
||||
case InputDataType::Luminance8:
|
||||
@@ -341,7 +341,7 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
vertex_shader +=
|
||||
"compositeAngle = (mix(startCompositeAngle, endCompositeAngle, lateral) / 32.0) * 3.141592654;"
|
||||
"compositeAmplitude = lineCompositeAmplitude / 255.0;"
|
||||
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));";
|
||||
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.95, lineCompositeAmplitude));";
|
||||
}
|
||||
|
||||
vertex_shader +=
|
||||
@@ -379,40 +379,42 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
|
||||
switch(modals.display_type) {
|
||||
case DisplayType::CompositeColour:
|
||||
fragment_shader +=
|
||||
"vec4 angles = compositeAngle + compositeAngleOffsets;"
|
||||
fragment_shader += R"x(
|
||||
vec4 angles = compositeAngle + compositeAngleOffsets;
|
||||
|
||||
// Sample four times over, at proper angle offsets.
|
||||
"vec4 samples = vec4("
|
||||
"composite_sample(textureCoordinates[0], angles.x),"
|
||||
"composite_sample(textureCoordinates[1], angles.y),"
|
||||
"composite_sample(textureCoordinates[2], angles.z),"
|
||||
"composite_sample(textureCoordinates[3], angles.w)"
|
||||
");"
|
||||
vec4 samples = vec4(
|
||||
composite_sample(textureCoordinates[0], angles.x),
|
||||
composite_sample(textureCoordinates[1], angles.y),
|
||||
composite_sample(textureCoordinates[2], angles.z),
|
||||
composite_sample(textureCoordinates[3], angles.w)
|
||||
);
|
||||
|
||||
// Compute a luminance for use if there's no colour information, now, before
|
||||
// modifying samples.
|
||||
"float mono_luminance = dot(samples, vec4(0.15, 0.35, 0.35, 0.15));"
|
||||
// The outer structure of the OpenGL scan target means in practice that
|
||||
// compositeAmplitude will be the same value across a piece of
|
||||
// geometry. I am therefore optimistic that this conditional will not
|
||||
// cause a divergence in fragment execution.
|
||||
if(compositeAmplitude < 0.01) {
|
||||
// Compute only a luminance for use if there's no colour information.
|
||||
fragColour3 = vec3(dot(samples, vec4(0.15, 0.35, 0.35, 0.15)));
|
||||
} else {
|
||||
// Take the average to calculate luminance, then subtract that from all four samples to
|
||||
// give chrominance.
|
||||
float luminance = dot(samples, vec4(0.25));
|
||||
|
||||
// Take the average to calculate luminance, then subtract that from all four samples to
|
||||
// give chrominance.
|
||||
"float luminance = dot(samples, vec4(0.25));"
|
||||
// Split and average chrominance.
|
||||
vec2 chrominances[4] = vec2[4](
|
||||
textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb
|
||||
);
|
||||
vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);
|
||||
|
||||
// Split and average chrominance.
|
||||
"vec2 chrominances[4] = vec2[4]("
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb"
|
||||
");"
|
||||
"vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);"
|
||||
|
||||
// Apply a colour space conversion to get RGB.
|
||||
"fragColour3 = mix("
|
||||
"lumaChromaToRGB * vec3(luminance / (1.0 - compositeAmplitude), channels),"
|
||||
"vec3(mono_luminance),"
|
||||
"step(oneOverCompositeAmplitude, 0.01)"
|
||||
");";
|
||||
// Apply a colour space conversion to get RGB.
|
||||
fragColour3 = lumaChromaToRGB * vec3(luminance / (1.0 - compositeAmplitude), channels);
|
||||
}
|
||||
)x";
|
||||
break;
|
||||
|
||||
case DisplayType::CompositeMonochrome:
|
||||
@@ -622,7 +624,7 @@ std::unique_ptr<Shader> ScanTarget::qam_separation_shader() const {
|
||||
|
||||
"compositeAngle = compositeAngle * 2.0 * 3.141592654;"
|
||||
"compositeAmplitude = lineCompositeAmplitude / 255.0;"
|
||||
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.01, lineCompositeAmplitude));";
|
||||
"oneOverCompositeAmplitude = mix(0.0, 255.0 / lineCompositeAmplitude, step(0.95, lineCompositeAmplitude));";
|
||||
|
||||
if(is_svideo) {
|
||||
vertex_shader +=
|
||||
|
||||
31
README.md
31
README.md
@@ -2,15 +2,12 @@
|
||||
# Clock Signal
|
||||
Clock Signal ('CLK') is an emulator for tourists that seeks to be invisible. Users directly launch classic software with no emulator or per-emulated-machine learning curve.
|
||||
|
||||
macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). For desktop Linux it is also available as a [Snap](https://snapcraft.io/clock-signal).
|
||||
macOS and source releases are [hosted on GitHub](https://github.com/TomHarte/CLK/releases). For desktop Linux it is also available as a [Snap](https://snapcraft.io/clock-signal). On the Mac it is a native Cocoa and Metal application; under Linux, BSD and other UNIXes and UNIX-alikes it uses OpenGL and can be built either with Qt or with SDL.
|
||||
|
||||
On the Mac it is a native Cocoa and Metal application; under Linux, BSD and other UNIXes and UNIX-alikes it uses OpenGL and can be built either with Qt or with SDL.
|
||||
|
||||
So its aims are:
|
||||
This emulator seeks to offer:
|
||||
* single-click load of any piece of source media for any supported platform;
|
||||
* with a heavy signal processing tilt for accurate reproduction of original outputs;
|
||||
* that aims for the lowest possible latency; and
|
||||
* 100% accurate emulations, naturally.
|
||||
* while minimising latency.
|
||||
|
||||
It currently contains emulations of the:
|
||||
* Acorn Electron;
|
||||
@@ -28,7 +25,7 @@ It currently contains emulations of the:
|
||||
|
||||
## 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.
|
||||
Through static and runtime analysis CLK seeks 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.
|
||||
|
||||
The full process of loading a title — even if you've never used the emulated machine before — is therefore:
|
||||
|
||||
@@ -37,12 +34,10 @@ The full process of loading a title — even if you've never used the emulated m
|
||||
|
||||
## Signal Processing
|
||||
|
||||
Consider an ordinary, unmodified Commodore Vic-20. Its only video output is composite. Therefore the emulated machine's only video output is composite. In order to display the video output, your GPU then decodes composite video. Therefore all composite video artefacts are present and exactly correct, not because of a post hoc filter combining all the subjective effects that this author associates with composite video but because the real signal is really being processed.
|
||||
Consider an ordinary, unmodified Commodore Vic-20. Its only video output is composite. Therefore the emulated machine's only video output is composite. In order to display the video output, your GPU must decode composite video. Therefore composite video artefacts are present and correct not because of a post hoc filter but because the real signal is really being processed.
|
||||
|
||||
Similar effort is put into audio generation. If the real machine normally generates audio at 192Khz then the emulator generates a 192Khz source signal and filters it down to whatever the host machine can output.
|
||||
|
||||
If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k rendering of a composite display and, assuming the emulated machine produces source audio at or above 96Khz, 96,000 individual distinct audio samples a second. Interlaced video also works and looks much as it always did on those machines that produce it.
|
||||
|
||||
### Samples
|
||||
|
||||
| 1:1 Pixel Copying | Composite Decoded |
|
||||
@@ -61,15 +56,15 @@ If your machine has a 4k monitor and a 96Khz audio output? Then you'll get a 4k
|
||||
|
||||
## Low Latency
|
||||
|
||||
The display produced is an emulated CRT, with phosphor decay. Therefore if you have a 140Hz monitor it can produce 140 distinct frames per second. Latency is dictated by the output hardware, not the emulated machine.
|
||||
The display produced is an emulated CRT, with phosphor decay. Therefore if you have a 140Hz 4k monitor it can produce 140 distinct frames per second at 4k resolution. Latency is dictated by the host hardware, not the emulated machine or emulator.
|
||||
|
||||
The machine update mechanism is influenced separately by both screen refresh and audio stream processing; audio latency is therefore generally restrained to 5–10ms regardless of your screen's refresh rate.
|
||||
|
||||
A corollary of emulating the continuous nature CRT, not merely performing end-of-frame transcriptions, is that the most common motion aliasing effects of displaying 50Hz video on a 60Hz display are minimised; you don't have to own niche equipment to benefit.
|
||||
Audio latency is disjoint from frame rate and is generlaly restrained to 5–10ms.
|
||||
|
||||
## Accurate Emulation
|
||||
|
||||
Cycle-accurate emulation for the supported target machines is fairly trite; this emulator seeks to follow that precedent. All emulation logic is written in C++ for explicit control over costs but, where a conflict arises, the presumption is towards clarity and simplicity of code. This emulator is willing to spend the processing resources available on modern hardware.
|
||||
Accuracy affects usability; the more accurate an emulator, the more likely that a user can run every piece of software they're interested in without further intervention.
|
||||
|
||||
This emulator attempts cycle-accurate emulation of all supported machines. In some cases it succeeds.
|
||||
|
||||
## Additional Screenshots
|
||||
| | |
|
||||
@@ -83,3 +78,9 @@ Cycle-accurate emulation for the supported target machines is fairly trite; this
|
||||
|
||||

|
||||

|
||||
|
||||
## Sponsorship
|
||||
|
||||
I've been asked several times whether it is possible to sponsor this project; I think that's a poor fit for this emulator's highly-malleable scope, and it makes me uncomfortable because as the author I primarily see only its defects.
|
||||
|
||||
An Amazon US wishlist is now attached in the hope of avoiding the question in future. A lot of it is old books now available only secondhand — I like to read about potential future additions well in advance of starting on them. Per the optimism of some book sellers, please don't purchase anything that is currnetly listed only at an absurd price; they were sorted by secondhand price when added to the list, with cheapest being $5.
|
||||
|
||||
@@ -4,13 +4,40 @@ Expected files:
|
||||
|
||||
basic.rom
|
||||
os.rom
|
||||
plus1.rom
|
||||
DFS-1770-2.20.rom
|
||||
ADFS-E00_1.rom
|
||||
DFS-1770-2.20.rom — used only if the user opens a DFS disk image
|
||||
ADFS-E00_1.rom — used only if the user opens an ADFS disk image
|
||||
ADFS-E00_2.rom
|
||||
AP6v133.rom — used only if the user opens a disk image that makes use of any of the commands given below.
|
||||
adfs.rom - used only if the user opens a hard disk image
|
||||
|
||||
Likely to be desired in the future:
|
||||
Possibly to be desired in the future:
|
||||
* os300.rom
|
||||
|
||||
adfs.rom
|
||||
ElectronExpansionRomPresAP2-v1.23.rom
|
||||
os300.rom
|
||||
Commands that trigger a request for the AP6v133 ROM:
|
||||
* *AQRPAGE
|
||||
* *BUILD
|
||||
* *DUMP
|
||||
* *FORMAT
|
||||
* *INSERT
|
||||
* *LANG
|
||||
* *LIST
|
||||
* *LOADROM
|
||||
* *LOCK
|
||||
* *LROMS
|
||||
* *RLOAD
|
||||
* *ROMS
|
||||
* *RSAVE
|
||||
* *SAVEROM
|
||||
* *SRLOAD
|
||||
* *SRLOCK
|
||||
* *SRPAGE
|
||||
* *SRSAVE
|
||||
* *SRUNLOCK
|
||||
* *SRWIPE
|
||||
* *TUBE
|
||||
* *TYPE
|
||||
* *UNLOCK
|
||||
* *UNPLUG
|
||||
* *UROMS
|
||||
* *VERIFY
|
||||
* *ZERO
|
||||
@@ -18,12 +18,13 @@ namespace {
|
||||
using namespace Storage::Disk;
|
||||
|
||||
AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
|
||||
// very loose validation: the file needs to be a multiple of 256 bytes
|
||||
// and not ungainly large
|
||||
// Check that the disk image contains a whole number of sector.
|
||||
if(file_.stats().st_size % off_t(128 << sector_size)) throw Error::InvalidFormat;
|
||||
|
||||
// Check that the disk image is at least large enough to hold an ADFS catalogue.
|
||||
if(file_.stats().st_size < 7 * off_t(128 << sector_size)) throw Error::InvalidFormat;
|
||||
|
||||
// check that the initial directory's 'Hugo's are present
|
||||
// Check that the initial directory's 'Hugo's are present.
|
||||
file_.seek(513, SEEK_SET);
|
||||
uint8_t bytes[4];
|
||||
file_.read(bytes, 4);
|
||||
@@ -33,6 +34,10 @@ AcornADF::AcornADF(const std::string &file_name) : MFMSectorDump(file_name) {
|
||||
file_.read(bytes, 4);
|
||||
if(bytes[0] != 'H' || bytes[1] != 'u' || bytes[2] != 'g' || bytes[3] != 'o') throw Error::InvalidFormat;
|
||||
|
||||
// Pick a number of heads; treat this image as double sided if it's too large to be single-sided.
|
||||
head_count_ = 1 + (file_.stats().st_size > sectors_per_track * off_t(128 << sector_size) * 80);
|
||||
|
||||
// Announce disk geometry.
|
||||
set_geometry(sectors_per_track, sector_size, 0, true);
|
||||
}
|
||||
|
||||
@@ -41,9 +46,9 @@ HeadPosition AcornADF::get_maximum_head_position() {
|
||||
}
|
||||
|
||||
int AcornADF::get_head_count() {
|
||||
return 1;
|
||||
return head_count_;
|
||||
}
|
||||
|
||||
long AcornADF::get_file_offset_for_position(Track::Address address) {
|
||||
return address.position.as_int() * (128 << sector_size) * sectors_per_track;
|
||||
return (address.position.as_int() * head_count_ + address.head) * (128 << sector_size) * sectors_per_track;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ class AcornADF: public MFMSectorDump {
|
||||
|
||||
private:
|
||||
long get_file_offset_for_position(Track::Address address) final;
|
||||
int head_count_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
41
Storage/MassStorage/Formats/DAT.cpp
Normal file
41
Storage/MassStorage/Formats/DAT.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// DAT.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/01/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "DAT.hpp"
|
||||
|
||||
using namespace Storage::MassStorage;
|
||||
|
||||
DAT::DAT(const std::string &file_name) : file_(file_name) {
|
||||
// Is the file a multiple of 256 bytes in size?
|
||||
const auto file_size = file_.stats().st_size;
|
||||
if(file_size & 255) throw std::exception();
|
||||
|
||||
// Does it contain the 'Hugo' signature?
|
||||
file_.seek(0x201, SEEK_SET);
|
||||
if(!file_.check_signature("Hugo")) {
|
||||
throw std::exception();
|
||||
}
|
||||
}
|
||||
|
||||
size_t DAT::get_block_size() {
|
||||
return 256;
|
||||
}
|
||||
|
||||
size_t DAT::get_number_of_blocks() {
|
||||
return size_t(file_.stats().st_size) / 256;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> DAT::get_block(size_t address) {
|
||||
file_.seek(long(address * 256), SEEK_SET);
|
||||
return file_.read(256);
|
||||
}
|
||||
|
||||
void DAT::set_block(size_t address, const std::vector<uint8_t> &contents) {
|
||||
file_.seek(long(address * 256), SEEK_SET);
|
||||
file_.write(contents);
|
||||
}
|
||||
40
Storage/MassStorage/Formats/DAT.hpp
Normal file
40
Storage/MassStorage/Formats/DAT.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// DAT.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 31/01/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef MassStorage_DAT_hpp
|
||||
#define MassStorage_DAT_hpp
|
||||
|
||||
#include "../MassStorageDevice.hpp"
|
||||
#include "../../FileHolder.hpp"
|
||||
|
||||
namespace Storage {
|
||||
namespace MassStorage {
|
||||
|
||||
/*!
|
||||
Provides a @c MassStorageDevice containing an Acorn ADFS image, which is just a
|
||||
sector dump of an ADFS volume. It will be validated for an ADFS catalogue and communicate
|
||||
in 256-byte blocks.
|
||||
*/
|
||||
class DAT: public MassStorageDevice {
|
||||
public:
|
||||
DAT(const std::string &file_name);
|
||||
|
||||
private:
|
||||
FileHolder file_;
|
||||
|
||||
/* 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* MassStorage_DAT_hpp */
|
||||
@@ -38,6 +38,7 @@ bool DirectAccessDevice::write(const Target::CommandState &state, Target::Respon
|
||||
if(!device_) return false;
|
||||
|
||||
const auto specs = state.read_write_specs();
|
||||
LOG("Write: " << specs.number_of_blocks << " to " << specs.address);
|
||||
|
||||
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();
|
||||
|
||||
@@ -90,6 +90,13 @@ constexpr double DeskewDelay = ns(45.0);
|
||||
/// any two devices.
|
||||
constexpr double CableSkew = ns(10.0);
|
||||
|
||||
/*!
|
||||
@returns The value of the data lines per @c state.
|
||||
*/
|
||||
constexpr uint8_t data_lines(BusState state) {
|
||||
return uint8_t(state & 0xff);
|
||||
}
|
||||
|
||||
#undef ns
|
||||
#undef us
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ template <typename Executor> void Target<Executor>::scsi_bus_did_change(Bus *, B
|
||||
/*
|
||||
"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."
|
||||
are false. It then asserts the signal within a selection
|
||||
abort time."
|
||||
*/
|
||||
|
||||
// Wait for deskew, at the very least.
|
||||
|
||||
Reference in New Issue
Block a user