mirror of
https://github.com/TomHarte/CLK.git
synced 2025-10-25 09:27:01 +00:00
Compare commits
344 Commits
2021-04-16
...
2021-07-03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceb62ac7f9 | ||
|
|
bda0756620 | ||
|
|
6b47fb38c6 | ||
|
|
38bf8a06a7 | ||
|
|
196651d9aa | ||
|
|
6b46212a4e | ||
|
|
2a6fff2008 | ||
|
|
c5944efe50 | ||
|
|
f384370b18 | ||
|
|
0c09275a9f | ||
|
|
278671cdb9 | ||
|
|
964d2d4fa4 | ||
|
|
f371221dba | ||
|
|
27b0579ec6 | ||
|
|
283092cfbc | ||
|
|
614953a222 | ||
|
|
4fffb3cf19 | ||
|
|
850aa2b23a | ||
|
|
d715e5fd1d | ||
|
|
7826a26c7b | ||
|
|
dc0a82cf9a | ||
|
|
2e60c81bd6 | ||
|
|
763b9ba0ec | ||
|
|
bae8bb0c00 | ||
|
|
bcf483fb7e | ||
|
|
a5b7d819a7 | ||
|
|
fe07a0b1d8 | ||
|
|
d9231e5d4a | ||
|
|
b7aa1a1c84 | ||
|
|
32e144115d | ||
|
|
177cc96f49 | ||
|
|
51d98ef9ab | ||
|
|
2327c48cc4 | ||
|
|
742d44a532 | ||
|
|
52b96db2b9 | ||
|
|
0b9de78c38 | ||
|
|
2c28cb8c57 | ||
|
|
483fe82e9d | ||
|
|
29492d6138 | ||
|
|
19310e32c4 | ||
|
|
c04a395499 | ||
|
|
1c424833a9 | ||
|
|
a46ff5590d | ||
|
|
ab059b63fd | ||
|
|
3d8fc9952d | ||
|
|
8ce8fbd977 | ||
|
|
7f08218b28 | ||
|
|
2c139ad931 | ||
|
|
1119779c8b | ||
|
|
5351ac560f | ||
|
|
49f0ab0f15 | ||
|
|
a5c57e777e | ||
|
|
3c59042388 | ||
|
|
919e211bc4 | ||
|
|
daa0737ce4 | ||
|
|
36805cb120 | ||
|
|
7de69e9874 | ||
|
|
b93575bbcc | ||
|
|
116e0f0105 | ||
|
|
e4a650aaff | ||
|
|
b5312b9ba0 | ||
|
|
6afee7bb9b | ||
|
|
5729e6e13a | ||
|
|
2f53b105bb | ||
|
|
b698056f78 | ||
|
|
95c906f03d | ||
|
|
be19fa9dde | ||
|
|
81e9ba5608 | ||
|
|
f2d7b9f6a9 | ||
|
|
1ea034310a | ||
|
|
bdcab447f9 | ||
|
|
10bf6744aa | ||
|
|
895d98e266 | ||
|
|
903e343895 | ||
|
|
f8b7c59616 | ||
|
|
fcd267a3f9 | ||
|
|
f8bb66d2a0 | ||
|
|
90782d3c27 | ||
|
|
f2336d2efc | ||
|
|
c2d093fa3c | ||
|
|
1a97cc8a91 | ||
|
|
c34a548fa0 | ||
|
|
d1b89392a2 | ||
|
|
ed734754e5 | ||
|
|
520c3c9218 | ||
|
|
9230cf1726 | ||
|
|
6e616972a5 | ||
|
|
f98888824d | ||
|
|
6c8b23e708 | ||
|
|
2c2bb3765f | ||
|
|
0d165740ea | ||
|
|
88f0f2b623 | ||
|
|
0afa143375 | ||
|
|
8319aca351 | ||
|
|
a66734883a | ||
|
|
d2ab0dd839 | ||
|
|
2574407afb | ||
|
|
83a54fd6d2 | ||
|
|
e062780968 | ||
|
|
3acd0be1f7 | ||
|
|
69c0734975 | ||
|
|
c1678d7be7 | ||
|
|
117f9a9794 | ||
|
|
b49cc407c6 | ||
|
|
954386f1cc | ||
|
|
d7ff6bd04d | ||
|
|
6025516f9f | ||
|
|
d8b9cdf7a2 | ||
|
|
09dbff39f2 | ||
|
|
2fe15a6168 | ||
|
|
07dc26f8fa | ||
|
|
a08d65b1ff | ||
|
|
199621db08 | ||
|
|
0e1e8c7faa | ||
|
|
42a98e1676 | ||
|
|
23e26e0333 | ||
|
|
fadb04f3f3 | ||
|
|
4968ccf46d | ||
|
|
1dcac304d3 | ||
|
|
1651efe4fc | ||
|
|
8f24aed43e | ||
|
|
a381374e31 | ||
|
|
9411c37d23 | ||
|
|
6af6f21868 | ||
|
|
9a0022cfcb | ||
|
|
266310d9c2 | ||
|
|
fbf1adef05 | ||
|
|
311ddfb05a | ||
|
|
2fd8a8aa66 | ||
|
|
0c3e9dca28 | ||
|
|
c331d15429 | ||
|
|
4414e96710 | ||
|
|
7161783a4f | ||
|
|
cbac48da86 | ||
|
|
d9142d5427 | ||
|
|
e5e988b28f | ||
|
|
e94e051c87 | ||
|
|
5fc91effb5 | ||
|
|
6c9dacbe89 | ||
|
|
6a7eb832cc | ||
|
|
60cf8486bb | ||
|
|
90b8163d54 | ||
|
|
a1e4389f63 | ||
|
|
440b11708b | ||
|
|
f90dce5c54 | ||
|
|
606c7709cf | ||
|
|
1d1e6d1820 | ||
|
|
3eb4dd74a2 | ||
|
|
853914480c | ||
|
|
fe04410681 | ||
|
|
1f686c4e6b | ||
|
|
2a2ac1227b | ||
|
|
b5340c8f74 | ||
|
|
196c4dcdd9 | ||
|
|
c5a86f0ef7 | ||
|
|
88f2a2940b | ||
|
|
26b019a4d4 | ||
|
|
5f7b3ae313 | ||
|
|
61c127ed2e | ||
|
|
333981e2a7 | ||
|
|
423fbc9ac7 | ||
|
|
1c1719e561 | ||
|
|
56c30e1651 | ||
|
|
1ea4130035 | ||
|
|
57713d63fa | ||
|
|
d18a537509 | ||
|
|
8e0a6df03b | ||
|
|
95a52a9f62 | ||
|
|
ae2993625c | ||
|
|
0982141442 | ||
|
|
85fab2abc4 | ||
|
|
de3b37799c | ||
|
|
70851f3b2d | ||
|
|
462bbf2e40 | ||
|
|
778b9ef683 | ||
|
|
96e7eb1bed | ||
|
|
05671f3553 | ||
|
|
6e4832f999 | ||
|
|
54e3332673 | ||
|
|
6c559d7556 | ||
|
|
9165a85484 | ||
|
|
98ada2588a | ||
|
|
43f686c22d | ||
|
|
4a2673d757 | ||
|
|
f27e331462 | ||
|
|
dd64aef910 | ||
|
|
95971f39f1 | ||
|
|
83beb3c0e6 | ||
|
|
76335e5cf2 | ||
|
|
4494320238 | ||
|
|
5acd97c860 | ||
|
|
b0f551c307 | ||
|
|
b6b3d845a3 | ||
|
|
505d84f336 | ||
|
|
1d5144b912 | ||
|
|
deff91e460 | ||
|
|
afd8dc0915 | ||
|
|
fbee74e1fe | ||
|
|
ccd82591aa | ||
|
|
64931e476d | ||
|
|
604a715a49 | ||
|
|
24757ef20c | ||
|
|
e36cc9e777 | ||
|
|
2e999889bd | ||
|
|
f4db4c3a73 | ||
|
|
d923fe72c0 | ||
|
|
f05cdd5e34 | ||
|
|
f9954619d4 | ||
|
|
0aa8c3c40d | ||
|
|
a30eeaab6a | ||
|
|
3858e79579 | ||
|
|
b4a5fa33b0 | ||
|
|
2a6e9c5e8a | ||
|
|
488c2aed51 | ||
|
|
5483f979dc | ||
|
|
ea11f3826a | ||
|
|
ceae81a332 | ||
|
|
50ea56e908 | ||
|
|
bfb2f79cff | ||
|
|
8268e8ee4c | ||
|
|
cb31e22f59 | ||
|
|
6752f4fd73 | ||
|
|
22c31e4f55 | ||
|
|
c2ff64c1e0 | ||
|
|
4db792591a | ||
|
|
1290a8e32b | ||
|
|
8ae38991b0 | ||
|
|
6d40549c0c | ||
|
|
93d5c9a3c7 | ||
|
|
9af6c0b37a | ||
|
|
7e3528c692 | ||
|
|
41f2fc51be | ||
|
|
11228dc265 | ||
|
|
ef50967793 | ||
|
|
5f6c08b7e0 | ||
|
|
6cb23ec5be | ||
|
|
1bae70bcf8 | ||
|
|
9820591ba4 | ||
|
|
77071b3c69 | ||
|
|
335e839b31 | ||
|
|
6fe947b8b9 | ||
|
|
22b29e77a7 | ||
|
|
4858cfce6b | ||
|
|
8da3e91f5e | ||
|
|
012235bfeb | ||
|
|
052e284c33 | ||
|
|
32e3dd71b1 | ||
|
|
95f4272919 | ||
|
|
00679b6135 | ||
|
|
2c18bb4508 | ||
|
|
0cf1c9040a | ||
|
|
9196341482 | ||
|
|
685140a4c2 | ||
|
|
1465b0ee4d | ||
|
|
0bf6b765d3 | ||
|
|
4774676e2a | ||
|
|
9c29655da2 | ||
|
|
c8ab18f2b6 | ||
|
|
8ebce466db | ||
|
|
1b39b17125 | ||
|
|
5a46853075 | ||
|
|
48ad4d4c4c | ||
|
|
056a036712 | ||
|
|
70eaa79108 | ||
|
|
20c814a4dd | ||
|
|
6a052e1900 | ||
|
|
cecdf8584a | ||
|
|
4758bc8615 | ||
|
|
c906dc3c0a | ||
|
|
d1dcb41b6f | ||
|
|
96ac86a757 | ||
|
|
4919786825 | ||
|
|
24b4185714 | ||
|
|
ad10d0037a | ||
|
|
b6554c8255 | ||
|
|
01dc83d0d6 | ||
|
|
2fd08789ab | ||
|
|
bc9e529995 | ||
|
|
708c24cc57 | ||
|
|
7fb3048257 | ||
|
|
9319f0525a | ||
|
|
b7a62e0121 | ||
|
|
bd5dd9b9a3 | ||
|
|
3348167c46 | ||
|
|
700c505974 | ||
|
|
d403036d86 | ||
|
|
5e08d7db39 | ||
|
|
c34cb310a8 | ||
|
|
8d86aa69bc | ||
|
|
cc41ccc5f1 | ||
|
|
e6252fe0ed | ||
|
|
03577de675 | ||
|
|
205518ba75 | ||
|
|
2510064218 | ||
|
|
0ef2806970 | ||
|
|
d80f03e369 | ||
|
|
fd271d920b | ||
|
|
2bbf8bc9fa | ||
|
|
9b65d56ed0 | ||
|
|
a5098a60ec | ||
|
|
0ebd900e40 | ||
|
|
7aeb17ac92 | ||
|
|
cc78bfb229 | ||
|
|
485c2a866c | ||
|
|
5b419ca5bf | ||
|
|
14ae579fca | ||
|
|
1c2ea0d7fe | ||
|
|
e7a9ae18a1 | ||
|
|
d61f478a39 | ||
|
|
9cc747b3e2 | ||
|
|
2f223f7db2 | ||
|
|
17f11a3be3 | ||
|
|
37dcf61130 | ||
|
|
856ebfacca | ||
|
|
9731fdd33b | ||
|
|
5ea605ccf7 | ||
|
|
d0c789ff9a | ||
|
|
9baa861742 | ||
|
|
30a1a53c97 | ||
|
|
bdb1b7e77c | ||
|
|
9293bcbc88 | ||
|
|
c481f475e7 | ||
|
|
ef01471e17 | ||
|
|
73c8157197 | ||
|
|
af1dc2d3b2 | ||
|
|
8f6b3feee1 | ||
|
|
a20f5528b7 | ||
|
|
f48876d80e | ||
|
|
db52f13c32 | ||
|
|
2590769d3f | ||
|
|
5667dcac36 | ||
|
|
bec71ead39 | ||
|
|
e4d9022d37 | ||
|
|
572be48f38 | ||
|
|
6f4ccebfa1 | ||
|
|
77fcf52d27 | ||
|
|
79c2bc1fd7 | ||
|
|
76370d9418 | ||
|
|
7bac18bd65 | ||
|
|
704737144a | ||
|
|
2a9c73a1d3 | ||
|
|
e87e851401 | ||
|
|
80d4846a27 | ||
|
|
9fd53c9c91 |
@@ -19,6 +19,7 @@ enum class Machine {
|
||||
AtariST,
|
||||
ColecoVision,
|
||||
Electron,
|
||||
Enterprise,
|
||||
Macintosh,
|
||||
MasterSystem,
|
||||
MSX,
|
||||
|
||||
81
Analyser/Static/Enterprise/StaticAnalyser.cpp
Normal file
81
Analyser/Static/Enterprise/StaticAnalyser.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// StaticAnalyser.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/06/2021.
|
||||
// Copyright 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "StaticAnalyser.hpp"
|
||||
#include "Target.hpp"
|
||||
|
||||
#include "../../../Storage/Disk/Parsers/FAT.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace {
|
||||
|
||||
bool insensitive_equal(const std::string &lhs, const std::string &rhs) {
|
||||
return std::equal(
|
||||
lhs.begin(), lhs.end(),
|
||||
rhs.begin(), rhs.end(),
|
||||
[] (char l, char r) {
|
||||
return tolower(l) == tolower(r);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(const Media &media, const std::string &, TargetPlatform::IntType) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
|
||||
// Otherwise, assume a return will happen.
|
||||
Analyser::Static::TargetList targets;
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
auto *const target = new Target;
|
||||
target->media = media;
|
||||
|
||||
// Always require a BASIC.
|
||||
target->basic_version = Target::BASICVersion::Any;
|
||||
|
||||
// Inspect any supplied disks.
|
||||
if(!media.disks.empty()) {
|
||||
// DOS will be needed.
|
||||
target->dos = Target::DOS::EXDOS;
|
||||
|
||||
// Grab the volume information, which includes the root directory.
|
||||
auto volume = Storage::Disk::FAT::GetVolume(media.disks.front());
|
||||
if(volume) {
|
||||
// If there's an EXDOS.INI then this disk should be able to boot itself.
|
||||
// If not but if there's only one .COM or .BAS, automatically load that.
|
||||
// Failing that, issue a :DIR and give the user a clue as to how to load.
|
||||
const Storage::Disk::FAT::File *selected_file = nullptr;
|
||||
bool has_exdos_ini = false;
|
||||
bool did_pick_file = false;
|
||||
for(const auto &file: (*volume).root_directory) {
|
||||
if(insensitive_equal(file.name, "exdos") && insensitive_equal(file.extension, "ini")) {
|
||||
has_exdos_ini = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if(insensitive_equal(file.extension, "com") || insensitive_equal(file.extension, "bas")) {
|
||||
did_pick_file = !selected_file;
|
||||
selected_file = &file;
|
||||
}
|
||||
}
|
||||
|
||||
if(!has_exdos_ini) {
|
||||
if(did_pick_file) {
|
||||
target->loading_command = std::string("run \"") + selected_file->name + "." + selected_file->extension + "\"";
|
||||
} else {
|
||||
target->loading_command = ":dir\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
|
||||
return targets;
|
||||
}
|
||||
27
Analyser/Static/Enterprise/StaticAnalyser.hpp
Normal file
27
Analyser/Static/Enterprise/StaticAnalyser.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// StaticAnalyser.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/06/2021.
|
||||
// Copyright 2018 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Enterprise_StaticAnalyser_hpp
|
||||
#define Analyser_Static_Enterprise_StaticAnalyser_hpp
|
||||
|
||||
#include "../StaticAnalyser.hpp"
|
||||
#include "../../../Storage/TargetPlatforms.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
|
||||
TargetList GetTargets(const Media &media, const std::string &file_name, TargetPlatform::IntType potential_platforms);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_StaticAnalyser_hpp */
|
||||
53
Analyser/Static/Enterprise/Target.hpp
Normal file
53
Analyser/Static/Enterprise/Target.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Target.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Analyser_Static_Enterprise_Target_h
|
||||
#define Analyser_Static_Enterprise_Target_h
|
||||
|
||||
#include "../../../Reflection/Enum.hpp"
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../StaticAnalyser.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
namespace Enterprise {
|
||||
|
||||
struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Target> {
|
||||
ReflectableEnum(Model, Enterprise64, Enterprise128, Enterprise256);
|
||||
ReflectableEnum(EXOSVersion, v10, v20, v21, v23, Any);
|
||||
ReflectableEnum(BASICVersion, v10, v11, v21, Any, None);
|
||||
ReflectableEnum(DOS, EXDOS, None);
|
||||
|
||||
Model model = Model::Enterprise128;
|
||||
EXOSVersion exos_version = EXOSVersion::Any;
|
||||
BASICVersion basic_version = BASICVersion::None;
|
||||
DOS dos = DOS::None;
|
||||
std::string loading_command;
|
||||
|
||||
Target() : Analyser::Static::Target(Machine::Enterprise) {
|
||||
if(needs_declare()) {
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(EXOSVersion);
|
||||
AnnounceEnum(BASICVersion);
|
||||
AnnounceEnum(DOS);
|
||||
|
||||
DeclareField(model);
|
||||
DeclareField(exos_version);
|
||||
DeclareField(basic_version);
|
||||
DeclareField(dos);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Analyser_Static_Enterprise_Target_h */
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "Coleco/StaticAnalyser.hpp"
|
||||
#include "Commodore/StaticAnalyser.hpp"
|
||||
#include "DiskII/StaticAnalyser.hpp"
|
||||
#include "Enterprise/StaticAnalyser.hpp"
|
||||
#include "Macintosh/StaticAnalyser.hpp"
|
||||
#include "MSX/StaticAnalyser.hpp"
|
||||
#include "Oric/StaticAnalyser.hpp"
|
||||
@@ -43,9 +44,9 @@
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MacintoshIMG.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/G64.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/DMK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/FAT12.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/HFE.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSA.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/MSXDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/NIB.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/OricMFMDSK.hpp"
|
||||
#include "../../Storage/Disk/DiskImage/Formats/SSD.hpp"
|
||||
@@ -55,8 +56,14 @@
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "../../Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/DSK.hpp"
|
||||
#include "../../Storage/MassStorage/Formats/HFV.hpp"
|
||||
|
||||
// State Snapshots
|
||||
#include "../../Storage/State/SNA.hpp"
|
||||
#include "../../Storage/State/SZX.hpp"
|
||||
#include "../../Storage/State/Z80.hpp"
|
||||
|
||||
// Tapes
|
||||
#include "../../Storage/Tape/Formats/CAS.hpp"
|
||||
#include "../../Storage/Tape/Formats/CommodoreTAP.hpp"
|
||||
@@ -73,15 +80,23 @@
|
||||
|
||||
using namespace Analyser::Static;
|
||||
|
||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||
Media result;
|
||||
namespace {
|
||||
|
||||
std::string get_extension(const std::string &name) {
|
||||
// Get the extension, if any; it will be assumed that extensions are reliable, so an extension is a broad-phase
|
||||
// test as to file format.
|
||||
std::string::size_type final_dot = file_name.find_last_of(".");
|
||||
if(final_dot == std::string::npos) return result;
|
||||
std::string extension = file_name.substr(final_dot + 1);
|
||||
std::string::size_type final_dot = name.find_last_of(".");
|
||||
if(final_dot == std::string::npos) return name;
|
||||
std::string extension = name.substr(final_dot + 1);
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
|
||||
return extension;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::IntType &potential_platforms) {
|
||||
Media result;
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
#define InsertInstance(list, instance, platforms) \
|
||||
list.emplace_back(instance);\
|
||||
@@ -131,8 +146,9 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
TargetPlatform::AmstradCPC | TargetPlatform::Oric | TargetPlatform::ZXSpectrum) // DSK (Amstrad CPC, etc)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::AppleDSK>, TargetPlatform::DiskII) // DSK (Apple II)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // DSK (Macintosh, floppy disk)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::MSXDSK>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::HFV, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, single volume image)
|
||||
Format("dsk", result.mass_storage_devices, MassStorage::DSK, TargetPlatform::Macintosh) // DSK (Macintosh, hard disk, full device image)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::MSX) // DSK (MSX)
|
||||
Format("dsk", result.disks, Disk::DiskImageHolder<Storage::Disk::OricMFMDSK>, TargetPlatform::Oric) // DSK (Oric)
|
||||
Format("g64", result.disks, Disk::DiskImageHolder<Storage::Disk::G64>, TargetPlatform::Commodore) // G64
|
||||
Format( "hfe",
|
||||
@@ -142,6 +158,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
// HFE (TODO: switch to AllDisk once the MSX stops being so greedy)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("image", result.disks, Disk::DiskImageHolder<Storage::Disk::MacintoshIMG>, TargetPlatform::Macintosh) // IMG (DiskCopy 4.2)
|
||||
Format("img", result.disks, Disk::DiskImageHolder<Storage::Disk::FAT12>, TargetPlatform::Enterprise) // IMG (Enterprise/MS-DOS style)
|
||||
Format("msa", result.disks, Disk::DiskImageHolder<Storage::Disk::MSA>, TargetPlatform::AtariST) // MSA
|
||||
Format("nib", result.disks, Disk::DiskImageHolder<Storage::Disk::NIB>, TargetPlatform::DiskII) // NIB
|
||||
Format("o", result.tapes, Tape::ZX80O81P, TargetPlatform::ZX8081) // O
|
||||
@@ -199,14 +216,35 @@ Media Analyser::Static::GetMedia(const std::string &file_name) {
|
||||
|
||||
TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
TargetList targets;
|
||||
const std::string extension = get_extension(file_name);
|
||||
|
||||
// Check whether the file directly identifies a target; if so then just return that.
|
||||
#define Format(ext, class) \
|
||||
if(extension == ext) { \
|
||||
try { \
|
||||
auto target = Storage::State::class::load(file_name); \
|
||||
if(target) { \
|
||||
targets.push_back(std::move(target)); \
|
||||
return targets; \
|
||||
} \
|
||||
} catch(...) {} \
|
||||
}
|
||||
|
||||
Format("sna", SNA);
|
||||
Format("szx", SZX);
|
||||
Format("z80", Z80);
|
||||
|
||||
#undef TryInsert
|
||||
|
||||
// Otherwise:
|
||||
//
|
||||
// Collect all disks, tapes ROMs, etc as can be extrapolated from this file, forming the
|
||||
// union of all platforms this file might be a target for.
|
||||
TargetPlatform::IntType potential_platforms = 0;
|
||||
Media media = GetMediaAndPlatforms(file_name, potential_platforms);
|
||||
|
||||
// Hand off to platform-specific determination of whether these things are actually compatible and,
|
||||
// if so, how to load them.
|
||||
// Hand off to platform-specific determination of whether these
|
||||
// things are actually compatible and, if so, how to load them.
|
||||
#define Append(x) if(potential_platforms & TargetPlatform::x) {\
|
||||
auto new_targets = x::GetTargets(media, file_name, potential_platforms);\
|
||||
std::move(new_targets.begin(), new_targets.end(), std::back_inserter(targets));\
|
||||
@@ -220,6 +258,7 @@ TargetList Analyser::Static::GetTargets(const std::string &file_name) {
|
||||
Append(Coleco);
|
||||
Append(Commodore);
|
||||
Append(DiskII);
|
||||
Append(Enterprise);
|
||||
Append(Macintosh);
|
||||
Append(MSX);
|
||||
Append(Oric);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "../../Storage/Disk/Disk.hpp"
|
||||
#include "../../Storage/MassStorage/MassStorageDevice.hpp"
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -23,8 +24,10 @@
|
||||
namespace Analyser {
|
||||
namespace Static {
|
||||
|
||||
struct State;
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges.
|
||||
A list of disks, tapes and cartridges, and possibly a state snapshot.
|
||||
*/
|
||||
struct Media {
|
||||
std::vector<std::shared_ptr<Storage::Disk::Disk>> disks;
|
||||
@@ -48,13 +51,16 @@ struct Media {
|
||||
};
|
||||
|
||||
/*!
|
||||
A list of disks, tapes and cartridges plus information about the machine to which to attach them and its configuration,
|
||||
and instructions on how to launch the software attached, plus a measure of confidence in this target's correctness.
|
||||
Describes a machine and possibly its state; conventionally subclassed to add other machine-specific configuration fields and any
|
||||
necessary instructions on how to launch any software provided, plus a measure of confidence in this target's correctness.
|
||||
*/
|
||||
struct Target {
|
||||
Target(Machine machine) : machine(machine) {}
|
||||
virtual ~Target() {}
|
||||
|
||||
// This field is entirely optional.
|
||||
std::unique_ptr<Reflection::Struct> state;
|
||||
|
||||
Machine machine;
|
||||
Media media;
|
||||
float confidence = 0.0f;
|
||||
|
||||
@@ -213,7 +213,7 @@ class HalfCycles: public WrappedInt<HalfCycles> {
|
||||
|
||||
/*!
|
||||
Severs from @c this the effect of dividing by @c divisor; @c this will end up with
|
||||
the value of @c this modulo @c divisor and @c divided by @c divisor is returned.
|
||||
the value of @c this modulo @c divisor . @c this divided by @c divisor is returned.
|
||||
*/
|
||||
forceinline Cycles divide_cycles(const Cycles &divisor) {
|
||||
const HalfCycles half_divisor = HalfCycles(divisor);
|
||||
|
||||
@@ -103,9 +103,9 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
}
|
||||
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
time_until_event_ -= rhs;
|
||||
time_until_event_ -= rhs * multiplier;
|
||||
if(time_until_event_ <= LocalTimeScale(0)) {
|
||||
time_overrun_ = time_until_event_;
|
||||
time_overrun_ = time_until_event_ / divider;
|
||||
flush();
|
||||
update_sequence_point();
|
||||
return true;
|
||||
@@ -145,13 +145,21 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, in the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush() const {
|
||||
// TODO: does this handle conversions properly where TargetTimeScale != LocalTimeScale?
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_;
|
||||
}
|
||||
return TargetTimeScale(time_since_update_.as_integral() / divider);
|
||||
}
|
||||
|
||||
/// @returns the amount of time since the object was last flushed, plus the local time scale @c offset,
|
||||
/// converted to the target time scale.
|
||||
[[nodiscard]] forceinline TargetTimeScale time_since_flush(LocalTimeScale offset) const {
|
||||
if constexpr (divider == 1) {
|
||||
return time_since_update_ + offset;
|
||||
}
|
||||
return TargetTimeScale((time_since_update_ + offset).as_integral() / divider);
|
||||
}
|
||||
|
||||
/// Flushes all accumulated time.
|
||||
///
|
||||
/// This does not affect this actor's record of when the next sequence point will occur.
|
||||
@@ -185,7 +193,7 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
/// @returns the number of cycles until the next sequence-point-based flush, if the embedded object
|
||||
/// supports sequence points; @c LocalTimeScale() otherwise.
|
||||
[[nodiscard]] LocalTimeScale cycles_until_implicit_flush() const {
|
||||
return time_until_event_;
|
||||
return time_until_event_ / divider;
|
||||
}
|
||||
|
||||
/// Indicates whether a sequence-point-caused flush will occur if the specified period is added.
|
||||
@@ -196,10 +204,43 @@ template <class T, class LocalTimeScale = HalfCycles, int multiplier = 1, int di
|
||||
return rhs >= time_until_event_;
|
||||
}
|
||||
|
||||
/// Indicates the amount of time, in the local time scale, until the first local slot that falls wholly
|
||||
/// after @c duration, if that delay were to occur in @c offset units of time from now.
|
||||
[[nodiscard]] forceinline LocalTimeScale back_map(TargetTimeScale duration, TargetTimeScale offset) const {
|
||||
// A 1:1 mapping is easy.
|
||||
if constexpr (multiplier == 1 && divider == 1) {
|
||||
return duration;
|
||||
}
|
||||
|
||||
// Work out when this query is placed, and the time to which it relates
|
||||
const auto base = time_since_update_ + offset * divider;
|
||||
const auto target = base + duration * divider;
|
||||
|
||||
// Figure out the number of whole input steps that is required to get
|
||||
// past target, and subtract the number of whole input steps necessary
|
||||
// to get to base.
|
||||
const auto steps_to_base = base.as_integral() / multiplier;
|
||||
const auto steps_to_target = (target.as_integral() + divider - 1) / multiplier;
|
||||
|
||||
return LocalTimeScale(steps_to_target - steps_to_base);
|
||||
}
|
||||
|
||||
/// Updates this template's record of the next sequence point.
|
||||
void update_sequence_point() {
|
||||
if constexpr (has_sequence_points<T>::value) {
|
||||
time_until_event_ = object_.get_next_sequence_point();
|
||||
// Keep a fast path where no conversions will be applied; if conversions are
|
||||
// going to be applied then do a direct max -> max translation rather than
|
||||
// allowing the arithmetic to overflow.
|
||||
if constexpr (divider == 1 && std::is_same_v<LocalTimeScale, TargetTimeScale>) {
|
||||
time_until_event_ = object_.get_next_sequence_point();
|
||||
} else {
|
||||
const auto time = object_.get_next_sequence_point();
|
||||
if(time == TargetTimeScale::max()) {
|
||||
time_until_event_ = LocalTimeScale::max();
|
||||
} else {
|
||||
time_until_event_ = time * divider;
|
||||
}
|
||||
}
|
||||
assert(time_until_event_ > LocalTimeScale(0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,7 +276,10 @@ void WD1770::posit_event(int new_event_type) {
|
||||
goto test_type1_type;
|
||||
|
||||
begin_type1_spin_up:
|
||||
if((command_&0x08) || get_drive().get_motor_on()) goto test_type1_type;
|
||||
if((command_&0x08) || get_drive().get_motor_on()) {
|
||||
set_motor_on(true);
|
||||
goto test_type1_type;
|
||||
}
|
||||
SPIN_UP();
|
||||
|
||||
test_type1_type:
|
||||
@@ -387,7 +390,10 @@ void WD1770::posit_event(int new_event_type) {
|
||||
distance_into_section_ = 0;
|
||||
|
||||
if((command_&0x08) && has_motor_on_line()) goto test_type2_delay;
|
||||
if(!has_motor_on_line() && !has_head_load_line()) goto test_type2_delay;
|
||||
if(!has_motor_on_line() && !has_head_load_line()) {
|
||||
if(has_motor_on_line()) set_motor_on(true);
|
||||
goto test_type2_delay;
|
||||
}
|
||||
|
||||
if(has_motor_on_line()) goto begin_type2_spin_up;
|
||||
goto begin_type2_load_head;
|
||||
|
||||
@@ -129,8 +129,8 @@ ClockingHint::Preference ACIA::preferred_clocking() const {
|
||||
// because it's unclear when the interrupt might come.
|
||||
if(bits_incoming_ && receive_interrupt_enabled_) return ClockingHint::Preference::RealTime;
|
||||
|
||||
// No clocking required then.
|
||||
return ClockingHint::Preference::None;
|
||||
// Real-time clocking not required then.
|
||||
return ClockingHint::Preference::JustInTime;
|
||||
}
|
||||
|
||||
bool ACIA::get_interrupt_line() const {
|
||||
|
||||
@@ -208,7 +208,7 @@ void MFP68901::run_for(HalfCycles time) {
|
||||
}
|
||||
|
||||
HalfCycles MFP68901::get_next_sequence_point() {
|
||||
return HalfCycles(-1);
|
||||
return HalfCycles::max();
|
||||
}
|
||||
|
||||
// MARK: - Timers
|
||||
|
||||
@@ -750,7 +750,7 @@ HalfCycles TMS9918::get_next_sequence_point() {
|
||||
if(next_line_interrupt_row == -1) {
|
||||
return generate_interrupts_ ?
|
||||
half_cycles_before_internal_cycles(time_until_frame_interrupt) :
|
||||
HalfCycles(-1);
|
||||
HalfCycles::max();
|
||||
}
|
||||
|
||||
// Figure out the number of internal cycles until the next line interrupt, which is the amount
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
|
||||
#include "../../Reflection/Struct.hpp"
|
||||
|
||||
namespace GI {
|
||||
namespace AY38910 {
|
||||
|
||||
@@ -162,6 +164,8 @@ template <bool is_stereo> class AY38910: public ::Outputs::Speaker::SampleSource
|
||||
uint8_t a_left_ = 255, a_right_ = 255;
|
||||
uint8_t b_left_ = 255, b_right_ = 255;
|
||||
uint8_t c_left_ = 255, c_right_ = 255;
|
||||
|
||||
friend struct State;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -192,6 +196,29 @@ struct Utility {
|
||||
|
||||
};
|
||||
|
||||
struct State: public Reflection::StructImpl<State> {
|
||||
uint8_t registers[16]{};
|
||||
uint8_t selected_register = 0;
|
||||
|
||||
// TODO: all audio-production thread state.
|
||||
|
||||
State() {
|
||||
if(needs_declare()) {
|
||||
DeclareField(registers);
|
||||
DeclareField(selected_register);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename AY> void apply(AY &target) {
|
||||
// Establish emulator-thread state
|
||||
for(uint8_t c = 0; c < 16; c++) {
|
||||
target.select_register(c);
|
||||
target.set_register_value(registers[c]);
|
||||
}
|
||||
target.select_register(selected_register);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
using namespace Concurrency;
|
||||
|
||||
AsyncTaskQueue::AsyncTaskQueue()
|
||||
#ifndef __APPLE__
|
||||
#ifndef USE_GCD
|
||||
: should_destruct_(false)
|
||||
#endif
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
serial_dispatch_queue_ = dispatch_queue_create("com.thomasharte.clocksignal.asyntaskqueue", DISPATCH_QUEUE_SERIAL);
|
||||
#else
|
||||
thread_ = std::make_unique<std::thread>([this]() {
|
||||
@@ -44,7 +44,7 @@ AsyncTaskQueue::AsyncTaskQueue()
|
||||
}
|
||||
|
||||
AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
flush();
|
||||
dispatch_release(serial_dispatch_queue_);
|
||||
serial_dispatch_queue_ = nullptr;
|
||||
@@ -57,7 +57,7 @@ AsyncTaskQueue::~AsyncTaskQueue() {
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_async(serial_dispatch_queue_, ^{function();});
|
||||
#else
|
||||
std::lock_guard lock(queue_mutex_);
|
||||
@@ -67,7 +67,7 @@ void AsyncTaskQueue::enqueue(std::function<void(void)> function) {
|
||||
}
|
||||
|
||||
void AsyncTaskQueue::flush() {
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_sync(serial_dispatch_queue_, ^{});
|
||||
#else
|
||||
auto flush_mutex = std::make_shared<std::mutex>();
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
#include <dispatch/dispatch.h>
|
||||
#define USE_GCD
|
||||
#endif
|
||||
|
||||
namespace Concurrency {
|
||||
@@ -47,7 +48,7 @@ class AsyncTaskQueue {
|
||||
void flush();
|
||||
|
||||
private:
|
||||
#ifdef __APPLE__
|
||||
#ifdef USE_GCD
|
||||
dispatch_queue_t serial_dispatch_queue_;
|
||||
#else
|
||||
std::unique_ptr<std::thread> thread_;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#ifndef Joystick_hpp
|
||||
#define Joystick_hpp
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace Inputs {
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
using namespace Inputs;
|
||||
|
||||
Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifiers_(essential_modifiers) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef CachingExecutor_hpp
|
||||
#define CachingExecutor_hpp
|
||||
|
||||
#include "Sizes.hpp"
|
||||
#include "../Numeric/Sizes.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#ifndef Disassembler_hpp
|
||||
#define Disassembler_hpp
|
||||
|
||||
#include "Sizes.hpp"
|
||||
#include "../Numeric/Sizes.hpp"
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
|
||||
@@ -787,45 +787,43 @@ template <bool has_fdc> class ConcreteMachine:
|
||||
ay_.ay().set_port_handler(&key_state_);
|
||||
|
||||
// construct the list of necessary ROMs
|
||||
const std::string machine_name = "AmstradCPC";
|
||||
std::vector<ROMMachine::ROM> required_roms = {
|
||||
ROMMachine::ROM(machine_name, "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecd)
|
||||
};
|
||||
std::string model_number;
|
||||
uint32_t crcs[2];
|
||||
bool has_amsdos = false;
|
||||
ROM::Name firmware, basic;
|
||||
|
||||
switch(target.model) {
|
||||
default:
|
||||
model_number = "6128";
|
||||
has_128k_ = true;
|
||||
crcs[0] = 0x0219bb74;
|
||||
crcs[1] = 0xca6af63d;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC464:
|
||||
model_number = "464";
|
||||
has_128k_ = false;
|
||||
crcs[0] = 0x815752df;
|
||||
crcs[1] = 0x7d9a3bac;
|
||||
firmware = ROM::Name::CPC464Firmware;
|
||||
basic = ROM::Name::CPC464BASIC;
|
||||
break;
|
||||
case Analyser::Static::AmstradCPC::Target::Model::CPC664:
|
||||
model_number = "664";
|
||||
has_128k_ = false;
|
||||
crcs[0] = 0x3f5a6dc4;
|
||||
crcs[1] = 0x32fee492;
|
||||
firmware = ROM::Name::CPC664Firmware;
|
||||
basic = ROM::Name::CPC664BASIC;
|
||||
has_amsdos = true;
|
||||
break;
|
||||
default:
|
||||
firmware = ROM::Name::CPC6128Firmware;
|
||||
basic = ROM::Name::CPC6128BASIC;
|
||||
has_amsdos = true;
|
||||
break;
|
||||
}
|
||||
required_roms.emplace_back(machine_name, "the CPC " + model_number + " firmware", "os" + model_number + ".rom", 16*1024, crcs[0]);
|
||||
required_roms.emplace_back(machine_name, "the CPC " + model_number + " BASIC ROM", "basic" + model_number + ".rom", 16*1024, crcs[1]);
|
||||
|
||||
// fetch and verify the ROMs
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
auto &data = roms[index];
|
||||
if(!data) throw ROMMachine::Error::MissingROMs;
|
||||
roms_[index] = std::move(*data);
|
||||
roms_[index].resize(16384);
|
||||
ROM::Request request = ROM::Request(firmware) && ROM::Request(basic);
|
||||
if(has_amsdos) {
|
||||
request = request && ROM::Request(ROM::Name::AMSDOS);
|
||||
}
|
||||
|
||||
// Fetch and verify the ROMs.
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
if(has_amsdos) {
|
||||
roms_[ROMType::AMSDOS] = roms.find(ROM::Name::AMSDOS)->second;
|
||||
}
|
||||
roms_[ROMType::OS] = roms.find(firmware)->second;
|
||||
roms_[ROMType::BASIC] = roms.find(basic)->second;
|
||||
|
||||
// Establish default memory map
|
||||
upper_rom_is_paged_ = true;
|
||||
upper_rom_ = ROMType::BASIC;
|
||||
|
||||
@@ -151,7 +151,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
return table_lookup_sequence_for_character(key_sequences, character);
|
||||
}
|
||||
|
||||
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {
|
||||
|
||||
@@ -16,6 +16,7 @@ void Keyboard::perform_command(const Command &command) {
|
||||
switch(command.type) {
|
||||
case Command::Type::Reset:
|
||||
modifiers_ = 0xffff;
|
||||
[[fallthrough]];
|
||||
case Command::Type::Flush: {
|
||||
std::lock_guard lock_guard(keys_mutex_);
|
||||
pending_events_.clear();
|
||||
|
||||
@@ -48,7 +48,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
public MachineTypes::JoystickMachine,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public Inputs::Keyboard,
|
||||
public Configurable::Device,
|
||||
public Activity::Source,
|
||||
public Apple::II::Card::Delegate {
|
||||
@@ -66,7 +65,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
uint8_t *ram_, *aux_ram_;
|
||||
};
|
||||
|
||||
CPU::MOS6502::Processor<(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502, ConcreteMachine, false> m6502_;
|
||||
using Processor = CPU::MOS6502::Processor<
|
||||
(model == Analyser::Static::AppleII::Target::Model::EnhancedIIe) ? CPU::MOS6502::Personality::PSynertek65C02 : CPU::MOS6502::Personality::P6502,
|
||||
ConcreteMachine,
|
||||
false>;
|
||||
Processor m6502_;
|
||||
VideoBusHandler video_bus_handler_;
|
||||
Apple::II::Video::Video<VideoBusHandler, is_iie()> video_;
|
||||
int cycles_into_current_line_ = 0;
|
||||
@@ -91,16 +94,6 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
uint8_t ram_[65536], aux_ram_[65536];
|
||||
std::vector<uint8_t> rom_;
|
||||
uint8_t keyboard_input_ = 0x00;
|
||||
bool key_is_down_ = false;
|
||||
|
||||
uint8_t get_keyboard_input() {
|
||||
if(string_serialiser_) {
|
||||
return string_serialiser_->head() | 0x80;
|
||||
} else {
|
||||
return keyboard_input_;
|
||||
}
|
||||
}
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Audio::Toggle audio_toggle_;
|
||||
@@ -258,16 +251,99 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
state.region_20_40.write ? &aux_ram_[0x2000] : &ram_[0x2000]);
|
||||
}
|
||||
|
||||
// MARK - typing
|
||||
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
|
||||
// MARK: - Keyboard and typing.
|
||||
|
||||
// MARK - Joysticks.
|
||||
struct Keyboard: public Inputs::Keyboard {
|
||||
Keyboard(Processor *m6502) : m6502_(m6502) {}
|
||||
|
||||
void reset_all_keys() final {
|
||||
open_apple_is_pressed = closed_apple_is_pressed = key_is_down = false;
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
key_is_down = true;
|
||||
} else {
|
||||
if((keyboard_input & 0x7f) == value) {
|
||||
key_is_down = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t get_keyboard_input() {
|
||||
if(string_serialiser) {
|
||||
return string_serialiser->head() | 0x80;
|
||||
} else {
|
||||
return keyboard_input;
|
||||
}
|
||||
}
|
||||
|
||||
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
|
||||
bool open_apple_is_pressed = false;
|
||||
bool closed_apple_is_pressed = false;
|
||||
uint8_t keyboard_input = 0x00;
|
||||
bool key_is_down = false;
|
||||
std::unique_ptr<Utility::StringSerialiser> string_serialiser;
|
||||
|
||||
private:
|
||||
Processor *const m6502_;
|
||||
};
|
||||
Keyboard keyboard_;
|
||||
|
||||
// MARK: - Joysticks.
|
||||
JoystickPair joysticks_;
|
||||
|
||||
// The IIe has three keys that are wired directly to the same input as the joystick buttons.
|
||||
bool open_apple_is_pressed_ = false;
|
||||
bool closed_apple_is_pressed_ = false;
|
||||
|
||||
public:
|
||||
ConcreteMachine(const Analyser::Static::AppleII::Target &target, const ROMMachine::ROMFetcher &rom_fetcher):
|
||||
m6502_(*this),
|
||||
@@ -276,7 +352,8 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
audio_toggle_(audio_queue_),
|
||||
speaker_(audio_toggle_),
|
||||
language_card_(*this),
|
||||
auxiliary_switches_(*this) {
|
||||
auxiliary_switches_(*this),
|
||||
keyboard_(&m6502_) {
|
||||
// The system's master clock rate.
|
||||
constexpr float master_clock = 14318180.0;
|
||||
|
||||
@@ -300,49 +377,55 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
|
||||
// Pick the required ROMs.
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
const std::string machine_name = "AppleII";
|
||||
std::vector<ROMMachine::ROM> rom_descriptions;
|
||||
size_t rom_size = 12*1024;
|
||||
ROM::Name character, system;
|
||||
|
||||
switch(target.model) {
|
||||
default:
|
||||
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
|
||||
rom_descriptions.emplace_back(machine_name, "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588);
|
||||
character = ROM::Name::AppleIICharacter;
|
||||
system = ROM::Name::AppleIIOriginal;
|
||||
break;
|
||||
case Target::Model::IIplus:
|
||||
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::II));
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26);
|
||||
character = ROM::Name::AppleIICharacter;
|
||||
system = ROM::Name::AppleIIPlus;
|
||||
break;
|
||||
case Target::Model::IIe:
|
||||
rom_size += 3840;
|
||||
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::IIe));
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18d);
|
||||
character = ROM::Name::AppleIIeCharacter;
|
||||
system = ROM::Name::AppleIIe;
|
||||
break;
|
||||
case Target::Model::EnhancedIIe:
|
||||
rom_size += 3840;
|
||||
rom_descriptions.push_back(video_.rom_description(Video::VideoBase::CharacterROM::EnhancedIIe));
|
||||
rom_descriptions.emplace_back(machine_name, "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942);
|
||||
character = ROM::Name::AppleIIEnhancedECharacter;
|
||||
system = ROM::Name::AppleIIEnhancedE;
|
||||
break;
|
||||
}
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
|
||||
// Try to install a Disk II card now, before checking the ROM list,
|
||||
// to make sure that Disk II dependencies have been communicated.
|
||||
if(target.disk_controller != Target::DiskController::None) {
|
||||
ROM::Request request = ROM::Request(character) && ROM::Request(system);
|
||||
|
||||
// Add the necessary Disk II requests if appropriate.
|
||||
const bool has_disk_controller = target.disk_controller != Target::DiskController::None;
|
||||
const bool is_sixteen_sector = target.disk_controller == Target::DiskController::SixteenSector;
|
||||
if(has_disk_controller) {
|
||||
// Apple recommended slot 6 for the (first) Disk II.
|
||||
install_card(6, new Apple::II::DiskIICard(rom_fetcher, target.disk_controller == Target::DiskController::SixteenSector));
|
||||
request = request && DiskIICard::rom_request(is_sixteen_sector);
|
||||
}
|
||||
|
||||
// Now, check and move the ROMs.
|
||||
if(!roms[0] || !roms[1]) {
|
||||
// Request, validate and install ROMs.
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
rom_ = std::move(*roms[1]);
|
||||
if(rom_.size() > rom_size) {
|
||||
rom_.erase(rom_.begin(), rom_.end() - off_t(rom_size));
|
||||
if(has_disk_controller) {
|
||||
install_card(6, new Apple::II::DiskIICard(roms, is_sixteen_sector));
|
||||
}
|
||||
|
||||
video_.set_character_rom(*roms[0]);
|
||||
rom_ = std::move(roms.find(system)->second);
|
||||
// The IIe and Enhanced IIe ROMs often distributed are oversized; trim if necessary.
|
||||
if(system == ROM::Name::AppleIIe || system == ROM::Name::AppleIIEnhancedE) {
|
||||
if(rom_.size() > 16128) {
|
||||
rom_.erase(rom_.begin(), rom_.end() - off_t(16128));
|
||||
}
|
||||
}
|
||||
video_.set_character_rom(roms.find(character)->second);
|
||||
|
||||
// Set up the default memory blocks. On a II or II+ these values will never change.
|
||||
// On a IIe they'll be affected by selection of auxiliary RAM.
|
||||
@@ -435,18 +518,18 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
default: break;
|
||||
|
||||
case 0xc000:
|
||||
*value = get_keyboard_input();
|
||||
*value = keyboard_.get_keyboard_input();
|
||||
break;
|
||||
case 0xc001: case 0xc002: case 0xc003: case 0xc004: case 0xc005: case 0xc006: case 0xc007:
|
||||
case 0xc008: case 0xc009: case 0xc00a: case 0xc00b: case 0xc00c: case 0xc00d: case 0xc00e: case 0xc00f:
|
||||
*value = (*value & 0x80) | (get_keyboard_input() & 0x7f);
|
||||
*value = (*value & 0x80) | (keyboard_.get_keyboard_input() & 0x7f);
|
||||
break;
|
||||
|
||||
case 0xc061: // Switch input 0.
|
||||
*value &= 0x7f;
|
||||
if(
|
||||
joysticks_.button(0) ||
|
||||
(is_iie() && open_apple_is_pressed_)
|
||||
(is_iie() && keyboard_.open_apple_is_pressed)
|
||||
)
|
||||
*value |= 0x80;
|
||||
break;
|
||||
@@ -454,7 +537,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
*value &= 0x7f;
|
||||
if(
|
||||
joysticks_.button(1) ||
|
||||
(is_iie() && closed_apple_is_pressed_)
|
||||
(is_iie() && keyboard_.closed_apple_is_pressed)
|
||||
)
|
||||
*value |= 0x80;
|
||||
break;
|
||||
@@ -476,7 +559,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
} break;
|
||||
|
||||
// The IIe-only state reads follow...
|
||||
#define IIeSwitchRead(s) *value = get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
|
||||
#define IIeSwitchRead(s) *value = keyboard_.get_keyboard_input(); if(is_iie()) *value = (*value & 0x7f) | (s ? 0x80 : 0x00);
|
||||
case 0xc011: IIeSwitchRead(language_card_.state().bank2); break;
|
||||
case 0xc012: IIeSwitchRead(language_card_.state().read); break;
|
||||
case 0xc013: IIeSwitchRead(auxiliary_switches_.switches().read_auxiliary_memory); break;
|
||||
@@ -559,15 +642,15 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
break;
|
||||
|
||||
case 0xc010:
|
||||
keyboard_input_ &= 0x7f;
|
||||
if(string_serialiser_) {
|
||||
if(!string_serialiser_->advance())
|
||||
string_serialiser_.reset();
|
||||
keyboard_.keyboard_input &= 0x7f;
|
||||
if(keyboard_.string_serialiser) {
|
||||
if(!keyboard_.string_serialiser->advance())
|
||||
keyboard_.string_serialiser.reset();
|
||||
}
|
||||
|
||||
// On the IIe, reading C010 returns additional key info.
|
||||
if(is_iie() && isReadOperation(operation)) {
|
||||
*value = (key_is_down_ ? 0x80 : 0x00) | (keyboard_input_ & 0x7f);
|
||||
*value = (keyboard_.key_is_down ? 0x80 : 0x00) | (keyboard_.keyboard_input & 0x7f);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -683,81 +766,16 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
m6502_.run_for(cycles);
|
||||
}
|
||||
|
||||
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) {
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
key_is_down_ = true;
|
||||
} else {
|
||||
if((keyboard_input_ & 0x7f) == value) {
|
||||
key_is_down_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Inputs::Keyboard &get_keyboard() final {
|
||||
return *this;
|
||||
return keyboard_;
|
||||
}
|
||||
|
||||
void type_string(const std::string &string) final {
|
||||
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(string, true);
|
||||
keyboard_.string_serialiser = std::make_unique<Utility::StringSerialiser>(string, true);
|
||||
}
|
||||
|
||||
bool can_type(char c) const final {
|
||||
@@ -769,12 +787,14 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
|
||||
std::unique_ptr<Reflection::Struct> get_options() final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
options->use_square_pixels = video_.get_use_square_pixels();
|
||||
return options;
|
||||
}
|
||||
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &str) {
|
||||
const auto options = dynamic_cast<Options *>(str.get());
|
||||
set_video_signal_configurable(options->output);
|
||||
video_.set_use_square_pixels(options->use_square_pixels);
|
||||
}
|
||||
|
||||
// MARK: MediaTarget
|
||||
|
||||
@@ -30,8 +30,11 @@ class Machine {
|
||||
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
|
||||
friend Configurable::DisplayOption<Options>;
|
||||
public:
|
||||
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
|
||||
bool use_square_pixels = false;
|
||||
|
||||
Options(Configurable::OptionsType) : Configurable::DisplayOption<Options>(Configurable::Display::CompositeColour) {
|
||||
if(needs_declare()) {
|
||||
DeclareField(use_square_pixels);
|
||||
declare_display_option();
|
||||
limit_enum(&output, Configurable::Display::CompositeMonochrome, Configurable::Display::CompositeColour, -1);
|
||||
}
|
||||
|
||||
@@ -10,27 +10,34 @@
|
||||
|
||||
using namespace Apple::II;
|
||||
|
||||
DiskIICard::DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector) : diskii_(2045454) {
|
||||
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
|
||||
ROM::Request DiskIICard::rom_request(bool is_16_sector) {
|
||||
if(is_16_sector) {
|
||||
roms = rom_fetcher({
|
||||
{"DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6},
|
||||
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
|
||||
});
|
||||
return ROM::Request(ROM::Name::DiskIIBoot16Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
|
||||
} else {
|
||||
roms = rom_fetcher({
|
||||
{"DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ff},
|
||||
{"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 } }
|
||||
// {"DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620 }
|
||||
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
|
||||
});
|
||||
/* TODO: once the DiskII knows how to decode common images of the 13-sector state machine, use that instead of the 16-sector. */
|
||||
return ROM::Request(ROM::Name::DiskIIBoot13Sector) && ROM::Request(ROM::Name::DiskIIStateMachine16Sector);
|
||||
}
|
||||
if(!roms[0] || !roms[1]) {
|
||||
}
|
||||
|
||||
|
||||
DiskIICard::DiskIICard(ROM::Map &map, bool is_16_sector) : diskii_(2045454) {
|
||||
std::vector<std::unique_ptr<std::vector<uint8_t>>> roms;
|
||||
ROM::Map::iterator state_machine, boot;
|
||||
if(is_16_sector) {
|
||||
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
|
||||
boot = map.find(ROM::Name::DiskIIBoot16Sector);
|
||||
} else {
|
||||
// TODO: see above re: 13-sector state machine.
|
||||
state_machine = map.find(ROM::Name::DiskIIStateMachine16Sector);
|
||||
boot = map.find(ROM::Name::DiskIIBoot13Sector);
|
||||
}
|
||||
|
||||
if(state_machine == map.end() || boot == map.end()) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
boot_ = std::move(*roms[0]);
|
||||
diskii_.set_state_machine(*roms[1]);
|
||||
boot_ = std::move(boot->second);
|
||||
diskii_.set_state_machine(state_machine->second);
|
||||
set_select_constraints(None);
|
||||
diskii_.set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace II {
|
||||
|
||||
class DiskIICard: public Card, public ClockingHint::Observer {
|
||||
public:
|
||||
DiskIICard(const ROMMachine::ROMFetcher &rom_fetcher, bool is_16_sector);
|
||||
static ROM::Request rom_request(bool is_16_sector);
|
||||
DiskIICard(ROM::Map &, bool is_16_sector);
|
||||
|
||||
void perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) final;
|
||||
void run_for(Cycles cycles, int stretches) final;
|
||||
|
||||
@@ -15,9 +15,8 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
||||
crt_(910, 1, Outputs::Display::Type::NTSC60, Outputs::Display::InputDataType::Luminance1),
|
||||
is_iie_(is_iie) {
|
||||
|
||||
// Show only the centre 75% of the TV frame.
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::CompositeColour);
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.118f, 0.122f, 0.77f, 0.77f));
|
||||
set_use_square_pixels(use_square_pixels_);
|
||||
|
||||
// TODO: there seems to be some sort of bug whereby switching modes can cause
|
||||
// a signal discontinuity that knocks phase out of whack. So it isn't safe to
|
||||
@@ -26,6 +25,41 @@ VideoBase::VideoBase(bool is_iie, std::function<void(Cycles)> &&target) :
|
||||
// crt_.set_immediate_default_phase(0.5f);
|
||||
}
|
||||
|
||||
void VideoBase::set_use_square_pixels(bool use_square_pixels) {
|
||||
use_square_pixels_ = use_square_pixels;
|
||||
|
||||
// HYPER-UGLY HACK. See correlated hack in the Macintosh.
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.122f, 0.75f, 0.77f));
|
||||
#else
|
||||
if(use_square_pixels) {
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.09f, 0.75f, 0.77f));
|
||||
} else {
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.128f, 0.12f, 0.75f, 0.77f));
|
||||
}
|
||||
#endif
|
||||
|
||||
if(use_square_pixels) {
|
||||
// From what I can make out, many contemporary Apple II monitors were
|
||||
// calibrated slightly to stretch the Apple II's display slightly wider
|
||||
// than it should be per the NTSC standards, for approximately square
|
||||
// pixels. This reproduces that.
|
||||
|
||||
// 243 lines and 52µs are visible.
|
||||
// i.e. to be square, 1 pixel should be: (1/243 * 52) * (3/4) = 156/972 = 39/243 µs
|
||||
// On an Apple II each pixel is actually 1/7µs.
|
||||
// Therefore the adjusted aspect ratio should be (4/3) * (39/243)/(1/7) = (4/3) * 273/243 = 1092/729 = 343/243 ~= 1.412
|
||||
crt_.set_aspect_ratio(343.0f / 243.0f);
|
||||
} else {
|
||||
// Standard NTSC aspect ratio.
|
||||
crt_.set_aspect_ratio(4.0f / 3.0f);
|
||||
}
|
||||
}
|
||||
bool VideoBase::get_use_square_pixels() {
|
||||
return use_square_pixels_;
|
||||
}
|
||||
|
||||
|
||||
void VideoBase::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
@@ -51,8 +51,14 @@ class VideoBase: public VideoSwitches<Cycles> {
|
||||
/// Gets the type of output.
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
/// Sets whether the current CRT should be recalibrated away from normative NTSC
|
||||
/// to produce square pixels in 40-column text mode.
|
||||
void set_use_square_pixels(bool);
|
||||
bool get_use_square_pixels();
|
||||
|
||||
protected:
|
||||
Outputs::CRT::CRT crt_;
|
||||
bool use_square_pixels_ = false;
|
||||
|
||||
// State affecting output video stream generation.
|
||||
uint8_t *pixel_pointer_ = nullptr;
|
||||
@@ -393,7 +399,7 @@ template <class BusHandler, bool is_iie> class Video: public VideoBase {
|
||||
// The OpenGL scan target introduces a phase error of 1/8th of a wave. The Metal one does not.
|
||||
// Supply the real phase value if this is an Apple build.
|
||||
// TODO: eliminate UGLY HACK.
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
constexpr int phase = 224;
|
||||
#else
|
||||
constexpr int phase = 0;
|
||||
|
||||
@@ -228,36 +228,6 @@ template <typename TimeUnit> class VideoSwitches {
|
||||
return external_.annunciator_3;
|
||||
}
|
||||
|
||||
enum class CharacterROM {
|
||||
/// The ROM that shipped with both the Apple II and the II+.
|
||||
II,
|
||||
/// The ROM that shipped with the original IIe.
|
||||
IIe,
|
||||
/// The ROM that shipped with the Enhanced IIe.
|
||||
EnhancedIIe,
|
||||
/// The ROM that shipped with the IIgs.
|
||||
IIgs
|
||||
};
|
||||
|
||||
/// @returns A file-level description of @c rom.
|
||||
static ROMMachine::ROM rom_description(CharacterROM rom) {
|
||||
const std::string machine_name = "AppleII";
|
||||
switch(rom) {
|
||||
case CharacterROM::II:
|
||||
return ROMMachine::ROM(machine_name, "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6);
|
||||
|
||||
case CharacterROM::IIe:
|
||||
return ROMMachine::ROM(machine_name, "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1);
|
||||
|
||||
default: // To appease GCC.
|
||||
case CharacterROM::EnhancedIIe:
|
||||
return ROMMachine::ROM(machine_name, "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014d);
|
||||
|
||||
case CharacterROM::IIgs:
|
||||
return ROMMachine::ROM(machine_name, "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the character ROM for this video output.
|
||||
void set_character_rom(const std::vector<uint8_t> &rom) {
|
||||
character_rom_ = rom;
|
||||
|
||||
@@ -189,31 +189,23 @@ class ConcreteMachine:
|
||||
speaker_.set_input_rate(float(CLOCK_RATE) / float(audio_divider));
|
||||
|
||||
using Target = Analyser::Static::AppleIIgs::Target;
|
||||
std::vector<ROMMachine::ROM> rom_descriptions;
|
||||
const std::string machine_name = "AppleIIgs";
|
||||
ROM::Name system;
|
||||
switch(target.model) {
|
||||
case Target::Model::ROM00:
|
||||
/* TODO */
|
||||
case Target::Model::ROM01:
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0);
|
||||
break;
|
||||
|
||||
case Target::Model::ROM03:
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29);
|
||||
break;
|
||||
case Target::Model::ROM00: system = ROM::Name::AppleIIgsROM00; break;
|
||||
case Target::Model::ROM01: system = ROM::Name::AppleIIgsROM01; break;
|
||||
default: system = ROM::Name::AppleIIgsROM03; break;
|
||||
}
|
||||
rom_descriptions.push_back(video_->rom_description(Video::Video::CharacterROM::EnhancedIIe));
|
||||
constexpr ROM::Name characters = ROM::Name::AppleIIEnhancedECharacter;
|
||||
constexpr ROM::Name microcontroller = ROM::Name::AppleIIgsMicrocontrollerROM03;
|
||||
|
||||
// TODO: pick a different ADB ROM for earlier machine revisions?
|
||||
rom_descriptions.emplace_back(machine_name, "the Apple IIgs ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0);
|
||||
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
if(!roms[0] || !roms[1] || !roms[2]) {
|
||||
ROM::Request request = ROM::Request(system) && ROM::Request(characters) && ROM::Request(microcontroller);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
rom_ = *roms[0];
|
||||
video_->set_character_rom(*roms[1]);
|
||||
adb_glu_->set_microcontroller_rom(*roms[2]);
|
||||
rom_ = roms.find(system)->second;
|
||||
video_->set_character_rom(roms.find(characters)->second);
|
||||
adb_glu_->set_microcontroller_rom(roms.find(microcontroller)->second);
|
||||
|
||||
// Run only the currently-interesting self test.
|
||||
// rom_[0x36402] = 2;
|
||||
|
||||
@@ -271,7 +271,7 @@ class Keyboard {
|
||||
/// so this may not be valid prior to Mode::PerformingCommand.
|
||||
int command_ = 0;
|
||||
/// Populated during PerformingCommand as the response to the most-recently-received command, this
|
||||
/// is then shifted out to teh host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
|
||||
/// is then shifted out to the host computer. So it is guaranteed valid at the beginning of Mode::SendingResponse,
|
||||
/// but not afterwards.
|
||||
int response_ = 0;
|
||||
|
||||
|
||||
@@ -96,25 +96,24 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
using Model = Analyser::Static::Macintosh::Target::Model;
|
||||
const std::string machine_name = "Macintosh";
|
||||
uint32_t ram_size, rom_size;
|
||||
std::vector<ROMMachine::ROM> rom_descriptions;
|
||||
ROM::Name rom_name;
|
||||
switch(model) {
|
||||
default:
|
||||
case Model::Mac128k:
|
||||
ram_size = 128*1024;
|
||||
rom_size = 64*1024;
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28);
|
||||
rom_name = ROM::Name::Macintosh128k;
|
||||
break;
|
||||
case Model::Mac512k:
|
||||
ram_size = 512*1024;
|
||||
rom_size = 64*1024;
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d);
|
||||
rom_name = ROM::Name::Macintosh512k;
|
||||
break;
|
||||
case Model::Mac512ke:
|
||||
case Model::MacPlus: {
|
||||
ram_size = ((model == Model::MacPlus) ? 4096 : 512)*1024;
|
||||
rom_size = 128*1024;
|
||||
const std::initializer_list<uint32_t> crc32s = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
|
||||
rom_descriptions.emplace_back(machine_name, "the Macintosh Plus ROM", "macplus.rom", 128*1024, crc32s);
|
||||
rom_name = ROM::Name::MacintoshPlus;
|
||||
} break;
|
||||
}
|
||||
ram_mask_ = ram_size - 1;
|
||||
@@ -123,12 +122,12 @@ template <Analyser::Static::Macintosh::Target::Model model> class ConcreteMachin
|
||||
video_.set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_mask_ >> 1);
|
||||
|
||||
// Grab a copy of the ROM and convert it into big-endian data.
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
if(!roms[0]) {
|
||||
ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
roms[0]->resize(rom_size);
|
||||
Memory::PackBigEndian16(*roms[0], rom_);
|
||||
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
|
||||
|
||||
// Randomise memory contents.
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
@@ -35,7 +35,7 @@ Video::Video(DeferredAudio &audio, DriveSpeedAccumulator &drive_speed_accumulato
|
||||
// The [newer] Metal scan target has no such issue. So assume that Apple => Metal,
|
||||
// and set a visible area to work around the OpenGL issue if required.
|
||||
// TODO: eliminate UGLY HACK.
|
||||
#ifdef __APPLE__
|
||||
#if defined(__APPLE__) && !defined(IGNORE_APPLE)
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, 10.0f / 368.0f, 0.82f, 344.0f / 368.0f));
|
||||
#else
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.08f, -0.025f, 0.82f, 0.82f));
|
||||
|
||||
@@ -74,15 +74,13 @@ class ConcreteMachine:
|
||||
video_->set_ram(reinterpret_cast<uint16_t *>(ram_.data()), ram_.size());
|
||||
Memory::Fuzz(ram_);
|
||||
|
||||
std::vector<ROMMachine::ROM> rom_descriptions = {
|
||||
{"AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64}
|
||||
// {"AtariST", "the UK TOS 1.04 ROM", "tos104.img", 192*1024, 0xa50d1d43}
|
||||
};
|
||||
const auto roms = rom_fetcher(rom_descriptions);
|
||||
if(!roms[0]) {
|
||||
constexpr ROM::Name rom_name = ROM::Name::AtariSTTOS100;
|
||||
ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
Memory::PackBigEndian16(*roms[0], rom_);
|
||||
Memory::PackBigEndian16(roms.find(rom_name)->second, rom_);
|
||||
|
||||
// Set up basic memory map.
|
||||
memory_map_[0] = BusDevice::MostlyRAM;
|
||||
|
||||
@@ -252,7 +252,7 @@ void DMAController::set_component_prefers_clocking(ClockingHint::Source *, Clock
|
||||
}
|
||||
|
||||
ClockingHint::Preference DMAController::preferred_clocking() const {
|
||||
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::None : ClockingHint::Preference::RealTime;
|
||||
return (fdc_.preferred_clocking() == ClockingHint::Preference::None) ? ClockingHint::Preference::JustInTime : ClockingHint::Preference::RealTime;
|
||||
}
|
||||
|
||||
void DMAController::set_activity_observer(Activity::Observer *observer) {
|
||||
|
||||
@@ -127,15 +127,13 @@ class ConcreteMachine:
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
|
||||
const auto roms = rom_fetcher(
|
||||
{ {"ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3} });
|
||||
|
||||
if(!roms[0]) {
|
||||
constexpr ROM::Name rom_name = ROM::Name::ColecoVisionBIOS;
|
||||
const ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
bios_ = *roms[0];
|
||||
bios_.resize(8192);
|
||||
bios_ = roms.find(rom_name)->second;
|
||||
|
||||
if(!target.media.cartridges.empty()) {
|
||||
const auto &segment = target.media.cartridges.front()->get_segments().front();
|
||||
|
||||
@@ -41,7 +41,8 @@ namespace C1540 {
|
||||
*/
|
||||
class Machine final: public MachineBase {
|
||||
public:
|
||||
Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
static ROM::Request rom_request(Personality personality);
|
||||
Machine(Personality personality, const ROM::Map &roms);
|
||||
|
||||
/*!
|
||||
Sets the serial bus to which this drive should attach itself.
|
||||
|
||||
@@ -16,7 +16,15 @@
|
||||
|
||||
using namespace Commodore::C1540;
|
||||
|
||||
MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
ROM::Request Machine::rom_request(Personality personality) {
|
||||
switch(personality) {
|
||||
default:
|
||||
case Personality::C1540: return ROM::Request(ROM::Name::Commodore1540);
|
||||
case Personality::C1541: return ROM::Request(ROM::Name::Commodore1541);
|
||||
}
|
||||
}
|
||||
|
||||
MachineBase::MachineBase(Personality personality, const ROM::Map &roms) :
|
||||
Storage::Disk::Controller(1000000),
|
||||
m6502_(*this),
|
||||
serial_port_VIA_port_handler_(new SerialPortVIA(serial_port_VIA_)),
|
||||
@@ -39,28 +47,22 @@ MachineBase::MachineBase(Personality personality, const ROMMachine::ROMFetcher &
|
||||
emplace_drive(1000000, 300, 2);
|
||||
set_drive(1);
|
||||
|
||||
std::string device_name;
|
||||
uint32_t crc = 0;
|
||||
ROM::Name rom_name;
|
||||
switch(personality) {
|
||||
case Personality::C1540:
|
||||
device_name = "1540";
|
||||
crc = 0x718d42b1;
|
||||
break;
|
||||
case Personality::C1541:
|
||||
device_name = "1541";
|
||||
crc = 0xfb760019;
|
||||
break;
|
||||
default:
|
||||
case Personality::C1540: rom_name = ROM::Name::Commodore1540; break;
|
||||
case Personality::C1541: rom_name = ROM::Name::Commodore1541; break;
|
||||
}
|
||||
|
||||
auto roms = rom_fetcher({ {"Commodore1540", "the " + device_name + " ROM", device_name + ".bin", 16*1024, crc} });
|
||||
if(!roms[0]) {
|
||||
auto rom = roms.find(rom_name);
|
||||
if(rom == roms.end()) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
std::memcpy(rom_, roms[0]->data(), std::min(sizeof(rom_), roms[0]->size()));
|
||||
std::memcpy(rom_, roms.find(rom_name)->second.data(), std::min(sizeof(rom_), roms.find(rom_name)->second.size()));
|
||||
}
|
||||
|
||||
Machine::Machine(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
MachineBase(personality, rom_fetcher) {}
|
||||
Machine::Machine(Personality personality, const ROM::Map &roms) :
|
||||
MachineBase(personality, roms) {}
|
||||
|
||||
void Machine::set_serial_bus(std::shared_ptr<::Commodore::Serial::Bus> serial_bus) {
|
||||
Commodore::Serial::AttachPortAndBus(serial_port_, serial_bus);
|
||||
|
||||
@@ -127,7 +127,7 @@ class MachineBase:
|
||||
public Storage::Disk::Controller {
|
||||
|
||||
public:
|
||||
MachineBase(Personality personality, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
MachineBase(Personality personality, const ROM::Map &roms);
|
||||
|
||||
// to satisfy CPU::MOS6502::Processor
|
||||
Cycles perform_bus_operation(CPU::MOS6502::BusOperation operation, uint16_t address, uint8_t *value);
|
||||
|
||||
@@ -151,5 +151,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
return table_lookup_sequence_for_character(key_sequences, character);
|
||||
}
|
||||
|
||||
@@ -316,53 +316,48 @@ class ConcreteMachine:
|
||||
// Install a joystick.
|
||||
joysticks_.emplace_back(new Joystick(*user_port_via_port_handler_, *keyboard_via_port_handler_));
|
||||
|
||||
const std::string machine_name = "Vic20";
|
||||
std::vector<ROMMachine::ROM> rom_names = {
|
||||
{machine_name, "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1}
|
||||
};
|
||||
ROM::Request request(ROM::Name::Vic20BASIC);
|
||||
ROM::Name kernel, character;
|
||||
switch(target.region) {
|
||||
default:
|
||||
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
|
||||
rom_names.emplace_back(machine_name, "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4);
|
||||
character = ROM::Name::Vic20EnglishCharacters;
|
||||
kernel = ROM::Name::Vic20EnglishPALKernel;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::American:
|
||||
rom_names.emplace_back(machine_name, "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6);
|
||||
rom_names.emplace_back(machine_name, "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174);
|
||||
character = ROM::Name::Vic20EnglishCharacters;
|
||||
kernel = ROM::Name::Vic20EnglishNTSCKernel;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Danish:
|
||||
rom_names.emplace_back(machine_name, "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454);
|
||||
rom_names.emplace_back(machine_name, "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16);
|
||||
character = ROM::Name::Vic20DanishCharacters;
|
||||
kernel = ROM::Name::Vic20DanishKernel;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Japanese:
|
||||
rom_names.emplace_back(machine_name, "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4b);
|
||||
rom_names.emplace_back(machine_name, "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7);
|
||||
character = ROM::Name::Vic20JapaneseCharacters;
|
||||
kernel = ROM::Name::Vic20JapaneseKernel;
|
||||
break;
|
||||
case Analyser::Static::Commodore::Target::Region::Swedish:
|
||||
rom_names.emplace_back(machine_name, "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551d);
|
||||
rom_names.emplace_back(machine_name, "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662);
|
||||
character = ROM::Name::Vic20SwedishCharacters;
|
||||
kernel = ROM::Name::Vic20SwedishKernel;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto roms = rom_fetcher(rom_names);
|
||||
if(target.has_c1540) {
|
||||
request = request && Commodore::C1540::Machine::rom_request(Commodore::C1540::Personality::C1540);
|
||||
}
|
||||
request = request && ROM::Request(character) && ROM::Request(kernel);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
if(!rom) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
basic_rom_ = std::move(*roms[0]);
|
||||
character_rom_ = std::move(*roms[1]);
|
||||
kernel_rom_ = std::move(*roms[2]);
|
||||
|
||||
// Characters ROMs should be 4kb.
|
||||
character_rom_.resize(4096);
|
||||
// Kernel ROMs and the BASIC ROM should be 8kb.
|
||||
kernel_rom_.resize(8192);
|
||||
basic_rom_ = std::move(roms.find(ROM::Name::Vic20BASIC)->second);
|
||||
character_rom_ = std::move(roms.find(character)->second);
|
||||
kernel_rom_ = std::move(roms.find(kernel)->second);
|
||||
|
||||
if(target.has_c1540) {
|
||||
// construct the 1540
|
||||
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, rom_fetcher);
|
||||
c1540_ = std::make_unique<::Commodore::C1540::Machine>(Commodore::C1540::Personality::C1540, roms);
|
||||
|
||||
// attach it to the serial bus
|
||||
c1540_->set_serial_bus(serial_bus_);
|
||||
|
||||
@@ -69,37 +69,25 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
speaker_.set_input_rate(2000000 / SoundGenerator::clock_rate_divider);
|
||||
speaker_.set_high_frequency_cutoff(6000);
|
||||
|
||||
const std::string machine_name = "Electron";
|
||||
std::vector<ROMMachine::ROM> required_roms = {
|
||||
{machine_name, "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781},
|
||||
{machine_name, "the Electron MOS ROM", "os.rom", 16*1024, 0xbf63fb1f}
|
||||
};
|
||||
const size_t pres_adfs_rom_position = required_roms.size();
|
||||
::ROM::Request request = ::ROM::Request(::ROM::Name::AcornBASICII) && ::ROM::Request(::ROM::Name::AcornElectronMOS100);
|
||||
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);
|
||||
request = request && ::ROM::Request(::ROM::Name::PRESADFSSlot1) && ::ROM::Request(::ROM::Name::PRESADFSSlot2);
|
||||
}
|
||||
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);
|
||||
request = request && ::ROM::Request(::ROM::Name::AcornADFS);
|
||||
}
|
||||
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);
|
||||
request = request && ::ROM::Request(::ROM::Name::Acorn1770DFS);
|
||||
}
|
||||
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);
|
||||
request = request && ::ROM::Request(::ROM::Name::PRESAdvancedPlus6);
|
||||
}
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
for(const auto &rom: roms) {
|
||||
if(!rom) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
set_rom(ROM::BASIC, *roms[0], false);
|
||||
set_rom(ROM::OS, *roms[1], false);
|
||||
set_rom(ROM::BASIC, roms.find(::ROM::Name::AcornBASICII)->second, false);
|
||||
set_rom(ROM::OS, roms.find(::ROM::Name::AcornElectronMOS100)->second, false);
|
||||
|
||||
/*
|
||||
ROM slot mapping applied:
|
||||
@@ -115,19 +103,18 @@ template <bool has_scsi_bus> class ConcreteMachine:
|
||||
plus3_ = std::make_unique<Plus3>();
|
||||
|
||||
if(target.has_dfs) {
|
||||
set_rom(ROM::Slot0, *roms[dfs_rom_position], true);
|
||||
set_rom(ROM::Slot0, roms.find(::ROM::Name::Acorn1770DFS)->second, 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);
|
||||
set_rom(ROM::Slot4, roms.find(::ROM::Name::PRESADFSSlot1)->second, true);
|
||||
set_rom(ROM::Slot5, roms.find(::ROM::Name::PRESADFSSlot2)->second, true);
|
||||
}
|
||||
if(target.has_acorn_adfs) {
|
||||
set_rom(ROM::Slot6, *roms[acorn_adfs_rom_position], true);
|
||||
set_rom(ROM::Slot6, roms.find(::ROM::Name::AcornADFS)->second, true);
|
||||
}
|
||||
}
|
||||
|
||||
if(target.has_ap6_rom) {
|
||||
set_rom(ROM::Slot15, *roms[ap6_rom_position], true);
|
||||
set_rom(ROM::Slot15, roms.find(::ROM::Name::PRESAdvancedPlus6)->second, true);
|
||||
}
|
||||
|
||||
if(target.has_sideways_ram) {
|
||||
|
||||
@@ -145,7 +145,7 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
return table_lookup_sequence_for_character(key_sequences, character);
|
||||
}
|
||||
|
||||
bool CharacterMapper::needs_pause_after_key(uint16_t key) const {
|
||||
|
||||
326
Machines/Enterprise/Dave.cpp
Normal file
326
Machines/Enterprise/Dave.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
//
|
||||
// Dave.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Dave.hpp"
|
||||
|
||||
using namespace Enterprise::Dave;
|
||||
|
||||
// MARK: - Audio generator
|
||||
|
||||
Audio::Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue) :
|
||||
audio_queue_(audio_queue) {}
|
||||
|
||||
void Audio::write(uint16_t address, uint8_t value) {
|
||||
address &= 0xf;
|
||||
audio_queue_.defer([address, value, this] {
|
||||
switch(address) {
|
||||
case 0: case 2: case 4:
|
||||
channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value;
|
||||
break;
|
||||
case 1: case 3: case 5:
|
||||
channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8));
|
||||
channels_[address >> 1].distortion = Channel::Distortion((value >> 4)&3);
|
||||
channels_[address >> 1].high_pass = value & 0x40;
|
||||
channels_[address >> 1].ring_modulate = value & 0x80;
|
||||
break;
|
||||
case 6:
|
||||
noise_.frequency = Noise::Frequency(value&3);
|
||||
noise_.polynomial = Noise::Polynomial((value >> 2)&3);
|
||||
noise_.swap_polynomial = value & 0x10;
|
||||
noise_.low_pass = value & 0x20;
|
||||
noise_.high_pass = value & 0x40;
|
||||
noise_.ring_modulate = value & 0x80;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
channels_[0].sync = value & 0x01;
|
||||
channels_[1].sync = value & 0x02;
|
||||
channels_[2].sync = value & 0x04;
|
||||
use_direct_output_[0] = value & 0x08;
|
||||
use_direct_output_[1] = value & 0x10;
|
||||
// Interrupt bits are handled separately.
|
||||
break;
|
||||
|
||||
case 8: case 9: case 10:
|
||||
channels_[address - 8].amplitude[0] = value & 0x3f;
|
||||
break;
|
||||
case 12: case 13: case 14:
|
||||
channels_[address - 12].amplitude[1] = value & 0x3f;
|
||||
break;
|
||||
case 11: noise_.amplitude[0] = value & 0x3f; break;
|
||||
case 15: noise_.amplitude[1] = value & 0x3f; break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::set_sample_volume_range(int16_t range) {
|
||||
audio_queue_.defer([range, this] {
|
||||
volume_ = range / (63*4);
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::update_channel(int c) {
|
||||
if(channels_[c].sync) {
|
||||
channels_[c].count = channels_[c].reload;
|
||||
channels_[c].output <<= 1;
|
||||
return;
|
||||
}
|
||||
|
||||
auto output = channels_[c].output & 1;
|
||||
channels_[c].output <<= 1;
|
||||
if(!channels_[c].count) {
|
||||
channels_[c].count = channels_[c].reload;
|
||||
|
||||
if(channels_[c].distortion == Channel::Distortion::None)
|
||||
output ^= 1;
|
||||
else
|
||||
output = poly_state_[int(channels_[c].distortion)];
|
||||
|
||||
if(channels_[c].high_pass && (channels_[(c+1)%3].output&3) == 2) {
|
||||
output = 0;
|
||||
}
|
||||
if(channels_[c].ring_modulate) {
|
||||
output = ~(output ^ channels_[(c+2)%3].output) & 1;
|
||||
}
|
||||
} else {
|
||||
--channels_[c].count;
|
||||
}
|
||||
|
||||
channels_[c].output |= output;
|
||||
}
|
||||
|
||||
void Audio::get_samples(std::size_t number_of_samples, int16_t *target) {
|
||||
for(size_t c = 0; c < number_of_samples; c++) {
|
||||
poly_state_[int(Channel::Distortion::FourBit)] = poly4_.next();
|
||||
poly_state_[int(Channel::Distortion::FiveBit)] = poly5_.next();
|
||||
poly_state_[int(Channel::Distortion::SevenBit)] = poly7_.next();
|
||||
if(noise_.swap_polynomial) {
|
||||
poly_state_[int(Channel::Distortion::SevenBit)] = poly_state_[int(Channel::Distortion::None)];
|
||||
}
|
||||
|
||||
// Update tone channels.
|
||||
update_channel(0);
|
||||
update_channel(1);
|
||||
update_channel(2);
|
||||
|
||||
// Update noise channel.
|
||||
|
||||
// Step 1: decide whether there is a tick to apply.
|
||||
bool noise_tick = false;
|
||||
if(noise_.frequency == Noise::Frequency::DivideByFour) {
|
||||
if(!noise_.count) {
|
||||
noise_tick = true;
|
||||
noise_.count = 3;
|
||||
} else {
|
||||
--noise_.count;
|
||||
}
|
||||
} else {
|
||||
noise_tick = (channels_[int(noise_.frequency) - 1].output&3) == 2;
|
||||
}
|
||||
|
||||
// Step 2: tick if necessary.
|
||||
int noise_output = noise_.output & 1;
|
||||
noise_.output <<= 1;
|
||||
if(noise_tick) {
|
||||
switch(noise_.polynomial) {
|
||||
case Noise::Polynomial::SeventeenBit:
|
||||
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly17_.next());
|
||||
break;
|
||||
case Noise::Polynomial::FifteenBit:
|
||||
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly15_.next());
|
||||
break;
|
||||
case Noise::Polynomial::ElevenBit:
|
||||
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly11_.next());
|
||||
break;
|
||||
case Noise::Polynomial::NineBit:
|
||||
poly_state_[int(Channel::Distortion::None)] = uint8_t(poly9_.next());
|
||||
break;
|
||||
}
|
||||
|
||||
noise_output = poly_state_[int(Channel::Distortion::None)];
|
||||
}
|
||||
noise_.output |= noise_output;
|
||||
|
||||
// Low pass: sample channel 2 on downward transitions of the prima facie output.
|
||||
if(noise_.low_pass && (noise_.output & 3) == 2) {
|
||||
noise_.output = (noise_.output & ~1) | (channels_[2].output & 1);
|
||||
}
|
||||
|
||||
// Apply noise high-pass.
|
||||
if(noise_.high_pass && (channels_[0].output & 3) == 2) {
|
||||
noise_.output &= ~1;
|
||||
}
|
||||
|
||||
// Update noise ring modulation, if any.
|
||||
if(noise_.ring_modulate) {
|
||||
noise_.final_output = !((noise_.output ^ channels_[1].output) & 1);
|
||||
} else {
|
||||
noise_.final_output = noise_.output & 1;
|
||||
}
|
||||
|
||||
// I'm unclear on the details of the time division multiplexing so,
|
||||
// for now, just sum the outputs.
|
||||
target[(c << 1) + 0] =
|
||||
volume_ *
|
||||
(use_direct_output_[0] ?
|
||||
channels_[0].amplitude[0]
|
||||
: (
|
||||
channels_[0].amplitude[0] * (channels_[0].output & 1) +
|
||||
channels_[1].amplitude[0] * (channels_[1].output & 1) +
|
||||
channels_[2].amplitude[0] * (channels_[2].output & 1) +
|
||||
noise_.amplitude[0] * noise_.final_output
|
||||
));
|
||||
|
||||
target[(c << 1) + 1] =
|
||||
volume_ *
|
||||
(use_direct_output_[1] ?
|
||||
channels_[0].amplitude[1]
|
||||
: (
|
||||
channels_[0].amplitude[1] * (channels_[0].output & 1) +
|
||||
channels_[1].amplitude[1] * (channels_[1].output & 1) +
|
||||
channels_[2].amplitude[1] * (channels_[2].output & 1) +
|
||||
noise_.amplitude[1] * noise_.final_output
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Interrupt source
|
||||
|
||||
uint8_t TimedInterruptSource::get_new_interrupts() {
|
||||
const uint8_t result = interrupts_;
|
||||
interrupts_ = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
void TimedInterruptSource::write(uint16_t address, uint8_t value) {
|
||||
address &= 15;
|
||||
switch(address) {
|
||||
default: break;
|
||||
|
||||
case 0: case 2:
|
||||
channels_[address >> 1].reload = (channels_[address >> 1].reload & 0xff00) | value;
|
||||
break;
|
||||
case 1: case 3:
|
||||
channels_[address >> 1].reload = uint16_t((channels_[address >> 1].reload & 0x00ff) | ((value & 0xf) << 8));
|
||||
break;
|
||||
|
||||
case 7: {
|
||||
channels_[0].sync = value & 0x01;
|
||||
channels_[1].sync = value & 0x02;
|
||||
|
||||
const InterruptRate rate = InterruptRate((value >> 5) & 3);
|
||||
if(rate != rate_) {
|
||||
rate_ = rate;
|
||||
|
||||
if(rate_ >= InterruptRate::ToneGenerator0) {
|
||||
programmable_level_ = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)].level;
|
||||
} else {
|
||||
programmable_offset_ = programmble_reload(rate_);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void TimedInterruptSource::update_channel(int c, bool is_linked, int decrement) {
|
||||
if(channels_[c].sync) {
|
||||
channels_[c].value = channels_[c].reload;
|
||||
} else {
|
||||
if(decrement <= channels_[c].value) {
|
||||
channels_[c].value -= decrement;
|
||||
} else {
|
||||
// The decrement is greater than the current value, therefore
|
||||
// there'll be at least one flip.
|
||||
//
|
||||
// After decreasing the decrement by the current value + 1,
|
||||
// it'll be clear how many decrements are left after reload.
|
||||
//
|
||||
// Dividing that by the number of decrements necessary for a
|
||||
// flip will provide the total number of flips.
|
||||
const int decrements_after_flip = decrement - (channels_[c].value + 1);
|
||||
const int num_flips = 1 + decrements_after_flip / (channels_[c].reload + 1);
|
||||
|
||||
// If this is a linked channel, set the interrupt mask if a transition
|
||||
// from high to low is amongst the included flips.
|
||||
if(is_linked && num_flips + channels_[c].level >= 2) {
|
||||
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
|
||||
}
|
||||
channels_[c].level ^= (num_flips & 1);
|
||||
|
||||
// Apply the modulo number of decrements to the reload value to
|
||||
// figure out where things stand now.
|
||||
channels_[c].value = channels_[c].reload - decrements_after_flip % (channels_[c].reload + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TimedInterruptSource::run_for(Cycles cycles) {
|
||||
// Update the 1Hz interrupt.
|
||||
one_hz_offset_ -= cycles;
|
||||
if(one_hz_offset_ <= Cycles(0)) {
|
||||
interrupts_ |= uint8_t(Interrupt::OneHz);
|
||||
one_hz_offset_ += clock_rate;
|
||||
}
|
||||
|
||||
// Update the two tone channels.
|
||||
update_channel(0, rate_ == InterruptRate::ToneGenerator0, cycles.as<int>());
|
||||
update_channel(1, rate_ == InterruptRate::ToneGenerator1, cycles.as<int>());
|
||||
|
||||
// Update the programmable-frequency interrupt.
|
||||
if(rate_ < InterruptRate::ToneGenerator0) {
|
||||
programmable_offset_ -= cycles.as<int>();
|
||||
if(programmable_offset_ <= 0) {
|
||||
if(programmable_level_) {
|
||||
interrupts_ |= uint8_t(Interrupt::VariableFrequency);
|
||||
}
|
||||
programmable_level_ ^= true;
|
||||
programmable_offset_ = programmble_reload(rate_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Cycles TimedInterruptSource::get_next_sequence_point() const {
|
||||
int result = one_hz_offset_.as<int>();
|
||||
|
||||
switch(rate_) {
|
||||
case InterruptRate::OnekHz:
|
||||
case InterruptRate::FiftyHz:
|
||||
result = std::min(result, programmable_offset_ + (!programmable_level_) * programmble_reload(rate_));
|
||||
break;
|
||||
case InterruptRate::ToneGenerator0:
|
||||
case InterruptRate::ToneGenerator1: {
|
||||
const auto &channel = channels_[int(rate_) - int(InterruptRate::ToneGenerator0)];
|
||||
const int cycles_until_interrupt = channel.value + 1 + (!channel.level) * (channel.reload + 1);
|
||||
result = std::min(result, cycles_until_interrupt);
|
||||
} break;
|
||||
}
|
||||
|
||||
return Cycles(result);
|
||||
}
|
||||
|
||||
uint8_t TimedInterruptSource::get_divider_state() {
|
||||
bool programmable_flag = false;
|
||||
switch(rate_) {
|
||||
case InterruptRate::OnekHz:
|
||||
case InterruptRate::FiftyHz:
|
||||
programmable_flag = programmable_level_;
|
||||
break;
|
||||
case InterruptRate::ToneGenerator0:
|
||||
programmable_flag = channels_[0].level;
|
||||
break;
|
||||
case InterruptRate::ToneGenerator1:
|
||||
programmable_flag = channels_[1].level;
|
||||
break;
|
||||
}
|
||||
|
||||
// one_hz_offset_ counts downwards, so when it crosses the halfway mark
|
||||
// it enters the high part of its wave.
|
||||
return
|
||||
(one_hz_offset_ < half_clock_rate ? 0x4 : 0x0) |
|
||||
(programmable_flag ? 0x1 : 0x0);
|
||||
}
|
||||
188
Machines/Enterprise/Dave.hpp
Normal file
188
Machines/Enterprise/Dave.hpp
Normal file
@@ -0,0 +1,188 @@
|
||||
//
|
||||
// Dave.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 22/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Dave_hpp
|
||||
#define Dave_hpp
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Concurrency/AsyncTaskQueue.hpp"
|
||||
#include "../../Numeric/LFSR.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
|
||||
namespace Enterprise {
|
||||
namespace Dave {
|
||||
|
||||
enum class Interrupt: uint8_t {
|
||||
VariableFrequency = 0x02,
|
||||
OneHz = 0x08,
|
||||
Nick = 0x20,
|
||||
};
|
||||
|
||||
/*!
|
||||
Models the audio-production subset of Dave's behaviour.
|
||||
*/
|
||||
class Audio: public Outputs::Speaker::SampleSource {
|
||||
public:
|
||||
Audio(Concurrency::DeferringAsyncTaskQueue &audio_queue);
|
||||
|
||||
/// Modifies an register in the audio range; only the low 4 bits are
|
||||
/// used for register decoding so it's assumed that the caller has
|
||||
/// already identified this write as being to an audio register.
|
||||
void write(uint16_t address, uint8_t value);
|
||||
|
||||
// MARK: - SampleSource.
|
||||
void set_sample_volume_range(int16_t range);
|
||||
static constexpr bool get_is_stereo() { return true; } // Dave produces stereo sound.
|
||||
void get_samples(std::size_t number_of_samples, int16_t *target);
|
||||
|
||||
private:
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
|
||||
// Tone channels.
|
||||
struct Channel {
|
||||
// User-set values.
|
||||
uint16_t reload = 0;
|
||||
bool high_pass = false;
|
||||
bool ring_modulate = false;
|
||||
enum class Distortion {
|
||||
None = 0,
|
||||
FourBit = 1,
|
||||
FiveBit = 2,
|
||||
SevenBit = 3,
|
||||
} distortion = Distortion::None;
|
||||
uint8_t amplitude[2]{};
|
||||
bool sync = false;
|
||||
|
||||
// Current state.
|
||||
uint16_t count = 0;
|
||||
int output = 0;
|
||||
} channels_[3];
|
||||
void update_channel(int);
|
||||
|
||||
// Noise channel.
|
||||
struct Noise {
|
||||
// User-set values.
|
||||
uint8_t amplitude[2]{};
|
||||
enum class Frequency {
|
||||
DivideByFour,
|
||||
ToneChannel0,
|
||||
ToneChannel1,
|
||||
ToneChannel2,
|
||||
} frequency = Frequency::DivideByFour;
|
||||
enum class Polynomial {
|
||||
SeventeenBit,
|
||||
FifteenBit,
|
||||
ElevenBit,
|
||||
NineBit
|
||||
} polynomial = Polynomial::SeventeenBit;
|
||||
bool swap_polynomial = false;
|
||||
bool low_pass = false;
|
||||
bool high_pass = false;
|
||||
bool ring_modulate = false;
|
||||
|
||||
// Current state.
|
||||
int count = 0;
|
||||
int output = 0;
|
||||
bool final_output = false;
|
||||
} noise_;
|
||||
void update_noise();
|
||||
|
||||
bool use_direct_output_[2]{};
|
||||
|
||||
// Global volume, per SampleSource obligations.
|
||||
int16_t volume_ = 0;
|
||||
|
||||
// Polynomials that are always running.
|
||||
Numeric::LFSRv<0xc> poly4_;
|
||||
Numeric::LFSRv<0x14> poly5_;
|
||||
Numeric::LFSRv<0x60> poly7_;
|
||||
|
||||
// The selectable, noise-related polynomial.
|
||||
Numeric::LFSRv<0x110> poly9_;
|
||||
Numeric::LFSRv<0x500> poly11_;
|
||||
Numeric::LFSRv<0x6000> poly15_;
|
||||
Numeric::LFSRv<0x12000> poly17_;
|
||||
|
||||
// Current state of the active polynomials.
|
||||
uint8_t poly_state_[4];
|
||||
};
|
||||
|
||||
/*!
|
||||
Provides Dave's timed interrupts — those that are provided at 1 kHz,
|
||||
50 Hz or according to the rate of tone generators 0 or 1, plus the fixed
|
||||
1 Hz interrupt.
|
||||
*/
|
||||
class TimedInterruptSource {
|
||||
public:
|
||||
/// Modifies an register in the audio range; only the low 4 bits are
|
||||
/// used for register decoding so it's assumed that the caller has
|
||||
/// already identified this write as being to an audio register.
|
||||
void write(uint16_t address, uint8_t value);
|
||||
|
||||
/// Returns a bitmask of interrupts that have become active since
|
||||
/// the last time this method was called; flags are as defined in
|
||||
/// @c Enterprise::Dave::Interrupt
|
||||
uint8_t get_new_interrupts();
|
||||
|
||||
/// Returns the current high or low states of the inputs that trigger
|
||||
/// the interrupts modelled here, as a bit mask compatible with that
|
||||
/// exposed by Dave as the register at 0xb4.
|
||||
uint8_t get_divider_state();
|
||||
|
||||
/// Advances the interrupt source.
|
||||
void run_for(Cycles);
|
||||
|
||||
/// @returns The amount of time from now until the earliest that
|
||||
/// @c get_new_interrupts() _might_ have new interrupts to report.
|
||||
Cycles get_next_sequence_point() const;
|
||||
|
||||
private:
|
||||
static constexpr Cycles clock_rate{250000};
|
||||
static constexpr Cycles half_clock_rate{125000};
|
||||
|
||||
// Interrupts that have fired since get_new_interrupts()
|
||||
// was last called.
|
||||
uint8_t interrupts_ = 0;
|
||||
|
||||
// A counter for the 1Hz interrupt.
|
||||
Cycles one_hz_offset_ = clock_rate;
|
||||
|
||||
// A counter specific to the 1kHz and 50Hz timers, if in use.
|
||||
enum class InterruptRate {
|
||||
OnekHz,
|
||||
FiftyHz,
|
||||
ToneGenerator0,
|
||||
ToneGenerator1,
|
||||
} rate_ = InterruptRate::OnekHz;
|
||||
int programmable_offset_ = programmble_reload(InterruptRate::OnekHz);
|
||||
bool programmable_level_ = false;
|
||||
|
||||
// A local duplicate of the counting state of the first two audio
|
||||
// channels, maintained in case either of those is used as an
|
||||
// interrupt source.
|
||||
struct Channel {
|
||||
int value = 100, reload = 100;
|
||||
bool sync = false;
|
||||
bool level = false;
|
||||
} channels_[2];
|
||||
void update_channel(int c, bool is_linked, int decrement);
|
||||
static constexpr int programmble_reload(InterruptRate rate) {
|
||||
switch(rate) {
|
||||
default: return 0;
|
||||
case InterruptRate::OnekHz: return 125;
|
||||
case InterruptRate::FiftyHz: return 2500;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* Dave_hpp */
|
||||
78
Machines/Enterprise/EXDos.cpp
Normal file
78
Machines/Enterprise/EXDos.cpp
Normal file
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// EXDos.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "EXDos.hpp"
|
||||
|
||||
// TODO: disk_did_change_ should be on the drive. Some drives report it.
|
||||
|
||||
using namespace Enterprise;
|
||||
|
||||
EXDos::EXDos() : WD1770(P1770) {
|
||||
emplace_drives(4, 8000000, 300, 2);
|
||||
set_control_register(0x00);
|
||||
}
|
||||
|
||||
void EXDos::set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive) {
|
||||
get_drive(drive).set_disk(disk);
|
||||
disk_did_change_ = true;
|
||||
}
|
||||
|
||||
// Documentation for the control register:
|
||||
//
|
||||
// Write:
|
||||
// b7 in use (???)
|
||||
// b6 disk change reset
|
||||
// b5 0 = double density, 1 = single density
|
||||
// b4 side 1 select
|
||||
// b3, b3, b1, b0 select drive 3, 2, 1, 0
|
||||
//
|
||||
// Read:
|
||||
// b7 data request from WD1770
|
||||
// b6 disk change
|
||||
// b5, b4, b3, b2: not used
|
||||
// b1 interrupt request from WD1770
|
||||
// b0 drive ready
|
||||
|
||||
void EXDos::set_control_register(uint8_t control) {
|
||||
if(control & 0x40) disk_did_change_ = false;
|
||||
set_is_double_density(!(control & 0x20));
|
||||
|
||||
// Set side.
|
||||
const int head = (control >> 4) & 1;
|
||||
for(size_t c = 0; c < 4; c++) {
|
||||
get_drive(c).set_head(head);
|
||||
}
|
||||
|
||||
// Select drive, ensuring handover of the motor-on state.
|
||||
const bool motor_state = get_drive().get_motor_on();
|
||||
for_all_drives([] (Storage::Disk::Drive &drive, size_t) {
|
||||
drive.set_motor_on(false);
|
||||
});
|
||||
set_drive(control & 0xf);
|
||||
get_drive().set_motor_on(motor_state);
|
||||
}
|
||||
|
||||
uint8_t EXDos::get_control_register() {
|
||||
const uint8_t status =
|
||||
(get_data_request_line() ? 0x80 : 0x00) |
|
||||
(disk_did_change_ ? 0x40 : 0x00) |
|
||||
(get_interrupt_request_line() ? 0x02 : 0x00) |
|
||||
(get_drive().get_is_ready() ? 0x01 : 0x00);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void EXDos::set_motor_on(bool on) {
|
||||
get_drive().set_motor_on(on);
|
||||
}
|
||||
|
||||
void EXDos::set_activity_observer(Activity::Observer *observer) {
|
||||
for_all_drives([observer] (Storage::Disk::Drive &drive, size_t index) {
|
||||
drive.set_activity_observer(observer, "Drive " + std::to_string(index+1), true);
|
||||
});
|
||||
}
|
||||
36
Machines/Enterprise/EXDos.hpp
Normal file
36
Machines/Enterprise/EXDos.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// EXDos.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef EXDos_hpp
|
||||
#define EXDos_hpp
|
||||
|
||||
#include "../../Components/1770/1770.hpp"
|
||||
#include "../../Activity/Observer.hpp"
|
||||
|
||||
namespace Enterprise {
|
||||
|
||||
class EXDos final : public WD::WD1770 {
|
||||
public:
|
||||
EXDos();
|
||||
|
||||
void set_disk(std::shared_ptr<Storage::Disk::Disk> disk, size_t drive);
|
||||
|
||||
void set_control_register(uint8_t control);
|
||||
uint8_t get_control_register();
|
||||
|
||||
void set_activity_observer(Activity::Observer *observer);
|
||||
|
||||
private:
|
||||
bool disk_did_change_ = false;
|
||||
|
||||
void set_motor_on(bool on) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* EXDos_hpp */
|
||||
740
Machines/Enterprise/Enterprise.cpp
Normal file
740
Machines/Enterprise/Enterprise.cpp
Normal file
@@ -0,0 +1,740 @@
|
||||
//
|
||||
// Enterprise.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Enterprise.hpp"
|
||||
|
||||
#include "Dave.hpp"
|
||||
#include "EXDos.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Nick.hpp"
|
||||
|
||||
#include "../MachineTypes.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
#include "../../Analyser/Static/Enterprise/Target.hpp"
|
||||
#include "../../ClockReceiver/JustInTime.hpp"
|
||||
#include "../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../Processors/Z80/Z80.hpp"
|
||||
|
||||
#define LOG_PREFIX "[Enterprise] "
|
||||
#include "../../Outputs/Log.hpp"
|
||||
|
||||
namespace Enterprise {
|
||||
|
||||
/*
|
||||
Notes to self on timing:
|
||||
|
||||
Nick divides each line into 57 windows; each window lasts 16 cycles and dedicates the
|
||||
first 10 of those to VRAM accesses, leaving the final six for a Z80 video RAM access
|
||||
if one has been requested.
|
||||
|
||||
The Z80 has a separate, asynchronous 4Mhz clock. That's that.
|
||||
|
||||
The documentation is also very forward in emphasising that Nick generates phaselocked
|
||||
(i.e. in-phase) PAL video.
|
||||
|
||||
So: 57*16 = 912 cycles/line.
|
||||
|
||||
A standard PAL line lasts 64µs and during that time outputs 283.7516 colour cycles.
|
||||
|
||||
I shall _guess_ that the Enterprise stretches each line to 284 colour cycles rather than
|
||||
reducing it to 283.
|
||||
|
||||
Therefore 912 cycles occurs in 284/283.7516 * 64 µs.
|
||||
|
||||
So one line = 181760000 / 2837516 µs = 45440000 / 709379 µs
|
||||
=> one cycle = 45440000 / 709379*912 = 45440000 / 646953648 = 2840000 / 40434603 µs
|
||||
=> clock rate of 40434603 / 2840000 Mhz
|
||||
|
||||
And, therefore, the ratio to a 4Mhz Z80 clock is:
|
||||
|
||||
40434603 / (2840000 * 4)
|
||||
= 40434603 / 11360000
|
||||
i.e. roughly 3.55 Nick cycles per Z80 cycle.
|
||||
|
||||
If that's true then the 6-cycle window is around 1.69 Z80 cycles long. Given that the Z80
|
||||
clock in an Enterprise can be stopped in half-cycle increments only, the Z80 can only be
|
||||
guaranteed to have around a 1.19 cycle minimum for its actual access. I'm therefore further
|
||||
postulating that the clock stoppage takes place so as to align the final cycle of a relevant
|
||||
access over the available window.
|
||||
|
||||
*/
|
||||
|
||||
template <bool has_disk_controller> class ConcreteMachine:
|
||||
public Activity::Source,
|
||||
public Configurable::Device,
|
||||
public CPU::Z80::BusHandler,
|
||||
public Machine,
|
||||
public MachineTypes::AudioProducer,
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
public MachineTypes::MediaTarget,
|
||||
public MachineTypes::ScanProducer,
|
||||
public MachineTypes::TimedMachine,
|
||||
public Utility::TypeRecipient<CharacterMapper> {
|
||||
private:
|
||||
constexpr uint8_t min_ram_slot(const Analyser::Static::Enterprise::Target &target) {
|
||||
size_t ram_size = 128*1024;
|
||||
switch(target.model) {
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise64: ram_size = 64*1024; break;
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise128: ram_size = 128*1024; break;
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise256: ram_size = 256*1024; break;
|
||||
}
|
||||
|
||||
return uint8_t(0x100 - ram_size / 0x4000);
|
||||
}
|
||||
|
||||
public:
|
||||
ConcreteMachine([[maybe_unused]] const Analyser::Static::Enterprise::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
|
||||
min_ram_slot_(min_ram_slot(target)),
|
||||
z80_(*this),
|
||||
nick_(ram_.end() - 65536),
|
||||
dave_audio_(audio_queue_),
|
||||
speaker_(dave_audio_) {
|
||||
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and Dave elsewhere.
|
||||
set_clock_rate(4'000'000);
|
||||
|
||||
ROM::Request request;
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
|
||||
// Pick one or more EXOS ROMs.
|
||||
switch(target.exos_version) {
|
||||
case Target::EXOSVersion::v10: request = request && ROM::Request(ROM::Name::EnterpriseEXOS10); break;
|
||||
case Target::EXOSVersion::v20: request = request && ROM::Request(ROM::Name::EnterpriseEXOS20); break;
|
||||
case Target::EXOSVersion::v21: request = request && ROM::Request(ROM::Name::EnterpriseEXOS21); break;
|
||||
case Target::EXOSVersion::v23: request = request && ROM::Request(ROM::Name::EnterpriseEXOS23); break;
|
||||
case Target::EXOSVersion::Any:
|
||||
request =
|
||||
request && (
|
||||
ROM::Request(ROM::Name::EnterpriseEXOS10) || ROM::Request(ROM::Name::EnterpriseEXOS20) ||
|
||||
ROM::Request(ROM::Name::EnterpriseEXOS21) || ROM::Request(ROM::Name::EnterpriseEXOS23)
|
||||
);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Similarly pick one or more BASIC ROMs.
|
||||
switch(target.basic_version) {
|
||||
case Target::BASICVersion::v10:
|
||||
request = request && (
|
||||
ROM::Request(ROM::Name::EnterpriseBASIC10) ||
|
||||
(ROM::Request(ROM::Name::EnterpriseBASIC10Part1) && ROM::Request(ROM::Name::EnterpriseBASIC10Part2))
|
||||
);
|
||||
break;
|
||||
case Target::BASICVersion::v11:
|
||||
request = request && (
|
||||
ROM::Request(ROM::Name::EnterpriseBASIC11) ||
|
||||
ROM::Request(ROM::Name::EnterpriseBASIC11Suffixed)
|
||||
);
|
||||
case Target::BASICVersion::v21:
|
||||
request = request && ROM::Request(ROM::Name::EnterpriseBASIC21);
|
||||
break;
|
||||
case Target::BASICVersion::Any:
|
||||
request =
|
||||
request && (
|
||||
ROM::Request(ROM::Name::EnterpriseBASIC10) ||
|
||||
(ROM::Request(ROM::Name::EnterpriseBASIC10Part1) && ROM::Request(ROM::Name::EnterpriseBASIC10Part2)) ||
|
||||
ROM::Request(ROM::Name::EnterpriseBASIC11) ||
|
||||
ROM::Request(ROM::Name::EnterpriseBASIC21)
|
||||
);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Possibly add in a DOS.
|
||||
switch(target.dos) {
|
||||
case Target::DOS::EXDOS: request = request && ROM::Request(ROM::Name::EnterpriseEXDOS); break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Get and validate ROMs.
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
// Extract the appropriate EXOS ROM.
|
||||
exos_.fill(0xff);
|
||||
for(const auto rom_name: { ROM::Name::EnterpriseEXOS10, ROM::Name::EnterpriseEXOS20, ROM::Name::EnterpriseEXOS21, ROM::Name::EnterpriseEXOS23 }) {
|
||||
const auto exos = roms.find(rom_name);
|
||||
if(exos != roms.end()) {
|
||||
memcpy(exos_.data(), exos->second.data(), std::min(exos_.size(), exos->second.size()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the appropriate BASIC ROM[s] (if any).
|
||||
basic_.fill(0xff);
|
||||
bool has_basic = false;
|
||||
for(const auto rom_name: { ROM::Name::EnterpriseBASIC10, ROM::Name::EnterpriseBASIC11, ROM::Name::EnterpriseBASIC11Suffixed, ROM::Name::EnterpriseBASIC21 }) {
|
||||
const auto basic = roms.find(rom_name);
|
||||
if(basic != roms.end()) {
|
||||
memcpy(basic_.data(), basic->second.data(), std::min(basic_.size(), basic->second.size()));
|
||||
has_basic = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!has_basic) {
|
||||
const auto basic1 = roms.find(ROM::Name::EnterpriseBASIC10Part1);
|
||||
const auto basic2 = roms.find(ROM::Name::EnterpriseBASIC10Part2);
|
||||
if(basic1 != roms.end() && basic2 != roms.end()) {
|
||||
memcpy(&basic_[0x0000], basic1->second.data(), std::min(size_t(8192), basic1->second.size()));
|
||||
memcpy(&basic_[0x2000], basic2->second.data(), std::min(size_t(8192), basic2->second.size()));
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the appropriate DOS ROMs.
|
||||
epdos_rom_.fill(0xff);
|
||||
const auto epdos = roms.find(ROM::Name::EnterpriseEPDOS);
|
||||
if(epdos != roms.end()) {
|
||||
memcpy(epdos_rom_.data(), epdos->second.data(), std::min(epdos_rom_.size(), epdos->second.size()));
|
||||
}
|
||||
exdos_rom_.fill(0xff);
|
||||
const auto exdos = roms.find(ROM::Name::EnterpriseEXDOS);
|
||||
if(exdos != roms.end()) {
|
||||
memcpy(exdos_rom_.data(), exdos->second.data(), std::min(exdos_rom_.size(), exdos->second.size()));
|
||||
}
|
||||
|
||||
// Seed key state.
|
||||
clear_all_keys();
|
||||
|
||||
// Take a reasonable guess at the initial memory configuration:
|
||||
// put EXOS into the first bank since this is a Z80 and therefore
|
||||
// starts from address 0; the third instruction in EXOS is a jump
|
||||
// to $c02e so it's reasonable to assume EXOS is in the highest bank
|
||||
// too, and it appears to act correctly if it's the first 16kb that's
|
||||
// in the highest bank. From there I guess: all banks are initialised
|
||||
// to 0.
|
||||
page<0>(0x00);
|
||||
page<1>(0x00);
|
||||
page<2>(0x00);
|
||||
page<3>(0x00);
|
||||
|
||||
// Set up audio.
|
||||
speaker_.set_input_rate(250000.0f); // TODO: a bigger number, and respect the programmable divider.
|
||||
|
||||
// Pass on any media.
|
||||
insert_media(target.media);
|
||||
if(!target.loading_command.empty()) {
|
||||
type_string(target.loading_command);
|
||||
}
|
||||
|
||||
// Ensure the splash screen is automatically skipped if any media has been provided.
|
||||
if(!target.media.empty()) {
|
||||
should_skip_splash_screen_ = !target.media.empty();
|
||||
typer_delay_ = 2;
|
||||
}
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
audio_queue_.flush();
|
||||
}
|
||||
|
||||
// MARK: - Z80::BusHandler.
|
||||
forceinline void advance_nick(HalfCycles duration) {
|
||||
if(nick_ += duration) {
|
||||
const auto nick = nick_.last_valid();
|
||||
const bool nick_interrupt_line = nick->get_interrupt_line();
|
||||
if(nick_interrupt_line && !previous_nick_interrupt_line_) {
|
||||
set_interrupts(uint8_t(Dave::Interrupt::Nick), nick_.last_sequence_point_overrun());
|
||||
}
|
||||
previous_nick_interrupt_line_ = nick_interrupt_line;
|
||||
}
|
||||
}
|
||||
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
using PartialMachineCycle = CPU::Z80::PartialMachineCycle;
|
||||
const uint16_t address = cycle.address ? *cycle.address : 0x0000;
|
||||
|
||||
// Calculate an access penalty, if applicable.
|
||||
//
|
||||
// Rule applied here, which is slightly inferred:
|
||||
//
|
||||
// Non-video reads and writes are delayed by exactly a cycle or not delayed at all,
|
||||
// depending on the programmer's configuration of Dave.
|
||||
//
|
||||
// Video reads and writes, and Nick port accesses, are delayed so that the last
|
||||
// clock cycle of the machine cycle falls wholly inside the designated Z80 access
|
||||
// window, per Nick.
|
||||
//
|
||||
// The switch statement below just attempts to implement that logic.
|
||||
//
|
||||
HalfCycles penalty;
|
||||
switch(cycle.operation) {
|
||||
default: break;
|
||||
|
||||
// For non-video pauses, insert during the initial part of the bus cycle.
|
||||
case CPU::Z80::PartialMachineCycle::ReadStart:
|
||||
case CPU::Z80::PartialMachineCycle::WriteStart:
|
||||
if(!is_video_[address >> 14] && wait_mode_ == WaitMode::OnAllAccesses) {
|
||||
penalty = HalfCycles(2);
|
||||
}
|
||||
break;
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcodeStart:
|
||||
if(!is_video_[address >> 14] && wait_mode_ != WaitMode::None) {
|
||||
penalty = HalfCycles(2);
|
||||
} else {
|
||||
// Query Nick for the amount of delay that would occur with one cycle left
|
||||
// in this read opcode.
|
||||
const auto delay_time = nick_.time_since_flush(HalfCycles(2));
|
||||
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
|
||||
penalty = nick_.back_map(delay, delay_time);
|
||||
}
|
||||
break;
|
||||
|
||||
// Video pauses: insert right at the end of the bus cycle.
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
// Ensure all video that should have been collected prior to
|
||||
// this write has been.
|
||||
if(is_video_[address >> 14]) {
|
||||
nick_.flush();
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
if(is_video_[address >> 14]) {
|
||||
// Get delay, in Nick cycles, for a Z80 access that occurs in 0.5
|
||||
// cycles from now (i.e. with one cycle left to run).
|
||||
const auto delay_time = nick_.time_since_flush(HalfCycles(1));
|
||||
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
|
||||
penalty = nick_.back_map(delay, delay_time);
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
case CPU::Z80::PartialMachineCycle::Output: {
|
||||
if((address & 0xf0) == 0x80) {
|
||||
// Get delay, in Nick cycles, for a Z80 access that occurs in 0.5
|
||||
// cycles from now (i.e. with one cycle left to run).
|
||||
const auto delay_time = nick_.time_since_flush(HalfCycles(1));
|
||||
const auto delay = nick_.last_valid()->get_time_until_z80_slot(delay_time);
|
||||
penalty = nick_.back_map(delay, delay_time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HalfCycles full_length = cycle.length + penalty;
|
||||
time_since_audio_update_ += full_length;
|
||||
advance_nick(full_length);
|
||||
if(dave_timer_ += full_length) {
|
||||
set_interrupts(dave_timer_.last_valid()->get_new_interrupts(), dave_timer_.last_sequence_point_overrun());
|
||||
}
|
||||
|
||||
// The WD/etc runs at a nominal 8Mhz.
|
||||
if constexpr (has_disk_controller) {
|
||||
exdos_.run_for(Cycles(full_length.as_integral()));
|
||||
}
|
||||
|
||||
switch(cycle.operation) {
|
||||
default: break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
switch(address & 0xff) {
|
||||
default:
|
||||
LOG("Unhandled input from " << PADHEX(2) << (address & 0xff));
|
||||
*cycle.value = 0xff;
|
||||
break;
|
||||
|
||||
case 0x10: case 0x11: case 0x12: case 0x13:
|
||||
case 0x14: case 0x15: case 0x16: case 0x17:
|
||||
if constexpr (has_disk_controller) {
|
||||
*cycle.value = exdos_.read(address);
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
break;
|
||||
case 0x18: case 0x19: case 0x1a: case 0x1b:
|
||||
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
|
||||
if constexpr (has_disk_controller) {
|
||||
*cycle.value = exdos_.get_control_register();
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x80: case 0x81: case 0x82: case 0x83:
|
||||
case 0x84: case 0x85: case 0x86: case 0x87:
|
||||
case 0x88: case 0x89: case 0x8a: case 0x8b:
|
||||
case 0x8c: case 0x8d: case 0x8e: case 0x8f:
|
||||
*cycle.value = nick_->read();
|
||||
break;
|
||||
|
||||
case 0xb0: *cycle.value = pages_[0]; break;
|
||||
case 0xb1: *cycle.value = pages_[1]; break;
|
||||
case 0xb2: *cycle.value = pages_[2]; break;
|
||||
case 0xb3: *cycle.value = pages_[3]; break;
|
||||
|
||||
case 0xb4:
|
||||
*cycle.value =
|
||||
(nick_->get_interrupt_line() ? 0x00 : 0x10) |
|
||||
dave_timer_->get_divider_state() |
|
||||
interrupt_state_;
|
||||
break;
|
||||
case 0xb5:
|
||||
if(active_key_line_ < key_lines_.size()) {
|
||||
*cycle.value = key_lines_[active_key_line_];
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
break;
|
||||
case 0xb6: {
|
||||
// TODO: selected keyboard row, 0 to 9, should return one bit of joystick
|
||||
// input. That being the case:
|
||||
//
|
||||
// b0: joystick input
|
||||
// b1, b2: unused (in theory read from control port, but not used by any hardware)
|
||||
// b3: 0 = printer ready; 1 = not ready
|
||||
// b4: serial, data in
|
||||
// b5: serial, status in
|
||||
// b6: tape input volume level, 0 = high, 1 = low
|
||||
// b7: tape data input
|
||||
*cycle.value = 0xff;
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
switch(address & 0xff) {
|
||||
default:
|
||||
LOG("Unhandled output: " << PADHEX(2) << *cycle.value << " to " << PADHEX(2) << (address & 0xff));
|
||||
break;
|
||||
|
||||
case 0x10: case 0x11: case 0x12: case 0x13:
|
||||
case 0x14: case 0x15: case 0x16: case 0x17:
|
||||
if constexpr(has_disk_controller) {
|
||||
exdos_.write(address, *cycle.value);
|
||||
}
|
||||
break;
|
||||
case 0x18: case 0x19: case 0x1a: case 0x1b:
|
||||
case 0x1c: case 0x1d: case 0x1e: case 0x1f:
|
||||
if constexpr(has_disk_controller) {
|
||||
exdos_.set_control_register(*cycle.value);
|
||||
}
|
||||
break;
|
||||
|
||||
case 0x80: case 0x81: case 0x82: case 0x83:
|
||||
case 0x84: case 0x85: case 0x86: case 0x87:
|
||||
case 0x88: case 0x89: case 0x8a: case 0x8b:
|
||||
case 0x8c: case 0x8d: case 0x8e: case 0x8f:
|
||||
nick_->write(address, *cycle.value);
|
||||
break;
|
||||
|
||||
case 0xb0: page<0>(*cycle.value); break;
|
||||
case 0xb1: page<1>(*cycle.value); break;
|
||||
case 0xb2: page<2>(*cycle.value); break;
|
||||
case 0xb3: page<3>(*cycle.value); break;
|
||||
|
||||
case 0xa0: case 0xa1: case 0xa2: case 0xa3:
|
||||
case 0xa4: case 0xa5: case 0xa6: case 0xa7:
|
||||
case 0xa8: case 0xa9: case 0xaa: case 0xab:
|
||||
case 0xac: case 0xad: case 0xae: case 0xaf:
|
||||
update_audio();
|
||||
dave_audio_.write(address, *cycle.value);
|
||||
dave_timer_->write(address, *cycle.value);
|
||||
break;
|
||||
|
||||
case 0xb4:
|
||||
interrupt_mask_ = *cycle.value & 0x55;
|
||||
interrupt_state_ &= ~*cycle.value;
|
||||
update_interrupts();
|
||||
break;
|
||||
case 0xb5:
|
||||
// Logic here: the ROM scans the keyboard by checking ascending
|
||||
// lines. It also seems to provide a line of 0 when using port B5
|
||||
// for non-keyboard uses.
|
||||
//
|
||||
// So: use the rollover from line 9 back to line 0 as a trigger to
|
||||
// spot that a scan of the keyboard just finished. Which makes it
|
||||
// time to enqueue the next keypress.
|
||||
//
|
||||
// Re: should_skip_splash_screen_ and typer_delay_, assume that a
|
||||
// single keypress is necessary to get past the Enterprise splash
|
||||
// screen, then a pause in keypressing while BASIC or whatever
|
||||
// starts up, then presses can resume.
|
||||
if(active_key_line_ == 9 && !(*cycle.value & 0xf) && (should_skip_splash_screen_ || typer_)) {
|
||||
if(should_skip_splash_screen_) {
|
||||
set_key_state(uint16_t(Key::Space), typer_delay_);
|
||||
if(typer_delay_) {
|
||||
--typer_delay_;
|
||||
} else {
|
||||
typer_delay_ = 60;
|
||||
should_skip_splash_screen_ = false;
|
||||
}
|
||||
} else {
|
||||
if(!typer_delay_) {
|
||||
if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_ = nullptr;
|
||||
}
|
||||
} else {
|
||||
--typer_delay_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
active_key_line_ = *cycle.value & 0xf;
|
||||
// TODO:
|
||||
//
|
||||
// b4: strobe output for printer
|
||||
// b5: tape sound control (?)
|
||||
// b6: tape motor control 1, 1 = on
|
||||
// b7: tape motor control 2, 1 = on
|
||||
break;
|
||||
case 0xb6:
|
||||
// Just 8 bits of printer data.
|
||||
LOG("TODO: printer output " << PADHEX(2) << *cycle.value);
|
||||
break;
|
||||
case 0xb7:
|
||||
// b0 = serial data out
|
||||
// b1 = serial status out
|
||||
LOG("TODO: serial output " << PADHEX(2) << *cycle.value);
|
||||
break;
|
||||
case 0xbf:
|
||||
// TODO: onboard RAM, Dave 8/12Mhz select.
|
||||
switch((*cycle.value >> 2)&3) {
|
||||
default: wait_mode_ = WaitMode::None; break;
|
||||
case 0: wait_mode_ = WaitMode::OnAllAccesses; break;
|
||||
case 1: wait_mode_ = WaitMode::OnM1; break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
if(read_pointers_[address >> 14]) {
|
||||
*cycle.value = read_pointers_[address >> 14][address];
|
||||
} else {
|
||||
*cycle.value = 0xff;
|
||||
}
|
||||
break;
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
if(write_pointers_[address >> 14]) {
|
||||
write_pointers_[address >> 14][address] = *cycle.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return penalty;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
nick_.flush();
|
||||
update_audio();
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
private:
|
||||
// MARK: - Memory layout
|
||||
std::array<uint8_t, 256 * 1024> ram_{};
|
||||
std::array<uint8_t, 64 * 1024> exos_;
|
||||
std::array<uint8_t, 16 * 1024> basic_;
|
||||
std::array<uint8_t, 16 * 1024> exdos_rom_;
|
||||
std::array<uint8_t, 32 * 1024> epdos_rom_;
|
||||
const uint8_t min_ram_slot_;
|
||||
|
||||
const uint8_t *read_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||
uint8_t *write_pointers_[4] = {nullptr, nullptr, nullptr, nullptr};
|
||||
uint8_t pages_[4] = {0x80, 0x80, 0x80, 0x80};
|
||||
|
||||
template <size_t slot> void page(uint8_t offset) {
|
||||
pages_[slot] = offset;
|
||||
|
||||
#define Map(location, source) \
|
||||
if(offset >= location && offset < location + source.size() / 0x4000) { \
|
||||
page<slot>(&source[(offset - location) * 0x4000], nullptr); \
|
||||
is_video_[slot] = false; \
|
||||
return; \
|
||||
}
|
||||
|
||||
Map(0, exos_);
|
||||
Map(16, basic_);
|
||||
Map(32, exdos_rom_);
|
||||
Map(48, epdos_rom_);
|
||||
|
||||
#undef Map
|
||||
|
||||
// Of whatever size of RAM I've declared above, use only the final portion.
|
||||
// This correlated with Nick always having been handed the final 64kb and,
|
||||
// at least while the RAM is the first thing declared above, does a little
|
||||
// to benefit data locality. Albeit not in a useful sense.
|
||||
if(offset >= min_ram_slot_) {
|
||||
const auto ram_floor = 4194304 - ram_.size();
|
||||
const size_t address = offset * 0x4000 - ram_floor;
|
||||
is_video_[slot] = offset >= 0xfc; // TODO: this hard-codes a 64kb video assumption.
|
||||
page<slot>(&ram_[address], &ram_[address]);
|
||||
return;
|
||||
}
|
||||
|
||||
page<slot>(nullptr, nullptr);
|
||||
}
|
||||
|
||||
template <size_t slot> void page(const uint8_t *read, uint8_t *write) {
|
||||
read_pointers_[slot] = read ? read - (slot * 0x4000) : nullptr;
|
||||
write_pointers_[slot] = write ? write - (slot * 0x4000) : nullptr;
|
||||
}
|
||||
|
||||
// MARK: - Memory Timing
|
||||
|
||||
// The wait mode affects all memory accesses _outside of the video area_.
|
||||
enum class WaitMode {
|
||||
None,
|
||||
OnM1,
|
||||
OnAllAccesses
|
||||
} wait_mode_ = WaitMode::None;
|
||||
bool is_video_[4]{};
|
||||
|
||||
// MARK: - ScanProducer
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target) override {
|
||||
nick_.last_valid()->set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const override {
|
||||
return nick_.last_valid()->get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void set_display_type(Outputs::Display::DisplayType display_type) final {
|
||||
nick_.last_valid()->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType get_display_type() const final {
|
||||
return nick_.last_valid()->get_display_type();
|
||||
}
|
||||
|
||||
// MARK: - AudioProducer
|
||||
|
||||
Outputs::Speaker::Speaker *get_speaker() final {
|
||||
return &speaker_;
|
||||
}
|
||||
|
||||
// MARK: - TimedMachine
|
||||
void run_for(const Cycles cycles) override {
|
||||
z80_.run_for(cycles);
|
||||
}
|
||||
|
||||
// MARK: - KeyboardMachine
|
||||
Enterprise::KeyboardMapper keyboard_mapper_;
|
||||
KeyboardMapper *get_keyboard_mapper() final {
|
||||
return &keyboard_mapper_;
|
||||
}
|
||||
|
||||
uint8_t active_key_line_ = 0;
|
||||
std::array<uint8_t, 10> key_lines_;
|
||||
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||
if(is_pressed) {
|
||||
key_lines_[key >> 8] &= ~uint8_t(key);
|
||||
} else {
|
||||
key_lines_[key >> 8] |= uint8_t(key);
|
||||
}
|
||||
}
|
||||
|
||||
void clear_all_keys() final {
|
||||
key_lines_.fill(0xff);
|
||||
}
|
||||
|
||||
// MARK: - Utility::TypeRecipient
|
||||
void type_string(const std::string &string) final {
|
||||
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
|
||||
|
||||
if(z80_.get_is_resetting()) {
|
||||
should_skip_splash_screen_ = true;
|
||||
typer_delay_ = 1;
|
||||
} else {
|
||||
should_skip_splash_screen_ = false;
|
||||
typer_delay_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool can_type(char c) const final {
|
||||
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
|
||||
}
|
||||
|
||||
bool should_skip_splash_screen_ = false;
|
||||
int typer_delay_ = 30;
|
||||
|
||||
// MARK: - MediaTarget
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if constexpr (has_disk_controller) {
|
||||
if(!media.disks.empty()) {
|
||||
exdos_.set_disk(media.disks.front(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: - Interrupts
|
||||
|
||||
uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00;
|
||||
void set_interrupts(uint8_t mask, HalfCycles offset = HalfCycles(0)) {
|
||||
interrupt_state_ |= uint8_t(mask);
|
||||
update_interrupts(offset);
|
||||
}
|
||||
void update_interrupts(HalfCycles offset = HalfCycles(0)) {
|
||||
z80_.set_interrupt_line((interrupt_state_ >> 1) & interrupt_mask_, offset);
|
||||
}
|
||||
|
||||
// MARK: - Chips.
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
JustInTimeActor<Nick, HalfCycles, 40434603, 11360000> nick_;
|
||||
bool previous_nick_interrupt_line_ = false;
|
||||
// Cf. timing guesses above.
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue audio_queue_;
|
||||
Dave::Audio dave_audio_;
|
||||
Outputs::Speaker::LowpassSpeaker<Dave::Audio> speaker_;
|
||||
HalfCycles time_since_audio_update_;
|
||||
|
||||
// The following two should both use the same divider.
|
||||
JustInTimeActor<Dave::TimedInterruptSource, HalfCycles, 1, 16> dave_timer_;
|
||||
inline void update_audio() {
|
||||
// TODO: divide by only 8, letting Dave divide itself by a further 2 or 3
|
||||
// as per its own register.
|
||||
speaker_.run_for(audio_queue_, time_since_audio_update_.divide_cycles(Cycles(16)));
|
||||
}
|
||||
|
||||
// MARK: - EXDos card.
|
||||
EXDos exdos_;
|
||||
|
||||
// MARK: - Activity Source
|
||||
void set_activity_observer(Activity::Observer *observer) final {
|
||||
if constexpr (has_disk_controller) {
|
||||
exdos_.set_activity_observer(observer);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
std::unique_ptr<Reflection::Struct> get_options() final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
return options;
|
||||
}
|
||||
|
||||
void set_options(const std::unique_ptr<Reflection::Struct> &str) final {
|
||||
const auto options = dynamic_cast<Options *>(str.get());
|
||||
set_video_signal_configurable(options->output);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
using namespace Enterprise;
|
||||
|
||||
Machine *Machine::Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher) {
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
const Target *const enterprise_target = dynamic_cast<const Target *>(target);
|
||||
|
||||
if(enterprise_target->dos == Target::DOS::None)
|
||||
return new Enterprise::ConcreteMachine<false>(*enterprise_target, rom_fetcher);
|
||||
else
|
||||
return new Enterprise::ConcreteMachine<true>(*enterprise_target, rom_fetcher);
|
||||
}
|
||||
|
||||
Machine::~Machine() {}
|
||||
47
Machines/Enterprise/Enterprise.hpp
Normal file
47
Machines/Enterprise/Enterprise.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Enterprise.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 10/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Enterprise_hpp
|
||||
#define Enterprise_hpp
|
||||
|
||||
#include "../../Analyser/Static/StaticAnalyser.hpp"
|
||||
#include "../../Configurable/Configurable.hpp"
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
#include "../ROMMachine.hpp"
|
||||
|
||||
namespace Enterprise {
|
||||
|
||||
/*!
|
||||
@abstract Represents an Elan Enterprise.
|
||||
|
||||
@discussion An instance of Enterprise::Machine represents the current state of an
|
||||
Elan Enterprise.
|
||||
*/
|
||||
class Machine {
|
||||
public:
|
||||
virtual ~Machine();
|
||||
|
||||
static Machine *Enterprise(const Analyser::Static::Target *target, const ROMMachine::ROMFetcher &rom_fetcher);
|
||||
|
||||
/// Defines the runtime options available for an Enterprise.
|
||||
class Options: public Reflection::StructImpl<Options>, public Configurable::DisplayOption<Options> {
|
||||
friend Configurable::DisplayOption<Options>;
|
||||
public:
|
||||
Options(Configurable::OptionsType type) :
|
||||
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour) {
|
||||
if(needs_declare()) {
|
||||
declare_display_option();
|
||||
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, Configurable::Display::CompositeMonochrome, -1);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* Enterprise_hpp */
|
||||
151
Machines/Enterprise/Keyboard.cpp
Normal file
151
Machines/Enterprise/Keyboard.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
//
|
||||
// Keyboard.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
using namespace Enterprise;
|
||||
|
||||
uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) const {
|
||||
#define BIND(source, dest) case Inputs::Keyboard::Key::source: return uint16_t(Key::dest)
|
||||
switch(key) {
|
||||
default: break;
|
||||
|
||||
BIND(Backslash, Backslash);
|
||||
BIND(CapsLock, Lock);
|
||||
BIND(Tab, Tab);
|
||||
BIND(Escape, Escape);
|
||||
BIND(Hyphen, Hyphen);
|
||||
BIND(Equals, Caret);
|
||||
BIND(Backspace, Erase);
|
||||
BIND(Delete, Delete);
|
||||
BIND(Semicolon, Semicolon);
|
||||
BIND(Quote, Colon);
|
||||
BIND(OpenSquareBracket, OpenSquareBracket);
|
||||
BIND(CloseSquareBracket, CloseSquareBracket);
|
||||
|
||||
BIND(End, Stop);
|
||||
BIND(Insert, Insert);
|
||||
BIND(BackTick, At);
|
||||
|
||||
BIND(k1, k1); BIND(k2, k2); BIND(k3, k3); BIND(k4, k4); BIND(k5, k5);
|
||||
BIND(k6, k6); BIND(k7, k7); BIND(k8, k8); BIND(k9, k9); BIND(k0, k0);
|
||||
|
||||
BIND(F1, F1); BIND(F2, F2); BIND(F3, F3); BIND(F4, F4);
|
||||
BIND(F5, F5); BIND(F6, F6); BIND(F7, F7); BIND(F8, F8);
|
||||
|
||||
BIND(Keypad1, F1); BIND(Keypad2, F2); BIND(Keypad3, F3); BIND(Keypad4, F4);
|
||||
BIND(Keypad5, F5); BIND(Keypad6, F6); BIND(Keypad7, F7); BIND(Keypad8, F8);
|
||||
|
||||
BIND(Q, Q); BIND(W, W); BIND(E, E); BIND(R, R); BIND(T, T);
|
||||
BIND(Y, Y); BIND(U, U); BIND(I, I); BIND(O, O); BIND(P, P);
|
||||
|
||||
BIND(A, A); BIND(S, S); BIND(D, D); BIND(F, F); BIND(G, G);
|
||||
BIND(H, H); BIND(J, J); BIND(K, K); BIND(L, L);
|
||||
|
||||
BIND(Z, Z); BIND(X, X); BIND(C, C); BIND(V, V);
|
||||
BIND(B, B); BIND(N, N); BIND(M, M);
|
||||
|
||||
BIND(FullStop, FullStop);
|
||||
BIND(Comma, Comma);
|
||||
BIND(ForwardSlash, ForwardSlash);
|
||||
|
||||
BIND(Space, Space); BIND(Enter, Enter);
|
||||
|
||||
BIND(LeftShift, LeftShift);
|
||||
BIND(RightShift, RightShift);
|
||||
BIND(LeftOption, Alt);
|
||||
BIND(RightOption, Alt);
|
||||
BIND(LeftControl, Control);
|
||||
BIND(RightControl, Control);
|
||||
|
||||
BIND(Left, Left);
|
||||
BIND(Right, Right);
|
||||
BIND(Up, Up);
|
||||
BIND(Down, Down);
|
||||
}
|
||||
#undef BIND
|
||||
|
||||
return MachineTypes::MappedKeyboardMachine::KeyNotMapped;
|
||||
}
|
||||
|
||||
const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
#define KEYS(x) {uint16_t(x), MachineTypes::MappedKeyboardMachine::KeyEndSequence}
|
||||
#define SHIFT(x) {uint16_t(Key::LeftShift), uint16_t(x), MachineTypes::MappedKeyboardMachine::KeyEndSequence}
|
||||
#define _ {MachineTypes::MappedKeyboardMachine::KeyNotMapped}
|
||||
static KeySequence key_sequences[] = {
|
||||
/* NUL */ _, /* SOH */ _,
|
||||
/* STX */ _, /* ETX */ _,
|
||||
/* EOT */ _, /* ENQ */ _,
|
||||
/* ACK */ _, /* BEL */ _,
|
||||
/* BS */ KEYS(Key::Erase), /* HT */ KEYS(Key::Tab),
|
||||
/* LF */ KEYS(Key::Enter), /* VT */ _,
|
||||
/* FF */ _, /* CR */ KEYS(Key::Enter),
|
||||
/* SO */ _, /* SI */ _,
|
||||
/* DLE */ _, /* DC1 */ _,
|
||||
/* DC2 */ _, /* DC3 */ _,
|
||||
/* DC4 */ _, /* NAK */ _,
|
||||
/* SYN */ _, /* ETB */ _,
|
||||
/* CAN */ _, /* EM */ _,
|
||||
/* SUB */ _, /* ESC */ KEYS(Key::Escape),
|
||||
/* FS */ _, /* GS */ _,
|
||||
/* RS */ _, /* US */ _,
|
||||
/* space */ KEYS(Key::Space), /* ! */ SHIFT(Key::k1),
|
||||
/* " */ SHIFT(Key::k2), /* # */ _,
|
||||
/* $ */ SHIFT(Key::k4), /* % */ SHIFT(Key::k5),
|
||||
/* & */ SHIFT(Key::k6), /* ' */ SHIFT(Key::k7),
|
||||
/* ( */ SHIFT(Key::k8), /* ) */ SHIFT(Key::k9),
|
||||
/* * */ SHIFT(Key::Colon), /* + */ SHIFT(Key::Semicolon),
|
||||
/* , */ KEYS(Key::Comma), /* - */ KEYS(Key::Hyphen),
|
||||
/* . */ KEYS(Key::FullStop), /* / */ KEYS(Key::ForwardSlash),
|
||||
/* 0 */ KEYS(Key::k0), /* 1 */ KEYS(Key::k1),
|
||||
/* 2 */ KEYS(Key::k2), /* 3 */ KEYS(Key::k3),
|
||||
/* 4 */ KEYS(Key::k4), /* 5 */ KEYS(Key::k5),
|
||||
/* 6 */ KEYS(Key::k6), /* 7 */ KEYS(Key::k7),
|
||||
/* 8 */ KEYS(Key::k8), /* 9 */ KEYS(Key::k9),
|
||||
/* : */ KEYS(Key::Colon), /* ; */ KEYS(Key::Semicolon),
|
||||
/* < */ SHIFT(Key::Comma), /* = */ SHIFT(Key::Hyphen),
|
||||
/* > */ SHIFT(Key::FullStop), /* ? */ SHIFT(Key::ForwardSlash),
|
||||
/* @ */ KEYS(Key::At), /* A */ SHIFT(Key::A),
|
||||
/* B */ SHIFT(Key::B), /* C */ SHIFT(Key::C),
|
||||
/* D */ SHIFT(Key::D), /* E */ SHIFT(Key::E),
|
||||
/* F */ SHIFT(Key::F), /* G */ SHIFT(Key::G),
|
||||
/* H */ SHIFT(Key::H), /* I */ SHIFT(Key::I),
|
||||
/* J */ SHIFT(Key::J), /* K */ SHIFT(Key::K),
|
||||
/* L */ SHIFT(Key::L), /* M */ SHIFT(Key::M),
|
||||
/* N */ SHIFT(Key::N), /* O */ SHIFT(Key::O),
|
||||
/* P */ SHIFT(Key::P), /* Q */ SHIFT(Key::Q),
|
||||
/* R */ SHIFT(Key::R), /* S */ SHIFT(Key::S),
|
||||
/* T */ SHIFT(Key::T), /* U */ SHIFT(Key::U),
|
||||
/* V */ SHIFT(Key::V), /* W */ SHIFT(Key::W),
|
||||
/* X */ SHIFT(Key::X), /* Y */ SHIFT(Key::Y),
|
||||
/* Z */ SHIFT(Key::Z), /* [ */ KEYS(Key::OpenSquareBracket),
|
||||
/* \ */ KEYS(Key::Backslash), /* ] */ KEYS(Key::CloseSquareBracket),
|
||||
/* ^ */ SHIFT(Key::Caret), /* _ */ SHIFT(Key::k0),
|
||||
/* ` */ SHIFT(Key::At), /* a */ KEYS(Key::A),
|
||||
/* b */ KEYS(Key::B), /* c */ KEYS(Key::C),
|
||||
/* d */ KEYS(Key::D), /* e */ KEYS(Key::E),
|
||||
/* f */ KEYS(Key::F), /* g */ KEYS(Key::G),
|
||||
/* h */ KEYS(Key::H), /* i */ KEYS(Key::I),
|
||||
/* j */ KEYS(Key::J), /* k */ KEYS(Key::K),
|
||||
/* l */ KEYS(Key::L), /* m */ KEYS(Key::M),
|
||||
/* n */ KEYS(Key::N), /* o */ KEYS(Key::O),
|
||||
/* p */ KEYS(Key::P), /* q */ KEYS(Key::Q),
|
||||
/* r */ KEYS(Key::R), /* s */ KEYS(Key::S),
|
||||
/* t */ KEYS(Key::T), /* u */ KEYS(Key::U),
|
||||
/* v */ KEYS(Key::V), /* w */ KEYS(Key::W),
|
||||
/* x */ KEYS(Key::X), /* y */ KEYS(Key::Y),
|
||||
/* z */ KEYS(Key::Z), /* { */ SHIFT(Key::OpenSquareBracket),
|
||||
/* | */ SHIFT(Key::Backslash), /* } */ SHIFT(Key::CloseSquareBracket),
|
||||
/* ~ */ SHIFT(Key::Caret)
|
||||
};
|
||||
#undef _
|
||||
#undef SHIFT
|
||||
#undef KEYS
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, character);
|
||||
}
|
||||
66
Machines/Enterprise/Keyboard.hpp
Normal file
66
Machines/Enterprise/Keyboard.hpp
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// Keyboard.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 17/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Machines_Enterprise_Keyboard_hpp
|
||||
#define Machines_Enterprise_Keyboard_hpp
|
||||
|
||||
#include "../KeyboardMachine.hpp"
|
||||
#include "../Utility/Typer.hpp"
|
||||
|
||||
namespace Enterprise {
|
||||
|
||||
#define KeyCode(line, mask) (line << 8) | mask
|
||||
|
||||
enum class Key: uint16_t {
|
||||
N = 0x0000 | 0x01, Backslash = 0x0000 | 0x02, B = 0x0000 | 0x04, C = 0x0000 | 0x08,
|
||||
V = 0x0000 | 0x10, X = 0x0000 | 0x20, Z = 0x0000 | 0x40, LeftShift = 0x0000 | 0x80,
|
||||
|
||||
H = 0x0100 | 0x01, Lock = 0x0100 | 0x02, G = 0x0100 | 0x04, D = 0x0100 | 0x08,
|
||||
F = 0x0100 | 0x10, S = 0x0100 | 0x20, A = 0x0100 | 0x40, Control = 0x0100 | 0x80,
|
||||
|
||||
U = 0x0200 | 0x01, Q = 0x0200 | 0x02, Y = 0x0200 | 0x04, R = 0x0200 | 0x08,
|
||||
T = 0x0200 | 0x10, E = 0x0200 | 0x20, W = 0x0200 | 0x40, Tab = 0x0200 | 0x80,
|
||||
|
||||
k7 = 0x0300 | 0x01, k1 = 0x0300 | 0x02, k6 = 0x0300 | 0x04, k4 = 0x0300 | 0x08,
|
||||
k5 = 0x0300 | 0x10, k3 = 0x0300 | 0x20, k2 = 0x0300 | 0x40, Escape = 0x0300 | 0x80,
|
||||
|
||||
F4 = 0x0400 | 0x01, F8 = 0x0400 | 0x02, F3 = 0x0400 | 0x04, F6 = 0x0400 | 0x08,
|
||||
F5 = 0x0400 | 0x10, F7 = 0x0400 | 0x20, F2 = 0x0400 | 0x40, F1 = 0x0400 | 0x80,
|
||||
|
||||
k8 = 0x0500 | 0x01, k9 = 0x0500 | 0x04, Hyphen = 0x0500 | 0x08,
|
||||
k0 = 0x0500 | 0x10, Caret = 0x0500 | 0x20, Erase = 0x0500 | 0x40,
|
||||
|
||||
J = 0x0600 | 0x01, K = 0x0600 | 0x04, Semicolon = 0x0600 | 0x08,
|
||||
L = 0x0600 | 0x10, Colon = 0x0600 | 0x20, CloseSquareBracket = 0x0600 | 0x40,
|
||||
|
||||
Stop = 0x0700 | 0x01, Down = 0x0700 | 0x02, Right = 0x0700 | 0x04, Up = 0x0700 | 0x08,
|
||||
Hold = 0x0700 | 0x10, Left = 0x0700 | 0x20, Enter = 0x0700 | 0x40, Alt = 0x0700 | 0x80,
|
||||
|
||||
M = 0x0800 | 0x01, Delete = 0x0800 | 0x02, Comma = 0x0800 | 0x04,
|
||||
ForwardSlash = 0x0800 | 0x08,
|
||||
FullStop = 0x0800 | 0x10,
|
||||
RightShift = 0x0800 | 0x20, Space = 0x0800 | 0x40, Insert = 0x0800 | 0x80,
|
||||
|
||||
I = 0x0900 | 0x01, O = 0x0900 | 0x04, At = 0x0900 | 0x08,
|
||||
P = 0x0900 | 0x10,
|
||||
OpenSquareBracket = 0x0900 | 0x20
|
||||
};
|
||||
|
||||
#undef KeyCode
|
||||
|
||||
struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper {
|
||||
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key) const final;
|
||||
};
|
||||
|
||||
struct CharacterMapper: public ::Utility::CharacterMapper {
|
||||
const uint16_t *sequence_for_character(char character) const override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* Keyboard_hpp */
|
||||
629
Machines/Enterprise/Nick.cpp
Normal file
629
Machines/Enterprise/Nick.cpp
Normal file
@@ -0,0 +1,629 @@
|
||||
//
|
||||
// Nick.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "Nick.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace {
|
||||
|
||||
uint16_t mapped_colour(uint8_t source) {
|
||||
// On the Enterprise, red and green are 3-bit quantities; blue is a 2-bit quantity.
|
||||
int red = ((source&0x01) << 2) | ((source&0x08) >> 2) | ((source&0x40) >> 6);
|
||||
int green = ((source&0x02) << 1) | ((source&0x10) >> 3) | ((source&0x80) >> 7);
|
||||
int blue = ((source&0x04) >> 1) | ((source&0x20) >> 5);
|
||||
|
||||
assert(red <= 7);
|
||||
assert(green <= 7);
|
||||
assert(blue <= 3);
|
||||
|
||||
red = (red << 1) + (red >> 3);
|
||||
green = (green << 1) + (green >> 3);
|
||||
blue = (blue << 2) + blue;
|
||||
|
||||
assert(red <= 15);
|
||||
assert(green <= 15);
|
||||
assert(blue <= 15);
|
||||
|
||||
// Duplicate bits where necessary to map to a full 4-bit range per channel.
|
||||
const uint8_t parts[2] = {
|
||||
uint8_t(
|
||||
red
|
||||
),
|
||||
uint8_t(
|
||||
(green << 4) + blue
|
||||
)
|
||||
};
|
||||
return *reinterpret_cast<const uint16_t *>(parts);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using namespace Enterprise;
|
||||
|
||||
Nick::Nick(const uint8_t *ram) :
|
||||
crt_(57*16, 16, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red4Green4Blue4),
|
||||
ram_(ram) {
|
||||
|
||||
// Just use RGB for now.
|
||||
set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
|
||||
// Crop to the centre 90% of the display.
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.05f, 0.05f, 0.9f, 0.9f));
|
||||
}
|
||||
|
||||
void Nick::write(uint16_t address, uint8_t value) {
|
||||
switch(address & 3) {
|
||||
case 0:
|
||||
// Ignored: everything to do with external colour.
|
||||
for(int c = 0; c < 8; c++) {
|
||||
palette_[c + 8] = mapped_colour(uint8_t(((value & 0x1f) << 3) + c));
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if(output_type_ == OutputType::Border) {
|
||||
set_output_type(OutputType::Border, true);
|
||||
}
|
||||
border_colour_ = mapped_colour(value);
|
||||
break;
|
||||
case 2:
|
||||
line_parameter_base_ = uint16_t((line_parameter_base_ & 0xf000) | (value << 4));
|
||||
break;
|
||||
case 3:
|
||||
line_parameter_base_ = uint16_t((line_parameter_base_ & 0x0ff0) | (value << 12));
|
||||
|
||||
// Still a mystery to me: the exact meaning of the top two bits here. For now
|
||||
// just treat a 0 -> 1 transition of the MSB as a forced frame restart.
|
||||
if((value^line_parameter_control_) & value & 0x80) {
|
||||
// For now: just force this to be the final line of this mode block.
|
||||
// I'm unclear whether I should also reset the horizontal counter
|
||||
// (i.e. completely abandon current video phase).
|
||||
lines_remaining_ = 0xff;
|
||||
should_reload_line_parameters_ = true;
|
||||
}
|
||||
line_parameter_control_ = value & 0xc0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Nick::read() {
|
||||
return last_read_;
|
||||
}
|
||||
|
||||
Cycles Nick::get_time_until_z80_slot(Cycles after_period) const {
|
||||
// Place Z80 accesses in the first six cycles in each sixteen-cycle window.
|
||||
// That models video accesses as being the final ten. Which has the net effect
|
||||
// of responding to the line parameter table interrupt flag as soon as it's
|
||||
// loaded.
|
||||
|
||||
// Assumed below: the Z80 can start its final cycle anywhere in the first three
|
||||
// of the permitted six.
|
||||
const int offset = (horizontal_counter_ + after_period.as<int>()) & 15;
|
||||
if(offset < 3) {
|
||||
return 0;
|
||||
} else {
|
||||
return 16 - offset;
|
||||
}
|
||||
}
|
||||
|
||||
void Nick::run_for(Cycles duration) {
|
||||
constexpr int line_length = 912;
|
||||
|
||||
#define add_window(x) \
|
||||
line_data_pointer_[0] += is_sync_or_pixels_ * line_data_per_column_increments_[0] * (x); \
|
||||
line_data_pointer_[1] += is_sync_or_pixels_ * line_data_per_column_increments_[1] * (x); \
|
||||
window += x; \
|
||||
if(window != 57 && window == left_margin_) is_sync_or_pixels_ = true; \
|
||||
if(window != 57 && window == right_margin_) is_sync_or_pixels_ = false;
|
||||
|
||||
int clocks_remaining = duration.as<int>();
|
||||
while(clocks_remaining) {
|
||||
// Determine how many cycles are left this line.
|
||||
const int clocks_this_line = std::min(clocks_remaining, line_length - horizontal_counter_);
|
||||
|
||||
// Convert that into a [start/current] and end window.
|
||||
int window = horizontal_counter_ >> 4;
|
||||
const int end_window = (horizontal_counter_ + clocks_this_line) >> 4;
|
||||
|
||||
// Advance the line counters.
|
||||
clocks_remaining -= clocks_this_line;
|
||||
horizontal_counter_ = (horizontal_counter_ + clocks_this_line) % line_length;
|
||||
|
||||
// Do nothing if a window boundary isn't crossed.
|
||||
if(window == end_window) continue;
|
||||
|
||||
// HSYNC is signalled for four windows at the start of the line.
|
||||
// I currently believe this happens regardless of Vsync mode.
|
||||
if(!window) {
|
||||
set_output_type(OutputType::Sync);
|
||||
|
||||
// There's no increment to get to 0, it happens when the horizontal_counter_
|
||||
// is reset. So test for active bit effect manually.
|
||||
if(!left_margin_) is_sync_or_pixels_ = true;
|
||||
if(!right_margin_) is_sync_or_pixels_ = false;
|
||||
}
|
||||
|
||||
// Default to noting read.
|
||||
last_read_ = 0xff;
|
||||
|
||||
while(window < 4 && window < end_window) {
|
||||
if(should_reload_line_parameters_) {
|
||||
switch(window) {
|
||||
// First slot: line count, mode and interrupt flag.
|
||||
case 0:
|
||||
// Byte 0: lines remaining.
|
||||
lines_remaining_ = ram_[line_parameter_pointer_];
|
||||
|
||||
// Byte 1: current interrupt output plus graphics modes...
|
||||
last_read_ = ram_[line_parameter_pointer_ + 1];
|
||||
|
||||
// Set the new interrupt line output.
|
||||
interrupt_line_ = ram_[line_parameter_pointer_ + 1] & 0x80;
|
||||
|
||||
// Determine the mode and depth, and hence the column size.
|
||||
mode_ = Mode((ram_[line_parameter_pointer_ + 1] >> 1)&7);
|
||||
bpp_ = 1 << ((ram_[line_parameter_pointer_ + 1] >> 5)&3);
|
||||
switch(mode_) {
|
||||
default:
|
||||
case Mode::Pixel:
|
||||
column_size_ = 16 / bpp_;
|
||||
line_data_per_column_increments_[0] = 2;
|
||||
line_data_per_column_increments_[1] = 0;
|
||||
break;
|
||||
|
||||
case Mode::LPixel:
|
||||
case Mode::CH64:
|
||||
case Mode::CH128:
|
||||
case Mode::CH256:
|
||||
column_size_ = 8 / bpp_;
|
||||
line_data_per_column_increments_[0] = 1;
|
||||
line_data_per_column_increments_[1] = 0;
|
||||
break;
|
||||
|
||||
case Mode::Attr:
|
||||
column_size_ = 8;
|
||||
line_data_per_column_increments_[0] = 1;
|
||||
line_data_per_column_increments_[1] = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
vres_ = ram_[line_parameter_pointer_ + 1] & 0x10;
|
||||
reload_line_parameter_pointer_ = ram_[line_parameter_pointer_ + 1] & 0x01;
|
||||
break;
|
||||
|
||||
// Second slot: margins and ALT/IND bits.
|
||||
case 1:
|
||||
// Determine the margins.
|
||||
left_margin_ = ram_[line_parameter_pointer_ + 2] & 0x3f;
|
||||
right_margin_ = ram_[line_parameter_pointer_ + 3] & 0x3f;
|
||||
last_read_ = ram_[line_parameter_pointer_ + 3];
|
||||
|
||||
// Set up the alternative palettes,
|
||||
switch(mode_) {
|
||||
default:
|
||||
break;
|
||||
|
||||
// NB: LSBALT/MSBALT and ALTIND0/ALTIND1 appear to have opposite effects on palette selection.
|
||||
|
||||
case Mode::Pixel:
|
||||
case Mode::LPixel: {
|
||||
const uint8_t flags = ram_[line_parameter_pointer_ + 2];
|
||||
|
||||
// Use MSBALT and LSBALT to pick the alt_ind_palettes.
|
||||
//
|
||||
// LSBALT = b6 of params[2], if set => character codes with bit 6 set should use palette indices 4... instead of 0... .
|
||||
// MSBALT = b7 of params[2], if set => character codes with bit 7 set should use palette indices 2 and 3.
|
||||
two_colour_mask_ = 0xff &~ (((flags&0x80) >> 7) | ((flags&0x40) << 1));
|
||||
|
||||
alt_ind_palettes[0] = palette_;
|
||||
alt_ind_palettes[2] = alt_ind_palettes[0] + ((flags & 0x80) ? 2 : 0);
|
||||
|
||||
alt_ind_palettes[1] = alt_ind_palettes[0] + ((flags & 0x40) ? 4 : 0);
|
||||
alt_ind_palettes[3] = alt_ind_palettes[2] + ((flags & 0x40) ? 4 : 0);
|
||||
} break;
|
||||
|
||||
case Mode::CH64:
|
||||
case Mode::CH128:
|
||||
case Mode::CH256: {
|
||||
const uint8_t flags = ram_[line_parameter_pointer_ + 3];
|
||||
|
||||
// Use ALTIND0 and ALTIND1 to pick the alt_ind_palettes.
|
||||
//
|
||||
// ALTIND1 = b6 of params[3], if set => character codes with bit 7 set should use palette indices 2 and 3.
|
||||
// ALTIND0 = b7 of params[3], if set => character codes with bit 6 set should use palette indices 4... instead of 0... .
|
||||
alt_ind_palettes[0] = palette_;
|
||||
alt_ind_palettes[2] = alt_ind_palettes[0] + ((flags & 0x40) ? 2 : 0);
|
||||
|
||||
alt_ind_palettes[1] = alt_ind_palettes[0] + ((flags & 0x80) ? 4 : 0);
|
||||
alt_ind_palettes[3] = alt_ind_palettes[2] + ((flags & 0x80) ? 4 : 0);
|
||||
} break;
|
||||
}
|
||||
break;
|
||||
|
||||
// Third slot: Line data pointer 1.
|
||||
case 2:
|
||||
start_line_data_pointer_[0] = ram_[line_parameter_pointer_ + 4];
|
||||
start_line_data_pointer_[0] |= ram_[line_parameter_pointer_ + 5] << 8;
|
||||
|
||||
line_data_pointer_[0] = start_line_data_pointer_[0];
|
||||
last_read_ = ram_[line_parameter_pointer_ + 5];
|
||||
break;
|
||||
|
||||
// Fourth slot: Line data pointer 2.
|
||||
case 3:
|
||||
start_line_data_pointer_[1] = ram_[line_parameter_pointer_ + 6];
|
||||
start_line_data_pointer_[1] |= ram_[line_parameter_pointer_ + 7] << 8;
|
||||
|
||||
line_data_pointer_[1] = start_line_data_pointer_[1];
|
||||
last_read_ = ram_[line_parameter_pointer_ + 7];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++output_duration_;
|
||||
add_window(1);
|
||||
}
|
||||
if(window == 4) {
|
||||
if(mode_ == Mode::Vsync) {
|
||||
set_output_type(is_sync_or_pixels_ ? OutputType::Sync : OutputType::Blank);
|
||||
} else {
|
||||
set_output_type(OutputType::Blank);
|
||||
}
|
||||
}
|
||||
|
||||
// Deal with vsync mode out here.
|
||||
if(mode_ == Mode::Vsync) {
|
||||
if(window >= 4) {
|
||||
while(window < end_window) {
|
||||
// Skip straight to the next event.
|
||||
int next_event = end_window;
|
||||
if(window < left_margin_) next_event = std::min(next_event, left_margin_);
|
||||
if(window < right_margin_) next_event = std::min(next_event, right_margin_);
|
||||
|
||||
output_duration_ += next_event - window;
|
||||
add_window(next_event - window);
|
||||
set_output_type(is_sync_or_pixels_ ? OutputType::Sync : OutputType::Blank);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If present then the colour burst is output for the period from
|
||||
// the start of window 6 to the end of window 10.
|
||||
//
|
||||
// The first 8 palette entries also need to be fetched here.
|
||||
while(window < first_pixel_window_ && window < end_window) {
|
||||
if(window == 6) {
|
||||
set_output_type(OutputType::ColourBurst);
|
||||
}
|
||||
|
||||
if(should_reload_line_parameters_ && window < 8) {
|
||||
const int base = (window - 4) << 1;
|
||||
assert(base < 7);
|
||||
palette_[base] = mapped_colour(ram_[line_parameter_pointer_ + base + 8]);
|
||||
palette_[base + 1] = mapped_colour(ram_[line_parameter_pointer_ + base + 9]);
|
||||
last_read_ = ram_[line_parameter_pointer_ + base + 9];
|
||||
}
|
||||
|
||||
++output_duration_;
|
||||
add_window(1);
|
||||
}
|
||||
|
||||
if(window >= first_pixel_window_) {
|
||||
if(window == first_pixel_window_) {
|
||||
set_output_type(is_sync_or_pixels_ ? OutputType::Pixels : OutputType::Border);
|
||||
}
|
||||
|
||||
while(window < end_window) {
|
||||
int next_event = end_window;
|
||||
if(window < left_margin_) next_event = std::min(next_event, left_margin_);
|
||||
if(window < right_margin_) next_event = std::min(next_event, right_margin_);
|
||||
|
||||
if(is_sync_or_pixels_) {
|
||||
|
||||
#define DispatchBpp(func) \
|
||||
switch(bpp_) { \
|
||||
default: \
|
||||
case 1: func(1)(pixel_pointer_, output_duration); break; \
|
||||
case 2: func(2)(pixel_pointer_, output_duration); break; \
|
||||
case 4: func(4)(pixel_pointer_, output_duration); break; \
|
||||
case 8: func(8)(pixel_pointer_, output_duration); break; \
|
||||
}
|
||||
|
||||
#define pixel(x) output_pixel<x, false>
|
||||
#define lpixel(x) output_pixel<x, true>
|
||||
#define ch256(x) output_character<x, 8>
|
||||
#define ch128(x) output_character<x, 7>
|
||||
#define ch64(x) output_character<x, 6>
|
||||
#define attr(x) output_attributed<x>
|
||||
|
||||
int columns_remaining = next_event - window;
|
||||
while(columns_remaining) {
|
||||
if(!pixel_pointer_) {
|
||||
if(output_duration_) {
|
||||
set_output_type(OutputType::Pixels, true);
|
||||
}
|
||||
pixel_pointer_ = allocated_pointer_ = reinterpret_cast<uint16_t *>(crt_.begin_data(allocation_size));
|
||||
}
|
||||
|
||||
if(allocated_pointer_) {
|
||||
const int output_duration = std::min(columns_remaining, int(allocated_pointer_ + allocation_size - pixel_pointer_) / column_size_);
|
||||
|
||||
switch(mode_) {
|
||||
default:
|
||||
case Mode::Pixel: DispatchBpp(pixel); break;
|
||||
case Mode::LPixel: DispatchBpp(lpixel); break;
|
||||
case Mode::CH256: DispatchBpp(ch256); break;
|
||||
case Mode::CH128: DispatchBpp(ch128); break;
|
||||
case Mode::CH64: DispatchBpp(ch64); break;
|
||||
case Mode::Attr: DispatchBpp(attr); break;
|
||||
}
|
||||
|
||||
pixel_pointer_ += output_duration * column_size_;
|
||||
output_duration_ += output_duration;
|
||||
if(pixel_pointer_ - allocated_pointer_ == allocation_size) {
|
||||
set_output_type(OutputType::Pixels, true);
|
||||
}
|
||||
columns_remaining -= output_duration;
|
||||
add_window(output_duration);
|
||||
} else {
|
||||
output_duration_ += columns_remaining;
|
||||
add_window(columns_remaining);
|
||||
columns_remaining = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#undef attr
|
||||
#undef ch64
|
||||
#undef ch128
|
||||
#undef ch256
|
||||
#undef pixel
|
||||
#undef lpixel
|
||||
#undef DispatchBpp
|
||||
} else {
|
||||
output_duration_ += next_event - window;
|
||||
add_window(next_event - window);
|
||||
}
|
||||
|
||||
set_output_type(is_sync_or_pixels_ ? OutputType::Pixels : OutputType::Border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for end of line.
|
||||
if(!horizontal_counter_) {
|
||||
assert(window == 57);
|
||||
|
||||
++lines_remaining_;
|
||||
if(!lines_remaining_) {
|
||||
should_reload_line_parameters_ = true;
|
||||
|
||||
// Check for end-of-frame.
|
||||
if(reload_line_parameter_pointer_) {
|
||||
line_parameter_pointer_ = line_parameter_base_;
|
||||
} else {
|
||||
line_parameter_pointer_ += 16;
|
||||
}
|
||||
} else {
|
||||
should_reload_line_parameters_ = false;
|
||||
}
|
||||
|
||||
// Deal with VRES and other address reloading, dependant upon mode.
|
||||
switch(mode_) {
|
||||
default: break;
|
||||
case Mode::CH64:
|
||||
case Mode::CH128:
|
||||
case Mode::CH256:
|
||||
line_data_pointer_[0] = start_line_data_pointer_[0];
|
||||
++line_data_pointer_[1];
|
||||
break;
|
||||
|
||||
case Mode::Pixel:
|
||||
case Mode::LPixel:
|
||||
case Mode::Attr:
|
||||
// Reload the pixel or attribute address if VRES is clear.
|
||||
if(!vres_) {
|
||||
line_data_pointer_[0] = start_line_data_pointer_[0];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#undef add_window
|
||||
|
||||
}
|
||||
|
||||
void Nick::set_output_type(OutputType type, bool force_flush) {
|
||||
if(type == output_type_ && !force_flush) {
|
||||
return;
|
||||
}
|
||||
if(output_duration_) {
|
||||
switch(output_type_) {
|
||||
case OutputType::Border: {
|
||||
uint16_t *const colour_pointer = reinterpret_cast<uint16_t *>(crt_.begin_data(1));
|
||||
if(colour_pointer) *colour_pointer = border_colour_;
|
||||
crt_.output_level(output_duration_*16);
|
||||
} break;
|
||||
|
||||
case OutputType::Pixels: {
|
||||
crt_.output_data(output_duration_*16, size_t(output_duration_*column_size_));
|
||||
pixel_pointer_ = nullptr;
|
||||
allocated_pointer_ = nullptr;
|
||||
} break;
|
||||
|
||||
case OutputType::Sync: crt_.output_sync(output_duration_*16); break;
|
||||
case OutputType::Blank: crt_.output_blank(output_duration_*16); break;
|
||||
case OutputType::ColourBurst: crt_.output_colour_burst(output_duration_*16, 0); break;
|
||||
}
|
||||
}
|
||||
|
||||
output_duration_ = 0;
|
||||
output_type_ = type;
|
||||
}
|
||||
|
||||
// MARK: - Sequence points.
|
||||
|
||||
Cycles Nick::get_next_sequence_point() const {
|
||||
constexpr int load_point = 16; // i.e. 16 cycles after the start of the line, the
|
||||
// interrupt line may change. That is, after the
|
||||
// second byte of the mode line has been read.
|
||||
|
||||
// Any mode line may cause a change in the interrupt output, so as a first blush
|
||||
// just always report the time until the end of the mode line.
|
||||
if(lines_remaining_ || horizontal_counter_ >= load_point) {
|
||||
return Cycles(load_point + (912 - horizontal_counter_) + (0xff - lines_remaining_) * 912);
|
||||
} else {
|
||||
return Cycles(load_point - horizontal_counter_);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - CRT passthroughs.
|
||||
|
||||
void Nick::set_scan_target(Outputs::Display::ScanTarget *scan_target) {
|
||||
crt_.set_scan_target(scan_target);
|
||||
}
|
||||
|
||||
Outputs::Display::ScanStatus Nick::get_scaled_scan_status() const {
|
||||
return crt_.get_scaled_scan_status();
|
||||
}
|
||||
|
||||
void Nick::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
first_pixel_window_ = display_type == Outputs::Display::DisplayType::RGB ? 8 : 10;
|
||||
crt_.set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType Nick::get_display_type() const {
|
||||
return crt_.get_display_type();
|
||||
}
|
||||
|
||||
// MARK: - Specific pixel outputters.
|
||||
|
||||
#define output1bpp(x) \
|
||||
target[0] = palette[(x & 0x80) >> 7]; \
|
||||
target[1] = palette[(x & 0x40) >> 6]; \
|
||||
target[2] = palette[(x & 0x20) >> 5]; \
|
||||
target[3] = palette[(x & 0x10) >> 4]; \
|
||||
target[4] = palette[(x & 0x08) >> 3]; \
|
||||
target[5] = palette[(x & 0x04) >> 2]; \
|
||||
target[6] = palette[(x & 0x02) >> 1]; \
|
||||
target[7] = palette[(x & 0x01) >> 0]; \
|
||||
target += 8
|
||||
|
||||
#define output2bpp(x) \
|
||||
target[0] = palette_[((x & 0x80) >> 7) | ((x & 0x08) >> 2)]; \
|
||||
target[1] = palette_[((x & 0x40) >> 6) | ((x & 0x04) >> 1)]; \
|
||||
target[2] = palette_[((x & 0x20) >> 5) | ((x & 0x02) >> 0)]; \
|
||||
target[3] = palette_[((x & 0x10) >> 4) | ((x & 0x01) << 1)]; \
|
||||
target += 4
|
||||
|
||||
#define output4bpp(x) \
|
||||
target[0] = palette_[((x & 0x02) << 2) | ((x & 0x20) >> 3) | ((x & 0x08) >> 2) | ((x & 0x80) >> 7)]; \
|
||||
target[1] = palette_[((x & 0x01) << 3) | ((x & 0x10) >> 2) | ((x & 0x04) >> 1) | ((x & 0x40) >> 6)]; \
|
||||
target += 2
|
||||
|
||||
#define output8bpp(x) \
|
||||
target[0] = mapped_colour(x); \
|
||||
++target
|
||||
|
||||
template <int bpp, bool is_lpixel> void Nick::output_pixel(uint16_t *target, int columns) const {
|
||||
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
|
||||
|
||||
int index = 0;
|
||||
for(int c = 0; c < columns; c++) {
|
||||
uint8_t pixels[2] = {
|
||||
ram_[(line_data_pointer_[0] + index) & 0xffff],
|
||||
ram_[(line_data_pointer_[0] + index + 1) & 0xffff]
|
||||
};
|
||||
index += is_lpixel ? 1 : 2;
|
||||
last_read_ = pixels[1];
|
||||
|
||||
switch(bpp) {
|
||||
default:
|
||||
case 1: {
|
||||
const uint16_t *palette = alt_ind_palettes[((pixels[0] >> 6) & 0x02) | (pixels[0]&1)];
|
||||
pixels[0] &= two_colour_mask_;
|
||||
output1bpp(pixels[0]);
|
||||
|
||||
if constexpr (!is_lpixel) {
|
||||
palette = alt_ind_palettes[((pixels[1] >> 6) & 0x02) | (pixels[1]&1)];
|
||||
pixels[1] &= two_colour_mask_;
|
||||
output1bpp(pixels[1]);
|
||||
}
|
||||
} break;
|
||||
|
||||
case 2:
|
||||
output2bpp(pixels[0]);
|
||||
if constexpr (!is_lpixel) {
|
||||
output2bpp(pixels[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
output4bpp(pixels[0]);
|
||||
if constexpr (!is_lpixel) {
|
||||
output4bpp(pixels[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
output8bpp(pixels[0]);
|
||||
if constexpr (!is_lpixel) {
|
||||
output8bpp(pixels[1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <int bpp, int index_bits> void Nick::output_character(uint16_t *target, int columns) const {
|
||||
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
|
||||
|
||||
for(int c = 0; c < columns; c++) {
|
||||
const uint8_t character = ram_[(line_data_pointer_[0] + c) & 0xffff];
|
||||
const uint8_t pixels = ram_[(
|
||||
(line_data_pointer_[1] << index_bits) +
|
||||
(character & ((1 << index_bits) - 1))
|
||||
) & 0xffff];
|
||||
last_read_ = pixels;
|
||||
|
||||
switch(bpp) {
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
|
||||
case 1: {
|
||||
// This applies ALTIND0 and ALTIND1.
|
||||
const uint16_t *palette = alt_ind_palettes[character >> 6];
|
||||
output1bpp(pixels);
|
||||
} break;
|
||||
|
||||
case 2: output2bpp(pixels); break;
|
||||
case 4: output4bpp(pixels); break;
|
||||
case 8: output8bpp(pixels); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <int bpp> void Nick::output_attributed(uint16_t *target, int columns) const {
|
||||
static_assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
|
||||
|
||||
for(int c = 0; c < columns; c++) {
|
||||
const uint8_t pixels = ram_[(line_data_pointer_[1] + c) & 0xffff];
|
||||
const uint8_t attributes = ram_[(line_data_pointer_[0] + c) & 0xffff];
|
||||
last_read_ = pixels;
|
||||
|
||||
const uint16_t palette[2] = {
|
||||
palette_[attributes >> 4], palette_[attributes & 0x0f]
|
||||
};
|
||||
output1bpp(pixels);
|
||||
}
|
||||
}
|
||||
|
||||
#undef output1bpp
|
||||
#undef output2bpp
|
||||
#undef output4bpp
|
||||
#undef output8bpp
|
||||
129
Machines/Enterprise/Nick.hpp
Normal file
129
Machines/Enterprise/Nick.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
//
|
||||
// Nick.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 14/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef Nick_hpp
|
||||
#define Nick_hpp
|
||||
|
||||
#include <cstdint>
|
||||
#include "../../ClockReceiver/ClockReceiver.hpp"
|
||||
#include "../../Outputs/CRT/CRT.hpp"
|
||||
|
||||
namespace Enterprise {
|
||||
|
||||
class Nick {
|
||||
public:
|
||||
Nick(const uint8_t *ram);
|
||||
|
||||
/// Writes to a Nick register; only the low two bits are decoded.
|
||||
void write(uint16_t address, uint8_t value);
|
||||
|
||||
/// Reads from the Nick range. Nobody seems to be completely clear what
|
||||
/// this should return; I've set it up to return the last fetched video or mode
|
||||
/// line byte during periods when those things are being fetched, 0xff at all
|
||||
/// other times. Including during refresh, since I don't know what addresses
|
||||
/// are generated then.
|
||||
///
|
||||
/// This likely isn't accurate, but is the most accurate guess I could make.
|
||||
uint8_t read();
|
||||
|
||||
void run_for(Cycles);
|
||||
Cycles get_time_until_z80_slot(Cycles after_period) const;
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *scan_target);
|
||||
Outputs::Display::ScanStatus get_scaled_scan_status() const;
|
||||
|
||||
/// @returns The amount of time until the next potential change in interrupt output.
|
||||
Cycles get_next_sequence_point() const;
|
||||
|
||||
/*!
|
||||
@returns The current state of the interrupt line — @c true for active;
|
||||
@c false for inactive.
|
||||
*/
|
||||
inline bool get_interrupt_line() const {
|
||||
return interrupt_line_;
|
||||
}
|
||||
|
||||
/// Sets the type of output.
|
||||
void set_display_type(Outputs::Display::DisplayType);
|
||||
|
||||
/// Gets the type of output.
|
||||
Outputs::Display::DisplayType get_display_type() const;
|
||||
|
||||
private:
|
||||
Outputs::CRT::CRT crt_;
|
||||
const uint8_t *const ram_;
|
||||
|
||||
// CPU-provided state.
|
||||
uint8_t line_parameter_control_ = 0xc0;
|
||||
uint16_t line_parameter_base_ = 0x0000;
|
||||
uint16_t border_colour_ = 0;
|
||||
|
||||
// Ephemerals, related to current video position.
|
||||
int horizontal_counter_ = 0;
|
||||
uint16_t line_parameter_pointer_ = 0x0000;
|
||||
bool should_reload_line_parameters_ = true;
|
||||
uint16_t line_data_pointer_[2];
|
||||
uint16_t start_line_data_pointer_[2];
|
||||
mutable uint8_t last_read_ = 0xff;
|
||||
|
||||
// Current mode line parameters.
|
||||
uint8_t lines_remaining_ = 0x00;
|
||||
uint8_t two_colour_mask_ = 0xff;
|
||||
int left_margin_ = 0, right_margin_ = 0;
|
||||
const uint16_t *alt_ind_palettes[4];
|
||||
enum class Mode {
|
||||
Vsync,
|
||||
Pixel,
|
||||
Attr,
|
||||
CH256,
|
||||
CH128,
|
||||
CH64,
|
||||
Unused,
|
||||
LPixel,
|
||||
} mode_ = Mode::Vsync;
|
||||
bool is_sync_or_pixels_ = false;
|
||||
int bpp_ = 0;
|
||||
int column_size_ = 0;
|
||||
bool interrupt_line_ = true;
|
||||
int line_data_per_column_increments_[2] = {0, 0};
|
||||
bool vres_ = false;
|
||||
bool reload_line_parameter_pointer_ = false;
|
||||
|
||||
// An accumulator for border output regions.
|
||||
int border_duration_ = 0;
|
||||
|
||||
// The destination for new pixels.
|
||||
static constexpr int allocation_size = 336;
|
||||
static_assert((allocation_size % 16) == 0, "Allocation size must be a multiple of 16");
|
||||
uint16_t *pixel_pointer_ = nullptr, *allocated_pointer_ = nullptr;
|
||||
|
||||
// Output transitions.
|
||||
enum class OutputType {
|
||||
Sync, Blank, Pixels, Border, ColourBurst
|
||||
};
|
||||
void set_output_type(OutputType, bool force_flush = false);
|
||||
int output_duration_ = 0;
|
||||
OutputType output_type_ = OutputType::Sync;
|
||||
|
||||
// Current palette.
|
||||
uint16_t palette_[16]{};
|
||||
|
||||
// The first column with pixels on it; will be either 8 or 10 depending
|
||||
// on whether the colour burst is meaningful to the current display type.
|
||||
int first_pixel_window_ = 10;
|
||||
|
||||
// Specific outputters.
|
||||
template <int bpp, bool is_lpixel> void output_pixel(uint16_t *target, int columns) const;
|
||||
template <int bpp, int index_bits> void output_character(uint16_t *target, int columns) const;
|
||||
template <int bpp> void output_attributed(uint16_t *target, int columns) const;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif /* Nick_hpp */
|
||||
@@ -169,19 +169,22 @@ class ConcreteMachine:
|
||||
|
||||
// Install the proper TV standard and select an ideal BIOS name.
|
||||
const std::string machine_name = "MSX";
|
||||
std::vector<ROMMachine::ROM> required_roms = {
|
||||
{machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3}
|
||||
};
|
||||
ROM::Request bios_request = ROM::Request(ROM::Name::MSXGenericBIOS);
|
||||
// std::vector<ROMMachine::ROM> required_roms = {
|
||||
// {machine_name, "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u}
|
||||
// };
|
||||
|
||||
bool is_ntsc = true;
|
||||
uint8_t character_generator = 1; /* 0 = Japan, 1 = USA, etc, 2 = USSR */
|
||||
uint8_t date_format = 1; /* 0 = Y/M/D, 1 = M/D/Y, 2 = D/M/Y */
|
||||
uint8_t keyboard = 1; /* 0 = Japan, 1 = USA, 2 = France, 3 = UK, 4 = Germany, 5 = USSR, 6 = Spain */
|
||||
ROM::Name regional_bios_name;
|
||||
|
||||
// TODO: CRCs below are incomplete, at best.
|
||||
switch(target.region) {
|
||||
default:
|
||||
case Target::Region::Japan:
|
||||
required_roms.emplace_back(machine_name, "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390);
|
||||
regional_bios_name = ROM::Name::MSXJapaneseBIOS;
|
||||
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
|
||||
|
||||
is_ntsc = true;
|
||||
@@ -189,7 +192,7 @@ class ConcreteMachine:
|
||||
date_format = 0;
|
||||
break;
|
||||
case Target::Region::USA:
|
||||
required_roms.emplace_back(machine_name, "an American MSX BIOS", "msx-american.rom", 32*1024, 0);
|
||||
regional_bios_name = ROM::Name::MSXAmericanBIOS;
|
||||
vdp_->set_tv_standard(TI::TMS::TVStandard::NTSC);
|
||||
|
||||
is_ntsc = true;
|
||||
@@ -197,7 +200,7 @@ class ConcreteMachine:
|
||||
date_format = 1;
|
||||
break;
|
||||
case Target::Region::Europe:
|
||||
required_roms.emplace_back(machine_name, "a European MSX BIOS", "msx-european.rom", 32*1024, 0);
|
||||
regional_bios_name = ROM::Name::MSXEuropeanBIOS;
|
||||
vdp_->set_tv_standard(TI::TMS::TVStandard::PAL);
|
||||
|
||||
is_ntsc = false;
|
||||
@@ -205,27 +208,30 @@ class ConcreteMachine:
|
||||
date_format = 2;
|
||||
break;
|
||||
}
|
||||
bios_request = bios_request || ROM::Request(regional_bios_name);
|
||||
|
||||
// Fetch the necessary ROMs; try the region-specific ROM first,
|
||||
// but failing that fall back on patching the main one.
|
||||
size_t disk_index = 0;
|
||||
ROM::Request request;
|
||||
if(target.has_disk_drive) {
|
||||
disk_index = required_roms.size();
|
||||
required_roms.emplace_back(machine_name, "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61df);
|
||||
request = ROM::Request(ROM::Name::MSXDOS) && bios_request;
|
||||
} else {
|
||||
request = bios_request;
|
||||
}
|
||||
const auto roms = rom_fetcher(required_roms);
|
||||
|
||||
if((!roms[0] && !roms[1]) || (target.has_disk_drive && !roms[2])) {
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
// Figure out which BIOS to use, either a specific one or the generic
|
||||
// one appropriately patched.
|
||||
if(roms[1]) {
|
||||
memory_slots_[0].source = std::move(*roms[1]);
|
||||
const auto regional_bios = roms.find(regional_bios_name);
|
||||
if(regional_bios != roms.end()) {
|
||||
memory_slots_[0].source = std::move(regional_bios->second);
|
||||
memory_slots_[0].source.resize(32768);
|
||||
} else {
|
||||
memory_slots_[0].source = std::move(*roms[0]);
|
||||
memory_slots_[0].source = std::move(roms.find(ROM::Name::MSXGenericBIOS)->second);
|
||||
memory_slots_[0].source.resize(32768);
|
||||
|
||||
memory_slots_[0].source[0x2b] = uint8_t(
|
||||
@@ -252,7 +258,7 @@ class ConcreteMachine:
|
||||
// Add a disk cartridge if any disks were supplied.
|
||||
if(target.has_disk_drive) {
|
||||
memory_slots_[2].set_handler(new DiskROM(memory_slots_[2].source));
|
||||
memory_slots_[2].source = std::move(*roms[disk_index]);
|
||||
memory_slots_[2].source = std::move(roms.find(ROM::Name::MSXDOS)->second);
|
||||
memory_slots_[2].source.resize(16384);
|
||||
|
||||
map(2, 0, 0x4000, 0x2000);
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "MediaTarget.hpp"
|
||||
#include "MouseMachine.hpp"
|
||||
#include "ScanProducer.hpp"
|
||||
#include "StateProducer.hpp"
|
||||
#include "TimedMachine.hpp"
|
||||
|
||||
#endif /* MachineTypes_h */
|
||||
|
||||
@@ -141,23 +141,20 @@ class ConcreteMachine:
|
||||
// 0072ed54 = US/European BIOS 1.3
|
||||
// 48d44a13 = Japanese BIOS 2.1
|
||||
const bool is_japanese = target.region == Target::Region::Japan;
|
||||
const auto roms = rom_fetcher(
|
||||
{ {"MasterSystem",
|
||||
is_japanese ? "the Japanese Master System BIOS" : "the European/US Master System BIOS",
|
||||
is_japanese ? "japanese-bios.sms" : "bios.sms",
|
||||
8*1024,
|
||||
{ is_japanese ? 0x48d44a13u : 0x0072ed54u }
|
||||
} }
|
||||
);
|
||||
if(!roms[0]) {
|
||||
const ROM::Name bios_name = is_japanese ? ROM::Name::MasterSystemJapaneseBIOS : ROM::Name::MasterSystemWesternBIOS;
|
||||
ROM::Request request(bios_name, true);
|
||||
auto roms = rom_fetcher(request);
|
||||
request.validate(roms);
|
||||
|
||||
const auto rom = roms.find(bios_name);
|
||||
if(rom == roms.end()) {
|
||||
// No BIOS found; attempt to boot as though it has already disabled itself.
|
||||
has_bios_ = false;
|
||||
memory_control_ |= 0x08;
|
||||
std::cerr << "No BIOS found; attempting to start cartridge directly" << std::endl;
|
||||
} else {
|
||||
has_bios_ = true;
|
||||
roms[0]->resize(8*1024);
|
||||
memcpy(&bios_, roms[0]->data(), roms[0]->size());
|
||||
memcpy(&bios_, rom->second.data(), std::min(sizeof(bios_), rom->second.size()));
|
||||
}
|
||||
page_cartridge();
|
||||
|
||||
|
||||
@@ -127,5 +127,5 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
#undef SHIFT
|
||||
#undef X
|
||||
|
||||
return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character);
|
||||
return table_lookup_sequence_for_character(key_sequences, character);
|
||||
}
|
||||
|
||||
@@ -40,6 +40,45 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
/*!
|
||||
Provides an Altai-style joystick.
|
||||
*/
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
Input(Input::Fire)
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &digital_input, bool is_active) final {
|
||||
#define APPLY(b) if(is_active) state_ &= ~b; else state_ |= b;
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
case Input::Right: APPLY(0x02); break;
|
||||
case Input::Left: APPLY(0x01); break;
|
||||
case Input::Down: APPLY(0x08); break;
|
||||
case Input::Up: APPLY(0x10); break;
|
||||
case Input::Fire: APPLY(0x20); break;
|
||||
}
|
||||
#undef APPLY
|
||||
}
|
||||
|
||||
uint8_t get_state() {
|
||||
return state_;
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t state_ = 0xff;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace Oric {
|
||||
|
||||
using DiskInterface = Analyser::Static::Oric::Target::DiskInterface;
|
||||
@@ -140,7 +179,12 @@ class TapePlayer: public Storage::Tape::BinaryTapePlayer {
|
||||
class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
public:
|
||||
VIAPortHandler(Concurrency::DeferringAsyncTaskQueue &audio_queue, AY &ay8910, Speaker &speaker, TapePlayer &tape_player, Keyboard &keyboard) :
|
||||
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard) {}
|
||||
audio_queue_(audio_queue), ay8910_(ay8910), speaker_(speaker), tape_player_(tape_player), keyboard_(keyboard)
|
||||
{
|
||||
// Attach a couple of joysticks.
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
}
|
||||
|
||||
/*!
|
||||
Reponds to the 6522's control line output change signal; on an Oric A2 is connected to
|
||||
@@ -165,6 +209,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
} else {
|
||||
update_ay();
|
||||
ay8910_.set_data_input(value);
|
||||
porta_output_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,7 +221,10 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
uint8_t column = ay8910_.get_port_output(false) ^ 0xff;
|
||||
return keyboard_.query_column(column) ? 0x08 : 0x00;
|
||||
} else {
|
||||
return ay8910_.get_data_output();
|
||||
uint8_t result = ay8910_.get_data_output();
|
||||
if(porta_output_ & 0x40) result &= static_cast<Joystick *>(joysticks_[0].get())->get_state();
|
||||
if(porta_output_ & 0x80) result &= static_cast<Joystick *>(joysticks_[1].get())->get_state();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,12 +241,17 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
audio_queue_.perform();
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
return joysticks_;
|
||||
}
|
||||
|
||||
private:
|
||||
void update_ay() {
|
||||
speaker_.run_for(audio_queue_, cycles_since_ay_update_.flush<Cycles>());
|
||||
}
|
||||
bool ay_bdir_ = false;
|
||||
bool ay_bc1_ = false;
|
||||
uint8_t porta_output_ = 0xff;
|
||||
HalfCycles cycles_since_ay_update_;
|
||||
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
@@ -206,12 +259,15 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
|
||||
Speaker &speaker_;
|
||||
TapePlayer &tape_player_;
|
||||
Keyboard &keyboard_;
|
||||
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
};
|
||||
|
||||
template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS6502Esque::Type processor_type> class ConcreteMachine:
|
||||
public MachineTypes::TimedMachine,
|
||||
public MachineTypes::ScanProducer,
|
||||
public MachineTypes::AudioProducer,
|
||||
public MachineTypes::JoystickMachine,
|
||||
public MachineTypes::MediaTarget,
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
public Configurable::Device,
|
||||
@@ -248,73 +304,64 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
|
||||
ram_[c] |= 0x40;
|
||||
}
|
||||
|
||||
const std::string machine_name = "Oric";
|
||||
std::vector<ROMMachine::ROM> rom_names = { {machine_name, "the Oric colour ROM", "colour.rom", 128, 0xd50fca65} };
|
||||
::ROM::Request request = ::ROM::Request(::ROM::Name::OricColourROM, true);
|
||||
::ROM::Name basic;
|
||||
switch(target.rom) {
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC10:
|
||||
rom_names.emplace_back(machine_name, "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4);
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC11:
|
||||
rom_names.emplace_back(machine_name, "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92bef);
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::ROM::Pravetz:
|
||||
rom_names.emplace_back(machine_name, "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502);
|
||||
break;
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC10: basic = ::ROM::Name::OricBASIC10; break;
|
||||
default:
|
||||
case Analyser::Static::Oric::Target::ROM::BASIC11: basic = ::ROM::Name::OricBASIC11; break;
|
||||
case Analyser::Static::Oric::Target::ROM::Pravetz: basic = ::ROM::Name::OricPravetzBASIC; break;
|
||||
}
|
||||
size_t diskii_state_machine_index = 0;
|
||||
request = request && ::ROM::Request(basic);
|
||||
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::BD500:
|
||||
rom_names.emplace_back(machine_name, "the Oric Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34);
|
||||
request = request && ::ROM::Request(::ROM::Name::OricByteDrive500);
|
||||
break;
|
||||
case DiskInterface::Jasmin:
|
||||
rom_names.emplace_back(machine_name, "the Oric Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89);
|
||||
request = request && ::ROM::Request(::ROM::Name::OricJasmin);
|
||||
break;
|
||||
case DiskInterface::Microdisc:
|
||||
rom_names.emplace_back(machine_name, "the Oric Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9c);
|
||||
request = request && ::ROM::Request(::ROM::Name::OricMicrodisc);
|
||||
break;
|
||||
case DiskInterface::Pravetz:
|
||||
rom_names.emplace_back(machine_name, "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06);
|
||||
// These ROM details are coupled with those in the DiskIICard.
|
||||
diskii_state_machine_index = rom_names.size();
|
||||
rom_names.push_back({"DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, { 0x9796a238, 0xb72a2c70 }});
|
||||
request = request && ::ROM::Request(::ROM::Name::Oric8DOSBoot) && ::ROM::Request(::ROM::Name::DiskIIStateMachine16Sector);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto roms = rom_fetcher(rom_names);
|
||||
|
||||
for(std::size_t index = 0; index < roms.size(); ++index) {
|
||||
if(!roms[index]) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
video_->set_colour_rom(*roms[0]);
|
||||
rom_ = std::move(*roms[1]);
|
||||
// The colour ROM is optional; an alternative composite encoding can be used if
|
||||
// it is absent.
|
||||
const auto colour_rom = roms.find(::ROM::Name::OricColourROM);
|
||||
if(colour_rom != roms.end()) {
|
||||
video_->set_colour_rom(colour_rom->second);
|
||||
}
|
||||
rom_ = std::move(roms.find(basic)->second);
|
||||
|
||||
switch(disk_interface) {
|
||||
default: break;
|
||||
case DiskInterface::BD500:
|
||||
disk_rom_ = std::move(*roms[2]);
|
||||
disk_rom_.resize(8192);
|
||||
disk_rom_ = std::move(roms.find(::ROM::Name::OricByteDrive500)->second);
|
||||
break;
|
||||
case DiskInterface::Jasmin:
|
||||
disk_rom_ = std::move(*roms[2]);
|
||||
disk_rom_.resize(2048);
|
||||
disk_rom_ = std::move(roms.find(::ROM::Name::OricJasmin)->second);
|
||||
break;
|
||||
case DiskInterface::Microdisc:
|
||||
disk_rom_ = std::move(*roms[2]);
|
||||
disk_rom_.resize(8192);
|
||||
disk_rom_ = std::move(roms.find(::ROM::Name::OricMicrodisc)->second);
|
||||
break;
|
||||
case DiskInterface::Pravetz: {
|
||||
pravetz_rom_ = std::move(*roms[2]);
|
||||
pravetz_rom_ = std::move(roms.find(::ROM::Name::Oric8DOSBoot)->second);
|
||||
pravetz_rom_.resize(512);
|
||||
|
||||
diskii_->set_state_machine(*roms[diskii_state_machine_index]);
|
||||
diskii_->set_state_machine(roms.find(::ROM::Name::DiskIIStateMachine16Sector)->second);
|
||||
} break;
|
||||
}
|
||||
|
||||
rom_.resize(16384);
|
||||
paged_rom_ = rom_.data();
|
||||
|
||||
switch(target.disk_interface) {
|
||||
@@ -731,8 +778,13 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface, CPU::MOS
|
||||
}
|
||||
}
|
||||
|
||||
// MARK - typing
|
||||
// MARK: - typing
|
||||
std::unique_ptr<Utility::StringSerialiser> string_serialiser_;
|
||||
|
||||
// MARK: - Joysticks
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return via_port_handler_.get_joysticks();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ void VideoOutput::set_display_type(Outputs::Display::DisplayType display_type) {
|
||||
|
||||
#ifdef SUPPLY_COMPOSITE
|
||||
const auto data_type =
|
||||
(display_type == Outputs::Display::DisplayType::RGB) ?
|
||||
(!has_colour_rom_ || display_type == Outputs::Display::DisplayType::RGB) ?
|
||||
Outputs::Display::InputDataType::Red1Green1Blue1 :
|
||||
Outputs::Display::InputDataType::PhaseLinkedLuminance8;
|
||||
#else
|
||||
@@ -80,6 +80,7 @@ Outputs::Display::ScanStatus VideoOutput::get_scaled_scan_status() const {
|
||||
}
|
||||
|
||||
void VideoOutput::set_colour_rom(const std::vector<uint8_t> &rom) {
|
||||
has_colour_rom_ = true;
|
||||
for(std::size_t c = 0; c < 8; c++) {
|
||||
colour_forms_[c] = 0;
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class VideoOutput {
|
||||
Outputs::CRT::CRT crt_;
|
||||
Outputs::CRT::CRTFrequencyMismatchWarner<VideoOutput> frequency_mismatch_warner_;
|
||||
bool crt_is_60Hz_ = false;
|
||||
bool has_colour_rom_ = false;
|
||||
|
||||
void update_crt_frequency();
|
||||
|
||||
|
||||
@@ -10,38 +10,16 @@
|
||||
#define ROMMachine_hpp
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Utility/ROMCatalogue.hpp"
|
||||
|
||||
namespace ROMMachine {
|
||||
|
||||
/*!
|
||||
Describes a ROM image; this term is used in this emulator strictly in the sense of firmware —
|
||||
system software that is an inherent part of a machine.
|
||||
*/
|
||||
struct ROM {
|
||||
/// The machine with which this ROM is associated, in a form that is safe for using as
|
||||
/// part of a file name.
|
||||
std::string machine_name;
|
||||
/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket
|
||||
/// clause, etc, e.g. "the Electron MOS 1.0".
|
||||
std::string descriptive_name;
|
||||
/// An idiomatic file name for this ROM, e.g. "os10.rom".
|
||||
std::string file_name;
|
||||
/// The expected size of this ROM in bytes, e.g. 32768.
|
||||
size_t size = 0;
|
||||
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
|
||||
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
|
||||
/// to exclude ROMs where the user's intent is otherwise clear.
|
||||
std::vector<uint32_t> crc32s;
|
||||
|
||||
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, uint32_t crc32) :
|
||||
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s({crc32}) {}
|
||||
ROM(std::string machine_name, std::string descriptive_name, std::string file_name, size_t size, std::initializer_list<uint32_t> crc32s) :
|
||||
machine_name(machine_name), descriptive_name(descriptive_name), file_name(file_name), size(size), crc32s(crc32s) {}
|
||||
};
|
||||
|
||||
/*!
|
||||
Defines the signature for a function that must be supplied by the host environment in order to give machines
|
||||
a route for fetching any system ROMs they might need.
|
||||
@@ -50,7 +28,7 @@ struct ROM {
|
||||
return a vector of unique_ptrs that either contain the contents of the ROM from @c names that corresponds by
|
||||
index, or else are @c nullptr.
|
||||
*/
|
||||
typedef std::function<std::vector<std::unique_ptr<std::vector<uint8_t>>>(const std::vector<ROM> &roms)> ROMFetcher;
|
||||
typedef std::function<ROM::Map(const ROM::Request &request)> ROMFetcher;
|
||||
|
||||
enum class Error {
|
||||
MissingROMs
|
||||
|
||||
@@ -275,13 +275,14 @@ const uint16_t *CharacterMapper::sequence_for_character(char character) const {
|
||||
|
||||
switch(machine_) {
|
||||
case Machine::ZX80:
|
||||
return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character);
|
||||
return table_lookup_sequence_for_character(zx80_key_sequences, character);
|
||||
|
||||
case Machine::ZX81:
|
||||
return table_lookup_sequence_for_character(zx81_key_sequences, sizeof(zx81_key_sequences), character);
|
||||
return table_lookup_sequence_for_character(zx81_key_sequences, character);
|
||||
|
||||
default:
|
||||
case Machine::ZXSpectrum:
|
||||
return table_lookup_sequence_for_character(spectrum_key_sequences, sizeof(zx81_key_sequences), character);
|
||||
return table_lookup_sequence_for_character(spectrum_key_sequences, character);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,15 +74,13 @@ template<bool is_zx81> class ConcreteMachine:
|
||||
speaker_.set_input_rate(float(ZX8081ClockRate) / 2.0f);
|
||||
|
||||
const bool use_zx81_rom = target.is_ZX81 || target.ZX80_uses_ZX81_ROM;
|
||||
const auto roms =
|
||||
use_zx81_rom ?
|
||||
rom_fetcher({ {"ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6eb} }) :
|
||||
rom_fetcher({ {"ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597} });
|
||||
|
||||
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
|
||||
|
||||
rom_ = std::move(*roms[0]);
|
||||
rom_.resize(use_zx81_rom ? 8192 : 4096);
|
||||
const ROM::Name rom_name = use_zx81_rom ? ROM::Name::ZX81 : ROM::Name::ZX80;
|
||||
const ROM::Request request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
rom_ = std::move(roms.find(rom_name)->second);
|
||||
|
||||
rom_mask_ = uint16_t(rom_.size() - 1);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ class Machine {
|
||||
class Options: public Reflection::StructImpl<Options>, public Configurable::QuickloadOption<Options> {
|
||||
friend Configurable::QuickloadOption<Options>;
|
||||
public:
|
||||
bool automatic_tape_motor_control;
|
||||
bool automatic_tape_motor_control = true;
|
||||
|
||||
Options(Configurable::OptionsType type):
|
||||
Configurable::QuickloadOption<Options>(type == Configurable::OptionsType::UserFriendly),
|
||||
|
||||
53
Machines/Sinclair/ZXSpectrum/State.hpp
Normal file
53
Machines/Sinclair/ZXSpectrum/State.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// State.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 25/04/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef State_hpp
|
||||
#define State_hpp
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
#include "../../../Processors/Z80/State/State.hpp"
|
||||
|
||||
#include "Video.hpp"
|
||||
#include "../../../Components/AY38910/AY38910.hpp"
|
||||
|
||||
namespace Sinclair {
|
||||
namespace ZXSpectrum {
|
||||
|
||||
|
||||
struct State: public Reflection::StructImpl<State> {
|
||||
CPU::Z80::State z80;
|
||||
Video::State video;
|
||||
|
||||
// In 16kb or 48kb mode, RAM will be 16kb or 48kb and represent
|
||||
// memory in standard linear order. In 128kb mode, RAM will be
|
||||
// 128kb with the first 16kb representing bank 0, the next bank 1, etc.
|
||||
std::vector<uint8_t> ram;
|
||||
|
||||
// Meaningful for 128kb machines only.
|
||||
uint8_t last_7ffd = 0;
|
||||
GI::AY38910::State ay;
|
||||
|
||||
// Meaningful for the +2a and +3 only.
|
||||
uint8_t last_1ffd = 0;
|
||||
|
||||
State() {
|
||||
if(needs_declare()) {
|
||||
DeclareField(z80);
|
||||
DeclareField(video);
|
||||
DeclareField(ram);
|
||||
DeclareField(last_7ffd);
|
||||
DeclareField(last_1ffd);
|
||||
DeclareField(ay);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* State_h */
|
||||
@@ -12,12 +12,15 @@
|
||||
#include "../../../Outputs/CRT/CRT.hpp"
|
||||
#include "../../../ClockReceiver/ClockReceiver.hpp"
|
||||
|
||||
#include "../../../Reflection/Struct.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Sinclair {
|
||||
namespace ZXSpectrum {
|
||||
namespace Video {
|
||||
|
||||
enum class VideoTiming {
|
||||
enum class Timing {
|
||||
FortyEightK,
|
||||
OneTwoEightK,
|
||||
Plus3,
|
||||
@@ -47,11 +50,11 @@ enum class VideoTiming {
|
||||
|
||||
*/
|
||||
|
||||
template <VideoTiming timing> class Video {
|
||||
template <Timing timing> class Video {
|
||||
private:
|
||||
struct Timings {
|
||||
// Number of cycles per line. Will be 224 or 228.
|
||||
int cycles_per_line;
|
||||
int half_cycles_per_line;
|
||||
// Number of lines comprising a whole frame. Will be 311 or 312.
|
||||
int lines_per_frame;
|
||||
|
||||
@@ -63,128 +66,56 @@ template <VideoTiming timing> class Video {
|
||||
// Number of cycles after first pixel fetch at which interrupt is first signalled.
|
||||
int interrupt_time;
|
||||
|
||||
// Contention to apply, in half-cycles, as a function of number of half cycles since
|
||||
// Contention to apply, in whole cycles, as a function of number of whole cycles since
|
||||
// contention began.
|
||||
int delays[16];
|
||||
int delays[8];
|
||||
|
||||
constexpr Timings(int cycles_per_line, int lines_per_frame, int contention_leadin, int contention_duration, int interrupt_offset, const int *delays) noexcept :
|
||||
half_cycles_per_line(cycles_per_line * 2),
|
||||
lines_per_frame(lines_per_frame),
|
||||
contention_leadin(contention_leadin * 2),
|
||||
contention_duration(contention_duration * 2),
|
||||
interrupt_time((cycles_per_line * lines_per_frame - interrupt_offset - contention_leadin) * 2),
|
||||
delays{ delays[0] * 2, delays[1] * 2, delays[2] * 2, delays[3] * 2, delays[4] * 2, delays[5] * 2, delays[6] * 2, delays[7] * 2}
|
||||
{}
|
||||
};
|
||||
|
||||
static constexpr Timings get_timings() {
|
||||
if constexpr (timing == VideoTiming::Plus3) {
|
||||
// Amstrad gate array timings, classic statement:
|
||||
//
|
||||
// Contention begins 14361 cycles "after interrupt" and follows the pattern [1, 0, 7, 6 5 4, 3, 2].
|
||||
// The first four bytes of video are fetched at 14365–14368 cycles, in the order [pixels, attribute, pixels, attribute].
|
||||
//
|
||||
// For my purposes:
|
||||
//
|
||||
// Video fetching always begins at 0. Since there are 311*228 = 70908 cycles per frame, and the interrupt
|
||||
// should "occur" (I assume: begin) 14365 before that, it should actually begin at 70908 - 14365 = 56543.
|
||||
//
|
||||
// Contention begins four cycles before the first video fetch, so it begins at 70904. I don't currently
|
||||
// know whether the four cycles is true across all models, so it's given here as convention_leadin.
|
||||
//
|
||||
// ... except that empirically that all seems to be two cycles off. So maybe I misunderstand what the
|
||||
// contention patterns are supposed to indicate relative to MREQ? It's frustrating that all documentation
|
||||
// I can find is vaguely in terms of contention patterns, and what they mean isn't well-defined in terms
|
||||
// of regular Z80 signalling.
|
||||
constexpr Timings result = {
|
||||
.cycles_per_line = 228 * 2,
|
||||
.lines_per_frame = 311,
|
||||
|
||||
// i.e. video fetching begins five cycles after the start of the
|
||||
// contended memory pattern below; that should put a clear two
|
||||
// cycles between a Z80 access and the first video fetch.
|
||||
.contention_leadin = 5 * 2,
|
||||
.contention_duration = 129 * 2,
|
||||
|
||||
// i.e. interrupt is first signalled 14368 cycles before the first video fetch.
|
||||
.interrupt_time = (228*311 - 14360) * 2,
|
||||
|
||||
.delays = { // Should start at 14365
|
||||
2, 1,
|
||||
0, 0,
|
||||
14, 13,
|
||||
12, 11,
|
||||
10, 9,
|
||||
8, 7,
|
||||
6, 5,
|
||||
4, 3,
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
if constexpr (timing == Timing::Plus3) {
|
||||
constexpr int delays[] = {1, 0, 7, 6, 5, 4, 3, 2};
|
||||
return Timings(228, 311, 6, 129, 14361, delays);
|
||||
}
|
||||
|
||||
if constexpr (timing == VideoTiming::OneTwoEightK) {
|
||||
constexpr Timings result = {
|
||||
.cycles_per_line = 228 * 2,
|
||||
.lines_per_frame = 311,
|
||||
|
||||
.contention_leadin = 4 * 2,
|
||||
.contention_duration = 128 * 2,
|
||||
|
||||
.interrupt_time = (228*311 - 14357) * 2,
|
||||
|
||||
.delays = { // Should start at 14361.
|
||||
12, 11,
|
||||
10, 9,
|
||||
8, 7,
|
||||
6, 5,
|
||||
4, 3,
|
||||
2, 1,
|
||||
0, 0,
|
||||
0, 0,
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
if constexpr (timing == Timing::OneTwoEightK) {
|
||||
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
|
||||
return Timings(228, 311, 4, 128, 14361, delays);
|
||||
}
|
||||
|
||||
if constexpr (timing == VideoTiming::FortyEightK) {
|
||||
constexpr Timings result = {
|
||||
.cycles_per_line = 224 * 2,
|
||||
.lines_per_frame = 312,
|
||||
|
||||
.contention_leadin = 4 * 2,
|
||||
.contention_duration = 128 * 2,
|
||||
|
||||
.interrupt_time = (224*312 - 14331) * 2,
|
||||
|
||||
.delays = { // Should start at 14335.
|
||||
12, 11,
|
||||
10, 9,
|
||||
8, 7,
|
||||
6, 5,
|
||||
4, 3,
|
||||
2, 1,
|
||||
0, 0,
|
||||
0, 0,
|
||||
}
|
||||
};
|
||||
|
||||
return result;
|
||||
if constexpr (timing == Timing::FortyEightK) {
|
||||
constexpr int delays[] = {6, 5, 4, 3, 2, 1, 0, 0};
|
||||
return Timings(224, 312, 4, 128, 14335, delays);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: how long is the interrupt line held for?
|
||||
static constexpr int interrupt_duration = 48;
|
||||
// Interrupt should be held for 32 cycles.
|
||||
static constexpr int interrupt_duration = 64;
|
||||
|
||||
public:
|
||||
void run_for(HalfCycles duration) {
|
||||
constexpr auto timings = get_timings();
|
||||
|
||||
constexpr int sync_line = (timings.interrupt_time / timings.cycles_per_line) + 1;
|
||||
constexpr int sync_line = (timings.interrupt_time / timings.half_cycles_per_line) + 1;
|
||||
|
||||
constexpr int sync_position = 166 * 2;
|
||||
constexpr int sync_position = (timing == Timing::FortyEightK) ? 164 * 2 : 166 * 2;
|
||||
constexpr int sync_length = 17 * 2;
|
||||
constexpr int burst_position = sync_position + 40;
|
||||
constexpr int burst_length = 17;
|
||||
|
||||
int cycles_remaining = duration.as<int>();
|
||||
while(cycles_remaining) {
|
||||
int line = time_into_frame_ / timings.cycles_per_line;
|
||||
int offset = time_into_frame_ % timings.cycles_per_line;
|
||||
const int cycles_this_line = std::min(cycles_remaining, timings.cycles_per_line - offset);
|
||||
int line = time_into_frame_ / timings.half_cycles_per_line;
|
||||
int offset = time_into_frame_ % timings.half_cycles_per_line;
|
||||
const int cycles_this_line = std::min(cycles_remaining, timings.half_cycles_per_line - offset);
|
||||
const int end_offset = offset + cycles_this_line;
|
||||
|
||||
if(!offset) {
|
||||
@@ -292,7 +223,7 @@ template <VideoTiming timing> class Video {
|
||||
if(offset >= burst_position && offset < burst_position+burst_length && end_offset > offset) {
|
||||
const int burst_duration = std::min(burst_position + burst_length, end_offset) - offset;
|
||||
|
||||
if constexpr (timing >= VideoTiming::OneTwoEightK) {
|
||||
if constexpr (timing >= Timing::OneTwoEightK) {
|
||||
crt_.output_colour_burst(burst_duration, 116, is_alternate_line_);
|
||||
// The colour burst phase above is an empirical guess. I need to research further.
|
||||
} else {
|
||||
@@ -308,7 +239,7 @@ template <VideoTiming timing> class Video {
|
||||
}
|
||||
|
||||
cycles_remaining -= cycles_this_line;
|
||||
time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.cycles_per_line * timings.lines_per_frame);
|
||||
time_into_frame_ = (time_into_frame_ + cycles_this_line) % (timings.half_cycles_per_line * timings.lines_per_frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +251,7 @@ template <VideoTiming timing> class Video {
|
||||
}
|
||||
|
||||
static constexpr int half_cycles_per_line() {
|
||||
if constexpr (timing == VideoTiming::FortyEightK) {
|
||||
if constexpr (timing == Timing::FortyEightK) {
|
||||
// TODO: determine real figure here, if one exists.
|
||||
// The source I'm looking at now suggests that the theoretical
|
||||
// ideal of 224*2 ignores the real-life effects of separate
|
||||
@@ -331,6 +262,39 @@ template <VideoTiming timing> class Video {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr HalfCycles frame_duration() {
|
||||
const auto timings = get_timings();
|
||||
return HalfCycles(timings.half_cycles_per_line * timings.lines_per_frame);
|
||||
}
|
||||
|
||||
HalfCycles time_since_interrupt() {
|
||||
const auto timings = get_timings();
|
||||
if(time_into_frame_ >= timings.interrupt_time) {
|
||||
return HalfCycles(time_into_frame_ - timings.interrupt_time);
|
||||
} else {
|
||||
return HalfCycles(time_into_frame_) + frame_duration() - HalfCycles(timings.interrupt_time);
|
||||
}
|
||||
}
|
||||
|
||||
void set_time_since_interrupt(const HalfCycles time) {
|
||||
// Advance using run_for to ensure that all proper CRT interactions occurred.
|
||||
const auto timings = get_timings();
|
||||
const auto target = (time + timings.interrupt_time) % frame_duration();
|
||||
const auto now = HalfCycles(time_into_frame_);
|
||||
|
||||
// Maybe this is easy?
|
||||
if(target == now) return;
|
||||
|
||||
// Is the time within this frame?
|
||||
if(time > now) {
|
||||
run_for(target - time);
|
||||
return;
|
||||
}
|
||||
|
||||
// Then it's necessary to finish this frame and run into the next.
|
||||
run_for(frame_duration() - now + time);
|
||||
}
|
||||
|
||||
public:
|
||||
Video() :
|
||||
crt_(half_cycles_per_line(), 2, Outputs::Display::Type::PAL50, Outputs::Display::InputDataType::Red2Green2Blue2)
|
||||
@@ -339,6 +303,11 @@ template <VideoTiming timing> class Video {
|
||||
crt_.set_display_type(Outputs::Display::DisplayType::RGB);
|
||||
crt_.set_visible_area(Outputs::Display::Rect(0.1f, 0.1f, 0.8f, 0.8f));
|
||||
|
||||
// Get the CRT roughly into phase.
|
||||
//
|
||||
// TODO: this is coupled to an assumption about the initial CRT. Fix.
|
||||
const auto timings = get_timings();
|
||||
crt_.output_blank(timings.lines_per_frame*timings.half_cycles_per_line - timings.interrupt_time);
|
||||
}
|
||||
|
||||
void set_video_source(const uint8_t *source) {
|
||||
@@ -362,7 +331,7 @@ template <VideoTiming timing> class Video {
|
||||
}
|
||||
|
||||
// If not, it'll be in the next batch.
|
||||
return timings.interrupt_time + timings.cycles_per_line * timings.lines_per_frame - time_into_frame_;
|
||||
return timings.interrupt_time + timings.half_cycles_per_line * timings.lines_per_frame - time_into_frame_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -377,21 +346,22 @@ template <VideoTiming timing> class Video {
|
||||
@returns How many cycles the [ULA/gate array] would delay the CPU for if it were to recognise that contention
|
||||
needs to be applied in @c offset half-cycles from now.
|
||||
*/
|
||||
int access_delay(HalfCycles offset) const {
|
||||
HalfCycles access_delay(HalfCycles offset) const {
|
||||
constexpr auto timings = get_timings();
|
||||
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.cycles_per_line * timings.lines_per_frame);
|
||||
const int delay_time = (time_into_frame_ + offset.as<int>() + timings.contention_leadin) % (timings.half_cycles_per_line * timings.lines_per_frame);
|
||||
assert(!(delay_time&1));
|
||||
|
||||
// Check for a time within the no-contention window.
|
||||
if(delay_time >= (191*timings.cycles_per_line + timings.contention_duration)) {
|
||||
if(delay_time >= (191*timings.half_cycles_per_line + timings.contention_duration)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int time_into_line = delay_time % timings.cycles_per_line;
|
||||
const int time_into_line = delay_time % timings.half_cycles_per_line;
|
||||
if(time_into_line >= timings.contention_duration) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return timings.delays[time_into_line & 15];
|
||||
return HalfCycles(timings.delays[(time_into_line >> 1) & 7]);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -399,21 +369,21 @@ template <VideoTiming timing> class Video {
|
||||
*/
|
||||
uint8_t get_floating_value() const {
|
||||
constexpr auto timings = get_timings();
|
||||
const uint8_t out_of_bounds = (timing == VideoTiming::Plus3) ? last_contended_access_ : 0xff;
|
||||
const uint8_t out_of_bounds = (timing == Timing::Plus3) ? last_contended_access_ : 0xff;
|
||||
|
||||
const int line = time_into_frame_ / timings.cycles_per_line;
|
||||
const int line = time_into_frame_ / timings.half_cycles_per_line;
|
||||
if(line >= 192) {
|
||||
return out_of_bounds;
|
||||
}
|
||||
|
||||
const int time_into_line = time_into_frame_ % timings.cycles_per_line;
|
||||
const int time_into_line = time_into_frame_ % timings.half_cycles_per_line;
|
||||
if(time_into_line >= 256 || (time_into_line&8)) {
|
||||
return out_of_bounds;
|
||||
}
|
||||
|
||||
// The +2a and +3 always return the low bit as set.
|
||||
const uint8_t value = last_fetches_[(time_into_line >> 1) & 3];
|
||||
if constexpr (timing == VideoTiming::Plus3) {
|
||||
if constexpr (timing == Timing::Plus3) {
|
||||
return value | 1;
|
||||
}
|
||||
return value;
|
||||
@@ -425,7 +395,7 @@ template <VideoTiming timing> class Video {
|
||||
bus is accessed when the gate array isn't currently reading.
|
||||
*/
|
||||
void set_last_contended_area_access([[maybe_unused]] uint8_t value) {
|
||||
if constexpr (timing == VideoTiming::Plus3) {
|
||||
if constexpr (timing == Timing::Plus3) {
|
||||
last_contended_access_ = value | 1;
|
||||
}
|
||||
}
|
||||
@@ -434,6 +404,7 @@ template <VideoTiming timing> class Video {
|
||||
Sets the current border colour.
|
||||
*/
|
||||
void set_border_colour(uint8_t colour) {
|
||||
border_byte_ = colour;
|
||||
border_colour_ = palette[colour];
|
||||
}
|
||||
|
||||
@@ -452,11 +423,17 @@ template <VideoTiming timing> class Video {
|
||||
crt_.set_display_type(type);
|
||||
}
|
||||
|
||||
/*! Gets the display type. */
|
||||
Outputs::Display::DisplayType get_display_type() const {
|
||||
return crt_.get_display_type();
|
||||
}
|
||||
|
||||
private:
|
||||
int time_into_frame_ = 0;
|
||||
Outputs::CRT::CRT crt_;
|
||||
const uint8_t *memory_ = nullptr;
|
||||
uint8_t border_colour_ = 0;
|
||||
uint8_t border_byte_ = 0;
|
||||
|
||||
uint8_t *pixel_target_ = nullptr;
|
||||
int attribute_address_ = 0;
|
||||
@@ -469,6 +446,8 @@ template <VideoTiming timing> class Video {
|
||||
uint8_t last_fetches_[4] = {0xff, 0xff, 0xff, 0xff};
|
||||
uint8_t last_contended_access_ = 0xff;
|
||||
|
||||
friend struct State;
|
||||
|
||||
#define RGB(r, g, b) (r << 4) | (g << 2) | b
|
||||
static constexpr uint8_t palette[] = {
|
||||
RGB(0, 0, 0), RGB(0, 0, 2), RGB(2, 0, 0), RGB(2, 0, 2),
|
||||
@@ -479,6 +458,41 @@ template <VideoTiming timing> class Video {
|
||||
#undef RGB
|
||||
};
|
||||
|
||||
struct State: public Reflection::StructImpl<State> {
|
||||
uint8_t border_colour = 0;
|
||||
int half_cycles_since_interrupt = 0;
|
||||
bool flash = 0;
|
||||
int flash_counter = 0;
|
||||
bool is_alternate_line = false;
|
||||
|
||||
State() {
|
||||
if(needs_declare()) {
|
||||
DeclareField(border_colour);
|
||||
DeclareField(half_cycles_since_interrupt);
|
||||
DeclareField(flash);
|
||||
DeclareField(flash_counter);
|
||||
DeclareField(is_alternate_line);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Video> State(const Video &source) : State() {
|
||||
border_colour = source.border_byte_;
|
||||
flash = source.flash_mask_;
|
||||
flash_counter = source.flash_counter_;
|
||||
is_alternate_line = source. is_alternate_line_;
|
||||
half_cycles_since_interrupt = source.time_since_interrupt().template as<int>();
|
||||
}
|
||||
|
||||
template <typename Video> void apply(Video &target) {
|
||||
target.set_border_colour(border_colour);
|
||||
target.flash_mask_ = flash ? 0xff : 0x00;
|
||||
target.flash_counter_ = flash_counter;
|
||||
target.is_alternate_line_ = is_alternate_line;
|
||||
target.set_time_since_interrupt(HalfCycles(half_cycles_since_interrupt));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
|
||||
#include "ZXSpectrum.hpp"
|
||||
|
||||
#include "State.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#define LOG_PREFIX "[Spectrum] "
|
||||
#include "../Keyboard/Keyboard.hpp"
|
||||
|
||||
#include "../../../Activity/Source.hpp"
|
||||
#include "../../MachineTypes.hpp"
|
||||
@@ -24,7 +24,9 @@
|
||||
// just grab the CPC's version of an FDC.
|
||||
#include "../../AmstradCPC/FDC.hpp"
|
||||
|
||||
#define LOG_PREFIX "[Spectrum] "
|
||||
#include "../../../Outputs/Log.hpp"
|
||||
|
||||
#include "../../../Outputs/Speaker/Implementation/CompoundSource.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "../../../Outputs/Speaker/Implementation/SampleSource.hpp"
|
||||
@@ -39,12 +41,76 @@
|
||||
|
||||
#include "../../../ClockReceiver/JustInTime.hpp"
|
||||
|
||||
#include "../../../Processors/Z80/State/State.hpp"
|
||||
|
||||
#include "../Keyboard/Keyboard.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace {
|
||||
|
||||
/*!
|
||||
Provides a simultaneous Kempston and Interface 2-style joystick.
|
||||
*/
|
||||
class Joystick: public Inputs::ConcreteJoystick {
|
||||
public:
|
||||
Joystick() :
|
||||
ConcreteJoystick({
|
||||
Input(Input::Up),
|
||||
Input(Input::Down),
|
||||
Input(Input::Left),
|
||||
Input(Input::Right),
|
||||
Input(Input::Fire)
|
||||
}) {}
|
||||
|
||||
void did_set_input(const Input &digital_input, bool is_active) final {
|
||||
#define APPLY_KEMPSTON(b) if(is_active) kempston_ |= b; else kempston_ &= ~b;
|
||||
#define APPLY_SINCLAIR(b) if(is_active) sinclair_ &= ~b; else sinclair_ |= b;
|
||||
|
||||
switch(digital_input.type) {
|
||||
default: return;
|
||||
|
||||
case Input::Right:
|
||||
APPLY_KEMPSTON(0x01);
|
||||
APPLY_SINCLAIR(0x0208);
|
||||
break;
|
||||
case Input::Left:
|
||||
APPLY_KEMPSTON(0x02);
|
||||
APPLY_SINCLAIR(0x0110);
|
||||
break;
|
||||
case Input::Down:
|
||||
APPLY_KEMPSTON(0x04);
|
||||
APPLY_SINCLAIR(0x0404);
|
||||
break;
|
||||
case Input::Up:
|
||||
APPLY_KEMPSTON(0x08);
|
||||
APPLY_SINCLAIR(0x0802);
|
||||
break;
|
||||
case Input::Fire:
|
||||
APPLY_KEMPSTON(0x10);
|
||||
APPLY_SINCLAIR(0x1001);
|
||||
break;
|
||||
}
|
||||
|
||||
#undef APPLY_KEMPSTON
|
||||
#undef APPLY_SINCLAIR
|
||||
}
|
||||
|
||||
/// @returns The value that a Kempston joystick interface would report if this joystick
|
||||
/// were plugged into it.
|
||||
uint8_t get_kempston() {
|
||||
return kempston_;
|
||||
}
|
||||
|
||||
/// @returns The value that a Sinclair interface would report if this joystick
|
||||
/// were plugged into it via @c port (which should be either 0 or 1, for ports 1 or 2).
|
||||
uint8_t get_sinclair(int port) {
|
||||
return uint8_t(sinclair_ >> (port * 8));
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t kempston_ = 0x00;
|
||||
uint16_t sinclair_ = 0xffff;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace Sinclair {
|
||||
namespace ZXSpectrum {
|
||||
|
||||
@@ -58,6 +124,7 @@ template<Model model> class ConcreteMachine:
|
||||
public CPU::Z80::BusHandler,
|
||||
public Machine,
|
||||
public MachineTypes::AudioProducer,
|
||||
public MachineTypes::JoystickMachine,
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
public MachineTypes::MediaTarget,
|
||||
public MachineTypes::ScanProducer,
|
||||
@@ -79,37 +146,32 @@ template<Model model> class ConcreteMachine:
|
||||
set_clock_rate(clock_rate());
|
||||
speaker_.set_input_rate(float(clock_rate()) / 2.0f);
|
||||
|
||||
// With only the +2a and +3 currently supported, the +3 ROM is always
|
||||
// the one required.
|
||||
std::vector<ROMMachine::ROM> rom_names;
|
||||
const std::string machine = "ZXSpectrum";
|
||||
ROM::Name rom_name;
|
||||
switch(model) {
|
||||
case Model::SixteenK:
|
||||
case Model::FortyEightK:
|
||||
rom_names.emplace_back(machine, "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531f);
|
||||
break;
|
||||
|
||||
case Model::OneTwoEightK:
|
||||
rom_names.emplace_back(machine, "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995);
|
||||
break;
|
||||
|
||||
case Model::Plus2:
|
||||
rom_names.emplace_back(machine, "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dc);
|
||||
break;
|
||||
|
||||
case Model::FortyEightK: rom_name = ROM::Name::Spectrum48k; break;
|
||||
case Model::OneTwoEightK: rom_name = ROM::Name::Spectrum128k; break;
|
||||
case Model::Plus2: rom_name = ROM::Name::SpecrumPlus2; break;
|
||||
case Model::Plus2a:
|
||||
case Model::Plus3: {
|
||||
const std::initializer_list<uint32_t> crc32s = { 0x96e3c17a, 0xbe0d9ec4 };
|
||||
rom_names.emplace_back(machine, "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crc32s);
|
||||
} break;
|
||||
case Model::Plus3: rom_name = ROM::Name::SpectrumPlus3; break;
|
||||
// TODO: possibly accept the +3 ROM in multiple parts?
|
||||
}
|
||||
const auto roms = rom_fetcher(rom_names);
|
||||
if(!roms[0]) throw ROMMachine::Error::MissingROMs;
|
||||
memcpy(rom_.data(), roms[0]->data(), std::min(rom_.size(), roms[0]->size()));
|
||||
const auto request = ROM::Request(rom_name);
|
||||
auto roms = rom_fetcher(request);
|
||||
if(!request.validate(roms)) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
const auto &rom = roms.find(rom_name)->second;
|
||||
memcpy(rom_.data(), rom.data(), std::min(rom_.size(), rom.size()));
|
||||
|
||||
// Register for sleeping notifications.
|
||||
tape_player_.set_clocking_hint_observer(this);
|
||||
|
||||
// Attach a couple of joysticks.
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
joysticks_.emplace_back(new Joystick);
|
||||
|
||||
// Set up initial memory map.
|
||||
update_memory_map();
|
||||
set_video_address();
|
||||
@@ -124,6 +186,29 @@ template<Model model> class ConcreteMachine:
|
||||
duration_to_press_enter_ = Cycles(5 * clock_rate());
|
||||
keyboard_.set_key_state(ZX::Keyboard::KeyEnter, true);
|
||||
}
|
||||
|
||||
// Install state if supplied.
|
||||
if(target.state) {
|
||||
const auto state = static_cast<State *>(target.state.get());
|
||||
state->z80.apply(z80_);
|
||||
state->video.apply(*video_.last_valid());
|
||||
state->ay.apply(ay_);
|
||||
|
||||
// If this is a 48k or 16k machine, remap source data from its original
|
||||
// linear form to whatever the banks end up being; otherwise copy as is.
|
||||
if(model <= Model::FortyEightK) {
|
||||
const size_t num_banks = std::min(size_t(48*1024), state->ram.size()) >> 14;
|
||||
for(size_t c = 0; c < num_banks; c++) {
|
||||
memcpy(&write_pointers_[c + 1][(c+1) * 0x4000], &state->ram[c * 0x4000], 0x4000);
|
||||
}
|
||||
} else {
|
||||
memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size()));
|
||||
|
||||
port1ffd_ = state->last_1ffd;
|
||||
port7ffd_ = state->last_7ffd;
|
||||
update_memory_map();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~ConcreteMachine() {
|
||||
@@ -202,6 +287,10 @@ template<Model model> class ConcreteMachine:
|
||||
video_->set_display_type(display_type);
|
||||
}
|
||||
|
||||
Outputs::Display::DisplayType get_display_type() const override {
|
||||
return video_->get_display_type();
|
||||
}
|
||||
|
||||
// MARK: - BusHandler.
|
||||
|
||||
forceinline HalfCycles perform_machine_cycle(const CPU::Z80::PartialMachineCycle &cycle) {
|
||||
@@ -218,12 +307,23 @@ template<Model model> class ConcreteMachine:
|
||||
cycle.operation >= PartialMachineCycle::ReadOpcodeStart &&
|
||||
cycle.operation <= PartialMachineCycle::WriteStart) {
|
||||
|
||||
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
|
||||
const auto delay = video_.last_valid()->access_delay(video_.time_since_flush());
|
||||
advance(cycle.length + delay);
|
||||
return delay;
|
||||
}
|
||||
} else {
|
||||
switch(cycle.operation) {
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
case CPU::Z80::PartialMachineCycle::Interrupt:
|
||||
// For these, carry on into the actual handler, below.
|
||||
break;
|
||||
|
||||
// For anything else that isn't listed below, just advance
|
||||
// time and conclude here.
|
||||
default:
|
||||
advance(cycle.length);
|
||||
return HalfCycles(0);
|
||||
@@ -234,7 +334,7 @@ template<Model model> class ConcreteMachine:
|
||||
// always occurs if it is in the $4000–$8000 range regardless of current
|
||||
// memory mapping.
|
||||
HalfCycles delay;
|
||||
HalfCycles time = video_.time_since_flush() + HalfCycles(1);
|
||||
HalfCycles time = video_.time_since_flush();
|
||||
|
||||
if((address & 0xc000) == 0x4000) {
|
||||
for(int c = 0; c < ((address & 1) ? 4 : 2); c++) {
|
||||
@@ -250,7 +350,7 @@ template<Model model> class ConcreteMachine:
|
||||
|
||||
advance(cycle.length + delay);
|
||||
return delay;
|
||||
}
|
||||
} break;
|
||||
|
||||
case PartialMachineCycle::ReadOpcodeStart:
|
||||
case PartialMachineCycle::ReadStart:
|
||||
@@ -258,12 +358,12 @@ template<Model model> class ConcreteMachine:
|
||||
// These all start by loading the address bus, then set MREQ
|
||||
// half a cycle later.
|
||||
if(is_contended_[address >> 14]) {
|
||||
const HalfCycles delay = video_.last_valid()->access_delay(video_.time_since_flush() + HalfCycles(1));
|
||||
const auto delay = video_.last_valid()->access_delay(video_.time_since_flush());
|
||||
|
||||
advance(cycle.length + delay);
|
||||
return delay;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case PartialMachineCycle::Internal: {
|
||||
// Whatever's on the address bus will remain there, without IOREQ or
|
||||
@@ -273,7 +373,7 @@ template<Model model> class ConcreteMachine:
|
||||
const auto half_cycles = cycle.length.as<int>();
|
||||
assert(!(half_cycles & 1));
|
||||
|
||||
HalfCycles time = video_.time_since_flush() + HalfCycles(1);
|
||||
HalfCycles time = video_.time_since_flush();
|
||||
HalfCycles delay;
|
||||
for(int c = 0; c < half_cycles; c += 2) {
|
||||
const auto next_delay = video_.last_valid()->access_delay(time);
|
||||
@@ -284,15 +384,7 @@ template<Model model> class ConcreteMachine:
|
||||
advance(cycle.length + delay);
|
||||
return delay;
|
||||
}
|
||||
}
|
||||
|
||||
case CPU::Z80::PartialMachineCycle::Input:
|
||||
case CPU::Z80::PartialMachineCycle::Output:
|
||||
case CPU::Z80::PartialMachineCycle::Read:
|
||||
case CPU::Z80::PartialMachineCycle::Write:
|
||||
case CPU::Z80::PartialMachineCycle::ReadOpcode:
|
||||
// For these, carry on into the actual handler, below.
|
||||
break;
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,6 +411,7 @@ template<Model model> class ConcreteMachine:
|
||||
break;
|
||||
}
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
case PartialMachineCycle::Read:
|
||||
if constexpr (model == Model::SixteenK) {
|
||||
@@ -381,10 +474,6 @@ template<Model model> class ConcreteMachine:
|
||||
|
||||
// Set the proper video base pointer.
|
||||
set_video_address();
|
||||
|
||||
// Potentially lock paging, _after_ the current
|
||||
// port values have taken effect.
|
||||
disable_paging_ |= *cycle.value & 0x20;
|
||||
}
|
||||
|
||||
// Test for +2a/+3 paging (i.e. port 1ffd).
|
||||
@@ -432,6 +521,11 @@ template<Model model> class ConcreteMachine:
|
||||
bool did_match = false;
|
||||
*cycle.value = 0xff;
|
||||
|
||||
if(!(address&32)) {
|
||||
did_match = true;
|
||||
*cycle.value &= static_cast<Joystick *>(joysticks_[0].get())->get_kempston();
|
||||
}
|
||||
|
||||
if(!(address&1)) {
|
||||
did_match = true;
|
||||
|
||||
@@ -444,11 +538,15 @@ template<Model model> class ConcreteMachine:
|
||||
*cycle.value &= keyboard_.read(address);
|
||||
*cycle.value &= tape_player_.get_input() ? 0xbf : 0xff;
|
||||
|
||||
// If this read is within 200 cycles of the previous,
|
||||
// count it as an adjacent hit; if 20 of those have
|
||||
// occurred then start the tape motor.
|
||||
// Add Joystick input on top.
|
||||
if(!(address&0x1000)) *cycle.value &= static_cast<Joystick *>(joysticks_[0].get())->get_sinclair(0);
|
||||
if(!(address&0x0800)) *cycle.value &= static_cast<Joystick *>(joysticks_[1].get())->get_sinclair(1);
|
||||
|
||||
// If this read is between 50 and 200 cycles since the
|
||||
// previous, count it as an adjacent hit; if 20 of those
|
||||
// have occurred then start the tape motor.
|
||||
if(use_automatic_tape_motor_control_) {
|
||||
if(cycles_since_tape_input_read_ < HalfCycles(400)) {
|
||||
if(cycles_since_tape_input_read_ >= HalfCycles(100) && cycles_since_tape_input_read_ < HalfCycles(200)) {
|
||||
++recent_tape_hits_;
|
||||
|
||||
if(recent_tape_hits_ == 20) {
|
||||
@@ -496,6 +594,13 @@ template<Model model> class ConcreteMachine:
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case PartialMachineCycle::Interrupt:
|
||||
// At least one piece of Spectrum software, Escape from M.O.N.J.A.S. explicitly
|
||||
// assumes that a 0xff value will be on the bus during an interrupt acknowledgment.
|
||||
// I wasn't otherwise aware that this value is reliable.
|
||||
*cycle.value = 0xff;
|
||||
break;
|
||||
}
|
||||
|
||||
return HalfCycles(0);
|
||||
@@ -513,11 +618,11 @@ template<Model model> class ConcreteMachine:
|
||||
if(!tape_player_is_sleeping_) tape_player_.run_for(duration.as_integral());
|
||||
|
||||
// Update automatic tape motor control, if enabled; if it's been
|
||||
// 3 seconds since software last possibly polled the tape, stop it.
|
||||
if(use_automatic_tape_motor_control_ && cycles_since_tape_input_read_ < HalfCycles(clock_rate() * 6)) {
|
||||
// 0.5 seconds since software last possibly polled the tape, stop it.
|
||||
if(use_automatic_tape_motor_control_ && cycles_since_tape_input_read_ < HalfCycles(clock_rate())) {
|
||||
cycles_since_tape_input_read_ += duration;
|
||||
|
||||
if(cycles_since_tape_input_read_ >= HalfCycles(clock_rate() * 6)) {
|
||||
if(cycles_since_tape_input_read_ >= HalfCycles(clock_rate())) {
|
||||
tape_player_.set_motor_control(false);
|
||||
recent_tape_hits_ = 0;
|
||||
}
|
||||
@@ -615,6 +720,7 @@ template<Model model> class ConcreteMachine:
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly); // OptionsType is arbitrary, but not optional.
|
||||
options->automatic_tape_motor_control = use_automatic_tape_motor_control_;
|
||||
options->quickload = allow_fast_tape_hack_;
|
||||
options->output = get_video_signal_configurable();
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -703,13 +809,17 @@ template<Model model> class ConcreteMachine:
|
||||
set_memory(2, 2);
|
||||
set_memory(3, port7ffd_ & 7);
|
||||
}
|
||||
|
||||
// Potentially lock paging, _after_ the current
|
||||
// port values have taken effect.
|
||||
disable_paging_ = port7ffd_ & 0x20;
|
||||
}
|
||||
|
||||
void set_memory(int bank, uint8_t source) {
|
||||
if constexpr (model >= Model::Plus2a) {
|
||||
is_contended_[bank] = (source >= 4 && source < 8);
|
||||
is_contended_[bank] = source >= 4 && source < 8;
|
||||
} else {
|
||||
is_contended_[bank] = source & 1;
|
||||
is_contended_[bank] = source < 0x80 && source & 1;
|
||||
}
|
||||
pages_[bank] = source;
|
||||
|
||||
@@ -748,10 +858,10 @@ template<Model model> class ConcreteMachine:
|
||||
// MARK: - Video.
|
||||
using VideoType =
|
||||
std::conditional_t<
|
||||
model <= Model::FortyEightK, Video<VideoTiming::FortyEightK>,
|
||||
model <= Model::FortyEightK, Video::Video<Video::Timing::FortyEightK>,
|
||||
std::conditional_t<
|
||||
model <= Model::Plus2, Video<VideoTiming::OneTwoEightK>,
|
||||
Video<VideoTiming::Plus3>
|
||||
model <= Model::Plus2, Video::Video<Video::Timing::OneTwoEightK>,
|
||||
Video::Video<Video::Timing::Plus3>
|
||||
>
|
||||
>;
|
||||
JustInTimeActor<VideoType> video_;
|
||||
@@ -862,6 +972,12 @@ template<Model model> class ConcreteMachine:
|
||||
|
||||
// MARK: - Automatic startup.
|
||||
Cycles duration_to_press_enter_;
|
||||
|
||||
// MARK: - Joysticks
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() override {
|
||||
return joysticks_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ class Machine {
|
||||
friend Configurable::DisplayOption<Options>;
|
||||
friend Configurable::QuickloadOption<Options>;
|
||||
public:
|
||||
bool automatic_tape_motor_control;
|
||||
bool automatic_tape_motor_control = true;
|
||||
|
||||
Options(Configurable::OptionsType type) :
|
||||
Configurable::DisplayOption<Options>(type == Configurable::OptionsType::UserFriendly ? Configurable::Display::RGB : Configurable::Display::CompositeColour),
|
||||
|
||||
26
Machines/StateProducer.hpp
Normal file
26
Machines/StateProducer.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// StateProducer.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 24/04/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef State_h
|
||||
#define State_h
|
||||
|
||||
#include <memory>
|
||||
#include "../Analyser/Static/StaticAnalyser.hpp"
|
||||
|
||||
namespace MachineTypes {
|
||||
|
||||
struct StateProducer {
|
||||
// TODO.
|
||||
// virtual bool get_state(Analyser::Static::State *, [[maybe_unused]] bool advance_to_simple = false) {
|
||||
// return false;
|
||||
// }
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif /* State_h */
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "../ColecoVision/ColecoVision.hpp"
|
||||
#include "../Commodore/Vic-20/Vic20.hpp"
|
||||
#include "../Electron/Electron.hpp"
|
||||
#include "../Enterprise/Enterprise.hpp"
|
||||
#include "../MasterSystem/MasterSystem.hpp"
|
||||
#include "../MSX/MSX.hpp"
|
||||
#include "../Oric/Oric.hpp"
|
||||
@@ -34,6 +35,7 @@
|
||||
#include "../../Analyser/Static/Atari2600/Target.hpp"
|
||||
#include "../../Analyser/Static/AtariST/Target.hpp"
|
||||
#include "../../Analyser/Static/Commodore/Target.hpp"
|
||||
#include "../../Analyser/Static/Enterprise/Target.hpp"
|
||||
#include "../../Analyser/Static/Macintosh/Target.hpp"
|
||||
#include "../../Analyser/Static/MSX/Target.hpp"
|
||||
#include "../../Analyser/Static/Oric/Target.hpp"
|
||||
@@ -49,7 +51,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe
|
||||
|
||||
Machine::DynamicMachine *machine = nullptr;
|
||||
try {
|
||||
#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<name::Machine>(name::Machine::m(target, rom_fetcher)); break;
|
||||
#define BindD(name, m) case Analyser::Machine::m: machine = new Machine::TypedDynamicMachine<::name::Machine>(name::Machine::m(target, rom_fetcher)); break;
|
||||
#define Bind(m) BindD(m, m)
|
||||
switch(target->machine) {
|
||||
Bind(AmstradCPC)
|
||||
@@ -61,6 +63,7 @@ Machine::DynamicMachine *Machine::MachineForTarget(const Analyser::Static::Targe
|
||||
BindD(Coleco::Vision, ColecoVision)
|
||||
BindD(Commodore::Vic20, Vic20)
|
||||
Bind(Electron)
|
||||
Bind(Enterprise)
|
||||
Bind(MSX)
|
||||
Bind(Oric)
|
||||
BindD(Sega::MasterSystem, MasterSystem)
|
||||
@@ -127,6 +130,7 @@ std::string Machine::ShortNameForTargetMachine(const Analyser::Machine machine)
|
||||
case Analyser::Machine::AtariST: return "AtariST";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Electron";
|
||||
case Analyser::Machine::Enterprise: return "Enterprise";
|
||||
case Analyser::Machine::Macintosh: return "Macintosh";
|
||||
case Analyser::Machine::MasterSystem: return "MasterSystem";
|
||||
case Analyser::Machine::MSX: return "MSX";
|
||||
@@ -148,6 +152,7 @@ std::string Machine::LongNameForTargetMachine(Analyser::Machine machine) {
|
||||
case Analyser::Machine::AtariST: return "Atari ST";
|
||||
case Analyser::Machine::ColecoVision: return "ColecoVision";
|
||||
case Analyser::Machine::Electron: return "Acorn Electron";
|
||||
case Analyser::Machine::Enterprise: return "Enterprise";
|
||||
case Analyser::Machine::Macintosh: return "Apple Macintosh";
|
||||
case Analyser::Machine::MasterSystem: return "Sega Master System";
|
||||
case Analyser::Machine::MSX: return "MSX";
|
||||
@@ -177,11 +182,13 @@ std::vector<std::string> Machine::AllMachines(Type type, bool long_names) {
|
||||
AddName(AppleIIgs);
|
||||
AddName(AtariST);
|
||||
AddName(Electron);
|
||||
AddName(Enterprise);
|
||||
AddName(Macintosh);
|
||||
AddName(MSX);
|
||||
AddName(Oric);
|
||||
AddName(Vic20);
|
||||
AddName(ZX8081);
|
||||
AddName(ZXSpectrum);
|
||||
}
|
||||
|
||||
#undef AddName
|
||||
@@ -200,6 +207,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> Machine::AllOptionsBy
|
||||
Emplace(AtariST, Atari::ST::Machine);
|
||||
Emplace(ColecoVision, Coleco::Vision::Machine);
|
||||
Emplace(Electron, Electron::Machine);
|
||||
Emplace(Enterprise, Enterprise::Machine);
|
||||
Emplace(Macintosh, Apple::Macintosh::Machine);
|
||||
Emplace(MasterSystem, Sega::MasterSystem::Machine);
|
||||
Emplace(MSX, MSX::Machine);
|
||||
@@ -225,6 +233,7 @@ std::map<std::string, std::unique_ptr<Analyser::Static::Target>> Machine::Target
|
||||
Add(AppleIIgs);
|
||||
Add(AtariST);
|
||||
AddMapped(Electron, Acorn);
|
||||
Add(Enterprise);
|
||||
Add(Macintosh);
|
||||
Add(MSX);
|
||||
Add(Oric);
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "MemoryPacker.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
void Memory::PackBigEndian16(const std::vector<uint8_t> &source, uint16_t *target) {
|
||||
for(size_t c = 0; c < source.size(); c += 2) {
|
||||
target[c >> 1] = uint16_t(source[c] << 8) | uint16_t(source[c+1]);
|
||||
|
||||
545
Machines/Utility/ROMCatalogue.cpp
Normal file
545
Machines/Utility/ROMCatalogue.cpp
Normal file
@@ -0,0 +1,545 @@
|
||||
//
|
||||
// ROMCatalogue.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "ROMCatalogue.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <codecvt>
|
||||
#include <iomanip>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
|
||||
using namespace ROM;
|
||||
|
||||
namespace {
|
||||
constexpr Name MaxName = Name::SpectrumPlus3;
|
||||
}
|
||||
|
||||
Request::Request(Name name, bool optional) {
|
||||
node.name = name;
|
||||
node.is_optional = optional;
|
||||
}
|
||||
|
||||
Request Request::append(Node::Type type, const Request &rhs) {
|
||||
// If either side is empty, act appropriately.
|
||||
if(node.empty() && !rhs.node.empty()) {
|
||||
return rhs;
|
||||
}
|
||||
if(rhs.node.empty()) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Just copy in the RHS child nodes if types match.
|
||||
if(node.type == type && rhs.node.type == type) {
|
||||
Request new_request = *this;
|
||||
new_request.node.children.insert(new_request.node.children.end(), rhs.node.children.begin(), rhs.node.children.end());
|
||||
new_request.node.sort();
|
||||
return new_request;
|
||||
}
|
||||
|
||||
// Possibly: left is appropriate request and rhs is just one more thing?
|
||||
if(node.type == type && rhs.node.type == Node::Type::One) {
|
||||
Request new_request = *this;
|
||||
new_request.node.children.push_back(rhs.node);
|
||||
new_request.node.sort();
|
||||
return new_request;
|
||||
}
|
||||
|
||||
// Or: right is appropriate request and this is just one more thing?
|
||||
if(rhs.node.type == type && node.type == Node::Type::One) {
|
||||
Request new_request = rhs;
|
||||
new_request.node.children.push_back(node);
|
||||
new_request.node.sort();
|
||||
return new_request;
|
||||
}
|
||||
|
||||
// Otherwise create a new parent node.
|
||||
Request parent;
|
||||
parent.node.type = type;
|
||||
parent.node.children.push_back(this->node);
|
||||
parent.node.children.push_back(rhs.node);
|
||||
return parent;
|
||||
}
|
||||
|
||||
Request Request::operator &&(const Request &rhs) {
|
||||
return append(Node::Type::All, rhs);
|
||||
}
|
||||
|
||||
Request Request::operator ||(const Request &rhs) {
|
||||
return append(Node::Type::Any, rhs);
|
||||
}
|
||||
|
||||
bool Request::validate(Map &map) const {
|
||||
return node.validate(map);
|
||||
}
|
||||
|
||||
std::vector<ROM::Description> Request::all_descriptions() const {
|
||||
std::vector<Description> result;
|
||||
node.add_descriptions(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void Request::Node::add_descriptions(std::vector<Description> &result) const {
|
||||
if(type == Type::One) {
|
||||
result.push_back(name);
|
||||
return;
|
||||
}
|
||||
|
||||
for(const auto &child: children) {
|
||||
child.add_descriptions(result);
|
||||
}
|
||||
}
|
||||
|
||||
Request Request::subtract(const ROM::Map &map) const {
|
||||
Request copy(*this);
|
||||
if(copy.node.subtract(map)) {
|
||||
copy.node.name = Name::None;
|
||||
copy.node.type = Node::Type::One;
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
bool Request::Node::subtract(const ROM::Map &map) {
|
||||
switch(type) {
|
||||
case Type::One:
|
||||
return map.find(name) != map.end();
|
||||
|
||||
default: {
|
||||
bool has_all = true;
|
||||
bool has_any = false;
|
||||
|
||||
auto iterator = children.begin();
|
||||
while(iterator != children.end()) {
|
||||
const bool did_subtract = iterator->subtract(map);
|
||||
has_all &= did_subtract;
|
||||
has_any |= did_subtract;
|
||||
if(did_subtract) {
|
||||
iterator = children.erase(iterator);
|
||||
} else {
|
||||
++iterator;
|
||||
}
|
||||
}
|
||||
|
||||
return (type == Type::All && has_all) || (type == Type::Any && has_any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Request::empty() {
|
||||
return node.type == Node::Type::One && node.name == Name::None;
|
||||
}
|
||||
|
||||
bool Request::Node::validate(Map &map) const {
|
||||
// Leaf nodes are easy: check that the named ROM is present,
|
||||
// unless it's optional, in which case it is always valid.
|
||||
//
|
||||
// If it is present, make sure it's the proper size.
|
||||
if(type == Type::One) {
|
||||
auto rom = map.find(name);
|
||||
if(rom == map.end()) {
|
||||
return is_optional;
|
||||
}
|
||||
|
||||
const Description description(name);
|
||||
rom->second.resize(description.size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// This is a collection node then. Check for both any or all
|
||||
// simultaneously, since all nodes will need to be visited
|
||||
// regardless of any/all in order to ensure proper sizing.
|
||||
bool has_all = true;
|
||||
bool has_any = false;
|
||||
|
||||
for(const auto &child: children) {
|
||||
const bool is_valid = child.validate(map);
|
||||
has_all &= is_valid;
|
||||
has_any |= is_valid;
|
||||
}
|
||||
|
||||
return (type == Type::Any && has_any) || (type == Type::All && has_all);
|
||||
}
|
||||
|
||||
void Request::visit(
|
||||
const std::function<void(ListType, size_t)> &enter_list,
|
||||
const std::function<void(void)> &exit_list,
|
||||
const std::function<void(ROM::Request::ListType, const ROM::Description &, bool, size_t)> &add_item
|
||||
) const {
|
||||
node.visit(enter_list, exit_list, add_item);
|
||||
}
|
||||
|
||||
void Request::visit(
|
||||
const std::function<void(LineItem, ListType, int, const ROM::Description *, bool, size_t)> &add_item
|
||||
) const {
|
||||
int indentation_level = 0;
|
||||
node.visit(
|
||||
[&indentation_level, &add_item] (ROM::Request::ListType type, size_t size) {
|
||||
add_item(LineItem::NewList, type, indentation_level, nullptr, false, size);
|
||||
++indentation_level;
|
||||
},
|
||||
[&indentation_level] {
|
||||
--indentation_level;
|
||||
},
|
||||
[&indentation_level, &add_item] (ROM::Request::ListType type, const ROM::Description &rom, bool is_optional, size_t remaining) {
|
||||
add_item(LineItem::Description, type, indentation_level, &rom, is_optional, remaining);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void Request::Node::visit(
|
||||
const std::function<void(ListType, size_t)> &enter_list,
|
||||
const std::function<void(void)> &exit_list,
|
||||
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
|
||||
) const {
|
||||
switch(type) {
|
||||
case Type::One:
|
||||
enter_list(ListType::Single, 1);
|
||||
add_item(ROM::Request::ListType::Any, Description(name), is_optional, 0);
|
||||
exit_list();
|
||||
break;
|
||||
|
||||
case Type::Any:
|
||||
case Type::All: {
|
||||
const ListType list_type = type == Type::Any ? ListType::Any : ListType::All;
|
||||
enter_list(list_type, children.size());
|
||||
for(size_t index = 0; index < children.size(); index++) {
|
||||
auto &child = children[index];
|
||||
|
||||
if(child.type == Type::One) {
|
||||
add_item(list_type, Description(child.name), child.is_optional, children.size() - 1 - index);
|
||||
} else {
|
||||
child.visit(enter_list, exit_list, add_item);
|
||||
}
|
||||
}
|
||||
exit_list();
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Description> ROM::all_descriptions() {
|
||||
std::vector<Description> result;
|
||||
for(int name = 1; name <= MaxName; name++) {
|
||||
result.push_back(Description(ROM::Name(name)));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<Description> Description::from_crc(uint32_t crc32) {
|
||||
for(int name = 1; name <= MaxName; name++) {
|
||||
const Description candidate = Description(ROM::Name(name));
|
||||
|
||||
const auto found_crc = std::find(candidate.crc32s.begin(), candidate.crc32s.end(), crc32);
|
||||
if(found_crc != candidate.crc32s.end()) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string Description::description(int flags) const {
|
||||
std::stringstream output;
|
||||
|
||||
// If there are no CRCs, don't output them.
|
||||
if(crc32s.empty()) flags &= ~ DescriptionFlag::CRC;
|
||||
|
||||
// Print the file name(s) and the descriptive name.
|
||||
if(flags & DescriptionFlag::Filename) {
|
||||
flags &= ~DescriptionFlag::Filename;
|
||||
|
||||
output << machine_name << '/';
|
||||
if(file_names.size() == 1) {
|
||||
output << file_names[0];
|
||||
} else {
|
||||
output << "{";
|
||||
bool is_first = true;
|
||||
for(const auto &file_name: file_names) {
|
||||
if(!is_first) output << " or ";
|
||||
output << file_name;
|
||||
is_first = false;
|
||||
}
|
||||
output << "}";
|
||||
}
|
||||
output << " (" << descriptive_name;
|
||||
if(!flags) {
|
||||
output << ")";
|
||||
return output.str();
|
||||
}
|
||||
output << "; ";
|
||||
} else {
|
||||
output << descriptive_name;
|
||||
if(!flags) {
|
||||
return output.str();
|
||||
}
|
||||
output << " (";
|
||||
}
|
||||
|
||||
// Print the size.
|
||||
if(flags & DescriptionFlag::Size) {
|
||||
flags &= ~DescriptionFlag::Size;
|
||||
output << size << " bytes";
|
||||
|
||||
if(!flags) {
|
||||
output << ")";
|
||||
return output.str();
|
||||
}
|
||||
output << "; ";
|
||||
}
|
||||
|
||||
// Print the CRC(s).
|
||||
if(flags & DescriptionFlag::CRC) {
|
||||
flags &= ~DescriptionFlag::CRC;
|
||||
|
||||
output << ((crc32s.size() > 1) ? "usual crc32s: " : "usual crc32: ");
|
||||
bool is_first = true;
|
||||
for(const auto crc32: crc32s) {
|
||||
if(!is_first) output << ", ";
|
||||
is_first = false;
|
||||
output << std::hex << std::setfill('0') << std::setw(8) << crc32;
|
||||
}
|
||||
|
||||
if(!flags) {
|
||||
output << ")";
|
||||
return output.str();
|
||||
}
|
||||
}
|
||||
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::wstring Request::description(int description_flags, wchar_t bullet_point) {
|
||||
std::wstringstream output;
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> wstring_converter;
|
||||
|
||||
visit(
|
||||
[&output, description_flags, bullet_point, &wstring_converter] (ROM::Request::LineItem item, ROM::Request::ListType type, int indentation_level, const ROM::Description *description, bool is_optional, size_t remaining) {
|
||||
if(indentation_level) {
|
||||
output << std::endl;
|
||||
for(int c = 0; c < indentation_level; c++) output << '\t';
|
||||
output << bullet_point << ' ';
|
||||
}
|
||||
|
||||
switch(item) {
|
||||
case ROM::Request::LineItem::NewList:
|
||||
if(remaining > 1) {
|
||||
if(!indentation_level) output << " ";
|
||||
switch(type) {
|
||||
default:
|
||||
case ROM::Request::ListType::All: output << "all of:"; break;
|
||||
case ROM::Request::ListType::Any:
|
||||
if(remaining == 2) {
|
||||
output << "either of:";
|
||||
} else {
|
||||
output << "any of:";
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
output << ":";
|
||||
}
|
||||
break;
|
||||
|
||||
case ROM::Request::LineItem::Description:
|
||||
if(is_optional) output << "optionally, ";
|
||||
|
||||
output << wstring_converter.from_bytes(description->description(description_flags));
|
||||
|
||||
if(remaining) {
|
||||
output << ";";
|
||||
if(remaining == 1) {
|
||||
output << ((type == ROM::Request::ListType::All) ? " and" : " or");
|
||||
}
|
||||
} else {
|
||||
output << ".";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return output.str();
|
||||
}
|
||||
|
||||
|
||||
Description::Description(Name name) {
|
||||
switch(name) {
|
||||
default: assert(false); break;
|
||||
|
||||
case Name::AMSDOS: *this = Description(name, "AmstradCPC", "the Amstrad Disk Operating System", "amsdos.rom", 16*1024, 0x1fe22ecdu); break;
|
||||
case Name::CPC464Firmware: *this = Description(name, "AmstradCPC", "the CPC 464 firmware", "os464.rom", 16*1024, 0x815752dfu); break;
|
||||
case Name::CPC464BASIC: *this = Description(name, "AmstradCPC", "the CPC 464 BASIC ROM", "basic464.rom", 16*1024, 0x7d9a3bacu); break;
|
||||
case Name::CPC664Firmware: *this = Description(name, "AmstradCPC", "the CPC 664 firmware", "os664.rom", 16*1024, 0x3f5a6dc4u); break;
|
||||
case Name::CPC664BASIC: *this = Description(name, "AmstradCPC", "the CPC 664 BASIC ROM", "basic664.rom", 16*1024, 0x32fee492u); break;
|
||||
case Name::CPC6128Firmware: *this = Description(name, "AmstradCPC", "the CPC 6128 firmware", "os6128.rom", 16*1024, 0x0219bb74u); break;
|
||||
case Name::CPC6128BASIC: *this = Description(name, "AmstradCPC", "the CPC 6128 BASIC ROM", "basic6128.rom", 16*1024, 0xca6af63du); break;
|
||||
|
||||
case Name::AppleIIEnhancedE: *this = Description(name, "AppleII", "the Enhanced Apple IIe ROM", "apple2e.rom", 32*1024, 0x65989942u); break;
|
||||
case Name::AppleIIe: *this = Description(name, "AppleII", "the Apple IIe ROM", "apple2eu.rom", 32*1024, 0xe12be18du); break;
|
||||
case Name::AppleIIPlus: *this = Description(name, "AppleII", "the Apple II+ ROM", "apple2.rom", 12*1024, 0xf66f9c26u); break;
|
||||
case Name::AppleIIOriginal: *this = Description(name, "AppleII", "the original Apple II ROM", "apple2o.rom", 12*1024, 0xba210588u); break;
|
||||
case Name::AppleIICharacter: *this = Description(name, "AppleII", "the basic Apple II character ROM", "apple2-character.rom", 2*1024, 0x64f415c6u); break;
|
||||
case Name::AppleIIeCharacter: *this = Description(name, "AppleII", "the Apple IIe character ROM", "apple2eu-character.rom", 4*1024, 0x816a86f1u); break;
|
||||
case Name::AppleIIEnhancedECharacter:
|
||||
*this = Description(name, "AppleII", "the Enhanced Apple IIe character ROM", "apple2e-character.rom", 4*1024, 0x2651014du);
|
||||
break;
|
||||
|
||||
case Name::AppleIIgsROM00: /* TODO */
|
||||
case Name::AppleIIgsROM01: *this = Description(name, "AppleIIgs", "the Apple IIgs ROM01", "apple2gs.rom", 128*1024, 0x42f124b0u); break;
|
||||
case Name::AppleIIgsROM03: *this = Description(name, "AppleIIgs", "the Apple IIgs ROM03", "apple2gs.rom2", 256*1024, 0xde7ddf29u); break;
|
||||
case Name::AppleIIgsCharacter: *this = Description(name, "AppleIIgs", "the Apple IIgs character ROM", "apple2gs.chr", 4*1024, 0x91e53cd8u); break;
|
||||
case AppleIIgsMicrocontrollerROM03:
|
||||
*this = Description(name, "AppleIIgs", "the Apple IIgs ROM03 ADB microcontroller ROM", "341s0632-2", 4*1024, 0xe1c11fb0u);
|
||||
break;
|
||||
|
||||
case Name::DiskIIBoot16Sector:
|
||||
*this = Description(name, "DiskII", "the Disk II 16-sector boot ROM", "boot-16.rom", 256, 0xce7144f6u);
|
||||
break;
|
||||
case Name::DiskIIStateMachine16Sector:
|
||||
*this = Description(name, "DiskII", "the Disk II 16-sector state machine ROM", "state-machine-16.rom", 256, std::initializer_list<uint32_t>{ 0x9796a238, 0xb72a2c70 } );
|
||||
break;
|
||||
case Name::DiskIIBoot13Sector:
|
||||
*this = Description(name, "DiskII", "the Disk II 13-sector boot ROM", "boot-13.rom", 256, 0xd34eb2ffu);
|
||||
break;
|
||||
case Name::DiskIIStateMachine13Sector:
|
||||
*this = Description(name, "DiskII", "the Disk II 13-sector state machine ROM", "state-machine-13.rom", 256, 0x62e22620u);
|
||||
break;
|
||||
|
||||
case Name::EnterpriseEXOS10: {
|
||||
const std::initializer_list<std::string> filenames = {"exos10.bin", "Exos (198x)(Enterprise).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v1.0", filenames, 32 * 1024, 0x30b26387u);
|
||||
} break;
|
||||
case Name::EnterpriseEXOS20: {
|
||||
const std::initializer_list<std::string> filenames = {"exos20.bin", "Expandible OS v2.0 (1984)(Intelligent Software).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.0", filenames, 32 * 1024, 0xd421795fu);
|
||||
} break;
|
||||
case Name::EnterpriseEXOS21: {
|
||||
const std::initializer_list<std::string> filenames = {"exos21.bin", "Expandible OS v2.1 (1985)(Intelligent Software).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.1", filenames, 32 * 1024, 0x982a3b44u);
|
||||
} break;
|
||||
case Name::EnterpriseEXOS23: {
|
||||
const std::initializer_list<std::string> filenames = {"exos23.bin", "Expandible OS v2.3 (1987)(Intelligent Software).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise EXOS ROM v2.1", filenames, 64 * 1024, 0x24838410u);
|
||||
} break;
|
||||
|
||||
case Name::EnterpriseBASIC10: {
|
||||
const std::initializer_list<std::string> filenames = {"basic10.bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0", filenames, 16 * 1024, 0xd62e4fb7u);
|
||||
} break;
|
||||
case Name::EnterpriseBASIC10Part1: {
|
||||
const std::initializer_list<std::string> filenames = {"BASIC 1.0 - EPROM 1-2 (198x)(Enterprise).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0, Part 1", filenames, 8193, 0x37bf48e1u);
|
||||
} break;
|
||||
case Name::EnterpriseBASIC10Part2: {
|
||||
const std::initializer_list<std::string> filenames = {"BASIC 1.0 - EPROM 2-2 (198x)(Enterprise).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.0, Part 2", filenames, 8193, 0xc5298c79u);
|
||||
} break;
|
||||
case Name::EnterpriseBASIC11: {
|
||||
const std::initializer_list<std::string> filenames = {"basic11.bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.1", filenames, 16 * 1024, 0x683cf455u);
|
||||
} break;
|
||||
case Name::EnterpriseBASIC11Suffixed: {
|
||||
const std::initializer_list<std::string> filenames = {"BASIC 1.1 - EPROM 1.1 (198x)(Enterprise).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v1.1, with trailing byte", filenames, 16385, 0xc96b7602u);
|
||||
} break;
|
||||
case Name::EnterpriseBASIC21: {
|
||||
const std::initializer_list<std::string> filenames = {
|
||||
"basic21.bin",
|
||||
"BASIC Interpreter v2.1 (1985)(Intelligent Software).bin",
|
||||
"BASIC Interpreter v2.1 (1985)(Intelligent Software)[a].bin"
|
||||
};
|
||||
const std::initializer_list<uint32_t> crcs = { 0x55f96251, 0x683cf455 };
|
||||
*this = Description(name, "Enterprise", "the Enterprise BASIC ROM v2.1", filenames, 16 * 1024, crcs);
|
||||
} break;
|
||||
|
||||
case Name::EnterpriseEPDOS: {
|
||||
const std::initializer_list<std::string> filenames = {"epdos.bin", "EPDOS v1.7 (19xx)(Haluska, Laszlo).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise EPDOS ROM", filenames, 32 * 1024, 0x201319ebu);
|
||||
} break;
|
||||
case Name::EnterpriseEXDOS: {
|
||||
const std::initializer_list<std::string> filenames = {"exdos.bin", "EX-DOS EPROM (198x)(Enterprise).bin"};
|
||||
*this = Description(name, "Enterprise", "the Enterprise EXDOS ROM", filenames, 16 * 1024, 0xe6daa0e9u);
|
||||
} break;
|
||||
|
||||
case Name::Macintosh128k: *this = Description(name, "Macintosh", "the Macintosh 128k ROM", "mac128k.rom", 64*1024, 0x6d0c8a28u); break;
|
||||
case Name::Macintosh512k: *this = Description(name, "Macintosh", "the Macintosh 512k ROM", "mac512k.rom", 64*1024, 0xcf759e0d); break;
|
||||
case Name::MacintoshPlus: {
|
||||
const std::initializer_list<uint32_t> crcs = { 0x4fa5b399, 0x7cacd18f, 0xb2102e8e };
|
||||
*this = Description(name, "Macintosh", "the Macintosh Plus ROM", "macplus.rom", 128*1024, crcs);
|
||||
} break;
|
||||
|
||||
case Name::AtariSTTOS100: *this = Description(name, "AtariST", "the UK TOS 1.00 ROM", "tos100.img", 192*1024, 0x1a586c64u); break;
|
||||
case Name::AtariSTTOS104: *this = Description(name, "AtariST", "the UK TOS 1.04 ROM", "tos104.img", 192*1024, 0xa50d1d43u); break;
|
||||
case Name::AtariSTEmuTOS192: *this = Description(name, "AtariST", "the UK EmuTOS 1.92 ROM", "etos192uk.img", 192*1024, 0xfc3b9e61u); break;
|
||||
|
||||
case Name::ColecoVisionBIOS:
|
||||
*this = Description(name, "ColecoVision", "the ColecoVision BIOS", "coleco.rom", 8*1024, 0x3aa93ef3u);
|
||||
break;
|
||||
|
||||
case Name::ZX80: *this = Description(name, "ZX8081", "the ZX80 BASIC ROM", "zx80.rom", 4 * 1024, 0x4c7fc597u); break;
|
||||
case Name::ZX81: *this = Description(name, "ZX8081", "the ZX81 BASIC ROM", "zx81.rom", 8 * 1024, 0x4b1dd6ebu); break;
|
||||
|
||||
case Name::Spectrum48k: *this = Description(name, "ZXSpectrum", "the 48kb ROM", "48.rom", 16 * 1024, 0xddee531fu); break;
|
||||
case Name::Spectrum128k: *this = Description(name, "ZXSpectrum", "the 128kb ROM", "128.rom", 32 * 1024, 0x2cbe8995u); break;
|
||||
case Name::SpecrumPlus2: *this = Description(name, "ZXSpectrum", "the +2 ROM", "plus2.rom", 32 * 1024, 0xe7a517dcu); break;
|
||||
case Name::SpectrumPlus3: {
|
||||
const std::initializer_list<uint32_t> crcs = { 0x96e3c17a, 0xbe0d9ec4 };
|
||||
*this = Description(name, "ZXSpectrum", "the +2a/+3 ROM", "plus3.rom", 64 * 1024, crcs);
|
||||
} break;
|
||||
|
||||
case Name::AcornBASICII: *this = Description(name, "Electron", "the Acorn BASIC II ROM", "basic.rom", 16*1024, 0x79434781u); break;
|
||||
case Name::PRESADFSSlot1: *this = Description(name, "Electron", "the E00 ADFS ROM, first slot", "ADFS-E00_1.rom", 16*1024, 0x51523993u); break;
|
||||
case Name::PRESADFSSlot2: *this = Description(name, "Electron", "the E00 ADFS ROM, second slot", "ADFS-E00_2.rom", 16*1024, 0x8d17de0eu); break;
|
||||
case Name::AcornADFS: *this = Description(name, "Electron", "the Acorn ADFS ROM", "adfs.rom", 16*1024, 0x3289bdc6u); break;
|
||||
case Name::Acorn1770DFS: *this = Description(name, "Electron", "the 1770 DFS ROM", "DFS-1770-2.20.rom", 16*1024, 0xf3dc9bc5u); break;
|
||||
case Name::PRESAdvancedPlus6:
|
||||
*this = Description(name, "Electron", "the 8kb Advanced Plus 6 ROM", "AP6v133.rom", 8*1024, 0xe0013cfcu);
|
||||
break;
|
||||
case Name::AcornElectronMOS100:
|
||||
*this = Description(name, "Electron", "the Electron MOS ROM v1.00", "os.rom", 16*1024, 0xbf63fb1fu);
|
||||
break;
|
||||
|
||||
case Name::MasterSystemJapaneseBIOS: *this = Description(name, "MasterSystem", "the Japanese Master System BIOS", "japanese-bios.sms", 8*1024, 0x48d44a13u); break;
|
||||
case Name::MasterSystemWesternBIOS: *this = Description(name, "MasterSystem", "the European/US Master System BIOS", "bios.sms", 8*1024, 0x0072ed54u); break;
|
||||
|
||||
case Name::Commodore1540: *this = Description(name, "Commodore1540", "the 1540 ROM", "1540.bin", 16*1024, 0x718d42b1u); break;
|
||||
case Name::Commodore1541: *this = Description(name, "Commodore1540", "the 1541 ROM", "1541.bin", 16*1024, 0xfb760019); break;
|
||||
|
||||
case Name::Vic20BASIC: *this = Description(name, "Vic20", "the VIC-20 BASIC ROM", "basic.bin", 8*1024, 0xdb4c43c1u); break;
|
||||
case Name::Vic20EnglishCharacters: *this = Description(name, "Vic20", "the English-language VIC-20 character ROM", "characters-english.bin", 4*1024, 0x83e032a6u); break;
|
||||
case Name::Vic20EnglishPALKernel: *this = Description(name, "Vic20", "the English-language PAL VIC-20 kernel ROM", "kernel-pal.bin", 8*1024, 0x4be07cb4u); break;
|
||||
case Name::Vic20EnglishNTSCKernel: *this = Description(name, "Vic20", "the English-language NTSC VIC-20 kernel ROM", "kernel-ntsc.bin", 8*1024, 0xe5e7c174u); break;
|
||||
case Name::Vic20DanishCharacters: *this = Description(name, "Vic20", "the Danish VIC-20 character ROM", "characters-danish.bin", 4*1024, 0x7fc11454u); break;
|
||||
case Name::Vic20DanishKernel: *this = Description(name, "Vic20", "the Danish VIC-20 kernel ROM", "kernel-danish.bin", 8*1024, 0x02adaf16u); break;
|
||||
case Name::Vic20JapaneseCharacters: *this = Description(name, "Vic20", "the Japanese VIC-20 character ROM", "characters-japanese.bin", 4*1024, 0xfcfd8a4bu); break;
|
||||
case Name::Vic20JapaneseKernel: *this = Description(name, "Vic20", "the Japanese VIC-20 kernel ROM", "kernel-japanese.bin", 8*1024, 0x336900d7u); break;
|
||||
case Name::Vic20SwedishCharacters: *this = Description(name, "Vic20", "the Swedish VIC-20 character ROM", "characters-swedish.bin", 4*1024, 0xd808551du); break;
|
||||
case Name::Vic20SwedishKernel: *this = Description(name, "Vic20", "the Swedish VIC-20 kernel ROM", "kernel-swedish.bin", 8*1024, 0xb2a60662u); break;
|
||||
|
||||
case Name::OricColourROM: *this = Description(name, "Oric", "the Oric colour ROM", "colour.rom", 128, 0xd50fca65u); break;
|
||||
case Name::OricBASIC10: *this = Description(name, "Oric", "Oric BASIC 1.0", "basic10.rom", 16*1024, 0xf18710b4u); break;
|
||||
case Name::OricBASIC11: *this = Description(name, "Oric", "Oric BASIC 1.1", "basic11.rom", 16*1024, 0xc3a92befu); break;
|
||||
case Name::OricPravetzBASIC: *this = Description(name, "Oric", "Pravetz BASIC", "pravetz.rom", 16*1024, 0x58079502u); break;
|
||||
case Name::OricByteDrive500: *this = Description(name, "Oric", "the Oric Byte Drive 500 ROM", "bd500.rom", 8*1024, 0x61952e34u); break;
|
||||
case Name::OricJasmin: *this = Description(name, "Oric", "the Oric Jasmin ROM", "jasmin.rom", 2*1024, 0x37220e89u); break;
|
||||
case Name::OricMicrodisc: *this = Description(name, "Oric", "the Oric Microdisc ROM", "microdisc.rom", 8*1024, 0xa9664a9cu); break;
|
||||
case Name::Oric8DOSBoot: *this = Description(name, "Oric", "the 8DOS boot ROM", "8dos.rom", 512, 0x49a74c06u); break;
|
||||
|
||||
case Name::MSXGenericBIOS: *this = Description(name, "MSX", "any MSX BIOS", "msx.rom", 32*1024, 0x94ee12f3u); break;
|
||||
case Name::MSXJapaneseBIOS: *this = Description(name, "MSX", "a Japanese MSX BIOS", "msx-japanese.rom", 32*1024, 0xee229390u); break;
|
||||
case Name::MSXAmericanBIOS: *this = Description(name, "MSX", "an American MSX BIOS", "msx-american.rom", 32*1024, 0u); break;
|
||||
case Name::MSXEuropeanBIOS: *this = Description(name, "MSX", "a European MSX BIOS", "msx-european.rom", 32*1024, 0u); break;
|
||||
case Name::MSXDOS: *this = Description(name, "MSX", "the MSX-DOS ROM", "disk.rom", 16*1024, 0x721f61dfu); break;
|
||||
|
||||
case Name::SinclairQLJS:
|
||||
*this = Description(name, "SinclairQL", "the Sinclair QL 'JS' ROM", "js.rom", 48*1024, 0x0f95aab5u);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
289
Machines/Utility/ROMCatalogue.hpp
Normal file
289
Machines/Utility/ROMCatalogue.hpp
Normal file
@@ -0,0 +1,289 @@
|
||||
//
|
||||
// ROMCatalogue.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 01/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#ifndef ROMCatalogue_hpp
|
||||
#define ROMCatalogue_hpp
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ROM {
|
||||
|
||||
enum Name {
|
||||
None,
|
||||
|
||||
// Acorn.
|
||||
AcornBASICII,
|
||||
AcornElectronMOS100,
|
||||
PRESADFSSlot1,
|
||||
PRESADFSSlot2,
|
||||
AcornADFS,
|
||||
PRESAdvancedPlus6,
|
||||
Acorn1770DFS,
|
||||
|
||||
// Amstrad CPC.
|
||||
AMSDOS,
|
||||
CPC464Firmware, CPC464BASIC,
|
||||
CPC664Firmware, CPC664BASIC,
|
||||
CPC6128Firmware, CPC6128BASIC,
|
||||
|
||||
// Apple II.
|
||||
AppleIIOriginal,
|
||||
AppleIIPlus,
|
||||
AppleIICharacter,
|
||||
AppleIIe,
|
||||
AppleIIeCharacter,
|
||||
AppleIIEnhancedE,
|
||||
AppleIIEnhancedECharacter,
|
||||
|
||||
// Apple IIgs.
|
||||
AppleIIgsROM00,
|
||||
AppleIIgsROM01,
|
||||
AppleIIgsROM03,
|
||||
AppleIIgsMicrocontrollerROM03,
|
||||
AppleIIgsCharacter,
|
||||
|
||||
// Atari ST.
|
||||
AtariSTTOS100,
|
||||
AtariSTTOS104,
|
||||
AtariSTEmuTOS192,
|
||||
|
||||
// ColecoVision.
|
||||
ColecoVisionBIOS,
|
||||
|
||||
// Commodore 1540/1541.
|
||||
Commodore1540,
|
||||
Commodore1541,
|
||||
|
||||
// Disk II.
|
||||
DiskIIStateMachine16Sector,
|
||||
DiskIIBoot16Sector,
|
||||
DiskIIStateMachine13Sector,
|
||||
DiskIIBoot13Sector,
|
||||
|
||||
// Enterprise.
|
||||
EnterpriseEXOS10,
|
||||
EnterpriseEXOS20,
|
||||
EnterpriseEXOS21,
|
||||
EnterpriseEXOS23,
|
||||
|
||||
EnterpriseBASIC10,
|
||||
EnterpriseBASIC10Part1,
|
||||
EnterpriseBASIC10Part2,
|
||||
EnterpriseBASIC11,
|
||||
EnterpriseBASIC11Suffixed,
|
||||
EnterpriseBASIC21,
|
||||
|
||||
EnterpriseEPDOS,
|
||||
EnterpriseEXDOS,
|
||||
|
||||
// Macintosh.
|
||||
Macintosh128k,
|
||||
Macintosh512k,
|
||||
MacintoshPlus,
|
||||
|
||||
// Master System.
|
||||
MasterSystemJapaneseBIOS,
|
||||
MasterSystemWesternBIOS,
|
||||
|
||||
// MSX.
|
||||
MSXGenericBIOS,
|
||||
MSXJapaneseBIOS,
|
||||
MSXAmericanBIOS,
|
||||
MSXEuropeanBIOS,
|
||||
MSXDOS,
|
||||
|
||||
// Oric.
|
||||
OricColourROM,
|
||||
OricBASIC10,
|
||||
OricBASIC11,
|
||||
OricPravetzBASIC,
|
||||
OricByteDrive500,
|
||||
OricJasmin,
|
||||
OricMicrodisc,
|
||||
Oric8DOSBoot,
|
||||
|
||||
// Sinclair QL.
|
||||
SinclairQLJS,
|
||||
|
||||
// Vic-20.
|
||||
Vic20BASIC,
|
||||
Vic20EnglishCharacters,
|
||||
Vic20EnglishPALKernel,
|
||||
Vic20EnglishNTSCKernel,
|
||||
Vic20DanishCharacters,
|
||||
Vic20DanishKernel,
|
||||
Vic20JapaneseCharacters,
|
||||
Vic20JapaneseKernel,
|
||||
Vic20SwedishCharacters,
|
||||
Vic20SwedishKernel,
|
||||
|
||||
// ZX80/81.
|
||||
ZX80,
|
||||
ZX81,
|
||||
|
||||
// ZX Spectrum.
|
||||
Spectrum48k,
|
||||
Spectrum128k,
|
||||
SpecrumPlus2,
|
||||
SpectrumPlus3,
|
||||
|
||||
};
|
||||
|
||||
using Map = std::map<ROM::Name, std::vector<uint8_t>>;
|
||||
|
||||
struct Description {
|
||||
/// The ROM's enum name.
|
||||
Name name = Name::None;
|
||||
/// The machine with which this ROM is associated, in a form that is safe for using as
|
||||
/// part of a file name.
|
||||
std::string machine_name;
|
||||
/// A descriptive name for this ROM, suitable for use in a bullet-point list, a bracket
|
||||
/// clause, etc, e.g. "the Electron MOS 1.0".
|
||||
std::string descriptive_name;
|
||||
/// All idiomatic file name for this ROM, e.g. "os10.rom".
|
||||
std::vector<std::string> file_names;
|
||||
/// The expected size of this ROM in bytes, e.g. 32768.
|
||||
size_t size = 0;
|
||||
/// CRC32s for all known acceptable copies of this ROM; intended to allow a host platform
|
||||
/// to test user-provided ROMs of unknown provenance. **Not** intended to be used
|
||||
/// to exclude ROMs where the user's intent is otherwise clear.
|
||||
std::set<uint32_t> crc32s;
|
||||
|
||||
/// Constructs the @c Description that correlates to @c name.
|
||||
Description(Name name);
|
||||
|
||||
/// Constructs the @c Description that correlates to @c crc32, if any.
|
||||
static std::optional<Description> from_crc(uint32_t crc32);
|
||||
|
||||
enum DescriptionFlag {
|
||||
Size = 1 << 0,
|
||||
CRC = 1 << 1,
|
||||
Filename = 1 << 2,
|
||||
};
|
||||
|
||||
/// Provides a single-line of text describing this ROM, including the usual base text
|
||||
/// plus all the fields provided as @c flags .
|
||||
std::string description(int flags) const;
|
||||
|
||||
private:
|
||||
template <typename FileNameT, typename CRC32T> Description(
|
||||
Name name, std::string machine_name, std::string descriptive_name, FileNameT file_names, size_t size, CRC32T crc32s = uint32_t(0)
|
||||
) : name{name}, machine_name{machine_name}, descriptive_name{descriptive_name}, file_names{file_names}, size{size}, crc32s{crc32s} {
|
||||
// Slightly lazy: deal with the case where the constructor wasn't provided with any
|
||||
// CRCs by spotting that the set has exactly one member, which has value 0. The alternative
|
||||
// would be to provide a partial specialisation that never put anything into the set.
|
||||
if(this->crc32s.size() == 1 && !*this->crc32s.begin()) {
|
||||
this->crc32s.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// @returns a vector of all possible instances of ROM::Description — i.e. descriptions of every ROM
|
||||
/// currently known to the ROM catalogue.
|
||||
std::vector<Description> all_descriptions();
|
||||
|
||||
struct Request {
|
||||
Request(Name name, bool optional = false);
|
||||
Request() {}
|
||||
|
||||
/// Forms the request that would be satisfied by @c this plus the right-hand side.
|
||||
Request operator &&(const Request &);
|
||||
|
||||
/// Forms the request that would be satisfied by either @c this or the right-hand side.
|
||||
Request operator ||(const Request &);
|
||||
|
||||
/// Inspects the ROMMap to ensure that it satisfies this @c Request.
|
||||
/// @c returns @c true if the request is satisfied; @c false otherwise.
|
||||
///
|
||||
/// All ROMs in the map will be resized to their idiomatic sizes.
|
||||
bool validate(Map &) const;
|
||||
|
||||
/// Returns a flattened array of all @c ROM::Descriptions that relate to anything
|
||||
/// anywhere in this ROM request.
|
||||
std::vector<Description> all_descriptions() const;
|
||||
|
||||
/// @returns @c true if this request is empty, i.e. would be satisfied with no ROMs; @c false otherwise.
|
||||
bool empty();
|
||||
|
||||
/// @returns what remains of this ROM request given that everything in @c map has been found.
|
||||
Request subtract(const ROM::Map &map) const;
|
||||
|
||||
enum class ListType {
|
||||
Any, All, Single
|
||||
};
|
||||
void visit(
|
||||
const std::function<void(ListType, size_t size)> &enter_list,
|
||||
const std::function<void(void)> &exit_list,
|
||||
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
|
||||
) const;
|
||||
|
||||
enum class LineItem {
|
||||
NewList, Description
|
||||
};
|
||||
void visit(
|
||||
const std::function<void(LineItem, ListType, int level, const ROM::Description *, bool is_optional, size_t remaining)> &add_item
|
||||
) const;
|
||||
|
||||
/// @returns a full bullet-pointed list of the requirements of this request, including
|
||||
/// appropriate conjuntives. This text is intended to be glued to the end of an opening
|
||||
/// portion of a sentence, e.g. "Please supply" + request.description(0, L'*').
|
||||
std::wstring description(int description_flags, wchar_t bullet_point);
|
||||
|
||||
private:
|
||||
struct Node {
|
||||
enum class Type {
|
||||
Any, All, One
|
||||
};
|
||||
Type type = Type::One;
|
||||
Name name = Name::None;
|
||||
/// @c true if this ROM is optional for machine startup. Generally indicates something
|
||||
/// that would make emulation more accurate, but not sufficiently so to make it
|
||||
/// a necessity.
|
||||
bool is_optional = false;
|
||||
std::vector<Node> children;
|
||||
|
||||
bool empty() const {
|
||||
return type == Type::One && name == Name::None;
|
||||
}
|
||||
|
||||
void add_descriptions(std::vector<Description> &) const;
|
||||
bool validate(Map &) const;
|
||||
void visit(
|
||||
const std::function<void(ListType, size_t)> &enter_list,
|
||||
const std::function<void(void)> &exit_list,
|
||||
const std::function<void(ROM::Request::ListType type, const ROM::Description &, bool is_optional, size_t remaining)> &add_item
|
||||
) const;
|
||||
bool subtract(const ROM::Map &map);
|
||||
void sort() {
|
||||
// Don't do a full sort, but move anything optional to the back.
|
||||
// This makes them print more nicely; it's a human-facing tweak only.
|
||||
ssize_t index = ssize_t(children.size() - 1);
|
||||
bool has_seen_non_optional = false;
|
||||
while(index >= 0) {
|
||||
has_seen_non_optional |= !children[size_t(index)].is_optional;
|
||||
if(children[size_t(index)].is_optional && has_seen_non_optional) {
|
||||
std::rotate(children.begin() + index, children.begin() + index + 1, children.end());
|
||||
}
|
||||
--index;
|
||||
}
|
||||
}
|
||||
};
|
||||
Node node;
|
||||
|
||||
Request append(Node::Type type, const Request &rhs);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif /* ROMCatalogue_hpp */
|
||||
@@ -131,13 +131,3 @@ bool Typer::type_next_character() {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: - Character mapper
|
||||
|
||||
const uint16_t *CharacterMapper::table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const {
|
||||
std::size_t ucharacter = size_t((unsigned char)character);
|
||||
if(ucharacter >= (length / sizeof(KeySequence))) return nullptr;
|
||||
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
|
||||
return sequences[ucharacter];
|
||||
}
|
||||
|
||||
|
||||
@@ -44,11 +44,15 @@ class CharacterMapper {
|
||||
typedef uint16_t KeySequence[16];
|
||||
|
||||
/*!
|
||||
Provided in the base class as a convenience: given the lookup table of key sequences @c sequences,
|
||||
with @c length entries, returns the sequence for character @c character if it exists; otherwise
|
||||
returns @c nullptr.
|
||||
Provided in the base class as a convenience: given the C array of key sequences @c sequences,
|
||||
returns the sequence for character @c character if it exists; otherwise returns @c nullptr.
|
||||
*/
|
||||
const uint16_t *table_lookup_sequence_for_character(const KeySequence *sequences, std::size_t length, char character) const;
|
||||
template <typename Collection> const uint16_t *table_lookup_sequence_for_character(const Collection &sequences, char character) const {
|
||||
std::size_t ucharacter = size_t((unsigned char)character);
|
||||
if(ucharacter >= sizeof(sequences) / sizeof(KeySequence)) return nullptr;
|
||||
if(sequences[ucharacter][0] == MachineTypes::MappedKeyboardMachine::KeyNotMapped) return nullptr;
|
||||
return sequences[ucharacter];
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "Sizes.hpp"
|
||||
|
||||
namespace Numeric {
|
||||
|
||||
template <typename IntType> struct LSFRPolynomial {};
|
||||
@@ -67,8 +69,8 @@ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntTy
|
||||
determining the bit that was just shifted out.
|
||||
*/
|
||||
IntType next() {
|
||||
const auto result = value_ & 1;
|
||||
value_ = (value_ >> 1) ^ (result * polynomial);
|
||||
const auto result = IntType(value_ & 1);
|
||||
value_ = IntType((value_ >> 1) ^ (result * polynomial));
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -76,6 +78,8 @@ template <typename IntType = uint64_t, IntType polynomial = LSFRPolynomial<IntTy
|
||||
IntType value_ = 0;
|
||||
};
|
||||
|
||||
template <uint64_t polynomial> class LFSRv: public LFSR<typename MinIntTypeValue<polynomial>::type, polynomial> {};
|
||||
|
||||
}
|
||||
|
||||
#endif /* LFSR_h */
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#define Sizes_h
|
||||
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
/*!
|
||||
Maps to the smallest integral type that can contain max_value, from the following options:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "1240"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -56,6 +56,18 @@
|
||||
argument = "/Users/thomasharte/Downloads/test-dsk-for-rw-and-50-60-hz/TEST-RW-60Hz.DSK"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Desktop/Soft/Apple II/WOZs/Prince of Persia side A.woz""
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--rompath=~/ROMs"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--has-disk-drive"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Macintosh/MusicWorks 0.42.image""
|
||||
isEnabled = "NO">
|
||||
@@ -70,11 +82,15 @@
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--volume=0.001"
|
||||
isEnabled = "NO">
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--new=amstradcpc"
|
||||
isEnabled = "NO">
|
||||
argument = "--new=enterprise"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--basic-version=any"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/ColecoVision/Galaxian (1983)(Atari).col""
|
||||
@@ -106,14 +122,22 @@
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--rompath=/Users/thomasharte/Projects/CLK/ROMImages"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--help"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--model=cpc6128"
|
||||
argument = "--help"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--model=mac512k"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--new=macintosh"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--use-square-pixels"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "1240"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -73,6 +73,7 @@
|
||||
debugDocumentVersioning = "YES"
|
||||
migratedStopOnEveryIssue = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUShaderValidationMode = "2"
|
||||
allowLocationSimulation = "NO">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1200"
|
||||
LastUpgradeVersion = "1240"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -11,12 +11,24 @@ import Cocoa
|
||||
@NSApplicationMain
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
||||
func applicationDidFinishLaunching(_ aNotification: Notification) {
|
||||
private var failedMetalCheck = false
|
||||
func applicationDidFinishLaunching(_ notification: Notification) {
|
||||
// Insert code here to initialize your application.
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ aNotification: Notification) {
|
||||
// Insert code here to tear down your application.
|
||||
// Check for at least one Metal-capable GPU; this check
|
||||
// will become unnecessary if/when the minimum OS version
|
||||
// that this project supports reascends to 10.14.
|
||||
if (MTLCopyAllDevices().count == 0) {
|
||||
self.failedMetalCheck = true
|
||||
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "This application requires a Metal-capable GPU"
|
||||
alert.addButton(withTitle: "Exit")
|
||||
alert.runModal()
|
||||
|
||||
let application = notification.object as! NSApplication
|
||||
application.terminate(self)
|
||||
}
|
||||
}
|
||||
|
||||
private var hasShownOpenDocument = false
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14460.31"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -13,17 +13,27 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="MachinePanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="ZW7-Bw-4RP" customClass="AppleIIOptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="61"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="900"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="159"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1440"/>
|
||||
<view key="contentView" id="tpZ-0B-QQu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="61"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="84"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="kDb-7g-cVx">
|
||||
<rect key="frame" x="18" y="47" width="162" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Use Square Pixels" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="h9q-Wb-em8">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="optionDidChange:" target="ZW7-Bw-4RP" id="pNS-aK-0no"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ex3-VM-58z">
|
||||
<rect key="frame" x="18" y="17" width="165" height="25"/>
|
||||
<rect key="frame" x="17" y="16" width="167" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Colour" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="1" imageScaling="proportionallyDown" inset="2" selectedItem="gOu-dv-tre" id="u3N-Je-c2L">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -40,16 +50,20 @@
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="bottom" secondItem="ex3-VM-58z" secondAttribute="bottom" constant="20" id="4ZS-q5-TJL"/>
|
||||
<constraint firstAttribute="bottom" secondItem="ex3-VM-58z" secondAttribute="bottom" constant="20" symbolic="YES" id="4ZS-q5-TJL"/>
|
||||
<constraint firstItem="ex3-VM-58z" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" id="8Pj-Ns-TrJ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="kDb-7g-cVx" secondAttribute="trailing" constant="20" symbolic="YES" id="KHa-of-eY7"/>
|
||||
<constraint firstItem="kDb-7g-cVx" firstAttribute="leading" secondItem="tpZ-0B-QQu" secondAttribute="leading" constant="20" symbolic="YES" id="OcZ-Xa-394"/>
|
||||
<constraint firstAttribute="trailing" secondItem="ex3-VM-58z" secondAttribute="trailing" constant="20" id="QWA-lY-ugz"/>
|
||||
<constraint firstItem="ex3-VM-58z" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" id="szw-WO-3tS"/>
|
||||
<constraint firstItem="ex3-VM-58z" firstAttribute="top" secondItem="kDb-7g-cVx" secondAttribute="bottom" constant="8" id="jDW-N8-c4V"/>
|
||||
<constraint firstItem="kDb-7g-cVx" firstAttribute="top" secondItem="tpZ-0B-QQu" secondAttribute="top" constant="20" symbolic="YES" id="wdj-PF-zxO"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="displayTypeButton" destination="ex3-VM-58z" id="lmZ-aN-lcj"/>
|
||||
<outlet property="squarePixelButton" destination="kDb-7g-cVx" id="zbx-o8-7yC"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="-161" y="38.5"/>
|
||||
<point key="canvasLocation" x="-161" y="104.5"/>
|
||||
</window>
|
||||
</objects>
|
||||
</document>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -13,17 +13,17 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<window title="Options" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="" animationBehavior="default" id="gsl-7V-TTU" customClass="Atari2600OptionsPanel" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" utility="YES" nonactivatingPanel="YES" HUD="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="80" y="150" width="200" height="121"/>
|
||||
<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" id="aQh-Pm-DEo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="200" height="121"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rQO-uD-fwn">
|
||||
<rect key="frame" x="14" y="73" width="86" height="32"/>
|
||||
<rect key="frame" x="13" y="74" width="88" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Reset" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="l3H-0m-aK0">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -33,7 +33,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="3qw-C1-NYW">
|
||||
<rect key="frame" x="18" y="58" width="164" height="18"/>
|
||||
<rect key="frame" x="18" y="58" width="162" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Black and White" bezelStyle="regularSquare" imagePosition="left" inset="2" id="UP7-mf-IKo">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -43,7 +43,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="Xbc-cw-Sc2">
|
||||
<rect key="frame" x="18" y="38" width="164" height="18"/>
|
||||
<rect key="frame" x="18" y="36" width="162" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Left Player Difficulty" bezelStyle="regularSquare" imagePosition="left" inset="2" id="wlJ-8s-PEh">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -53,7 +53,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="kPV-Tm-TTc">
|
||||
<rect key="frame" x="18" y="18" width="164" height="18"/>
|
||||
<rect key="frame" x="18" y="14" width="162" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Right Player Difficulty" bezelStyle="regularSquare" imagePosition="left" inset="2" id="F05-cA-66S">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -63,7 +63,7 @@
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nt7-8K-xY9">
|
||||
<rect key="frame" x="100" y="73" width="86" height="32"/>
|
||||
<rect key="frame" x="99" y="74" width="88" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Select" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="8Na-Z1-EXS">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// AppleIIOptionsPanel.swift
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2021.
|
||||
// Copyright 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
class AppleIIOptionsPanel: MachinePanel {
|
||||
var appleII: CSAppleII! {
|
||||
get {
|
||||
return self.machine.appleII
|
||||
}
|
||||
}
|
||||
var squarePixelsUserDefaultsKey: String {
|
||||
return prefixedUserDefaultsKey("useSquarePixels")
|
||||
}
|
||||
|
||||
@IBOutlet var squarePixelButton: NSButton!
|
||||
|
||||
@IBAction func optionDidChange(_ sender: AnyObject!) {
|
||||
let useSquarePixels = squarePixelButton.state == .on
|
||||
appleII.useSquarePixels = useSquarePixels
|
||||
|
||||
let standardUserDefaults = UserDefaults.standard
|
||||
standardUserDefaults.set(useSquarePixels, forKey: squarePixelsUserDefaultsKey)
|
||||
}
|
||||
|
||||
override func establishStoredOptions() {
|
||||
super.establishStoredOptions()
|
||||
|
||||
let standardUserDefaults = UserDefaults.standard
|
||||
let useSquarePixels = standardUserDefaults.bool(forKey: squarePixelsUserDefaultsKey)
|
||||
appleII.useSquarePixels = useSquarePixels
|
||||
squarePixelButton.state = useSquarePixels ? .on : .off
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,14 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
private func dismissPanels() {
|
||||
activityPanel?.setIsVisible(false)
|
||||
activityPanel = nil
|
||||
|
||||
optionsPanel?.setIsVisible(false)
|
||||
optionsPanel = nil
|
||||
}
|
||||
|
||||
override func close() {
|
||||
// Close any dangling sheets.
|
||||
//
|
||||
@@ -105,11 +113,7 @@ class MachineDocument:
|
||||
machine?.stop()
|
||||
|
||||
// Dismiss panels.
|
||||
activityPanel?.setIsVisible(false)
|
||||
activityPanel = nil
|
||||
|
||||
optionsPanel?.setIsVisible(false)
|
||||
optionsPanel = nil
|
||||
dismissPanels()
|
||||
|
||||
// End the update cycle.
|
||||
actionLock.lock()
|
||||
@@ -133,23 +137,27 @@ class MachineDocument:
|
||||
volumeSlider.floatValue = userDefaultsVolume()
|
||||
}
|
||||
|
||||
private var missingROMs: [CSMissingROM] = []
|
||||
private var missingROMs: String = ""
|
||||
func configureAs(_ analysis: CSStaticAnalyser) {
|
||||
self.machineDescription = analysis
|
||||
|
||||
let missingROMs = NSMutableArray()
|
||||
actionLock.lock()
|
||||
drawLock.lock()
|
||||
|
||||
let missingROMs = NSMutableString()
|
||||
if let machine = CSMachine(analyser: analysis, missingROMs: missingROMs) {
|
||||
setRomRequesterIsVisible(false)
|
||||
|
||||
self.machine = machine
|
||||
setupActivityDisplay()
|
||||
machine.setVolume(userDefaultsVolume())
|
||||
setupMachineOutput()
|
||||
} else {
|
||||
// Store the selected machine and list of missing ROMs, and
|
||||
// show the missing ROMs dialogue.
|
||||
self.missingROMs = missingROMs.map({$0 as! CSMissingROM})
|
||||
|
||||
self.missingROMs = missingROMs as String
|
||||
requestRoms()
|
||||
}
|
||||
|
||||
actionLock.unlock()
|
||||
drawLock.unlock()
|
||||
}
|
||||
|
||||
enum InteractionMode {
|
||||
@@ -200,6 +208,9 @@ class MachineDocument:
|
||||
let aspectRatio = self.aspectRatio()
|
||||
machine.setView(scanTargetView, aspectRatio: Float(aspectRatio.width / aspectRatio.height))
|
||||
|
||||
// Get rid of all existing accessory panels.
|
||||
dismissPanels()
|
||||
|
||||
// Attach an options panel if one is available.
|
||||
if let optionsPanelNibName = self.machineDescription?.optionsPanelNibName {
|
||||
Bundle.main.loadNibNamed(optionsPanelNibName, owner: self, topLevelObjects: nil)
|
||||
@@ -208,11 +219,10 @@ class MachineDocument:
|
||||
showOptions(self)
|
||||
}
|
||||
|
||||
machine.delegate = self
|
||||
// Create and populate an activity display if required.
|
||||
setupActivityDisplay()
|
||||
|
||||
// Callbacks from the OpenGL may come on a different thread, immediately following the .delegate set;
|
||||
// hence the full setup of the best-effort updater prior to setting self as a delegate.
|
||||
// scanTargetView.delegate = self
|
||||
machine.delegate = self
|
||||
scanTargetView.responderDelegate = self
|
||||
|
||||
// If this machine has a mouse, enable mouse capture; also indicate whether usurption
|
||||
@@ -252,7 +262,7 @@ class MachineDocument:
|
||||
let isStereo = self.machine.isStereo
|
||||
if selectedSamplingRate > 0 {
|
||||
// [Re]create the audio queue only if necessary.
|
||||
if self.audioQueue == nil || self.audioQueue.samplingRate != selectedSamplingRate {
|
||||
if self.audioQueue == nil || self.audioQueue.samplingRate != selectedSamplingRate || self.audioQueue != self.machine.audioQueue {
|
||||
self.machine.audioQueue = nil
|
||||
self.audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate), isStereo:isStereo)
|
||||
self.audioQueue.delegate = self
|
||||
@@ -280,8 +290,7 @@ class MachineDocument:
|
||||
|
||||
/// Delegate message to receive drag and drop files.
|
||||
final func scanTargetView(_ view: CSScanTargetView, didReceiveFileAt URL: URL) {
|
||||
let mediaSet = CSMediaSet(fileAt: URL)
|
||||
mediaSet.apply(to: self.machine)
|
||||
insertFile(URL)
|
||||
}
|
||||
|
||||
/// Action for the insert menu command; displays an NSOpenPanel and then segues into the same process
|
||||
@@ -292,13 +301,30 @@ class MachineDocument:
|
||||
openPanel.beginSheetModal(for: self.windowControllers[0].window!) { (response) in
|
||||
if response == .OK {
|
||||
for url in openPanel.urls {
|
||||
let mediaSet = CSMediaSet(fileAt: url)
|
||||
mediaSet.apply(to: self.machine)
|
||||
self.insertFile(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func insertFile(_ URL: URL) {
|
||||
// Try to insert media.
|
||||
let mediaSet = CSMediaSet(fileAt: URL)
|
||||
if !mediaSet.empty {
|
||||
mediaSet.apply(to: self.machine)
|
||||
return
|
||||
}
|
||||
|
||||
// Failing that see whether a new machine is required.
|
||||
// TODO.
|
||||
if let newMachine = CSStaticAnalyser(fileAt: URL) {
|
||||
machine?.stop()
|
||||
self.interactionMode = .notStarted
|
||||
self.scanTargetView.willChangeScanTargetOwner()
|
||||
configureAs(newMachine)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Input Management.
|
||||
|
||||
/// Upon a resign key, immediately releases all ongoing input mechanisms — any currently pressed keys,
|
||||
@@ -387,23 +413,42 @@ class MachineDocument:
|
||||
@IBOutlet var romReceiverErrorField: NSTextField?
|
||||
@IBOutlet var romReceiverView: CSROMReceiverView?
|
||||
private var romRequestBaseText = ""
|
||||
|
||||
private func setRomRequesterIsVisible(_ visible : Bool) {
|
||||
if !visible && self.romRequesterPanel == nil {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.romRequesterPanel!.isVisible == visible {
|
||||
return
|
||||
}
|
||||
|
||||
if visible {
|
||||
self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil)
|
||||
} else {
|
||||
self.windowControllers[0].window?.endSheet(self.romRequesterPanel!)
|
||||
}
|
||||
}
|
||||
|
||||
func requestRoms() {
|
||||
// Don't act yet if there's no window controller yet.
|
||||
if self.windowControllers.count == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Load the ROM requester dialogue.
|
||||
Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil)
|
||||
self.romReceiverView!.delegate = self
|
||||
self.romRequestBaseText = romRequesterText!.stringValue
|
||||
romReceiverErrorField?.alphaValue = 0.0
|
||||
// Load the ROM requester dialogue if it's not already loaded.
|
||||
if self.romRequesterPanel == nil {
|
||||
Bundle.main.loadNibNamed("ROMRequester", owner: self, topLevelObjects: nil)
|
||||
self.romReceiverView!.delegate = self
|
||||
self.romRequestBaseText = romRequesterText!.stringValue
|
||||
romReceiverErrorField?.alphaValue = 0.0
|
||||
}
|
||||
|
||||
// Populate the current absentee list.
|
||||
populateMissingRomList()
|
||||
|
||||
// Show the thing.
|
||||
self.windowControllers[0].window?.beginSheet(self.romRequesterPanel!, completionHandler: nil)
|
||||
setRomRequesterIsVisible(true)
|
||||
}
|
||||
|
||||
@IBAction func cancelRequestROMs(_ sender: NSButton?) {
|
||||
@@ -411,89 +456,17 @@ class MachineDocument:
|
||||
}
|
||||
|
||||
func populateMissingRomList() {
|
||||
// Fill in the missing details; first build a list of all the individual
|
||||
// line items.
|
||||
var requestLines: [String] = []
|
||||
for missingROM in self.missingROMs {
|
||||
if let descriptiveName = missingROM.descriptiveName {
|
||||
requestLines.append("• " + descriptiveName)
|
||||
} else {
|
||||
requestLines.append("• " + missingROM.fileName)
|
||||
}
|
||||
}
|
||||
|
||||
// Suffix everything up to the penultimate line with a semicolon;
|
||||
// the penultimate line with a semicolon and a conjunctive; the final
|
||||
// line with a full stop.
|
||||
for x in 0 ..< requestLines.count {
|
||||
if x < requestLines.count - 2 {
|
||||
requestLines[x].append(";")
|
||||
} else if x < requestLines.count - 1 {
|
||||
requestLines[x].append("; and")
|
||||
} else {
|
||||
requestLines[x].append(".")
|
||||
}
|
||||
}
|
||||
romRequesterText!.stringValue = self.romRequestBaseText + requestLines.joined(separator: "\n")
|
||||
romRequesterText!.stringValue = self.romRequestBaseText + self.missingROMs
|
||||
}
|
||||
|
||||
func romReceiverView(_ view: CSROMReceiverView, didReceiveFileAt URL: URL) {
|
||||
// Test whether the file identified matches any of the currently missing ROMs.
|
||||
// If so then remove that ROM from the missing list and update the request screen.
|
||||
// If no ROMs are still missing, start the machine.
|
||||
do {
|
||||
let fileData = try Data(contentsOf: URL)
|
||||
var didInstallRom = false
|
||||
|
||||
// Try to match by size first, CRC second. Accept that some ROMs may have
|
||||
// some additional appended data. Arbitrarily allow them to be up to 10kb
|
||||
// too large.
|
||||
var index = 0
|
||||
for missingROM in self.missingROMs {
|
||||
if fileData.count >= missingROM.size && fileData.count < missingROM.size + 10*1024 {
|
||||
// Trim to size.
|
||||
let trimmedData = fileData[0 ..< missingROM.size]
|
||||
|
||||
// Get CRC.
|
||||
if missingROM.crc32s.contains( (trimmedData as NSData).crc32 ) {
|
||||
// This ROM matches; copy it into the application library,
|
||||
// strike it from the missing ROM list and decide how to
|
||||
// proceed.
|
||||
let fileManager = FileManager.default
|
||||
let targetPath = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent("ROMImages")
|
||||
.appendingPathComponent(missingROM.machineName)
|
||||
let targetFile = targetPath
|
||||
.appendingPathComponent(missingROM.fileName)
|
||||
|
||||
do {
|
||||
try fileManager.createDirectory(atPath: targetPath.path, withIntermediateDirectories: true, attributes: nil)
|
||||
try trimmedData.write(to: targetFile)
|
||||
} catch let error {
|
||||
showRomReceiverError(error: "Couldn't write to application support directory: \(error)")
|
||||
}
|
||||
|
||||
self.missingROMs.remove(at: index)
|
||||
didInstallRom = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
index = index + 1
|
||||
}
|
||||
|
||||
if didInstallRom {
|
||||
if self.missingROMs.count == 0 {
|
||||
self.windowControllers[0].window?.endSheet(self.romRequesterPanel!)
|
||||
configureAs(self.machineDescription!)
|
||||
} else {
|
||||
populateMissingRomList()
|
||||
}
|
||||
} else {
|
||||
showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)")
|
||||
}
|
||||
} catch let error {
|
||||
showRomReceiverError(error: "Couldn't read file at \(URL.absoluteString): \(error)")
|
||||
if CSMachine.attemptInstallROM(URL) {
|
||||
configureAs(self.machineDescription!)
|
||||
} else {
|
||||
showRomReceiverError(error: "Didn't recognise contents of \(URL.lastPathComponent)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -592,6 +592,66 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>sna</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX Spectrum SNA snapshot</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>z80</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX Spectrum Z80 snapshot</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>szx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ZX Spectrum SZX snapshot</string>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>????</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<false/>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
|
||||
@@ -368,7 +368,7 @@ API_AVAILABLE(macos(11.0))
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)update {
|
||||
- (void)update {
|
||||
// Update buttons.
|
||||
for(CSGCJoystickButton *button in _buttons) {
|
||||
// This assumes that the values provided by GCDeviceButtonInput are
|
||||
@@ -379,7 +379,11 @@ API_AVAILABLE(macos(11.0))
|
||||
float val = axis.axis.value;
|
||||
val += 1;
|
||||
val /= 2;
|
||||
axis.position = val;
|
||||
if(axis.type == CSJoystickAxisTypeY) {
|
||||
axis.position = 1 - val;
|
||||
} else {
|
||||
axis.position = val;
|
||||
}
|
||||
}
|
||||
for(CSGCJoystickHat *hat in _hats) {
|
||||
// This assumes that the values provided by GCDeviceDirectionPad are
|
||||
|
||||
@@ -33,20 +33,15 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
CSMachineKeyboardInputModeJoystick,
|
||||
};
|
||||
|
||||
@interface CSMissingROM: NSObject
|
||||
@property (nonatomic, readonly, nonnull) NSString *machineName;
|
||||
@property (nonatomic, readonly, nonnull) NSString *fileName;
|
||||
@property (nonatomic, readonly, nullable) NSString *descriptiveName;
|
||||
@property (nonatomic, readonly) NSUInteger size;
|
||||
@property (nonatomic, readonly, nonnull) NSArray<NSNumber *> *crc32s;
|
||||
@end
|
||||
|
||||
// Deliberately low; to ensure CSMachine has been declared as an @class already.
|
||||
#import "CSAtari2600.h"
|
||||
#import "CSZX8081.h"
|
||||
#import "CSAppleII.h"
|
||||
|
||||
@interface CSMachine : NSObject
|
||||
|
||||
+ (BOOL)attemptInstallROM:(nonnull NSURL *)url;
|
||||
|
||||
- (nonnull instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/*!
|
||||
@@ -56,7 +51,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
@param missingROMs An array that is filled with a list of ROMs that the machine requested but which
|
||||
were not found; populated only if this `init` has failed.
|
||||
*/
|
||||
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableArray<CSMissingROM *> *)missingROMs NS_DESIGNATED_INITIALIZER;
|
||||
- (nullable instancetype)initWithAnalyser:(nonnull CSStaticAnalyser *)result missingROMs:(nullable inout NSMutableString *)missingROMs NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (float)idealSamplingRateFromRange:(NSRange)range;
|
||||
@property (readonly, getter=isStereo) BOOL stereo;
|
||||
@@ -109,5 +104,6 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
// Special-case accessors; undefined behaviour if accessed for a machine not of the corresponding type.
|
||||
@property (nonatomic, readonly, nullable) CSAtari2600 *atari2600;
|
||||
@property (nonatomic, readonly, nullable) CSZX8081 *zx8081;
|
||||
@property (nonatomic, readonly, nullable) CSAppleII *appleII;
|
||||
|
||||
@end
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <bitset>
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
|
||||
@interface CSMachine() <CSScanTargetViewDisplayLinkDelegate>
|
||||
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
||||
@@ -74,28 +76,6 @@ struct ActivityObserver: public Activity::Observer {
|
||||
__unsafe_unretained CSMachine *machine;
|
||||
};
|
||||
|
||||
@interface CSMissingROM (/*Mutability*/)
|
||||
@property (nonatomic, nonnull, copy) NSString *machineName;
|
||||
@property (nonatomic, nonnull, copy) NSString *fileName;
|
||||
@property (nonatomic, nullable, copy) NSString *descriptiveName;
|
||||
@property (nonatomic, readwrite) NSUInteger size;
|
||||
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
|
||||
@end
|
||||
|
||||
@implementation CSMissingROM
|
||||
|
||||
@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];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSMachine {
|
||||
SpeakerDelegate _speakerDelegate;
|
||||
ActivityObserver _activityObserver;
|
||||
@@ -126,35 +106,18 @@ struct ActivityObserver: public Activity::Observer {
|
||||
NSMutableArray<dispatch_block_t> *_inputEvents;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
|
||||
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableString *)missingROMs {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_analyser = result;
|
||||
|
||||
Machine::Error error;
|
||||
std::vector<ROMMachine::ROM> missing_roms;
|
||||
ROM::Request missing_roms;
|
||||
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error));
|
||||
if(!_machine) {
|
||||
for(const auto &missing_rom : missing_roms) {
|
||||
CSMissingROM *rom = [[CSMissingROM alloc] init];
|
||||
|
||||
// Copy/convert the primitive fields.
|
||||
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] initWithCapacity:missing_rom.crc32s.size()];
|
||||
for(const auto &crc : missing_rom.crc32s) {
|
||||
[crc32s addObject:@(crc)];
|
||||
}
|
||||
rom.crc32s = crc32s;
|
||||
|
||||
// Add to the missing list.
|
||||
[missingROMs addObject:rom];
|
||||
}
|
||||
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>> wstring_converter;
|
||||
const std::wstring description = missing_roms.description(0, L'•');
|
||||
[missingROMs appendString:[NSString stringWithUTF8String:wstring_converter.to_bytes(description).c_str()]];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -614,6 +577,10 @@ struct ActivityObserver: public Activity::Observer {
|
||||
return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self];
|
||||
}
|
||||
|
||||
- (CSAppleII *)appleII {
|
||||
return [[CSAppleII alloc] initWithAppleII:_machine->raw_pointer() owner:self];
|
||||
}
|
||||
|
||||
#pragma mark - Input device queries
|
||||
|
||||
- (BOOL)hasJoystick {
|
||||
@@ -783,4 +750,8 @@ struct ActivityObserver: public Activity::Observer {
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
+ (BOOL)attemptInstallROM:(NSURL *)url {
|
||||
return CSInstallROM(url);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -8,4 +8,5 @@
|
||||
|
||||
#include "ROMMachine.hpp"
|
||||
|
||||
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms = nullptr);
|
||||
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing = nullptr);
|
||||
BOOL CSInstallROM(NSURL *);
|
||||
|
||||
@@ -11,47 +11,96 @@
|
||||
|
||||
#import "NSBundle+DataResource.h"
|
||||
#import "NSData+StdVector.h"
|
||||
#import "NSData+CRC32.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
ROMMachine::ROMFetcher CSROMFetcher(std::vector<ROMMachine::ROM> *missing_roms) {
|
||||
return [missing_roms] (const std::vector<ROMMachine::ROM> &roms) -> std::vector<std::unique_ptr<std::vector<std::uint8_t>>> {
|
||||
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
|
||||
namespace {
|
||||
|
||||
std::vector<std::unique_ptr<std::vector<std::uint8_t>>> results;
|
||||
for(const auto &rom: roms) {
|
||||
NSData *fileData;
|
||||
NSString *const subdirectory = [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:rom.machine_name.c_str()]];
|
||||
NSString *directoryFor(const ROM::Description &description) {
|
||||
return [@"ROMImages/" stringByAppendingString:[NSString stringWithUTF8String:description.machine_name.c_str()]];
|
||||
}
|
||||
|
||||
// Check for this file first within the application support directories.
|
||||
for(NSURL *supportURL in supportURLs) {
|
||||
NSURL *const fullURL = [[supportURL URLByAppendingPathComponent:subdirectory]
|
||||
URLByAppendingPathComponent:[NSString stringWithUTF8String:rom.file_name.c_str()]];
|
||||
fileData = [NSData dataWithContentsOfURL:fullURL];
|
||||
if(fileData) break;
|
||||
NSArray<NSURL *> *urlsFor(const ROM::Description &description, const std::string &file_name) {
|
||||
NSMutableArray<NSURL *> *const urls = [[NSMutableArray alloc] init];
|
||||
NSArray<NSURL *> *const supportURLs = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask];
|
||||
NSString *const subdirectory = directoryFor(description);
|
||||
|
||||
for(NSURL *supportURL in supportURLs) {
|
||||
[urls addObject:[[supportURL URLByAppendingPathComponent:subdirectory]
|
||||
URLByAppendingPathComponent:[NSString stringWithUTF8String:file_name.c_str()]]];
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BOOL CSInstallROM(NSURL *url) {
|
||||
NSData *const data = [NSData dataWithContentsOfURL:url];
|
||||
if(!data) return NO;
|
||||
|
||||
// Try for a direct CRC match.
|
||||
std::optional<ROM::Description> target_description;
|
||||
target_description = ROM::Description::from_crc(uint32_t(data.crc32.integerValue));
|
||||
|
||||
// See whether there's an acceptable trimming that creates a CRC match.
|
||||
if(!target_description) {
|
||||
const std::vector<ROM::Description> descriptions = ROM::all_descriptions();
|
||||
for(const auto &description: descriptions) {
|
||||
if(description.size > data.length) continue;
|
||||
|
||||
NSData *const trimmedData = [data subdataWithRange:NSMakeRange(0, description.size)];
|
||||
if(description.crc32s.find(uint32_t(trimmedData.crc32.unsignedIntValue)) != description.crc32s.end()) {
|
||||
target_description = description;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Failing that, check inside the application bundle.
|
||||
if(!fileData) {
|
||||
fileData = [[NSBundle mainBundle]
|
||||
dataForResource:[NSString stringWithUTF8String:rom.file_name.c_str()]
|
||||
withExtension:nil
|
||||
subdirectory:subdirectory];
|
||||
}
|
||||
// If no destination was found, stop.
|
||||
if(!target_description) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
// Store an appropriate result, accumulating a list of the missing if requested.
|
||||
if(!fileData) {
|
||||
results.emplace_back(nullptr);
|
||||
// Copy the data to its destination and report success.
|
||||
NSURL *const targetURL = [urlsFor(*target_description, target_description->file_names[0]) firstObject];
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:targetURL.URLByDeletingLastPathComponent.path withIntermediateDirectories:YES attributes:nil error:nil];
|
||||
[data writeToURL:targetURL atomically:NO];
|
||||
|
||||
if(missing_roms) {
|
||||
missing_roms->push_back(rom);
|
||||
return YES;
|
||||
}
|
||||
|
||||
ROMMachine::ROMFetcher CSROMFetcher(ROM::Request *missing) {
|
||||
return [missing] (const ROM::Request &roms) -> ROM::Map {
|
||||
ROM::Map results;
|
||||
for(const auto &description: roms.all_descriptions()) {
|
||||
for(const auto &file_name: description.file_names) {
|
||||
NSData *fileData;
|
||||
|
||||
// Check for this file first within the application support directories.
|
||||
for(NSURL *fileURL in urlsFor(description, file_name)) {
|
||||
fileData = [NSData dataWithContentsOfURL:fileURL];
|
||||
if(fileData) break;
|
||||
}
|
||||
|
||||
// Failing that, check inside the application bundle.
|
||||
if(!fileData) {
|
||||
fileData = [[NSBundle mainBundle]
|
||||
dataForResource:[NSString stringWithUTF8String:file_name.c_str()]
|
||||
withExtension:nil
|
||||
subdirectory:directoryFor(description)];
|
||||
}
|
||||
|
||||
// Store an appropriate result.
|
||||
if(fileData) {
|
||||
results[description.name] = fileData.stdVector8;
|
||||
}
|
||||
}
|
||||
else {
|
||||
auto data = std::make_unique<std::vector<std::uint8_t>>();
|
||||
*data = fileData.stdVector8;
|
||||
results.emplace_back(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
if(missing) {
|
||||
*missing = roms.subtract(results);
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
@@ -41,6 +41,30 @@ typedef NS_ENUM(NSInteger, CSMachineCPCModel) {
|
||||
CSMachineCPCModel6128
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineEnterpriseModel) {
|
||||
CSMachineEnterpriseModel64,
|
||||
CSMachineEnterpriseModel128,
|
||||
CSMachineEnterpriseModel256,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineEnterpriseEXOS) {
|
||||
CSMachineEnterpriseEXOSVersion21,
|
||||
CSMachineEnterpriseEXOSVersion20,
|
||||
CSMachineEnterpriseEXOSVersion10,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineEnterpriseBASIC) {
|
||||
CSMachineEnterpriseBASICVersion21,
|
||||
CSMachineEnterpriseBASICVersion11,
|
||||
CSMachineEnterpriseBASICVersion10,
|
||||
CSMachineEnterpriseBASICNone,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineEnterpriseDOS) {
|
||||
CSMachineEnterpriseDOSEXDOS,
|
||||
CSMachineEnterpriseDOSNone,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineMacintoshModel) {
|
||||
CSMachineMacintoshModel128k,
|
||||
CSMachineMacintoshModel512k,
|
||||
@@ -96,6 +120,7 @@ typedef int Kilobytes;
|
||||
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize;
|
||||
- (instancetype)initWithAtariSTModel:(CSMachineAtariSTModel)model;
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM;
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos;
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
|
||||
- (instancetype)initWithMSXRegion:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive;
|
||||
- (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface;
|
||||
@@ -114,6 +139,8 @@ typedef int Kilobytes;
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url;
|
||||
- (void)applyToMachine:(CSMachine *)machine;
|
||||
|
||||
@property(nonatomic, readonly) BOOL empty;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "../../../../../Analyser/Static/AppleIIgs/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/AtariST/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Commodore/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Enterprise/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Macintosh/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/MSX/Target.hpp"
|
||||
#include "../../../../../Analyser/Static/Oric/Target.hpp"
|
||||
@@ -130,6 +131,45 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
auto target = std::make_unique<Target>();
|
||||
|
||||
switch(model) {
|
||||
case CSMachineEnterpriseModel64: target->model = Target::Model::Enterprise64; break;
|
||||
default:
|
||||
case CSMachineEnterpriseModel128: target->model = Target::Model::Enterprise128; break;
|
||||
case CSMachineEnterpriseModel256: target->model = Target::Model::Enterprise256; break;
|
||||
}
|
||||
|
||||
switch(exosVersion) {
|
||||
case CSMachineEnterpriseEXOSVersion21: target->exos_version = Target::EXOSVersion::v21; break;
|
||||
default:
|
||||
case CSMachineEnterpriseEXOSVersion20: target->exos_version = Target::EXOSVersion::v20; break;
|
||||
case CSMachineEnterpriseEXOSVersion10: target->exos_version = Target::EXOSVersion::v10; break;
|
||||
}
|
||||
|
||||
switch(basicVersion) {
|
||||
case CSMachineEnterpriseBASICNone: target->basic_version = Target::BASICVersion::None; break;
|
||||
default:
|
||||
case CSMachineEnterpriseBASICVersion21: target->basic_version = Target::BASICVersion::v21; break;
|
||||
case CSMachineEnterpriseBASICVersion11: target->basic_version = Target::BASICVersion::v11; break;
|
||||
case CSMachineEnterpriseBASICVersion10: target->basic_version = Target::BASICVersion::v10; break;
|
||||
}
|
||||
|
||||
switch(dos) {
|
||||
case CSMachineEnterpriseDOSEXDOS: target->dos = Target::DOS::EXDOS; break;
|
||||
default:
|
||||
case CSMachineEnterpriseDOSNone: target->dos = Target::DOS::None; break;
|
||||
}
|
||||
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
@@ -276,6 +316,7 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
case Analyser::Machine::AtariST: return @"CompositeOptions";
|
||||
case Analyser::Machine::ColecoVision: return @"CompositeOptions";
|
||||
case Analyser::Machine::Electron: return @"QuickLoadCompositeOptions";
|
||||
case Analyser::Machine::Enterprise: return @"CompositeOptions";
|
||||
case Analyser::Machine::Macintosh: return @"MacintoshOptions";
|
||||
case Analyser::Machine::MasterSystem: return @"CompositeOptions";
|
||||
case Analyser::Machine::MSX: return @"QuickLoadCompositeOptions";
|
||||
@@ -309,4 +350,8 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
[machine applyMedia:_media];
|
||||
}
|
||||
|
||||
- (BOOL)empty {
|
||||
return _media.empty();
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
18
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAppleII.h
Normal file
18
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAppleII.h
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// CSAppleII.h
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
@class CSAppleII;
|
||||
#import "CSMachine.h"
|
||||
|
||||
@interface CSAppleII : NSObject
|
||||
|
||||
- (instancetype)initWithAppleII:(void *)appleII owner:(CSMachine *)machine;
|
||||
|
||||
@property (nonatomic, assign) BOOL useSquarePixels;
|
||||
|
||||
@end
|
||||
57
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAppleII.mm
Normal file
57
OSBindings/Mac/Clock Signal/Machine/Wrappers/CSAppleII.mm
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// CSAppleII.m
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 07/06/2021.
|
||||
// Copyright © 2021 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#import "CSAppleII.h"
|
||||
|
||||
#include "AppleII.hpp"
|
||||
|
||||
@implementation CSAppleII {
|
||||
Apple::II::Machine *_appleII;
|
||||
__weak CSMachine *_machine;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAppleII:(void *)appleII owner:(CSMachine *)machine {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_appleII = (Apple::II::Machine *)appleII;
|
||||
_machine = machine;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark - Options
|
||||
|
||||
- (void)setUseSquarePixels:(BOOL)useSquarePixels {
|
||||
Configurable::Device *const configurable = dynamic_cast<Configurable::Device *>(_appleII);
|
||||
#ifndef NDEBUG
|
||||
assert(configurable);
|
||||
#endif
|
||||
|
||||
@synchronized(_machine) {
|
||||
auto options = configurable->get_options();
|
||||
#ifndef NDEBUG
|
||||
assert(dynamic_cast<Apple::II::Machine::Options *>(options.get()));
|
||||
#endif
|
||||
|
||||
auto appleii_configurable = static_cast<Apple::II::Machine::Options *>(options.get());
|
||||
appleii_configurable->use_square_pixels = useSquarePixels;
|
||||
configurable->set_options(options);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)useSquarePixels {
|
||||
Configurable::Device *const configurable = dynamic_cast<Configurable::Device *>(_appleII);
|
||||
|
||||
@synchronized(_machine) {
|
||||
auto options = configurable->get_options();
|
||||
auto appleii_configurable = dynamic_cast<Apple::II::Machine::Options *>(options.get());
|
||||
return appleii_configurable->use_square_pixels ? YES : NO;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17701" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17701"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -17,10 +17,10 @@
|
||||
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" titleVisibility="hidden" id="QvC-M9-y7g">
|
||||
<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="590" height="316"/>
|
||||
<rect key="contentRect" x="196" y="240" width="590" height="353"/>
|
||||
<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="590" height="316"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="590" height="353"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hKn-1l-OSN">
|
||||
@@ -59,16 +59,16 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<tabView type="noTabsBezelBorder" translatesAutoresizingMaskIntoConstraints="NO" id="VUb-QG-x7c">
|
||||
<rect key="frame" x="154" y="56" width="420" height="206"/>
|
||||
<rect key="frame" x="154" y="56" width="420" height="243"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<tabViewItems>
|
||||
<tabViewItem label="Apple II" identifier="appleii" id="P59-QG-LOa">
|
||||
<view key="view" id="dHz-Yv-GNq">
|
||||
<rect key="frame" x="10" y="7" width="400" height="186"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="V5Z-dX-Ns4">
|
||||
<rect key="frame" x="18" y="163" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="qV3-2P-3JW">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -76,7 +76,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="WnO-ef-IC6">
|
||||
<rect key="frame" x="18" y="133" width="96" height="16"/>
|
||||
<rect key="frame" x="18" y="155" width="96" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk controller:" id="kbf-rc-Y4M">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -84,7 +84,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jli-ac-Sij">
|
||||
<rect key="frame" x="67" y="157" width="117" height="25"/>
|
||||
<rect key="frame" x="67" y="179" width="117" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Apple II" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="VBQ-JG-AeM" id="U6V-us-O2F">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -99,7 +99,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LSB-WP-FMi">
|
||||
<rect key="frame" x="117" y="127" width="134" height="25"/>
|
||||
<rect key="frame" x="117" y="149" width="134" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Sixteen Sector" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="QaV-Yr-k9o" id="8BT-RV-2Nm">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -115,11 +115,11 @@ Gw
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="WnO-ef-IC6" firstAttribute="leading" secondItem="dHz-Yv-GNq" secondAttribute="leading" constant="20" symbolic="YES" id="5RF-1w-9HW"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="bottom" constant="5" id="865-cv-qVk"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="bottom" constant="20" symbolic="YES" id="865-cv-qVk"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LSB-WP-FMi" secondAttribute="trailing" constant="20" symbolic="YES" id="9GL-al-1qi"/>
|
||||
<constraint firstItem="WnO-ef-IC6" firstAttribute="centerY" secondItem="LSB-WP-FMi" secondAttribute="centerY" id="Fuj-zT-MIm"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="jli-ac-Sij" secondAttribute="trailing" constant="20" symbolic="YES" id="I8d-OR-ICN"/>
|
||||
<constraint firstItem="jli-ac-Sij" firstAttribute="top" secondItem="dHz-Yv-GNq" secondAttribute="top" constant="5" id="Qi1-CV-A0c"/>
|
||||
<constraint firstItem="jli-ac-Sij" firstAttribute="top" secondItem="dHz-Yv-GNq" secondAttribute="top" constant="20" symbolic="YES" id="Qi1-CV-A0c"/>
|
||||
<constraint firstItem="V5Z-dX-Ns4" firstAttribute="leading" secondItem="dHz-Yv-GNq" secondAttribute="leading" constant="20" symbolic="YES" id="SWc-iX-1We"/>
|
||||
<constraint firstItem="LSB-WP-FMi" firstAttribute="leading" secondItem="WnO-ef-IC6" secondAttribute="trailing" constant="8" symbolic="YES" id="bte-XA-xNQ"/>
|
||||
<constraint firstItem="LSB-WP-FMi" firstAttribute="top" secondItem="jli-ac-Sij" secondAttribute="bottom" constant="10" symbolic="YES" id="ki5-JR-vRe"/>
|
||||
@@ -129,12 +129,12 @@ Gw
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Apple IIgs" identifier="appleiigs" id="u5E-8n-ghF">
|
||||
<view key="view" ambiguous="YES" id="jOM-9f-vkk">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<view key="view" id="jOM-9f-vkk">
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0d9-IG-gKU">
|
||||
<rect key="frame" x="18" y="53" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="kiv-1P-FWc">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -142,7 +142,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="LES-76-Ovz">
|
||||
<rect key="frame" x="18" y="23" width="85" height="16"/>
|
||||
<rect key="frame" x="18" y="155" width="85" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory size:" id="OLJ-nC-yyj">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -150,7 +150,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gcS-uy-mzl">
|
||||
<rect key="frame" x="67" y="47" width="89" height="25"/>
|
||||
<rect key="frame" x="67" y="179" width="89" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="ROM 03" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="2" imageScaling="axesIndependently" inset="2" selectedItem="0TS-DO-O9h" id="hjw-g8-e2f">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -164,7 +164,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nQa-YS-utT">
|
||||
<rect key="frame" x="106" y="17" width="82" height="25"/>
|
||||
<rect key="frame" x="106" y="149" width="82" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="8 mb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8192" imageScaling="axesIndependently" inset="2" selectedItem="UHg-gU-Xnn" id="dl3-cq-uWO">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -182,24 +182,24 @@ Gw
|
||||
<constraint firstItem="LES-76-Ovz" firstAttribute="leading" secondItem="jOM-9f-vkk" secondAttribute="leading" constant="20" symbolic="YES" id="2D3-Ve-6CN"/>
|
||||
<constraint firstItem="0d9-IG-gKU" firstAttribute="leading" secondItem="jOM-9f-vkk" secondAttribute="leading" constant="20" symbolic="YES" id="8Xz-86-tDf"/>
|
||||
<constraint firstItem="0d9-IG-gKU" firstAttribute="centerY" secondItem="gcS-uy-mzl" secondAttribute="centerY" id="Eww-Qq-eBT"/>
|
||||
<constraint firstItem="gcS-uy-mzl" firstAttribute="top" secondItem="jOM-9f-vkk" secondAttribute="top" constant="5" id="F6i-cP-7AR"/>
|
||||
<constraint firstItem="gcS-uy-mzl" firstAttribute="top" secondItem="jOM-9f-vkk" secondAttribute="top" constant="20" symbolic="YES" id="F6i-cP-7AR"/>
|
||||
<constraint firstItem="gcS-uy-mzl" firstAttribute="leading" secondItem="0d9-IG-gKU" secondAttribute="trailing" constant="8" symbolic="YES" id="LUm-rI-LYP"/>
|
||||
<constraint firstItem="LES-76-Ovz" firstAttribute="centerY" secondItem="nQa-YS-utT" secondAttribute="centerY" id="UdP-U6-OFE"/>
|
||||
<constraint firstItem="nQa-YS-utT" firstAttribute="leading" secondItem="LES-76-Ovz" secondAttribute="trailing" constant="8" symbolic="YES" id="Xm1-iG-Dgj"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gcS-uy-mzl" secondAttribute="trailing" constant="20" symbolic="YES" id="a38-Cx-CEh"/>
|
||||
<constraint firstItem="nQa-YS-utT" firstAttribute="top" secondItem="gcS-uy-mzl" secondAttribute="bottom" constant="10" symbolic="YES" id="aM9-m8-s7z"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="trailing" constant="20" symbolic="YES" id="cqx-Jc-rUb"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="bottom" constant="5" id="sbT-If-NTU"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="nQa-YS-utT" secondAttribute="bottom" constant="20" symbolic="YES" id="sbT-If-NTU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Amstrad CPC" identifier="cpc" id="JmB-OF-xcM">
|
||||
<view key="view" id="5zS-Nj-Ynx">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="00d-sg-Krh">
|
||||
<rect key="frame" x="67" y="47" width="96" height="25"/>
|
||||
<rect key="frame" x="67" y="179" width="96" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="CPC6128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="6128" imageScaling="axesIndependently" inset="2" selectedItem="klh-ZE-Agp" id="hVJ-h6-iea">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -213,7 +213,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="q9q-sl-J0q">
|
||||
<rect key="frame" x="18" y="53" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Cw3-q5-1bC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -224,8 +224,8 @@ Gw
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="trailing" constant="20" symbolic="YES" id="4AF-5C-2IF"/>
|
||||
<constraint firstItem="q9q-sl-J0q" firstAttribute="leading" secondItem="5zS-Nj-Ynx" secondAttribute="leading" constant="20" symbolic="YES" id="Wof-5h-gfD"/>
|
||||
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="5" id="c92-uU-NRr"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="5" id="enU-LN-Nep"/>
|
||||
<constraint firstItem="00d-sg-Krh" firstAttribute="top" secondItem="5zS-Nj-Ynx" secondAttribute="top" constant="20" symbolic="YES" id="c92-uU-NRr"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="00d-sg-Krh" secondAttribute="bottom" constant="20" symbolic="YES" id="enU-LN-Nep"/>
|
||||
<constraint firstItem="00d-sg-Krh" firstAttribute="leading" secondItem="q9q-sl-J0q" secondAttribute="trailing" constant="8" symbolic="YES" id="mA8-US-ndo"/>
|
||||
<constraint firstItem="q9q-sl-J0q" firstAttribute="centerY" secondItem="00d-sg-Krh" secondAttribute="centerY" id="vA8-IA-Uwf"/>
|
||||
</constraints>
|
||||
@@ -233,11 +233,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Atari ST" identifier="atarist" id="a6Y-mx-yFn">
|
||||
<view key="view" id="nnv-Wi-7hc">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nLf-LI-nWO">
|
||||
<rect key="frame" x="18" y="57" width="728" height="16"/>
|
||||
<rect key="frame" x="18" y="204" width="364" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="At present only a 512k Atari ST is supported." id="gBA-ke-mur">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -254,32 +254,32 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Electron" identifier="electron" id="muc-z9-Vqc">
|
||||
<view key="view" id="SRc-2D-95G">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="JqM-IK-FMP">
|
||||
<rect key="frame" x="18" y="56" width="168" height="18"/>
|
||||
<rect key="frame" x="18" y="186" 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="18" y="34" width="232" height="18"/>
|
||||
<rect key="frame" x="18" y="164" 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="18" y="12" width="231" height="18"/>
|
||||
<rect key="frame" x="18" y="142" 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="18" y="-10" width="284" height="18"/>
|
||||
<rect key="frame" x="18" y="120" 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"/>
|
||||
@@ -289,13 +289,13 @@ Gw
|
||||
<constraints>
|
||||
<constraint firstItem="945-wU-JOH" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="1iM-70-oZq"/>
|
||||
<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="5" id="FM6-AA-Vhf"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="20" symbolic="YES" id="FM6-AA-Vhf"/>
|
||||
<constraint firstItem="JqM-IK-FMP" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" 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 firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="trailing" constant="20" symbolic="YES" id="X3p-qJ-ENH"/>
|
||||
<constraint firstItem="lzo-8g-o4S" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="b5a-SX-2ty"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="945-wU-JOH" secondAttribute="trailing" constant="20" symbolic="YES" id="dmY-PV-ap4"/>
|
||||
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="3" id="ggl-QH-mV4"/>
|
||||
<constraint firstItem="JqM-IK-FMP" firstAttribute="top" secondItem="SRc-2D-95G" secondAttribute="top" constant="20" symbolic="YES" id="ggl-QH-mV4"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="cG2-Ph-S3Z" secondAttribute="trailing" constant="20" symbolic="YES" id="m6t-dP-71d"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="JqM-IK-FMP" secondAttribute="trailing" constant="20" symbolic="YES" id="mvO-UZ-BtT"/>
|
||||
<constraint firstItem="cG2-Ph-S3Z" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="npw-IZ-6xU"/>
|
||||
@@ -303,13 +303,133 @@ Gw
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Enterprise" identifier="enterprise" id="zhO-EO-wUe">
|
||||
<view key="view" id="1cs-PX-RAH">
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PhH-bu-pb5">
|
||||
<rect key="frame" x="80" y="179" width="129" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Enterprise 128" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="128" imageScaling="axesIndependently" inset="2" selectedItem="roH-nL-f8o" id="z9O-XC-XBv">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="ojI-Vb-iHz">
|
||||
<items>
|
||||
<menuItem title="Enterprise 256" tag="256" id="Al3-A0-tvw"/>
|
||||
<menuItem title="Enterprise 128" state="on" tag="128" id="roH-nL-f8o"/>
|
||||
<menuItem title="Enterprise 64" tag="64" id="vNG-Tv-bDI"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nen-Za-7zH">
|
||||
<rect key="frame" x="64" y="149" width="107" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="Qja-xZ-wVM" id="xGG-ri-8Sb">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="uNC-hA-d5z">
|
||||
<items>
|
||||
<menuItem title="Version 2.1" state="on" tag="21" id="Qja-xZ-wVM"/>
|
||||
<menuItem title="Version 2.0" tag="20" id="XTj-l7-KX3"/>
|
||||
<menuItem title="Version 1.0" tag="10" id="Ky2-2D-wcY"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hIr-GH-7xi">
|
||||
<rect key="frame" x="67" y="119" width="105" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Version 2.1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="21" imageScaling="axesIndependently" inset="2" selectedItem="TME-cv-Jh1" id="9mQ-GW-lq9">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="VcH-Xm-1hY">
|
||||
<items>
|
||||
<menuItem title="Version 2.1" state="on" tag="21" id="TME-cv-Jh1"/>
|
||||
<menuItem title="Version 1.1" tag="11" id="7P2-aF-6fp"/>
|
||||
<menuItem title="Version 1.0" tag="10" id="j8p-uY-BhG"/>
|
||||
<menuItem title="None" id="eGk-Bj-IVT"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="syE-e7-TjU">
|
||||
<rect key="frame" x="57" y="89" width="83" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="1" imageScaling="axesIndependently" inset="2" selectedItem="8rP-2w-PdU" id="NvO-Zm-2Rq">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" title="DOS" id="sdr-al-7mi">
|
||||
<items>
|
||||
<menuItem title="EXDOS" tag="1" id="8rP-2w-PdU"/>
|
||||
<menuItem title="None" state="on" id="qoS-KO-iEZ"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
|
||||
<rect key="frame" x="18" y="155" width="43" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="EXOS:" id="gUC-PN-zVL">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
|
||||
<rect key="frame" x="18" y="185" width="59" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Machine:" id="uTv-hH-mIC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
|
||||
<rect key="frame" x="18" y="125" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="BASIC:" id="ai1-oR-X6Y">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
|
||||
<rect key="frame" x="18" y="95" width="36" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="DOS:" id="NFk-cp-DfS">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="dzd-tH-BjX" firstAttribute="centerY" secondItem="hIr-GH-7xi" secondAttribute="centerY" id="3TV-RU-Kgh"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="hIr-GH-7xi" secondAttribute="trailing" constant="20" symbolic="YES" id="44v-9O-y7L"/>
|
||||
<constraint firstItem="frx-nk-c3P" firstAttribute="centerY" secondItem="PhH-bu-pb5" secondAttribute="centerY" id="6Wc-aR-wuL"/>
|
||||
<constraint firstItem="dzd-tH-BjX" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="7RZ-Om-TAa"/>
|
||||
<constraint firstItem="ykc-W1-YaS" firstAttribute="centerY" secondItem="nen-Za-7zH" secondAttribute="centerY" id="CLa-6E-8BB"/>
|
||||
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="ENF-TY-TQ7"/>
|
||||
<constraint firstItem="nen-Za-7zH" firstAttribute="leading" secondItem="ykc-W1-YaS" secondAttribute="trailing" constant="8" symbolic="YES" id="GWR-VI-9PG"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="bottom" constant="20" symbolic="YES" id="K3s-FA-zMB"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="PhH-bu-pb5" secondAttribute="trailing" constant="20" symbolic="YES" id="LwB-ef-uF4"/>
|
||||
<constraint firstItem="PhH-bu-pb5" firstAttribute="top" secondItem="1cs-PX-RAH" secondAttribute="top" constant="20" symbolic="YES" id="Myt-i4-jVq"/>
|
||||
<constraint firstItem="pxr-Bq-yh0" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="Qzp-IY-Pa0"/>
|
||||
<constraint firstItem="PhH-bu-pb5" firstAttribute="leading" secondItem="frx-nk-c3P" secondAttribute="trailing" constant="8" symbolic="YES" id="T3e-u7-fiQ"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="syE-e7-TjU" secondAttribute="trailing" constant="20" symbolic="YES" id="TEX-Nw-y2K"/>
|
||||
<constraint firstItem="frx-nk-c3P" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="TgR-RR-eA1"/>
|
||||
<constraint firstItem="pxr-Bq-yh0" firstAttribute="centerY" secondItem="syE-e7-TjU" secondAttribute="centerY" id="UYw-uz-Am0"/>
|
||||
<constraint firstItem="hIr-GH-7xi" firstAttribute="top" secondItem="nen-Za-7zH" secondAttribute="bottom" constant="10" symbolic="YES" id="VOc-2v-s3u"/>
|
||||
<constraint firstItem="syE-e7-TjU" firstAttribute="top" secondItem="hIr-GH-7xi" secondAttribute="bottom" constant="10" symbolic="YES" id="W9S-on-Heq"/>
|
||||
<constraint firstItem="nen-Za-7zH" firstAttribute="top" secondItem="PhH-bu-pb5" secondAttribute="bottom" constant="10" symbolic="YES" id="Yes-o3-V0m"/>
|
||||
<constraint firstItem="ykc-W1-YaS" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="bAP-lx-pdi"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="nen-Za-7zH" secondAttribute="trailing" constant="20" symbolic="YES" id="eEI-5Q-TnO"/>
|
||||
<constraint firstItem="syE-e7-TjU" firstAttribute="leading" secondItem="pxr-Bq-yh0" secondAttribute="trailing" constant="8" symbolic="YES" id="fmM-Ma-Jyu"/>
|
||||
<constraint firstItem="hIr-GH-7xi" firstAttribute="leading" secondItem="dzd-tH-BjX" secondAttribute="trailing" constant="8" symbolic="YES" id="jDQ-TF-Ogf"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Macintosh" identifier="mac" id="lmR-z3-xSm">
|
||||
<view key="view" id="7Yf-vi-Q0W">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZOY-4E-Cfl">
|
||||
<rect key="frame" x="18" y="53" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="h9r-i6-66j">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -317,7 +437,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="xa6-NA-JY5">
|
||||
<rect key="frame" x="67" y="47" width="75" height="25"/>
|
||||
<rect key="frame" x="67" y="179" width="75" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Plus" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3" imageScaling="axesIndependently" inset="2" selectedItem="R6T-hg-rOF" id="1Kb-Q2-BGM">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -333,9 +453,9 @@ Gw
|
||||
</popUpButton>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="5" id="3hY-Ca-mnR"/>
|
||||
<constraint firstItem="xa6-NA-JY5" firstAttribute="top" secondItem="7Yf-vi-Q0W" secondAttribute="top" constant="20" symbolic="YES" id="3hY-Ca-mnR"/>
|
||||
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="leading" secondItem="7Yf-vi-Q0W" secondAttribute="leading" constant="20" symbolic="YES" id="5s6-87-VT6"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="5" id="KYf-GJ-Y7k"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="bottom" constant="20" symbolic="YES" id="KYf-GJ-Y7k"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="xa6-NA-JY5" secondAttribute="trailing" constant="20" symbolic="YES" id="LZ5-xH-fU0"/>
|
||||
<constraint firstItem="ZOY-4E-Cfl" firstAttribute="centerY" secondItem="xa6-NA-JY5" secondAttribute="centerY" id="Wa5-KX-3Me"/>
|
||||
<constraint firstItem="xa6-NA-JY5" firstAttribute="leading" secondItem="ZOY-4E-Cfl" secondAttribute="trailing" constant="8" symbolic="YES" id="ktS-sr-F8L"/>
|
||||
@@ -344,18 +464,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="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8xT-Pr-8SE">
|
||||
<rect key="frame" x="18" y="26" width="128" height="18"/>
|
||||
<rect key="frame" x="18" y="158" 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="71" y="47" width="146" height="25"/>
|
||||
<rect key="frame" x="71" y="179" width="146" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="xAh-Ch-tby" id="yR4-yv-Lvu">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -369,7 +489,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ZaD-7v-rMS">
|
||||
<rect key="frame" x="18" y="53" width="50" height="16"/>
|
||||
<rect key="frame" x="18" y="185" 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"/>
|
||||
@@ -380,9 +500,9 @@ Gw
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="LG6-mP-SeG" secondAttribute="trailing" constant="20" symbolic="YES" id="0Oc-n7-gaM"/>
|
||||
<constraint firstItem="8xT-Pr-8SE" firstAttribute="top" secondItem="LG6-mP-SeG" secondAttribute="bottom" constant="8" symbolic="YES" id="LBt-4m-GDc"/>
|
||||
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="5" id="bcb-ZZ-VpQ"/>
|
||||
<constraint firstItem="LG6-mP-SeG" firstAttribute="top" secondItem="mWD-An-tR7" secondAttribute="top" constant="20" symbolic="YES" id="bcb-ZZ-VpQ"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="trailing" constant="20" symbolic="YES" id="l8P-UW-8ig"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="bottom" constant="3" id="mga-YX-Bek"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="8xT-Pr-8SE" secondAttribute="bottom" constant="20" symbolic="YES" id="mga-YX-Bek"/>
|
||||
<constraint firstItem="8xT-Pr-8SE" firstAttribute="leading" secondItem="mWD-An-tR7" secondAttribute="leading" constant="20" symbolic="YES" id="q8Q-kh-Opj"/>
|
||||
<constraint firstItem="LG6-mP-SeG" firstAttribute="leading" secondItem="ZaD-7v-rMS" secondAttribute="trailing" constant="8" symbolic="YES" id="svb-nH-GlP"/>
|
||||
<constraint firstItem="ZaD-7v-rMS" firstAttribute="leading" secondItem="mWD-An-tR7" secondAttribute="leading" constant="20" symbolic="YES" id="zgh-a5-FNF"/>
|
||||
@@ -392,11 +512,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Oric" identifier="oric" id="NSx-DC-p4M">
|
||||
<view key="view" id="sOR-e0-8iZ">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="0ct-tf-uRH">
|
||||
<rect key="frame" x="18" y="53" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="Xm1-7x-YVl">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -404,7 +524,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ENP-hI-BVZ">
|
||||
<rect key="frame" x="67" y="47" width="107" height="25"/>
|
||||
<rect key="frame" x="67" y="179" width="107" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Oric-1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="jGN-1a-biF" id="Jll-EJ-cMr">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -418,7 +538,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fYL-p6-wyn">
|
||||
<rect key="frame" x="113" y="17" width="130" height="25"/>
|
||||
<rect key="frame" x="113" y="149" width="130" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="XhK-Jh-oTW" id="aYb-m1-H9X">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -434,7 +554,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="okM-ZI-NbF">
|
||||
<rect key="frame" x="18" y="23" width="92" height="16"/>
|
||||
<rect key="frame" x="18" y="155" width="92" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Disk interface:" id="SFK-hS-tFC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -443,7 +563,7 @@ Gw
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="ENP-hI-BVZ" firstAttribute="top" secondItem="sOR-e0-8iZ" secondAttribute="top" constant="5" id="Bgq-8R-8I4"/>
|
||||
<constraint firstItem="ENP-hI-BVZ" firstAttribute="top" secondItem="sOR-e0-8iZ" secondAttribute="top" constant="20" symbolic="YES" id="Bgq-8R-8I4"/>
|
||||
<constraint firstItem="fYL-p6-wyn" firstAttribute="leading" secondItem="okM-ZI-NbF" secondAttribute="trailing" constant="8" symbolic="YES" id="Pra-lC-JPB"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ENP-hI-BVZ" secondAttribute="trailing" constant="20" symbolic="YES" id="Sr5-QZ-xU3"/>
|
||||
<constraint firstItem="ENP-hI-BVZ" firstAttribute="leading" secondItem="0ct-tf-uRH" secondAttribute="trailing" constant="8" symbolic="YES" id="Wcg-1P-z6l"/>
|
||||
@@ -453,17 +573,17 @@ Gw
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="trailing" constant="20" symbolic="YES" id="ewV-Dw-zj2"/>
|
||||
<constraint firstItem="0ct-tf-uRH" firstAttribute="leading" secondItem="sOR-e0-8iZ" secondAttribute="leading" constant="20" symbolic="YES" id="huH-9L-97Y"/>
|
||||
<constraint firstItem="fYL-p6-wyn" firstAttribute="top" secondItem="ENP-hI-BVZ" secondAttribute="bottom" constant="10" symbolic="YES" id="l3T-Ve-0Jw"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="bottom" constant="5" id="mN9-AZ-wSn"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="fYL-p6-wyn" secondAttribute="bottom" constant="20" symbolic="YES" id="mN9-AZ-wSn"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="Vic-20" identifier="vic20" id="cyO-PU-hSU">
|
||||
<view key="view" id="fLI-XB-QCr">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ueK-gq-gaF">
|
||||
<rect key="frame" x="71" y="47" width="146" height="25"/>
|
||||
<rect key="frame" x="71" y="179" width="146" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="European (PAL)" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="45i-0n-gau" id="yi7-eo-I0q">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -479,7 +599,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="2eV-Us-eEv">
|
||||
<rect key="frame" x="108" y="17" width="116" height="25"/>
|
||||
<rect key="frame" x="108" y="149" width="116" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="fOl-8Q-fsA" id="rH0-7T-pJE">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -493,7 +613,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="MTh-9p-FqC">
|
||||
<rect key="frame" x="18" y="53" width="50" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="50" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Region:" id="F3g-Ya-ypU">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -501,7 +621,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gRS-DK-rIy">
|
||||
<rect key="frame" x="18" y="23" width="87" height="16"/>
|
||||
<rect key="frame" x="18" y="155" width="87" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="a4I-vG-yCp">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -509,7 +629,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lrf-gL-6EI">
|
||||
<rect key="frame" x="18" y="-4" width="177" height="18"/>
|
||||
<rect key="frame" x="18" y="128" width="177" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Attach C-1540 disk drive" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tsq-YD-xw8">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -521,7 +641,7 @@ Gw
|
||||
<constraint firstItem="MTh-9p-FqC" firstAttribute="centerY" secondItem="ueK-gq-gaF" secondAttribute="centerY" id="8KD-Bm-KRA"/>
|
||||
<constraint firstItem="ueK-gq-gaF" firstAttribute="leading" secondItem="MTh-9p-FqC" secondAttribute="trailing" constant="8" symbolic="YES" id="9m6-6j-8j2"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="trailing" constant="20" symbolic="YES" id="M08-mP-Plz"/>
|
||||
<constraint firstItem="ueK-gq-gaF" firstAttribute="top" secondItem="fLI-XB-QCr" secondAttribute="top" constant="5" id="XN3-GK-BX9"/>
|
||||
<constraint firstItem="ueK-gq-gaF" firstAttribute="top" secondItem="fLI-XB-QCr" secondAttribute="top" constant="20" symbolic="YES" id="XN3-GK-BX9"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ueK-gq-gaF" secondAttribute="trailing" constant="20" symbolic="YES" id="YZ9-7N-ssA"/>
|
||||
<constraint firstItem="MTh-9p-FqC" firstAttribute="leading" secondItem="fLI-XB-QCr" secondAttribute="leading" constant="20" symbolic="YES" id="bgZ-k9-IQC"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="2eV-Us-eEv" secondAttribute="trailing" constant="20" symbolic="YES" id="eiB-vH-17d"/>
|
||||
@@ -529,18 +649,18 @@ Gw
|
||||
<constraint firstItem="2eV-Us-eEv" firstAttribute="leading" secondItem="gRS-DK-rIy" secondAttribute="trailing" constant="8" symbolic="YES" id="hQ9-uy-KHg"/>
|
||||
<constraint firstItem="gRS-DK-rIy" firstAttribute="leading" secondItem="fLI-XB-QCr" secondAttribute="leading" constant="20" symbolic="YES" id="iyp-1K-rdC"/>
|
||||
<constraint firstItem="gRS-DK-rIy" firstAttribute="centerY" secondItem="2eV-Us-eEv" secondAttribute="centerY" id="qWf-Nb-PfJ"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="bottom" constant="3" id="vNb-Sw-Mxw"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="Lrf-gL-6EI" secondAttribute="bottom" constant="20" symbolic="YES" id="vNb-Sw-Mxw"/>
|
||||
<constraint firstItem="Lrf-gL-6EI" firstAttribute="top" secondItem="2eV-Us-eEv" secondAttribute="bottom" constant="8" symbolic="YES" id="zBX-Qq-j5f"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX80" identifier="zx80" id="tMH-kF-GUz">
|
||||
<view key="view" id="8hL-Vn-Hg0">
|
||||
<rect key="frame" x="10" y="7" width="400" height="76"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="I1a-Eu-5UB">
|
||||
<rect key="frame" x="108" y="47" width="116" height="25"/>
|
||||
<rect key="frame" x="108" y="179" width="116" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="4Sa-jR-xOd" id="B8M-do-Yod">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -554,7 +674,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="NCX-4e-lSu">
|
||||
<rect key="frame" x="18" y="53" width="87" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="87" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="e6x-TE-OC5">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -562,7 +682,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ReP-bV-Thu">
|
||||
<rect key="frame" x="18" y="26" width="118" height="18"/>
|
||||
<rect key="frame" x="18" y="158" width="118" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Use ZX81 ROM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="VQH-nv-Pfm">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
@@ -574,8 +694,8 @@ Gw
|
||||
<constraint firstItem="NCX-4e-lSu" firstAttribute="centerY" secondItem="I1a-Eu-5UB" secondAttribute="centerY" id="1ve-sc-QwI"/>
|
||||
<constraint firstItem="I1a-Eu-5UB" firstAttribute="leading" secondItem="NCX-4e-lSu" secondAttribute="trailing" constant="8" symbolic="YES" id="Bu6-60-74x"/>
|
||||
<constraint firstItem="NCX-4e-lSu" firstAttribute="leading" secondItem="8hL-Vn-Hg0" secondAttribute="leading" constant="20" symbolic="YES" id="W09-iG-ARI"/>
|
||||
<constraint firstItem="I1a-Eu-5UB" firstAttribute="top" secondItem="8hL-Vn-Hg0" secondAttribute="top" constant="5" id="fkf-wO-Q8i"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="bottom" constant="3" id="fmT-7E-hUT"/>
|
||||
<constraint firstItem="I1a-Eu-5UB" firstAttribute="top" secondItem="8hL-Vn-Hg0" secondAttribute="top" constant="20" symbolic="YES" id="fkf-wO-Q8i"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="bottom" constant="20" symbolic="YES" id="fmT-7E-hUT"/>
|
||||
<constraint firstItem="ReP-bV-Thu" firstAttribute="top" secondItem="I1a-Eu-5UB" secondAttribute="bottom" constant="8" symbolic="YES" id="hYk-xC-63o"/>
|
||||
<constraint firstItem="ReP-bV-Thu" firstAttribute="leading" secondItem="8hL-Vn-Hg0" secondAttribute="leading" constant="20" symbolic="YES" id="qen-KS-rWi"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="ReP-bV-Thu" secondAttribute="trailing" constant="20" symbolic="YES" id="r7F-DT-oRc"/>
|
||||
@@ -584,11 +704,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX81" identifier="zx81" id="Wnn-nQ-gZ6">
|
||||
<view key="view" id="bmd-gL-gzT">
|
||||
<rect key="frame" x="10" y="7" width="400" height="186"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="5aO-UX-HnX">
|
||||
<rect key="frame" x="108" y="157" width="116" height="25"/>
|
||||
<rect key="frame" x="108" y="179" width="116" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Unexpanded" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="7QC-Ij-hES" id="d3W-Gl-3Mf">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
@@ -602,7 +722,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8tU-73-XEE">
|
||||
<rect key="frame" x="18" y="163" width="87" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="87" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Memory Size:" id="z4b-oR-Yl2">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -613,37 +733,37 @@ Gw
|
||||
<constraints>
|
||||
<constraint firstItem="8tU-73-XEE" firstAttribute="centerY" secondItem="5aO-UX-HnX" secondAttribute="centerY" id="1Jm-YL-OKU"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="trailing" constant="20" symbolic="YES" id="ALj-0x-bFb"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="bottom" constant="5" id="LJ2-W9-fOf"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="5aO-UX-HnX" secondAttribute="bottom" constant="20" symbolic="YES" id="LJ2-W9-fOf"/>
|
||||
<constraint firstItem="8tU-73-XEE" firstAttribute="leading" secondItem="bmd-gL-gzT" secondAttribute="leading" constant="20" symbolic="YES" id="M6Y-jN-LAf"/>
|
||||
<constraint firstItem="5aO-UX-HnX" firstAttribute="top" secondItem="bmd-gL-gzT" secondAttribute="top" constant="5" id="PPD-Jz-qCL"/>
|
||||
<constraint firstItem="5aO-UX-HnX" firstAttribute="top" secondItem="bmd-gL-gzT" secondAttribute="top" constant="20" symbolic="YES" id="PPD-Jz-qCL"/>
|
||||
<constraint firstItem="5aO-UX-HnX" firstAttribute="leading" secondItem="8tU-73-XEE" secondAttribute="trailing" constant="8" symbolic="YES" id="j1u-n4-2IK"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
<tabViewItem label="ZX Spectrum" identifier="spectrum" id="HQv-oF-k8b">
|
||||
<view key="view" id="bMx-F6-JUb">
|
||||
<rect key="frame" x="10" y="7" width="400" height="186"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="223"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gFZ-d4-WFv">
|
||||
<rect key="frame" x="67" y="157" width="76" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
|
||||
<rect key="frame" x="67" y="179" width="76" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="16kb" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="16" imageScaling="axesIndependently" inset="2" selectedItem="Fo7-NL-Kv5" id="tYs-sA-oek">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="menu"/>
|
||||
<menu key="menu" id="8lt-dk-zPr">
|
||||
<items>
|
||||
<menuItem title="16kb" tag="16" id="Fo7-NL-Kv5"/>
|
||||
<menuItem title="16kb" state="on" tag="16" id="Fo7-NL-Kv5"/>
|
||||
<menuItem title="48kb" tag="48" id="xks-Rv-Umd"/>
|
||||
<menuItem title="128kb" tag="128" id="w8h-lY-JLX"/>
|
||||
<menuItem title="+2" tag="2" id="Vvu-ua-pjg"/>
|
||||
<menuItem title="+2a" state="on" tag="21" id="bFk-nC-Txe"/>
|
||||
<menuItem title="+2a" tag="21" id="bFk-nC-Txe"/>
|
||||
<menuItem title="+3" tag="3" id="jwx-fZ-vXp"/>
|
||||
</items>
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="fJ3-ma-Byy">
|
||||
<rect key="frame" x="18" y="163" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="185" width="46" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Model:" id="JId-Tp-LrE">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -655,23 +775,23 @@ Gw
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="trailing" constant="20" symbolic="YES" id="90E-uI-MQg"/>
|
||||
<constraint firstItem="fJ3-ma-Byy" firstAttribute="leading" secondItem="bMx-F6-JUb" secondAttribute="leading" constant="20" symbolic="YES" id="9kE-iQ-dxd"/>
|
||||
<constraint firstItem="fJ3-ma-Byy" firstAttribute="centerY" secondItem="gFZ-d4-WFv" secondAttribute="centerY" id="LxG-5E-Q5Y"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="bottom" constant="5" id="d8S-vX-B5e"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gFZ-d4-WFv" secondAttribute="bottom" constant="20" symbolic="YES" id="d8S-vX-B5e"/>
|
||||
<constraint firstItem="gFZ-d4-WFv" firstAttribute="leading" secondItem="fJ3-ma-Byy" secondAttribute="trailing" constant="8" symbolic="YES" id="hKS-47-R2y"/>
|
||||
<constraint firstItem="gFZ-d4-WFv" firstAttribute="top" secondItem="bMx-F6-JUb" secondAttribute="top" constant="5" id="wsX-Wq-iPt"/>
|
||||
<constraint firstItem="gFZ-d4-WFv" firstAttribute="top" secondItem="bMx-F6-JUb" secondAttribute="top" constant="20" symbolic="YES" id="wsX-Wq-iPt"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</tabViewItem>
|
||||
</tabViewItems>
|
||||
</tabView>
|
||||
<scrollView borderType="line" autohidesScrollers="YES" horizontalLineScroll="24" horizontalPageScroll="10" verticalLineScroll="24" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="z5Q-Bs-hJj">
|
||||
<rect key="frame" x="20" y="60" width="130" height="201"/>
|
||||
<rect key="frame" x="20" y="60" width="130" height="238"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" heightSizable="YES"/>
|
||||
<clipView key="contentView" id="O8s-Vw-9yQ">
|
||||
<rect key="frame" x="1" y="1" width="128" height="199"/>
|
||||
<rect key="frame" x="1" y="1" width="128" height="236"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="none" columnReordering="NO" columnResizing="NO" multipleSelection="NO" emptySelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="24" id="3go-Eb-GOy">
|
||||
<rect key="frame" x="0.0" y="0.0" width="128" height="199"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="128" height="236"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<size key="intercellSpacing" width="17" height="0.0"/>
|
||||
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -708,7 +828,7 @@ Gw
|
||||
</scroller>
|
||||
</scrollView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VAc-6N-O7q">
|
||||
<rect key="frame" x="18" y="269" width="554" height="27"/>
|
||||
<rect key="frame" x="18" y="306" width="554" height="27"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Choose a machine" id="32m-Vs-dPO">
|
||||
<font key="font" textStyle="title2" name=".SFNS-Regular"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -737,7 +857,7 @@ Gw
|
||||
<constraint firstItem="9YM-5x-pc0" firstAttribute="top" secondItem="z5Q-Bs-hJj" secondAttribute="bottom" constant="14" id="suf-rn-Bmy"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="-1" y="88"/>
|
||||
<point key="canvasLocation" x="-1" y="106.5"/>
|
||||
</window>
|
||||
<customObject id="192-Eb-Rpg" customClass="MachinePicker" customModule="Clock_Signal" customModuleProvider="target">
|
||||
<connections>
|
||||
@@ -750,6 +870,10 @@ Gw
|
||||
<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="enterpriseBASICButton" destination="hIr-GH-7xi" id="fM6-It-9UO"/>
|
||||
<outlet property="enterpriseDOSButton" destination="syE-e7-TjU" id="sCW-Bj-ZTW"/>
|
||||
<outlet property="enterpriseEXOSButton" destination="nen-Za-7zH" id="NwS-ua-FdA"/>
|
||||
<outlet property="enterpriseModelButton" destination="PhH-bu-pb5" id="8wD-sW-aBw"/>
|
||||
<outlet property="machineNameTable" destination="3go-Eb-GOy" id="Ppf-S0-IP1"/>
|
||||
<outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/>
|
||||
<outlet property="macintoshModelTypeButton" destination="xa6-NA-JY5" id="2jX-PY-v2z"/>
|
||||
|
||||
@@ -27,14 +27,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
@IBOutlet var appleIIgsModelButton: NSPopUpButton!
|
||||
@IBOutlet var appleIIgsMemorySizeButton: NSPopUpButton!
|
||||
|
||||
// MARK: - CPC properties
|
||||
@IBOutlet var cpcModelTypeButton: NSPopUpButton!
|
||||
|
||||
// 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!
|
||||
// MARK: - Enterprise properties
|
||||
@IBOutlet var enterpriseModelButton: NSPopUpButton!
|
||||
@IBOutlet var enterpriseEXOSButton: NSPopUpButton!
|
||||
@IBOutlet var enterpriseBASICButton: NSPopUpButton!
|
||||
@IBOutlet var enterpriseDOSButton: NSPopUpButton!
|
||||
|
||||
// MARK: - Macintosh properties
|
||||
@IBOutlet var macintoshModelTypeButton: NSPopUpButton!
|
||||
@@ -93,14 +99,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
appleIIgsModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsModel"))
|
||||
appleIIgsMemorySizeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.appleIIgsMemorySize"))
|
||||
|
||||
// CPC settings
|
||||
cpcModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.cpcModel"))
|
||||
|
||||
// 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"))
|
||||
// Enterprise settings
|
||||
enterpriseModelButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseModel"))
|
||||
enterpriseEXOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseEXOSVersion"))
|
||||
enterpriseBASICButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseBASICVersion"))
|
||||
enterpriseDOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseDOS"))
|
||||
|
||||
// Macintosh settings
|
||||
macintoshModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel"))
|
||||
@@ -143,14 +155,20 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
standardUserDefaults.set(appleIIgsModelButton.selectedTag(), forKey: "new.appleIIgsModel")
|
||||
standardUserDefaults.set(appleIIgsMemorySizeButton.selectedTag(), forKey: "new.appleIIgsMemorySize")
|
||||
|
||||
// CPC settings
|
||||
standardUserDefaults.set(cpcModelTypeButton.selectedTag(), forKey: "new.cpcModel")
|
||||
|
||||
// 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")
|
||||
// Enterprise settings
|
||||
standardUserDefaults.set(enterpriseModelButton.selectedTag(), forKey: "new.enterpriseModel")
|
||||
standardUserDefaults.set(enterpriseEXOSButton.selectedTag(), forKey: "new.enterpriseEXOSVersion")
|
||||
standardUserDefaults.set(enterpriseBASICButton.selectedTag(), forKey: "new.enterpriseBASICVersion")
|
||||
standardUserDefaults.set(enterpriseDOSButton.selectedTag(), forKey: "new.enterpriseDOS")
|
||||
|
||||
// Macintosh settings
|
||||
standardUserDefaults.set(macintoshModelTypeButton.selectedTag(), forKey: "new.macintoshModel")
|
||||
@@ -205,12 +223,6 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
storeOptions()
|
||||
|
||||
switch machineSelector.selectedTabViewItem!.identifier as! String {
|
||||
case "electron":
|
||||
return CSStaticAnalyser(
|
||||
electronDFS: electronDFSButton.state == .on,
|
||||
adfs: electronADFSButton.state == .on,
|
||||
ap6: electronAP6Button.state == .on,
|
||||
sidewaysRAM: electronSidewaysRAMButton.state == .on)
|
||||
|
||||
case "appleii":
|
||||
var model: CSMachineAppleIIModel = .appleII
|
||||
@@ -255,6 +267,48 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
default: return CSStaticAnalyser(amstradCPCModel: .model6128)
|
||||
}
|
||||
|
||||
case "electron":
|
||||
return CSStaticAnalyser(
|
||||
electronDFS: electronDFSButton.state == .on,
|
||||
adfs: electronADFSButton.state == .on,
|
||||
ap6: electronAP6Button.state == .on,
|
||||
sidewaysRAM: electronSidewaysRAMButton.state == .on)
|
||||
|
||||
case "enterprise":
|
||||
var model: CSMachineEnterpriseModel = .model128
|
||||
switch enterpriseModelButton.selectedItem!.tag {
|
||||
case 64: model = .model64
|
||||
case 256: model = .model256
|
||||
case 128: fallthrough
|
||||
default: model = .model128
|
||||
}
|
||||
|
||||
var exos: CSMachineEnterpriseEXOS = .version21
|
||||
switch enterpriseEXOSButton.selectedItem!.tag {
|
||||
case 10: exos = .version10
|
||||
case 20: exos = .version20
|
||||
case 21: fallthrough
|
||||
default: exos = .version21
|
||||
}
|
||||
|
||||
var basic: CSMachineEnterpriseBASIC = .version21
|
||||
switch enterpriseBASICButton.selectedTag() {
|
||||
case 0: basic = .none
|
||||
case 10: basic = .version10
|
||||
case 11: basic = .version11
|
||||
case 21: fallthrough
|
||||
default: basic = .version21
|
||||
}
|
||||
|
||||
var dos: CSMachineEnterpriseDOS = .dosNone
|
||||
switch enterpriseDOSButton.selectedTag() {
|
||||
case 1: dos = .DOSEXDOS
|
||||
case 0: fallthrough
|
||||
default: dos = .dosNone
|
||||
}
|
||||
|
||||
return CSStaticAnalyser(enterpriseModel: model, exosVersion: exos, basicVersion: basic, dos: dos)
|
||||
|
||||
case "mac":
|
||||
switch macintoshModelTypeButton.selectedItem!.tag {
|
||||
case 0: return CSStaticAnalyser(macintoshModel: .model128k)
|
||||
|
||||
@@ -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="18122" 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="18122"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -20,18 +20,16 @@
|
||||
<windowStyleMask key="styleMask" titled="YES"/>
|
||||
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="270"/>
|
||||
<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" customClass="CSROMReceiverView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="270"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" textCompletion="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5qG-I3-Qav">
|
||||
<rect key="frame" x="18" y="154" width="444" height="96"/>
|
||||
<rect key="frame" x="18" y="186" width="444" height="64"/>
|
||||
<textFieldCell key="cell" enabled="NO" allowsUndo="NO" id="itJ-2T-0ia">
|
||||
<font key="font" usesAppearanceFont="YES"/>
|
||||
<string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop the following over this text:
|
||||
|
||||
</string>
|
||||
<string key="title">Clock Signal requires you to provide images of the system ROMs for this machine. They will be stored permanently; you need do this only once.
Please drag and drop over this text</string>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
|
||||
@@ -22,4 +22,6 @@
|
||||
|
||||
- (nonnull NSBitmapImageRep *)imageRepresentation;
|
||||
|
||||
- (void)willChangeOwner;
|
||||
|
||||
@end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user