mirror of
https://github.com/TomHarte/CLK.git
synced 2026-04-25 11:17:26 +00:00
Compare commits
260 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1f960bc864 | |||
| df80ad77c5 | |||
| 67443f9287 | |||
| f0394976cc | |||
| 69fd0b9dab | |||
| 959f26c195 | |||
| e4aea4781f | |||
| 3e37ef22ba | |||
| 89295b7b64 | |||
| acf8dba51b | |||
| 46964500f0 | |||
| 1b5b0e3dd1 | |||
| 40ed0203d2 | |||
| 9ed0cc49c0 | |||
| ec06aa45a6 | |||
| 5a6cff2244 | |||
| 1e186c9428 | |||
| e7e8df6b6f | |||
| 55d5696f5b | |||
| e547e0eb44 | |||
| 43b8c52d21 | |||
| 8829d8daed | |||
| a510286439 | |||
| d9cb719941 | |||
| c763a0fbec | |||
| 5038587d89 | |||
| f097eb62ae | |||
| 66d0cfccf3 | |||
| 79f16e3b38 | |||
| 46ad212792 | |||
| f58fb168fc | |||
| 999431111f | |||
| b9daacf3ef | |||
| 0ac8f9ea77 | |||
| 97d664f0e7 | |||
| 558447f784 | |||
| 0d8fa93a05 | |||
| 7c06a79445 | |||
| 68ac4d8fd5 | |||
| 388bc0d68e | |||
| 1c0b209e8c | |||
| 7f262dbb49 | |||
| be29079f38 | |||
| 6effcc5d82 | |||
| 83f9ff259f | |||
| 7646382106 | |||
| 3ec8d8485c | |||
| e6f245885b | |||
| c1a8df8dee | |||
| 68f7400533 | |||
| a0b69e14b7 | |||
| 378321d6f2 | |||
| fd33ee0e09 | |||
| 072c23857c | |||
| 21b7b95bec | |||
| 87e5bd7154 | |||
| 67171f348a | |||
| c491ba5cac | |||
| 00a2fd5cb0 | |||
| dcef276f6d | |||
| 47debf7742 | |||
| 4e7d6e01e8 | |||
| e12c0ee511 | |||
| 4b8c4cde3c | |||
| 923db17dae | |||
| c97d83a1d3 | |||
| 7900586ca5 | |||
| f4f51068bf | |||
| 94bb2d4437 | |||
| 7c68f52ae7 | |||
| a8588e1a22 | |||
| 9b60efd343 | |||
| 2e2193d850 | |||
| 2aba617b5f | |||
| 3e42d35f34 | |||
| c11f0eced2 | |||
| b4902d8fb5 | |||
| 68ad1bec7d | |||
| be23cf67c6 | |||
| 4668b12817 | |||
| 1c7db60d31 | |||
| 388862a605 | |||
| 2012ba6f33 | |||
| 25a96c0ceb | |||
| b77065c2aa | |||
| 8840b7ae32 | |||
| 0a433537c7 | |||
| 6ae085e6ce | |||
| e1dd4aa651 | |||
| 30ca241f7e | |||
| e0b595477b | |||
| 886f153a3e | |||
| db48fc6ec1 | |||
| 6fb13a4903 | |||
| 461239e2e9 | |||
| 574de95403 | |||
| 6ac6157d06 | |||
| 171b890f0d | |||
| 7d30762253 | |||
| daadf62ea8 | |||
| 70d1b97b97 | |||
| 80d37ee744 | |||
| fb82fa3a39 | |||
| 4c6383562f | |||
| 45a49ec861 | |||
| 64e738f6b5 | |||
| 9c42a70635 | |||
| 2e67f19726 | |||
| f56d714e75 | |||
| d5bd03b9e6 | |||
| d6f77c6bb2 | |||
| aa73b09074 | |||
| fffbbb55f7 | |||
| bdfb9c0684 | |||
| 1a7e0bd6a5 | |||
| 078fdaacbf | |||
| 29b24b6107 | |||
| 52494e916a | |||
| acc9cc4b7d | |||
| b3be6fda21 | |||
| bb3c070d2d | |||
| 6113023e19 | |||
| 1e48689566 | |||
| f11e2319eb | |||
| 90da2f2dcb | |||
| ce57fb837d | |||
| 8363c57a03 | |||
| 9582af95f3 | |||
| 16e79551ae | |||
| e2dcfa2e6e | |||
| fd5e26f84a | |||
| 30d85a9d13 | |||
| 863c3761dd | |||
| 6aa55dd69f | |||
| 947a3bc5b0 | |||
| 0a79abf0f7 | |||
| cca49c1a43 | |||
| e20aac5588 | |||
| 971db920e3 | |||
| d1bab5b589 | |||
| 4deae7b45d | |||
| cf777816ce | |||
| e4fd4519c4 | |||
| b01bdb2bf1 | |||
| 3738fb1e2d | |||
| b9d115d066 | |||
| 52a32ef947 | |||
| 1cb2222a69 | |||
| c7dfcda157 | |||
| 21bab2d4eb | |||
| 5bed2ce551 | |||
| 4ceef827a3 | |||
| 8b227deafb | |||
| 0d75199db5 | |||
| 72de3bfc99 | |||
| 9d4c5fe976 | |||
| e6484962e1 | |||
| 9b268530af | |||
| f87be822d3 | |||
| c6a1c571e7 | |||
| e3bc4fc265 | |||
| f162bf58ac | |||
| 16f031df4d | |||
| 73eb5d9a04 | |||
| 0f458f6807 | |||
| 2cc4b6469f | |||
| 95ccdeb0e9 | |||
| 906a16a6c1 | |||
| 3393df9a68 | |||
| a93bfa9511 | |||
| 3c6add252f | |||
| 40820b18ca | |||
| 386c438bfd | |||
| 94dffe2046 | |||
| e861a50158 | |||
| 4bad2aeed5 | |||
| c23791ba1d | |||
| f1e4de1670 | |||
| 203a03ef24 | |||
| edab2a7fd5 | |||
| 74e6e7a80d | |||
| b0438c1ce1 | |||
| 7db46f76e9 | |||
| e98e7b8f91 | |||
| af255a43f3 | |||
| d86274b039 | |||
| d8600500c3 | |||
| d9138016ef | |||
| 4f6289f293 | |||
| 8e598882b6 | |||
| 2e780e2b11 | |||
| 80590d4434 | |||
| 74736d9723 | |||
| 5c20fcefc4 | |||
| e9ba1c4ede | |||
| 6767ddde4c | |||
| 56a326d7db | |||
| d340a01513 | |||
| 2dd2b279c8 | |||
| 88c124aa9f | |||
| e2ef3226af | |||
| 507b81a8a4 | |||
| 31d7639761 | |||
| 10847b3e0b | |||
| 7b513a95a1 | |||
| 411b96128c | |||
| 5ef5478861 | |||
| 50adbaefc8 | |||
| 424d57c2c1 | |||
| 76ed9d1703 | |||
| c4bab00c6d | |||
| ac9fc15981 | |||
| 1b977cae64 | |||
| 0cd1921971 | |||
| c193315e17 | |||
| 364996021e | |||
| f287c80e39 | |||
| f4a9a64c93 | |||
| c464ffaeac | |||
| 822aa4155d | |||
| 3d192e22f2 | |||
| 6e8e2b6201 | |||
| 16e4144409 | |||
| 6dacc50163 | |||
| 6d71ad9bcc | |||
| fc5d93f9cc | |||
| 3196840b05 | |||
| 51eea4dea3 | |||
| 314154e9fd | |||
| 4a93264dc5 | |||
| 39a96a7f73 | |||
| 9ee7425627 | |||
| 923fdd42ec | |||
| 50cd28f882 | |||
| 7bc865a2e0 | |||
| 1cf3c77ae9 | |||
| d3cda5d878 | |||
| 4f09f38f2e | |||
| 4980caee1b | |||
| ae89c66b17 | |||
| bb7059a9e1 | |||
| 05de67ba76 | |||
| 25bf7df4d1 | |||
| e44cbcc1d5 | |||
| c876bcb849 | |||
| febff84421 | |||
| 1ca261986e | |||
| 71e319a815 | |||
| a67e222c35 | |||
| e173a93b57 | |||
| 3dbf62ca08 | |||
| bb5239e553 | |||
| faec5c3f84 | |||
| 9c359627f3 | |||
| 2a0208c554 | |||
| f513edc006 | |||
| 9e39be282b | |||
| d628f75244 | |||
| cd09b5d356 | |||
| bb2cf0170d |
@@ -1,13 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://www.amazon.com/hz/wishlist/ls/8WPVFLQQDPTA']
|
||||
# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
+29
-28
@@ -3,7 +3,7 @@ on: [pull_request]
|
||||
jobs:
|
||||
|
||||
build-mac-xcodebuild:
|
||||
name: Mac UI / xcodebuild / ${{ matrix.os }}
|
||||
name: Mac / xcodebuild / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest] #[macos-13, macos-14, macos-15]
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
xcodebuild CODE_SIGN_IDENTITY=-
|
||||
|
||||
build-sdl-cmake:
|
||||
name: SDL UI / cmake / ${{ matrix.os }}
|
||||
name: SDL / cmake / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
cmake --build build -v -j"$jobs"
|
||||
|
||||
build-sdl-scons:
|
||||
name: SDL UI / scons / ${{ matrix.os }}
|
||||
name: SDL / scons / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-latest]
|
||||
@@ -96,8 +96,8 @@ jobs:
|
||||
esac
|
||||
scons -j"$jobs"
|
||||
|
||||
build-qt5:
|
||||
name: Qt 5 / ${{ matrix.os }}
|
||||
build-qt6:
|
||||
name: Qt / qmake / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
@@ -106,10 +106,10 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
uses: jurplel/install-qt-action@v3
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
version: '5.15.2'
|
||||
archives: 'qtbase qtmultimedia qtx11extras icu'
|
||||
version: '6.8'
|
||||
modules: 'qtmultimedia'
|
||||
- name: Make
|
||||
working-directory: OSBindings/Qt
|
||||
shell: bash
|
||||
@@ -117,23 +117,24 @@ jobs:
|
||||
qmake -o Makefile clksignal.pro
|
||||
make
|
||||
|
||||
# build-qt6:
|
||||
# name: Qt 6 / ${{ matrix.os }}
|
||||
# strategy:
|
||||
# matrix:
|
||||
# os: [ubuntu-latest]
|
||||
# runs-on: ${{ matrix.os }}
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v4
|
||||
# - name: Install dependencies
|
||||
# uses: jurplel/install-qt-action@v4
|
||||
# with:
|
||||
# version: '6.8'
|
||||
# archives: qtbase
|
||||
# - name: Make
|
||||
# working-directory: OSBindings/Qt
|
||||
# shell: bash
|
||||
# run: |
|
||||
# qmake -o Makefile clksignal.pro
|
||||
# make
|
||||
build-qt6-cmake:
|
||||
name: Qt / cmake / ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt-get --allow-releaseinfo-change update
|
||||
sudo apt-get --fix-missing install cmake gcc-10 qt6-base-dev qt6-multimedia-dev
|
||||
- name: Make
|
||||
shell: bash
|
||||
run: |
|
||||
cmake -S. -Bbuild -DCLK_UI=Qt -DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
jobs=$(nproc --all)
|
||||
cmake --build build -v -j"$jobs"
|
||||
|
||||
@@ -96,6 +96,7 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
auto targetArchimedes = std::make_unique<ArchimedesTarget>();
|
||||
int bbc_hits = 0;
|
||||
int electron_hits = 0;
|
||||
int beebsid_hits = 0;
|
||||
bool format_prefers_bbc = false;
|
||||
|
||||
// Copy appropriate cartridges to the 8-bit targets.
|
||||
@@ -198,7 +199,12 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
"SRUNLOCK", "SRWIPE", "TUBE", "TYPE", "UNLOCK", "UNPLUG", "UROMS",
|
||||
"VERIFY", "ZERO"
|
||||
}) {
|
||||
if(std::search(file.data.begin(), file.data.end(), command, command+strlen(command)) != file.data.end()) {
|
||||
if(std::search(
|
||||
file.data.begin(),
|
||||
file.data.end(),
|
||||
command,
|
||||
command + strlen(command)
|
||||
) != file.data.end()) {
|
||||
targetElectron->has_ap6_rom = true;
|
||||
targetElectron->has_sideways_ram = true;
|
||||
}
|
||||
@@ -206,7 +212,6 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
|
||||
// Look for any 'BBC indicators', i.e. direct access to BBC-specific hardware.
|
||||
// Also currently a dense search.
|
||||
|
||||
const auto hits = [&](const std::initializer_list<uint16_t> collection) {
|
||||
int hits = 0;
|
||||
for(const auto address: collection) {
|
||||
@@ -253,6 +258,18 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
0xfe0c, 0xfe0d, 0xfe0e,
|
||||
0xfe0f,
|
||||
});
|
||||
|
||||
// While here, look for attempted SID accesses.
|
||||
beebsid_hits += hits({
|
||||
// ULA addresses that aren't also the BBC's CRTC.
|
||||
0xfc20, 0xfc21, 0xfc22, 0xfc23,
|
||||
0xfc24, 0xfc25, 0xfc26, 0xfc27,
|
||||
0xfc28, 0xfc29, 0xfc2a, 0xfc2b,
|
||||
0xfc2c, 0xfc2d, 0xfc2e, 0xfc2f,
|
||||
0xfc30, 0xfc31, 0xfc32, 0xfc33,
|
||||
0xfc34, 0xfc35, 0xfc36, 0xfc37,
|
||||
0xfc38,
|
||||
});
|
||||
}
|
||||
} else if(adfs_catalogue) {
|
||||
// Archimedes options, implicitly: ADFS, non-Hugo.
|
||||
@@ -316,6 +333,9 @@ Analyser::Static::TargetList Analyser::Static::Acorn::GetTargets(
|
||||
}
|
||||
}
|
||||
|
||||
// Make a BeebSID guess.
|
||||
targetBBC->has_beebsid = beebsid_hits > 20;
|
||||
|
||||
TargetList targets;
|
||||
if(!targetElectron->media.empty() && !targetBBC->media.empty()) {
|
||||
if(bbc_hits > electron_hits || (bbc_hits == electron_hits && format_prefers_bbc)) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -20,8 +20,8 @@ struct Target: public Analyser::Static::Target, public Reflection::StructImpl<Ta
|
||||
Model model = Model::CPC464;
|
||||
std::string loading_command;
|
||||
|
||||
ReflectableEnum(CRTCType, Type0, Type1, Type2, Type3);
|
||||
CRTCType crtc_type = CRTCType::Type2;
|
||||
// ReflectableEnum(CRTCType, Type0, Type1, Type2, Type3);
|
||||
// CRTCType crtc_type = CRTCType::Type2;
|
||||
|
||||
// This is used internally for testing; it therefore isn't exposed reflectively.
|
||||
bool catch_ssm_codes = false;
|
||||
@@ -32,9 +32,9 @@ private:
|
||||
friend Reflection::StructImpl<Target>;
|
||||
void declare_fields() {
|
||||
DeclareField(model);
|
||||
DeclareField(crtc_type);
|
||||
// DeclareField(crtc_type);
|
||||
AnnounceEnum(Model);
|
||||
AnnounceEnum(CRTCType);
|
||||
// AnnounceEnum(CRTCType);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
|
||||
bool
|
||||
) {
|
||||
// This analyser can comprehend disks only.
|
||||
if(media.disks.empty()) return {};
|
||||
if(media.disks.empty() && media.file_bundles.empty()) return {};
|
||||
|
||||
// Otherwise, assume a return will happen.
|
||||
Analyser::Static::TargetList targets;
|
||||
@@ -86,7 +86,36 @@ Analyser::Static::TargetList Analyser::Static::Enterprise::GetTargets(
|
||||
}
|
||||
}
|
||||
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
if(!media.file_bundles.empty()) {
|
||||
auto &bundle = *media.file_bundles.front();
|
||||
const auto key = bundle.key_file();
|
||||
|
||||
if(key.has_value()) {
|
||||
auto file = bundle.open(*key, Storage::FileMode::Read);
|
||||
|
||||
enum class FileType: uint16_t {
|
||||
COM = 0x0500,
|
||||
BAS = 0x0400,
|
||||
};
|
||||
|
||||
// Check for a .COM by inspecting the header.
|
||||
const auto type = FileType(file.get_le<uint16_t>());
|
||||
const uint16_t size = file.get_le<uint16_t>();
|
||||
// There are then 12 bytes of 0 padding that could be tested for.
|
||||
|
||||
if((type != FileType::COM && type != FileType::BAS) || size > file.stats().st_size - 16) {
|
||||
target->media.file_bundles.clear();
|
||||
} else {
|
||||
target->loading_command = "run \"file:\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: look for a key file, similar logic to above.
|
||||
}
|
||||
|
||||
if(!target->media.empty()) {
|
||||
targets.push_back(std::unique_ptr<Analyser::Static::Target>(target));
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
#include "Storage/Disk/DiskImage/Formats/STX.hpp"
|
||||
#include "Storage/Disk/DiskImage/Formats/WOZ.hpp"
|
||||
|
||||
// File Bundles.
|
||||
#include "Storage/FileBundle/FileBundle.hpp"
|
||||
|
||||
// Mass Storage Devices (i.e. usually, hard disks)
|
||||
#include "Storage/MassStorage/Formats/DAT.hpp"
|
||||
#include "Storage/MassStorage/Formats/DSK.hpp"
|
||||
@@ -123,6 +126,8 @@ public:
|
||||
media.cartridges.push_back(instance);
|
||||
} else if constexpr (std::is_base_of_v<Storage::MassStorage::MassStorageDevice, InstanceT>) {
|
||||
media.mass_storage_devices.push_back(instance);
|
||||
} else if constexpr (std::is_base_of_v<Storage::FileBundle::FileBundle, InstanceT>) {
|
||||
media.file_bundles.push_back(instance);
|
||||
} else {
|
||||
static_assert(always_false_v<InstanceT>, "Unexpected type encountered.");
|
||||
}
|
||||
@@ -208,13 +213,14 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adf");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AmigaADF>>(TargetPlatform::Amiga, "adf");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::AcornADF>>(TargetPlatform::Acorn, "adl");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
|
||||
|
||||
accumulator.try_standard<FileBundle::LocalFSFileBundle>(TargetPlatform::Enterprise, "bas");
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::AllCartridge, "bin");
|
||||
|
||||
accumulator.try_standard<Tape::CAS>(TargetPlatform::MSX, "cas");
|
||||
accumulator.try_standard<Tape::TZX>(TargetPlatform::AmstradCPC, "cdt");
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::Coleco, "col");
|
||||
accumulator.try_standard<FileBundle::LocalFSFileBundle>(TargetPlatform::Enterprise, "com");
|
||||
accumulator.try_standard<Tape::CSW>(TargetPlatform::AllTape, "csw");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::D64>>(TargetPlatform::Commodore8bit, "d64");
|
||||
@@ -257,6 +263,7 @@ static Media GetMediaAndPlatforms(const std::string &file_name, TargetPlatform::
|
||||
TargetPlatform::Amiga | TargetPlatform::AtariST | TargetPlatform::AmstradCPC | TargetPlatform::ZXSpectrum,
|
||||
"ipf");
|
||||
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::JFD>>(TargetPlatform::Archimedes, "jfd");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::MSA>>(TargetPlatform::AtariST, "msa");
|
||||
accumulator.try_standard<Cartridge::BinaryDump>(TargetPlatform::MSX, "mx2");
|
||||
accumulator.try_standard<Disk::DiskImageHolder<Disk::NIB>>(TargetPlatform::DiskII, "nib");
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "Storage/Cartridge/Cartridge.hpp"
|
||||
#include "Storage/Disk/Disk.hpp"
|
||||
#include "Storage/FileBundle/FileBundle.hpp"
|
||||
#include "Storage/MassStorage/MassStorageDevice.hpp"
|
||||
#include "Storage/Tape/Tape.hpp"
|
||||
#include "Storage/TargetPlatforms.hpp"
|
||||
@@ -33,9 +34,15 @@ struct Media {
|
||||
std::vector<std::shared_ptr<Storage::Tape::Tape>> tapes;
|
||||
std::vector<std::shared_ptr<Storage::Cartridge::Cartridge>> cartridges;
|
||||
std::vector<std::shared_ptr<Storage::MassStorage::MassStorageDevice>> mass_storage_devices;
|
||||
std::vector<std::shared_ptr<Storage::FileBundle::FileBundle>> file_bundles;
|
||||
|
||||
bool empty() const {
|
||||
return disks.empty() && tapes.empty() && cartridges.empty() && mass_storage_devices.empty();
|
||||
return
|
||||
disks.empty() &&
|
||||
tapes.empty() &&
|
||||
cartridges.empty() &&
|
||||
mass_storage_devices.empty() &&
|
||||
file_bundles.empty();
|
||||
}
|
||||
|
||||
Media &operator +=(const Media &rhs) {
|
||||
@@ -47,6 +54,7 @@ struct Media {
|
||||
append(tapes, rhs.tapes);
|
||||
append(cartridges, rhs.cartridges);
|
||||
append(mass_storage_devices, rhs.mass_storage_devices);
|
||||
append(file_bundles, rhs.file_bundles);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ bool IsSpectrumDisk(const std::shared_ptr<Storage::Disk::Disk> &disk) {
|
||||
// Check the first ten bytes of the first sector for the disk format; if these are all
|
||||
// the same value then instead substitute a default format.
|
||||
std::array<uint8_t, 10> format;
|
||||
std::copy(boot_sector->samples[0].begin(), boot_sector->samples[0].begin() + 10, format.begin());
|
||||
std::copy_n(boot_sector->samples[0].begin(), 10, format.begin());
|
||||
if(std::all_of(format.begin(), format.end(), [&](const uint8_t v) { return v == format[0]; })) {
|
||||
format = {0x00, 0x00, 0x28, 0x09, 0x02, 0x01, 0x03, 0x02, 0x2a, 0x52};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||

|
||||
# Building Clock Signal
|
||||
|
||||
Clock Signal is available as [a macOS native application using
|
||||
Metal](#macos-app) or as [a cross-platform command-line-driven SDL executable
|
||||
using OpenGL](#sdl-app).
|
||||
Clock Signal is available as:
|
||||
* [a macOS native application using Metal](#macos-app);
|
||||
* [a cross-platform command-line-driven SDL executable using OpenGL](#sdl-app); and
|
||||
* [an X11-focussed Qt application](#Qt-app).
|
||||
|
||||
## macOS app
|
||||
|
||||
@@ -12,8 +13,8 @@ later and has no prerequisites beyond the normal system libraries. It can be
|
||||
built [using Xcode](#building-the-macos-app-using-xcode) or on the command line
|
||||
[using `xcodebuild`](#building-the-macos-app-using-xcodebuild).
|
||||
|
||||
Machine ROMs are intended to be built into the application bundle; populate the
|
||||
dummy folders below ROMImages before building.
|
||||
Machine ROMs may be built into the application bundle; populate the
|
||||
dummy folders below ROMImages before building to take advantage of that option.
|
||||
|
||||
The Xcode project is configured to sign the application using the developer's
|
||||
certificate, but if you are not the developer then you will get a "No signing
|
||||
@@ -22,7 +23,7 @@ application to run locally.
|
||||
|
||||
### Building the macOS app using Xcode
|
||||
|
||||
Open the Clock Signal Xcode project in OSBindings/Mac.
|
||||
Open the Clock Signal Xcode project in `OSBindings/Mac`.
|
||||
|
||||
To avoid signing errors, edit the project, select the Signing & Capabilities
|
||||
tab, and change the Signing Certificate drop-down menu from "Development" to
|
||||
@@ -61,7 +62,13 @@ after the configuration (e.g. "Debug" or "Release").
|
||||
The SDL app can be built on Linux, BSD, macOS, and other Unix-like operating
|
||||
systems. Prerequisites are SDL 2, ZLib and OpenGL (or Mesa). OpenGL 3.2 or
|
||||
better is required at runtime. It can be built [using
|
||||
SCons](#building-the-sdl-app-using-scons).
|
||||
SCons](#building-the-sdl-app-using-scons) or [CMake](#building-the-sdl-app-using-cmake).
|
||||
|
||||
### Dependency installation
|
||||
|
||||
If using Ubuntu:
|
||||
|
||||
sudo apt-get --fix-missing install cmake gcc-10 libsdl2-dev
|
||||
|
||||
### Building the SDL app using SCons
|
||||
|
||||
@@ -76,6 +83,25 @@ from here or install it by copying it where you want it, for example:
|
||||
|
||||
cp clksignal /usr/local/bin
|
||||
|
||||
### Building the SDL app using CMake
|
||||
|
||||
From the root directory of this repository, run:
|
||||
|
||||
cmake -S. -Bbuild -DCLK_UI=SDL -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
|
||||
You can add a `-j` flag to the second `cmak` invocation to build in parallel,
|
||||
e.g. if you if you have 8 cores:
|
||||
|
||||
cmake --build build -j8
|
||||
|
||||
The `clksignal` executable will be created in a new `build` subdirectory.
|
||||
You can run it from there or install it by copying it where you want it, for example:
|
||||
|
||||
cp build/clksignal /usr/local/bin
|
||||
|
||||
### Usage
|
||||
|
||||
To start an emulator with a particular disk image `file`, if you've installed
|
||||
`clksignal` to a directory in your `PATH`, run:
|
||||
|
||||
@@ -97,3 +123,39 @@ not included and may be located in either /usr/local/share/CLK/ or
|
||||
/usr/share/CLK/. You will be prompted for them if they are found to be missing.
|
||||
The structure should mirror that under OSBindings in the source archive; see the
|
||||
readme.txt in each folder to determine the proper files and names ahead of time.
|
||||
|
||||
## Qt app
|
||||
|
||||
The Qt app can be built on any Unix-like operating system, though it's optimised
|
||||
for use under X11. Prerequisites are t 6, ZLib and OpenGL (or Mesa). OpenGL 3.2 or
|
||||
better is required at runtime. It can be built using [CMake](#building-the-qt-app-using-cmake)
|
||||
or [QMake](#building-the-qt-app-using-qmake).
|
||||
|
||||
### Dependency installation
|
||||
|
||||
If using Ubuntu:
|
||||
|
||||
sudo apt-get --fix-missing install cmake gcc-10 qt6-base-dev qt6-multimedia-dev
|
||||
|
||||
### Building the Qt app using CMake
|
||||
|
||||
From the root directory of this repository, run:
|
||||
|
||||
cmake -S. -Bbuild -DCLK_UI=Qt -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build
|
||||
|
||||
You can add a `-j` flag to the second `cmak` invocation to build in parallel,
|
||||
e.g. if you if you have 8 cores:
|
||||
|
||||
cmake --build build -j8
|
||||
|
||||
The `clksignal` executable will be created in a new `build` subdirectory.
|
||||
|
||||
### Building the Qt app using QMake
|
||||
|
||||
From `OSBindings/Qt` run:
|
||||
|
||||
qmake -o Makefile clksignal.pro
|
||||
make
|
||||
|
||||
Alternatively open the project file in Qt Creator and use it's build button.
|
||||
|
||||
+21
-5
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
|
||||
|
||||
project(CLK
|
||||
LANGUAGES CXX
|
||||
VERSION 24.01.22
|
||||
VERSION 25.12.12
|
||||
)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
@@ -10,7 +10,8 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
set(CLK_UIS "SDL")
|
||||
#list(PREPEND CLK_UIS "QT")
|
||||
list(PREPEND CLK_UIS "Qt")
|
||||
|
||||
#if(APPLE)
|
||||
# list(PREPEND CLK_UIS "MAC")
|
||||
# set(CLK_DEFAULT_UI "MAC")
|
||||
@@ -30,8 +31,16 @@ message(STATUS "Configuring for ${CLK_UI} UI")
|
||||
|
||||
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
include_directories(".")
|
||||
include("CLK_SOURCES")
|
||||
|
||||
if(CLK_UI STREQUAL "Qt")
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} $ENV{QT6_DIR})
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets OpenGLWidgets Multimedia)
|
||||
endif()
|
||||
|
||||
include("CLK_SOURCES")
|
||||
add_executable(clksignal ${CLK_SOURCES})
|
||||
|
||||
if(MSVC)
|
||||
@@ -55,8 +64,12 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CLK_UI STREQUAL "QT")
|
||||
# TODO: Build the Qt version.
|
||||
if(CLK_UI STREQUAL "Qt")
|
||||
target_link_libraries(clksignal PRIVATE Qt::Widgets Qt::OpenGLWidgets Qt::Multimedia)
|
||||
find_package(X11 REQUIRED)
|
||||
target_link_libraries(clksignal PRIVATE X11)
|
||||
include_directories("OSBindings/Qt")
|
||||
add_compile_definitions(TARGET_QT)
|
||||
elseif(APPLE)
|
||||
set(BLA_VENDOR Apple)
|
||||
find_package(BLAS REQUIRED)
|
||||
@@ -68,4 +81,7 @@ if(CLK_UI STREQUAL "SDL")
|
||||
target_link_libraries(clksignal PRIVATE SDL2::SDL2)
|
||||
endif()
|
||||
|
||||
# Somewhat boilerplate; more for the Snap than anything.
|
||||
install(TARGETS clksignal RUNTIME DESTINATION bin)
|
||||
|
||||
# TODO: Investigate building on Windows.
|
||||
|
||||
@@ -562,7 +562,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
}
|
||||
|
||||
set_data_mode(DataMode::Writing);
|
||||
begin_writing(false);
|
||||
begin_writing(false, false);
|
||||
for(int c = 0; c < (get_is_double_density() ? 12 : 6); c++) {
|
||||
write_byte(0);
|
||||
}
|
||||
@@ -755,7 +755,7 @@ void WD1770::posit_event(const int new_event_type) {
|
||||
}
|
||||
|
||||
WAIT_FOR_EVENT(Event1770::IndexHoleTarget);
|
||||
begin_writing(true);
|
||||
begin_writing(true, false);
|
||||
index_hole_count_ = 0;
|
||||
|
||||
write_track_write_loop:
|
||||
|
||||
@@ -487,7 +487,7 @@ void i8272::posit_event(const int event_type) {
|
||||
|
||||
write_data_found_header:
|
||||
WAIT_FOR_BYTES(get_is_double_density() ? 22 : 11);
|
||||
begin_writing(true);
|
||||
begin_writing(true, false);
|
||||
|
||||
write_id_data_joiner(command_.command() == Command::WriteDeletedData, true);
|
||||
|
||||
@@ -603,7 +603,7 @@ void i8272::posit_event(const int event_type) {
|
||||
// Wait for the index hole.
|
||||
WAIT_FOR_EVENT(Event::IndexHole);
|
||||
index_hole_count_ = 0;
|
||||
begin_writing(true);
|
||||
begin_writing(true, false);
|
||||
|
||||
// Write start-of-track.
|
||||
write_start_of_track();
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "Storage.hpp"
|
||||
#include "YamahaCommands.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -42,7 +42,7 @@ void Base<personality>::draw_sprites(
|
||||
|
||||
int sprite_buffer[256];
|
||||
int sprite_collision = 0;
|
||||
memset(&sprite_buffer[start], 0, size_t(end - start)*sizeof(sprite_buffer[0]));
|
||||
std::fill(&sprite_buffer[start], &sprite_buffer[end], 0);
|
||||
|
||||
if constexpr (mode == SpriteMode::MasterSystem) {
|
||||
// Draw all sprites into the sprite buffer.
|
||||
|
||||
@@ -403,10 +403,10 @@ void AY38910SampleSource<is_stereo>::reset() {
|
||||
// TODO: the below is a guess. Look up real answers.
|
||||
|
||||
selected_register_ = 0;
|
||||
std::fill(registers_, registers_ + 16, 0);
|
||||
std::fill_n(registers_, 16, 0);
|
||||
|
||||
task_queue_.enqueue([&] {
|
||||
std::fill(output_registers_, output_registers_ + 16, 0);
|
||||
std::fill_n(output_registers_, 16, 0);
|
||||
evaluate_output_volume();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -296,8 +296,9 @@ int DiskII::read_address(int address) {
|
||||
inputs_ &= ~input_mode;
|
||||
break;
|
||||
case 0xf:
|
||||
if(!(inputs_ & input_mode))
|
||||
drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false);
|
||||
if(!(inputs_ & input_mode)) {
|
||||
drives_[active_drive_].begin_writing(Storage::Time(1, int(clock_rate_)), false, false);
|
||||
}
|
||||
inputs_ |= input_mode;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -346,7 +346,9 @@ void IWM::select_shift_mode() {
|
||||
|
||||
// If writing mode just began, set the drive into write mode and cue up the first output byte.
|
||||
if(old_shift_mode != ShiftMode::Writing && shift_mode_ == ShiftMode::Writing) {
|
||||
if(drives_[active_drive_]) drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false);
|
||||
if(drives_[active_drive_]) {
|
||||
drives_[active_drive_]->begin_writing(Storage::Time(1, clock_rate_ / bit_length_.as_integral()), false, false);
|
||||
}
|
||||
shift_register_ = next_output_;
|
||||
write_handshake_ |= 0x80 | 0x40;
|
||||
output_bits_remaining_ = 8;
|
||||
|
||||
@@ -27,6 +27,7 @@ ReflectableEnum(Display,
|
||||
|
||||
namespace Options {
|
||||
|
||||
static constexpr auto DisplayOptionName = "display";
|
||||
template <typename Owner> class Display {
|
||||
public:
|
||||
Configurable::Display output;
|
||||
@@ -34,11 +35,12 @@ public:
|
||||
|
||||
protected:
|
||||
void declare_display_option() {
|
||||
static_cast<Owner *>(this)->declare(&output, "output");
|
||||
static_cast<Owner *>(this)->declare(&output, DisplayOptionName);
|
||||
AnnounceEnumNS(Configurable, Display);
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto QuickLoadOptionName = "accelerate_media_loading";
|
||||
template <typename Owner> class QuickLoad {
|
||||
public:
|
||||
bool quick_load;
|
||||
@@ -46,10 +48,11 @@ public:
|
||||
|
||||
protected:
|
||||
void declare_quickload_option() {
|
||||
static_cast<Owner *>(this)->declare(&quick_load, "quickload");
|
||||
static_cast<Owner *>(this)->declare(&quick_load, QuickLoadOptionName);
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto QuickBootOptionName = "shorten_machine_startup";
|
||||
template <typename Owner> class QuickBoot {
|
||||
public:
|
||||
bool quick_boot;
|
||||
@@ -57,10 +60,11 @@ public:
|
||||
|
||||
protected:
|
||||
void declare_quickboot_option() {
|
||||
static_cast<Owner *>(this)->declare(&quick_boot, "quickboot");
|
||||
static_cast<Owner *>(this)->declare(&quick_boot, QuickBootOptionName);
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr auto DynamicCropOptionName = "select_visible_area_dynamically";
|
||||
template <typename Owner> class DynamicCrop {
|
||||
public:
|
||||
bool dynamic_crop;
|
||||
@@ -68,7 +72,7 @@ public:
|
||||
|
||||
protected:
|
||||
void declare_dynamic_crop_option() {
|
||||
static_cast<Owner *>(this)->declare(&dynamic_crop, "dynamiccrop");
|
||||
static_cast<Owner *>(this)->declare(&dynamic_crop, DynamicCropOptionName);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -258,7 +258,7 @@ std::pair<int, InstructionSet::M50740::Instruction> Decoder::decode(const uint8_
|
||||
const int bytes_to_consume = std::min(int(end - source), outstanding_bytes);
|
||||
|
||||
consumed_ += bytes_to_consume;
|
||||
source += bytes_to_consume;
|
||||
/* source += bytes_to_consume; */
|
||||
operand_bytes_ += bytes_to_consume;
|
||||
|
||||
if(operand_size_ == operand_bytes_) {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
using namespace InstructionSet::M50740;
|
||||
|
||||
@@ -39,13 +38,13 @@ Executor::Executor(PortHandler &port_handler) : port_handler_(port_handler) {
|
||||
|
||||
// Fuzz RAM; then set anything that may be replaced by ROM to FF.
|
||||
Memory::Fuzz(memory_);
|
||||
memset(&memory_[0x100], 0xff, memory_.size() - 0x100);
|
||||
std::fill(memory_.begin() + 0x100, memory_.end(), 0xff);
|
||||
}
|
||||
|
||||
void Executor::set_rom(const std::vector<uint8_t> &rom) {
|
||||
// Copy into place, and reset.
|
||||
const auto length = std::min(size_t(0x1000), rom.size());
|
||||
memcpy(&memory_[0x2000 - length], rom.data(), length);
|
||||
std::copy_n(rom.begin(), length, memory_.end() - length);
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Outputs/Log.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
namespace Archimedes {
|
||||
|
||||
|
||||
@@ -612,7 +612,7 @@ public:
|
||||
pixels <<= 1;
|
||||
}
|
||||
} else {
|
||||
std::fill(pixel_pointer_, pixel_pointer_ + 12, 0);
|
||||
std::fill_n(pixel_pointer_, 12, 0);
|
||||
pixel_pointer_ += 12;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -34,6 +34,8 @@
|
||||
#include "Tape.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Electron {
|
||||
|
||||
template <bool has_scsi_bus> class ConcreteMachine:
|
||||
@@ -59,10 +61,12 @@ public:
|
||||
scsi_device_(scsi_bus_.add_device()),
|
||||
video_(ram_),
|
||||
sound_generator_(audio_queue_),
|
||||
speaker_(sound_generator_) {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
for(int c = 0; c < 16; c++)
|
||||
memset(roms_[c], 0xff, 16384);
|
||||
speaker_(sound_generator_)
|
||||
{
|
||||
std::fill(std::begin(key_states_), std::end(key_states_), 0);
|
||||
for(int c = 0; c < 16; c++) {
|
||||
std::fill(std::begin(roms_[c]), std::end(roms_[c]), 0xff);
|
||||
}
|
||||
|
||||
tape_.set_delegate(this);
|
||||
set_clock_rate(2000000);
|
||||
@@ -181,7 +185,7 @@ public:
|
||||
}
|
||||
|
||||
void clear_all_keys() final {
|
||||
memset(key_states_, 0, sizeof(key_states_));
|
||||
std::fill(std::begin(key_states_), std::end(key_states_), 0);
|
||||
if(is_holding_shift_) set_key_state(KeyShift, true);
|
||||
}
|
||||
|
||||
@@ -660,8 +664,8 @@ private:
|
||||
// Copy in, with mirroring.
|
||||
std::size_t rom_ptr = 0;
|
||||
while(rom_ptr < 16384) {
|
||||
std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size());
|
||||
std::memcpy(&target[rom_ptr], data.data(), size_to_copy);
|
||||
const std::size_t size_to_copy = std::min(16384 - rom_ptr, data.size());
|
||||
std::copy_n(data.begin(), size_to_copy, &target[rom_ptr]);
|
||||
rom_ptr += size_to_copy;
|
||||
}
|
||||
|
||||
@@ -673,8 +677,9 @@ private:
|
||||
/*!
|
||||
Enables @c slot as sideways RAM; ensures that it does not currently contain a valid ROM signature.
|
||||
*/
|
||||
void set_sideways_ram(ROM slot) {
|
||||
std::memset(roms_[int(slot)], 0xff, 16*1024);
|
||||
void set_sideways_ram(const ROM slot) {
|
||||
auto &rom = roms_[int(slot)];
|
||||
std::fill(std::begin(rom), std::end(rom), 0xff);
|
||||
if(int(slot) < 16) {
|
||||
rom_inserted_[int(slot)] = true;
|
||||
rom_write_masks_[int(slot)] = true;
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
#include "Numeric/CRC.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
@@ -626,7 +627,7 @@ public:
|
||||
Sets all keys as currently unpressed.
|
||||
*/
|
||||
void clear_all_keys() {
|
||||
memset(rows_, 0xff, sizeof(rows_));
|
||||
std::fill(std::begin(rows_), std::end(rows_), 0xff);
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<Inputs::Joystick>> &get_joysticks() {
|
||||
|
||||
@@ -125,9 +125,14 @@ private:
|
||||
public:
|
||||
VideoBusHandler(uint8_t *ram, uint8_t *aux_ram) : ram_(ram), aux_ram_(aux_ram) {}
|
||||
|
||||
void perform_read(uint16_t address, size_t count, uint8_t *base_target, uint8_t *auxiliary_target) {
|
||||
memcpy(base_target, &ram_[address], count);
|
||||
memcpy(auxiliary_target, &aux_ram_[address], count);
|
||||
void perform_read(
|
||||
const uint16_t address,
|
||||
const size_t count,
|
||||
uint8_t *const base_target,
|
||||
uint8_t *const auxiliary_target
|
||||
) {
|
||||
std::copy_n(ram_ + address, count, base_target);
|
||||
std::copy_n(aux_ram_ + address, count, auxiliary_target);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
#include "SCSICard.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Apple::II;
|
||||
|
||||
@@ -58,7 +58,7 @@ ROM::Request SCSICard::rom_request() {
|
||||
}
|
||||
|
||||
// TODO: accept and supply real clock rate.
|
||||
SCSICard::SCSICard(ROM::Map &map, int clock_rate) :
|
||||
SCSICard::SCSICard(ROM::Map &map, const int clock_rate) :
|
||||
scsi_bus_(clock_rate),
|
||||
ncr5380_(scsi_bus_, clock_rate),
|
||||
storage_(scsi_bus_, 6)
|
||||
@@ -68,14 +68,19 @@ SCSICard::SCSICard(ROM::Map &map, int clock_rate) :
|
||||
if(rom == map.end()) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
memcpy(rom_.data(), rom->second.data(), rom_.size());
|
||||
std::copy(rom->second.begin(), rom->second.end(), rom_.begin());
|
||||
|
||||
// Set up initial banking.
|
||||
rom_pointer_ = rom_.data();
|
||||
ram_pointer_ = ram_.data();
|
||||
}
|
||||
|
||||
void SCSICard::perform_bus_operation(Select select, bool is_read, uint16_t address, uint8_t *value) {
|
||||
void SCSICard::perform_bus_operation(
|
||||
const Select select,
|
||||
const bool is_read,
|
||||
uint16_t address,
|
||||
uint8_t *const value
|
||||
) {
|
||||
switch(select) {
|
||||
default: break;
|
||||
|
||||
@@ -168,6 +173,6 @@ void SCSICard::set_storage_device(const std::shared_ptr<Storage::MassStorage::Ma
|
||||
storage_->set_storage(device);
|
||||
}
|
||||
|
||||
void SCSICard::set_activity_observer(Activity::Observer *observer) {
|
||||
void SCSICard::set_activity_observer(Activity::Observer *const observer) {
|
||||
scsi_bus_.set_activity_observer(observer);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "TIA.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
@@ -401,7 +402,7 @@ void TIA::output_for_cycles(int number_of_cycles) {
|
||||
bool is_reset = output_cursor < 224 && horizontal_counter_ >= 224;
|
||||
|
||||
if(!output_cursor) {
|
||||
std::memset(collision_buffer_, 0, sizeof(collision_buffer_));
|
||||
std::fill(std::begin(collision_buffer_), std::end(collision_buffer_), 0);
|
||||
|
||||
ball_.motion_time %= 228;
|
||||
player_[0].motion_time %= 228;
|
||||
|
||||
@@ -436,7 +436,7 @@ public:
|
||||
}
|
||||
|
||||
void reinstall_rom_vector() {
|
||||
std::copy(rom_.begin(), rom_.begin() + 8, ram_.begin());
|
||||
std::copy_n(rom_.begin(), 8, ram_.begin());
|
||||
}
|
||||
|
||||
void flush_output(int outputs) final {
|
||||
|
||||
@@ -58,7 +58,7 @@ MachineBase::MachineBase(const Personality personality, const ROM::Map &roms) :
|
||||
if(rom == roms.end()) {
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
std::memcpy(rom_, rom->second.data(), std::min(sizeof(rom_), rom->second.size()));
|
||||
std::copy_n(rom->second.begin(), std::min(sizeof(rom_), rom->second.size()), rom_);
|
||||
}
|
||||
|
||||
Machine::Machine(const Personality personality, const ROM::Map &roms) :
|
||||
@@ -109,10 +109,7 @@ Cycles MachineBase::perform(const AddressT address, CPU::MOS6502Mk2::data_t<oper
|
||||
|
||||
void Machine::run_for(const Cycles cycles) {
|
||||
m6502_.run_for(cycles);
|
||||
|
||||
const bool drive_motor = drive_VIA_port_handler_.motor_enabled();
|
||||
get_drive().set_motor_on(drive_motor);
|
||||
if(drive_motor) {
|
||||
if(get_drive().get_motor_on()) {
|
||||
Storage::Disk::Controller::run_for(cycles);
|
||||
}
|
||||
}
|
||||
@@ -150,18 +147,40 @@ void MachineBase::process_input_bit(const int value) {
|
||||
} else {
|
||||
drive_VIA_port_handler_.set_sync_detected(false);
|
||||
}
|
||||
bit_window_offset_++;
|
||||
|
||||
++bit_window_offset_;
|
||||
if(bit_window_offset_ == 8) {
|
||||
drive_VIA_port_handler_.set_data_input(uint8_t(shift_register_));
|
||||
bit_window_offset_ = 0;
|
||||
if(drive_VIA_port_handler_.should_set_overflow()) {
|
||||
if(set_cpu_overflow_) {
|
||||
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(true);
|
||||
}
|
||||
} else {
|
||||
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
|
||||
}
|
||||
else m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
|
||||
}
|
||||
|
||||
// the 1540 does not recognise index holes
|
||||
void MachineBase::is_writing_final_bit() {
|
||||
if(set_cpu_overflow_) {
|
||||
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MachineBase::process_write_completed() {
|
||||
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
|
||||
serialise_shift_output();
|
||||
}
|
||||
|
||||
void MachineBase::serialise_shift_output() {
|
||||
auto &drive = get_drive();
|
||||
uint8_t value = port_a_output_;
|
||||
for(int c = 0; c < 8; c++) {
|
||||
drive.write_bit(value & 0x80);
|
||||
value <<= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The 1540 does not recognise index holes.
|
||||
void MachineBase::process_index_hole() {}
|
||||
|
||||
// MARK: - Drive VIA delegate
|
||||
@@ -174,6 +193,29 @@ void MachineBase::drive_via_did_set_data_density(DriveVIA &, const int density)
|
||||
set_expected_bit_length(Storage::Encodings::CommodoreGCR::length_of_a_bit_in_time_zone(unsigned(density)));
|
||||
}
|
||||
|
||||
void MachineBase::drive_via_did_set_drive_motor(DriveVIA &, const bool enabled) {
|
||||
get_drive().set_motor_on(enabled);
|
||||
}
|
||||
|
||||
void MachineBase::drive_via_did_set_write_mode(DriveVIA &, const bool enabled) {
|
||||
if(enabled) {
|
||||
begin_writing(false, true);
|
||||
} else {
|
||||
end_writing();
|
||||
}
|
||||
}
|
||||
|
||||
void MachineBase::drive_via_set_to_shifter_output(DriveVIA &, const uint8_t value) {
|
||||
port_a_output_ = value;
|
||||
}
|
||||
|
||||
void MachineBase::drive_via_should_set_cpu_overflow(DriveVIA &, const bool overflow) {
|
||||
set_cpu_overflow_ = overflow;
|
||||
if(!overflow) {
|
||||
m6502_.set<CPU::MOS6502Mk2::Line::Overflow>(false);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - SerialPortVIA
|
||||
|
||||
template <MOS::MOS6522::Port port>
|
||||
@@ -200,13 +242,17 @@ void SerialPortVIA::set_serial_line_state(
|
||||
const bool value,
|
||||
MOS::MOS6522::MOS6522<SerialPortVIA> &via
|
||||
) {
|
||||
const auto set = [&](const uint8_t mask) {
|
||||
port_b_ = (port_b_ & ~mask) | (value ? 0x00 : mask);
|
||||
};
|
||||
|
||||
switch(line) {
|
||||
default: break;
|
||||
case ::Commodore::Serial::Line::Data: port_b_ = (port_b_ & ~0x01) | (value ? 0x00 : 0x01); break;
|
||||
case ::Commodore::Serial::Line::Clock: port_b_ = (port_b_ & ~0x04) | (value ? 0x00 : 0x04); break;
|
||||
case ::Commodore::Serial::Line::Data: set(0x01); break;
|
||||
case ::Commodore::Serial::Line::Clock: set(0x04); break;
|
||||
case ::Commodore::Serial::Line::Attention:
|
||||
set(0x80);
|
||||
attention_level_input_ = !value;
|
||||
port_b_ = (port_b_ & ~0x80) | (value ? 0x00 : 0x80);
|
||||
via.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!value);
|
||||
update_data_line();
|
||||
break;
|
||||
@@ -218,7 +264,8 @@ void SerialPortVIA::set_serial_port(Commodore::Serial::Port &port) {
|
||||
}
|
||||
|
||||
void SerialPortVIA::update_data_line() {
|
||||
// "ATN (Attention) is an input on pin 3 of P2 and P3 that is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||
// "ATN (Attention) is an input on pin 3 of P2 and P3 that
|
||||
// is sensed at PB7 and CA1 of UC3 after being inverted by UA1"
|
||||
serial_port_->set_output(
|
||||
::Commodore::Serial::Line::Data,
|
||||
Serial::LineLevel(!data_level_output_ && (attention_level_input_ != attention_acknowledge_level_))
|
||||
@@ -249,24 +296,24 @@ void DriveVIA::set_data_input(const uint8_t value) {
|
||||
port_a_ = value;
|
||||
}
|
||||
|
||||
bool DriveVIA::should_set_overflow() {
|
||||
return should_set_overflow_;
|
||||
}
|
||||
|
||||
bool DriveVIA::motor_enabled() {
|
||||
return drive_motor_;
|
||||
}
|
||||
|
||||
template <MOS::MOS6522::Port port, MOS::MOS6522::Line line>
|
||||
void DriveVIA::set_control_line_output(const bool value) {
|
||||
if(port == MOS::MOS6522::Port::A && line == MOS::MOS6522::Line::Two) {
|
||||
should_set_overflow_ = value;
|
||||
if(set_cpu_overflow_ != value) {
|
||||
set_cpu_overflow_ = value;
|
||||
if(delegate_) {
|
||||
delegate_->drive_via_should_set_cpu_overflow(*this, set_cpu_overflow_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(port == MOS::MOS6522::Port::B && line == MOS::MOS6522::Line::Two) {
|
||||
// TODO: 0 = write, 1 = read.
|
||||
if(!value) {
|
||||
printf("NOT IMPLEMENTED: write mode\n");
|
||||
const bool new_write_mode = !value;
|
||||
if(new_write_mode != write_mode_) {
|
||||
write_mode_ = new_write_mode;
|
||||
if(delegate_) {
|
||||
delegate_->drive_via_did_set_write_mode(*this, write_mode_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,7 +322,13 @@ template <>
|
||||
void DriveVIA::set_port_output<MOS::MOS6522::Port::B>(const uint8_t value, uint8_t) {
|
||||
if(previous_port_b_output_ != value) {
|
||||
// Record drive motor state.
|
||||
drive_motor_ = value&4;
|
||||
const bool new_drive_motor = value & 4;
|
||||
if(new_drive_motor != drive_motor_) {
|
||||
drive_motor_ = new_drive_motor;
|
||||
if(delegate_) {
|
||||
delegate_->drive_via_did_set_drive_motor(*this, drive_motor_);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a head step.
|
||||
const int step_difference = ((value&3) - (previous_port_b_output_&3))&3;
|
||||
@@ -300,7 +353,9 @@ void DriveVIA::set_port_output<MOS::MOS6522::Port::B>(const uint8_t value, uint8
|
||||
|
||||
template <>
|
||||
void DriveVIA::set_port_output<MOS::MOS6522::Port::A>(const uint8_t value, uint8_t) {
|
||||
printf("TODO: output is %02x\n", value);
|
||||
if(delegate_) {
|
||||
delegate_->drive_via_set_to_shifter_output(*this, value);
|
||||
}
|
||||
}
|
||||
|
||||
void DriveVIA::set_activity_observer(Activity::Observer *const observer) {
|
||||
|
||||
@@ -82,6 +82,10 @@ public:
|
||||
struct Delegate {
|
||||
virtual void drive_via_did_step_head(DriveVIA &, int direction) = 0;
|
||||
virtual void drive_via_did_set_data_density(DriveVIA &, int density) = 0;
|
||||
virtual void drive_via_did_set_drive_motor(DriveVIA &, bool enabled) = 0;
|
||||
virtual void drive_via_did_set_write_mode(DriveVIA &, bool write) = 0;
|
||||
virtual void drive_via_should_set_cpu_overflow(DriveVIA &, bool overflow) = 0;
|
||||
virtual void drive_via_set_to_shifter_output(DriveVIA &, uint8_t) = 0;
|
||||
};
|
||||
void set_delegate(Delegate *);
|
||||
|
||||
@@ -91,8 +95,6 @@ public:
|
||||
void set_sync_detected(bool);
|
||||
void set_data_input(uint8_t);
|
||||
void set_is_read_only(bool);
|
||||
bool should_set_overflow();
|
||||
bool motor_enabled();
|
||||
|
||||
template <MOS::MOS6522::Port, MOS::MOS6522::Line>
|
||||
void set_control_line_output(bool value);
|
||||
@@ -104,8 +106,11 @@ public:
|
||||
|
||||
private:
|
||||
uint8_t port_b_ = 0xff, port_a_ = 0xff;
|
||||
bool should_set_overflow_ = false;
|
||||
|
||||
bool set_cpu_overflow_ = false;
|
||||
bool drive_motor_ = false;
|
||||
bool write_mode_ = false;
|
||||
|
||||
uint8_t previous_port_b_output_ = 0;
|
||||
Delegate *delegate_ = nullptr;
|
||||
Activity::Observer *observer_ = nullptr;
|
||||
@@ -144,8 +149,12 @@ protected:
|
||||
void mos6522_did_change_interrupt_status(void *mos6522) override;
|
||||
|
||||
// to satisfy DriveVIA::Delegate
|
||||
void drive_via_did_step_head(DriveVIA &, int direction) override;
|
||||
void drive_via_did_set_data_density(DriveVIA &, int density) override;
|
||||
void drive_via_did_step_head(DriveVIA &, int) override;
|
||||
void drive_via_did_set_data_density(DriveVIA &, int) override;
|
||||
void drive_via_did_set_drive_motor(DriveVIA &, bool) override;
|
||||
void drive_via_did_set_write_mode(DriveVIA &, bool) override;
|
||||
void drive_via_should_set_cpu_overflow(DriveVIA &, bool) override;
|
||||
void drive_via_set_to_shifter_output(DriveVIA &, uint8_t) override;
|
||||
|
||||
struct M6502Traits {
|
||||
static constexpr auto uses_ready_line = false;
|
||||
@@ -164,9 +173,15 @@ protected:
|
||||
MOS::MOS6522::MOS6522<DriveVIA> drive_VIA_;
|
||||
MOS::MOS6522::MOS6522<SerialPortVIA> serial_port_VIA_;
|
||||
|
||||
bool set_cpu_overflow_ = false;
|
||||
int shift_register_ = 0, bit_window_offset_;
|
||||
void process_input_bit(int value) override;
|
||||
void process_index_hole() override;
|
||||
void process_write_completed() override;
|
||||
void is_writing_final_bit() override;
|
||||
|
||||
uint8_t port_a_output_ = 0xff;
|
||||
void serialise_shift_output();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Interrupts.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Pager.hpp"
|
||||
#include "TapeHandler.hpp"
|
||||
#include "Video.hpp"
|
||||
|
||||
#include "Machines/MachineTypes.hpp"
|
||||
@@ -25,7 +26,6 @@
|
||||
#include "Analyser/Dynamic/ConfidenceCounter.hpp"
|
||||
#include "Analyser/Static/Commodore/Target.hpp"
|
||||
|
||||
#include "Storage/Tape/Parsers/Commodore.hpp"
|
||||
#include "Storage/Tape/Tape.hpp"
|
||||
#include "Machines/Commodore/SerialBus.hpp"
|
||||
#include "Machines/Commodore/1540/C1540.hpp"
|
||||
@@ -34,6 +34,8 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
using namespace Commodore;
|
||||
@@ -169,7 +171,6 @@ private:
|
||||
class ConcreteMachine:
|
||||
public Activity::Source,
|
||||
public BusController,
|
||||
public ClockingHint::Observer,
|
||||
public Configurable::Device,
|
||||
public CPU::MOS6502::BusHandler,
|
||||
public MachineTypes::AudioProducer,
|
||||
@@ -222,8 +223,7 @@ public:
|
||||
c1541_->run_for(Cycles(2000000));
|
||||
}
|
||||
|
||||
tape_player_ = std::make_unique<Storage::Tape::BinaryTapePlayer>(clock);
|
||||
tape_player_->set_clocking_hint_observer(this);
|
||||
tape_handler_.set_clock_rate(clock);
|
||||
|
||||
joysticks_.emplace_back(std::make_unique<Joystick>());
|
||||
joysticks_.emplace_back(std::make_unique<Joystick>());
|
||||
@@ -239,18 +239,32 @@ public:
|
||||
audio_queue_.lock_flush();
|
||||
}
|
||||
|
||||
// HACK. NOCOMMIT.
|
||||
// int pulse_num_ = 0;
|
||||
|
||||
template <CPU::MOS6502Mk2::BusOperation operation, typename AddressT>
|
||||
Cycles perform(const AddressT address, CPU::MOS6502Mk2::data_t<operation> value) {
|
||||
// Determine from the TED video subsystem the length of this clock cycle as perceived by the 6502,
|
||||
// relative to the master clock.
|
||||
const auto length = video_.cycle_length(operation == CPU::MOS6502Mk2::BusOperation::Ready);
|
||||
auto length = video_.cycle_length(operation == CPU::MOS6502Mk2::BusOperation::Ready);
|
||||
|
||||
// Update other subsystems.
|
||||
advance_timers_and_tape(length);
|
||||
if(!superspeed_) {
|
||||
|
||||
// Requiring a non-ready cycle here does two things:
|
||||
//
|
||||
// (1) avoids a race condition where skip_range_ is populated just before hitting a ready cycle,
|
||||
// and thereby the CPU can never escape the ready period; and
|
||||
// (2) providing more real time, periodically, during tape loading.
|
||||
if(operation != CPU::MOS6502Mk2::BusOperation::Ready && skip_range_) [[unlikely]] {
|
||||
if(
|
||||
!tape_handler_.apply_accelerated_range() ||
|
||||
(
|
||||
operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode &&
|
||||
(address < skip_range_->low || address >= skip_range_->high)
|
||||
)
|
||||
) {
|
||||
skip_range_ = std::nullopt;
|
||||
}
|
||||
length = Cycles(0);
|
||||
} else {
|
||||
video_.run_for(length);
|
||||
|
||||
if(c1541_) {
|
||||
@@ -258,11 +272,10 @@ public:
|
||||
c1541_->run_for(c1541_cycles_.divide(media_divider_));
|
||||
}
|
||||
|
||||
|
||||
time_since_audio_update_ += length;
|
||||
}
|
||||
|
||||
if(operation == CPU::MOS6502Mk2::BusOperation::Ready) {
|
||||
if constexpr (operation == CPU::MOS6502Mk2::BusOperation::Ready) {
|
||||
return length;
|
||||
}
|
||||
|
||||
@@ -284,6 +297,14 @@ public:
|
||||
value = io_direction_;
|
||||
} else {
|
||||
value = io_input();
|
||||
|
||||
if(!skip_range_ && tape_handler_.apply_accelerated_range()) {
|
||||
const auto pc = m6502_.registers().pc;
|
||||
const auto a = m6502_.registers().a;
|
||||
if(a == 0x10) {
|
||||
skip_range_ = tape_handler_.accelerated_range(pc.full, m6502_, map_);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(!address) {
|
||||
@@ -293,133 +314,42 @@ public:
|
||||
}
|
||||
|
||||
const auto output = io_output_ | ~io_direction_;
|
||||
update_tape_motor();
|
||||
tape_handler_.set_io(io_output_, io_direction_);
|
||||
serial_port_.set_output(Serial::Line::Data, Serial::LineLevel(~output & 0x01));
|
||||
serial_port_.set_output(Serial::Line::Clock, Serial::LineLevel(~output & 0x02));
|
||||
serial_port_.set_output(Serial::Line::Attention, Serial::LineLevel(~output & 0x04));
|
||||
}
|
||||
} else if(address < 0xfd00 || address >= 0xff40) {
|
||||
// if(
|
||||
// use_fast_tape_hack_ &&
|
||||
// operation == CPU::MOS6502Esque::BusOperation::ReadOpcode
|
||||
// ) {
|
||||
// superspeed_ |= address == 0xe5fd;
|
||||
// superspeed_ &= (address != 0xe68b) && (address != 0xe68d);
|
||||
// }
|
||||
|
||||
static constexpr bool use_hle = true;
|
||||
|
||||
// if(
|
||||
// use_fast_tape_hack_ &&
|
||||
// operation == CPU::MOS6502Esque::BusOperation::ReadOpcode &&
|
||||
// address == 0xe5fd
|
||||
// ) {
|
||||
// printf("Pulse %d from %lld ",
|
||||
// pulse_num_,
|
||||
// tape_player_->serialiser()->offset()
|
||||
// );
|
||||
// }
|
||||
|
||||
if constexpr (is_read(operation)) {
|
||||
if(
|
||||
use_fast_tape_hack_ &&
|
||||
operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode &&
|
||||
(
|
||||
(use_hle && address == 0xe5fd) ||
|
||||
address == 0xe68b ||
|
||||
address == 0xe68d
|
||||
)
|
||||
) {
|
||||
// ++pulse_num_;
|
||||
if(use_hle) {
|
||||
read_dipole();
|
||||
}
|
||||
value = map_.read(address);
|
||||
|
||||
// using Flag = CPU::MOS6502::Flag;
|
||||
// using Register = CPU::MOS6502::Register;
|
||||
// const auto flags = m6502_.value_of(Register::Flags);
|
||||
// printf("to %lld: %c%c%c\n",
|
||||
// tape_player_->serialiser()->offset(),
|
||||
// flags & Flag::Sign ? 'n' : '-',
|
||||
// flags & Flag::Overflow ? 'v' : '-',
|
||||
// flags & Flag::Carry ? 'c' : '-'
|
||||
// );
|
||||
if(
|
||||
operation == CPU::MOS6502Mk2::BusOperation::ReadOpcode &&
|
||||
tape_handler_.test_rom_trap() &&
|
||||
address == TapeHandler::ROMTrapAddress &&
|
||||
tape_handler_.perform_ldcass(m6502_, ram_, video_.timer_cycle_length())
|
||||
) [[unlikely]] {
|
||||
value = 0x60;
|
||||
} else {
|
||||
value = map_.read(address);
|
||||
}
|
||||
} else {
|
||||
map_.write(address) = value;
|
||||
}
|
||||
|
||||
|
||||
// TODO: rdbyte and ldsync is probably sufficient?
|
||||
|
||||
// if(use_fast_tape_hack_ && operation == CPU::MOS6502Esque::BusOperation::ReadOpcode) {
|
||||
// static constexpr uint16_t ldsync = 0;
|
||||
// switch(address) {
|
||||
// default: break;
|
||||
//
|
||||
// case ldsync:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// if(address == 0xe9cc) {
|
||||
// // Skip the `jsr rdblok` that opens `fah` (i.e. find any header), performing
|
||||
// // its function as a high-level emulation.
|
||||
// Storage::Tape::Commodore::Parser parser(TargetPlatform::Plus4);
|
||||
// auto header = parser.get_next_header(*tape_player_->serialiser());
|
||||
//
|
||||
// const auto tape_position = tape_player_->serialiser()->offset();
|
||||
// if(header) {
|
||||
// // Copy to in-memory buffer and set type.
|
||||
// std::memcpy(&ram_[0x0333], header->data.data(), 191);
|
||||
// map_.write(0xb6) = 0x33;
|
||||
// map_.write(0xb7) = 0x03;
|
||||
// map_.write(0xf8) = header->type_descriptor();
|
||||
//// hold_tape_ = true;
|
||||
// Logger::info().append("Found header");
|
||||
// } else {
|
||||
// // no header found, so pretend this hack never interceded
|
||||
// tape_player_->serialiser()->set_offset(tape_position);
|
||||
//// hold_tape_ = false;
|
||||
// Logger::info().append("Didn't find header");
|
||||
// }
|
||||
//
|
||||
// // Clear status and the verify flags.
|
||||
// ram_[0x90] = 0;
|
||||
// ram_[0x93] = 0;
|
||||
//
|
||||
// value = 0x0c; // NOP abs.
|
||||
// }
|
||||
// }
|
||||
} else if(address < 0xff00) {
|
||||
// Miscellaneous hardware. All TODO.
|
||||
// Miscellaneous hardware.
|
||||
if constexpr (is_read(operation)) {
|
||||
switch(address & 0xfff0) {
|
||||
case 0xfd10:
|
||||
// 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies.
|
||||
tape_handler_.read_parallel_port([&] {
|
||||
const uint16_t pc = m6502_.registers().pc.full;
|
||||
return std::array<uint8_t, 4>{
|
||||
map_.read(pc+0),
|
||||
map_.read(pc+1),
|
||||
map_.read(pc+2),
|
||||
map_.read(pc+3),
|
||||
};
|
||||
});
|
||||
|
||||
// If play button is not currently pressed and this read is immediately followed by
|
||||
// an AND 4, press it. The kernel will deal with motor control subsequently.
|
||||
if(!play_button_) {
|
||||
const uint16_t pc = m6502_.registers().pc.full;
|
||||
const uint8_t next[] = {
|
||||
map_.read(pc+0),
|
||||
map_.read(pc+1),
|
||||
map_.read(pc+2),
|
||||
map_.read(pc+3),
|
||||
};
|
||||
|
||||
// TODO: boil this down to a PC check. It's currently in this form as I'm unclear what
|
||||
// diversity of kernels exist.
|
||||
if(next[0] == 0x29 && next[1] == 0x04 && next[2] == 0xd0 && next[3] == 0xf4) {
|
||||
play_button_ = true;
|
||||
update_tape_motor();
|
||||
}
|
||||
}
|
||||
|
||||
value = 0xff ^ (play_button_ ? 0x4 :0x0);
|
||||
value = 0xff ^ (tape_handler_.play_button() ? 0x4 :0x0);
|
||||
break;
|
||||
|
||||
case 0xfdd0:
|
||||
@@ -539,7 +469,7 @@ public:
|
||||
// Observation here: the kernel posts a 0 to this
|
||||
// address upon completing each keyboard scan cycle,
|
||||
// once per frame.
|
||||
if(typer_ && !value) {
|
||||
if(typer_ && !value) [[unlikely]] {
|
||||
if(!typer_->type_next_character()) {
|
||||
clear_all_keys();
|
||||
typer_.reset();
|
||||
@@ -621,7 +551,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
return superspeed_ ? Cycles(0) : length;
|
||||
return length;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -660,12 +590,12 @@ private:
|
||||
map_.page<PagerSide::Read, 0x8000, 16384>(basic_.data());
|
||||
map_.page<PagerSide::Read, 0xc000, 16384>(kernel_.data());
|
||||
rom_is_paged_ = true;
|
||||
set_use_fast_tape();
|
||||
tape_handler_.set_rom_is_paged(true);
|
||||
}
|
||||
void page_cpu_ram() {
|
||||
map_.page<PagerSide::Read, 0x8000, 32768>(&ram_[0x8000]);
|
||||
rom_is_paged_ = false;
|
||||
set_use_fast_tape();
|
||||
tape_handler_.set_rom_is_paged(false);
|
||||
}
|
||||
bool rom_is_paged_ = false;
|
||||
|
||||
@@ -702,7 +632,7 @@ private:
|
||||
|
||||
bool insert_media(const Analyser::Static::Media &media) final {
|
||||
if(!media.tapes.empty()) {
|
||||
tape_player_->set_tape(media.tapes[0], TargetPlatform::Plus4);
|
||||
tape_handler_.set_tape(media.tapes[0]);
|
||||
}
|
||||
|
||||
if(!media.disks.empty() && c1541_) {
|
||||
@@ -767,377 +697,20 @@ private:
|
||||
Serial::Bus serial_bus_;
|
||||
SerialPort serial_port_;
|
||||
|
||||
std::unique_ptr<Storage::Tape::BinaryTapePlayer> tape_player_;
|
||||
bool play_button_ = false;
|
||||
bool allow_fast_tape_hack_ = false; // TODO: implement fast-tape hack.
|
||||
bool use_fast_tape_hack_ = false;
|
||||
bool superspeed_ = false;
|
||||
void set_use_fast_tape() {
|
||||
use_fast_tape_hack_ =
|
||||
allow_fast_tape_hack_ && tape_player_->motor_control() && rom_is_paged_ && !tape_player_->is_at_end();
|
||||
}
|
||||
void update_tape_motor() {
|
||||
const auto output = io_output_ | ~io_direction_;
|
||||
tape_player_->set_motor_control(play_button_ && (~output & 0x08));
|
||||
}
|
||||
void advance_timers_and_tape(const Cycles length) {
|
||||
timers_subcycles_ += length;
|
||||
const auto timers_cycles = timers_subcycles_.divide(video_.timer_cycle_length());
|
||||
timers_.tick(timers_cycles.as<int>());
|
||||
|
||||
tape_player_->run_for(length);
|
||||
}
|
||||
|
||||
// TODO: substantially simplify the below; at the minute it's a
|
||||
// literal transcription of the original as a simple first step.
|
||||
void read_dipole() {
|
||||
using Flag = CPU::MOS6502Mk2::Flag;
|
||||
|
||||
//
|
||||
// Get registers now and ensure they'll be written back at function exit.
|
||||
//
|
||||
auto registers = m6502_.registers();
|
||||
struct ScopeGuard {
|
||||
ScopeGuard(std::function<void(void)> at_exit) : at_exit_(at_exit) {}
|
||||
~ScopeGuard() { at_exit_(); }
|
||||
private:
|
||||
std::function<void(void)> at_exit_;
|
||||
} store_registers([&] {
|
||||
m6502_.set_registers(registers);
|
||||
});
|
||||
|
||||
//
|
||||
// Time advancement.
|
||||
//
|
||||
const auto advance_cycles = [&](int cycles) -> bool {
|
||||
advance_timers_and_tape(video_.cycle_length(false) * cycles);
|
||||
return !use_fast_tape_hack_;
|
||||
};
|
||||
|
||||
//
|
||||
// 6502 pseudo-ops.
|
||||
//
|
||||
const auto ldabs = [&] (uint8_t &target, const uint16_t address) {
|
||||
registers.flags.set_per<Flag::NegativeZero>(target = map_.read(address));
|
||||
};
|
||||
const auto ldimm = [&] (uint8_t &target, const uint8_t value) {
|
||||
registers.flags.set_per<Flag::NegativeZero>(target = value);
|
||||
};
|
||||
const auto pha = [&] () {
|
||||
map_.write(0x100 + registers.s) = registers.a;
|
||||
--registers.s;
|
||||
};
|
||||
const auto pla = [&] () {
|
||||
++registers.s;
|
||||
registers.a = map_.read(0x100 + registers.s);
|
||||
};
|
||||
const auto bit = [&] (const uint8_t value) {
|
||||
registers.flags.set_per<Flag::Zero>(registers.a & value);
|
||||
registers.flags.set_per<Flag::Negative>(value);
|
||||
registers.flags.set_per<Flag::Overflow>(value);
|
||||
};
|
||||
const auto cmp = [&] (const uint8_t value) {
|
||||
const uint16_t temp16 = registers.a - value;
|
||||
registers.flags.set_per<Flag::NegativeZero>(uint8_t(temp16));
|
||||
registers.flags.set_per<Flag::Carry>(((~temp16) >> 8)&1);
|
||||
};
|
||||
const auto andimm = [&] (const uint8_t value) {
|
||||
registers.a &= value;
|
||||
registers.flags.set_per<Flag::NegativeZero>(registers.a);
|
||||
};
|
||||
const auto ne = [&]() -> bool {
|
||||
return !registers.flags.get<Flag::Zero>();
|
||||
};
|
||||
const auto eq = [&]() -> bool {
|
||||
return registers.flags.get<Flag::Zero>();
|
||||
};
|
||||
|
||||
//
|
||||
// Common branch points.
|
||||
//
|
||||
const auto dipok = [&] {
|
||||
// clc ; everything's fine
|
||||
// rts
|
||||
registers.flags.set_per<Flag::Carry>(0);
|
||||
};
|
||||
const auto rshort = [&] {
|
||||
// bit tshrtd ; got a short
|
||||
// bvs dipok ; !bra
|
||||
bit(0x40);
|
||||
dipok();
|
||||
};
|
||||
const auto rlong = [&] {
|
||||
// bit tlongd ; got a long
|
||||
bit(0x00);
|
||||
dipok();
|
||||
};
|
||||
const auto rderr1 = [&] {
|
||||
// sec ; i'm confused
|
||||
// rts
|
||||
registers.flags.set_per<Flag::Carry>(Flag::Carry);
|
||||
};
|
||||
|
||||
//
|
||||
// Labels.
|
||||
//
|
||||
static constexpr uint16_t dsamp1 = 0x7b8;
|
||||
static constexpr uint16_t dsamp2 = 0x7ba;
|
||||
static constexpr uint16_t zcell = 0x07bc;
|
||||
|
||||
//rddipl
|
||||
// ldx dsamp1 ; setup x,y with 1st sample point
|
||||
// ldy dsamp1+1
|
||||
ldabs(registers.x, dsamp1);
|
||||
ldabs(registers.y, dsamp1 + 1);
|
||||
advance_cycles(8);
|
||||
|
||||
//badeg1
|
||||
do {
|
||||
// lda dsamp2+1 ; put 2nd samp value on stack in reverse order
|
||||
// pha
|
||||
// lda dsamp2
|
||||
// pha
|
||||
ldabs(registers.a, dsamp2 + 1);
|
||||
pha();
|
||||
ldabs(registers.a, dsamp2);
|
||||
pha();
|
||||
advance_cycles(14);
|
||||
|
||||
// lda #$10
|
||||
//rwtl ; wait till rd line is high
|
||||
// bit port [= $0001]
|
||||
// beq rwtl ; !ls!
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(2);
|
||||
do {
|
||||
bit(io_input());
|
||||
if(advance_cycles(6)) {
|
||||
return;
|
||||
}
|
||||
} while(eq());
|
||||
|
||||
//rwth ;it's high...now wait till it's low
|
||||
// bit port
|
||||
// bne rwth ; caught the edge
|
||||
do {
|
||||
bit(io_input());
|
||||
if(advance_cycles(6)) {
|
||||
return;
|
||||
}
|
||||
} while(ne());
|
||||
|
||||
|
||||
// stx timr2l
|
||||
// sty timr2h
|
||||
timers_.write<2>(registers.x);
|
||||
timers_.write<3>(registers.y);
|
||||
advance_cycles(8);
|
||||
|
||||
|
||||
//; go! ...ta
|
||||
//
|
||||
// pla ;go! ...ta
|
||||
// sta timr3l
|
||||
// pla
|
||||
// sta timr3h ;go! ...tb
|
||||
pla();
|
||||
timers_.write<4>(registers.a);
|
||||
pla();
|
||||
timers_.write<5>(registers.a);
|
||||
advance_cycles(14);
|
||||
|
||||
|
||||
//; clear timer flags
|
||||
//
|
||||
// lda #$50 ; clr ta,tb
|
||||
// sta tedirq
|
||||
ldimm(registers.a, 0x50);
|
||||
interrupts_.set_status(registers.a);
|
||||
advance_cycles(6);
|
||||
|
||||
|
||||
//; um...check that edge again
|
||||
//
|
||||
//casdb1
|
||||
// lda port
|
||||
// cmp port
|
||||
// bne casdb1 ; something is going on here...
|
||||
// and #$10 ; a look at that edge again
|
||||
// bne badeg1 ; woa! got a bad edge trigger !ls!
|
||||
do {
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
}
|
||||
} while(ne());
|
||||
andimm(0x10);
|
||||
advance_cycles(5);
|
||||
} while(ne());
|
||||
|
||||
|
||||
//
|
||||
//; must have been a valid edge
|
||||
//;
|
||||
//; do stop key check here
|
||||
//
|
||||
// jsr balout
|
||||
|
||||
/* balout not checked */
|
||||
|
||||
|
||||
// lda #$10
|
||||
//wata ; wait for ta to timeout
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(3);
|
||||
do {
|
||||
// bit port ; kuldge, kludge, kludge !!! <<><>>
|
||||
// bne rshort ; kuldge, kludge, kludge !!! <<><>>
|
||||
bit(io_input());
|
||||
if(ne()) {
|
||||
rshort();
|
||||
return;
|
||||
}
|
||||
|
||||
// bit tedirq
|
||||
// beq wata
|
||||
bit(interrupts_.status());
|
||||
|
||||
if(advance_cycles(12)) {
|
||||
return;
|
||||
}
|
||||
} while(eq());
|
||||
|
||||
|
||||
//
|
||||
//; now do the dipole sample #1
|
||||
//
|
||||
//casdb2
|
||||
do {
|
||||
// lda port
|
||||
// cmp port
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
}
|
||||
// bne casdb2
|
||||
} while(ne());
|
||||
|
||||
// and #$10
|
||||
// bne rshort ; shorts anyone?
|
||||
andimm(0x10);
|
||||
advance_cycles(3);
|
||||
if(ne()) {
|
||||
rshort();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
//; perhaps a long or a word?
|
||||
//
|
||||
// lda #$40
|
||||
//watb
|
||||
// bit tedirq
|
||||
// beq watb
|
||||
//
|
||||
//; wait for tb to timeout
|
||||
//; now do the dipole sample #2
|
||||
ldimm(registers.a, 0x40);
|
||||
advance_cycles(3);
|
||||
do {
|
||||
bit(interrupts_.status());
|
||||
if(advance_cycles(6)) {
|
||||
return;
|
||||
}
|
||||
} while(eq());
|
||||
|
||||
|
||||
//casdb3
|
||||
// lda port
|
||||
// cmp port
|
||||
// bne casdb3
|
||||
do {
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
}
|
||||
} while(ne());
|
||||
|
||||
// and #$10
|
||||
// bne rlong ; looks like a long from here !ls!
|
||||
andimm(0x10);
|
||||
advance_cycles(2);
|
||||
if(ne()) {
|
||||
rlong();
|
||||
return;
|
||||
}
|
||||
|
||||
// ; or could it be a word?
|
||||
// lda zcell
|
||||
// sta timr2l
|
||||
// lda zcell+1
|
||||
// sta timr2h
|
||||
ldabs(registers.a, zcell);
|
||||
timers_.write<2>(registers.a);
|
||||
ldabs(registers.a, zcell + 1);
|
||||
timers_.write<3>(registers.y);
|
||||
advance_cycles(16);
|
||||
|
||||
|
||||
// ; go! z-cell check
|
||||
// ; clear ta flag
|
||||
// lda #$10
|
||||
// sta tedirq ; verify +180 half of word dipole
|
||||
// lda #$10
|
||||
ldimm(registers.a, 0x10);
|
||||
interrupts_.set_status(registers.a);
|
||||
ldimm(registers.a, 0x10);
|
||||
advance_cycles(8);
|
||||
|
||||
//wata2
|
||||
// bit tedirq
|
||||
// beq wata2 ; check z-cell is low
|
||||
do {
|
||||
bit(interrupts_.status());
|
||||
if(advance_cycles(7)) {
|
||||
return;
|
||||
}
|
||||
} while(eq());
|
||||
|
||||
//casdb4
|
||||
// lda port
|
||||
// cmp port
|
||||
// bne casdb4
|
||||
do {
|
||||
ldimm(registers.a, io_input());
|
||||
cmp(io_input());
|
||||
if(advance_cycles(9)) {
|
||||
return;
|
||||
}
|
||||
} while(ne());
|
||||
|
||||
// and #$10
|
||||
// beq rderr1 ; !ls!
|
||||
// bit twordd ; got a word dipole
|
||||
// bmi dipok ; !bra
|
||||
andimm(0x10);
|
||||
advance_cycles(2);
|
||||
if(eq()) {
|
||||
rderr1();
|
||||
return;
|
||||
}
|
||||
bit(0x80);
|
||||
advance_cycles(2);
|
||||
dipok();
|
||||
tape_handler_.run_for(length);
|
||||
}
|
||||
TapeHandler tape_handler_;
|
||||
std::optional<AcceleratedRange> skip_range_;
|
||||
|
||||
uint8_t io_direction_ = 0x00, io_output_ = 0x00;
|
||||
uint8_t io_input() const {
|
||||
const uint8_t all_inputs =
|
||||
(tape_player_->input() ? 0x00 : 0x10) |
|
||||
(tape_handler_.tape_player().input() ? 0x00 : 0x10) |
|
||||
(serial_port_.level(Serial::Line::Data) ? 0x80 : 0x00) |
|
||||
(serial_port_.level(Serial::Line::Clock) ? 0x40 : 0x00);
|
||||
return
|
||||
@@ -1153,11 +726,6 @@ private:
|
||||
}
|
||||
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
|
||||
|
||||
// MARK: - ClockingHint::Observer.
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) override {
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
// MARK: - Confidence.
|
||||
Analyser::Dynamic::ConfidenceCounter confidence_;
|
||||
float get_confidence() final { return confidence_.confidence(); }
|
||||
@@ -1169,7 +737,7 @@ private:
|
||||
std::unique_ptr<Reflection::Struct> get_options() const final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
options->quick_load = allow_fast_tape_hack_;
|
||||
options->quick_load = tape_handler_.allow_accelerated_tape_loading();
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -1177,8 +745,7 @@ private:
|
||||
const auto options = dynamic_cast<Options *>(str.get());
|
||||
|
||||
set_video_signal_configurable(options->output);
|
||||
allow_fast_tape_hack_ = options->quick_load;
|
||||
set_use_fast_tape();
|
||||
tape_handler_.set_allow_accelerated_tape_loading(options->quick_load);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
//
|
||||
// TapeHandler.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 03/12/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Processors/6502Mk2/6502Mk2.hpp"
|
||||
#include "Storage/Data/Commodore.hpp"
|
||||
#include "Storage/Tape/Tape.hpp"
|
||||
#include "Storage/Tape/Parsers/Commodore.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Commodore::Plus4 {
|
||||
|
||||
/*!
|
||||
Describes a continuous block of memory that the tape handler asserts should be completed as quickly as possible, regardless
|
||||
of wall-clock time, and that depends upon only timers and tape hardware running at the correct rate relative to one another.
|
||||
|
||||
i.e. this is used to indicate where the machine can apply accelerated loading, running the machine without video or disk drives as
|
||||
quickly as possible until the program counter exists the nominated range.
|
||||
*/
|
||||
struct AcceleratedRange {
|
||||
uint16_t low, high;
|
||||
};
|
||||
|
||||
/*!
|
||||
All tape assistance, bundled into a single place, including:
|
||||
|
||||
(i) automatic motor control; and
|
||||
(ii) fast loading, including loader detection.
|
||||
*/
|
||||
struct TapeHandler: public ClockingHint::Observer {
|
||||
static constexpr uint16_t ROMTrapAddress = 0xf0f0;
|
||||
|
||||
// MARK: - Getters.
|
||||
|
||||
Storage::Tape::BinaryTapePlayer &tape_player() {
|
||||
return *tape_player_;
|
||||
}
|
||||
|
||||
const Storage::Tape::BinaryTapePlayer &tape_player() const {
|
||||
return *tape_player_;
|
||||
}
|
||||
|
||||
bool test_rom_trap() const {
|
||||
return use_fast_tape_hack_;
|
||||
}
|
||||
|
||||
bool apply_accelerated_range() const {
|
||||
return allow_fast_tape_hack_ && !tape_player_->is_at_end();
|
||||
}
|
||||
|
||||
bool play_button() const {
|
||||
return play_button_;
|
||||
}
|
||||
|
||||
// MARK: - Rote Setters.
|
||||
|
||||
void set_allow_accelerated_tape_loading(const bool allow) {
|
||||
allow_fast_tape_hack_ = allow;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
bool allow_accelerated_tape_loading() const {
|
||||
return allow_fast_tape_hack_;
|
||||
}
|
||||
|
||||
void set_rom_is_paged(const bool is_paged) {
|
||||
rom_is_paged_ = is_paged;
|
||||
set_use_fast_tape();
|
||||
}
|
||||
|
||||
void set_io(const uint8_t output, const uint8_t direction) {
|
||||
io_output_ = output;
|
||||
io_direction_ = direction;
|
||||
update_tape_motor();
|
||||
}
|
||||
|
||||
void set_tape(std::shared_ptr<Storage::Tape::Tape> tape) {
|
||||
tape_player_->set_tape(tape, TargetPlatform::Plus4);
|
||||
}
|
||||
|
||||
// MARK: - Clocking.
|
||||
|
||||
void set_clock_rate(const int rate) {
|
||||
clock_rate_ = rate;
|
||||
tape_player_ = std::make_unique<Storage::Tape::BinaryTapePlayer>(rate);
|
||||
tape_player_->set_clocking_hint_observer(this);
|
||||
}
|
||||
|
||||
void run_for(const Cycles length) {
|
||||
tape_player_->run_for(length);
|
||||
}
|
||||
|
||||
// MARK: - Automatic play button detection.
|
||||
|
||||
void read_parallel_port(const std::function<std::array<uint8_t, 4>(void)> &test_memory) {
|
||||
// 6529 parallel port, about which I know only what I've found in kernel ROM disassemblies.
|
||||
|
||||
// Intended logic: if play button is not currently pressed and this read is immediately followed by
|
||||
// an AND 4, press it. The kernel will deal with motor control subsequently. This seems to be how
|
||||
// the ROM tests whether the user has yet responded to its invitation to press play.
|
||||
if(play_button_) return;
|
||||
|
||||
// TODO: boil this down to a PC check. It's currently in this form as I'm unclear what
|
||||
// diversity of kernels exist.
|
||||
const auto next = test_memory();
|
||||
|
||||
if(next[0] == 0x29 && next[1] == 0x04 && next[2] == 0xd0 && next[3] == 0xf4) {
|
||||
play_button_ = true;
|
||||
update_tape_motor();
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Loading accelerators.
|
||||
|
||||
template <typename M6502T>
|
||||
bool perform_ldcass(M6502T &m6502, std::array<uint8_t, 65536> &ram, const Cycles timer_cycle_length) {
|
||||
// Magic constants.
|
||||
static constexpr uint16_t FileNameLength = 0xab;
|
||||
static constexpr uint16_t FileNameAddress = 0xaf;
|
||||
static constexpr uint16_t TapeBlockType = 0xf8;
|
||||
static constexpr uint16_t SecondAddressFlag = 0xad;
|
||||
static constexpr uint16_t HeaderBuffer = 0x0333;
|
||||
|
||||
// Imply an automatic motor start.
|
||||
play_button_ = true;
|
||||
update_tape_motor();
|
||||
|
||||
// Input:
|
||||
// A: 0 = Load, 1-255 = Verify;
|
||||
// X/Y = Load address (if secondary address = 0).
|
||||
// Output:
|
||||
// Carry: 0 = No errors, 1 = Error;
|
||||
// A = KERNAL error code (if Carry = 1);
|
||||
// X/Y = Address of last byte loaded/verified (if Carry = 0).
|
||||
// Used registers: A, X, Y. Real address: $F49E.
|
||||
|
||||
auto registers = m6502.registers();
|
||||
|
||||
// Check for a filename.
|
||||
std::vector<uint8_t> raw_name;
|
||||
const uint8_t name_length = ram[FileNameLength];
|
||||
if(name_length) {
|
||||
const uint16_t address = uint16_t(ram[FileNameAddress] | (ram[FileNameAddress + 1] << 8));
|
||||
for(uint16_t c = 0; c < name_length; c++) {
|
||||
raw_name.push_back(ram[address + c]);
|
||||
}
|
||||
}
|
||||
|
||||
const auto start_offset = tape_player_->serialiser()->offset();
|
||||
|
||||
// Search for first thing that matches the file name.
|
||||
Storage::Tape::Commodore::Parser parser(TargetPlatform::Plus4);
|
||||
auto &serialiser = *tape_player_->serialiser();
|
||||
std::unique_ptr<Storage::Tape::Commodore::Header> header;
|
||||
while(!parser.is_at_end(serialiser)) {
|
||||
header = parser.get_next_header(serialiser);
|
||||
if(!header || !header->parity_was_valid) {
|
||||
continue;
|
||||
}
|
||||
if(!raw_name.empty() && raw_name != header->raw_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto body = parser.get_next_data(serialiser);
|
||||
if(!body || !body->parity_was_valid) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copy header into place.
|
||||
header->serialise(&ram[HeaderBuffer], uint16_t(ram.size() - HeaderBuffer));
|
||||
|
||||
// Set block type; 0x00 = data body.
|
||||
ram[TapeBlockType] = 0;
|
||||
|
||||
// TODO: F5 = checksum.
|
||||
|
||||
auto load_address =
|
||||
ram[SecondAddressFlag] ? header->starting_address : uint16_t((registers.y << 8) | registers.x);
|
||||
|
||||
// Set 'load ram base', 'sta' and 'tapebs'.
|
||||
ram[0xb2] = ram[0xb4] = ram[0xb6] = load_address & 0xff;
|
||||
ram[0xb3] = ram[0xb5] = ram[0xb7] = load_address >> 8;
|
||||
|
||||
if(load_address + body->data.size() < 65536) {
|
||||
std::copy(body->data.begin(), body->data.end(), &ram[load_address]);
|
||||
} else {
|
||||
const auto split_point = body->data.begin() + 65536 - load_address;
|
||||
std::copy(body->data.begin(), split_point, &ram[load_address]);
|
||||
std::copy(split_point, body->data.end(), &ram[0]);
|
||||
}
|
||||
load_address += body->data.size();
|
||||
|
||||
// Set final tape byte.
|
||||
ram[0xa7] = body->data.back();
|
||||
|
||||
// Set 'ea' pointer.
|
||||
ram[0x9d] = load_address & 0xff;
|
||||
ram[0x9e] = load_address >> 8;
|
||||
|
||||
registers.a = 0xa2;
|
||||
registers.x = load_address & 0xff;
|
||||
registers.y = load_address >> 8;
|
||||
registers.flags.template set_per<CPU::MOS6502Mk2::Flag::Carry>(0); // C = 0 => success.
|
||||
|
||||
ram[0x90] = 0; // IO status: no error.
|
||||
ram[0x93] = 0; // Load/verify flag: was load.
|
||||
|
||||
// Tape timing constants.
|
||||
using WaveType = Storage::Tape::Commodore::WaveType;
|
||||
const float medium_length = parser.expected_length(WaveType::Medium);
|
||||
const float short_length = parser.expected_length(WaveType::Short);
|
||||
|
||||
const float timer_ticks_per_second = float(clock_rate_) / float(timer_cycle_length.as<int>());
|
||||
const auto medium_cutoff = uint16_t((medium_length * timer_ticks_per_second) * 0.75f);
|
||||
const auto short_cutoff = uint16_t((short_length * timer_ticks_per_second) * 0.75f);
|
||||
|
||||
ram[0x7b8] = uint8_t(short_cutoff);
|
||||
ram[0x7b9] = uint8_t(short_cutoff >> 8);
|
||||
|
||||
ram[0x7bc] = ram[0x7ba] = uint8_t(medium_cutoff);
|
||||
ram[0x7bd] = ram[0x7bb] = uint8_t(medium_cutoff >> 8);
|
||||
|
||||
m6502.set_registers(registers);
|
||||
return true;
|
||||
}
|
||||
|
||||
tape_player_->serialiser()->set_offset(start_offset);
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename M6502T, typename MemoryT>
|
||||
std::optional<AcceleratedRange> accelerated_range(const uint16_t pc, M6502T &, MemoryT &map) {
|
||||
// Potential sequence:
|
||||
//
|
||||
// 24 01 BIT 01
|
||||
// d0 fc BNE 3c8 <- PC will be here; trigger is the BIT operation above.
|
||||
// 24 01 BIT 01
|
||||
// f0 fc BEQ 3cc
|
||||
//
|
||||
// Also check for BNE and BEQ the other way around.
|
||||
static constexpr uint8_t bne_beq[] = {
|
||||
0x24, 0x01, 0xd0, 0xfc, 0x24, 0x01, 0xf0, 0xfc
|
||||
};
|
||||
static constexpr uint8_t beq_bne[] = {
|
||||
0x24, 0x01, 0xf0, 0xfc, 0x24, 0x01, 0xd0, 0xfc
|
||||
};
|
||||
const uint8_t *memory_begin = &map.write(pc - 2); // TODO: formalise getting a block pointer on `map`.
|
||||
if(
|
||||
std::equal(std::begin(bne_beq), std::end(bne_beq), memory_begin) ||
|
||||
std::equal(std::begin(beq_bne), std::end(bne_beq), memory_begin)
|
||||
) {
|
||||
return AcceleratedRange{uint16_t(pc - 2), uint16_t(pc + 6)};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
int clock_rate_ = 0;
|
||||
|
||||
std::unique_ptr<Storage::Tape::BinaryTapePlayer> tape_player_;
|
||||
bool play_button_ = false;
|
||||
|
||||
void set_component_prefers_clocking(ClockingHint::Source *, ClockingHint::Preference) override {
|
||||
set_use_fast_tape();
|
||||
}
|
||||
bool use_fast_tape_hack_ = false;
|
||||
bool allow_fast_tape_hack_ = false;
|
||||
bool rom_is_paged_ = false;
|
||||
void set_use_fast_tape() {
|
||||
use_fast_tape_hack_ = allow_fast_tape_hack_ && rom_is_paged_ && !tape_player_->is_at_end();
|
||||
}
|
||||
|
||||
uint8_t io_output_ = 0x00;
|
||||
uint8_t io_direction_ = 0x00;
|
||||
void update_tape_motor() {
|
||||
const auto output = io_output_ | ~io_direction_;
|
||||
tape_player_->set_motor_control(play_button_ && (~output & 0x08));
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -978,7 +978,7 @@ private:
|
||||
|
||||
case VideoMode::Blank:
|
||||
if(target) {
|
||||
std::fill(target, target + length, 0x0000);
|
||||
std::fill_n(target, length, 0x0000);
|
||||
}
|
||||
output_.advance_pixels(length);
|
||||
break;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
using Logger = Log::Logger<Log::Source::Vic20>;
|
||||
@@ -456,27 +457,24 @@ public:
|
||||
}
|
||||
|
||||
void set_key_state(const uint16_t key, const bool is_pressed) final {
|
||||
if(key < KeyUp) {
|
||||
const auto apply_shifted = [&](const uint16_t key) {
|
||||
keyboard_via_port_handler_.set_key_state(KeyLShift, is_pressed);
|
||||
keyboard_via_port_handler_.set_key_state(key, is_pressed);
|
||||
} else {
|
||||
switch(key) {
|
||||
case KeyRestore:
|
||||
user_port_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!is_pressed);
|
||||
break;
|
||||
#define ShiftedMap(source, target) \
|
||||
case source: \
|
||||
keyboard_via_port_handler_.set_key_state(KeyLShift, is_pressed); \
|
||||
keyboard_via_port_handler_.set_key_state(target, is_pressed); \
|
||||
break;
|
||||
};
|
||||
|
||||
ShiftedMap(KeyUp, KeyDown);
|
||||
ShiftedMap(KeyLeft, KeyRight);
|
||||
ShiftedMap(KeyF2, KeyF1);
|
||||
ShiftedMap(KeyF4, KeyF3);
|
||||
ShiftedMap(KeyF6, KeyF5);
|
||||
ShiftedMap(KeyF8, KeyF7);
|
||||
#undef ShiftedMap
|
||||
}
|
||||
switch(key) {
|
||||
default:
|
||||
keyboard_via_port_handler_.set_key_state(key, is_pressed);
|
||||
break;
|
||||
case KeyRestore:
|
||||
user_port_via_.set_control_line_input<MOS::MOS6522::Port::A, MOS::MOS6522::Line::One>(!is_pressed);
|
||||
break;
|
||||
case KeyUp: apply_shifted(KeyDown); break;
|
||||
case KeyLeft: apply_shifted(KeyRight); break;
|
||||
case KeyF2: apply_shifted(KeyF1); break;
|
||||
case KeyF4: apply_shifted(KeyF3); break;
|
||||
case KeyF6: apply_shifted(KeyF5); break;
|
||||
case KeyF8: apply_shifted(KeyF7); break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,6 +551,7 @@ public:
|
||||
|
||||
value = 0x0c; // i.e. NOP abs, to swallow the entire JSR
|
||||
} else if(address == 0xf90b) {
|
||||
// second JSR in TAPE / LAB_F8F4.
|
||||
auto registers = m6502_.registers();
|
||||
if(registers.x == 0xe) {
|
||||
Storage::Tape::Commodore::Parser parser(TargetPlatform::Vic20);
|
||||
@@ -563,7 +562,7 @@ public:
|
||||
start_address = uint16_t(ram_[0xc1] | (ram_[0xc2] << 8));
|
||||
end_address = uint16_t(ram_[0xae] | (ram_[0xaf] << 8));
|
||||
|
||||
// perform a via-processor_write_memory_map_ memcpy
|
||||
// Perform a via-processor_write_memory_map_ copy.
|
||||
uint8_t *data_ptr = data->data.data();
|
||||
std::size_t data_left = data->data.size();
|
||||
while(data_left && start_address != end_address) {
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// EXOSCodes.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/11/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
// Various EXOS codes, transcribed from EXOS20_technical_information.pdf via archive.org,
|
||||
// which appears to be a compilation of original documentation so page numbers below
|
||||
// refer to the page within the PDF. Numbers printed on the in-document pages are inconsistent.
|
||||
|
||||
namespace Enterprise::EXOS {
|
||||
|
||||
// Page 67.
|
||||
enum class Function: uint8_t {
|
||||
ResetSystem = 0, // RESET
|
||||
OpenChannel = 1, // OPEN
|
||||
CreateChannel = 2, // CREAT
|
||||
CloseChannel = 3, // CLOSE
|
||||
DestroyChannel = 4, // DEST
|
||||
ReadCharacter = 5, // RDCH
|
||||
ReadBlock = 6, // RDBLK
|
||||
WriteCharacter = 7, // WRCH
|
||||
WriteBlock = 8, // WRBLK
|
||||
ReadChannelStatus = 9, // RSTAT
|
||||
SetChannelStatus = 10, // SSTAT
|
||||
SpecialFunction = 11, // SFUNC
|
||||
|
||||
SetReadToggleEXOSVariable = 16, // EVAR
|
||||
CaptureChannel = 17, // CAPT
|
||||
RedirectChannel = 18, // REDIR
|
||||
SetDefaultDevice = 19, // DDEV
|
||||
ReturnSystemStatus = 20, // SYSS
|
||||
LinkDevices = 21, // LINK
|
||||
ReadEXOSBoundary = 22, // READB
|
||||
SetUSERBoundary = 23, // SETB,
|
||||
AllocateSegment = 24, // ALLOC,
|
||||
FreeSegment = 25, // FREE
|
||||
LocateROMs = 26, // ROMS
|
||||
AllocateChannelBuffer = 27, // BUFF
|
||||
ReturnErrorMessage = 28, // ERRMSG
|
||||
};
|
||||
|
||||
// Page 25.
|
||||
enum class DeviceDescriptorFunction: uint8_t {
|
||||
//
|
||||
// Codes are the same as `Function` in the range 1–11.
|
||||
//
|
||||
Interrupt = 0,
|
||||
Initialise = 12,
|
||||
BufferMoved = 13,
|
||||
};
|
||||
|
||||
enum class Error: uint8_t {
|
||||
NoError = 0x00,
|
||||
|
||||
//
|
||||
// General Kernel Errors.
|
||||
//
|
||||
InvalidFunctionCode = 0xff, // IFUNC
|
||||
FunctionCallNotAllowed = 0xfe, // ILLFN
|
||||
InvalidString = 0xfd, // INAME
|
||||
InsufficientStack = 0xfc, // STACK
|
||||
ChannelIllegalOrDoesNotExist = 0xfb, // ICHAN
|
||||
DeviceDoesNotExist = 0xfa, // NODEV
|
||||
ChannelAlreadyExists = 0xf9, // CHANX
|
||||
NoAllocateBufferCallMade = 0xf8, // NOBUF
|
||||
InsufficientRAMForBuffer = 0xf7, // NORAM
|
||||
InsufficientVideoRAM = 0xf6, // NOVID
|
||||
NoFreeSegments = 0xf5, // NOSEG
|
||||
InvalidSegment = 0xf4, // ISEG
|
||||
InvalidUserBoundary = 0xf3, // IBOUND
|
||||
InvalidEXOSVariableNumber = 0xf2, // IVAR
|
||||
InvalidDesviceDescriptorType = 0xf1, // IDESC
|
||||
UnrecognisedCommandString = 0xf0, // NOSTR
|
||||
InvalidFileHeader = 0xef, // ASCII
|
||||
UnknownModuleType = 0xee, // ITYPE
|
||||
InvalidRelocatableModule = 0xed, // IREL
|
||||
NoModule = 0xec, // NOMOD
|
||||
InvalidTimeOrDateValue, // ITIME
|
||||
|
||||
//
|
||||
// General Device Errors.
|
||||
//
|
||||
InvalidSpecialFunctionCode = 0xea, // ISPEC
|
||||
AttemptToOpenSecondChannel = 0xe9, // 2NDCH
|
||||
InvalidUnitNumber = 0xe8, // IUNIT
|
||||
FunctionNotSupported = 0xe7, // NOFN
|
||||
InvalidEscapeSequence = 0xe6, // ESC
|
||||
StopKeyPressed = 0xe5, // STOP
|
||||
EndOfFileMetInRead = 0xe4, // EOF
|
||||
ProtectionViolation = 0xe3, // PROT
|
||||
|
||||
//
|
||||
// Device-Specific Errors.
|
||||
//
|
||||
// FileDoesNotExist = 0xea, // NOFIL
|
||||
// FileAlreadyExists = 0xe9, // EXFIL
|
||||
// FileAlreadyOpen = 0xe8, // FOPEN
|
||||
// FileIsTooBig = 0xe6, // FSIZE
|
||||
// InvalidFilePointerValue = 0xe5, // FPTR
|
||||
//
|
||||
// //
|
||||
// // Keyboard errors.
|
||||
// //
|
||||
// InvalidFunctionKeyNumber = 0xe3, // KFKEY
|
||||
// RunOutOfFunctionKeySpace = 0xe2, // KFSPC
|
||||
//
|
||||
// //
|
||||
// // Sound errors.
|
||||
// //
|
||||
// EnvelopeInvalidOrTooBig = 0xe1, // SENV
|
||||
// NotEnoughRoomToDefineEnvelope = 0xe0, // SENDBF
|
||||
// EnvelopeStorageRequestedTooSmall = 0xdf, // SENFLO
|
||||
// SoundQueueFull = 0xde, // SQFUL
|
||||
//
|
||||
// //
|
||||
// // Video errors.
|
||||
// //
|
||||
// InvalidRowNumberToScroll = 0xdd, // VROW
|
||||
// AttemptToMoveCursorOffPage = 0xdc, // VCURS
|
||||
// InvalidColourPassedToINKOrPAPER = 0xdb, // VCOLR
|
||||
// InvalidXOrYSizeToOPEN = 0xda, // VSIZE
|
||||
// InvalidVideoModeToOPEN = 0xd9, // VMODE
|
||||
// BadParameterToDISPLAY = 0xdb, // VDISP, and officially 'naff' rather than 'bad'
|
||||
// NotEnoughRowsInPageToDISPLAY = 0xd7, // VDSP2
|
||||
// AttemptToMoveBeamOffPage = 0xd6, // VBEAM
|
||||
// LineStyleTooBig = 0xd5, // VLSTY
|
||||
// LineModeTooBig = 0xd4, // VLMOD
|
||||
// CantDisplayCharacterOrGraphic = 0xd3, // VCHAR
|
||||
//
|
||||
// //
|
||||
// // Serial errors.
|
||||
// //
|
||||
// InvalidBaudRate = 0xd2, // BAUD
|
||||
//
|
||||
// //
|
||||
// // Editor errors.
|
||||
// //
|
||||
// InvalidVideoPageForOPEN = 0xd1, // EVID
|
||||
// TroubleInCommunicatingWithKeyboard = 0xd0, // EKEY
|
||||
// InvalidCoordinatesForPosition = 0xcf, // ECURS
|
||||
//
|
||||
// //
|
||||
// // Cassette errors.
|
||||
// //
|
||||
// CRCErrorFromCassetteDriver = 0xce, // CCRC
|
||||
//
|
||||
// //
|
||||
// // Network errors
|
||||
// //
|
||||
// SerialDeviceOpenCannotUseNetwork = 0xcd, // SEROP
|
||||
// ADDR_NETNotSetUp = 0xcc, // NOADR
|
||||
};
|
||||
|
||||
}
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "Dave.hpp"
|
||||
#include "EXDos.hpp"
|
||||
#include "HostFSHandler.hpp"
|
||||
#include "Keyboard.hpp"
|
||||
#include "Nick.hpp"
|
||||
|
||||
@@ -23,8 +24,20 @@
|
||||
#include "Outputs/Speaker/Implementation/LowpassSpeaker.hpp"
|
||||
#include "Processors/Z80/Z80.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace {
|
||||
using Logger = Log::Logger<Log::Source::Enterprise>;
|
||||
|
||||
static constexpr size_t ram_size(const Analyser::Static::Enterprise::Target::Model model) {
|
||||
switch(model) {
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise64: return 64 * 1024;
|
||||
default:
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise128: return 128 * 1024;
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise256: return 256 * 1024;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Enterprise {
|
||||
@@ -68,10 +81,15 @@ namespace Enterprise {
|
||||
|
||||
*/
|
||||
|
||||
template <bool has_disk_controller, bool is_6mhz> class ConcreteMachine:
|
||||
template <
|
||||
Analyser::Static::Enterprise::Target::Model model,
|
||||
bool has_disk_controller,
|
||||
bool is_6mhz
|
||||
> class ConcreteMachine:
|
||||
public Activity::Source,
|
||||
public Configurable::Device,
|
||||
public CPU::Z80::BusHandler,
|
||||
public HostFSHandler::MemoryAccessor,
|
||||
public Machine,
|
||||
public MachineTypes::AudioProducer,
|
||||
public MachineTypes::MappedKeyboardMachine,
|
||||
@@ -80,17 +98,6 @@ template <bool has_disk_controller, bool is_6mhz> class ConcreteMachine:
|
||||
public MachineTypes::TimedMachine,
|
||||
public Utility::TypeRecipient<CharacterMapper> {
|
||||
private:
|
||||
constexpr uint8_t min_ram_slot(const Analyser::Static::Enterprise::Target &target) {
|
||||
const auto ram_size = [&] {
|
||||
switch(target.model) {
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise64: return 64*1024;
|
||||
default:
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise128: return 128*1024;
|
||||
case Analyser::Static::Enterprise::Target::Model::Enterprise256: return 256*1024;
|
||||
}
|
||||
}();
|
||||
return uint8_t(0x100 - ram_size / 0x4000);
|
||||
}
|
||||
|
||||
static constexpr double clock_rate = is_6mhz ? 6'000'000.0 : 4'000'000.0;
|
||||
using NickType =
|
||||
@@ -100,11 +107,11 @@ private:
|
||||
|
||||
public:
|
||||
ConcreteMachine(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_) {
|
||||
speaker_(dave_audio_),
|
||||
host_fs_(*this) {
|
||||
|
||||
// Request a clock of 4Mhz; this'll be mapped upwards for Nick and downwards for Dave elsewhere.
|
||||
set_clock_rate(clock_rate);
|
||||
@@ -180,15 +187,27 @@ public:
|
||||
throw ROMMachine::Error::MissingROMs;
|
||||
}
|
||||
|
||||
const auto install = [&](const ROM::Name rom_name, auto &destination) {
|
||||
const auto source = roms.find(rom_name);
|
||||
if(source == roms.end()) {
|
||||
return false;
|
||||
}
|
||||
std::copy_n(
|
||||
source->second.begin(),
|
||||
std::min(destination.size(), source->second.size()),
|
||||
destination.begin()
|
||||
);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// 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()));
|
||||
if(install(rom_name, exos_)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -200,9 +219,7 @@ public:
|
||||
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()));
|
||||
if(install(rom_name, basic_)) {
|
||||
has_basic = true;
|
||||
break;
|
||||
}
|
||||
@@ -211,21 +228,32 @@ public:
|
||||
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()));
|
||||
std::copy_n(
|
||||
basic1->second.begin(),
|
||||
std::min(size_t(8192), basic1->second.size()),
|
||||
&basic_[0x0000]
|
||||
);
|
||||
std::copy_n(
|
||||
basic2->second.begin(),
|
||||
std::min(size_t(8192), basic2->second.size()),
|
||||
&basic_[0x2000]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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()));
|
||||
}
|
||||
install(ROM::Name::EnterpriseEPDOS, epdos_rom_);
|
||||
|
||||
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()));
|
||||
install(ROM::Name::EnterpriseEXDOS, exdos_rom_);
|
||||
|
||||
// Possibly install the host FS ROM.
|
||||
host_fs_rom_.fill(0xff);
|
||||
if(!target.media.file_bundles.empty()) {
|
||||
const auto rom = host_fs_.rom();
|
||||
std::copy(rom.begin(), rom.end(), host_fs_rom_.begin());
|
||||
find_host_fs_hooks();
|
||||
}
|
||||
|
||||
// Seed key state.
|
||||
@@ -539,8 +567,40 @@ public:
|
||||
}
|
||||
break;
|
||||
|
||||
case PartialMachineCycle::Read:
|
||||
case PartialMachineCycle::ReadOpcode:
|
||||
{
|
||||
static bool print_opcode = false;
|
||||
if(print_opcode) {
|
||||
printf("%04x: %02x\n", address, read_pointers_[address >> 14][address]);
|
||||
}
|
||||
}
|
||||
|
||||
// Potential segue for the host FS. I'm relying on branch prediction to
|
||||
// avoid this cost almost always.
|
||||
if(test_host_fs_traps_ && (address >> 14) == 3) [[unlikely]] {
|
||||
const auto is_trap = host_fs_traps_.contains(address);
|
||||
|
||||
if(is_trap) {
|
||||
using Register = CPU::Z80::Register;
|
||||
|
||||
uint8_t a = uint8_t(z80_.value_of(Register::A));
|
||||
uint16_t bc = z80_.value_of(Register::BC);
|
||||
uint16_t de = z80_.value_of(Register::DE);
|
||||
|
||||
// Grab function code from where the PC actually is, and return a NOP
|
||||
host_fs_.perform(read_pointers_[address >> 14][address], a, bc, de);
|
||||
*cycle.value = 0x00; // i.e. NOP.
|
||||
|
||||
z80_.set_value_of(Register::A, a);
|
||||
z80_.set_value_of(Register::BC, bc);
|
||||
z80_.set_value_of(Register::DE, de);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
[[fallthrough]];
|
||||
|
||||
case PartialMachineCycle::Read:
|
||||
if(read_pointers_[address >> 14]) {
|
||||
*cycle.value = read_pointers_[address >> 14][address];
|
||||
} else {
|
||||
@@ -570,58 +630,97 @@ public:
|
||||
|
||||
private:
|
||||
// MARK: - Memory layout
|
||||
std::array<uint8_t, 256 * 1024> ram_{};
|
||||
|
||||
std::array<uint8_t, ram_size(model)> 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_;
|
||||
std::array<uint8_t, 16 * 1024> host_fs_rom_;
|
||||
static constexpr auto MinRAMSlot = uint8_t(0x100 - (ram_size(model) >> 14));
|
||||
|
||||
/// @returns A pointer to the start of the RAM segment representing @c page if any; otherwise @c nullptr.
|
||||
uint8_t *ram_segment(const uint8_t page) {
|
||||
if(page < MinRAMSlot) return nullptr;
|
||||
return &ram_[size_t((page - MinRAMSlot) << 14)];
|
||||
}
|
||||
|
||||
struct ROMPage {
|
||||
const uint8_t *rom;
|
||||
uint8_t page_offset;
|
||||
|
||||
operator bool() const {
|
||||
return bool(rom);
|
||||
}
|
||||
uint8_t operator[](const size_t offset) const {
|
||||
return rom[page_offset * 0x4000 + offset];
|
||||
}
|
||||
};
|
||||
|
||||
/// @returns A pointer to the ROM segment representing @c page if any; otherwise an object that converts to bool @c false. The returned object
|
||||
/// names both the start of the ROM and how many pages into it this page rests.
|
||||
ROMPage rom_segment(const uint8_t page) {
|
||||
const auto rom_segment = [&](const uint8_t base, auto &source) -> ROMPage {
|
||||
if(page < base || page >= base + source.size() / 0x4000) {
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
return {
|
||||
source.data(),
|
||||
uint8_t(page - base)
|
||||
};
|
||||
};
|
||||
|
||||
// This is where I've effectively dictated the overall 22-bit RAM layout.
|
||||
// The first argument before each ROM dictates its starting page.
|
||||
if(const auto segment = rom_segment(0, exos_); segment.rom) return segment;
|
||||
if(const auto segment = rom_segment(16, basic_); segment.rom) return segment;
|
||||
if(const auto segment = rom_segment(32, exdos_rom_); segment.rom) return segment;
|
||||
if(const auto segment = rom_segment(48, epdos_rom_); segment.rom) return segment;
|
||||
if(const auto segment = rom_segment(64, host_fs_rom_); segment.rom) return segment;
|
||||
return { nullptr, 0 };
|
||||
}
|
||||
|
||||
// Ephemeral, user-set state, representing the current memory map as viewed from the Z80.
|
||||
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, typename RomT>
|
||||
bool page_rom(const uint8_t offset, const uint8_t location, const RomT &source) {
|
||||
if(offset < location || offset >= location + source.size() / 0x4000) {
|
||||
return false;
|
||||
}
|
||||
|
||||
page<slot>(&source[(offset - location) * 0x4000], nullptr);
|
||||
is_video_[slot] = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Pages whatever is supposed to be at @c offset into memory at @c slot, whether ROM or RAM, and updates the
|
||||
/// @c test_host_fs_traps_ and relevant @c is_video_ flags.
|
||||
template <size_t slot> void page(const uint8_t offset) {
|
||||
const auto apply = [&](const uint8_t *const read, uint8_t *const write) {
|
||||
read_pointers_[slot] = read ? read - (slot * 0x4000) : nullptr;
|
||||
write_pointers_[slot] = write ? write - (slot * 0x4000) : nullptr;
|
||||
};
|
||||
|
||||
pages_[slot] = offset;
|
||||
|
||||
if(page_rom<slot>(offset, 0, exos_)) return;
|
||||
if(page_rom<slot>(offset, 16, basic_)) return;
|
||||
if(page_rom<slot>(offset, 32, exdos_rom_)) return;
|
||||
if(page_rom<slot>(offset, 48, epdos_rom_)) return;
|
||||
const auto rom = rom_segment(offset);
|
||||
if constexpr (slot == 3) {
|
||||
if(rom) {
|
||||
test_host_fs_traps_ = rom.rom == host_fs_rom_.data();
|
||||
} else {
|
||||
test_host_fs_traps_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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]);
|
||||
if(rom) {
|
||||
apply(rom.rom + rom.page_offset * 0x4000, nullptr);
|
||||
is_video_[slot] = false;
|
||||
return;
|
||||
}
|
||||
|
||||
page<slot>(nullptr, nullptr);
|
||||
}
|
||||
auto pointer = ram_segment(offset);
|
||||
if(pointer) {
|
||||
is_video_[slot] = offset >= 0xfc; // TODO: this hard-codes a 64kb video assumption.
|
||||
apply(pointer, pointer);
|
||||
return;
|
||||
}
|
||||
|
||||
template <size_t slot> void page(const uint8_t *const read, uint8_t *const write) {
|
||||
read_pointers_[slot] = read ? read - (slot * 0x4000) : nullptr;
|
||||
write_pointers_[slot] = write ? write - (slot * 0x4000) : nullptr;
|
||||
apply(nullptr, nullptr);
|
||||
}
|
||||
|
||||
// MARK: - Memory Timing
|
||||
|
||||
// The wait mode affects all memory accesses _outside of the video area_.
|
||||
enum class WaitMode {
|
||||
None,
|
||||
@@ -631,6 +730,7 @@ private:
|
||||
bool is_video_[4]{};
|
||||
|
||||
// MARK: - ScanProducer
|
||||
|
||||
void set_scan_target(Outputs::Display::ScanTarget *const scan_target) override {
|
||||
nick_.last_valid()->set_scan_target(scan_target);
|
||||
}
|
||||
@@ -706,11 +806,14 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
if(!media.file_bundles.empty()) {
|
||||
host_fs_.set_file_bundle(media.file_bundles.front());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// MARK: - Interrupts
|
||||
|
||||
uint8_t interrupt_mask_ = 0x00, interrupt_state_ = 0x00;
|
||||
void set_interrupts(const uint8_t mask, const HalfCycles offset = HalfCycles(0)) {
|
||||
interrupt_state_ |= uint8_t(mask);
|
||||
@@ -742,9 +845,93 @@ private:
|
||||
}
|
||||
|
||||
// MARK: - EXDos card.
|
||||
|
||||
EXDos exdos_;
|
||||
|
||||
// MARK: - Host FS.
|
||||
|
||||
HostFSHandler host_fs_;
|
||||
std::unordered_set<uint16_t> host_fs_traps_;
|
||||
bool test_host_fs_traps_ = false;
|
||||
|
||||
/// Reads from mamory as currently laid out.
|
||||
uint8_t hostfs_read(const uint16_t address) override {
|
||||
if(read_pointers_[address >> 14]) {
|
||||
return read_pointers_[address >> 14][address];
|
||||
} else {
|
||||
return 0xff;
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns The page that should be used to access memory at @c address within the current user memory map.
|
||||
/// This is purely an EXOS construct. It has no basis in hardware.
|
||||
uint8_t user_page(const uint16_t address) {
|
||||
const auto page_id = address >> 14;
|
||||
return read_pointers_[0xbffc >> 14] ? read_pointers_[0xbffc >> 14][0xbffc + page_id] : 0xff;
|
||||
}
|
||||
|
||||
/// @returns The byte of RAM at @c address in the user memory map, if RAM is paged there. @c nullptr otherwise.
|
||||
uint8_t *user_ram(const uint8_t page, const uint16_t address) {
|
||||
// "User" accesses go to to wherever the user last had paged;
|
||||
// per 5.4 System Segment Usage those pages are stored in memory from
|
||||
// 0xbffc, so grab from there.
|
||||
const auto offset = address & 0x3fff;
|
||||
auto segment = ram_segment(page);
|
||||
if(segment) {
|
||||
return &segment[offset];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/// @returns The byte at @c address in the user memory map, whether ROM or RAM.
|
||||
uint8_t hostfs_user_read(const uint16_t address) override {
|
||||
const auto page = user_page(address);
|
||||
|
||||
const auto ram = ram_segment(page);
|
||||
if(ram) return ram[address & 0x3fff];
|
||||
|
||||
const auto rom = rom_segment(page);
|
||||
if(rom) return rom[address & 0x3fff];
|
||||
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
/// Writes a byte to an address in the user memory map, if it's RAM. Otherwise acts as a no-op.
|
||||
void hostfs_user_write(const uint16_t address, const uint8_t value) override {
|
||||
const auto ram = user_ram(user_page(address), address);
|
||||
if(ram) *ram = value;
|
||||
}
|
||||
|
||||
/// Searches @c host_fs_rom_ for high-level hooks and records those addresses into @c host_fs_traps_ with the assumption that the rom will
|
||||
/// be paged at 0xc000. Then covers up the hook with NOPs other than the first byte, which captures the hook code.
|
||||
void find_host_fs_hooks() {
|
||||
static constexpr uint8_t syscall[] = {
|
||||
0xed, 0xfe, 0xfe
|
||||
};
|
||||
|
||||
auto begin = host_fs_rom_.begin();
|
||||
while(true) {
|
||||
begin = std::search(
|
||||
begin, host_fs_rom_.end(),
|
||||
std::begin(syscall), std::end(syscall)
|
||||
);
|
||||
|
||||
if(begin == host_fs_rom_.end()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const auto offset = begin - host_fs_rom_.begin() + 0xc000; // ROM will be paged in slot 3, i.e. at $c000.
|
||||
host_fs_traps_.insert(uint16_t(offset));
|
||||
|
||||
// Move function code up to where this trap was, and NOP out the tail.
|
||||
begin[0] = begin[3];
|
||||
begin[1] = begin[2] = begin[3] = 0x00;
|
||||
begin += 4;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Activity Source
|
||||
|
||||
void set_activity_observer([[maybe_unused]] Activity::Observer *const observer) final {
|
||||
if constexpr (has_disk_controller) {
|
||||
exdos_.set_activity_observer(observer);
|
||||
@@ -752,6 +939,7 @@ private:
|
||||
}
|
||||
|
||||
// MARK: - Configuration options.
|
||||
|
||||
std::unique_ptr<Reflection::Struct> get_options() const final {
|
||||
auto options = std::make_unique<Options>(Configurable::OptionsType::UserFriendly);
|
||||
options->output = get_video_signal_configurable();
|
||||
@@ -770,15 +958,37 @@ using namespace Enterprise;
|
||||
|
||||
namespace {
|
||||
|
||||
template <bool has_disk_controller, bool is_6mhz>
|
||||
std::unique_ptr<Machine> machine(
|
||||
const Analyser::Static::Enterprise::Target &target,
|
||||
const ROMMachine::ROMFetcher &rom_fetcher
|
||||
) {
|
||||
switch(target.model) {
|
||||
using enum Analyser::Static::Enterprise::Target::Model;
|
||||
|
||||
default: __builtin_unreachable();
|
||||
|
||||
case Enterprise64:
|
||||
return std::make_unique<Enterprise::ConcreteMachine<Enterprise64, has_disk_controller, true>>
|
||||
(target, rom_fetcher);
|
||||
case Enterprise128:
|
||||
return std::make_unique<Enterprise::ConcreteMachine<Enterprise128, has_disk_controller, true>>
|
||||
(target, rom_fetcher);
|
||||
case Enterprise256:
|
||||
return std::make_unique<Enterprise::ConcreteMachine<Enterprise256, has_disk_controller, true>>
|
||||
(target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
template <bool has_disk_controller>
|
||||
std::unique_ptr<Machine> machine(
|
||||
const Analyser::Static::Enterprise::Target &target,
|
||||
const ROMMachine::ROMFetcher &rom_fetcher
|
||||
) {
|
||||
if(target.speed == Analyser::Static::Enterprise::Target::Speed::SixMHz) {
|
||||
return std::make_unique<Enterprise::ConcreteMachine<has_disk_controller, true>>(target, rom_fetcher);
|
||||
return machine<has_disk_controller, true>(target, rom_fetcher);
|
||||
} else {
|
||||
return std::make_unique<Enterprise::ConcreteMachine<has_disk_controller, false>>(target, rom_fetcher);
|
||||
return machine<has_disk_controller, false>(target, rom_fetcher);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
pyz80.py --obj=hostfs.rom hostfs.z80s && xxd -i hostfs.rom
|
||||
@@ -0,0 +1,215 @@
|
||||
;
|
||||
; Designed for assembly with pyz80, https://github.com/simonowen/pyz80/
|
||||
; E.g. pyz80 --obj=hostfs.rom hostfs.z80s
|
||||
;
|
||||
|
||||
;
|
||||
; Sources:
|
||||
;
|
||||
; http://ep.homeserver.hu/Dokumentacio/Konyvek/EXOS_2.1_technikal_information/exos/kernel/Ch9.html
|
||||
; on signature, device chain pointer and ROM entry point
|
||||
;
|
||||
; http://ep.homeserver.hu/Dokumentacio/Konyvek/EXOS_2.1_technikal_information/exos/kernel/Ch6.html
|
||||
; on the device chain
|
||||
;
|
||||
|
||||
|
||||
;
|
||||
; This code adapts the same mechanism for a host call as that used by EP128Emu's FILE IO ROM.
|
||||
; My original thinking was that one could be substituted for the other to permit comparative testing.
|
||||
; EP128 has a couple of emulator-specific call codes that I don't implement though, and otherwise
|
||||
; doesn't seem to work in this emulator. And likely the converse holds.
|
||||
;
|
||||
hostfscall: macro
|
||||
db 0xed, 0xfe, 0xfe
|
||||
db \0
|
||||
endm
|
||||
|
||||
exoscall: macro
|
||||
rst 0x30
|
||||
db \0
|
||||
endm
|
||||
|
||||
org 0xc000
|
||||
|
||||
dm "EXOS_ROM" ; Standard ROM signature.
|
||||
|
||||
; Pointer to the included device chain, which should be valid when this
|
||||
; ROM is paged at $4000, though when executed from it'll be at $c000.
|
||||
dw 0x4000 + (device_chain & 0x3fff)
|
||||
|
||||
; ROM entry point; handle nothing.
|
||||
ret
|
||||
|
||||
dw 0 ; XX_NEXT_LOW/HI: Pointer to start of next device. There is no next device.
|
||||
dw 0xfffe ; XX_RAM_LOW/HI: [(Amount of host RAM used) + 2] negatived.
|
||||
|
||||
device_chain_type:
|
||||
db 0 ; DD_TYPE: Type, which must be 0.
|
||||
db 0 ; DD_IRQFLAG: No interrupts required.
|
||||
db 0 ; DD_FLAGS: Not a video device.
|
||||
|
||||
dw 0x4000 + (dispatch & 0x3fff)
|
||||
db 0 ; DD_TAB_LOW/HI/SEG:
|
||||
|
||||
db 0 ; DD_UNIT_COUNT: ?
|
||||
device_name:
|
||||
db 4
|
||||
dm "FILE" ; DD_NAME
|
||||
|
||||
device_chain:
|
||||
dw device_chain - device_chain_type
|
||||
|
||||
dispatch:
|
||||
@dispatch: EQU FOR 14
|
||||
dw call{@dispatch}
|
||||
NEXT @dispatch
|
||||
|
||||
;
|
||||
; Interrupt.
|
||||
;
|
||||
; The device chain indicates that this ROM doesn't receive interrupts. So no need to escalate.
|
||||
;
|
||||
call0:
|
||||
ret
|
||||
|
||||
;
|
||||
; Open channel.
|
||||
;
|
||||
; EXOS requires the programmer manually to call its function 27 to allocate a channel buffer if
|
||||
; it otherwise expects to succeed. So some handling is most easily done within the client machine.
|
||||
;
|
||||
call1:
|
||||
ld b, a ; Backup the channel number
|
||||
hostfscall 1
|
||||
call allocate_exos_buffer
|
||||
ret z ; Exit on success.
|
||||
|
||||
; Otherwise, close the file and return the EXOS error.
|
||||
ld c, a
|
||||
ld a, b
|
||||
hostfscall 3
|
||||
ld a, c
|
||||
ret
|
||||
|
||||
;
|
||||
; Create channel.
|
||||
;
|
||||
call2:
|
||||
ld b, a
|
||||
hostfscall 2
|
||||
call allocate_exos_buffer
|
||||
ret z ; Exit on success.
|
||||
|
||||
; Otherwise, erase the newly-created file and return the EXOS error.
|
||||
ld c, a
|
||||
ld a, b
|
||||
hostfscall 4
|
||||
ld a, c
|
||||
ret
|
||||
|
||||
;
|
||||
; Close channel.
|
||||
;
|
||||
call3:
|
||||
hostfscall 3
|
||||
ret
|
||||
|
||||
;
|
||||
; Destroy channel.
|
||||
;
|
||||
call4:
|
||||
hostfscall 4
|
||||
ret
|
||||
|
||||
;
|
||||
; Read character.
|
||||
;
|
||||
call5:
|
||||
hostfscall 5
|
||||
ret
|
||||
|
||||
;
|
||||
; Read block.
|
||||
;
|
||||
call6:
|
||||
hostfscall 6
|
||||
ret
|
||||
|
||||
;
|
||||
; Write character.
|
||||
;
|
||||
call7:
|
||||
hostfscall 7
|
||||
ret
|
||||
|
||||
;
|
||||
; Write block.
|
||||
;
|
||||
call8:
|
||||
hostfscall 8
|
||||
ret
|
||||
|
||||
;
|
||||
; Read channel status.
|
||||
;
|
||||
call9:
|
||||
hostfscall 9
|
||||
ret
|
||||
|
||||
;
|
||||
; Set channel status.
|
||||
;
|
||||
call10:
|
||||
hostfscall 10
|
||||
ret
|
||||
|
||||
;
|
||||
; Special function.
|
||||
;
|
||||
call11:
|
||||
hostfscall 11
|
||||
ret
|
||||
|
||||
;
|
||||
; Initialise.
|
||||
;
|
||||
call12:
|
||||
hostfscall 12
|
||||
|
||||
;
|
||||
; Set this as the default filing system.
|
||||
; Disk dives do this, it's not unprecedented.
|
||||
;
|
||||
ld de, device_name
|
||||
ld c, 1
|
||||
exoscall 19
|
||||
ret
|
||||
|
||||
;
|
||||
; Buffer moved.
|
||||
;
|
||||
call13:
|
||||
hostfscall 13
|
||||
ret
|
||||
|
||||
;
|
||||
; Attempts to allocate EXOS storage for a channel.
|
||||
; Returns Z set for success, clear for failure.
|
||||
;
|
||||
allocate_exos_buffer:
|
||||
; Exit immediately if that call already failed.
|
||||
and a
|
||||
ret nz
|
||||
|
||||
; Restore the channel number and otherwise configure to allocate a buffer.
|
||||
push bc
|
||||
ld a, b
|
||||
ld bc, 0
|
||||
ld de, 1
|
||||
exoscall 27
|
||||
|
||||
; If there's no error from that, exit.
|
||||
pop bc
|
||||
and a
|
||||
ret
|
||||
@@ -0,0 +1,263 @@
|
||||
//
|
||||
// HostFSHandler.cpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/11/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#include "HostFSHandler.hpp"
|
||||
#include "EXOSCodes.hpp"
|
||||
|
||||
using namespace Enterprise;
|
||||
|
||||
HostFSHandler::HostFSHandler(MemoryAccessor &accessor) : accessor_(accessor) {}
|
||||
|
||||
void HostFSHandler::perform(const uint8_t function, uint8_t &a, uint16_t &bc, uint16_t &de) {
|
||||
const auto set_error = [&](const EXOS::Error error) {
|
||||
a = uint8_t(error);
|
||||
};
|
||||
const auto set_b = [&](const uint8_t ch) {
|
||||
bc = uint16_t((bc & 0xffff) | (ch << 8));
|
||||
};
|
||||
const auto set_c = [&](const uint8_t ch) {
|
||||
bc = (bc & 0xff00) | (ch);
|
||||
};
|
||||
const auto b = [&]() -> uint8_t {
|
||||
return bc >> 8;
|
||||
};
|
||||
const auto read_name = [&]() {
|
||||
// Get name.
|
||||
uint8_t length = accessor_.hostfs_read(de++);
|
||||
std::string name;
|
||||
while(length--) {
|
||||
name.push_back(char(accessor_.hostfs_read(de++)));
|
||||
}
|
||||
|
||||
// Use the key file if no name is specified.
|
||||
if(name.empty()) {
|
||||
if(const auto key_file = bundle_->key_file(); key_file.has_value()) {
|
||||
name = *key_file;
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
};
|
||||
|
||||
//
|
||||
// Functions that don't require an existing channel.
|
||||
//
|
||||
switch(function) {
|
||||
default: break;
|
||||
|
||||
case uint8_t(EXOS::DeviceDescriptorFunction::Initialise):
|
||||
channels_.clear();
|
||||
set_error(EXOS::Error::NoError);
|
||||
return;
|
||||
|
||||
case uint8_t(EXOS::DeviceDescriptorFunction::Interrupt):
|
||||
case uint8_t(EXOS::DeviceDescriptorFunction::BufferMoved):
|
||||
set_error(EXOS::Error::NoError);
|
||||
return;
|
||||
|
||||
// Page 54.
|
||||
// Emprically: C contains the unit number.
|
||||
case uint8_t(EXOS::Function::OpenChannel): {
|
||||
if(a == 255) {
|
||||
set_error(EXOS::Error::ChannelIllegalOrDoesNotExist);
|
||||
break;
|
||||
}
|
||||
const auto name = read_name();
|
||||
|
||||
try {
|
||||
channels_.emplace(a, bundle_->open(name, Storage::FileMode::ReadWrite));
|
||||
set_error(EXOS::Error::NoError);
|
||||
} catch(Storage::FileHolder::Error) {
|
||||
try {
|
||||
channels_.emplace(a, bundle_->open(name, Storage::FileMode::Read));
|
||||
set_error(EXOS::Error::NoError);
|
||||
} catch(Storage::FileHolder::Error) {
|
||||
// set_error(EXOS::Error::FileDoesNotExist);
|
||||
set_error(EXOS::Error::ProtectionViolation);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
// Page 54.
|
||||
case uint8_t(EXOS::Function::CreateChannel): {
|
||||
if(a == 255) {
|
||||
set_error(EXOS::Error::ChannelIllegalOrDoesNotExist);
|
||||
break;
|
||||
}
|
||||
const auto name = read_name();
|
||||
|
||||
try {
|
||||
channels_.emplace(a, bundle_->open(name, Storage::FileMode::Rewrite));
|
||||
set_error(EXOS::Error::NoError);
|
||||
} catch(Storage::FileHolder::Error) {
|
||||
// set_error(EXOS::Error::FileAlreadyExists);
|
||||
set_error(EXOS::Error::ProtectionViolation);
|
||||
}
|
||||
} return;
|
||||
|
||||
case uint8_t(EXOS::Function::SpecialFunction):
|
||||
// Not supported;
|
||||
set_error(EXOS::Error::InvalidSpecialFunctionCode);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Functions from here require a channel already open.
|
||||
//
|
||||
const auto channel = channels_.find(a);
|
||||
if(channel == channels_.end()) {
|
||||
set_error(EXOS::Error::ChannelIllegalOrDoesNotExist);
|
||||
return;
|
||||
}
|
||||
auto &file = channel->second;
|
||||
|
||||
switch(function) {
|
||||
default:
|
||||
printf("UNIMPLEMENTED function %d with A:%02x BC:%04x DE:%04x\n", function, a, bc, de);
|
||||
break;
|
||||
|
||||
// Page 54.
|
||||
case uint8_t(EXOS::Function::CloseChannel):
|
||||
set_error(EXOS::Error::NoError);
|
||||
channels_.erase(channel);
|
||||
break;
|
||||
|
||||
// Page 54.
|
||||
case uint8_t(EXOS::Function::DestroyChannel): {
|
||||
const auto name = file.name();
|
||||
channels_.erase(channel);
|
||||
if(bundle_->erase(name)) {
|
||||
set_error(EXOS::Error::NoError);
|
||||
} else {
|
||||
set_error(EXOS::Error::ProtectionViolation);
|
||||
}
|
||||
} break;
|
||||
|
||||
// Page 55.
|
||||
case uint8_t(EXOS::Function::ReadCharacter): {
|
||||
const auto next = file.get();
|
||||
if(file.eof()) {
|
||||
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||
} else {
|
||||
set_b(next);
|
||||
set_error(EXOS::Error::NoError);
|
||||
}
|
||||
} break;
|
||||
|
||||
// Page 55.
|
||||
case uint8_t(EXOS::Function::WriteCharacter): {
|
||||
if(file.put(b())) {
|
||||
set_error(EXOS::Error::NoError);
|
||||
} else {
|
||||
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||
}
|
||||
} break;
|
||||
|
||||
// Page 55.
|
||||
case uint8_t(EXOS::Function::ReadBlock): {
|
||||
set_error(EXOS::Error::NoError);
|
||||
while(bc) {
|
||||
const auto next = file.get();
|
||||
if(channel->second.eof()) {
|
||||
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||
break;
|
||||
}
|
||||
|
||||
accessor_.hostfs_user_write(de++, next);
|
||||
--bc;
|
||||
}
|
||||
} break;
|
||||
|
||||
// Page 56.
|
||||
case uint8_t(EXOS::Function::WriteBlock): {
|
||||
set_error(EXOS::Error::NoError);
|
||||
while(bc) {
|
||||
const auto next = accessor_.hostfs_user_read(de);
|
||||
if(!file.put(next)) {
|
||||
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||
break;
|
||||
}
|
||||
|
||||
++de;
|
||||
--bc;
|
||||
}
|
||||
} break;
|
||||
|
||||
// Page 56.
|
||||
case uint8_t(EXOS::Function::ReadChannelStatus):
|
||||
a = file.eof() ? 0xff : 0x00;
|
||||
break;
|
||||
|
||||
// Page 56.
|
||||
case uint8_t(EXOS::Function::SetChannelStatus): {
|
||||
if(bc & 4) {
|
||||
// Protection byte is not supported.
|
||||
set_error(EXOS::Error::FunctionNotSupported);
|
||||
break;
|
||||
}
|
||||
|
||||
if(bc & 1) { // User is requesting a seek.
|
||||
auto pointer = de;
|
||||
uint32_t file_pointer;
|
||||
file_pointer = accessor_.hostfs_user_read(pointer++);
|
||||
file_pointer |= uint32_t(accessor_.hostfs_user_read(pointer++) << 8);
|
||||
file_pointer |= uint32_t(accessor_.hostfs_user_read(pointer++) << 16);
|
||||
file_pointer |= uint32_t(accessor_.hostfs_user_read(pointer++) << 24);
|
||||
|
||||
if(!file.seek(file_pointer, Storage::Whence::SET)) {
|
||||
set_error(EXOS::Error::EndOfFileMetInRead);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in both position and length.
|
||||
set_c(3);
|
||||
const uint32_t file_pointer = uint32_t(file.tell());
|
||||
const uint32_t file_length = uint32_t(file.stats().st_size);
|
||||
|
||||
auto pointer = de;
|
||||
const auto write = [&](const uint32_t source) {
|
||||
accessor_.hostfs_user_write(pointer++, uint8_t(source >> 0));
|
||||
accessor_.hostfs_user_write(pointer++, uint8_t(source >> 8));
|
||||
accessor_.hostfs_user_write(pointer++, uint8_t(source >> 16));
|
||||
accessor_.hostfs_user_write(pointer++, uint8_t(source >> 24));
|
||||
};
|
||||
write(file_pointer);
|
||||
write(file_length);
|
||||
|
||||
set_error(EXOS::Error::NoError);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void HostFSHandler::set_file_bundle(std::shared_ptr<Storage::FileBundle::FileBundle> bundle) {
|
||||
bundle_ = bundle;
|
||||
bundle_->set_case_insensitive(true);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> HostFSHandler::rom() {
|
||||
// Assembled and transcribed from hostfs.z80.
|
||||
return std::vector<uint8_t>{
|
||||
0x45, 0x58, 0x4f, 0x53, 0x5f, 0x52, 0x4f, 0x4d, 0x1b, 0x40, 0xc9, 0x00,
|
||||
0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x1d, 0x40, 0x00, 0x00, 0x04, 0x46,
|
||||
0x49, 0x4c, 0x45, 0x0c, 0x00, 0x39, 0xc0, 0x3a, 0xc0, 0x4b, 0xc0, 0x5c,
|
||||
0xc0, 0x61, 0xc0, 0x66, 0xc0, 0x6b, 0xc0, 0x70, 0xc0, 0x75, 0xc0, 0x7a,
|
||||
0xc0, 0x7f, 0xc0, 0x84, 0xc0, 0x89, 0xc0, 0x95, 0xc0, 0xc9, 0x47, 0xed,
|
||||
0xfe, 0xfe, 0x01, 0xcd, 0x9a, 0xc0, 0xc8, 0x4f, 0x78, 0xed, 0xfe, 0xfe,
|
||||
0x03, 0x79, 0xc9, 0x47, 0xed, 0xfe, 0xfe, 0x02, 0xcd, 0x9a, 0xc0, 0xc8,
|
||||
0x4f, 0x78, 0xed, 0xfe, 0xfe, 0x04, 0x79, 0xc9, 0xed, 0xfe, 0xfe, 0x03,
|
||||
0xc9, 0xed, 0xfe, 0xfe, 0x04, 0xc9, 0xed, 0xfe, 0xfe, 0x05, 0xc9, 0xed,
|
||||
0xfe, 0xfe, 0x06, 0xc9, 0xed, 0xfe, 0xfe, 0x07, 0xc9, 0xed, 0xfe, 0xfe,
|
||||
0x08, 0xc9, 0xed, 0xfe, 0xfe, 0x09, 0xc9, 0xed, 0xfe, 0xfe, 0x0a, 0xc9,
|
||||
0xed, 0xfe, 0xfe, 0x0b, 0xc9, 0xed, 0xfe, 0xfe, 0x0c, 0x11, 0x16, 0xc0,
|
||||
0x0e, 0x01, 0xf7, 0x13, 0xc9, 0xed, 0xfe, 0xfe, 0x0d, 0xc9, 0xa7, 0xc0,
|
||||
0xc5, 0x78, 0x01, 0x00, 0x00, 0x11, 0x01, 0x00, 0xf7, 0x1b, 0xc1, 0xa7,
|
||||
0xc9
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// HostFSHandler.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 20/11/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Storage/FileBundle/FileBundle.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace Enterprise {
|
||||
|
||||
struct HostFSHandler {
|
||||
struct MemoryAccessor {
|
||||
// Accessors that read from however the Z80's 64kb is currently laid out.
|
||||
virtual uint8_t hostfs_read(uint16_t) = 0;
|
||||
// virtual void hostfs_write(uint16_t, uint8_t) = 0;
|
||||
|
||||
// Accessors that read from 'user' address space, i.e. the 64kb Z80 address space as currently
|
||||
// mapped according to the user's preference.
|
||||
virtual uint8_t hostfs_user_read(uint16_t) = 0;
|
||||
virtual void hostfs_user_write(uint16_t, uint8_t) = 0;
|
||||
};
|
||||
|
||||
HostFSHandler(MemoryAccessor &);
|
||||
|
||||
/// Perform the internally-defined @c function given other provided state.
|
||||
/// These function calls mostly align with those in EXOSCodes.hpp
|
||||
void perform(uint8_t function, uint8_t &a, uint16_t &bc, uint16_t &de);
|
||||
|
||||
/// Sets the bundle of files on which this handler should operate.
|
||||
void set_file_bundle(std::shared_ptr<Storage::FileBundle::FileBundle> bundle);
|
||||
|
||||
/// @returns A suitable in-client filing system ROM.
|
||||
std::vector<uint8_t> rom();
|
||||
|
||||
private:
|
||||
MemoryAccessor &accessor_;
|
||||
std::shared_ptr<Storage::FileBundle::FileBundle> bundle_;
|
||||
|
||||
using ChannelHandler = uint8_t;
|
||||
std::unordered_map<ChannelHandler, Storage::FileHolder> channels_;
|
||||
};
|
||||
|
||||
};
|
||||
@@ -798,7 +798,7 @@ public:
|
||||
}
|
||||
|
||||
void clear_all_keys() final {
|
||||
std::memset(key_states_, 0xff, sizeof(key_states_));
|
||||
std::fill(std::begin(key_states_), std::end(key_states_), 0xff);
|
||||
}
|
||||
|
||||
void set_key_state(uint16_t key, bool is_pressed) final {
|
||||
|
||||
@@ -120,10 +120,10 @@ public:
|
||||
if(!target.media.cartridges.empty()) {
|
||||
cartridge_ = target.media.cartridges[0]->get_segments()[0].data;
|
||||
}
|
||||
if(cartridge_.size() < 48*1024) {
|
||||
std::size_t new_space = 48*1024 - cartridge_.size();
|
||||
cartridge_.resize(48*1024);
|
||||
memset(&cartridge_[48*1024 - new_space], 0xff, new_space);
|
||||
|
||||
static constexpr size_t TargetSize = 48*1024;
|
||||
if(cartridge_.size() < TargetSize) {
|
||||
cartridge_.resize(TargetSize, 0xff);
|
||||
}
|
||||
|
||||
if(paging_scheme_ == Target::PagingScheme::Codemasters) {
|
||||
@@ -154,7 +154,7 @@ public:
|
||||
std::cerr << "No BIOS found; attempting to start cartridge directly" << std::endl;
|
||||
} else {
|
||||
has_bios_ = true;
|
||||
memcpy(&bios_, rom->second.data(), std::min(sizeof(bios_), rom->second.size()));
|
||||
std::copy_n(rom->second.begin(), std::min(sizeof(bios_), rom->second.size()), bios_);
|
||||
}
|
||||
page_cartridge();
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
|
||||
#include "ClockReceiver/JustInTime.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -132,7 +133,7 @@ public:
|
||||
|
||||
/// Sets all keys as unpressed.
|
||||
void clear_all_keys() {
|
||||
memset(rows_, 0, sizeof(rows_));
|
||||
std::fill(std::begin(rows_), std::end(rows_), 0);
|
||||
}
|
||||
|
||||
/// Selects the active row.
|
||||
|
||||
@@ -220,7 +220,7 @@ private:
|
||||
|
||||
if(pixels) {
|
||||
if(state.cursor) {
|
||||
std::fill(pixel_pointer, pixel_pointer + pixels_per_tick, 0x3f); // i.e. white.
|
||||
std::fill_n(pixel_pointer, pixels_per_tick, 0x3f); // i.e. white.
|
||||
} else {
|
||||
if(mode_ == Mode::Text) {
|
||||
serialise_text(state);
|
||||
|
||||
@@ -59,7 +59,7 @@ struct LinearPool {
|
||||
|
||||
// Provided for setup.
|
||||
void install(const uint32_t address, const uint8_t *const data, const uint32_t length) {
|
||||
std::copy(data, data + length, memory.begin() + std::vector<uint8_t>::difference_type(address));
|
||||
std::copy_n(data, length, memory.begin() + std::vector<uint8_t>::difference_type(address));
|
||||
}
|
||||
|
||||
// Used by both DMA devices and by the CGA and MDA cards to set up their base pointers.
|
||||
|
||||
@@ -184,7 +184,7 @@ private:
|
||||
|
||||
if(((attributes & 7) == 1) && state.line.get() == 13) {
|
||||
// Draw as underline.
|
||||
std::fill(pixel_pointer, pixel_pointer + 9, intensity);
|
||||
std::fill_n(pixel_pointer, 9, intensity);
|
||||
} else {
|
||||
// Draw according to ROM contents, possibly duplicating final column.
|
||||
pixel_pointer[0] = (row & 0x80) ? intensity : off;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
#include "Keyboard.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace Sinclair::ZX::Keyboard;
|
||||
|
||||
@@ -339,7 +339,7 @@ void Keyboard::set_key_state(const uint16_t key, const bool is_pressed) {
|
||||
}
|
||||
|
||||
void Keyboard::clear_all_keys() {
|
||||
memset(key_states_, 0xff, 8);
|
||||
std::fill(std::begin(key_states_), std::end(key_states_), 0xff);
|
||||
}
|
||||
|
||||
uint8_t Keyboard::read(const uint16_t address) {
|
||||
|
||||
@@ -164,7 +164,7 @@ public:
|
||||
}
|
||||
|
||||
const auto &rom = roms.find(rom_name)->second;
|
||||
memcpy(rom_.data(), rom.data(), std::min(rom_.size(), rom.size()));
|
||||
std::copy_n(rom.begin(), std::min(rom_.size(), rom.size()), rom_.begin());
|
||||
|
||||
// Register for sleeping notifications.
|
||||
tape_player_.set_clocking_hint_observer(this);
|
||||
@@ -200,10 +200,10 @@ public:
|
||||
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(&banks_[c + 1].write[(c+1) * 0x4000], &state->ram[c * 0x4000], 0x4000);
|
||||
std::copy_n(&state->ram[c * 0x4000], 0x4000, &banks_[c + 1].write[(c+1) * 0x4000]);
|
||||
}
|
||||
} else {
|
||||
memcpy(ram_.data(), state->ram.data(), std::min(ram_.size(), state->ram.size()));
|
||||
std::copy_n(state->ram.begin(), std::min(ram_.size(), state->ram.size()), ram_.data());
|
||||
|
||||
port1ffd_ = state->last_1ffd;
|
||||
port7ffd_ = state->last_7ffd;
|
||||
|
||||
@@ -47,6 +47,7 @@ struct Machine {
|
||||
DeclareField(automatic_tape_motor_control);
|
||||
declare_display_option();
|
||||
declare_quickload_option();
|
||||
limit_enum(&output, Configurable::Display::RGB, Configurable::Display::CompositeColour, -1);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -253,6 +253,7 @@ std::map<std::string, std::unique_ptr<Reflection::Struct>> Machine::AllOptionsBy
|
||||
Emplace(AppleII, Apple::II::Machine);
|
||||
Emplace(Archimedes, Archimedes::Machine);
|
||||
Emplace(AtariST, Atari::ST::Machine);
|
||||
Emplace(BBCMicro, BBCMicro::Machine);
|
||||
Emplace(ColecoVision, Coleco::Vision::Machine);
|
||||
Emplace(Electron, Electron::Machine);
|
||||
Emplace(Enterprise, Enterprise::Machine);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cstdlib>
|
||||
#include <iomanip>
|
||||
#include <locale>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
using namespace ROM;
|
||||
|
||||
+25
-21
@@ -10,6 +10,8 @@
|
||||
|
||||
#include "Sizes.hpp"
|
||||
|
||||
#include <concepts>
|
||||
|
||||
namespace Numeric {
|
||||
|
||||
/*!
|
||||
@@ -21,42 +23,44 @@ struct SizedInt {
|
||||
using IntT = MinIntForValue<1 << bits>::type;
|
||||
inline static constexpr IntT Mask = (1 << bits) - 1;
|
||||
|
||||
constexpr SizedInt(const IntT start_value) noexcept : counter_(start_value & Mask) {}
|
||||
template <std::integral ConstructionT>
|
||||
constexpr SizedInt(const ConstructionT start_value) noexcept : value_(IntT(start_value & Mask)) {}
|
||||
|
||||
SizedInt() = default;
|
||||
|
||||
template <int begin = 0>
|
||||
IntT get() const {
|
||||
return counter_ >> begin;
|
||||
return value_ >> begin;
|
||||
}
|
||||
|
||||
SizedInt operator +(const SizedInt offset) const { return SizedInt<bits>(counter_ + offset.counter_); }
|
||||
SizedInt operator -(const SizedInt offset) const { return SizedInt<bits>(counter_ - offset.counter_); }
|
||||
SizedInt operator &(const SizedInt offset) const { return SizedInt<bits>(counter_ & offset.counter_); }
|
||||
SizedInt operator |(const SizedInt offset) const { return SizedInt<bits>(counter_ | offset.counter_); }
|
||||
SizedInt operator ^(const SizedInt offset) const { return SizedInt<bits>(counter_ ^ offset.counter_); }
|
||||
SizedInt operator >>(const int shift) const { return SizedInt<bits>(counter_ >> shift); }
|
||||
SizedInt operator <<(const int shift) const { return SizedInt<bits>(counter_ << shift); }
|
||||
SizedInt operator +(const SizedInt offset) const { return SizedInt<bits>(value_ + offset.value_); }
|
||||
SizedInt operator -(const SizedInt offset) const { return SizedInt<bits>(value_ - offset.value_); }
|
||||
SizedInt operator &(const SizedInt offset) const { return SizedInt<bits>(value_ & offset.value_); }
|
||||
SizedInt operator |(const SizedInt offset) const { return SizedInt<bits>(value_ | offset.value_); }
|
||||
SizedInt operator ^(const SizedInt offset) const { return SizedInt<bits>(value_ ^ offset.value_); }
|
||||
SizedInt operator >>(const int shift) const { return SizedInt<bits>(value_ >> shift); }
|
||||
SizedInt operator <<(const int shift) const { return SizedInt<bits>(value_ << shift); }
|
||||
|
||||
SizedInt &operator &=(const SizedInt offset) {
|
||||
counter_ &= offset.counter_;
|
||||
value_ &= offset.value_;
|
||||
return *this;
|
||||
}
|
||||
SizedInt &operator |=(const SizedInt offset) {
|
||||
counter_ |= offset.counter_;
|
||||
value_ |= offset.value_;
|
||||
return *this;
|
||||
}
|
||||
SizedInt &operator ^=(const SizedInt offset) {
|
||||
counter_ ^= offset.counter_;
|
||||
value_ ^= offset.value_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SizedInt &operator <<=(const int shift) {
|
||||
counter_ = (counter_ << shift) & Mask;
|
||||
value_ = (value_ << shift) & Mask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SizedInt &operator >>=(const int shift) {
|
||||
counter_ >>= shift;
|
||||
value_ >>= shift;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -66,17 +70,17 @@ struct SizedInt {
|
||||
}
|
||||
|
||||
SizedInt &operator ++() {
|
||||
counter_ = (counter_ + 1) & Mask;
|
||||
value_ = (value_ + 1) & Mask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
SizedInt &operator +=(const IntT rhs) {
|
||||
counter_ = (counter_ + rhs) & Mask;
|
||||
value_ = (value_ + rhs) & Mask;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator!() const {
|
||||
return !counter_;
|
||||
return !value_;
|
||||
}
|
||||
|
||||
auto operator <=>(const SizedInt &) const = default;
|
||||
@@ -85,8 +89,8 @@ struct SizedInt {
|
||||
template <int begin, int end>
|
||||
void load(const MinIntForValue<1 << (end - begin)>::type value) {
|
||||
const auto mask = (1 << end) - (1 << begin);
|
||||
counter_ &= ~mask;
|
||||
counter_ |= IntT((value << begin) & mask);
|
||||
value_ &= ~mask;
|
||||
value_ |= IntT((value << begin) & mask);
|
||||
}
|
||||
|
||||
template <int begin, typename IntT>
|
||||
@@ -97,11 +101,11 @@ struct SizedInt {
|
||||
template <int index>
|
||||
requires (index < bits)
|
||||
bool bit() const {
|
||||
return counter_ & (1 << index);
|
||||
return value_ & (1 << index);
|
||||
}
|
||||
|
||||
private:
|
||||
IntT counter_{};
|
||||
IntT value_{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -42,8 +42,6 @@
|
||||
42EB812D2B4700B700429AF4 /* MemoryMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */; };
|
||||
42EB812E2B4700B700429AF4 /* MemoryMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */; };
|
||||
42EB812F2B4700B800429AF4 /* MemoryMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 42EB812C2B47008D00429AF4 /* MemoryMap.cpp */; };
|
||||
4B0150262D71286B00F270C7 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; };
|
||||
4B0150272D71286B00F270C7 /* SDL2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
4B018B89211930DE002A3937 /* 65C02_extended_opcodes_test.bin in Resources */ = {isa = PBXBuildFile; fileRef = 4B018B88211930DE002A3937 /* 65C02_extended_opcodes_test.bin */; };
|
||||
4B01A6881F22F0DB001FD6E3 /* Z80MemptrTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B01A6871F22F0DB001FD6E3 /* Z80MemptrTests.swift */; };
|
||||
4B0333AF2094081A0050B93D /* AppleDSK.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B0333AD2094081A0050B93D /* AppleDSK.cpp */; };
|
||||
@@ -635,6 +633,8 @@
|
||||
4B8334951F5E25B60097E338 /* C1540.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8334941F5E25B60097E338 /* C1540.cpp */; };
|
||||
4B85322F2277ABDE00F26553 /* tos100.trace.txt.gz in Resources */ = {isa = PBXBuildFile; fileRef = 4B85322E2277ABDD00F26553 /* tos100.trace.txt.gz */; };
|
||||
4B86E25B1F8C628F006FAA45 /* Keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B86E2591F8C628F006FAA45 /* Keyboard.cpp */; };
|
||||
4B87C8E12EEA296E000BDF3E /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; };
|
||||
4B87C8E22EEA298B000BDF3E /* SDL2.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 4B055A771FAE78210060FFFF /* SDL2.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
4B8805F01DCFC99C003085B1 /* Acorn.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805EE1DCFC99C003085B1 /* Acorn.cpp */; };
|
||||
4B8805F41DCFD22A003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F21DCFD22A003085B1 /* Commodore.cpp */; };
|
||||
4B8805F71DCFF6C9003085B1 /* Commodore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B8805F51DCFF6C9003085B1 /* Commodore.cpp */; };
|
||||
@@ -1148,6 +1148,12 @@
|
||||
4BCE0060227D39AB000CA200 /* Video.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE005E227D39AB000CA200 /* Video.cpp */; };
|
||||
4BCE1DF125D4C3FA00AE7A2B /* Bus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */; };
|
||||
4BCE1DF225D4C3FA00AE7A2B /* Bus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */; };
|
||||
4BCF1ACF2ECE759000109999 /* FileBundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1ACD2ECE759000109999 /* FileBundle.cpp */; };
|
||||
4BCF1AD02ECE759000109999 /* FileBundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1ACD2ECE759000109999 /* FileBundle.cpp */; };
|
||||
4BCF1AD12ECE759000109999 /* FileBundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1ACD2ECE759000109999 /* FileBundle.cpp */; };
|
||||
4BCF1AD52ECF884100109999 /* HostFSHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */; };
|
||||
4BCF1AD62ECF884100109999 /* HostFSHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */; };
|
||||
4BCF1AD72ECF884100109999 /* HostFSHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */; };
|
||||
4BCF1FA41DADC3DD0039D2E7 /* Oric.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */; };
|
||||
4BD0FBC3233706A200148981 /* CSApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 4BD0FBC2233706A200148981 /* CSApplication.m */; };
|
||||
4BD191F52191180E0042E144 /* ScanTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4BD191F22191180E0042E144 /* ScanTarget.cpp */; };
|
||||
@@ -1252,7 +1258,7 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
4B0150272D71286B00F270C7 /* SDL2.framework in Embed Frameworks */,
|
||||
4B87C8E22EEA298B000BDF3E /* SDL2.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -1803,6 +1809,7 @@
|
||||
4B8671EC2D99BE2C009E1610 /* MachineStatus.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = MachineStatus.hpp; sourceTree = "<group>"; };
|
||||
4B86E2591F8C628F006FAA45 /* Keyboard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Keyboard.cpp; sourceTree = "<group>"; };
|
||||
4B86E25A1F8C628F006FAA45 /* Keyboard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Keyboard.hpp; sourceTree = "<group>"; };
|
||||
4B87C8DC2EE07C46000BDF3E /* TapeHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = TapeHandler.hpp; sourceTree = "<group>"; };
|
||||
4B8805EE1DCFC99C003085B1 /* Acorn.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Acorn.cpp; path = Parsers/Acorn.cpp; sourceTree = "<group>"; };
|
||||
4B8805EF1DCFC99C003085B1 /* Acorn.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = Acorn.hpp; path = Parsers/Acorn.hpp; sourceTree = "<group>"; };
|
||||
4B8805F21DCFD22A003085B1 /* Commodore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = Commodore.cpp; path = Parsers/Commodore.cpp; sourceTree = "<group>"; };
|
||||
@@ -1987,6 +1994,7 @@
|
||||
4BA0F68D1EEA0E8400E9489E /* ZX8081.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZX8081.hpp; sourceTree = "<group>"; };
|
||||
4BA141C12073100800A31EC9 /* Target.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Target.hpp; sourceTree = "<group>"; };
|
||||
4BA3AE44283317CB00328FED /* RegisterSet.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = RegisterSet.hpp; sourceTree = "<group>"; };
|
||||
4BA3AF5C2EF252320088C3BC /* API.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = API.hpp; sourceTree = "<group>"; };
|
||||
4BA61EAE1D91515900B3C876 /* NSData+StdVector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+StdVector.h"; sourceTree = "<group>"; };
|
||||
4BA61EAF1D91515900B3C876 /* NSData+StdVector.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = "NSData+StdVector.mm"; sourceTree = "<group>"; };
|
||||
4BA6B6AD284EDAC000A3B7A8 /* 68000OldVsNew.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = 68000OldVsNew.mm; sourceTree = "<group>"; };
|
||||
@@ -2425,6 +2433,11 @@
|
||||
4BCE005F227D39AB000CA200 /* Video.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Video.hpp; sourceTree = "<group>"; };
|
||||
4BCE1DEF25D4C3FA00AE7A2B /* Bus.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Bus.cpp; sourceTree = "<group>"; };
|
||||
4BCE1DF025D4C3FA00AE7A2B /* Bus.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Bus.hpp; sourceTree = "<group>"; };
|
||||
4BCF1ACC2ECE759000109999 /* FileBundle.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = FileBundle.hpp; sourceTree = "<group>"; };
|
||||
4BCF1ACD2ECE759000109999 /* FileBundle.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = FileBundle.cpp; sourceTree = "<group>"; };
|
||||
4BCF1AD22ECF743500109999 /* EXOSCodes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = EXOSCodes.hpp; sourceTree = "<group>"; };
|
||||
4BCF1AD32ECF884100109999 /* HostFSHandler.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = HostFSHandler.hpp; sourceTree = "<group>"; };
|
||||
4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = HostFSHandler.cpp; sourceTree = "<group>"; };
|
||||
4BCF1FA21DADC3DD0039D2E7 /* Oric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Oric.cpp; sourceTree = "<group>"; };
|
||||
4BCF1FA31DADC3DD0039D2E7 /* Oric.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Oric.hpp; sourceTree = "<group>"; };
|
||||
4BD060A51FE49D3C006E14BE /* Speaker.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Speaker.hpp; sourceTree = "<group>"; };
|
||||
@@ -2560,7 +2573,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4B0150262D71286B00F270C7 /* SDL2.framework in Frameworks */,
|
||||
4B87C8E12EEA296E000BDF3E /* SDL2.framework in Frameworks */,
|
||||
4B055AF21FAE9C1C0060FFFF /* OpenGL.framework in Frameworks */,
|
||||
4BB8617224E22F5A00A00E03 /* Accelerate.framework in Frameworks */,
|
||||
4B055ABD1FAE86530060FFFF /* libz.tbd in Frameworks */,
|
||||
@@ -2700,11 +2713,14 @@
|
||||
4BFEA2ED2682A7B900EBF94C /* Dave.cpp */,
|
||||
4B051CA12676F52200CA44E8 /* Enterprise.cpp */,
|
||||
4B051CB42680158600CA44E8 /* EXDos.cpp */,
|
||||
4BCF1AD42ECF884100109999 /* HostFSHandler.cpp */,
|
||||
4B051CAE267C1CA200CA44E8 /* Keyboard.cpp */,
|
||||
4B051CAA26783E2000CA44E8 /* Nick.cpp */,
|
||||
4BFEA2EE2682A7B900EBF94C /* Dave.hpp */,
|
||||
4B051CA02676F52200CA44E8 /* Enterprise.hpp */,
|
||||
4B051CB52680158600CA44E8 /* EXDos.hpp */,
|
||||
4BCF1AD22ECF743500109999 /* EXOSCodes.hpp */,
|
||||
4BCF1AD32ECF884100109999 /* HostFSHandler.hpp */,
|
||||
4B051CAF267C1CA200CA44E8 /* Keyboard.hpp */,
|
||||
4B051CAB26783E2000CA44E8 /* Nick.hpp */,
|
||||
);
|
||||
@@ -3533,12 +3549,13 @@
|
||||
children = (
|
||||
4B8058022D0D0B55007EAD46 /* Keyboard.cpp */,
|
||||
4B596EB02D037E8800FBF4B1 /* Plus4.cpp */,
|
||||
4B8058062D23902B007EAD46 /* Audio.hpp */,
|
||||
4B0329222D0BD32500C51EB5 /* Interrupts.hpp */,
|
||||
4B8058012D0D0B55007EAD46 /* Keyboard.hpp */,
|
||||
4B0329212D0A8C4700C51EB5 /* Pager.hpp */,
|
||||
4B596EAF2D037E8800FBF4B1 /* Plus4.hpp */,
|
||||
4B87C8DC2EE07C46000BDF3E /* TapeHandler.hpp */,
|
||||
4B03291F2D0923E300C51EB5 /* Video.hpp */,
|
||||
4B8058062D23902B007EAD46 /* Audio.hpp */,
|
||||
);
|
||||
path = Plus4;
|
||||
sourceTree = "<group>";
|
||||
@@ -3587,6 +3604,7 @@
|
||||
4BEE0A691D72496600532C7B /* Cartridge */,
|
||||
4B8805F81DCFF6CD003085B1 /* Data */,
|
||||
4BAB62AA1D3272D200DF5BA0 /* Disk */,
|
||||
4BCF1ACE2ECE759000109999 /* FileBundle */,
|
||||
4B6AAEA1230E3E1D0078E864 /* MassStorage */,
|
||||
4B8DD3832634D37E00B3C866 /* State */,
|
||||
4B69FB3A1C4D908A00B5F0AA /* Tape */,
|
||||
@@ -5289,6 +5307,15 @@
|
||||
path = ADB;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCF1ACE2ECE759000109999 /* FileBundle */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4BCF1ACC2ECE759000109999 /* FileBundle.hpp */,
|
||||
4BCF1ACD2ECE759000109999 /* FileBundle.cpp */,
|
||||
);
|
||||
path = FileBundle;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4BCF1FA51DADC3E10039D2E7 /* Oric */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -5323,6 +5350,7 @@
|
||||
children = (
|
||||
4BD191F22191180E0042E144 /* ScanTarget.cpp */,
|
||||
4BD5D2672199148100DDF17D /* ScanTargetGLSLFragments.cpp */,
|
||||
4BA3AF5C2EF252320088C3BC /* API.hpp */,
|
||||
4BD191D9219113B80042E144 /* OpenGL.hpp */,
|
||||
4BD191F32191180E0042E144 /* ScanTarget.hpp */,
|
||||
4B961408222760E0001A7BF2 /* Screenshot.hpp */,
|
||||
@@ -5686,7 +5714,7 @@
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 1630;
|
||||
LastUpgradeCheck = 2620;
|
||||
ORGANIZATIONNAME = "Thomas Harte";
|
||||
TargetAttributes = {
|
||||
4B055A691FAE763F0060FFFF = {
|
||||
@@ -6197,6 +6225,7 @@
|
||||
4B65086122F4CFE0009C1100 /* Keyboard.cpp in Sources */,
|
||||
4BBB70A9202014E2002FE009 /* MultiProducer.cpp in Sources */,
|
||||
4B2E86BF25D74F160024F1E9 /* Mouse.cpp in Sources */,
|
||||
4BCF1AD12ECE759000109999 /* FileBundle.cpp in Sources */,
|
||||
4B6ED2F1208E2F8A0047B343 /* WOZ.cpp in Sources */,
|
||||
4B5D5C9825F56FC7001B4623 /* Spectrum.cpp in Sources */,
|
||||
4B7C681727517A59001671EC /* Sprites.cpp in Sources */,
|
||||
@@ -6339,6 +6368,7 @@
|
||||
4B055AC41FAE9AE80060FFFF /* Keyboard.cpp in Sources */,
|
||||
4B8DF506254E3C9D00F3433C /* ADB.cpp in Sources */,
|
||||
4B055A941FAE85B50060FFFF /* CommodoreROM.cpp in Sources */,
|
||||
4BCF1AD72ECF884100109999 /* HostFSHandler.cpp in Sources */,
|
||||
4BBB70A5202011C2002FE009 /* MultiMediaTarget.cpp in Sources */,
|
||||
4B8318BC22D3E588006DB630 /* DisplayMetrics.cpp in Sources */,
|
||||
4BEDA40E25B2844B000C2DBD /* Decoder.cpp in Sources */,
|
||||
@@ -6432,6 +6462,7 @@
|
||||
4B1082C42C1F5E7D00B07C5D /* CSL.cpp in Sources */,
|
||||
4B0ACC3023775819008902D0 /* TIASound.cpp in Sources */,
|
||||
4B7136861F78724F008B8ED9 /* Encoder.cpp in Sources */,
|
||||
4BCF1AD62ECF884100109999 /* HostFSHandler.cpp in Sources */,
|
||||
4B0E04EA1FC9E5DA00F43484 /* CAS.cpp in Sources */,
|
||||
4B7A90ED20410A85008514A2 /* StaticAnalyser.cpp in Sources */,
|
||||
4B58601E1F806AB200AEE2E3 /* MFMSectorDump.cpp in Sources */,
|
||||
@@ -6497,6 +6528,7 @@
|
||||
4BEDA3BF25B25563000C2DBD /* Decoder.cpp in Sources */,
|
||||
4B051C95266EF50200CA44E8 /* AppleIIController.swift in Sources */,
|
||||
4B4DC82B1D2C27A4003C5BF8 /* SerialBus.cpp in Sources */,
|
||||
4BCF1ACF2ECE759000109999 /* FileBundle.cpp in Sources */,
|
||||
4BE8EB6625C750B50040BC40 /* DAT.cpp in Sources */,
|
||||
4BBFFEE61F7B27F1005F3FEB /* TrackSerialiser.cpp in Sources */,
|
||||
4B8855A52E84D51B00E251DD /* SAA5050.cpp in Sources */,
|
||||
@@ -6857,8 +6889,10 @@
|
||||
4B778F4023A5F1910000D260 /* z8530.cpp in Sources */,
|
||||
4B778EFD23A5EB8E0000D260 /* AppleDSK.cpp in Sources */,
|
||||
4B7752B728217EF40073E2C5 /* Chipset.cpp in Sources */,
|
||||
4BCF1AD02ECE759000109999 /* FileBundle.cpp in Sources */,
|
||||
4B06AAF72C64606E0034D014 /* DiskII.cpp in Sources */,
|
||||
4B778EFB23A5EB7E0000D260 /* HFE.cpp in Sources */,
|
||||
4BCF1AD52ECF884100109999 /* HostFSHandler.cpp in Sources */,
|
||||
4BC751B21D157E61006C31D9 /* 6522Tests.swift in Sources */,
|
||||
4B0DA67D282DCDF300C12F17 /* Instruction.cpp in Sources */,
|
||||
4B06AAE12C645F8B0034D014 /* Video.cpp in Sources */,
|
||||
@@ -7224,6 +7258,7 @@
|
||||
DEVELOPMENT_TEAM = DV3346VVUN;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
@@ -7245,6 +7280,7 @@
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = macosx;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -7286,6 +7322,7 @@
|
||||
DEVELOPMENT_TEAM = DV3346VVUN;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
@@ -7299,6 +7336,7 @@
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.13;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
STRING_CATALOG_GENERATE_SYMBOLS = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
|
||||
@@ -7325,7 +7363,11 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_FILE_ACCESS_PICTURE_FOLDER = readwrite;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = YES;
|
||||
ENABLE_RESOURCE_ACCESS_USB = YES;
|
||||
ENABLE_USER_SELECTED_FILES = readwrite;
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
|
||||
@@ -7373,7 +7415,11 @@
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_FILE_ACCESS_PICTURE_FOLDER = readwrite;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
ENABLE_RESOURCE_ACCESS_BLUETOOTH = YES;
|
||||
ENABLE_RESOURCE_ACCESS_USB = YES;
|
||||
ENABLE_USER_SELECTED_FILES = readwrite;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = "NDEBUG=1";
|
||||
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
|
||||
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1630"
|
||||
LastUpgradeVersion = "2620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -54,16 +54,16 @@
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Soft/Master System/R-Type (NTSC).sms""
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/Users/thomasharte/Library/Mobile\ Documents/com\~apple\~CloudDocs/Soft/Archimedes/Lemmings.adf""
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--new=archimedes"
|
||||
argument = ""/Users/thomasharte/Library/Mobile\ Documents/com\~apple\~CloudDocs/Soft/Archimedes/Lemmings.adf""
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--new=zxspectrum"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "/Users/thomasharte/Downloads/Program/Program.prg"
|
||||
isEnabled = "NO">
|
||||
@@ -74,7 +74,7 @@
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--help"
|
||||
isEnabled = "NO">
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1630"
|
||||
LastUpgradeVersion = "2620"
|
||||
version = "1.8">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1630"
|
||||
LastUpgradeVersion = "2620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.pictures.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.usb</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<dict/>
|
||||
</plist>
|
||||
|
||||
@@ -68,6 +68,7 @@ class MachineDocument:
|
||||
var fileObserver: CSFileContentChangeObserver?
|
||||
override func read(from url: URL, ofType typeName: String) throws {
|
||||
if let analyser = CSStaticAnalyser(fileAt: url) {
|
||||
checkPermisions(analyser.mediaSet)
|
||||
self.displayName = analyser.displayName
|
||||
self.configureAs(analyser)
|
||||
self.fileObserver = CSFileContentChangeObserver.init(url: url, handler: {
|
||||
@@ -332,6 +333,7 @@ class MachineDocument:
|
||||
private func insertFile(_ URL: URL) {
|
||||
// Try to insert media.
|
||||
let mediaSet = CSMediaSet(fileAt: URL)
|
||||
checkPermisions(mediaSet)
|
||||
if !mediaSet.empty {
|
||||
mediaSet.apply(to: self.machine)
|
||||
return
|
||||
@@ -347,6 +349,10 @@ class MachineDocument:
|
||||
}
|
||||
}
|
||||
|
||||
private func checkPermisions(_ mediaSet: CSMediaSet) {
|
||||
mediaSet.addPermissionHandler()
|
||||
}
|
||||
|
||||
// MARK: - Input Management.
|
||||
|
||||
/// Upon a resign key, immediately releases all ongoing input mechanisms — any currently pressed keys,
|
||||
|
||||
@@ -807,6 +807,29 @@
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>com</string>
|
||||
<string>bas</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Enterprise Executable</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>
|
||||
@@ -821,11 +844,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>25.11.07</string>
|
||||
<string>25.12.12</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>25.11.07</string>
|
||||
<string>25.12.12</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.entertainment</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -590,7 +590,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
_useFastLoadingHack = useFastLoadingHack;
|
||||
|
||||
auto options = configurable_device->get_options();
|
||||
Reflection::set(*options, "quickload", useFastLoadingHack ? true : false);
|
||||
Reflection::set(*options, Configurable::Options::QuickLoadOptionName, useFastLoadingHack ? true : false);
|
||||
configurable_device->set_options(options);
|
||||
}
|
||||
}
|
||||
@@ -611,7 +611,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
}
|
||||
|
||||
auto options = configurable_device->get_options();
|
||||
Reflection::set(*options, "output", int(display));
|
||||
Reflection::set(*options, Configurable::Options::DisplayOptionName, int(display));
|
||||
configurable_device->set_options(options);
|
||||
}
|
||||
}
|
||||
@@ -635,7 +635,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
|
||||
// Map to a string and check against returned options for the 'output' field.
|
||||
const auto string_option = Reflection::Enum::to_string<Configurable::Display>(option);
|
||||
const auto all_values = options->values_for("output");
|
||||
const auto all_values = options->values_for(Configurable::Options::DisplayOptionName);
|
||||
|
||||
return std::find(all_values.begin(), all_values.end(), string_option) != all_values.end();
|
||||
}
|
||||
@@ -651,6 +651,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
_useAutomaticTapeMotorControl = useAutomaticTapeMotorControl;
|
||||
|
||||
auto options = configurable_device->get_options();
|
||||
// TODO: get name.
|
||||
Reflection::set(*options, "automatic_tape_motor_control", useAutomaticTapeMotorControl ? true : false);
|
||||
configurable_device->set_options(options);
|
||||
}
|
||||
@@ -664,7 +665,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
_useQuickBootingHack = useQuickBootingHack;
|
||||
|
||||
auto options = configurable_device->get_options();
|
||||
Reflection::set(*options, "quickboot", useQuickBootingHack ? true : false);
|
||||
Reflection::set(*options, Configurable::Options::QuickBootOptionName, useQuickBootingHack ? true : false);
|
||||
configurable_device->set_options(options);
|
||||
}
|
||||
}
|
||||
@@ -677,7 +678,7 @@ struct ActivityObserver: public Activity::Observer {
|
||||
_useDynamicCropping = useDynamicCropping;
|
||||
|
||||
auto options = configurable_device->get_options();
|
||||
Reflection::set(*options, "dynamiccrop", useDynamicCropping ? true : false);
|
||||
Reflection::set(*options, Configurable::Options::DynamicCropOptionName, useDynamicCropping ? true : false);
|
||||
configurable_device->set_options(options);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#import "NSData+StdVector.h"
|
||||
#import "NSData+CRC32.h"
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -12,6 +12,16 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@class CSMachine;
|
||||
|
||||
@interface CSMediaSet : NSObject
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url;
|
||||
- (void)applyToMachine:(CSMachine *)machine;
|
||||
- (void)addPermissionHandler;
|
||||
|
||||
@property(nonatomic, readonly) BOOL empty;
|
||||
|
||||
@end
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineAmigaModel) {
|
||||
CSMachineAmigaModelA500,
|
||||
};
|
||||
@@ -150,36 +160,71 @@ typedef int Kilobytes;
|
||||
|
||||
- (nullable instancetype)initWithFileAtURL:(NSURL *)url;
|
||||
|
||||
- (instancetype)initWithAmigaModel:(CSMachineAmigaModel)model chipMemorySize:(Kilobytes)chipMemorySize fastMemorySize:(Kilobytes)fastMemorySize;
|
||||
- (instancetype)initWithAmigaModel:(CSMachineAmigaModel)model
|
||||
chipMemorySize:(Kilobytes)chipMemorySize
|
||||
fastMemorySize:(Kilobytes)fastMemorySize;
|
||||
|
||||
- (instancetype)initWithAmstradCPCModel:(CSMachineCPCModel)model;
|
||||
- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController hasMockingboard:(BOOL)hasMockingboard;
|
||||
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize;
|
||||
|
||||
- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model
|
||||
diskController:(CSMachineAppleIIDiskController)diskController
|
||||
hasMockingboard:(BOOL)hasMockingboard;
|
||||
|
||||
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model
|
||||
memorySize:(Kilobytes)memorySize;
|
||||
|
||||
- (instancetype)initWithArchimedesModel:(CSMachineArchimedesModel)model;
|
||||
|
||||
- (instancetype)initWithAtariSTMemorySize:(Kilobytes)memorySize;
|
||||
- (instancetype)initWithBBCMicroDFS:(BOOL)dfs adfs:(BOOL)adfs sidewaysRAM:(BOOL)sidewaysRAM secondProcessor:(CSMachineBBCMicroSecondProcessor)secondProcessor;
|
||||
- (instancetype)initWithCommodoreTEDModel:(CSMachineCommodoreTEDModel)model hasC1541:(BOOL)hasC1541;
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM;
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model speed:(CSMachineEnterpriseSpeed)speed exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos;
|
||||
|
||||
- (instancetype)initWithBBCMicroDFS:(BOOL)dfs
|
||||
adfs:(BOOL)adfs
|
||||
sidewaysRAM:(BOOL)sidewaysRAM
|
||||
beebSID:(BOOL)beebSID
|
||||
secondProcessor:(CSMachineBBCMicroSecondProcessor)secondProcessor;
|
||||
|
||||
- (instancetype)initWithCommodoreTEDModel:(CSMachineCommodoreTEDModel)model
|
||||
hasC1541:(BOOL)hasC1541;
|
||||
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs
|
||||
adfs:(BOOL)adfs
|
||||
ap6:(BOOL)ap6
|
||||
sidewaysRAM:(BOOL)sidewaysRAM;
|
||||
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model
|
||||
speed:(CSMachineEnterpriseSpeed)speed
|
||||
exosVersion:(CSMachineEnterpriseEXOS)exosVersion
|
||||
basicVersion:(CSMachineEnterpriseBASIC)basicVersion
|
||||
dos:(CSMachineEnterpriseDOS)dos
|
||||
exposedLocalPath:(nullable NSURL *)path;
|
||||
|
||||
- (instancetype)initWithMacintoshModel:(CSMachineMacintoshModel)model;
|
||||
- (instancetype)initWithMSXModel:(CSMachineMSXModel)model region:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive hasMSXMUSIC:(BOOL)hasMSXMUSIC;
|
||||
- (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface;
|
||||
|
||||
- (instancetype)initWithMSXModel:(CSMachineMSXModel)model
|
||||
region:(CSMachineMSXRegion)region
|
||||
hasDiskDrive:(BOOL)hasDiskDrive
|
||||
hasMSXMUSIC:(BOOL)hasMSXMUSIC;
|
||||
|
||||
- (instancetype)initWithOricModel:(CSMachineOricModel)model
|
||||
diskInterface:(CSMachineOricDiskInterface)diskInterface;
|
||||
|
||||
- (instancetype)initWithSpectrumModel:(CSMachineSpectrumModel)model;
|
||||
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540;
|
||||
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM;
|
||||
|
||||
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region
|
||||
memorySize:(Kilobytes)memorySize
|
||||
hasC1540:(BOOL)hasC1540;
|
||||
|
||||
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize
|
||||
useZX81ROM:(BOOL)useZX81ROM;
|
||||
|
||||
- (instancetype)initWithZX81MemorySize:(Kilobytes)memorySize;
|
||||
- (instancetype)initWithPCCompatibleSpeed:(CSPCCompatibleSpeed)speed videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor;
|
||||
|
||||
- (instancetype)initWithPCCompatibleSpeed:(CSPCCompatibleSpeed)speed
|
||||
videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor;
|
||||
|
||||
@property(nonatomic, readonly, nullable) NSString *optionsNibName;
|
||||
@property(nonatomic, readonly) NSString *displayName;
|
||||
|
||||
@end
|
||||
|
||||
@interface CSMediaSet : NSObject
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url;
|
||||
- (void)applyToMachine:(CSMachine *)machine;
|
||||
|
||||
@property(nonatomic, readonly) BOOL empty;
|
||||
@property(nonatomic, readonly, nonnull) CSMediaSet *mediaSet;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -28,8 +28,166 @@
|
||||
#include "Analyser/Static/ZX8081/Target.hpp"
|
||||
#include "Analyser/Static/ZXSpectrum/Target.hpp"
|
||||
|
||||
#include "Storage/FileBundle/FileBundle.hpp"
|
||||
|
||||
#import "Clock_Signal-Swift.h"
|
||||
|
||||
namespace {
|
||||
|
||||
struct PermissionDelegate: public Storage::FileBundle::FileBundle::PermissionDelegate {
|
||||
void validate_open(Storage::FileBundle::FileBundle &bundle, const std::string &path, const Storage::FileMode mode) {
|
||||
NSData *bookmarkData;
|
||||
NSString *stringPath = [NSString stringWithUTF8String:path.c_str()];
|
||||
NSURL *url = [NSURL fileURLWithPath:stringPath isDirectory:NO];
|
||||
NSError *error;
|
||||
|
||||
// Check for and possibly apply an existing bookmark.
|
||||
NSString *bookmarkKey = [[url URLByDeletingLastPathComponent] absoluteString];
|
||||
bookmarkData = [[NSUserDefaults standardUserDefaults] objectForKey:bookmarkKey];
|
||||
if(bookmarkData) {
|
||||
NSURL *accessURL =
|
||||
[NSURL
|
||||
URLByResolvingBookmarkData:bookmarkData
|
||||
options:NSURLBookmarkResolutionWithSecurityScope | NSURLBookmarkResolutionWithoutUI
|
||||
relativeToURL:nil
|
||||
bookmarkDataIsStale:nil
|
||||
error:nil];
|
||||
[accessURL startAccessingSecurityScopedResource];
|
||||
}
|
||||
|
||||
// If the file exists can now be accessed, no further action required.
|
||||
NSFileHandle *file = [&]() {
|
||||
switch(mode) {
|
||||
case Storage::FileMode::ReadWrite: {
|
||||
NSFileHandle *updating = [NSFileHandle fileHandleForUpdatingURL:url error:&error];
|
||||
if(updating) return updating;
|
||||
[[fallthrough]];
|
||||
}
|
||||
default:
|
||||
case Storage::FileMode::Read: return [NSFileHandle fileHandleForReadingFromURL:url error:&error];
|
||||
case Storage::FileMode::Rewrite: return [NSFileHandle fileHandleForWritingToURL:url error:&error];
|
||||
}
|
||||
}();
|
||||
|
||||
// Managed to open the file: that's enough.
|
||||
if(file) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise: if not being opened exclusively for reading, see whether the file can be created.
|
||||
if(
|
||||
error.domain == NSCocoaErrorDomain &&
|
||||
error.code == NSFileNoSuchFileError &&
|
||||
mode != Storage::FileMode::Read
|
||||
) {
|
||||
NSFileManager *manager = [NSFileManager defaultManager];
|
||||
if([manager createFileAtPath:url.path contents:nil attributes:nil]) {
|
||||
[manager removeItemAtPath:url.path error:&error];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Failing that, ask the user for permission and keep the bookmark.
|
||||
__block NSURL *selectedURL;
|
||||
|
||||
// Ask the user for permission.
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
NSOpenPanel *request = [NSOpenPanel openPanel];
|
||||
request.prompt = NSLocalizedString(@"Grant Permission", @"");
|
||||
request.message = NSLocalizedString(@"Please Grant Permission For Full Folder Access", @"");
|
||||
request.canChooseFiles = NO;
|
||||
request.allowsMultipleSelection = NO;
|
||||
request.canChooseDirectories = YES;
|
||||
[request setDirectoryURL:[url URLByDeletingLastPathComponent]];
|
||||
|
||||
// TODO: a nicer accessory view; NSTextField or the relevant equivalent
|
||||
// with an attributed string might work.
|
||||
request.accessoryView = [NSTextField labelWithString:[&] {
|
||||
const auto key_file = bundle.key_file();
|
||||
|
||||
if(key_file) {
|
||||
return [NSString stringWithFormat:
|
||||
@"Clock Signal cannot access your files without explicit permission but "
|
||||
@"%s is trying to use additional files in its folder.\n"
|
||||
@"Please select 'Grant Permission' if you are willing to let it to do so.",
|
||||
key_file->c_str()
|
||||
];
|
||||
} else {
|
||||
assert(bundle.base_path().has_value());
|
||||
return [NSString stringWithFormat:
|
||||
@"Clock Signal cannot access your files without explicit permission but "
|
||||
@"your emulated machine is trying to use additional files from %s.\n"
|
||||
@"Please select 'Grant Permission' if you are willing to let it to do so.",
|
||||
bundle.base_path()->c_str()
|
||||
];
|
||||
}
|
||||
}()];
|
||||
|
||||
request.accessoryViewDisclosed = YES;
|
||||
[request runModal];
|
||||
|
||||
selectedURL = request.URL;
|
||||
});
|
||||
|
||||
// Store bookmark data for potential later retrieval.
|
||||
// That amounts to this application remembering the user's permission.
|
||||
error = nil;
|
||||
[[NSUserDefaults standardUserDefaults]
|
||||
setObject:[selectedURL
|
||||
bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope
|
||||
includingResourceValuesForKeys:nil
|
||||
relativeToURL:nil
|
||||
error:&error]
|
||||
forKey:bookmarkKey];
|
||||
}
|
||||
|
||||
void validate_erase(Storage::FileBundle::FileBundle &, const std::string &) {
|
||||
// Currently a no-op, as it so happens that the only machine that currently
|
||||
// uses a file bundle is the Enterprise, and its semantics involve opening
|
||||
// a file before it can be erased.
|
||||
}
|
||||
};
|
||||
|
||||
PermissionDelegate permission_delegate;
|
||||
|
||||
}
|
||||
|
||||
@implementation CSMediaSet {
|
||||
Analyser::Static::Media _media;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMedia:(Analyser::Static::Media)media {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_media = media;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_media = Analyser::Static::GetMedia([url fileSystemRepresentation]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)empty {
|
||||
return _media.empty();
|
||||
}
|
||||
|
||||
- (void)applyToMachine:(CSMachine *)machine {
|
||||
[machine applyMedia:_media];
|
||||
}
|
||||
|
||||
- (void)addPermissionHandler {
|
||||
for(const auto &bundle: _media.file_bundles) {
|
||||
bundle->set_permission_delegate(&permission_delegate);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSStaticAnalyser {
|
||||
Analyser::Static::TargetList _targets;
|
||||
}
|
||||
@@ -51,7 +209,10 @@
|
||||
|
||||
// MARK: - Machine-based Initialisers
|
||||
|
||||
- (instancetype)initWithAmigaModel:(CSMachineAmigaModel)model chipMemorySize:(Kilobytes)chipMemorySize fastMemorySize:(Kilobytes)fastMemorySize {
|
||||
- (instancetype)initWithAmigaModel:(CSMachineAmigaModel)model
|
||||
chipMemorySize:(Kilobytes)chipMemorySize
|
||||
fastMemorySize:(Kilobytes)fastMemorySize
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Amiga::Target;
|
||||
@@ -93,7 +254,10 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model diskController:(CSMachineAppleIIDiskController)diskController hasMockingboard:(BOOL)hasMockingboard {
|
||||
- (instancetype)initWithAppleIIModel:(CSMachineAppleIIModel)model
|
||||
diskController:(CSMachineAppleIIDiskController)diskController
|
||||
hasMockingboard:(BOOL)hasMockingboard
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::AppleII::Target;
|
||||
@@ -116,7 +280,9 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model memorySize:(Kilobytes)memorySize {
|
||||
- (instancetype)initWithAppleIIgsModel:(CSMachineAppleIIgsModel)model
|
||||
memorySize:(Kilobytes)memorySize
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::AppleIIgs::Target;
|
||||
@@ -160,13 +326,19 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBBCMicroDFS:(BOOL)dfs adfs:(BOOL)adfs sidewaysRAM:(BOOL)sidewaysRAM secondProcessor:(CSMachineBBCMicroSecondProcessor)secondProcessor {
|
||||
- (instancetype)initWithBBCMicroDFS:(BOOL)dfs
|
||||
adfs:(BOOL)adfs
|
||||
sidewaysRAM:(BOOL)sidewaysRAM
|
||||
beebSID:(BOOL)beebSID
|
||||
secondProcessor:(CSMachineBBCMicroSecondProcessor)secondProcessor
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Acorn::BBCMicroTarget;
|
||||
auto target = std::make_unique<Target>();
|
||||
target->has_1770dfs = dfs;
|
||||
target->has_adfs = adfs;
|
||||
target->has_beebsid = beebSID;
|
||||
target->has_sideways_ram = sidewaysRAM;
|
||||
|
||||
switch(secondProcessor) {
|
||||
@@ -179,7 +351,9 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCommodoreTEDModel:(CSMachineCommodoreTEDModel)model hasC1541:(BOOL)hasC1541 {
|
||||
- (instancetype)initWithCommodoreTEDModel:(CSMachineCommodoreTEDModel)model
|
||||
hasC1541:(BOOL)hasC1541
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Commodore::Plus4Target;
|
||||
@@ -190,7 +364,11 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs adfs:(BOOL)adfs ap6:(BOOL)ap6 sidewaysRAM:(BOOL)sidewaysRAM {
|
||||
- (instancetype)initWithElectronDFS:(BOOL)dfs
|
||||
adfs:(BOOL)adfs
|
||||
ap6:(BOOL)ap6
|
||||
sidewaysRAM:(BOOL)sidewaysRAM
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
auto target = std::make_unique<Analyser::Static::Acorn::ElectronTarget>();
|
||||
@@ -203,7 +381,13 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model speed:(CSMachineEnterpriseSpeed)speed exosVersion:(CSMachineEnterpriseEXOS)exosVersion basicVersion:(CSMachineEnterpriseBASIC)basicVersion dos:(CSMachineEnterpriseDOS)dos {
|
||||
- (instancetype)initWithEnterpriseModel:(CSMachineEnterpriseModel)model
|
||||
speed:(CSMachineEnterpriseSpeed)speed
|
||||
exosVersion:(CSMachineEnterpriseEXOS)exosVersion
|
||||
basicVersion:(CSMachineEnterpriseBASIC)basicVersion
|
||||
dos:(CSMachineEnterpriseDOS)dos
|
||||
exposedLocalPath:(nullable NSURL *)path
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Enterprise::Target;
|
||||
@@ -243,6 +427,12 @@
|
||||
case CSMachineEnterpriseDOSNone: target->dos = Target::DOS::None; break;
|
||||
}
|
||||
|
||||
if(path) {
|
||||
const auto bundle = std::make_shared<Storage::FileBundle::LocalFSFileBundle>(path.path.UTF8String);
|
||||
bundle->set_permission_delegate(&permission_delegate);
|
||||
target->media.file_bundles.push_back(std::move(bundle));
|
||||
}
|
||||
|
||||
_targets.push_back(std::move(target));
|
||||
}
|
||||
return self;
|
||||
@@ -268,7 +458,11 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithMSXModel:(CSMachineMSXModel)model region:(CSMachineMSXRegion)region hasDiskDrive:(BOOL)hasDiskDrive hasMSXMUSIC:(BOOL)hasMSXMUSIC {
|
||||
- (instancetype)initWithMSXModel:(CSMachineMSXModel)model
|
||||
region:(CSMachineMSXRegion)region
|
||||
hasDiskDrive:(BOOL)hasDiskDrive
|
||||
hasMSXMUSIC:(BOOL)hasMSXMUSIC
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::MSX::Target;
|
||||
@@ -289,7 +483,9 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithOricModel:(CSMachineOricModel)model diskInterface:(CSMachineOricDiskInterface)diskInterface {
|
||||
- (instancetype)initWithOricModel:(CSMachineOricModel)model
|
||||
diskInterface:(CSMachineOricDiskInterface)diskInterface
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Oric::Target;
|
||||
@@ -311,7 +507,10 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPCCompatibleSpeed:(CSPCCompatibleSpeed)speed videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor {
|
||||
|
||||
- (instancetype)initWithPCCompatibleSpeed:(CSPCCompatibleSpeed)speed
|
||||
videoAdaptor:(CSPCCompatibleVideoAdaptor)adaptor
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::PCCompatible::Target;
|
||||
@@ -347,7 +546,10 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region memorySize:(Kilobytes)memorySize hasC1540:(BOOL)hasC1540 {
|
||||
- (instancetype)initWithVic20Region:(CSMachineVic20Region)region
|
||||
memorySize:(Kilobytes)memorySize
|
||||
hasC1540:(BOOL)hasC1540
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::Commodore::Vic20Target;
|
||||
@@ -381,7 +583,9 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
}
|
||||
}
|
||||
|
||||
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize useZX81ROM:(BOOL)useZX81ROM {
|
||||
- (instancetype)initWithZX80MemorySize:(Kilobytes)memorySize
|
||||
useZX81ROM:(BOOL)useZX81ROM
|
||||
{
|
||||
self = [super init];
|
||||
if(self) {
|
||||
using Target = Analyser::Static::ZX8081::Target;
|
||||
@@ -438,26 +642,12 @@ static Analyser::Static::ZX8081::Target::MemoryModel ZX8081MemoryModelFromSize(K
|
||||
return _targets;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation CSMediaSet {
|
||||
Analyser::Static::Media _media;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFileAtURL:(NSURL *)url {
|
||||
self = [super init];
|
||||
if(self) {
|
||||
_media = Analyser::Static::GetMedia([url fileSystemRepresentation]);
|
||||
- (nonnull CSMediaSet *)mediaSet {
|
||||
Analyser::Static::Media net;
|
||||
for(const auto &target: _targets) {
|
||||
net += target->media;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)applyToMachine:(CSMachine *)machine {
|
||||
[machine applyMedia:_media];
|
||||
}
|
||||
|
||||
- (BOOL)empty {
|
||||
return _media.empty();
|
||||
return [[CSMediaSet alloc] initWithMedia:net];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -440,15 +440,15 @@ Gw
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lzo-8g-o4S">
|
||||
<rect key="frame" x="18" y="211" 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">
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="UgW-vD-OSo">
|
||||
<rect key="frame" x="18" y="211" width="110" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="With BeebSID" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="ucb-pL-9rx">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="y7H-nu-CPH">
|
||||
<rect key="frame" x="20" y="174" width="118" height="16"/>
|
||||
<rect key="frame" x="20" y="152" width="118" height="16"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" title="Second Processor:" id="11b-Ml-o3Y">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -456,7 +456,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="yhA-Dw-hqn">
|
||||
<rect key="frame" x="141" y="168" width="81" height="25"/>
|
||||
<rect key="frame" x="141" y="146" width="81" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="None" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Rpc-1u-8X2" id="vk3-Qo-uxV">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
@@ -469,18 +469,28 @@ Gw
|
||||
</menu>
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lzo-8g-o4S">
|
||||
<rect key="frame" x="18" y="189" width="284" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Fill unused ROM banks with sideways RAM" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="JEz-eK-uWp">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="945-wU-JOH" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="1iM-70-oZq"/>
|
||||
<constraint firstItem="yhA-Dw-hqn" firstAttribute="top" secondItem="lzo-8g-o4S" secondAttribute="bottom" constant="20" symbolic="YES" id="28I-GC-l4K"/>
|
||||
<constraint firstItem="y7H-nu-CPH" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="22" id="3yR-gS-ciE"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="yhA-Dw-hqn" secondAttribute="trailing" constant="20" symbolic="YES" id="8V6-jM-LW7"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="UgW-vD-OSo" secondAttribute="trailing" constant="20" symbolic="YES" id="8v9-Ly-7XU"/>
|
||||
<constraint firstItem="y7H-nu-CPH" firstAttribute="centerY" secondItem="yhA-Dw-hqn" secondAttribute="centerY" id="IUr-qS-L4l"/>
|
||||
<constraint firstItem="lzo-8g-o4S" firstAttribute="top" secondItem="UgW-vD-OSo" secondAttribute="bottom" constant="6" symbolic="YES" id="LpK-aW-KOr"/>
|
||||
<constraint firstItem="UgW-vD-OSo" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="N2b-ZW-ffU"/>
|
||||
<constraint firstItem="JqM-IK-FMP" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="NfY-dE-aJw"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="yhA-Dw-hqn" secondAttribute="bottom" constant="20" symbolic="YES" id="Rg8-zf-0ep"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="lzo-8g-o4S" secondAttribute="trailing" constant="20" symbolic="YES" id="X3p-qJ-ENH"/>
|
||||
<constraint firstItem="UgW-vD-OSo" firstAttribute="top" secondItem="945-wU-JOH" secondAttribute="bottom" constant="6" symbolic="YES" id="YqY-Yk-Puc"/>
|
||||
<constraint firstItem="lzo-8g-o4S" firstAttribute="leading" secondItem="SRc-2D-95G" secondAttribute="leading" constant="20" symbolic="YES" id="b5a-SX-2ty"/>
|
||||
<constraint firstItem="lzo-8g-o4S" firstAttribute="top" secondItem="945-wU-JOH" secondAttribute="bottom" constant="6" symbolic="YES" id="d1Y-Ia-IjQ"/>
|
||||
<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="20" symbolic="YES" id="ggl-QH-mV4"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="JqM-IK-FMP" secondAttribute="trailing" constant="20" symbolic="YES" id="mvO-UZ-BtT"/>
|
||||
@@ -562,11 +572,11 @@ Gw
|
||||
</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="274"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="292"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="PhH-bu-pb5">
|
||||
<rect key="frame" x="80" y="230" width="129" height="25"/>
|
||||
<rect key="frame" x="80" y="248" 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="message"/>
|
||||
@@ -580,7 +590,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Lfl-5c-b8j">
|
||||
<rect key="frame" x="67" y="200" width="77" height="25"/>
|
||||
<rect key="frame" x="67" y="218" width="77" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="4 Mhz" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="4" imageScaling="axesIndependently" inset="2" selectedItem="5N6-tD-uN7" id="BU2-NJ-Kii">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
@@ -593,7 +603,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="nen-Za-7zH">
|
||||
<rect key="frame" x="64" y="170" width="107" height="25"/>
|
||||
<rect key="frame" x="64" y="188" 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="message"/>
|
||||
@@ -607,7 +617,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hIr-GH-7xi">
|
||||
<rect key="frame" x="67" y="140" width="105" height="25"/>
|
||||
<rect key="frame" x="67" y="158" 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="message"/>
|
||||
@@ -622,7 +632,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="syE-e7-TjU">
|
||||
<rect key="frame" x="57" y="110" width="83" height="25"/>
|
||||
<rect key="frame" x="57" y="128" width="83" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="EXDOS" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" 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="message"/>
|
||||
@@ -635,7 +645,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ykc-W1-YaS">
|
||||
<rect key="frame" x="18" y="176" width="43" height="16"/>
|
||||
<rect key="frame" x="18" y="194" 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"/>
|
||||
@@ -643,7 +653,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="frx-nk-c3P">
|
||||
<rect key="frame" x="18" y="236" width="59" height="16"/>
|
||||
<rect key="frame" x="18" y="254" 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"/>
|
||||
@@ -651,7 +661,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dzd-tH-BjX">
|
||||
<rect key="frame" x="18" y="146" width="46" height="16"/>
|
||||
<rect key="frame" x="18" y="164" 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"/>
|
||||
@@ -659,7 +669,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pxr-Bq-yh0">
|
||||
<rect key="frame" x="18" y="116" width="36" height="16"/>
|
||||
<rect key="frame" x="18" y="134" 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"/>
|
||||
@@ -667,17 +677,38 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rHr-bh-QMV">
|
||||
<rect key="frame" x="17" y="206" width="47" height="16"/>
|
||||
<rect key="frame" x="17" y="224" width="47" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Speed:" id="sAw-C9-Sf7">
|
||||
<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>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="gv6-nD-GXj">
|
||||
<rect key="frame" x="18" y="107" width="207" height="18"/>
|
||||
<buttonCell key="cell" type="check" title="Expose local path to machine:" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="l0h-Ih-sZi">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
</button>
|
||||
<pathControl verticalHuggingPriority="750" allowsExpansionToolTips="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Q4Y-IX-PrB">
|
||||
<rect key="frame" x="230" y="103" width="96" height="25"/>
|
||||
<pathCell key="cell" selectable="YES" editable="YES" alignment="left" pathStyle="popUp" id="gwZ-3L-JoC">
|
||||
<font key="font" metaFont="system"/>
|
||||
<url key="url" string="~">
|
||||
<url key="baseURL" string="file:///"/>
|
||||
</url>
|
||||
</pathCell>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="192-Eb-Rpg" id="o8l-Od-rKa"/>
|
||||
</connections>
|
||||
</pathControl>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Q4Y-IX-PrB" secondAttribute="trailing" constant="20" symbolic="YES" id="3KV-Zx-nbH"/>
|
||||
<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="Q4Y-IX-PrB" firstAttribute="centerY" secondItem="gv6-nD-GXj" secondAttribute="centerY" id="4CW-jc-zZZ"/>
|
||||
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="Lfl-5c-b8j" secondAttribute="trailing" constant="20" symbolic="YES" id="5tb-fZ-HpY"/>
|
||||
<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"/>
|
||||
@@ -685,11 +716,14 @@ Gw
|
||||
<constraint firstItem="nen-Za-7zH" firstAttribute="top" secondItem="Lfl-5c-b8j" secondAttribute="bottom" constant="10" symbolic="YES" id="Df8-qv-3dY"/>
|
||||
<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="gv6-nD-GXj" secondAttribute="trailing" constant="20" symbolic="YES" id="H04-Wk-kvx"/>
|
||||
<constraint firstItem="Q4Y-IX-PrB" firstAttribute="leading" secondItem="gv6-nD-GXj" secondAttribute="trailing" constant="8" symbolic="YES" id="HVH-bw-Pnk"/>
|
||||
<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="Lfl-5c-b8j" firstAttribute="top" secondItem="PhH-bu-pb5" secondAttribute="bottom" constant="10" symbolic="YES" id="Nl3-hL-jwg"/>
|
||||
<constraint firstItem="pxr-Bq-yh0" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="Qzp-IY-Pa0"/>
|
||||
<constraint firstItem="gv6-nD-GXj" firstAttribute="top" secondItem="syE-e7-TjU" secondAttribute="bottom" constant="8" symbolic="YES" id="S68-zD-nye"/>
|
||||
<constraint firstAttribute="bottom" relation="greaterThanOrEqual" secondItem="gv6-nD-GXj" secondAttribute="bottom" constant="20" symbolic="YES" id="SUq-nZ-P0W"/>
|
||||
<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"/>
|
||||
@@ -703,6 +737,7 @@ Gw
|
||||
<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"/>
|
||||
<constraint firstItem="rHr-bh-QMV" firstAttribute="centerY" secondItem="Lfl-5c-b8j" secondAttribute="centerY" id="mnp-JF-dVQ"/>
|
||||
<constraint firstItem="gv6-nD-GXj" firstAttribute="leading" secondItem="1cs-PX-RAH" secondAttribute="leading" constant="20" symbolic="YES" id="uhb-Rp-Gs2"/>
|
||||
<constraint firstItem="Lfl-5c-b8j" firstAttribute="leading" secondItem="rHr-bh-QMV" secondAttribute="trailing" constant="8" symbolic="YES" id="yfb-SL-v1H"/>
|
||||
</constraints>
|
||||
</view>
|
||||
@@ -899,11 +934,11 @@ Gw
|
||||
</tabViewItem>
|
||||
<tabViewItem label="PC Compatible" identifier="pc" id="8cB-MZ-g3U">
|
||||
<view key="view" id="gJD-vd-WWu">
|
||||
<rect key="frame" x="10" y="7" width="400" height="274"/>
|
||||
<rect key="frame" x="10" y="7" width="400" height="292"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="stw-i3-ikG">
|
||||
<rect key="frame" x="116" y="230" width="68" height="25"/>
|
||||
<rect key="frame" x="116" y="248" width="68" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="MDA" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="axesIndependently" inset="2" selectedItem="oIy-If-5bQ" id="xz8-mu-ynU">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
@@ -916,7 +951,7 @@ Gw
|
||||
</popUpButtonCell>
|
||||
</popUpButton>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uhf-1k-ibT">
|
||||
<rect key="frame" x="18" y="236" width="95" height="16"/>
|
||||
<rect key="frame" x="18" y="254" width="95" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Video Adaptor:" id="ROV-EU-T3W">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -924,7 +959,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField focusRingType="none" horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8vF-eu-ClP">
|
||||
<rect key="frame" x="18" y="206" width="47" height="16"/>
|
||||
<rect key="frame" x="18" y="224" width="47" height="16"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Speed:" id="qXc-wf-5jm">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -932,7 +967,7 @@ Gw
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ci6-TG-5tW">
|
||||
<rect key="frame" x="68" y="200" width="146" height="25"/>
|
||||
<rect key="frame" x="68" y="218" width="146" height="25"/>
|
||||
<popUpButtonCell key="cell" type="push" title="Similar to Original" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="8086" imageScaling="axesIndependently" inset="2" selectedItem="R4W-s4-KFx" id="9i0-UG-B2c">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="message"/>
|
||||
@@ -1200,6 +1235,7 @@ Gw
|
||||
<outlet property="appleIIgsModelButton" destination="gcS-uy-mzl" id="Jcc-jC-cV1"/>
|
||||
<outlet property="atariSTMemorySizeButton" destination="QD0-qk-qCa" id="aKa-L6-2Te"/>
|
||||
<outlet property="bbcADFSButton" destination="945-wU-JOH" id="fec-Lp-8F2"/>
|
||||
<outlet property="bbcBeebSIDButton" destination="UgW-vD-OSo" id="k9j-Vc-dyN"/>
|
||||
<outlet property="bbcDFSButton" destination="JqM-IK-FMP" id="ocY-a5-u7h"/>
|
||||
<outlet property="bbcSecondProcessorButton" destination="yhA-Dw-hqn" id="lzf-f5-4iQ"/>
|
||||
<outlet property="bbcSidewaysRAMButton" destination="lzo-8g-o4S" id="3P1-20-Ne7"/>
|
||||
@@ -1211,7 +1247,9 @@ Gw
|
||||
<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="enterpriseExposePathButton" destination="gv6-nD-GXj" id="04H-5s-e06"/>
|
||||
<outlet property="enterpriseModelButton" destination="PhH-bu-pb5" id="8wD-sW-aBw"/>
|
||||
<outlet property="enterprisePathControl" destination="Q4Y-IX-PrB" id="g3m-Z7-C9D"/>
|
||||
<outlet property="enterpriseSpeedButton" destination="Lfl-5c-b8j" id="ClN-yN-Nbx"/>
|
||||
<outlet property="machineNameTable" destination="3go-Eb-GOy" id="Ppf-S0-IP1"/>
|
||||
<outlet property="machineSelector" destination="VUb-QG-x7c" id="crR-hB-jGd"/>
|
||||
|
||||
@@ -15,7 +15,7 @@ import Cocoa
|
||||
// in the interface builder easier.
|
||||
//
|
||||
// I accept that I'll have to rethink this again if the machine list keeps growing.
|
||||
class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate, NSPathControlDelegate {
|
||||
@IBOutlet var machineSelector: NSTabView!
|
||||
@IBOutlet var machineNameTable: NSTableView!
|
||||
|
||||
@@ -39,6 +39,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
@IBOutlet var bbcDFSButton: NSButton!
|
||||
@IBOutlet var bbcADFSButton: NSButton!
|
||||
@IBOutlet var bbcSidewaysRAMButton: NSButton!
|
||||
@IBOutlet var bbcBeebSIDButton: NSButton!
|
||||
@IBOutlet var bbcSecondProcessorButton: NSPopUpButton!
|
||||
|
||||
// MARK: - CPC properties
|
||||
@@ -57,6 +58,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
@IBOutlet var enterpriseBASICButton: NSPopUpButton!
|
||||
@IBOutlet var enterpriseDOSButton: NSPopUpButton!
|
||||
|
||||
@IBOutlet var enterpriseExposePathButton: NSButton!
|
||||
@IBOutlet var enterprisePathControl: NSPathControl!
|
||||
|
||||
// MARK: - Macintosh properties
|
||||
@IBOutlet var macintoshModelTypeButton: NSPopUpButton!
|
||||
|
||||
@@ -137,6 +141,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
bbcDFSButton.state = standardUserDefaults.bool(forKey: "new.bbcDFS") ? .on : .off
|
||||
bbcADFSButton.state = standardUserDefaults.bool(forKey: "new.bbcADFS") ? .on : .off
|
||||
bbcSidewaysRAMButton.state = standardUserDefaults.bool(forKey: "new.bbcSidewaysRAM") ? .on : .off
|
||||
bbcBeebSIDButton.state = standardUserDefaults.bool(forKey: "new.bbcBeebSID") ? .on : .off
|
||||
bbcSecondProcessorButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.bbcSecondProcessor"))
|
||||
|
||||
// CPC settings
|
||||
@@ -155,6 +160,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
enterpriseBASICButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseBASICVersion"))
|
||||
enterpriseDOSButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.enterpriseDOS"))
|
||||
|
||||
enterpriseExposePathButton.state = standardUserDefaults.bool(forKey: "new.enterpriseExposeLocalPath") ? .on : .off
|
||||
establishPathControl(enterprisePathControl, userDefaultsKey: "new.enterpriseExposedLocalPath")
|
||||
|
||||
// Macintosh settings
|
||||
macintoshModelTypeButton.selectItem(withTag: standardUserDefaults.integer(forKey: "new.macintoshModel"))
|
||||
|
||||
@@ -217,6 +225,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
standardUserDefaults.set(bbcDFSButton.state == .on, forKey: "new.bbcDFS")
|
||||
standardUserDefaults.set(bbcADFSButton.state == .on, forKey: "new.bbcADFS")
|
||||
standardUserDefaults.set(bbcSidewaysRAMButton.state == .on, forKey: "new.bbcSidewaysRAM")
|
||||
standardUserDefaults.set(bbcBeebSIDButton.state == .on, forKey: "new.bbcBeebSID")
|
||||
standardUserDefaults.set(bbcSecondProcessorButton.selectedTag(), forKey: "new.bbcSecondProcessor")
|
||||
|
||||
// CPC settings
|
||||
@@ -235,6 +244,9 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
standardUserDefaults.set(enterpriseBASICButton.selectedTag(), forKey: "new.enterpriseBASICVersion")
|
||||
standardUserDefaults.set(enterpriseDOSButton.selectedTag(), forKey: "new.enterpriseDOS")
|
||||
|
||||
standardUserDefaults.set(enterpriseExposePathButton.state == .on, forKey: "new.enterpriseExposeLocalPath")
|
||||
storePathControl(enterprisePathControl, userDefaultsKey: "new.enterpriseExposedLocalPath")
|
||||
|
||||
// Macintosh settings
|
||||
standardUserDefaults.set(macintoshModelTypeButton.selectedTag(), forKey: "new.macintoshModel")
|
||||
|
||||
@@ -360,6 +372,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
bbcMicroDFS: bbcDFSButton.state == .on,
|
||||
adfs: bbcADFSButton.state == .on,
|
||||
sidewaysRAM: bbcSidewaysRAMButton.state == .on,
|
||||
beebSID: bbcBeebSIDButton.state == .on,
|
||||
secondProcessor: secondProcessor)
|
||||
|
||||
case "c16plus4":
|
||||
@@ -426,7 +439,8 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
speed: speed,
|
||||
exosVersion: exos,
|
||||
basicVersion: basic,
|
||||
dos: dos
|
||||
dos: dos,
|
||||
exposedLocalPath: enterpriseExposePathButton.state == .on ? enterprisePathControl.url : nil
|
||||
)
|
||||
|
||||
case "mac":
|
||||
@@ -528,4 +542,38 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate {
|
||||
default: return CSStaticAnalyser()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - NSPathControlDelegate (and paths in general)
|
||||
|
||||
func pathControl(_ pathControl: NSPathControl, willDisplay openPanel: NSOpenPanel) {
|
||||
openPanel.canChooseFiles = false
|
||||
openPanel.canChooseDirectories = true
|
||||
}
|
||||
|
||||
func pathControl(_ pathControl: NSPathControl, validateDrop info: any NSDraggingInfo) -> NSDragOperation {
|
||||
// Accept only directories.
|
||||
if let url = NSURL(from: info.draggingPasteboard) {
|
||||
if url.hasDirectoryPath {
|
||||
return NSDragOperation.link
|
||||
}
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
func establishPathControl(_ pathControl: NSPathControl, userDefaultsKey: String) {
|
||||
pathControl.url = FileManager.default.homeDirectoryForCurrentUser
|
||||
if let bookmarkData = UserDefaults.standard.data(forKey: userDefaultsKey) {
|
||||
var isStale: Bool = false
|
||||
if let url = try? URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) {
|
||||
enterprisePathControl.url = url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func storePathControl(_ pathControl: NSPathControl, userDefaultsKey: String) {
|
||||
let url = pathControl.url
|
||||
if let bookmarkData = try? url?.bookmarkData(options: [.withSecurityScope]) {
|
||||
UserDefaults.standard.set(bookmarkData, forKey: userDefaultsKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "MachineForTarget.hpp"
|
||||
|
||||
struct ScanTarget: public Outputs::Display::ScanTarget {
|
||||
void set_modals(Modals modals) override {
|
||||
void set_modals(const Modals modals) override {
|
||||
modals_ = modals;
|
||||
}
|
||||
Scan *begin_scan() override {
|
||||
@@ -62,7 +62,7 @@ struct ScanTarget: public Outputs::Display::ScanTarget {
|
||||
}
|
||||
x_ = x2;
|
||||
}
|
||||
void announce(Event event, bool, const Scan::EndPoint &, uint8_t) override {
|
||||
void announce(const Event event, bool, const Scan::EndPoint &, uint8_t) override {
|
||||
switch(event) {
|
||||
case Event::EndHorizontalRetrace: {
|
||||
if(line_ == ImageHeight - 1) break;
|
||||
@@ -128,11 +128,11 @@ struct SSMDelegate: public AmstradCPC::Machine::SSMDelegate {
|
||||
NSLog(@"Outputting to %@", temp_dir_);
|
||||
}
|
||||
|
||||
void set_crtc(int number) {
|
||||
void set_crtc(const int number) {
|
||||
crtc_ = number;
|
||||
}
|
||||
|
||||
void perform(uint16_t code) {
|
||||
void perform(const uint16_t code) {
|
||||
if(!code) {
|
||||
// A code of 0000 is supposed to end a wait0000 command; at present
|
||||
// there seem to be no wait0000 commands to unblock.
|
||||
@@ -141,7 +141,8 @@ struct SSMDelegate: public AmstradCPC::Machine::SSMDelegate {
|
||||
|
||||
NSData *const data =
|
||||
[scan_target_.image_representation() representationUsingType:NSPNGFileType properties:@{}];
|
||||
NSString *const name = [temp_dir_ stringByAppendingPathComponent:[NSString stringWithFormat:@"CLK_%d_%04x.png", crtc_, code]];
|
||||
NSString *const name =
|
||||
[temp_dir_ stringByAppendingPathComponent:[NSString stringWithFormat:@"CLK_%d_%04x.png", crtc_, code]];
|
||||
[data
|
||||
writeToFile:name
|
||||
atomically:NO];
|
||||
@@ -209,12 +210,12 @@ private:
|
||||
const auto argument = static_cast<int>(std::get<uint64_t>(step.argument));
|
||||
switch(argument) {
|
||||
default:
|
||||
NSLog(@"Unrecognised CRTC type %d", argument);
|
||||
NSLog(@"Unsupported CRTC type %d", argument);
|
||||
break;
|
||||
case 0: target.crtc_type = Target::CRTCType::Type0; break;
|
||||
case 1: target.crtc_type = Target::CRTCType::Type1; break;
|
||||
case 2: target.crtc_type = Target::CRTCType::Type2; break;
|
||||
case 3: target.crtc_type = Target::CRTCType::Type3; break;
|
||||
// case 0: target.crtc_type = Target::CRTCType::Type0; break;
|
||||
// case 1: target.crtc_type = Target::CRTCType::Type1; break;
|
||||
// case 2: target.crtc_type = Target::CRTCType::Type2; break;
|
||||
// case 3: target.crtc_type = Target::CRTCType::Type3; break;
|
||||
}
|
||||
ssm_delegate.set_crtc(argument);
|
||||
} break;
|
||||
@@ -265,7 +266,10 @@ private:
|
||||
last_down = event.down;
|
||||
|
||||
// If this was the release of a carriage return, wait some more after release.
|
||||
if(key_delay.carriage_return_delay && (event.key == AmstradCPC::Key::KeyEnter || event.key == AmstradCPC::Key::KeyReturn)) {
|
||||
if(
|
||||
key_delay.carriage_return_delay &&
|
||||
(event.key == AmstradCPC::Key::KeyEnter || event.key == AmstradCPC::Key::KeyReturn)
|
||||
) {
|
||||
delay(*key_delay.carriage_return_delay);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
QT += core gui multimedia widgets
|
||||
greaterThan(5, QT_MAJOR_VERSION) QT += openglwidgets
|
||||
QT += core gui multimedia widgets openglwidgets
|
||||
|
||||
# Be specific about C++17 but also try the vaguer C++1z for older
|
||||
# Be specific about C++20 but also try the vaguer C++2a for older
|
||||
# versions of Qt.
|
||||
CONFIG += c++20
|
||||
CONFIG += c++2a
|
||||
@@ -13,12 +12,17 @@ CONFIG += object_parallel_to_source
|
||||
INCLUDEPATH += $$[QT_INSTALL_PREFIX]/src/3rdparty/zlib
|
||||
LIBS += -lz
|
||||
|
||||
# If targetting X11, link against that.
|
||||
# If targetting X11, link against X11 directly; this project avoid Qt's faulty
|
||||
# approach to keyboard handling where it can.
|
||||
linux {
|
||||
QT += x11extras
|
||||
LIBS += -lX11
|
||||
}
|
||||
|
||||
debug {
|
||||
# CONFIG += sanitizer
|
||||
# CONFIG += sanitize_address
|
||||
}
|
||||
|
||||
# Add flags (i) to identify that this is a Qt build; and
|
||||
# (ii) to disable asserts in release builds.
|
||||
DEFINES += TARGET_QT
|
||||
@@ -146,6 +150,7 @@ SOURCES += \
|
||||
$$SRC/Storage/Disk/Encodings/MFM/*.cpp \
|
||||
$$SRC/Storage/Disk/Parsers/*.cpp \
|
||||
$$SRC/Storage/Disk/Track/*.cpp \
|
||||
$$SRC/Storage/FileBundle/*.cpp \
|
||||
$$SRC/Storage/MassStorage/*.cpp \
|
||||
$$SRC/Storage/MassStorage/Encodings/*.cpp \
|
||||
$$SRC/Storage/MassStorage/Formats/*.cpp \
|
||||
@@ -303,6 +308,7 @@ HEADERS += \
|
||||
$$SRC/Storage/Disk/Encodings/MFM/*.hpp \
|
||||
$$SRC/Storage/Disk/Parsers/*.hpp \
|
||||
$$SRC/Storage/Disk/Track/*.hpp \
|
||||
$$SRC/Storage/FileBundle/*.hpp \
|
||||
$$SRC/Storage/MassStorage/*.hpp \
|
||||
$$SRC/Storage/MassStorage/Encodings/*.hpp \
|
||||
$$SRC/Storage/MassStorage/Formats/*.hpp \
|
||||
|
||||
+12
-10
@@ -34,8 +34,6 @@
|
||||
#endif
|
||||
|
||||
#ifdef HAS_X11
|
||||
#include <QX11Info>
|
||||
|
||||
#include <X11/Xutil.h>
|
||||
#include <X11/keysym.h>
|
||||
#endif
|
||||
@@ -124,23 +122,27 @@ KeyboardMapper::KeyboardMapper() {
|
||||
// (2) from there, use any of the X11 KeySyms I'd expect to be achievable from each physical key to
|
||||
// look up the X11 KeyCode;
|
||||
// (3) henceforth, map from X11 KeyCode to the Inputs::Keyboard::Key.
|
||||
const DesiredMapping *mapping = mappings;
|
||||
while(mapping->source != 0) {
|
||||
const auto code = XKeysymToKeycode(QX11Info::display(), mapping->source);
|
||||
keyByKeySym[code] = mapping->destination;
|
||||
++mapping;
|
||||
|
||||
if (auto *const x11Application = qGuiApp->nativeInterface<QNativeInterface::QX11Application>()) {
|
||||
is_x11_ = true;
|
||||
const auto display = x11Application->display();
|
||||
|
||||
const DesiredMapping *mapping = mappings;
|
||||
while(mapping->source != 0) {
|
||||
const auto code = XKeysymToKeycode(display, mapping->source);
|
||||
keyByKeySym[code] = mapping->destination;
|
||||
++mapping;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<Inputs::Keyboard::Key> KeyboardMapper::keyForEvent(QKeyEvent *event) {
|
||||
#ifdef HAS_X11
|
||||
if(QGuiApplication::platformName() == QLatin1String("xcb")) {
|
||||
if(is_x11_) {
|
||||
const auto key = keyByKeySym.find(event->nativeScanCode());
|
||||
if(key == keyByKeySym.end()) return std::nullopt;
|
||||
return key->second;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Fall back on a limited, faulty adaptation.
|
||||
#define BIND2(qtKey, clkKey) case Qt::qtKey: return Inputs::Keyboard::Key::clkKey;
|
||||
|
||||
@@ -12,4 +12,5 @@ class KeyboardMapper {
|
||||
|
||||
private:
|
||||
std::map<quint32, Inputs::Keyboard::Key> keyByKeySym;
|
||||
bool is_x11_ = false;
|
||||
};
|
||||
|
||||
@@ -8,10 +8,29 @@ int main(int argc, char *argv[])
|
||||
// QApplication instance is mandatory on some platforms ... when an
|
||||
// OpenGL core profile context is requested."
|
||||
QSurfaceFormat format;
|
||||
|
||||
#ifndef __APPLE__
|
||||
// This project has a fully-native Mac port; therefore the Qt version isn't
|
||||
// actually built for Apple devices in any meaningful capacity. But it's useful
|
||||
// to maintain.
|
||||
//
|
||||
// Sadly macOS is quite a hostile platform for OpenGL development at this point,
|
||||
// and has never supported OpenGL ES on the desktop. So there, and there only,
|
||||
// use full-fat desktop OpenGL.
|
||||
//
|
||||
// Using ES in most places gives this project much better compatibility with
|
||||
// Raspberry Pis, with various virtualisers, etc. Thanks to WebGL's basis in
|
||||
// OpenGL ES there just seems to be a lot more lingering support there.
|
||||
format.setVersion(3, 0);
|
||||
format.setRenderableType(QSurfaceFormat::RenderableType::OpenGLES);
|
||||
#else
|
||||
format.setVersion(3, 2);
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
#endif
|
||||
format.setDepthBufferSize(0);
|
||||
format.setStencilBufferSize(0);
|
||||
format.setAlphaBufferSize(0);
|
||||
// format.setSwapBehavior(QSurfaceFormat::SingleBuffer);
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
|
||||
QApplication a(argc, argv);
|
||||
|
||||
+299
-255
@@ -7,20 +7,19 @@
|
||||
#include <QObject>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QAudioDevice>
|
||||
#include <QMediaDevices>
|
||||
#endif
|
||||
|
||||
#include <QtWidgets>
|
||||
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
#include "../../Numeric/CRC.hpp"
|
||||
#include "../../Configurable/StandardOptions.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<std::vector<uint8_t>> fileContentsAndClose(FILE *file) {
|
||||
std::unique_ptr<std::vector<uint8_t>> fileContentsAndClose(FILE *const file) {
|
||||
auto data = std::make_unique<std::vector<uint8_t>>();
|
||||
|
||||
fseek(file, 0, SEEK_END);
|
||||
@@ -46,7 +45,7 @@ std::unique_ptr<std::vector<uint8_t>> fileContentsAndClose(FILE *file) {
|
||||
affect the window, so isn't useful for this project). Therefore the emulation window resizes freely.
|
||||
*/
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
MainWindow::MainWindow(QWidget *const parent) : QMainWindow(parent) {
|
||||
init();
|
||||
setUIPhase(UIPhase::SelectingMachine);
|
||||
}
|
||||
@@ -97,7 +96,7 @@ MainWindow::~MainWindow() {
|
||||
storeSelections();
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event) {
|
||||
void MainWindow::closeEvent(QCloseEvent *const event) {
|
||||
// SDI behaviour, which may or may not be normal (?): if the user is closing a
|
||||
// final window, and it is anywher ebeyond the machine picker, send them back
|
||||
// to the start. i.e. assume they were closing that document, not the application.
|
||||
@@ -202,7 +201,7 @@ void MainWindow::addHelpMenu() {
|
||||
});
|
||||
}
|
||||
|
||||
QString MainWindow::getFilename(const char *title) {
|
||||
QString MainWindow::getFilename(const char *const title) {
|
||||
Settings settings;
|
||||
|
||||
// Use the Settings to get a default open path; write it back afterwards.
|
||||
@@ -238,7 +237,7 @@ bool MainWindow::launchFile(const QString &fileName) {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::tile(const QMainWindow *previous) {
|
||||
void MainWindow::tile(const QMainWindow *const previous) {
|
||||
// This entire function is essentially verbatim from the Qt SDI example.
|
||||
if (!previous)
|
||||
return;
|
||||
@@ -248,10 +247,9 @@ void MainWindow::tile(const QMainWindow *previous) {
|
||||
topFrameWidth = 40;
|
||||
|
||||
const QPoint pos = previous->pos() + 2 * QPoint(topFrameWidth, topFrameWidth);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
||||
if (screen()->availableGeometry().contains(rect().bottomRight() + pos))
|
||||
#endif
|
||||
if (screen()->availableGeometry().contains(rect().bottomRight() + pos)) {
|
||||
move(pos);
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Machine launch.
|
||||
@@ -320,32 +318,18 @@ void MainWindow::launchMachine() {
|
||||
static constexpr size_t samplesPerBuffer = 256; // TODO: select this dynamically.
|
||||
const auto speaker = audio_producer->get_speaker();
|
||||
if(speaker) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QAudioDevice device(QMediaDevices::defaultAudioOutput());
|
||||
if(true) { // TODO: how to check that audio output is available in Qt6?
|
||||
QAudioFormat idealFormat = device.preferredFormat();
|
||||
#else
|
||||
const QAudioDeviceInfo &defaultDeviceInfo = QAudioDeviceInfo::defaultOutputDevice();
|
||||
if(!defaultDeviceInfo.isNull()) {
|
||||
QAudioFormat idealFormat = defaultDeviceInfo.preferredFormat();
|
||||
#endif
|
||||
|
||||
// Use the ideal format's sample rate, provide stereo as long as at least two channels
|
||||
// are available, and — at least for now — assume a good buffer size.
|
||||
audioIsStereo = (idealFormat.channelCount() > 1) && speaker->get_is_stereo();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
audioIs8bit = idealFormat.sampleFormat() == QAudioFormat::UInt8;
|
||||
#else
|
||||
audioIs8bit = idealFormat.sampleSize() < 16;
|
||||
#endif
|
||||
|
||||
idealFormat.setChannelCount(1 + int(audioIsStereo));
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
idealFormat.setSampleFormat(audioIs8bit ? QAudioFormat::UInt8 : QAudioFormat::Int16);
|
||||
#else
|
||||
idealFormat.setSampleSize(audioIs8bit ? 8 : 16);
|
||||
#endif
|
||||
|
||||
speaker->set_output_rate(idealFormat.sampleRate(), samplesPerBuffer, audioIsStereo);
|
||||
speaker->set_delegate(this);
|
||||
@@ -353,11 +337,7 @@ void MainWindow::launchMachine() {
|
||||
audioThread.start();
|
||||
audioThread.performAsync([&] {
|
||||
// Create an audio output.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
audioOutput = std::make_unique<QAudioSink>(device, idealFormat);
|
||||
#else
|
||||
audioOutput = std::make_unique<QAudioOutput>(idealFormat);
|
||||
#endif
|
||||
|
||||
// Start the output. The additional `audioBuffer` is meant to minimise latency,
|
||||
// believe it or not, given Qt's semantics.
|
||||
@@ -399,17 +379,11 @@ void MainWindow::launchMachine() {
|
||||
QAction *const asKeyboardAction = new QAction(tr("Use Keyboard as Keyboard"), this);
|
||||
asKeyboardAction->setCheckable(true);
|
||||
asKeyboardAction->setChecked(true);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
asKeyboardAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_K));
|
||||
#endif
|
||||
inputMenu->addAction(asKeyboardAction);
|
||||
|
||||
QAction *const asJoystickAction = new QAction(tr("Use Keyboard as Joystick"), this);
|
||||
asJoystickAction->setCheckable(true);
|
||||
asJoystickAction->setChecked(false);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
asJoystickAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_J));
|
||||
#endif
|
||||
inputMenu->addAction(asJoystickAction);
|
||||
|
||||
connect(asKeyboardAction, &QAction::triggered, this, [=, this] {
|
||||
@@ -428,11 +402,74 @@ void MainWindow::launchMachine() {
|
||||
|
||||
// Add machine-specific UI.
|
||||
const std::string settingsPrefix = Machine::ShortNameForTargetMachine(machineType);
|
||||
switch(machineType) {
|
||||
case Analyser::Machine::AmstradCPC:
|
||||
addDisplayMenu(settingsPrefix, "Television", "", "", "Monitor");
|
||||
break;
|
||||
auto configurableMachine = machine->configurable_device();
|
||||
if(configurableMachine) {
|
||||
auto options = configurableMachine->get_options();
|
||||
const auto allKeys = options->all_keys();
|
||||
const auto allDisplayValues = options->values_for(Configurable::Options::DisplayOptionName);
|
||||
const auto hasDynamicCrop = std::find(allKeys.begin(), allKeys.end(), Configurable::Options::DynamicCropOptionName) != allKeys.end();
|
||||
if(hasDynamicCrop || allDisplayValues.size() > 1) {
|
||||
const auto contains = [&](const Configurable::Display option) {
|
||||
const auto name = Reflection::Enum::to_string<Configurable::Display>(option);
|
||||
return std::find(allDisplayValues.begin(), allDisplayValues.end(), name) != allDisplayValues.end();
|
||||
};
|
||||
|
||||
|
||||
const bool hasCompositeColour = contains(Configurable::Display::CompositeColour);
|
||||
const bool hasCompositeMonochrome = contains(Configurable::Display::CompositeMonochrome);
|
||||
const bool hasSVideo = contains(Configurable::Display::SVideo);
|
||||
const bool hasRGB = contains(Configurable::Display::RGB);
|
||||
|
||||
const bool differentiateComposite = hasCompositeColour && hasCompositeMonochrome;
|
||||
const bool hasMultipleTelevisionConnections = hasSVideo && (hasCompositeColour || hasCompositeMonochrome);
|
||||
const bool hasNonCompositeConnections = hasSVideo || hasRGB;
|
||||
|
||||
const auto compositeColourName = [&]() {
|
||||
if(!hasNonCompositeConnections) {
|
||||
return "Colour";
|
||||
}
|
||||
if(hasMultipleTelevisionConnections) {
|
||||
return differentiateComposite ? "Colour Composite" : "Composite";
|
||||
} else {
|
||||
return differentiateComposite ? "Colour Television" : "Television";
|
||||
}
|
||||
};
|
||||
|
||||
const auto compositeMonochromeName = [&]() {
|
||||
if(!hasNonCompositeConnections) {
|
||||
return "Monochrome";
|
||||
}
|
||||
if(hasMultipleTelevisionConnections) {
|
||||
return differentiateComposite ? "Monochrome Composite" : "Composite";
|
||||
} else {
|
||||
return differentiateComposite ? "Black and White Television" : "Television";
|
||||
}
|
||||
};
|
||||
|
||||
const auto rgbName = [&]() {
|
||||
return hasMultipleTelevisionConnections ? "RGB" : "Monitor";
|
||||
};
|
||||
|
||||
addDisplayMenu(
|
||||
settingsPrefix,
|
||||
hasCompositeColour ? compositeColourName() : "",
|
||||
hasCompositeMonochrome ? compositeMonochromeName() : "",
|
||||
hasSVideo ? "S-Video" : "",
|
||||
hasRGB ? rgbName() : "",
|
||||
hasDynamicCrop
|
||||
);
|
||||
}
|
||||
|
||||
// The ZX80 and ZX81 have a specialised version of this.
|
||||
// It might become general later if I generalite automatic tape motor control, which I probably should.
|
||||
if(machineType != Analyser::Machine::ZX8081) {
|
||||
const auto hasQuickLoad = std::find(allKeys.begin(), allKeys.end(), Configurable::Options::QuickLoadOptionName) != allKeys.end();
|
||||
const auto hasQuickBoot = std::find(allKeys.begin(), allKeys.end(), Configurable::Options::QuickBootOptionName) != allKeys.end();
|
||||
addEnhancementsMenu(settingsPrefix, hasQuickLoad, hasQuickBoot);
|
||||
}
|
||||
}
|
||||
|
||||
switch(machineType) {
|
||||
case Analyser::Machine::AppleII:
|
||||
addAppleIIMenu();
|
||||
break;
|
||||
@@ -441,58 +478,10 @@ void MainWindow::launchMachine() {
|
||||
addAtari2600Menu();
|
||||
break;
|
||||
|
||||
case Analyser::Machine::Archimedes:
|
||||
addEnhancementsMenu(settingsPrefix, true, false);
|
||||
break;
|
||||
|
||||
case Analyser::Machine::AtariST:
|
||||
addDisplayMenu(settingsPrefix, "Television", "", "", "Monitor");
|
||||
break;
|
||||
|
||||
case Analyser::Machine::ColecoVision:
|
||||
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "");
|
||||
break;
|
||||
|
||||
case Analyser::Machine::Electron:
|
||||
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "RGB");
|
||||
addEnhancementsMenu(settingsPrefix, true, false);
|
||||
break;
|
||||
|
||||
case Analyser::Machine::Enterprise:
|
||||
addDisplayMenu(settingsPrefix, "Composite", "", "", "RGB");
|
||||
break;
|
||||
|
||||
case Analyser::Machine::Macintosh:
|
||||
addEnhancementsMenu(settingsPrefix, false, true);
|
||||
break;
|
||||
|
||||
case Analyser::Machine::MasterSystem:
|
||||
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "SCART");
|
||||
break;
|
||||
|
||||
case Analyser::Machine::MSX:
|
||||
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "SCART");
|
||||
addEnhancementsMenu(settingsPrefix, true, false);
|
||||
break;
|
||||
|
||||
case Analyser::Machine::Oric:
|
||||
addDisplayMenu(settingsPrefix, "Composite", "", "", "SCART");
|
||||
break;
|
||||
|
||||
case Analyser::Machine::Vic20:
|
||||
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "");
|
||||
addEnhancementsMenu(settingsPrefix, true, false);
|
||||
break;
|
||||
|
||||
case Analyser::Machine::ZX8081:
|
||||
addZX8081Menu(settingsPrefix);
|
||||
break;
|
||||
|
||||
case Analyser::Machine::ZXSpectrum:
|
||||
addDisplayMenu(settingsPrefix, "Composite", "", "S-Video", "SCART");
|
||||
addEnhancementsMenu(settingsPrefix, true, false);
|
||||
break;
|
||||
|
||||
default: break;
|
||||
}
|
||||
|
||||
@@ -503,7 +492,14 @@ void MainWindow::launchMachine() {
|
||||
addActivityObserver();
|
||||
}
|
||||
|
||||
void MainWindow::addDisplayMenu(const std::string &machinePrefix, const std::string &compositeColour, const std::string &compositeMono, const std::string &svideo, const std::string &rgb) {
|
||||
void MainWindow::addDisplayMenu(
|
||||
const std::string &machinePrefix,
|
||||
const std::string &compositeColour,
|
||||
const std::string &compositeMono,
|
||||
const std::string &svideo,
|
||||
const std::string &rgb,
|
||||
const bool offerDynamicCrop
|
||||
) {
|
||||
// Create a display menu.
|
||||
displayMenu = menuBar()->addMenu(tr("&Display"));
|
||||
|
||||
@@ -513,23 +509,21 @@ void MainWindow::addDisplayMenu(const std::string &machinePrefix, const std::str
|
||||
QAction *rgbAction = nullptr;
|
||||
|
||||
// Add all requested actions.
|
||||
#define Add(name, action) \
|
||||
if(!name.empty()) { \
|
||||
action = new QAction(tr(name.c_str()), this); \
|
||||
action->setCheckable(true); \
|
||||
displayMenu->addAction(action); \
|
||||
}
|
||||
|
||||
Add(compositeColour, compositeColourAction);
|
||||
Add(compositeMono, compositeMonochromeAction);
|
||||
Add(svideo, sVideoAction);
|
||||
Add(rgb, rgbAction);
|
||||
|
||||
#undef Add
|
||||
const auto add = [&](const std::string &name, QAction *(&action)) {
|
||||
if(!name.empty()) {
|
||||
action = new QAction(tr(name.c_str()), this);
|
||||
action->setCheckable(true);
|
||||
displayMenu->addAction(action);
|
||||
}
|
||||
};
|
||||
add(compositeColour, compositeColourAction);
|
||||
add(compositeMono, compositeMonochromeAction);
|
||||
add(svideo, sVideoAction);
|
||||
add(rgb, rgbAction);
|
||||
|
||||
// Get the machine's default setting.
|
||||
auto options = machine->configurable_device()->get_options();
|
||||
auto defaultDisplay = Reflection::get<Configurable::Display>(*options, "output");
|
||||
auto defaultDisplay = Reflection::get<Configurable::Display>(*options, Configurable::Options::DisplayOptionName);
|
||||
|
||||
// Check whether there's an alternative selection in the user settings. If so, apply it.
|
||||
Settings settings;
|
||||
@@ -538,7 +532,7 @@ void MainWindow::addDisplayMenu(const std::string &machinePrefix, const std::str
|
||||
auto userSelectedDisplay = Configurable::Display(settings.value(settingName).toInt());
|
||||
if(userSelectedDisplay != defaultDisplay) {
|
||||
defaultDisplay = userSelectedDisplay;
|
||||
Reflection::set(*options, "output", int(userSelectedDisplay));
|
||||
Reflection::set(*options, Configurable::Options::DisplayOptionName, int(userSelectedDisplay));
|
||||
machine->configurable_device()->set_options(options);
|
||||
}
|
||||
}
|
||||
@@ -568,53 +562,80 @@ void MainWindow::addDisplayMenu(const std::string &machinePrefix, const std::str
|
||||
|
||||
std::lock_guard lock_guard(machineMutex);
|
||||
auto options = machine->configurable_device()->get_options();
|
||||
Reflection::set(*options, "output", int(displaySelection));
|
||||
Reflection::set(*options, Configurable::Options::DisplayOptionName, int(displaySelection));
|
||||
machine->configurable_device()->set_options(options);
|
||||
});
|
||||
}
|
||||
|
||||
// Possibly add a dynamic crop selector.
|
||||
if(offerDynamicCrop) {
|
||||
displayMenu->addSeparator();
|
||||
|
||||
QAction *const action = new QAction(tr("Crop Dynamically"), this);
|
||||
action->setCheckable(true);
|
||||
displayMenu->addAction(action);
|
||||
|
||||
const auto dynamicCropSettingName = QString::fromStdString(machinePrefix + ".dynamicCrop");
|
||||
if(settings.contains(dynamicCropSettingName)) {
|
||||
const auto useDynamicCrop = settings.value(settingName).toBool();
|
||||
action->setChecked(useDynamicCrop);
|
||||
Reflection::set(*options, Configurable::Options::DynamicCropOptionName, useDynamicCrop);
|
||||
}
|
||||
connect(action, &QAction::toggled, this, [=, this] (const bool ticked) {
|
||||
Settings settings;
|
||||
settings.setValue(dynamicCropSettingName, ticked);
|
||||
|
||||
std::lock_guard lock_guard(machineMutex);
|
||||
auto options = machine->configurable_device()->get_options();
|
||||
Reflection::set(*options, Configurable::Options::DynamicCropOptionName, ticked);
|
||||
machine->configurable_device()->set_options(options);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::addEnhancementsMenu(const std::string &machinePrefix, bool offerQuickLoad, bool offerQuickBoot) {
|
||||
void MainWindow::addEnhancementsMenu(const std::string &machinePrefix, const bool offerQuickLoad, const bool offerQuickBoot) {
|
||||
if(!offerQuickLoad && !offerQuickBoot) {
|
||||
return;
|
||||
}
|
||||
enhancementsMenu = menuBar()->addMenu(tr("&Enhancements"));
|
||||
addEnhancementsItems(machinePrefix, enhancementsMenu, offerQuickLoad, offerQuickBoot, false);
|
||||
}
|
||||
|
||||
void MainWindow::addEnhancementsItems(const std::string &machinePrefix, QMenu *menu, bool offerQuickLoad, bool offerQuickBoot, bool offerAutomaticTapeControl) {
|
||||
void MainWindow::addEnhancementsItems(const std::string &machinePrefix, QMenu *const menu, const bool offerQuickLoad, const bool offerQuickBoot, const bool offerAutomaticTapeControl) {
|
||||
auto options = machine->configurable_device()->get_options();
|
||||
Settings settings;
|
||||
|
||||
#define Add(offered, text, setting, action) \
|
||||
if(offered) { \
|
||||
action = new QAction(tr(text), this); \
|
||||
action->setCheckable(true); \
|
||||
menu->addAction(action); \
|
||||
\
|
||||
const auto settingName = QString::fromStdString(machinePrefix + "." + setting); \
|
||||
if(settings.contains(settingName)) { \
|
||||
const bool isSelected = settings.value(settingName).toBool(); \
|
||||
Reflection::set(*options, setting, isSelected); \
|
||||
} \
|
||||
action->setChecked(Reflection::get<bool>(*options, setting) ? Qt::Checked : Qt::Unchecked); \
|
||||
\
|
||||
connect(action, &QAction::triggered, this, [=, this] { \
|
||||
std::lock_guard lock_guard(machineMutex); \
|
||||
auto options = machine->configurable_device()->get_options(); \
|
||||
Reflection::set(*options, setting, action->isChecked()); \
|
||||
machine->configurable_device()->set_options(options); \
|
||||
\
|
||||
Settings settings; \
|
||||
settings.setValue(settingName, action->isChecked()); \
|
||||
}); \
|
||||
}
|
||||
const auto add = [&](const bool offered, const char *text, const char *setting, QAction *(&action)) {
|
||||
if(offered) {
|
||||
action = new QAction(tr(text), this);
|
||||
action->setCheckable(true);
|
||||
menu->addAction(action);
|
||||
|
||||
const auto settingName = QString::fromStdString(machinePrefix + "." + setting);
|
||||
if(settings.contains(settingName)) {
|
||||
const bool isSelected = settings.value(settingName).toBool();
|
||||
Reflection::set(*options, setting, isSelected);
|
||||
}
|
||||
action->setChecked(Reflection::get<bool>(*options, setting));
|
||||
|
||||
connect(action, &QAction::triggered, this, [=, this] {
|
||||
std::lock_guard lock_guard(machineMutex);
|
||||
auto options = machine->configurable_device()->get_options();
|
||||
Reflection::set(*options, setting, action->isChecked());
|
||||
machine->configurable_device()->set_options(options);
|
||||
|
||||
Settings settings;
|
||||
settings.setValue(settingName, action->isChecked());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
QAction *action;
|
||||
Add(offerQuickLoad, "Load Quickly", "quickload", action);
|
||||
Add(offerQuickBoot, "Start Quickly", "quickboot", action);
|
||||
add(offerQuickLoad, "Load Quickly", Configurable::Options::QuickLoadOptionName, action);
|
||||
add(offerQuickBoot, "Start Quickly", Configurable::Options::QuickBootOptionName, action);
|
||||
|
||||
if(offerAutomaticTapeControl) menu->addSeparator();
|
||||
Add(offerAutomaticTapeControl, "Start and Stop Tape Automatically", "automatic_tape_motor_control", automaticTapeControlAction);
|
||||
|
||||
#undef Add
|
||||
add(offerAutomaticTapeControl, "Start and Stop Tape Automatically", "automatic_tape_motor_control", automaticTapeControlAction);
|
||||
|
||||
machine->configurable_device()->set_options(options);
|
||||
}
|
||||
@@ -700,7 +721,7 @@ void MainWindow::addAtari2600Menu() {
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::toggleAtari2600Switch(Atari2600Switch toggleSwitch) {
|
||||
void MainWindow::toggleAtari2600Switch(const Atari2600Switch toggleSwitch) {
|
||||
std::lock_guard lock_guard(machineMutex);
|
||||
const auto atari2600 = static_cast<Atari2600::Machine *>(machine->raw_pointer());
|
||||
|
||||
@@ -711,9 +732,6 @@ void MainWindow::toggleAtari2600Switch(Atari2600Switch toggleSwitch) {
|
||||
}
|
||||
|
||||
void MainWindow::addAppleIIMenu() {
|
||||
// Add the standard display settings.
|
||||
addDisplayMenu("appleII", "Colour", "Monochrome", "", "");
|
||||
|
||||
// Add an additional tick box, for square pixels.
|
||||
QAction *const squarePixelsAction = new QAction(tr("Square Pixels"));
|
||||
squarePixelsAction->setCheckable(true);
|
||||
@@ -736,7 +754,7 @@ void MainWindow::addAppleIIMenu() {
|
||||
setAppleIISquarePixels(useSquarePixels);
|
||||
}
|
||||
|
||||
void MainWindow::setAppleIISquarePixels(bool squarePixels) {
|
||||
void MainWindow::setAppleIISquarePixels(const bool squarePixels) {
|
||||
Configurable::Device *const configurable = machine->configurable_device();
|
||||
auto options = configurable->get_options();
|
||||
auto appleii_options = static_cast<Apple::II::Machine::Options *>(options.get());
|
||||
@@ -749,13 +767,13 @@ void MainWindow::speaker_did_complete_samples(Outputs::Speaker::Speaker &, const
|
||||
audioBuffer.write(buffer);
|
||||
}
|
||||
|
||||
void MainWindow::dragEnterEvent(QDragEnterEvent* event) {
|
||||
void MainWindow::dragEnterEvent(QDragEnterEvent *const event) {
|
||||
// Always accept dragged files.
|
||||
if(event->mimeData()->hasUrls())
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void MainWindow::dropEvent(QDropEvent* event) {
|
||||
void MainWindow::dropEvent(QDropEvent *const event) {
|
||||
if(!event->mimeData()->hasUrls()) {
|
||||
return;
|
||||
}
|
||||
@@ -829,7 +847,7 @@ void MainWindow::dropEvent(QDropEvent* event) {
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setUIPhase(UIPhase phase) {
|
||||
void MainWindow::setUIPhase(const UIPhase phase) {
|
||||
uiPhase = phase;
|
||||
|
||||
// The volume slider is never visible by default; a running machine
|
||||
@@ -886,7 +904,7 @@ void MainWindow::setWindowTitle() {
|
||||
|
||||
// MARK: - Event Processing
|
||||
|
||||
void MainWindow::changeEvent(QEvent *event) {
|
||||
void MainWindow::changeEvent(QEvent *const event) {
|
||||
// Clear current key state upon any window activation change.
|
||||
if(machine && event->type() == QEvent::ActivationChange) {
|
||||
const auto keyboardMachine = machine->keyboard_machine();
|
||||
@@ -899,15 +917,15 @@ void MainWindow::changeEvent(QEvent *event) {
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void MainWindow::keyPressEvent(QKeyEvent *event) {
|
||||
void MainWindow::keyPressEvent(QKeyEvent *const event) {
|
||||
processEvent(event);
|
||||
}
|
||||
|
||||
void MainWindow::keyReleaseEvent(QKeyEvent *event) {
|
||||
void MainWindow::keyReleaseEvent(QKeyEvent *const event) {
|
||||
processEvent(event);
|
||||
}
|
||||
|
||||
bool MainWindow::processEvent(QKeyEvent *event) {
|
||||
bool MainWindow::processEvent(QKeyEvent *const event) {
|
||||
if(!machine) return true;
|
||||
|
||||
const auto key = keyMapper.keyForEvent(event);
|
||||
@@ -922,7 +940,8 @@ bool MainWindow::processEvent(QKeyEvent *event) {
|
||||
if(!keyboardMachine) return true;
|
||||
|
||||
auto &keyboard = keyboardMachine->get_keyboard();
|
||||
keyboard.set_key_pressed(*key, event->text().size() ? event->text()[0].toLatin1() : '\0', isPressed, event->isAutoRepeat());
|
||||
const auto text = event->text();
|
||||
keyboard.set_key_pressed(*key, event->text().size() ? text[0].toLatin1() : '\0', isPressed, event->isAutoRepeat());
|
||||
if(keyboard.is_exclusive() || keyboard.observed_keys().find(*key) != keyboard.observed_keys().end()) {
|
||||
return false;
|
||||
}
|
||||
@@ -948,7 +967,8 @@ bool MainWindow::processEvent(QKeyEvent *event) {
|
||||
case Key::F: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), isPressed); break;
|
||||
default:
|
||||
if(event->text().size()) {
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input(event->text()[0].toLatin1()), isPressed);
|
||||
const auto text = event->text();
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input(text[0].toLatin1()), isPressed);
|
||||
} else {
|
||||
joysticks[0]->set_input(Inputs::Joystick::Input::Fire, isPressed);
|
||||
}
|
||||
@@ -961,12 +981,12 @@ bool MainWindow::processEvent(QKeyEvent *event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void MainWindow::setMouseIsCaptured(bool isCaptured) {
|
||||
void MainWindow::setMouseIsCaptured(const bool isCaptured) {
|
||||
mouseIsCaptured = isCaptured;
|
||||
setWindowTitle();
|
||||
}
|
||||
|
||||
void MainWindow::moveMouse(QPoint vector) {
|
||||
void MainWindow::moveMouse(const QPoint vector) {
|
||||
std::unique_lock lock(machineMutex);
|
||||
auto mouseMachine = machine->mouse_machine();
|
||||
if(!mouseMachine) return;
|
||||
@@ -974,7 +994,7 @@ void MainWindow::moveMouse(QPoint vector) {
|
||||
mouseMachine->get_mouse().move(vector.x(), vector.y());
|
||||
}
|
||||
|
||||
void MainWindow::setButtonPressed(int index, bool isPressed) {
|
||||
void MainWindow::setButtonPressed(const int index, const bool isPressed) {
|
||||
std::unique_lock lock(machineMutex);
|
||||
auto mouseMachine = machine->mouse_machine();
|
||||
if(!mouseMachine) return;
|
||||
@@ -1000,33 +1020,9 @@ void MainWindow::setButtonPressed(int index, bool isPressed) {
|
||||
#include "../../Analyser/Static/ZXSpectrum/Target.hpp"
|
||||
|
||||
void MainWindow::startMachine() {
|
||||
const auto selectedTab = ui->machineSelectionTabs->currentWidget();
|
||||
|
||||
#define TEST(x) \
|
||||
if(selectedTab == ui->x ## Tab) { \
|
||||
start_##x(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
TEST(amiga);
|
||||
TEST(appleII);
|
||||
TEST(appleIIgs);
|
||||
TEST(amstradCPC);
|
||||
TEST(archimedes);
|
||||
TEST(atariST);
|
||||
TEST(electron);
|
||||
TEST(enterprise);
|
||||
TEST(macintosh);
|
||||
TEST(msx);
|
||||
TEST(oric);
|
||||
TEST(plus4);
|
||||
TEST(pc);
|
||||
TEST(spectrum);
|
||||
TEST(vic20);
|
||||
TEST(zx80);
|
||||
TEST(zx81);
|
||||
|
||||
#undef TEST
|
||||
const auto selectedTabName = ui->machineSelectionTabs->currentWidget()->objectName().chopped(3);
|
||||
const auto starter = QString("start_") + selectedTabName;
|
||||
QMetaObject::invokeMethod(this, starter.toStdString().c_str());
|
||||
}
|
||||
|
||||
void MainWindow::start_appleII() {
|
||||
@@ -1123,6 +1119,24 @@ void MainWindow::start_atariST() {
|
||||
launchTarget(std::move(target));
|
||||
}
|
||||
|
||||
void MainWindow::start_bbc() {
|
||||
using Target = Analyser::Static::Acorn::BBCMicroTarget;
|
||||
auto target = std::make_unique<Target>();
|
||||
|
||||
target->has_1770dfs = ui->bbcMicroDFSCheckBox->isChecked();
|
||||
target->has_adfs = ui->bbcMicroADFSCheckBox->isChecked();
|
||||
target->has_beebsid = ui->bbcMicroBeebSIDCheckBox->isChecked();
|
||||
target->has_sideways_ram = ui->bbcMicroSidewaysRAMCheckBox->isChecked();
|
||||
|
||||
switch(ui->bbcMicroSecondProcessorComboBox->currentIndex()) {
|
||||
default: target->tube_processor = Target::TubeProcessor::None; break;
|
||||
case 1: target->tube_processor = Target::TubeProcessor::WDC65C02; break;
|
||||
case 2: target->tube_processor = Target::TubeProcessor::Z80; break;
|
||||
}
|
||||
|
||||
launchTarget(std::move(target));
|
||||
}
|
||||
|
||||
void MainWindow::start_electron() {
|
||||
using Target = Analyser::Static::Acorn::ElectronTarget;
|
||||
auto target = std::make_unique<Target>();
|
||||
@@ -1333,89 +1347,119 @@ void MainWindow::launchTarget(std::unique_ptr<Analyser::Static::Target> &&target
|
||||
// than indices. This has historically been true on the Mac, as I tend to add additional
|
||||
// options but the existing text is rarely affected.
|
||||
|
||||
#define AllSettings() \
|
||||
/* Machine selection. */ \
|
||||
Tabs(machineSelectionTabs, "machineSelection"); \
|
||||
\
|
||||
/* Amiga. */ \
|
||||
ComboBox(amigaChipRAMComboBox, "amiga.chipRAM"); \
|
||||
ComboBox(amigaFastRAMComboBox, "amiga.fastRAM"); \
|
||||
\
|
||||
/* Apple II. */ \
|
||||
ComboBox(appleIIModelComboBox, "appleII.model"); \
|
||||
ComboBox(appleIIDiskControllerComboBox, "appleII.diskController"); \
|
||||
\
|
||||
/* Apple IIgs. */ \
|
||||
ComboBox(appleIIgsModelComboBox, "appleIIgs.model"); \
|
||||
ComboBox(appleIIgsMemorySizeComboBox, "appleIIgs.memorySize"); \
|
||||
\
|
||||
/* Amstrad CPC. */ \
|
||||
ComboBox(amstradCPCModelComboBox, "amstradcpc.model"); \
|
||||
\
|
||||
/* Atari ST. */ \
|
||||
ComboBox(atariSTRAMComboBox, "atarist.memorySize"); \
|
||||
\
|
||||
/* Electron. */ \
|
||||
CheckBox(electronDFSCheckBox, "electron.hasDFS"); \
|
||||
CheckBox(electronADFSCheckBox, "electron.hasADFS"); \
|
||||
CheckBox(electronAP6CheckBox, "electron.hasAP6"); \
|
||||
CheckBox(electronSidewaysRAMCheckBox, "electron.fillSidewaysRAM"); \
|
||||
\
|
||||
/* Enterprise. */ \
|
||||
ComboBox(enterpriseModelComboBox, "enterprise.model"); \
|
||||
ComboBox(enterpriseSpeedComboBox, "enterprise.speed"); \
|
||||
ComboBox(enterpriseEXOSComboBox, "enterprise.exos"); \
|
||||
ComboBox(enterpriseBASICComboBox, "enterprise.basic"); \
|
||||
ComboBox(enterpriseDOSComboBox, "enterprise.dos"); \
|
||||
\
|
||||
/* Macintosh. */ \
|
||||
ComboBox(macintoshModelComboBox, "macintosh.model"); \
|
||||
\
|
||||
/* MSX. */ \
|
||||
ComboBox(msxRegionComboBox, "msx.region"); \
|
||||
CheckBox(msxDiskDriveCheckBox, "msx.hasDiskDrive"); \
|
||||
\
|
||||
/* Oric. */ \
|
||||
ComboBox(oricModelComboBox, "msx.model"); \
|
||||
ComboBox(oricDiskInterfaceComboBox, "msx.diskInterface"); \
|
||||
\
|
||||
/* Vic-20 */ \
|
||||
ComboBox(vic20RegionComboBox, "vic20.region"); \
|
||||
ComboBox(vic20MemorySizeComboBox, "vic20.memorySize"); \
|
||||
CheckBox(vic20C1540CheckBox, "vic20.has1540"); \
|
||||
\
|
||||
/* ZX80. */ \
|
||||
ComboBox(zx80MemorySizeComboBox, "zx80.memorySize"); \
|
||||
CheckBox(zx80UseZX81ROMCheckBox, "zx80.usesZX81ROM"); \
|
||||
\
|
||||
/* ZX81. */ \
|
||||
ComboBox(zx81MemorySizeComboBox, "zx81.memorySize");
|
||||
template <typename ApplierT>
|
||||
void MainWindow::processAllSettings() {
|
||||
ApplierT applier;
|
||||
|
||||
/* Machine selection. */
|
||||
applier(ui->machineSelectionTabs, "machineSelection");
|
||||
|
||||
/* Amiga. */
|
||||
applier(ui->amigaChipRAMComboBox, "amiga.chipRAM");
|
||||
applier(ui->amigaFastRAMComboBox, "amiga.fastRAM");
|
||||
|
||||
/* Apple II. */
|
||||
applier(ui->appleIIModelComboBox, "appleII.model");
|
||||
applier(ui->appleIIDiskControllerComboBox, "appleII.diskController");
|
||||
|
||||
/* Apple IIgs. */
|
||||
applier(ui->appleIIgsModelComboBox, "appleIIgs.model");
|
||||
applier(ui->appleIIgsMemorySizeComboBox, "appleIIgs.memorySize");
|
||||
|
||||
/* Amstrad CPC. */
|
||||
applier(ui->amstradCPCModelComboBox, "amstradcpc.model");
|
||||
|
||||
/* Atari ST. */
|
||||
applier(ui->atariSTRAMComboBox, "atarist.memorySize");
|
||||
|
||||
/* BBC Micro. */
|
||||
applier(ui->bbcMicroDFSCheckBox, "bbc.hasDFS");
|
||||
applier(ui->bbcMicroADFSCheckBox, "bbc.hasADFS");
|
||||
applier(ui->bbcMicroBeebSIDCheckBox, "bbc.hasBeebSID");
|
||||
applier(ui->bbcMicroSidewaysRAMCheckBox, "bbc.fillSidewaysRAM");
|
||||
applier(ui->bbcMicroSecondProcessorComboBox, "bbc.secondProcessor");
|
||||
|
||||
/* Electron. */
|
||||
applier(ui->electronDFSCheckBox, "electron.hasDFS");
|
||||
applier(ui->electronADFSCheckBox, "electron.hasADFS");
|
||||
applier(ui->electronAP6CheckBox, "electron.hasAP6");
|
||||
applier(ui->electronSidewaysRAMCheckBox, "electron.fillSidewaysRAM");
|
||||
|
||||
/* Enterprise. */
|
||||
applier(ui->enterpriseModelComboBox, "enterprise.model");
|
||||
applier(ui->enterpriseSpeedComboBox, "enterprise.speed");
|
||||
applier(ui->enterpriseEXOSComboBox, "enterprise.exos");
|
||||
applier(ui->enterpriseBASICComboBox, "enterprise.basic");
|
||||
applier(ui->enterpriseDOSComboBox, "enterprise.dos");
|
||||
|
||||
/* Macintosh. */
|
||||
applier(ui->macintoshModelComboBox, "macintosh.model");
|
||||
|
||||
/* MSX. */
|
||||
applier(ui->msxModelComboBox, "msx.model");
|
||||
applier(ui->msxRegionComboBox, "msx.region");
|
||||
applier(ui->msxDiskDriveCheckBox, "msx.hasDiskDrive");
|
||||
applier(ui->msxMSXMUSICCheckBox, "msx.hasMSXMUSIC");
|
||||
|
||||
/* Oric. */
|
||||
applier(ui->oricModelComboBox, "msx.model");
|
||||
applier(ui->oricDiskInterfaceComboBox, "msx.diskInterface");
|
||||
|
||||
/* Plus 4. */
|
||||
applier(ui->plus4C1541CheckBox, "plus4.hasC1541");
|
||||
|
||||
/* PC Compatible. */
|
||||
applier(ui->pcSpeedComboBox, "pc.speed");
|
||||
applier(ui->pcVideoAdaptorComboBox, "pc.videoAdaptor");
|
||||
|
||||
/* Vic-20 */
|
||||
applier(ui->vic20RegionComboBox, "vic20.region");
|
||||
applier(ui->vic20MemorySizeComboBox, "vic20.memorySize");
|
||||
applier(ui->vic20C1540CheckBox, "vic20.has1540");
|
||||
|
||||
/* ZX80. */
|
||||
applier(ui->zx80MemorySizeComboBox, "zx80.memorySize");
|
||||
applier(ui->zx80UseZX81ROMCheckBox, "zx80.usesZX81ROM");
|
||||
|
||||
/* ZX81. */
|
||||
applier(ui->zx81MemorySizeComboBox, "zx81.memorySize");
|
||||
|
||||
/* ZX Spectrum. */
|
||||
applier(ui->spectrumModelComboBox, "spectrum.model");
|
||||
}
|
||||
|
||||
void MainWindow::storeSelections() {
|
||||
Settings settings;
|
||||
#define Tabs(name, key) settings.setValue(key, ui->name->currentIndex())
|
||||
#define CheckBox(name, key) settings.setValue(key, ui->name->isChecked())
|
||||
#define ComboBox(name, key) settings.setValue(key, ui->name->currentText())
|
||||
struct Storer {
|
||||
Settings settings;
|
||||
|
||||
AllSettings();
|
||||
|
||||
#undef Tabs
|
||||
#undef CheckBox
|
||||
#undef ComboBox
|
||||
void operator()(QCheckBox *const checkBox, const char *key) {
|
||||
settings.setValue(key, checkBox->isChecked());
|
||||
}
|
||||
void operator()(QComboBox *const comboBox, const char *key) {
|
||||
settings.setValue(key, comboBox->currentText());
|
||||
}
|
||||
void operator()(QTabWidget *const tabs, const char *key) {
|
||||
settings.setValue(key, tabs->currentIndex());
|
||||
}
|
||||
};
|
||||
processAllSettings<Storer>();
|
||||
}
|
||||
|
||||
void MainWindow::restoreSelections() {
|
||||
Settings settings;
|
||||
struct Retriever {
|
||||
Settings settings;
|
||||
|
||||
#define Tabs(name, key) ui->name->setCurrentIndex(settings.value(key).toInt())
|
||||
#define CheckBox(name, key) ui->name->setCheckState(settings.value(key).toBool() ? Qt::Checked : Qt::Unchecked)
|
||||
#define ComboBox(name, key) ui->name->setCurrentText(settings.value(key).toString())
|
||||
|
||||
AllSettings();
|
||||
|
||||
#undef Tabs
|
||||
#undef CheckBox
|
||||
#undef ComboBox
|
||||
void operator()(QCheckBox *const checkBox, const char *key) {
|
||||
checkBox->setCheckState(settings.value(key).toBool() ? Qt::Checked : Qt::Unchecked);
|
||||
}
|
||||
void operator()(QComboBox *const comboBox, const char *key) {
|
||||
comboBox->setCurrentText(settings.value(key).toString());
|
||||
}
|
||||
void operator()(QTabWidget *const tabs, const char *key) {
|
||||
tabs->setCurrentIndex(settings.value(key).toInt());
|
||||
}
|
||||
};
|
||||
processAllSettings<Retriever>();
|
||||
}
|
||||
|
||||
// MARK: - Activity observation
|
||||
@@ -1435,7 +1479,7 @@ void MainWindow::register_led(const std::string &name, uint8_t) {
|
||||
QMetaObject::invokeMethod(this, "updateStatusBarText");
|
||||
}
|
||||
|
||||
void MainWindow::set_led_status(const std::string &name, bool isLit) {
|
||||
void MainWindow::set_led_status(const std::string &name, const bool isLit) {
|
||||
std::lock_guard guard(ledStatusesLock);
|
||||
ledStatuses[name] = isLit;
|
||||
QMetaObject::invokeMethod(this, "updateStatusBarText");
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <QAudioSink>
|
||||
#else
|
||||
#include <QAudioOutput>
|
||||
#endif
|
||||
|
||||
#include <QAudioSink>
|
||||
#include <QMainWindow>
|
||||
|
||||
#include <memory>
|
||||
@@ -76,11 +70,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
|
||||
std::unique_ptr<Machine::DynamicMachine> machine;
|
||||
std::mutex machineMutex;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
std::unique_ptr<QAudioSink> audioOutput;
|
||||
#else
|
||||
std::unique_ptr<QAudioOutput> audioOutput;
|
||||
#endif
|
||||
bool audioIs8bit = false, audioIsStereo = false;
|
||||
void speaker_did_complete_samples(Outputs::Speaker::Speaker &, const std::vector<int16_t> &) override;
|
||||
AudioBuffer audioBuffer;
|
||||
@@ -94,13 +84,13 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
|
||||
void startMachine();
|
||||
void updateStatusBarText();
|
||||
|
||||
private:
|
||||
void start_amiga();
|
||||
void start_appleII();
|
||||
void start_appleIIgs();
|
||||
void start_amstradCPC();
|
||||
void start_archimedes();
|
||||
void start_atariST();
|
||||
void start_bbc();
|
||||
void start_electron();
|
||||
void start_enterprise();
|
||||
void start_macintosh();
|
||||
@@ -113,6 +103,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
|
||||
void start_zx80();
|
||||
void start_zx81();
|
||||
|
||||
private:
|
||||
enum class KeyboardInputMode {
|
||||
Keyboard, Joystick
|
||||
} keyboardInputMode;
|
||||
@@ -136,7 +127,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
|
||||
void deleteMachine();
|
||||
|
||||
QMenu *displayMenu = nullptr;
|
||||
void addDisplayMenu(const std::string &machinePrefix, const std::string &compositeColour, const std::string &compositeMono, const std::string &svideo, const std::string &rgb);
|
||||
void addDisplayMenu(const std::string &machinePrefix, const std::string &compositeColour, const std::string &compositeMono, const std::string &svideo, const std::string &rgb, bool allowDynamicCrop);
|
||||
|
||||
QMenu *enhancementsMenu = nullptr;
|
||||
QAction *automaticTapeControlAction = nullptr;
|
||||
@@ -172,4 +163,7 @@ class MainWindow : public QMainWindow, public Outputs::Speaker::Speaker::Delegat
|
||||
std::map<std::string, bool> ledStatuses;
|
||||
|
||||
void addActivityObserver();
|
||||
|
||||
template <typename ApplierT>
|
||||
void processAllSettings();
|
||||
};
|
||||
|
||||
+115
-24
@@ -27,7 +27,7 @@
|
||||
<item>
|
||||
<widget class="QTabWidget" name="machineSelectionTabs">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>6</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="amigaTab">
|
||||
<attribute name="title">
|
||||
@@ -105,7 +105,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -190,7 +190,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -277,7 +277,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -331,7 +331,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -404,10 +404,101 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="bbcTab">
|
||||
<attribute name="title">
|
||||
<string>BBC Micro</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bbcMicroDFSCheckBox">
|
||||
<property name="text">
|
||||
<string>With Disk Filing System</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bbcMicroADFSCheckBox">
|
||||
<property name="text">
|
||||
<string>With Advanced Disk Filing System</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bbcMicroBeebSIDCheckBox">
|
||||
<property name="text">
|
||||
<string>With BeebSID</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bbcMicroSidewaysRAMCheckBox">
|
||||
<property name="text">
|
||||
<string>Fill unused ROM banks with sideways RAM</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel">
|
||||
<property name="text">
|
||||
<string>Second Processor:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="bbcMicroSecondProcessorComboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>None</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>65C02</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Z80</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="electronTab">
|
||||
<attribute name="title">
|
||||
<string>Electron</string>
|
||||
@@ -444,7 +535,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -595,7 +686,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -654,7 +745,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -703,7 +794,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -750,7 +841,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -779,7 +870,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -867,7 +958,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -896,7 +987,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -964,7 +1055,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -1054,7 +1145,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -1076,7 +1167,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -1123,7 +1214,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -1145,7 +1236,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -1192,7 +1283,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -1261,7 +1352,7 @@
|
||||
<item>
|
||||
<spacer>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -1277,7 +1368,7 @@
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignBottom">
|
||||
<item alignment="Qt::AlignmentFlag::AlignBottom">
|
||||
<widget class="QLabel" name="topTipLabel">
|
||||
<property name="text">
|
||||
<string>TIP: the easiest way to get started is just to open the disk, tape or cartridge you want to use. The emulator will automatically configure a suitable machine and attempt to launch the software you've selected. Use this method to load Atari 2600, ColecoVision or Master System games.</string>
|
||||
@@ -1287,7 +1378,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignRight|Qt::AlignBottom">
|
||||
<item alignment="Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignBottom">
|
||||
<widget class="QPushButton" name="startMachineButton">
|
||||
<property name="text">
|
||||
<string>Start Machine</string>
|
||||
@@ -1306,7 +1397,7 @@ Please drag and drop over this window</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item alignment="Qt::AlignHCenter|Qt::AlignBottom">
|
||||
<item alignment="Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignBottom">
|
||||
<widget class="QSlider" name="volumeSlider">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
@@ -1315,7 +1406,7 @@ Please drag and drop over this window</string>
|
||||
</size>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -11,8 +11,9 @@
|
||||
#include <QTimer>
|
||||
|
||||
#include "../../ClockReceiver/TimeTypes.hpp"
|
||||
#include "../../Outputs/OpenGL/Primitives/Shader.hpp"
|
||||
|
||||
ScanTargetWidget::ScanTargetWidget(QWidget *parent) : QOpenGLWidget(parent) {}
|
||||
ScanTargetWidget::ScanTargetWidget(QWidget *const parent) : QOpenGLWidget(parent) {}
|
||||
ScanTargetWidget::~ScanTargetWidget() {}
|
||||
|
||||
void ScanTargetWidget::initializeGL() {
|
||||
@@ -36,7 +37,6 @@ void ScanTargetWidget::paintGL() {
|
||||
resize();
|
||||
}
|
||||
vsyncPredictor.set_frame_rate(float(screen->refreshRate()));
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// Gmynastics ahoy: if a producer has been specified or previously connected then:
|
||||
@@ -50,10 +50,18 @@ void ScanTargetWidget::paintGL() {
|
||||
// a scan target in ::initializeGL did not work (and no other arrangement really works
|
||||
// with regard to starting up).
|
||||
if(isConnected || producer) {
|
||||
// Qt-specific workaround. I can but speculate as to why, but the bound program does
|
||||
// not necessarily survive between calls into paintGL.
|
||||
Outputs::Display::OpenGL::Shader::unbind();
|
||||
|
||||
if(producer) {
|
||||
isConnected = true;
|
||||
framebuffer = defaultFramebufferObject();
|
||||
scanTarget = std::make_unique<Outputs::Display::OpenGL::ScanTarget>(framebuffer);
|
||||
|
||||
const auto api = format().renderableType() == QSurfaceFormat::RenderableType::OpenGLES ?
|
||||
Outputs::Display::OpenGL::API::OpenGLES3 : Outputs::Display::OpenGL::API::OpenGL32Core;
|
||||
scanTarget = std::make_unique<Outputs::Display::OpenGL::ScanTarget>(api, framebuffer);
|
||||
|
||||
producer->set_scan_target(scanTarget.get());
|
||||
producer = nullptr;
|
||||
}
|
||||
@@ -86,11 +94,16 @@ void ScanTargetWidget::vsync() {
|
||||
QTimer::singleShot(delay_time, this, SLOT(repaint()));
|
||||
} else {
|
||||
requestedRedrawTime = 0;
|
||||
repaint();
|
||||
|
||||
// Schedule an immediate repaint, but put it on the event loop
|
||||
// to happen soon, don't do it now. That's because it isn't formally
|
||||
// defined whether vsync will be separately scheduled from repaint,
|
||||
// so an infinite loop might occur here.
|
||||
QTimer::singleShot(0, this, SLOT(repaint()));
|
||||
}
|
||||
}
|
||||
|
||||
void ScanTargetWidget::resizeGL(int w, int h) {
|
||||
void ScanTargetWidget::resizeGL(const int w, const int h) {
|
||||
if(rawWidth != w || rawHeight != h) {
|
||||
rawWidth = w;
|
||||
rawHeight = h;
|
||||
@@ -109,7 +122,7 @@ void ScanTargetWidget::resize() {
|
||||
}
|
||||
}
|
||||
|
||||
void ScanTargetWidget::setScanProducer(MachineTypes::ScanProducer *producer) {
|
||||
void ScanTargetWidget::setScanProducer(MachineTypes::ScanProducer *const producer) {
|
||||
this->producer = producer;
|
||||
repaint();
|
||||
}
|
||||
@@ -130,7 +143,7 @@ void ScanTargetWidget::setDefaultClearColour() {
|
||||
glClearColor(backgroundColour.redF(), backgroundColour.greenF(), backgroundColour.blueF(), 1.0);
|
||||
}
|
||||
|
||||
void ScanTargetWidget::setMouseDelegate(MouseDelegate *delegate) {
|
||||
void ScanTargetWidget::setMouseDelegate(MouseDelegate *const delegate) {
|
||||
if(!delegate && mouseIsCaptured) {
|
||||
releaseMouse();
|
||||
}
|
||||
@@ -138,7 +151,7 @@ void ScanTargetWidget::setMouseDelegate(MouseDelegate *delegate) {
|
||||
setMouseTracking(delegate);
|
||||
}
|
||||
|
||||
void ScanTargetWidget::keyReleaseEvent(QKeyEvent *event) {
|
||||
void ScanTargetWidget::keyReleaseEvent(QKeyEvent *const event) {
|
||||
// Releasing F8 or F12 needs to be tracked but doesn't actively do anything,
|
||||
// so I'm counting that as a Qt ignore.
|
||||
if(event->key() == Qt::Key_F8) f8State = false;
|
||||
@@ -146,7 +159,7 @@ void ScanTargetWidget::keyReleaseEvent(QKeyEvent *event) {
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void ScanTargetWidget::keyPressEvent(QKeyEvent *event) {
|
||||
void ScanTargetWidget::keyPressEvent(QKeyEvent *const event) {
|
||||
// Use either CTRL+Escape or F8+F12 to end mouse captured mode, if currently captured;
|
||||
// otherwise ignore the event.
|
||||
|
||||
@@ -174,7 +187,7 @@ void ScanTargetWidget::releaseMouse() {
|
||||
mouseDelegate->setMouseIsCaptured(false);
|
||||
}
|
||||
|
||||
void ScanTargetWidget::mousePressEvent(QMouseEvent *event) {
|
||||
void ScanTargetWidget::mousePressEvent(QMouseEvent *const event) {
|
||||
if(mouseDelegate) {
|
||||
if(!mouseIsCaptured) {
|
||||
mouseIsCaptured = true;
|
||||
@@ -192,13 +205,13 @@ void ScanTargetWidget::mousePressEvent(QMouseEvent *event) {
|
||||
}
|
||||
}
|
||||
|
||||
void ScanTargetWidget::mouseReleaseEvent(QMouseEvent *event) {
|
||||
void ScanTargetWidget::mouseReleaseEvent(QMouseEvent *const event) {
|
||||
if(mouseDelegate && !mouseIsCaptured) {
|
||||
setMouseButtonPressed(event->button(), false);
|
||||
}
|
||||
}
|
||||
|
||||
void ScanTargetWidget::setMouseButtonPressed(Qt::MouseButton button, bool isPressed) {
|
||||
void ScanTargetWidget::setMouseButtonPressed(const Qt::MouseButton button, const bool isPressed) {
|
||||
switch(button) {
|
||||
default: break;
|
||||
case Qt::LeftButton: mouseDelegate->setButtonPressed(0, isPressed); break;
|
||||
@@ -207,7 +220,7 @@ void ScanTargetWidget::setMouseButtonPressed(Qt::MouseButton button, bool isPres
|
||||
}
|
||||
}
|
||||
|
||||
void ScanTargetWidget::mouseMoveEvent(QMouseEvent *event) {
|
||||
void ScanTargetWidget::mouseMoveEvent(QMouseEvent *const event) {
|
||||
// Recentre the mouse cursor upon every move if it is currently captured.
|
||||
if(mouseDelegate && mouseIsCaptured) {
|
||||
const QPoint centre = QPoint(width() / 2, height() / 2);
|
||||
|
||||
@@ -134,6 +134,7 @@ SOURCES += glob.glob('../../Storage/Disk/Encodings/MFM/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Disk/Parsers/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Disk/Track/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/Disk/Data/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/FileBundle/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/MassStorage/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/MassStorage/Encodings/*.cpp')
|
||||
SOURCES += glob.glob('../../Storage/MassStorage/Formats/*.cpp')
|
||||
|
||||
+44
-19
@@ -188,7 +188,7 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
|
||||
audio_buffer_.insert(audio_buffer_.end(), buffer.begin(), buffer.end());
|
||||
}
|
||||
|
||||
void audio_callback(Uint8 *stream, int len) {
|
||||
void audio_callback(Uint8 *const stream, const int len) {
|
||||
std::lock_guard lock_guard(audio_buffer_mutex_);
|
||||
|
||||
// SDL buffer length is in bytes, so there's no need to adjust for stereo/mono in here.
|
||||
@@ -196,14 +196,14 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
|
||||
const std::size_t copy_length = std::min(sample_length, audio_buffer_.size());
|
||||
int16_t *const target = static_cast<int16_t *>(static_cast<void *>(stream));
|
||||
|
||||
std::memcpy(stream, audio_buffer_.data(), copy_length * sizeof(int16_t));
|
||||
std::copy_n(audio_buffer_.begin(), copy_length, target);
|
||||
if(copy_length < sample_length) {
|
||||
std::memset(&target[copy_length], 0, (sample_length - copy_length) * sizeof(int16_t));
|
||||
std::fill(&target[copy_length], &target[sample_length], 0);
|
||||
}
|
||||
audio_buffer_.erase(audio_buffer_.begin(), audio_buffer_.begin() + copy_length);
|
||||
}
|
||||
|
||||
static void SDL_audio_callback(void *userdata, Uint8 *stream, int len) {
|
||||
static void SDL_audio_callback(void *const userdata, Uint8 *const stream, const int len) {
|
||||
reinterpret_cast<SpeakerDelegate *>(userdata)->audio_callback(stream, len);
|
||||
}
|
||||
|
||||
@@ -215,7 +215,11 @@ struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate {
|
||||
|
||||
class ActivityObserver: public Activity::Observer {
|
||||
public:
|
||||
ActivityObserver(Activity::Source *source, float aspect_ratio) {
|
||||
ActivityObserver(
|
||||
Outputs::Display::OpenGL::API api,
|
||||
Activity::Source *const source,
|
||||
const float aspect_ratio
|
||||
) : api_(api) {
|
||||
// Get the suorce to supply all LEDs and drives.
|
||||
source->set_activity_observer(this);
|
||||
|
||||
@@ -236,7 +240,7 @@ public:
|
||||
set_aspect_ratio(aspect_ratio);
|
||||
}
|
||||
|
||||
void set_aspect_ratio(float aspect_ratio) {
|
||||
void set_aspect_ratio(const float aspect_ratio) {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
lights_.clear();
|
||||
|
||||
@@ -246,7 +250,10 @@ public:
|
||||
const float right_x = 1.0f - 2.0f * width;
|
||||
float y = 1.0f - 2.0f * height;
|
||||
for(const auto &drive: drives_) {
|
||||
lights_.emplace(drive, std::make_unique<Outputs::Display::OpenGL::Rectangle>(right_x, y, width, height));
|
||||
lights_.emplace(
|
||||
drive,
|
||||
std::make_unique<Outputs::Display::OpenGL::Rectangle>(api_, right_x, y, width, height)
|
||||
);
|
||||
y -= height * 2.0f;
|
||||
}
|
||||
|
||||
@@ -273,6 +280,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
Outputs::Display::OpenGL::API api_;
|
||||
std::vector<std::string> leds_;
|
||||
void register_led(const std::string &name, uint8_t) final {
|
||||
std::lock_guard lock_guard(mutex);
|
||||
@@ -545,9 +553,12 @@ int main(int argc, char *argv[]) {
|
||||
if(arguments.selections.find("help") != arguments.selections.end() || arguments.selections.find("h") != arguments.selections.end()) {
|
||||
const auto all_machines = Machine::AllMachines(Machine::Type::DoesntRequireMedia, false);
|
||||
|
||||
std::cout << "Usage: " << final_path_component(argv[0]) << usage_suffix << std::endl;
|
||||
std::cout << "Use alt+enter to toggle full screen display. Use control+shift+V to paste text." << std::endl;
|
||||
std::cout << "Required machine type **and all options** are determined from the file if specified; otherwise use:" << std::endl << std::endl;
|
||||
std::cout << "Usage: " << final_path_component(argv[0]) << usage_suffix << std::endl << std::endl;
|
||||
std::cout <<
|
||||
"Use alt+enter to toggle full screen display. Use control+shift+V to paste text." << std::endl << std::endl;
|
||||
std::cout <<
|
||||
"Required machine type and hardware options are determined from the file if specified; otherwise use:"
|
||||
<< std::endl << std::endl;
|
||||
std::cout << "\t--new={";
|
||||
bool is_first = true;
|
||||
for(const auto &name: all_machines) {
|
||||
@@ -557,13 +568,14 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
std::cout << "}" << std::endl << std::endl;
|
||||
|
||||
std::cout << "Media is required to start the: ";
|
||||
std::cout << "A file is required to start the ";
|
||||
const auto other_machines = Machine::AllMachines(Machine::Type::RequiresMedia, true);
|
||||
is_first = true;
|
||||
size_t index = 0;
|
||||
for(const auto &name: other_machines) {
|
||||
if(!is_first) std::cout << ", ";
|
||||
if(index == other_machines.size() - 1) std::cout << " and ";
|
||||
else if(index > 0) std::cout << ", ";
|
||||
std::cout << name;
|
||||
is_first = false;
|
||||
++index;
|
||||
}
|
||||
std::cout << "." << std::endl << std::endl;
|
||||
|
||||
@@ -905,13 +917,15 @@ int main(int argc, char *argv[]) {
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &target_framebuffer);
|
||||
|
||||
// Setup output, assuming a CRT machine for now, and prepare a best-effort updater.
|
||||
Outputs::Display::OpenGL::ScanTarget scan_target(target_framebuffer);
|
||||
const auto api = Outputs::Display::OpenGL::API::OpenGL32Core;
|
||||
Outputs::Display::OpenGL::ScanTarget scan_target(api, target_framebuffer);
|
||||
std::unique_ptr<ActivityObserver> activity_observer;
|
||||
bool uses_mouse;
|
||||
std::vector<SDLJoystick> joysticks;
|
||||
|
||||
machine_runner.machine_mutex = &machine_mutex;
|
||||
const auto setup_machine_input_output = [&scan_target, &machine, &speaker_delegate, &activity_observer, &joysticks, &uses_mouse, &machine_runner] {
|
||||
const auto setup_machine_input_output =
|
||||
[&scan_target, &machine, &speaker_delegate, &activity_observer, &joysticks, &uses_mouse, &machine_runner, api] {
|
||||
// Wire up the best-effort updater, its delegate, and the speaker delegate.
|
||||
machine_runner.machine = machine.get();
|
||||
|
||||
@@ -934,9 +948,20 @@ int main(int argc, char *argv[]) {
|
||||
desired_audio_spec.callback = SpeakerDelegate::SDL_audio_callback;
|
||||
desired_audio_spec.userdata = &speaker_delegate;
|
||||
|
||||
speaker_delegate.audio_device = SDL_OpenAudioDevice(nullptr, 0, &desired_audio_spec, &obtained_audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
|
||||
speaker_delegate.audio_device =
|
||||
SDL_OpenAudioDevice(
|
||||
nullptr,
|
||||
0,
|
||||
&desired_audio_spec,
|
||||
&obtained_audio_spec,
|
||||
SDL_AUDIO_ALLOW_FREQUENCY_CHANGE
|
||||
);
|
||||
|
||||
speaker->set_output_rate(obtained_audio_spec.freq, desired_audio_spec.samples, obtained_audio_spec.channels == 2);
|
||||
speaker->set_output_rate(
|
||||
obtained_audio_spec.freq,
|
||||
desired_audio_spec.samples,
|
||||
obtained_audio_spec.channels == 2
|
||||
);
|
||||
speaker_delegate.is_stereo = obtained_audio_spec.channels == 2;
|
||||
speaker->set_delegate(&speaker_delegate);
|
||||
SDL_PauseAudioDevice(speaker_delegate.audio_device, 0);
|
||||
@@ -949,7 +974,7 @@ int main(int argc, char *argv[]) {
|
||||
*/
|
||||
Activity::Source *const activity_source = machine->activity_source();
|
||||
if(activity_source) {
|
||||
activity_observer = std::make_unique<ActivityObserver>(activity_source, 4.0f / 3.0f);
|
||||
activity_observer = std::make_unique<ActivityObserver>(api, activity_source, 4.0f / 3.0f);
|
||||
} else {
|
||||
activity_observer = nullptr;
|
||||
}
|
||||
|
||||
+7
-3
@@ -14,6 +14,7 @@
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <optional>
|
||||
|
||||
using namespace Outputs::CRT;
|
||||
using Logger = Log::Logger<Log::Source::CRT>;
|
||||
@@ -94,7 +95,7 @@ void CRT::set_new_timing(
|
||||
|
||||
// Default crop: middle 90%.
|
||||
if(is_first_set) {
|
||||
scan_target_modals_.visible_area = posted_rect_ = Display::Rect(
|
||||
posted_rect_ = scan_target_modals_.visible_area = Display::Rect(
|
||||
0.05f, 0.05f, 0.9f, 0.9f
|
||||
);
|
||||
}
|
||||
@@ -126,8 +127,10 @@ void CRT::set_dynamic_framing(
|
||||
|
||||
if(!has_first_reading_) {
|
||||
previous_posted_rect_ = posted_rect_ = scan_target_modals_.visible_area = initial;
|
||||
scan_target_->set_modals(scan_target_modals_);
|
||||
}
|
||||
has_first_reading_ = true;
|
||||
animation_step_ = AnimationSteps;
|
||||
}
|
||||
|
||||
void CRT::set_fixed_framing(const std::function<void()> &advance) {
|
||||
@@ -468,7 +471,7 @@ void CRT::posit(Display::Rect rect) {
|
||||
};
|
||||
|
||||
// Continue with any ongoing animation.
|
||||
if(animation_step_ < AnimationSteps) {
|
||||
if(animation_step_ != NoFrameYet && animation_step_ < AnimationSteps) {
|
||||
set_rect(current_rect());
|
||||
++animation_step_;
|
||||
if(animation_step_ == AnimationSteps) {
|
||||
@@ -506,8 +509,9 @@ void CRT::posit(Display::Rect rect) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const auto output_frame = rect_accumulator_.posit(rect);
|
||||
dynamic_framer_.update(rect, output_frame, first_reading);
|
||||
|
||||
+32
-23
@@ -13,6 +13,24 @@
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#ifdef TARGET_QT
|
||||
#include <QDebug>
|
||||
namespace {
|
||||
[[maybe_unused]] QDebug stream(const bool is_info) {
|
||||
return (is_info ? qInfo() : qWarning()).noquote().nospace();
|
||||
}
|
||||
static constexpr char EndLine = 0;
|
||||
}
|
||||
#else
|
||||
#include <iostream>
|
||||
namespace {
|
||||
[[maybe_unused]] std::ostream &stream(const bool is_info) {
|
||||
return is_info ? std::cout : std::cerr;
|
||||
}
|
||||
static constexpr char EndLine = '\n';
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Log {
|
||||
// TODO: if adopting C++20, std::format would be a better model to apply below.
|
||||
// But I prefer C files to C++ streams, so here it is for now.
|
||||
@@ -174,7 +192,7 @@ struct RepeatAccumulator {
|
||||
Source source;
|
||||
|
||||
size_t count = 0;
|
||||
FILE *stream;
|
||||
bool is_info;
|
||||
};
|
||||
|
||||
struct AccumulatingLog {
|
||||
@@ -184,11 +202,10 @@ struct AccumulatingLog {
|
||||
template <Source source>
|
||||
struct LogLine<source, true>: private AccumulatingLog {
|
||||
public:
|
||||
explicit LogLine(FILE *const stream) noexcept :
|
||||
stream_(stream) {}
|
||||
explicit LogLine(const bool is_info) noexcept : is_info_(is_info) {}
|
||||
|
||||
~LogLine() {
|
||||
if(output_ == accumulator_.last && source == accumulator_.source && stream_ == accumulator_.stream) {
|
||||
if(output_ == accumulator_.last && source == accumulator_.source && is_info_ == accumulator_.is_info) {
|
||||
++accumulator_.count;
|
||||
return;
|
||||
}
|
||||
@@ -202,34 +219,26 @@ public:
|
||||
prefix += "] ";
|
||||
}
|
||||
|
||||
auto &&out = stream(accumulator_.is_info);
|
||||
out << prefix.c_str() << accumulator_.last.c_str(); // Use of .c_str() avoids an ambiguity affecting older
|
||||
// versions of Qt.
|
||||
if(accumulator_.count > 1) {
|
||||
fprintf(
|
||||
accumulator_.stream,
|
||||
"%s%s [* %zu]\n",
|
||||
prefix.c_str(),
|
||||
accumulator_.last.c_str(),
|
||||
accumulator_.count
|
||||
);
|
||||
} else {
|
||||
fprintf(
|
||||
accumulator_.stream,
|
||||
"%s%s\n",
|
||||
prefix.c_str(),
|
||||
accumulator_.last.c_str()
|
||||
);
|
||||
out << " [* " << accumulator_.count << "]";
|
||||
}
|
||||
if(EndLine) out << EndLine;
|
||||
}
|
||||
|
||||
accumulator_.count = 1;
|
||||
accumulator_.last = output_;
|
||||
accumulator_.source = source;
|
||||
accumulator_.stream = stream_;
|
||||
accumulator_.is_info = is_info_;
|
||||
}
|
||||
|
||||
template <size_t size, typename... Args>
|
||||
auto &append(const char (&format)[size], Args... args) {
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat"
|
||||
#pragma GCC diagnostic ignored "-Wformat-security"
|
||||
const auto append_size = std::snprintf(nullptr, 0, format, args...);
|
||||
const auto end = output_.size();
|
||||
output_.resize(output_.size() + size_t(append_size) + 1);
|
||||
@@ -246,13 +255,13 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
FILE *stream_;
|
||||
bool is_info_;
|
||||
std::string output_;
|
||||
};
|
||||
|
||||
template <Source source>
|
||||
struct LogLine<source, false> {
|
||||
explicit LogLine(FILE *) noexcept {}
|
||||
explicit LogLine(bool) noexcept {}
|
||||
|
||||
template <size_t size, typename... Args>
|
||||
auto &append(const char (&)[size], Args...) { return *this; }
|
||||
@@ -269,8 +278,8 @@ public:
|
||||
static constexpr bool InfoEnabled = enabled_level(source) == EnabledLevel::ErrorsAndInfo;
|
||||
static constexpr bool ErrorsEnabled = enabled_level(source) >= EnabledLevel::Errors;
|
||||
|
||||
static auto info() { return LogLine<source, InfoEnabled>(stdout); }
|
||||
static auto error() { return LogLine<source, ErrorsEnabled>(stderr); }
|
||||
static auto info() { return LogLine<source, InfoEnabled>(true); }
|
||||
static auto error() { return LogLine<source, ErrorsEnabled>(false); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// API.hpp
|
||||
// Clock Signal
|
||||
//
|
||||
// Created by Thomas Harte on 16/12/2025.
|
||||
// Copyright © 2025 Thomas Harte. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Outputs::Display::OpenGL {
|
||||
enum class API {
|
||||
OpenGL32Core,
|
||||
OpenGLES3,
|
||||
};
|
||||
}
|
||||
+14
-15
@@ -30,25 +30,24 @@
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
#define test_gl_error() { \
|
||||
const auto error = glGetError(); \
|
||||
if(error) { \
|
||||
switch(error) { \
|
||||
default: std::cerr << "Error " << error; break; \
|
||||
case GL_INVALID_ENUM: std::cerr << "GL_INVALID_ENUM"; break; \
|
||||
case GL_INVALID_VALUE: std::cerr << "GL_INVALID_VALUE"; break; \
|
||||
case GL_INVALID_OPERATION: std::cerr << "GL_INVALID_OPERATION"; break; \
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION: std::cerr << "GL_INVALID_FRAMEBUFFER_OPERATION"; break; \
|
||||
case GL_OUT_OF_MEMORY: std::cerr << "GL_OUT_OF_MEMORY"; break; \
|
||||
}; \
|
||||
inline void test_gl_error() {
|
||||
const auto error = glGetError();
|
||||
if(error) {
|
||||
switch(error) {
|
||||
default: std::cerr << "Error " << error; break;
|
||||
case GL_INVALID_ENUM: std::cerr << "GL_INVALID_ENUM"; break;
|
||||
case GL_INVALID_VALUE: std::cerr << "GL_INVALID_VALUE"; break;
|
||||
case GL_INVALID_OPERATION: std::cerr << "GL_INVALID_OPERATION"; break;
|
||||
case GL_INVALID_FRAMEBUFFER_OPERATION: std::cerr << "GL_INVALID_FRAMEBUFFER_OPERATION"; break;
|
||||
case GL_OUT_OF_MEMORY: std::cerr << "GL_OUT_OF_MEMORY"; break;
|
||||
};
|
||||
std::cerr << " at line " << __LINE__ << " in " << __FILE__ << std::endl; \
|
||||
assert(false); \
|
||||
} \
|
||||
\
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
#define test_gl_error() while(false) {}
|
||||
inline void test_gl_error() {}
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
||||
@@ -10,26 +10,23 @@
|
||||
|
||||
using namespace Outputs::Display::OpenGL;
|
||||
|
||||
Rectangle::Rectangle(float x, float y, float width, float height):
|
||||
Rectangle::Rectangle(const API api, const float x, const float y, const float width, const float height):
|
||||
pixel_shader_(
|
||||
"#version 150\n"
|
||||
api,
|
||||
R"str(
|
||||
in vec2 position;
|
||||
void main(void) {
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
)str",
|
||||
R"str(
|
||||
uniform vec4 colour;
|
||||
out vec4 fragColour;
|
||||
|
||||
"in vec2 position;"
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"gl_Position = vec4(position, 0.0, 1.0);"
|
||||
"}",
|
||||
|
||||
"#version 150\n"
|
||||
|
||||
"uniform vec4 colour;"
|
||||
"out vec4 fragColour;"
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"fragColour = colour;"
|
||||
"}"
|
||||
void main(void) {
|
||||
fragColour = colour;
|
||||
}
|
||||
)str"
|
||||
){
|
||||
pixel_shader_.bind();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Outputs/OpenGL/API.hpp"
|
||||
#include "Outputs/OpenGL/OpenGL.hpp"
|
||||
#include "Shader.hpp"
|
||||
#include <memory>
|
||||
@@ -22,7 +23,7 @@ public:
|
||||
/*!
|
||||
Instantiates an instance of Rectange with the coordinates given.
|
||||
*/
|
||||
Rectangle(float x, float y, float width, float height);
|
||||
Rectangle(API, float x, float y, float width, float height);
|
||||
|
||||
/*!
|
||||
Draws this rectangle in the colour supplied.
|
||||
|
||||
@@ -20,8 +20,37 @@ using Logger = Log::Logger<Log::Source::OpenGL>;
|
||||
|
||||
GLuint Shader::compile_shader(const std::string &source, GLenum type) {
|
||||
GLuint shader = glCreateShader(type);
|
||||
const char *c_str = source.c_str();
|
||||
test_gl(glShaderSource, shader, 1, &c_str, NULL);
|
||||
|
||||
switch(api_) {
|
||||
case API::OpenGL32Core: {
|
||||
// Desktop OpenGL: ensure the precision specifiers act as no-ops
|
||||
// and request GLSL 1.5.
|
||||
const char *const sources[] = {
|
||||
R"glsl(
|
||||
#version 150
|
||||
#define highp
|
||||
#define mediump
|
||||
#define lowp
|
||||
)glsl",
|
||||
source.c_str()
|
||||
};
|
||||
test_gl(glShaderSource, shader, 2, sources, NULL);
|
||||
} break;
|
||||
case API::OpenGLES3:
|
||||
// OpenGL ES: supply default precisions for where they might have
|
||||
// been omitted and specify GLSL ES 3.0 as a floor. The project
|
||||
// otherwise assumes that integers and bitwise operations are available.
|
||||
const char *const sources[] = {
|
||||
R"glsl(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
precision highp usampler2D;
|
||||
)glsl",
|
||||
source.c_str()
|
||||
};
|
||||
test_gl(glShaderSource, shader, 2, sources, NULL);
|
||||
break;
|
||||
}
|
||||
test_gl(glCompileShader, shader);
|
||||
|
||||
if constexpr (Logger::ErrorsEnabled) {
|
||||
@@ -34,7 +63,9 @@ GLuint Shader::compile_shader(const std::string &source, GLenum type) {
|
||||
const auto length = std::vector<GLchar>::size_type(logLength);
|
||||
std::vector<GLchar> log(length);
|
||||
test_gl(glGetShaderInfoLog, shader, logLength, &logLength, log.data());
|
||||
Logger::error().append("Sought to compile: %s", source.c_str());
|
||||
Logger::error().append("Compile log: %s", log.data());
|
||||
Logger::error().append("");
|
||||
}
|
||||
|
||||
throw (type == GL_VERTEX_SHADER) ? VertexShaderCompilationError : FragmentShaderCompilationError;
|
||||
@@ -44,11 +75,21 @@ GLuint Shader::compile_shader(const std::string &source, GLenum type) {
|
||||
return shader;
|
||||
}
|
||||
|
||||
Shader::Shader(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector<AttributeBinding> &attribute_bindings) {
|
||||
Shader::Shader(
|
||||
const API api,
|
||||
const std::string &vertex_shader,
|
||||
const std::string &fragment_shader,
|
||||
const std::vector<AttributeBinding> &attribute_bindings
|
||||
) : api_(api) {
|
||||
init(vertex_shader, fragment_shader, attribute_bindings);
|
||||
}
|
||||
|
||||
Shader::Shader(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector<std::string> &binding_names) {
|
||||
Shader::Shader(
|
||||
const API api,
|
||||
const std::string &vertex_shader,
|
||||
const std::string &fragment_shader,
|
||||
const std::vector<std::string> &binding_names
|
||||
) : api_(api) {
|
||||
std::vector<AttributeBinding> bindings;
|
||||
GLuint index = 0;
|
||||
for(const auto &name: binding_names) {
|
||||
@@ -128,7 +169,9 @@ GLint Shader::get_attrib_location(const std::string &name) const {
|
||||
}
|
||||
|
||||
GLint Shader::get_uniform_location(const std::string &name) const {
|
||||
return glGetUniformLocation(shader_program_, name.c_str());
|
||||
const auto location = glGetUniformLocation(shader_program_, name.c_str());
|
||||
test_gl_error();
|
||||
return location;
|
||||
}
|
||||
|
||||
void Shader::enable_vertex_attribute_with_pointer(const std::string &name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor) {
|
||||
@@ -293,8 +336,9 @@ void Shader::enqueue_function(std::function<void(void)> function) {
|
||||
|
||||
void Shader::flush_functions() const {
|
||||
std::lock_guard function_guard(function_mutex_);
|
||||
for(std::function<void(void)> function : enqueued_functions_) {
|
||||
for(std::function<void(void)> &function : enqueued_functions_) {
|
||||
function();
|
||||
test_gl_error();
|
||||
}
|
||||
enqueued_functions_.clear();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Outputs/OpenGL/API.hpp"
|
||||
#include "Outputs/OpenGL/OpenGL.hpp"
|
||||
|
||||
#include <functional>
|
||||
@@ -31,25 +32,37 @@ public:
|
||||
};
|
||||
|
||||
struct AttributeBinding {
|
||||
AttributeBinding(const std::string &name, GLuint index) : name(name), index(index) {}
|
||||
AttributeBinding(const std::string &name, const GLuint index) : name(name), index(index) {}
|
||||
const std::string name;
|
||||
const GLuint index;
|
||||
};
|
||||
|
||||
/*!
|
||||
Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError or @c ProgramLinkageError upon failure.
|
||||
Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError
|
||||
or @c ProgramLinkageError upon failure.
|
||||
@param vertex_shader The vertex shader source code.
|
||||
@param fragment_shader The fragment shader source code.
|
||||
@param attribute_bindings A vector of attribute bindings.
|
||||
*/
|
||||
Shader(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector<AttributeBinding> &attribute_bindings = {});
|
||||
Shader(
|
||||
API,
|
||||
const std::string &vertex_shader,
|
||||
const std::string &fragment_shader,
|
||||
const std::vector<AttributeBinding> &attribute_bindings = {}
|
||||
);
|
||||
/*!
|
||||
Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError or @c ProgramLinkageError upon failure.
|
||||
Attempts to compile a shader, throwing @c VertexShaderCompilationError, @c FragmentShaderCompilationError
|
||||
or @c ProgramLinkageError upon failure.
|
||||
@param vertex_shader The vertex shader source code.
|
||||
@param fragment_shader The fragment shader source code.
|
||||
@param binding_names A list of attributes to generate bindings for; these will be given indices 0, 1, 2 ... n-1.
|
||||
*/
|
||||
Shader(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector<std::string> &binding_names);
|
||||
Shader(
|
||||
API,
|
||||
const std::string &vertex_shader,
|
||||
const std::string &fragment_shader,
|
||||
const std::vector<std::string> &binding_names
|
||||
);
|
||||
~Shader();
|
||||
|
||||
/*!
|
||||
@@ -87,7 +100,15 @@ public:
|
||||
* @c glVertexAttribPointer;
|
||||
* @c glVertexAttribDivisor.
|
||||
*/
|
||||
void enable_vertex_attribute_with_pointer(const std::string &name, GLint size, GLenum type, GLboolean normalised, GLsizei stride, const GLvoid *pointer, GLuint divisor);
|
||||
void enable_vertex_attribute_with_pointer(
|
||||
const std::string &name,
|
||||
GLint size,
|
||||
GLenum type,
|
||||
GLboolean normalised,
|
||||
GLsizei stride,
|
||||
const GLvoid *pointer,
|
||||
GLuint divisor
|
||||
);
|
||||
|
||||
/*!
|
||||
All @c set_uniforms queue up the requested uniform changes. Changes are applied automatically the next time the shader is bound.
|
||||
@@ -114,9 +135,14 @@ public:
|
||||
void set_uniform_matrix(const std::string &name, GLint size, GLsizei count, bool transpose, const GLfloat *values);
|
||||
|
||||
private:
|
||||
void init(const std::string &vertex_shader, const std::string &fragment_shader, const std::vector<AttributeBinding> &attribute_bindings);
|
||||
void init(
|
||||
const std::string &vertex_shader,
|
||||
const std::string &fragment_shader,
|
||||
const std::vector<AttributeBinding> &
|
||||
);
|
||||
|
||||
GLuint compile_shader(const std::string &source, GLenum type);
|
||||
API api_;
|
||||
GLuint shader_program_;
|
||||
|
||||
void flush_functions() const;
|
||||
|
||||
@@ -8,13 +8,19 @@
|
||||
|
||||
#include "TextureTarget.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace Outputs::Display::OpenGL;
|
||||
|
||||
TextureTarget::TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter, bool has_stencil_buffer) :
|
||||
TextureTarget::TextureTarget(
|
||||
const API api,
|
||||
const GLsizei width,
|
||||
const GLsizei height,
|
||||
const GLenum texture_unit,
|
||||
const GLint mag_filter,
|
||||
const bool has_stencil_buffer
|
||||
) :
|
||||
api_(api),
|
||||
width_(width),
|
||||
height_(height),
|
||||
texture_unit_(texture_unit) {
|
||||
@@ -93,36 +99,34 @@ void TextureTarget::bind_texture() const {
|
||||
test_gl(glBindTexture, GL_TEXTURE_2D, texture_);
|
||||
}
|
||||
|
||||
void TextureTarget::draw(float aspect_ratio, float colour_threshold) const {
|
||||
void TextureTarget::draw(const float aspect_ratio, const float colour_threshold) const {
|
||||
if(!pixel_shader_) {
|
||||
const char *vertex_shader =
|
||||
"#version 150\n"
|
||||
const char *const vertex_shader =
|
||||
R"str(
|
||||
in vec2 texCoord;
|
||||
in vec2 position;
|
||||
|
||||
"in vec2 texCoord;"
|
||||
"in vec2 position;"
|
||||
out vec2 texCoordVarying;
|
||||
|
||||
"out vec2 texCoordVarying;"
|
||||
void main(void) {
|
||||
texCoordVarying = texCoord;
|
||||
gl_Position = vec4(position, 0.0, 1.0);
|
||||
}
|
||||
)str";
|
||||
const char *const fragment_shader =
|
||||
R"str(
|
||||
in vec2 texCoordVarying;
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"texCoordVarying = texCoord;"
|
||||
"gl_Position = vec4(position, 0.0, 1.0);"
|
||||
"}";
|
||||
const char *fragment_shader =
|
||||
"#version 150\n"
|
||||
uniform sampler2D texID;
|
||||
uniform float threshold;
|
||||
|
||||
"in vec2 texCoordVarying;"
|
||||
out vec4 fragColour;
|
||||
|
||||
"uniform sampler2D texID;"
|
||||
"uniform float threshold;"
|
||||
|
||||
"out vec4 fragColour;"
|
||||
|
||||
"void main(void)"
|
||||
"{"
|
||||
"fragColour = clamp(texture(texID, texCoordVarying), threshold, 1.0);"
|
||||
"}";
|
||||
pixel_shader_ = std::make_unique<Shader>(vertex_shader, fragment_shader);
|
||||
void main(void) {
|
||||
fragColour = clamp(texture(texID, texCoordVarying), threshold, 1.0);
|
||||
}
|
||||
)str";
|
||||
pixel_shader_ = std::make_unique<Shader>(api_, vertex_shader, fragment_shader);
|
||||
pixel_shader_->bind();
|
||||
|
||||
test_gl(glGenVertexArrays, 1, &drawing_vertex_array_);
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Outputs/OpenGL/API.hpp"
|
||||
#include "Outputs/OpenGL/OpenGL.hpp"
|
||||
#include "Shader.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Outputs::Display::OpenGL {
|
||||
@@ -32,7 +34,7 @@ public:
|
||||
@param texture_unit A texture unit on which to bind the texture.
|
||||
@param has_stencil_buffer An 8-bit stencil buffer is attached if this is @c true; no stencil buffer is attached otherwise.
|
||||
*/
|
||||
TextureTarget(GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter, bool has_stencil_buffer);
|
||||
TextureTarget(API, GLsizei width, GLsizei height, GLenum texture_unit, GLint mag_filter, bool has_stencil_buffer);
|
||||
~TextureTarget();
|
||||
|
||||
/*!
|
||||
@@ -48,14 +50,14 @@ public:
|
||||
/*!
|
||||
@returns the width of the texture target.
|
||||
*/
|
||||
GLsizei get_width() const {
|
||||
GLsizei width() const {
|
||||
return width_;
|
||||
}
|
||||
|
||||
/*!
|
||||
@returns the height of the texture target.
|
||||
*/
|
||||
GLsizei get_height() const {
|
||||
GLsizei height() const {
|
||||
return height_;
|
||||
}
|
||||
|
||||
@@ -73,6 +75,7 @@ public:
|
||||
void draw(float aspect_ratio, float colour_threshold = 0.0f) const;
|
||||
|
||||
private:
|
||||
API api_;
|
||||
GLuint framebuffer_ = 0, texture_ = 0, renderbuffer_ = 0;
|
||||
const GLsizei width_ = 0, height_ = 0;
|
||||
GLsizei expanded_width_ = 0, expanded_height_ = 0;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "OpenGL.hpp"
|
||||
#include "Primitives/Rectangle.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
@@ -75,11 +76,12 @@ template <typename T> void ScanTarget::allocate_buffer(const T &array, GLuint &b
|
||||
test_gl(glBindBuffer, GL_ARRAY_BUFFER, buffer_name);
|
||||
}
|
||||
|
||||
ScanTarget::ScanTarget(GLuint target_framebuffer, float output_gamma) :
|
||||
ScanTarget::ScanTarget(const API api, const GLuint target_framebuffer, const float output_gamma) :
|
||||
api_(api),
|
||||
target_framebuffer_(target_framebuffer),
|
||||
output_gamma_(output_gamma),
|
||||
unprocessed_line_texture_(LineBufferWidth, LineBufferHeight, UnprocessedLineBufferTextureUnit, GL_NEAREST, false),
|
||||
full_display_rectangle_(-1.0f, -1.0f, 2.0f, 2.0f) {
|
||||
unprocessed_line_texture_(api, LineBufferWidth, LineBufferHeight, UnprocessedLineBufferTextureUnit, GL_NEAREST, false),
|
||||
full_display_rectangle_(api, -1.0f, -1.0f, 2.0f, 2.0f) {
|
||||
|
||||
set_scan_buffer(scan_buffer_.data(), scan_buffer_.size());
|
||||
set_line_buffer(line_buffer_.data(), line_metadata_buffer_.data(), line_buffer_.size());
|
||||
@@ -138,6 +140,7 @@ void ScanTarget::setup_pipeline() {
|
||||
if(needs_qam_buffer) {
|
||||
if(!qam_chroma_texture_) {
|
||||
qam_chroma_texture_ = std::make_unique<TextureTarget>(
|
||||
api_,
|
||||
LineBufferWidth,
|
||||
LineBufferHeight,
|
||||
QAMChromaTextureUnit,
|
||||
@@ -222,7 +225,7 @@ void ScanTarget::update(int, int output_height) {
|
||||
|
||||
// Grab the new output list.
|
||||
perform([&] {
|
||||
OutputArea area = get_output_area();
|
||||
const OutputArea area = get_output_area();
|
||||
|
||||
// Establish the pipeline if necessary.
|
||||
const auto new_modals = BufferingScanTarget::new_modals();
|
||||
@@ -236,24 +239,29 @@ void ScanTarget::update(int, int output_height) {
|
||||
lines_submitted_ = (area.end.line - area.start.line + line_buffer_.size()) % line_buffer_.size();
|
||||
|
||||
// Submit scans; only the new ones need to be communicated.
|
||||
size_t new_scans = (area.end.scan - area.start.scan + scan_buffer_.size()) % scan_buffer_.size();
|
||||
const size_t new_scans = (area.end.scan - area.start.scan + scan_buffer_.size()) % scan_buffer_.size();
|
||||
if(new_scans) {
|
||||
test_gl(glBindBuffer, GL_ARRAY_BUFFER, scan_buffer_name_);
|
||||
|
||||
// Map only the required portion of the buffer.
|
||||
const size_t new_scans_size = new_scans * sizeof(Scan);
|
||||
uint8_t *const destination = static_cast<uint8_t *>(
|
||||
glMapBufferRange(GL_ARRAY_BUFFER, 0, GLsizeiptr(new_scans_size), GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)
|
||||
const auto destination = static_cast<Scan *>(
|
||||
glMapBufferRange(
|
||||
GL_ARRAY_BUFFER,
|
||||
0,
|
||||
GLsizeiptr(new_scans_size),
|
||||
GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT
|
||||
)
|
||||
);
|
||||
test_gl_error();
|
||||
|
||||
// Copy as a single chunk if possible; otherwise copy in two parts.
|
||||
if(area.start.scan < area.end.scan) {
|
||||
memcpy(destination, &scan_buffer_[size_t(area.start.scan)], new_scans_size);
|
||||
std::copy_n(scan_buffer_.begin() + area.start.scan, new_scans, destination);
|
||||
} else {
|
||||
const size_t first_portion_length = (scan_buffer_.size() - area.start.scan) * sizeof(Scan);
|
||||
memcpy(destination, &scan_buffer_[area.start.scan], first_portion_length);
|
||||
memcpy(&destination[first_portion_length], &scan_buffer_[0], new_scans_size - first_portion_length);
|
||||
const size_t first_portion_count = scan_buffer_.size() - area.start.scan;
|
||||
std::copy_n(scan_buffer_.begin() + area.start.scan, first_portion_count, destination);
|
||||
std::copy_n(scan_buffer_.begin(), new_scans - first_portion_count, destination + first_portion_count);
|
||||
}
|
||||
|
||||
// Flush and unmap the buffer.
|
||||
@@ -338,12 +346,12 @@ void ScanTarget::update(int, int output_height) {
|
||||
}
|
||||
|
||||
if(first_line_to_clear < final_line_to_clear) {
|
||||
test_gl(glScissor, GLint(0), GLint(first_line_to_clear), unprocessed_line_texture_.get_width(), final_line_to_clear - first_line_to_clear);
|
||||
test_gl(glScissor, GLint(0), GLint(first_line_to_clear), unprocessed_line_texture_.width(), final_line_to_clear - first_line_to_clear);
|
||||
test_gl(glClear, GL_COLOR_BUFFER_BIT);
|
||||
} else {
|
||||
test_gl(glScissor, GLint(0), GLint(0), unprocessed_line_texture_.get_width(), final_line_to_clear);
|
||||
test_gl(glScissor, GLint(0), GLint(0), unprocessed_line_texture_.width(), final_line_to_clear);
|
||||
test_gl(glClear, GL_COLOR_BUFFER_BIT);
|
||||
test_gl(glScissor, GLint(0), GLint(first_line_to_clear), unprocessed_line_texture_.get_width(), unprocessed_line_texture_.get_height() - first_line_to_clear);
|
||||
test_gl(glScissor, GLint(0), GLint(first_line_to_clear), unprocessed_line_texture_.width(), unprocessed_line_texture_.height() - first_line_to_clear);
|
||||
test_gl(glClear, GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
@@ -372,20 +380,26 @@ void ScanTarget::update(int, int output_height) {
|
||||
// feelings about whether too high a resolution is being used.
|
||||
const int framebuffer_height = std::max(output_height / resolution_reduction_level_, std::min(540, output_height));
|
||||
const int proportional_width = (framebuffer_height * 4) / 3;
|
||||
const bool did_create_accumulation_texture = !accumulation_texture_ || ( (accumulation_texture_->get_width() != proportional_width || accumulation_texture_->get_height() != framebuffer_height));
|
||||
const bool did_create_accumulation_texture =
|
||||
!accumulation_texture_ ||
|
||||
(
|
||||
accumulation_texture_->width() != proportional_width ||
|
||||
accumulation_texture_->height() != framebuffer_height
|
||||
);
|
||||
|
||||
// Work with the accumulation_buffer_ potentially starts from here onwards; set its flag.
|
||||
while(is_drawing_to_accumulation_buffer_.test_and_set());
|
||||
if(did_create_accumulation_texture) {
|
||||
Logger::info().append("Changed output resolution to %d by %d", proportional_width, framebuffer_height);
|
||||
display_metrics_.announce_did_resize();
|
||||
std::unique_ptr<OpenGL::TextureTarget> new_framebuffer(
|
||||
new TextureTarget(
|
||||
GLsizei(proportional_width),
|
||||
GLsizei(framebuffer_height),
|
||||
AccumulationTextureUnit,
|
||||
GL_NEAREST,
|
||||
true));
|
||||
auto new_framebuffer = std::make_unique<TextureTarget>(
|
||||
api_,
|
||||
GLsizei(proportional_width),
|
||||
GLsizei(framebuffer_height),
|
||||
AccumulationTextureUnit,
|
||||
GL_NEAREST,
|
||||
true
|
||||
);
|
||||
if(accumulation_texture_) {
|
||||
new_framebuffer->bind_framebuffer();
|
||||
test_gl(glClear, GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
@@ -465,16 +479,19 @@ void ScanTarget::update(int, int output_height) {
|
||||
if(!end_line || end_line > start_line) {
|
||||
test_gl(glBufferSubData, GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size), &line_buffer_[start_line]);
|
||||
} else {
|
||||
uint8_t *destination = static_cast<uint8_t *>(
|
||||
glMapBufferRange(GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size), GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT)
|
||||
auto destination = static_cast<Line *>(
|
||||
glMapBufferRange(
|
||||
GL_ARRAY_BUFFER,
|
||||
0,
|
||||
GLsizeiptr(buffer_size),
|
||||
GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT
|
||||
)
|
||||
);
|
||||
assert(destination);
|
||||
test_gl_error();
|
||||
|
||||
const size_t buffer_length = line_buffer_.size() * sizeof(Line);
|
||||
const size_t start_position = start_line * sizeof(Line);
|
||||
memcpy(&destination[0], &line_buffer_[start_line], buffer_length - start_position);
|
||||
memcpy(&destination[buffer_length - start_position], &line_buffer_[0], end_line * sizeof(Line));
|
||||
std::copy_n(line_buffer_.begin(), end_line, destination + line_buffer_.size() - start_line);
|
||||
std::copy_n(line_buffer_.begin() + start_line, line_buffer_.size() - start_line, destination);
|
||||
|
||||
test_gl(glFlushMappedBufferRange, GL_ARRAY_BUFFER, 0, GLsizeiptr(buffer_size));
|
||||
test_gl(glUnmapBuffer, GL_ARRAY_BUFFER);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "Outputs/DisplayMetrics.hpp"
|
||||
#include "Outputs/ScanTargets/BufferingScanTarget.hpp"
|
||||
|
||||
#include "API.hpp"
|
||||
#include "OpenGL.hpp"
|
||||
#include "Primitives/TextureTarget.hpp"
|
||||
#include "Primitives/Rectangle.hpp"
|
||||
@@ -24,12 +25,12 @@
|
||||
#include <chrono>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Outputs::Display::OpenGL {
|
||||
|
||||
|
||||
/*!
|
||||
Provides a ScanTarget that uses OpenGL to render its output;
|
||||
this uses various internal buffers so that the only geometry
|
||||
@@ -37,7 +38,7 @@ namespace Outputs::Display::OpenGL {
|
||||
*/
|
||||
class ScanTarget: public Outputs::Display::BufferingScanTarget { // TODO: use private inheritance and expose only display_metrics() and a custom cast?
|
||||
public:
|
||||
ScanTarget(GLuint target_framebuffer = 0, float output_gamma = 2.2f);
|
||||
ScanTarget(API, GLuint target_framebuffer = 0, float output_gamma = 2.2f);
|
||||
~ScanTarget();
|
||||
|
||||
void set_target_framebuffer(GLuint);
|
||||
@@ -48,6 +49,7 @@ public:
|
||||
void update(int output_width, int output_height);
|
||||
|
||||
private:
|
||||
API api_;
|
||||
static constexpr int LineBufferWidth = 2048;
|
||||
static constexpr int LineBufferHeight = 2048;
|
||||
|
||||
|
||||
@@ -212,8 +212,8 @@ std::string ScanTarget::sampling_function() const {
|
||||
// Easy, just copy across.
|
||||
fragment_shader +=
|
||||
is_svideo ?
|
||||
"return vec2(textureLod(textureName, coordinate, 0).r, 0.0);" :
|
||||
"return textureLod(textureName, coordinate, 0).r;";
|
||||
"return vec2(textureLod(textureName, coordinate, 0.0).r, 0.0);" :
|
||||
"return textureLod(textureName, coordinate, 0.0).r;";
|
||||
break;
|
||||
|
||||
case InputDataType::PhaseLinkedLuminance8:
|
||||
@@ -222,13 +222,13 @@ std::string ScanTarget::sampling_function() const {
|
||||
|
||||
fragment_shader +=
|
||||
is_svideo ?
|
||||
"return vec2(textureLod(textureName, coordinate, 0)[iPhase], 0.0);" :
|
||||
"return textureLod(textureName, coordinate, 0)[iPhase];";
|
||||
"return vec2(textureLod(textureName, coordinate, 0.0)[iPhase], 0.0);" :
|
||||
"return textureLod(textureName, coordinate, 0.0)[iPhase];";
|
||||
break;
|
||||
|
||||
case InputDataType::Luminance8Phase8:
|
||||
fragment_shader +=
|
||||
"vec2 yc = textureLod(textureName, coordinate, 0).rg;"
|
||||
"vec2 yc = textureLod(textureName, coordinate, 0.0).rg;"
|
||||
|
||||
"float phaseOffset = 3.141592654 * 2.0 * 2.0 * yc.y;"
|
||||
"float rawChroma = step(yc.y, 0.75) * cos(angle + phaseOffset);";
|
||||
@@ -244,7 +244,7 @@ std::string ScanTarget::sampling_function() const {
|
||||
case InputDataType::Red4Green4Blue4:
|
||||
case InputDataType::Red8Green8Blue8:
|
||||
fragment_shader +=
|
||||
"vec3 colour = rgbToLumaChroma * textureLod(textureName, coordinate, 0).rgb;"
|
||||
"vec3 colour = rgbToLumaChroma * textureLod(textureName, coordinate, 0.0).rgb;"
|
||||
"vec2 quadrature = vec2(cos(angle), sin(angle));";
|
||||
|
||||
fragment_shader +=
|
||||
@@ -274,8 +274,6 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
// If the display type is S-Video, generate three textureCoordinates, at
|
||||
// -45, 0, +45.
|
||||
std::string vertex_shader = R"glsl(
|
||||
#version 150
|
||||
|
||||
uniform vec2 scale;
|
||||
uniform float rowHeight;
|
||||
|
||||
@@ -300,8 +298,6 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
)glsl";
|
||||
|
||||
std::string fragment_shader = R"glsl(
|
||||
#version 150
|
||||
|
||||
uniform sampler2D textureName;
|
||||
uniform sampler2D qamTextureName;
|
||||
|
||||
@@ -354,20 +350,20 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
|
||||
vertex_shader += R"glsl(
|
||||
float centreClock = mix(startClock, endClock, lateral);
|
||||
textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);
|
||||
textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / textureSize(textureName, 0);
|
||||
textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / textureSize(textureName, 0);
|
||||
textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);
|
||||
textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
)glsl";
|
||||
|
||||
if((modals.display_type == DisplayType::SVideo) || (modals.display_type == DisplayType::CompositeColour)) {
|
||||
vertex_shader += R"glsl(
|
||||
float centreCompositeAngle = abs(mix(startCompositeAngle, endCompositeAngle, lateral)) * 4.0 / 64.0;
|
||||
centreCompositeAngle = floor(centreCompositeAngle);
|
||||
qamTextureCoordinates[0] = vec2(centreCompositeAngle - 1.5, lineY + 0.5) / textureSize(textureName, 0);
|
||||
qamTextureCoordinates[1] = vec2(centreCompositeAngle - 0.5, lineY + 0.5) / textureSize(textureName, 0);
|
||||
qamTextureCoordinates[2] = vec2(centreCompositeAngle + 0.5, lineY + 0.5) / textureSize(textureName, 0);
|
||||
qamTextureCoordinates[3] = vec2(centreCompositeAngle + 1.5, lineY + 0.5) / textureSize(textureName, 0);
|
||||
qamTextureCoordinates[0] = vec2(centreCompositeAngle - 1.5, lineY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
qamTextureCoordinates[1] = vec2(centreCompositeAngle - 0.5, lineY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
qamTextureCoordinates[2] = vec2(centreCompositeAngle + 0.5, lineY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
qamTextureCoordinates[3] = vec2(centreCompositeAngle + 1.5, lineY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
)glsl";
|
||||
}
|
||||
|
||||
@@ -414,10 +410,10 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
|
||||
// Split and average chrominance.
|
||||
vec2 chrominances[4] = vec2[4](
|
||||
textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb
|
||||
textureLod(qamTextureName, qamTextureCoordinates[0], 0.0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[1], 0.0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[2], 0.0).gb,
|
||||
textureLod(qamTextureName, qamTextureCoordinates[3], 0.0).gb
|
||||
);
|
||||
vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);
|
||||
|
||||
@@ -442,10 +438,10 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
case DisplayType::RGB:
|
||||
fragment_shader +=
|
||||
"vec3 samples[4] = vec3[4]("
|
||||
"textureLod(textureName, textureCoordinates[0], 0).rgb,"
|
||||
"textureLod(textureName, textureCoordinates[1], 0).rgb,"
|
||||
"textureLod(textureName, textureCoordinates[2], 0).rgb,"
|
||||
"textureLod(textureName, textureCoordinates[3], 0).rgb"
|
||||
"textureLod(textureName, textureCoordinates[0], 0.0).rgb,"
|
||||
"textureLod(textureName, textureCoordinates[1], 0.0).rgb,"
|
||||
"textureLod(textureName, textureCoordinates[2], 0.0).rgb,"
|
||||
"textureLod(textureName, textureCoordinates[3], 0.0).rgb"
|
||||
");"
|
||||
"fragColour3 = samples[0]*0.15 + samples[1]*0.35 + samples[2]*0.35 + samples[2]*0.15;";
|
||||
break;
|
||||
@@ -464,10 +460,10 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
|
||||
// Split and average chrominance.
|
||||
"vec2 chrominances[4] = vec2[4]("
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[0], 0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[1], 0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[2], 0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[3], 0).gb"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[0], 0.0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[1], 0.0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[2], 0.0).gb,"
|
||||
"textureLod(qamTextureName, qamTextureCoordinates[3], 0.0).gb"
|
||||
");"
|
||||
"vec2 channels = (chrominances[0] + chrominances[1] + chrominances[2] + chrominances[3])*0.5 - vec2(1.0);"
|
||||
|
||||
@@ -492,6 +488,7 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
"}";
|
||||
|
||||
return std::make_unique<Shader>(
|
||||
api_,
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
bindings(ShaderType::Conversion)
|
||||
@@ -501,8 +498,6 @@ std::unique_ptr<Shader> ScanTarget::conversion_shader() const {
|
||||
std::unique_ptr<Shader> ScanTarget::composition_shader() const {
|
||||
const auto modals = BufferingScanTarget::modals();
|
||||
const std::string vertex_shader = R"glsl(
|
||||
#version 150
|
||||
|
||||
in float startDataX;
|
||||
in float startClock;
|
||||
|
||||
@@ -519,15 +514,13 @@ std::unique_ptr<Shader> ScanTarget::composition_shader() const {
|
||||
float lateral = float(gl_VertexID & 1);
|
||||
float longitudinal = float((gl_VertexID & 2) >> 1);
|
||||
|
||||
textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY + 0.5) / textureSize(textureName, 0);
|
||||
textureCoordinate = vec2(mix(startDataX, endDataX, lateral), dataY + 0.5) / vec2(textureSize(textureName, 0));
|
||||
vec2 eyePosition = vec2(mix(startClock, endClock, lateral), lineY + longitudinal) / vec2(2048.0, 2048.0);
|
||||
gl_Position = vec4(eyePosition*2.0 - vec2(1.0), 0.0, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
|
||||
std::string fragment_shader =
|
||||
R"x(#version 150
|
||||
|
||||
std::string fragment_shader = R"x(
|
||||
out vec4 fragColour;
|
||||
in vec2 textureCoordinate;
|
||||
|
||||
@@ -538,37 +531,38 @@ std::unique_ptr<Shader> ScanTarget::composition_shader() const {
|
||||
|
||||
switch(modals.input_data_type) {
|
||||
case InputDataType::Luminance1:
|
||||
fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0).rrrr;";
|
||||
fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0.0).rrrr;";
|
||||
break;
|
||||
|
||||
case InputDataType::Luminance8:
|
||||
fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0).rrrr / vec4(255.0);";
|
||||
fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0.0).rrrr / vec4(255.0);";
|
||||
break;
|
||||
|
||||
case InputDataType::PhaseLinkedLuminance8:
|
||||
case InputDataType::Luminance8Phase8:
|
||||
case InputDataType::Red8Green8Blue8:
|
||||
fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0) / vec4(255.0);";
|
||||
fragment_shader += "fragColour = textureLod(textureName, textureCoordinate, 0.0) / vec4(255.0);";
|
||||
break;
|
||||
|
||||
case InputDataType::Red1Green1Blue1:
|
||||
fragment_shader += "fragColour = vec4(textureLod(textureName, textureCoordinate, 0).rrr & uvec3(4u, 2u, 1u), 1.0);";
|
||||
fragment_shader += "fragColour = vec4(textureLod(textureName, textureCoordinate, 0.0).rrr & uvec3(4u, 2u, 1u), 1.0);";
|
||||
break;
|
||||
|
||||
case InputDataType::Red2Green2Blue2:
|
||||
fragment_shader +=
|
||||
"uint textureValue = textureLod(textureName, textureCoordinate, 0).r;"
|
||||
"uint textureValue = textureLod(textureName, textureCoordinate, 0.0).r;"
|
||||
"fragColour = vec4(float((textureValue >> 4) & 3u), float((textureValue >> 2) & 3u), float(textureValue & 3u), 3.0) / 3.0;";
|
||||
break;
|
||||
|
||||
case InputDataType::Red4Green4Blue4:
|
||||
fragment_shader +=
|
||||
"uvec2 textureValue = textureLod(textureName, textureCoordinate, 0).rg;"
|
||||
"uvec2 textureValue = textureLod(textureName, textureCoordinate, 0.0).rg;"
|
||||
"fragColour = vec4(float(textureValue.r) / 15.0, float(textureValue.g & 240u) / 240.0, float(textureValue.g & 15u) / 15.0, 1.0);";
|
||||
break;
|
||||
}
|
||||
|
||||
return std::make_unique<Shader>(
|
||||
api_,
|
||||
vertex_shader,
|
||||
fragment_shader + "}",
|
||||
bindings(ShaderType::Composition)
|
||||
@@ -582,8 +576,6 @@ std::unique_ptr<Shader> ScanTarget::qam_separation_shader() const {
|
||||
// Sets up texture coordinates to run between startClock and endClock, mapping to
|
||||
// coordinates that correlate with four times the absolute value of the composite angle.
|
||||
std::string vertex_shader =
|
||||
"#version 150\n"
|
||||
|
||||
"in float startClock;"
|
||||
"in float startCompositeAngle;"
|
||||
"in float endClock;"
|
||||
@@ -600,8 +592,6 @@ std::unique_ptr<Shader> ScanTarget::qam_separation_shader() const {
|
||||
"out float oneOverCompositeAmplitude;";
|
||||
|
||||
std::string fragment_shader =
|
||||
"#version 150\n"
|
||||
|
||||
"uniform sampler2D textureName;"
|
||||
"uniform mat3 rgbToLumaChroma;"
|
||||
|
||||
@@ -638,13 +628,13 @@ std::unique_ptr<Shader> ScanTarget::qam_separation_shader() const {
|
||||
|
||||
if(is_svideo) {
|
||||
vertex_shader +=
|
||||
"textureCoordinate = vec2(centreClock, lineY + 0.5) / textureSize(textureName, 0);";
|
||||
"textureCoordinate = vec2(centreClock, lineY + 0.5) / vec2(textureSize(textureName, 0));";
|
||||
} else {
|
||||
vertex_shader +=
|
||||
"textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / textureSize(textureName, 0);"
|
||||
"textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / textureSize(textureName, 0);";
|
||||
"textureCoordinates[0] = vec2(centreClock + textureCoordinateOffsets[0], lineY + 0.5) / vec2(textureSize(textureName, 0));"
|
||||
"textureCoordinates[1] = vec2(centreClock + textureCoordinateOffsets[1], lineY + 0.5) / vec2(textureSize(textureName, 0));"
|
||||
"textureCoordinates[2] = vec2(centreClock + textureCoordinateOffsets[2], lineY + 0.5) / vec2(textureSize(textureName, 0));"
|
||||
"textureCoordinates[3] = vec2(centreClock + textureCoordinateOffsets[3], lineY + 0.5) / vec2(textureSize(textureName, 0));";
|
||||
}
|
||||
|
||||
vertex_shader += "}";
|
||||
@@ -682,6 +672,7 @@ std::unique_ptr<Shader> ScanTarget::qam_separation_shader() const {
|
||||
"}";
|
||||
|
||||
return std::make_unique<Shader>(
|
||||
api_,
|
||||
vertex_shader,
|
||||
fragment_shader,
|
||||
bindings(ShaderType::QAMSeparation)
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "OpenGL.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Outputs::Display::OpenGL {
|
||||
@@ -21,7 +22,7 @@ namespace Outputs::Display::OpenGL {
|
||||
The image will then be available as RGBA data, in raster order via the struct members.
|
||||
*/
|
||||
struct Screenshot {
|
||||
Screenshot(int aspect_width, int aspect_height) {
|
||||
Screenshot(const int aspect_width, const int aspect_height) {
|
||||
// Get the current viewport to establish framebuffer size. Then determine how wide the
|
||||
// centre portion of that would be, allowing for the requested aspect ratio.
|
||||
GLint dimensions[4];
|
||||
@@ -39,13 +40,11 @@ struct Screenshot {
|
||||
|
||||
// Flip the contents into raster order.
|
||||
const size_t line_size = size_t(width * 4);
|
||||
std::vector temp(line_size, 0);
|
||||
for(size_t y = 0; y < size_t(height) / 2; ++y) {
|
||||
const size_t flipped_y = size_t(height - 1) - y;
|
||||
|
||||
memcpy(temp.data(), &pixel_data[flipped_y * line_size], line_size);
|
||||
memcpy(&pixel_data[flipped_y * line_size], &pixel_data[y * line_size], line_size);
|
||||
memcpy(&pixel_data[y * line_size], temp.data(), line_size);
|
||||
for(size_t x = 0; x < line_size; x++) {
|
||||
std::swap(pixel_data[flipped_y * line_size + x], pixel_data[y * line_size + x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,20 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#define TextureAddressGetY(v) uint16_t((v) >> 11)
|
||||
#define TextureAddressGetX(v) uint16_t((v) & 0x7ff)
|
||||
#define TextureSub(a, b) (((a) - (b)) & 0x3fffff)
|
||||
#define TextureAddress(x, y) (((y) << 11) | (x))
|
||||
namespace {
|
||||
constexpr uint16_t texture_address_get_y(const int32_t address) {
|
||||
return uint16_t(address >> 11);
|
||||
}
|
||||
constexpr uint16_t texture_address_get_x(const int32_t address) {
|
||||
return uint16_t(address & 0x7ff);
|
||||
}
|
||||
constexpr int32_t texture_address(const uint16_t x, const uint16_t y) {
|
||||
return (y << 11) | x;
|
||||
}
|
||||
constexpr int32_t texture_address_sub(const int32_t a, const int32_t b) {
|
||||
return (a - b) & 0x3fffff;
|
||||
};
|
||||
}
|
||||
|
||||
using namespace Outputs::Display;
|
||||
|
||||
@@ -29,7 +39,7 @@ BufferingScanTarget::BufferingScanTarget() {
|
||||
|
||||
// MARK: - Producer; pixel data.
|
||||
|
||||
uint8_t *BufferingScanTarget::begin_data(size_t required_length, size_t required_alignment) {
|
||||
uint8_t *BufferingScanTarget::begin_data(const size_t required_length, const size_t required_alignment) {
|
||||
assert(required_alignment);
|
||||
|
||||
// Acquire the standard producer lock, nominally over write_pointers_.
|
||||
@@ -45,9 +55,9 @@ uint8_t *BufferingScanTarget::begin_data(size_t required_length, size_t required
|
||||
}
|
||||
|
||||
// Determine where the proposed write area would start and end.
|
||||
uint16_t output_y = TextureAddressGetY(write_pointers_.write_area);
|
||||
uint16_t output_y = texture_address_get_y(write_pointers_.write_area);
|
||||
|
||||
uint16_t aligned_start_x = TextureAddressGetX(write_pointers_.write_area & 0xffff) + 1;
|
||||
uint16_t aligned_start_x = texture_address_get_x(write_pointers_.write_area & 0xffff) + 1;
|
||||
aligned_start_x += uint16_t((required_alignment - aligned_start_x%required_alignment)%required_alignment);
|
||||
|
||||
uint16_t end_x = aligned_start_x + uint16_t(1 + required_length);
|
||||
@@ -60,11 +70,11 @@ uint8_t *BufferingScanTarget::begin_data(size_t required_length, size_t required
|
||||
|
||||
// Check whether that steps over the read pointer; if so then the final address will be closer
|
||||
// to the write pointer than the old.
|
||||
const auto end_address = TextureAddress(end_x, output_y);
|
||||
const auto end_address = texture_address(end_x, output_y);
|
||||
const auto read_pointers = read_pointers_.load(std::memory_order_relaxed);
|
||||
|
||||
const auto end_distance = TextureSub(end_address, read_pointers.write_area);
|
||||
const auto previous_distance = TextureSub(write_pointers_.write_area, read_pointers.write_area);
|
||||
const auto end_distance = texture_address_sub(end_address, read_pointers.write_area);
|
||||
const auto previous_distance = texture_address_sub(write_pointers_.write_area, read_pointers.write_area);
|
||||
|
||||
// Perform a quick sanity check.
|
||||
assert(end_distance >= 0);
|
||||
@@ -80,23 +90,26 @@ uint8_t *BufferingScanTarget::begin_data(size_t required_length, size_t required
|
||||
// Everything checks out, note expectation of a future end_data and return the pointer.
|
||||
assert(!data_is_allocated_);
|
||||
data_is_allocated_ = true;
|
||||
vended_write_area_pointer_ = write_pointers_.write_area = TextureAddress(aligned_start_x, output_y);
|
||||
vended_write_area_pointer_ = write_pointers_.write_area = texture_address(aligned_start_x, output_y);
|
||||
|
||||
assert(write_pointers_.write_area >= 1 && ((size_t(write_pointers_.write_area) + required_length + 1) * data_type_size_) <= WriteAreaWidth*WriteAreaHeight*data_type_size_);
|
||||
assert(
|
||||
write_pointers_.write_area >= 1 &&
|
||||
((size_t(write_pointers_.write_area) + required_length + 1) * data_type_size_)
|
||||
<= WriteAreaWidth*WriteAreaHeight*data_type_size_);
|
||||
return &write_area_[size_t(write_pointers_.write_area) * data_type_size_];
|
||||
|
||||
// Note state at exit:
|
||||
// write_pointers_.write_area points to the first pixel the client is expected to draw to.
|
||||
}
|
||||
|
||||
template <typename DataUnit> void BufferingScanTarget::end_data(size_t actual_length) {
|
||||
template <typename DataUnit> void BufferingScanTarget::end_data(const size_t actual_length) {
|
||||
// Bookend the start and end of the new data, to safeguard for precision errors in sampling.
|
||||
DataUnit *const sized_write_area = &reinterpret_cast<DataUnit *>(write_area_)[write_pointers_.write_area];
|
||||
sized_write_area[-1] = sized_write_area[0];
|
||||
sized_write_area[actual_length] = sized_write_area[actual_length - 1];
|
||||
}
|
||||
|
||||
void BufferingScanTarget::end_data(size_t actual_length) {
|
||||
void BufferingScanTarget::end_data(const size_t actual_length) {
|
||||
// Acquire the producer lock.
|
||||
std::lock_guard lock_guard(producer_mutex_);
|
||||
|
||||
@@ -181,17 +194,22 @@ void BufferingScanTarget::end_scan() {
|
||||
|
||||
// Complete the scan only if one is afoot.
|
||||
if(vended_scan_) {
|
||||
vended_scan_->data_y = TextureAddressGetY(vended_write_area_pointer_);
|
||||
vended_scan_->data_y = texture_address_get_y(vended_write_area_pointer_);
|
||||
vended_scan_->line = write_pointers_.line;
|
||||
vended_scan_->scan.end_points[0].data_offset += TextureAddressGetX(vended_write_area_pointer_);
|
||||
vended_scan_->scan.end_points[1].data_offset += TextureAddressGetX(vended_write_area_pointer_);
|
||||
vended_scan_->scan.end_points[0].data_offset += texture_address_get_x(vended_write_area_pointer_);
|
||||
vended_scan_->scan.end_points[1].data_offset += texture_address_get_x(vended_write_area_pointer_);
|
||||
vended_scan_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Producer; lines.
|
||||
|
||||
void BufferingScanTarget::announce(Event event, bool is_visible, const Outputs::Display::ScanTarget::Scan::EndPoint &location, uint8_t composite_amplitude) {
|
||||
void BufferingScanTarget::announce(
|
||||
const Event event,
|
||||
const bool is_visible,
|
||||
const Outputs::Display::ScanTarget::Scan::EndPoint &location,
|
||||
const uint8_t composite_amplitude
|
||||
) {
|
||||
std::lock_guard lock_guard(producer_mutex_);
|
||||
|
||||
// Forward the event to the display metrics tracker.
|
||||
@@ -226,7 +244,8 @@ void BufferingScanTarget::announce(Event event, bool is_visible, const Outputs::
|
||||
Line &active_line = line_buffer_[size_t(write_pointers_.line)];
|
||||
active_line.end_points[0].x = location.x;
|
||||
active_line.end_points[0].y = location.y;
|
||||
active_line.end_points[0].cycles_since_end_of_horizontal_retrace = location.cycles_since_end_of_horizontal_retrace;
|
||||
active_line.end_points[0].cycles_since_end_of_horizontal_retrace
|
||||
= location.cycles_since_end_of_horizontal_retrace;
|
||||
active_line.end_points[0].composite_angle = location.composite_angle;
|
||||
active_line.line = write_pointers_.line;
|
||||
active_line.composite_amplitude = composite_amplitude;
|
||||
@@ -252,7 +271,8 @@ void BufferingScanTarget::announce(Event event, bool is_visible, const Outputs::
|
||||
Line &active_line = line_buffer_[size_t(write_pointers_.line)];
|
||||
active_line.end_points[1].x = location.x;
|
||||
active_line.end_points[1].y = location.y;
|
||||
active_line.end_points[1].cycles_since_end_of_horizontal_retrace = location.cycles_since_end_of_horizontal_retrace;
|
||||
active_line.end_points[1].cycles_since_end_of_horizontal_retrace
|
||||
= location.cycles_since_end_of_horizontal_retrace;
|
||||
active_line.end_points[1].composite_angle = location.composite_angle;
|
||||
|
||||
// Advance the line pointer.
|
||||
@@ -285,7 +305,7 @@ const Outputs::Display::Metrics &BufferingScanTarget::display_metrics() {
|
||||
return display_metrics_;
|
||||
}
|
||||
|
||||
void BufferingScanTarget::set_write_area(uint8_t *base) {
|
||||
void BufferingScanTarget::set_write_area(uint8_t *const base) {
|
||||
std::lock_guard lock_guard(producer_mutex_);
|
||||
write_area_ = base;
|
||||
write_pointers_ = submit_pointers_ = read_pointers_ = PointerSet();
|
||||
@@ -299,7 +319,7 @@ size_t BufferingScanTarget::write_area_data_size() const {
|
||||
return data_type_size_;
|
||||
}
|
||||
|
||||
void BufferingScanTarget::set_modals(Modals modals) {
|
||||
void BufferingScanTarget::set_modals(const Modals modals) {
|
||||
perform([&] {
|
||||
modals_ = modals;
|
||||
modals_are_dirty_.store(true, std::memory_order_relaxed);
|
||||
@@ -324,10 +344,10 @@ BufferingScanTarget::OutputArea BufferingScanTarget::get_output_area() {
|
||||
area.start.scan = read_ahead_pointers.scan;
|
||||
area.end.scan = submit_pointers.scan;
|
||||
|
||||
area.start.write_area_x = TextureAddressGetX(read_ahead_pointers.write_area);
|
||||
area.start.write_area_y = TextureAddressGetY(read_ahead_pointers.write_area);
|
||||
area.end.write_area_x = TextureAddressGetX(submit_pointers.write_area);
|
||||
area.end.write_area_y = TextureAddressGetY(submit_pointers.write_area);
|
||||
area.start.write_area_x = texture_address_get_x(read_ahead_pointers.write_area);
|
||||
area.start.write_area_y = texture_address_get_y(read_ahead_pointers.write_area);
|
||||
area.end.write_area_x = texture_address_get_x(submit_pointers.write_area);
|
||||
area.end.write_area_y = texture_address_get_y(submit_pointers.write_area);
|
||||
|
||||
// Update the read-ahead pointers.
|
||||
read_ahead_pointers_.store(submit_pointers, std::memory_order_relaxed);
|
||||
@@ -346,7 +366,7 @@ void BufferingScanTarget::complete_output_area(const OutputArea &area) {
|
||||
PointerSet new_read_pointers;
|
||||
new_read_pointers.line = uint16_t(area.end.line);
|
||||
new_read_pointers.scan = uint16_t(area.end.scan);
|
||||
new_read_pointers.write_area = TextureAddress(area.end.write_area_x, area.end.write_area_y);
|
||||
new_read_pointers.write_area = texture_address(uint16_t(area.end.write_area_x), uint16_t(area.end.write_area_y));
|
||||
read_pointers_.store(new_read_pointers, std::memory_order_relaxed);
|
||||
|
||||
#ifndef NDEBUG
|
||||
@@ -362,12 +382,16 @@ void BufferingScanTarget::perform(const std::function<void(void)> &function) {
|
||||
is_updating_.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
void BufferingScanTarget::set_scan_buffer(Scan *buffer, size_t size) {
|
||||
void BufferingScanTarget::set_scan_buffer(Scan *const buffer, const size_t size) {
|
||||
scan_buffer_ = buffer;
|
||||
scan_buffer_size_ = size;
|
||||
}
|
||||
|
||||
void BufferingScanTarget::set_line_buffer(Line *line_buffer, LineMetadata *metadata_buffer, size_t size) {
|
||||
void BufferingScanTarget::set_line_buffer(
|
||||
Line *const line_buffer,
|
||||
LineMetadata *const metadata_buffer,
|
||||
const size_t size
|
||||
) {
|
||||
line_buffer_ = line_buffer;
|
||||
line_metadata_buffer_ = metadata_buffer;
|
||||
line_buffer_size_ = size;
|
||||
@@ -382,7 +406,7 @@ const Outputs::Display::ScanTarget::Modals *BufferingScanTarget::new_modals() {
|
||||
modals_are_dirty_.store(false, std::memory_order_relaxed);
|
||||
|
||||
// MAJOR SHARP EDGE HERE: assume that because the new_modals have been fetched then the caller will
|
||||
// now ensure their texture buffer is appropriate and swt the data size implied by the data type.
|
||||
// now ensure their texture buffer is appropriate and set the data size implied by the data type.
|
||||
std::lock_guard lock_guard(producer_mutex_);
|
||||
data_type_size_ = Outputs::Display::size_for_data_type(modals_.input_data_type);
|
||||
assert((data_type_size_ == 1) || (data_type_size_ == 2) || (data_type_size_ == 4));
|
||||
|
||||
@@ -168,7 +168,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
inline void resample_input_buffer(int scale) {
|
||||
inline void resample_input_buffer(const int scale) {
|
||||
if(output_buffer_.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -200,16 +200,17 @@ private:
|
||||
did_complete_samples(this, output_buffer_, is_stereo);
|
||||
}
|
||||
|
||||
// If the next loop around is going to reuse some of the samples just collected, use a memmove to
|
||||
// If the next loop around is going to reuse some of the samples just collected, copy them to
|
||||
// preserve them in the correct locations (TODO: use a longer buffer to fix that?) and don't skip
|
||||
// anything. Otherwise skip as required to get to the next sample batch and don't expect to reuse.
|
||||
const size_t steps = size_t(step_rate_ + position_error_) * (is_stereo + 1);
|
||||
position_error_ = fmodf(step_rate_ + position_error_, 1.0f);
|
||||
if(steps < input_buffer_.size()) {
|
||||
auto *const input_buffer = input_buffer_.data();
|
||||
std::memmove( input_buffer,
|
||||
&input_buffer[steps],
|
||||
sizeof(int16_t) * (input_buffer_.size() - steps));
|
||||
std::copy(
|
||||
input_buffer_.begin() + ptrdiff_t(steps),
|
||||
input_buffer_.end(),
|
||||
input_buffer_.begin()
|
||||
);
|
||||
input_buffer_depth_ -= steps;
|
||||
} else {
|
||||
if(steps > input_buffer_.size()) {
|
||||
@@ -306,13 +307,13 @@ private:
|
||||
|
||||
const int16_t *buffer_ = nullptr;
|
||||
|
||||
void skip_samples(size_t count) {
|
||||
void skip_samples(const size_t count) {
|
||||
buffer_ += count;
|
||||
}
|
||||
|
||||
void get_samples(size_t length, int16_t *target) {
|
||||
void get_samples(const size_t length, int16_t *const target) {
|
||||
const auto word_length = length * (1 + is_stereo);
|
||||
memcpy(target, buffer_, word_length * sizeof(int16_t));
|
||||
std::copy_n(buffer_, word_length, target);
|
||||
buffer_ += word_length;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,9 @@ struct Flags {
|
||||
auto operator <=> (const Flags &rhs) const {
|
||||
return static_cast<uint8_t>(*this) <=> static_cast<uint8_t>(rhs);
|
||||
}
|
||||
auto operator ==(const Flags &rhs) const {
|
||||
return static_cast<uint8_t>(*this) == static_cast<uint8_t>(rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t overflow = 0; /// Contains Flag::Overflow.
|
||||
|
||||
@@ -8,33 +8,33 @@
|
||||
|
||||
#include "AllRAMProcessor.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace CPU;
|
||||
|
||||
AllRAMProcessor::AllRAMProcessor(std::size_t memory_size) :
|
||||
AllRAMProcessor::AllRAMProcessor(const size_t memory_size) :
|
||||
memory_(memory_size),
|
||||
traps_(memory_size, false),
|
||||
timestamp_(0) {}
|
||||
|
||||
void AllRAMProcessor::set_data_at_address(size_t start_address, std::size_t length, const uint8_t *data) {
|
||||
void AllRAMProcessor::set_data_at_address(const size_t start_address, const size_t length, const uint8_t *const data) {
|
||||
const size_t end_address = std::min(start_address + length, memory_.size());
|
||||
memcpy(&memory_[start_address], data, end_address - start_address);
|
||||
std::copy_n(data, end_address - start_address, &memory_[start_address]);
|
||||
}
|
||||
|
||||
void AllRAMProcessor::get_data_at_address(size_t start_address, std::size_t length, uint8_t *data) {
|
||||
void AllRAMProcessor::get_data_at_address(const size_t start_address, const size_t length, uint8_t *const data) {
|
||||
const size_t end_address = std::min(start_address + length, memory_.size());
|
||||
memcpy(data, &memory_[start_address], end_address - start_address);
|
||||
std::copy_n(&memory_[start_address], end_address - start_address, data);
|
||||
}
|
||||
|
||||
HalfCycles AllRAMProcessor::get_timestamp() {
|
||||
return timestamp_;
|
||||
}
|
||||
|
||||
void AllRAMProcessor::set_trap_handler(TrapHandler *trap_handler) {
|
||||
void AllRAMProcessor::set_trap_handler(TrapHandler *const trap_handler) {
|
||||
trap_handler_ = trap_handler;
|
||||
}
|
||||
|
||||
void AllRAMProcessor::add_trap_address(uint16_t address) {
|
||||
void AllRAMProcessor::add_trap_address(const uint16_t address) {
|
||||
traps_[address] = true;
|
||||
}
|
||||
|
||||
@@ -10,13 +10,6 @@
|
||||
|
||||
using namespace CPU::Z80;
|
||||
|
||||
PartialMachineCycle::PartialMachineCycle(const PartialMachineCycle &rhs) noexcept :
|
||||
operation(rhs.operation),
|
||||
length(rhs.length),
|
||||
address(rhs.address),
|
||||
value(rhs.value),
|
||||
was_requested(rhs.was_requested) {}
|
||||
|
||||
PartialMachineCycle::PartialMachineCycle(Operation operation, HalfCycles length, uint16_t *address, uint8_t *value, bool was_requested) noexcept :
|
||||
operation(operation), length(length), address(address), value(value), was_requested(was_requested) {}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#include "Processors/Z80/Z80.hpp"
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
using namespace CPU::Z80;
|
||||
|
||||
@@ -496,7 +496,7 @@ void ProcessorStorage::assemble_base_page(InstructionPage &target, RegisterPair1
|
||||
InstructionTable copy_table = {
|
||||
Sequence(FINDEX(), ReadInc(pc_, temp8_), InternalOperation(4), Write(INDEX_ADDR(), temp8_))
|
||||
};
|
||||
std::memcpy(&base_program_table[0x36], ©_table[0], sizeof(copy_table[0]));
|
||||
std::copy(std::begin(copy_table[0]), std::end(copy_table[0]), std::begin(base_program_table[0x36]));
|
||||
}
|
||||
|
||||
assemble_cb_page(cb_page, index, add_offsets);
|
||||
|
||||
@@ -115,15 +115,15 @@ struct PartialMachineCycle {
|
||||
InterruptStart,
|
||||
};
|
||||
/// The operation being carried out by the Z80. See the various getters below for better classification.
|
||||
const Operation operation = Operation::Internal;
|
||||
Operation operation = Operation::Internal;
|
||||
/// The length of this operation.
|
||||
const HalfCycles length;
|
||||
HalfCycles length;
|
||||
/// The current value of the address bus.
|
||||
const uint16_t *const address = nullptr;
|
||||
const uint16_t *address = nullptr;
|
||||
/// If the Z80 is outputting to the data bus, a pointer to that value. Otherwise, a pointer to the location where the current data bus value should be placed.
|
||||
uint8_t *const value = nullptr;
|
||||
uint8_t *value = nullptr;
|
||||
/// @c true if this operation is occurring only because of an external request; @c false otherwise.
|
||||
const bool was_requested = false;
|
||||
bool was_requested = false;
|
||||
|
||||
/*!
|
||||
@returns @c true if the processor believes that the bus handler should actually do something with
|
||||
@@ -376,7 +376,7 @@ struct PartialMachineCycle {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PartialMachineCycle(const PartialMachineCycle &rhs) noexcept;
|
||||
PartialMachineCycle(const PartialMachineCycle &) = default;
|
||||
PartialMachineCycle(Operation, HalfCycles, uint16_t *address, uint8_t *value, bool was_requested) noexcept;
|
||||
PartialMachineCycle() noexcept;
|
||||
};
|
||||
|
||||
@@ -158,9 +158,3 @@ This emulator attempts cycle-accurate emulation of all supported machines. In so
|
||||
|
||||

|
||||

|
||||
|
||||
## Sponsorship
|
||||
|
||||
I've been asked several times whether it is possible to sponsor this project; I think that's a poor fit for this emulator's highly-malleable scope, and it makes me uncomfortable because as the author I primarily see only its defects.
|
||||
|
||||
An Amazon US wishlist is now attached in the hope of avoiding the question in future. A lot of it is old books now available only secondhand — I like to read about potential future additions well in advance of starting on them. Despite the optimism of some book sellers, please don't purchase anything that is currently listed only at an absurd price; they were sorted by secondhand price when added to the list, with the cheapest being $5.
|
||||
|
||||
+81
-44
@@ -8,52 +8,89 @@
|
||||
|
||||
#include "Commodore.hpp"
|
||||
|
||||
std::wstring Storage::Data::Commodore::petscii_from_bytes(const uint8_t *string, int length, bool shifted) {
|
||||
std::wstring Storage::Data::Commodore::petscii_from_bytes(
|
||||
const uint8_t *const string,
|
||||
const int length,
|
||||
const bool shifted
|
||||
) {
|
||||
static constexpr wchar_t unshifted_characters[256] = {
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u000d', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0008', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0020', u'\u0021', u'\u0022', u'\u0023', u'\u0024', u'\u0025', u'\u0026', u'\u0027',
|
||||
u'\u0028', u'\u0029', u'\u002a', u'\u002b', u'\u002c', u'\u002d', u'\u002e', u'\u002f',
|
||||
u'\u0030', u'\u0031', u'\u0032', u'\u0033', u'\u0034', u'\u0035', u'\u0036', u'\u0037',
|
||||
u'\u0038', u'\u0039', u'\u0022', u'\u003b', u'\u003c', u'\u003d', u'\u003e', u'\u003f',
|
||||
u'\u0040', u'\u0041', u'\u0042', u'\u0043', u'\u0044', u'\u0045', u'\u0046', u'\u0047',
|
||||
u'\u0048', u'\u0049', u'\u004a', u'\u004b', u'\u004c', u'\u004d', u'\u004e', u'\u004f',
|
||||
u'\u0050', u'\u0051', u'\u0052', u'\u0053', u'\u0054', u'\u0055', u'\u0056', u'\u0057',
|
||||
u'\u0058', u'\u0059', u'\u005a', u'\u005b', u'\u00a3', u'\u005d', u'\u2191', u'\u2190',
|
||||
u'\u2500', u'\u2660', u'\u2502', u'\u2500', u'\ufffd', u'\ufffd', u'\ufffd', u'\ufffd',
|
||||
u'\ufffd', u'\u256e', u'\u2570', u'\u256f', u'\ufffd', u'\u2572', u'\u2571', u'\ufffd',
|
||||
u'\ufffd', u'\u25cf', u'\ufffd', u'\u2665', u'\ufffd', u'\u256d', u'\u2573', u'\u25cb',
|
||||
u'\u2663', u'\ufffd', u'\u2666', u'\u253c', u'\ufffd', u'\u2502', u'\u03c0', u'\u25e5',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u000d', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0008', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0020', u'\u258c', u'\u2584', u'\u2594', u'\u2581', u'\u258f', u'\u2592', u'\u2595',
|
||||
u'\ufffd', u'\u25e4', u'\ufffd', u'\u251c', u'\u2597', u'\u2514', u'\u2510', u'\u2582',
|
||||
u'\u250c', u'\u2534', u'\u252c', u'\u2524', u'\u258e', u'\u258d', u'\ufffd', u'\ufffd',
|
||||
u'\ufffd', u'\u2583', u'\ufffd', u'\u2596', u'\u259d', u'\u2518', u'\u2598', u'\u259a',
|
||||
u'\u2500', u'\u2660', u'\u2502', u'\u2500', u'\ufffd', u'\ufffd', u'\ufffd', u'\ufffd',
|
||||
u'\ufffd', u'\u256e', u'\u2570', u'\u256f', u'\ufffd', u'\u2572', u'\u2571', u'\ufffd',
|
||||
u'\ufffd', u'\u25cf', u'\ufffd', u'\u2665', u'\ufffd', u'\u256d', u'\u2573', u'\u25cb',
|
||||
u'\u2663', u'\ufffd', u'\u2666', u'\u253c', u'\ufffd', u'\u2502', u'\u03c0', u'\u25e5',
|
||||
u'\u0020', u'\u258c', u'\u2584', u'\u2594', u'\u2581', u'\u258f', u'\u2592', u'\u2595',
|
||||
u'\ufffd', u'\u25e4', u'\ufffd', u'\u251c', u'\u2597', u'\u2514', u'\u2510', u'\u2582',
|
||||
u'\u250c', u'\u2534', u'\u252c', u'\u2524', u'\u258e', u'\u258d', u'\ufffd', u'\ufffd',
|
||||
u'\ufffd', u'\u2583', u'\ufffd', u'\u2596', u'\u259d', u'\u2518', u'\u2598', u'\u03c0',
|
||||
};
|
||||
|
||||
static constexpr wchar_t shifted_characters[256] = {
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u000d', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0008', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0020', u'\u0021', u'\u0022', u'\u0023', u'\u0024', u'\u0025', u'\u0026', u'\u0027',
|
||||
u'\u0028', u'\u0029', u'\u002a', u'\u002b', u'\u002c', u'\u002d', u'\u002e', u'\u002f',
|
||||
u'\u0030', u'\u0031', u'\u0032', u'\u0033', u'\u0034', u'\u0035', u'\u0036', u'\u0037',
|
||||
u'\u0038', u'\u0039', u'\u0022', u'\u003b', u'\u003c', u'\u003d', u'\u003e', u'\u003f',
|
||||
u'\u0040', u'\u0061', u'\u0062', u'\u0063', u'\u0064', u'\u0065', u'\u0066', u'\u0067',
|
||||
u'\u0068', u'\u0069', u'\u006a', u'\u006b', u'\u006c', u'\u006d', u'\u006e', u'\u006f',
|
||||
u'\u0070', u'\u0071', u'\u0072', u'\u0073', u'\u0074', u'\u0075', u'\u0076', u'\u0077',
|
||||
u'\u0078', u'\u0079', u'\u007a', u'\u005b', u'\u00a3', u'\u005d', u'\u2191', u'\u2190',
|
||||
u'\u2500', u'\u0041', u'\u0042', u'\u0043', u'\u0044', u'\u0045', u'\u0046', u'\u0047',
|
||||
u'\u0048', u'\u0049', u'\u004a', u'\u004b', u'\u004c', u'\u004d', u'\u004e', u'\u004f',
|
||||
u'\u0050', u'\u0051', u'\u0052', u'\u0053', u'\u0054', u'\u0055', u'\u0056', u'\u0057',
|
||||
u'\u0058', u'\u0059', u'\u005a', u'\u253c', u'\ufffd', u'\u2502', u'\u2592', u'\u25e5',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u000d', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0008', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0020', u'\u258c', u'\u2584', u'\u2594', u'\u2581', u'\u258f', u'\u2592', u'\u2595',
|
||||
u'\ufffd', u'\ufffd', u'\ufffd', u'\u251c', u'\u2597', u'\u2514', u'\u2510', u'\u2582',
|
||||
u'\u250c', u'\u2534', u'\u252c', u'\u2524', u'\u258e', u'\u258d', u'\ufffd', u'\ufffd',
|
||||
u'\ufffd', u'\u2583', u'\u2713', u'\u2596', u'\u259d', u'\u2518', u'\u2598', u'\u259a',
|
||||
u'\u2500', u'\u0041', u'\u0042', u'\u0043', u'\u0044', u'\u0045', u'\u0046', u'\u0047',
|
||||
u'\u0048', u'\u0049', u'\u004a', u'\u004b', u'\u004c', u'\u004d', u'\u004e', u'\u004f',
|
||||
u'\u0050', u'\u0051', u'\u0052', u'\u0053', u'\u0054', u'\u0055', u'\u0056', u'\u0057',
|
||||
u'\u0058', u'\u0059', u'\u005a', u'\u253c', u'\ufffd', u'\u2502', u'\u2592', u'\ufffd',
|
||||
u'\u0020', u'\u258c', u'\u2584', u'\u2594', u'\u2581', u'\u258f', u'\u2592', u'\u2595',
|
||||
u'\ufffd', u'\ufffd', u'\ufffd', u'\u251c', u'\u2597', u'\u2514', u'\u2510', u'\u2582',
|
||||
u'\u250c', u'\u2534', u'\u252c', u'\u2524', u'\u258e', u'\u258d', u'\ufffd', u'\ufffd',
|
||||
u'\ufffd', u'\u2583', u'\u2713', u'\u2596', u'\u259d', u'\u2518', u'\u2598', u'\u2592',
|
||||
};
|
||||
|
||||
std::wstring result;
|
||||
|
||||
wchar_t unshifted_characters[256] = {
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u000d', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0008', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0020', u'\u0021', u'\u0022', u'\u0023', u'\u0024', u'\u0025', u'\u0026', u'\u0027', u'\u0028', u'\u0029', u'\u002a', u'\u002b', u'\u002c', u'\u002d', u'\u002e', u'\u002f',
|
||||
u'\u0030', u'\u0031', u'\u0032', u'\u0033', u'\u0034', u'\u0035', u'\u0036', u'\u0037', u'\u0038', u'\u0039', u'\u0022', u'\u003b', u'\u003c', u'\u003d', u'\u003e', u'\u003f',
|
||||
u'\u0040', u'\u0041', u'\u0042', u'\u0043', u'\u0044', u'\u0045', u'\u0046', u'\u0047', u'\u0048', u'\u0049', u'\u004a', u'\u004b', u'\u004c', u'\u004d', u'\u004e', u'\u004f',
|
||||
u'\u0050', u'\u0051', u'\u0052', u'\u0053', u'\u0054', u'\u0055', u'\u0056', u'\u0057', u'\u0058', u'\u0059', u'\u005a', u'\u005b', u'\u00a3', u'\u005d', u'\u2191', u'\u2190',
|
||||
u'\u2500', u'\u2660', u'\u2502', u'\u2500', u'\ufffd', u'\ufffd', u'\ufffd', u'\ufffd', u'\ufffd', u'\u256e', u'\u2570', u'\u256f', u'\ufffd', u'\u2572', u'\u2571', u'\ufffd',
|
||||
u'\ufffd', u'\u25cf', u'\ufffd', u'\u2665', u'\ufffd', u'\u256d', u'\u2573', u'\u25cb', u'\u2663', u'\ufffd', u'\u2666', u'\u253c', u'\ufffd', u'\u2502', u'\u03c0', u'\u25e5',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u000d', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0008', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0020', u'\u258c', u'\u2584', u'\u2594', u'\u2581', u'\u258f', u'\u2592', u'\u2595', u'\ufffd', u'\u25e4', u'\ufffd', u'\u251c', u'\u2597', u'\u2514', u'\u2510', u'\u2582',
|
||||
u'\u250c', u'\u2534', u'\u252c', u'\u2524', u'\u258e', u'\u258d', u'\ufffd', u'\ufffd', u'\ufffd', u'\u2583', u'\ufffd', u'\u2596', u'\u259d', u'\u2518', u'\u2598', u'\u259a',
|
||||
u'\u2500', u'\u2660', u'\u2502', u'\u2500', u'\ufffd', u'\ufffd', u'\ufffd', u'\ufffd', u'\ufffd', u'\u256e', u'\u2570', u'\u256f', u'\ufffd', u'\u2572', u'\u2571', u'\ufffd',
|
||||
u'\ufffd', u'\u25cf', u'\ufffd', u'\u2665', u'\ufffd', u'\u256d', u'\u2573', u'\u25cb', u'\u2663', u'\ufffd', u'\u2666', u'\u253c', u'\ufffd', u'\u2502', u'\u03c0', u'\u25e5',
|
||||
u'\u0020', u'\u258c', u'\u2584', u'\u2594', u'\u2581', u'\u258f', u'\u2592', u'\u2595', u'\ufffd', u'\u25e4', u'\ufffd', u'\u251c', u'\u2597', u'\u2514', u'\u2510', u'\u2582',
|
||||
u'\u250c', u'\u2534', u'\u252c', u'\u2524', u'\u258e', u'\u258d', u'\ufffd', u'\ufffd', u'\ufffd', u'\u2583', u'\ufffd', u'\u2596', u'\u259d', u'\u2518', u'\u2598', u'\u03c0'
|
||||
};
|
||||
|
||||
wchar_t shifted_characters[256] = {
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u000d', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0008', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0020', u'\u0021', u'\u0022', u'\u0023', u'\u0024', u'\u0025', u'\u0026', u'\u0027', u'\u0028', u'\u0029', u'\u002a', u'\u002b', u'\u002c', u'\u002d', u'\u002e', u'\u002f',
|
||||
u'\u0030', u'\u0031', u'\u0032', u'\u0033', u'\u0034', u'\u0035', u'\u0036', u'\u0037', u'\u0038', u'\u0039', u'\u0022', u'\u003b', u'\u003c', u'\u003d', u'\u003e', u'\u003f',
|
||||
u'\u0040', u'\u0061', u'\u0062', u'\u0063', u'\u0064', u'\u0065', u'\u0066', u'\u0067', u'\u0068', u'\u0069', u'\u006a', u'\u006b', u'\u006c', u'\u006d', u'\u006e', u'\u006f',
|
||||
u'\u0070', u'\u0071', u'\u0072', u'\u0073', u'\u0074', u'\u0075', u'\u0076', u'\u0077', u'\u0078', u'\u0079', u'\u007a', u'\u005b', u'\u00a3', u'\u005d', u'\u2191', u'\u2190',
|
||||
u'\u2500', u'\u0041', u'\u0042', u'\u0043', u'\u0044', u'\u0045', u'\u0046', u'\u0047', u'\u0048', u'\u0049', u'\u004a', u'\u004b', u'\u004c', u'\u004d', u'\u004e', u'\u004f',
|
||||
u'\u0050', u'\u0051', u'\u0052', u'\u0053', u'\u0054', u'\u0055', u'\u0056', u'\u0057', u'\u0058', u'\u0059', u'\u005a', u'\u253c', u'\ufffd', u'\u2502', u'\u2592', u'\u25e5',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u000d', u'\u0000', u'\u0000',
|
||||
u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0008', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000', u'\u0000',
|
||||
u'\u0020', u'\u258c', u'\u2584', u'\u2594', u'\u2581', u'\u258f', u'\u2592', u'\u2595', u'\ufffd', u'\ufffd', u'\ufffd', u'\u251c', u'\u2597', u'\u2514', u'\u2510', u'\u2582',
|
||||
u'\u250c', u'\u2534', u'\u252c', u'\u2524', u'\u258e', u'\u258d', u'\ufffd', u'\ufffd', u'\ufffd', u'\u2583', u'\u2713', u'\u2596', u'\u259d', u'\u2518', u'\u2598', u'\u259a',
|
||||
u'\u2500', u'\u0041', u'\u0042', u'\u0043', u'\u0044', u'\u0045', u'\u0046', u'\u0047', u'\u0048', u'\u0049', u'\u004a', u'\u004b', u'\u004c', u'\u004d', u'\u004e', u'\u004f',
|
||||
u'\u0050', u'\u0051', u'\u0052', u'\u0053', u'\u0054', u'\u0055', u'\u0056', u'\u0057', u'\u0058', u'\u0059', u'\u005a', u'\u253c', u'\ufffd', u'\u2502', u'\u2592', u'\ufffd',
|
||||
u'\u0020', u'\u258c', u'\u2584', u'\u2594', u'\u2581', u'\u258f', u'\u2592', u'\u2595', u'\ufffd', u'\ufffd', u'\ufffd', u'\u251c', u'\u2597', u'\u2514', u'\u2510', u'\u2582',
|
||||
u'\u250c', u'\u2534', u'\u252c', u'\u2524', u'\u258e', u'\u258d', u'\ufffd', u'\ufffd', u'\ufffd', u'\u2583', u'\u2713', u'\u2596', u'\u259d', u'\u2518', u'\u2598', u'\u2592'
|
||||
};
|
||||
|
||||
wchar_t *table = shifted ? shifted_characters : unshifted_characters;
|
||||
result.reserve(size_t(length));
|
||||
const wchar_t *const table = shifted ? shifted_characters : unshifted_characters;
|
||||
for(int c = 0; c < length; c++) {
|
||||
wchar_t next_character = table[string[c]];
|
||||
if(next_character) result.push_back(next_character);
|
||||
const wchar_t next_character = table[string[c]];
|
||||
if(next_character) {
|
||||
result.push_back(next_character);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user