diff --git a/CubeChip (SDL).vcxproj b/CubeChip (SDL).vcxproj
index 9711cd8..693f983 100644
--- a/CubeChip (SDL).vcxproj
+++ b/CubeChip (SDL).vcxproj
@@ -32,6 +32,7 @@
+
@@ -54,6 +55,7 @@
+
diff --git a/CubeChip (SDL).vcxproj.filters b/CubeChip (SDL).vcxproj.filters
index d90abe9..e2da89a 100644
--- a/CubeChip (SDL).vcxproj.filters
+++ b/CubeChip (SDL).vcxproj.filters
@@ -83,6 +83,9 @@
Source Files\Systems\BYTEPUSHER\Cores
+
+ Source Files\Systems\CHIP8\Cores
+
@@ -151,5 +154,8 @@
Source Files\Systems\BYTEPUSHER\Cores
+
+ Source Files\Systems\CHIP8\Cores
+
\ No newline at end of file
diff --git a/src/Assistants/HomeDirManager.cpp b/src/Assistants/HomeDirManager.cpp
index c1c10d0..637012c 100644
--- a/src/Assistants/HomeDirManager.cpp
+++ b/src/Assistants/HomeDirManager.cpp
@@ -22,6 +22,11 @@ static auto getFileModTime(const fsPath& filePath) noexcept {
return std::filesystem::last_write_time(filePath, error);
}
+static auto getFileSize(const fsPath& filePath) noexcept {
+ std::error_code error;
+ return std::filesystem::file_size(filePath, error);
+}
+
/*==================================================================*/
#pragma region HomeDirManager Class
diff --git a/src/Assistants/Map2D.hpp b/src/Assistants/Map2D.hpp
index 05c5929..6e2694c 100644
--- a/src/Assistants/Map2D.hpp
+++ b/src/Assistants/Map2D.hpp
@@ -1149,24 +1149,19 @@ class Map2D final {
};
#pragma endregion
-private:
- #pragma region Main Ctor
- explicit Map2D(const paramS rows, const paramS cols)
- : mRows{ rows }
- , mCols{ cols }
- , pData{ std::make_unique(rows * cols) }
- {}
- #pragma endregion
-
public:
#pragma region Trivial Ctor
Map2D() : Map2D{ 1, 1 } {}
- Map2D(const integral auto rows, const integral auto cols)
- : Map2D{
- std::max(1, std::abs(rows)),
- std::max(1, std::abs(cols))
- }
+ Map2D(const paramS rows, const paramS cols)
+ : mRows{ std::max(1, std::abs(rows)) }
+ , mCols{ std::max(1, std::abs(cols)) }
+ , pData{ std::make_unique(mRows * mCols) }
+ {}
+ Map2D(const paramU rows, const paramU cols)
+ : mRows{ rows }
+ , mCols{ cols }
+ , pData{ std::make_unique(mRows * mCols) }
{}
#pragma endregion
diff --git a/src/Systems/CHIP8/Chip8_CoreInterface.cpp b/src/Systems/CHIP8/Chip8_CoreInterface.cpp
index 4859ebf..4c80bdc 100644
--- a/src/Systems/CHIP8/Chip8_CoreInterface.cpp
+++ b/src/Systems/CHIP8/Chip8_CoreInterface.cpp
@@ -96,6 +96,53 @@ bool Chip8_CoreInterface::keyHeld_P2(const u32 keyIndex) const noexcept {
/*==================================================================*/
+void Chip8_CoreInterface::handlePreFrameInterrupt() noexcept {
+ switch (mInterrupt)
+ {
+ case Interrupt::FRAME:
+ mInterrupt = Interrupt::CLEAR;
+ mActiveCPF = std::abs(mActiveCPF);
+ return;
+
+ case Interrupt::SOUND:
+ if (!mSoundTimer) {
+ mInterrupt = Interrupt::FINAL;
+ mActiveCPF = 0;
+ }
+ return;
+
+ case Interrupt::DELAY:
+ if (!mSoundTimer) {
+ mInterrupt = Interrupt::CLEAR;
+ mActiveCPF = std::abs(mActiveCPF);
+ }
+ return;
+ }
+}
+
+void Chip8_CoreInterface::handleEndFrameInterrupt() noexcept {
+ switch (mInterrupt)
+ {
+ case Interrupt::INPUT:
+ if (keyPressed(mInputReg, mTotalFrames)) {
+ mInterrupt = Interrupt::CLEAR;
+ mActiveCPF = std::abs(mActiveCPF);
+ mBuzzerTone = calcBuzzerTone();
+ mSoundTimer = 2;
+ isBuzzerEnabled(true);
+ }
+ return;
+
+ case Interrupt::ERROR:
+ case Interrupt::FINAL:
+ setCoreState(EmuState::HALTED);
+ mActiveCPF = 0;
+ return;
+ }
+}
+
+/*==================================================================*/
+
void Chip8_CoreInterface::processFrame() {
if (isSystemStopped()) { return; }
else [[likely]] { ++mTotalFrames; }
@@ -111,6 +158,19 @@ void Chip8_CoreInterface::processFrame() {
renderVideoData();
}
+/*==================================================================*/
+
+std::string Chip8_CoreInterface::formatOpcode(const u32 OP) const {
+ char buffer[5];
+ std::format_to(buffer, "{:04X}{}", OP, '\0');
+ return buffer;
+}
+
+void Chip8_CoreInterface::instructionError(const u32 HI, const u32 LO) {
+ blog.newEntry(BLOG::INFO, "Unknown instruction: " + formatOpcode(HI << 8 | LO));
+ triggerInterrupt(Interrupt::ERROR);
+}
+
void Chip8_CoreInterface::triggerInterrupt(const Interrupt type) noexcept {
mInterrupt = type;
mActiveCPF = -std::abs(mActiveCPF);
@@ -121,17 +181,95 @@ void Chip8_CoreInterface::triggerCritError(const std::string& msg) noexcept {
triggerInterrupt(Interrupt::ERROR);
}
-std::string Chip8_CoreInterface::formatOpcode(const u32 OP) const {
- char buffer[5];
- std::format_to(buffer, "{:04X}{}", OP, '\0');
- return buffer;
+/*==================================================================*/
+
+bool Chip8_CoreInterface::setPermaRegs(const s32 X) noexcept {
+ const auto path{ *sPermaRegsPath / HDM->getFileSHA1() };
+
+ if (std::filesystem::exists(path)) {
+ if (!std::filesystem::is_regular_file(path)) {
+ blog.newEntry(BLOG::ERROR, "SHA1 file is malformed: " + path.string());
+ return true;
+ }
+
+ char tempV[16]{};
+ std::ifstream in(path, std::ios::binary);
+
+ if (in.is_open()) {
+ in.seekg(0, std::ios::end);
+ const auto totalBytes{ in.tellg() };
+ in.seekg(0, std::ios::beg);
+
+ in.read(tempV, std::min(totalBytes, X));
+ in.close();
+ } else {
+ blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to read: " + path.string());
+ return true;
+ }
+
+ std::copy_n(mRegisterV, X, tempV);
+
+ std::ofstream out(path, std::ios::binary);
+ if (out.is_open()) {
+ out.write(tempV, 16);
+ out.close();
+ } else {
+ blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to write: " + path.string());
+ return true;
+ }
+ } else {
+ std::ofstream out(path, std::ios::binary);
+ if (out.is_open()) {
+ out.write(reinterpret_cast(mRegisterV), X);
+ if (X < 16) {
+ const char padding[16]{};
+ out.write(padding, 16 - X);
+ }
+ out.close();
+ } else {
+ blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to write: " + path.string());
+ return true;
+ }
+ }
+ return false;
}
-void Chip8_CoreInterface::instructionError(const u32 HI, const u32 LO) {
- blog.newEntry(BLOG::INFO, "Unknown instruction: " + formatOpcode(HI << 8 | LO));
- triggerInterrupt(Interrupt::ERROR);
+bool Chip8_CoreInterface::getPermaRegs(const s32 X) noexcept {
+ const auto path{ *sPermaRegsPath / HDM->getFileSHA1() };
+
+ if (std::filesystem::exists(path)) {
+ if (!std::filesystem::is_regular_file(path)) {
+ blog.newEntry(BLOG::ERROR, "SHA1 file is malformed: " + path.string());
+ return true;
+ }
+
+ std::ifstream in(path, std::ios::binary);
+ if (in.is_open()) {
+ in.seekg(0, std::ios::end);
+ const auto totalBytes{ static_cast(in.tellg()) };
+ in.seekg(0, std::ios::beg);
+
+ in.read(reinterpret_cast(mRegisterV), std::min(totalBytes, X));
+ in.close();
+
+ if (totalBytes < X) {
+ std::fill_n(mRegisterV + totalBytes, X - totalBytes, u8());
+ }
+ } else {
+ blog.newEntry(BLOG::ERROR, "Could not open SHA1 file to read: " + path.string());
+ return true;
+ }
+ } else {
+ std::fill_n(
+ std::execution::unseq,
+ mRegisterV, X, u8{}
+ );
+ }
+ return false;
}
+/*==================================================================*/
+
void Chip8_CoreInterface::copyGameToMemory(
u8* dest, const u32 offset
) noexcept {
@@ -151,3 +289,11 @@ void Chip8_CoreInterface::copyFontToMemory(
cFontData, size, dest + offset
);
}
+
+/*==================================================================*/
+
+f32 Chip8_CoreInterface::calcBuzzerTone() const noexcept {
+ return (160.0f + 8.0f * (
+ (mCurrentPC >> 1) + mStackTop + 1 & 0x3E
+ )) / ASB->getFrequency();
+}
diff --git a/src/Systems/CHIP8/Chip8_CoreInterface.hpp b/src/Systems/CHIP8/Chip8_CoreInterface.hpp
index 8ed4e00..f6aae1d 100644
--- a/src/Systems/CHIP8/Chip8_CoreInterface.hpp
+++ b/src/Systems/CHIP8/Chip8_CoreInterface.hpp
@@ -57,6 +57,7 @@ class Chip8_CoreInterface : public EmuInterface {
bool mLoresExtended{};
bool mManualRefresh{};
bool mPixelTrailing{};
+ bool mBuzzerEnabled{};
} Trait;
enum class Interrupt {
@@ -69,6 +70,15 @@ class Chip8_CoreInterface : public EmuInterface {
ERROR, // end state, error occured
};
+ enum class Resolution {
+ ERROR,
+ HI, // 128 x 64 - 2:1
+ LO, // 64 x 32 - 2:1
+ TP, // 64 x 64 - 2:1
+ FP, // 64 x 128 - 2:1
+ MC, // 256 x 192 - 4:3
+ };
+
/*==================================================================*/
void addCoreState(const EmuState state) noexcept { Trait.mCoreState |= state; }
@@ -84,9 +94,11 @@ class Chip8_CoreInterface : public EmuInterface {
bool isLoresExtended() const noexcept { return Trait.mLoresExtended; }
bool isManualRefresh() const noexcept { return Trait.mManualRefresh; }
bool isPixelTrailing() const noexcept { return Trait.mPixelTrailing; }
+ bool isBuzzerEnabled() const noexcept { return Trait.mBuzzerEnabled; }
void isLoresExtended(const bool state) noexcept { Trait.mLoresExtended = state; }
void isManualRefresh(const bool state) noexcept { Trait.mManualRefresh = state; }
void isPixelTrailing(const bool state) noexcept { Trait.mPixelTrailing = state; }
+ void isBuzzerEnabled(const bool state) noexcept { Trait.mBuzzerEnabled = state; }
/*==================================================================*/
@@ -107,14 +119,15 @@ class Chip8_CoreInterface : public EmuInterface {
}
f32 mBuzzerTone{};
+ u32 mPlanarMask{ 0x1 };
u32 mCurrentPC{};
u32 mRegisterI{};
- u8 mDelayTimer{};
- u8 mSoundTimer{};
+ u32 mDelayTimer{};
+ u32 mSoundTimer{};
- u8 mStackTop{};
+ u32 mStackTop{};
u8* mInputReg{};
u8 mRegisterV[16]{};
@@ -128,11 +141,14 @@ class Chip8_CoreInterface : public EmuInterface {
void triggerInterrupt(const Interrupt type) noexcept;
void triggerCritError(const std::string& msg) noexcept;
+ bool setPermaRegs(const s32 X) noexcept;
+ bool getPermaRegs(const s32 X) noexcept;
+
void copyGameToMemory(u8* dest, const u32 offset) noexcept;
void copyFontToMemory(u8* dest, const u32 offset, const u32 size) noexcept;
- virtual void handlePreFrameInterrupt() noexcept = 0;
- virtual void handleEndFrameInterrupt() noexcept = 0;
+ virtual void handlePreFrameInterrupt() noexcept;
+ virtual void handleEndFrameInterrupt() noexcept;
virtual void handleTimerTick() noexcept = 0;
virtual void instructionLoop() noexcept = 0;
@@ -140,6 +156,10 @@ class Chip8_CoreInterface : public EmuInterface {
virtual void renderAudioData() = 0;
virtual void renderVideoData() = 0;
+ virtual void prepDisplayArea(const Resolution mode) = 0;
+
+ f32 calcBuzzerTone() const noexcept;
+
public:
Chip8_CoreInterface() noexcept;
~Chip8_CoreInterface() noexcept;
diff --git a/src/Systems/CHIP8/Cores/CHIP8_MODERN.cpp b/src/Systems/CHIP8/Cores/CHIP8_MODERN.cpp
index e7390ca..a6210d7 100644
--- a/src/Systems/CHIP8/Cores/CHIP8_MODERN.cpp
+++ b/src/Systems/CHIP8/Cores/CHIP8_MODERN.cpp
@@ -32,43 +32,6 @@ CHIP8_MODERN::CHIP8_MODERN() {
/*==================================================================*/
-void CHIP8_MODERN::handlePreFrameInterrupt() noexcept {
- switch (mInterrupt)
- {
- case Interrupt::FRAME:
- mInterrupt = Interrupt::CLEAR;
- mActiveCPF = std::abs(mActiveCPF);
- return;
-
- case Interrupt::SOUND:
- if (!mSoundTimer) {
- mInterrupt = Interrupt::FINAL;
- mActiveCPF = 0;
- }
- return;
- }
-}
-
-void CHIP8_MODERN::handleEndFrameInterrupt() noexcept {
- switch (mInterrupt)
- {
- case Interrupt::INPUT:
- if (keyPressed(mInputReg, mTotalFrames)) {
- mInterrupt = Interrupt::CLEAR;
- mActiveCPF = std::abs(mActiveCPF);
- mBuzzerTone = calcAudioTone();
- mSoundTimer = 2;
- }
- return;
-
- case Interrupt::ERROR:
- case Interrupt::FINAL:
- setCoreState(EmuState::HALTED);
- mActiveCPF = 0;
- return;
- }
-}
-
void CHIP8_MODERN::handleTimerTick() noexcept {
if (mDelayTimer) { --mDelayTimer; }
if (mSoundTimer) { --mSoundTimer; }
@@ -231,7 +194,7 @@ void CHIP8_MODERN::renderAudioData() {
if (mSoundTimer) {
for (auto& sample : samplesBuffer) {
- sample = static_cast(wavePhase > 0.5f ? 64 : -64);
+ sample = static_cast(wavePhase > 0.5f ? 16 : -16);
wavePhase = std::fmod(wavePhase + mBuzzerTone, 1.0f);
}
BVS->setFrameColor(cBitsColor[0], cBitsColor[1]);
@@ -270,12 +233,6 @@ void CHIP8_MODERN::renderVideoData() {
/*==================================================================*/
-f32 CHIP8_MODERN::calcAudioTone() const noexcept {
- return (160.0f + 8.0f * (
- (mCurrentPC >> 1) + mStackTop + 1 & 0x3E
- )) / ASB->getFrequency();
-}
-
void CHIP8_MODERN::nextInstruction() noexcept {
mCurrentPC += 2;
}
@@ -583,7 +540,7 @@ void CHIP8_MODERN::jumpProgramTo(const u32 next) noexcept {
// FX07 - set VX = delay timer
void CHIP8_MODERN::instruction_Fx07(const s32 X) noexcept {
- mRegisterV[X] = mDelayTimer;
+ mRegisterV[X] = static_cast(mDelayTimer);
}
// FX0A - set VX = key, wait for keypress
void CHIP8_MODERN::instruction_Fx0A(const s32 X) noexcept {
@@ -592,11 +549,11 @@ void CHIP8_MODERN::jumpProgramTo(const u32 next) noexcept {
}
// FX15 - set delay timer = VX
void CHIP8_MODERN::instruction_Fx15(const s32 X) noexcept {
- mDelayTimer = mRegisterV[X];
+ mDelayTimer = static_cast(mRegisterV[X]);
}
// FX18 - set sound timer = VX
void CHIP8_MODERN::instruction_Fx18(const s32 X) noexcept {
- mBuzzerTone = calcAudioTone();
+ mBuzzerTone = calcBuzzerTone();
mSoundTimer = mRegisterV[X] + (mRegisterV[X] == 1);
}
// FX1E - set I = I + VX
@@ -614,18 +571,18 @@ void CHIP8_MODERN::jumpProgramTo(const u32 next) noexcept {
writeMemoryI(mRegisterV[X] % 10, 2);
}
// FN55 - store V0..VX to RAM at I..I+N
- void CHIP8_MODERN::instruction_FN55(const s32 X) noexcept {
- for (auto idx{ 0 }; idx <= X; ++idx)
+ void CHIP8_MODERN::instruction_FN55(const s32 N) noexcept {
+ for (auto idx{ 0 }; idx <= N; ++idx)
{ writeMemoryI(mRegisterV[idx], idx); }
if (!Quirk.idxRegNoInc) [[likely]]
- { mRegisterI = mRegisterI + X + 1 & 0xFFF; }
+ { mRegisterI = mRegisterI + N + 1 & 0xFFF; }
}
// FN65 - load V0..VX from RAM at I..I+N
- void CHIP8_MODERN::instruction_FN65(const s32 X) noexcept {
- for (auto idx{ 0 }; idx <= X; ++idx)
+ void CHIP8_MODERN::instruction_FN65(const s32 N) noexcept {
+ for (auto idx{ 0 }; idx <= N; ++idx)
{ mRegisterV[idx] = readMemoryI(idx); }
if (!Quirk.idxRegNoInc) [[likely]]
- { mRegisterI = mRegisterI + X + 1 & 0xFFF; }
+ { mRegisterI = mRegisterI + N + 1 & 0xFFF; }
}
#pragma endregion
diff --git a/src/Systems/CHIP8/Cores/CHIP8_MODERN.hpp b/src/Systems/CHIP8/Cores/CHIP8_MODERN.hpp
index 7cd8fb2..9523bdd 100644
--- a/src/Systems/CHIP8/Cores/CHIP8_MODERN.hpp
+++ b/src/Systems/CHIP8/Cores/CHIP8_MODERN.hpp
@@ -46,16 +46,14 @@ class CHIP8_MODERN final : public Chip8_CoreInterface {
}
private:
- void handlePreFrameInterrupt() noexcept override;
- void handleEndFrameInterrupt() noexcept override;
-
void handleTimerTick() noexcept override;
void instructionLoop() noexcept override;
void renderAudioData() override;
void renderVideoData() override;
- f32 calcAudioTone() const noexcept;
+ void prepDisplayArea(const Resolution) override {}
+
void nextInstruction() noexcept;
void jumpProgramTo(const u32 next) noexcept;
@@ -234,9 +232,9 @@ class CHIP8_MODERN final : public Chip8_CoreInterface {
// FX33 - store BCD of VX to RAM at I, I+1, I+2
void instruction_Fx33(const s32 X) noexcept;
// FN55 - store V0..VN to RAM at I..I+N
- void instruction_FN55(const s32 X) noexcept;
+ void instruction_FN55(const s32 N) noexcept;
// FN65 - load V0..VN from RAM at I..I+N
- void instruction_FN65(const s32 X) noexcept;
+ void instruction_FN65(const s32 N) noexcept;
#pragma endregion
/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
diff --git a/src/Systems/CHIP8/Cores/XOCHIP.cpp b/src/Systems/CHIP8/Cores/XOCHIP.cpp
new file mode 100644
index 0000000..f01a398
--- /dev/null
+++ b/src/Systems/CHIP8/Cores/XOCHIP.cpp
@@ -0,0 +1,849 @@
+/*
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+#include "../../../Assistants/BasicVideoSpec.hpp"
+#include "../../../Assistants/BasicAudioSpec.hpp"
+#include "../../../Assistants/Well512.hpp"
+
+#include "XOCHIP.hpp"
+
+/*==================================================================*/
+
+XOCHIP::XOCHIP()
+
+ : mAudioStep{ 4'000.0f / 128.0f / ASB->getFrequency() }
+ , mAudioTone{ mAudioStep }
+ , mDisplayBuffer{
+ {cScreenSizeY, cScreenSizeX},
+ {cScreenSizeY, cScreenSizeX},
+ {cScreenSizeY, cScreenSizeX},
+ {cScreenSizeY, cScreenSizeX},
+ }
+{
+ if (getCoreState() != EmuState::FAILED) {
+ Quirk.wrapSprite = true;
+
+ copyGameToMemory(mMemoryBank.data(), cGameLoadPos);
+ copyFontToMemory(mMemoryBank.data(), 0x0, 0x50);
+
+ setDisplayResolution(cScreenSizeX, cScreenSizeY);
+
+ BVS->setBackColor(cBitsColor[0]);
+ BVS->createTexture(cScreenSizeX, cScreenSizeY);
+ BVS->setAspectRatio(cScreenSizeX * 8, cScreenSizeY * 8, +2);
+
+ std::copy_n(
+ std::execution::unseq,
+ cBitsColor, 16,
+ mBitsColor.data()
+ );
+
+ mCurrentPC = cStartOffset;
+ mFramerate = cRefreshRate;
+ mActiveCPF = cInstSpeedLo;
+ }
+}
+
+/*==================================================================*/
+
+void XOCHIP::handleTimerTick() noexcept {
+ if (mDelayTimer) { --mDelayTimer; }
+ if (mSoundTimer) { --mSoundTimer; }
+}
+
+void XOCHIP::instructionLoop() noexcept {
+
+ auto cycleCount{ 0 };
+ for (; cycleCount < mActiveCPF; ++cycleCount) {
+ const auto HI{ mMemoryBank[mCurrentPC + 0u] };
+ const auto LO{ mMemoryBank[mCurrentPC + 1u] };
+ nextInstruction();
+
+ switch (HI >> 4) {
+ case 0x0:
+ switch (HI << 8 | LO) {
+ case 0x00C0: case 0x00C1: case 0x00C2: case 0x00C3:
+ case 0x00C4: case 0x00C5: case 0x00C6: case 0x00C7:
+ case 0x00C8: case 0x00C9: case 0x00CA: case 0x00CB:
+ case 0x00CC: case 0x00CD: case 0x00CE: case 0x00CF:
+ instruction_00CN(LO & 0xF);
+ break;
+ case 0x00D0: case 0x00D1: case 0x00D2: case 0x00D3:
+ case 0x00D4: case 0x00D5: case 0x00D6: case 0x00D7:
+ case 0x00D8: case 0x00D9: case 0x00DA: case 0x00DB:
+ case 0x00DC: case 0x00DD: case 0x00DE: case 0x00DF:
+ instruction_00DN(LO & 0xF);
+ break;
+ case 0x00E0:
+ instruction_00E0();
+ break;
+ case 0x00EE:
+ instruction_00EE();
+ break;
+ case 0x00FB:
+ instruction_00FB();
+ break;
+ case 0x00FC:
+ instruction_00FC();
+ break;
+ case 0x00FD:
+ instruction_00FD();
+ break;
+ case 0x00FE:
+ instruction_00FE();
+ break;
+ case 0x00FF:
+ instruction_00FF();
+ break;
+ [[unlikely]]
+ default: instructionError(HI, LO);
+ }
+ break;
+ case 0x1:
+ instruction_1NNN(HI << 8 | LO);
+ break;
+ case 0x2:
+ instruction_2NNN(HI << 8 | LO);
+ break;
+ case 0x3:
+ instruction_3xNN(HI & 0xF, LO);
+ break;
+ case 0x4:
+ instruction_4xNN(HI & 0xF, LO);
+ break;
+ case 0x5:
+ switch (LO & 0xF) {
+ case 0x0:
+ instruction_5xy0(HI & 0xF, LO >> 4);
+ break;
+ case 0x2:
+ instruction_5xy2(HI & 0xF, LO >> 4);
+ break;
+ case 0x3:
+ instruction_5xy3(HI & 0xF, LO >> 4);
+ break;
+ case 0x4:
+ instruction_5xy4(HI & 0xF, LO >> 4);
+ break;
+ [[unlikely]]
+ default:
+ instructionError(HI, LO);
+ }
+ break;
+ case 0x6:
+ instruction_6xNN(HI & 0xF, LO);
+ break;
+ case 0x7:
+ instruction_7xNN(HI & 0xF, LO);
+ break;
+ case 0x8:
+ switch (LO & 0xF) {
+ case 0x0:
+ instruction_8xy0(HI & 0xF, LO >> 4);
+ break;
+ case 0x1:
+ instruction_8xy1(HI & 0xF, LO >> 4);
+ break;
+ case 0x2:
+ instruction_8xy2(HI & 0xF, LO >> 4);
+ break;
+ case 0x3:
+ instruction_8xy3(HI & 0xF, LO >> 4);
+ break;
+ case 0x4:
+ instruction_8xy4(HI & 0xF, LO >> 4);
+ break;
+ case 0x5:
+ instruction_8xy5(HI & 0xF, LO >> 4);
+ break;
+ case 0x7:
+ instruction_8xy7(HI & 0xF, LO >> 4);
+ break;
+ case 0x6:
+ instruction_8xy6(HI & 0xF, LO >> 4);
+ break;
+ case 0xE:
+ instruction_8xyE(HI & 0xF, LO >> 4);
+ break;
+ [[unlikely]]
+ default: instructionError(HI, LO);
+ }
+ break;
+ case 0x9:
+ if (LO & 0xF) [[unlikely]] {
+ instructionError(HI, LO);
+ } else {
+ instruction_9xy0(HI & 0xF, LO >> 4);
+ }
+ break;
+ case 0xA:
+ instruction_ANNN(HI << 8 | LO);
+ break;
+ case 0xB:
+ instruction_BNNN(HI << 8 | LO);
+ break;
+ case 0xC:
+ instruction_CxNN(HI & 0xF, LO);
+ break;
+ [[likely]]
+ case 0xD:
+ instruction_DxyN(HI & 0xF, LO >> 4, LO & 0xF);
+ break;
+ case 0xE:
+ switch (LO) {
+ case 0x9E:
+ instruction_Ex9E(HI & 0xF);
+ break;
+ case 0xA1:
+ instruction_ExA1(HI & 0xF);
+ break;
+ [[unlikely]]
+ default: instructionError(HI, LO);
+ }
+ break;
+ case 0xF:
+ switch (HI << 8 | LO) {
+ case 0xF000:
+ instruction_F000();
+ break;
+ case 0xF002:
+ instruction_F002();
+ break;
+ default:
+ switch (LO) {
+ case 0x01:
+ instruction_FN01(HI & 0xF);
+ break;
+ case 0x07:
+ instruction_Fx07(HI & 0xF);
+ break;
+ case 0x0A:
+ instruction_Fx0A(HI & 0xF);
+ break;
+ case 0x15:
+ instruction_Fx15(HI & 0xF);
+ break;
+ case 0x18:
+ instruction_Fx18(HI & 0xF);
+ break;
+ case 0x1E:
+ instruction_Fx1E(HI & 0xF);
+ break;
+ case 0x29:
+ instruction_Fx29(HI & 0xF);
+ break;
+ case 0x30:
+ instruction_Fx30(HI & 0xF);
+ break;
+ case 0x33:
+ instruction_Fx33(HI & 0xF);
+ break;
+ case 0x3A:
+ instruction_Fx3A(HI & 0xF);
+ break;
+ case 0x55:
+ instruction_FN55(HI & 0xF);
+ break;
+ case 0x65:
+ instruction_FN65(HI & 0xF);
+ break;
+ case 0x75:
+ instruction_FN75(HI & 0xF);
+ break;
+ case 0x85:
+ instruction_FN85(HI & 0xF);
+ break;
+ [[unlikely]]
+ default: instructionError(HI, LO);
+ }
+ break;
+ }
+ break;
+ }
+ }
+ mTotalCycles += cycleCount;
+}
+
+void XOCHIP::renderAudioData() {
+ std::vector samplesBuffer \
+ (static_cast(ASB->getSampleRate(cRefreshRate)));
+
+ static f32 wavePhase{};
+
+
+ if (mSoundTimer) {
+ if (isBuzzerEnabled()) {
+ for (auto& sample : samplesBuffer) {
+ sample = static_cast(wavePhase > 0.5f ? 16 : -16);
+ wavePhase = std::fmod(wavePhase + mBuzzerTone, 1.0f);
+ }
+ BVS->setFrameColor(cBitsColor[0], cBitsColor[1]);
+ } else {
+ for (auto& sample : samplesBuffer) {
+ const auto step{ static_cast(std::clamp(wavePhase * 128.0f, 0.0f, 127.0f)) };
+ const auto mask{ 1 << (7 ^ step & 7) };
+ sample = mSamplePattern[step >> 3] & mask ? 16 : -16;
+ wavePhase = std::fmod(wavePhase + mAudioTone, 1.0f);
+ }
+ BVS->setFrameColor(cBitsColor[0], cBitsColor[0]);
+ }
+ } else {
+ wavePhase = 0.0f;
+ isBuzzerEnabled(false);
+ BVS->setFrameColor(cBitsColor[0], cBitsColor[0]);
+ }
+
+ ASB->pushAudioData(samplesBuffer);
+}
+
+void XOCHIP::renderVideoData() {
+ std::vector textureBuffer(mDisplayW * mDisplayH);
+
+ std::for_each(
+ std::execution::unseq,
+ textureBuffer.begin(),
+ textureBuffer.end(),
+ [&](u8& pixel) noexcept {
+ const auto idx{ &pixel - textureBuffer.data() };
+ pixel = mDisplayBuffer[3].at_raw(idx) << 3
+ | mDisplayBuffer[2].at_raw(idx) << 2
+ | mDisplayBuffer[1].at_raw(idx) << 1
+ | mDisplayBuffer[0].at_raw(idx);
+ }
+ );
+
+ BVS->modifyTexture(textureBuffer,
+ [this](const u32 pixel) noexcept {
+ return 0xFF000000 | mBitsColor[pixel];
+ }
+ );
+}
+
+void XOCHIP::prepDisplayArea(const Resolution mode) {
+ isLoresExtended(mode != Resolution::LO);
+
+ const auto W{ isLoresExtended() ? 128 : 64 };
+ const auto H{ isLoresExtended() ? 64 : 32 };
+
+ BVS->createTexture(W, H);
+ setDisplayResolution(W, H);
+
+ mDisplayBuffer[0].resize(false, H, W);
+ mDisplayBuffer[1].resize(false, H, W);
+ mDisplayBuffer[2].resize(false, H, W);
+ mDisplayBuffer[3].resize(false, H, W);
+};
+
+void XOCHIP::setColorBit332(const s32 bit, const s32 color) noexcept {
+ static constexpr u8 map3b[]{ 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xFF };
+ static constexpr u8 map2b[]{ 0x00, 0x60, 0xA0, 0xFF };
+
+ mBitsColor[bit & 0xF] = map3b[color >> 5 & 0x7] << 16 // red
+ | map3b[color >> 2 & 0x7] << 8 // green
+ | map2b[color & 0x3]; // blue
+}
+
+/*==================================================================*/
+
+void XOCHIP::nextInstruction() noexcept {
+ mCurrentPC += 2;
+}
+
+void XOCHIP::skipInstruction() noexcept {
+ mCurrentPC += NNNN() == 0xF000 ? 4 : 2;
+}
+
+void XOCHIP::jumpProgramTo(const u32 next) noexcept {
+ const auto NNN{ next & 0xFFF };
+ if (mCurrentPC - 2u != NNN) [[likely]] {
+ mCurrentPC = NNN & 0xFFF;
+ } else {
+ triggerInterrupt(Interrupt::SOUND);
+ }
+}
+
+void XOCHIP::scrollDisplayUP(const s32 N) {
+ if (!mPlanarMask) { return; }
+
+ for (auto P{ 0 }; P < 4; ++P) {
+ if (mPlanarMask & (1 << P))
+ { mDisplayBuffer[P].shift(-N, 0); }
+ }
+}
+void XOCHIP::scrollDisplayDN(const s32 N) {
+ if (!mPlanarMask) { return; }
+
+ for (auto P{ 0 }; P < 4; ++P) {
+ if (mPlanarMask & (1 << P))
+ { mDisplayBuffer[P].shift(+N, 0); }
+ }
+}
+void XOCHIP::scrollDisplayLT(const s32) {
+ if (!mPlanarMask) { return; }
+
+ for (auto P{ 0 }; P < 4; ++P) {
+ if (mPlanarMask & (1 << P))
+ { mDisplayBuffer[P].shift(0, -4); }
+ }
+}
+void XOCHIP::scrollDisplayRT(const s32) {
+ if (!mPlanarMask) { return; }
+
+ for (auto P{ 0 }; P < 4; ++P) {
+ if (mPlanarMask & (1 << P))
+ { mDisplayBuffer[P].shift(0, +4); }
+ }
+}
+
+/*==================================================================*/
+ #pragma region 0 instruction branch
+
+ // 00DN - scroll selected color plane N lines down
+ void XOCHIP::instruction_00CN(const s32 N) noexcept {
+ if (Quirk.waitScroll) [[unlikely]]
+ { triggerInterrupt(Interrupt::FRAME); }
+ if (N) { scrollDisplayDN(N); }
+ }
+ // 00DN - scroll selected color plane N lines up
+ void XOCHIP::instruction_00DN(const s32 N) noexcept {
+ if (Quirk.waitScroll) [[unlikely]]
+ { triggerInterrupt(Interrupt::FRAME); }
+ if (N) { scrollDisplayUP(N); }
+ }
+ // 00E0 - erase whole display
+ void XOCHIP::instruction_00E0() noexcept {
+ for (auto P{ 0 }; P < 4; ++P) {
+ if (!(mPlanarMask & (1 << P))) { continue; }
+ mDisplayBuffer[P].wipeAll();
+ }
+ }
+ // 00EE - return from subroutine
+ void XOCHIP::instruction_00EE() noexcept {
+ mCurrentPC = mStackBank[--mStackTop & 0xF];
+ }
+ // 00FB - scroll selected color plane 4 pixels right
+ void XOCHIP::instruction_00FB() noexcept {
+ if (Quirk.waitScroll) [[unlikely]]
+ { triggerInterrupt(Interrupt::FRAME); }
+ scrollDisplayRT(4);
+ }
+ // 00FC - scroll selected color plane 4 pixels left
+ void XOCHIP::instruction_00FC() noexcept {
+ if (Quirk.waitScroll) [[unlikely]]
+ { triggerInterrupt(Interrupt::FRAME); }
+ scrollDisplayLT(4);
+ }
+ // 00FD - stop signal
+ void XOCHIP::instruction_00FD() noexcept {
+ triggerInterrupt(Interrupt::SOUND);
+ }
+ // 00FE - display == 64*32, erase the screen
+ void XOCHIP::instruction_00FE() noexcept {
+ prepDisplayArea(Resolution::LO);
+ }
+ // 00FF - display == 128*64, erase the screen
+ void XOCHIP::instruction_00FF() noexcept {
+ prepDisplayArea(Resolution::HI);
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 1 instruction branch
+
+ // 1NNN - jump to NNN
+ void XOCHIP::instruction_1NNN(const s32 NNN) noexcept {
+ jumpProgramTo(NNN);
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 2 instruction branch
+
+ // 2NNN - call subroutine at NNN
+ void XOCHIP::instruction_2NNN(const s32 NNN) noexcept {
+ mStackBank[mStackTop++ & 0xF] = mCurrentPC;
+ jumpProgramTo(NNN);
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 3 instruction branch
+
+ // 3XNN - skip next instruction if VX == NN
+ void XOCHIP::instruction_3xNN(const s32 X, const s32 NN) noexcept {
+ if (mRegisterV[X] == NN) { nextInstruction(); }
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 4 instruction branch
+
+ // 4XNN - skip next instruction if VX != NN
+ void XOCHIP::instruction_4xNN(const s32 X, const s32 NN) noexcept {
+ if (mRegisterV[X] != NN) { nextInstruction(); }
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 5 instruction branch
+
+ // 5XY0 - skip next instruction if VX == VY
+ void XOCHIP::instruction_5xy0(const s32 X, const s32 Y) noexcept {
+ if (mRegisterV[X] == mRegisterV[Y]) { nextInstruction(); }
+ }
+ // 5XY2 - store range of registers to memory
+ void XOCHIP::instruction_5xy2(const s32 X, const s32 Y) noexcept {
+ const auto dist{ std::abs(X - Y) + 1 };
+ if (X < Y) {
+ for (auto Z{ 0 }; Z < dist; ++Z) {
+ writeMemoryI(mRegisterV[X + Z], Z);
+ }
+ } else {
+ for (auto Z{ 0 }; Z < dist; ++Z) {
+ writeMemoryI(mRegisterV[X - Z], Z);
+ }
+ }
+ }
+ // 5XY3 - load range of registers from memory
+ void XOCHIP::instruction_5xy3(const s32 X, const s32 Y) noexcept {
+ const auto dist{ std::abs(X - Y) + 1 };
+ if (X < Y) {
+ for (auto Z{ 0 }; Z < dist; ++Z) {
+ mRegisterV[X + Z] = readMemoryI(Z);
+ }
+ } else {
+ for (auto Z{ 0 }; Z < dist; ++Z) {
+ mRegisterV[X - Z] = readMemoryI(Z);
+ }
+ }
+ }
+ // 5XY4 - load range of colors from memory
+ void XOCHIP::instruction_5xy4(const s32 X, const s32 Y) noexcept {
+ const auto dist{ std::abs(X - Y) + 1 };
+ if (X < Y) {
+ for (auto Z{ 0 }; Z < dist; ++Z) {
+ setColorBit332(X + Z, readMemoryI(Z));
+ }
+ } else {
+ for (auto Z{ 0 }; Z < dist; ++Z) {
+ setColorBit332(X - Z, readMemoryI(Z));
+ }
+ }
+ BVS->setBackColor(mBitsColor[0]);
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 6 instruction branch
+
+ // 6XNN - set VX = NN
+ void XOCHIP::instruction_6xNN(const s32 X, const s32 NN) noexcept {
+ mRegisterV[X] = static_cast(NN);
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 7 instruction branch
+
+ // 7XNN - set VX = VX + NN
+ void XOCHIP::instruction_7xNN(const s32 X, const s32 NN) noexcept {
+ mRegisterV[X] += static_cast(NN);
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 8 instruction branch
+
+ // 8XY0 - set VX = VY
+ void XOCHIP::instruction_8xy0(const s32 X, const s32 Y) noexcept {
+ mRegisterV[X] = mRegisterV[Y];
+ }
+ // 8XY1 - set VX = VX | VY
+ void XOCHIP::instruction_8xy1(const s32 X, const s32 Y) noexcept {
+ mRegisterV[X] |= mRegisterV[Y];
+ }
+ // 8XY2 - set VX = VX & VY
+ void XOCHIP::instruction_8xy2(const s32 X, const s32 Y) noexcept {
+ mRegisterV[X] &= mRegisterV[Y];
+ }
+ // 8XY3 - set VX = VX ^ VY
+ void XOCHIP::instruction_8xy3(const s32 X, const s32 Y) noexcept {
+ mRegisterV[X] ^= mRegisterV[Y];
+ }
+ // 8XY4 - set VX = VX + VY, VF = carry
+ void XOCHIP::instruction_8xy4(const s32 X, const s32 Y) noexcept {
+ const auto sum{ mRegisterV[X] + mRegisterV[Y] };
+ mRegisterV[X] = static_cast(sum);
+ mRegisterV[0xF] = static_cast(sum >> 8);
+ }
+ // 8XY5 - set VX = VX - VY, VF = !borrow
+ void XOCHIP::instruction_8xy5(const s32 X, const s32 Y) noexcept {
+ const bool nborrow{ mRegisterV[X] >= mRegisterV[Y] };
+ mRegisterV[X] = mRegisterV[X] - mRegisterV[Y];
+ mRegisterV[0xF] = nborrow;
+ }
+ // 8XY7 - set VX = VY - VX, VF = !borrow
+ void XOCHIP::instruction_8xy7(const s32 X, const s32 Y) noexcept {
+ const bool nborrow{ mRegisterV[Y] >= mRegisterV[X] };
+ mRegisterV[X] = mRegisterV[Y] - mRegisterV[X];
+ mRegisterV[0xF] = nborrow;
+ }
+ // 8XY6 - set VX = VY >> 1, VF = carry
+ void XOCHIP::instruction_8xy6(const s32 X, const s32 Y) noexcept {
+ if (!Quirk.shiftVX) { mRegisterV[X] = mRegisterV[Y]; }
+ const bool lsb{ (mRegisterV[X] & 1) == 1 };
+ mRegisterV[X] = mRegisterV[X] >> 1;
+ mRegisterV[0xF] = lsb;
+ }
+ // 8XYE - set VX = VY << 1, VF = carry
+ void XOCHIP::instruction_8xyE(const s32 X, const s32 Y) noexcept {
+ if (!Quirk.shiftVX) { mRegisterV[X] = mRegisterV[Y]; }
+ const bool msb{ (mRegisterV[X] >> 7) == 1 };
+ mRegisterV[X] = mRegisterV[X] << 1;
+ mRegisterV[0xF] = msb;
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 9 instruction branch
+
+ // 9XY0 - skip next instruction if VX != VY
+ void XOCHIP::instruction_9xy0(const s32 X, const s32 Y) noexcept {
+ if (mRegisterV[X] != mRegisterV[Y]) { nextInstruction(); }
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region A instruction branch
+
+ // ANNN - set I = NNN
+ void XOCHIP::instruction_ANNN(const s32 NNN) noexcept {
+ mRegisterI = NNN & 0xFFF;
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region B instruction branch
+
+ // BXNN - jump to NNN + V0
+ void XOCHIP::instruction_BNNN(const s32 NNN) noexcept {
+ jumpProgramTo(NNN + mRegisterV[0]);
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region C instruction branch
+
+ // CXNN - set VX = rnd(256) & NN
+ void XOCHIP::instruction_CxNN(const s32 X, const s32 NN) noexcept {
+ mRegisterV[X] = static_cast(Wrand->get() & NN);
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region D instruction branch
+
+ void XOCHIP::drawByte(
+ s32 X, s32 Y, const s32 P,
+ const u32 DATA
+ ) noexcept {
+ switch (DATA) {
+ [[unlikely]]
+ case 0b00000000:
+ return;
+
+ [[unlikely]]
+ case 0b10000000:
+ if (Quirk.wrapSprite) { X &= mDisplayWb; }
+ if (X < mDisplayW) {
+ if (!((mDisplayBuffer[P].at_raw(Y, X) ^= 1) & 1))
+ { mRegisterV[0xF] = 1; }
+ }
+ return;
+
+ [[likely]]
+ default:
+ if (Quirk.wrapSprite) { X &= mDisplayWb; }
+ else if (X >= mDisplayW) { return; }
+
+ for (auto B{ 0 }; B < 8; ++B, ++X &= mDisplayWb) {
+ if (DATA & 0x80 >> B) {
+ if (!((mDisplayBuffer[P].at_raw(Y, X) ^= 1) & 1))
+ { mRegisterV[0xF] = 1; }
+ }
+ if (!Quirk.wrapSprite && X == mDisplayWb) { return; }
+ }
+ return;
+ }
+ }
+
+ // DXYN - draw N sprite rows at VX and VY
+ void XOCHIP::instruction_DxyN(const s32 X, const s32 Y, const s32 N) noexcept {
+ if (!mPlanarMask) {
+ mRegisterV[0xF] = 0;
+ } else {
+ const auto pX{ mRegisterV[X] & mDisplayWb };
+ const auto pY{ mRegisterV[Y] & mDisplayHb };
+
+ mRegisterV[0xF] = 0;
+
+ if (N == 0) {
+ for (auto P{ 0 }, I{ 0 }; P < 4; ++P) {
+ if (!(mPlanarMask & 1 << P)) { continue; }
+
+ for (auto tN{ 0 }, tY{ pY }; tN < 16; ++tN) {
+ drawByte(pX + 0, tY, P, readMemoryI(I + 0));
+ drawByte(pX + 8, tY, P, readMemoryI(I + 1));
+ if (!Quirk.wrapSprite && tY == mDisplayHb) { break; }
+ else { I += 2; ++tY &= mDisplayHb; }
+ }
+ }
+ } else [[likely]] {
+ for (auto P{ 0 }, I{ 0 }; P < 4; ++P) {
+ if (!(mPlanarMask & 1 << P)) { continue; }
+
+ for (auto tN{ 0 }, tY{ pY }; tN < N; ++tN) {
+ drawByte(pX, tY, P, readMemoryI(I));
+ if (!Quirk.wrapSprite && tY == mDisplayHb) { break; }
+ else { I += 1; ++tY &= mDisplayHb; }
+ }
+ }
+ }
+ }
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region E instruction branch
+
+ // EX9E - skip next instruction if key VX down (p1)
+ void XOCHIP::instruction_Ex9E(const s32 X) noexcept {
+ if (keyHeld_P1(mRegisterV[X])) { nextInstruction(); }
+ }
+ // EXA1 - skip next instruction if key VX up (p1)
+ void XOCHIP::instruction_ExA1(const s32 X) noexcept {
+ if (!keyHeld_P1(mRegisterV[X])) { nextInstruction(); }
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region F instruction branch
+
+ // F000 - set I = NEXT NNNN then skip instruction
+ void XOCHIP::instruction_F000() noexcept {
+ mRegisterI = NNNN();
+ nextInstruction();
+ }
+ // F002 - load 16-byte audio pattern from RAM at I
+ void XOCHIP::instruction_F002() noexcept {
+ for (auto idx{ 0 }; idx < 16; ++idx) {
+ mSamplePattern[idx] = readMemoryI(idx);
+ }
+ isBuzzerEnabled(false);
+ }
+ // FN01 - set plane drawing to N
+ void XOCHIP::instruction_FN01(const s32 N) noexcept {
+ mPlanarMask = N;
+ }
+ // FX07 - set VX = delay timer
+ void XOCHIP::instruction_Fx07(const s32 X) noexcept {
+ mRegisterV[X] = static_cast(mDelayTimer);
+ }
+ // FX0A - set VX = key, wait for keypress
+ void XOCHIP::instruction_Fx0A(const s32 X) noexcept {
+ triggerInterrupt(Interrupt::INPUT);
+ mInputReg = &mRegisterV[X];
+ }
+ // FX15 - set delay timer = VX
+ void XOCHIP::instruction_Fx15(const s32 X) noexcept {
+ mDelayTimer = static_cast(mRegisterV[X]);
+ }
+ // FX18 - set sound timer = VX
+ void XOCHIP::instruction_Fx18(const s32 X) noexcept {
+ mBuzzerTone = calcBuzzerTone();
+ mSoundTimer = mRegisterV[X] + (mRegisterV[X] == 1);
+ }
+ // FX1E - set I = I + VX
+ void XOCHIP::instruction_Fx1E(const s32 X) noexcept {
+ mRegisterI = mRegisterI + mRegisterV[X] & 0xFFFF;
+ }
+ // FX29 - point I to 5 byte hex sprite from value in VX
+ void XOCHIP::instruction_Fx29(const s32 X) noexcept {
+ mRegisterI = (mRegisterV[X] & 0xF) * 5;
+ }
+ // FX30 - point I to 10 byte hex sprite from value in VX
+ void XOCHIP::instruction_Fx30(const s32 X) noexcept {
+ mRegisterI = (mRegisterV[X] & 0xF) * 10 + 80;
+ }
+ // FX33 - store BCD of VX to RAM at I, I+1, I+2
+ void XOCHIP::instruction_Fx33(const s32 X) noexcept {
+ writeMemoryI(mRegisterV[X] / 100, 0);
+ writeMemoryI(mRegisterV[X] / 10 % 10, 1);
+ writeMemoryI(mRegisterV[X] % 10, 2);
+ }
+ // FX3A - set sound pitch = VX
+ void XOCHIP::instruction_Fx3A(const s32 X) noexcept {
+ const auto stepValue{ (mRegisterV[X] - 64.0f) / 48.0f };
+ mAudioTone = mAudioStep * std::pow(2.0f, stepValue);
+ isBuzzerEnabled(false);
+ }
+ // FN55 - store V0..VX to RAM at I..I+N
+ void XOCHIP::instruction_FN55(const s32 N) noexcept {
+ for (auto idx{ 0 }; idx <= N; ++idx)
+ { writeMemoryI(mRegisterV[idx], idx); }
+ if (!Quirk.idxRegNoInc) [[likely]]
+ { mRegisterI = mRegisterI + N + 1 & 0xFFFF; }
+ }
+ // FN65 - load V0..VN from RAM at I..I+N
+ void XOCHIP::instruction_FN65(const s32 N) noexcept {
+ for (auto idx{ 0 }; idx <= N; ++idx)
+ { mRegisterV[idx] = readMemoryI(idx); }
+ if (!Quirk.idxRegNoInc) [[likely]]
+ { mRegisterI = mRegisterI + N + 1 & 0xFFFF; }
+ }
+ // FN75 - store V0..VN to the P flags
+ void XOCHIP::instruction_FN75(const s32 N) noexcept {
+ if (setPermaRegs(N + 1)) [[unlikely]]
+ { triggerCritError("Error :: Failed writing persistent registers!"); }
+ }
+ // FN85 - load V0..VN from the P flags
+ void XOCHIP::instruction_FN85(const s32 N) noexcept {
+ if (getPermaRegs(N + 1)) [[unlikely]]
+ { triggerCritError("Error :: Failed reading persistent registers!"); }
+ }
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
diff --git a/src/Systems/CHIP8/Cores/XOCHIP.hpp b/src/Systems/CHIP8/Cores/XOCHIP.hpp
new file mode 100644
index 0000000..ddfda85
--- /dev/null
+++ b/src/Systems/CHIP8/Cores/XOCHIP.hpp
@@ -0,0 +1,299 @@
+/*
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+#pragma once
+
+#include "../../../Assistants/Map2D.hpp"
+
+#include "../Chip8_CoreInterface.hpp"
+
+/*==================================================================*/
+
+class XOCHIP final : public Chip8_CoreInterface {
+ static constexpr u32 cTotalMemory{ 65536 };
+ static constexpr u32 cSafezoneOOB{ 32 };
+ static constexpr u32 cGameLoadPos{ 512 };
+ static constexpr u32 cStartOffset{ 512 };
+ static constexpr f32 cRefreshRate{ 60.0f };
+ static constexpr s32 cScreenSizeX{ 64 };
+ static constexpr s32 cScreenSizeY{ 32 };
+ static constexpr s32 cInstSpeedHi{ 50000 };
+ static constexpr s32 cInstSpeedLo{ 1000 };
+
+private:
+ std::array mBitsColor{};
+
+ void setColorBit332(const s32 bit, const s32 color) noexcept;
+
+/*==================================================================*/
+
+ std::array mSamplePattern{};
+
+ f32 mAudioStep{};
+ f32 mAudioTone{};
+
+/*==================================================================*/
+
+ Map2D mDisplayBuffer[4];
+
+ std::array
+ mMemoryBank{};
+
+ void writeMemoryI(const u32 value, const u32 pos) noexcept {
+ const auto index{ mRegisterI + pos };
+ if (!(index & cTotalMemory)) [[likely]]
+ { mMemoryBank[index] = value & 0xFF; }
+ }
+
+ auto readMemoryI(const u32 pos) const noexcept {
+ return mMemoryBank[mRegisterI + pos];
+ }
+
+/*==================================================================*/
+
+ auto NNNN() const noexcept { return mMemoryBank[mCurrentPC] << 8 | mMemoryBank[mCurrentPC + 1]; }
+
+public:
+ XOCHIP();
+
+ static constexpr bool testGameSize(const usz size) noexcept {
+ return size + cGameLoadPos <= cTotalMemory;
+ }
+
+private:
+ void handleTimerTick() noexcept override;
+ void instructionLoop() noexcept override;
+
+ void renderAudioData() override;
+ void renderVideoData() override;
+
+ void prepDisplayArea(const Resolution mode) override;
+
+ void nextInstruction() noexcept;
+ void skipInstruction() noexcept;
+ void jumpProgramTo(const u32 next) noexcept;
+
+ void scrollDisplayUP(const s32 N);
+ void scrollDisplayDN(const s32 N);
+ void scrollDisplayLT(const s32);
+ void scrollDisplayRT(const s32);
+
+/*==================================================================*/
+ #pragma region 0 instruction branch
+
+ // 00DN - scroll selected color plane N lines down
+ void instruction_00CN(const s32 N) noexcept;
+ // 00DN - scroll selected color plane N lines up
+ void instruction_00DN(const s32 N) noexcept;
+ // 00E0 - erase whole display
+ void instruction_00E0() noexcept;
+ // 00EE - return from subroutine
+ void instruction_00EE() noexcept;
+ // 00FB - scroll selected color plane 4 pixels right
+ void instruction_00FB() noexcept;
+ // 00FC - scroll selected color plane 4 pixels left
+ void instruction_00FC() noexcept;
+ // 00FD - stop signal
+ void instruction_00FD() noexcept;
+ // 00FE - display == 64*32, erase the screen
+ void instruction_00FE() noexcept;
+ // 00FF - display == 128*64, erase the screen
+ void instruction_00FF() noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 1 instruction branch
+
+ // 1NNN - jump to NNN
+ void instruction_1NNN(const s32 NNN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 2 instruction branch
+
+ // 2NNN - call subroutine at NNN
+ void instruction_2NNN(const s32 NNN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 3 instruction branch
+
+ // 3XNN - skip next instruction if VX == NN
+ void instruction_3xNN(const s32 X, const s32 NN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 4 instruction branch
+
+ // 4XNN - skip next instruction if VX != NN
+ void instruction_4xNN(const s32 X, const s32 NN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 5 instruction branch
+
+ // 5XY0 - skip next instruction if VX == VY
+ void instruction_5xy0(const s32 X, const s32 Y) noexcept;
+ // 5XY2 - store range of registers to memory *XOCHIP*
+ void instruction_5xy2(const s32 X, const s32 Y) noexcept;
+ // 5XY3 - load range of registers from memory *XOCHIP*
+ void instruction_5xy3(const s32 X, const s32 Y) noexcept;
+ // 5XY4 - load range of colors from memory *EXPERIMENTAL*
+ void instruction_5xy4(const s32 X, const s32 Y) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 6 instruction branch
+
+ // 6XNN - set VX = NN
+ void instruction_6xNN(const s32 X, const s32 NN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 7 instruction branch
+
+ // 7XNN - set VX = VX + NN
+ void instruction_7xNN(const s32 X, const s32 NN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 8 instruction branch
+
+ // 8XY0 - set VX = VY
+ void instruction_8xy0(const s32 X, const s32 Y) noexcept;
+ // 8XY1 - set VX = VX | VY
+ void instruction_8xy1(const s32 X, const s32 Y) noexcept;
+ // 8XY2 - set VX = VX & VY
+ void instruction_8xy2(const s32 X, const s32 Y) noexcept;
+ // 8XY3 - set VX = VX ^ VY
+ void instruction_8xy3(const s32 X, const s32 Y) noexcept;
+ // 8XY4 - set VX = VX + VY, VF = carry
+ void instruction_8xy4(const s32 X, const s32 Y) noexcept;
+ // 8XY5 - set VX = VX - VY, VF = !borrow
+ void instruction_8xy5(const s32 X, const s32 Y) noexcept;
+ // 8XY7 - set VX = VY - VX, VF = !borrow
+ void instruction_8xy7(const s32 X, const s32 Y) noexcept;
+ // 8XY6 - set VX = VY >> 1, VF = carry
+ void instruction_8xy6(const s32 X, const s32 Y) noexcept;
+ // 8XYE - set VX = VY << 1, VF = carry
+ void instruction_8xyE(const s32 X, const s32 Y) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region 9 instruction branch
+
+ // 9XY0 - skip next instruction if VX != VY
+ void instruction_9xy0(const s32 X, const s32 Y) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region A instruction branch
+
+ // ANNN - set I = NNN
+ void instruction_ANNN(const s32 NNN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region B instruction branch
+
+ // BXNN - jump to NNN + V0
+ void instruction_BNNN(const s32 NNN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region C instruction branch
+
+ // CXNN - set VX = rnd(256) & NN
+ void instruction_CxNN(const s32 X, const s32 NN) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region D instruction branch
+
+ void drawByte(s32 X, s32 Y, const s32 P, const u32 DATA) noexcept;
+
+ // DXYN - draw N sprite rows at VX and VY
+ void instruction_DxyN(const s32 X, const s32 Y, const s32 N) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region E instruction branch
+
+ // EX9E - skip next instruction if key VX down (p1)
+ void instruction_Ex9E(const s32 X) noexcept;
+ // EXA1 - skip next instruction if key VX up (p1)
+ void instruction_ExA1(const s32 X) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+
+/*==================================================================*/
+ #pragma region F instruction branch
+
+ // F000 - set I = NEXT NNNN then skip instruction
+ void instruction_F000() noexcept;
+ // F002 - load 16-byte audio pattern from RAM at I
+ void instruction_F002() noexcept;
+ // FN01 - set plane drawing to N
+ void instruction_FN01(const s32 N) noexcept;
+ // FX07 - set VX = delay timer
+ void instruction_Fx07(const s32 X) noexcept;
+ // FX0A - set VX = key, wait for keypress
+ void instruction_Fx0A(const s32 X) noexcept;
+ // FX15 - set delay timer = VX
+ void instruction_Fx15(const s32 X) noexcept;
+ // FX18 - set sound timer = VX
+ void instruction_Fx18(const s32 X) noexcept;
+ // FX1E - set I = I + VX
+ void instruction_Fx1E(const s32 X) noexcept;
+ // FX29 - point I to 5 byte hex sprite from value in VX
+ void instruction_Fx29(const s32 X) noexcept;
+ // FX30 - point I to 10 byte hex sprite from value in VX
+ void instruction_Fx30(const s32 X) noexcept;
+ // FX33 - store BCD of VX to RAM at I, I+1, I+2
+ void instruction_Fx33(const s32 X) noexcept;
+ // FX3A - set sound pitch = VX
+ void instruction_Fx3A(const s32 X) noexcept;
+ // FN55 - store V0..VN to RAM at I..I+N
+ void instruction_FN55(const s32 N) noexcept;
+ // FN65 - load V0..VN from RAM at I..I+N
+ void instruction_FN65(const s32 N) noexcept;
+ // FN75 - store V0..VN to the P flags
+ void instruction_FN75(const s32 N) noexcept;
+ // FN85 - load V0..VN from the P flags
+ void instruction_FN85(const s32 N) noexcept;
+
+ #pragma endregion
+/*VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV*/
+};
diff --git a/src/Systems/EmuInterface.hpp b/src/Systems/EmuInterface.hpp
index 28556d4..4338d6f 100644
--- a/src/Systems/EmuInterface.hpp
+++ b/src/Systems/EmuInterface.hpp
@@ -8,6 +8,7 @@
#include
#include
+#include
#include
#include
#include
diff --git a/src/Systems/GameFileChecker.cpp b/src/Systems/GameFileChecker.cpp
index bf9dc6f..6e78e6c 100644
--- a/src/Systems/GameFileChecker.cpp
+++ b/src/Systems/GameFileChecker.cpp
@@ -16,6 +16,7 @@ using json = nlohmann::json;
#include "CHIP8/Cores/CHIP8_MODERN.hpp"
+#include "CHIP8/Cores/XOCHIP.hpp"
#include "BYTEPUSHER/Cores/BYTEPUSHER_STANDARD.hpp"
@@ -41,7 +42,7 @@ std::unique_ptr GameFileChecker::constructCore() {
try {
switch (sEmuCore) {
case GameCoreType::XOCHIP:
- //return std::make_unique();
+ return std::make_unique();
case GameCoreType::CHIP8E:
//return std::make_unique();
@@ -202,8 +203,7 @@ bool GameFileChecker::validate(
case (GameFileType::xo8):
return testGame(
- true,
- //XOCHIP::testGameSize(size),
+ XOCHIP::testGameSize(size),
GameCoreType::XOCHIP
);