├── .clang-format ├── .clang-tidy ├── .github ├── images │ ├── blarggs-tests-pass.png │ ├── pokemon-gameplay.png │ ├── pokemon-menu.png │ ├── tetris-gameplay.png │ ├── tetris-menu.png │ ├── zelda-gameplay.png │ └── zelda-menu.png └── workflows │ └── ci.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── cmake ├── add_sources.cmake └── add_warnings.cmake ├── platforms ├── cli │ └── cli.h ├── sdl │ ├── CMakeLists.txt │ └── main.cc ├── sfml │ ├── CMakeLists.txt │ ├── FindSFML.cmake │ └── main.cc └── test │ ├── CMakeLists.txt │ └── main.cc ├── scripts ├── clang-fix ├── clang-format ├── clang-tidy ├── run_test_roms └── test_roms │ ├── 01-special.gb │ ├── 02-interrupts.gb │ ├── 03-op sp,hl.gb │ ├── 04-op r,imm.gb │ ├── 05-op rp.gb │ ├── 06-ld r,r.gb │ ├── 07-jr,jp,call,ret,rst.gb │ ├── 08-misc instrs.gb │ ├── 09-op r,r.gb │ ├── 10-bit ops.gb │ └── 11-op a,(hl).gb └── src ├── CMakeLists.txt ├── address.cc ├── address.h ├── boot.h ├── cartridge ├── CMakeLists.txt ├── cartridge.cc ├── cartridge.h ├── cartridge_info.cc └── cartridge_info.h ├── cpu ├── CMakeLists.txt ├── cpu.cc ├── cpu.h ├── opcode_cycles.h ├── opcode_mapping.cc ├── opcode_names.h └── opcodes.cc ├── debugger.cc ├── debugger.h ├── definitions.h ├── gameboy.cc ├── gameboy.h ├── gameboy_prelude.h ├── input.cc ├── input.h ├── mmu.cc ├── mmu.h ├── options.h ├── register.cc ├── register.h ├── serial.cc ├── serial.h ├── timer.cc ├── timer.h ├── util ├── CMakeLists.txt ├── bitwise.h ├── files.cc ├── files.h ├── log.cc ├── log.h ├── string_utils.cc └── string_utils.h └── video ├── CMakeLists.txt ├── color.cc ├── color.h ├── framebuffer.cc ├── framebuffer.h ├── tile.cc ├── tile.h ├── video.cc └── video.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlinesLeft: false 8 | AlignOperands: true 9 | AlignTrailingComments: false 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortFunctionsOnASingleLine: All 14 | AllowShortIfStatementsOnASingleLine: false 15 | AllowShortLoopsOnASingleLine: false 16 | AlwaysBreakAfterDefinitionReturnType: None 17 | AlwaysBreakAfterReturnType: None 18 | AlwaysBreakBeforeMultilineStrings: false 19 | AlwaysBreakTemplateDeclarations: false 20 | BinPackArguments: true 21 | BinPackParameters: true 22 | BraceWrapping: 23 | AfterClass: false 24 | AfterControlStatement: false 25 | AfterEnum: false 26 | AfterFunction: false 27 | AfterNamespace: false 28 | AfterObjCDeclaration: false 29 | AfterStruct: false 30 | AfterUnion: false 31 | BeforeCatch: false 32 | BeforeElse: false 33 | IndentBraces: false 34 | BreakBeforeBinaryOperators: None 35 | BreakBeforeBraces: Attach 36 | BreakBeforeTernaryOperators: true 37 | BreakConstructorInitializersBeforeComma: false 38 | ColumnLimit: 100 39 | CommentPragmas: '^ IWYU pragma:' 40 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 41 | ConstructorInitializerIndentWidth: 4 42 | ContinuationIndentWidth: 4 43 | Cpp11BracedListStyle: true 44 | DerivePointerAlignment: false 45 | DisableFormat: false 46 | ExperimentalAutoDetectBinPacking: false 47 | ForEachMacros: 48 | - foreach 49 | - Q_FOREACH 50 | - BOOST_FOREACH 51 | IncludeCategories: 52 | - Regex: ^"(llvm|llvm-c|clang|clang-c)/ 53 | Priority: 2 54 | - Regex: ^(<|"(gtest|isl|json)/) 55 | Priority: 3 56 | - Regex: .* 57 | Priority: 1 58 | IndentCaseLabels: true 59 | IndentWidth: 4 60 | IndentWrappedFunctionNames: false 61 | KeepEmptyLinesAtTheStartOfBlocks: true 62 | MacroBlockBegin: '' 63 | MacroBlockEnd: '' 64 | MaxEmptyLinesToKeep: 2 65 | NamespaceIndentation: None 66 | ObjCBlockIndentWidth: 2 67 | ObjCSpaceAfterProperty: false 68 | ObjCSpaceBeforeProtocolList: true 69 | PenaltyBreakBeforeFirstCallParameter: 19 70 | PenaltyBreakComment: 300 71 | PenaltyBreakFirstLessLess: 120 72 | PenaltyBreakString: 1000 73 | PenaltyExcessCharacter: 1000000 74 | PenaltyReturnTypeOnItsOwnLine: 60 75 | PointerAlignment: Left 76 | ReflowComments: true 77 | SortIncludes: true 78 | SpaceAfterCStyleCast: false 79 | SpaceBeforeAssignmentOperators: true 80 | SpaceBeforeParens: ControlStatements 81 | SpaceInEmptyParentheses: false 82 | SpacesBeforeTrailingComments: 1 83 | SpacesInAngles: false 84 | SpacesInContainerLiterals: true 85 | SpacesInCStyleCastParentheses: false 86 | SpacesInParentheses: false 87 | SpacesInSquareBrackets: false 88 | Standard: Cpp11 89 | TabWidth: 4 90 | UseTab: Never 91 | ... 92 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | -*, 4 | bugprone-*, 5 | clang-diagnostic-*, 6 | -clang-diagnostic-unused-command-line-argument, 7 | google-*, 8 | -google-runtime-references, 9 | -google-readability-todo, 10 | -google-explicit-constructor, 11 | misc-*, 12 | -misc-noexcept*, 13 | -misc-unused-parameters, 14 | -misc-non-private-member-variables-in-classes, 15 | -misc-no-recursion, 16 | modernize-*, 17 | -modernize-avoid-c-arrays, 18 | -modernize-deprecated-headers, 19 | -modernize-raw-string-literal, 20 | -modernize-return-braced-init-list, 21 | -modernize-use-auto, 22 | -modernize-use-equals-delete, 23 | -modernize-use-nodiscard, 24 | -modernize-use-trailing-return-type, 25 | performance-*, 26 | readability-*, 27 | -readability-function-cognitive-complexity, 28 | -readability-implicit-bool-conversion, 29 | -readability-isolate-declaration, 30 | -readability-magic-numbers, 31 | -readability-qualified-auto, 32 | -readability-uppercase-literal-suffix, 33 | -readability-braces-around-statements, 34 | -readability-convert-member-functions-to-static, 35 | WarningsAsErrors: false 36 | AnalyzeTemporaryDtors: false 37 | FormatStyle: file 38 | ... 39 | -------------------------------------------------------------------------------- /.github/images/blarggs-tests-pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/.github/images/blarggs-tests-pass.png -------------------------------------------------------------------------------- /.github/images/pokemon-gameplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/.github/images/pokemon-gameplay.png -------------------------------------------------------------------------------- /.github/images/pokemon-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/.github/images/pokemon-menu.png -------------------------------------------------------------------------------- /.github/images/tetris-gameplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/.github/images/tetris-gameplay.png -------------------------------------------------------------------------------- /.github/images/tetris-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/.github/images/tetris-menu.png -------------------------------------------------------------------------------- /.github/images/zelda-gameplay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/.github/images/zelda-gameplay.png -------------------------------------------------------------------------------- /.github/images/zelda-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/.github/images/zelda-menu.png -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@master 11 | - name: Install dependencies 12 | run: | 13 | sudo apt-get update 14 | sudo apt-get install libsdl2-dev 15 | - name: Build 16 | run: | 17 | export CLICOLOR_FORCE=1 18 | make --no-print-directory release 19 | - name: Run test roms 20 | run: ./scripts/run_test_roms ./scripts/test_roms 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /tags 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | list(APPEND CMAKE_MODULE_PATH 3 | ${CMAKE_CURRENT_SOURCE_DIR}/cmake 4 | ${CMAKE_CURRENT_SOURCE_DIR}/platforms/sfml 5 | ) 6 | 7 | include(add_warnings) 8 | include(add_sources) 9 | 10 | project(gbemu) 11 | 12 | add_definitions(-std=c++17) 13 | add_warnings() 14 | 15 | declare_library(gbemu-core src) 16 | 17 | # SFML target 18 | # find_package(SFML 2 COMPONENTS system window graphics) 19 | 20 | # if (SFML_FOUND) 21 | # declare_executable(gbemu-sfml platforms/sfml) 22 | # include_directories(SYSTEM ${SFML_INCLUDE_DIR}) 23 | # target_link_libraries(gbemu-sfml gbemu-core ${SFML_LIBRARIES}) 24 | # endif() 25 | 26 | # SDL target 27 | find_package(SDL2) 28 | 29 | if (SDL2_FOUND) 30 | declare_executable(gbemu platforms/sdl) 31 | include_directories(SYSTEM ${SDL2_INCLUDE_DIRS}) 32 | target_link_libraries(gbemu gbemu-core ${SDL2_LIBRARIES}) 33 | endif() 34 | 35 | # Test target 36 | declare_executable(gbemu-test platforms/test) 37 | target_link_libraries(gbemu-test gbemu-core) 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2021 Jonathan Gilchrist 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of gbemu nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME=gbemu 2 | BUILD_DIR=build 3 | 4 | .PHONY: default 5 | default: release tags 6 | 7 | ################# 8 | # Utility targets 9 | ################# 10 | 11 | .PHONY: build-dir 12 | build-dir: 13 | @mkdir -p $(BUILD_DIR) 14 | 15 | .PHONY: rebuild 16 | rebuild: clean release tags 17 | 18 | .PHONY: compile 19 | compile: 20 | @cd $(BUILD_DIR) && make 21 | 22 | .PHONY: clean 23 | clean: 24 | @rm -rf $(BUILD_DIR) 25 | 26 | .PHONY: tags 27 | tags: 28 | @ctags . 29 | 30 | .PHONY: format 31 | format: 32 | @./scripts/clang-format 33 | 34 | .PHONY: tidy 35 | tidy: debug 36 | @./scripts/clang-tidy 37 | 38 | ################### 39 | # Commands to build 40 | ################### 41 | 42 | .PHONY: cmake-debug 43 | cmake-debug: build-dir 44 | @cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_EXPORT_COMPILE_COMMANDS=on .. 45 | 46 | .PHONY: cmake-release 47 | cmake-release: build-dir 48 | @cd $(BUILD_DIR) && cmake -DCMAKE_BUILD_TYPE=Release .. 49 | 50 | .PHONY: debug 51 | debug: cmake-debug compile 52 | 53 | .PHONY: release 54 | release: cmake-release compile 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gbemu 2 | 3 | `gbemu` is a Nintendo Gameboy emulator written in C++. It was written as an exercise (and for fun!) so its goals are exploration of modern C++ and clean code rather than total accuracy. 4 | 5 | ## Building 6 | 7 | Building the emulator requires `cmake` and SDL and has been tested on macOS and Debian. To compile the project, run: 8 | 9 | ```sh 10 | $ make 11 | ``` 12 | 13 | This builds two versions of the emulator: 14 | 15 | * `gbemu` - the main emulator, using SDL for graphics and input 16 | * `gbemu-test` - a headless version of the emulator for debugging & running tests 17 | 18 | ## Playing 19 | 20 | ``` 21 | usage: gbemu [--debug] [--trace] [--silent] [--exit-on-infinite-jr] [--print-serial-output] 22 | 23 | arguments: 24 | --debug Enable the debugger 25 | --exit-on-infinite-jr Stop emulation if an infinite JR loop is detected 26 | --print-serial-output Print data sent to the serial port 27 | --trace Enable trace logging 28 | --silent Disable logging 29 | ``` 30 | 31 | The key bindings are: , , , , X, Z, Enter, Backspace. 32 | 33 | ## Tests 34 | 35 | The emulator is tested using [Blargg's tests][blarggs] - these can be ran with `./scripts/run_test_roms`. 36 | 37 | 38 | 39 | ## Missing features 40 | 41 | Currently, `gbemu` only supports Gameboy games. I'm working on Gameboy Color support off-and-on at the moment. There's also no audio support yet. 42 | 43 | ## Screenshots 44 | 45 | Menu | Gameplay 46 | :-------------------------:|:-------------------------: 47 | | 48 | | 49 | | 50 | 51 | [blarggs]: http://gbdev.gg8.se/wiki/articles/Test_ROMs 52 | -------------------------------------------------------------------------------- /cmake/add_sources.cmake: -------------------------------------------------------------------------------- 1 | function(add_sources) 2 | foreach(source ${ARGN}) 3 | if(NOT IS_ABSOLUTE "${source}") 4 | set(source "${CMAKE_CURRENT_SOURCE_DIR}/${source}") 5 | endif() 6 | 7 | set_property( 8 | GLOBAL APPEND PROPERTY 9 | "ALL_SRC_FILES" 10 | "${source}" 11 | ) 12 | endforeach() 13 | endfunction() 14 | 15 | function(declare_library binary_name subdirectory) 16 | add_subdirectory(${subdirectory}) 17 | 18 | get_property(sources GLOBAL PROPERTY ALL_SRC_FILES) 19 | add_library("${binary_name}" STATIC "${sources}") 20 | 21 | set_property( 22 | GLOBAL PROPERTY 23 | "ALL_SRC_FILES" 24 | "" 25 | ) 26 | endfunction() 27 | 28 | function(declare_executable binary_name subdirectory) 29 | add_subdirectory(${subdirectory}) 30 | 31 | get_property(sources GLOBAL PROPERTY ALL_SRC_FILES) 32 | add_executable("${binary_name}" "${sources}") 33 | 34 | set_property( 35 | GLOBAL PROPERTY 36 | "ALL_SRC_FILES" 37 | "" 38 | ) 39 | endfunction() 40 | -------------------------------------------------------------------------------- /cmake/add_warnings.cmake: -------------------------------------------------------------------------------- 1 | function(add_warnings) 2 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 3 | set(extra_warn 4 | -Weverything 5 | -Wno-extra-semi-stmt 6 | -Wno-weak-vtables 7 | -Wno-exit-time-destructors 8 | -Wno-gnu-zero-variadic-macro-arguments 9 | -Wno-global-constructors 10 | -Wno-c++98-compat 11 | -Wno-c++98-compat-pedantic 12 | ) 13 | else() 14 | set(extra_warn 15 | -Wno-variadic-macros 16 | -Wno-unknown-pragmas 17 | -Wno-unused-variable 18 | ) 19 | endif() 20 | 21 | set(all_warn 22 | ${extra_warn} 23 | -Wall 24 | -Wextra 25 | -Wpedantic 26 | -Wno-padded 27 | -Wno-format-nonliteral 28 | -Wno-unused-parameter 29 | ) 30 | 31 | # FIXME: Temporarily silenced during development 32 | list(APPEND all_warn -Wno-sign-conversion) 33 | 34 | # FIXME: Temporarily silenced during development 35 | list(APPEND all_warn -Wno-implicit-int-conversion) 36 | 37 | # FIXME: Temporarily silence warnings during development 38 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 39 | list(APPEND all_warn -Wno-missing-noreturn) 40 | else() 41 | list(APPEND all_warn -Wno-return-type) 42 | endif() 43 | 44 | add_definitions(${all_warn}) 45 | endfunction() 46 | -------------------------------------------------------------------------------- /platforms/cli/cli.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../../src/options.h" 4 | #include 5 | 6 | struct CliOptions { 7 | Options options; 8 | std::string filename; 9 | }; 10 | 11 | CliOptions get_cli_options(int argc, char* argv[]); 12 | CliOptions get_cli_options(int argc, char* argv[]) { 13 | if (argc < 2) { 14 | fatal_error("Please provide a ROM file to run"); 15 | } 16 | 17 | CliOptions cliOptions; 18 | cliOptions.filename = argv[1]; 19 | 20 | std::vector flags(argv + 2, argv + argc); 21 | 22 | for (std::string& flag : flags) { 23 | if (flag == "--debug") { cliOptions.options.debugger = true; } 24 | else if (flag == "--trace") { cliOptions.options.trace = true; } 25 | else if (flag == "--silent") { cliOptions.options.disable_logs = true; } 26 | else if (flag == "--headless") { cliOptions.options.headless = true; } 27 | else if (flag == "--whole-framebuffer") { cliOptions.options.show_full_framebuffer = true; } 28 | else if (flag == "--exit-on-infinite-jr") { cliOptions.options.exit_on_infinite_jr = true; } 29 | else if (flag == "--print-serial-output") { cliOptions.options.print_serial = true; } 30 | else { fatal_error("Unknown flag: %s", flag.c_str()); } 31 | } 32 | 33 | return cliOptions; 34 | } 35 | -------------------------------------------------------------------------------- /platforms/sdl/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sources( 2 | main.cc 3 | ) 4 | -------------------------------------------------------------------------------- /platforms/sdl/main.cc: -------------------------------------------------------------------------------- 1 | #include "../../src/gameboy_prelude.h" 2 | #include "../cli/cli.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | static uint pixel_size = 2; 11 | 12 | static uint width = GAMEBOY_WIDTH * pixel_size; 13 | static uint height = GAMEBOY_HEIGHT * pixel_size; 14 | 15 | static SDL_Window* window; 16 | static SDL_Renderer* renderer; 17 | static SDL_Texture* gb_screen_texture; 18 | 19 | static std::unique_ptr gameboy; 20 | 21 | static CliOptions cliOptions; 22 | 23 | static bool should_exit = false; 24 | 25 | static std::optional get_gb_button(int keyCode) { 26 | switch (keyCode) { 27 | case SDLK_UP: return GbButton::Up; 28 | case SDLK_DOWN: return GbButton::Down; 29 | case SDLK_LEFT: return GbButton::Left; 30 | case SDLK_RIGHT: return GbButton::Right; 31 | case SDLK_x: return GbButton::A; 32 | case SDLK_z: return GbButton::B; 33 | case SDLK_BACKSPACE: return GbButton::Select; 34 | case SDLK_RETURN: return GbButton::Start; 35 | case SDLK_b: gameboy->debug_toggle_background(); return {}; 36 | case SDLK_s: gameboy->debug_toggle_sprites(); return {}; 37 | case SDLK_w: gameboy->debug_toggle_window(); return {}; 38 | default: return {}; 39 | } 40 | } 41 | 42 | static uint32_t get_real_color(Color color) { 43 | uint8_t r; 44 | uint8_t g; 45 | uint8_t b; 46 | 47 | switch (color) { 48 | case Color::White: r = g = b = 255; break; 49 | case Color::LightGray: r = g = b = 170; break; 50 | case Color::DarkGray: r = g = b = 85; break; 51 | case Color::Black: r = g = b = 0; break; 52 | } 53 | 54 | return (r << 16) | (g << 8) | (b << 0); 55 | } 56 | 57 | static void set_pixel(uint32_t* pixels, uint x, uint y, uint32_t pixel_argb) { 58 | pixels[width * y + x] = pixel_argb; 59 | } 60 | 61 | static void set_large_pixel(uint32_t* pixels, uint x, uint y, uint32_t pixel_argb) { 62 | for (uint w = 0; w < pixel_size; w++) { 63 | for (uint h = 0; h < pixel_size; h++) { 64 | set_pixel(pixels, x * pixel_size + w, y * pixel_size + h, pixel_argb); 65 | } 66 | } 67 | } 68 | 69 | static void set_pixels(uint32_t* pixels, const FrameBuffer& buffer) { 70 | for (uint y = 0; y < GAMEBOY_HEIGHT; y++) { 71 | for (uint x = 0; x < GAMEBOY_WIDTH; x++) { 72 | Color color = buffer.get_pixel(x, y); 73 | uint32_t pixel_argb = get_real_color(color); 74 | set_large_pixel(pixels, x, y, pixel_argb); 75 | } 76 | } 77 | } 78 | 79 | static std::string get_save_filename() { 80 | return cliOptions.filename + ".sav"; 81 | } 82 | 83 | static bool file_exists(const std::string& filename) { 84 | std::ifstream f(filename.c_str()); 85 | return f.good(); 86 | } 87 | 88 | static void save_state() { 89 | auto cartridge_ram = gameboy->get_cartridge_ram(); 90 | 91 | // Don't save empty cartridge RAM 92 | if (cartridge_ram.size() == 0) { return; } 93 | 94 | auto filename = get_save_filename(); 95 | std::ofstream output_file(filename); 96 | std::copy(cartridge_ram.begin(), cartridge_ram.end(), std::ostreambuf_iterator(output_file)); 97 | log_info("Wrote %d KB to %s", cartridge_ram.size() / 1024, filename.c_str()); 98 | } 99 | 100 | static std::vector load_state() { 101 | auto filename = get_save_filename(); 102 | if (!file_exists(filename)) { 103 | return {}; 104 | } else { 105 | auto save_data = read_bytes(filename); 106 | log_info("Read %d KB from %s", save_data.size() / 1024, filename.c_str()); 107 | return save_data; 108 | } 109 | } 110 | 111 | static void process_events() { 112 | SDL_Event event; 113 | 114 | while (SDL_PollEvent(&event)) { 115 | switch (event.type) { 116 | case SDL_KEYDOWN: 117 | if (event.key.repeat == true) { break; } 118 | if (auto button_pressed = get_gb_button(event.key.keysym.sym); button_pressed) { 119 | gameboy->button_pressed(*button_pressed); 120 | } 121 | break; 122 | case SDL_KEYUP: 123 | if (event.key.repeat == true) { break; } 124 | if (auto button_released = get_gb_button(event.key.keysym.sym); button_released) { 125 | gameboy->button_released(*button_released); 126 | } 127 | break; 128 | case SDL_WINDOWEVENT: 129 | if (event.window.event == SDL_WINDOWEVENT_CLOSE) { 130 | should_exit = true; 131 | } 132 | break; 133 | case SDL_QUIT: 134 | should_exit = true; 135 | break; 136 | } 137 | } 138 | } 139 | 140 | static void draw(const FrameBuffer& buffer) { 141 | process_events(); 142 | 143 | SDL_RenderClear(renderer); 144 | 145 | void* pixels_ptr; 146 | int pitch; 147 | SDL_LockTexture(gb_screen_texture, nullptr, &pixels_ptr, &pitch); 148 | 149 | uint32_t* pixels = static_cast(pixels_ptr); 150 | set_pixels(pixels, buffer); 151 | SDL_UnlockTexture(gb_screen_texture); 152 | 153 | SDL_RenderCopy(renderer, gb_screen_texture, nullptr, nullptr); 154 | SDL_RenderPresent(renderer); 155 | } 156 | 157 | static bool is_closed() { 158 | return should_exit; 159 | } 160 | 161 | int main(int argc, char* argv[]) { 162 | cliOptions = get_cli_options(argc, argv); 163 | 164 | SDL_Init(SDL_INIT_VIDEO); 165 | 166 | window = SDL_CreateWindow( 167 | "gbemu", 168 | SDL_WINDOWPOS_UNDEFINED, 169 | SDL_WINDOWPOS_UNDEFINED, 170 | width, 171 | height, 172 | SDL_WINDOW_OPENGL 173 | ); 174 | 175 | if (window == nullptr) { 176 | fatal_error("Failed to initialise SDL"); 177 | } 178 | 179 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 180 | 181 | gb_screen_texture = SDL_CreateTexture( 182 | renderer, 183 | SDL_PIXELFORMAT_ARGB8888, 184 | SDL_TEXTUREACCESS_STREAMING, 185 | width, height 186 | ); 187 | 188 | auto rom_data = read_bytes(cliOptions.filename); 189 | log_info("Read %d KB from %s", rom_data.size() / 1024, cliOptions.filename.c_str()); 190 | 191 | auto save_data = load_state(); 192 | log_info(""); 193 | 194 | gameboy = std::make_unique(rom_data, cliOptions.options, save_data); 195 | gameboy->run(&is_closed, &draw); 196 | 197 | save_state(); 198 | SDL_DestroyTexture(gb_screen_texture); 199 | SDL_DestroyRenderer(renderer); 200 | SDL_DestroyWindow(window); 201 | SDL_Quit(); 202 | 203 | return 0; 204 | } 205 | -------------------------------------------------------------------------------- /platforms/sfml/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sources( 2 | main.cc 3 | ) 4 | -------------------------------------------------------------------------------- /platforms/sfml/FindSFML.cmake: -------------------------------------------------------------------------------- 1 | # This script locates the SFML library 2 | # ------------------------------------ 3 | # 4 | # Usage 5 | # ----- 6 | # 7 | # When you try to locate the SFML libraries, you must specify which modules you want to use (system, window, graphics, network, audio, main). 8 | # If none is given, the SFML_LIBRARIES variable will be empty and you'll end up linking to nothing. 9 | # example: 10 | # find_package(SFML COMPONENTS graphics window system) # find the graphics, window and system modules 11 | # 12 | # You can enforce a specific version, either MAJOR.MINOR or only MAJOR. 13 | # If nothing is specified, the version won't be checked (i.e. any version will be accepted). 14 | # example: 15 | # find_package(SFML COMPONENTS ...) # no specific version required 16 | # find_package(SFML 2 COMPONENTS ...) # any 2.x version 17 | # find_package(SFML 2.4 COMPONENTS ...) # version 2.4 or greater 18 | # 19 | # By default, the dynamic libraries of SFML will be found. To find the static ones instead, 20 | # you must set the SFML_STATIC_LIBRARIES variable to TRUE before calling find_package(SFML ...). 21 | # Since you have to link yourself all the SFML dependencies when you link it statically, the following 22 | # additional variables are defined: SFML_XXX_DEPENDENCIES and SFML_DEPENDENCIES (see their detailed 23 | # description below). 24 | # In case of static linking, the SFML_STATIC macro will also be defined by this script. 25 | # example: 26 | # set(SFML_STATIC_LIBRARIES TRUE) 27 | # find_package(SFML 2 COMPONENTS network system) 28 | # 29 | # On Mac OS X if SFML_STATIC_LIBRARIES is not set to TRUE then by default CMake will search for frameworks unless 30 | # CMAKE_FIND_FRAMEWORK is set to "NEVER" for example. Please refer to CMake documentation for more details. 31 | # Moreover, keep in mind that SFML frameworks are only available as release libraries unlike dylibs which 32 | # are available for both release and debug modes. 33 | # 34 | # If SFML is not installed in a standard path, you can use the SFML_ROOT CMake (or environment) variable 35 | # to tell CMake where SFML is. 36 | # 37 | # Output 38 | # ------ 39 | # 40 | # This script defines the following variables: 41 | # - For each specified module XXX (system, window, graphics, network, audio, main): 42 | # - SFML_XXX_LIBRARY_DEBUG: the name of the debug library of the xxx module (set to SFML_XXX_LIBRARY_RELEASE is no debug version is found) 43 | # - SFML_XXX_LIBRARY_RELEASE: the name of the release library of the xxx module (set to SFML_XXX_LIBRARY_DEBUG is no release version is found) 44 | # - SFML_XXX_LIBRARY: the name of the library to link to for the xxx module (includes both debug and optimized names if necessary) 45 | # - SFML_XXX_FOUND: true if either the debug or release library of the xxx module is found 46 | # - SFML_XXX_DEPENDENCIES: the list of libraries the module depends on, in case of static linking 47 | # - SFML_LIBRARIES: the list of all libraries corresponding to the required modules 48 | # - SFML_FOUND: true if all the required modules are found 49 | # - SFML_INCLUDE_DIR: the path where SFML headers are located (the directory containing the SFML/Config.hpp file) 50 | # - SFML_DEPENDENCIES: the list of libraries SFML depends on, in case of static linking 51 | # 52 | # example: 53 | # find_package(SFML 2 COMPONENTS system window graphics audio REQUIRED) 54 | # include_directories(${SFML_INCLUDE_DIR}) 55 | # add_executable(myapp ...) 56 | # target_link_libraries(myapp ${SFML_LIBRARIES}) 57 | 58 | # define the SFML_STATIC macro if static build was chosen 59 | if(SFML_STATIC_LIBRARIES) 60 | add_definitions(-DSFML_STATIC) 61 | endif() 62 | 63 | # define the list of search paths for headers and libraries 64 | set(FIND_SFML_PATHS 65 | ${SFML_ROOT} 66 | $ENV{SFML_ROOT} 67 | ~/Library/Frameworks 68 | /Library/Frameworks 69 | /usr/local 70 | /usr 71 | /sw 72 | /opt/local 73 | /opt/csw 74 | /opt) 75 | 76 | # find the SFML include directory 77 | find_path(SFML_INCLUDE_DIR SFML/Config.hpp 78 | PATH_SUFFIXES include 79 | PATHS ${FIND_SFML_PATHS}) 80 | 81 | # check the version number 82 | set(SFML_VERSION_OK TRUE) 83 | if(SFML_FIND_VERSION AND SFML_INCLUDE_DIR) 84 | # extract the major and minor version numbers from SFML/Config.hpp 85 | # we have to handle framework a little bit differently: 86 | if("${SFML_INCLUDE_DIR}" MATCHES "SFML.framework") 87 | set(SFML_CONFIG_HPP_INPUT "${SFML_INCLUDE_DIR}/Headers/Config.hpp") 88 | else() 89 | set(SFML_CONFIG_HPP_INPUT "${SFML_INCLUDE_DIR}/SFML/Config.hpp") 90 | endif() 91 | FILE(READ "${SFML_CONFIG_HPP_INPUT}" SFML_CONFIG_HPP_CONTENTS) 92 | STRING(REGEX REPLACE ".*#define SFML_VERSION_MAJOR ([0-9]+).*" "\\1" SFML_VERSION_MAJOR "${SFML_CONFIG_HPP_CONTENTS}") 93 | STRING(REGEX REPLACE ".*#define SFML_VERSION_MINOR ([0-9]+).*" "\\1" SFML_VERSION_MINOR "${SFML_CONFIG_HPP_CONTENTS}") 94 | STRING(REGEX REPLACE ".*#define SFML_VERSION_PATCH ([0-9]+).*" "\\1" SFML_VERSION_PATCH "${SFML_CONFIG_HPP_CONTENTS}") 95 | if (NOT "${SFML_VERSION_PATCH}" MATCHES "^[0-9]+$") 96 | set(SFML_VERSION_PATCH 0) 97 | endif() 98 | math(EXPR SFML_REQUESTED_VERSION "${SFML_FIND_VERSION_MAJOR} * 10000 + ${SFML_FIND_VERSION_MINOR} * 100 + ${SFML_FIND_VERSION_PATCH}") 99 | 100 | # if we could extract them, compare with the requested version number 101 | if (SFML_VERSION_MAJOR) 102 | # transform version numbers to an integer 103 | math(EXPR SFML_VERSION "${SFML_VERSION_MAJOR} * 10000 + ${SFML_VERSION_MINOR} * 100 + ${SFML_VERSION_PATCH}") 104 | 105 | # compare them 106 | if(SFML_VERSION LESS SFML_REQUESTED_VERSION) 107 | set(SFML_VERSION_OK FALSE) 108 | endif() 109 | else() 110 | # SFML version is < 2.0 111 | if (SFML_REQUESTED_VERSION GREATER 10900) 112 | set(SFML_VERSION_OK FALSE) 113 | set(SFML_VERSION_MAJOR 1) 114 | set(SFML_VERSION_MINOR x) 115 | set(SFML_VERSION_PATCH x) 116 | endif() 117 | endif() 118 | endif() 119 | 120 | # find the requested modules 121 | set(SFML_FOUND TRUE) # will be set to false if one of the required modules is not found 122 | foreach(FIND_SFML_COMPONENT ${SFML_FIND_COMPONENTS}) 123 | string(TOLOWER ${FIND_SFML_COMPONENT} FIND_SFML_COMPONENT_LOWER) 124 | string(TOUPPER ${FIND_SFML_COMPONENT} FIND_SFML_COMPONENT_UPPER) 125 | set(FIND_SFML_COMPONENT_NAME sfml-${FIND_SFML_COMPONENT_LOWER}) 126 | 127 | # no suffix for sfml-main, it is always a static library 128 | if(FIND_SFML_COMPONENT_LOWER STREQUAL "main") 129 | # release library 130 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE 131 | NAMES ${FIND_SFML_COMPONENT_NAME} 132 | PATH_SUFFIXES lib64 lib 133 | PATHS ${FIND_SFML_PATHS}) 134 | 135 | # debug library 136 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG 137 | NAMES ${FIND_SFML_COMPONENT_NAME}-d 138 | PATH_SUFFIXES lib64 lib 139 | PATHS ${FIND_SFML_PATHS}) 140 | else() 141 | # static release library 142 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE 143 | NAMES ${FIND_SFML_COMPONENT_NAME}-s 144 | PATH_SUFFIXES lib64 lib 145 | PATHS ${FIND_SFML_PATHS}) 146 | 147 | # static debug library 148 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG 149 | NAMES ${FIND_SFML_COMPONENT_NAME}-s-d 150 | PATH_SUFFIXES lib64 lib 151 | PATHS ${FIND_SFML_PATHS}) 152 | 153 | # dynamic release library 154 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE 155 | NAMES ${FIND_SFML_COMPONENT_NAME} 156 | PATH_SUFFIXES lib64 lib 157 | PATHS ${FIND_SFML_PATHS}) 158 | 159 | # dynamic debug library 160 | find_library(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG 161 | NAMES ${FIND_SFML_COMPONENT_NAME}-d 162 | PATH_SUFFIXES lib64 lib 163 | PATHS ${FIND_SFML_PATHS}) 164 | 165 | # choose the entries that fit the requested link type 166 | if(SFML_STATIC_LIBRARIES) 167 | if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE) 168 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE}) 169 | endif() 170 | if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG) 171 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG}) 172 | endif() 173 | else() 174 | if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE) 175 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE}) 176 | endif() 177 | if(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG) 178 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG}) 179 | endif() 180 | endif() 181 | endif() 182 | 183 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG OR SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 184 | # library found 185 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_FOUND TRUE) 186 | 187 | # if both are found, set SFML_XXX_LIBRARY to contain both 188 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG AND SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 189 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY debug ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG} 190 | optimized ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 191 | endif() 192 | 193 | # if only one debug/release variant is found, set the other to be equal to the found one 194 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG AND NOT SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE) 195 | # debug and not release 196 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG}) 197 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG}) 198 | endif() 199 | if (SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE AND NOT SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG) 200 | # release and not debug 201 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 202 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY ${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE}) 203 | endif() 204 | else() 205 | # library not found 206 | set(SFML_FOUND FALSE) 207 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_FOUND FALSE) 208 | set(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY "") 209 | set(FIND_SFML_MISSING "${FIND_SFML_MISSING} SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY") 210 | endif() 211 | 212 | # mark as advanced 213 | MARK_AS_ADVANCED(SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY 214 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_RELEASE 215 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DEBUG 216 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_RELEASE 217 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_STATIC_DEBUG 218 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_RELEASE 219 | SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY_DYNAMIC_DEBUG) 220 | 221 | # add to the global list of libraries 222 | set(SFML_LIBRARIES ${SFML_LIBRARIES} "${SFML_${FIND_SFML_COMPONENT_UPPER}_LIBRARY}") 223 | endforeach() 224 | 225 | # in case of static linking, we must also define the list of all the dependencies of SFML libraries 226 | if(SFML_STATIC_LIBRARIES) 227 | 228 | # detect the OS 229 | if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") 230 | set(FIND_SFML_OS_WINDOWS 1) 231 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") 232 | set(FIND_SFML_OS_LINUX 1) 233 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") 234 | set(FIND_SFML_OS_FREEBSD 1) 235 | elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") 236 | set(FIND_SFML_OS_MACOSX 1) 237 | endif() 238 | 239 | # start with an empty list 240 | set(SFML_DEPENDENCIES) 241 | set(FIND_SFML_DEPENDENCIES_NOTFOUND) 242 | 243 | # macro that searches for a 3rd-party library 244 | macro(find_sfml_dependency output friendlyname) 245 | # No lookup in environment variables (PATH on Windows), as they may contain wrong library versions 246 | find_library(${output} NAMES ${ARGN} PATHS ${FIND_SFML_PATHS} PATH_SUFFIXES lib NO_SYSTEM_ENVIRONMENT_PATH) 247 | if(${${output}} STREQUAL "${output}-NOTFOUND") 248 | unset(output) 249 | set(FIND_SFML_DEPENDENCIES_NOTFOUND "${FIND_SFML_DEPENDENCIES_NOTFOUND} ${friendlyname}") 250 | endif() 251 | endmacro() 252 | 253 | # sfml-system 254 | list(FIND SFML_FIND_COMPONENTS "system" FIND_SFML_SYSTEM_COMPONENT) 255 | if(NOT ${FIND_SFML_SYSTEM_COMPONENT} EQUAL -1) 256 | 257 | # update the list -- these are only system libraries, no need to find them 258 | if(FIND_SFML_OS_LINUX OR FIND_SFML_OS_FREEBSD OR FIND_SFML_OS_MACOSX) 259 | set(SFML_SYSTEM_DEPENDENCIES "pthread") 260 | endif() 261 | if(FIND_SFML_OS_LINUX) 262 | set(SFML_SYSTEM_DEPENDENCIES ${SFML_SYSTEM_DEPENDENCIES} "rt") 263 | endif() 264 | if(FIND_SFML_OS_WINDOWS) 265 | set(SFML_SYSTEM_DEPENDENCIES "winmm") 266 | endif() 267 | set(SFML_DEPENDENCIES ${SFML_SYSTEM_DEPENDENCIES} ${SFML_DEPENDENCIES}) 268 | endif() 269 | 270 | # sfml-network 271 | list(FIND SFML_FIND_COMPONENTS "network" FIND_SFML_NETWORK_COMPONENT) 272 | if(NOT ${FIND_SFML_NETWORK_COMPONENT} EQUAL -1) 273 | 274 | # update the list -- these are only system libraries, no need to find them 275 | if(FIND_SFML_OS_WINDOWS) 276 | set(SFML_NETWORK_DEPENDENCIES "ws2_32") 277 | endif() 278 | set(SFML_DEPENDENCIES ${SFML_NETWORK_DEPENDENCIES} ${SFML_DEPENDENCIES}) 279 | endif() 280 | 281 | # sfml-window 282 | list(FIND SFML_FIND_COMPONENTS "window" FIND_SFML_WINDOW_COMPONENT) 283 | if(NOT ${FIND_SFML_WINDOW_COMPONENT} EQUAL -1) 284 | 285 | # find libraries 286 | if(FIND_SFML_OS_LINUX OR FIND_SFML_OS_FREEBSD) 287 | find_sfml_dependency(X11_LIBRARY "X11" X11) 288 | find_sfml_dependency(LIBXCB_LIBRARIES "XCB" xcb libxcb) 289 | find_sfml_dependency(X11_XCB_LIBRARY "X11-xcb" X11-xcb libX11-xcb) 290 | find_sfml_dependency(XCB_RANDR_LIBRARY "xcb-randr" xcb-randr libxcb-randr) 291 | find_sfml_dependency(XCB_IMAGE_LIBRARY "xcb-image" xcb-image libxcb-image) 292 | endif() 293 | 294 | if(FIND_SFML_OS_LINUX) 295 | find_sfml_dependency(UDEV_LIBRARIES "UDev" udev libudev) 296 | endif() 297 | 298 | # update the list 299 | if(FIND_SFML_OS_WINDOWS) 300 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "opengl32" "winmm" "gdi32") 301 | elseif(FIND_SFML_OS_LINUX) 302 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "GL" ${X11_LIBRARY} ${LIBXCB_LIBRARIES} ${X11_XCB_LIBRARY} ${XCB_RANDR_LIBRARY} ${XCB_IMAGE_LIBRARY} ${UDEV_LIBRARIES}) 303 | elseif(FIND_SFML_OS_FREEBSD) 304 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "GL" ${X11_LIBRARY} ${LIBXCB_LIBRARIES} ${X11_XCB_LIBRARY} ${XCB_RANDR_LIBRARY} ${XCB_IMAGE_LIBRARY} "usbhid") 305 | elseif(FIND_SFML_OS_MACOSX) 306 | set(SFML_WINDOW_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} "-framework OpenGL -framework Foundation -framework AppKit -framework IOKit -framework Carbon") 307 | endif() 308 | set(SFML_DEPENDENCIES ${SFML_WINDOW_DEPENDENCIES} ${SFML_DEPENDENCIES}) 309 | endif() 310 | 311 | # sfml-graphics 312 | list(FIND SFML_FIND_COMPONENTS "graphics" FIND_SFML_GRAPHICS_COMPONENT) 313 | if(NOT ${FIND_SFML_GRAPHICS_COMPONENT} EQUAL -1) 314 | 315 | # find libraries 316 | find_sfml_dependency(FREETYPE_LIBRARY "FreeType" freetype) 317 | find_sfml_dependency(JPEG_LIBRARY "libjpeg" jpeg) 318 | 319 | # update the list 320 | set(SFML_GRAPHICS_DEPENDENCIES ${FREETYPE_LIBRARY} ${JPEG_LIBRARY}) 321 | set(SFML_DEPENDENCIES ${SFML_GRAPHICS_DEPENDENCIES} ${SFML_DEPENDENCIES}) 322 | endif() 323 | 324 | # sfml-audio 325 | list(FIND SFML_FIND_COMPONENTS "audio" FIND_SFML_AUDIO_COMPONENT) 326 | if(NOT ${FIND_SFML_AUDIO_COMPONENT} EQUAL -1) 327 | 328 | # find libraries 329 | find_sfml_dependency(OPENAL_LIBRARY "OpenAL" openal openal32) 330 | find_sfml_dependency(OGG_LIBRARY "Ogg" ogg) 331 | find_sfml_dependency(VORBIS_LIBRARY "Vorbis" vorbis) 332 | find_sfml_dependency(VORBISFILE_LIBRARY "VorbisFile" vorbisfile) 333 | find_sfml_dependency(VORBISENC_LIBRARY "VorbisEnc" vorbisenc) 334 | find_sfml_dependency(FLAC_LIBRARY "FLAC" FLAC) 335 | 336 | # update the list 337 | set(SFML_AUDIO_DEPENDENCIES ${OPENAL_LIBRARY} ${FLAC_LIBRARY} ${VORBISENC_LIBRARY} ${VORBISFILE_LIBRARY} ${VORBIS_LIBRARY} ${OGG_LIBRARY}) 338 | set(SFML_DEPENDENCIES ${SFML_DEPENDENCIES} ${SFML_AUDIO_DEPENDENCIES}) 339 | endif() 340 | 341 | endif() 342 | 343 | # handle errors 344 | if(NOT SFML_VERSION_OK) 345 | # SFML version not ok 346 | set(FIND_SFML_ERROR "SFML found but version too low (requested: ${SFML_FIND_VERSION}, found: ${SFML_VERSION_MAJOR}.${SFML_VERSION_MINOR}.${SFML_VERSION_PATCH})") 347 | set(SFML_FOUND FALSE) 348 | elseif(SFML_STATIC_LIBRARIES AND FIND_SFML_DEPENDENCIES_NOTFOUND) 349 | set(FIND_SFML_ERROR "SFML found but some of its dependencies are missing (${FIND_SFML_DEPENDENCIES_NOTFOUND})") 350 | set(SFML_FOUND FALSE) 351 | elseif(NOT SFML_FOUND) 352 | # include directory or library not found 353 | set(FIND_SFML_ERROR "Could NOT find SFML (missing: ${FIND_SFML_MISSING})") 354 | endif() 355 | if (NOT SFML_FOUND) 356 | if(SFML_FIND_REQUIRED) 357 | # fatal error 358 | message(FATAL_ERROR ${FIND_SFML_ERROR}) 359 | elseif(NOT SFML_FIND_QUIETLY) 360 | # error but continue 361 | message("${FIND_SFML_ERROR}") 362 | endif() 363 | endif() 364 | 365 | # handle success 366 | if(SFML_FOUND AND NOT SFML_FIND_QUIETLY) 367 | message(STATUS "Found SFML ${SFML_VERSION_MAJOR}.${SFML_VERSION_MINOR}.${SFML_VERSION_PATCH} in ${SFML_INCLUDE_DIR}") 368 | endif() 369 | -------------------------------------------------------------------------------- /platforms/sfml/main.cc: -------------------------------------------------------------------------------- 1 | #include "../../src/gameboy_prelude.h" 2 | #include "../cli/cli.h" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | static uint pixel_size = 5; 10 | 11 | static uint width = GAMEBOY_WIDTH * pixel_size; 12 | static uint height = GAMEBOY_HEIGHT * pixel_size; 13 | 14 | static std::unique_ptr window; 15 | static sf::Image image; 16 | static sf::Texture texture; 17 | static sf::Sprite sprite; 18 | 19 | static std::unique_ptr gameboy; 20 | 21 | static CliOptions cliOptions; 22 | 23 | static bool should_exit = false; 24 | 25 | static bool get_gb_button(int keyCode, GbButton& button) { 26 | switch (keyCode) { 27 | case sf::Keyboard::Up: button = GbButton::Up; break; 28 | case sf::Keyboard::Down: button = GbButton::Down; break; 29 | case sf::Keyboard::Left: button = GbButton::Left; break; 30 | case sf::Keyboard::Right: button = GbButton::Right; break; 31 | case sf::Keyboard::X: button = GbButton::A; break; 32 | case sf::Keyboard::Z: button = GbButton::B; break; 33 | case sf::Keyboard::BackSpace: button = GbButton::Select; break; 34 | case sf::Keyboard::Return: button = GbButton::Start; break; 35 | case sf::Keyboard::Escape: should_exit = true; return false; 36 | case sf::Keyboard::B: gameboy->debug_toggle_background(); return false; 37 | case sf::Keyboard::S: gameboy->debug_toggle_sprites(); return false; 38 | case sf::Keyboard::W: gameboy->debug_toggle_window(); return false; 39 | default: return false; 40 | } 41 | 42 | return true; 43 | } 44 | 45 | static sf::Color get_real_color(Color color) { 46 | switch (color) { 47 | case Color::White: return sf::Color(255, 255, 255); 48 | case Color::LightGray: return sf::Color(170, 170, 170); 49 | case Color::DarkGray: return sf::Color( 85, 85, 85); 50 | case Color::Black: return sf::Color( 0, 0, 0); 51 | } 52 | } 53 | 54 | static void set_large_pixel(uint x, uint y, sf::Color color) { 55 | for (uint w = 0; w < pixel_size; w++) { 56 | for (uint h = 0; h < pixel_size; h++) { 57 | image.setPixel(x * pixel_size + w, y * pixel_size + h, color); 58 | } 59 | } 60 | } 61 | 62 | static void set_pixels(const FrameBuffer& buffer) { 63 | for (uint y = 0; y < GAMEBOY_HEIGHT; y++) { 64 | for (uint x = 0; x < GAMEBOY_WIDTH; x++) { 65 | Color color = buffer.get_pixel(x, y); 66 | sf::Color pixel_color = get_real_color(color); 67 | 68 | set_large_pixel(x, y, pixel_color); 69 | } 70 | } 71 | } 72 | 73 | static std::string get_save_filename() { 74 | return cliOptions.filename + ".sav"; 75 | } 76 | 77 | static bool file_exists(const std::string& filename) { 78 | std::ifstream f(filename.c_str()); 79 | return f.good(); 80 | } 81 | 82 | static void save_state() { 83 | auto cartridge_ram = gameboy->get_cartridge_ram(); 84 | 85 | // Don't save empty cartridge RAM 86 | if (cartridge_ram.size() == 0) { return; } 87 | 88 | auto filename = get_save_filename(); 89 | std::ofstream output_file(filename); 90 | std::copy(cartridge_ram.begin(), cartridge_ram.end(), std::ostreambuf_iterator(output_file)); 91 | log_info("Wrote %d KB to %s", cartridge_ram.size() / 1024, filename.c_str()); 92 | } 93 | 94 | static std::vector load_state() { 95 | auto filename = get_save_filename(); 96 | if (!file_exists(filename)) { 97 | return {}; 98 | } else { 99 | auto save_data = read_bytes(filename); 100 | log_info("Read %d KB from %s", save_data.size() / 1024, filename.c_str()); 101 | return save_data; 102 | } 103 | } 104 | 105 | static void process_events() { 106 | sf::Event event; 107 | 108 | while (window->pollEvent(event)) { 109 | if (event.type == sf::Event::KeyPressed) { 110 | GbButton button; 111 | 112 | auto relevant_button = get_gb_button(event.key.code, button); 113 | if (relevant_button) { 114 | gameboy->button_pressed(button); 115 | } 116 | } 117 | 118 | if (event.type == sf::Event::KeyReleased) { 119 | GbButton button; 120 | 121 | auto relevant_button = get_gb_button(event.key.code, button); 122 | if (relevant_button) { 123 | gameboy->button_released(button); 124 | } 125 | } 126 | 127 | if (event.type == sf::Event::Closed) { 128 | save_state(); 129 | window->close(); 130 | } 131 | } 132 | } 133 | 134 | static void draw(const FrameBuffer& buffer) { 135 | process_events(); 136 | 137 | window->clear(sf::Color::White); 138 | 139 | set_pixels(buffer); 140 | texture.loadFromImage(image); 141 | sprite.setTexture(texture, true); 142 | 143 | window->draw(sprite); 144 | 145 | window->display(); 146 | } 147 | 148 | static bool is_closed() { 149 | return !window->isOpen() || should_exit; 150 | } 151 | 152 | int main(int argc, char* argv[]) { 153 | cliOptions = get_cli_options(argc, argv); 154 | 155 | window = std::make_unique(sf::VideoMode(width, height), "gbemu", sf::Style::Titlebar | sf::Style::Close); 156 | image.create(width, height); 157 | window->setFramerateLimit(60); 158 | window->setVerticalSyncEnabled(true); 159 | window->setKeyRepeatEnabled(false); 160 | window->display(); 161 | 162 | auto rom_data = read_bytes(cliOptions.filename); 163 | log_info("Read %d KB from %s", rom_data.size() / 1024, cliOptions.filename.c_str()); 164 | 165 | auto save_data = load_state(); 166 | log_info(""); 167 | 168 | gameboy = std::make_unique(rom_data, cliOptions.options, save_data); 169 | gameboy->run(&is_closed, &draw); 170 | return 0; 171 | } 172 | -------------------------------------------------------------------------------- /platforms/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sources( 2 | main.cc 3 | ) 4 | -------------------------------------------------------------------------------- /platforms/test/main.cc: -------------------------------------------------------------------------------- 1 | #include "../../src/gameboy_prelude.h" 2 | #include "../cli/cli.h" 3 | 4 | static std::unique_ptr gameboy; 5 | 6 | static void draw(const FrameBuffer& buffer) { 7 | } 8 | 9 | static bool is_closed() { 10 | return false; 11 | } 12 | 13 | int main(int argc, char* argv[]) { 14 | CliOptions cliOptions = get_cli_options(argc, argv); 15 | auto rom_data = read_bytes(cliOptions.filename); 16 | gameboy = std::make_unique(rom_data, cliOptions.options); 17 | gameboy->run(&is_closed, &draw); 18 | } 19 | -------------------------------------------------------------------------------- /scripts/clang-fix: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check=$1 4 | 5 | for i in $(find src -name "*.cc"); do 6 | clang-tidy --checks="-*,${check}" --fix -header-filter=.* $i \ 7 | 2> >(grep -v "warnings generated." | grep -v "in non-user code" | grep -v "Use -header-filter=*" 1>&2) -- 8 | done 9 | -------------------------------------------------------------------------------- /scripts/clang-format: -------------------------------------------------------------------------------- 1 | for i in $(find src -name "*.cc" -or -name "*.h"); do 2 | clang-format -i $i; 3 | done 4 | -------------------------------------------------------------------------------- /scripts/clang-tidy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for i in $(find src -name "*.cc"); do 4 | clang-tidy --config-file="./.clang-tidy" -header-filter=.* $i \ 5 | 2> >(grep -v "warnings generated." | grep -v "in non-user code" | grep -v "Use -header-filter=*" 1>&2) $@ -- 6 | done 7 | -------------------------------------------------------------------------------- /scripts/run_test_roms: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o nounset 4 | 5 | TEST_ROM_DIR="./scripts/test_roms" 6 | 7 | RED="\e[31m" 8 | GREEN="\e[32m" 9 | RESET="\e[0m" 10 | 11 | run_test_rom() { 12 | local FILENAME=$(basename "$1") 13 | 14 | printf "%-30s" "${FILENAME}" 15 | 16 | local OUTPUT=$(./build/gbemu-test "$1" --headless --print-serial-output --exit-on-infinite-jr) 17 | echo $OUTPUT | grep 'Passed' &> /dev/null 18 | 19 | if [ $? == 0 ]; then 20 | printf "${GREEN}Passed${RESET}\n" 21 | return 0 22 | else 23 | printf "${RED}Failed${RESET}\n" 24 | return 1 25 | fi 26 | } 27 | 28 | main() { 29 | local failed_test=0 30 | 31 | for test_rom in ${TEST_ROM_DIR}/*; do 32 | run_test_rom "$test_rom" 33 | 34 | if [ $? != 0 ]; then 35 | failed_test=1 36 | fi 37 | done 38 | 39 | return $failed_test 40 | } 41 | 42 | main 43 | exit $? 44 | -------------------------------------------------------------------------------- /scripts/test_roms/01-special.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/01-special.gb -------------------------------------------------------------------------------- /scripts/test_roms/02-interrupts.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/02-interrupts.gb -------------------------------------------------------------------------------- /scripts/test_roms/03-op sp,hl.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/03-op sp,hl.gb -------------------------------------------------------------------------------- /scripts/test_roms/04-op r,imm.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/04-op r,imm.gb -------------------------------------------------------------------------------- /scripts/test_roms/05-op rp.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/05-op rp.gb -------------------------------------------------------------------------------- /scripts/test_roms/06-ld r,r.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/06-ld r,r.gb -------------------------------------------------------------------------------- /scripts/test_roms/07-jr,jp,call,ret,rst.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/07-jr,jp,call,ret,rst.gb -------------------------------------------------------------------------------- /scripts/test_roms/08-misc instrs.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/08-misc instrs.gb -------------------------------------------------------------------------------- /scripts/test_roms/09-op r,r.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/09-op r,r.gb -------------------------------------------------------------------------------- /scripts/test_roms/10-bit ops.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/10-bit ops.gb -------------------------------------------------------------------------------- /scripts/test_roms/11-op a,(hl).gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jgilchrist/gbemu/becc2e4475c2ba765b43701a8211f06d61b21219/scripts/test_roms/11-op a,(hl).gb -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sources( 2 | address.cc 3 | debugger.cc 4 | gameboy.cc 5 | input.cc 6 | mmu.cc 7 | register.cc 8 | serial.cc 9 | timer.cc 10 | ) 11 | 12 | add_subdirectory(cartridge) 13 | add_subdirectory(cpu) 14 | add_subdirectory(util) 15 | add_subdirectory(video) 16 | -------------------------------------------------------------------------------- /src/address.cc: -------------------------------------------------------------------------------- 1 | #include "address.h" 2 | 3 | Address::Address(u16 location) : addr(location) { 4 | } 5 | 6 | Address::Address(const RegisterPair& from) : addr(from.value()) { 7 | } 8 | 9 | Address::Address(const WordRegister& from) : addr(from.value()) { 10 | } 11 | 12 | auto Address::value() const -> u16 { return addr; } 13 | 14 | auto Address::in_range(Address low, Address high) const -> bool { 15 | return low.value() <= value() && value() <= high.value(); 16 | } 17 | 18 | auto Address::operator==(u16 other) const -> bool { return addr == other; } 19 | 20 | auto Address::operator+(uint other) const -> Address { 21 | u16 new_addr = static_cast(addr + other); 22 | return Address{new_addr}; 23 | } 24 | 25 | auto Address::operator-(uint other) const -> Address { 26 | u16 new_addr = static_cast(addr - other); 27 | return Address{new_addr}; 28 | } 29 | -------------------------------------------------------------------------------- /src/address.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "definitions.h" 4 | #include "register.h" 5 | 6 | class Address { 7 | public: 8 | Address(u16 location); 9 | explicit Address(const RegisterPair& from); 10 | explicit Address(const WordRegister& from); 11 | 12 | auto value() const -> u16; 13 | 14 | auto in_range(Address low, Address high) const -> bool; 15 | 16 | auto operator==(u16 other) const -> bool; 17 | auto operator+(uint other) const -> Address; 18 | auto operator-(uint other) const -> Address; 19 | 20 | private: 21 | u16 addr = 0x0; 22 | }; 23 | -------------------------------------------------------------------------------- /src/boot.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* clang-format off */ 3 | 4 | #include 5 | 6 | /* From the Gearboy emulator */ 7 | const std::array bootDMG = { 8 | 0x31, 0xFE, 0xFF, 0xAF, 0x21, 0xFF, 0x9F, 0x32, 0xCB, 0x7C, 0x20, 0xFB, 0x21, 0x26, 0xFF, 0x0E, 9 | 0x11, 0x3E, 0x80, 0x32, 0xE2, 0x0C, 0x3E, 0xF3, 0xE2, 0x32, 0x3E, 0x77, 0x77, 0x3E, 0xFC, 0xE0, 10 | 0x47, 0x11, 0x04, 0x01, 0x21, 0x10, 0x80, 0x1A, 0xCD, 0x95, 0x00, 0xCD, 0x96, 0x00, 0x13, 0x7B, 11 | 0xFE, 0x34, 0x20, 0xF3, 0x11, 0xD8, 0x00, 0x06, 0x08, 0x1A, 0x13, 0x22, 0x23, 0x05, 0x20, 0xF9, 12 | 0x3E, 0x19, 0xEA, 0x10, 0x99, 0x21, 0x2F, 0x99, 0x0E, 0x0C, 0x3D, 0x28, 0x08, 0x32, 0x0D, 0x20, 13 | 0xF9, 0x2E, 0x0F, 0x18, 0xF3, 0x67, 0x3E, 0x64, 0x57, 0xE0, 0x42, 0x3E, 0x91, 0xE0, 0x40, 0x04, 14 | 0x1E, 0x02, 0x0E, 0x0C, 0xF0, 0x44, 0xFE, 0x90, 0x20, 0xFA, 0x0D, 0x20, 0xF7, 0x1D, 0x20, 0xF2, 15 | 0x0E, 0x13, 0x24, 0x7C, 0x1E, 0x83, 0xFE, 0x62, 0x28, 0x06, 0x1E, 0xC1, 0xFE, 0x64, 0x20, 0x06, 16 | 0x7B, 0xE2, 0x0C, 0x3E, 0x87, 0xE2, 0xF0, 0x42, 0x90, 0xE0, 0x42, 0x15, 0x20, 0xD2, 0x05, 0x20, 17 | 0x4F, 0x16, 0x20, 0x18, 0xCB, 0x4F, 0x06, 0x04, 0xC5, 0xCB, 0x11, 0x17, 0xC1, 0xCB, 0x11, 0x17, 18 | 0x05, 0x20, 0xF5, 0x22, 0x23, 0x22, 0x23, 0xC9, 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, 19 | 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, 20 | 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, 21 | 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, 0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA5, 0x42, 0x3C, 22 | 0x21, 0x04, 0x01, 0x11, 0xA8, 0x00, 0x1A, 0x13, 0xBE, 0x00, 0x00, 0x23, 0x7D, 0xFE, 0x34, 0x20, 23 | 0xF5, 0x06, 0x19, 0x78, 0x86, 0x23, 0x05, 0x20, 0xFB, 0x86, 0x00, 0x00, 0x3E, 0x01, 0xE0, 0x50 24 | }; 25 | -------------------------------------------------------------------------------- /src/cartridge/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sources( 2 | cartridge.cc 3 | cartridge_info.cc 4 | ) 5 | -------------------------------------------------------------------------------- /src/cartridge/cartridge.cc: -------------------------------------------------------------------------------- 1 | #include "cartridge.h" 2 | 3 | #include 4 | 5 | #include "../util/files.h" 6 | #include "../util/log.h" 7 | 8 | auto get_cartridge(const std::vector& rom_data, const std::vector& ram_data) 9 | -> std::shared_ptr { 10 | std::unique_ptr info = get_info(rom_data); 11 | 12 | switch (info->type) { 13 | case CartridgeType::ROMOnly: 14 | return std::make_shared(rom_data, ram_data, std::move(info)); 15 | case CartridgeType::MBC1: 16 | return std::make_shared(rom_data, ram_data, std::move(info)); 17 | case CartridgeType::MBC2: 18 | fatal_error("MBC2 is unimplemented"); 19 | case CartridgeType::MBC3: 20 | return std::make_shared(rom_data, ram_data, std::move(info)); 21 | case CartridgeType::MBC4: 22 | fatal_error("MBC4 is unimplemented"); 23 | case CartridgeType::MBC5: 24 | fatal_error("MBC5 is unimplemented"); 25 | case CartridgeType::Unknown: 26 | fatal_error("Unknown cartridge type"); 27 | } 28 | } 29 | 30 | Cartridge::Cartridge(std::vector rom_data, const std::vector& ram_data, 31 | std::unique_ptr in_cartridge_info) 32 | : rom(std::move(rom_data)), cartridge_info(std::move(in_cartridge_info)) { 33 | auto ram_size_for_cartridge = get_actual_ram_size(cartridge_info->ram_size); 34 | 35 | if (!ram_data.empty()) { 36 | if (ram_data.size() != ram_size_for_cartridge) { fatal_error("Invalid or corrupted RAM file. Read %d bytes, expected %d", ram_data.size(), ram_size_for_cartridge); } 37 | ram = ram_data; 38 | } else { 39 | ram = std::vector(ram_size_for_cartridge, 0); 40 | } 41 | } 42 | 43 | auto Cartridge::get_cartridge_ram() const -> const std::vector& { return ram; } 44 | 45 | NoMBC::NoMBC(std::vector rom_data, const std::vector& ram_data, 46 | std::unique_ptr in_cartridge_info) 47 | : Cartridge(std::move(rom_data), ram_data, std::move(in_cartridge_info)) {} 48 | 49 | void NoMBC::write(const Address& address, u8 value) { 50 | log_warn("Attempting to write to cartridge ROM without an MBC"); 51 | } 52 | 53 | auto NoMBC::read(const Address& address) const -> u8 { 54 | /* TODO: check this address is in sensible bounds */ 55 | return rom.at(address.value()); 56 | } 57 | 58 | MBC1::MBC1(std::vector rom_data, const std::vector& ram_data, 59 | std::unique_ptr in_cartridge_info) 60 | : Cartridge(std::move(rom_data), ram_data, std::move(in_cartridge_info)) { 61 | unused(rom_banking_mode); 62 | 63 | rom_bank.set(0x1); 64 | } 65 | 66 | void MBC1::write(const Address& address, u8 value) { 67 | if (address.in_range(0x0000, 0x1FFF)) { 68 | ram_enabled = true; 69 | } 70 | 71 | if (address.in_range(0x2000, 0x3FFF)) { 72 | if (value == 0x0) { rom_bank.set(0x1); } 73 | 74 | if (value == 0x20) { rom_bank.set(0x21); return; } 75 | if (value == 0x40) { rom_bank.set(0x41); return; } 76 | if (value == 0x60) { rom_bank.set(0x61); return; } 77 | 78 | u16 rom_bank_bits = value & 0x1F; 79 | rom_bank.set(rom_bank_bits); 80 | } 81 | 82 | if (address.in_range(0x4000, 0x5FFF)) { 83 | log_unimplemented("Unimplemented: Setting upper bits of ROM bank number"); 84 | } 85 | 86 | if (address.in_range(0x6000, 0x7FFF)) { 87 | log_unimplemented("Unimplemented: Selecting ROM/RAM Mode"); 88 | } 89 | 90 | if (address.in_range(0xA000, 0xBFFF)) { 91 | if (!ram_enabled) { return; } 92 | 93 | auto offset_into_ram = 0x2000 * ram_bank.value(); 94 | auto address_in_ram = (address - 0xA000) + offset_into_ram; 95 | ram.at(address_in_ram.value()) = value; 96 | } 97 | } 98 | 99 | auto MBC1::read(const Address& address) const -> u8 { 100 | if (address.in_range(0x0000, 0x3FFF)) { 101 | return rom.at(address.value()); 102 | } 103 | 104 | if (address.in_range(0x4000, 0x7FFF)) { 105 | u16 address_into_bank = address.value() - 0x4000; 106 | uint bank_offset = 0x4000 * rom_bank.value(); 107 | 108 | uint address_in_rom = bank_offset + address_into_bank; 109 | return rom.at(address_in_rom); 110 | } 111 | 112 | if (address.in_range(0xA000, 0xBFFF)) { 113 | auto offset_into_ram = 0x2000 * ram_bank.value(); 114 | auto address_in_ram = (address - 0xA000) + offset_into_ram; 115 | return ram.at(address_in_ram.value()); 116 | } 117 | 118 | fatal_error("Attempted to read from unmapped MBC1 address 0x%x", address.value()); 119 | } 120 | 121 | MBC3::MBC3(std::vector rom_data, const std::vector& ram_data, 122 | std::unique_ptr in_cartridge_info) 123 | : Cartridge(std::move(rom_data), ram_data, std::move(in_cartridge_info)) { 124 | unused(rom_banking_mode); 125 | 126 | rom_bank.set(0x1); 127 | } 128 | 129 | void MBC3::write(const Address& address, u8 value) { 130 | if (address.in_range(0x0000, 0x1FFF)) { 131 | if (value == 0x0A) { 132 | ram_enabled = true; 133 | } 134 | 135 | if (value == 0x0) { 136 | ram_enabled = false; 137 | } 138 | } 139 | 140 | if (address.in_range(0x2000, 0x3FFF)) { 141 | if (value == 0x0) { rom_bank.set(0x1); } 142 | 143 | u16 rom_bank_bits = value & 0x7F; 144 | rom_bank.set(rom_bank_bits); 145 | } 146 | 147 | if (address.in_range(0x4000, 0x5FFF)) { 148 | if (value <= 0x03) { 149 | ram_over_rtc = true; 150 | ram_bank.set(value); 151 | } 152 | 153 | if (value >= 0x08 && value <= 0xC) { 154 | ram_over_rtc = false; 155 | log_unimplemented("Using RTC registers of MBC3 cartridge"); 156 | } 157 | } 158 | 159 | if (address.in_range(0x6000, 0x7FFF)) { 160 | log_unimplemented("Unimplemented: Latch clock data"); 161 | } 162 | 163 | if (address.in_range(0xA000, 0xBFFF)) { 164 | if (!ram_enabled) { return; } 165 | 166 | if (ram_over_rtc) { 167 | auto offset_into_ram = 0x2000 * ram_bank.value(); 168 | auto address_in_ram = (address - 0xA000) + offset_into_ram; 169 | ram.at(address_in_ram.value()) = value; 170 | } 171 | } 172 | } 173 | 174 | auto MBC3::read(const Address& address) const -> u8 { 175 | if (address.in_range(0x0000, 0x3FFF)) { 176 | return rom.at(address.value()); 177 | } 178 | 179 | if (address.in_range(0x4000, 0x7FFF)) { 180 | u16 address_into_bank = address.value() - 0x4000; 181 | uint bank_offset = 0x4000 * rom_bank.value(); 182 | 183 | uint address_in_rom = bank_offset + address_into_bank; 184 | return rom.at(address_in_rom); 185 | } 186 | 187 | if (address.in_range(0xA000, 0xBFFF)) { 188 | auto offset_into_ram = 0x2000 * ram_bank.value(); 189 | auto address_in_ram = (address - 0xA000) + offset_into_ram; 190 | return ram.at(address_in_ram.value()); 191 | } 192 | 193 | fatal_error("Attempted to read from unmapped MBC1 address 0x%x", address.value()); 194 | } 195 | -------------------------------------------------------------------------------- /src/cartridge/cartridge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cartridge_info.h" 4 | #include "../address.h" 5 | #include "../register.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | class Cartridge { 12 | public: 13 | Cartridge(std::vector rom_data, const std::vector& ram_data, 14 | std::unique_ptr cartridge_info); 15 | virtual ~Cartridge() = default; 16 | 17 | virtual auto read(const Address& address) const -> u8 = 0; 18 | virtual void write(const Address& address, u8 value) = 0; 19 | 20 | auto get_cartridge_ram() const -> const std::vector&; 21 | 22 | protected: 23 | std::vector rom; 24 | std::vector ram; 25 | 26 | std::unique_ptr cartridge_info; 27 | }; 28 | 29 | auto get_cartridge(const std::vector& rom_data, const std::vector& ram_data = {}) 30 | -> std::shared_ptr; 31 | 32 | class NoMBC : public Cartridge { 33 | public: 34 | NoMBC(std::vector rom_data, const std::vector& ram_data, 35 | std::unique_ptr cartridge_info); 36 | 37 | auto read(const Address& address) const -> u8 override; 38 | void write(const Address& address, u8 value) override; 39 | }; 40 | 41 | class MBC1 : public Cartridge { 42 | public: 43 | MBC1(std::vector rom_data, const std::vector& ram_data, 44 | std::unique_ptr cartridge_info); 45 | 46 | auto read(const Address& address) const -> u8 override; 47 | void write(const Address& address, u8 value) override; 48 | 49 | private: 50 | WordRegister rom_bank; 51 | WordRegister ram_bank; 52 | bool ram_enabled = false; 53 | 54 | // TODO: ROM/RAM Mode Select (6000-7FFF) 55 | // This 1bit Register selects whether the two bits of the above register should 56 | // be used as upper two bits of the ROM Bank, or as RAM Bank Number. 57 | bool rom_banking_mode = true; 58 | }; 59 | 60 | class MBC3 : public Cartridge { 61 | public: 62 | MBC3(std::vector rom_data, const std::vector& ram_data, 63 | std::unique_ptr cartridge_info); 64 | 65 | auto read(const Address& address) const -> u8 override; 66 | void write(const Address& address, u8 value) override; 67 | 68 | private: 69 | WordRegister rom_bank; 70 | WordRegister ram_bank; 71 | bool ram_enabled = false; 72 | bool ram_over_rtc = true; 73 | 74 | // TODO: ROM/RAM Mode Select (6000-7FFF) 75 | // This 1bit Register selects whether the two bits of the above register should 76 | // be used as upper two bits of the ROM Bank, or as RAM Bank Number. 77 | bool rom_banking_mode = true; 78 | }; 79 | -------------------------------------------------------------------------------- /src/cartridge/cartridge_info.cc: -------------------------------------------------------------------------------- 1 | #include "cartridge_info.h" 2 | 3 | #include "../util/log.h" 4 | 5 | auto get_info(std::vector rom) -> std::unique_ptr { 6 | std::unique_ptr info = std::make_unique(); 7 | 8 | u8 type_code = rom[header::cartridge_type]; 9 | u8 version_code = rom[header::version_number]; 10 | u8 rom_size_code = rom[header::rom_size]; 11 | u8 ram_size_code = rom[header::ram_size]; 12 | 13 | info->type = get_type(type_code); 14 | info->version = version_code; 15 | info->rom_size = get_rom_size(rom_size_code); 16 | info->ram_size = get_ram_size(ram_size_code); 17 | info->title = get_title(rom); 18 | 19 | log_info("Title:\t\t %s (version %d)", info->title.c_str(), info->version); 20 | log_info("Cartridge:\t\t %s", describe(info->type).c_str()); 21 | log_info("Rom Size:\t\t %s", describe(info->rom_size).c_str()); 22 | log_info("Ram Size:\t\t %s", describe(info->ram_size).c_str()); 23 | log_info(""); 24 | 25 | return info; 26 | } 27 | 28 | auto get_type(u8 type) -> CartridgeType { 29 | switch (type) { 30 | case 0x00: 31 | case 0x08: 32 | case 0x09: 33 | return CartridgeType::ROMOnly; 34 | 35 | case 0x01: 36 | case 0x02: 37 | case 0x03: 38 | case 0xFF: 39 | return CartridgeType::MBC1; 40 | 41 | case 0x05: 42 | case 0x06: 43 | return CartridgeType::MBC2; 44 | 45 | case 0x0F: 46 | case 0x10: 47 | case 0x11: 48 | case 0x12: 49 | case 0x13: 50 | return CartridgeType::MBC3; 51 | 52 | case 0x15: 53 | case 0x16: 54 | case 0x17: 55 | return CartridgeType::MBC4; 56 | 57 | case 0x19: 58 | case 0x1A: 59 | case 0x1B: 60 | case 0x1C: 61 | case 0x1D: 62 | case 0x1E: 63 | return CartridgeType::MBC5; 64 | 65 | case 0x0B: 66 | case 0x0C: 67 | case 0x0D: 68 | case 0x20: 69 | case 0x22: 70 | case 0xFC: 71 | case 0xFD: 72 | case 0xFE: 73 | return CartridgeType::Unknown; 74 | 75 | default: 76 | log_error("Unknown cartridge type: %X", type); 77 | return CartridgeType::Unknown; 78 | } 79 | } 80 | 81 | auto describe(CartridgeType type) -> std::string { 82 | switch (type) { 83 | case CartridgeType::ROMOnly: 84 | return "ROM Only"; 85 | case CartridgeType::MBC1: 86 | return "MBC1"; 87 | case CartridgeType::MBC2: 88 | return "MBC2"; 89 | case CartridgeType::MBC3: 90 | return "MBC3"; 91 | case CartridgeType::MBC4: 92 | return "MBC4"; 93 | case CartridgeType::MBC5: 94 | return "MBC5"; 95 | case CartridgeType::Unknown: 96 | return "Unknown"; 97 | } 98 | } 99 | 100 | 101 | auto get_license(u16 old_license, u16 new_license) -> std::string { 102 | /* TODO */ 103 | unused(old_license, new_license); 104 | log_error("License not implemented"); 105 | return ""; 106 | } 107 | 108 | 109 | auto get_rom_size(u8 size_code) -> ROMSize { 110 | switch (size_code) { 111 | case 0x00: 112 | return ROMSize::KB32; 113 | case 0x01: 114 | return ROMSize::KB64; 115 | case 0x02: 116 | return ROMSize::KB128; 117 | case 0x03: 118 | return ROMSize::KB256; 119 | case 0x04: 120 | return ROMSize::KB512; 121 | case 0x05: 122 | return ROMSize::MB1; 123 | case 0x06: 124 | return ROMSize::MB2; 125 | case 0x07: 126 | return ROMSize::MB4; 127 | case 0x52: 128 | return ROMSize::MB1p1; 129 | case 0x53: 130 | return ROMSize::MB1p2; 131 | case 0x54: 132 | return ROMSize::MB1p5; 133 | default: 134 | log_error("Unknown ROM size: %X", size_code); 135 | return ROMSize::KB32; 136 | } 137 | } 138 | 139 | auto describe(ROMSize size) -> std::string { 140 | switch (size) { 141 | case ROMSize::KB32: 142 | return "32KB (no ROM banking)"; 143 | case ROMSize::KB64: 144 | return "64KB (4 banks)"; 145 | case ROMSize::KB128: 146 | return "128KB (8 banks)"; 147 | case ROMSize::KB256: 148 | return "256KB (16 banks)"; 149 | case ROMSize::KB512: 150 | return "512KB (32 banks)"; 151 | case ROMSize::MB1: 152 | return "1MB (64 banks)"; 153 | case ROMSize::MB2: 154 | return "2MB (128 banks)"; 155 | case ROMSize::MB4: 156 | return "4MB (256 banks)"; 157 | case ROMSize::MB1p1: 158 | return "1.1MB (72 banks)"; 159 | case ROMSize::MB1p2: 160 | return "1.2MB (80 banks)"; 161 | case ROMSize::MB1p5: 162 | return "1.5MB (96 banks)"; 163 | } 164 | } 165 | 166 | 167 | auto get_ram_size(u8 size_code) -> RAMSize { 168 | switch (size_code) { 169 | case 0x00: 170 | return RAMSize::None; 171 | case 0x01: 172 | return RAMSize::KB2; 173 | case 0x02: 174 | return RAMSize::KB8; 175 | case 0x03: 176 | return RAMSize::KB32; 177 | case 0x04: 178 | return RAMSize::KB128; 179 | case 0x05: 180 | return RAMSize::KB64; 181 | default: 182 | log_error("Unknown RAM size: %X", size_code); 183 | return RAMSize::None; 184 | } 185 | } 186 | 187 | auto get_actual_ram_size(RAMSize size) -> uint { 188 | switch (size) { 189 | case RAMSize::None: 190 | return 0x0; 191 | case RAMSize::KB2: 192 | return 0x800; 193 | case RAMSize::KB8: 194 | return 0x2000; 195 | case RAMSize::KB32: 196 | return 0x8000; 197 | case RAMSize::KB128: 198 | return 0x20000; 199 | case RAMSize::KB64: 200 | return 0x10000; 201 | } 202 | } 203 | 204 | auto describe(RAMSize size) -> std::string { 205 | switch (size) { 206 | case RAMSize::None: 207 | return "No RAM"; 208 | case RAMSize::KB2: 209 | return "2KB"; 210 | case RAMSize::KB8: 211 | return "8KB"; 212 | case RAMSize::KB32: 213 | return "32KB"; 214 | case RAMSize::KB128: 215 | return "128KB"; 216 | case RAMSize::KB64: 217 | return "64KB"; 218 | } 219 | } 220 | 221 | 222 | auto get_destination(u8 destination) -> Destination { 223 | switch (destination) { 224 | case 0x00: 225 | return Destination::Japanese; 226 | case 0x01: 227 | return Destination::NonJapanese; 228 | default: 229 | log_error("Unknown destination: %X", destination); 230 | return Destination::NonJapanese; 231 | } 232 | } 233 | 234 | auto describe(Destination destination) -> std::string { 235 | switch (destination) { 236 | case Destination::Japanese: 237 | return "Japanese"; 238 | case Destination::NonJapanese: 239 | return "Non-Japanese"; 240 | } 241 | } 242 | 243 | auto get_title(std::vector& rom) -> std::string { 244 | char name[TITLE_LENGTH] = {0}; 245 | 246 | for (u8 i = 0; i < TITLE_LENGTH; i++) { 247 | name[i] = static_cast(rom[header::title + i]); 248 | } 249 | 250 | return std::string(name); 251 | } 252 | -------------------------------------------------------------------------------- /src/cartridge/cartridge_info.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../definitions.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | const int TITLE_LENGTH = 11; 10 | 11 | namespace header { 12 | const int entry_point = 0x100; 13 | const int logo = 0x104; 14 | const int title = 0x134; 15 | const int manufacturer_code = 0x13F; 16 | const int cgb_flag = 0x143; 17 | const int new_license_code = 0x144; 18 | const int sgb_flag = 0x146; 19 | const int cartridge_type = 0x147; 20 | const int rom_size = 0x148; 21 | const int ram_size = 0x149; 22 | const int destination_code = 0x14A; 23 | const int old_license_code = 0x14B; 24 | const int version_number = 0x14C; 25 | const int header_checksum = 0x14D; 26 | const int global_checksum = 0x14E; 27 | } // namespace header 28 | 29 | enum class CartridgeType { 30 | ROMOnly, 31 | MBC1, 32 | MBC2, 33 | MBC3, 34 | MBC4, 35 | MBC5, 36 | Unknown, 37 | }; 38 | 39 | extern auto get_type(u8 type) -> CartridgeType; 40 | extern auto describe(CartridgeType type) -> std::string; 41 | 42 | extern auto get_title(std::vector& rom) -> std::string; 43 | 44 | extern auto get_license(u16 old_license, u16 new_license) -> std::string; 45 | 46 | enum class ROMSize { 47 | KB32, 48 | KB64, 49 | KB128, 50 | KB256, 51 | KB512, 52 | MB1, 53 | MB2, 54 | MB4, 55 | MB1p1, 56 | MB1p2, 57 | MB1p5, 58 | }; 59 | 60 | extern auto get_rom_size(u8 size_code) -> ROMSize; 61 | extern auto describe(ROMSize size) -> std::string; 62 | 63 | enum class RAMSize { 64 | None, 65 | KB2, 66 | KB8, 67 | KB32, 68 | KB128, 69 | KB64, 70 | }; 71 | 72 | extern auto get_ram_size(u8 size_code) -> RAMSize; 73 | extern auto get_actual_ram_size(RAMSize size_code) -> uint; 74 | extern auto describe(RAMSize size) -> std::string; 75 | 76 | enum class Destination { 77 | Japanese, 78 | NonJapanese, 79 | }; 80 | 81 | extern auto get_destination(u8 destination) -> Destination; 82 | extern auto describe(Destination destination) -> std::string; 83 | 84 | class CartridgeInfo { 85 | public: 86 | std::string title; 87 | 88 | /* Cartridge information */ 89 | CartridgeType type; 90 | Destination destination; 91 | ROMSize rom_size; 92 | RAMSize ram_size; 93 | std::string license_code; 94 | u8 version; 95 | 96 | u16 header_checksum; 97 | u16 global_checksum; 98 | 99 | bool supports_cgb; 100 | bool supports_sgb; 101 | }; 102 | 103 | extern auto get_info(std::vector rom) -> std::unique_ptr; 104 | -------------------------------------------------------------------------------- /src/cpu/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sources( 2 | cpu.cc 3 | opcode_mapping.cc 4 | opcodes.cc 5 | ) 6 | -------------------------------------------------------------------------------- /src/cpu/cpu.cc: -------------------------------------------------------------------------------- 1 | #include "cpu.h" 2 | 3 | #include "../gameboy.h" 4 | #include "opcode_cycles.h" 5 | #include "opcode_names.h" 6 | #include "../util/bitwise.h" 7 | #include "../util/log.h" 8 | 9 | using bitwise::compose_bytes; 10 | 11 | CPU::CPU(Gameboy& inGb, Options& inOptions) : 12 | gb(inGb), 13 | options(inOptions), 14 | af(a, f), 15 | bc(b, c), 16 | de(d, e), 17 | hl(h, l) 18 | { 19 | } 20 | 21 | auto CPU::tick() -> Cycles { 22 | handle_interrupts(); 23 | 24 | if (halted) { return 1; } 25 | 26 | u16 opcode_pc = pc.value(); 27 | auto opcode = get_byte_from_pc(); 28 | auto cycles = execute_opcode(opcode, opcode_pc); 29 | return cycles; 30 | } 31 | 32 | auto CPU::execute_opcode(const u8 opcode, u16 opcode_pc) -> Cycles { 33 | branch_taken = false; 34 | 35 | if (opcode == 0xCB) { 36 | u8 cb_opcode = get_byte_from_pc(); 37 | return execute_cb_opcode(cb_opcode, opcode_pc); 38 | } 39 | 40 | return execute_normal_opcode(opcode, opcode_pc); 41 | } 42 | 43 | void CPU::handle_interrupts() { 44 | u8 fired_interrupts = interrupt_flag.value() & interrupt_enabled.value(); 45 | if (!fired_interrupts) { return; } 46 | 47 | if (halted && fired_interrupts != 0x0) { 48 | // TODO: Handle halt bug 49 | halted = false; 50 | } 51 | 52 | if (!interrupts_enabled) { 53 | return; 54 | } 55 | 56 | stack_push(pc); 57 | 58 | bool handled_interrupt = false; 59 | 60 | handled_interrupt = handle_interrupt(0, interrupts::vblank, fired_interrupts); 61 | if (handled_interrupt) { return; } 62 | 63 | handled_interrupt = handle_interrupt(1, interrupts::lcdc_status, fired_interrupts); 64 | if (handled_interrupt) { return; } 65 | 66 | handled_interrupt = handle_interrupt(2, interrupts::timer, fired_interrupts); 67 | if (handled_interrupt) { return; } 68 | 69 | handled_interrupt = handle_interrupt(3, interrupts::serial, fired_interrupts); 70 | if (handled_interrupt) { return; } 71 | 72 | handled_interrupt = handle_interrupt(4, interrupts::joypad, fired_interrupts); 73 | if (handled_interrupt) { return; } 74 | } 75 | 76 | auto CPU::handle_interrupt(u8 interrupt_bit, u16 interrupt_vector, u8 fired_interrupts) -> bool { 77 | using bitwise::check_bit; 78 | 79 | if (!check_bit(fired_interrupts, interrupt_bit)) { return false; } 80 | 81 | interrupt_flag.set_bit_to(interrupt_bit, false); 82 | pc.set(interrupt_vector); 83 | interrupts_enabled = false; 84 | return true; 85 | } 86 | 87 | auto CPU::get_byte_from_pc() -> u8 { 88 | u8 byte = gb.mmu.read(Address(pc)); 89 | pc.increment(); 90 | 91 | return byte; 92 | } 93 | 94 | auto CPU::get_signed_byte_from_pc() -> s8 { 95 | u8 byte = get_byte_from_pc(); 96 | return static_cast(byte); 97 | } 98 | 99 | auto CPU::get_word_from_pc() -> u16 { 100 | u8 low_byte = get_byte_from_pc(); 101 | u8 high_byte = get_byte_from_pc(); 102 | 103 | return compose_bytes(high_byte, low_byte); 104 | } 105 | 106 | void CPU::set_flag_zero(bool set) { f.set_flag_zero(set); } 107 | void CPU::set_flag_subtract(bool set) { f.set_flag_subtract(set); } 108 | void CPU::set_flag_half_carry(bool set) { f.set_flag_half_carry(set); } 109 | void CPU::set_flag_carry(bool set) { f.set_flag_carry(set); } 110 | 111 | auto CPU::is_condition(Condition condition) -> bool { 112 | bool should_branch; 113 | 114 | switch (condition) { 115 | case Condition::C: 116 | should_branch = f.flag_carry(); 117 | break; 118 | case Condition::NC: 119 | should_branch = !f.flag_carry(); 120 | break; 121 | case Condition::Z: 122 | should_branch = f.flag_zero(); 123 | break; 124 | case Condition::NZ: 125 | should_branch = !f.flag_zero(); 126 | break; 127 | } 128 | 129 | /* If the branch is taken, remember so that the correct processor cycles 130 | * can be used */ 131 | branch_taken = should_branch; 132 | return should_branch; 133 | } 134 | 135 | void CPU::stack_push(const WordValue& reg) { 136 | sp.decrement(); 137 | gb.mmu.write(Address(sp), reg.high()); 138 | sp.decrement(); 139 | gb.mmu.write(Address(sp), reg.low()); 140 | } 141 | 142 | void CPU::stack_pop(WordValue& reg) { 143 | u8 low_byte = gb.mmu.read(Address(sp)); 144 | sp.increment(); 145 | u8 high_byte = gb.mmu.read(Address(sp)); 146 | sp.increment(); 147 | 148 | u16 value = compose_bytes(high_byte, low_byte); 149 | reg.set(value); 150 | } 151 | 152 | /* clang-format off */ 153 | auto CPU::execute_normal_opcode(const u8 opcode, u16 opcode_pc) -> Cycles { 154 | log_trace("0x%04X: %s (0x%x)", opcode_pc, opcode_names[opcode].c_str(), opcode); 155 | 156 | switch (opcode) { 157 | case 0x00: opcode_00(); break; case 0x01: opcode_01(); break; case 0x02: opcode_02(); break; case 0x03: opcode_03(); break; case 0x04: opcode_04(); break; case 0x05: opcode_05(); break; case 0x06: opcode_06(); break; case 0x07: opcode_07(); break; case 0x08: opcode_08(); break; case 0x09: opcode_09(); break; case 0x0A: opcode_0A(); break; case 0x0B: opcode_0B(); break; case 0x0C: opcode_0C(); break; case 0x0D: opcode_0D(); break; case 0x0E: opcode_0E(); break; case 0x0F: opcode_0F(); break; 158 | case 0x10: opcode_10(); break; case 0x11: opcode_11(); break; case 0x12: opcode_12(); break; case 0x13: opcode_13(); break; case 0x14: opcode_14(); break; case 0x15: opcode_15(); break; case 0x16: opcode_16(); break; case 0x17: opcode_17(); break; case 0x18: opcode_18(); break; case 0x19: opcode_19(); break; case 0x1A: opcode_1A(); break; case 0x1B: opcode_1B(); break; case 0x1C: opcode_1C(); break; case 0x1D: opcode_1D(); break; case 0x1E: opcode_1E(); break; case 0x1F: opcode_1F(); break; 159 | case 0x20: opcode_20(); break; case 0x21: opcode_21(); break; case 0x22: opcode_22(); break; case 0x23: opcode_23(); break; case 0x24: opcode_24(); break; case 0x25: opcode_25(); break; case 0x26: opcode_26(); break; case 0x27: opcode_27(); break; case 0x28: opcode_28(); break; case 0x29: opcode_29(); break; case 0x2A: opcode_2A(); break; case 0x2B: opcode_2B(); break; case 0x2C: opcode_2C(); break; case 0x2D: opcode_2D(); break; case 0x2E: opcode_2E(); break; case 0x2F: opcode_2F(); break; 160 | case 0x30: opcode_30(); break; case 0x31: opcode_31(); break; case 0x32: opcode_32(); break; case 0x33: opcode_33(); break; case 0x34: opcode_34(); break; case 0x35: opcode_35(); break; case 0x36: opcode_36(); break; case 0x37: opcode_37(); break; case 0x38: opcode_38(); break; case 0x39: opcode_39(); break; case 0x3A: opcode_3A(); break; case 0x3B: opcode_3B(); break; case 0x3C: opcode_3C(); break; case 0x3D: opcode_3D(); break; case 0x3E: opcode_3E(); break; case 0x3F: opcode_3F(); break; 161 | case 0x40: opcode_40(); break; case 0x41: opcode_41(); break; case 0x42: opcode_42(); break; case 0x43: opcode_43(); break; case 0x44: opcode_44(); break; case 0x45: opcode_45(); break; case 0x46: opcode_46(); break; case 0x47: opcode_47(); break; case 0x48: opcode_48(); break; case 0x49: opcode_49(); break; case 0x4A: opcode_4A(); break; case 0x4B: opcode_4B(); break; case 0x4C: opcode_4C(); break; case 0x4D: opcode_4D(); break; case 0x4E: opcode_4E(); break; case 0x4F: opcode_4F(); break; 162 | case 0x50: opcode_50(); break; case 0x51: opcode_51(); break; case 0x52: opcode_52(); break; case 0x53: opcode_53(); break; case 0x54: opcode_54(); break; case 0x55: opcode_55(); break; case 0x56: opcode_56(); break; case 0x57: opcode_57(); break; case 0x58: opcode_58(); break; case 0x59: opcode_59(); break; case 0x5A: opcode_5A(); break; case 0x5B: opcode_5B(); break; case 0x5C: opcode_5C(); break; case 0x5D: opcode_5D(); break; case 0x5E: opcode_5E(); break; case 0x5F: opcode_5F(); break; 163 | case 0x60: opcode_60(); break; case 0x61: opcode_61(); break; case 0x62: opcode_62(); break; case 0x63: opcode_63(); break; case 0x64: opcode_64(); break; case 0x65: opcode_65(); break; case 0x66: opcode_66(); break; case 0x67: opcode_67(); break; case 0x68: opcode_68(); break; case 0x69: opcode_69(); break; case 0x6A: opcode_6A(); break; case 0x6B: opcode_6B(); break; case 0x6C: opcode_6C(); break; case 0x6D: opcode_6D(); break; case 0x6E: opcode_6E(); break; case 0x6F: opcode_6F(); break; 164 | case 0x70: opcode_70(); break; case 0x71: opcode_71(); break; case 0x72: opcode_72(); break; case 0x73: opcode_73(); break; case 0x74: opcode_74(); break; case 0x75: opcode_75(); break; case 0x76: opcode_76(); break; case 0x77: opcode_77(); break; case 0x78: opcode_78(); break; case 0x79: opcode_79(); break; case 0x7A: opcode_7A(); break; case 0x7B: opcode_7B(); break; case 0x7C: opcode_7C(); break; case 0x7D: opcode_7D(); break; case 0x7E: opcode_7E(); break; case 0x7F: opcode_7F(); break; 165 | case 0x80: opcode_80(); break; case 0x81: opcode_81(); break; case 0x82: opcode_82(); break; case 0x83: opcode_83(); break; case 0x84: opcode_84(); break; case 0x85: opcode_85(); break; case 0x86: opcode_86(); break; case 0x87: opcode_87(); break; case 0x88: opcode_88(); break; case 0x89: opcode_89(); break; case 0x8A: opcode_8A(); break; case 0x8B: opcode_8B(); break; case 0x8C: opcode_8C(); break; case 0x8D: opcode_8D(); break; case 0x8E: opcode_8E(); break; case 0x8F: opcode_8F(); break; 166 | case 0x90: opcode_90(); break; case 0x91: opcode_91(); break; case 0x92: opcode_92(); break; case 0x93: opcode_93(); break; case 0x94: opcode_94(); break; case 0x95: opcode_95(); break; case 0x96: opcode_96(); break; case 0x97: opcode_97(); break; case 0x98: opcode_98(); break; case 0x99: opcode_99(); break; case 0x9A: opcode_9A(); break; case 0x9B: opcode_9B(); break; case 0x9C: opcode_9C(); break; case 0x9D: opcode_9D(); break; case 0x9E: opcode_9E(); break; case 0x9F: opcode_9F(); break; 167 | case 0xA0: opcode_A0(); break; case 0xA1: opcode_A1(); break; case 0xA2: opcode_A2(); break; case 0xA3: opcode_A3(); break; case 0xA4: opcode_A4(); break; case 0xA5: opcode_A5(); break; case 0xA6: opcode_A6(); break; case 0xA7: opcode_A7(); break; case 0xA8: opcode_A8(); break; case 0xA9: opcode_A9(); break; case 0xAA: opcode_AA(); break; case 0xAB: opcode_AB(); break; case 0xAC: opcode_AC(); break; case 0xAD: opcode_AD(); break; case 0xAE: opcode_AE(); break; case 0xAF: opcode_AF(); break; 168 | case 0xB0: opcode_B0(); break; case 0xB1: opcode_B1(); break; case 0xB2: opcode_B2(); break; case 0xB3: opcode_B3(); break; case 0xB4: opcode_B4(); break; case 0xB5: opcode_B5(); break; case 0xB6: opcode_B6(); break; case 0xB7: opcode_B7(); break; case 0xB8: opcode_B8(); break; case 0xB9: opcode_B9(); break; case 0xBA: opcode_BA(); break; case 0xBB: opcode_BB(); break; case 0xBC: opcode_BC(); break; case 0xBD: opcode_BD(); break; case 0xBE: opcode_BE(); break; case 0xBF: opcode_BF(); break; 169 | case 0xC0: opcode_C0(); break; case 0xC1: opcode_C1(); break; case 0xC2: opcode_C2(); break; case 0xC3: opcode_C3(); break; case 0xC4: opcode_C4(); break; case 0xC5: opcode_C5(); break; case 0xC6: opcode_C6(); break; case 0xC7: opcode_C7(); break; case 0xC8: opcode_C8(); break; case 0xC9: opcode_C9(); break; case 0xCA: opcode_CA(); break; case 0xCB: opcode_CB(); break; case 0xCC: opcode_CC(); break; case 0xCD: opcode_CD(); break; case 0xCE: opcode_CE(); break; case 0xCF: opcode_CF(); break; 170 | case 0xD0: opcode_D0(); break; case 0xD1: opcode_D1(); break; case 0xD2: opcode_D2(); break; case 0xD3: opcode_D3(); break; case 0xD4: opcode_D4(); break; case 0xD5: opcode_D5(); break; case 0xD6: opcode_D6(); break; case 0xD7: opcode_D7(); break; case 0xD8: opcode_D8(); break; case 0xD9: opcode_D9(); break; case 0xDA: opcode_DA(); break; case 0xDB: opcode_DB(); break; case 0xDC: opcode_DC(); break; case 0xDD: opcode_DD(); break; case 0xDE: opcode_DE(); break; case 0xDF: opcode_DF(); break; 171 | case 0xE0: opcode_E0(); break; case 0xE1: opcode_E1(); break; case 0xE2: opcode_E2(); break; case 0xE3: opcode_E3(); break; case 0xE4: opcode_E4(); break; case 0xE5: opcode_E5(); break; case 0xE6: opcode_E6(); break; case 0xE7: opcode_E7(); break; case 0xE8: opcode_E8(); break; case 0xE9: opcode_E9(); break; case 0xEA: opcode_EA(); break; case 0xEB: opcode_EB(); break; case 0xEC: opcode_EC(); break; case 0xED: opcode_ED(); break; case 0xEE: opcode_EE(); break; case 0xEF: opcode_EF(); break; 172 | case 0xF0: opcode_F0(); break; case 0xF1: opcode_F1(); break; case 0xF2: opcode_F2(); break; case 0xF3: opcode_F3(); break; case 0xF4: opcode_F4(); break; case 0xF5: opcode_F5(); break; case 0xF6: opcode_F6(); break; case 0xF7: opcode_F7(); break; case 0xF8: opcode_F8(); break; case 0xF9: opcode_F9(); break; case 0xFA: opcode_FA(); break; case 0xFB: opcode_FB(); break; case 0xFC: opcode_FC(); break; case 0xFD: opcode_FD(); break; case 0xFE: opcode_FE(); break; case 0xFF: opcode_FF(); break; 173 | } 174 | 175 | return !branch_taken 176 | ? opcode_cycles[opcode] 177 | : opcode_cycles_branched[opcode]; 178 | } 179 | 180 | auto CPU::execute_cb_opcode(const u8 opcode, u16 opcode_pc) -> Cycles { 181 | log_trace("0x%04X: %s (CB 0x%x)", opcode_pc, opcode_cb_names[opcode].c_str(), opcode); 182 | 183 | switch (opcode) { 184 | case 0x00: opcode_CB_00(); break; case 0x01: opcode_CB_01(); break; case 0x02: opcode_CB_02(); break; case 0x03: opcode_CB_03(); break; case 0x04: opcode_CB_04(); break; case 0x05: opcode_CB_05(); break; case 0x06: opcode_CB_06(); break; case 0x07: opcode_CB_07(); break; case 0x08: opcode_CB_08(); break; case 0x09: opcode_CB_09(); break; case 0x0A: opcode_CB_0A(); break; case 0x0B: opcode_CB_0B(); break; case 0x0C: opcode_CB_0C(); break; case 0x0D: opcode_CB_0D(); break; case 0x0E: opcode_CB_0E(); break; case 0x0F: opcode_CB_0F(); break; 185 | case 0x10: opcode_CB_10(); break; case 0x11: opcode_CB_11(); break; case 0x12: opcode_CB_12(); break; case 0x13: opcode_CB_13(); break; case 0x14: opcode_CB_14(); break; case 0x15: opcode_CB_15(); break; case 0x16: opcode_CB_16(); break; case 0x17: opcode_CB_17(); break; case 0x18: opcode_CB_18(); break; case 0x19: opcode_CB_19(); break; case 0x1A: opcode_CB_1A(); break; case 0x1B: opcode_CB_1B(); break; case 0x1C: opcode_CB_1C(); break; case 0x1D: opcode_CB_1D(); break; case 0x1E: opcode_CB_1E(); break; case 0x1F: opcode_CB_1F(); break; 186 | case 0x20: opcode_CB_20(); break; case 0x21: opcode_CB_21(); break; case 0x22: opcode_CB_22(); break; case 0x23: opcode_CB_23(); break; case 0x24: opcode_CB_24(); break; case 0x25: opcode_CB_25(); break; case 0x26: opcode_CB_26(); break; case 0x27: opcode_CB_27(); break; case 0x28: opcode_CB_28(); break; case 0x29: opcode_CB_29(); break; case 0x2A: opcode_CB_2A(); break; case 0x2B: opcode_CB_2B(); break; case 0x2C: opcode_CB_2C(); break; case 0x2D: opcode_CB_2D(); break; case 0x2E: opcode_CB_2E(); break; case 0x2F: opcode_CB_2F(); break; 187 | case 0x30: opcode_CB_30(); break; case 0x31: opcode_CB_31(); break; case 0x32: opcode_CB_32(); break; case 0x33: opcode_CB_33(); break; case 0x34: opcode_CB_34(); break; case 0x35: opcode_CB_35(); break; case 0x36: opcode_CB_36(); break; case 0x37: opcode_CB_37(); break; case 0x38: opcode_CB_38(); break; case 0x39: opcode_CB_39(); break; case 0x3A: opcode_CB_3A(); break; case 0x3B: opcode_CB_3B(); break; case 0x3C: opcode_CB_3C(); break; case 0x3D: opcode_CB_3D(); break; case 0x3E: opcode_CB_3E(); break; case 0x3F: opcode_CB_3F(); break; 188 | case 0x40: opcode_CB_40(); break; case 0x41: opcode_CB_41(); break; case 0x42: opcode_CB_42(); break; case 0x43: opcode_CB_43(); break; case 0x44: opcode_CB_44(); break; case 0x45: opcode_CB_45(); break; case 0x46: opcode_CB_46(); break; case 0x47: opcode_CB_47(); break; case 0x48: opcode_CB_48(); break; case 0x49: opcode_CB_49(); break; case 0x4A: opcode_CB_4A(); break; case 0x4B: opcode_CB_4B(); break; case 0x4C: opcode_CB_4C(); break; case 0x4D: opcode_CB_4D(); break; case 0x4E: opcode_CB_4E(); break; case 0x4F: opcode_CB_4F(); break; 189 | case 0x50: opcode_CB_50(); break; case 0x51: opcode_CB_51(); break; case 0x52: opcode_CB_52(); break; case 0x53: opcode_CB_53(); break; case 0x54: opcode_CB_54(); break; case 0x55: opcode_CB_55(); break; case 0x56: opcode_CB_56(); break; case 0x57: opcode_CB_57(); break; case 0x58: opcode_CB_58(); break; case 0x59: opcode_CB_59(); break; case 0x5A: opcode_CB_5A(); break; case 0x5B: opcode_CB_5B(); break; case 0x5C: opcode_CB_5C(); break; case 0x5D: opcode_CB_5D(); break; case 0x5E: opcode_CB_5E(); break; case 0x5F: opcode_CB_5F(); break; 190 | case 0x60: opcode_CB_60(); break; case 0x61: opcode_CB_61(); break; case 0x62: opcode_CB_62(); break; case 0x63: opcode_CB_63(); break; case 0x64: opcode_CB_64(); break; case 0x65: opcode_CB_65(); break; case 0x66: opcode_CB_66(); break; case 0x67: opcode_CB_67(); break; case 0x68: opcode_CB_68(); break; case 0x69: opcode_CB_69(); break; case 0x6A: opcode_CB_6A(); break; case 0x6B: opcode_CB_6B(); break; case 0x6C: opcode_CB_6C(); break; case 0x6D: opcode_CB_6D(); break; case 0x6E: opcode_CB_6E(); break; case 0x6F: opcode_CB_6F(); break; 191 | case 0x70: opcode_CB_70(); break; case 0x71: opcode_CB_71(); break; case 0x72: opcode_CB_72(); break; case 0x73: opcode_CB_73(); break; case 0x74: opcode_CB_74(); break; case 0x75: opcode_CB_75(); break; case 0x76: opcode_CB_76(); break; case 0x77: opcode_CB_77(); break; case 0x78: opcode_CB_78(); break; case 0x79: opcode_CB_79(); break; case 0x7A: opcode_CB_7A(); break; case 0x7B: opcode_CB_7B(); break; case 0x7C: opcode_CB_7C(); break; case 0x7D: opcode_CB_7D(); break; case 0x7E: opcode_CB_7E(); break; case 0x7F: opcode_CB_7F(); break; 192 | case 0x80: opcode_CB_80(); break; case 0x81: opcode_CB_81(); break; case 0x82: opcode_CB_82(); break; case 0x83: opcode_CB_83(); break; case 0x84: opcode_CB_84(); break; case 0x85: opcode_CB_85(); break; case 0x86: opcode_CB_86(); break; case 0x87: opcode_CB_87(); break; case 0x88: opcode_CB_88(); break; case 0x89: opcode_CB_89(); break; case 0x8A: opcode_CB_8A(); break; case 0x8B: opcode_CB_8B(); break; case 0x8C: opcode_CB_8C(); break; case 0x8D: opcode_CB_8D(); break; case 0x8E: opcode_CB_8E(); break; case 0x8F: opcode_CB_8F(); break; 193 | case 0x90: opcode_CB_90(); break; case 0x91: opcode_CB_91(); break; case 0x92: opcode_CB_92(); break; case 0x93: opcode_CB_93(); break; case 0x94: opcode_CB_94(); break; case 0x95: opcode_CB_95(); break; case 0x96: opcode_CB_96(); break; case 0x97: opcode_CB_97(); break; case 0x98: opcode_CB_98(); break; case 0x99: opcode_CB_99(); break; case 0x9A: opcode_CB_9A(); break; case 0x9B: opcode_CB_9B(); break; case 0x9C: opcode_CB_9C(); break; case 0x9D: opcode_CB_9D(); break; case 0x9E: opcode_CB_9E(); break; case 0x9F: opcode_CB_9F(); break; 194 | case 0xA0: opcode_CB_A0(); break; case 0xA1: opcode_CB_A1(); break; case 0xA2: opcode_CB_A2(); break; case 0xA3: opcode_CB_A3(); break; case 0xA4: opcode_CB_A4(); break; case 0xA5: opcode_CB_A5(); break; case 0xA6: opcode_CB_A6(); break; case 0xA7: opcode_CB_A7(); break; case 0xA8: opcode_CB_A8(); break; case 0xA9: opcode_CB_A9(); break; case 0xAA: opcode_CB_AA(); break; case 0xAB: opcode_CB_AB(); break; case 0xAC: opcode_CB_AC(); break; case 0xAD: opcode_CB_AD(); break; case 0xAE: opcode_CB_AE(); break; case 0xAF: opcode_CB_AF(); break; 195 | case 0xB0: opcode_CB_B0(); break; case 0xB1: opcode_CB_B1(); break; case 0xB2: opcode_CB_B2(); break; case 0xB3: opcode_CB_B3(); break; case 0xB4: opcode_CB_B4(); break; case 0xB5: opcode_CB_B5(); break; case 0xB6: opcode_CB_B6(); break; case 0xB7: opcode_CB_B7(); break; case 0xB8: opcode_CB_B8(); break; case 0xB9: opcode_CB_B9(); break; case 0xBA: opcode_CB_BA(); break; case 0xBB: opcode_CB_BB(); break; case 0xBC: opcode_CB_BC(); break; case 0xBD: opcode_CB_BD(); break; case 0xBE: opcode_CB_BE(); break; case 0xBF: opcode_CB_BF(); break; 196 | case 0xC0: opcode_CB_C0(); break; case 0xC1: opcode_CB_C1(); break; case 0xC2: opcode_CB_C2(); break; case 0xC3: opcode_CB_C3(); break; case 0xC4: opcode_CB_C4(); break; case 0xC5: opcode_CB_C5(); break; case 0xC6: opcode_CB_C6(); break; case 0xC7: opcode_CB_C7(); break; case 0xC8: opcode_CB_C8(); break; case 0xC9: opcode_CB_C9(); break; case 0xCA: opcode_CB_CA(); break; case 0xCB: opcode_CB_CB(); break; case 0xCC: opcode_CB_CC(); break; case 0xCD: opcode_CB_CD(); break; case 0xCE: opcode_CB_CE(); break; case 0xCF: opcode_CB_CF(); break; 197 | case 0xD0: opcode_CB_D0(); break; case 0xD1: opcode_CB_D1(); break; case 0xD2: opcode_CB_D2(); break; case 0xD3: opcode_CB_D3(); break; case 0xD4: opcode_CB_D4(); break; case 0xD5: opcode_CB_D5(); break; case 0xD6: opcode_CB_D6(); break; case 0xD7: opcode_CB_D7(); break; case 0xD8: opcode_CB_D8(); break; case 0xD9: opcode_CB_D9(); break; case 0xDA: opcode_CB_DA(); break; case 0xDB: opcode_CB_DB(); break; case 0xDC: opcode_CB_DC(); break; case 0xDD: opcode_CB_DD(); break; case 0xDE: opcode_CB_DE(); break; case 0xDF: opcode_CB_DF(); break; 198 | case 0xE0: opcode_CB_E0(); break; case 0xE1: opcode_CB_E1(); break; case 0xE2: opcode_CB_E2(); break; case 0xE3: opcode_CB_E3(); break; case 0xE4: opcode_CB_E4(); break; case 0xE5: opcode_CB_E5(); break; case 0xE6: opcode_CB_E6(); break; case 0xE7: opcode_CB_E7(); break; case 0xE8: opcode_CB_E8(); break; case 0xE9: opcode_CB_E9(); break; case 0xEA: opcode_CB_EA(); break; case 0xEB: opcode_CB_EB(); break; case 0xEC: opcode_CB_EC(); break; case 0xED: opcode_CB_ED(); break; case 0xEE: opcode_CB_EE(); break; case 0xEF: opcode_CB_EF(); break; 199 | case 0xF0: opcode_CB_F0(); break; case 0xF1: opcode_CB_F1(); break; case 0xF2: opcode_CB_F2(); break; case 0xF3: opcode_CB_F3(); break; case 0xF4: opcode_CB_F4(); break; case 0xF5: opcode_CB_F5(); break; case 0xF6: opcode_CB_F6(); break; case 0xF7: opcode_CB_F7(); break; case 0xF8: opcode_CB_F8(); break; case 0xF9: opcode_CB_F9(); break; case 0xFA: opcode_CB_FA(); break; case 0xFB: opcode_CB_FB(); break; case 0xFC: opcode_CB_FC(); break; case 0xFD: opcode_CB_FD(); break; case 0xFE: opcode_CB_FE(); break; case 0xFF: opcode_CB_FF(); break; 200 | } 201 | 202 | return opcode_cycles_cb[opcode]; 203 | } 204 | -------------------------------------------------------------------------------- /src/cpu/cpu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../address.h" 4 | #include "../register.h" 5 | #include "../options.h" 6 | 7 | class Gameboy; 8 | 9 | enum class Condition { 10 | NZ, 11 | Z, 12 | NC, 13 | C, 14 | }; 15 | 16 | namespace rst { 17 | const u16 rst1 = 0x00; 18 | const u16 rst2 = 0x08; 19 | const u16 rst3 = 0x10; 20 | const u16 rst4 = 0x18; 21 | const u16 rst5 = 0x20; 22 | const u16 rst6 = 0x28; 23 | const u16 rst7 = 0x30; 24 | const u16 rst8 = 0x38; 25 | } // namespace rst 26 | 27 | namespace interrupts { 28 | const u16 vblank = 0x40; 29 | const u16 lcdc_status = 0x48; 30 | const u16 timer = 0x50; 31 | const u16 serial = 0x58; 32 | const u16 joypad = 0x60; 33 | } // namespace interrupts 34 | 35 | 36 | class CPU { 37 | public: 38 | CPU(Gameboy& inGb, Options& inOptions); 39 | 40 | auto tick() -> Cycles; 41 | 42 | auto execute_opcode(u8 opcode, u16 opcode_pc) -> Cycles; 43 | 44 | auto execute_normal_opcode(u8 opcode, u16 opcode_pc) -> Cycles; 45 | auto execute_cb_opcode(u8 opcode, u16 opcode_pc) -> Cycles; 46 | 47 | ByteRegister interrupt_flag; 48 | ByteRegister interrupt_enabled; 49 | 50 | private: 51 | void handle_interrupts(); 52 | auto handle_interrupt(u8 interrupt_bit, u16 interrupt_vector, u8 fired_interrupts) -> bool; 53 | 54 | Gameboy& gb; 55 | Options& options; 56 | 57 | bool interrupts_enabled = false; 58 | bool halted = false; 59 | 60 | bool branch_taken = false; 61 | 62 | /* Basic registers */ 63 | ByteRegister a, b, c, d, e, h, l; 64 | 65 | /* 'Group' registers for operations which use two registers as a word */ 66 | RegisterPair af; 67 | RegisterPair bc; 68 | RegisterPair de; 69 | RegisterPair hl; 70 | 71 | /* 72 | * Flags set dependant on the result of the last operation 73 | * 0x80 - produced 0 74 | * 0x40 - was a subtraction 75 | * 0x20 - lower half of the byte overflowed 15 76 | * 0x10 - overflowed 255 or underflowed 0 for additions/subtractions 77 | */ 78 | FlagRegister f; 79 | 80 | void set_flag_zero(bool set); 81 | void set_flag_subtract(bool set); 82 | void set_flag_half_carry(bool set); 83 | void set_flag_carry(bool set); 84 | 85 | /* Note: Not const because this also sets the 'branch_taken' member 86 | * variable if a branch is taken. This allows the correct cycle 87 | * count to be used */ 88 | auto is_condition(Condition condition) -> bool; 89 | 90 | /* Program counter */ 91 | WordRegister pc; 92 | 93 | /* Stack pointer */ 94 | WordRegister sp; 95 | 96 | auto get_byte_from_pc() -> u8; 97 | auto get_signed_byte_from_pc() -> s8; 98 | auto get_word_from_pc() -> u16; 99 | 100 | void stack_push(const WordValue& reg); 101 | void stack_pop(WordValue& reg); 102 | 103 | /* Opcode Helper Functions */ 104 | 105 | /* ADC */ 106 | void _opcode_adc(u8 value); 107 | 108 | void opcode_adc(); 109 | void opcode_adc(const ByteRegister& reg); 110 | void opcode_adc(const Address&& addr); 111 | 112 | /* ADD */ 113 | void _opcode_add(u8 reg, u8 value); 114 | 115 | void opcode_add_a(); 116 | void opcode_add_a(const ByteRegister& reg); 117 | void opcode_add_a(const Address& addr); 118 | 119 | void _opcode_add_hl(u16 value); 120 | void opcode_add_hl(const RegisterPair& reg_pair); 121 | void opcode_add_hl(const WordRegister& word_reg); 122 | 123 | void opcode_add_sp(); 124 | 125 | void opcode_add_signed(); 126 | 127 | /* AND */ 128 | void _opcode_and(u8 value); 129 | 130 | void opcode_and(); 131 | void opcode_and(ByteRegister& reg); 132 | void opcode_and(Address&& addr); 133 | 134 | /* BIT */ 135 | void _opcode_bit(u8 bit, u8 value); 136 | 137 | void opcode_bit(u8 bit, ByteRegister& reg); 138 | void opcode_bit(u8 bit, Address&& addr); 139 | 140 | /* CALL */ 141 | void opcode_call(); 142 | void opcode_call(Condition condition); 143 | 144 | /* CCF */ 145 | void opcode_ccf(); 146 | 147 | /* CP */ 148 | void _opcode_cp(u8 value); 149 | 150 | void opcode_cp(); 151 | void opcode_cp(const ByteRegister& reg); 152 | void opcode_cp(const Address& addr); 153 | 154 | /* CPL */ 155 | void opcode_cpl(); 156 | 157 | /* DAA */ 158 | void opcode_daa(); 159 | 160 | /* DEC */ 161 | void opcode_dec(ByteRegister& reg); 162 | void opcode_dec(RegisterPair& reg); 163 | void opcode_dec(WordRegister& reg); 164 | void opcode_dec(Address&& addr); 165 | 166 | /* DI */ 167 | void opcode_di(); 168 | 169 | /* EI */ 170 | void opcode_ei(); 171 | 172 | /* INC */ 173 | void opcode_inc(ByteRegister& reg); 174 | void opcode_inc(RegisterPair& reg); 175 | void opcode_inc(WordRegister& reg); 176 | void opcode_inc(Address&& addr); 177 | 178 | /* JP */ 179 | void opcode_jp(); 180 | void opcode_jp(Condition condition); 181 | void opcode_jp(const Address& addr); 182 | 183 | /* JR */ 184 | void opcode_jr(); 185 | void opcode_jr(Condition condition); 186 | 187 | /* HALT */ 188 | void opcode_halt(); 189 | 190 | /* LD */ 191 | void opcode_ld(ByteRegister& reg); 192 | void opcode_ld(ByteRegister& reg, const ByteRegister& byte_reg); 193 | void opcode_ld(ByteRegister& reg, const Address& address); 194 | 195 | void opcode_ld(RegisterPair& reg); 196 | 197 | void opcode_ld(WordRegister& reg); 198 | void opcode_ld(WordRegister& reg, const RegisterPair& reg_pair); 199 | 200 | void opcode_ld(const Address& address); 201 | void opcode_ld(const Address& address, const ByteRegister& byte_reg); 202 | void opcode_ld(const Address& address, const WordRegister& word_reg); 203 | 204 | // (nn), A 205 | void opcode_ld_to_addr(const ByteRegister& reg); 206 | void opcode_ld_from_addr(ByteRegister& reg); 207 | 208 | /* LDD */ 209 | auto _opcode_ldd(u8 value) -> u8; 210 | 211 | void opcode_ldd(ByteRegister& reg, const Address& address); 212 | void opcode_ldd(const Address& address, const ByteRegister& reg); 213 | 214 | /* LDH */ 215 | // A, (n) 216 | void opcode_ldh_into_a(); 217 | // (n), A 218 | void opcode_ldh_into_data(); 219 | // (reg), A 220 | void opcode_ldh_into_c(); 221 | // A, (reg) 222 | void opcode_ldh_c_into_a(); 223 | 224 | /* LDHL */ 225 | void opcode_ldhl(); 226 | 227 | /* LDI */ 228 | void opcode_ldi(ByteRegister& reg, const Address& address); 229 | void opcode_ldi(const Address& address, const ByteRegister& reg); 230 | 231 | /* NOP */ 232 | void opcode_nop(); 233 | 234 | /* OR */ 235 | void _opcode_or(u8 value); 236 | 237 | void opcode_or(); 238 | void opcode_or(const ByteRegister& reg); 239 | void opcode_or(const Address& addr); 240 | 241 | /* POP */ 242 | void opcode_pop(RegisterPair& reg); 243 | 244 | /* PUSH */ 245 | void opcode_push(const RegisterPair& reg); 246 | 247 | /* RES */ 248 | void opcode_res(u8 bit, ByteRegister& reg); 249 | void opcode_res(u8 bit, Address&& addr); 250 | 251 | /* RET */ 252 | void opcode_ret(); 253 | void opcode_ret(Condition condition); 254 | 255 | /* RETI */ 256 | void opcode_reti(); 257 | 258 | /* RL */ 259 | auto _opcode_rl(u8 value) -> u8; 260 | 261 | void opcode_rla(); 262 | void opcode_rl(ByteRegister& reg); 263 | void opcode_rl(Address&& addr); 264 | 265 | /* RLC */ 266 | auto _opcode_rlc(u8 value) -> u8; 267 | 268 | void opcode_rlca(); 269 | void opcode_rlc(ByteRegister& reg); 270 | void opcode_rlc(Address&& addr); 271 | 272 | /* RR */ 273 | auto _opcode_rr(u8 value) -> u8; 274 | 275 | void opcode_rra(); 276 | void opcode_rr(ByteRegister& reg); 277 | void opcode_rr(Address&& addr); 278 | 279 | /* RRC */ 280 | auto _opcode_rrc(u8 value) -> u8; 281 | 282 | void opcode_rrca(); 283 | void opcode_rrc(ByteRegister& reg); 284 | void opcode_rrc(Address&& addr); 285 | 286 | /* RST */ 287 | void opcode_rst(u8 offset); 288 | 289 | /* SBC */ 290 | void _opcode_sbc(u8 value); 291 | 292 | void opcode_sbc(); 293 | void opcode_sbc(ByteRegister& reg); 294 | void opcode_sbc(Address&& addr); 295 | 296 | /* SCF */ 297 | void opcode_scf(); 298 | 299 | /* SET */ 300 | void opcode_set(u8 bit, ByteRegister& reg); 301 | void opcode_set(u8 bit, Address&& addr); 302 | 303 | /* SLA */ 304 | auto _opcode_sla(u8 value) -> u8; 305 | 306 | void opcode_sla(ByteRegister& reg); 307 | void opcode_sla(Address&& addr); 308 | 309 | /* SRA */ 310 | auto _opcode_sra(u8 value) -> u8; 311 | 312 | void opcode_sra(ByteRegister& reg); 313 | void opcode_sra(Address&& addr); 314 | 315 | /* SRL */ 316 | auto _opcode_srl(u8 value) -> u8; 317 | 318 | void opcode_srl(ByteRegister& reg); 319 | void opcode_srl(Address&& addr); 320 | 321 | /* STOP */ 322 | void opcode_stop(); 323 | 324 | /* SUB */ 325 | void _opcode_sub(u8 value); 326 | 327 | void opcode_sub(); 328 | void opcode_sub(ByteRegister& reg); 329 | void opcode_sub(Address&& addr); 330 | 331 | /* SWAP */ 332 | auto _opcode_swap(u8 value) -> u8; 333 | 334 | void opcode_swap(ByteRegister& reg); 335 | void opcode_swap(Address&& addr); 336 | 337 | /* XOR */ 338 | void _opcode_xor(u8 value); 339 | 340 | void opcode_xor(); 341 | void opcode_xor(const ByteRegister& reg); 342 | void opcode_xor(const Address& addr); 343 | 344 | 345 | /* clang-format off */ 346 | /* Opcodes */ 347 | void opcode_00(); void opcode_01(); void opcode_02(); void opcode_03(); void opcode_04(); void opcode_05(); void opcode_06(); void opcode_07(); void opcode_08(); void opcode_09(); void opcode_0A(); void opcode_0B(); void opcode_0C(); void opcode_0D(); void opcode_0E(); void opcode_0F(); 348 | void opcode_10(); void opcode_11(); void opcode_12(); void opcode_13(); void opcode_14(); void opcode_15(); void opcode_16(); void opcode_17(); void opcode_18(); void opcode_19(); void opcode_1A(); void opcode_1B(); void opcode_1C(); void opcode_1D(); void opcode_1E(); void opcode_1F(); 349 | void opcode_20(); void opcode_21(); void opcode_22(); void opcode_23(); void opcode_24(); void opcode_25(); void opcode_26(); void opcode_27(); void opcode_28(); void opcode_29(); void opcode_2A(); void opcode_2B(); void opcode_2C(); void opcode_2D(); void opcode_2E(); void opcode_2F(); 350 | void opcode_30(); void opcode_31(); void opcode_32(); void opcode_33(); void opcode_34(); void opcode_35(); void opcode_36(); void opcode_37(); void opcode_38(); void opcode_39(); void opcode_3A(); void opcode_3B(); void opcode_3C(); void opcode_3D(); void opcode_3E(); void opcode_3F(); 351 | void opcode_40(); void opcode_41(); void opcode_42(); void opcode_43(); void opcode_44(); void opcode_45(); void opcode_46(); void opcode_47(); void opcode_48(); void opcode_49(); void opcode_4A(); void opcode_4B(); void opcode_4C(); void opcode_4D(); void opcode_4E(); void opcode_4F(); 352 | void opcode_50(); void opcode_51(); void opcode_52(); void opcode_53(); void opcode_54(); void opcode_55(); void opcode_56(); void opcode_57(); void opcode_58(); void opcode_59(); void opcode_5A(); void opcode_5B(); void opcode_5C(); void opcode_5D(); void opcode_5E(); void opcode_5F(); 353 | void opcode_60(); void opcode_61(); void opcode_62(); void opcode_63(); void opcode_64(); void opcode_65(); void opcode_66(); void opcode_67(); void opcode_68(); void opcode_69(); void opcode_6A(); void opcode_6B(); void opcode_6C(); void opcode_6D(); void opcode_6E(); void opcode_6F(); 354 | void opcode_70(); void opcode_71(); void opcode_72(); void opcode_73(); void opcode_74(); void opcode_75(); void opcode_76(); void opcode_77(); void opcode_78(); void opcode_79(); void opcode_7A(); void opcode_7B(); void opcode_7C(); void opcode_7D(); void opcode_7E(); void opcode_7F(); 355 | void opcode_80(); void opcode_81(); void opcode_82(); void opcode_83(); void opcode_84(); void opcode_85(); void opcode_86(); void opcode_87(); void opcode_88(); void opcode_89(); void opcode_8A(); void opcode_8B(); void opcode_8C(); void opcode_8D(); void opcode_8E(); void opcode_8F(); 356 | void opcode_90(); void opcode_91(); void opcode_92(); void opcode_93(); void opcode_94(); void opcode_95(); void opcode_96(); void opcode_97(); void opcode_98(); void opcode_99(); void opcode_9A(); void opcode_9B(); void opcode_9C(); void opcode_9D(); void opcode_9E(); void opcode_9F(); 357 | void opcode_A0(); void opcode_A1(); void opcode_A2(); void opcode_A3(); void opcode_A4(); void opcode_A5(); void opcode_A6(); void opcode_A7(); void opcode_A8(); void opcode_A9(); void opcode_AA(); void opcode_AB(); void opcode_AC(); void opcode_AD(); void opcode_AE(); void opcode_AF(); 358 | void opcode_B0(); void opcode_B1(); void opcode_B2(); void opcode_B3(); void opcode_B4(); void opcode_B5(); void opcode_B6(); void opcode_B7(); void opcode_B8(); void opcode_B9(); void opcode_BA(); void opcode_BB(); void opcode_BC(); void opcode_BD(); void opcode_BE(); void opcode_BF(); 359 | void opcode_C0(); void opcode_C1(); void opcode_C2(); void opcode_C3(); void opcode_C4(); void opcode_C5(); void opcode_C6(); void opcode_C7(); void opcode_C8(); void opcode_C9(); void opcode_CA(); void opcode_CB(); void opcode_CC(); void opcode_CD(); void opcode_CE(); void opcode_CF(); 360 | void opcode_D0(); void opcode_D1(); void opcode_D2(); void opcode_D3(); void opcode_D4(); void opcode_D5(); void opcode_D6(); void opcode_D7(); void opcode_D8(); void opcode_D9(); void opcode_DA(); void opcode_DB(); void opcode_DC(); void opcode_DD(); void opcode_DE(); void opcode_DF(); 361 | void opcode_E0(); void opcode_E1(); void opcode_E2(); void opcode_E3(); void opcode_E4(); void opcode_E5(); void opcode_E6(); void opcode_E7(); void opcode_E8(); void opcode_E9(); void opcode_EA(); void opcode_EB(); void opcode_EC(); void opcode_ED(); void opcode_EE(); void opcode_EF(); 362 | void opcode_F0(); void opcode_F1(); void opcode_F2(); void opcode_F3(); void opcode_F4(); void opcode_F5(); void opcode_F6(); void opcode_F7(); void opcode_F8(); void opcode_F9(); void opcode_FA(); void opcode_FB(); void opcode_FC(); void opcode_FD(); void opcode_FE(); void opcode_FF(); 363 | 364 | /* CB Opcodes */ 365 | void opcode_CB_00(); void opcode_CB_01(); void opcode_CB_02(); void opcode_CB_03(); void opcode_CB_04(); void opcode_CB_05(); void opcode_CB_06(); void opcode_CB_07(); void opcode_CB_08(); void opcode_CB_09(); void opcode_CB_0A(); void opcode_CB_0B(); void opcode_CB_0C(); void opcode_CB_0D(); void opcode_CB_0E(); void opcode_CB_0F(); 366 | void opcode_CB_10(); void opcode_CB_11(); void opcode_CB_12(); void opcode_CB_13(); void opcode_CB_14(); void opcode_CB_15(); void opcode_CB_16(); void opcode_CB_17(); void opcode_CB_18(); void opcode_CB_19(); void opcode_CB_1A(); void opcode_CB_1B(); void opcode_CB_1C(); void opcode_CB_1D(); void opcode_CB_1E(); void opcode_CB_1F(); 367 | void opcode_CB_20(); void opcode_CB_21(); void opcode_CB_22(); void opcode_CB_23(); void opcode_CB_24(); void opcode_CB_25(); void opcode_CB_26(); void opcode_CB_27(); void opcode_CB_28(); void opcode_CB_29(); void opcode_CB_2A(); void opcode_CB_2B(); void opcode_CB_2C(); void opcode_CB_2D(); void opcode_CB_2E(); void opcode_CB_2F(); 368 | void opcode_CB_30(); void opcode_CB_31(); void opcode_CB_32(); void opcode_CB_33(); void opcode_CB_34(); void opcode_CB_35(); void opcode_CB_36(); void opcode_CB_37(); void opcode_CB_38(); void opcode_CB_39(); void opcode_CB_3A(); void opcode_CB_3B(); void opcode_CB_3C(); void opcode_CB_3D(); void opcode_CB_3E(); void opcode_CB_3F(); 369 | void opcode_CB_40(); void opcode_CB_41(); void opcode_CB_42(); void opcode_CB_43(); void opcode_CB_44(); void opcode_CB_45(); void opcode_CB_46(); void opcode_CB_47(); void opcode_CB_48(); void opcode_CB_49(); void opcode_CB_4A(); void opcode_CB_4B(); void opcode_CB_4C(); void opcode_CB_4D(); void opcode_CB_4E(); void opcode_CB_4F(); 370 | void opcode_CB_50(); void opcode_CB_51(); void opcode_CB_52(); void opcode_CB_53(); void opcode_CB_54(); void opcode_CB_55(); void opcode_CB_56(); void opcode_CB_57(); void opcode_CB_58(); void opcode_CB_59(); void opcode_CB_5A(); void opcode_CB_5B(); void opcode_CB_5C(); void opcode_CB_5D(); void opcode_CB_5E(); void opcode_CB_5F(); 371 | void opcode_CB_60(); void opcode_CB_61(); void opcode_CB_62(); void opcode_CB_63(); void opcode_CB_64(); void opcode_CB_65(); void opcode_CB_66(); void opcode_CB_67(); void opcode_CB_68(); void opcode_CB_69(); void opcode_CB_6A(); void opcode_CB_6B(); void opcode_CB_6C(); void opcode_CB_6D(); void opcode_CB_6E(); void opcode_CB_6F(); 372 | void opcode_CB_70(); void opcode_CB_71(); void opcode_CB_72(); void opcode_CB_73(); void opcode_CB_74(); void opcode_CB_75(); void opcode_CB_76(); void opcode_CB_77(); void opcode_CB_78(); void opcode_CB_79(); void opcode_CB_7A(); void opcode_CB_7B(); void opcode_CB_7C(); void opcode_CB_7D(); void opcode_CB_7E(); void opcode_CB_7F(); 373 | void opcode_CB_80(); void opcode_CB_81(); void opcode_CB_82(); void opcode_CB_83(); void opcode_CB_84(); void opcode_CB_85(); void opcode_CB_86(); void opcode_CB_87(); void opcode_CB_88(); void opcode_CB_89(); void opcode_CB_8A(); void opcode_CB_8B(); void opcode_CB_8C(); void opcode_CB_8D(); void opcode_CB_8E(); void opcode_CB_8F(); 374 | void opcode_CB_90(); void opcode_CB_91(); void opcode_CB_92(); void opcode_CB_93(); void opcode_CB_94(); void opcode_CB_95(); void opcode_CB_96(); void opcode_CB_97(); void opcode_CB_98(); void opcode_CB_99(); void opcode_CB_9A(); void opcode_CB_9B(); void opcode_CB_9C(); void opcode_CB_9D(); void opcode_CB_9E(); void opcode_CB_9F(); 375 | void opcode_CB_A0(); void opcode_CB_A1(); void opcode_CB_A2(); void opcode_CB_A3(); void opcode_CB_A4(); void opcode_CB_A5(); void opcode_CB_A6(); void opcode_CB_A7(); void opcode_CB_A8(); void opcode_CB_A9(); void opcode_CB_AA(); void opcode_CB_AB(); void opcode_CB_AC(); void opcode_CB_AD(); void opcode_CB_AE(); void opcode_CB_AF(); 376 | void opcode_CB_B0(); void opcode_CB_B1(); void opcode_CB_B2(); void opcode_CB_B3(); void opcode_CB_B4(); void opcode_CB_B5(); void opcode_CB_B6(); void opcode_CB_B7(); void opcode_CB_B8(); void opcode_CB_B9(); void opcode_CB_BA(); void opcode_CB_BB(); void opcode_CB_BC(); void opcode_CB_BD(); void opcode_CB_BE(); void opcode_CB_BF(); 377 | void opcode_CB_C0(); void opcode_CB_C1(); void opcode_CB_C2(); void opcode_CB_C3(); void opcode_CB_C4(); void opcode_CB_C5(); void opcode_CB_C6(); void opcode_CB_C7(); void opcode_CB_C8(); void opcode_CB_C9(); void opcode_CB_CA(); void opcode_CB_CB(); void opcode_CB_CC(); void opcode_CB_CD(); void opcode_CB_CE(); void opcode_CB_CF(); 378 | void opcode_CB_D0(); void opcode_CB_D1(); void opcode_CB_D2(); void opcode_CB_D3(); void opcode_CB_D4(); void opcode_CB_D5(); void opcode_CB_D6(); void opcode_CB_D7(); void opcode_CB_D8(); void opcode_CB_D9(); void opcode_CB_DA(); void opcode_CB_DB(); void opcode_CB_DC(); void opcode_CB_DD(); void opcode_CB_DE(); void opcode_CB_DF(); 379 | void opcode_CB_E0(); void opcode_CB_E1(); void opcode_CB_E2(); void opcode_CB_E3(); void opcode_CB_E4(); void opcode_CB_E5(); void opcode_CB_E6(); void opcode_CB_E7(); void opcode_CB_E8(); void opcode_CB_E9(); void opcode_CB_EA(); void opcode_CB_EB(); void opcode_CB_EC(); void opcode_CB_ED(); void opcode_CB_EE(); void opcode_CB_EF(); 380 | void opcode_CB_F0(); void opcode_CB_F1(); void opcode_CB_F2(); void opcode_CB_F3(); void opcode_CB_F4(); void opcode_CB_F5(); void opcode_CB_F6(); void opcode_CB_F7(); void opcode_CB_F8(); void opcode_CB_F9(); void opcode_CB_FA(); void opcode_CB_FB(); void opcode_CB_FC(); void opcode_CB_FD(); void opcode_CB_FE(); void opcode_CB_FF(); 381 | /* clang-format on */ 382 | 383 | friend class Debugger; 384 | }; 385 | -------------------------------------------------------------------------------- /src/cpu/opcode_cycles.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* clang-format off */ 3 | 4 | #include 5 | 6 | const std::array opcode_cycles = { 7 | 1, 3, 2, 2, 1, 1, 2, 1, 5, 2, 2, 2, 1, 1, 2, 1, 8 | 1, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, 9 | 2, 3, 2, 2, 1, 1, 2, 1, 2, 2, 2, 2, 1, 1, 2, 1, 10 | 2, 3, 2, 2, 3, 3, 3, 1, 2, 2, 2, 2, 1, 1, 2, 1, 11 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 12 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 13 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 14 | 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 15 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 16 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 17 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 18 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 19 | 2, 3, 3, 4, 3, 4, 2, 4, 2, 4, 3, 0, 3, 6, 2, 4, 20 | 2, 3, 3, 0, 3, 4, 2, 4, 2, 4, 3, 0, 3, 0, 2, 4, 21 | 3, 3, 2, 0, 0, 4, 2, 4, 4, 1, 4, 0, 0, 0, 2, 4, 22 | 3, 3, 2, 1, 0, 4, 2, 4, 3, 2, 4, 1, 0, 0, 2, 4 23 | }; 24 | 25 | const std::array opcode_cycles_branched = { 26 | 1, 3, 2, 2, 1, 1, 2, 1, 5, 2, 2, 2, 1, 1, 2, 1, 27 | 1, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, 28 | 3, 3, 2, 2, 1, 1, 2, 1, 3, 2, 2, 2, 1, 1, 2, 1, 29 | 3, 3, 2, 2, 3, 3, 3, 1, 3, 2, 2, 2, 1, 1, 2, 1, 30 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 31 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 32 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 33 | 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 34 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 35 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 36 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 37 | 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 38 | 5, 3, 4, 4, 6, 4, 2, 4, 5, 4, 4, 0, 6, 6, 2, 4, 39 | 5, 3, 4, 0, 6, 4, 2, 4, 5, 4, 4, 0, 6, 0, 2, 4, 40 | 3, 3, 2, 0, 0, 4, 2, 4, 4, 1, 4, 0, 0, 0, 2, 4, 41 | 3, 3, 2, 1, 0, 4, 2, 4, 3, 2, 4, 1, 0, 0, 2, 4 42 | }; 43 | 44 | const std::array opcode_cycles_cb = { 45 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 46 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 47 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 48 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 49 | 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 50 | 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 51 | 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 52 | 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, 53 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 54 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 55 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 56 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 57 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 58 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 59 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 60 | 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2 61 | }; 62 | -------------------------------------------------------------------------------- /src/cpu/opcode_names.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | /* clang-format off */ 3 | 4 | #include 5 | 6 | static const std::array opcode_names = { 7 | "NOP", 8 | "LD BC,nn", 9 | "LD (BC),A", 10 | "INC BC", 11 | "INC B", 12 | "DEC B", 13 | "LD B,n", 14 | "RLCA", 15 | "LD (nn),SP", 16 | "ADD HL,BC", 17 | "LD A,(BC)", 18 | "DEC BC", 19 | "INC C", 20 | "DEC C", 21 | "LD C,n", 22 | "RRCA", 23 | 24 | "STOP", 25 | "LD DE,nn", 26 | "LD (DE),A", 27 | "INC DE", 28 | "INC D", 29 | "DEC D", 30 | "LD D,n", 31 | "RLA", 32 | "JR n", 33 | "ADD HL,DE", 34 | "LD A,(DE)", 35 | "DEC DE", 36 | "INC E", 37 | "DEC E", 38 | "LD E,n", 39 | "RRA", 40 | 41 | "JR NZ,n", 42 | "LD HL,nn", 43 | "LD (HL+),A", 44 | "INC HL", 45 | "INC H", 46 | "DEC H", 47 | "LD H,n", 48 | "DAA", 49 | "JR Z,n", 50 | "ADD HL,HL", 51 | "LD A,(HLI)", 52 | "DEC HL", 53 | "INC L", 54 | "DEC L", 55 | "LD L,n", 56 | "CPL", 57 | 58 | "JR NC,n", 59 | "LD SP,nn", 60 | "LD (HL-),A", 61 | "INC SP", 62 | "INC (HL)", 63 | "DEC (HL)", 64 | "LD (HL),n", 65 | "SCF", 66 | "JR C,n", 67 | "ADD HL,SP", 68 | "LD A,(HLD)", 69 | "DEC SP", 70 | "INC A", 71 | "DEC A", 72 | "LDA,n", 73 | "CCF", 74 | 75 | "LD B,B", 76 | "LD B,C", 77 | "LD B,D", 78 | "LD B,E", 79 | "LD B,H", 80 | "LD B,L", 81 | "LD B,(HL)", 82 | "LD B,A", 83 | "LD C,B", 84 | "LD C,C", 85 | "LD C,D", 86 | "LD C,E", 87 | "LD C,H", 88 | "LD C,L", 89 | "LD C,(HL)", 90 | "LD C,A", 91 | 92 | "LD D,B", 93 | "LD D,C", 94 | "LD D,D", 95 | "LD D,E", 96 | "LD D,H", 97 | "LD D,L", 98 | "LD D,(HL)", 99 | "LD D,A", 100 | "LD E,B", 101 | "LD E,C", 102 | "LD E,D", 103 | "LD E,E", 104 | "LD E,H", 105 | "LD E,L", 106 | "LD E,(HL)", 107 | "LD E,A", 108 | 109 | "LD H,B", 110 | "LD H,C", 111 | "LD H,D", 112 | "LD H,E", 113 | "LD H,H", 114 | "LD H,L", 115 | "LD H,(HL)", 116 | "LD H,A", 117 | "LD L,B", 118 | "LD L,C", 119 | "LD L,D", 120 | "LD L,E", 121 | "LD L,H", 122 | "LD L,L", 123 | "LD L,(HL)", 124 | "LD L,A", 125 | 126 | "LD (HL),B", 127 | "LD (HL),C", 128 | "LD (HL),D", 129 | "LD (HL),E", 130 | "LD (HL),H", 131 | "LD (HL),L", 132 | "HALT", 133 | "LD (HL),A", 134 | "LD A,B", 135 | "LD A,C", 136 | "LD A,D", 137 | "LD A,E", 138 | "LD A,H", 139 | "LD A,L", 140 | "LD A,(HL)", 141 | "LD A,A", 142 | 143 | "ADD A,B", 144 | "ADD A,C", 145 | "ADD A,D", 146 | "ADD A,E", 147 | "ADD A,H", 148 | "ADD A,L", 149 | "ADD A,(HL)", 150 | "ADD A,A", 151 | "ADC A,B", 152 | "ADC A,C", 153 | "ADC A,D", 154 | "ADC A,E", 155 | "ADC A,H", 156 | "ADC A,L", 157 | "ADC A,(HL)", 158 | "ADC A,A", 159 | 160 | "SUB B", 161 | "SUB C", 162 | "SUB D", 163 | "SUB E", 164 | "SUB H", 165 | "SUB L", 166 | "SUB (HL)", 167 | "SUB A", 168 | "SBC A,B", 169 | "SBC A,C", 170 | "SBC A,D", 171 | "SBC A,E", 172 | "SBC A,H", 173 | "SBC A,L", 174 | "SBC A,(HL)", 175 | "SBC A,A", 176 | 177 | "AND B", 178 | "AND C", 179 | "AND D", 180 | "AND E", 181 | "AND H", 182 | "AND L", 183 | "AND (HL)", 184 | "AND A", 185 | "XOR B", 186 | "XOR C", 187 | "XOR D", 188 | "XOR E", 189 | "XOR H", 190 | "XOR L", 191 | "XOR (HL)", 192 | "XOR A", 193 | 194 | "OR B", 195 | "OR C", 196 | "OR D", 197 | "OR E", 198 | "OR H", 199 | "OR L", 200 | "OR (HL)", 201 | "OR A", 202 | "CP B", 203 | "CP C", 204 | "CP D", 205 | "CP E", 206 | "CP H", 207 | "CP L", 208 | "CP (HL)", 209 | "CP A", 210 | 211 | "RET NZ", 212 | "POP BC", 213 | "JP NZ,nn", 214 | "JP nn", 215 | "CALL NZ,nn", 216 | "PUSH BC", 217 | "ADD A,n", 218 | "RST ", 219 | "RET Z", 220 | "RET", 221 | "JP Z,nn", 222 | "cb opcode", 223 | "CALL Z,nn", 224 | "CALL nn", 225 | "ADC A,n", 226 | "RST 0x08", 227 | 228 | "RET NC", 229 | "POP DE", 230 | "JP NC,nn", 231 | "unused opcode", 232 | "CALL NC,nn", 233 | "PUSH DE", 234 | "SUB n", 235 | "RST 0x10", 236 | "RET C", 237 | "RETI", 238 | "JP C,nn", 239 | "unused opcode", 240 | "CALL C,nn", 241 | "unused opcode", 242 | "SBC A,n", 243 | "RST 0x18", 244 | 245 | "LD (0xFF00+n),A", 246 | "POP HL", 247 | "LD (0xFF00+C),A", 248 | "unused opcode", 249 | "unused opcode", 250 | "PUSH HL", 251 | "AND n", 252 | "RST 0x20", 253 | "ADD SP,n", 254 | "JP (HL)", 255 | "LD (nn),A", 256 | "unused opcode", 257 | "unused opcode", 258 | "unused opcode", 259 | "XOR n", 260 | "RST 0x28", 261 | 262 | "LD A,(0xFF00+n)", 263 | "POP AF", 264 | "LD A,(0xFF00+C)", 265 | "DI", 266 | "unused opcode", 267 | "PUSH AF", 268 | "OR n", 269 | "RST 0x30", 270 | "LD HL,SP", 271 | "LD SP,HL", 272 | "LD A,(nn)", 273 | "EI", 274 | "unused opcode", 275 | "unused opcode", 276 | "CP n", 277 | "RST 0x38" 278 | }; 279 | 280 | static const std::array opcode_cb_names = { 281 | "RLC B", 282 | "RLC C", 283 | "RLC D", 284 | "RLC E", 285 | "RLC H", 286 | "RLC L", 287 | "RLC (HL)", 288 | "RLC A", 289 | "RRC B", 290 | "RRC C", 291 | "RRC D", 292 | "RRC E", 293 | "RRC H", 294 | "RRC L", 295 | "RRC (HL)", 296 | "RRC A", 297 | 298 | "RL B", 299 | "RL C", 300 | "RL D", 301 | "RL E", 302 | "RL H", 303 | "RL L ", 304 | "RL (HL)", 305 | "RL A", 306 | "RR B", 307 | "RR C", 308 | "RR D", 309 | "RR E", 310 | "RR H", 311 | "RR L", 312 | "RR (HL)", 313 | "RR A", 314 | 315 | "SLA B", 316 | "SLA C", 317 | "SLA D", 318 | "SLA E", 319 | "SLA H", 320 | "SLA L", 321 | "SLA (HL)", 322 | "SLA A", 323 | "SRA B", 324 | "SRA C", 325 | "SRA D", 326 | "SRA E", 327 | "SRA H", 328 | "SRA L", 329 | "SRA (HL)", 330 | "SRA A", 331 | 332 | "SWAP B", 333 | "SWAP C", 334 | "SWAP D", 335 | "SWAP E", 336 | "SWAP H", 337 | "SWAP L", 338 | "SWAP (HL)", 339 | "SWAP A", 340 | "SRL B", 341 | "SRL C", 342 | "SRL D", 343 | "SRL E", 344 | "SRL H", 345 | "SRL L", 346 | "SRL (HL)", 347 | "SRL A", 348 | 349 | "BIT 0 B", 350 | "BIT 0 C", 351 | "BIT 0 D", 352 | "BIT 0 E", 353 | "BIT 0 H", 354 | "BIT 0 L", 355 | "BIT 0 (HL)", 356 | "BIT 0 A", 357 | "BIT 1 B", 358 | "BIT 1 C", 359 | "BIT 1 D", 360 | "BIT 1 E", 361 | "BIT 1 H", 362 | "BIT 1 L", 363 | "BIT 1 (HL)", 364 | "BIT 1 A", 365 | 366 | "BIT 2 B", 367 | "BIT 2 C", 368 | "BIT 2 D", 369 | "BIT 2 E", 370 | "BIT 2 H", 371 | "BIT 2 L", 372 | "BIT 2 (HL)", 373 | "BIT 2 A", 374 | "BIT 3 B", 375 | "BIT 3 C", 376 | "BIT 3 D", 377 | "BIT 3 E", 378 | "BIT 3 H", 379 | "BIT 3 L", 380 | "BIT 3 (HL)", 381 | "BIT 3 A", 382 | 383 | "BIT 4 B", 384 | "BIT 4 C", 385 | "BIT 4 D", 386 | "BIT 4 E", 387 | "BIT 4 H", 388 | "BIT 4 L", 389 | "BIT 4 (HL)", 390 | "BIT 4 A", 391 | "BIT 5 B", 392 | "BIT 5 C", 393 | "BIT 5 D", 394 | "BIT 5 E", 395 | "BIT 5 H", 396 | "BIT 5 L", 397 | "BIT 5 (HL)", 398 | "BIT 5 A", 399 | 400 | "BIT 6 B", 401 | "BIT 6 C", 402 | "BIT 6 D", 403 | "BIT 6 E", 404 | "BIT 6 H", 405 | "BIT 6 L", 406 | "BIT 6 (HL)", 407 | "BIT 6 A", 408 | "BIT 7 B", 409 | "BIT 7 C", 410 | "BIT 7 D", 411 | "BIT 7 E", 412 | "BIT 7 H", 413 | "BIT 7 L", 414 | "BIT 7 (HL)", 415 | "BIT 7 A", 416 | 417 | "RES 0 B", 418 | "RES 0 C", 419 | "RES 0 D", 420 | "RES 0 E", 421 | "RES 0 H", 422 | "RES 0 L", 423 | "RES 0 (HL)", 424 | "RES 0 A", 425 | "RES 1 B", 426 | "RES 1 C", 427 | "RES 1 D", 428 | "RES 1 E", 429 | "RES 1 H", 430 | "RES 1 L", 431 | "RES 1 (HL)", 432 | "RES 1 A", 433 | 434 | "RES 2 B", 435 | "RES 2 C", 436 | "RES 2 D", 437 | "RES 2 E", 438 | "RES 2 H", 439 | "RES 2 L", 440 | "RES 2 (HL)", 441 | "RES 2 A", 442 | "RES 3 B", 443 | "RES 3 C", 444 | "RES 3 D", 445 | "RES 3 E", 446 | "RES 3 H", 447 | "RES 3 L", 448 | "RES 3 (HL)", 449 | "RES 3 A", 450 | 451 | "RES 4 B", 452 | "RES 4 C", 453 | "RES 4 D", 454 | "RES 4 E", 455 | "RES 4 H", 456 | "RES 4 L", 457 | "RES 4 (HL)", 458 | "RES 4 A", 459 | "RES 5 B", 460 | "RES 5 C", 461 | "RES 5 D", 462 | "RES 5 E", 463 | "RES 5 H", 464 | "RES 5 L", 465 | "RES 5 (HL)", 466 | "RES 5 A", 467 | 468 | "RES 6 B", 469 | "RES 6 C", 470 | "RES 6 D", 471 | "RES 6 E", 472 | "RES 6 H", 473 | "RES 6 L", 474 | "RES 6 (HL)", 475 | "RES 6 A", 476 | "RES 7 B", 477 | "RES 7 C", 478 | "RES 7 D", 479 | "RES 7 E", 480 | "RES 7 H", 481 | "RES 7 L", 482 | "RES 7 (HL)", 483 | "RES 7 A", 484 | 485 | "SET 0 B", 486 | "SET 0 C", 487 | "SET 0 D", 488 | "SET 0 E", 489 | "SET 0 H", 490 | "SET 0 L", 491 | "SET 0 (HL)", 492 | "SET 0 A", 493 | "SET 1 B", 494 | "SET 1 C", 495 | "SET 1 D", 496 | "SET 1 E", 497 | "SET 1 H", 498 | "SET 1 L", 499 | "SET 1 (HL)", 500 | "SET 1 A", 501 | 502 | "SET 2 B", 503 | "SET 2 C", 504 | "SET 2 D", 505 | "SET 2 E", 506 | "SET 2 H", 507 | "SET 2 L", 508 | "SET 2 (HL)", 509 | "SET 2 A", 510 | "SET 3 B", 511 | "SET 3 C", 512 | "SET 3 D", 513 | "SET 3 E", 514 | "SET 3 H", 515 | "SET 3 L", 516 | "SET 3 (HL)", 517 | "SET 3 A", 518 | 519 | "SET 4 B", 520 | "SET 4 C", 521 | "SET 4 D", 522 | "SET 4 E", 523 | "SET 4 H", 524 | "SET 4 L", 525 | "SET 4 (HL)", 526 | "SET 4 A", 527 | "SET 5 B", 528 | "SET 5 C", 529 | "SET 5 D", 530 | "SET 5 E", 531 | "SET 5 H", 532 | "SET 5 L", 533 | "SET 5 (HL)", 534 | "SET 5 A", 535 | 536 | "SET 6 B", 537 | "SET 6 C", 538 | "SET 6 D", 539 | "SET 6 E", 540 | "SET 6 H", 541 | "SET 6 L", 542 | "SET 6 (HL)", 543 | "SET 6 A", 544 | "SET 7 B", 545 | "SET 7 C", 546 | "SET 7 D", 547 | "SET 7 E", 548 | "SET 7 H", 549 | "SET 7 L", 550 | "SET 7 (HL)", 551 | "SET 7 A" 552 | }; 553 | -------------------------------------------------------------------------------- /src/debugger.cc: -------------------------------------------------------------------------------- 1 | #include "debugger.h" 2 | 3 | #include "gameboy.h" 4 | #include "cpu/cpu.h" 5 | #include "util/log.h" 6 | #include "util/string_utils.h" 7 | 8 | #include 9 | #include 10 | 11 | Debugger::Debugger(Gameboy& inGameboy, Options& inOptions) : 12 | gameboy(inGameboy), 13 | options(inOptions), 14 | enabled(inOptions.debugger) 15 | { 16 | unused(options); 17 | } 18 | 19 | void Debugger::set_enabled(bool _enabled) { 20 | enabled = _enabled; 21 | } 22 | 23 | void Debugger::cycle() { 24 | if (!enabled) return; 25 | 26 | steps++; 27 | 28 | if (breakpoint_addr != 0 && !debugger_enabled) { 29 | if (gameboy.cpu.pc.value() != breakpoint_addr) { return; } 30 | debugger_enabled = true; 31 | } 32 | 33 | if (breakpoint_value_addr != 0 && !debugger_enabled) { 34 | if (gameboy.mmu.read(breakpoint_value_addr) != breakpoint_value) { return; } 35 | breakpoint_value_addr = 0; 36 | breakpoint_value = 0; 37 | debugger_enabled = true; 38 | } 39 | 40 | if (!debugger_enabled) { return; } 41 | 42 | if (counter > 0) { 43 | counter--; 44 | return; 45 | } 46 | 47 | while (enabled) { 48 | Command cmd = get_command(); 49 | 50 | bool should_continue = execute(cmd); 51 | 52 | if (should_continue) break; 53 | } 54 | } 55 | 56 | auto Debugger::execute(const Command& command) -> bool { 57 | switch (command.type) { 58 | case CommandType::Step: 59 | /* Note: 'Step' allows the program to break 60 | * out of the debugger loop so the boolean 61 | * return value of this function is returned */ 62 | return command_step(command.args); 63 | 64 | case CommandType::Run: 65 | debugger_enabled = false; 66 | return true; 67 | 68 | case CommandType::BreakAddr: command_breakaddr(command.args); break; 69 | case CommandType::BreakValue: command_breakvalue(command.args); break; 70 | case CommandType::Registers: command_registers(command.args); break; 71 | case CommandType::Flags: command_flags(command.args); break; 72 | case CommandType::Memory: command_memory(command.args); break; 73 | case CommandType::MemoryCell: command_memory_cell(command.args); break; 74 | case CommandType::Steps: command_steps(command.args); break; 75 | case CommandType::Log: command_log(command.args); break; 76 | case CommandType::Exit: command_exit(command.args); break; 77 | case CommandType::Help: command_help(command.args); break; 78 | 79 | case CommandType::Unknown: 80 | printf("Unknown command\n"); 81 | break; 82 | } 83 | 84 | return false; 85 | } 86 | 87 | auto Debugger::command_step(Args args) -> bool { 88 | switch (args.size()) { 89 | case 0: 90 | return true; 91 | case 1: 92 | /* TODO: Clean this up 93 | * Try/catch should be moved somewhere (could use optional?) 94 | * Should be able to avoid subtracting 1 */ 95 | try { 96 | int nsteps = std::stoi(args[0]); 97 | if (nsteps == 0) { 98 | log_error("Cannot step zero times"); 99 | return false; 100 | } 101 | if (nsteps < 0) { 102 | log_error("Cannot step a negative number of times"); 103 | return false; 104 | } 105 | counter = static_cast(nsteps - 1); 106 | } catch (std::invalid_argument&) { 107 | log_error("Invalid number of steps: %s", args[0].c_str()); 108 | /* If an invalid argument was encountered, the program 109 | * should not step at all, thus false is returned */ 110 | return false; 111 | } 112 | return true; 113 | default: 114 | log_error("Invalid arguments to 'step'"); 115 | return false; 116 | } 117 | } 118 | 119 | void Debugger::command_registers(const Args& args) { 120 | unused(args); 121 | 122 | printf("AF: %04X\n", gameboy.cpu.af.value()); 123 | printf("BC: %04X\n", gameboy.cpu.bc.value()); 124 | printf("DE: %04X\n", gameboy.cpu.de.value()); 125 | printf("HL: %04X\n", gameboy.cpu.hl.value()); 126 | printf("SP: %04X\n", gameboy.cpu.sp.value()); 127 | printf("PC: %04X\n", gameboy.cpu.pc.value()); 128 | } 129 | 130 | void Debugger::command_flags(const Args& args) { 131 | unused(args); 132 | 133 | printf("Zero: %d\n", gameboy.cpu.f.flag_zero_value()); 134 | printf("Subtract: %d\n", gameboy.cpu.f.flag_subtract_value()); 135 | printf("Half Carry: %d\n", gameboy.cpu.f.flag_half_carry_value()); 136 | printf("Carry: %d\n", gameboy.cpu.f.flag_carry_value()); 137 | } 138 | 139 | void Debugger::command_memory(Args args) { 140 | if (args.size() > 3) { 141 | log_error("Invalid arguments to command"); 142 | return; 143 | } 144 | 145 | /* TODO: Error handling for mis-parsed arguments */ 146 | u16 memory_location = static_cast(std::stoul(args[0], nullptr, 16)); 147 | 148 | uint lines = 10; 149 | if (args.size() >= 2) { 150 | lines = static_cast(std::stoi(args[1])); 151 | } 152 | 153 | uint line_length = 16; 154 | if (args.size() == 3) { 155 | line_length = static_cast(std::stoul(args[2])); 156 | } 157 | 158 | for (uint i = 0; i < lines; i++) { 159 | Address addr = static_cast(memory_location + i * line_length); 160 | printf("0x%04X | ", addr.value()); 161 | 162 | for (uint cell = 0; cell < line_length; cell++) { 163 | Address cell_addr = static_cast(addr.value() + cell); 164 | printf("%02X ", gameboy.mmu.read(cell_addr)); 165 | } 166 | 167 | printf("\n"); 168 | } 169 | } 170 | 171 | void Debugger::command_memory_cell(Args args) { 172 | if (args.size() != 1) { 173 | log_error("Invalid arguments to command"); 174 | return; 175 | } 176 | 177 | u16 memory_location = static_cast(std::stoul(args[0], nullptr, 16)); 178 | 179 | printf("0x%02X\n", gameboy.mmu.read(memory_location)); 180 | } 181 | 182 | void Debugger::command_breakaddr(Args args) { 183 | if (args.size() != 1) { 184 | log_error("Invalid arguments to command"); 185 | return; 186 | } 187 | 188 | u16 addr = static_cast(std::stoul(args[0], nullptr, 16)); 189 | breakpoint_addr = addr; 190 | log_info("Breakpoint set for address 0x%04X", breakpoint_addr); 191 | } 192 | 193 | void Debugger::command_breakvalue(Args args) { 194 | if (args.size() != 2) { 195 | log_error("Invalid arguments to command"); 196 | return; 197 | } 198 | 199 | u16 addr = static_cast(std::stoul(args[0], nullptr, 16)); 200 | u8 value = static_cast(std::stoul(args[1], nullptr, 16)); 201 | breakpoint_value_addr = addr; 202 | breakpoint_value = value; 203 | log_info("Breakpoint set for value 0x%02X at address 0x%04X", breakpoint_value, breakpoint_value_addr); 204 | } 205 | 206 | void Debugger::command_steps(const Args& args) const { 207 | unused(args); 208 | 209 | printf("Steps: %d\n", steps); 210 | } 211 | 212 | void Debugger::command_log(Args args) { 213 | if (args.size() != 1) { 214 | log_error("Invalid arguments to command"); 215 | return; 216 | } 217 | 218 | std::string desired_log_level = args[0]; 219 | std::transform(desired_log_level.begin(), desired_log_level.end(), desired_log_level.begin(), ::tolower); 220 | 221 | if (desired_log_level == "none") { 222 | log_set_level(LogLevel::Error); 223 | printf("Log level: Error\n"); 224 | } else if (desired_log_level == "error") { 225 | log_set_level(LogLevel::Error); 226 | printf("Log level: Error\n"); 227 | } else if (desired_log_level == "debug") { 228 | log_set_level(LogLevel::Debug); 229 | printf("Log level: Debug\n"); 230 | } else if (desired_log_level == "trace") { 231 | log_set_level(LogLevel::Trace); 232 | printf("Log level: Trace\n"); 233 | } else { 234 | log_error("Invalid log level"); 235 | } 236 | } 237 | 238 | void Debugger::command_exit(const Args& args) { 239 | unused(args); 240 | 241 | log_error("Exiting"); 242 | exit(1); 243 | } 244 | 245 | void Debugger::command_help(const Args& args) { 246 | unused(args); 247 | 248 | printf("\n"); 249 | printf("= Flow Control\n"); 250 | printf("[s]tep $steps=1 Run $steps cycles\n"); 251 | printf("[r]un Run until the next breakpoint\n"); 252 | printf("breakaddr $addr Set a breakpoint at $addr\n"); 253 | printf("breakvalue $addr #n Set a breakpoint at $addr\n"); 254 | printf("\n"); 255 | printf("= Debug Information\n"); 256 | printf("registers Print a dump of the CPU registers\n"); 257 | printf("flags Print a dump of the CPU flags\n"); 258 | printf("[mem]ory $start $lines Print a dump of memory from $start to $end\n"); 259 | printf("[addr]ess $addr Print the value of the memory at $addr\n"); 260 | printf("\n"); 261 | printf("= Other\n"); 262 | printf("steps Print the number of steps so far\n"); 263 | printf("help Print this help page\n"); 264 | printf("exit Exit the emulator\n"); 265 | printf("\n"); 266 | } 267 | 268 | auto Debugger::get_command() -> Command { 269 | printf("%s", PROMPT); 270 | std::string input_line; 271 | std::getline(std::cin, input_line); 272 | return parse(input_line); 273 | } 274 | 275 | auto Debugger::parse(const std::string& input) -> Command { 276 | using std::string; 277 | using std::vector; 278 | 279 | vector elems = split(input); 280 | 281 | /* If nothing was entered, repeat the last command */ 282 | if (elems.empty()) return last_command; 283 | 284 | string cmd = elems[0]; 285 | CommandType cmd_type = parse_command(cmd); 286 | 287 | vector args(elems.begin() + 1, elems.end()); 288 | 289 | last_command = Command { cmd_type, args }; 290 | 291 | return { cmd_type, args }; 292 | } 293 | 294 | auto Debugger::parse_command(std::string cmd) -> CommandType { 295 | std::transform(cmd.begin(), cmd.end(), cmd.begin(), ::tolower); 296 | 297 | if (cmd == "step" || cmd == "s") return CommandType::Step; 298 | if (cmd == "run" || cmd == "r") return CommandType::Run; 299 | 300 | if (cmd == "breakaddr") return CommandType::BreakAddr; 301 | if (cmd == "breakvalue") return CommandType::BreakValue; 302 | 303 | if (cmd == "regs" || cmd == "registers") return CommandType::Registers; 304 | if (cmd == "flags") return CommandType::Flags; 305 | if (cmd == "memory" || cmd == "mem") return CommandType::Memory; 306 | if (cmd == "address" || cmd == "addr") return CommandType::MemoryCell; 307 | if (cmd == "steps") return CommandType::Steps; 308 | 309 | if (cmd == "log") return CommandType::Log; 310 | 311 | if (cmd == "exit") return CommandType::Exit; 312 | if (cmd == "help") return CommandType::Help; 313 | 314 | return CommandType::Unknown; 315 | } 316 | -------------------------------------------------------------------------------- /src/debugger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "definitions.h" 4 | #include "options.h" 5 | 6 | #include 7 | #include 8 | 9 | class Gameboy; 10 | class CPU; 11 | 12 | static const char* PROMPT = "> "; 13 | 14 | enum class CommandType { 15 | Step, 16 | Run, 17 | 18 | BreakAddr, 19 | BreakValue, 20 | 21 | Registers, 22 | Flags, 23 | Memory, 24 | MemoryCell, 25 | Steps, 26 | 27 | Log, 28 | 29 | Exit, 30 | Help, 31 | 32 | Unknown, 33 | }; 34 | 35 | using Args = std::vector; 36 | 37 | struct Command { 38 | CommandType type; 39 | Args args; 40 | }; 41 | 42 | class Debugger { 43 | public: 44 | Debugger(Gameboy& inGameboy, Options& inOptions); 45 | 46 | void set_enabled(bool enabled); 47 | void cycle(); 48 | 49 | private: 50 | Gameboy& gameboy; 51 | Options& options; 52 | 53 | Command last_command; 54 | 55 | auto get_command() -> Command; 56 | 57 | auto execute(const Command& command) -> bool; 58 | 59 | /* Commands */ 60 | auto command_step(Args args) -> bool; 61 | 62 | void command_registers(const Args& args); 63 | void command_flags(const Args& args); 64 | void command_memory(Args args); 65 | void command_memory_cell(Args args); 66 | 67 | void command_breakaddr(Args args); 68 | void command_breakvalue(Args args); 69 | 70 | static void command_log(Args args); 71 | 72 | void command_steps(const Args& args) const; 73 | static void command_exit(const Args& args); 74 | static void command_help(const Args& args); 75 | 76 | auto parse(const std::string& input) -> Command; 77 | static auto parse_command(std::string cmd) -> CommandType; 78 | 79 | bool enabled; 80 | 81 | int steps = 0; 82 | uint counter = 0; 83 | 84 | u16 breakpoint_addr = 0; 85 | u16 breakpoint_value_addr = 0; 86 | u8 breakpoint_value = 0; 87 | bool debugger_enabled = true; 88 | }; 89 | -------------------------------------------------------------------------------- /src/definitions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | using uint = unsigned int; 7 | 8 | using u8 = uint8_t; 9 | using u16 = uint16_t; 10 | using s8 = int8_t; 11 | using s16 = uint16_t; 12 | 13 | struct Noncopyable { 14 | auto operator=(const Noncopyable&) -> Noncopyable& = delete; 15 | Noncopyable(const Noncopyable&) = delete; 16 | Noncopyable() = default; 17 | ~Noncopyable() = default; 18 | }; 19 | 20 | #pragma clang diagnostic push 21 | #pragma clang diagnostic ignored "-Wunused-parameter" 22 | template void unused(T&&... unused_vars) {} 23 | #pragma clang diagnostic pop 24 | 25 | #define fatal_error(...) \ 26 | log_error("Fatal error @ %s (line %d)", __PRETTY_FUNCTION__, __LINE__); \ 27 | log_error(__VA_ARGS__); \ 28 | exit(1); 29 | 30 | 31 | const uint GAMEBOY_WIDTH = 160; 32 | const uint GAMEBOY_HEIGHT = 144; 33 | const uint BG_MAP_SIZE = 256; 34 | 35 | const int CLOCK_RATE = 4194304; 36 | 37 | enum class GBColor { 38 | Color0, /* White */ 39 | Color1, /* Light gray */ 40 | Color2, /* Dark gray */ 41 | Color3, /* Black */ 42 | }; 43 | 44 | enum class Color { 45 | White, 46 | LightGray, 47 | DarkGray, 48 | Black, 49 | }; 50 | 51 | struct Palette { 52 | Color color0 = Color::White; 53 | Color color1 = Color::LightGray; 54 | Color color2 = Color::DarkGray; 55 | Color color3 = Color::Black; 56 | }; 57 | 58 | class Cycles { 59 | public: 60 | Cycles(uint nCycles) : cycles(nCycles) {} 61 | 62 | const uint cycles; 63 | }; 64 | -------------------------------------------------------------------------------- /src/gameboy.cc: -------------------------------------------------------------------------------- 1 | #include "gameboy.h" 2 | 3 | Gameboy::Gameboy(const std::vector& cartridge_data, Options& options, 4 | const std::vector& save_data) 5 | : cartridge(get_cartridge(cartridge_data, save_data)), 6 | cpu(*this, options), 7 | video(*this, options), 8 | mmu(*this, options), 9 | timer(*this), 10 | serial(options), 11 | debugger(*this, options) 12 | { 13 | if (options.disable_logs) log_set_level(LogLevel::Error); 14 | 15 | log_set_level(options.trace 16 | ? LogLevel::Trace 17 | : LogLevel::Info 18 | ); 19 | } 20 | 21 | void Gameboy::button_pressed(GbButton button) { 22 | input.button_pressed(button); 23 | } 24 | 25 | void Gameboy::button_released(GbButton button) { 26 | input.button_released(button); 27 | } 28 | 29 | void Gameboy::debug_toggle_background() { 30 | video.debug_disable_background = !video.debug_disable_background; 31 | } 32 | 33 | void Gameboy::debug_toggle_sprites() { 34 | video.debug_disable_sprites = !video.debug_disable_sprites; 35 | } 36 | 37 | void Gameboy::debug_toggle_window() { 38 | video.debug_disable_window = !video.debug_disable_window; 39 | } 40 | 41 | void Gameboy::run( 42 | const should_close_callback_t& _should_close_callback, 43 | const vblank_callback_t& _vblank_callback 44 | ) { 45 | should_close_callback = _should_close_callback; 46 | 47 | video.register_vblank_callback(_vblank_callback); 48 | 49 | while (!should_close_callback()) { 50 | tick(); 51 | } 52 | 53 | debugger.set_enabled(false); 54 | } 55 | 56 | void Gameboy::tick() { 57 | debugger.cycle(); 58 | 59 | auto cycles = cpu.tick(); 60 | elapsed_cycles += cycles.cycles; 61 | 62 | video.tick(cycles); 63 | timer.tick(cycles.cycles); 64 | } 65 | 66 | auto Gameboy::get_cartridge_ram() const -> const std::vector& { 67 | return cartridge->get_cartridge_ram(); 68 | } 69 | -------------------------------------------------------------------------------- /src/gameboy.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "debugger.h" 4 | #include "input.h" 5 | #include "cpu/cpu.h" 6 | #include "video/video.h" 7 | #include "serial.h" 8 | #include "timer.h" 9 | #include "options.h" 10 | #include "util/log.h" 11 | 12 | #include 13 | #include 14 | 15 | using should_close_callback_t = std::function; 16 | 17 | class Gameboy { 18 | public: 19 | Gameboy(const std::vector& cartridge_data, Options& options, 20 | const std::vector& save_data = {}); 21 | 22 | void run( 23 | const should_close_callback_t& _should_close_callback, 24 | const vblank_callback_t& _vblank_callback 25 | ); 26 | 27 | void button_pressed(GbButton button); 28 | void button_released(GbButton button); 29 | 30 | void debug_toggle_background(); 31 | void debug_toggle_sprites(); 32 | void debug_toggle_window(); 33 | 34 | auto get_cartridge_ram() const -> const std::vector&; 35 | 36 | private: 37 | void tick(); 38 | 39 | std::shared_ptr cartridge; 40 | 41 | CPU cpu; 42 | friend class CPU; 43 | 44 | Video video; 45 | friend class Video; 46 | 47 | MMU mmu; 48 | friend class MMU; 49 | 50 | Timer timer; 51 | friend class Timer; 52 | 53 | Input input; 54 | Serial serial; 55 | 56 | Debugger debugger; 57 | friend class Debugger; 58 | 59 | uint elapsed_cycles = 0; 60 | 61 | should_close_callback_t should_close_callback; 62 | }; 63 | -------------------------------------------------------------------------------- /src/gameboy_prelude.h: -------------------------------------------------------------------------------- 1 | #include "gameboy.h" 2 | #include "input.h" 3 | #include "cartridge/cartridge.h" 4 | #include "util/log.h" 5 | #include "util/files.h" 6 | -------------------------------------------------------------------------------- /src/input.cc: -------------------------------------------------------------------------------- 1 | #include "input.h" 2 | 3 | #include "util/bitwise.h" 4 | 5 | void Input::button_pressed(GbButton button) { 6 | set_button(button, true); 7 | } 8 | 9 | void Input::button_released(GbButton button) { 10 | set_button(button, false); 11 | } 12 | 13 | void Input::set_button(GbButton button, bool set) { 14 | if (button == GbButton::Up) { up = set; } 15 | if (button == GbButton::Down) { down = set; } 16 | if (button == GbButton::Left) { left = set; } 17 | if (button == GbButton::Right) { right = set; } 18 | if (button == GbButton::A) { a = set; } 19 | if (button == GbButton::B) { b = set; } 20 | if (button == GbButton::Select) { select = set; } 21 | if (button == GbButton::Start) { start = set; } 22 | } 23 | 24 | void Input::write(u8 set) { 25 | using bitwise::check_bit; 26 | 27 | direction_switch = !check_bit(set, 4); 28 | button_switch = !check_bit(set, 5); 29 | } 30 | 31 | auto Input::get_input() const -> u8 { 32 | using bitwise::set_bit_to; 33 | 34 | u8 buttons = 0b1111; 35 | 36 | if (direction_switch) { 37 | buttons = set_bit_to(buttons, 0, !right); 38 | buttons = set_bit_to(buttons, 1, !left); 39 | buttons = set_bit_to(buttons, 2, !up); 40 | buttons = set_bit_to(buttons, 3, !down); 41 | } 42 | 43 | if (button_switch) { 44 | buttons = set_bit_to(buttons, 0, !a); 45 | buttons = set_bit_to(buttons, 1, !b); 46 | buttons = set_bit_to(buttons, 2, !select); 47 | buttons = set_bit_to(buttons, 3, !start); 48 | } 49 | 50 | buttons = set_bit_to(buttons, 4, !direction_switch); 51 | buttons = set_bit_to(buttons, 5, !button_switch); 52 | 53 | return buttons; 54 | } 55 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "definitions.h" 4 | 5 | enum class GbButton { 6 | Up, 7 | Down, 8 | Left, 9 | Right, 10 | A, 11 | B, 12 | Select, 13 | Start, 14 | }; 15 | 16 | class Input { 17 | public: 18 | void button_pressed(GbButton button); 19 | void button_released(GbButton button); 20 | void write(u8 set); 21 | 22 | auto get_input() const -> u8; 23 | 24 | private: 25 | void set_button(GbButton button, bool set); 26 | 27 | bool up = false; 28 | bool down = false; 29 | bool left = false; 30 | bool right = false; 31 | bool a = false; 32 | bool b = false; 33 | bool select = false; 34 | bool start = false; 35 | 36 | bool button_switch = false; 37 | bool direction_switch = false; 38 | }; 39 | -------------------------------------------------------------------------------- /src/mmu.cc: -------------------------------------------------------------------------------- 1 | #include "mmu.h" 2 | #include "gameboy.h" 3 | #include "boot.h" 4 | #include "serial.h" 5 | #include "input.h" 6 | #include "timer.h" 7 | #include "util/log.h" 8 | #include "util/bitwise.h" 9 | #include "cpu/cpu.h" 10 | #include "video/video.h" 11 | 12 | MMU::MMU(Gameboy& inGb, Options& inOptions) : 13 | gb(inGb), 14 | options(inOptions) 15 | { 16 | work_ram = std::vector(0x8000); 17 | oam_ram = std::vector(0xA0); 18 | high_ram = std::vector(0x80); 19 | } 20 | 21 | auto MMU::read(const Address& address) const -> u8 { 22 | if (address.in_range(0x0, 0x7FFF)) { 23 | if (address.in_range(0x0, 0xFF) && boot_rom_active()) { 24 | return bootDMG[address.value()]; 25 | } 26 | return gb.cartridge->read(address); 27 | } 28 | 29 | /* VRAM */ 30 | if (address.in_range(0x8000, 0x9FFF)) { 31 | return gb.video.read(address.value() - 0x8000); 32 | } 33 | 34 | /* External (cartridge) RAM */ 35 | if (address.in_range(0xA000, 0xBFFF)) { 36 | return gb.cartridge->read(address); 37 | } 38 | 39 | /* Internal work RAM */ 40 | if (address.in_range(0xC000, 0xDFFF)) { 41 | return work_ram.at(address.value() - 0xC000); 42 | } 43 | 44 | if (address.in_range(0xE000, 0xFDFF)) { 45 | /* log_warn("Attempting to read from mirrored work RAM"); */ 46 | return read(address.value() - 0x2000); 47 | } 48 | 49 | /* OAM */ 50 | if (address.in_range(0xFE00, 0xFE9F)) { 51 | return oam_ram.at(address.value() - 0xFE00); 52 | } 53 | 54 | if (address.in_range(0xFEA0, 0xFEFF)) { 55 | log_warn("Attempting to read from unusable memory 0x%x", address.value()); 56 | return 0xFF; 57 | } 58 | 59 | /* Mapped IO */ 60 | if (address.in_range(0xFF00, 0xFF7F)) { 61 | return read_io(address); 62 | } 63 | 64 | /* Zero Page ram */ 65 | if (address.in_range(0xFF80, 0xFFFE)) { 66 | return high_ram.at(address.value() - 0xFF80); 67 | } 68 | 69 | /* Interrupt Enable register */ 70 | if (address == 0xFFFF) { 71 | return gb.cpu.interrupt_enabled.value(); 72 | } 73 | 74 | fatal_error("Attempted to read from unmapped memory address 0x%X", address.value()); 75 | } 76 | 77 | auto MMU::read_io(const Address& address) const -> u8 { 78 | switch (address.value()) { 79 | case 0xFF00: 80 | return gb.input.get_input(); 81 | 82 | case 0xFF01: 83 | return gb.serial.read(); 84 | 85 | case 0xFF02: 86 | log_unimplemented("Attempted to read serial transfer control"); 87 | return 0xFF; 88 | 89 | case 0xFF03: 90 | return unmapped_io_read(address); 91 | 92 | case 0xFF04: 93 | return gb.timer.get_divider(); 94 | 95 | case 0xFF05: 96 | return gb.timer.get_timer(); 97 | 98 | case 0xFF06: 99 | return gb.timer.get_timer_modulo(); 100 | 101 | case 0xFF07: 102 | return gb.timer.get_timer_control(); 103 | 104 | case 0xFF08: 105 | case 0xFF09: 106 | case 0xFF0A: 107 | case 0xFF0B: 108 | case 0xFF0C: 109 | case 0xFF0D: 110 | case 0xFF0E: 111 | return unmapped_io_read(address); 112 | 113 | case 0xFF0F: 114 | return gb.cpu.interrupt_flag.value(); 115 | 116 | /* TODO: Audio - Channel 1: Tone & Sweep */ 117 | case 0xFF10: 118 | case 0xFF11: 119 | case 0xFF12: 120 | case 0xFF13: 121 | case 0xFF14: 122 | return 0xFF; 123 | 124 | case 0xFF15: 125 | return unmapped_io_read(address); 126 | 127 | /* TODO: Audio - Channel 2: Tone */ 128 | case 0xFF16: 129 | case 0xFF17: 130 | case 0xFF18: 131 | case 0xFF19: 132 | return 0xFF; 133 | 134 | /* TODO: Audio - Channel 3: Wave Output */ 135 | case 0xFF1A: 136 | case 0xFF1B: 137 | case 0xFF1C: 138 | case 0xFF1D: 139 | case 0xFF1E: 140 | case 0xFF1F: 141 | return 0xFF; 142 | 143 | /* TODO: Audio - Channel 4: Noise */ 144 | case 0xFF20: 145 | case 0xFF21: 146 | case 0xFF22: 147 | case 0xFF23: 148 | return 0xFF; 149 | 150 | /* TODO: Audio - Channel control/ON-OFF/Volume */ 151 | case 0xFF24: 152 | return 0xFF; 153 | 154 | /* TODO: Audio - Selection of sound output terminal */ 155 | case 0xFF25: 156 | return 0xFF; 157 | 158 | /* TODO: Audio - Sound on/off */ 159 | case 0xFF26: 160 | return 0xFF; 161 | 162 | case 0xFF27: 163 | case 0xFF28: 164 | case 0xFF29: 165 | case 0xFF2A: 166 | case 0xFF2B: 167 | case 0xFF2C: 168 | case 0xFF2D: 169 | case 0xFF2E: 170 | case 0xFF2F: 171 | return unmapped_io_read(address); 172 | 173 | /* TODO: Audio - Wave pattern RAM */ 174 | case 0xFF30: 175 | case 0xFF31: 176 | case 0xFF32: 177 | case 0xFF33: 178 | case 0xFF34: 179 | case 0xFF35: 180 | case 0xFF36: 181 | case 0xFF37: 182 | case 0xFF38: 183 | case 0xFF39: 184 | case 0xFF3A: 185 | case 0xFF3B: 186 | case 0xFF3C: 187 | case 0xFF3D: 188 | case 0xFF3E: 189 | case 0xFF3F: 190 | return 0xFF; 191 | 192 | case 0xFF40: 193 | return gb.video.control_byte; 194 | 195 | case 0xFF41: 196 | return gb.video.lcd_status.value(); 197 | 198 | case 0xFF42: 199 | return gb.video.scroll_y.value(); 200 | 201 | case 0xFF43: 202 | return gb.video.scroll_x.value(); 203 | 204 | case 0xFF44: 205 | return gb.video.line.value(); 206 | 207 | case 0xFF45: 208 | return gb.video.ly_compare.value(); 209 | 210 | case 0xFF46: 211 | log_warn("Attempted to read from write-only DMA Transfer/Start Address (0xFF46)"); 212 | return 0xFF; 213 | 214 | case 0xFF47: 215 | return gb.video.bg_palette.value(); 216 | 217 | case 0xFF48: 218 | return gb.video.sprite_palette_0.value(); 219 | 220 | case 0xFF49: 221 | return gb.video.sprite_palette_1.value(); 222 | 223 | case 0xFF4A: 224 | return gb.video.window_y.value(); 225 | 226 | case 0xFF4B: 227 | return gb.video.window_x.value(); 228 | 229 | /* TODO: CGB mode behaviour */ 230 | case 0xFF4C: 231 | return 0xFF; 232 | 233 | case 0xFF4D: 234 | log_unimplemented("Attempted to read from 'Prepare Speed Switch' register"); 235 | return 0x0; 236 | 237 | case 0xFF4E: 238 | case 0xFF4F: 239 | return unmapped_io_read(address); 240 | 241 | /* Disable boot rom switch */ 242 | case 0xFF50: 243 | return disable_boot_rom_switch.value(); 244 | 245 | case 0xFF51: 246 | log_unimplemented("Attempted to read from VRAM DMA Source (hi)"); 247 | return 0xFF; 248 | 249 | case 0xFF52: 250 | log_unimplemented("Attempted to read from VRAM DMA Source (lo)"); 251 | return 0xFF; 252 | 253 | case 0xFF53: 254 | log_unimplemented("Attempted to read from VRAM DMA Destination (hi)"); 255 | return 0xFF; 256 | 257 | case 0xFF54: 258 | log_unimplemented("Attempted to read from VRAM DMA Destination (lo)"); 259 | return 0xFF; 260 | 261 | case 0xFF55: 262 | log_unimplemented("Attempted to read from VRAM DMA Length/Mode/Start"); 263 | return 0xFF; 264 | 265 | case 0xFF56: 266 | log_unimplemented("Attempted to read from infrared port"); 267 | return 0xFF; 268 | 269 | case 0xFF57: 270 | case 0xFF58: 271 | case 0xFF59: 272 | case 0xFF5A: 273 | case 0xFF5B: 274 | case 0xFF5C: 275 | case 0xFF5D: 276 | case 0xFF5E: 277 | case 0xFF5F: 278 | case 0xFF60: 279 | case 0xFF61: 280 | case 0xFF62: 281 | case 0xFF63: 282 | case 0xFF64: 283 | case 0xFF65: 284 | case 0xFF66: 285 | case 0xFF67: 286 | return unmapped_io_read(address); 287 | 288 | /* TODO: Background color palette spec/index */ 289 | case 0xFF68: 290 | log_unimplemented("Attempted to read from CGB background color palette spec/index"); 291 | return 0xFF; 292 | 293 | /* TODO: Background color palette data */ 294 | case 0xFF69: 295 | log_unimplemented("Attempted to read from CGB background color data"); 296 | return 0xFF; 297 | 298 | /* TODO: OBJ color palette spec/index */ 299 | case 0xFF6A: 300 | log_unimplemented("Attempted to read from CGB OBJ color palette spec/index"); 301 | return 0xFF; 302 | 303 | /* TODO: OBJ color palette data */ 304 | case 0xFF6B: 305 | log_unimplemented("Attempted to read from CGB OBJ color palette data"); 306 | return 0xFF; 307 | 308 | /* TODO: Object priority mode */ 309 | case 0xFF6C: 310 | log_unimplemented("Attempted to read from CGB object priority mode"); 311 | return 0xFF; 312 | 313 | case 0xFF6D: 314 | case 0xFF6E: 315 | case 0xFF6F: 316 | return unmapped_io_read(address); 317 | 318 | /* TODO: CGB WRAM bank */ 319 | case 0xFF70: 320 | log_unimplemented("Attempted to read from CGB WRAM bank"); 321 | return 0xFF; 322 | 323 | /* TODO: Some undocumented registers in this range */ 324 | case 0xFF71: 325 | case 0xFF72: 326 | case 0xFF73: 327 | case 0xFF74: 328 | case 0xFF75: 329 | case 0xFF76: 330 | case 0xFF77: 331 | case 0xFF78: 332 | case 0xFF79: 333 | case 0xFF7A: 334 | case 0xFF7B: 335 | case 0xFF7C: 336 | case 0xFF7D: 337 | case 0xFF7E: 338 | case 0xFF7F: 339 | return unmapped_io_read(address); 340 | 341 | default: 342 | fatal_error("Unmapped IO address: 0x%x", address.value()); 343 | } 344 | } 345 | 346 | auto MMU::unmapped_io_read(const Address& address) const -> u8 { 347 | log_warn("Attempting to read from unused IO address 0x%x", address.value()); 348 | return 0xFF; 349 | } 350 | 351 | void MMU::write(const Address& address, const u8 byte) { 352 | if (address.in_range(0x0000, 0x7FFF)) { 353 | gb.cartridge->write(address, byte); 354 | return; 355 | } 356 | 357 | /* VRAM */ 358 | if (address.in_range(0x8000, 0x9FFF)) { 359 | gb.video.write(address.value() - 0x8000, byte); 360 | return; 361 | } 362 | 363 | /* External (cartridge) RAM */ 364 | if (address.in_range(0xA000, 0xBFFF)) { 365 | gb.cartridge->write(address, byte); 366 | return; 367 | } 368 | 369 | /* Internal work RAM */ 370 | if (address.in_range(0xC000, 0xDFFF)) { 371 | work_ram.at(address.value() - 0xC000) = byte; 372 | return; 373 | } 374 | 375 | /* Mirrored RAM */ 376 | if (address.in_range(0xE000, 0xFDFF)) { 377 | log_warn("Attempting to write to mirrored work RAM"); 378 | write(address.value() - 0x2000, byte); 379 | return; 380 | } 381 | 382 | /* OAM */ 383 | if (address.in_range(0xFE00, 0xFE9F)) { 384 | oam_ram.at(address.value() - 0xFE00) = byte; 385 | return; 386 | } 387 | 388 | if (address.in_range(0xFEA0, 0xFEFF)) { 389 | log_warn("Attempting to write to unusable memory 0x%x - 0x%x", address.value(), byte); 390 | return; 391 | } 392 | 393 | /* Mapped IO */ 394 | if (address.in_range(0xFF00, 0xFF7F)) { 395 | write_io(address, byte); 396 | return; 397 | } 398 | 399 | /* Zero Page ram */ 400 | if (address.in_range(0xFF80, 0xFFFE)) { 401 | high_ram.at(address.value() - 0xFF80) = byte; 402 | return; 403 | } 404 | 405 | /* Interrupt Enable register */ 406 | if (address == 0xFFFF) { 407 | gb.cpu.interrupt_enabled.set(byte); 408 | return; 409 | } 410 | 411 | fatal_error("Attempted to write to unmapped memory address 0x%X", address.value()); 412 | } 413 | 414 | void MMU::write_io(const Address& address, const u8 byte) { 415 | switch (address.value()) { 416 | case 0xFF00: 417 | gb.input.write(byte); 418 | return; 419 | 420 | case 0xFF01: 421 | /* Serial data transfer (SB) */ 422 | gb.serial.write(byte); 423 | return; 424 | 425 | case 0xFF02: 426 | /* Serial data transfer (SB) */ 427 | gb.serial.write_control(byte); 428 | return; 429 | 430 | case 0xFF03: 431 | return unmapped_io_write(address, byte); 432 | 433 | case 0xFF04: 434 | gb.timer.reset_divider(); 435 | return; 436 | 437 | case 0xFF05: 438 | gb.timer.set_timer(byte); 439 | return; 440 | 441 | case 0xFF06: 442 | gb.timer.set_timer_modulo(byte); 443 | return; 444 | 445 | case 0xFF07: 446 | gb.timer.set_timer_control(byte); 447 | return; 448 | 449 | case 0xFF08: 450 | case 0xFF09: 451 | case 0xFF0A: 452 | case 0xFF0B: 453 | case 0xFF0C: 454 | case 0xFF0D: 455 | case 0xFF0E: 456 | return unmapped_io_write(address, byte); 457 | 458 | case 0xFF0F: 459 | gb.cpu.interrupt_flag.set(byte); 460 | return; 461 | 462 | /* TODO: Audio - Channel 1: Tone & Sweep */ 463 | case 0xFF10: 464 | case 0xFF11: 465 | case 0xFF12: 466 | case 0xFF13: 467 | case 0xFF14: 468 | return; 469 | 470 | case 0xFF15: 471 | return unmapped_io_write(address, byte); 472 | 473 | /* TODO: Audio - Channel 2: Tone */ 474 | case 0xFF16: 475 | case 0xFF17: 476 | case 0xFF18: 477 | case 0xFF19: 478 | return; 479 | 480 | /* TODO: Audio - Channel 3: Wave Output */ 481 | case 0xFF1A: 482 | case 0xFF1B: 483 | case 0xFF1C: 484 | case 0xFF1D: 485 | case 0xFF1E: 486 | return; 487 | 488 | case 0xFF1F: 489 | return unmapped_io_write(address, byte); 490 | 491 | /* TODO: Audio - Channel 4: Noise */ 492 | case 0xFF20: 493 | case 0xFF21: 494 | case 0xFF22: 495 | case 0xFF23: 496 | return; 497 | 498 | /* TODO: Audio - Channel control/ON-OFF/Volume */ 499 | case 0xFF24: 500 | return; 501 | 502 | /* TODO: Audio - Selection of sound output terminal */ 503 | case 0xFF25: 504 | return; 505 | 506 | /* TODO: Audio - Sound on/off */ 507 | case 0xFF26: 508 | log_unimplemented("Wrote to sound on/off address 0x%x - 0x%x", address.value(), byte); 509 | return; 510 | 511 | case 0xFF27: 512 | case 0xFF28: 513 | case 0xFF29: 514 | case 0xFF2A: 515 | case 0xFF2B: 516 | case 0xFF2C: 517 | case 0xFF2D: 518 | case 0xFF2E: 519 | case 0xFF2F: 520 | return unmapped_io_write(address, byte); 521 | 522 | /* TODO: Audio - Wave pattern RAM */ 523 | case 0xFF30: 524 | case 0xFF31: 525 | case 0xFF32: 526 | case 0xFF33: 527 | case 0xFF34: 528 | case 0xFF35: 529 | case 0xFF36: 530 | case 0xFF37: 531 | case 0xFF38: 532 | case 0xFF39: 533 | case 0xFF3A: 534 | case 0xFF3B: 535 | case 0xFF3C: 536 | case 0xFF3D: 537 | case 0xFF3E: 538 | case 0xFF3F: 539 | return; 540 | 541 | /* Switch on LCD */ 542 | case 0xFF40: 543 | gb.video.control_byte = byte; 544 | return; 545 | 546 | case 0xFF41: 547 | gb.video.lcd_status.set(byte); 548 | return; 549 | 550 | /* Vertical Scroll Register */ 551 | case 0xFF42: 552 | gb.video.scroll_y.set(byte); 553 | return; 554 | 555 | /* Horizontal Scroll Register */ 556 | case 0xFF43: 557 | gb.video.scroll_x.set(byte); 558 | return; 559 | 560 | /* LY - Line Y coordinate */ 561 | case 0xFF44: 562 | /* "Writing will reset the counter */ 563 | gb.video.line.set(0x0); 564 | return; 565 | 566 | case 0xFF45: 567 | gb.video.ly_compare.set(byte); 568 | return; 569 | 570 | case 0xFF46: 571 | dma_transfer(byte); 572 | return; 573 | 574 | case 0xFF47: 575 | gb.video.bg_palette.set(byte); 576 | log_trace("Set video palette: 0x%x", byte); 577 | return; 578 | 579 | case 0xFF48: 580 | gb.video.sprite_palette_0.set(byte); 581 | log_trace("Set sprite palette 0: 0x%x", byte); 582 | return; 583 | 584 | case 0xFF49: 585 | gb.video.sprite_palette_1.set(byte); 586 | log_trace("Set sprite palette 1: 0x%x", byte); 587 | return; 588 | 589 | case 0xFF4A: 590 | gb.video.window_y.set(byte); 591 | return; 592 | 593 | case 0xFF4B: 594 | gb.video.window_x.set(byte); 595 | return; 596 | 597 | /* TODO: CGB mode behaviour */ 598 | case 0xFF4C: 599 | return; 600 | 601 | case 0xFF4D: 602 | log_unimplemented("Attempted to write to 'Prepare Speed Switch' register"); 603 | return; 604 | 605 | case 0xFF4E: 606 | case 0xFF4F: 607 | return unmapped_io_write(address, byte); 608 | 609 | /* Disable boot rom switch */ 610 | case 0xFF50: 611 | disable_boot_rom_switch.set(byte); 612 | global_logger.enable_tracing(); 613 | log_debug("Boot rom was disabled"); 614 | return; 615 | 616 | case 0xFF51: 617 | log_unimplemented("Attempted to write to VRAM DMA Source (hi)"); 618 | return; 619 | 620 | case 0xFF52: 621 | log_unimplemented("Attempted to write to VRAM DMA Source (lo)"); 622 | return; 623 | 624 | case 0xFF53: 625 | log_unimplemented("Attempted to write to VRAM DMA Destination (hi)"); 626 | return; 627 | 628 | case 0xFF54: 629 | log_unimplemented("Attempted to write to VRAM DMA Destination (lo)"); 630 | return; 631 | 632 | case 0xFF55: 633 | log_unimplemented("Attempted to write to VRAM DMA Length/Mode/Start"); 634 | return; 635 | 636 | case 0xFF56: 637 | log_unimplemented("Attempted to write to infrared port"); 638 | return; 639 | 640 | case 0xFF57: 641 | case 0xFF58: 642 | case 0xFF59: 643 | case 0xFF5A: 644 | case 0xFF5B: 645 | case 0xFF5C: 646 | case 0xFF5D: 647 | case 0xFF5E: 648 | case 0xFF5F: 649 | case 0xFF60: 650 | case 0xFF61: 651 | case 0xFF62: 652 | case 0xFF63: 653 | case 0xFF64: 654 | case 0xFF65: 655 | case 0xFF66: 656 | case 0xFF67: 657 | return unmapped_io_write(address, byte); 658 | 659 | /* TODO: Background color palette spec/index */ 660 | case 0xFF68: 661 | log_unimplemented("Attempted to write to CGB background color palette spec/index"); 662 | return; 663 | 664 | /* TODO: Background color palette data */ 665 | case 0xFF69: 666 | log_unimplemented("Attempted to write to CGB background color data"); 667 | return; 668 | 669 | /* TODO: OBJ color palette spec/index */ 670 | case 0xFF6A: 671 | log_unimplemented("Attempted to write to CGB OBJ color palette spec/index"); 672 | return; 673 | 674 | /* TODO: OBJ color palette data */ 675 | case 0xFF6B: 676 | log_unimplemented("Attempted to write to CGB OBJ color palette data"); 677 | return; 678 | 679 | /* TODO: Object priority mode */ 680 | case 0xFF6C: 681 | log_unimplemented("Attempted to write to CGB object priority mode"); 682 | return; 683 | 684 | case 0xFF6D: 685 | case 0xFF6E: 686 | case 0xFF6F: 687 | return unmapped_io_write(address, byte); 688 | 689 | /* TODO: CGB WRAM bank */ 690 | case 0xFF70: 691 | log_unimplemented("Attempted to write to CGB WRAM bank"); 692 | return; 693 | 694 | /* TODO: Some undocumented registers in this range */ 695 | case 0xFF71: 696 | case 0xFF72: 697 | case 0xFF73: 698 | case 0xFF74: 699 | case 0xFF75: 700 | case 0xFF76: 701 | case 0xFF77: 702 | case 0xFF78: 703 | case 0xFF79: 704 | case 0xFF7A: 705 | case 0xFF7B: 706 | case 0xFF7C: 707 | case 0xFF7D: 708 | case 0xFF7E: 709 | case 0xFF7F: 710 | return unmapped_io_write(address, byte); 711 | 712 | default: 713 | fatal_error("Unmapped IO address: 0x%x", address.value()); 714 | } 715 | } 716 | 717 | void MMU::unmapped_io_write(const Address& address, const u8 byte) { 718 | log_warn("Attempting to write to unused IO address 0x%x - 0x%x", address.value(), byte); 719 | } 720 | 721 | auto MMU::boot_rom_active() const -> bool { return read(0xFF50) != 0x1; } 722 | 723 | void MMU::dma_transfer(const u8 byte) { 724 | Address start_address = byte * 0x100; 725 | 726 | for (u8 i = 0x0; i <= 0x9F; i++) { 727 | Address from_address = start_address.value() + i; 728 | Address to_address = 0xFE00 + i; 729 | 730 | u8 value_at_address = read(from_address); 731 | write(to_address, value_at_address); 732 | } 733 | } 734 | -------------------------------------------------------------------------------- /src/mmu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "address.h" 4 | #include "options.h" 5 | #include "cartridge/cartridge.h" 6 | 7 | #include 8 | #include 9 | 10 | class Gameboy; 11 | 12 | class MMU { 13 | public: 14 | MMU(Gameboy& inGb, Options& options); 15 | 16 | auto read(const Address& address) const -> u8; 17 | void write(const Address& address, u8 byte); 18 | 19 | private: 20 | auto boot_rom_active() const -> bool; 21 | 22 | auto read_io(const Address& address) const -> u8; 23 | void write_io(const Address& address, u8 byte); 24 | 25 | auto unmapped_io_read(const Address& address) const -> u8; 26 | void unmapped_io_write(const Address& address, u8 byte); 27 | 28 | void dma_transfer(u8 byte); 29 | 30 | Gameboy& gb; 31 | Options& options; 32 | 33 | std::vector work_ram; 34 | std::vector oam_ram; 35 | std::vector high_ram; 36 | 37 | ByteRegister disable_boot_rom_switch; 38 | 39 | friend class Debugger; 40 | }; 41 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct Options { 4 | bool debugger = false; 5 | bool trace = false; 6 | bool disable_logs = false; 7 | bool headless = false; 8 | bool show_full_framebuffer = false; 9 | bool exit_on_infinite_jr = false; 10 | bool print_serial = false; 11 | }; 12 | -------------------------------------------------------------------------------- /src/register.cc: -------------------------------------------------------------------------------- 1 | #include "register.h" 2 | 3 | #include "util/bitwise.h" 4 | 5 | void ByteRegister::set(const u8 new_value) { 6 | val = new_value; 7 | } 8 | 9 | void ByteRegister::reset() { 10 | val = 0; 11 | } 12 | 13 | auto ByteRegister::value() const -> u8 { return val; } 14 | 15 | auto ByteRegister::check_bit(u8 bit) const -> bool { return bitwise::check_bit(val, bit); } 16 | 17 | void ByteRegister::set_bit_to(u8 bit, bool set) { 18 | val = bitwise::set_bit_to(val, bit, set); 19 | } 20 | 21 | void ByteRegister::increment() { 22 | val += 1; 23 | } 24 | void ByteRegister::decrement() { 25 | val -= 1; 26 | } 27 | 28 | auto ByteRegister::operator==(u8 other) const -> bool { return val == other; } 29 | 30 | 31 | void WordRegister::set(const u16 new_value) { 32 | val = new_value; 33 | } 34 | auto WordRegister::value() const -> u16 { return val; } 35 | 36 | auto WordRegister::low() const -> u8 { return static_cast(val); } 37 | 38 | auto WordRegister::high() const -> u8 { return static_cast((val) >> 8); } 39 | 40 | void WordRegister::increment() { 41 | val += 1; 42 | } 43 | void WordRegister::decrement() { 44 | val -= 1; 45 | } 46 | 47 | RegisterPair::RegisterPair(ByteRegister& high, ByteRegister& low) : 48 | low_byte(low), 49 | high_byte(high) 50 | { 51 | } 52 | 53 | void RegisterPair::set(const u16 word) { 54 | /* Discard the upper byte */ 55 | low_byte.set(static_cast(word)); 56 | 57 | /* Discard the lower byte */ 58 | high_byte.set(static_cast((word) >> 8)); 59 | } 60 | 61 | auto RegisterPair::low() const -> u8 { return low_byte.value(); } 62 | 63 | auto RegisterPair::high() const -> u8 { return high_byte.value(); } 64 | 65 | auto RegisterPair::value() const -> u16 { 66 | return bitwise::compose_bytes(high_byte.value(), low_byte.value()); 67 | } 68 | 69 | void RegisterPair::increment() { 70 | set(value() + 1); 71 | } 72 | 73 | void RegisterPair::decrement() { 74 | set(value() - 1); 75 | } 76 | 77 | 78 | void FlagRegister::set(const u8 new_value) { 79 | val = new_value & 0xF0; 80 | } 81 | 82 | void FlagRegister::set_flag_zero(bool set) { 83 | set_bit_to(7, set); 84 | } 85 | 86 | void FlagRegister::set_flag_subtract(bool set) { 87 | set_bit_to(6, set); 88 | } 89 | 90 | void FlagRegister::set_flag_half_carry(bool set) { 91 | set_bit_to(5, set); 92 | } 93 | 94 | void FlagRegister::set_flag_carry(bool set) { 95 | set_bit_to(4, set); 96 | } 97 | 98 | auto FlagRegister::flag_zero() const -> bool { return check_bit(7); } 99 | 100 | auto FlagRegister::flag_subtract() const -> bool { return check_bit(6); } 101 | 102 | auto FlagRegister::flag_half_carry() const -> bool { return check_bit(5); } 103 | 104 | auto FlagRegister::flag_carry() const -> bool { return check_bit(4); } 105 | 106 | auto FlagRegister::flag_zero_value() const -> u8 { return static_cast(flag_zero() ? 1 : 0); } 107 | 108 | auto FlagRegister::flag_subtract_value() const -> u8 { 109 | return static_cast(flag_subtract() ? 1 : 0); 110 | } 111 | 112 | auto FlagRegister::flag_half_carry_value() const -> u8 { 113 | return static_cast(flag_half_carry() ? 1 : 0); 114 | } 115 | 116 | auto FlagRegister::flag_carry_value() const -> u8 { return static_cast(flag_carry() ? 1 : 0); } 117 | -------------------------------------------------------------------------------- /src/register.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "definitions.h" 4 | 5 | class ByteRegister : Noncopyable { 6 | public: 7 | ByteRegister() = default; 8 | virtual ~ByteRegister() = default; 9 | 10 | virtual void set(u8 new_value); 11 | void reset(); 12 | auto value() const -> u8; 13 | 14 | auto check_bit(u8 bit) const -> bool; 15 | void set_bit_to(u8 bit, bool set); 16 | 17 | void increment(); 18 | void decrement(); 19 | 20 | auto operator==(u8 other) const -> bool; 21 | 22 | protected: 23 | u8 val = 0x0; 24 | }; 25 | 26 | class FlagRegister : public ByteRegister { 27 | public: 28 | FlagRegister() = default; 29 | 30 | /* Specialise behaviour for the flag register 'f'. 31 | * (its lower nibble is always 0s */ 32 | void set(u8 new_value) override; 33 | 34 | void set_flag_zero(bool set); 35 | void set_flag_subtract(bool set); 36 | void set_flag_half_carry(bool set); 37 | void set_flag_carry(bool set); 38 | 39 | auto flag_zero() const -> bool; 40 | auto flag_subtract() const -> bool; 41 | auto flag_half_carry() const -> bool; 42 | auto flag_carry() const -> bool; 43 | 44 | auto flag_zero_value() const -> u8; 45 | auto flag_subtract_value() const -> u8; 46 | auto flag_half_carry_value() const -> u8; 47 | auto flag_carry_value() const -> u8; 48 | }; 49 | 50 | class WordValue : Noncopyable { 51 | public: 52 | WordValue() = default; 53 | virtual ~WordValue() = default; 54 | 55 | virtual void set(u16 new_value) = 0; 56 | 57 | virtual auto value() const -> u16 = 0; 58 | 59 | virtual auto low() const -> u8 = 0; 60 | virtual auto high() const -> u8 = 0; 61 | }; 62 | 63 | class WordRegister : public WordValue { 64 | public: 65 | WordRegister() = default; 66 | 67 | void set(u16 new_value) override; 68 | 69 | auto value() const -> u16 override; 70 | 71 | auto low() const -> u8 override; 72 | auto high() const -> u8 override; 73 | 74 | void increment(); 75 | void decrement(); 76 | 77 | private: 78 | u16 val = 0x0; 79 | }; 80 | 81 | class RegisterPair : public WordValue { 82 | public: 83 | RegisterPair(ByteRegister& high, ByteRegister& low); 84 | 85 | void set(u16 word) override; 86 | 87 | auto value() const -> u16 override; 88 | 89 | auto low() const -> u8 override; 90 | auto high() const -> u8 override; 91 | 92 | void increment(); 93 | void decrement(); 94 | 95 | private: 96 | ByteRegister& low_byte; 97 | ByteRegister& high_byte; 98 | }; 99 | -------------------------------------------------------------------------------- /src/serial.cc: -------------------------------------------------------------------------------- 1 | #include "serial.h" 2 | 3 | #include "util/bitwise.h" 4 | #include "util/log.h" 5 | 6 | #include 7 | 8 | auto Serial::read() const -> u8 { return data; } 9 | 10 | void Serial::write(const u8 byte) { 11 | data = byte; 12 | } 13 | 14 | void Serial::write_control(const u8 byte) const { 15 | if (bitwise::check_bit(byte, 7) && options.print_serial) { 16 | printf("%c", data); 17 | fflush(stdout); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/serial.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "definitions.h" 4 | #include "options.h" 5 | 6 | class Serial { 7 | public: 8 | Serial(Options& inOptions) : options(inOptions) {} 9 | 10 | auto read() const -> u8; 11 | void write(u8 byte); 12 | void write_control(u8 byte) const; 13 | 14 | private: 15 | Options& options; 16 | 17 | u8 data; 18 | }; 19 | -------------------------------------------------------------------------------- /src/timer.cc: -------------------------------------------------------------------------------- 1 | #include "timer.h" 2 | 3 | #include "definitions.h" 4 | #include "gameboy.h" 5 | #include "cpu/cpu.h" 6 | #include "util/bitwise.h" 7 | 8 | const uint CLOCKS_PER_CYCLE = 4; 9 | 10 | Timer::Timer(Gameboy& _gb) : gb(_gb) {} 11 | 12 | void Timer::tick(uint cycles) { 13 | u8 new_divider = static_cast(divider.value() + cycles); 14 | divider.set(new_divider); 15 | 16 | clocks += cycles * CLOCKS_PER_CYCLE; 17 | 18 | auto timer_is_on = timer_control.check_bit(2); 19 | if (timer_is_on == 0) { return; } 20 | 21 | auto clock_limit = clocks_needed_to_increment(); 22 | 23 | if (clocks >= clock_limit) { 24 | clocks = clocks % clock_limit; 25 | 26 | u8 old_timer_counter = timer_counter.value(); 27 | timer_counter.increment(); 28 | 29 | if (timer_counter.value() < old_timer_counter) { 30 | gb.cpu.interrupt_flag.set_bit_to(2, true); 31 | timer_counter.set(timer_modulo.value()); 32 | } 33 | } 34 | } 35 | 36 | auto Timer::get_divider() const -> u8 { return divider.value(); } 37 | 38 | auto Timer::get_timer() const -> u8 { return timer_counter.value(); } 39 | 40 | auto Timer::get_timer_modulo() const -> u8 { return timer_modulo.value(); } 41 | 42 | // Only the bottom three bits of this register are usable 43 | auto Timer::get_timer_control() const -> u8 { return timer_control.value() & 0x3; } 44 | 45 | void Timer::reset_divider() { 46 | divider.set(0x0); 47 | } 48 | 49 | void Timer::set_timer(u8 value) { 50 | timer_counter.set(value); 51 | } 52 | 53 | void Timer::set_timer_modulo(u8 value) { 54 | timer_modulo.set(value); 55 | } 56 | 57 | void Timer::set_timer_control(u8 value) { 58 | timer_control.set(value); 59 | } 60 | 61 | uint Timer::clocks_needed_to_increment() { 62 | using bitwise::check_bit; 63 | 64 | switch (get_timer_control()) { 65 | case 0: return CLOCK_RATE / 4096; 66 | case 1: return CLOCK_RATE / 262144; 67 | case 2: return CLOCK_RATE / 65536; 68 | case 3: return CLOCK_RATE / 16384; 69 | default: fatal_error("Invalid calculation in timer"); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "definitions.h" 4 | #include "register.h" 5 | 6 | class Gameboy; 7 | 8 | class Timer { 9 | public: 10 | Timer(Gameboy& inGb); 11 | 12 | void tick(uint cycles); 13 | 14 | auto get_divider() const -> u8; 15 | auto get_timer() const -> u8; 16 | auto get_timer_modulo() const -> u8; 17 | auto get_timer_control() const -> u8; 18 | 19 | void reset_divider(); 20 | void set_timer(u8 value); 21 | void set_timer_modulo(u8 value); 22 | void set_timer_control(u8 value); 23 | 24 | private: 25 | uint clocks_needed_to_increment(); 26 | 27 | uint clocks = 0; 28 | 29 | Gameboy& gb; 30 | 31 | ByteRegister divider; 32 | ByteRegister timer_counter; 33 | 34 | ByteRegister timer_modulo; 35 | ByteRegister timer_control; 36 | }; 37 | -------------------------------------------------------------------------------- /src/util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sources( 2 | files.cc 3 | log.cc 4 | string_utils.cc 5 | ) 6 | -------------------------------------------------------------------------------- /src/util/bitwise.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../definitions.h" 4 | #include "log.h" 5 | 6 | namespace bitwise { 7 | 8 | inline auto compose_bits(const u8 high, const u8 low) -> u8 { 9 | return static_cast(high << 1 | low); 10 | } 11 | 12 | inline auto compose_nibbles(const u8 high, const u8 low) -> u8 { 13 | return static_cast((high << 4) | low); 14 | } 15 | 16 | inline auto compose_bytes(const u8 high, const u8 low) -> u16 { 17 | return static_cast((high << 8) | low); 18 | } 19 | 20 | inline auto check_bit(const u8 value, const u8 bit) -> bool { return (value & (1 << bit)) != 0; } 21 | 22 | inline auto bit_value(const u8 value, const u8 bit) -> u8 { return (value >> bit) & 1; } 23 | 24 | inline auto set_bit(const u8 value, const u8 bit) -> u8 { 25 | auto value_set = value | (1 << bit); 26 | return static_cast(value_set); 27 | } 28 | 29 | inline auto clear_bit(const u8 value, const u8 bit) -> u8 { 30 | auto value_cleared = value & ~(1 << bit); 31 | return static_cast(value_cleared); 32 | } 33 | 34 | inline auto set_bit_to(const u8 value, const u8 bit, bool bit_on) -> u8 { 35 | return bit_on 36 | ? set_bit(value, bit) 37 | : clear_bit(value, bit); 38 | } 39 | 40 | } // namespace bitwise 41 | -------------------------------------------------------------------------------- /src/util/files.cc: -------------------------------------------------------------------------------- 1 | #include "files.h" 2 | 3 | #include "log.h" 4 | #include 5 | 6 | auto read_bytes(const std::string& filename) -> std::vector { 7 | using std::ifstream; 8 | using std::ios; 9 | 10 | ifstream stream(filename.c_str(), ios::binary|ios::ate); 11 | 12 | if (!stream.good()) { 13 | fatal_error("Cannot read from file: %s", filename.c_str()); 14 | } 15 | 16 | ifstream::pos_type position = stream.tellg(); 17 | auto file_size = static_cast(position); 18 | 19 | std::vector file_contents(file_size); 20 | 21 | stream.seekg(0, ios::beg); 22 | stream.read(&file_contents[0], static_cast(position)); 23 | stream.close(); 24 | 25 | auto data = std::vector(file_contents.begin(), file_contents.end()); 26 | 27 | return data; 28 | } 29 | -------------------------------------------------------------------------------- /src/util/files.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "../definitions.h" 7 | 8 | auto read_bytes(const std::string& filename) -> std::vector; 9 | -------------------------------------------------------------------------------- /src/util/log.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Alex Smith 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | #include "log.h" 18 | 19 | #include "string_utils.h" 20 | 21 | #include 22 | 23 | Logger global_logger; 24 | const char* COLOR_TRACE = "\033[1;30m"; 25 | const char* COLOR_DEBUG = "\033[1;37m"; 26 | const char* COLOR_UNIMPLEMENTED = "\033[1;35m"; 27 | const char* COLOR_INFO = "\033[1;34m"; 28 | const char* COLOR_WARNING = "\033[1;33m"; 29 | const char* COLOR_ERROR = "\033[1;31m"; 30 | const char* COLOR_RESET = "\033[0m"; 31 | 32 | void Logger::log(LogLevel level, const char* fmt, ...) { 33 | if (!should_log(level)) { 34 | return; 35 | } 36 | 37 | va_list args; 38 | va_start(args, fmt); 39 | std::string msg = str_format(fmt, args); 40 | va_end(args); 41 | 42 | fprintf((level < LogLevel::Error) ? stdout : stderr, 43 | "%s| %s%s\n", 44 | level_color(level), COLOR_RESET, msg.c_str()); 45 | } 46 | 47 | void Logger::set_level(LogLevel level) { 48 | current_level = level; 49 | } 50 | 51 | void Logger::enable_tracing() { 52 | tracing_enabled = true; 53 | } 54 | 55 | auto Logger::should_log(LogLevel level) const -> bool { 56 | if (!tracing_enabled && level == LogLevel::Trace) { return false; } 57 | 58 | return enabled && (current_level <= level); 59 | } 60 | 61 | inline auto Logger::level_color(LogLevel level) -> const char* { 62 | switch (level) { 63 | case LogLevel::Trace: 64 | return COLOR_TRACE; 65 | case LogLevel::Debug: 66 | return COLOR_DEBUG; 67 | case LogLevel::Unimplemented: 68 | return COLOR_UNIMPLEMENTED; 69 | case LogLevel::Info: 70 | return COLOR_INFO; 71 | case LogLevel::Warning: 72 | return COLOR_WARNING; 73 | case LogLevel::Error: 74 | return COLOR_ERROR; 75 | } 76 | } 77 | 78 | void log_set_level(LogLevel level) { 79 | global_logger.set_level(level); 80 | } 81 | -------------------------------------------------------------------------------- /src/util/log.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* 4 | * Copyright (C) 2015 Alex Smith 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | 19 | #include 20 | 21 | enum class LogLevel { 22 | Trace, 23 | Debug, 24 | Unimplemented, 25 | Info, 26 | Warning, 27 | Error, 28 | }; 29 | 30 | class Logger { 31 | public: 32 | Logger() = default; 33 | 34 | void log(LogLevel level, const char* fmt, ...); 35 | void set_level(LogLevel level); 36 | 37 | void enable_tracing(); 38 | 39 | private: 40 | auto should_log(LogLevel level) const -> bool; 41 | static auto level_color(LogLevel level) -> const char*; 42 | 43 | LogLevel current_level = LogLevel::Debug; 44 | bool enabled = true; 45 | bool tracing_enabled = false; 46 | }; 47 | 48 | extern Logger global_logger; 49 | extern const char* COLOR_TRACE; 50 | extern const char* COLOR_DEBUG; 51 | extern const char* COLOR_UNIMPLEMENTED; 52 | extern const char* COLOR_INFO; 53 | extern const char* COLOR_WARNING; 54 | extern const char* COLOR_ERROR; 55 | extern const char* COLOR_RESET; 56 | 57 | #pragma clang diagnostic push 58 | #pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments" 59 | 60 | #define log_trace(...) global_logger.log(LogLevel::Trace, ##__VA_ARGS__); 61 | #define log_debug(...) global_logger.log(LogLevel::Debug, ##__VA_ARGS__); 62 | #define log_unimplemented(...) global_logger.log(LogLevel::Unimplemented, ##__VA_ARGS__); 63 | #define log_info(...) global_logger.log(LogLevel::Info, ##__VA_ARGS__); 64 | #define log_warn(...) global_logger.log(LogLevel::Warning, ##__VA_ARGS__); 65 | #define log_error(...) global_logger.log(LogLevel::Error, ##__VA_ARGS__); 66 | 67 | #pragma clang diagnostic pop 68 | 69 | extern void log_set_level(LogLevel level); 70 | -------------------------------------------------------------------------------- /src/util/string_utils.cc: -------------------------------------------------------------------------------- 1 | #include "string_utils.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | using std::string; 9 | using std::vector; 10 | 11 | auto str_format(const char* fmt, va_list args) -> string { 12 | char buf[8192]; 13 | vsnprintf(buf, 8192, fmt, args); 14 | return string(buf); 15 | } 16 | 17 | auto str_format(const char* fmt, ...) -> string { 18 | char buf[8192]; 19 | va_list args; 20 | 21 | va_start(args, fmt); 22 | vsnprintf(buf, 8192, fmt, args); 23 | va_end(args); 24 | 25 | return string(buf); 26 | } 27 | 28 | auto split(const string& str, char delim) -> vector { 29 | vector elems; 30 | 31 | std::stringstream stream(str); 32 | string item; 33 | while (getline(stream, item, delim)) { 34 | elems.push_back(item); 35 | } 36 | 37 | return elems; 38 | } 39 | -------------------------------------------------------------------------------- /src/util/string_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | auto str_format(const char* fmt, va_list args) -> std::string; 7 | auto str_format(const char* fmt, ...) -> std::string; 8 | 9 | auto split(const std::string& str, char delim = ' ') -> std::vector; 10 | -------------------------------------------------------------------------------- /src/video/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_sources( 2 | color.cc 3 | framebuffer.cc 4 | tile.cc 5 | video.cc 6 | ) 7 | -------------------------------------------------------------------------------- /src/video/color.cc: -------------------------------------------------------------------------------- 1 | #include "color.h" 2 | #include "../util/log.h" 3 | 4 | auto get_color(u8 pixel_value) -> GBColor { 5 | switch (pixel_value) { 6 | case 0: 7 | return GBColor::Color0; 8 | case 1: 9 | return GBColor::Color1; 10 | case 2: 11 | return GBColor::Color2; 12 | case 3: 13 | return GBColor::Color3; 14 | default: 15 | fatal_error("Invalid color value: %d", pixel_value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/video/color.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../definitions.h" 4 | 5 | auto get_color(u8 pixel_value) -> GBColor; 6 | -------------------------------------------------------------------------------- /src/video/framebuffer.cc: -------------------------------------------------------------------------------- 1 | #include "framebuffer.h" 2 | 3 | FrameBuffer::FrameBuffer(uint _width, uint _height) : 4 | width(_width), 5 | height(_height), 6 | buffer(width*height, Color::White) 7 | { 8 | } 9 | 10 | void FrameBuffer::set_pixel(uint x, uint y, Color color) { 11 | buffer[pixel_index(x, y)] = color; 12 | } 13 | 14 | auto FrameBuffer::get_pixel(uint x, uint y) const -> Color { return buffer.at(pixel_index(x, y)); } 15 | 16 | inline auto FrameBuffer::pixel_index(uint x, uint y) const -> uint { return (y * width) + x; } 17 | 18 | void FrameBuffer::reset() { 19 | for (uint i = 0; i < width * height; i++) { 20 | buffer[i] = Color::White; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/video/framebuffer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../definitions.h" 4 | 5 | #include 6 | 7 | class FrameBuffer { 8 | public: 9 | FrameBuffer(uint width, uint height); 10 | 11 | void set_pixel(uint x, uint y, Color color); 12 | auto get_pixel(uint x, uint y) const -> Color; 13 | 14 | void reset(); 15 | 16 | private: 17 | uint width; 18 | uint height; 19 | 20 | auto pixel_index(uint x, uint y) const -> uint; 21 | 22 | std::vector buffer; 23 | }; 24 | -------------------------------------------------------------------------------- /src/video/tile.cc: -------------------------------------------------------------------------------- 1 | #include "tile.h" 2 | 3 | #include "color.h" 4 | #include "../util/bitwise.h" 5 | 6 | using bitwise::bit_value; 7 | 8 | Tile::Tile(Address& tile_address, MMU& mmu, uint size_multiplier) { 9 | /* Set the whole framebuffer to be black */ 10 | for (uint x = 0; x < TILE_WIDTH_PX; x++) { 11 | for (uint y = 0; y < TILE_HEIGHT_PX * size_multiplier; y++) { 12 | buffer[pixel_index(x, y)] = GBColor::Color0; 13 | } 14 | } 15 | 16 | for (uint tile_line = 0; tile_line < TILE_HEIGHT_PX * size_multiplier; tile_line++) { 17 | /* 2 (bytes per line of pixels) * y (lines) */ 18 | uint index_into_tile = 2 * tile_line; 19 | Address line_start = tile_address + index_into_tile; 20 | 21 | u8 pixels_1 = mmu.read(line_start); 22 | u8 pixels_2 = mmu.read(line_start + 1); 23 | 24 | std::vector pixel_line = get_pixel_line(pixels_1, pixels_2); 25 | 26 | for (uint x = 0; x < TILE_WIDTH_PX; x++) { 27 | buffer[pixel_index(x, tile_line)] = get_color(pixel_line[x]); 28 | } 29 | } 30 | } 31 | 32 | auto Tile::get_pixel(uint x, uint y) const -> GBColor { return buffer[pixel_index(x, y)]; } 33 | 34 | auto Tile::get_pixel_line(u8 byte1, u8 byte2) -> std::vector { 35 | using bitwise::bit_value; 36 | 37 | std::vector pixel_line; 38 | for (u8 i = 0; i < 8; i++) { 39 | u8 color_value = static_cast((bit_value(byte2, 7-i) << 1) | bit_value(byte1, 7-i)); 40 | pixel_line.push_back(color_value); 41 | } 42 | 43 | return pixel_line; 44 | } 45 | 46 | inline auto Tile::pixel_index(uint x, uint y) -> uint { return (y * TILE_HEIGHT_PX) + x; } 47 | -------------------------------------------------------------------------------- /src/video/tile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../address.h" 4 | #include "../definitions.h" 5 | #include "../mmu.h" 6 | 7 | #include 8 | 9 | const uint TILES_PER_LINE = 32; 10 | const uint TILE_HEIGHT_PX = 8; 11 | const uint TILE_WIDTH_PX = 8; 12 | 13 | const Address TILE_SET_ZERO_ADDRESS = 0x8000; 14 | const Address TILE_SET_ONE_ADDRESS = 0x8800; 15 | 16 | const Address TILE_MAP_ZERO_ADDRESS = 0x9800; 17 | const Address TILE_MAP_ONE_ADDRESS = 0x9C00; 18 | 19 | /* A single tile contains 8 lines, each of which is two bytes */ 20 | const uint TILE_BYTES = 2 * 8; 21 | 22 | const uint SPRITE_BYTES = 4; 23 | 24 | class Tile { 25 | public: 26 | Tile(Address& address, MMU& mmu, uint size_multiplier = 1); 27 | 28 | auto get_pixel(uint x, uint y) const -> GBColor; 29 | 30 | private: 31 | static auto pixel_index(uint x, uint y) -> uint; 32 | static auto get_pixel_line(u8 byte1, u8 byte2) -> std::vector; 33 | 34 | std::array buffer; 35 | }; 36 | -------------------------------------------------------------------------------- /src/video/video.cc: -------------------------------------------------------------------------------- 1 | #include "video.h" 2 | 3 | #include "color.h" 4 | #include "../gameboy.h" 5 | #include "../cpu/cpu.h" 6 | 7 | #include "../util/bitwise.h" 8 | #include "../util/log.h" 9 | 10 | using bitwise::check_bit; 11 | 12 | Video::Video(Gameboy& inGb, Options& inOptions) : 13 | gb(inGb), 14 | buffer(GAMEBOY_WIDTH, GAMEBOY_HEIGHT), 15 | background_map(BG_MAP_SIZE, BG_MAP_SIZE) 16 | { 17 | video_ram = std::vector(0x4000); 18 | } 19 | 20 | u8 Video::read(const Address& address) { 21 | return video_ram.at(address.value()); 22 | } 23 | 24 | void Video::write(const Address& address, u8 value) { 25 | video_ram.at(address.value()) = value; 26 | } 27 | 28 | void Video::tick(Cycles cycles) { 29 | cycle_counter += cycles.cycles; 30 | 31 | switch (current_mode) { 32 | case VideoMode::ACCESS_OAM: 33 | if (cycle_counter >= CLOCKS_PER_SCANLINE_OAM) { 34 | cycle_counter = cycle_counter % CLOCKS_PER_SCANLINE_OAM; 35 | lcd_status.set_bit_to(1, true); 36 | lcd_status.set_bit_to(0, true); 37 | current_mode = VideoMode::ACCESS_VRAM; 38 | } 39 | break; 40 | case VideoMode::ACCESS_VRAM: 41 | if (cycle_counter >= CLOCKS_PER_SCANLINE_VRAM) { 42 | cycle_counter = cycle_counter % CLOCKS_PER_SCANLINE_VRAM; 43 | current_mode = VideoMode::HBLANK; 44 | 45 | bool hblank_interrupt = bitwise::check_bit(lcd_status.value(), 3); 46 | 47 | if (hblank_interrupt) { 48 | gb.cpu.interrupt_flag.set_bit_to(1, true); 49 | } 50 | 51 | bool ly_coincidence_interrupt = bitwise::check_bit(lcd_status.value(), 6); 52 | bool ly_coincidence = ly_compare.value() == line.value(); 53 | if (ly_coincidence_interrupt && ly_coincidence) { 54 | gb.cpu.interrupt_flag.set_bit_to(1, true); 55 | } 56 | lcd_status.set_bit_to(2, ly_coincidence); 57 | 58 | lcd_status.set_bit_to(1, false); 59 | lcd_status.set_bit_to(0, false); 60 | } 61 | break; 62 | case VideoMode::HBLANK: 63 | if (cycle_counter >= CLOCKS_PER_HBLANK) { 64 | 65 | write_scanline(line.value()); 66 | line.increment(); 67 | 68 | cycle_counter = cycle_counter % CLOCKS_PER_HBLANK; 69 | 70 | /* Line 145 (index 144) is the first line of VBLANK */ 71 | if (line == 144) { 72 | current_mode = VideoMode::VBLANK; 73 | lcd_status.set_bit_to(1, false); 74 | lcd_status.set_bit_to(0, true); 75 | gb.cpu.interrupt_flag.set_bit_to(0, true); 76 | } else { 77 | lcd_status.set_bit_to(1, true); 78 | lcd_status.set_bit_to(0, false); 79 | current_mode = VideoMode::ACCESS_OAM; 80 | } 81 | } 82 | break; 83 | case VideoMode::VBLANK: 84 | if (cycle_counter >= CLOCKS_PER_SCANLINE) { 85 | line.increment(); 86 | 87 | cycle_counter = cycle_counter % CLOCKS_PER_SCANLINE; 88 | 89 | /* Line 155 (index 154) is the last line */ 90 | if (line == 154) { 91 | write_sprites(); 92 | draw(); 93 | buffer.reset(); 94 | line.reset(); 95 | current_mode = VideoMode::ACCESS_OAM; 96 | lcd_status.set_bit_to(1, true); 97 | lcd_status.set_bit_to(0, false); 98 | }; 99 | } 100 | break; 101 | } 102 | } 103 | 104 | auto Video::display_enabled() const -> bool { return check_bit(control_byte, 7); } 105 | auto Video::window_tile_map() const -> bool { return check_bit(control_byte, 6); } 106 | auto Video::window_enabled() const -> bool { return check_bit(control_byte, 5); } 107 | auto Video::bg_window_tile_data() const -> bool { return check_bit(control_byte, 4); } 108 | auto Video::bg_tile_map_display() const -> bool { return check_bit(control_byte, 3); } 109 | auto Video::sprite_size() const -> bool { return check_bit(control_byte, 2); } 110 | auto Video::sprites_enabled() const -> bool { return check_bit(control_byte, 1); } 111 | auto Video::bg_enabled() const -> bool { return check_bit(control_byte, 0); } 112 | 113 | void Video::write_scanline(u8 current_line) { 114 | if (!display_enabled()) { return; } 115 | 116 | if (bg_enabled() && !debug_disable_background) { 117 | draw_bg_line(current_line); 118 | } 119 | 120 | if (window_enabled() && !debug_disable_window) { 121 | draw_window_line(current_line); 122 | } 123 | } 124 | 125 | void Video::write_sprites() { 126 | if (!sprites_enabled() || debug_disable_sprites) { return; } 127 | 128 | for (uint sprite_n = 0; sprite_n < 40; sprite_n++) { 129 | draw_sprite(sprite_n); 130 | } 131 | } 132 | 133 | void Video::draw_bg_line(uint current_line) { 134 | /* Note: tileset two uses signed numbering to share half the tiles with tileset 1 */ 135 | bool use_tile_set_zero = bg_window_tile_data(); 136 | bool use_tile_map_zero = !bg_tile_map_display(); 137 | 138 | Palette palette = load_palette(bg_palette); 139 | 140 | Address tile_set_address = use_tile_set_zero 141 | ? TILE_SET_ZERO_ADDRESS 142 | : TILE_SET_ONE_ADDRESS; 143 | 144 | Address tile_map_address = use_tile_map_zero 145 | ? TILE_MAP_ZERO_ADDRESS 146 | : TILE_MAP_ONE_ADDRESS; 147 | 148 | /* The pixel row we're drawing on the screen is constant since we're only 149 | * drawing a single line */ 150 | uint screen_y = current_line; 151 | 152 | for (uint screen_x = 0; screen_x < GAMEBOY_WIDTH; screen_x++) { 153 | /* Work out the position of the pixel in the framebuffer */ 154 | uint scrolled_x = screen_x + scroll_x.value(); 155 | uint scrolled_y = screen_y + scroll_y.value(); 156 | 157 | /* Work out the index of the pixel in the full background map */ 158 | uint bg_map_x = scrolled_x % BG_MAP_SIZE; 159 | uint by_map_y = scrolled_y % BG_MAP_SIZE; 160 | 161 | /* Work out which tile of the bg_map this pixel is in, and the index of that tile 162 | * in the array of all tiles */ 163 | uint tile_x = bg_map_x / TILE_WIDTH_PX; 164 | uint tile_y = by_map_y / TILE_HEIGHT_PX; 165 | 166 | /* Work out which specific (x,y) inside that tile we're going to render */ 167 | uint tile_pixel_x = bg_map_x % TILE_WIDTH_PX; 168 | uint tile_pixel_y = by_map_y % TILE_HEIGHT_PX; 169 | 170 | /* Work out the address of the tile ID from the tile map */ 171 | uint tile_index = tile_y * TILES_PER_LINE + tile_x; 172 | Address tile_id_address = tile_map_address + tile_index; 173 | 174 | /* Grab the ID of the tile we'll get data from in the tile map */ 175 | u8 tile_id = gb.mmu.read(tile_id_address); 176 | 177 | /* Calculate the offset from the start of the tile data memory where 178 | * the data for our tile lives */ 179 | uint tile_data_mem_offset = use_tile_set_zero 180 | ? tile_id * TILE_BYTES 181 | : (static_cast(tile_id) + 128) * TILE_BYTES; 182 | 183 | /* Calculate the extra offset to the data for the line of pixels we 184 | * are rendering from. 185 | * 2 (bytes per line of pixels) * y (lines) */ 186 | uint tile_data_line_offset = tile_pixel_y * 2; 187 | 188 | Address tile_line_data_start_address = tile_set_address + tile_data_mem_offset + tile_data_line_offset; 189 | 190 | /* FIXME: We fetch the full line of pixels for each pixel in the tile 191 | * we render. This could be altered to work in a way that avoids re-fetching 192 | * for a more performant renderer */ 193 | u8 pixels_1 = gb.mmu.read(tile_line_data_start_address); 194 | u8 pixels_2 = gb.mmu.read(tile_line_data_start_address + 1); 195 | 196 | GBColor pixel_color = get_pixel_from_line(pixels_1, pixels_2, tile_pixel_x); 197 | Color screen_color = get_color_from_palette(pixel_color, palette); 198 | 199 | buffer.set_pixel(screen_x, screen_y, screen_color); 200 | } 201 | } 202 | 203 | void Video::draw_window_line(uint current_line) { 204 | /* Note: tileset two uses signed numbering to share half the tiles with tileset 1 */ 205 | bool use_tile_set_zero = bg_window_tile_data(); 206 | bool use_tile_map_zero = !window_tile_map(); 207 | 208 | Palette palette = load_palette(bg_palette); 209 | 210 | Address tile_set_address = use_tile_set_zero 211 | ? TILE_SET_ZERO_ADDRESS 212 | : TILE_SET_ONE_ADDRESS; 213 | 214 | Address tile_map_address = use_tile_map_zero 215 | ? TILE_MAP_ZERO_ADDRESS 216 | : TILE_MAP_ONE_ADDRESS; 217 | 218 | uint screen_y = current_line; 219 | uint scrolled_y = screen_y - window_y.value(); 220 | 221 | if (scrolled_y >= GAMEBOY_HEIGHT) { return; } 222 | // if (!is_on_screen_y(scrolled_y)) { return; } 223 | 224 | for (uint screen_x = 0; screen_x < GAMEBOY_WIDTH; screen_x++) { 225 | /* Work out the position of the pixel in the framebuffer */ 226 | uint scrolled_x = screen_x + window_x.value() - 7; 227 | 228 | /* Work out which tile of the bg_map this pixel is in, and the index of that tile 229 | * in the array of all tiles */ 230 | uint tile_x = scrolled_x / TILE_WIDTH_PX; 231 | uint tile_y = scrolled_y / TILE_HEIGHT_PX; 232 | 233 | /* Work out which specific (x,y) inside that tile we're going to render */ 234 | uint tile_pixel_x = scrolled_x % TILE_WIDTH_PX; 235 | uint tile_pixel_y = scrolled_y % TILE_HEIGHT_PX; 236 | 237 | /* Work out the address of the tile ID from the tile map */ 238 | uint tile_index = tile_y * TILES_PER_LINE + tile_x; 239 | Address tile_id_address = tile_map_address + tile_index; 240 | 241 | /* Grab the ID of the tile we'll get data from in the tile map */ 242 | u8 tile_id = gb.mmu.read(tile_id_address); 243 | 244 | /* Calculate the offset from the start of the tile data memory where 245 | * the data for our tile lives */ 246 | uint tile_data_mem_offset = use_tile_set_zero 247 | ? tile_id * TILE_BYTES 248 | : (static_cast(tile_id) + 128) * TILE_BYTES; 249 | 250 | /* Calculate the extra offset to the data for the line of pixels we 251 | * are rendering from. 252 | * 2 (bytes per line of pixels) * y (lines) */ 253 | uint tile_data_line_offset = tile_pixel_y * 2; 254 | 255 | Address tile_line_data_start_address = tile_set_address + tile_data_mem_offset + tile_data_line_offset; 256 | 257 | /* FIXME: We fetch the full line of pixels for each pixel in the tile 258 | * we render. This could be altered to work in a way that avoids re-fetching 259 | * for a more performant renderer */ 260 | u8 pixels_1 = gb.mmu.read(tile_line_data_start_address); 261 | u8 pixels_2 = gb.mmu.read(tile_line_data_start_address + 1); 262 | 263 | GBColor pixel_color = get_pixel_from_line(pixels_1, pixels_2, tile_pixel_x); 264 | Color screen_color = get_color_from_palette(pixel_color, palette); 265 | 266 | buffer.set_pixel(screen_x, screen_y, screen_color); 267 | } 268 | } 269 | 270 | void Video::draw_sprite(const uint sprite_n) { 271 | using bitwise::check_bit; 272 | 273 | /* Each sprite is represented by 4 bytes, or 8 bytes in 8x16 mode */ 274 | Address offset_in_oam = sprite_n * SPRITE_BYTES; 275 | 276 | Address oam_start = 0xFE00 + offset_in_oam.value(); 277 | u8 sprite_y = gb.mmu.read(oam_start); 278 | u8 sprite_x = gb.mmu.read(oam_start + 1); 279 | 280 | /* If the sprite would be drawn offscreen, don't draw it */ 281 | if (sprite_y == 0 || sprite_y >= 160) { return; } 282 | if (sprite_x == 0 || sprite_x >= 168) { return; } 283 | 284 | uint sprite_size_multiplier = sprite_size() 285 | ? 2 : 1; 286 | 287 | /* Sprites are always taken from the first tileset */ 288 | Address tile_set_location = TILE_SET_ZERO_ADDRESS; 289 | 290 | u8 pattern_n = gb.mmu.read(oam_start + 2); 291 | u8 sprite_attrs = gb.mmu.read(oam_start + 3); 292 | 293 | /* Bits 0-3 are used only for CGB */ 294 | bool use_palette_1 = check_bit(sprite_attrs, 4); 295 | bool flip_x = check_bit(sprite_attrs, 5); 296 | bool flip_y = check_bit(sprite_attrs, 6); 297 | bool obj_behind_bg = check_bit(sprite_attrs, 7); 298 | 299 | Palette palette = use_palette_1 300 | ? load_palette(sprite_palette_1) 301 | : load_palette(sprite_palette_0); 302 | 303 | uint tile_offset = pattern_n * TILE_BYTES; 304 | 305 | Address pattern_address = tile_set_location + tile_offset; 306 | 307 | Tile tile(pattern_address, gb.mmu, sprite_size_multiplier); 308 | int start_y = sprite_y - 16; 309 | int start_x = sprite_x - 8; 310 | 311 | for (uint y = 0; y < TILE_HEIGHT_PX * sprite_size_multiplier; y++) { 312 | for (uint x = 0; x < TILE_WIDTH_PX; x++) { 313 | uint maybe_flipped_y = !flip_y ? y : (TILE_HEIGHT_PX * sprite_size_multiplier) - y - 1; 314 | uint maybe_flipped_x = !flip_x ? x : TILE_WIDTH_PX - x - 1; 315 | 316 | GBColor gb_color = tile.get_pixel(maybe_flipped_x, maybe_flipped_y); 317 | 318 | // Color 0 is transparent 319 | if (gb_color == GBColor::Color0) { continue; } 320 | 321 | int screen_x = start_x + x; 322 | int screen_y = start_y + y; 323 | 324 | if (!is_on_screen(screen_x, screen_y)) { continue; } 325 | 326 | auto existing_pixel = buffer.get_pixel(screen_x, screen_y); 327 | 328 | // FIXME: We need to see if the color we're writing over is 329 | // logically Color0, rather than looking at the color after 330 | // the current palette has been applied 331 | if (obj_behind_bg && existing_pixel != Color::White) { continue; } 332 | 333 | Color screen_color = get_color_from_palette(gb_color, palette); 334 | 335 | buffer.set_pixel(screen_x, screen_y, screen_color); 336 | } 337 | } 338 | } 339 | 340 | auto Video::get_pixel_from_line(u8 byte1, u8 byte2, u8 pixel_index) -> GBColor { 341 | using bitwise::bit_value; 342 | 343 | u8 color_u8 = static_cast((bit_value(byte2, 7-pixel_index) << 1) | bit_value(byte1, 7-pixel_index)); 344 | return get_color(color_u8); 345 | } 346 | 347 | auto Video::is_on_screen_x(u8 x) -> bool { return x < GAMEBOY_WIDTH; } 348 | 349 | auto Video::is_on_screen_y(u8 y) -> bool { return y < GAMEBOY_HEIGHT; } 350 | 351 | auto Video::is_on_screen(u8 x, u8 y) -> bool { return is_on_screen_x(x) && is_on_screen_y(y); } 352 | 353 | auto Video::load_palette(ByteRegister& palette_register) -> Palette { 354 | using bitwise::compose_bits; 355 | using bitwise::bit_value; 356 | 357 | /* TODO: Reduce duplication */ 358 | u8 color0 = compose_bits(bit_value(palette_register.value(), 1), bit_value(palette_register.value(), 0)); 359 | u8 color1 = compose_bits(bit_value(palette_register.value(), 3), bit_value(palette_register.value(), 2)); 360 | u8 color2 = compose_bits(bit_value(palette_register.value(), 5), bit_value(palette_register.value(), 4)); 361 | u8 color3 = compose_bits(bit_value(palette_register.value(), 7), bit_value(palette_register.value(), 6)); 362 | 363 | Color real_color_0 = get_real_color(color0); 364 | Color real_color_1 = get_real_color(color1); 365 | Color real_color_2 = get_real_color(color2); 366 | Color real_color_3 = get_real_color(color3); 367 | 368 | return { real_color_0, real_color_1, real_color_2, real_color_3 }; 369 | } 370 | 371 | auto Video::get_color_from_palette(GBColor color, const Palette& palette) -> Color { 372 | switch (color) { 373 | case GBColor::Color0: return palette.color0; 374 | case GBColor::Color1: return palette.color1; 375 | case GBColor::Color2: return palette.color2; 376 | case GBColor::Color3: return palette.color3; 377 | } 378 | } 379 | 380 | 381 | auto Video::get_real_color(u8 pixel_value) -> Color { 382 | switch (pixel_value) { 383 | case 0: return Color::White; 384 | case 1: return Color::LightGray; 385 | case 2: return Color::DarkGray; 386 | case 3: return Color::Black; 387 | default: 388 | fatal_error("Invalid color value"); 389 | } 390 | } 391 | 392 | void Video::register_vblank_callback(const vblank_callback_t& _vblank_callback) { 393 | vblank_callback = _vblank_callback; 394 | } 395 | 396 | void Video::draw() { 397 | vblank_callback(buffer); 398 | } 399 | -------------------------------------------------------------------------------- /src/video/video.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "framebuffer.h" 4 | #include "tile.h" 5 | 6 | #include "../mmu.h" 7 | #include "../register.h" 8 | #include "../definitions.h" 9 | #include "../options.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | class Gameboy; 16 | 17 | using vblank_callback_t = std::function; 18 | 19 | enum class VideoMode { 20 | ACCESS_OAM, 21 | ACCESS_VRAM, 22 | HBLANK, 23 | VBLANK, 24 | }; 25 | 26 | struct TileInfo { 27 | u8 line; 28 | std::vector pixels; 29 | }; 30 | 31 | class Video { 32 | public: 33 | Video(Gameboy& inGb, Options& inOptions); 34 | 35 | void tick(Cycles cycles); 36 | void register_vblank_callback(const vblank_callback_t& _vblank_callback); 37 | 38 | u8 read(const Address& address); 39 | void write(const Address& address, u8 byte); 40 | 41 | u8 control_byte; 42 | 43 | ByteRegister lcd_control; 44 | ByteRegister lcd_status; 45 | 46 | ByteRegister scroll_y; 47 | ByteRegister scroll_x; 48 | 49 | /* LCDC Y-coordinate */ 50 | ByteRegister line; /* Line y-position: register LY */ 51 | ByteRegister ly_compare; 52 | 53 | ByteRegister window_y; 54 | ByteRegister window_x; /* Note: x - 7 */ 55 | 56 | ByteRegister bg_palette; 57 | ByteRegister sprite_palette_0; /* OBP0 */ 58 | ByteRegister sprite_palette_1; /* OBP1 */ 59 | 60 | /* TODO: LCD Color Palettes (CGB) */ 61 | /* TODO: LCD VRAM Bank (CGB) */ 62 | 63 | ByteRegister dma_transfer; /* DMA */ 64 | 65 | bool debug_disable_background = false; 66 | bool debug_disable_sprites = false; 67 | bool debug_disable_window = false; 68 | 69 | private: 70 | void write_scanline(u8 current_line); 71 | void write_sprites(); 72 | void draw(); 73 | void draw_bg_line(uint current_line); 74 | void draw_window_line(uint current_line); 75 | void draw_sprite(uint sprite_n); 76 | static auto get_pixel_from_line(u8 byte1, u8 byte2, u8 pixel_index) -> GBColor; 77 | 78 | static auto is_on_screen(u8 x, u8 y) -> bool; 79 | static auto is_on_screen_x(u8 x) -> bool; 80 | static auto is_on_screen_y(u8 y) -> bool; 81 | 82 | auto display_enabled() const -> bool; 83 | auto window_tile_map() const -> bool; 84 | auto window_enabled() const -> bool; 85 | auto bg_window_tile_data() const -> bool; 86 | auto bg_tile_map_display() const -> bool; 87 | auto sprite_size() const -> bool; 88 | auto sprites_enabled() const -> bool; 89 | auto bg_enabled() const -> bool; 90 | 91 | auto get_tile_info(Address tile_set_location, u8 tile_id, u8 tile_line) const -> TileInfo; 92 | 93 | static auto get_real_color(u8 pixel_value) -> Color; 94 | static auto load_palette(ByteRegister& palette_register) -> Palette; 95 | static auto get_color_from_palette(GBColor color, const Palette& palette) -> Color; 96 | 97 | Gameboy& gb; 98 | 99 | FrameBuffer buffer; 100 | FrameBuffer background_map; 101 | 102 | std::vector video_ram; 103 | 104 | VideoMode current_mode = VideoMode::ACCESS_OAM; 105 | uint cycle_counter = 0; 106 | 107 | vblank_callback_t vblank_callback; 108 | }; 109 | 110 | const uint CLOCKS_PER_HBLANK = 204; /* Mode 0 */ 111 | const uint CLOCKS_PER_SCANLINE_OAM = 80; /* Mode 2 */ 112 | const uint CLOCKS_PER_SCANLINE_VRAM = 172; /* Mode 3 */ 113 | const uint CLOCKS_PER_SCANLINE = 114 | (CLOCKS_PER_SCANLINE_OAM + CLOCKS_PER_SCANLINE_VRAM + CLOCKS_PER_HBLANK); 115 | 116 | const uint CLOCKS_PER_VBLANK = 4560; /* Mode 1 */ 117 | const uint SCANLINES_PER_FRAME = 144; 118 | const uint CLOCKS_PER_FRAME = (CLOCKS_PER_SCANLINE * SCANLINES_PER_FRAME) + CLOCKS_PER_VBLANK; 119 | --------------------------------------------------------------------------------