Compare commits
23 Commits
eaec6ed8ab
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
02b5f6ecaf
|
|||
|
4125e866b4
|
|||
|
437fab8efe
|
|||
|
4b897f48e0
|
|||
|
a1a52024c8
|
|||
|
a477a21d7f
|
|||
|
a803e733da
|
|||
|
b9d2377088
|
|||
|
d8e3aa16f8
|
|||
|
ab4a43163a
|
|||
|
82ed59edeb
|
|||
|
95b1fb3b43
|
|||
|
f469e3d27b
|
|||
|
cb0d31fb9e
|
|||
|
8b20f4499d
|
|||
|
5ebc235cae
|
|||
|
4d129018e3
|
|||
|
b186b25a9c
|
|||
|
de3b9f6769
|
|||
|
13010642b6
|
|||
|
ae863ee4e9
|
|||
|
b0d8e6135d
|
|||
|
8df1300bd1
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,4 +13,4 @@ _deps
|
|||||||
CMakeUserPresets.json
|
CMakeUserPresets.json
|
||||||
cmake-build-debug
|
cmake-build-debug
|
||||||
imgui.ini
|
imgui.ini
|
||||||
|
cmake-build*/
|
||||||
8
.idea/dictionaries/project.xml
generated
Normal file
8
.idea/dictionaries/project.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<component name="ProjectDictionaryState">
|
||||||
|
<dictionary name="project">
|
||||||
|
<words>
|
||||||
|
<w>cosmac</w>
|
||||||
|
<w>skrd</w>
|
||||||
|
</words>
|
||||||
|
</dictionary>
|
||||||
|
</component>
|
||||||
21
.idea/editor.xml
generated
21
.idea/editor.xml
generated
@@ -112,11 +112,12 @@
|
|||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
@@ -243,23 +244,5 @@
|
|||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
|
||||||
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ALLOW_COMMENT_AFTER_LBRACE/@EntryValue" value="true" type="bool" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/CASE_BLOCK_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/DISABLE_SPACE_CHANGES_BEFORE_TRAILING_COMMENT/@EntryValue" value="true" type="bool" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/EMPTY_BLOCK_STYLE/@EntryValue" value="TOGETHER" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/EXPORT_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INDENT_CASE_FROM_SWITCH/@EntryValue" value="true" type="bool" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_COMMENTS/@EntryValue" value="true" type="bool" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INT_ALIGN_ENUM_INITIALIZERS/@EntryValue" value="true" type="bool" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/INVOCABLE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/NAMESPACE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/OTHER_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_CATCH_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_ELSE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/PLACE_WHILE_ON_NEW_LINE/@EntryValue" value="false" type="bool" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/REQUIRES_EXPRESSION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CodeFormatting/CppFormatting/TYPE_DECLARATION_BRACES/@EntryValue" value="END_OF_LINE" type="string" />
|
|
||||||
<option name="/Default/CodeStyle/CppIncludeDirective/SortIncludeDirectives/@EntryValue" value="true" type="bool" />
|
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
9
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
9
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="ClangTidy" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
|
<option name="clangTidyChecks" value="-*,bugprone-argument-comment,bugprone-assert-side-effect,bugprone-bad-signal-to-kill-thread,bugprone-branch-clone,bugprone-copy-constructor-init,bugprone-dangling-handle,bugprone-dynamic-static-initializers,bugprone-fold-init-type,bugprone-forward-declaration-namespace,bugprone-forwarding-reference-overload,bugprone-inaccurate-erase,bugprone-incorrect-roundings,bugprone-integer-division,bugprone-lambda-function-name,bugprone-macro-parentheses,bugprone-macro-repeated-side-effects,bugprone-misplaced-operator-in-strlen-in-alloc,bugprone-misplaced-pointer-arithmetic-in-alloc,bugprone-misplaced-widening-cast,bugprone-move-forwarding-reference,bugprone-multiple-statement-macro,bugprone-no-escape,bugprone-parent-virtual-call,bugprone-posix-return,bugprone-reserved-identifier,bugprone-sizeof-container,bugprone-sizeof-expression,bugprone-spuriously-wake-up-functions,bugprone-string-constructor,bugprone-string-integer-assignment,bugprone-string-literal-with-embedded-nul,bugprone-suspicious-enum-usage,bugprone-suspicious-include,bugprone-suspicious-memset-usage,bugprone-suspicious-missing-comma,bugprone-suspicious-semicolon,bugprone-suspicious-string-compare,bugprone-suspicious-memory-comparison,bugprone-suspicious-realloc-usage,bugprone-swapped-arguments,bugprone-terminating-continue,bugprone-throw-keyword-missing,bugprone-too-small-loop-variable,bugprone-undefined-memory-manipulation,bugprone-undelegated-constructor,bugprone-unhandled-self-assignment,bugprone-unused-raii,bugprone-unused-return-value,bugprone-use-after-move,bugprone-virtual-near-miss,cert-dcl21-cpp,cert-dcl58-cpp,cert-err34-c,cert-err52-cpp,cert-err60-cpp,cert-flp30-c,cert-msc50-cpp,cert-msc51-cpp,cert-str34-c,cppcoreguidelines-interfaces-global-init,cppcoreguidelines-narrowing-conversions,cppcoreguidelines-pro-type-member-init,cppcoreguidelines-pro-type-static-cast-downcast,cppcoreguidelines-slicing,google-default-arguments,google-explicit-constructor,google-runtime-operator,hicpp-exception-baseclass,hicpp-multiway-paths-covered,misc-misplaced-const,misc-new-delete-overloads,misc-no-recursion,misc-non-copyable-objects,misc-throw-by-value-catch-by-reference,misc-unconventional-assign-operator,misc-uniqueptr-reset-release,modernize-avoid-bind,modernize-concat-nested-namespaces,modernize-deprecated-headers,modernize-deprecated-ios-base-aliases,modernize-loop-convert,modernize-make-shared,modernize-make-unique,modernize-pass-by-value,modernize-raw-string-literal,modernize-redundant-void-arg,modernize-replace-auto-ptr,modernize-replace-disallow-copy-and-assign-macro,modernize-replace-random-shuffle,modernize-return-braced-init-list,modernize-shrink-to-fit,modernize-unary-static-assert,modernize-use-auto,modernize-use-bool-literals,modernize-use-emplace,modernize-use-equals-default,modernize-use-equals-delete,modernize-use-nodiscard,modernize-use-noexcept,modernize-use-nullptr,modernize-use-override,modernize-use-transparent-functors,modernize-use-uncaught-exceptions,mpi-buffer-deref,mpi-type-mismatch,openmp-use-default-none,performance-faster-string-find,performance-for-range-copy,performance-implicit-conversion-in-loop,performance-inefficient-algorithm,performance-inefficient-string-concatenation,performance-inefficient-vector-operation,performance-move-const-arg,performance-move-constructor-init,performance-no-automatic-move,performance-noexcept-move-constructor,performance-trivially-destructible,performance-type-promotion-in-math-fn,performance-unnecessary-copy-initialization,performance-unnecessary-value-param,portability-simd-intrinsics,readability-avoid-const-params-in-decls,readability-const-return-type,readability-container-size-empty,readability-delete-null-pointer,readability-deleted-default,readability-inconsistent-declaration-parameter-name,readability-make-member-function-const,readability-misleading-indentation,readability-misplaced-array-index,readability-non-const-parameter,readability-redundant-control-flow,readability-redundant-declaration,readability-redundant-function-ptr-dereference,readability-redundant-smartptr-get,readability-redundant-string-cstr,readability-redundant-string-init,readability-simplify-subscript-expr,readability-static-accessed-through-instance,readability-static-definition-in-anonymous-namespace,readability-string-compare,readability-uniqueptr-delete-release,readability-use-anyofallof" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="CppMemberFunctionMayBeStatic" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
10
.idea/misc.xml
generated
10
.idea/misc.xml
generated
@@ -4,4 +4,14 @@
|
|||||||
<option name="pythonIntegrationState" value="YES" />
|
<option name="pythonIntegrationState" value="YES" />
|
||||||
</component>
|
</component>
|
||||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||||
|
<component name="CidrRootsConfiguration">
|
||||||
|
<libraryRoots>
|
||||||
|
<file path="$PROJECT_DIR$/vendor" />
|
||||||
|
<file path="$PROJECT_DIR$/vendor/imgui" />
|
||||||
|
</libraryRoots>
|
||||||
|
<excludeRoots>
|
||||||
|
<file path="$PROJECT_DIR$/src/old" />
|
||||||
|
</excludeRoots>
|
||||||
|
</component>
|
||||||
|
<component name="WestSettings"><![CDATA[{}]]></component>
|
||||||
</project>
|
</project>
|
||||||
@@ -1,10 +1,43 @@
|
|||||||
add_executable(${PROJECT_NAME}
|
add_executable(${PROJECT_NAME}
|
||||||
Chip8.cpp
|
|
||||||
Chip8.h
|
|
||||||
Interpreter.cpp
|
|
||||||
Interpreter.h)
|
|
||||||
target_sources(${PROJECT_NAME} PRIVATE main.cpp
|
|
||||||
Graphics.cpp
|
|
||||||
Graphics.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_sources(
|
||||||
|
${PROJECT_NAME}
|
||||||
|
PRIVATE
|
||||||
|
main.cpp
|
||||||
|
Machine.cpp
|
||||||
|
Machine.h
|
||||||
|
Sound.cpp
|
||||||
|
Sound.h
|
||||||
|
Graphics/Graphics.cpp
|
||||||
|
Graphics/Graphics.h
|
||||||
|
Graphics/Color.h
|
||||||
|
Interpreter/MachineState.cpp
|
||||||
|
Interpreter/MachineState.h
|
||||||
|
Interpreter/Interpreter.cpp
|
||||||
|
Interpreter/Interpreter.h
|
||||||
|
Interpreter/Instruction.h
|
||||||
|
Interpreter/OpCode.h
|
||||||
|
UI/CallbackManager.cpp
|
||||||
|
UI/CallbackManager.h
|
||||||
|
UI/ControlPanel.cpp
|
||||||
|
UI/ControlPanel.h
|
||||||
|
UI/Display.cpp
|
||||||
|
UI/Display.h
|
||||||
|
UI/MemoryViewer.cpp
|
||||||
|
UI/MemoryViewer.h
|
||||||
|
UI/RomInfo.cpp
|
||||||
|
UI/RomInfo.h
|
||||||
|
UI/UIManager.cpp
|
||||||
|
UI/UIManager.h
|
||||||
|
UI/Disassembler.cpp
|
||||||
|
UI/Disassembler.h
|
||||||
|
UI/RegisterView.cpp
|
||||||
|
UI/RegisterView.h
|
||||||
|
UI/StackViewer.cpp
|
||||||
|
UI/StackViewer.h
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE vendor)
|
target_link_libraries(${PROJECT_NAME} PRIVATE vendor)
|
||||||
|
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror)
|
||||||
|
target_link_options(${PROJECT_NAME} PRIVATE)
|
||||||
133
src/Chip8.cpp
133
src/Chip8.cpp
@@ -1,133 +0,0 @@
|
|||||||
#include "Chip8.h"
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <format>
|
|
||||||
#include <fstream>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "bitops.h"
|
|
||||||
|
|
||||||
Chip8::Chip8(): machine_state{std::make_shared<MachineState>()},
|
|
||||||
graphics{machine_state},
|
|
||||||
interpreter{machine_state, 0},
|
|
||||||
target_cycle_time{1.0 / 700.0},
|
|
||||||
last_update_time{0},
|
|
||||||
accumulator{0},
|
|
||||||
run{true},
|
|
||||||
step{false} {}
|
|
||||||
|
|
||||||
bool Chip8::init() {
|
|
||||||
if (!graphics.init()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
last_update_time = SDL_GetTicks();
|
|
||||||
accumulator = 0;
|
|
||||||
|
|
||||||
const auto rom = read_rom("roms/1-chip8-logo.ch8");
|
|
||||||
interpreter.load_rom(rom);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chip8::set_keyboard_state(std::span<const bool> keyboard_state) {
|
|
||||||
this->keyboard_state = keyboard_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> Chip8::read_rom(const std::string& path) {
|
|
||||||
std::ifstream rom_file(path, std::ios::binary);
|
|
||||||
|
|
||||||
rom_file.seekg(0, std::ios::end);
|
|
||||||
const std::streampos file_size = rom_file.tellg();
|
|
||||||
rom_file.seekg(0, std::ios::beg);
|
|
||||||
|
|
||||||
std::cout << "ROM size: " << file_size << std::endl;
|
|
||||||
|
|
||||||
std::vector<std::uint8_t> rom(file_size);
|
|
||||||
|
|
||||||
rom.insert(
|
|
||||||
rom.begin(),
|
|
||||||
std::istreambuf_iterator<char>(rom_file),
|
|
||||||
std::istreambuf_iterator<char>()
|
|
||||||
);
|
|
||||||
|
|
||||||
return rom;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chip8::load_keyboard() {
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_X] ? bit_set(machine_state->keyboard, 0x0) : bit_clear(machine_state->keyboard, 0x0);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_1] ? bit_set(machine_state->keyboard, 0x1) : bit_clear(machine_state->keyboard, 0x1);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_2] ? bit_set(machine_state->keyboard, 0x2) : bit_clear(machine_state->keyboard, 0x2);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_3] ? bit_set(machine_state->keyboard, 0x3) : bit_clear(machine_state->keyboard, 0x3);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_Q] ? bit_set(machine_state->keyboard, 0x4) : bit_clear(machine_state->keyboard, 0x4);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_W] ? bit_set(machine_state->keyboard, 0x5) : bit_clear(machine_state->keyboard, 0x5);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_E] ? bit_set(machine_state->keyboard, 0x6) : bit_clear(machine_state->keyboard, 0x6);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_A] ? bit_set(machine_state->keyboard, 0x7) : bit_clear(machine_state->keyboard, 0x7);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_S] ? bit_set(machine_state->keyboard, 0x8) : bit_clear(machine_state->keyboard, 0x8);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_D] ? bit_set(machine_state->keyboard, 0x9) : bit_clear(machine_state->keyboard, 0x9);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_Z] ? bit_set(machine_state->keyboard, 0xA) : bit_clear(machine_state->keyboard, 0xA);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_C] ? bit_set(machine_state->keyboard, 0xB) : bit_clear(machine_state->keyboard, 0xB);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_4] ? bit_set(machine_state->keyboard, 0xC) : bit_clear(machine_state->keyboard, 0xC);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_R] ? bit_set(machine_state->keyboard, 0xD) : bit_clear(machine_state->keyboard, 0xD);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_F] ? bit_set(machine_state->keyboard, 0xE) : bit_clear(machine_state->keyboard, 0xE);
|
|
||||||
machine_state->keyboard = keyboard_state[SDL_SCANCODE_V] ? bit_set(machine_state->keyboard, 0xF) : bit_clear(machine_state->keyboard, 0xF);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Chip8::update() {
|
|
||||||
load_keyboard();
|
|
||||||
|
|
||||||
std::stringstream buffer;
|
|
||||||
buffer << std::format(
|
|
||||||
"PC: {:03X} | SP: {:02X} | I {:02X} | DT {:02X} | ST {:02X} | INST: {:02X}{:02X} | V0-VF: ",
|
|
||||||
machine_state->pc,
|
|
||||||
machine_state->sp,
|
|
||||||
machine_state->i,
|
|
||||||
machine_state->dt,
|
|
||||||
machine_state->st,
|
|
||||||
machine_state->memory[machine_state->pc],
|
|
||||||
machine_state->memory[machine_state->pc + 1]
|
|
||||||
);
|
|
||||||
for (int i = 0; i < 16; ++i) {
|
|
||||||
buffer << std::format("{:02X}", machine_state->v[i]);
|
|
||||||
if (i < 15) buffer << ",";
|
|
||||||
}
|
|
||||||
buffer << " | ";
|
|
||||||
|
|
||||||
if (run) {
|
|
||||||
execute_instructions();
|
|
||||||
} else if (step) {
|
|
||||||
execute_instruction();
|
|
||||||
step = false;
|
|
||||||
last_update_time = SDL_GetTicks();
|
|
||||||
} else {
|
|
||||||
last_update_time = SDL_GetTicks();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
graphics.draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chip8::execute_instructions() {
|
|
||||||
const auto current_time = SDL_GetTicks();
|
|
||||||
const auto delta_time = static_cast<double>(current_time - last_update_time) / 1000.0;
|
|
||||||
last_update_time = current_time;
|
|
||||||
accumulator += delta_time;
|
|
||||||
|
|
||||||
while (accumulator >= target_cycle_time) {
|
|
||||||
execute_instruction();
|
|
||||||
accumulator -= target_cycle_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chip8::execute_instruction() {
|
|
||||||
interpreter.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Chip8::on_keydown(SDL_Scancode scancode) {
|
|
||||||
if (scancode == SDL_SCANCODE_F1) {
|
|
||||||
run = !run;
|
|
||||||
} else if (scancode == SDL_SCANCODE_F2) {
|
|
||||||
step = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
src/Chip8.h
40
src/Chip8.h
@@ -1,40 +0,0 @@
|
|||||||
#ifndef CHIP8_H
|
|
||||||
#define CHIP8_H
|
|
||||||
#include <span>
|
|
||||||
|
|
||||||
#include "Graphics.h"
|
|
||||||
#include "Interpreter.h"
|
|
||||||
|
|
||||||
|
|
||||||
class Chip8 {
|
|
||||||
std::shared_ptr<MachineState> machine_state;
|
|
||||||
Graphics graphics;
|
|
||||||
Interpreter interpreter;
|
|
||||||
|
|
||||||
double target_cycle_time;
|
|
||||||
|
|
||||||
Uint64 last_update_time;
|
|
||||||
double accumulator;
|
|
||||||
|
|
||||||
std::span<const bool> keyboard_state;
|
|
||||||
bool run;
|
|
||||||
bool step;
|
|
||||||
|
|
||||||
void load_keyboard();
|
|
||||||
void execute_instructions();
|
|
||||||
void execute_instruction();
|
|
||||||
public:
|
|
||||||
Chip8();
|
|
||||||
|
|
||||||
bool init();
|
|
||||||
|
|
||||||
void update();
|
|
||||||
|
|
||||||
std::vector<uint8_t> read_rom(const std::string& path);
|
|
||||||
|
|
||||||
void set_keyboard_state(std::span<const bool> keyboard_state);
|
|
||||||
void on_keydown(SDL_Scancode scancode);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //CHIP8_H
|
|
||||||
172
src/Graphics.cpp
172
src/Graphics.cpp
@@ -1,172 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by ryuuji on 6/20/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "Graphics.h"
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "imgui.h"
|
|
||||||
#include "imgui_impl_sdl3.h"
|
|
||||||
#include "imgui_impl_sdlrenderer3.h"
|
|
||||||
|
|
||||||
void SDLWindowDestroyer::operator()(SDL_Window* window) const {
|
|
||||||
std::cout << "Destroying window" << std::endl;
|
|
||||||
SDL_DestroyWindow(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLRendererDestroyer::operator()(SDL_Renderer* renderer) const {
|
|
||||||
std::cout << "Destroying renderer" << std::endl;
|
|
||||||
SDL_DestroyRenderer(renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLTextureDestroyer::operator()(SDL_Texture* texture) const {
|
|
||||||
std::cout << "Destroying texture" << std::endl;
|
|
||||||
SDL_DestroyTexture(texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
Graphics::Graphics(std::shared_ptr<MachineState> machine_state): machine_state{std::move(machine_state)},
|
|
||||||
window_width{1920},
|
|
||||||
window_height{1080},
|
|
||||||
chip8_width(64),
|
|
||||||
chip8_height(32),
|
|
||||||
main_scale(1.0f) {}
|
|
||||||
bool Graphics::init_sdl() {
|
|
||||||
SDL_SetAppMetadata("CHIP-8 Emulator", "0.0.1", "fun.skrd.chip8");
|
|
||||||
|
|
||||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
|
||||||
SDL_Log("Couldn't initialize SDL: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Window* raw_window = nullptr;
|
|
||||||
SDL_Renderer* raw_renderer = nullptr;
|
|
||||||
|
|
||||||
main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
|
|
||||||
SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
|
||||||
|
|
||||||
if (!SDL_CreateWindowAndRenderer("CHIP-8 Emulator", window_width * main_scale, window_height * main_scale, window_flags, &raw_window, &raw_renderer)) {
|
|
||||||
SDL_Log("Couldn't create window/renderer: %s", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->window = std::unique_ptr<SDL_Window, SDLWindowDestroyer>(raw_window);
|
|
||||||
this->renderer = std::unique_ptr<SDL_Renderer, SDLRendererDestroyer>(raw_renderer);
|
|
||||||
|
|
||||||
SDL_SetRenderVSync(renderer.get(), 1);
|
|
||||||
SDL_SetWindowPosition(window.get(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
||||||
SDL_ShowWindow(window.get());
|
|
||||||
|
|
||||||
SDL_Texture* raw_texture = SDL_CreateTexture(renderer.get(), SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, chip8_width, chip8_height);
|
|
||||||
this->texture = std::unique_ptr<SDL_Texture, SDLTextureDestroyer>(raw_texture);
|
|
||||||
SDL_SetTextureScaleMode(texture.get(), SDL_SCALEMODE_NEAREST);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Graphics::init_imgui() {
|
|
||||||
IMGUI_CHECKVERSION();
|
|
||||||
ImGui::CreateContext();
|
|
||||||
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
|
||||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
|
||||||
|
|
||||||
ImGui::StyleColorsDark();
|
|
||||||
ImGuiStyle& style = ImGui::GetStyle();
|
|
||||||
style.ScaleAllSizes(main_scale);
|
|
||||||
style.FontScaleDpi = main_scale;
|
|
||||||
|
|
||||||
ImGui_ImplSDL3_InitForSDLRenderer(window.get(), renderer.get());
|
|
||||||
ImGui_ImplSDLRenderer3_Init(renderer.get());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool Graphics::init() {
|
|
||||||
if (!init_sdl()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!init_imgui()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Graphics::draw() {
|
|
||||||
if (SDL_GetWindowFlags(window.get()) & SDL_WINDOW_MINIMIZED) {
|
|
||||||
SDL_Delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
build_chip8_texture();
|
|
||||||
|
|
||||||
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
|
||||||
|
|
||||||
ImGui_ImplSDLRenderer3_NewFrame();
|
|
||||||
ImGui_ImplSDL3_NewFrame();
|
|
||||||
ImGui::NewFrame();
|
|
||||||
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());
|
|
||||||
|
|
||||||
draw_chip8_widget();
|
|
||||||
|
|
||||||
ImGui::Render();
|
|
||||||
SDL_SetRenderScale(renderer.get(), io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);
|
|
||||||
SDL_SetRenderDrawColor(renderer.get(), 0, 0, 0, 0xFF);
|
|
||||||
SDL_RenderClear(renderer.get());
|
|
||||||
|
|
||||||
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), renderer.get());
|
|
||||||
SDL_RenderPresent(renderer.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Graphics::build_chip8_texture() {
|
|
||||||
SDL_Surface* surface = nullptr;
|
|
||||||
auto& display = machine_state->display;
|
|
||||||
|
|
||||||
if (SDL_LockTextureToSurface(texture.get(), nullptr, &surface)) {
|
|
||||||
SDL_FillSurfaceRect(surface, nullptr, SDL_MapRGB(SDL_GetPixelFormatDetails(surface->format), nullptr, 0, 0, 0));
|
|
||||||
|
|
||||||
for (int i = 0; i < display.size(); i++) {
|
|
||||||
if (display[i]) {
|
|
||||||
int x = (i % chip8_width);
|
|
||||||
int y = (i / chip8_width);
|
|
||||||
|
|
||||||
SDL_Rect rect = {x, y, 1, 1};
|
|
||||||
Uint32 color = SDL_MapRGB(
|
|
||||||
SDL_GetPixelFormatDetails(surface->format),
|
|
||||||
nullptr,
|
|
||||||
0,
|
|
||||||
255,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
SDL_FillSurfaceRect(surface, &rect, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SDL_UnlockTexture(texture.get());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Graphics::draw_chip8_widget() {
|
|
||||||
ImGui::Begin("CHIP-8");
|
|
||||||
|
|
||||||
const ImVec2 available_size = ImGui::GetContentRegionAvail();
|
|
||||||
|
|
||||||
int scale_x = static_cast<int>(available_size.x / chip8_width);
|
|
||||||
int scale_y = static_cast<int>(available_size.y / chip8_height);
|
|
||||||
int scale = std::max(1, std::min(scale_x, scale_y));
|
|
||||||
ImVec2 scaled_size(chip8_width * scale, chip8_height * scale);
|
|
||||||
|
|
||||||
ImVec2 cursor_pos = ImGui::GetCursorPos();
|
|
||||||
ImVec2 center_offset((available_size.x - scaled_size.x) / 2, (available_size.y - scaled_size.y) / 2);
|
|
||||||
ImGui::SetCursorPos(ImVec2(cursor_pos.x + center_offset.x, cursor_pos.y + center_offset.y));
|
|
||||||
|
|
||||||
ImGui::Image((ImTextureID)(intptr_t)texture.get(), scaled_size);
|
|
||||||
ImGui::End();
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by ryuuji on 6/20/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef RENDERER_H
|
|
||||||
#define RENDERER_H
|
|
||||||
#include <bitset>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "imgui.h"
|
|
||||||
#include "MachineState.h"
|
|
||||||
#include "SDL3/SDL.h"
|
|
||||||
|
|
||||||
struct SDLWindowDestroyer {
|
|
||||||
void operator()(SDL_Window *window) const;
|
|
||||||
};
|
|
||||||
struct SDLRendererDestroyer {
|
|
||||||
void operator()(SDL_Renderer *renderer) const;
|
|
||||||
};
|
|
||||||
struct SDLTextureDestroyer {
|
|
||||||
void operator()(SDL_Texture *texture) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Graphics {
|
|
||||||
std::shared_ptr<MachineState> machine_state;
|
|
||||||
|
|
||||||
std::unique_ptr<SDL_Window, SDLWindowDestroyer> window;
|
|
||||||
std::unique_ptr<SDL_Renderer, SDLRendererDestroyer> renderer;
|
|
||||||
std::unique_ptr<SDL_Texture, SDLTextureDestroyer> texture;
|
|
||||||
|
|
||||||
int window_width;
|
|
||||||
int window_height;
|
|
||||||
|
|
||||||
int chip8_width;
|
|
||||||
int chip8_height;
|
|
||||||
|
|
||||||
float main_scale;
|
|
||||||
|
|
||||||
bool init_sdl();
|
|
||||||
bool init_imgui();
|
|
||||||
|
|
||||||
void build_chip8_texture();
|
|
||||||
void draw_chip8_widget();
|
|
||||||
|
|
||||||
public:
|
|
||||||
Graphics(std::shared_ptr<MachineState> machine_state);
|
|
||||||
|
|
||||||
bool init();
|
|
||||||
void draw();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endif //RENDERER_H
|
|
||||||
16
src/Graphics/Color.h
Normal file
16
src/Graphics/Color.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef COLOR_H
|
||||||
|
#define COLOR_H
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct Color
|
||||||
|
{
|
||||||
|
uint8_t r;
|
||||||
|
uint8_t g;
|
||||||
|
uint8_t b;
|
||||||
|
|
||||||
|
Color(const uint8_t r, const uint8_t g, const uint8_t b) : r(r), g(g), b(b)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //COLOR_H
|
||||||
99
src/Graphics/Graphics.cpp
Normal file
99
src/Graphics/Graphics.cpp
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#include "Graphics.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "imgui_impl_sdl3.h"
|
||||||
|
#include "imgui_impl_sdlrenderer3.h"
|
||||||
|
|
||||||
|
Graphics::Graphics() : window_width{1366}, window_height{768}, main_scale{1.0f}
|
||||||
|
{
|
||||||
|
create_sdl();
|
||||||
|
create_imgui();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graphics::create_sdl()
|
||||||
|
{
|
||||||
|
SDL_SetAppMetadata("CHIP-8 Emulator", "0.0.1", "fun.skrd.chip8");
|
||||||
|
|
||||||
|
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO))
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::format("Couldn't initialize SDL: {}", SDL_GetError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Window* raw_window = nullptr;
|
||||||
|
SDL_Renderer* raw_renderer = nullptr;
|
||||||
|
|
||||||
|
this->main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
|
||||||
|
constexpr SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||||
|
|
||||||
|
if (!SDL_CreateWindowAndRenderer(
|
||||||
|
"CHIP-8 Emulator",
|
||||||
|
std::floor(static_cast<float>(this->window_width) * main_scale),
|
||||||
|
std::floor(static_cast<float>(this->window_height) * main_scale),
|
||||||
|
window_flags,
|
||||||
|
&raw_window,
|
||||||
|
&raw_renderer
|
||||||
|
))
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::format("Couldn't create window/renderer: {}", SDL_GetError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
this->window = std::shared_ptr<SDL_Window>(raw_window, SDL_DestroyWindow);
|
||||||
|
this->renderer = std::shared_ptr<SDL_Renderer>(raw_renderer, SDL_DestroyRenderer);
|
||||||
|
|
||||||
|
SDL_SetRenderVSync(this->renderer.get(), 1);
|
||||||
|
SDL_SetWindowPosition(this->window.get(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||||
|
SDL_ShowWindow(this->window.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graphics::create_imgui() const
|
||||||
|
{
|
||||||
|
IMGUI_CHECKVERSION();
|
||||||
|
ImGui::CreateContext();
|
||||||
|
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||||
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||||
|
|
||||||
|
ImGui::StyleColorsDark();
|
||||||
|
ImGuiStyle& style = ImGui::GetStyle();
|
||||||
|
style.ScaleAllSizes(this->main_scale);
|
||||||
|
style.FontScaleDpi = this->main_scale;
|
||||||
|
|
||||||
|
ImGui_ImplSDL3_InitForSDLRenderer(this->window.get(), this->renderer.get());
|
||||||
|
ImGui_ImplSDLRenderer3_Init(this->renderer.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SDL_Renderer> Graphics::get_renderer()
|
||||||
|
{
|
||||||
|
return this->renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<SDL_Window> Graphics::get_window()
|
||||||
|
{
|
||||||
|
return this->window;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graphics::start() const
|
||||||
|
{
|
||||||
|
ImGui_ImplSDLRenderer3_NewFrame();
|
||||||
|
ImGui_ImplSDL3_NewFrame();
|
||||||
|
ImGui::NewFrame();
|
||||||
|
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Graphics::end() const
|
||||||
|
{
|
||||||
|
const ImGuiIO& io = ImGui::GetIO();
|
||||||
|
|
||||||
|
ImGui::Render();
|
||||||
|
SDL_SetRenderScale(this->renderer.get(), io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y);
|
||||||
|
SDL_SetRenderDrawColor(this->renderer.get(), 0, 0, 0, 0xFF);
|
||||||
|
SDL_RenderClear(this->renderer.get());
|
||||||
|
|
||||||
|
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), this->renderer.get());
|
||||||
|
SDL_RenderPresent(this->renderer.get());
|
||||||
|
}
|
||||||
33
src/Graphics/Graphics.h
Normal file
33
src/Graphics/Graphics.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef GRAPHICS_H
|
||||||
|
#define GRAPHICS_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include "SDL3/SDL.h"
|
||||||
|
|
||||||
|
struct SDLWindowDestroyer
|
||||||
|
{
|
||||||
|
void operator()(SDL_Window* window) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Graphics
|
||||||
|
{
|
||||||
|
int window_width;
|
||||||
|
int window_height;
|
||||||
|
float main_scale;
|
||||||
|
|
||||||
|
std::shared_ptr<SDL_Window> window;
|
||||||
|
std::shared_ptr<SDL_Renderer> renderer;
|
||||||
|
|
||||||
|
void create_sdl();
|
||||||
|
void create_imgui() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Graphics();
|
||||||
|
|
||||||
|
void start() const;
|
||||||
|
void end() const;
|
||||||
|
std::shared_ptr<SDL_Renderer> get_renderer();
|
||||||
|
std::shared_ptr<SDL_Window> get_window();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //GRAPHICS_H
|
||||||
@@ -1,496 +0,0 @@
|
|||||||
#include "Interpreter.h"
|
|
||||||
|
|
||||||
#include <format>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <ios>
|
|
||||||
#include <iostream>
|
|
||||||
#include <stack>
|
|
||||||
#include <bits/ostream.tcc>
|
|
||||||
|
|
||||||
#include "SDL3/SDL_log.h"
|
|
||||||
|
|
||||||
Interpreter::Interpreter(std::shared_ptr<MachineState> machine_state, uint8_t quirks):
|
|
||||||
machine_state{std::move(machine_state)},
|
|
||||||
quirks{quirks} {
|
|
||||||
this->random_generator = std::mt19937(std::random_device{}());
|
|
||||||
this->load_fonts();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::load_fonts() {
|
|
||||||
constexpr uint8_t font_set[] = {
|
|
||||||
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
|
||||||
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
|
||||||
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
|
||||||
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
|
|
||||||
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
|
|
||||||
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
|
|
||||||
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
|
|
||||||
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
|
|
||||||
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
|
|
||||||
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
|
|
||||||
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
|
|
||||||
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
|
|
||||||
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
|
|
||||||
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
|
||||||
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
|
||||||
0xF0, 0x80, 0xF0, 0x80, 0x80 // F
|
|
||||||
};
|
|
||||||
|
|
||||||
std::copy(std::begin(font_set), std::end(font_set), machine_state->memory.begin() + 0x050);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Interpreter::load_rom(const std::vector<uint8_t>& rom) {
|
|
||||||
std::copy(std::begin(rom), std::end(rom), machine_state->memory.begin() + 0x200);
|
|
||||||
}
|
|
||||||
|
|
||||||
Instruction Interpreter::decode_next() {
|
|
||||||
const uint8_t high_word = machine_state->memory[machine_state->pc];
|
|
||||||
const uint8_t low_word = machine_state->memory[machine_state->pc + 1];
|
|
||||||
const uint16_t word = (high_word << 8) | low_word;
|
|
||||||
|
|
||||||
const uint8_t operation = (word & 0xF000) >> 12;
|
|
||||||
const uint8_t x = (word & 0x0F00) >> 8;
|
|
||||||
const uint8_t y = (word & 0x00F0) >> 4;
|
|
||||||
const uint8_t n = word & 0x000F;
|
|
||||||
const uint8_t kk = word & 0x00FF;
|
|
||||||
const uint16_t nnn = word & 0x0FFF;
|
|
||||||
|
|
||||||
OpCode op_code = OpCode::NOP;
|
|
||||||
|
|
||||||
if (operation == 0) {
|
|
||||||
if (kk == 0xE0) {
|
|
||||||
op_code = OpCode::CLS;
|
|
||||||
} else if (kk == 0xEE) {
|
|
||||||
op_code = OpCode::RET;
|
|
||||||
}
|
|
||||||
} else if (operation == 1) {
|
|
||||||
op_code = OpCode::JP_ADDR;
|
|
||||||
} else if (operation == 2) {
|
|
||||||
op_code = OpCode::CALL_ADDR;
|
|
||||||
} else if (operation == 3) {
|
|
||||||
op_code = OpCode::SE_VX_BYTE;
|
|
||||||
} else if (operation == 4) {
|
|
||||||
op_code = OpCode::SNE_VX_BYTE;
|
|
||||||
} else if (operation == 5) {
|
|
||||||
op_code = OpCode::SE_VX_VY;
|
|
||||||
} else if (operation == 6) {
|
|
||||||
op_code = OpCode::LD_VX_BYTE;
|
|
||||||
} else if (operation == 7) {
|
|
||||||
op_code = OpCode::ADD_VX_BYTE;
|
|
||||||
} else if (operation == 8) {
|
|
||||||
if (n == 0) {
|
|
||||||
op_code = OpCode::LD_VX_VY;
|
|
||||||
} else if (n == 1) {
|
|
||||||
op_code = OpCode::OR_VX_VY;
|
|
||||||
} else if (n == 2) {
|
|
||||||
op_code = OpCode::AND_VX_VY;
|
|
||||||
} else if (n == 3) {
|
|
||||||
op_code = OpCode::XOR_VX_VY;
|
|
||||||
} else if (n == 4) {
|
|
||||||
op_code = OpCode::ADD_VX_VY;
|
|
||||||
} else if (n == 5) {
|
|
||||||
op_code = OpCode::SUB_VX_VY;
|
|
||||||
} else if (n == 6) {
|
|
||||||
op_code = OpCode::SHR_VX_VY;
|
|
||||||
} else if (n == 7) {
|
|
||||||
op_code = OpCode::SUBN_VX_VY;
|
|
||||||
} else if (n == 0xE) {
|
|
||||||
op_code = OpCode::SHL_VX_VY;
|
|
||||||
}
|
|
||||||
} else if (operation == 9) {
|
|
||||||
op_code = OpCode::SNE_VX_VY;
|
|
||||||
} else if (operation == 0xA) {
|
|
||||||
op_code = OpCode::LD_I_ADDR;
|
|
||||||
} else if (operation == 0xB) {
|
|
||||||
op_code = OpCode::JP_V0_ADDR;
|
|
||||||
} else if (operation == 0xC) {
|
|
||||||
op_code = OpCode::RND_VX_BYTE;
|
|
||||||
} else if (operation == 0xD) {
|
|
||||||
op_code = OpCode::DRW_VX_VY_NIBBLE;
|
|
||||||
} else if (operation == 0xE) {
|
|
||||||
if (kk == 0x9E) {
|
|
||||||
op_code = OpCode::SKP_VX;
|
|
||||||
} else if (kk == 0xA1) {
|
|
||||||
op_code = OpCode::SKNP_VX;
|
|
||||||
}
|
|
||||||
} else if (operation == 0xF) {
|
|
||||||
if (kk == 0x07) {
|
|
||||||
op_code = OpCode::LD_VX_DT;
|
|
||||||
} else if (kk == 0x0A) {
|
|
||||||
op_code = OpCode::LD_VX_K;
|
|
||||||
} else if (kk == 0x15) {
|
|
||||||
op_code = OpCode::LD_DT_VX;
|
|
||||||
} else if (kk == 0x18) {
|
|
||||||
op_code = OpCode::LD_ST_VX;
|
|
||||||
} else if (kk == 0x1E) {
|
|
||||||
op_code = OpCode::ADD_I_VX;
|
|
||||||
} else if (kk == 0x29) {
|
|
||||||
op_code = OpCode::LD_F_VX;
|
|
||||||
} else if (kk == 0x33) {
|
|
||||||
op_code = OpCode::LD_B_VX;
|
|
||||||
} else if (kk == 0x55) {
|
|
||||||
op_code = OpCode::LD_I_VX;
|
|
||||||
} else if (kk == 0x65) {
|
|
||||||
op_code = OpCode::LD_VX_I;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Instruction{
|
|
||||||
.op_code = op_code,
|
|
||||||
.operation = operation,
|
|
||||||
.instruction = word,
|
|
||||||
.x = x,
|
|
||||||
.y = y,
|
|
||||||
.n = n,
|
|
||||||
.kk = kk,
|
|
||||||
.nnn = nnn
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::execute(const Instruction& instruction) {
|
|
||||||
if (instruction.op_code == OpCode::CLS) cls(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::RET) ret(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SYS_ADDR) sys_addr(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::JP_ADDR) jp_addr(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::CALL_ADDR) call_addr(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SE_VX_BYTE) se_vx_byte(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SNE_VX_BYTE) sne_vx_byte(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SE_VX_VY) se_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_VX_BYTE) ld_vx_byte(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::ADD_VX_BYTE) add_vx_byte(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_VX_VY) ld_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::OR_VX_VY) or_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::AND_VX_VY) and_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::XOR_VX_VY) xor_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::ADD_VX_VY) add_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SUB_VX_VY) sub_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SHR_VX_VY) shr_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SUBN_VX_VY) subn_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SHL_VX_VY) shl_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SNE_VX_VY) sne_vx_vy(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_I_ADDR) ld_i_addr(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::JP_V0_ADDR) jp_v0_addr(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::RND_VX_BYTE) rnd_vx_byte(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::DRW_VX_VY_NIBBLE) drw_vx_vy_nibble(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SKP_VX) skp_vx(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::SKNP_VX) sknp_vx(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_VX_DT) ld_vx_dt(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_VX_K) ld_vx_k(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_DT_VX) ld_dt_vx(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_ST_VX) ld_st_vx(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::ADD_I_VX) add_i_vx(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_F_VX) ld_f_vx(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_B_VX) ld_b_vx(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_I_VX) ld_i_vx(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::LD_VX_I) ld_vx_i(instruction);
|
|
||||||
else if (instruction.op_code == OpCode::NOP) nop(instruction);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void Interpreter::run() {
|
|
||||||
const Instruction instruction = decode_next();
|
|
||||||
|
|
||||||
machine_state->pc += 2;
|
|
||||||
|
|
||||||
execute(instruction);
|
|
||||||
|
|
||||||
if (machine_state->pc >= 0xFFF) {
|
|
||||||
SDL_Log("PC Outside of memory, going back 0x200");
|
|
||||||
machine_state->pc = 0x200;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::sys_addr(const Instruction& instruction) {
|
|
||||||
// NOP
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::nop(const Instruction& instruction) {
|
|
||||||
// NOP
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::cls(const Instruction& instruction) {
|
|
||||||
machine_state->display.fill(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ret(const Instruction& instruction) {
|
|
||||||
machine_state->sp -= 1;
|
|
||||||
machine_state->pc = machine_state->stack[machine_state->sp];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::jp_addr(const Instruction& instruction) {
|
|
||||||
machine_state->pc = instruction.nnn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::call_addr(const Instruction& instruction) {
|
|
||||||
machine_state->stack[machine_state->sp] = machine_state->pc;
|
|
||||||
machine_state->sp += 1;
|
|
||||||
machine_state->pc = instruction.nnn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::se_vx_byte(const Instruction& instruction) {
|
|
||||||
if (machine_state->v[instruction.x] == instruction.kk) {
|
|
||||||
machine_state->pc += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::sne_vx_byte(const Instruction& instruction) {
|
|
||||||
if (machine_state->v[instruction.x] != instruction.kk) {
|
|
||||||
machine_state->pc += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::se_vx_vy(const Instruction& instruction) {
|
|
||||||
if (machine_state->v[instruction.x] == machine_state->v[instruction.y]) {
|
|
||||||
machine_state->pc += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_vx_byte(const Instruction& instruction) {
|
|
||||||
machine_state->v[instruction.x] = instruction.kk;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::add_vx_byte(const Instruction& instruction) {
|
|
||||||
machine_state->v[instruction.x] += instruction.kk;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_vx_vy(const Instruction& instruction) {
|
|
||||||
machine_state->v[instruction.x] = machine_state->v[instruction.y];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::or_vx_vy(const Instruction& instruction) {
|
|
||||||
machine_state->v[instruction.x] |= machine_state->v[instruction.y];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::and_vx_vy(const Instruction& instruction) {
|
|
||||||
machine_state->v[instruction.x] &= machine_state->v[instruction.y];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::xor_vx_vy(const Instruction& instruction) {
|
|
||||||
machine_state->v[instruction.x] ^= machine_state->v[instruction.y];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::add_vx_vy(const Instruction& instruction) {
|
|
||||||
auto& vx = machine_state->v[instruction.x];
|
|
||||||
auto& vy = machine_state->v[instruction.y];
|
|
||||||
auto& vf = machine_state->v[0xF];
|
|
||||||
|
|
||||||
if (vx + vy > 0xFF) {
|
|
||||||
vf = 1;
|
|
||||||
} else {
|
|
||||||
vf = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
vx += vy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::sub_vx_vy(const Instruction& instruction) {
|
|
||||||
auto& vx = machine_state->v[instruction.x];
|
|
||||||
auto& vy = machine_state->v[instruction.y];
|
|
||||||
auto& vf = machine_state->v[0xF];
|
|
||||||
|
|
||||||
if (vx > vy) {
|
|
||||||
vf = 1;
|
|
||||||
} else {
|
|
||||||
vf = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
vx -= vy;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::shr_vx_vy(const Instruction& instruction) {
|
|
||||||
auto& vx = machine_state->v[instruction.x];
|
|
||||||
auto& vy = machine_state->v[instruction.y];
|
|
||||||
auto& vf = machine_state->v[0xF];
|
|
||||||
|
|
||||||
if (quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_SHIFT)) {
|
|
||||||
vx = vy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vx & 0x01) {
|
|
||||||
vf = 1;
|
|
||||||
} else {
|
|
||||||
vf = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
vx = vx >> 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::subn_vx_vy(const Instruction& instruction) {
|
|
||||||
auto& vx = machine_state->v[instruction.x];
|
|
||||||
auto& vy = machine_state->v[instruction.y];
|
|
||||||
auto& vf = machine_state->v[0xF];
|
|
||||||
|
|
||||||
if (vy > vx) {
|
|
||||||
vf = 1;
|
|
||||||
} else {
|
|
||||||
vf = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
vx = vy - vx;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::shl_vx_vy(const Instruction& instruction) {
|
|
||||||
auto& vx = machine_state->v[instruction.x];
|
|
||||||
auto& vy = machine_state->v[instruction.y];
|
|
||||||
auto& vf = machine_state->v[0xF];
|
|
||||||
|
|
||||||
if (quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_SHIFT)) {
|
|
||||||
vx = vy;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vx & 0x80) {
|
|
||||||
vf = 1;
|
|
||||||
} else {
|
|
||||||
vf = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
vx = vx << 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::sne_vx_vy(const Instruction& instruction) {
|
|
||||||
if (machine_state->v[instruction.x] != machine_state->v[instruction.y]) {
|
|
||||||
machine_state->pc += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_i_addr(const Instruction& instruction) {
|
|
||||||
machine_state->i = instruction.nnn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::jp_v0_addr(const Instruction& instruction) {
|
|
||||||
if (quirks & static_cast<uint8_t>(InterpreterQuirks::SUPER_CHIP_JUMP)) {
|
|
||||||
machine_state->pc = instruction.nnn + machine_state->v[instruction.x];
|
|
||||||
} else {
|
|
||||||
machine_state->pc = instruction.nnn + machine_state->v[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::rnd_vx_byte(const Instruction& instruction) {
|
|
||||||
auto random = std::uniform_int_distribution<uint8_t>(0, 0xFF);
|
|
||||||
const auto value = random(random_generator);
|
|
||||||
|
|
||||||
machine_state->v[instruction.x] = value & instruction.kk;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::drw_vx_vy_nibble(const Instruction& instruction) {
|
|
||||||
auto& memory = machine_state->memory;
|
|
||||||
auto& display = machine_state->display;
|
|
||||||
auto& vx = machine_state->v[instruction.x];
|
|
||||||
auto& vy = machine_state->v[instruction.y];
|
|
||||||
auto& vf = machine_state->v[0xF];
|
|
||||||
|
|
||||||
const uint8_t start_x = vx & 63;
|
|
||||||
const uint8_t start_y = vy & 31;
|
|
||||||
vf = 0;
|
|
||||||
|
|
||||||
for (auto row = 0; row < instruction.n; row++) {
|
|
||||||
const auto current_y = start_y + row;
|
|
||||||
|
|
||||||
if (current_y > 31) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto sprite_byte = memory[machine_state->i + row];
|
|
||||||
|
|
||||||
for (auto bit = 0; bit < 8; bit++) {
|
|
||||||
const auto current_x = start_x + bit;
|
|
||||||
|
|
||||||
if (current_x > 63) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto pixel = (sprite_byte >> (7 - bit)) & 0x01;
|
|
||||||
const auto index = (current_y * 64) + current_x;
|
|
||||||
|
|
||||||
if (pixel) {
|
|
||||||
if (display[index]) {
|
|
||||||
display[index] = false;
|
|
||||||
vf = 1;
|
|
||||||
} else {
|
|
||||||
display[index] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::skp_vx(const Instruction& instruction) {
|
|
||||||
if (machine_state->keyboard & (1 << instruction.x)) {
|
|
||||||
machine_state->pc += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::sknp_vx(const Instruction& instruction) {
|
|
||||||
if (!(machine_state->keyboard & (1 << instruction.x))) {
|
|
||||||
machine_state->pc += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_vx_dt(const Instruction& instruction) {
|
|
||||||
machine_state->v[instruction.x] = machine_state->dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_vx_k(const Instruction& instruction) {
|
|
||||||
if (machine_state->keyboard == 0) {
|
|
||||||
machine_state->pc -= 2;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto key = 0; key < 16; key++) {
|
|
||||||
if (machine_state->keyboard & (1 << key)) {
|
|
||||||
machine_state->v[instruction.x] = key;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_dt_vx(const Instruction& instruction) {
|
|
||||||
machine_state->dt = machine_state->v[instruction.x];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_st_vx(const Instruction& instruction) {
|
|
||||||
machine_state->st = machine_state->v[instruction.x];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::add_i_vx(const Instruction& instruction) {
|
|
||||||
machine_state->i += machine_state->v[instruction.x];
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_f_vx(const Instruction& instruction) {
|
|
||||||
auto& vx = machine_state->v[instruction.x];
|
|
||||||
machine_state->i = ((vx & 0xF) * 5) + 0x50;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_b_vx(const Instruction& instruction) {
|
|
||||||
const auto number = machine_state->v[instruction.x];
|
|
||||||
machine_state->memory[machine_state->i] = number / 100;
|
|
||||||
machine_state->memory[machine_state->i + 1] = (number - (machine_state->memory[machine_state->i] * 100)) / 10;
|
|
||||||
machine_state->memory[machine_state->i + 2] = number - (machine_state->memory[machine_state->i] * 100) - (machine_state->memory[machine_state->i + 1] * 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_i_vx(const Instruction& instruction) {
|
|
||||||
bool use_quirk = quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_STORE_AND_LOAD);
|
|
||||||
|
|
||||||
for (auto reg = 0; reg <= instruction.x; reg++) {
|
|
||||||
if (use_quirk) {
|
|
||||||
machine_state->memory[machine_state->i] = machine_state->v[reg];
|
|
||||||
machine_state->i++;
|
|
||||||
} else {
|
|
||||||
machine_state->memory[machine_state->i + reg] = machine_state->v[reg];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Interpreter::ld_vx_i(const Instruction& instruction) {
|
|
||||||
bool use_quirk = quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_STORE_AND_LOAD);
|
|
||||||
|
|
||||||
for (auto reg = 0; reg <= instruction.x; reg++) {
|
|
||||||
if (use_quirk) {
|
|
||||||
machine_state->v[reg] = machine_state->memory[machine_state->i];
|
|
||||||
machine_state->i++;
|
|
||||||
} else {
|
|
||||||
machine_state->v[reg] = machine_state->memory[machine_state->i + reg];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
#ifndef INTERPRETER_H
|
|
||||||
#define INTERPRETER_H
|
|
||||||
#include <bitset>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <memory>
|
|
||||||
#include <random>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "MachineState.h"
|
|
||||||
|
|
||||||
enum class InterpreterQuirks {
|
|
||||||
COSMAC_SHIFT = 1 << 0,
|
|
||||||
SUPER_CHIP_JUMP = 1 << 1,
|
|
||||||
COSMAC_STORE_AND_LOAD = 1 << 2,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class OpCode {
|
|
||||||
CLS, // 00E0 - CLS
|
|
||||||
RET, // 00EE - RET
|
|
||||||
SYS_ADDR, // 0nnn - SYS addr
|
|
||||||
JP_ADDR, // 1nnn - JP addr
|
|
||||||
CALL_ADDR, // 2nnn - CALL addr
|
|
||||||
SE_VX_BYTE, // 3xkk - SE Vx, byte
|
|
||||||
SNE_VX_BYTE, // 4xkk - SNE Vx, byte
|
|
||||||
SE_VX_VY, // 5xy0 - SE Vx, Vy
|
|
||||||
LD_VX_BYTE, // 6xkk - LD Vx, byte
|
|
||||||
ADD_VX_BYTE, // 7xkk - ADD Vx, byte
|
|
||||||
LD_VX_VY, // 8xy0 - LD Vx, Vy
|
|
||||||
OR_VX_VY, // 8xy1 - OR Vx, Vy
|
|
||||||
AND_VX_VY, // 8xy2 - AND Vx, Vy
|
|
||||||
XOR_VX_VY, // 8xy3 - XOR Vx, Vy
|
|
||||||
ADD_VX_VY, // 8xy4 - ADD Vx, Vy
|
|
||||||
SUB_VX_VY, // 8xy5 - SUB Vx, Vy
|
|
||||||
SHR_VX_VY, // 8xy6 - SHR Vx {, Vy}
|
|
||||||
SUBN_VX_VY, // 8xy7 - SUBN Vx, Vy
|
|
||||||
SHL_VX_VY, // 8xyE - SHL Vx {, Vy}
|
|
||||||
SNE_VX_VY, // 9xy0 - SNE Vx, Vy
|
|
||||||
LD_I_ADDR, // Annn - LD I, addr
|
|
||||||
JP_V0_ADDR, // Bnnn - JP V0, addr
|
|
||||||
RND_VX_BYTE, // Cxkk - RND Vx, byte
|
|
||||||
DRW_VX_VY_NIBBLE, // Dxyn - DRW Vx, Vy, nibble
|
|
||||||
SKP_VX, // Ex9E - SKP Vx
|
|
||||||
SKNP_VX, // ExA1 - SKNP Vx
|
|
||||||
LD_VX_DT, // Fx07 - LD Vx, DT
|
|
||||||
LD_VX_K, // Fx0A - LD Vx, K
|
|
||||||
LD_DT_VX, // Fx15 - LD DT, Vx
|
|
||||||
LD_ST_VX, // Fx18 - LD ST, Vx
|
|
||||||
ADD_I_VX, // Fx1E - ADD I, Vx
|
|
||||||
LD_F_VX, // Fx29 - LD F, Vx
|
|
||||||
LD_B_VX, // Fx33 - LD B, Vx
|
|
||||||
LD_I_VX, // Fx55 - LD [I], Vx
|
|
||||||
LD_VX_I, // Fx65 - LD Vx, [I]
|
|
||||||
NOP, // INVALID OPERATION
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Instruction {
|
|
||||||
OpCode op_code;
|
|
||||||
uint8_t operation;
|
|
||||||
uint16_t instruction;
|
|
||||||
uint8_t x;
|
|
||||||
uint8_t y;
|
|
||||||
uint8_t n;
|
|
||||||
uint8_t kk;
|
|
||||||
uint16_t nnn;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Interpreter {
|
|
||||||
std::shared_ptr<MachineState> machine_state;
|
|
||||||
|
|
||||||
std::mt19937 random_generator;
|
|
||||||
uint8_t quirks;
|
|
||||||
|
|
||||||
void load_fonts();
|
|
||||||
|
|
||||||
Instruction decode_next();
|
|
||||||
void execute(const Instruction& instruction);
|
|
||||||
|
|
||||||
void sys_addr(const Instruction& instruction);
|
|
||||||
void nop(const Instruction& instruction);
|
|
||||||
void cls(const Instruction& instruction);
|
|
||||||
void ret(const Instruction& instruction);
|
|
||||||
void jp_addr(const Instruction& instruction);
|
|
||||||
void call_addr(const Instruction& instruction);
|
|
||||||
void se_vx_byte(const Instruction& instruction);
|
|
||||||
void sne_vx_byte(const Instruction& instruction);
|
|
||||||
void se_vx_vy(const Instruction& instruction);
|
|
||||||
void ld_vx_byte(const Instruction& instruction);
|
|
||||||
void add_vx_byte(const Instruction& instruction);
|
|
||||||
void ld_vx_vy(const Instruction& instruction);
|
|
||||||
void or_vx_vy(const Instruction& instruction);
|
|
||||||
void and_vx_vy(const Instruction& instruction);
|
|
||||||
void xor_vx_vy(const Instruction& instruction);
|
|
||||||
void add_vx_vy(const Instruction& instruction);
|
|
||||||
void sub_vx_vy(const Instruction& instruction);
|
|
||||||
void shr_vx_vy(const Instruction& instruction);
|
|
||||||
void subn_vx_vy(const Instruction& instruction);
|
|
||||||
void shl_vx_vy(const Instruction& instruction);
|
|
||||||
void sne_vx_vy(const Instruction& instruction);
|
|
||||||
void ld_i_addr(const Instruction& instruction);
|
|
||||||
void jp_v0_addr(const Instruction& instruction);
|
|
||||||
void jump_with_offset_super_chip(const Instruction& instruction);
|
|
||||||
void rnd_vx_byte(const Instruction& instruction);
|
|
||||||
void drw_vx_vy_nibble(const Instruction& instruction);
|
|
||||||
void skp_vx(const Instruction& instruction);
|
|
||||||
void sknp_vx(const Instruction& instruction);
|
|
||||||
void ld_vx_dt(const Instruction& instruction);
|
|
||||||
void ld_vx_k(const Instruction& instruction);
|
|
||||||
void ld_dt_vx(const Instruction& instruction);
|
|
||||||
void ld_st_vx(const Instruction& instruction);
|
|
||||||
void add_i_vx(const Instruction& instruction);
|
|
||||||
void ld_f_vx(const Instruction& instruction);
|
|
||||||
void ld_b_vx(const Instruction& instruction);
|
|
||||||
void ld_i_vx(const Instruction& instruction);
|
|
||||||
void ld_vx_i(const Instruction& instruction);
|
|
||||||
|
|
||||||
public:
|
|
||||||
Interpreter(std::shared_ptr<MachineState> machine_state, uint8_t quirks);
|
|
||||||
|
|
||||||
void load_rom(const std::vector<uint8_t>& rom);
|
|
||||||
|
|
||||||
void run();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //INTERPRETER_H
|
|
||||||
21
src/Interpreter/Instruction.h
Normal file
21
src/Interpreter/Instruction.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef INSTRUCTION_H
|
||||||
|
#define INSTRUCTION_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "OpCode.h"
|
||||||
|
|
||||||
|
struct Instruction
|
||||||
|
{
|
||||||
|
OpCode op_code;
|
||||||
|
uint16_t address;
|
||||||
|
uint8_t operation;
|
||||||
|
uint16_t instruction;
|
||||||
|
uint8_t x;
|
||||||
|
uint8_t y;
|
||||||
|
uint8_t n;
|
||||||
|
uint8_t kk;
|
||||||
|
uint16_t nnn;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //INSTRUCTION_H
|
||||||
637
src/Interpreter/Interpreter.cpp
Normal file
637
src/Interpreter/Interpreter.cpp
Normal file
@@ -0,0 +1,637 @@
|
|||||||
|
#include "Interpreter.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
Interpreter::Interpreter(std::shared_ptr<MachineState> machine_state) :
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
random_generator{std::random_device{}()}
|
||||||
|
{
|
||||||
|
srand(time(nullptr));
|
||||||
|
// quirks = static_cast<uint8_t>(InterpreterQuirks::COSMAC_STORE_AND_LOAD);
|
||||||
|
for (bool& display : this->machine_state->display)
|
||||||
|
{
|
||||||
|
display = rand() % 100 > 50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t Interpreter::get_word(uint16_t address) const
|
||||||
|
{
|
||||||
|
const uint8_t high_word = this->machine_state->memory[address];
|
||||||
|
const uint8_t low_word = this->machine_state->memory[address + 1];
|
||||||
|
const uint16_t word = high_word << 8 | low_word;
|
||||||
|
|
||||||
|
return word;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::tick()
|
||||||
|
{
|
||||||
|
auto word = this->get_word(this->machine_state->pc);
|
||||||
|
|
||||||
|
const auto instruction = this->decode(word, this->machine_state->pc);
|
||||||
|
this->machine_state->pc += 2;
|
||||||
|
this->execute_instruction(instruction);
|
||||||
|
|
||||||
|
if (this->machine_state->pc >= 0xFFF)
|
||||||
|
{
|
||||||
|
std::cout << "PC Outside of memory, going back 0x200" << std::endl;
|
||||||
|
this->machine_state->pc = 0x200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction Interpreter::decode(const uint16_t word, const uint16_t address) const
|
||||||
|
{
|
||||||
|
const uint8_t operation = (word & 0xF000) >> 12;
|
||||||
|
const uint8_t x = (word & 0x0F00) >> 8;
|
||||||
|
const uint8_t y = (word & 0x00F0) >> 4;
|
||||||
|
const uint8_t n = word & 0x000F;
|
||||||
|
const uint8_t kk = word & 0x00FF;
|
||||||
|
const uint16_t nnn = word & 0x0FFF;
|
||||||
|
|
||||||
|
auto op_code = OpCode::NOP;
|
||||||
|
|
||||||
|
if (operation == 0)
|
||||||
|
{
|
||||||
|
if (kk == 0xE0)
|
||||||
|
{
|
||||||
|
op_code = OpCode::CLS;
|
||||||
|
}
|
||||||
|
else if (kk == 0xEE)
|
||||||
|
{
|
||||||
|
op_code = OpCode::RET;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
op_code = OpCode::SYS_ADDR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation == 1)
|
||||||
|
{
|
||||||
|
op_code = OpCode::JP_ADDR;
|
||||||
|
}
|
||||||
|
else if (operation == 2)
|
||||||
|
{
|
||||||
|
op_code = OpCode::CALL_ADDR;
|
||||||
|
}
|
||||||
|
else if (operation == 3)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SE_VX_BYTE;
|
||||||
|
}
|
||||||
|
else if (operation == 4)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SNE_VX_BYTE;
|
||||||
|
}
|
||||||
|
else if (operation == 5)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SE_VX_VY;
|
||||||
|
}
|
||||||
|
else if (operation == 6)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_VX_BYTE;
|
||||||
|
}
|
||||||
|
else if (operation == 7)
|
||||||
|
{
|
||||||
|
op_code = OpCode::ADD_VX_BYTE;
|
||||||
|
}
|
||||||
|
else if (operation == 8)
|
||||||
|
{
|
||||||
|
if (n == 0)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_VX_VY;
|
||||||
|
}
|
||||||
|
else if (n == 1)
|
||||||
|
{
|
||||||
|
op_code = OpCode::OR_VX_VY;
|
||||||
|
}
|
||||||
|
else if (n == 2)
|
||||||
|
{
|
||||||
|
op_code = OpCode::AND_VX_VY;
|
||||||
|
}
|
||||||
|
else if (n == 3)
|
||||||
|
{
|
||||||
|
op_code = OpCode::XOR_VX_VY;
|
||||||
|
}
|
||||||
|
else if (n == 4)
|
||||||
|
{
|
||||||
|
op_code = OpCode::ADD_VX_VY;
|
||||||
|
}
|
||||||
|
else if (n == 5)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SUB_VX_VY;
|
||||||
|
}
|
||||||
|
else if (n == 6)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SHR_VX_VY;
|
||||||
|
}
|
||||||
|
else if (n == 7)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SUBN_VX_VY;
|
||||||
|
}
|
||||||
|
else if (n == 0xE)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SHL_VX_VY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation == 9)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SNE_VX_VY;
|
||||||
|
}
|
||||||
|
else if (operation == 0xA)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_I_ADDR;
|
||||||
|
}
|
||||||
|
else if (operation == 0xB)
|
||||||
|
{
|
||||||
|
op_code = OpCode::JP_V0_ADDR;
|
||||||
|
}
|
||||||
|
else if (operation == 0xC)
|
||||||
|
{
|
||||||
|
op_code = OpCode::RND_VX_BYTE;
|
||||||
|
}
|
||||||
|
else if (operation == 0xD)
|
||||||
|
{
|
||||||
|
op_code = OpCode::DRW_VX_VY_NIBBLE;
|
||||||
|
}
|
||||||
|
else if (operation == 0xE)
|
||||||
|
{
|
||||||
|
if (kk == 0x9E)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SKP_VX;
|
||||||
|
}
|
||||||
|
else if (kk == 0xA1)
|
||||||
|
{
|
||||||
|
op_code = OpCode::SKNP_VX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation == 0xF)
|
||||||
|
{
|
||||||
|
if (kk == 0x07)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_VX_DT;
|
||||||
|
}
|
||||||
|
else if (kk == 0x0A)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_VX_K;
|
||||||
|
}
|
||||||
|
else if (kk == 0x15)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_DT_VX;
|
||||||
|
}
|
||||||
|
else if (kk == 0x18)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_ST_VX;
|
||||||
|
}
|
||||||
|
else if (kk == 0x1E)
|
||||||
|
{
|
||||||
|
op_code = OpCode::ADD_I_VX;
|
||||||
|
}
|
||||||
|
else if (kk == 0x29)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_F_VX;
|
||||||
|
}
|
||||||
|
else if (kk == 0x33)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_B_VX;
|
||||||
|
}
|
||||||
|
else if (kk == 0x55)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_I_VX;
|
||||||
|
}
|
||||||
|
else if (kk == 0x65)
|
||||||
|
{
|
||||||
|
op_code = OpCode::LD_VX_I;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Instruction{
|
||||||
|
.op_code = op_code,
|
||||||
|
.address = address,
|
||||||
|
.operation = operation,
|
||||||
|
.instruction = word,
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
.n = n,
|
||||||
|
.kk = kk,
|
||||||
|
.nnn = nnn
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Instruction> Interpreter::disassembly() const
|
||||||
|
{
|
||||||
|
std::vector<Instruction> instructions(std::size(machine_state->memory) / 2);
|
||||||
|
|
||||||
|
for (std::size_t address = 0; address < std::size(machine_state->memory); address += 2)
|
||||||
|
{
|
||||||
|
const auto word = this->get_word(address);
|
||||||
|
instructions[address / 2] = (this->decode(word, address));
|
||||||
|
}
|
||||||
|
|
||||||
|
return instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::execute_instruction(const Instruction& instruction)
|
||||||
|
{
|
||||||
|
if (instruction.op_code == OpCode::CLS) this->cls();
|
||||||
|
else if (instruction.op_code == OpCode::RET) this->ret();
|
||||||
|
else if (instruction.op_code == OpCode::SYS_ADDR) this->sys_addr();
|
||||||
|
else if (instruction.op_code == OpCode::JP_ADDR) this->jp_addr(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::CALL_ADDR) this->call_addr(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SE_VX_BYTE) this->se_vx_byte(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SNE_VX_BYTE) this->sne_vx_byte(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SE_VX_VY) this->se_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_VX_BYTE) this->ld_vx_byte(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::ADD_VX_BYTE) this->add_vx_byte(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_VX_VY) this->ld_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::OR_VX_VY) this->or_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::AND_VX_VY) this->and_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::XOR_VX_VY) this->xor_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::ADD_VX_VY) this->add_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SUB_VX_VY) this->sub_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SHR_VX_VY) this->shr_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SUBN_VX_VY) this->subn_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SHL_VX_VY) this->shl_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SNE_VX_VY) this->sne_vx_vy(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_I_ADDR) this->ld_i_addr(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::JP_V0_ADDR) this->jp_v0_addr(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::RND_VX_BYTE) this->rnd_vx_byte(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::DRW_VX_VY_NIBBLE) this->drw_vx_vy_nibble(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SKP_VX) this->skp_vx(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::SKNP_VX) this->sknp_vx(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_VX_DT) this->ld_vx_dt(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_VX_K) this->ld_vx_k(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_DT_VX) this->ld_dt_vx(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_ST_VX) this->ld_st_vx(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::ADD_I_VX) this->add_i_vx(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_F_VX) this->ld_f_vx(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_B_VX) this->ld_b_vx(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_I_VX) this->ld_i_vx(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::LD_VX_I) this->ld_vx_i(instruction);
|
||||||
|
else if (instruction.op_code == OpCode::NOP) this->nop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::sys_addr() const
|
||||||
|
{
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::nop() const
|
||||||
|
{
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::cls() const
|
||||||
|
{
|
||||||
|
this->machine_state->display.fill(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ret() const
|
||||||
|
{
|
||||||
|
this->machine_state->sp -= 1;
|
||||||
|
this->machine_state->pc = this->machine_state->stack[this->machine_state->sp];
|
||||||
|
this->machine_state->stack[this->machine_state->sp] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::jp_addr(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->pc = instruction.nnn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::call_addr(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->stack[this->machine_state->sp] = this->machine_state->pc;
|
||||||
|
this->machine_state->sp += 1;
|
||||||
|
this->machine_state->pc = instruction.nnn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::se_vx_byte(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
if (this->machine_state->v[instruction.x] == instruction.kk)
|
||||||
|
{
|
||||||
|
this->machine_state->pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::sne_vx_byte(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
if (this->machine_state->v[instruction.x] != instruction.kk)
|
||||||
|
{
|
||||||
|
this->machine_state->pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::se_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
if (this->machine_state->v[instruction.x] == this->machine_state->v[instruction.y])
|
||||||
|
{
|
||||||
|
this->machine_state->pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_vx_byte(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->v[instruction.x] = instruction.kk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::add_vx_byte(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
auto& vx = this->machine_state->v[instruction.x];
|
||||||
|
vx = (vx + instruction.kk) & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->v[instruction.x] = this->machine_state->v[instruction.y];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::or_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->v[instruction.x] |= this->machine_state->v[instruction.y];
|
||||||
|
this->machine_state->v[0xF] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::and_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->v[instruction.x] &= this->machine_state->v[instruction.y];
|
||||||
|
this->machine_state->v[0xF] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::xor_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->v[instruction.x] ^= this->machine_state->v[instruction.y];
|
||||||
|
this->machine_state->v[0xF] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::add_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
auto& vx = this->machine_state->v[instruction.x];
|
||||||
|
const auto& vy = this->machine_state->v[instruction.y];
|
||||||
|
auto& vf = this->machine_state->v[0xF];
|
||||||
|
|
||||||
|
const bool carry = vx + vy > 0x100;
|
||||||
|
vx = (vx + vy) & 0xFF;
|
||||||
|
vf = carry ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::sub_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
auto& vx = this->machine_state->v[instruction.x];
|
||||||
|
const auto& vy = this->machine_state->v[instruction.y];
|
||||||
|
auto& vf = this->machine_state->v[0xF];
|
||||||
|
|
||||||
|
const bool carry = vx >= vy;
|
||||||
|
vx = (vx - vy) & 0xFF;
|
||||||
|
vf = carry ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::shr_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
auto& vx = this->machine_state->v[instruction.x];
|
||||||
|
const auto& vy = this->machine_state->v[instruction.y];
|
||||||
|
auto& vf = this->machine_state->v[0xF];
|
||||||
|
|
||||||
|
if (quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_SHIFT))
|
||||||
|
{
|
||||||
|
vx = vy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool carry = vx & 0x01;
|
||||||
|
vx = vx >> 1;
|
||||||
|
vf = carry ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::subn_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
auto& vx = this->machine_state->v[instruction.x];
|
||||||
|
const auto& vy = this->machine_state->v[instruction.y];
|
||||||
|
auto& vf = this->machine_state->v[0xF];
|
||||||
|
|
||||||
|
const bool carry = vy >= vx;
|
||||||
|
vx = (vy - vx) & 0xFF;
|
||||||
|
vf = carry ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::shl_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
auto& vx = this->machine_state->v[instruction.x];
|
||||||
|
const auto& vy = this->machine_state->v[instruction.y];
|
||||||
|
auto& vf = this->machine_state->v[0xF];
|
||||||
|
|
||||||
|
if (this->quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_SHIFT))
|
||||||
|
{
|
||||||
|
vx = vy;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool carry = vx & 0x80;
|
||||||
|
vx = vx << 1 & 0xFF;
|
||||||
|
vf = carry ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::sne_vx_vy(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
if (this->machine_state->v[instruction.x] != this->machine_state->v[instruction.y])
|
||||||
|
{
|
||||||
|
this->machine_state->pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_i_addr(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->i = instruction.nnn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::jp_v0_addr(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
if (this->quirks & static_cast<uint8_t>(InterpreterQuirks::SUPER_CHIP_JUMP))
|
||||||
|
{
|
||||||
|
this->machine_state->pc = instruction.nnn + this->machine_state->v[instruction.x];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->machine_state->pc = instruction.nnn + this->machine_state->v[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::rnd_vx_byte(const Instruction& instruction)
|
||||||
|
{
|
||||||
|
auto distribution = std::uniform_int_distribution<uint8_t>(0, 0xFF);
|
||||||
|
const auto value = distribution(this->random_generator);
|
||||||
|
|
||||||
|
this->machine_state->v[instruction.x] = value & instruction.kk;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::drw_vx_vy_nibble(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
const auto& memory = this->machine_state->memory;
|
||||||
|
auto& display = this->machine_state->display;
|
||||||
|
const auto& vx = this->machine_state->v[instruction.x];
|
||||||
|
const auto& vy = this->machine_state->v[instruction.y];
|
||||||
|
auto& vf = this->machine_state->v[0xF];
|
||||||
|
const auto& i = this->machine_state->i;
|
||||||
|
|
||||||
|
const uint8_t start_x = vx & 63;
|
||||||
|
const uint8_t start_y = vy & 31;
|
||||||
|
vf = 0;
|
||||||
|
|
||||||
|
bool carry = false;
|
||||||
|
for (auto row = 0; row < instruction.n; row++)
|
||||||
|
{
|
||||||
|
const auto current_y = start_y + row;
|
||||||
|
|
||||||
|
if (current_y > 31)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto sprite_byte = memory[i + row];
|
||||||
|
|
||||||
|
for (auto bit = 0; bit < 8; bit++)
|
||||||
|
{
|
||||||
|
const auto current_x = start_x + bit;
|
||||||
|
|
||||||
|
if (current_x > 63)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pixel = sprite_byte >> (7 - bit) & 0x01;
|
||||||
|
const auto index = current_y * 64 + current_x;
|
||||||
|
|
||||||
|
if (pixel)
|
||||||
|
{
|
||||||
|
if (display[index])
|
||||||
|
{
|
||||||
|
display[index] = false;
|
||||||
|
carry = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
display[index] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vf = carry ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::skp_vx(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
if (this->machine_state->keyboard & 1 << this->machine_state->v[instruction.x])
|
||||||
|
{
|
||||||
|
this->machine_state->pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::sknp_vx(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
if (!(this->machine_state->keyboard & 1 << this->machine_state->v[instruction.x]))
|
||||||
|
{
|
||||||
|
this->machine_state->pc += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_vx_dt(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->v[instruction.x] = this->machine_state->dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_vx_k(const Instruction& instruction)
|
||||||
|
{
|
||||||
|
if (this->machine_state->keyboard == 0 && this->wait_for_key_released == 0)
|
||||||
|
{
|
||||||
|
this->machine_state->pc -= 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->machine_state->keyboard != 0 && this->wait_for_key_released == 0)
|
||||||
|
{
|
||||||
|
this->wait_for_key_released = this->machine_state->keyboard;
|
||||||
|
this->machine_state->pc -= 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->wait_for_key_released != 0)
|
||||||
|
{
|
||||||
|
const auto released_keys = this->wait_for_key_released & (~this->machine_state->keyboard);
|
||||||
|
if (released_keys != 0)
|
||||||
|
{
|
||||||
|
for (auto key = 0; key < 16; key++)
|
||||||
|
{
|
||||||
|
if (released_keys & 1 << key)
|
||||||
|
{
|
||||||
|
this->machine_state->v[instruction.x] = key;
|
||||||
|
this->wait_for_key_released = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->machine_state->pc -= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_dt_vx(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->dt = this->machine_state->v[instruction.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_st_vx(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->st = this->machine_state->v[instruction.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::add_i_vx(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->i += this->machine_state->v[instruction.x];
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_f_vx(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
this->machine_state->i = (this->machine_state->v[instruction.x] & 0xF) * 5 + 0x50;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_b_vx(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
const auto number = this->machine_state->v[instruction.x];
|
||||||
|
this->machine_state->memory[this->machine_state->i] = number / 100;
|
||||||
|
this->machine_state->memory[this->machine_state->i + 1] = (number - this->machine_state->memory[this->machine_state
|
||||||
|
->i] * 100) / 10;
|
||||||
|
this->machine_state->memory[this->machine_state->i + 2] = number - this->machine_state->memory[this->machine_state->
|
||||||
|
i] * 100 - this->machine_state->memory[this->machine_state->i + 1] * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_i_vx(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
const bool use_quirk = this->quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_STORE_AND_LOAD);
|
||||||
|
|
||||||
|
for (auto reg = 0; reg <= instruction.x; reg++)
|
||||||
|
{
|
||||||
|
if (use_quirk)
|
||||||
|
{
|
||||||
|
this->machine_state->memory[this->machine_state->i] = this->machine_state->v[reg];
|
||||||
|
this->machine_state->i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->machine_state->memory[this->machine_state->i + reg] = this->machine_state->v[reg];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Interpreter::ld_vx_i(const Instruction& instruction) const
|
||||||
|
{
|
||||||
|
const bool use_quirk = this->quirks & static_cast<uint8_t>(InterpreterQuirks::COSMAC_STORE_AND_LOAD);
|
||||||
|
|
||||||
|
for (auto reg = 0; reg <= instruction.x; reg++)
|
||||||
|
{
|
||||||
|
if (use_quirk)
|
||||||
|
{
|
||||||
|
this->machine_state->v[reg] = this->machine_state->memory[this->machine_state->i];
|
||||||
|
this->machine_state->i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->machine_state->v[reg] = this->machine_state->memory[this->machine_state->i + reg];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
74
src/Interpreter/Interpreter.h
Normal file
74
src/Interpreter/Interpreter.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#ifndef INTERPRETER_H
|
||||||
|
#define INTERPRETER_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include "Instruction.h"
|
||||||
|
#include "MachineState.h"
|
||||||
|
|
||||||
|
enum class InterpreterQuirks
|
||||||
|
{
|
||||||
|
COSMAC_SHIFT = 1 << 0,
|
||||||
|
SUPER_CHIP_JUMP = 1 << 1,
|
||||||
|
COSMAC_STORE_AND_LOAD = 1 << 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Interpreter
|
||||||
|
{
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::mt19937 random_generator;
|
||||||
|
|
||||||
|
uint8_t quirks = 0;
|
||||||
|
uint16_t wait_for_key_released = false;
|
||||||
|
|
||||||
|
void execute_instruction(const Instruction& instruction);
|
||||||
|
|
||||||
|
void sys_addr() const;
|
||||||
|
void nop() const;
|
||||||
|
void cls() const;
|
||||||
|
void ret() const;
|
||||||
|
void jp_addr(const Instruction& instruction) const;
|
||||||
|
void call_addr(const Instruction& instruction) const;
|
||||||
|
void se_vx_byte(const Instruction& instruction) const;
|
||||||
|
void sne_vx_byte(const Instruction& instruction) const;
|
||||||
|
void se_vx_vy(const Instruction& instruction) const;
|
||||||
|
void ld_vx_byte(const Instruction& instruction) const;
|
||||||
|
void add_vx_byte(const Instruction& instruction) const;
|
||||||
|
void ld_vx_vy(const Instruction& instruction) const;
|
||||||
|
void or_vx_vy(const Instruction& instruction) const;
|
||||||
|
void and_vx_vy(const Instruction& instruction) const;
|
||||||
|
void xor_vx_vy(const Instruction& instruction) const;
|
||||||
|
void add_vx_vy(const Instruction& instruction) const;
|
||||||
|
void sub_vx_vy(const Instruction& instruction) const;
|
||||||
|
void shr_vx_vy(const Instruction& instruction) const;
|
||||||
|
void subn_vx_vy(const Instruction& instruction) const;
|
||||||
|
void shl_vx_vy(const Instruction& instruction) const;
|
||||||
|
void sne_vx_vy(const Instruction& instruction) const;
|
||||||
|
void ld_i_addr(const Instruction& instruction) const;
|
||||||
|
void jp_v0_addr(const Instruction& instruction) const;
|
||||||
|
void rnd_vx_byte(const Instruction& instruction);
|
||||||
|
void drw_vx_vy_nibble(const Instruction& instruction) const;
|
||||||
|
void skp_vx(const Instruction& instruction) const;
|
||||||
|
void sknp_vx(const Instruction& instruction) const;
|
||||||
|
void ld_vx_dt(const Instruction& instruction) const;
|
||||||
|
void ld_vx_k(const Instruction& instruction);
|
||||||
|
void ld_dt_vx(const Instruction& instruction) const;
|
||||||
|
void ld_st_vx(const Instruction& instruction) const;
|
||||||
|
void add_i_vx(const Instruction& instruction) const;
|
||||||
|
void ld_f_vx(const Instruction& instruction) const;
|
||||||
|
void ld_b_vx(const Instruction& instruction) const;
|
||||||
|
void ld_i_vx(const Instruction& instruction) const;
|
||||||
|
void ld_vx_i(const Instruction& instruction) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Interpreter(std::shared_ptr<MachineState> machine_state);
|
||||||
|
uint16_t get_word(uint16_t address) const;
|
||||||
|
Instruction decode(uint16_t word, uint16_t address) const;
|
||||||
|
std::vector<Instruction> disassembly() const;
|
||||||
|
|
||||||
|
void tick();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //INTERPRETER_H
|
||||||
29
src/Interpreter/MachineState.cpp
Normal file
29
src/Interpreter/MachineState.cpp
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#include "MachineState.h"
|
||||||
|
|
||||||
|
MachineState::MachineState() :
|
||||||
|
memory{},
|
||||||
|
v{},
|
||||||
|
stack{},
|
||||||
|
display{},
|
||||||
|
keyboard{0},
|
||||||
|
pc{0x200},
|
||||||
|
sp{0},
|
||||||
|
i{0},
|
||||||
|
dt{0},
|
||||||
|
st{0}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MachineState::reset()
|
||||||
|
{
|
||||||
|
this->memory.fill(0);
|
||||||
|
this->v.fill(0);
|
||||||
|
this->stack.fill(0);
|
||||||
|
this->display.fill(false);
|
||||||
|
this->keyboard = 0;
|
||||||
|
this->pc = 0x200;
|
||||||
|
this->sp = 0;
|
||||||
|
this->i = 0;
|
||||||
|
this->dt = 0;
|
||||||
|
this->st = 0;
|
||||||
|
}
|
||||||
24
src/Interpreter/MachineState.h
Normal file
24
src/Interpreter/MachineState.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef MACHINESTATE_H
|
||||||
|
#define MACHINESTATE_H
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
struct MachineState
|
||||||
|
{
|
||||||
|
std::array<uint8_t, 4096> memory;
|
||||||
|
std::array<uint16_t, 16> v;
|
||||||
|
std::array<uint16_t, 16> stack;
|
||||||
|
std::array<bool, 2048> display;
|
||||||
|
uint16_t keyboard;
|
||||||
|
uint16_t pc;
|
||||||
|
uint8_t sp;
|
||||||
|
uint16_t i;
|
||||||
|
uint8_t dt;
|
||||||
|
uint8_t st;
|
||||||
|
|
||||||
|
MachineState();
|
||||||
|
|
||||||
|
void reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //MACHINESTATE_H
|
||||||
44
src/Interpreter/OpCode.h
Normal file
44
src/Interpreter/OpCode.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef OPCODE_H
|
||||||
|
#define OPCODE_H
|
||||||
|
|
||||||
|
enum class OpCode
|
||||||
|
{
|
||||||
|
CLS, // 00E0 - CLS
|
||||||
|
RET, // 00EE - RET
|
||||||
|
SYS_ADDR, // 0nnn - SYS addr
|
||||||
|
JP_ADDR, // 1nnn - JP addr
|
||||||
|
CALL_ADDR, // 2nnn - CALL addr
|
||||||
|
SE_VX_BYTE, // 3xkk - SE Vx, byte
|
||||||
|
SNE_VX_BYTE, // 4xkk - SNE Vx, byte
|
||||||
|
SE_VX_VY, // 5xy0 - SE Vx, Vy
|
||||||
|
LD_VX_BYTE, // 6xkk - LD Vx, byte
|
||||||
|
ADD_VX_BYTE, // 7xkk - ADD Vx, byte
|
||||||
|
LD_VX_VY, // 8xy0 - LD Vx, Vy
|
||||||
|
OR_VX_VY, // 8xy1 - OR Vx, Vy
|
||||||
|
AND_VX_VY, // 8xy2 - AND Vx, Vy
|
||||||
|
XOR_VX_VY, // 8xy3 - XOR Vx, Vy
|
||||||
|
ADD_VX_VY, // 8xy4 - ADD Vx, Vy
|
||||||
|
SUB_VX_VY, // 8xy5 - SUB Vx, Vy
|
||||||
|
SHR_VX_VY, // 8xy6 - SHR Vx {, Vy}
|
||||||
|
SUBN_VX_VY, // 8xy7 - SUBN Vx, Vy
|
||||||
|
SHL_VX_VY, // 8xyE - SHL Vx {, Vy}
|
||||||
|
SNE_VX_VY, // 9xy0 - SNE Vx, Vy
|
||||||
|
LD_I_ADDR, // Annn - LD I, addr
|
||||||
|
JP_V0_ADDR, // Bnnn - JP V0, addr
|
||||||
|
RND_VX_BYTE, // Cxkk - RND Vx, byte
|
||||||
|
DRW_VX_VY_NIBBLE, // Dxyn - DRW Vx, Vy, nibble
|
||||||
|
SKP_VX, // Ex9E - SKP Vx
|
||||||
|
SKNP_VX, // ExA1 - SKNP Vx
|
||||||
|
LD_VX_DT, // Fx07 - LD Vx, DT
|
||||||
|
LD_VX_K, // Fx0A - LD Vx, K
|
||||||
|
LD_DT_VX, // Fx15 - LD DT, Vx
|
||||||
|
LD_ST_VX, // Fx18 - LD ST, Vx
|
||||||
|
ADD_I_VX, // Fx1E - ADD I, Vx
|
||||||
|
LD_F_VX, // Fx29 - LD F, Vx
|
||||||
|
LD_B_VX, // Fx33 - LD B, Vx
|
||||||
|
LD_I_VX, // Fx55 - LD [I], Vx
|
||||||
|
LD_VX_I, // Fx65 - LD Vx, [I]
|
||||||
|
NOP, // INVALID OPERATION
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //OPCODE_H
|
||||||
291
src/Machine.cpp
Normal file
291
src/Machine.cpp
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
#include "Machine.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <iterator>
|
||||||
|
#include <bits/ostream.tcc>
|
||||||
|
|
||||||
|
#include "Graphics/Graphics.h"
|
||||||
|
#include "Interpreter/Interpreter.h"
|
||||||
|
#include "SDL3/SDL.h"
|
||||||
|
#include "UI/Display.h"
|
||||||
|
#include "UI/UIManager.h"
|
||||||
|
|
||||||
|
#include "imgui_impl_sdl3.h"
|
||||||
|
|
||||||
|
Machine::Machine() :
|
||||||
|
machine_state{std::make_shared<MachineState>()},
|
||||||
|
graphics{std::make_shared<Graphics>()},
|
||||||
|
sound{std::make_unique<Sound>()},
|
||||||
|
callback_manager(std::make_shared<CallbackManager>()),
|
||||||
|
|
||||||
|
interpreter{std::make_shared<Interpreter>(this->machine_state)},
|
||||||
|
ui_manager{
|
||||||
|
std::make_unique<UIManager>(this->graphics, this->machine_state, this->interpreter, this->callback_manager)
|
||||||
|
},
|
||||||
|
ips{60},
|
||||||
|
last_update_time{0},
|
||||||
|
accumulator{0},
|
||||||
|
target_cycle_time{1.0 / this->ips},
|
||||||
|
last_timer_time{0},
|
||||||
|
timer_accumulator{0},
|
||||||
|
timer_cycle_time{1.0 / 60},
|
||||||
|
running{false}
|
||||||
|
{
|
||||||
|
this->register_callbacks();
|
||||||
|
this->callback_manager->trigger(this->callback_manager->ips_callback, this->ips);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::iterate()
|
||||||
|
{
|
||||||
|
if (running)
|
||||||
|
{
|
||||||
|
this->update_timers();
|
||||||
|
this->execute_interpreter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (machine_state->st > 0)
|
||||||
|
{
|
||||||
|
this->sound->square();
|
||||||
|
}
|
||||||
|
this->graphics->start();
|
||||||
|
this->ui_manager->render();
|
||||||
|
this->graphics->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Machine::on_event(const SDL_Event* event) const
|
||||||
|
{
|
||||||
|
ImGui_ImplSDL3_ProcessEvent(event);
|
||||||
|
|
||||||
|
if (event->type == SDL_EVENT_QUIT)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_KEY_UP)
|
||||||
|
{
|
||||||
|
const bool pressed = event->type == SDL_EVENT_KEY_DOWN;
|
||||||
|
switch (event->key.scancode)
|
||||||
|
{
|
||||||
|
case SDL_SCANCODE_1:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x1)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x1);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_2:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x2)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x2);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_3:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x3)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x3);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_4:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0xC)
|
||||||
|
: machine_state->keyboard & ~(1 << 0xC);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_Q:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x4)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x4);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_W:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x5)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x5);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_E:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x6)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x6);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_R:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0xD)
|
||||||
|
: machine_state->keyboard & ~(1 << 0xD);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_A:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x7)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x7);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_S:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x8)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x8);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_D:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x9)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x9);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_F:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0xE)
|
||||||
|
: machine_state->keyboard & ~(1 << 0xE);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_Z:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0xA)
|
||||||
|
: machine_state->keyboard & ~(1 << 0xA);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_X:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0x0)
|
||||||
|
: machine_state->keyboard & ~(1 << 0x0);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_C:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0xB)
|
||||||
|
: machine_state->keyboard & ~(1 << 0xB);
|
||||||
|
break;
|
||||||
|
case SDL_SCANCODE_V:
|
||||||
|
machine_state->keyboard = pressed
|
||||||
|
? machine_state->keyboard | (1 << 0xF)
|
||||||
|
: machine_state->keyboard & ~(1 << 0xF);
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::execute_interpreter()
|
||||||
|
{
|
||||||
|
const auto current_time = SDL_GetTicks();
|
||||||
|
const auto delta_time = static_cast<double>(current_time - this->last_update_time) / 1000.0;
|
||||||
|
this->last_update_time = current_time;
|
||||||
|
this->accumulator += delta_time;
|
||||||
|
|
||||||
|
while (this->accumulator >= this->target_cycle_time)
|
||||||
|
{
|
||||||
|
this->interpreter->tick();
|
||||||
|
this->accumulator -= this->target_cycle_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::update_timers()
|
||||||
|
{
|
||||||
|
const auto current_time = SDL_GetTicks();
|
||||||
|
const auto delta_time = static_cast<double>(current_time - this->last_update_time) / 1000.0;
|
||||||
|
this->last_timer_time = current_time;
|
||||||
|
this->timer_accumulator += delta_time;
|
||||||
|
|
||||||
|
while (this->timer_accumulator >= this->timer_cycle_time)
|
||||||
|
{
|
||||||
|
machine_state->dt = machine_state->dt > 0 ? machine_state->dt - 1 : 0;
|
||||||
|
machine_state->st = machine_state->st > 0 ? machine_state->st - 1 : 0;
|
||||||
|
this->timer_accumulator -= this->timer_cycle_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::register_callbacks()
|
||||||
|
{
|
||||||
|
callback_manager->rom_load_callback.push_back(std::bind(
|
||||||
|
&Machine::on_rom_load,
|
||||||
|
this,
|
||||||
|
std::placeholders::_1
|
||||||
|
));
|
||||||
|
callback_manager->reset_callback.push_back(std::bind(
|
||||||
|
&Machine::on_reset,
|
||||||
|
this
|
||||||
|
));
|
||||||
|
callback_manager->stop_callback.push_back(std::bind(
|
||||||
|
&Machine::on_stop,
|
||||||
|
this
|
||||||
|
));
|
||||||
|
callback_manager->resume_callback.push_back(std::bind(
|
||||||
|
&Machine::on_resume,
|
||||||
|
this
|
||||||
|
));
|
||||||
|
callback_manager->step_callback.push_back(std::bind(
|
||||||
|
&Machine::on_step,
|
||||||
|
this,
|
||||||
|
std::placeholders::_1
|
||||||
|
));
|
||||||
|
callback_manager->ips_callback.push_back(std::bind(
|
||||||
|
&Machine::on_speed_change,
|
||||||
|
this,
|
||||||
|
std::placeholders::_1
|
||||||
|
));
|
||||||
|
callback_manager->reload_callback.push_back(std::bind(
|
||||||
|
&Machine::on_reload,
|
||||||
|
this
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::on_rom_load(const std::string& path)
|
||||||
|
{
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
file.unsetf(std::ios::skipws);
|
||||||
|
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
const std::streampos file_size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
// La memoria para los rom es desde 0x200 hasta el final de 0x1000
|
||||||
|
// Por lo que un rom sería muy grande si sobrepasa este tamaño
|
||||||
|
if (file_size >= 0x1000 - 0x200)
|
||||||
|
{
|
||||||
|
std::cout << "File too large!" << file_size << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->machine_state->reset();
|
||||||
|
std::copy(
|
||||||
|
std::istream_iterator<uint8_t>(file),
|
||||||
|
std::istream_iterator<uint8_t>(),
|
||||||
|
this->machine_state->memory.begin() + 0x200
|
||||||
|
);
|
||||||
|
|
||||||
|
this->rom = path;
|
||||||
|
std::cout << "ROM Loaded: " << path << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::on_reset() const
|
||||||
|
{
|
||||||
|
this->machine_state->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::on_stop()
|
||||||
|
{
|
||||||
|
this->running = false;
|
||||||
|
this->accumulator = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::on_resume()
|
||||||
|
{
|
||||||
|
this->running = true;
|
||||||
|
this->last_update_time = SDL_GetTicks();
|
||||||
|
this->accumulator = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::on_step(int steps)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < steps; i++)
|
||||||
|
{
|
||||||
|
this->interpreter->tick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::on_speed_change(const int ips)
|
||||||
|
{
|
||||||
|
this->ips = ips;
|
||||||
|
if (this->ips > 0)
|
||||||
|
{
|
||||||
|
this->target_cycle_time = 1.0 / this->ips;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->target_cycle_time = std::numeric_limits<double>::infinity();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Machine::on_reload()
|
||||||
|
{
|
||||||
|
this->on_reset();
|
||||||
|
this->on_rom_load(this->rom);
|
||||||
|
}
|
||||||
51
src/Machine.h
Normal file
51
src/Machine.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef MACHINE_H
|
||||||
|
#define MACHINE_H
|
||||||
|
|
||||||
|
#include "Sound.h"
|
||||||
|
#include "Graphics/Graphics.h"
|
||||||
|
#include "Interpreter/Interpreter.h"
|
||||||
|
#include "SDL3/SDL_events.h"
|
||||||
|
#include "UI/Display.h"
|
||||||
|
#include "UI/UIManager.h"
|
||||||
|
|
||||||
|
class Machine
|
||||||
|
{
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::shared_ptr<Graphics> graphics;
|
||||||
|
std::unique_ptr<Sound> sound;
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager;
|
||||||
|
|
||||||
|
std::shared_ptr<Interpreter> interpreter;
|
||||||
|
std::unique_ptr<UIManager> ui_manager;
|
||||||
|
|
||||||
|
int ips;
|
||||||
|
uint64_t last_update_time;
|
||||||
|
double accumulator;
|
||||||
|
double target_cycle_time;
|
||||||
|
|
||||||
|
uint64_t last_timer_time;
|
||||||
|
double timer_accumulator;
|
||||||
|
double timer_cycle_time;
|
||||||
|
|
||||||
|
std::string rom;
|
||||||
|
bool running;
|
||||||
|
|
||||||
|
void execute_interpreter();
|
||||||
|
void update_timers();
|
||||||
|
|
||||||
|
void on_rom_load(const std::string& path);
|
||||||
|
void on_reset() const;
|
||||||
|
void on_stop();
|
||||||
|
void on_resume();
|
||||||
|
void on_step(int steps);
|
||||||
|
void on_speed_change(int ips);
|
||||||
|
void on_reload();
|
||||||
|
|
||||||
|
public:
|
||||||
|
void register_callbacks();
|
||||||
|
Machine();
|
||||||
|
void iterate();
|
||||||
|
bool on_event(const SDL_Event* event) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //MACHINE_H
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
#ifndef MACHINESTATE_H
|
|
||||||
#define MACHINESTATE_H
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
struct MachineState {
|
|
||||||
std::array<uint8_t, 4096> memory{};
|
|
||||||
std::array<uint16_t, 16> v{};
|
|
||||||
std::array<uint16_t, 16> stack{};
|
|
||||||
std::array<bool, 2048> display{};
|
|
||||||
uint16_t keyboard = 0;
|
|
||||||
uint16_t pc = 0x200;
|
|
||||||
uint8_t sp = 0;
|
|
||||||
uint16_t i = 0;
|
|
||||||
uint8_t dt = 0;
|
|
||||||
uint8_t st = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif //MACHINESTATE_H
|
|
||||||
88
src/Sound.cpp
Normal file
88
src/Sound.cpp
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
#include "Sound.h"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
#include "SDL3/SDL.h"
|
||||||
|
|
||||||
|
void SDLAudioStreamDestroyer::operator()(SDL_AudioStream* stream) const
|
||||||
|
{
|
||||||
|
SDL_DestroyAudioStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sound::Sound()
|
||||||
|
{
|
||||||
|
spec.channels = 1;
|
||||||
|
spec.format = SDL_AUDIO_F32;
|
||||||
|
spec.freq = 8000;
|
||||||
|
|
||||||
|
SDL_AudioStream* raw_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, nullptr, nullptr);
|
||||||
|
if (!raw_stream)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::format("Couldn't initialize audio stream: {}", SDL_GetError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream = std::unique_ptr<SDL_AudioStream, SDLAudioStreamDestroyer>(raw_stream);
|
||||||
|
|
||||||
|
|
||||||
|
SDL_ResumeAudioStreamDevice(stream.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::sine()
|
||||||
|
{
|
||||||
|
constexpr int minimum_audio = (8000 * sizeof(float)) / 6;
|
||||||
|
constexpr int freq = 440;
|
||||||
|
|
||||||
|
if (SDL_GetAudioStreamQueued(stream.get()) < minimum_audio)
|
||||||
|
{
|
||||||
|
static float samples[512];
|
||||||
|
for (std::size_t i = 0; i < std::size(samples); i++)
|
||||||
|
{
|
||||||
|
const float phase = current_sine_sample * freq / 8000.0f;
|
||||||
|
samples[i] = SDL_sinf(phase * 2 * SDL_PI_F);
|
||||||
|
current_sine_sample++;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_sine_sample %= 8000;
|
||||||
|
|
||||||
|
SDL_PutAudioStreamData(stream.get(), samples, sizeof (samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::noise()
|
||||||
|
{
|
||||||
|
constexpr int minimum_audio = (8000 * sizeof(float)) / 6;
|
||||||
|
constexpr int freq = 440;
|
||||||
|
if (SDL_GetAudioStreamQueued(stream.get()) < minimum_audio)
|
||||||
|
{
|
||||||
|
static float samples[512];
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < std::size(samples); i++)
|
||||||
|
{
|
||||||
|
samples[i] = SDL_rand(2) * freq;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_PutAudioStreamData(stream.get(), samples, sizeof (samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sound::square()
|
||||||
|
{
|
||||||
|
constexpr int minimum_audio = (8000 * sizeof(float)) / 6;
|
||||||
|
constexpr int freq = 440;
|
||||||
|
constexpr float amplitude = 0.5f;
|
||||||
|
|
||||||
|
if (SDL_GetAudioStreamQueued(stream.get()) < minimum_audio)
|
||||||
|
{
|
||||||
|
static float samples[512];
|
||||||
|
for (std::size_t i = 0; i < std::size(samples); i++)
|
||||||
|
{
|
||||||
|
const float phase = current_sine_sample * freq / 8000.0f;
|
||||||
|
samples[i] = (SDL_sinf(phase * 2 * SDL_PI_F) >= 0.0f) ? amplitude : -amplitude;
|
||||||
|
current_sine_sample++;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_sine_sample %= 8000;
|
||||||
|
|
||||||
|
SDL_PutAudioStreamData(stream.get(), samples, sizeof(samples));
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/Sound.h
Normal file
28
src/Sound.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef SOUND_H
|
||||||
|
#define SOUND_H
|
||||||
|
#include <memory>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
|
||||||
|
struct SDLAudioStreamDestroyer
|
||||||
|
{
|
||||||
|
void operator()(SDL_AudioStream* stream) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Sound
|
||||||
|
{
|
||||||
|
SDL_AudioSpec spec;
|
||||||
|
std::unique_ptr<SDL_AudioStream, SDLAudioStreamDestroyer> stream;
|
||||||
|
|
||||||
|
int current_sine_sample = 0;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Sound();
|
||||||
|
|
||||||
|
void sine();
|
||||||
|
void noise();
|
||||||
|
void square();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //SOUND_H
|
||||||
3
src/UI/CallbackManager.cpp
Normal file
3
src/UI/CallbackManager.cpp
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#include "CallbackManager.h"
|
||||||
|
|
||||||
|
|
||||||
30
src/UI/CallbackManager.h
Normal file
30
src/UI/CallbackManager.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#ifndef CALLBACKMANAGER_H
|
||||||
|
#define CALLBACKMANAGER_H
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../Interpreter/Instruction.h"
|
||||||
|
|
||||||
|
struct CallbackManager
|
||||||
|
{
|
||||||
|
std::vector<std::function<void(const std::string&)>> rom_load_callback;
|
||||||
|
std::vector<std::function<void()>> reset_callback;
|
||||||
|
std::vector<std::function<void()>> resume_callback;
|
||||||
|
std::vector<std::function<void()>> stop_callback;
|
||||||
|
std::vector<std::function<void(int)>> step_callback;
|
||||||
|
std::vector<std::function<void(int)>> ips_callback;
|
||||||
|
std::vector<std::function<void()>> reload_callback;
|
||||||
|
|
||||||
|
template <typename Func, typename... Args>
|
||||||
|
void trigger(const std::vector<Func>& callbacks, Args&&... args)
|
||||||
|
{
|
||||||
|
for (const auto& callback : callbacks)
|
||||||
|
{
|
||||||
|
callback(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //CALLBACKMANAGER_H
|
||||||
143
src/UI/ControlPanel.cpp
Normal file
143
src/UI/ControlPanel.cpp
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
#include "ControlPanel.h"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
|
||||||
|
ControlPanel::ControlPanel(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
) : graphics{std::move(graphics)},
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
callback_manager{std::move(callback_manager)}
|
||||||
|
{
|
||||||
|
this->callback_manager->ips_callback.push_back(std::bind(
|
||||||
|
&ControlPanel::initial_ips_load,
|
||||||
|
this,
|
||||||
|
std::placeholders::_1
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlPanel::render()
|
||||||
|
{
|
||||||
|
int starting_speed = this->ips;
|
||||||
|
|
||||||
|
constexpr auto full_width = ImVec2(-FLT_MIN, 0.0f);
|
||||||
|
|
||||||
|
if (ImGui::Begin("Chip-8 - Controls"))
|
||||||
|
{
|
||||||
|
if (ImGui::Button("Load Rom", full_width))
|
||||||
|
{
|
||||||
|
this->on_click_load_rom();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button(run ? "Pause" : "Run", full_width))
|
||||||
|
{
|
||||||
|
this->run = !run;
|
||||||
|
if (this->run)
|
||||||
|
{
|
||||||
|
this->callback_manager->trigger(this->callback_manager->resume_callback);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->callback_manager->trigger(this->callback_manager->stop_callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Text("Status: %s", run ? "Running" : "Stopped");
|
||||||
|
|
||||||
|
ImGui::SeparatorText("Debug");
|
||||||
|
|
||||||
|
|
||||||
|
if (ImGui::Button("Step One", full_width))
|
||||||
|
{
|
||||||
|
this->callback_manager->trigger(this->callback_manager->step_callback, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::Button(std::format("Step +{}", steps).data(), full_width))
|
||||||
|
{
|
||||||
|
this->callback_manager->trigger(this->callback_manager->step_callback, steps);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
ImGui::InputInt("##steps", &steps);
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
|
|
||||||
|
ImGui::SeparatorText("Emulation speed (IPS)");
|
||||||
|
|
||||||
|
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
ImGui::InputInt("##speed", &ips);
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
|
ImGui::SeparatorText("");
|
||||||
|
|
||||||
|
if (ImGui::Button("Reset", full_width))
|
||||||
|
{
|
||||||
|
this->callback_manager->trigger(this->callback_manager->reset_callback);
|
||||||
|
}
|
||||||
|
if (ImGui::Button("Reload ROM", full_width))
|
||||||
|
{
|
||||||
|
this->callback_manager->trigger(this->callback_manager->reload_callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ips < 0)
|
||||||
|
{
|
||||||
|
ips = 0;
|
||||||
|
}
|
||||||
|
if (steps < 1)
|
||||||
|
{
|
||||||
|
steps = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (starting_speed != ips)
|
||||||
|
{
|
||||||
|
this->callback_manager->trigger(this->callback_manager->ips_callback, ips);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlPanel::on_click_load_rom()
|
||||||
|
{
|
||||||
|
constexpr SDL_DialogFileFilter filters[] = {
|
||||||
|
{"CHIP8 ROMs", "ch8"},
|
||||||
|
{"All files", "*"}
|
||||||
|
};
|
||||||
|
|
||||||
|
SDL_ShowOpenFileDialog(
|
||||||
|
on_callback_load_rom,
|
||||||
|
this,
|
||||||
|
graphics->get_window().get(),
|
||||||
|
filters,
|
||||||
|
std::size(filters),
|
||||||
|
SDL_GetCurrentDirectory(),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlPanel::on_callback_load_rom(void* self, const char* const* filelist, int)
|
||||||
|
{
|
||||||
|
const auto control_panel = static_cast<ControlPanel*>(self);
|
||||||
|
|
||||||
|
if (!filelist || !*filelist)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
control_panel->callback_manager->trigger(
|
||||||
|
control_panel->callback_manager->rom_load_callback,
|
||||||
|
std::string(filelist[0])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ControlPanel::initial_ips_load(int ips)
|
||||||
|
{
|
||||||
|
this->ips = ips;
|
||||||
|
}
|
||||||
35
src/UI/ControlPanel.h
Normal file
35
src/UI/ControlPanel.h
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef CONTROLPANEL_H
|
||||||
|
#define CONTROLPANEL_H
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "../Graphics/Graphics.h"
|
||||||
|
#include "../Interpreter/MachineState.h"
|
||||||
|
|
||||||
|
|
||||||
|
class ControlPanel
|
||||||
|
{
|
||||||
|
std::shared_ptr<Graphics> graphics;
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager;
|
||||||
|
|
||||||
|
bool run = false;
|
||||||
|
int steps = 1;
|
||||||
|
int ips = 0;
|
||||||
|
|
||||||
|
static SDLCALL void on_callback_load_rom(void* self, const char* const* filelist, int filter);
|
||||||
|
void initial_ips_load(int ips);
|
||||||
|
|
||||||
|
public:
|
||||||
|
ControlPanel(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
);
|
||||||
|
|
||||||
|
void render();
|
||||||
|
void on_click_load_rom();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //CONTROLPANEL_H
|
||||||
222
src/UI/Disassembler.cpp
Normal file
222
src/UI/Disassembler.cpp
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
#include "Disassembler.h"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <ostream>
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
Disassembler::Disassembler(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<Interpreter> interpreter,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager) :
|
||||||
|
graphis{std::move(graphics)},
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
interpreter{std::move(interpreter)},
|
||||||
|
callback_manager{std::move(callback_manager)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Disassembler::render()
|
||||||
|
{
|
||||||
|
if (ImGui::Begin("Chip-8 - Disassembly"))
|
||||||
|
{
|
||||||
|
ImGui::Checkbox("Follow", &this->follow);
|
||||||
|
ImGui::Checkbox("Show odd instructions", &this->show_odd);
|
||||||
|
|
||||||
|
ImGuiListClipper clipper;
|
||||||
|
clipper.Begin((machine_state->memory.size() / (this->show_odd ? 1 : 2)) - ((this->show_odd ? 1 : 0)));
|
||||||
|
ImGui::BeginChild("##instructions");
|
||||||
|
|
||||||
|
if (this->follow)
|
||||||
|
{
|
||||||
|
const float line_h = ImGui::GetTextLineHeightWithSpacing();
|
||||||
|
|
||||||
|
const auto current_pc = machine_state->pc / (this->show_odd ? 1 : 2);
|
||||||
|
|
||||||
|
const float window_h = ImGui::GetWindowHeight();
|
||||||
|
const float target_y = current_pc * line_h;
|
||||||
|
const float scroll_y = target_y - (window_h * 0.1f) + (line_h * 0.1f);
|
||||||
|
ImGui::SetScrollY(scroll_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (clipper.Step())
|
||||||
|
{
|
||||||
|
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
||||||
|
{
|
||||||
|
const auto address = i * (this->show_odd ? 1 : 2);
|
||||||
|
auto line = this->build_line(
|
||||||
|
this->interpreter->decode(
|
||||||
|
this->interpreter->get_word(address),
|
||||||
|
address
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (address == machine_state->pc)
|
||||||
|
{
|
||||||
|
if (address % 2 == 0)
|
||||||
|
{
|
||||||
|
ImGui::TextColored(ImVec4(1, 1, 0, 1), "%s", line.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::TextColored(ImVec4(1, 1, 0, 1), "%s", line.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (address % 2 == 0)
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted(line.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::TextColored(ImVec4(1, 1, 1, .5), "%s", line.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string Disassembler::build_line(const Instruction instruction) const
|
||||||
|
{
|
||||||
|
std::string addr_prefix;
|
||||||
|
if (instruction.address == machine_state->pc)
|
||||||
|
{
|
||||||
|
if (instruction.address % 2 == 0)
|
||||||
|
{
|
||||||
|
addr_prefix = std::format(">0x{:0>4x} | ", instruction.address);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addr_prefix = std::format("> 0x{:0>4x} | ", instruction.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (instruction.address % 2 == 0)
|
||||||
|
{
|
||||||
|
addr_prefix = std::format(" 0x{:0>4x} | ", instruction.address);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addr_prefix = std::format("- 0x{:0>4x} | ", instruction.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (instruction.op_code)
|
||||||
|
{
|
||||||
|
case OpCode::CLS:
|
||||||
|
return addr_prefix + "CLS";
|
||||||
|
|
||||||
|
case OpCode::RET:
|
||||||
|
return addr_prefix + "RET";
|
||||||
|
|
||||||
|
case OpCode::SYS_ADDR:
|
||||||
|
return addr_prefix + "SYS";
|
||||||
|
|
||||||
|
case OpCode::JP_ADDR:
|
||||||
|
return addr_prefix + std::format("JP 0x{:0>3x}", instruction.nnn);
|
||||||
|
|
||||||
|
case OpCode::CALL_ADDR:
|
||||||
|
return addr_prefix + std::format("CALL 0x{:0>3x}", instruction.nnn);
|
||||||
|
|
||||||
|
case OpCode::SE_VX_BYTE:
|
||||||
|
return addr_prefix + std::format("SE V{}, 0x{:0>2x}", instruction.x, instruction.kk);
|
||||||
|
|
||||||
|
case OpCode::SNE_VX_BYTE:
|
||||||
|
return addr_prefix + std::format("SNE V{}, 0x{:0>2x}", instruction.x, instruction.kk);
|
||||||
|
|
||||||
|
case OpCode::SE_VX_VY:
|
||||||
|
return addr_prefix + std::format("SE V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::LD_VX_BYTE:
|
||||||
|
return addr_prefix + std::format("LD V{}, 0x{:0>2x}", instruction.x, instruction.kk);
|
||||||
|
|
||||||
|
case OpCode::ADD_VX_BYTE:
|
||||||
|
return addr_prefix + std::format("ADD V{}, 0x{:0>2x}", instruction.x, instruction.kk);
|
||||||
|
|
||||||
|
case OpCode::LD_VX_VY:
|
||||||
|
return addr_prefix + std::format("LD V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::OR_VX_VY:
|
||||||
|
return addr_prefix + std::format("OR V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::AND_VX_VY:
|
||||||
|
return addr_prefix + std::format("AND V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::XOR_VX_VY:
|
||||||
|
return addr_prefix + std::format("XOR V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::ADD_VX_VY:
|
||||||
|
return addr_prefix + std::format("ADD V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::SUB_VX_VY:
|
||||||
|
return addr_prefix + std::format("SUB V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::SHR_VX_VY:
|
||||||
|
return addr_prefix + std::format("SHR V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::SUBN_VX_VY:
|
||||||
|
return addr_prefix + std::format("SUBN V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::SHL_VX_VY:
|
||||||
|
return addr_prefix + std::format("SHL V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::SNE_VX_VY:
|
||||||
|
return addr_prefix + std::format("SNE V{}, V{}", instruction.x, instruction.y);
|
||||||
|
|
||||||
|
case OpCode::LD_I_ADDR:
|
||||||
|
return addr_prefix + std::format("LD I, 0x{:0>3x}", instruction.nnn);
|
||||||
|
|
||||||
|
case OpCode::JP_V0_ADDR:
|
||||||
|
return addr_prefix + std::format("JP V0, 0x{:0>3x}", instruction.nnn);
|
||||||
|
|
||||||
|
case OpCode::RND_VX_BYTE:
|
||||||
|
return addr_prefix + std::format("RAND V{}, 0x{:0>2x}", instruction.x, instruction.kk);
|
||||||
|
|
||||||
|
case OpCode::DRW_VX_VY_NIBBLE:
|
||||||
|
return addr_prefix + std::format("DRW V{}, V{} 0x{:x}", instruction.x, instruction.y, instruction.n);
|
||||||
|
|
||||||
|
case OpCode::SKP_VX:
|
||||||
|
return addr_prefix + std::format("SKP V{}", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::SKNP_VX:
|
||||||
|
return addr_prefix + std::format("SKNP V{}", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::LD_VX_DT:
|
||||||
|
return addr_prefix + std::format("LD V{}, DT", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::LD_VX_K:
|
||||||
|
return addr_prefix + std::format("LD V{}, K", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::LD_DT_VX:
|
||||||
|
return addr_prefix + std::format("LD DT, V{}", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::LD_ST_VX:
|
||||||
|
return addr_prefix + std::format("LD ST, V{}", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::ADD_I_VX:
|
||||||
|
return addr_prefix + std::format("ADD I, V{}", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::LD_F_VX:
|
||||||
|
return addr_prefix + std::format("LD F, V{}", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::LD_B_VX:
|
||||||
|
return addr_prefix + std::format("LD B, V{}", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::LD_I_VX:
|
||||||
|
return addr_prefix + std::format("LD I, V{}", instruction.x);
|
||||||
|
|
||||||
|
case OpCode::LD_VX_I:
|
||||||
|
return addr_prefix + std::format("LD V{}, I", instruction.x);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return addr_prefix + "-";
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/UI/Disassembler.h
Normal file
34
src/UI/Disassembler.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#ifndef DISASSEMBLER_H
|
||||||
|
#define DISASSEMBLER_H
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "../Graphics/Graphics.h"
|
||||||
|
#include "../Interpreter/Interpreter.h"
|
||||||
|
#include "../Interpreter/MachineState.h"
|
||||||
|
|
||||||
|
|
||||||
|
class Disassembler
|
||||||
|
{
|
||||||
|
std::shared_ptr<Graphics> graphis;
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::shared_ptr<Interpreter> interpreter;
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager;
|
||||||
|
|
||||||
|
bool follow = true;
|
||||||
|
bool show_odd = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Disassembler(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<Interpreter> interpreter,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
);
|
||||||
|
|
||||||
|
void render();
|
||||||
|
std::string build_line(Instruction instruction) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //DISASSEMBLER_H
|
||||||
111
src/UI/Display.cpp
Normal file
111
src/UI/Display.cpp
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
#include "Display.h"
|
||||||
|
|
||||||
|
#include <format>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "SDL3/SDL.h"
|
||||||
|
|
||||||
|
void SDLTextureDestroyer::operator()(SDL_Texture* texture) const
|
||||||
|
{
|
||||||
|
SDL_DestroyTexture(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
Display::Display(std::shared_ptr<Graphics> graphics, std::shared_ptr<MachineState> machine_state) :
|
||||||
|
graphics{std::move(graphics)},
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
width(64),
|
||||||
|
height(32),
|
||||||
|
background_color{0x00, 0x2b, 0x59},
|
||||||
|
foreground_color(0x00, 0xb9, 0xbe),
|
||||||
|
texture{create_texture()}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<SDL_Texture, SDLTextureDestroyer> Display::create_texture() const
|
||||||
|
{
|
||||||
|
SDL_Texture* raw_texture = SDL_CreateTexture(this->graphics->get_renderer().get(), SDL_PIXELFORMAT_RGBA8888,
|
||||||
|
SDL_TEXTUREACCESS_STREAMING, width, height);
|
||||||
|
|
||||||
|
if (!raw_texture)
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::format("Couldn't create texture: {}", SDL_GetError()));
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_SetTextureScaleMode(raw_texture, SDL_SCALEMODE_NEAREST);
|
||||||
|
|
||||||
|
return std::unique_ptr<SDL_Texture, SDLTextureDestroyer>(raw_texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::render() const
|
||||||
|
{
|
||||||
|
update_texture();
|
||||||
|
display_widget();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::update_texture() const
|
||||||
|
{
|
||||||
|
SDL_Surface* surface = nullptr;
|
||||||
|
const auto& display = machine_state->display;
|
||||||
|
|
||||||
|
if (SDL_LockTextureToSurface(this->texture.get(), nullptr, &surface))
|
||||||
|
{
|
||||||
|
SDL_FillSurfaceRect(
|
||||||
|
surface,
|
||||||
|
nullptr,
|
||||||
|
SDL_MapRGB(
|
||||||
|
SDL_GetPixelFormatDetails(surface->format),
|
||||||
|
nullptr,
|
||||||
|
this->background_color.r,
|
||||||
|
this->background_color.g,
|
||||||
|
this->background_color.b
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < display.size(); i++)
|
||||||
|
{
|
||||||
|
if (display[i])
|
||||||
|
{
|
||||||
|
const int x = (i % this->width);
|
||||||
|
const int y = (i / this->width);
|
||||||
|
|
||||||
|
SDL_Rect rect = {x, y, 1, 1};
|
||||||
|
const Uint32 color = SDL_MapRGB(
|
||||||
|
SDL_GetPixelFormatDetails(surface->format),
|
||||||
|
nullptr,
|
||||||
|
this->foreground_color.r,
|
||||||
|
this->foreground_color.g,
|
||||||
|
this->foreground_color.b
|
||||||
|
);
|
||||||
|
|
||||||
|
SDL_FillSurfaceRect(surface, &rect, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SDL_UnlockTexture(this->texture.get());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw std::runtime_error(std::format("Couldn't get texture lock onto surface: {}", SDL_GetError()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::display_widget() const
|
||||||
|
{
|
||||||
|
ImGui::Begin("CHIP-8 - Display");
|
||||||
|
|
||||||
|
const ImVec2 available_size = ImGui::GetContentRegionAvail();
|
||||||
|
|
||||||
|
const int scale_x = static_cast<int>(static_cast<float>(available_size.x) / static_cast<float>(this->width));
|
||||||
|
const int scale_y = static_cast<int>(static_cast<float>(available_size.y) / static_cast<float>(this->height));
|
||||||
|
const int scale = std::max(1, std::min(scale_x, scale_y));
|
||||||
|
const ImVec2 scaled_size(static_cast<float>(this->width * scale), static_cast<float>(this->height * scale));
|
||||||
|
|
||||||
|
const ImVec2 cursor_pos = ImGui::GetCursorPos();
|
||||||
|
const ImVec2 center_offset((available_size.x - scaled_size.x) / 2, (available_size.y - scaled_size.y) / 2);
|
||||||
|
ImGui::SetCursorPos(ImVec2(cursor_pos.x + center_offset.x, cursor_pos.y + center_offset.y));
|
||||||
|
|
||||||
|
ImGui::Image(static_cast<ImTextureID>(reinterpret_cast<intptr_t>(texture.get())), scaled_size);
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
42
src/UI/Display.h
Normal file
42
src/UI/Display.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#ifndef DISPLAY_H
|
||||||
|
#define DISPLAY_H
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "../Graphics/Color.h"
|
||||||
|
#include "../Graphics/Graphics.h"
|
||||||
|
#include "../Interpreter/MachineState.h"
|
||||||
|
#include "SDL3/SDL.h"
|
||||||
|
|
||||||
|
struct SDLTextureDestroyer
|
||||||
|
{
|
||||||
|
void operator()(SDL_Texture* texture) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Display
|
||||||
|
{
|
||||||
|
std::shared_ptr<Graphics> graphics;
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
Color background_color;
|
||||||
|
Color foreground_color;
|
||||||
|
|
||||||
|
std::unique_ptr<SDL_Texture, SDLTextureDestroyer> texture;
|
||||||
|
|
||||||
|
std::unique_ptr<SDL_Texture, SDLTextureDestroyer> create_texture() const;
|
||||||
|
void update_texture() const;
|
||||||
|
void display_widget() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Display(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state
|
||||||
|
);
|
||||||
|
|
||||||
|
void render() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //DISPLAY_H
|
||||||
101
src/UI/MemoryViewer.cpp
Normal file
101
src/UI/MemoryViewer.cpp
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
#include "MemoryViewer.h"
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
MemoryViewer::MemoryViewer(std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
) : graphics{std::move(graphics)},
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
callback_manager{std::move(callback_manager)},
|
||||||
|
width{16}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryViewer::render()
|
||||||
|
{
|
||||||
|
if (ImGui::Begin("CHIP-8 - CPU Memory"))
|
||||||
|
{
|
||||||
|
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
ImGui::SliderInt("##memory_view_width", &width, 1, 32, "%d", ImGuiSliderFlags_AlwaysClamp);
|
||||||
|
ImGui::PopItemWidth();
|
||||||
|
|
||||||
|
auto total_lines = (std::size(this->machine_state->memory) + width - 1) / width;
|
||||||
|
ImGuiListClipper clipper;
|
||||||
|
clipper.Begin(total_lines);
|
||||||
|
|
||||||
|
ImGui::BeginChild("##memory_view");
|
||||||
|
while (clipper.Step())
|
||||||
|
{
|
||||||
|
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++)
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted(get_memory_line(i).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string MemoryViewer::get_memory_line(const int line) const
|
||||||
|
{
|
||||||
|
auto& memory = this->machine_state->memory;
|
||||||
|
|
||||||
|
const auto size = std::size(memory);
|
||||||
|
const auto i = line * this->width;
|
||||||
|
|
||||||
|
std::stringstream text_builder;
|
||||||
|
text_builder << "0x";
|
||||||
|
text_builder << std::hex
|
||||||
|
<< std::setw(4)
|
||||||
|
<< std::setfill('0')
|
||||||
|
<< std::uppercase
|
||||||
|
<< static_cast<unsigned int>(i)
|
||||||
|
<< " | ";
|
||||||
|
|
||||||
|
for (int j = 0; j < this->width; j++)
|
||||||
|
{
|
||||||
|
if (static_cast<std::size_t>(i + j) >= size)
|
||||||
|
{
|
||||||
|
text_builder << " ";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text_builder << std::hex
|
||||||
|
<< std::setw(2)
|
||||||
|
<< std::setfill('0')
|
||||||
|
<< std::uppercase
|
||||||
|
<< static_cast<unsigned int>(memory[i + j])
|
||||||
|
<< " ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text_builder << "| ";
|
||||||
|
|
||||||
|
for (int j = 0; j < this->width; j++)
|
||||||
|
{
|
||||||
|
if (static_cast<std::size_t>(i + j) >= size)
|
||||||
|
{
|
||||||
|
text_builder << ".";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto byte = memory[i + j];
|
||||||
|
if (byte >= 32 && byte <= 126)
|
||||||
|
{
|
||||||
|
text_builder << static_cast<char>(memory[i + j]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text_builder << ".";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text_builder.str();
|
||||||
|
}
|
||||||
29
src/UI/MemoryViewer.h
Normal file
29
src/UI/MemoryViewer.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef MEMORYVIEWER_H
|
||||||
|
#define MEMORYVIEWER_H
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "../Graphics/Graphics.h"
|
||||||
|
#include "../Interpreter/MachineState.h"
|
||||||
|
|
||||||
|
class MemoryViewer
|
||||||
|
{
|
||||||
|
std::shared_ptr<Graphics> graphics;
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager;
|
||||||
|
|
||||||
|
int width;
|
||||||
|
|
||||||
|
public:
|
||||||
|
MemoryViewer(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
);
|
||||||
|
|
||||||
|
void render();
|
||||||
|
std::string get_memory_line(int line) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //MEMORYVIEWER_H
|
||||||
59
src/UI/RegisterView.cpp
Normal file
59
src/UI/RegisterView.cpp
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#include "RegisterView.h"
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
#include "../Interpreter/MachineState.h"
|
||||||
|
|
||||||
|
RegisterView::RegisterView(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager) :
|
||||||
|
graphics{std::move(graphics)},
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
callback_manager{std::move(callback_manager)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterView::render()
|
||||||
|
{
|
||||||
|
if (ImGui::Begin("Chip8 - Registers"))
|
||||||
|
{
|
||||||
|
if (ImGui::BeginTable("Registers", 4, ImGuiTableFlags_Borders))
|
||||||
|
{
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::Text("PC");
|
||||||
|
ImGui::Text("0x%04X", this->machine_state->pc);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::Text("I");
|
||||||
|
ImGui::Text("0x%02X", this->machine_state->i);
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(2);
|
||||||
|
ImGui::Text("DT");
|
||||||
|
ImGui::Text("0x%04X", this->machine_state->dt);
|
||||||
|
|
||||||
|
ImGui::TableSetColumnIndex(3);
|
||||||
|
ImGui::Text("ST");
|
||||||
|
ImGui::Text("0x%04X", this->machine_state->st);
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("Registers", 8, ImGuiTableFlags_Borders))
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
for (int j = 0; j < 8; j++)
|
||||||
|
{
|
||||||
|
ImGui::TableSetColumnIndex(j);
|
||||||
|
ImGui::Text("V%i", i * 8 + j);
|
||||||
|
ImGui::Text("0x%02X", this->machine_state->v[i * 8 + j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
|
||||||
|
ImGui::Text("Keyboard: 0b%016B", this->machine_state->keyboard);
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
27
src/UI/RegisterView.h
Normal file
27
src/UI/RegisterView.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#ifndef REGISTERVIEW_H
|
||||||
|
#define REGISTERVIEW_H
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "../Graphics/Graphics.h"
|
||||||
|
#include "../Interpreter/MachineState.h"
|
||||||
|
|
||||||
|
|
||||||
|
class RegisterView
|
||||||
|
{
|
||||||
|
std::shared_ptr<Graphics> graphics;
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RegisterView(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
);
|
||||||
|
|
||||||
|
void render();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //REGISTERVIEW_H
|
||||||
44
src/UI/RomInfo.cpp
Normal file
44
src/UI/RomInfo.cpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#include "RomInfo.h"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
RomInfo::RomInfo(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
) : graphics{std::move(graphics)},
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
callback_manager{std::move(callback_manager)}
|
||||||
|
{
|
||||||
|
this->callback_manager->rom_load_callback.push_back(std::bind(
|
||||||
|
&RomInfo::on_rom_load,
|
||||||
|
this,
|
||||||
|
std::placeholders::_1
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RomInfo::render()
|
||||||
|
{
|
||||||
|
if (ImGui::Begin("Chip-8 - ROM Info"))
|
||||||
|
{
|
||||||
|
if (!this->rom_path.empty())
|
||||||
|
{
|
||||||
|
ImGui::Text("ROM: %s", this->rom_path.c_str());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui::Text("ROM: None");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RomInfo::on_rom_load(const std::string& rom_path)
|
||||||
|
{
|
||||||
|
std::filesystem::path home = SDL_GetUserFolder(SDL_FOLDER_HOME);
|
||||||
|
std::filesystem::path path = rom_path;
|
||||||
|
|
||||||
|
this->rom_path = "~/" + std::filesystem::relative(path, home).string();
|
||||||
|
}
|
||||||
28
src/UI/RomInfo.h
Normal file
28
src/UI/RomInfo.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef ROMINFO_H
|
||||||
|
#define ROMINFO_H
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "../Graphics/Graphics.h"
|
||||||
|
#include "../Interpreter/MachineState.h"
|
||||||
|
|
||||||
|
class RomInfo
|
||||||
|
{
|
||||||
|
std::shared_ptr<Graphics> graphics;
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager;
|
||||||
|
std::string rom_path;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RomInfo(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
);
|
||||||
|
|
||||||
|
void render();
|
||||||
|
void on_rom_load(const std::string& rom_path);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //ROMINFO_H
|
||||||
34
src/UI/StackViewer.cpp
Normal file
34
src/UI/StackViewer.cpp
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#include "StackViewer.h"
|
||||||
|
|
||||||
|
#include "imgui.h"
|
||||||
|
|
||||||
|
StackViewer::StackViewer(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
) :
|
||||||
|
graphics{std::move(graphics)},
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
callback_manager{std::move(callback_manager)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void StackViewer::render()
|
||||||
|
{
|
||||||
|
if (ImGui::Begin("Chip8 - Stack"))
|
||||||
|
{
|
||||||
|
if (ImGui::BeginTable("Stack", 2, ImGuiTableFlags_BordersV))
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < std::size(machine_state->stack); i++)
|
||||||
|
{
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableSetColumnIndex(0);
|
||||||
|
ImGui::Text("Depth %zu", i);
|
||||||
|
ImGui::TableSetColumnIndex(1);
|
||||||
|
ImGui::Text("0x%04X", machine_state->stack[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
25
src/UI/StackViewer.h
Normal file
25
src/UI/StackViewer.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef STACKVIEWER_H
|
||||||
|
#define STACKVIEWER_H
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "../Graphics/Graphics.h"
|
||||||
|
#include "../Interpreter/MachineState.h"
|
||||||
|
|
||||||
|
class StackViewer
|
||||||
|
{
|
||||||
|
std::shared_ptr<Graphics> graphics;
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager;
|
||||||
|
|
||||||
|
public:
|
||||||
|
StackViewer(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager);
|
||||||
|
|
||||||
|
void render();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //STACKVIEWER_H
|
||||||
41
src/UI/UIManager.cpp
Normal file
41
src/UI/UIManager.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include "UIManager.h"
|
||||||
|
|
||||||
|
#include "Disassembler.h"
|
||||||
|
#include "MemoryViewer.h"
|
||||||
|
#include "RegisterView.h"
|
||||||
|
#include "RomInfo.h"
|
||||||
|
#include "StackViewer.h"
|
||||||
|
|
||||||
|
UIManager::UIManager(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<Interpreter> interpreter,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
) :
|
||||||
|
graphics{std::move(graphics)},
|
||||||
|
machine_state{std::move(machine_state)},
|
||||||
|
interpreter{std::move(interpreter)},
|
||||||
|
callback_manager{std::move(callback_manager)},
|
||||||
|
|
||||||
|
display{std::make_unique<Display>(this->graphics, this->machine_state)},
|
||||||
|
control_panel{std::make_unique<ControlPanel>(this->graphics, this->machine_state, this->callback_manager)},
|
||||||
|
rom_info{std::make_unique<RomInfo>(this->graphics, this->machine_state, this->callback_manager)},
|
||||||
|
memory_viewer{std::make_unique<MemoryViewer>(this->graphics, this->machine_state, this->callback_manager)},
|
||||||
|
disassembler{
|
||||||
|
std::make_unique<Disassembler>(this->graphics, this->machine_state, this->interpreter, this->callback_manager)
|
||||||
|
},
|
||||||
|
register_view{std::make_unique<RegisterView>(this->graphics, this->machine_state, this->callback_manager)},
|
||||||
|
stack_viewer{std::make_unique<StackViewer>(this->graphics, this->machine_state, this->callback_manager)}
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIManager::render()
|
||||||
|
{
|
||||||
|
this->display->render();
|
||||||
|
this->control_panel->render();
|
||||||
|
this->rom_info->render();
|
||||||
|
this->memory_viewer->render();
|
||||||
|
this->disassembler->render();
|
||||||
|
this->register_view->render();
|
||||||
|
this->stack_viewer->render();
|
||||||
|
}
|
||||||
41
src/UI/UIManager.h
Normal file
41
src/UI/UIManager.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#ifndef UIMANAGER_H
|
||||||
|
#define UIMANAGER_H
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "CallbackManager.h"
|
||||||
|
#include "ControlPanel.h"
|
||||||
|
#include "Disassembler.h"
|
||||||
|
#include "Display.h"
|
||||||
|
#include "MemoryViewer.h"
|
||||||
|
#include "RegisterView.h"
|
||||||
|
#include "RomInfo.h"
|
||||||
|
#include "StackViewer.h"
|
||||||
|
#include "../Graphics/Graphics.h"
|
||||||
|
#include "../Interpreter/Interpreter.h"
|
||||||
|
|
||||||
|
class UIManager
|
||||||
|
{
|
||||||
|
std::shared_ptr<Graphics> graphics;
|
||||||
|
std::shared_ptr<MachineState> machine_state;
|
||||||
|
std::shared_ptr<Interpreter> interpreter;
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager;
|
||||||
|
std::unique_ptr<Display> display;
|
||||||
|
std::unique_ptr<ControlPanel> control_panel;
|
||||||
|
std::unique_ptr<RomInfo> rom_info;
|
||||||
|
std::unique_ptr<MemoryViewer> memory_viewer;
|
||||||
|
std::unique_ptr<Disassembler> disassembler;
|
||||||
|
std::unique_ptr<RegisterView> register_view;
|
||||||
|
std::unique_ptr<StackViewer> stack_viewer;
|
||||||
|
|
||||||
|
public:
|
||||||
|
UIManager(
|
||||||
|
std::shared_ptr<Graphics> graphics,
|
||||||
|
std::shared_ptr<MachineState> machine_state,
|
||||||
|
std::shared_ptr<Interpreter> interpreter,
|
||||||
|
std::shared_ptr<CallbackManager> callback_manager
|
||||||
|
);
|
||||||
|
|
||||||
|
void render();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //UIMANAGER_H
|
||||||
33
src/bitops.h
33
src/bitops.h
@@ -1,33 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by ryuuji on 6/21/25.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef BITOPS_H
|
|
||||||
#define BITOPS_H
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
T bit_set(const T number, const int bit) {
|
|
||||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned integral type");
|
|
||||||
return number | (1 << bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
T bit_clear(const T number, const int bit) {
|
|
||||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned integral type");
|
|
||||||
return number & ~(1 << bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
T bit_toggle(const T number, const int bit) {
|
|
||||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned integral type");
|
|
||||||
return number ^ (1 << bit);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
bool bit_check(const T number, const int bit) {
|
|
||||||
static_assert(std::is_unsigned_v<T>, "T must be an unsigned integral type");
|
|
||||||
return (number >> bit) & 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif //BITOPS_H
|
|
||||||
52
src/main.cpp
52
src/main.cpp
@@ -1,53 +1,39 @@
|
|||||||
#define SDL_MAIN_USE_CALLBACKS 1
|
#define SDL_MAIN_USE_CALLBACKS 1
|
||||||
#include <iostream>
|
|
||||||
#include <bits/ostream.tcc>
|
|
||||||
#include <SDL3/SDL_main.h>
|
#include <SDL3/SDL_main.h>
|
||||||
|
|
||||||
#include "Chip8.h"
|
#include <memory>
|
||||||
#include "imgui.h"
|
#include "Machine.h"
|
||||||
#include "imgui_impl_sdl3.h"
|
|
||||||
|
|
||||||
SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
SDL_AppResult SDL_AppInit(void** appstate, [[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
|
||||||
const auto chip8 = new Chip8();
|
{
|
||||||
*appstate = chip8;
|
auto machine = std::make_unique<Machine>();
|
||||||
|
*appstate = machine.release();
|
||||||
if (!chip8->init()) {
|
|
||||||
return SDL_APP_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int num_keys;
|
|
||||||
const bool* kb_state = SDL_GetKeyboardState(&num_keys);
|
|
||||||
const std::span kb_state_view(kb_state, num_keys);
|
|
||||||
chip8->set_keyboard_state(kb_state_view);
|
|
||||||
|
|
||||||
return SDL_APP_CONTINUE;
|
return SDL_APP_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AppResult SDL_AppIterate(void* appstate) {
|
SDL_AppResult SDL_AppIterate(void* appstate)
|
||||||
const auto chip8 = static_cast<Chip8*>(appstate);
|
{
|
||||||
|
const auto machine = static_cast<Machine*>(appstate);
|
||||||
|
|
||||||
|
machine->iterate();
|
||||||
|
|
||||||
chip8->update();
|
|
||||||
return SDL_APP_CONTINUE;
|
return SDL_APP_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) {
|
SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
|
||||||
const auto chip8 = static_cast<Chip8*>(appstate);
|
{
|
||||||
|
const auto machine = static_cast<Machine*>(appstate);
|
||||||
|
|
||||||
ImGui_ImplSDL3_ProcessEvent(event);
|
if (!machine->on_event(event))
|
||||||
|
{
|
||||||
if (event->type == SDL_EVENT_QUIT) {
|
|
||||||
return SDL_APP_SUCCESS;
|
return SDL_APP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event->type == SDL_EVENT_KEY_DOWN) {
|
|
||||||
chip8->on_keydown(event->key.scancode);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return SDL_APP_CONTINUE;
|
return SDL_APP_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDL_AppQuit(void* appstate, SDL_AppResult result) {
|
void SDL_AppQuit(void* appstate, [[maybe_unused]] SDL_AppResult result)
|
||||||
const auto chip8 = static_cast<Chip8*>(appstate);
|
{
|
||||||
delete chip8;
|
std::unique_ptr<Machine> chip8(static_cast<Machine*>(appstate));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user