├── .gitignore ├── LICENSE ├── CMakeLists.txt ├── src ├── utils.hpp ├── wrapper.hpp ├── wrapper.cpp ├── apu.hpp ├── nes.hpp ├── cpu.hpp ├── nes.cpp ├── ppu.hpp ├── apu.cpp ├── mapper.hpp ├── mapper.cpp ├── ppu.cpp └── cpu.cpp ├── cynes ├── __init__.py ├── emulator.pyi └── windowed.py ├── .github └── workflows │ └── wheels.yml ├── pyproject.toml ├── setup.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .cache/ 3 | .ruff_cache/ 4 | __pycache__/ 5 | *.egg-info/ 6 | *.so 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 - 2025 Combey Theo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | project(cynes C CXX) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED True) 7 | 8 | include(FetchContent) 9 | 10 | FetchContent_Declare( 11 | pybind11 12 | GIT_REPOSITORY https://github.com/pybind/pybind11 13 | GIT_TAG v3.0.1 14 | ) 15 | 16 | FetchContent_MakeAvailable(pybind11) 17 | 18 | add_library(cynes_core OBJECT 19 | src/apu.cpp 20 | src/cpu.cpp 21 | src/ppu.cpp 22 | src/nes.cpp 23 | src/mapper.cpp 24 | ) 25 | 26 | set_property(TARGET cynes_core PROPERTY POSITION_INDEPENDENT_CODE ON) 27 | 28 | target_compile_options(cynes_core PRIVATE 29 | $<$:/W4> 30 | $<$>:-Wall -Wextra -Wpedantic -Werror -Wno-missing-braces> 31 | ) 32 | 33 | target_include_directories(cynes_core PRIVATE 34 | src/ 35 | ) 36 | 37 | pybind11_add_module(emulator 38 | src/wrapper.cpp 39 | ) 40 | 41 | target_include_directories(emulator PRIVATE 42 | src/ 43 | ) 44 | 45 | target_compile_options(emulator PRIVATE 46 | $<$:/W4> 47 | $<$>:-Wall -Wextra -Wpedantic -Werror> 48 | ) 49 | 50 | target_link_libraries(emulator PRIVATE 51 | cynes_core 52 | ) 53 | 54 | target_compile_definitions(emulator PRIVATE 55 | PYTHON_MODULE_VERSION="${PYTHON_MODULE_VERSION}" 56 | ) 57 | 58 | -------------------------------------------------------------------------------- /src/utils.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CYNES_UTILS__ 2 | #define __CYNES_UTILS__ 3 | 4 | #include 5 | #include 6 | 7 | namespace cynes { 8 | enum class DumpOperation { 9 | SIZE, DUMP, LOAD 10 | }; 11 | 12 | template 13 | constexpr void dump(uint8_t*& buffer, T& value) { 14 | if constexpr (operation == DumpOperation::DUMP) { 15 | memcpy(buffer, &value, sizeof(T)); 16 | } else if constexpr (operation == DumpOperation::LOAD) { 17 | memcpy(&value, buffer, sizeof(T)); 18 | } 19 | 20 | buffer += sizeof(T); 21 | } 22 | 23 | template 24 | constexpr void dump(unsigned int& buffer_size, T&) { 25 | if constexpr (operation == DumpOperation::SIZE) { 26 | buffer_size += sizeof(T); 27 | } 28 | } 29 | 30 | template 31 | constexpr void dump(uint8_t*& buffer, T* values, unsigned int size) { 32 | if constexpr (operation == DumpOperation::DUMP) { 33 | memcpy(buffer, values, sizeof(T) * size); 34 | } else if constexpr (operation == DumpOperation::LOAD) { 35 | memcpy(values, buffer, sizeof(T) * size); 36 | } 37 | 38 | buffer += sizeof(T) * size; 39 | } 40 | 41 | template 42 | constexpr void dump(unsigned int& buffer_size, T*, unsigned int size) { 43 | if constexpr (operation == DumpOperation::SIZE) { 44 | buffer_size += sizeof(T) * size; 45 | } 46 | } 47 | } 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /cynes/__init__.py: -------------------------------------------------------------------------------- 1 | # cynes - C/C++ NES emulator with Python bindings 2 | # Copyright (C) 2021 - 2025 Combey Theo 3 | 4 | """Main cynes package. 5 | 6 | This package contains two sub-module: 7 | - `cynes.emulator` with the main `NES` class, which is a direct wrapper around the C/C++ 8 | API. This class can be used to run an emulator in 'headless' mode, which means that 9 | nothing will be rendered to the screen. The content of the frame buffer can be 10 | accessed nonetheless. 11 | - `cynes.windowed` with the `WindowedNES` class, derived from `NES`. This class is a 12 | simple wrapper around the base emulator providing a basic renderer and input handling 13 | using SDL2. The python wrapper `pysdl2` must be installed to use this class. 14 | 15 | Below is the simplest way of running a ROM with a rendering window. 16 | ``` 17 | from cynes.windowed import WindowedNES 18 | nes = WindowedNES("rom.nes") 19 | while not nes.should_close: 20 | nes.step() 21 | ``` 22 | """ 23 | 24 | from cynes.emulator import NES, __version__ 25 | 26 | NES_INPUT_RIGHT = 0x01 27 | NES_INPUT_LEFT = 0x02 28 | NES_INPUT_DOWN = 0x04 29 | NES_INPUT_UP = 0x08 30 | NES_INPUT_START = 0x10 31 | NES_INPUT_SELECT = 0x20 32 | NES_INPUT_B = 0x40 33 | NES_INPUT_A = 0x80 34 | 35 | __all__ = [ 36 | "__version__", 37 | "NES", 38 | "NES_INPUT_RIGHT", 39 | "NES_INPUT_LEFT", 40 | "NES_INPUT_DOWN", 41 | "NES_INPUT_UP", 42 | "NES_INPUT_START", 43 | "NES_INPUT_SELECT", 44 | "NES_INPUT_B", 45 | "NES_INPUT_A" 46 | ] 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/wheels.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | pull_request: {} 5 | push: 6 | tags: 7 | - "v*" 8 | 9 | jobs: 10 | build_wheels: 11 | name: Build wheels on ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | env: 14 | CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-* cp314-*" 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest, macos-13, macos-14] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - name: Build wheels 23 | uses: pypa/cibuildwheel@v3.1.4 24 | 25 | - uses: actions/upload-artifact@v4 26 | with: 27 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 28 | path: ./wheelhouse/*.whl 29 | 30 | build_sdist: 31 | name: Build source distribution 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v2 35 | 36 | - uses: actions/setup-python@v5 37 | name: Install Python 38 | with: 39 | python-version: '3.13' 40 | 41 | - name: Install dependencies 42 | run: | 43 | pip install numpy cibuildwheel==3.1.4 44 | 45 | - name: Build sdist 46 | run: python -m build . --sdist 47 | 48 | - uses: actions/upload-artifact@v4 49 | with: 50 | name: sdist 51 | path: dist/*.tar.gz 52 | 53 | upload_pypi: 54 | if: startsWith(github.ref, 'refs/tags/v') 55 | needs: [build_wheels, build_sdist] 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/download-artifact@v4 59 | with: 60 | path: dist 61 | 62 | - uses: pypa/gh-action-pypi-publish@v1.13.0 63 | with: 64 | user: __token__ 65 | password: ${{ secrets.PYPI_TOKEN }} 66 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cynes" 3 | version = "0.1.1" 4 | description = "C/C++ NES emulator with Python bindings" 5 | readme = "README.md" 6 | authors = [{ name = "Theo Combey", email = "combey.theo@hotmail.com" }] 7 | license = "MIT" 8 | license-files = ["LICENSE"] 9 | urls = { Homepage = "https://github.com/Youlixx/cynes" } 10 | dependencies = ["numpy"] 11 | requires-python = ">=3.9" 12 | classifiers = [ 13 | "Development Status :: 4 - Beta", 14 | "Operating System :: MacOS", 15 | "Operating System :: Microsoft :: Windows", 16 | "Operating System :: Unix", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "Programming Language :: Python :: 3.12", 21 | "Programming Language :: Python :: 3.13", 22 | "Programming Language :: Python :: 3.14" 23 | ] 24 | 25 | [build-system] 26 | requires = ["setuptools>=61.0", "wheel", "cmake>=3.14"] 27 | build-backend = "setuptools.build_meta" 28 | 29 | [tool.setuptools] 30 | packages = ["cynes"] 31 | 32 | [tool.ruff] 33 | exclude = [ 34 | ".bzr", 35 | ".direnv", 36 | ".eggs", 37 | ".git", 38 | ".git-rewrite", 39 | ".hg", 40 | ".ipynb_checkpoints", 41 | ".mypy_cache", 42 | ".nox", 43 | ".pants.d", 44 | ".pyenv", 45 | ".pytest_cache", 46 | ".pytype", 47 | ".ruff_cache", 48 | ".svn", 49 | ".tox", 50 | ".venv", 51 | ".vscode", 52 | "__pypackages__", 53 | "_build", 54 | "buck-out", 55 | "build", 56 | "dist", 57 | "node_modules", 58 | "site-packages", 59 | "venv", 60 | ] 61 | 62 | line-length = 88 63 | indent-width = 4 64 | target-version = "py38" 65 | 66 | [tool.ruff.lint] 67 | select = ["A", "B", "C", "D", "E", "F", "I", "N", "W"] 68 | ignore = ["D203", "D213", "D407", "D406", "D413", "N801", "C901"] 69 | 70 | fixable = ["ALL"] 71 | unfixable = [] 72 | 73 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 74 | 75 | [tool.ruff.format] 76 | quote-style = "double" 77 | indent-style = "space" 78 | skip-magic-trailing-comma = true 79 | line-ending = "auto" 80 | docstring-code-format = true 81 | docstring-code-line-length = "dynamic" 82 | -------------------------------------------------------------------------------- /src/wrapper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CYNES_WRAPPER__ 2 | #define __CYNES_WRAPPER__ 3 | 4 | #include "nes.hpp" 5 | 6 | #include 7 | #include 8 | 9 | namespace cynes { 10 | namespace wrapper { 11 | /// NES Wrapper for Python bindings. 12 | class NesWrapper { 13 | public: 14 | /// Initialize the emulator. 15 | /// @param path_rom Path to the ROM file. 16 | NesWrapper(const char* path_rom); 17 | 18 | // Default destructor. 19 | ~NesWrapper() = default; 20 | 21 | /// Step the emulation by the given amount of frame. 22 | /// @param frames Number of frame of the step. 23 | /// @return Read-only framebuffer. 24 | const pybind11::array_t& step(uint32_t frames); 25 | 26 | /// Return a save state of the emulator. 27 | /// @return Save state buffer. 28 | pybind11::array_t save(); 29 | 30 | /// Load a previous emulator state from a buffer. 31 | /// @note This function also reset the crashed flag. 32 | /// @param buffer Save state buffer. 33 | void load(pybind11::array_t buffer); 34 | 35 | /// Write to the console memory. 36 | /// @note This function has other side effects than simply writing to the memory, it 37 | /// should not be used as a memory set function. 38 | /// @param address Memory address within the console memory address space. 39 | /// @param value Value to write. 40 | inline void write(uint16_t address, uint8_t value) { 41 | _nes.write_cpu(address, value); 42 | } 43 | 44 | /// Read from the console memory while ticking its components. 45 | /// @note This function has other side effects than simply reading from memory, it 46 | /// should not be used as a memory watch function. 47 | /// @param address Memory address within the console memory address space. 48 | /// @return The value stored at the given address. 49 | inline uint8_t read(uint16_t address) { return _nes.read_cpu(address); } 50 | 51 | /// Reset the emulator (same effect as pressing the reset button). 52 | inline void reset() { _nes.reset(); } 53 | 54 | /// Check whether or not the emulator has hit a JAM instruction. 55 | /// @note When the emulator has crashed, subsequent calls to `NesWrapper::step` will 56 | /// not do anything. Resetting the emulator or loading a valid save-state will reset 57 | /// this flag. 58 | /// @return True if the emulator crashed, false otherwise. 59 | inline bool has_crashed() const { return _crashed; } 60 | 61 | public: 62 | uint16_t controller; 63 | 64 | private: 65 | NES _nes; 66 | const size_t _save_state_size; 67 | 68 | pybind11::array_t _frame; 69 | bool _crashed; 70 | }; 71 | } 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "wrapper.hpp" 2 | #include "nes.hpp" 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | cynes::wrapper::NesWrapper::NesWrapper(const char* path_rom) 12 | : controller{0x00} 13 | , _nes{path_rom} 14 | , _save_state_size{_nes.size()} 15 | , _frame{ 16 | {240, 256, 3}, 17 | {256 * 3, 3, 1}, 18 | _nes.get_frame_buffer(), 19 | pybind11::capsule(_nes.get_frame_buffer(), [](void *) {}) 20 | } 21 | , _crashed{false} 22 | { 23 | pybind11::detail::array_proxy(_frame.ptr())->flags &= ~pybind11::detail::npy_api::NPY_ARRAY_WRITEABLE_; 24 | } 25 | 26 | const pybind11::array_t& cynes::wrapper::NesWrapper::step(uint32_t frames) { 27 | _crashed |= _nes.step(controller, frames); 28 | return _frame; 29 | } 30 | 31 | pybind11::array_t cynes::wrapper::NesWrapper::save() { 32 | pybind11::array_t buffer{static_cast(_save_state_size)}; 33 | _nes.save(buffer.mutable_data()); 34 | return buffer; 35 | } 36 | 37 | void cynes::wrapper::NesWrapper::load(pybind11::array_t buffer) { 38 | _nes.load(buffer.mutable_data()); 39 | _crashed = false; 40 | } 41 | 42 | 43 | PYBIND11_MODULE(emulator, mod) { 44 | mod.doc() = "C/C++ NES emulator with Python bindings"; 45 | 46 | #ifdef PYTHON_MODULE_VERSION 47 | mod.attr("__version__") = PYTHON_MODULE_VERSION; 48 | #else 49 | mod.attr("__version__") = "0.0.0"; 50 | #endif 51 | 52 | pybind11::class_(mod, "NES") 53 | .def( 54 | pybind11::init(), 55 | pybind11::arg("path_rom"), 56 | "Initialize the emulator." 57 | ) 58 | .def( 59 | "__setitem__", 60 | &cynes::wrapper::NesWrapper::write, 61 | pybind11::arg("address"), 62 | pybind11::arg("value"), 63 | "Write a value in the emulator memory at the specified address." 64 | ) 65 | .def( 66 | "__getitem__", 67 | &cynes::wrapper::NesWrapper::read, 68 | pybind11::arg("address"), 69 | "Read a value in the emulator memory at the specified address." 70 | ) 71 | .def( 72 | "reset", 73 | &cynes::wrapper::NesWrapper::reset, 74 | "Send a reset signal to the emulator." 75 | ) 76 | .def( 77 | "step", 78 | &cynes::wrapper::NesWrapper::step, 79 | pybind11::arg("frames") = 1, 80 | "Run the emulator for the specified amount of frame." 81 | ) 82 | .def( 83 | "save", 84 | &cynes::wrapper::NesWrapper::save, 85 | "Dump the current emulator state into a save state." 86 | ) 87 | .def( 88 | "load", 89 | &cynes::wrapper::NesWrapper::load, 90 | pybind11::arg("buffer"), 91 | "Restore the emulator state from a save state." 92 | ) 93 | .def_readwrite( 94 | "controller", 95 | &cynes::wrapper::NesWrapper::controller, 96 | "Emulator controller state." 97 | ) 98 | .def_property_readonly( 99 | "has_crashed", 100 | &cynes::wrapper::NesWrapper::has_crashed, 101 | "Indicate whether the CPU crashed after hitting an invalid op-code." 102 | ) 103 | .doc() = "Headless NES emulator"; 104 | } 105 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """Build and generate the wheel for cynes.""" 2 | 3 | import os 4 | import re 5 | import subprocess 6 | import sys 7 | from pathlib import Path 8 | 9 | from setuptools import Extension, setup 10 | from setuptools.command.build_ext import build_ext 11 | 12 | with open("README.md", "r") as file: 13 | long_description = file.read() 14 | 15 | 16 | class CMakeExtension(Extension): 17 | """CMake extension.""" 18 | 19 | def __init__(self, name: str, source_dir: str = "") -> None: 20 | """Initialize the extension. 21 | 22 | Args: 23 | name (str): Name of the extension. 24 | source_dir (str, optional): C++ source directory. 25 | """ 26 | super().__init__(name, sources=[]) 27 | 28 | self.source_dir = os.fspath(Path(source_dir).resolve()) 29 | 30 | 31 | class CMakeBuild(build_ext): 32 | """CMake build extension.""" 33 | 34 | PLAT_TO_CMAKE = { 35 | "win32": "Win32", 36 | "win-amd64": "x64", 37 | "win-arm32": "ARM", 38 | "win-arm64": "ARM64", 39 | } 40 | 41 | def build_extension(self, ext: CMakeExtension) -> None: 42 | """Build the shared python binaries. 43 | 44 | Args: 45 | ext (CMakeExtension): Builder extension. 46 | """ 47 | ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) 48 | extdir = ext_fullpath.parent.resolve() 49 | 50 | debug = int(os.environ.get("DEBUG", 0)) if self.debug is None else self.debug 51 | cfg = "Debug" if debug else "Release" 52 | 53 | cmake_generator = os.environ.get("CMAKE_GENERATOR", "") 54 | 55 | cmake_args = [ 56 | f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY={extdir}{os.sep}", 57 | f"-DPYTHON_EXECUTABLE={sys.executable}", 58 | f"-DCMAKE_BUILD_TYPE={cfg}", 59 | f"-DPYTHON_MODULE_VERSION={self.distribution.get_version()}", 60 | ] 61 | 62 | build_args = [] 63 | if "CMAKE_ARGS" in os.environ: 64 | cmake_args += [item for item in os.environ["CMAKE_ARGS"].split(" ") if item] 65 | 66 | if self.compiler.compiler_type == "msvc": 67 | single_config = any(x in cmake_generator for x in {"NMake", "Ninja"}) 68 | contains_arch = any(x in cmake_generator for x in {"ARM", "Win64"}) 69 | 70 | # Specify the arch if using MSVC generator, but only if it doesn't contain 71 | # a backward-compatibility arch spec already in the generator name. 72 | if not single_config and not contains_arch: 73 | cmake_args += ["-A", CMakeBuild.PLAT_TO_CMAKE[self.plat_name]] 74 | 75 | # Multi-config generators have a different way to specify configs 76 | if not single_config: 77 | build_args += ["--config", cfg] 78 | cmake_args += [ 79 | f"-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{cfg.upper()}={extdir}" 80 | ] 81 | 82 | if sys.platform.startswith("darwin"): 83 | archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) 84 | 85 | if archs: 86 | cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] 87 | 88 | if "CMAKE_BUILD_PARALLEL_LEVEL" not in os.environ: 89 | if hasattr(self, "parallel") and self.parallel: 90 | build_args += [f"-j{self.parallel}"] 91 | 92 | build_temp = Path(self.build_temp) / ext.name 93 | 94 | if not build_temp.exists(): 95 | build_temp.mkdir(parents=True) 96 | 97 | subprocess.run( 98 | ["cmake", ext.source_dir, *cmake_args], cwd=build_temp, check=True 99 | ) 100 | 101 | subprocess.run( 102 | ["cmake", "--build", ".", *build_args], cwd=build_temp, check=True 103 | ) 104 | 105 | setup( 106 | ext_modules=[CMakeExtension("cynes.emulator")], 107 | cmdclass={"build_ext": CMakeBuild}, 108 | ) 109 | -------------------------------------------------------------------------------- /src/apu.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CYNES_APU__ 2 | #define __CYNES_APU__ 3 | 4 | #include 5 | 6 | #include "utils.hpp" 7 | 8 | namespace cynes { 9 | // Forward declaration. 10 | class NES; 11 | 12 | /// Audio Processing Unit (see https://www.nesdev.org/wiki/APU). 13 | /// This implementation does not produce any sound, it is only emulated for timing and 14 | /// interrupt purposes. 15 | class APU { 16 | public: 17 | /// Initialize the APU. 18 | APU(NES& nes); 19 | 20 | /// Default destructor. 21 | ~APU() = default; 22 | 23 | public: 24 | /// Set the APU in its power-up state. 25 | void power(); 26 | 27 | /// Set the APU in its reset state. 28 | void reset(); 29 | 30 | /// Tick the APU. 31 | /// @param reading Should be true if the APU is ticked on a reading cycle. 32 | /// @param prevent_load False by default, should be set to true only when called 33 | /// from `APU::load_delta_channel_byte` to avoid recursion. 34 | void tick(bool reading, bool prevent_load = false); 35 | 36 | /// Write to the APU memory. 37 | /// @param address Memory address within the APU memory address space. 38 | /// @param value Value to write. 39 | void write(uint8_t address, uint8_t value); 40 | 41 | /// Read from the APU memory. 42 | /// @note This function has other side effects than simply reading from memory, it 43 | /// should not be used as a memory watch function. 44 | /// @param address Memory address within the APU memory address space. 45 | /// @return The value stored at the given address. 46 | uint8_t read(uint8_t address); 47 | 48 | private: 49 | NES& _nes; 50 | 51 | private: 52 | void update_counters(); 53 | void load_delta_channel_byte(bool reading); 54 | 55 | void perform_dma(uint8_t address); 56 | void perform_pending_dma(); 57 | 58 | void set_frame_interrupt(bool interrupt); 59 | void set_delta_interrupt(bool interrupt); 60 | 61 | private: 62 | bool _latch_cycle; 63 | 64 | uint8_t _delay_dma; 65 | uint8_t _address_dma; 66 | 67 | bool _pending_dma; 68 | 69 | uint8_t _internal_open_bus; 70 | 71 | private: 72 | uint32_t _frame_counter_clock; 73 | uint32_t _delay_frame_reset; 74 | 75 | uint8_t _channels_counters[0x4]; 76 | 77 | bool _channel_enabled[0x4]; 78 | bool _channel_halted[0x4]; 79 | 80 | bool _step_mode; 81 | 82 | bool _inhibit_frame_interrupt; 83 | bool _send_frame_interrupt; 84 | 85 | private: 86 | uint16_t _delta_channel_remaining_bytes; 87 | uint16_t _delta_channel_sample_length; 88 | uint16_t _delta_channel_period_counter; 89 | uint16_t _delta_channel_period_load; 90 | 91 | uint8_t _delta_channel_bits_in_buffer; 92 | 93 | bool _delta_channel_should_loop; 94 | bool _delta_channel_enable_interrupt; 95 | bool _delta_channel_sample_buffer_empty; 96 | 97 | bool _enable_dmc; 98 | bool _send_delta_channel_interrupt; 99 | 100 | private: 101 | enum class Register : uint8_t { 102 | PULSE_1_0 = 0x00, 103 | PULSE_1_3 = 0x03, 104 | PULSE_2_0 = 0x04, 105 | PULSE_2_3 = 0x07, 106 | TRIANGLE_0 = 0x08, 107 | TRIANGLE_3 = 0x0B, 108 | NOISE_0 = 0x0C, 109 | NOISE_3 = 0x0F, 110 | DELTA_0 = 0x10, 111 | DELTA_3 = 0x13, 112 | OAM_DMA = 0x14, 113 | CTRL_STATUS = 0x15, 114 | FRAME_COUNTER = 0x17 115 | }; 116 | 117 | public: 118 | template 119 | constexpr void dump(T& buffer) { 120 | cynes::dump(buffer, _latch_cycle); 121 | cynes::dump(buffer, _delay_dma); 122 | cynes::dump(buffer, _address_dma); 123 | cynes::dump(buffer, _pending_dma); 124 | 125 | cynes::dump(buffer, _frame_counter_clock); 126 | cynes::dump(buffer, _delay_frame_reset); 127 | cynes::dump(buffer, _channels_counters); 128 | cynes::dump(buffer, _channel_enabled); 129 | cynes::dump(buffer, _channel_halted); 130 | cynes::dump(buffer, _step_mode); 131 | cynes::dump(buffer, _inhibit_frame_interrupt); 132 | cynes::dump(buffer, _send_frame_interrupt); 133 | 134 | cynes::dump(buffer, _delta_channel_remaining_bytes); 135 | cynes::dump(buffer, _delta_channel_sample_length); 136 | cynes::dump(buffer, _delta_channel_period_counter); 137 | cynes::dump(buffer, _delta_channel_period_load); 138 | cynes::dump(buffer, _delta_channel_bits_in_buffer); 139 | cynes::dump(buffer, _delta_channel_should_loop); 140 | cynes::dump(buffer, _delta_channel_enable_interrupt); 141 | cynes::dump(buffer, _delta_channel_sample_buffer_empty); 142 | cynes::dump(buffer, _enable_dmc); 143 | cynes::dump(buffer, _send_delta_channel_interrupt); 144 | } 145 | }; 146 | } 147 | 148 | #endif 149 | -------------------------------------------------------------------------------- /src/nes.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CYNES_EMULATOR__ 2 | #define __CYNES_EMULATOR__ 3 | 4 | #include 5 | #include 6 | 7 | #include "apu.hpp" 8 | #include "cpu.hpp" 9 | #include "ppu.hpp" 10 | #include "mapper.hpp" 11 | 12 | #include "utils.hpp" 13 | 14 | namespace cynes { 15 | /// Main NES class, contains the RAM, CPU, PPU, APU, Mapper, etc... 16 | class NES { 17 | public: 18 | // TODO maybe allow to use a constructor with a raw byte ptr. 19 | /// Initialize the NES. 20 | /// @param path Path to the ROM. 21 | NES(const char* path); 22 | 23 | /// Default destructor. 24 | ~NES() = default; 25 | 26 | public: 27 | /// Reset the emulator (same effect as pressing the reset button). 28 | void reset(); 29 | 30 | /// Perform a dummy read cycle. 31 | void dummy_read(); 32 | 33 | /// Write to the console memory while ticking its components. 34 | /// @note This function has other side effects than simply writing to the memory, it 35 | /// should not be used as a memory set function. 36 | /// @param address Memory address within the console memory address space. 37 | /// @param value Value to write. 38 | void write(uint16_t address, uint8_t value); 39 | 40 | /// Write to the console memory. 41 | /// @note This function has other side effects than simply writing to the memory, it 42 | /// should not be used as a memory set function. 43 | /// @param address Memory address within the console memory address space. 44 | /// @param value Value to write. 45 | void write_cpu(uint16_t address, uint8_t value); 46 | 47 | /// Write to the PPU memory. 48 | /// @note This function has other side effects than simply writing to the memory, it 49 | /// should not be used as a memory set function. 50 | /// @param address Memory address within the console memory address space. 51 | /// @param value Value to write. 52 | void write_ppu(uint16_t address, uint8_t value); 53 | 54 | /// Write to the OAM memory. 55 | /// @note This function has other side effects than simply writing to the memory, it 56 | /// should not be used as a memory set function. 57 | /// @param address Memory address within the console memory address space. 58 | /// @param value Value to write. 59 | void write_oam(uint8_t address, uint8_t value); 60 | 61 | /// Read from the console memory while ticking its components. 62 | /// @note This function has other side effects than simply reading from memory, it 63 | /// should not be used as a memory watch function. 64 | /// @param address Memory address within the console memory address space. 65 | /// @return The value stored at the given address. 66 | uint8_t read(uint16_t address); 67 | 68 | /// Read from the console memory. 69 | /// @note This function has other side effects than simply reading from memory, it 70 | /// should not be used as a memory watch function. 71 | /// @param address Memory address within the console memory address space. 72 | /// @return The value stored at the given address. 73 | uint8_t read_cpu(uint16_t address); 74 | 75 | /// Read from the PPU memory. 76 | /// @note This function has other side effects than simply reading from memory, it 77 | /// should not be used as a memory watch function. 78 | /// @param address Memory address within the console memory address space. 79 | /// @return The value stored at the given address. 80 | uint8_t read_ppu(uint16_t address); 81 | 82 | /// Read from the OAM memory. 83 | /// @note This function has other side effects than simply reading from memory, it 84 | /// should not be used as a memory watch function. 85 | /// @param address Memory address within the console memory address space. 86 | /// @return The value stored at the given address. 87 | uint8_t read_oam(uint8_t address) const; 88 | 89 | /// Get the current open bus state. 90 | /// @return The open bus value. 91 | uint8_t get_open_bus() const; 92 | 93 | /// Step the emulation by the given amount of frame. 94 | /// @param controllers Controllers states (first 8-bits for controller 1, the 95 | /// remaining 8-bits fro controller 2). 96 | /// @param frames Number of frame of the step. 97 | /// @return True if the CPU is frozen, false otherwise. 98 | bool step(uint16_t controllers, unsigned int frames); 99 | 100 | /// Get the size of the save state. 101 | /// @return The size of the save state buffer. 102 | unsigned int size(); 103 | 104 | /// Save the state of the emulator to the buffer. 105 | /// @param buffer Save state buffer. 106 | void save(uint8_t* buffer); 107 | 108 | /// Load a previous emulator state from the buffer. 109 | /// @param buffer Save state buffer. 110 | void load(uint8_t* buffer); 111 | 112 | /// Get a pointer to the internal frame buffer. 113 | inline const uint8_t* get_frame_buffer() const { 114 | return ppu.get_frame_buffer(); 115 | } 116 | 117 | public: 118 | CPU cpu; 119 | PPU ppu; 120 | APU apu; 121 | 122 | Mapper& get_mapper(); 123 | 124 | private: 125 | std::unique_ptr _mapper; 126 | 127 | private: 128 | std::unique_ptr _memory_cpu; 129 | std::unique_ptr _memory_oam; 130 | std::unique_ptr _memory_palette; 131 | 132 | uint8_t _open_bus; 133 | 134 | uint8_t _controller_status[0x2]; 135 | uint8_t _controller_shifters[0x2]; 136 | 137 | private: 138 | void load_controller_shifter(bool polling); 139 | 140 | uint8_t poll_controller(uint8_t player); 141 | 142 | private: 143 | template void dump(T& buffer); 144 | }; 145 | } 146 | 147 | #endif 148 | -------------------------------------------------------------------------------- /cynes/emulator.pyi: -------------------------------------------------------------------------------- 1 | # cynes - C/C++ NES emulator with Python bindings 2 | # Copyright (C) 2021 - 2025 Combey Theo 3 | 4 | import numpy as np 5 | from numpy.typing import NDArray 6 | 7 | __version__ = ... 8 | 9 | 10 | class NES: 11 | """The base emulator class.""" 12 | 13 | controller: int 14 | """Emulator controller state. 15 | 16 | This variable is a 16-bit register representing the states of both P1 (lower 8 bits) 17 | and P2 (higher 8 bits) controllers. Each bit in the 8-bit registers corresponds to a 18 | specific button on the controller: 19 | - Bit 0: D-pad right 20 | - Bit 1: D-pad left 21 | - Bit 2: D-pad down 22 | - Bit 3: D-pad up 23 | - Bit 4: Start 24 | - Bit 5: Select 25 | - Bit 6: B button 26 | - Bit 7: A button 27 | 28 | Setting a bit to 1 indicates that the corresponding button is pressed, while 29 | resetting a bit to 0 indicates that the button is released. The controller state is 30 | persistent across frames, meaning that buttons must be explicitly marked as released 31 | by resetting the corresponding bit in the register. 32 | """ 33 | 34 | def __init__(self, path_rom: str) -> None: 35 | """Initialize the NES emulator. 36 | 37 | This function sets up the NES emulator by loading the specified ROM file. The 38 | initialization process can fail for several reasons, including: 39 | - The ROM file cannot be found at the specified path. 40 | - The given file is not a valid ROM file. 41 | - The mapper used by the ROM is currently unsupported by the emulator. 42 | 43 | Args: 44 | path_rom (str): The path to the NES ROM file containing the game data. 45 | """ 46 | ... 47 | 48 | def __setitem__(self, address: int, value: int) -> None: 49 | """Write a value to the emulator's memory at the specified address. 50 | 51 | Certain memory addresses are reserved for specific hardware components, and 52 | writing to these addresses may lead to undefined behavior or de-synchronization 53 | of console components. 54 | 55 | Safe memory regions for writing include: 56 | - RAM: Addresses between $0000 and $1FFF 57 | - Mapper RAM: Addresses between $6000 and $7FFF 58 | 59 | Args: 60 | address (int): The memory address where the value will be written. 61 | value (int): The value to be written at the specified memory address. 62 | 63 | Raises: 64 | TypeError: Error raised if the given address is out of bound (must be 65 | between $0000 and $FFFF), or when the given value is not a 8-bit 66 | positive integer. 67 | """ 68 | ... 69 | 70 | def __getitem__(self, address: int) -> int: 71 | """Read a value from the emulator's memory at the specified address. 72 | 73 | Certain memory addresses are reserved for specific hardware components, and 74 | reading from these addresses may lead to undefined behavior or 75 | de-synchronization of console components. 76 | 77 | Safe memory regions for reading include: 78 | - RAM: Addresses between $0000 and $1FFF 79 | - Mapper RAM: Addresses between $6000 and $7FFF 80 | 81 | Args: 82 | address (int): The RAM address to read from. 83 | 84 | Returns: 85 | value (int): The value read from the RAM. 86 | 87 | Raises: 88 | TypeError: Error raised if the given address is out of bound (must be 89 | between $0000 and $FFFF). 90 | """ 91 | ... 92 | 93 | def reset(self) -> None: 94 | """Send a reset signal to the emulator. 95 | 96 | This method simulates a reset of the NES console. Unlike creating a new emulator 97 | instance, resetting the emulator does not clear the RAM content. This means that 98 | any data stored in RAM will persist across the reset. 99 | """ 100 | ... 101 | 102 | def step(self, frames: int = 1) -> NDArray[np.uint8]: 103 | """Run the emulator for the specified number of frames. 104 | 105 | This method advances the emulator's state by the specified number of frames. By 106 | default, it runs the emulator for a single frame. 107 | 108 | Args: 109 | frames (int): The number of frames to run the emulator for. Default is 1. 110 | 111 | Returns: 112 | framebuffer (NDArray[np.uint8]): A NumPy array containing the frame buffer 113 | in RGB format. The array has a shape of (240, 256, 3) and provides a 114 | read-only view of the framebuffer. If modifications are needed, a copy 115 | of the array should be made. 116 | """ 117 | ... 118 | 119 | def save(self) -> NDArray[np.uint8]: 120 | """Dump the current emulator state into a save state. 121 | 122 | This method creates a snapshot of the emulator's current state, which can be 123 | used as a checkpoint. The size of the save state may vary depending on the 124 | mapper used by the currently running game. This save state can be restored at 125 | any time without corrupting the NES memory by using the `load` method. 126 | 127 | Returns: 128 | buffer (NDArray[np.uint8]): A NumPy array containing the dump of the 129 | emulator's state. 130 | """ 131 | ... 132 | 133 | def load(self, buffer: NDArray[np.uint8]) -> None: 134 | """Restore the emulator state from a save state. 135 | 136 | This method restores a save state generated using the `save` method. When 137 | restoring a save state, you must ensure that the emulator was instantiated using 138 | the exact same ROM as the one used to generate the state. 139 | 140 | Args: 141 | buffer (NDArray[np.uint8]): A NumPy array containing the dump of the 142 | emulator's state to be restored. 143 | """ 144 | ... 145 | 146 | @property 147 | def has_crashed(self) -> int: 148 | """Indicate whether the CPU has crashed due to encountering an invalid op-code. 149 | 150 | This method returns a flag that signals if the emulator has crashed. When the 151 | emulator is in a crashed state, subsequent calls to the `step` method will have 152 | no effect. To resume normal operation, the emulator must be reset or a valid 153 | save state must be loaded, which will clear this crash flag. 154 | """ 155 | ... 156 | -------------------------------------------------------------------------------- /src/cpu.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CYNES_CPU__ 2 | #define __CYNES_CPU__ 3 | 4 | #include 5 | 6 | #include "utils.hpp" 7 | 8 | namespace cynes { 9 | // Forward declaration. 10 | class NES; 11 | 12 | /// NES 6502 CPU implementation (see https://www.nesdev.org/wiki/CPU). 13 | class CPU { 14 | public: 15 | /// Initialize the CPU. 16 | CPU(NES& nes); 17 | 18 | /// Default destructor. 19 | ~CPU() = default; 20 | 21 | public: 22 | /// Set the CPU in its power-up state. 23 | void power(); 24 | 25 | /// Set the CPU in its reset state. 26 | void reset(); 27 | 28 | /// Tick the CPU. 29 | void tick(); 30 | 31 | /// Poll the CPU for the non-maskable interrupt. 32 | void poll(); 33 | 34 | /// Set the non-maskable interrupt flag value. 35 | /// @param interrupt Non-maskable interrupt value. 36 | void set_non_maskable_interrupt(bool interrupt); 37 | 38 | /// Set the state of the mapper interrupt. 39 | /// @param interrupt Interrupt state. 40 | void set_mapper_interrupt(bool interrupt); 41 | 42 | /// Set the state of the frame interrupt. 43 | /// @param interrupt Interrupt state. 44 | void set_frame_interrupt(bool interrupt); 45 | 46 | /// Set the state of the delta interrupt. 47 | /// @param interrupt Interrupt state. 48 | void set_delta_interrupt(bool interrupt); 49 | 50 | /// Check whether or not the CPU has hit an invalid opcode. 51 | bool is_frozen() const; 52 | 53 | private: 54 | NES& _nes; 55 | 56 | private: 57 | bool _frozen; 58 | 59 | uint8_t _register_a; 60 | uint8_t _register_x; 61 | uint8_t _register_y; 62 | uint8_t _register_m; 63 | uint8_t _stack_pointer; 64 | 65 | uint16_t _program_counter; 66 | 67 | uint8_t fetch_next(); 68 | 69 | private: 70 | bool _delay_interrupt; 71 | bool _should_issue_interrupt; 72 | 73 | bool _line_mapper_interrupt; 74 | bool _line_frame_interrupt; 75 | bool _line_delta_interrupt; 76 | 77 | bool _line_non_maskable_interrupt; 78 | bool _edge_detector_non_maskable_interrupt; 79 | 80 | bool _delay_non_maskable_interrupt; 81 | bool _should_issue_non_maskable_interrupt; 82 | 83 | private: 84 | uint8_t _status; 85 | 86 | void set_status(uint8_t flag, bool value); 87 | bool get_status(uint8_t flag) const; 88 | 89 | enum Flag : uint8_t { 90 | C = 0x01, Z = 0x02, I = 0x04, D = 0x08, B = 0x10, U = 0x20, V = 0x40, N = 0x80 91 | }; 92 | 93 | private: 94 | uint16_t _target_address; 95 | 96 | void addr_abr(); 97 | void addr_abw(); 98 | void addr_acc(); 99 | void addr_axm(); 100 | void addr_axr(); 101 | void addr_axw(); 102 | void addr_aym(); 103 | void addr_ayr(); 104 | void addr_ayw(); 105 | void addr_imm(); 106 | void addr_imp(); 107 | void addr_ind(); 108 | void addr_ixr(); 109 | void addr_ixw(); 110 | void addr_iym(); 111 | void addr_iyr(); 112 | void addr_iyw(); 113 | void addr_rel(); 114 | void addr_zpr(); 115 | void addr_zpw(); 116 | void addr_zxr(); 117 | void addr_zxw(); 118 | void addr_zyr(); 119 | void addr_zyw(); 120 | 121 | using _addr_ptr = void (CPU::*)(); 122 | static const _addr_ptr ADDRESSING_MODES[256]; 123 | 124 | private: 125 | void op_aal(); 126 | void op_adc(); 127 | void op_alr(); 128 | void op_anc(); 129 | void op_and(); 130 | void op_ane(); 131 | void op_arr(); 132 | void op_asl(); 133 | void op_bcc(); 134 | void op_bcs(); 135 | void op_beq(); 136 | void op_bit(); 137 | void op_bmi(); 138 | void op_bne(); 139 | void op_bpl(); 140 | void op_brk(); 141 | void op_bvc(); 142 | void op_bvs(); 143 | void op_clc(); 144 | void op_cld(); 145 | void op_cli(); 146 | void op_clv(); 147 | void op_cmp(); 148 | void op_cpx(); 149 | void op_cpy(); 150 | void op_dcp(); 151 | void op_dec(); 152 | void op_dex(); 153 | void op_dey(); 154 | void op_eor(); 155 | void op_inc(); 156 | void op_inx(); 157 | void op_iny(); 158 | void op_isc(); 159 | void op_jam(); 160 | void op_jmp(); 161 | void op_jsr(); 162 | void op_lar(); 163 | void op_las(); 164 | void op_lax(); 165 | void op_lda(); 166 | void op_ldx(); 167 | void op_ldy(); 168 | void op_lsr(); 169 | void op_lxa(); 170 | void op_nop(); 171 | void op_ora(); 172 | void op_pha(); 173 | void op_php(); 174 | void op_pla(); 175 | void op_plp(); 176 | void op_ral(); 177 | void op_rar(); 178 | void op_rla(); 179 | void op_rol(); 180 | void op_ror(); 181 | void op_rra(); 182 | void op_rti(); 183 | void op_rts(); 184 | void op_sax(); 185 | void op_sbc(); 186 | void op_sbx(); 187 | void op_sec(); 188 | void op_sed(); 189 | void op_sei(); 190 | void op_sha(); 191 | void op_shx(); 192 | void op_shy(); 193 | void op_slo(); 194 | void op_sre(); 195 | void op_sta(); 196 | void op_stx(); 197 | void op_sty(); 198 | void op_tas(); 199 | void op_tax(); 200 | void op_tay(); 201 | void op_tsx(); 202 | void op_txa(); 203 | void op_txs(); 204 | void op_tya(); 205 | void op_usb(); 206 | 207 | using _op_ptr = void (CPU::*)(); 208 | static const _op_ptr INSTRUCTIONS[256]; 209 | 210 | public: 211 | template 212 | constexpr void dump(T& buffer) { 213 | cynes::dump(buffer, _frozen); 214 | cynes::dump(buffer, _register_a); 215 | cynes::dump(buffer, _register_x); 216 | cynes::dump(buffer, _register_y); 217 | cynes::dump(buffer, _register_m); 218 | cynes::dump(buffer, _stack_pointer); 219 | cynes::dump(buffer, _program_counter); 220 | cynes::dump(buffer, _target_address); 221 | cynes::dump(buffer, _status); 222 | 223 | cynes::dump(buffer, _delay_interrupt); 224 | cynes::dump(buffer, _should_issue_interrupt); 225 | cynes::dump(buffer, _line_mapper_interrupt); 226 | cynes::dump(buffer, _line_frame_interrupt); 227 | cynes::dump(buffer, _line_delta_interrupt); 228 | cynes::dump(buffer, _line_non_maskable_interrupt); 229 | cynes::dump(buffer, _edge_detector_non_maskable_interrupt); 230 | cynes::dump(buffer, _delay_non_maskable_interrupt); 231 | cynes::dump(buffer, _should_issue_non_maskable_interrupt); 232 | } 233 | }; 234 | } 235 | 236 | #endif 237 | -------------------------------------------------------------------------------- /src/nes.cpp: -------------------------------------------------------------------------------- 1 | #include "nes.hpp" 2 | 3 | #include "apu.hpp" 4 | #include "cpu.hpp" 5 | #include "ppu.hpp" 6 | #include "mapper.hpp" 7 | 8 | 9 | constexpr uint8_t PALETTE_RAM_BOOT_VALUES[0x20] = { 10 | 0x09, 0x01, 0x00, 0x01, 0x00, 0x02, 0x02, 0x0D, 11 | 0x08, 0x10, 0x08, 0x24, 0x00, 0x00, 0x04, 0x2C, 12 | 0x09, 0x01, 0x34, 0x03, 0x00, 0x04, 0x00, 0x14, 13 | 0x08, 0x3A, 0x00, 0x02, 0x00, 0x20, 0x2C, 0x08 14 | }; 15 | 16 | 17 | cynes::NES::NES(const char* path) 18 | : cpu{*this} 19 | , ppu{*this} 20 | , apu{*this} 21 | , _mapper{Mapper::load_mapper(static_cast(*this), path)} 22 | , _memory_cpu{new uint8_t[0x800]} 23 | , _memory_oam{new uint8_t[0x100]} 24 | , _memory_palette{new uint8_t[0x20]} 25 | { 26 | cpu.power(); 27 | ppu.power(); 28 | apu.power(); 29 | 30 | std::memcpy(_memory_palette.get(), PALETTE_RAM_BOOT_VALUES, 0x20); 31 | std::memset(_memory_cpu.get(), 0x00, 0x800); 32 | std::memset(_memory_oam.get(), 0x00, 0x100); 33 | std::memset(_controller_status, 0x00, 0x2); 34 | std::memset(_controller_shifters, 0x00, 0x2); 35 | 36 | for (int i = 0; i < 8; i++) { 37 | dummy_read(); 38 | } 39 | } 40 | 41 | void cynes::NES::reset() { 42 | cpu.reset(); 43 | ppu.reset(); 44 | apu.reset(); 45 | 46 | for (int i = 0; i < 8; i++) { 47 | dummy_read(); 48 | } 49 | } 50 | 51 | void cynes::NES::dummy_read() { 52 | apu.tick(true); 53 | ppu.tick(); 54 | ppu.tick(); 55 | ppu.tick(); 56 | cpu.poll(); 57 | } 58 | 59 | void cynes::NES::write(uint16_t address, uint8_t value) { 60 | apu.tick(false); 61 | ppu.tick(); 62 | ppu.tick(); 63 | 64 | write_cpu(address, value); 65 | 66 | ppu.tick(); 67 | cpu.poll(); 68 | } 69 | 70 | void cynes::NES::write_cpu(uint16_t address, uint8_t value) { 71 | _open_bus = value; 72 | 73 | if (address < 0x2000) { 74 | _memory_cpu[address & 0x7FF] = value; 75 | } else if (address < 0x4000) { 76 | ppu.write(address & 0x7, value); 77 | } else if (address == 0x4016) { 78 | load_controller_shifter(~value & 0x01); 79 | } else if (address < 0x4018) { 80 | apu.write(address & 0xFF, value); 81 | } else { 82 | _mapper->write_cpu(address, value); 83 | } 84 | } 85 | 86 | void cynes::NES::write_ppu(uint16_t address, uint8_t value) { 87 | address &= 0x3FFF; 88 | 89 | if (address < 0x3F00) { 90 | _mapper->write_ppu(address, value); 91 | } else { 92 | address &= 0x1F; 93 | 94 | if (address == 0x10) { 95 | address = 0x00; 96 | } else if (address == 0x14) { 97 | address = 0x04; 98 | } else if (address == 0x18) { 99 | address = 0x08; 100 | } else if (address == 0x1C) { 101 | address = 0x0C; 102 | } 103 | 104 | _memory_palette[address] = value & 0x3F; 105 | } 106 | } 107 | 108 | void cynes::NES::write_oam(uint8_t address, uint8_t value) { 109 | _memory_oam[address] = value; 110 | } 111 | 112 | uint8_t cynes::NES::read(uint16_t address) { 113 | apu.tick(true); 114 | ppu.tick(); 115 | ppu.tick(); 116 | 117 | _open_bus = read_cpu(address); 118 | 119 | ppu.tick(); 120 | cpu.poll(); 121 | 122 | return _open_bus; 123 | } 124 | 125 | uint8_t cynes::NES::read_cpu(uint16_t address) { 126 | if (address < 0x2000) { 127 | return _memory_cpu[address & 0x7FF]; 128 | } else if (address < 0x4000) { 129 | return ppu.read(address & 0x7); 130 | } else if (address == 0x4016) { 131 | return poll_controller(0x0); 132 | } else if (address == 0x4017) { 133 | return poll_controller(0x1); 134 | } else if (address < 0x4018) { 135 | return apu.read(address & 0xFF); 136 | } else { 137 | return _mapper->read_cpu(address); 138 | } 139 | } 140 | 141 | uint8_t cynes::NES::read_ppu(uint16_t address) { 142 | address &= 0x3FFF; 143 | 144 | if (address < 0x3F00) { 145 | return _mapper->read_ppu(address); 146 | } else { 147 | address &= 0x1F; 148 | 149 | if (address == 0x10) { 150 | address = 0x00; 151 | } else if (address == 0x14) { 152 | address = 0x04; 153 | } else if (address == 0x18) { 154 | address = 0x08; 155 | } else if (address == 0x1C) { 156 | address = 0x0C; 157 | } 158 | 159 | return _memory_palette[address]; 160 | } 161 | } 162 | 163 | uint8_t cynes::NES::read_oam(uint8_t address) const { 164 | return _memory_oam[address]; 165 | } 166 | 167 | uint8_t cynes::NES::get_open_bus() const { 168 | return _open_bus; 169 | } 170 | 171 | bool cynes::NES::step(uint16_t controllers, unsigned int frames) { 172 | _controller_status[0x0] = controllers & 0xFF; 173 | _controller_status[0x1] = controllers >> 8; 174 | 175 | for (unsigned int k = 0; k < frames; k++) { 176 | while (!ppu.is_frame_ready()) { 177 | cpu.tick(); 178 | 179 | if (cpu.is_frozen()) { 180 | return true; 181 | } 182 | } 183 | } 184 | 185 | return false; 186 | } 187 | 188 | unsigned int cynes::NES::size() { 189 | unsigned int buffer_size = 0; 190 | dump(buffer_size); 191 | 192 | return buffer_size; 193 | } 194 | 195 | void cynes::NES::save(uint8_t* buffer) { 196 | dump(buffer); 197 | } 198 | 199 | void cynes::NES::load(uint8_t* buffer) { 200 | dump(buffer); 201 | } 202 | 203 | cynes::Mapper& cynes::NES::get_mapper() { 204 | return static_cast(*_mapper.get()); 205 | } 206 | 207 | void cynes::NES::load_controller_shifter(bool polling) { 208 | if (polling) { 209 | memcpy(_controller_shifters, _controller_status, 0x2); 210 | } 211 | } 212 | 213 | uint8_t cynes::NES::poll_controller(uint8_t player) { 214 | uint8_t value = _controller_shifters[player] >> 7; 215 | 216 | _controller_shifters[player] <<= 1; 217 | 218 | return (_open_bus & 0xE0) | value; 219 | } 220 | 221 | template 222 | void cynes::NES::dump(T& buffer) { 223 | cpu.dump(buffer); 224 | ppu.dump(buffer); 225 | apu.dump(buffer); 226 | 227 | _mapper->dump(buffer); 228 | 229 | cynes::dump(buffer, _memory_cpu.get(), 0x800); 230 | cynes::dump(buffer, _memory_oam.get(), 0x100); 231 | cynes::dump(buffer, _memory_palette.get(), 0x20); 232 | 233 | cynes::dump(buffer, _controller_status); 234 | cynes::dump(buffer, _controller_shifters); 235 | } 236 | 237 | template void cynes::NES::dump(unsigned int&); 238 | template void cynes::NES::dump(uint8_t*&); 239 | template void cynes::NES::dump(uint8_t*&); 240 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cynes - C/C++ NES emulator with Python bindings 2 | cynes is a lightweight multi-platform NES emulator providing a simple Python interface. The core of the emulation is based on the very complete documentation provided by the [Nesdev Wiki](https://www.nesdev.org/wiki/NES_reference_guide). The current implementation consists of 3 | - A cycle-accurate CPU emulation 4 | - A cycle-accurate PPU emulation 5 | - A cycle-accurate APU emulation (even though it does not produce any sound) 6 | - Few basic NES mappers (more to come) 7 | 8 | The Python bindings allow to interact easily with one or several NES emulators at the same time, ideal for machine learning application. 9 | 10 | ## Installation 11 | cynes can be installed using pip : 12 | ```sh 13 | pip install cynes 14 | ``` 15 | 16 | It can also be built from source using (requires `cmake`) : 17 | ```sh 18 | git clone https://github.com/Youlixx/cynes 19 | cd cynes/ 20 | pip install . 21 | ``` 22 | 23 | ## How to use 24 | A cynes NES emulator can be created by instantiating a new NES object. The following code is the minimal code to run a ROM file. 25 | ```python 26 | from cynes.windowed import WindowedNES 27 | 28 | # We initialize a new emulator by specifying the ROM file used 29 | with WindowedNES("rom.nes") as nes: 30 | # While the emulator should not be closed, we can continue the emulation 31 | while not nes.should_close: 32 | # The step method run the emulation for a single frame 33 | # It also returns the content of the frame buffer as a numpy array 34 | frame = nes.step() 35 | ``` 36 | Multiple emulators can be created at once by instantiating several NES objects. 37 | 38 | ### Windowed / Headless modes 39 | The default NES class run in "headless" mode, meaning that no rendering is performed. A simple wrapper around the base emulator providing a basic renderer and input handling using SDL2 is present in the `windowed` submodule. 40 | ```python 41 | from cynes import NES 42 | from cynes.windowed import WindowedNES 43 | 44 | # We can create a NES emulator without a rendering window 45 | nes_headless = NES("rom.nes") 46 | 47 | while not nes_headless.has_crashed: 48 | frame = nes_headless.step() 49 | 50 | # And with the rendering window 51 | nes_windowed = WindowedNES("rom.nes") 52 | 53 | while not nes_windowed.should_close: 54 | frame = nes_windowed.step() 55 | ``` 56 | While the rendering overhead is quite small, running in headless mode can improve the performances when the window is not needed. The content of the frame buffer can always be accessed using the `step` method. 57 | 58 | ### Controller 59 | The state of the controller can be directly modified using the following syntax : 60 | ```python 61 | from cynes import * 62 | 63 | # Simple input 64 | nes.controller = NES_INPUT_RIGHT 65 | 66 | # Multiple button presses at once 67 | nes.controller = NES_INPUT_RIGHT | NES_INPUT_A 68 | 69 | # Chaining multiple button presses at once 70 | nes.controller = NES_INPUT_START 71 | nes.controller |= NES_INPUT_B 72 | nes.controller |= NES_INPUT_SELECT 73 | 74 | # Undefined behavior 75 | nes.controller = NES_INPUT_RIGHT | NES_INPUT_LEFT 76 | nes.controller = NES_INPUT_DOWN | NES_INPUT_UP 77 | 78 | # Run the emulator with the specified controller state for 5 frames 79 | nes.step(frames=5) 80 | ``` 81 | Note that the state of the controller is maintained even after the `step` method is called. This means that it has to be reset to 0 to release the buttons. 82 | 83 | Two controllers can be used at the same time. The state of the second controller can be modified by updating the 8 most significant bits of the same variable. 84 | 85 | ```python 86 | # P1 will press left and P2 will press the right button 87 | nes.controller = NES_INPUT_LEFT | NES_INPUT_RIGHT << 8 88 | ``` 89 | 90 | ### Key handlers 91 | Key handlers are a simple way of associating custom actions to shortcuts. This feature is only present with the windowed mode. The key events (and their associated handlers) are fired when calling the `step` method. 92 | ```python 93 | # Disable the default window controls 94 | nes = WindowedNES("rom.nes", default_handlers=False) 95 | 96 | # Custom key handlers can be defined using the register method 97 | import sdl2 98 | 99 | def kill(): 100 | nes.close() 101 | 102 | nes.register(sdl2.SDL_SCANCODE_O, kill) 103 | ``` 104 | By default, the emulator comes with key handlers that map window keys to the controller buttons. The mapping is the following : 105 | - the arrow keys for the D-pad 106 | - the keys X and Z for the A and B buttons respectively 107 | - the keys A and S for the SELECT and START buttons respectively 108 | 109 | ### Save states 110 | The state of the emulator can be saved as a numpy array and later be restored. 111 | ```python 112 | # The state of the emulator can be dump using the save method 113 | save_state = nes.save() 114 | 115 | # And restored using the load method 116 | nes.load(save_state) 117 | ``` 118 | Memory modification should never be performed directly on a save state, as it is prone to memory corruption. Theses two methods can be quite slow, therefore, they should be called sparsely. 119 | 120 | ### Memory access 121 | The memory of the emulator can be read from and written to using the following syntax : 122 | ```python 123 | # The memory content can be accessed as if the emulator was an array 124 | player_state = nes[0x000E] 125 | 126 | # And can be written in a similar fashion 127 | nes[0x075A] = 0x8 128 | ``` 129 | Note that only the CPU RAM `$0000 - $1FFFF` and the mapper RAM `$6000 - $7FFF` should be accessed. Trying to read / write a value to other addresses may de-synchronize the components of the emulator, resulting in a undefined behavior. 130 | 131 | ### Closing 132 | An emulator is automatically closed when the object is released by Python. In windowed mode, the `close` method can be used to close the window without having to wait for Python to release the object. As presented previously, the WindowedNES can also be used as a context manager, which will call `close` automatically when exiting the context. 133 | It can also be closed manually using the `close` method. 134 | ```python 135 | # In windowed mode, this can be used to close the window 136 | nes.close() 137 | 138 | # Deleting the emulator in windowed mode also closes the window 139 | del nes 140 | 141 | # The method should_close indicates whether or not the emulator function should be called 142 | nes.close() 143 | nes.should_close # True 144 | ``` 145 | When the emulator is closed, but the object is not deleted yet, the `should_close` property will be set to True, indicating that calling any NES function will not work properly. This method can also return True in two other cases : 146 | - When the CPU of the emulator is frozen. When the CPU hits a JAM instruction (illegal opcode), it is frozen until the emulator is reset. This should never happen, but memory corruptions can cause them, so be careful when accessing the NES memory. 147 | - In windowed mode, when the window is closed or when the ESC key is pressed. 148 | 149 | ## License 150 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 151 | -------------------------------------------------------------------------------- /src/ppu.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CYNES_PPU__ 2 | #define __CYNES_PPU__ 3 | 4 | #include 5 | #include 6 | 7 | #include "utils.hpp" 8 | 9 | namespace cynes { 10 | // Forward declaration. 11 | class NES; 12 | 13 | /// Picture Processing Unit (see https://www.nesdev.org/wiki/PPU). 14 | class PPU { 15 | public: 16 | /// Initialize the PPU. 17 | PPU(NES& nes); 18 | 19 | /// Default destructor. 20 | ~PPU() = default; 21 | 22 | public: 23 | /// Set the PPU in its power-up state. 24 | void power(); 25 | 26 | /// Set the PPU in its reset state. 27 | void reset(); 28 | 29 | /// Tick the PPU. 30 | void tick(); 31 | 32 | /// Write to the PPU memory. 33 | /// @note This function has other side effects than simply writing to the memory, it 34 | /// should not be used as a memory set function. 35 | /// @param address Memory address within the PPU memory address space. 36 | /// @param value Value to write. 37 | void write(uint8_t address, uint8_t value); 38 | 39 | /// Read from the APU memory. 40 | /// @note This function has other side effects than simply reading from memory, it 41 | /// should not be used as a memory watch function. 42 | /// @param address Memory address within the PPU memory address space. 43 | /// @return The value stored at the given address. 44 | uint8_t read(uint8_t address); 45 | 46 | /// Get a pointer to the internal frame buffer. 47 | const uint8_t* get_frame_buffer() const; 48 | 49 | /// Check whether or not the frame is ready. 50 | /// @note Calling this function will reset the flag. 51 | /// @return True if the frame is ready, false otherwise. 52 | bool is_frame_ready(); 53 | 54 | private: 55 | NES& _nes; 56 | 57 | private: 58 | std::unique_ptr _frame_buffer; 59 | 60 | uint16_t _current_x; 61 | uint16_t _current_y; 62 | 63 | bool _frame_ready; 64 | 65 | bool _rendering_enabled; 66 | bool _rendering_enabled_delayed; 67 | bool _prevent_vertical_blank; 68 | 69 | private: 70 | bool _control_increment_mode; 71 | bool _control_foreground_table; 72 | bool _control_background_table; 73 | bool _control_foreground_large; 74 | bool _control_interrupt_on_vertical_blank; 75 | 76 | private: 77 | bool _mask_grayscale_mode; 78 | bool _mask_render_background_left; 79 | bool _mask_render_foreground_left; 80 | bool _mask_render_background; 81 | bool _mask_render_foreground; 82 | 83 | uint8_t _mask_color_emphasize; 84 | 85 | private: 86 | bool _status_sprite_overflow; 87 | bool _status_sprite_zero_hit; 88 | bool _status_vertical_blank; 89 | 90 | private: 91 | const uint8_t DECAY_PERIOD = 30; 92 | uint8_t _clock_decays[3]; 93 | uint8_t _register_decay; 94 | 95 | private: 96 | bool _latch_cycle; 97 | bool _latch_address; 98 | 99 | uint16_t _register_t; 100 | uint16_t _register_v; 101 | uint16_t _delayed_register_v; 102 | 103 | uint8_t _scroll_x; 104 | uint8_t _delay_data_read_counter; 105 | uint8_t _delay_data_write_counter; 106 | uint8_t _buffer_data; 107 | 108 | void increment_scroll_x(); 109 | void increment_scroll_y(); 110 | 111 | void reset_scroll_x(); 112 | void reset_scroll_y(); 113 | 114 | private: 115 | uint8_t _background_data[0x4]; 116 | uint16_t _background_shifter[0x4]; 117 | 118 | void load_background_shifters(); 119 | void update_background_shifters(); 120 | 121 | private: 122 | uint8_t _foreground_data[0x20]; 123 | uint8_t _foreground_shifter[0x10]; 124 | uint8_t _foreground_attributes[0x8]; 125 | uint8_t _foreground_positions[0x8]; 126 | 127 | uint8_t _foreground_data_pointer; 128 | uint8_t _foreground_sprite_count; 129 | uint8_t _foreground_sprite_count_next; 130 | uint8_t _foreground_sprite_pointer; 131 | uint8_t _foreground_read_delay_counter; 132 | 133 | uint16_t _foreground_sprite_address; 134 | 135 | bool _foreground_sprite_zero_line; 136 | bool _foreground_sprite_zero_should; 137 | bool _foreground_sprite_zero_hit; 138 | 139 | enum class SpriteEvaluationStep { 140 | LOAD_SECONDARY_OAM, INCREMENT_POINTER, IDLE 141 | } _foreground_evaluation_step; 142 | 143 | void reset_foreground_data(); 144 | void clear_foreground_data(); 145 | void fetch_foreground_data(); 146 | void load_foreground_shifter(); 147 | void update_foreground_shifter(); 148 | 149 | uint8_t blend_colors(); 150 | 151 | private: 152 | enum class Register : uint8_t { 153 | PPU_CTRL = 0x00, 154 | PPU_MASK = 0x01, 155 | PPU_STATUS = 0x02, 156 | OAM_ADDR = 0x03, 157 | OAM_DATA = 0x04, 158 | PPU_SCROLL = 0x05, 159 | PPU_ADDR = 0x06, 160 | PPU_DATA = 0x07 161 | }; 162 | 163 | public: 164 | template 165 | constexpr void dump(T& buffer) { 166 | cynes::dump(buffer, _current_x); 167 | cynes::dump(buffer, _current_y); 168 | cynes::dump(buffer, _frame_ready); 169 | cynes::dump(buffer, _rendering_enabled); 170 | cynes::dump(buffer, _rendering_enabled_delayed); 171 | cynes::dump(buffer, _prevent_vertical_blank); 172 | 173 | cynes::dump(buffer, _control_increment_mode); 174 | cynes::dump(buffer, _control_foreground_table); 175 | cynes::dump(buffer, _control_background_table); 176 | cynes::dump(buffer, _control_foreground_large); 177 | cynes::dump(buffer, _control_interrupt_on_vertical_blank); 178 | 179 | cynes::dump(buffer, _mask_grayscale_mode); 180 | cynes::dump(buffer, _mask_render_background_left); 181 | cynes::dump(buffer, _mask_render_foreground_left); 182 | cynes::dump(buffer, _mask_render_background); 183 | cynes::dump(buffer, _mask_render_foreground); 184 | cynes::dump(buffer, _mask_color_emphasize); 185 | 186 | cynes::dump(buffer, _status_sprite_overflow); 187 | cynes::dump(buffer, _status_sprite_zero_hit); 188 | cynes::dump(buffer, _status_vertical_blank); 189 | 190 | cynes::dump(buffer, _clock_decays); 191 | cynes::dump(buffer, _register_decay); 192 | 193 | cynes::dump(buffer, _latch_cycle); 194 | cynes::dump(buffer, _latch_address); 195 | cynes::dump(buffer, _register_t); 196 | cynes::dump(buffer, _register_v); 197 | cynes::dump(buffer, _delayed_register_v); 198 | cynes::dump(buffer, _scroll_x); 199 | cynes::dump(buffer, _delay_data_read_counter); 200 | cynes::dump(buffer, _delay_data_write_counter); 201 | cynes::dump(buffer, _buffer_data); 202 | 203 | cynes::dump(buffer, _background_data); 204 | cynes::dump(buffer, _background_shifter); 205 | 206 | cynes::dump(buffer, _foreground_data); 207 | cynes::dump(buffer, _foreground_shifter); 208 | cynes::dump(buffer, _foreground_attributes); 209 | cynes::dump(buffer, _foreground_positions); 210 | cynes::dump(buffer, _foreground_data_pointer); 211 | cynes::dump(buffer, _foreground_sprite_count); 212 | cynes::dump(buffer, _foreground_sprite_count_next); 213 | cynes::dump(buffer, _foreground_sprite_pointer); 214 | cynes::dump(buffer, _foreground_read_delay_counter); 215 | cynes::dump(buffer, _foreground_sprite_address); 216 | cynes::dump(buffer, _foreground_sprite_zero_line); 217 | cynes::dump(buffer, _foreground_sprite_zero_should); 218 | cynes::dump(buffer, _foreground_sprite_zero_hit); 219 | cynes::dump(buffer, _foreground_evaluation_step); 220 | } 221 | }; 222 | } 223 | 224 | #endif 225 | -------------------------------------------------------------------------------- /cynes/windowed.py: -------------------------------------------------------------------------------- 1 | # cynes - C/C++ NES emulator with Python bindings 2 | # Copyright (C) 2021 - 2025 Combey Theo 3 | 4 | """Module containing a simple NES wrapper using SDL2 for the rendering.""" 5 | 6 | from typing import Any, Callable, Optional, Type 7 | 8 | import numpy as np 9 | import sdl2 10 | from numpy.typing import NDArray 11 | 12 | from cynes import ( 13 | NES_INPUT_A, 14 | NES_INPUT_B, 15 | NES_INPUT_DOWN, 16 | NES_INPUT_LEFT, 17 | NES_INPUT_RIGHT, 18 | NES_INPUT_SELECT, 19 | NES_INPUT_START, 20 | NES_INPUT_UP, 21 | ) 22 | from cynes.emulator import NES 23 | 24 | 25 | class SDLContext: 26 | """SDL helper context.""" 27 | 28 | def __init__(self, window_name: str, scaling_factor: int = 3) -> None: 29 | """Initialize the SDL context. 30 | 31 | Args: 32 | window_name (str): Name of the window. 33 | scaling_factor (int): Scaling factor of the window size. 34 | """ 35 | self._hidden = False 36 | 37 | self._window = sdl2.SDL_CreateWindow( 38 | bytes(window_name, "ascii"), 39 | sdl2.SDL_WINDOWPOS_UNDEFINED, 40 | sdl2.SDL_WINDOWPOS_UNDEFINED, 41 | scaling_factor * 256, 42 | scaling_factor * 240, 43 | sdl2.SDL_WINDOW_SHOWN 44 | ) 45 | 46 | self._renderer = sdl2.SDL_CreateRenderer( 47 | self._window, 48 | -1, 49 | sdl2.SDL_RENDERER_ACCELERATED 50 | ) 51 | 52 | self._texture = sdl2.SDL_CreateTexture( 53 | self._renderer, 54 | sdl2.SDL_PIXELFORMAT_RGB24, 55 | sdl2.SDL_TEXTUREACCESS_STREAMING, 56 | 256, 57 | 240 58 | ) 59 | 60 | self.keyboard_state = sdl2.SDL_GetKeyboardState(None) 61 | 62 | def __del__(self) -> None: 63 | """Destroy SDL2 variables.""" 64 | sdl2.SDL_DestroyRenderer(self._renderer) 65 | sdl2.SDL_DestroyTexture(self._texture) 66 | sdl2.SDL_DestroyWindow(self._window) 67 | 68 | def render_frame(self, framebuffer: NDArray[np.uint8]) -> None: 69 | """Update the rendered frame. 70 | 71 | Args: 72 | framebuffer (NDArray[np.uint8]): The numpy array containing the framebuffer 73 | in the same format as returned by the emulator (shape 240x256x3). 74 | """ 75 | sdl2.SDL_UpdateTexture( 76 | self._texture, 77 | None, 78 | framebuffer.ctypes._as_parameter_, 79 | 768 80 | ) 81 | 82 | sdl2.SDL_RenderCopy(self._renderer, self._texture, None, None) 83 | sdl2.SDL_RenderPresent(self._renderer) 84 | 85 | def hide_window(self) -> None: 86 | """Hide the SDL2 window.""" 87 | if not self._hidden: 88 | self._hidden = True 89 | sdl2.SDL_HideWindow(self._window) 90 | 91 | @property 92 | def has_focus(self) -> bool: 93 | """Indicate whether the emulator window has the focus.""" 94 | return sdl2.SDL_GetWindowFlags(self._window) & sdl2.SDL_WINDOW_INPUT_FOCUS 95 | 96 | 97 | class WindowedNES(NES): 98 | """The windowed emulator class.""" 99 | 100 | def __init__( 101 | self, 102 | path_rom: str, 103 | scaling_factor: int = 3, 104 | default_handlers: bool = True 105 | ) -> None: 106 | """Initialize the NES emulator. 107 | 108 | This function sets up the NES emulator by loading the specified ROM file. The 109 | initialization process can fail for several reasons, including: 110 | - The ROM file cannot be found at the specified path. 111 | - The given file is not a valid ROM file. 112 | - The mapper used by the ROM is currently unsupported by the emulator. 113 | 114 | Args: 115 | path_rom (str): The path to the NES ROM file containing the game data. 116 | scaling_factor (int): Scaling factor of the window size. 117 | default_handlers (bool): If set to True, the default key handlers will be 118 | registered. 119 | """ 120 | super().__init__(path_rom) 121 | 122 | self._should_close = False 123 | self._handlers = {sdl2.SDL_SCANCODE_ESCAPE: self.__input_escape} 124 | 125 | self._context = SDLContext( 126 | window_name=path_rom, 127 | scaling_factor=scaling_factor 128 | ) 129 | 130 | if default_handlers: 131 | self.register_handler(sdl2.SDL_SCANCODE_X, self.__input_a) 132 | self.register_handler(sdl2.SDL_SCANCODE_Z, self.__input_b) 133 | self.register_handler(sdl2.SDL_SCANCODE_A, self.__input_select) 134 | self.register_handler(sdl2.SDL_SCANCODE_S, self.__input_start) 135 | self.register_handler(sdl2.SDL_SCANCODE_UP, self.__input_up) 136 | self.register_handler(sdl2.SDL_SCANCODE_DOWN, self.__input_down) 137 | self.register_handler(sdl2.SDL_SCANCODE_LEFT, self.__input_left) 138 | self.register_handler(sdl2.SDL_SCANCODE_RIGHT, self.__input_right) 139 | 140 | def __enter__(self) -> "WindowedNES": 141 | """Enters the runtime context related to the emulator. 142 | 143 | This method is part of the context management protocol and is called when 144 | exiting a `with` statement block. It ensures that the emulator window is 145 | properly closed, releasing any associated resources. 146 | 147 | Returns: 148 | emulator (WindowedNES): The current instance of the NES emulator. 149 | """ 150 | return self 151 | 152 | def __exit__( 153 | self, 154 | error: Optional[Type[BaseException]], 155 | value: Optional[BaseException], 156 | traceback: Optional[Any] 157 | ) -> bool: 158 | """Close the window when exiting the runtime context related to the emulator. 159 | 160 | This method is part of the context management protocol and is called when 161 | exiting a `with` statement block. It ensures that the emulator window is 162 | properly closed, releasing any associated resources. 163 | 164 | Args: 165 | error (Type[BaseException], optional): If an error occurred, the type of the 166 | exception. 167 | value (BaseException, optional): If an error occurred, the exception itself. 168 | traceback (Any, optional): If an error occurred, the current traceback. 169 | 170 | Returns: 171 | should_suppress_error (bool): True if the exception should be suppressed, 172 | False otherwise. 173 | """ 174 | self.close() 175 | return False 176 | 177 | def __input_a(self) -> None: 178 | self.controller |= NES_INPUT_A 179 | 180 | def __input_b(self) -> None: 181 | self.controller |= NES_INPUT_B 182 | 183 | def __input_select(self) -> None: 184 | self.controller |= NES_INPUT_SELECT 185 | 186 | def __input_start(self) -> None: 187 | self.controller |= NES_INPUT_START 188 | 189 | def __input_up(self) -> None: 190 | self.controller |= NES_INPUT_UP 191 | 192 | def __input_down(self) -> None: 193 | self.controller |= NES_INPUT_DOWN 194 | 195 | def __input_left(self) -> None: 196 | self.controller |= NES_INPUT_LEFT 197 | 198 | def __input_right(self) -> None: 199 | self.controller |= NES_INPUT_RIGHT 200 | 201 | def __input_escape(self) -> None: 202 | self.close() 203 | 204 | def register_handler(self, key_code: int, handler: Callable[[], None]) -> None: 205 | """Register a new key handler. 206 | 207 | Args: 208 | key_code (int): The code of the key that will trigger the handler function. 209 | handler (Callable[[], None]): A function that takes no arguments and is 210 | called when the specified key is pressed. 211 | """ 212 | self._handlers[key_code] = handler 213 | 214 | def step(self, frames: int = 1) -> NDArray[np.uint8]: 215 | """Run the emulator for the specified number of frames. 216 | 217 | This method advances the emulator's state by the specified number of frames. By 218 | default, it runs the emulator for a single frame. In windowed mode, also updates 219 | the rendered frame. 220 | 221 | Args: 222 | frames (int): The number of frames to run the emulator for. Default is 1. 223 | 224 | Returns: 225 | framebuffer (NDArray[np.uint8]): A NumPy array containing the frame buffer 226 | in RGB format. The array has a shape of (240, 256, 3) and provides a 227 | read-only view of the framebuffer. If modifications are needed, a copy 228 | of the array should be made. 229 | """ 230 | if self.should_close: 231 | self._context.hide_window() 232 | return 233 | 234 | sdl2.SDL_PumpEvents() 235 | 236 | # TODO: necessary? 237 | previous_state = self.controller 238 | 239 | if self._context.has_focus: 240 | for handler in self._handlers: 241 | if self._context.keyboard_state[handler]: 242 | self._handlers[handler]() 243 | 244 | event = sdl2.SDL_Event() 245 | 246 | while sdl2.SDL_PollEvent(event): 247 | if event.type == sdl2.SDL_QUIT: 248 | self.close() 249 | return 250 | 251 | frame_buffer = super().step(frames=frames) 252 | 253 | self._context.render_frame(frame_buffer) 254 | self.controller = previous_state 255 | 256 | return frame_buffer 257 | 258 | def close(self) -> None: 259 | """Close the window.""" 260 | self._should_close = True 261 | self._context.hide_window() 262 | 263 | @property 264 | def should_close(self) -> bool: 265 | """Indicate whether the emulator should close. 266 | 267 | This is set to True if the emulator has crashed or if the user has requested to 268 | close the emulator window. 269 | """ 270 | return self.has_crashed or self._should_close 271 | -------------------------------------------------------------------------------- /src/apu.cpp: -------------------------------------------------------------------------------- 1 | #include "apu.hpp" 2 | #include "cpu.hpp" 3 | #include "ppu.hpp" 4 | #include "nes.hpp" 5 | 6 | #include 7 | 8 | constexpr uint8_t LENGTH_COUNTER_TABLE[0x20] = { 9 | 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 10 | 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, 11 | 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 12 | 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E 13 | }; 14 | 15 | constexpr uint16_t PERIOD_DMC_TABLE[0x10] = { 16 | 0x1AC, 0x17C, 0x154, 0x140, 0x11E, 0x0FE, 0x0E2, 0x0D6, 17 | 0x0BE, 0x0A0, 0x08E, 0x080, 0x06A, 0x054, 0x048, 0x036 18 | }; 19 | 20 | 21 | cynes::APU::APU(NES& nes) 22 | : _nes{nes} 23 | , _latch_cycle{false} 24 | , _delay_dma{0x00} 25 | , _address_dma{0x00} 26 | , _pending_dma{false} 27 | , _internal_open_bus{0x00} 28 | , _frame_counter_clock{0x0000} 29 | , _delay_frame_reset{0x0000} 30 | , _channels_counters{} 31 | , _channel_enabled{} 32 | , _channel_halted{} 33 | , _step_mode{false} 34 | , _inhibit_frame_interrupt{false} 35 | , _send_frame_interrupt{false} 36 | , _delta_channel_remaining_bytes{0x0000} 37 | , _delta_channel_sample_length{0x0000} 38 | , _delta_channel_period_counter{0x0000} 39 | , _delta_channel_period_load{0x0000} 40 | , _delta_channel_bits_in_buffer{0x00} 41 | , _delta_channel_should_loop{false} 42 | , _delta_channel_enable_interrupt{false} 43 | , _delta_channel_sample_buffer_empty{false} 44 | , _enable_dmc{false} 45 | , _send_delta_channel_interrupt{false} 46 | { 47 | std::memset(_channels_counters, 0x00, 4); 48 | std::memset(_channel_enabled, false, 4); 49 | std::memset(_channel_halted, false, 4); 50 | } 51 | 52 | void cynes::APU::power() { 53 | _latch_cycle = false; 54 | _delay_dma = 0x00; 55 | _address_dma = 0x00; 56 | _pending_dma = false; 57 | _internal_open_bus = 0x00; 58 | _frame_counter_clock = 0x0000; 59 | _delay_frame_reset = 0x0000; 60 | 61 | std::memset(_channels_counters, 0x00, 4); 62 | std::memset(_channel_enabled, false, 4); 63 | std::memset(_channel_halted, false, 4); 64 | 65 | _step_mode = false; 66 | _inhibit_frame_interrupt = false; 67 | _send_frame_interrupt = false; 68 | _delta_channel_remaining_bytes = 0x0000; 69 | _delta_channel_sample_length = 0x0000; 70 | _delta_channel_period_counter = PERIOD_DMC_TABLE[0]; 71 | _delta_channel_period_load = PERIOD_DMC_TABLE[0]; 72 | _delta_channel_bits_in_buffer = 0x08; 73 | _delta_channel_should_loop = false; 74 | _delta_channel_enable_interrupt = false; 75 | _delta_channel_sample_buffer_empty = true; 76 | _enable_dmc = false; 77 | _send_delta_channel_interrupt = false; 78 | } 79 | 80 | void cynes::APU::reset() { 81 | _enable_dmc = false; 82 | 83 | std::memset(_channels_counters, 0x00, 4); 84 | std::memset(_channel_enabled, false, 4); 85 | 86 | _send_delta_channel_interrupt = false; 87 | _delta_channel_remaining_bytes = 0; 88 | _latch_cycle = false; 89 | _delay_dma = 0x00; 90 | _send_frame_interrupt = false; 91 | _send_delta_channel_interrupt = false; 92 | _delta_channel_period_counter = PERIOD_DMC_TABLE[0]; 93 | _delta_channel_period_load = PERIOD_DMC_TABLE[0]; 94 | _delta_channel_remaining_bytes = 0; 95 | _delta_channel_sample_buffer_empty = true; 96 | _delta_channel_bits_in_buffer = 8; 97 | 98 | _nes.write(0x4015, 0x00); 99 | _nes.write(0x4017, _step_mode << 7 | _inhibit_frame_interrupt << 6); 100 | } 101 | 102 | void cynes::APU::tick(bool reading, bool prevent_load) { 103 | if (reading) { 104 | perform_pending_dma(); 105 | } 106 | 107 | _latch_cycle = !_latch_cycle; 108 | 109 | if (_step_mode) { 110 | if (_delay_frame_reset > 0 && --_delay_frame_reset == 0) { 111 | _frame_counter_clock = 0; 112 | } else if (++_frame_counter_clock == 37282) { 113 | _frame_counter_clock = 0; 114 | } if (_frame_counter_clock == 14913 || _frame_counter_clock == 37281) { 115 | update_counters(); 116 | } 117 | } else { 118 | if (_delay_frame_reset > 0 && --_delay_frame_reset == 0) { 119 | _frame_counter_clock = 0; 120 | } else if (++_frame_counter_clock == 29830) { 121 | _frame_counter_clock = 0; 122 | 123 | if (!_inhibit_frame_interrupt) { 124 | set_frame_interrupt(true); 125 | } 126 | } 127 | 128 | if (_frame_counter_clock == 14913 || _frame_counter_clock == 29829) { 129 | update_counters(); 130 | } 131 | 132 | if (_frame_counter_clock >= 29828 && !_inhibit_frame_interrupt) { 133 | set_frame_interrupt(true); 134 | } 135 | } 136 | 137 | _delta_channel_period_counter--; 138 | 139 | if (_delta_channel_period_counter == 0) { 140 | _delta_channel_period_counter = _delta_channel_period_load; 141 | _delta_channel_bits_in_buffer--; 142 | 143 | if (_delta_channel_bits_in_buffer == 0) { 144 | _delta_channel_bits_in_buffer = 8; 145 | 146 | if (!_delta_channel_sample_buffer_empty) { 147 | _delta_channel_sample_buffer_empty = true; 148 | } 149 | 150 | if (_delta_channel_remaining_bytes > 0 && !prevent_load) { 151 | load_delta_channel_byte(reading); 152 | } 153 | } 154 | } 155 | } 156 | 157 | void cynes::APU::write(uint8_t address, uint8_t value) { 158 | switch (static_cast(address)) { 159 | case Register::PULSE_1_0: { 160 | _channel_halted[0x0] = value & 0x20; 161 | break; 162 | } 163 | 164 | case Register::PULSE_1_3: { 165 | if (_channel_enabled[0x0]) { 166 | _channels_counters[0x0] = LENGTH_COUNTER_TABLE[value >> 3]; 167 | } 168 | break; 169 | } 170 | 171 | case Register::PULSE_2_0: { 172 | _channel_halted[0x1] = value & 0x20; 173 | break; 174 | } 175 | 176 | case Register::PULSE_2_3: { 177 | if (_channel_enabled[0x1]) { 178 | _channels_counters[0x1] = LENGTH_COUNTER_TABLE[value >> 3]; 179 | } 180 | break; 181 | } 182 | 183 | case Register::TRIANGLE_0: { 184 | _channel_halted[0x2] = value & 0x80; 185 | break; 186 | } 187 | 188 | case Register::TRIANGLE_3: { 189 | if (_channel_enabled[0x2]) { 190 | _channels_counters[0x2] = LENGTH_COUNTER_TABLE[value >> 3]; 191 | } 192 | break; 193 | } 194 | 195 | case Register::NOISE_0: { 196 | _channel_halted[0x3] = value & 0x20; 197 | break; 198 | } 199 | 200 | case Register::NOISE_3: 201 | if (_channel_enabled[0x3]) { 202 | _channels_counters[0x3] = LENGTH_COUNTER_TABLE[value >> 3]; 203 | } 204 | break; 205 | 206 | case Register::OAM_DMA:{ 207 | perform_dma(value); 208 | break; 209 | } 210 | 211 | case Register::DELTA_3: { 212 | _delta_channel_sample_length = (value << 4) + 1; 213 | break; 214 | } 215 | 216 | case Register::DELTA_0: { 217 | _delta_channel_enable_interrupt = value & 0x80; 218 | _delta_channel_should_loop = value & 0x40; 219 | _delta_channel_period_load = PERIOD_DMC_TABLE[value & 0x0F]; 220 | 221 | if (!_delta_channel_enable_interrupt) { 222 | set_delta_interrupt(false); 223 | } 224 | 225 | break; 226 | } 227 | 228 | case Register::CTRL_STATUS: { 229 | _enable_dmc = value & 0x10; 230 | _internal_open_bus = value; 231 | 232 | for (uint8_t channel = 0; channel < 0x4; channel++) { 233 | _channel_enabled[channel] = value & (1 << channel); 234 | 235 | if (!_channel_enabled[channel]) { 236 | _channels_counters[channel] = 0; 237 | } 238 | } 239 | 240 | set_delta_interrupt(false); 241 | 242 | if (!_enable_dmc) { 243 | _delta_channel_remaining_bytes = 0; 244 | } else { 245 | if (_delta_channel_remaining_bytes == 0) { 246 | _delta_channel_remaining_bytes = _delta_channel_sample_length; 247 | if (_delta_channel_sample_buffer_empty) { 248 | load_delta_channel_byte(false); 249 | } 250 | } 251 | } 252 | 253 | break; 254 | } 255 | 256 | case Register::FRAME_COUNTER: { 257 | _step_mode = value & 0x80; 258 | _inhibit_frame_interrupt = value & 0x40; 259 | 260 | if (_inhibit_frame_interrupt) { 261 | set_frame_interrupt(false); 262 | } 263 | 264 | _delay_frame_reset = _latch_cycle ? 4 : 3; 265 | 266 | if (_step_mode) { 267 | update_counters(); 268 | } 269 | 270 | break; 271 | } 272 | } 273 | } 274 | 275 | // Since $4015 is an internal CPU registers, its open bus behavior is a bit different. 276 | // See https://www.nesdev.org/wiki/APU#Status_($4015). 277 | uint8_t cynes::APU::read(uint8_t address) { 278 | if (static_cast(address) == Register::CTRL_STATUS) { 279 | _internal_open_bus = _send_delta_channel_interrupt << 7; 280 | _internal_open_bus |= _send_frame_interrupt << 6; 281 | _internal_open_bus |= (_delta_channel_remaining_bytes > 0) << 4; 282 | 283 | for (uint8_t channel = 0; channel < 0x4; channel++) { 284 | _internal_open_bus |= (_channels_counters[channel] > 0) << channel; 285 | } 286 | 287 | set_frame_interrupt(false); 288 | 289 | return _internal_open_bus; 290 | } 291 | 292 | return _nes.get_open_bus(); 293 | } 294 | 295 | void cynes::APU::update_counters() { 296 | for (uint8_t channel = 0; channel < 0x4; channel++) { 297 | if (!_channel_halted[channel] && _channels_counters[channel] > 0) { 298 | _channels_counters[channel]--; 299 | } 300 | } 301 | } 302 | 303 | void cynes::APU::load_delta_channel_byte(bool reading) { 304 | uint8_t delay = _delay_dma; 305 | 306 | if (delay == 0) { 307 | if (reading) { 308 | delay = 0x4; 309 | } else { 310 | delay = 0x3; 311 | } 312 | } 313 | 314 | for (uint8_t i = 0; i < delay; i++) { 315 | tick(false, true); 316 | 317 | _nes.ppu.tick(); 318 | _nes.ppu.tick(); 319 | _nes.ppu.tick(); 320 | _nes.cpu.poll(); 321 | } 322 | 323 | _delta_channel_sample_buffer_empty = false; 324 | _delta_channel_remaining_bytes--; 325 | 326 | if (_delta_channel_remaining_bytes == 0) { 327 | if (_delta_channel_should_loop) { 328 | _delta_channel_remaining_bytes = _delta_channel_sample_length; 329 | } else if (_delta_channel_enable_interrupt) { 330 | set_delta_interrupt(true); 331 | } 332 | } 333 | } 334 | 335 | void cynes::APU::perform_dma(uint8_t address) { 336 | _address_dma = address; 337 | _pending_dma = true; 338 | } 339 | 340 | void cynes::APU::perform_pending_dma() { 341 | if (!_pending_dma) { 342 | return; 343 | } 344 | 345 | _pending_dma = false; 346 | _delay_dma = 0x2; 347 | 348 | if (!_latch_cycle) { 349 | _nes.dummy_read(); 350 | } 351 | 352 | _nes.dummy_read(); 353 | 354 | uint16_t current_address = _address_dma << 8; 355 | uint8_t low_byte = 0x00; 356 | 357 | _nes.write(0x2004, _nes.read(current_address++)); 358 | 359 | while ((low_byte = current_address & 0xFF) != 0) { 360 | uint8_t value = _nes.read(current_address++); 361 | 362 | if (low_byte == 254) { 363 | _delay_dma = 0x1; 364 | _nes.write(0x2004, value); 365 | _delay_dma = 0x2; 366 | } else if (low_byte == 255) { 367 | _delay_dma = 0x3; 368 | _nes.write(0x2004, value); 369 | _delay_dma = 0x0; 370 | } else { 371 | _nes.write(0x2004, value); 372 | } 373 | } 374 | } 375 | 376 | void cynes::APU::set_frame_interrupt(bool interrupt) { 377 | _send_frame_interrupt = interrupt; 378 | _nes.cpu.set_frame_interrupt(interrupt); 379 | } 380 | 381 | void cynes::APU::set_delta_interrupt(bool interrupt) { 382 | _send_delta_channel_interrupt = interrupt; 383 | _nes.cpu.set_delta_interrupt(interrupt); 384 | } 385 | -------------------------------------------------------------------------------- /src/mapper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __CYNES_MAPPER__ 2 | #define __CYNES_MAPPER__ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "utils.hpp" 10 | 11 | namespace cynes { 12 | // Forward declaration. 13 | class NES; 14 | 15 | enum class MirroringMode : uint8_t { 16 | NONE, ONE_SCREEN_LOW, ONE_SCREEN_HIGH, HORIZONTAL, VERTICAL 17 | }; 18 | 19 | /// Simple wrapper storing memory parsed from a ROM file. 20 | struct ParsedMemory { 21 | public: 22 | bool read_only_chr = true; 23 | uint16_t size_prg = 0x00; 24 | uint16_t size_chr = 0x00; 25 | 26 | std::unique_ptr trainer; 27 | std::unique_ptr memory_prg; 28 | std::unique_ptr memory_chr; 29 | }; 30 | 31 | /// Generic NES Mapper (see https://www.nesdev.org/wiki/Mapper). 32 | class Mapper { 33 | public: 34 | /// Initialize the mapper. 35 | /// @param nes Emulator. 36 | /// @param metadata ROM metadata. 37 | /// @param mode Mapper mirroring mode. 38 | /// @param size_cpu_ram Size of the CPU RAM. 39 | /// @param size_ppu_ram Size of the PPU RAM. 40 | Mapper( 41 | NES& nes, 42 | const ParsedMemory& metadata, 43 | MirroringMode mode, 44 | uint8_t size_cpu_ram = 0x8, 45 | uint8_t size_ppu_ram = 0x2 46 | ); 47 | 48 | /// Default destructor. 49 | virtual ~Mapper() = default; 50 | 51 | /// Load and deserialize a ROM into a mapper. 52 | /// @param nes Emulator. 53 | /// @param path_rom Path to the NES ROM file. 54 | /// @return A pointer to the instantiated mapper. 55 | static std::unique_ptr load_mapper(NES& nes, const char* path_rom); 56 | 57 | public: 58 | /// Tick the mapper. 59 | virtual void tick(); 60 | 61 | /// Write to a CPU mapped memory bank. 62 | /// @note This function has other side effects than simply writing to the memory, it 63 | /// should not be used as a memory set function. 64 | /// @param address Memory address within the console memory address space. 65 | /// @param value Value to write. 66 | virtual void write_cpu(uint16_t address, uint8_t value); 67 | 68 | /// Write to a PPU mapped memory bank. 69 | /// @note This function has other side effects than simply writing to the memory, it 70 | /// should not be used as a memory set function. 71 | /// @param address Memory address within the console memory address space. 72 | /// @param value Value to write. 73 | virtual void write_ppu(uint16_t address, uint8_t value); 74 | 75 | /// Read from the CPU memory mapped banks. 76 | /// @note This function has other side effects than simply reading from memory, it 77 | /// should not be used as a memory watch function. 78 | /// @param address Memory address within the console memory address space. 79 | /// @return The value stored at the given address. 80 | virtual uint8_t read_cpu(uint16_t address); 81 | 82 | /// Read from the PPU memory mapped banks. 83 | /// @note This function has other side effects than simply reading from memory, it 84 | /// should not be used as a memory watch function. 85 | /// @param address Memory address within the console memory address space. 86 | /// @return The value stored at the given address. 87 | virtual uint8_t read_ppu(uint16_t address); 88 | 89 | protected: 90 | /// A memory bank provides a view within the mapper memory. 91 | // Each bank is exactly 0x400 bytes large. 92 | struct MemoryBank { 93 | public: 94 | /// Initialize an unmapped bank. 95 | MemoryBank(); 96 | 97 | /// Initialize a mapped bank using the given offset. 98 | /// @param offset Mapper memory offset. 99 | /// @param read_only Bank read only flag. 100 | MemoryBank(size_t offset, bool read_only); 101 | 102 | /// Default destructor. 103 | ~MemoryBank() = default; 104 | 105 | public: 106 | size_t offset; 107 | bool read_only; 108 | bool mapped; 109 | 110 | template 111 | constexpr void dump(T& buffer) { 112 | cynes::dump(buffer, offset); 113 | cynes::dump(buffer, read_only); 114 | cynes::dump(buffer, mapped); 115 | } 116 | }; 117 | 118 | protected: 119 | NES& _nes; 120 | 121 | protected: 122 | const uint16_t _banks_prg; 123 | const uint16_t _banks_chr; 124 | const uint8_t _banks_cpu_ram; 125 | const uint8_t _banks_ppu_ram; 126 | 127 | private: 128 | const size_t _size_prg; 129 | const size_t _size_chr; 130 | const size_t _size_cpu_ram; 131 | const size_t _size_ppu_ram; 132 | const bool _read_only_chr; 133 | 134 | std::unique_ptr _memory; 135 | 136 | std::array _banks_cpu; 137 | std::array _banks_ppu; 138 | 139 | protected: 140 | void map_bank_prg(uint8_t page, uint16_t address); 141 | void map_bank_prg(uint8_t page, uint8_t size, uint16_t address); 142 | 143 | void map_bank_cpu_ram(uint8_t page, uint16_t address, bool read_only); 144 | void map_bank_cpu_ram(uint8_t page, uint8_t size, uint16_t address, bool read_only); 145 | 146 | void map_bank_chr(uint8_t page, uint16_t address); 147 | void map_bank_chr(uint8_t page, uint8_t size, uint16_t address); 148 | 149 | void map_bank_ppu_ram(uint8_t page, uint16_t address, bool read_only); 150 | void map_bank_ppu_ram(uint8_t page, uint8_t size, uint16_t address, bool read_only); 151 | 152 | void unmap_bank_cpu(uint8_t page); 153 | void unmap_bank_cpu(uint8_t page, uint8_t size); 154 | 155 | void set_mirroring_mode(MirroringMode mode); 156 | 157 | void mirror_cpu_banks(uint8_t page, uint8_t size, uint8_t mirror); 158 | void mirror_ppu_banks(uint8_t page, uint8_t size, uint8_t mirror); 159 | 160 | public: 161 | template 162 | constexpr void dump(T& buffer) { 163 | for (uint8_t k = 0x00; k < 0x40; k++) { 164 | _banks_cpu[k].dump(buffer); 165 | } 166 | 167 | for (uint8_t k = 0x00; k < 0x10; k++) { 168 | _banks_ppu[k].dump(buffer); 169 | } 170 | 171 | if (!_read_only_chr) { 172 | cynes::dump(buffer, _memory.get() + _size_prg, _size_chr); 173 | } 174 | 175 | if (_size_cpu_ram) { 176 | cynes::dump(buffer, _memory.get() + _size_prg + _size_chr, _size_cpu_ram); 177 | } 178 | 179 | if (_size_ppu_ram) { 180 | cynes::dump(buffer, _memory.get() + _size_prg + _size_chr + _size_cpu_ram, _size_ppu_ram); 181 | } 182 | } 183 | }; 184 | 185 | 186 | /// NROM mapper (see https://www.nesdev.org/wiki/NROM). 187 | class NROM : public Mapper { 188 | public: 189 | NROM(NES& nes, const ParsedMemory& metadata, MirroringMode mode); 190 | ~NROM() = default; 191 | }; 192 | 193 | 194 | /// MMC1 mapper (see https://www.nesdev.org/wiki/MMC1). 195 | class MMC1 : public Mapper { 196 | public: 197 | MMC1(NES& nes, const ParsedMemory& metadata, MirroringMode mode); 198 | ~MMC1() = default; 199 | 200 | public: 201 | /// Tick the mapper. 202 | virtual void tick(); 203 | 204 | /// Write to a CPU mapped memory bank. 205 | /// @note This function has other side effects than simply writing to the memory, it 206 | /// should not be used as a memory set function. 207 | /// @param address Memory address within the console memory address space. 208 | /// @param value Value to write. 209 | virtual void write_cpu(uint16_t address, uint8_t value); 210 | 211 | private: 212 | void write_registers(uint8_t register_target, uint8_t value); 213 | void update_banks(); 214 | 215 | private: 216 | uint8_t _tick; 217 | uint8_t _registers[0x4]; 218 | uint8_t _register; 219 | uint8_t _counter; 220 | 221 | public: 222 | template 223 | constexpr void dump(T& buffer) { 224 | Mapper::dump(buffer); 225 | 226 | cynes::dump(buffer, _tick); 227 | cynes::dump(buffer, _registers); 228 | cynes::dump(buffer, _register); 229 | cynes::dump(buffer, _counter); 230 | } 231 | }; 232 | 233 | 234 | /// UxROM mapper (see https://www.nesdev.org/wiki/UxROM). 235 | class UxROM : public Mapper { 236 | public: 237 | UxROM(NES& nes, const ParsedMemory& metadata, MirroringMode mode); 238 | ~UxROM() = default; 239 | 240 | public: 241 | /// Write to a CPU mapped memory bank. 242 | /// @note This function has other side effects than simply writing to the memory, it 243 | /// should not be used as a memory set function. 244 | /// @param address Memory address within the console memory address space. 245 | /// @param value Value to write. 246 | virtual void write_cpu(uint16_t address, uint8_t value); 247 | }; 248 | 249 | 250 | /// CNROM mapper (see https://www.nesdev.org/wiki/CNROM). 251 | class CNROM : public Mapper { 252 | public: 253 | CNROM(NES& nes, const ParsedMemory& metadata, MirroringMode mode); 254 | ~CNROM() = default; 255 | 256 | public: 257 | /// Write to a CPU mapped memory bank. 258 | /// @note This function has other side effects than simply writing to the memory, it 259 | /// should not be used as a memory set function. 260 | /// @param address Memory address within the console memory address space. 261 | /// @param value Value to write. 262 | virtual void write_cpu(uint16_t address, uint8_t value); 263 | }; 264 | 265 | 266 | /// MMC3 mapper (see https://www.nesdev.org/wiki/MMC3). 267 | class MMC3 : public Mapper { 268 | public: 269 | MMC3(NES& nes, const ParsedMemory& metadata, MirroringMode mode); 270 | ~MMC3() = default; 271 | 272 | public: 273 | /// Tick the mapper. 274 | virtual void tick(); 275 | 276 | /// Write to a CPU mapped memory bank. 277 | /// @note This function has other side effects than simply writing to the memory, it 278 | /// should not be used as a memory set function. 279 | /// @param address Memory address within the console memory address space. 280 | /// @param value Value to write. 281 | virtual void write_cpu(uint16_t address, uint8_t value); 282 | 283 | /// Write to a PPU mapped memory bank. 284 | /// @note This function has other side effects than simply writing to the memory, it 285 | /// should not be used as a memory set function. 286 | /// @param address Memory address within the console memory address space. 287 | /// @param value Value to write. 288 | virtual void write_ppu(uint16_t address, uint8_t value); 289 | 290 | /// Read from the PPU memory mapped banks. 291 | /// @note This function has other side effects than simply reading from memory, it 292 | /// should not be used as a memory watch function. 293 | /// @param address Memory address within the console memory address space. 294 | /// @return The value stored at the given address. 295 | virtual uint8_t read_ppu(uint16_t address); 296 | 297 | private: 298 | void update_state(bool state); 299 | 300 | private: 301 | uint32_t _tick; 302 | uint32_t _registers[0x8]; 303 | uint16_t _counter; 304 | uint16_t _counter_reset_value; 305 | 306 | uint8_t _register_target; 307 | 308 | bool _mode_prg; 309 | bool _mode_chr; 310 | bool _enable_interrupt; 311 | bool _should_reload_interrupt; 312 | 313 | public: 314 | template 315 | constexpr void dump(T& buffer) { 316 | Mapper::dump(buffer); 317 | 318 | cynes::dump(buffer, _tick); 319 | cynes::dump(buffer, _registers); 320 | cynes::dump(buffer, _counter); 321 | cynes::dump(buffer, _counter_reset_value); 322 | cynes::dump(buffer, _register_target); 323 | cynes::dump(buffer, _mode_prg); 324 | cynes::dump(buffer, _mode_chr); 325 | cynes::dump(buffer, _enable_interrupt); 326 | cynes::dump(buffer, _should_reload_interrupt); 327 | } 328 | }; 329 | 330 | 331 | /// AxROM mapper (see https://www.nesdev.org/wiki/AxROM). 332 | class AxROM : public Mapper { 333 | public: 334 | AxROM(NES& nes, const ParsedMemory& metadata); 335 | ~AxROM() = default; 336 | 337 | public: 338 | /// Write to a CPU mapped memory bank. 339 | /// @note This function has other side effects than simply writing to the memory, it 340 | /// should not be used as a memory set function. 341 | /// @param address Memory address within the console memory address space. 342 | /// @param value Value to write. 343 | virtual void write_cpu(uint16_t address, uint8_t value); 344 | }; 345 | 346 | /// Generic MMC mapper (see https://www.nesdev.org/wiki/MMC2). 347 | template 348 | class MMC : public Mapper { 349 | public: 350 | MMC(NES& nes, const ParsedMemory& metadata, MirroringMode mode) : 351 | Mapper(nes, metadata, mode) { 352 | map_bank_chr(0x0, 0x8, 0x0); 353 | 354 | map_bank_prg(0x20, BANK_SIZE, 0x0); 355 | map_bank_prg(0x20 + BANK_SIZE, 0x20 - BANK_SIZE, _banks_prg - 0x20 + BANK_SIZE); 356 | 357 | map_bank_cpu_ram(0x18, 0x8, 0x0, true); 358 | 359 | memset(_latches, false, 0x2); 360 | memset(_selected_banks, 0x0, 0x4); 361 | } 362 | 363 | ~MMC() = default; 364 | 365 | public: 366 | /// Write to a CPU mapped memory bank. 367 | /// @note This function has other side effects than simply writing to the memory, it 368 | /// should not be used as a memory set function. 369 | /// @param address Memory address within the console memory address space. 370 | /// @param value Value to write. 371 | virtual void write_cpu(uint16_t address, uint8_t value) { 372 | if (address < 0xA000) { 373 | Mapper::write_cpu(address, value); 374 | } else if (address < 0xB000) { 375 | map_bank_prg(0x20, BANK_SIZE, (value & 0xF) * BANK_SIZE); 376 | } else if (address < 0xC000) { 377 | _selected_banks[0x0] = value & 0x1F; update_banks(); 378 | } else if (address < 0xD000) { 379 | _selected_banks[0x1] = value & 0x1F; update_banks(); 380 | } else if (address < 0xE000) { 381 | _selected_banks[0x2] = value & 0x1F; update_banks(); 382 | } else if (address < 0xF000) { 383 | _selected_banks[0x3] = value & 0x1F; update_banks(); 384 | } else { 385 | if (value & 0x01) { 386 | set_mirroring_mode(MirroringMode::HORIZONTAL); 387 | } else { 388 | set_mirroring_mode(MirroringMode::VERTICAL); 389 | } 390 | } 391 | } 392 | 393 | /// Read from the PPU memory mapped banks. 394 | /// @note This function has other side effects than simply reading from memory, it 395 | /// should not be used as a memory watch function. 396 | /// @param address Memory address within the console memory address space. 397 | /// @return The value stored at the given address. 398 | virtual uint8_t read_ppu(uint16_t address) { 399 | uint8_t value = Mapper::read_ppu(address); 400 | 401 | if (address == 0x0FD8) { 402 | _latches[0] = true; update_banks(); 403 | } else if (address == 0x0FE8) { 404 | _latches[0] = false; update_banks(); 405 | } else if (address >= 0x1FD8 && address < 0x1FE0) { 406 | _latches[1] = true; update_banks(); 407 | } else if (address >= 0x1FE8 && address < 0x1FF0) { 408 | _latches[1] = false; update_banks(); 409 | } 410 | 411 | return value; 412 | } 413 | 414 | private: 415 | void update_banks() { 416 | if (_latches[0]) { 417 | map_bank_chr(0x0, 0x4, _selected_banks[0x0] << 2); 418 | } else { 419 | map_bank_chr(0x0, 0x4, _selected_banks[0x1] << 2); 420 | } 421 | 422 | if (_latches[1]) { 423 | map_bank_chr(0x4, 0x4, _selected_banks[0x2] << 2); 424 | } else { 425 | map_bank_chr(0x4, 0x4, _selected_banks[0x3] << 2); 426 | } 427 | } 428 | 429 | private: 430 | bool _latches[0x2]; 431 | 432 | uint8_t _selected_banks[0x4]; 433 | 434 | public: 435 | template 436 | constexpr void dump(T& buffer) { 437 | Mapper::dump(buffer); 438 | 439 | cynes::dump(buffer, _latches); 440 | cynes::dump(buffer, _selected_banks); 441 | } 442 | }; 443 | 444 | using MMC2 = MMC<0x08>; 445 | using MMC4 = MMC<0x10>; 446 | 447 | 448 | /// GxROM mapper (see https://www.nesdev.org/wiki/GxROM). 449 | class GxROM : public Mapper { 450 | public: 451 | GxROM(NES& nes, const ParsedMemory& metadata, MirroringMode mode); 452 | ~GxROM() = default; 453 | 454 | public: 455 | /// Write to a CPU mapped memory bank. 456 | /// @note This function has other side effects than simply writing to the memory, it 457 | /// should not be used as a memory set function. 458 | /// @param address Memory address within the console memory address space. 459 | /// @param value Value to write. 460 | virtual void write_cpu(uint16_t address, uint8_t value); 461 | }; 462 | } 463 | 464 | #endif 465 | -------------------------------------------------------------------------------- /src/mapper.cpp: -------------------------------------------------------------------------------- 1 | #include "mapper.hpp" 2 | 3 | #include "cpu.hpp" 4 | #include "nes.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | using random_bytes_engine = std::independent_bits_engine< 13 | std::default_random_engine, 14 | sizeof(uint8_t), 15 | uint32_t 16 | >; 17 | 18 | 19 | cynes::Mapper::MemoryBank::MemoryBank() 20 | : offset{0}, read_only{true}, mapped{false} {} 21 | 22 | cynes::Mapper::MemoryBank::MemoryBank(size_t offset, bool read_only) 23 | : offset{offset}, read_only{read_only}, mapped{true} {} 24 | 25 | 26 | cynes::Mapper::Mapper( 27 | NES& nes, 28 | const ParsedMemory& metadata, 29 | MirroringMode mode, 30 | uint8_t size_cpu_ram, 31 | uint8_t size_ppu_ram 32 | ) : _nes{nes} 33 | , _banks_prg{metadata.size_prg} 34 | , _banks_chr{metadata.size_chr} 35 | , _banks_cpu_ram{size_cpu_ram} 36 | , _banks_ppu_ram{size_ppu_ram} 37 | , _size_prg{static_cast(_banks_prg) << 10} 38 | , _size_chr{static_cast(_banks_chr) << 10} 39 | , _size_cpu_ram{static_cast(_banks_cpu_ram) << 10} 40 | , _size_ppu_ram{static_cast(_banks_ppu_ram) << 10} 41 | , _read_only_chr{metadata.read_only_chr} 42 | , _memory{new uint8_t[_size_prg + _size_chr + _size_cpu_ram + _size_ppu_ram]} 43 | , _banks_cpu{} 44 | , _banks_ppu{} 45 | { 46 | if (_size_prg > 0) { 47 | std::memcpy( 48 | _memory.get(), 49 | metadata.memory_prg.get(), 50 | _size_prg 51 | ); 52 | } 53 | 54 | if (_size_chr > 0) { 55 | std::memcpy( 56 | _memory.get() + _size_prg, 57 | metadata.memory_chr.get(), 58 | _size_chr 59 | ); 60 | } 61 | 62 | random_bytes_engine engine{}; 63 | 64 | if (metadata.trainer != nullptr) { 65 | std::memcpy( 66 | _memory.get() + _size_prg + _size_chr, 67 | metadata.trainer.get(), 68 | 0x200 69 | ); 70 | 71 | std::generate( 72 | _memory.get() + _size_prg + _size_chr + 0x200, 73 | _memory.get() + _size_prg + _size_chr + _size_cpu_ram, 74 | [&engine]() { return static_cast(engine()); } 75 | ); 76 | } else { 77 | std::generate( 78 | _memory.get() + _size_prg + _size_chr, 79 | _memory.get() + _size_prg + _size_chr + _size_cpu_ram, 80 | [&engine]() { return static_cast(engine()); } 81 | ); 82 | } 83 | 84 | if (_size_ppu_ram > 0) { 85 | std::generate( 86 | _memory.get() + _size_prg + _size_chr + _size_cpu_ram, 87 | _memory.get() + _size_prg + _size_chr + _size_cpu_ram + _size_ppu_ram, 88 | [&engine]() { return static_cast(engine()); } 89 | ); 90 | } 91 | 92 | set_mirroring_mode(mode); 93 | } 94 | 95 | std::unique_ptr cynes::Mapper::load_mapper( 96 | NES &nes, 97 | const char* path_rom 98 | ) { 99 | std::ifstream stream{path_rom, std::ios::binary}; 100 | 101 | if (!stream.is_open()) { 102 | throw std::runtime_error("The file cannot be read."); 103 | } 104 | 105 | uint32_t header; 106 | stream.read(reinterpret_cast(&header), sizeof(uint32_t)); 107 | 108 | if (header != 0x1A53454E) { 109 | throw std::runtime_error("The specified file is not a NES ROM."); 110 | } 111 | 112 | uint8_t program_banks = stream.get(); 113 | uint8_t character_banks = stream.get(); 114 | uint8_t flag6 = stream.get(); 115 | uint8_t flag7 = stream.get(); 116 | 117 | stream.seekg(8, std::ios::cur); 118 | 119 | cynes::ParsedMemory metadata{}; 120 | metadata.size_prg = static_cast(program_banks) << 4; 121 | metadata.size_chr = static_cast(character_banks) << 3; 122 | 123 | if (flag6 & 0x04) { 124 | metadata.trainer.reset(new uint8_t[0x200]); 125 | stream.read(reinterpret_cast(metadata.trainer.get()), 0x200); 126 | } 127 | 128 | if (metadata.size_prg > 0) { 129 | size_t memory_size = static_cast(metadata.size_prg) << 10; 130 | metadata.memory_prg.reset(new uint8_t[memory_size]); 131 | stream.read(reinterpret_cast(metadata.memory_prg.get()), memory_size); 132 | } 133 | 134 | if (metadata.size_chr > 0) { 135 | size_t memory_size = static_cast(metadata.size_chr) << 10; 136 | metadata.read_only_chr = true; 137 | metadata.memory_chr.reset(new uint8_t[memory_size]); 138 | stream.read(reinterpret_cast(metadata.memory_chr.get()), memory_size); 139 | } else { 140 | metadata.size_chr = 8; 141 | metadata.read_only_chr = false; 142 | metadata.memory_chr.reset(new uint8_t[0x2000]); 143 | } 144 | 145 | stream.close(); 146 | 147 | uint8_t mapper_index = (flag7 & 0xF0) | flag6 >> 4; 148 | 149 | cynes::MirroringMode mode = (flag6 & 0x01) == 1 150 | ? cynes::MirroringMode::VERTICAL 151 | : cynes::MirroringMode::HORIZONTAL; 152 | 153 | switch (mapper_index) { 154 | case 0: return std::make_unique (nes, metadata, mode); 155 | case 1: return std::make_unique (nes, metadata, mode); 156 | case 2: return std::make_unique(nes, metadata, mode); 157 | case 3: return std::make_unique(nes, metadata, mode); 158 | case 4: return std::make_unique (nes, metadata, mode); 159 | case 7: return std::make_unique(nes, metadata); 160 | case 9: return std::make_unique (nes, metadata, mode); 161 | case 10: return std::make_unique (nes, metadata, mode); 162 | case 66: return std::make_unique(nes, metadata, mode); 163 | case 71: return std::make_unique(nes, metadata, mode); 164 | default: break; 165 | } 166 | 167 | std::stringstream error_message{}; 168 | error_message 169 | << "The mapped used by the ROM is currently not supported (mapper id: " 170 | << mapper_index 171 | << ")."; 172 | 173 | throw std::runtime_error(error_message.str()); 174 | } 175 | 176 | void cynes::Mapper::tick() { } 177 | 178 | void cynes::Mapper::write_cpu(uint16_t address, uint8_t value) { 179 | const auto& bank = _banks_cpu[address >> 10]; 180 | 181 | if (!bank.read_only && bank.mapped) { 182 | _memory[bank.offset + (address & 0x3FF)] = value; 183 | } 184 | } 185 | 186 | void cynes::Mapper::write_ppu(uint16_t address, uint8_t value) { 187 | const auto& bank = _banks_ppu[address >> 10]; 188 | 189 | if (!bank.read_only && bank.mapped) { 190 | _memory[bank.offset + (address & 0x3FF)] = value; 191 | } 192 | } 193 | 194 | uint8_t cynes::Mapper::read_cpu(uint16_t address) { 195 | const auto& bank = _banks_cpu[address >> 10]; 196 | 197 | if (!bank.mapped) { 198 | return _nes.get_open_bus(); 199 | } 200 | 201 | return _memory[bank.offset + (address & 0x3FF)]; 202 | } 203 | 204 | uint8_t cynes::Mapper::read_ppu(uint16_t address) { 205 | const auto& bank = _banks_ppu[address >> 10]; 206 | 207 | if (!bank.mapped) { 208 | return 0x00; 209 | } 210 | 211 | return _memory[bank.offset + (address & 0x3FF)]; 212 | } 213 | 214 | void cynes::Mapper::map_bank_prg(uint8_t page, uint16_t address) { 215 | _banks_cpu[page] = { 216 | static_cast(address << 10), 217 | true 218 | }; 219 | } 220 | 221 | void cynes::Mapper::map_bank_prg(uint8_t page, uint8_t size, uint16_t address) { 222 | for (uint8_t index = 0; index < size; index++) { 223 | map_bank_prg(page + index, address + index); 224 | } 225 | } 226 | 227 | void cynes::Mapper::map_bank_cpu_ram(uint8_t page, uint16_t address, bool read_only) { 228 | _banks_cpu[page] = { 229 | _size_prg + _size_chr + static_cast(address << 10), 230 | read_only 231 | }; 232 | } 233 | 234 | void cynes::Mapper::map_bank_cpu_ram(uint8_t page, uint8_t size, uint16_t address, bool read_only) { 235 | for (uint8_t index = 0; index < size; index++) { 236 | map_bank_cpu_ram(page + index, address + index, read_only); 237 | } 238 | } 239 | 240 | void cynes::Mapper::map_bank_chr(uint8_t page, uint16_t address) { 241 | _banks_ppu[page] = { 242 | _size_prg + static_cast(address << 10), 243 | _read_only_chr 244 | }; 245 | } 246 | 247 | void cynes::Mapper::map_bank_chr(uint8_t page, uint8_t size, uint16_t address) { 248 | for (uint8_t index = 0; index < size; index++) { 249 | map_bank_chr(page + index, address + index); 250 | } 251 | } 252 | 253 | void cynes::Mapper::map_bank_ppu_ram(uint8_t page, uint16_t address, bool read_only) { 254 | _banks_ppu[page] = { 255 | _size_prg + _size_chr + _size_cpu_ram + static_cast(address << 10), 256 | read_only 257 | }; 258 | } 259 | 260 | void cynes::Mapper::map_bank_ppu_ram(uint8_t page, uint8_t size, uint16_t address, bool read_only) { 261 | for (uint8_t index = 0; index < size; index++) { 262 | map_bank_ppu_ram(page + index, address + index, read_only); 263 | } 264 | } 265 | 266 | void cynes::Mapper::unmap_bank_cpu(uint8_t page) { 267 | _banks_cpu[page] = {}; 268 | } 269 | 270 | void cynes::Mapper::unmap_bank_cpu(uint8_t page, uint8_t size) { 271 | for (uint8_t index = 0; index < size; index++) { 272 | unmap_bank_cpu(page + index); 273 | } 274 | } 275 | 276 | void cynes::Mapper::set_mirroring_mode(MirroringMode mode) { 277 | if (mode == MirroringMode::ONE_SCREEN_LOW) { 278 | map_bank_ppu_ram(0x8, 0x00, false); 279 | map_bank_ppu_ram(0x9, 0x00, false); 280 | map_bank_ppu_ram(0xA, 0x00, false); 281 | map_bank_ppu_ram(0xB, 0x00, false); 282 | } else if (mode == MirroringMode::ONE_SCREEN_HIGH) { 283 | map_bank_ppu_ram(0x8, 0x01, false); 284 | map_bank_ppu_ram(0x9, 0x01, false); 285 | map_bank_ppu_ram(0xA, 0x01, false); 286 | map_bank_ppu_ram(0xB, 0x01, false); 287 | } else if (mode == MirroringMode::VERTICAL) { 288 | map_bank_ppu_ram(0x8, 0x2, 0x00, false); 289 | map_bank_ppu_ram(0xA, 0x2, 0x00, false); 290 | } else if (mode == MirroringMode::HORIZONTAL) { 291 | map_bank_ppu_ram(0x8, 0x00, false); 292 | map_bank_ppu_ram(0x9, 0x00, false); 293 | map_bank_ppu_ram(0xA, 0x01, false); 294 | map_bank_ppu_ram(0xB, 0x01, false); 295 | } 296 | 297 | mirror_ppu_banks(0x8, 0x4, 0xC); 298 | } 299 | 300 | void cynes::Mapper::mirror_cpu_banks(uint8_t page, uint8_t size, uint8_t mirror) { 301 | for (uint8_t index = 0; index < size; index++) { 302 | _banks_cpu[mirror + index] = _banks_cpu[page + index]; 303 | } 304 | } 305 | 306 | void cynes::Mapper::mirror_ppu_banks(uint8_t page, uint8_t size, uint8_t mirror) { 307 | for (uint8_t index = 0; index < size; index++) { 308 | _banks_ppu[mirror + index] = _banks_ppu[page + index]; 309 | } 310 | } 311 | 312 | 313 | cynes::NROM::NROM(NES& nes, const ParsedMemory& metadata, MirroringMode mode) 314 | : Mapper(nes, metadata, mode) 315 | { 316 | map_bank_chr(0x0, 0x8, 0x0); 317 | 318 | if (_banks_prg == 0x20) { 319 | map_bank_prg(0x20, 0x20, 0x0); 320 | } else { 321 | map_bank_prg(0x20, 0x10, 0x0); 322 | map_bank_prg(0x30, 0x10, 0x0); 323 | } 324 | 325 | map_bank_cpu_ram(0x18, 0x8, 0x0, false); 326 | } 327 | 328 | 329 | cynes::MMC1::MMC1( 330 | NES& nes, 331 | const ParsedMemory& metadata, 332 | MirroringMode mode 333 | ) : Mapper(nes, metadata, mode) 334 | , _tick{0x00} 335 | , _registers{} 336 | , _register{0x00} 337 | , _counter{0x00} 338 | { 339 | memset(_registers, 0x00, 0x4); 340 | _registers[0x0] = 0xC; 341 | 342 | update_banks(); 343 | } 344 | 345 | void cynes::MMC1::tick() { 346 | if (_tick < 6) { 347 | _tick++; 348 | } 349 | } 350 | 351 | void cynes::MMC1::write_cpu(uint16_t address, uint8_t value) { 352 | if (address < 0x8000) { 353 | cynes::Mapper::write_cpu(address, value); 354 | } else { 355 | write_registers((address >> 13) & 0x03, value); 356 | } 357 | } 358 | 359 | void cynes::MMC1::write_registers(uint8_t register_target, uint8_t value) { 360 | if (_tick == 6) { 361 | if (value & 0x80) { 362 | _registers[0x0] |= 0xC; 363 | 364 | update_banks(); 365 | 366 | _register = 0x00; 367 | _counter = 0; 368 | } else { 369 | _register >>= 1; 370 | _register |= (value & 0x1) << 4; 371 | 372 | if (++_counter == 5) { 373 | _registers[register_target] = _register; 374 | 375 | update_banks(); 376 | 377 | _register = 0x00; 378 | _counter = 0x00; 379 | } 380 | } 381 | } 382 | 383 | _tick = 0; 384 | } 385 | 386 | void cynes::MMC1::update_banks() { 387 | switch (_registers[0x0] & 0x03) { 388 | case 0: set_mirroring_mode(MirroringMode::ONE_SCREEN_LOW); break; 389 | case 1: set_mirroring_mode(MirroringMode::ONE_SCREEN_HIGH); break; 390 | case 2: set_mirroring_mode(MirroringMode::VERTICAL); break; 391 | case 3: set_mirroring_mode(MirroringMode::HORIZONTAL); break; 392 | } 393 | 394 | if (_registers[0x0] & 0x10) { 395 | map_bank_chr(0x0, 0x4, (_registers[0x1] & 0x1F) << 2); 396 | map_bank_chr(0x4, 0x4, (_registers[0x2] & 0x1F) << 2); 397 | } else { 398 | map_bank_chr(0x0, 0x8, (_registers[0x1] & 0x1E) << 2); 399 | } 400 | 401 | if (_registers[0x0] & 0x08) { 402 | if (_registers[0x0] & 0x04) { 403 | map_bank_prg(0x20, 0x10, (_registers[0x3] & 0x0F) << 4); 404 | map_bank_prg(0x30, 0x10, _banks_prg - 0x10); 405 | } else { 406 | map_bank_prg(0x20, 0x10, 0x0); 407 | map_bank_prg(0x30, 0x10, (_registers[0x3] & 0xF) << 4); 408 | } 409 | } else { 410 | map_bank_prg(0x20, 0x20, (_registers[0x3] & 0x0E) << 4); 411 | } 412 | 413 | bool read_only = _registers[0x3] & 0x10; 414 | map_bank_cpu_ram(0x18, 0x8, 0x0, read_only); 415 | } 416 | 417 | 418 | cynes::UxROM::UxROM(NES& nes, const ParsedMemory& metadata, MirroringMode mode) 419 | : Mapper(nes, metadata, mode, 0x0, 0x10) 420 | { 421 | map_bank_prg(0x20, 0x10, 0x00); 422 | map_bank_prg(0x30, 0x10, _banks_prg - 0x10); 423 | 424 | map_bank_ppu_ram(0x0, 0x8, 0x02, false); 425 | } 426 | 427 | void cynes::UxROM::write_cpu(uint16_t address, uint8_t value) { 428 | if (address < 0x8000) { 429 | cynes::Mapper::write_cpu(address, value); 430 | } else { 431 | map_bank_prg(0x20, 0x10, value << 4); 432 | } 433 | } 434 | 435 | 436 | cynes::CNROM::CNROM(NES& nes, const ParsedMemory& metadata, MirroringMode mode) 437 | : Mapper(nes, metadata, mode, 0x0) 438 | { 439 | map_bank_chr(0x0, 0x8, 0x0); 440 | 441 | if (_banks_prg == 0x20) { 442 | map_bank_prg(0x20, 0x20, 0x0); 443 | } else { 444 | map_bank_prg(0x20, 0x10, 0x0); 445 | map_bank_prg(0x30, 0x10, 0x0); 446 | } 447 | } 448 | 449 | void cynes::CNROM::write_cpu(uint16_t address, uint8_t value) { 450 | if (address < 0x8000) { 451 | cynes::Mapper::write_cpu(address, value); 452 | } else { 453 | map_bank_chr(0x0, 0x8, (value & 0x3) << 3); 454 | } 455 | } 456 | 457 | 458 | cynes::MMC3::MMC3( 459 | NES& nes, 460 | const ParsedMemory& metadata, 461 | MirroringMode mode 462 | ) : Mapper(nes, metadata, mode) 463 | , _tick{0x0000} 464 | , _registers{} 465 | , _counter{0x0000} 466 | , _counter_reset_value{0x0000} 467 | , _register_target{0x00} 468 | , _mode_prg{false} 469 | , _mode_chr{false} 470 | , _enable_interrupt{false} 471 | , _should_reload_interrupt{false} 472 | { 473 | map_bank_chr(0x0, 0x8, 0x0); 474 | map_bank_prg(0x20, 0x10, 0x0); 475 | map_bank_prg(0x30, 0x10, _banks_prg - 0x10); 476 | map_bank_cpu_ram(0x18, 0x8, 0x0, false); 477 | 478 | memset(_registers, 0x0000, 0x20); 479 | } 480 | 481 | void cynes::MMC3::tick() { 482 | if (_tick > 0 && _tick < 11) { 483 | _tick++; 484 | } 485 | } 486 | 487 | void cynes::MMC3::write_cpu(uint16_t address, uint8_t value) { 488 | if (address < 0x8000) { 489 | cynes::Mapper::write_cpu(address, value); 490 | } else if (address < 0xA000) { 491 | if (address & 0x1) { 492 | if (_register_target < 2) { 493 | value &= 0xFE; 494 | } 495 | 496 | _registers[_register_target] = value; 497 | 498 | if (_mode_prg) { 499 | map_bank_prg(0x20, 0x08, _banks_prg - 0x10); 500 | map_bank_prg(0x28, 0x08, (_registers[0x7] & 0x3F) << 3); 501 | map_bank_prg(0x30, 0x08, (_registers[0x6] & 0x3F) << 3); 502 | map_bank_prg(0x38, 0x08, _banks_prg - 0x8); 503 | } else { 504 | map_bank_prg(0x20, 0x08, (_registers[0x6] & 0x3F) << 3); 505 | map_bank_prg(0x28, 0x08, (_registers[0x7] & 0x3F) << 3); 506 | map_bank_prg(0x30, 0x10, _banks_prg - 0x10); 507 | } 508 | 509 | if (_mode_chr) { 510 | map_bank_chr(0x0, _registers[0x2]); 511 | map_bank_chr(0x1, _registers[0x3]); 512 | map_bank_chr(0x2, _registers[0x4]); 513 | map_bank_chr(0x3, _registers[0x5]); 514 | map_bank_chr(0x4, 0x2, _registers[0x0]); 515 | map_bank_chr(0x6, 0x2, _registers[0x1]); 516 | } else { 517 | map_bank_chr(0x0, 0x2, _registers[0x0]); 518 | map_bank_chr(0x2, 0x2, _registers[0x1]); 519 | map_bank_chr(0x4, _registers[0x2]); 520 | map_bank_chr(0x5, _registers[0x3]); 521 | map_bank_chr(0x6, _registers[0x4]); 522 | map_bank_chr(0x7, _registers[0x5]); 523 | } 524 | } else { 525 | _register_target = value & 0x07; 526 | _mode_prg = value & 0x40; 527 | _mode_chr = value & 0x80; 528 | } 529 | } else if (address < 0xC000) { 530 | if (address & 0x1) { 531 | bool read_only = value & 0x40; 532 | map_bank_cpu_ram(0x18, 0x8, 0x0, read_only); 533 | } else if (value & 0x1) { 534 | set_mirroring_mode(MirroringMode::HORIZONTAL); 535 | } else { 536 | set_mirroring_mode(MirroringMode::VERTICAL); 537 | } 538 | } else if (address < 0xE000) { 539 | if (address & 0x1) { 540 | _counter = 0x0000; 541 | _should_reload_interrupt = true; 542 | } else { 543 | _counter_reset_value = value; 544 | } 545 | } else { 546 | if (address & 0x1) { 547 | _enable_interrupt = true; 548 | } else { 549 | _enable_interrupt = false; 550 | _nes.cpu.set_mapper_interrupt(false); 551 | } 552 | } 553 | } 554 | 555 | void cynes::MMC3::write_ppu(uint16_t address, uint8_t value) { 556 | update_state(address & 0x1000); 557 | cynes::Mapper::write_ppu(address, value); 558 | } 559 | 560 | uint8_t cynes::MMC3::read_ppu(uint16_t address) { 561 | update_state(address & 0x1000); 562 | return cynes::Mapper::read_ppu(address); 563 | } 564 | 565 | void cynes::MMC3::update_state(bool state) { 566 | if (state) { 567 | if (_tick > 10) { 568 | if (_counter == 0 || _should_reload_interrupt) { 569 | _counter = _counter_reset_value; 570 | } else { 571 | _counter--; 572 | } 573 | 574 | if (_counter == 0 && _enable_interrupt) { 575 | _nes.cpu.set_mapper_interrupt(true); 576 | } 577 | 578 | _should_reload_interrupt = false; 579 | } 580 | 581 | _tick = 0; 582 | } else if (_tick == 0) { 583 | _tick = 1; 584 | } 585 | } 586 | 587 | 588 | cynes::AxROM::AxROM(NES& nes, const ParsedMemory& metadata) 589 | : Mapper(nes, metadata, MirroringMode::ONE_SCREEN_LOW, 0x8, 0x10) 590 | { 591 | map_bank_ppu_ram(0x0, 0x8, 0x2, false); 592 | map_bank_prg(0x20, 0x20, 0x0); 593 | } 594 | 595 | void cynes::AxROM::write_cpu(uint16_t address, uint8_t value) { 596 | if (address < 0x8000) { 597 | cynes::Mapper::write_cpu(address, value); 598 | } else { 599 | map_bank_prg(0x20, 0x20, (value & 0x07) << 5); 600 | 601 | if (value & 0x10) { 602 | set_mirroring_mode(MirroringMode::ONE_SCREEN_HIGH); 603 | } else { 604 | set_mirroring_mode(MirroringMode::ONE_SCREEN_LOW); 605 | } 606 | } 607 | } 608 | 609 | 610 | cynes::GxROM::GxROM(NES& nes, const ParsedMemory& metadata, MirroringMode mode) 611 | : Mapper(nes, metadata, mode, 0x0) 612 | { 613 | map_bank_prg(0x20, 0x20, 0x0); 614 | map_bank_chr(0x00, 0x08, 0x0); 615 | } 616 | 617 | void cynes::GxROM::write_cpu(uint16_t address, uint8_t value) { 618 | if (address < 0x8000) { 619 | cynes::Mapper::write_cpu(address, value); 620 | } else { 621 | map_bank_prg(0x20, 0x20, (value & 0x30) << 1); 622 | map_bank_chr(0x00, 0x08, (value & 0x03) << 3); 623 | } 624 | } 625 | -------------------------------------------------------------------------------- /src/ppu.cpp: -------------------------------------------------------------------------------- 1 | #include "ppu.hpp" 2 | #include "cpu.hpp" 3 | #include "nes.hpp" 4 | #include "mapper.hpp" 5 | 6 | #include 7 | 8 | 9 | constexpr uint8_t PALETTE_COLORS[0x8][0x40][0x3] = { 10 | 0x54, 0x54, 0x54, 0x00, 0x1E, 0x74, 0x08, 0x10, 0x90, 0x30, 0x00, 0x88, 0x44, 0x00, 0x64, 0x5C, 11 | 0x00, 0x30, 0x54, 0x04, 0x00, 0x3C, 0x18, 0x00, 0x20, 0x2A, 0x00, 0x08, 0x3A, 0x00, 0x00, 0x40, 12 | 0x00, 0x00, 0x3C, 0x00, 0x00, 0x32, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x98, 0x96, 0x98, 0x08, 0x4C, 0xC4, 0x30, 0x32, 0xEC, 0x5C, 0x1E, 0xE4, 0x88, 0x14, 0xB0, 0xA0, 14 | 0x14, 0x64, 0x98, 0x22, 0x20, 0x78, 0x3C, 0x00, 0x54, 0x5A, 0x00, 0x28, 0x72, 0x00, 0x08, 0x7C, 15 | 0x00, 0x00, 0x76, 0x28, 0x00, 0x66, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 16 | 0xEC, 0xEE, 0xEC, 0x4C, 0x9A, 0xEC, 0x78, 0x7C, 0xEC, 0xB0, 0x62, 0xEC, 0xE4, 0x54, 0xEC, 0xEC, 17 | 0x58, 0xB4, 0xEC, 0x6A, 0x64, 0xD4, 0x88, 0x20, 0xA0, 0xAA, 0x00, 0x74, 0xC4, 0x00, 0x4C, 0xD0, 18 | 0x20, 0x38, 0xCC, 0x6C, 0x38, 0xB4, 0xCC, 0x3C, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 19 | 0xEC, 0xEE, 0xEC, 0xA8, 0xCC, 0xEC, 0xBC, 0xBC, 0xEC, 0xD4, 0xB2, 0xEC, 0xEC, 0xAE, 0xEC, 0xEC, 20 | 0xAE, 0xD4, 0xEC, 0xB4, 0xB0, 0xE4, 0xC4, 0x90, 0xCC, 0xD2, 0x78, 0xB4, 0xDE, 0x78, 0xA8, 0xE2, 21 | 0x90, 0x98, 0xE2, 0xB4, 0xA0, 0xD6, 0xE4, 0xA0, 0xA2, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0x5C, 0x4B, 0x4B, 0x00, 0x1B, 0x68, 0x08, 0x0E, 0x81, 0x34, 0x00, 0x7A, 0x4A, 0x00, 0x5A, 0x65, 23 | 0x00, 0x2B, 0x5C, 0x03, 0x00, 0x42, 0x15, 0x00, 0x23, 0x25, 0x00, 0x08, 0x34, 0x00, 0x00, 0x39, 24 | 0x00, 0x00, 0x36, 0x00, 0x00, 0x2D, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 25 | 0xA7, 0x87, 0x88, 0x08, 0x44, 0xB0, 0x34, 0x2D, 0xD4, 0x65, 0x1B, 0xCD, 0x95, 0x12, 0x9E, 0xB0, 26 | 0x12, 0x5A, 0xA7, 0x1E, 0x1C, 0x84, 0x36, 0x00, 0x5C, 0x51, 0x00, 0x2C, 0x66, 0x00, 0x08, 0x6F, 27 | 0x00, 0x00, 0x6A, 0x24, 0x00, 0x5B, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0xFF, 0xD6, 0xD4, 0x53, 0x8A, 0xD4, 0x84, 0x6F, 0xD4, 0xC1, 0x58, 0xD4, 0xFA, 0x4B, 0xD4, 0xFF, 29 | 0x4F, 0xA2, 0xFF, 0x5F, 0x5A, 0xE9, 0x7A, 0x1C, 0xB0, 0x99, 0x00, 0x7F, 0xB0, 0x00, 0x53, 0xBB, 30 | 0x1C, 0x3D, 0xB7, 0x61, 0x3D, 0xA2, 0xB7, 0x42, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 31 | 0xFF, 0xD6, 0xD4, 0xB8, 0xB7, 0xD4, 0xCE, 0xA9, 0xD4, 0xE9, 0xA0, 0xD4, 0xFF, 0x9C, 0xD4, 0xFF, 32 | 0x9C, 0xBE, 0xFF, 0xA2, 0x9E, 0xFA, 0xB0, 0x81, 0xE0, 0xBD, 0x6C, 0xC6, 0xC7, 0x6C, 0xB8, 0xCB, 33 | 0x81, 0xA7, 0xCB, 0xA2, 0xB0, 0xC0, 0xCD, 0xB0, 0x91, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 34 | 0x4B, 0x5C, 0x4B, 0x00, 0x21, 0x68, 0x07, 0x11, 0x81, 0x2B, 0x00, 0x7A, 0x3D, 0x00, 0x5A, 0x52, 35 | 0x00, 0x2B, 0x4B, 0x04, 0x00, 0x36, 0x1A, 0x00, 0x1C, 0x2E, 0x00, 0x07, 0x3F, 0x00, 0x00, 0x46, 36 | 0x00, 0x00, 0x42, 0x00, 0x00, 0x37, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 37 | 0x88, 0xA5, 0x88, 0x07, 0x53, 0xB0, 0x2B, 0x37, 0xD4, 0x52, 0x21, 0xCD, 0x7A, 0x16, 0x9E, 0x90, 38 | 0x16, 0x5A, 0x88, 0x25, 0x1C, 0x6C, 0x42, 0x00, 0x4B, 0x63, 0x00, 0x24, 0x7D, 0x00, 0x07, 0x88, 39 | 0x00, 0x00, 0x81, 0x24, 0x00, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 40 | 0xD4, 0xFF, 0xD4, 0x44, 0xA9, 0xD4, 0x6C, 0x88, 0xD4, 0x9E, 0x6B, 0xD4, 0xCD, 0x5C, 0xD4, 0xD4, 41 | 0x60, 0xA2, 0xD4, 0x74, 0x5A, 0xBE, 0x95, 0x1C, 0x90, 0xBB, 0x00, 0x68, 0xD7, 0x00, 0x44, 0xE4, 42 | 0x1C, 0x32, 0xE0, 0x61, 0x32, 0xC6, 0xB7, 0x36, 0x42, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 43 | 0xD4, 0xFF, 0xD4, 0x97, 0xE0, 0xD4, 0xA9, 0xCE, 0xD4, 0xBE, 0xC3, 0xD4, 0xD4, 0xBF, 0xD4, 0xD4, 44 | 0xBF, 0xBE, 0xD4, 0xC6, 0x9E, 0xCD, 0xD7, 0x81, 0xB7, 0xE7, 0x6C, 0xA2, 0xF4, 0x6C, 0x97, 0xF8, 45 | 0x81, 0x88, 0xF8, 0xA2, 0x90, 0xEB, 0xCD, 0x90, 0xB2, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 | 0x53, 0x53, 0x44, 0x00, 0x1D, 0x5D, 0x07, 0x0F, 0x74, 0x2F, 0x00, 0x6E, 0x43, 0x00, 0x51, 0x5B, 47 | 0x00, 0x26, 0x53, 0x03, 0x00, 0x3B, 0x17, 0x00, 0x1F, 0x29, 0x00, 0x07, 0x39, 0x00, 0x00, 0x3F, 48 | 0x00, 0x00, 0x3B, 0x00, 0x00, 0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x96, 0x94, 0x7B, 0x07, 0x4B, 0x9E, 0x2F, 0x31, 0xBF, 0x5B, 0x1D, 0xB8, 0x86, 0x13, 0x8E, 0x9E, 50 | 0x13, 0x51, 0x96, 0x21, 0x19, 0x76, 0x3B, 0x00, 0x53, 0x59, 0x00, 0x27, 0x70, 0x00, 0x07, 0x7A, 51 | 0x00, 0x00, 0x74, 0x20, 0x00, 0x64, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 52 | 0xE9, 0xEB, 0xBF, 0x4B, 0x98, 0xBF, 0x76, 0x7A, 0xBF, 0xAE, 0x61, 0xBF, 0xE1, 0x53, 0xBF, 0xE9, 53 | 0x57, 0x91, 0xE9, 0x68, 0x51, 0xD1, 0x86, 0x19, 0x9E, 0xA8, 0x00, 0x72, 0xC2, 0x00, 0x4B, 0xCD, 54 | 0x19, 0x37, 0xC9, 0x57, 0x37, 0xB2, 0xA5, 0x3B, 0x3B, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 55 | 0xE9, 0xEB, 0xBF, 0xA6, 0xC9, 0xBF, 0xBA, 0xBA, 0xBF, 0xD1, 0xB0, 0xBF, 0xE9, 0xAC, 0xBF, 0xE9, 56 | 0xAC, 0xAB, 0xE9, 0xB2, 0x8E, 0xE1, 0xC2, 0x74, 0xC9, 0xCF, 0x61, 0xB2, 0xDB, 0x61, 0xA6, 0xDF, 57 | 0x74, 0x96, 0xDF, 0x91, 0x9E, 0xD3, 0xB8, 0x9E, 0xA0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 58 | 0x4B, 0x4B, 0x5C, 0x00, 0x1B, 0x7F, 0x07, 0x0E, 0x9E, 0x2B, 0x00, 0x95, 0x3D, 0x00, 0x6E, 0x52, 59 | 0x00, 0x34, 0x4B, 0x03, 0x00, 0x36, 0x15, 0x00, 0x1C, 0x25, 0x00, 0x07, 0x34, 0x00, 0x00, 0x39, 60 | 0x00, 0x00, 0x36, 0x00, 0x00, 0x2D, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 61 | 0x88, 0x87, 0xA7, 0x07, 0x44, 0xD7, 0x2B, 0x2D, 0xFF, 0x52, 0x1B, 0xFA, 0x7A, 0x12, 0xC1, 0x90, 62 | 0x12, 0x6E, 0x88, 0x1E, 0x23, 0x6C, 0x36, 0x00, 0x4B, 0x51, 0x00, 0x24, 0x66, 0x00, 0x07, 0x6F, 63 | 0x00, 0x00, 0x6A, 0x2C, 0x00, 0x5B, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 64 | 0xD4, 0xD6, 0xFF, 0x44, 0x8A, 0xFF, 0x6C, 0x6F, 0xFF, 0x9E, 0x58, 0xFF, 0xCD, 0x4B, 0xFF, 0xD4, 65 | 0x4F, 0xC6, 0xD4, 0x5F, 0x6E, 0xBE, 0x7A, 0x23, 0x90, 0x99, 0x00, 0x68, 0xB0, 0x00, 0x44, 0xBB, 66 | 0x23, 0x32, 0xB7, 0x76, 0x32, 0xA2, 0xE0, 0x36, 0x36, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 67 | 0xD4, 0xD6, 0xFF, 0x97, 0xB7, 0xFF, 0xA9, 0xA9, 0xFF, 0xBE, 0xA0, 0xFF, 0xD4, 0x9C, 0xFF, 0xD4, 68 | 0x9C, 0xE9, 0xD4, 0xA2, 0xC1, 0xCD, 0xB0, 0x9E, 0xB7, 0xBD, 0x84, 0xA2, 0xC7, 0x84, 0x97, 0xCB, 69 | 0x9E, 0x88, 0xCB, 0xC6, 0x90, 0xC0, 0xFA, 0x90, 0x91, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x53, 0x44, 0x53, 0x00, 0x18, 0x72, 0x07, 0x0C, 0x8E, 0x2F, 0x00, 0x86, 0x43, 0x00, 0x63, 0x5B, 71 | 0x00, 0x2F, 0x53, 0x03, 0x00, 0x3B, 0x13, 0x00, 0x1F, 0x22, 0x00, 0x07, 0x2E, 0x00, 0x00, 0x33, 72 | 0x00, 0x00, 0x30, 0x00, 0x00, 0x28, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 73 | 0x96, 0x79, 0x96, 0x07, 0x3D, 0xC2, 0x2F, 0x28, 0xE9, 0x5B, 0x18, 0xE1, 0x86, 0x10, 0xAE, 0x9E, 74 | 0x10, 0x63, 0x96, 0x1B, 0x1F, 0x76, 0x30, 0x00, 0x53, 0x48, 0x00, 0x27, 0x5C, 0x00, 0x07, 0x64, 75 | 0x00, 0x00, 0x5F, 0x27, 0x00, 0x52, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 76 | 0xE9, 0xC0, 0xE9, 0x4B, 0x7C, 0xE9, 0x76, 0x64, 0xE9, 0xAE, 0x4F, 0xE9, 0xE1, 0x44, 0xE9, 0xE9, 77 | 0x47, 0xB2, 0xE9, 0x55, 0x63, 0xD1, 0x6E, 0x1F, 0x9E, 0x89, 0x00, 0x72, 0x9E, 0x00, 0x4B, 0xA8, 78 | 0x1F, 0x37, 0xA5, 0x6A, 0x37, 0x91, 0xC9, 0x3B, 0x30, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 79 | 0xE9, 0xC0, 0xE9, 0xA6, 0xA5, 0xE9, 0xBA, 0x98, 0xE9, 0xD1, 0x90, 0xE9, 0xE9, 0x8C, 0xE9, 0xE9, 80 | 0x8C, 0xD1, 0xE9, 0x91, 0xAE, 0xE1, 0x9E, 0x8E, 0xC9, 0xAA, 0x76, 0xB2, 0xB3, 0x76, 0xA6, 0xB7, 81 | 0x8E, 0x96, 0xB7, 0xB2, 0x9E, 0xAD, 0xE1, 0x9E, 0x83, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 82 | 0x44, 0x53, 0x53, 0x00, 0x1D, 0x72, 0x06, 0x0F, 0x8E, 0x26, 0x00, 0x86, 0x37, 0x00, 0x63, 0x4A, 83 | 0x00, 0x2F, 0x44, 0x03, 0x00, 0x30, 0x17, 0x00, 0x19, 0x29, 0x00, 0x06, 0x39, 0x00, 0x00, 0x3F, 84 | 0x00, 0x00, 0x3B, 0x00, 0x00, 0x31, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 85 | 0x7B, 0x94, 0x96, 0x06, 0x4B, 0xC2, 0x26, 0x31, 0xE9, 0x4A, 0x1D, 0xE1, 0x6E, 0x13, 0xAE, 0x81, 86 | 0x13, 0x63, 0x7B, 0x21, 0x1F, 0x61, 0x3B, 0x00, 0x44, 0x59, 0x00, 0x20, 0x70, 0x00, 0x06, 0x7A, 87 | 0x00, 0x00, 0x74, 0x27, 0x00, 0x64, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 88 | 0xBF, 0xEB, 0xE9, 0x3D, 0x98, 0xE9, 0x61, 0x7A, 0xE9, 0x8E, 0x61, 0xE9, 0xB8, 0x53, 0xE9, 0xBF, 89 | 0x57, 0xB2, 0xBF, 0x68, 0x63, 0xAB, 0x86, 0x1F, 0x81, 0xA8, 0x00, 0x5D, 0xC2, 0x00, 0x3D, 0xCD, 90 | 0x1F, 0x2D, 0xC9, 0x6A, 0x2D, 0xB2, 0xC9, 0x30, 0x3B, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 91 | 0xBF, 0xEB, 0xE9, 0x88, 0xC9, 0xE9, 0x98, 0xBA, 0xE9, 0xAB, 0xB0, 0xE9, 0xBF, 0xAC, 0xE9, 0xBF, 92 | 0xAC, 0xD1, 0xBF, 0xB2, 0xAE, 0xB8, 0xC2, 0x8E, 0xA5, 0xCF, 0x76, 0x91, 0xDB, 0x76, 0x88, 0xDF, 93 | 0x8E, 0x7B, 0xDF, 0xB2, 0x81, 0xD3, 0xE1, 0x81, 0xA0, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 94 | 0x4A, 0x4A, 0x4A, 0x00, 0x1A, 0x67, 0x07, 0x0E, 0x80, 0x2A, 0x00, 0x79, 0x3C, 0x00, 0x59, 0x51, 95 | 0x00, 0x2A, 0x4A, 0x03, 0x00, 0x35, 0x15, 0x00, 0x1C, 0x25, 0x00, 0x07, 0x33, 0x00, 0x00, 0x39, 96 | 0x00, 0x00, 0x35, 0x00, 0x00, 0x2C, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 97 | 0x87, 0x85, 0x87, 0x07, 0x43, 0xAE, 0x2A, 0x2C, 0xD2, 0x51, 0x1A, 0xCB, 0x79, 0x11, 0x9C, 0x8E, 98 | 0x11, 0x59, 0x87, 0x1E, 0x1C, 0x6A, 0x35, 0x00, 0x4A, 0x50, 0x00, 0x23, 0x65, 0x00, 0x07, 0x6E, 99 | 0x00, 0x00, 0x69, 0x23, 0x00, 0x5A, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 100 | 0xD2, 0xD4, 0xD2, 0x43, 0x89, 0xD2, 0x6A, 0x6E, 0xD2, 0x9C, 0x57, 0xD2, 0xCB, 0x4A, 0xD2, 0xD2, 101 | 0x4E, 0xA0, 0xD2, 0x5E, 0x59, 0xBC, 0x79, 0x1C, 0x8E, 0x97, 0x00, 0x67, 0xAE, 0x00, 0x43, 0xB9, 102 | 0x1C, 0x31, 0xB5, 0x60, 0x31, 0xA0, 0xB5, 0x35, 0x35, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 103 | 0xD2, 0xD4, 0xD2, 0x95, 0xB5, 0xD2, 0xA7, 0xA7, 0xD2, 0xBC, 0x9E, 0xD2, 0xD2, 0x9B, 0xD2, 0xD2, 104 | 0x9B, 0xBC, 0xD2, 0xA0, 0x9C, 0xCB, 0xAE, 0x80, 0xB5, 0xBB, 0x6A, 0xA0, 0xC5, 0x6A, 0x95, 0xC9, 105 | 0x80, 0x87, 0xC9, 0xA0, 0x8E, 0xBE, 0xCB, 0x8E, 0x90, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 106 | }; 107 | 108 | 109 | cynes::PPU::PPU(NES& nes) 110 | : _nes{nes} 111 | , _frame_buffer{new uint8_t[0x2D000]} 112 | , _current_x{0x0000} 113 | , _current_y{0x0000} 114 | , _rendering_enabled{false} 115 | , _rendering_enabled_delayed{false} 116 | , _prevent_vertical_blank{false} 117 | , _control_increment_mode{false} 118 | , _control_foreground_table{false} 119 | , _control_background_table{false} 120 | , _control_foreground_large{false} 121 | , _control_interrupt_on_vertical_blank{false} 122 | , _mask_grayscale_mode{false} 123 | , _mask_render_background_left{false} 124 | , _mask_render_foreground_left{false} 125 | , _mask_render_background{false} 126 | , _mask_render_foreground{false} 127 | , _mask_color_emphasize{0x00} 128 | , _status_sprite_overflow{false} 129 | , _status_sprite_zero_hit{false} 130 | , _status_vertical_blank{false} 131 | , _clock_decays{} 132 | , _register_decay{0x00} 133 | , _latch_cycle{false} 134 | , _latch_address{false} 135 | , _register_t{0x0000} 136 | , _register_v{0x0000} 137 | , _delayed_register_v{0x0000} 138 | , _scroll_x{0x00} 139 | , _delay_data_read_counter{0x00} 140 | , _delay_data_write_counter{0x00} 141 | , _buffer_data{0x00} 142 | , _background_data{} 143 | , _background_shifter{} 144 | , _foreground_data{} 145 | , _foreground_shifter{} 146 | , _foreground_attributes{} 147 | , _foreground_positions{} 148 | , _foreground_data_pointer{0x00} 149 | , _foreground_sprite_count{0x00} 150 | , _foreground_sprite_count_next{0x00} 151 | , _foreground_sprite_pointer{0x00} 152 | , _foreground_read_delay_counter{0x00} 153 | , _foreground_sprite_address{0x0000} 154 | , _foreground_sprite_zero_line{false} 155 | , _foreground_sprite_zero_should{false} 156 | , _foreground_sprite_zero_hit{false} 157 | , _foreground_evaluation_step{SpriteEvaluationStep::LOAD_SECONDARY_OAM} 158 | { 159 | std::memset(_clock_decays, 0x00, 0x3); 160 | std::memset(_background_data, 0x00, 0x4); 161 | std::memset(_background_shifter, 0x0000, 0x8); 162 | std::memset(_foreground_data, 0x00, 0x20); 163 | std::memset(_foreground_shifter, 0x00, 0x10); 164 | std::memset(_foreground_attributes, 0x00, 0x8); 165 | std::memset(_foreground_positions, 0x00, 0x8); 166 | } 167 | 168 | void cynes::PPU::power() { 169 | _current_y = 0xFF00; 170 | _current_x = 0xFF00; 171 | 172 | _rendering_enabled = false; 173 | _rendering_enabled_delayed = false; 174 | _prevent_vertical_blank = false; 175 | 176 | _control_increment_mode = false; 177 | _control_foreground_table = false; 178 | _control_background_table = false; 179 | _control_foreground_large = false; 180 | _control_interrupt_on_vertical_blank = false; 181 | 182 | _mask_grayscale_mode = false; 183 | _mask_render_background_left = false; 184 | _mask_render_foreground_left = false; 185 | _mask_render_background = false; 186 | _mask_render_foreground = false; 187 | 188 | _mask_color_emphasize = 0x00; 189 | 190 | _status_sprite_overflow = true; 191 | _status_sprite_zero_hit = false; 192 | _status_vertical_blank = true; 193 | 194 | _foreground_sprite_pointer = 0x00; 195 | 196 | _latch_address = false; 197 | _latch_cycle = false; 198 | 199 | _register_t = 0x0000; 200 | _register_v = 0x0000; 201 | _scroll_x = 0x00; 202 | 203 | _delay_data_write_counter = 0x00; 204 | _delay_data_read_counter = 0x00; 205 | _buffer_data = 0x00; 206 | } 207 | 208 | void cynes::PPU::reset() { 209 | _current_y = 0xFF00; 210 | _current_x = 0xFF00; 211 | 212 | _rendering_enabled = false; 213 | _rendering_enabled_delayed = false; 214 | _prevent_vertical_blank = false; 215 | 216 | _control_increment_mode = false; 217 | _control_foreground_table = false; 218 | _control_background_table = false; 219 | _control_foreground_large = false; 220 | _control_interrupt_on_vertical_blank = false; 221 | 222 | _mask_grayscale_mode = false; 223 | _mask_render_background_left = false; 224 | _mask_render_foreground_left = false; 225 | _mask_render_background = false; 226 | _mask_render_foreground = false; 227 | 228 | _mask_color_emphasize = 0x00; 229 | 230 | _latch_address = false; 231 | _latch_cycle = false; 232 | 233 | _register_t = 0x0000; 234 | _register_v = 0x0000; 235 | _scroll_x = 0x00; 236 | 237 | _delay_data_write_counter = 0x00; 238 | _delay_data_read_counter = 0x00; 239 | _buffer_data = 0x00; 240 | } 241 | 242 | void cynes::PPU::tick() { 243 | if (_current_x > 339) { 244 | _current_x = 0; 245 | 246 | if (++_current_y > 261) { 247 | _current_y = 0; 248 | _foreground_sprite_count = 0; 249 | 250 | _latch_cycle = !_latch_cycle; 251 | 252 | for (int k = 0; k < 3; k++) { 253 | if (_clock_decays[k] > 0 && --_clock_decays[k] == 0) { 254 | switch (k) { 255 | case 0: _register_decay &= 0x3F; break; 256 | case 1: _register_decay &= 0xDF; break; 257 | case 2: _register_decay &= 0xE0; break; 258 | } 259 | } 260 | } 261 | } 262 | 263 | reset_foreground_data(); 264 | 265 | if (_current_y == 261) { 266 | _status_sprite_overflow = false; 267 | _status_sprite_zero_hit = false; 268 | 269 | memset(_foreground_shifter, 0x00, 0x10); 270 | } 271 | } else { 272 | _current_x++; 273 | 274 | if (_current_y < 240) { 275 | if (_current_x < 257 || (_current_x >= 321 && _current_x < 337)) { 276 | load_background_shifters(); 277 | } 278 | 279 | if (_current_x == 256) { 280 | increment_scroll_y(); 281 | } else if (_current_x == 257) { 282 | reset_scroll_x(); 283 | } 284 | 285 | if (_current_x >= 2 && _current_x < 257) { 286 | update_foreground_shifter(); 287 | } 288 | 289 | if (_current_x < 65) { 290 | clear_foreground_data(); 291 | } else if (_current_x < 257) { 292 | fetch_foreground_data(); 293 | } else if (_current_x < 321) { 294 | load_foreground_shifter(); 295 | } 296 | 297 | if (_current_x > 0 && _current_x < 257 && _current_y < 240) { 298 | memcpy(_frame_buffer.get() + ((_current_y << 8) + _current_x - 1) * 3, PALETTE_COLORS[_mask_color_emphasize][_nes.read_ppu(0x3F00 | blend_colors())], 3); 299 | } 300 | } else if (_current_y == 240 && _current_x == 1) { 301 | _nes.read_ppu(_register_v); 302 | } else if (_current_y == 261) { 303 | if (_current_x == 1) { 304 | _status_vertical_blank = false; 305 | 306 | _nes.cpu.set_non_maskable_interrupt(false); 307 | } 308 | 309 | if (_current_x < 257 || (_current_x >= 321 && _current_x < 337)) { 310 | load_background_shifters(); 311 | } 312 | 313 | if (_current_x == 256) { 314 | increment_scroll_y(); 315 | } else if (_current_x == 257) { 316 | reset_scroll_x(); 317 | } else if (_current_x >= 280 && _current_x < 305) { 318 | reset_scroll_y(); 319 | } 320 | 321 | if (_current_x > 1) { 322 | if (_current_x < 257) { 323 | update_foreground_shifter(); 324 | } else if (_current_x < 321) { 325 | load_foreground_shifter(); 326 | } 327 | } 328 | 329 | if (_rendering_enabled && (_current_x == 337 || _current_x == 339)) { 330 | _nes.read_ppu(0x2000 | (_register_v & 0x0FFF)); 331 | 332 | if (_current_x == 339 && _latch_cycle) { 333 | _current_x = 340; 334 | } 335 | } 336 | } else if (_current_x == 1 && _current_y == 241) { 337 | if (!_prevent_vertical_blank) { 338 | _status_vertical_blank = true; 339 | 340 | if (_control_interrupt_on_vertical_blank) { 341 | _nes.cpu.set_non_maskable_interrupt(true); 342 | } 343 | } 344 | 345 | _prevent_vertical_blank = false; 346 | _frame_ready = true; 347 | } 348 | } 349 | 350 | if (_rendering_enabled_delayed != _rendering_enabled) { 351 | _rendering_enabled_delayed = _rendering_enabled; 352 | 353 | if (_current_y < 240 || _current_y == 261) { 354 | if (!_rendering_enabled_delayed) { 355 | _nes.read_ppu(_register_v); 356 | 357 | if (_current_x >= 65 && _current_x <= 256) { 358 | _foreground_sprite_pointer++; 359 | } 360 | } 361 | } 362 | } 363 | 364 | if (_rendering_enabled != (_mask_render_background || _mask_render_foreground)) { 365 | _rendering_enabled = _mask_render_background || _mask_render_foreground; 366 | } 367 | 368 | 369 | if (_delay_data_write_counter > 0 && --_delay_data_write_counter == 0) { 370 | _register_v = _delayed_register_v; 371 | _register_t = _register_v; 372 | 373 | if ((_current_y >= 240 && _current_y != 261) || !_rendering_enabled) { 374 | _nes.read_ppu(_register_v); 375 | } 376 | } 377 | 378 | if (_delay_data_read_counter > 0) { 379 | _delay_data_read_counter--; 380 | } 381 | 382 | _nes.get_mapper().tick(); 383 | } 384 | 385 | void cynes::PPU::write(uint8_t address, uint8_t value) { 386 | memset(_clock_decays, DECAY_PERIOD, 3); 387 | 388 | _register_decay = value; 389 | 390 | switch (static_cast(address)) { 391 | case Register::PPU_CTRL: { 392 | _register_t &= 0xF3FF; 393 | _register_t |= (value & 0x03) << 10; 394 | 395 | _control_increment_mode = value & 0x04; 396 | _control_foreground_table = value & 0x08; 397 | _control_background_table = value & 0x10; 398 | _control_foreground_large = value & 0x20; 399 | _control_interrupt_on_vertical_blank = value & 0x80; 400 | 401 | if (!_control_interrupt_on_vertical_blank) { 402 | _nes.cpu.set_non_maskable_interrupt(false); 403 | } else if (_status_vertical_blank) { 404 | _nes.cpu.set_non_maskable_interrupt(true); 405 | } 406 | 407 | break; 408 | } 409 | 410 | case Register::PPU_MASK: { 411 | _mask_grayscale_mode = value & 0x01; 412 | _mask_render_background_left = value & 0x02; 413 | _mask_render_foreground_left = value & 0x04; 414 | _mask_render_background = value & 0x08; 415 | _mask_render_foreground = value & 0x10; 416 | _mask_color_emphasize = value >> 5; 417 | 418 | break; 419 | } 420 | 421 | case Register::OAM_ADDR: { 422 | _foreground_sprite_pointer = value; 423 | 424 | break; 425 | } 426 | 427 | case Register::OAM_DATA: { 428 | if ((_current_y >= 240 && _current_y != 261) || !_rendering_enabled) { 429 | if ((_foreground_sprite_pointer & 0x03) == 0x02) { 430 | value &= 0xE3; 431 | } 432 | 433 | _nes.write_oam(_foreground_sprite_pointer++, value); 434 | } else { 435 | _foreground_sprite_pointer += 4; 436 | } 437 | 438 | break; 439 | } 440 | 441 | case Register::PPU_SCROLL: { 442 | if (!_latch_address) { 443 | _scroll_x = value & 0x07; 444 | 445 | _register_t &= 0xFFE0; 446 | _register_t |= value >> 3; 447 | } else { 448 | _register_t &= 0x8C1F; 449 | 450 | _register_t |= (value & 0xF8) << 2; 451 | _register_t |= (value & 0x07) << 12; 452 | } 453 | 454 | _latch_address = !_latch_address; 455 | 456 | break; 457 | } 458 | 459 | case Register::PPU_ADDR: { 460 | if (!_latch_address) { 461 | _register_t &= 0x00FF; 462 | _register_t |= value << 8; 463 | } else { 464 | _register_t &= 0xFF00; 465 | _register_t |= value; 466 | 467 | _delay_data_write_counter = 3; 468 | _delayed_register_v = _register_t; 469 | } 470 | 471 | _latch_address = !_latch_address; 472 | 473 | break; 474 | } 475 | 476 | case Register::PPU_DATA: { 477 | if ((_register_v & 0x3FFF) >= 0x3F00) { 478 | _nes.write_ppu(_register_v, value); 479 | } else { 480 | if ((_current_y >= 240 && _current_y != 261) || !_rendering_enabled) { 481 | _nes.write_ppu(_register_v, value); 482 | } else { 483 | _nes.write_ppu(_register_v, _register_v & 0xFF); 484 | } 485 | } 486 | 487 | if ((_current_y >= 240 && _current_y != 261) || !_rendering_enabled) { 488 | _register_v += _control_increment_mode ? 32 : 1; 489 | _register_v &= 0x7FFF; 490 | 491 | _nes.read_ppu(_register_v); 492 | } else { 493 | increment_scroll_x(); 494 | increment_scroll_y(); 495 | } 496 | 497 | break; 498 | } 499 | 500 | default: break; 501 | } 502 | } 503 | 504 | uint8_t cynes::PPU::read(uint8_t address) { 505 | switch (static_cast(address)) { 506 | case Register::PPU_STATUS: { 507 | memset(_clock_decays, DECAY_PERIOD, 2); 508 | 509 | _latch_address = false; 510 | 511 | _register_decay &= 0x1F; 512 | _register_decay |= _status_sprite_overflow << 5; 513 | _register_decay |= _status_sprite_zero_hit << 6; 514 | _register_decay |= _status_vertical_blank << 7; 515 | 516 | _status_vertical_blank = false; 517 | _nes.cpu.set_non_maskable_interrupt(false); 518 | 519 | if (_current_y == 241 && _current_x == 0) { 520 | _prevent_vertical_blank = true; 521 | } 522 | 523 | break; 524 | } 525 | 526 | case Register::OAM_DATA: { 527 | memset(_clock_decays, DECAY_PERIOD, 3); 528 | 529 | _register_decay = _nes.read_oam(_foreground_sprite_pointer); 530 | 531 | break; 532 | } 533 | 534 | case Register::PPU_DATA: { 535 | if (_delay_data_read_counter == 0) { 536 | uint8_t value = _nes.read_ppu(_register_v); 537 | 538 | if ((_register_v & 0x3FFF) >= 0x3F00) { 539 | _register_decay &= 0xC0; 540 | _register_decay |= value & 0x3F; 541 | 542 | _clock_decays[0] = _clock_decays[2] = DECAY_PERIOD; 543 | 544 | _buffer_data = _nes.read_ppu(_register_v - 0x1000); 545 | } else { 546 | _register_decay = _buffer_data; 547 | _buffer_data = value; 548 | 549 | memset(_clock_decays, DECAY_PERIOD, 3); 550 | } 551 | 552 | if ((_current_y >= 240 && _current_y != 261) || !_rendering_enabled) { 553 | _register_v += _control_increment_mode ? 32 : 1; 554 | _register_v &= 0x7FFF; 555 | 556 | _nes.read_ppu(_register_v); 557 | } else { 558 | increment_scroll_x(); 559 | increment_scroll_y(); 560 | } 561 | 562 | _delay_data_read_counter = 6; 563 | } 564 | 565 | break; 566 | } 567 | 568 | default: break; 569 | } 570 | 571 | return _register_decay; 572 | } 573 | 574 | const uint8_t* cynes::PPU::get_frame_buffer() const { 575 | return _frame_buffer.get(); 576 | } 577 | 578 | bool cynes::PPU::is_frame_ready() { 579 | bool frame_ready = _frame_ready; 580 | _frame_ready = false; 581 | 582 | return frame_ready; 583 | } 584 | 585 | void cynes::PPU::increment_scroll_x() { 586 | if (_mask_render_background || _mask_render_foreground) { 587 | if ((_register_v & 0x001F) == 0x1F) { 588 | _register_v &= 0xFFE0; 589 | _register_v ^= 0x0400; 590 | } else { 591 | _register_v++; 592 | } 593 | } 594 | } 595 | 596 | void cynes::PPU::increment_scroll_y() { 597 | if (_mask_render_background || _mask_render_foreground) { 598 | if ((_register_v & 0x7000) != 0x7000) { 599 | _register_v += 0x1000; 600 | } else { 601 | _register_v &= 0x8FFF; 602 | 603 | uint8_t coarse_y = (_register_v & 0x03E0) >> 5; 604 | 605 | if (coarse_y == 0x1D) { 606 | coarse_y = 0; 607 | _register_v ^= 0x0800; 608 | } else if (((_register_v >> 5) & 0x1F) == 0x1F) { 609 | coarse_y = 0; 610 | } else { 611 | coarse_y++; 612 | } 613 | 614 | _register_v &= 0xFC1F; 615 | _register_v |= coarse_y << 5; 616 | } 617 | } 618 | } 619 | 620 | void cynes::PPU::reset_scroll_x() { 621 | if (_mask_render_background || _mask_render_foreground) { 622 | _register_v &= 0xFBE0; 623 | _register_v |= _register_t & 0x041F; 624 | } 625 | } 626 | 627 | void cynes::PPU::reset_scroll_y() { 628 | if (_mask_render_background || _mask_render_foreground) { 629 | _register_v &= 0x841F; 630 | _register_v |= _register_t & 0x7BE0; 631 | } 632 | } 633 | 634 | 635 | void cynes::PPU::load_background_shifters() { 636 | update_background_shifters(); 637 | 638 | if (_rendering_enabled) { 639 | switch (_current_x & 0x07) { 640 | case 0x1: { 641 | _background_shifter[0] = (_background_shifter[0] & 0xFF00) | _background_data[2]; 642 | _background_shifter[1] = (_background_shifter[1] & 0xFF00) | _background_data[3]; 643 | 644 | if (_background_data[1] & 0x01) { 645 | _background_shifter[2] = (_background_shifter[2] & 0xFF00) | 0xFF; 646 | } else { 647 | _background_shifter[2] = (_background_shifter[2] & 0xFF00); 648 | } 649 | 650 | if (_background_data[1] & 0x02) { 651 | _background_shifter[3] = (_background_shifter[3] & 0xFF00) | 0xFF; 652 | } else { 653 | _background_shifter[3] = (_background_shifter[3] & 0xFF00); 654 | } 655 | 656 | uint16_t address = 0x2000; 657 | address |= _register_v & 0x0FFF; 658 | 659 | _background_data[0] = _nes.read_ppu(address); 660 | 661 | break; 662 | } 663 | 664 | case 0x3: { 665 | uint16_t address = 0x23C0; 666 | address |= _register_v & 0x0C00; 667 | address |= (_register_v >> 4) & 0x38; 668 | address |= (_register_v >> 2) & 0x07; 669 | 670 | _background_data[1] = _nes.read_ppu(address); 671 | 672 | if (_register_v & 0x0040) { 673 | _background_data[1] >>= 4; 674 | } 675 | 676 | if (_register_v & 0x0002) { 677 | _background_data[1] >>= 2; 678 | } 679 | 680 | _background_data[1] &= 0x03; 681 | 682 | break; 683 | } 684 | 685 | case 0x5: { 686 | uint16_t address = _control_background_table << 12; 687 | address |= _background_data[0] << 4; 688 | address |= _register_v >> 12; 689 | 690 | _background_data[2] = _nes.read_ppu(address); 691 | 692 | break; 693 | } 694 | 695 | case 0x7: { 696 | uint16_t address = _control_background_table << 12; 697 | address |= _background_data[0] << 4; 698 | address |= _register_v >> 12; 699 | address += 0x8; 700 | 701 | _background_data[3] = _nes.read_ppu(address); 702 | 703 | break; 704 | 705 | } 706 | 707 | case 0x0: increment_scroll_x(); break; 708 | } 709 | } 710 | } 711 | 712 | void cynes::PPU::update_background_shifters() { 713 | if (_mask_render_background || _mask_render_foreground) { 714 | _background_shifter[0] <<= 1; 715 | _background_shifter[1] <<= 1; 716 | _background_shifter[2] <<= 1; 717 | _background_shifter[3] <<= 1; 718 | } 719 | } 720 | 721 | void cynes::PPU::reset_foreground_data() { 722 | _foreground_sprite_count_next = _foreground_sprite_count; 723 | 724 | _foreground_data_pointer = 0; 725 | _foreground_sprite_count = 0; 726 | _foreground_evaluation_step = SpriteEvaluationStep::LOAD_SECONDARY_OAM; 727 | _foreground_sprite_zero_line = _foreground_sprite_zero_should; 728 | _foreground_sprite_zero_should = false; 729 | _foreground_sprite_zero_hit = false; 730 | } 731 | 732 | void cynes::PPU::clear_foreground_data() { 733 | if (_current_x & 0x01) { 734 | _foreground_data[_foreground_data_pointer++] = 0xFF; 735 | 736 | _foreground_data_pointer &= 0x1F; 737 | } 738 | } 739 | 740 | void cynes::PPU::fetch_foreground_data() { 741 | if (_current_x % 2 == 0 && _rendering_enabled) { 742 | uint8_t sprite_size = _control_foreground_large ? 16 : 8; 743 | 744 | switch (_foreground_evaluation_step) { 745 | case SpriteEvaluationStep::LOAD_SECONDARY_OAM: { 746 | uint8_t sprite_data = _nes.read_oam(_foreground_sprite_pointer); 747 | 748 | _foreground_data[_foreground_sprite_count * 4 + (_foreground_sprite_pointer & 0x03)] = sprite_data; 749 | 750 | if (!(_foreground_sprite_pointer & 0x3)) { 751 | int16_t offset_y = int16_t(_current_y) - int16_t(sprite_data); 752 | 753 | if (offset_y >= 0 && offset_y < sprite_size) { 754 | if (!_foreground_sprite_pointer++) { 755 | _foreground_sprite_zero_should = true; 756 | } 757 | } else { 758 | _foreground_sprite_pointer += 4; 759 | 760 | if (!_foreground_sprite_pointer) { 761 | _foreground_evaluation_step = SpriteEvaluationStep::IDLE; 762 | } else if (_foreground_sprite_count == 8) { 763 | _foreground_evaluation_step = SpriteEvaluationStep::INCREMENT_POINTER; 764 | } 765 | } 766 | } else if (!(++_foreground_sprite_pointer & 0x03)) { 767 | _foreground_sprite_count++; 768 | 769 | if (!_foreground_sprite_pointer) { 770 | _foreground_evaluation_step = SpriteEvaluationStep::IDLE; 771 | } else if (_foreground_sprite_count == 8) { 772 | _foreground_evaluation_step = SpriteEvaluationStep::INCREMENT_POINTER; 773 | } 774 | } 775 | 776 | break; 777 | } 778 | 779 | case SpriteEvaluationStep::INCREMENT_POINTER: { 780 | if (_foreground_read_delay_counter) { 781 | _foreground_read_delay_counter--; 782 | } else { 783 | int16_t offset_y = int16_t(_current_y) - int16_t(_nes.read_oam(_foreground_sprite_pointer)); 784 | 785 | if (offset_y >= 0 && offset_y < sprite_size) { 786 | _status_sprite_overflow = true; 787 | 788 | _foreground_sprite_pointer++; 789 | _foreground_read_delay_counter = 3; 790 | } else { 791 | uint8_t low = (_foreground_sprite_pointer + 1) & 0x03; 792 | 793 | _foreground_sprite_pointer += 0x04; 794 | _foreground_sprite_pointer &= 0xFC; 795 | 796 | if (!_foreground_sprite_pointer) { 797 | _foreground_evaluation_step = SpriteEvaluationStep::IDLE; 798 | } 799 | 800 | _foreground_sprite_pointer |= low; 801 | } 802 | } 803 | 804 | break; 805 | } 806 | 807 | default: _foreground_sprite_pointer = 0; 808 | } 809 | } 810 | } 811 | 812 | void cynes::PPU::load_foreground_shifter() { 813 | if (_rendering_enabled) { 814 | _foreground_sprite_pointer = 0; 815 | 816 | if (_current_x == 257) { 817 | _foreground_data_pointer = 0; 818 | } 819 | 820 | switch (_current_x & 0x7) { 821 | case 0x1: { 822 | uint16_t address = 0x2000; 823 | address |= _register_v & 0x0FFF; 824 | 825 | _nes.read_ppu(address); 826 | 827 | break; 828 | } 829 | 830 | case 0x3: { 831 | uint16_t address = 0x23C0; 832 | address |= _register_v & 0x0C00; 833 | address |= (_register_v >> 4) & 0x38; 834 | address |= (_register_v >> 2) & 0x07; 835 | 836 | _nes.read_ppu(address); 837 | 838 | break; 839 | } 840 | 841 | case 0x5: { 842 | uint8_t sprite_index = _foreground_data[_foreground_data_pointer * 4 + 1]; 843 | uint8_t sprite_attribute = _foreground_data[_foreground_data_pointer * 4 + 2]; 844 | 845 | uint8_t offset = 0x00; 846 | 847 | if (_foreground_data_pointer < _foreground_sprite_count) { 848 | offset = _current_y - _foreground_data[_foreground_data_pointer * 4]; 849 | } 850 | 851 | _foreground_sprite_address = 0x0000; 852 | 853 | if (_control_foreground_large) { 854 | _foreground_sprite_address = (sprite_index & 0x01) << 12; 855 | 856 | if (sprite_attribute & 0x80) { 857 | if (offset < 8) { 858 | _foreground_sprite_address |= ((sprite_index & 0xFE) + 1) << 4; 859 | } else { 860 | _foreground_sprite_address |= ((sprite_index & 0xFE)) << 4; 861 | } 862 | } else { 863 | if (offset < 8) { 864 | _foreground_sprite_address |= ((sprite_index & 0xFE)) << 4; 865 | } else { 866 | _foreground_sprite_address |= ((sprite_index & 0xFE) + 1) << 4; 867 | } 868 | } 869 | } else { 870 | _foreground_sprite_address = _control_foreground_table << 12 | sprite_index << 4; 871 | } 872 | 873 | if (sprite_attribute & 0x80) { 874 | _foreground_sprite_address |= (7 - offset) & 0x07; 875 | } else { 876 | _foreground_sprite_address |= offset & 0x07; 877 | } 878 | 879 | uint8_t sprite_pattern_lsb_plane = _nes.read_ppu(_foreground_sprite_address); 880 | 881 | 882 | if (sprite_attribute & 0x40) { 883 | sprite_pattern_lsb_plane = (sprite_pattern_lsb_plane & 0xF0) >> 4 | (sprite_pattern_lsb_plane & 0x0F) << 4; 884 | sprite_pattern_lsb_plane = (sprite_pattern_lsb_plane & 0xCC) >> 2 | (sprite_pattern_lsb_plane & 0x33) << 2; 885 | sprite_pattern_lsb_plane = (sprite_pattern_lsb_plane & 0xAA) >> 1 | (sprite_pattern_lsb_plane & 0x55) << 1; 886 | } 887 | 888 | _foreground_shifter[_foreground_data_pointer * 2] = sprite_pattern_lsb_plane; 889 | 890 | break; 891 | } 892 | 893 | case 0x7: { 894 | uint8_t sprite_pattern_msb_plane = _nes.read_ppu(_foreground_sprite_address + 8); 895 | 896 | if (_foreground_data[_foreground_data_pointer * 4 + 2] & 0x40) { 897 | sprite_pattern_msb_plane = (sprite_pattern_msb_plane & 0xF0) >> 4 | (sprite_pattern_msb_plane & 0x0F) << 4; 898 | sprite_pattern_msb_plane = (sprite_pattern_msb_plane & 0xCC) >> 2 | (sprite_pattern_msb_plane & 0x33) << 2; 899 | sprite_pattern_msb_plane = (sprite_pattern_msb_plane & 0xAA) >> 1 | (sprite_pattern_msb_plane & 0x55) << 1; 900 | } 901 | 902 | _foreground_shifter[_foreground_data_pointer * 2 + 1] = sprite_pattern_msb_plane; 903 | _foreground_positions[_foreground_data_pointer] = _foreground_data[_foreground_data_pointer * 4 + 3]; 904 | _foreground_attributes[_foreground_data_pointer] = _foreground_data[_foreground_data_pointer * 4 + 2]; 905 | 906 | _foreground_data_pointer++; 907 | 908 | break; 909 | } 910 | } 911 | } 912 | } 913 | 914 | void cynes::PPU::update_foreground_shifter() { 915 | if (_mask_render_foreground) { 916 | for (uint8_t sprite = 0; sprite < _foreground_sprite_count_next; sprite++) { 917 | if (_foreground_positions[sprite] > 0) { 918 | _foreground_positions[sprite] --; 919 | } else { 920 | _foreground_shifter[sprite * 2] <<= 1; 921 | _foreground_shifter[sprite * 2 + 1] <<= 1; 922 | } 923 | } 924 | } 925 | } 926 | 927 | uint8_t cynes::PPU::blend_colors() { 928 | if (!_rendering_enabled && (_register_v & 0x3FFF) >= 0x3F00) { 929 | return _register_v & 0x1F; 930 | } 931 | 932 | uint8_t background_pixel = 0x00; 933 | uint8_t background_palette = 0x00; 934 | 935 | if (_mask_render_background && (_current_x > 8 || _mask_render_background_left)) { 936 | uint16_t bit_mask = 0x8000 >> _scroll_x; 937 | 938 | background_pixel = ((_background_shifter[0] & bit_mask) > 0) | (((_background_shifter[1] & bit_mask) > 0) << 1); 939 | background_palette = ((_background_shifter[2] & bit_mask) > 0) | (((_background_shifter[3] & bit_mask) > 0) << 1); 940 | } 941 | 942 | uint8_t foreground_pixel = 0x00; 943 | uint8_t foreground_palette = 0x00; 944 | uint8_t foreground_priority = 0x00; 945 | 946 | if (_mask_render_foreground && (_current_x > 8 || _mask_render_foreground_left)) { 947 | _foreground_sprite_zero_hit = false; 948 | 949 | for (uint8_t sprite = 0; sprite < _foreground_sprite_count_next; sprite++) { 950 | if (_foreground_positions[sprite] == 0) { 951 | foreground_pixel = ((_foreground_shifter[sprite * 2] & 0x80) > 0) | (((_foreground_shifter[sprite * 2 + 1] & 0x80) > 0) << 1); 952 | foreground_palette = (_foreground_attributes[sprite] & 0x03) + 0x04; 953 | foreground_priority = (_foreground_attributes[sprite] & 0x20) == 0x00; 954 | 955 | if (foreground_pixel != 0) { 956 | if (sprite == 0 && _current_x != 256) { 957 | _foreground_sprite_zero_hit = true; 958 | } 959 | 960 | break; 961 | } 962 | } 963 | } 964 | } 965 | 966 | uint8_t final_pixel = 0x00; 967 | uint8_t final_palette = 0x00; 968 | 969 | if (background_pixel == 0 && foreground_pixel > 0) { 970 | final_pixel = foreground_pixel; 971 | final_palette = foreground_palette; 972 | } else if (background_pixel > 0 && foreground_pixel == 0) { 973 | final_pixel = background_pixel; 974 | final_palette = background_palette; 975 | } else if (background_pixel > 0 && foreground_pixel > 0) { 976 | if (foreground_priority) { 977 | final_pixel = foreground_pixel; 978 | final_palette = foreground_palette; 979 | } else { 980 | final_pixel = background_pixel; 981 | final_palette = background_palette; 982 | } 983 | 984 | if (_foreground_sprite_zero_hit && _foreground_sprite_zero_line && (_current_x > 8 || _mask_render_background_left || _mask_render_foreground_left)) { 985 | _status_sprite_zero_hit = true; 986 | } 987 | } 988 | 989 | final_pixel |= final_palette << 2; 990 | 991 | if (_mask_grayscale_mode) { 992 | final_pixel &= 0x30; 993 | } 994 | 995 | return final_pixel; 996 | } 997 | -------------------------------------------------------------------------------- /src/cpu.cpp: -------------------------------------------------------------------------------- 1 | #include "cpu.hpp" 2 | #include "nes.hpp" 3 | 4 | #include 5 | 6 | using _addr_ptr = void (cynes::CPU::*)();; 7 | const _addr_ptr cynes::CPU::ADDRESSING_MODES[256] = { 8 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ixr, &cynes::CPU::addr_acc, &cynes::CPU::addr_ixr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, 9 | &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_acc, &cynes::CPU::addr_imm, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, 10 | &cynes::CPU::addr_rel, &cynes::CPU::addr_iyr, &cynes::CPU::addr_acc, &cynes::CPU::addr_iym, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, 11 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ayr, &cynes::CPU::addr_imp, &cynes::CPU::addr_aym, &cynes::CPU::addr_axr, &cynes::CPU::addr_axr, &cynes::CPU::addr_axm, &cynes::CPU::addr_axm, 12 | &cynes::CPU::addr_abw, &cynes::CPU::addr_ixr, &cynes::CPU::addr_acc, &cynes::CPU::addr_ixr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, 13 | &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_acc, &cynes::CPU::addr_imm, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, 14 | &cynes::CPU::addr_rel, &cynes::CPU::addr_iyr, &cynes::CPU::addr_acc, &cynes::CPU::addr_iym, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, 15 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ayr, &cynes::CPU::addr_imp, &cynes::CPU::addr_aym, &cynes::CPU::addr_axr, &cynes::CPU::addr_axr, &cynes::CPU::addr_axm, &cynes::CPU::addr_axm, 16 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ixr, &cynes::CPU::addr_acc, &cynes::CPU::addr_ixr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, 17 | &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_acc, &cynes::CPU::addr_imm, &cynes::CPU::addr_abw, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, 18 | &cynes::CPU::addr_rel, &cynes::CPU::addr_iyr, &cynes::CPU::addr_acc, &cynes::CPU::addr_iym, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, 19 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ayr, &cynes::CPU::addr_imp, &cynes::CPU::addr_aym, &cynes::CPU::addr_axr, &cynes::CPU::addr_axr, &cynes::CPU::addr_axm, &cynes::CPU::addr_axm, 20 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ixr, &cynes::CPU::addr_acc, &cynes::CPU::addr_ixr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, 21 | &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_acc, &cynes::CPU::addr_imm, &cynes::CPU::addr_ind, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, 22 | &cynes::CPU::addr_rel, &cynes::CPU::addr_iyr, &cynes::CPU::addr_acc, &cynes::CPU::addr_iym, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, 23 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ayr, &cynes::CPU::addr_imp, &cynes::CPU::addr_aym, &cynes::CPU::addr_axr, &cynes::CPU::addr_axr, &cynes::CPU::addr_axm, &cynes::CPU::addr_axm, 24 | &cynes::CPU::addr_imm, &cynes::CPU::addr_ixw, &cynes::CPU::addr_imm, &cynes::CPU::addr_ixw, &cynes::CPU::addr_zpw, &cynes::CPU::addr_zpw, &cynes::CPU::addr_zpw, &cynes::CPU::addr_zpw, 25 | &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_abw, &cynes::CPU::addr_abw, &cynes::CPU::addr_abw, &cynes::CPU::addr_abw, 26 | &cynes::CPU::addr_rel, &cynes::CPU::addr_iyw, &cynes::CPU::addr_acc, &cynes::CPU::addr_iyw, &cynes::CPU::addr_zxw, &cynes::CPU::addr_zxw, &cynes::CPU::addr_zyw, &cynes::CPU::addr_zyw, 27 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ayw, &cynes::CPU::addr_imp, &cynes::CPU::addr_ayw, &cynes::CPU::addr_axw, &cynes::CPU::addr_axw, &cynes::CPU::addr_ayw, &cynes::CPU::addr_ayw, 28 | &cynes::CPU::addr_imm, &cynes::CPU::addr_ixr, &cynes::CPU::addr_imm, &cynes::CPU::addr_ixr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, 29 | &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, 30 | &cynes::CPU::addr_rel, &cynes::CPU::addr_iyr, &cynes::CPU::addr_acc, &cynes::CPU::addr_iyr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zyr, &cynes::CPU::addr_zyr, 31 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ayr, &cynes::CPU::addr_imp, &cynes::CPU::addr_ayr, &cynes::CPU::addr_axr, &cynes::CPU::addr_axr, &cynes::CPU::addr_ayr, &cynes::CPU::addr_ayr, 32 | &cynes::CPU::addr_imm, &cynes::CPU::addr_ixr, &cynes::CPU::addr_imm, &cynes::CPU::addr_ixr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, 33 | &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, 34 | &cynes::CPU::addr_rel, &cynes::CPU::addr_iyr, &cynes::CPU::addr_acc, &cynes::CPU::addr_iym, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, 35 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ayr, &cynes::CPU::addr_imp, &cynes::CPU::addr_aym, &cynes::CPU::addr_axr, &cynes::CPU::addr_axr, &cynes::CPU::addr_axm, &cynes::CPU::addr_axm, 36 | &cynes::CPU::addr_imm, &cynes::CPU::addr_ixr, &cynes::CPU::addr_imm, &cynes::CPU::addr_ixr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, &cynes::CPU::addr_zpr, 37 | &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_imp, &cynes::CPU::addr_imm, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, &cynes::CPU::addr_abr, 38 | &cynes::CPU::addr_rel, &cynes::CPU::addr_iyr, &cynes::CPU::addr_acc, &cynes::CPU::addr_iym, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, &cynes::CPU::addr_zxr, 39 | &cynes::CPU::addr_imp, &cynes::CPU::addr_ayr, &cynes::CPU::addr_imp, &cynes::CPU::addr_aym, &cynes::CPU::addr_axr, &cynes::CPU::addr_axr, &cynes::CPU::addr_axm, &cynes::CPU::addr_axm 40 | }; 41 | 42 | using _op_ptr = void (cynes::CPU::*)();; 43 | const _op_ptr cynes::CPU::INSTRUCTIONS[256] = { 44 | &cynes::CPU::op_brk, &cynes::CPU::op_ora, &cynes::CPU::op_jam, &cynes::CPU::op_slo, &cynes::CPU::op_nop, &cynes::CPU::op_ora, &cynes:: CPU::op_asl, &cynes::CPU::op_slo, 45 | &cynes::CPU::op_php, &cynes::CPU::op_ora, &cynes::CPU::op_aal, &cynes::CPU::op_anc, &cynes::CPU::op_nop, &cynes::CPU::op_ora, &cynes:: CPU::op_asl, &cynes::CPU::op_slo, 46 | &cynes::CPU::op_bpl, &cynes::CPU::op_ora, &cynes::CPU::op_jam, &cynes::CPU::op_slo, &cynes::CPU::op_nop, &cynes::CPU::op_ora, &cynes:: CPU::op_asl, &cynes::CPU::op_slo, 47 | &cynes::CPU::op_clc, &cynes::CPU::op_ora, &cynes::CPU::op_nop, &cynes::CPU::op_slo, &cynes::CPU::op_nop, &cynes::CPU::op_ora, &cynes:: CPU::op_asl, &cynes::CPU::op_slo, 48 | &cynes::CPU::op_jsr, &cynes::CPU::op_and, &cynes::CPU::op_jam, &cynes::CPU::op_rla, &cynes::CPU::op_bit, &cynes::CPU::op_and, &cynes:: CPU::op_rol, &cynes::CPU::op_rla, 49 | &cynes::CPU::op_plp, &cynes::CPU::op_and, &cynes::CPU::op_ral, &cynes::CPU::op_anc, &cynes::CPU::op_bit, &cynes::CPU::op_and, &cynes:: CPU::op_rol, &cynes::CPU::op_rla, 50 | &cynes::CPU::op_bmi, &cynes::CPU::op_and, &cynes::CPU::op_jam, &cynes::CPU::op_rla, &cynes::CPU::op_nop, &cynes::CPU::op_and, &cynes:: CPU::op_rol, &cynes::CPU::op_rla, 51 | &cynes::CPU::op_sec, &cynes::CPU::op_and, &cynes::CPU::op_nop, &cynes::CPU::op_rla, &cynes::CPU::op_nop, &cynes::CPU::op_and, &cynes:: CPU::op_rol, &cynes::CPU::op_rla, 52 | &cynes::CPU::op_rti, &cynes::CPU::op_eor, &cynes::CPU::op_jam, &cynes::CPU::op_sre, &cynes::CPU::op_nop, &cynes::CPU::op_eor, &cynes:: CPU::op_lsr, &cynes::CPU::op_sre, 53 | &cynes::CPU::op_pha, &cynes::CPU::op_eor, &cynes::CPU::op_lar, &cynes::CPU::op_alr, &cynes::CPU::op_jmp, &cynes::CPU::op_eor, &cynes:: CPU::op_lsr, &cynes::CPU::op_sre, 54 | &cynes::CPU::op_bvc, &cynes::CPU::op_eor, &cynes::CPU::op_jam, &cynes::CPU::op_sre, &cynes::CPU::op_nop, &cynes::CPU::op_eor, &cynes:: CPU::op_lsr, &cynes::CPU::op_sre, 55 | &cynes::CPU::op_cli, &cynes::CPU::op_eor, &cynes::CPU::op_nop, &cynes::CPU::op_sre, &cynes::CPU::op_nop, &cynes::CPU::op_eor, &cynes:: CPU::op_lsr, &cynes::CPU::op_sre, 56 | &cynes::CPU::op_rts, &cynes::CPU::op_adc, &cynes::CPU::op_jam, &cynes::CPU::op_rra, &cynes::CPU::op_nop, &cynes::CPU::op_adc, &cynes:: CPU::op_ror, &cynes::CPU::op_rra, 57 | &cynes::CPU::op_pla, &cynes::CPU::op_adc, &cynes::CPU::op_rar, &cynes::CPU::op_arr, &cynes::CPU::op_jmp, &cynes::CPU::op_adc, &cynes:: CPU::op_ror, &cynes::CPU::op_rra, 58 | &cynes::CPU::op_bvs, &cynes::CPU::op_adc, &cynes::CPU::op_jam, &cynes::CPU::op_rra, &cynes::CPU::op_nop, &cynes::CPU::op_adc, &cynes:: CPU::op_ror, &cynes::CPU::op_rra, 59 | &cynes::CPU::op_sei, &cynes::CPU::op_adc, &cynes::CPU::op_nop, &cynes::CPU::op_rra, &cynes::CPU::op_nop, &cynes::CPU::op_adc, &cynes:: CPU::op_ror, &cynes::CPU::op_rra, 60 | &cynes::CPU::op_nop, &cynes::CPU::op_sta, &cynes::CPU::op_nop, &cynes::CPU::op_sax, &cynes::CPU::op_sty, &cynes::CPU::op_sta, &cynes:: CPU::op_stx, &cynes::CPU::op_sax, 61 | &cynes::CPU::op_dey, &cynes::CPU::op_nop, &cynes::CPU::op_txa, &cynes::CPU::op_ane, &cynes::CPU::op_sty, &cynes::CPU::op_sta, &cynes:: CPU::op_stx, &cynes::CPU::op_sax, 62 | &cynes::CPU::op_bcc, &cynes::CPU::op_sta, &cynes::CPU::op_jam, &cynes::CPU::op_sha, &cynes::CPU::op_sty, &cynes::CPU::op_sta, &cynes:: CPU::op_stx, &cynes::CPU::op_sax, 63 | &cynes::CPU::op_tya, &cynes::CPU::op_sta, &cynes::CPU::op_txs, &cynes::CPU::op_tas, &cynes::CPU::op_shy, &cynes::CPU::op_sta, &cynes:: CPU::op_shx, &cynes::CPU::op_sha, 64 | &cynes::CPU::op_ldy, &cynes::CPU::op_lda, &cynes::CPU::op_ldx, &cynes::CPU::op_lax, &cynes::CPU::op_ldy, &cynes::CPU::op_lda, &cynes:: CPU::op_ldx, &cynes::CPU::op_lax, 65 | &cynes::CPU::op_tay, &cynes::CPU::op_lda, &cynes::CPU::op_tax, &cynes::CPU::op_lxa, &cynes::CPU::op_ldy, &cynes::CPU::op_lda, &cynes:: CPU::op_ldx, &cynes::CPU::op_lax, 66 | &cynes::CPU::op_bcs, &cynes::CPU::op_lda, &cynes::CPU::op_jam, &cynes::CPU::op_lax, &cynes::CPU::op_ldy, &cynes::CPU::op_lda, &cynes:: CPU::op_ldx, &cynes::CPU::op_lax, 67 | &cynes::CPU::op_clv, &cynes::CPU::op_lda, &cynes::CPU::op_tsx, &cynes::CPU::op_las, &cynes::CPU::op_ldy, &cynes::CPU::op_lda, &cynes:: CPU::op_ldx, &cynes::CPU::op_lax, 68 | &cynes::CPU::op_cpy, &cynes::CPU::op_cmp, &cynes::CPU::op_nop, &cynes::CPU::op_dcp, &cynes::CPU::op_cpy, &cynes::CPU::op_cmp, &cynes:: CPU::op_dec, &cynes::CPU::op_dcp, 69 | &cynes::CPU::op_iny, &cynes::CPU::op_cmp, &cynes::CPU::op_dex, &cynes::CPU::op_sbx, &cynes::CPU::op_cpy, &cynes::CPU::op_cmp, &cynes:: CPU::op_dec, &cynes::CPU::op_dcp, 70 | &cynes::CPU::op_bne, &cynes::CPU::op_cmp, &cynes::CPU::op_jam, &cynes::CPU::op_dcp, &cynes::CPU::op_nop, &cynes::CPU::op_cmp, &cynes:: CPU::op_dec, &cynes::CPU::op_dcp, 71 | &cynes::CPU::op_cld, &cynes::CPU::op_cmp, &cynes::CPU::op_nop, &cynes::CPU::op_dcp, &cynes::CPU::op_nop, &cynes::CPU::op_cmp, &cynes:: CPU::op_dec, &cynes::CPU::op_dcp, 72 | &cynes::CPU::op_cpx, &cynes::CPU::op_sbc, &cynes::CPU::op_nop, &cynes::CPU::op_isc, &cynes::CPU::op_cpx, &cynes::CPU::op_sbc, &cynes:: CPU::op_inc, &cynes::CPU::op_isc, 73 | &cynes::CPU::op_inx, &cynes::CPU::op_sbc, &cynes::CPU::op_nop, &cynes::CPU::op_usb, &cynes::CPU::op_cpx, &cynes::CPU::op_sbc, &cynes:: CPU::op_inc, &cynes::CPU::op_isc, 74 | &cynes::CPU::op_beq, &cynes::CPU::op_sbc, &cynes::CPU::op_jam, &cynes::CPU::op_isc, &cynes::CPU::op_nop, &cynes::CPU::op_sbc, &cynes:: CPU::op_inc, &cynes::CPU::op_isc, 75 | &cynes::CPU::op_sed, &cynes::CPU::op_sbc, &cynes::CPU::op_nop, &cynes::CPU::op_isc, &cynes::CPU::op_nop, &cynes::CPU::op_sbc, &cynes:: CPU::op_inc, &cynes::CPU::op_isc 76 | }; 77 | 78 | 79 | cynes::CPU::CPU(NES& nes) 80 | : _nes{nes} 81 | , _frozen{false} 82 | , _register_a{0x00} 83 | , _register_x{0x00} 84 | , _register_y{0x00} 85 | , _register_m{0x00} 86 | , _stack_pointer{0x00} 87 | , _program_counter{0x0000} 88 | , _delay_interrupt{false} 89 | , _should_issue_interrupt{false} 90 | , _line_mapper_interrupt{false} 91 | , _line_frame_interrupt{false} 92 | , _line_delta_interrupt{false} 93 | , _line_non_maskable_interrupt{false} 94 | , _edge_detector_non_maskable_interrupt{false} 95 | , _delay_non_maskable_interrupt{false} 96 | , _should_issue_non_maskable_interrupt{false} 97 | , _status{0x00} 98 | , _target_address{0x0000} {} 99 | 100 | void cynes::CPU::power() { 101 | _frozen = false; 102 | _line_non_maskable_interrupt = false; 103 | _line_mapper_interrupt = false; 104 | _line_frame_interrupt = false; 105 | _line_delta_interrupt = false; 106 | _should_issue_interrupt = false; 107 | _register_a = 0x00; 108 | _register_x = 0x00; 109 | _register_y = 0x00; 110 | _stack_pointer = 0xFD; 111 | _status = Flag::I; 112 | _program_counter = _nes.read_cpu(0xFFFC); 113 | _program_counter |= _nes.read_cpu(0xFFFD) << 8; 114 | } 115 | 116 | void cynes::CPU::reset() { 117 | _frozen = false; 118 | _line_non_maskable_interrupt = false; 119 | _line_mapper_interrupt = false; 120 | _line_frame_interrupt = false; 121 | _line_delta_interrupt = false; 122 | _stack_pointer -= 3; 123 | _status |= Flag::I; 124 | _program_counter = _nes.read_cpu(0xFFFC); 125 | _program_counter |= _nes.read_cpu(0xFFFD) << 8; 126 | } 127 | 128 | void cynes::CPU::tick() { 129 | if (_frozen) { 130 | return; 131 | } 132 | 133 | uint8_t instruction = fetch_next(); 134 | 135 | (this->*ADDRESSING_MODES[instruction])(); 136 | (this->*INSTRUCTIONS[instruction])(); 137 | 138 | if (_delay_non_maskable_interrupt || _delay_interrupt) { 139 | _nes.read(_program_counter); 140 | _nes.read(_program_counter); 141 | 142 | _nes.write(0x100 | _stack_pointer--, _program_counter >> 8); 143 | _nes.write(0x100 | _stack_pointer--, _program_counter & 0x00FF); 144 | 145 | uint16_t address = _should_issue_non_maskable_interrupt ? 0xFFFA : 0xFFFE; 146 | 147 | _should_issue_non_maskable_interrupt = false; 148 | 149 | _nes.write(0x100 | _stack_pointer--, _status | Flag::U); 150 | 151 | set_status(Flag::I, true); 152 | 153 | _program_counter = _nes.read(address); 154 | _program_counter |= _nes.read(address + 1) << 8; 155 | } 156 | } 157 | 158 | void cynes::CPU::poll() { 159 | _delay_non_maskable_interrupt = _should_issue_non_maskable_interrupt; 160 | 161 | if (!_edge_detector_non_maskable_interrupt && _line_non_maskable_interrupt) { 162 | _should_issue_non_maskable_interrupt = true; 163 | } 164 | 165 | _edge_detector_non_maskable_interrupt = _line_non_maskable_interrupt; 166 | _delay_interrupt = _should_issue_interrupt; 167 | 168 | _should_issue_interrupt = (_line_mapper_interrupt || _line_frame_interrupt || _line_delta_interrupt) && !get_status(Flag::I); 169 | } 170 | 171 | void cynes::CPU::set_non_maskable_interrupt(bool interrupt) { 172 | _line_non_maskable_interrupt = interrupt; 173 | } 174 | 175 | void cynes::CPU::set_mapper_interrupt(bool interrupt) { 176 | _line_mapper_interrupt = interrupt; 177 | } 178 | 179 | void cynes::CPU::set_frame_interrupt(bool interrupt) { 180 | _line_frame_interrupt = interrupt; 181 | } 182 | 183 | void cynes::CPU::set_delta_interrupt(bool interrupt) { 184 | _line_delta_interrupt = interrupt; 185 | } 186 | 187 | bool cynes::CPU::is_frozen() const { 188 | return _frozen; 189 | } 190 | 191 | uint8_t cynes::CPU::fetch_next() { 192 | return _nes.read(_program_counter++); 193 | } 194 | 195 | void cynes::CPU::set_status(uint8_t flag, bool value) { 196 | if (value) { 197 | _status |= flag; 198 | } else { 199 | _status &= ~flag; 200 | } 201 | } 202 | 203 | bool cynes::CPU::get_status(uint8_t flag) const { 204 | return _status & flag; 205 | } 206 | 207 | void cynes::CPU::addr_abr() { 208 | addr_abw(); 209 | _register_m = _nes.read(_target_address); 210 | } 211 | 212 | void cynes::CPU::addr_abw() { 213 | _target_address = fetch_next(); 214 | _target_address |= fetch_next() << 8; 215 | } 216 | 217 | void cynes::CPU::addr_acc() { 218 | _register_m = _nes.read(_program_counter); 219 | } 220 | 221 | void cynes::CPU::addr_axm() { 222 | addr_axw(); 223 | _register_m = _nes.read(_target_address); 224 | } 225 | 226 | void cynes::CPU::addr_axr() { 227 | _target_address = fetch_next(); 228 | 229 | uint16_t translated = _target_address + _register_x; 230 | bool invalid_address = (_target_address & 0xFF00) != (translated & 0xFF00); 231 | 232 | _target_address = translated & 0x00FF; 233 | _target_address |= fetch_next() << 8; 234 | _register_m = _nes.read(_target_address); 235 | 236 | if (invalid_address) { 237 | _target_address += 0x100; 238 | _register_m = _nes.read(_target_address); 239 | } 240 | } 241 | 242 | void cynes::CPU::addr_axw() { 243 | _target_address = fetch_next(); 244 | 245 | uint16_t translated = _target_address + _register_x; 246 | bool invalid_address = (_target_address & 0xFF00) != (translated & 0xFF00); 247 | 248 | _target_address = translated & 0x00FF; 249 | _target_address |= fetch_next() << 8; 250 | _register_m = _nes.read(_target_address); 251 | 252 | if (invalid_address) { 253 | _target_address += 0x100; 254 | } 255 | } 256 | 257 | void cynes::CPU::addr_aym() { 258 | addr_ayw(); 259 | _register_m = _nes.read(_target_address); 260 | } 261 | 262 | void cynes::CPU::addr_ayr() { 263 | _target_address = fetch_next(); 264 | 265 | uint16_t translated = _target_address + _register_y; 266 | bool invalid_address = (_target_address & 0xFF00) != (translated & 0xFF00); 267 | 268 | _target_address = translated & 0x00FF; 269 | _target_address |= fetch_next() << 8; 270 | _register_m = _nes.read(_target_address); 271 | 272 | if (invalid_address) { 273 | _target_address += 0x100; 274 | _register_m = _nes.read(_target_address); 275 | } 276 | } 277 | 278 | void cynes::CPU::addr_ayw() { 279 | _target_address = fetch_next(); 280 | 281 | uint16_t translated = _target_address + _register_y; 282 | bool invalid_address = (_target_address & 0xFF00) != (translated & 0xFF00); 283 | 284 | _target_address = translated & 0x00FF; 285 | _target_address |= fetch_next() << 8; 286 | _register_m = _nes.read(_target_address); 287 | 288 | if (invalid_address) { 289 | _target_address += 0x100; 290 | } 291 | } 292 | 293 | void cynes::CPU::addr_imm() { 294 | _register_m = fetch_next(); 295 | } 296 | 297 | void cynes::CPU::addr_imp() { 298 | _register_m = _nes.read(_program_counter); 299 | } 300 | 301 | void cynes::CPU::addr_ind() { 302 | uint16_t pointer = fetch_next(); 303 | 304 | pointer |= fetch_next() << 8; 305 | 306 | if ((pointer & 0x00FF) == 0xFF) { 307 | _target_address = _nes.read(pointer); 308 | _target_address |= _nes.read(pointer & 0xFF00) << 8; 309 | } else { 310 | _target_address = _nes.read(pointer); 311 | _target_address |= _nes.read(pointer + 1) << 8; 312 | } 313 | } 314 | 315 | void cynes::CPU::addr_ixr() { 316 | addr_ixw(); 317 | _register_m = _nes.read(_target_address); 318 | } 319 | 320 | void cynes::CPU::addr_ixw() { 321 | uint8_t pointer = fetch_next(); 322 | 323 | _register_m = _nes.read(pointer); 324 | 325 | pointer += _register_x; 326 | 327 | _target_address = _nes.read(pointer); 328 | _target_address |= _nes.read(++pointer & 0xFF) << 8; 329 | } 330 | 331 | void cynes::CPU::addr_iym() { 332 | addr_iyw(); 333 | _register_m = _nes.read(_target_address); 334 | } 335 | 336 | void cynes::CPU::addr_iyr() { 337 | uint8_t pointer = fetch_next(); 338 | 339 | _target_address = _nes.read(pointer); 340 | 341 | uint16_t translated = _target_address + _register_y; 342 | bool invalid_address = translated & 0xFF00; 343 | 344 | _target_address = translated & 0x00FF; 345 | _target_address |= _nes.read(++pointer & 0xFF) << 8; 346 | _register_m = _nes.read(_target_address); 347 | 348 | if (invalid_address) { 349 | _target_address += 0x100; 350 | _register_m = _nes.read(_target_address); 351 | } 352 | } 353 | 354 | void cynes::CPU::addr_iyw() { 355 | uint8_t pointer = fetch_next(); 356 | 357 | _target_address = _nes.read(pointer); 358 | 359 | uint16_t translated = _target_address + _register_y; 360 | bool invalid_address = (_target_address & 0xFF00) != (translated & 0xFF00); 361 | 362 | _target_address = translated & 0x00FF; 363 | _target_address |= _nes.read(++pointer & 0xFF) << 8; 364 | _register_m = _nes.read(_target_address); 365 | 366 | if (invalid_address) { 367 | _target_address += 0x100; 368 | } 369 | } 370 | 371 | void cynes::CPU::addr_rel() { 372 | _target_address = fetch_next(); 373 | 374 | if (_target_address & 0x80) { 375 | _target_address |= 0xFF00; 376 | } 377 | } 378 | 379 | void cynes::CPU::addr_zpr() { 380 | addr_zpw(); 381 | _register_m = _nes.read(_target_address); 382 | } 383 | 384 | void cynes::CPU::addr_zpw() { 385 | _target_address = fetch_next(); 386 | } 387 | 388 | void cynes::CPU::addr_zxr() { 389 | addr_zxw(); 390 | _register_m = _nes.read(_target_address); 391 | } 392 | 393 | void cynes::CPU::addr_zxw() { 394 | _target_address = fetch_next(); 395 | _register_m = _nes.read(_target_address); 396 | _target_address += _register_x; 397 | _target_address &= 0x00FF; 398 | } 399 | 400 | void cynes::CPU::addr_zyr() { 401 | addr_zyw(); 402 | _register_m = _nes.read(_target_address); 403 | } 404 | 405 | void cynes::CPU::addr_zyw() { 406 | _target_address = fetch_next(); 407 | _register_m = _nes.read(_target_address); 408 | _target_address += _register_y; 409 | _target_address &= 0x00FF; 410 | } 411 | 412 | void cynes::CPU::op_aal() { 413 | set_status(Flag::C, _register_a & 0x80); 414 | 415 | _register_a <<= 1; 416 | 417 | set_status(Flag::Z, !_register_a); 418 | set_status(Flag::N, _register_a & 0x80); 419 | } 420 | 421 | void cynes::CPU::op_adc() { 422 | uint16_t result = _register_a + _register_m + (get_status(Flag::C) ? 0x01 : 0x00); 423 | 424 | set_status(Flag::C, result & 0xFF00); 425 | set_status(Flag::V, ~(_register_a ^ _register_m) & (_register_a ^ result) & 0x80); 426 | 427 | _register_a = result & 0x00FF; 428 | 429 | set_status(Flag::Z, !_register_a); 430 | set_status(Flag::N, _register_a & 0x80); 431 | } 432 | 433 | void cynes::CPU::op_alr() { 434 | _register_a &= _register_m; 435 | 436 | set_status(Flag::C, _register_a & 0x01); 437 | 438 | _register_a >>= 1; 439 | 440 | set_status(Flag::Z, !_register_a); 441 | set_status(Flag::N, _register_a & 0x80); 442 | } 443 | 444 | void cynes::CPU::op_anc() { 445 | _register_a &= _register_m; 446 | 447 | set_status(Flag::Z, !_register_a); 448 | set_status(Flag::N, _register_a & 0x80); 449 | set_status(Flag::C, _register_a & 0x80); 450 | } 451 | 452 | void cynes::CPU::op_and() { 453 | _register_a &= _register_m; 454 | 455 | set_status(Flag::Z, !_register_a); 456 | set_status(Flag::N, _register_a & 0x80); 457 | } 458 | 459 | void cynes::CPU::op_ane() { 460 | _register_a = (_register_a | 0xEE) & _register_x & _register_m; 461 | } 462 | 463 | void cynes::CPU::op_arr() { 464 | _register_a &= _register_m; 465 | _register_a = (get_status(Flag::C) ? 0x80 : 0x00) | (_register_a >> 1); 466 | 467 | set_status(Flag::C, _register_a & 0x40); 468 | set_status(Flag::V, bool(_register_a & 0x40) ^ bool(_register_a & 0x20)); 469 | set_status(Flag::Z, !_register_a); 470 | set_status(Flag::N, _register_a & 0x80); 471 | } 472 | 473 | void cynes::CPU::op_asl() { 474 | _nes.write(_target_address, _register_m); 475 | 476 | set_status(Flag::C, _register_m & 0x80); 477 | 478 | _register_m <<= 1; 479 | 480 | set_status(Flag::Z, !_register_m); 481 | set_status(Flag::N, _register_m & 0x80); 482 | 483 | _nes.write(_target_address, _register_m); 484 | } 485 | 486 | void cynes::CPU::op_bcc() { 487 | if (!get_status(Flag::C)) { 488 | if (_should_issue_interrupt && !_delay_interrupt) { 489 | _should_issue_interrupt = false; 490 | } 491 | 492 | _nes.read(_program_counter); 493 | 494 | uint16_t translated = _target_address + _program_counter; 495 | 496 | if ((translated & 0xFF00) != (_program_counter & 0xFF00)) { 497 | _nes.read(_program_counter); 498 | } 499 | 500 | _program_counter = translated; 501 | } 502 | } 503 | 504 | void cynes::CPU::op_bcs() { 505 | if (get_status(Flag::C)) { 506 | if (_should_issue_interrupt && !_delay_interrupt) { 507 | _should_issue_interrupt = false; 508 | } 509 | 510 | _nes.read(_program_counter); 511 | 512 | uint16_t translated = _target_address + _program_counter; 513 | 514 | if ((translated & 0xFF00) != (_program_counter & 0xFF00)) { 515 | _nes.read(_program_counter); 516 | } 517 | 518 | _program_counter = translated; 519 | } 520 | } 521 | 522 | void cynes::CPU::op_beq() { 523 | if (get_status(Flag::Z)) { 524 | if (_should_issue_interrupt && !_delay_interrupt) { 525 | _should_issue_interrupt = false; 526 | } 527 | 528 | _nes.read(_program_counter); 529 | 530 | uint16_t translated = _target_address + _program_counter; 531 | 532 | if ((translated & 0xFF00) != (_program_counter & 0xFF00)) { 533 | _nes.read(_program_counter); 534 | } 535 | 536 | _program_counter = translated; 537 | } 538 | } 539 | 540 | void cynes::CPU::op_bit() { 541 | set_status(Flag::Z, !(_register_a & _register_m)); 542 | set_status(Flag::V, _register_m & 0x40); 543 | set_status(Flag::N, _register_m & 0x80); 544 | } 545 | 546 | void cynes::CPU::op_bmi() { 547 | if (get_status(Flag::N)) { 548 | if (_should_issue_interrupt && !_delay_interrupt) { 549 | _should_issue_interrupt = false; 550 | } 551 | 552 | _nes.read(_program_counter); 553 | 554 | uint16_t translated = _target_address + _program_counter; 555 | 556 | if ((translated & 0xFF00) != (_program_counter & 0xFF00)) { 557 | _nes.read(_program_counter); 558 | } 559 | 560 | _program_counter = translated; 561 | } 562 | } 563 | 564 | void cynes::CPU::op_bne() { 565 | if (!get_status(Flag::Z)) { 566 | if (_should_issue_interrupt && !_delay_interrupt) { 567 | _should_issue_interrupt = false; 568 | } 569 | 570 | _nes.read(_program_counter); 571 | 572 | uint16_t translated = _target_address + _program_counter; 573 | 574 | if ((translated & 0xFF00) != (_program_counter & 0xFF00)) { 575 | _nes.read(_program_counter); 576 | } 577 | 578 | _program_counter = translated; 579 | } 580 | } 581 | 582 | void cynes::CPU::op_bpl() { 583 | if (!get_status(Flag::N)) { 584 | if (_should_issue_interrupt && !_delay_interrupt) { 585 | _should_issue_interrupt = false; 586 | } 587 | 588 | _nes.read(_program_counter); 589 | 590 | uint16_t translated = _target_address + _program_counter; 591 | 592 | if ((translated & 0xFF00) != (_program_counter & 0xFF00)) { 593 | _nes.read(_program_counter); 594 | } 595 | 596 | _program_counter = translated; 597 | } 598 | } 599 | 600 | void cynes::CPU::op_brk() { 601 | _program_counter++; 602 | 603 | _nes.write(0x100 | _stack_pointer--, _program_counter >> 8); 604 | _nes.write(0x100 | _stack_pointer--, _program_counter & 0x00FF); 605 | 606 | uint16_t address = _should_issue_non_maskable_interrupt ? 0xFFFA : 0xFFFE; 607 | 608 | _should_issue_non_maskable_interrupt = false; 609 | 610 | _nes.write(0x100 | _stack_pointer--, _status | Flag::B | Flag::U); 611 | 612 | set_status(Flag::I, true); 613 | 614 | _program_counter = _nes.read(address); 615 | _program_counter |= _nes.read(address + 1) << 8; 616 | 617 | _delay_non_maskable_interrupt = false; 618 | } 619 | 620 | void cynes::CPU::op_bvc() { 621 | if (!get_status(Flag::V)) { 622 | if (_should_issue_interrupt && !_delay_interrupt) { 623 | _should_issue_interrupt = false; 624 | } 625 | 626 | _nes.read(_program_counter); 627 | 628 | uint16_t translated = _target_address + _program_counter; 629 | 630 | if ((translated & 0xFF00) != (_program_counter & 0xFF00)) { 631 | _nes.read(_program_counter); 632 | } 633 | 634 | _program_counter = translated; 635 | } 636 | } 637 | 638 | void cynes::CPU::op_bvs() { 639 | if (get_status(Flag::V)) { 640 | if (_should_issue_interrupt && !_delay_interrupt) { 641 | _should_issue_interrupt = false; 642 | } 643 | 644 | _nes.read(_program_counter); 645 | 646 | uint16_t translated = _target_address + _program_counter; 647 | 648 | if ((translated & 0xFF00) != (_program_counter & 0xFF00)) { 649 | _nes.read(_program_counter); 650 | } 651 | 652 | _program_counter = translated; 653 | } 654 | } 655 | 656 | void cynes::CPU::op_clc() { 657 | set_status(Flag::C, false); 658 | } 659 | 660 | void cynes::CPU::op_cld() { 661 | set_status(Flag::D, false); 662 | } 663 | 664 | void cynes::CPU::op_cli() { 665 | set_status(Flag::I, false); 666 | } 667 | 668 | void cynes::CPU::op_clv() { 669 | set_status(Flag::V, false); 670 | } 671 | 672 | void cynes::CPU::op_cmp() { 673 | set_status(Flag::C, _register_a >= _register_m); 674 | set_status(Flag::Z, _register_a == _register_m); 675 | set_status(Flag::N, (_register_a - _register_m) & 0x80); 676 | } 677 | 678 | void cynes::CPU::op_cpx() { 679 | set_status(Flag::C, _register_x >= _register_m); 680 | set_status(Flag::Z, _register_x == _register_m); 681 | set_status(Flag::N, (_register_x - _register_m) & 0x80); 682 | } 683 | 684 | void cynes::CPU::op_cpy() { 685 | set_status(Flag::C, _register_y >= _register_m); 686 | set_status(Flag::Z, _register_y == _register_m); 687 | set_status(Flag::N, (_register_y - _register_m) & 0x80); 688 | } 689 | 690 | void cynes::CPU::op_dcp() { 691 | _nes.write(_target_address, _register_m); 692 | 693 | _register_m--; 694 | 695 | set_status(Flag::C, _register_a >= _register_m); 696 | set_status(Flag::Z, _register_a == _register_m); 697 | set_status(Flag::N, (_register_a - _register_m) & 0x80); 698 | 699 | _nes.write(_target_address, _register_m); 700 | } 701 | 702 | void cynes::CPU::op_dec() { 703 | _nes.write(_target_address, _register_m); 704 | 705 | _register_m--; 706 | 707 | set_status(Flag::Z, !_register_m); 708 | set_status(Flag::N, _register_m & 0x80); 709 | 710 | _nes.write(_target_address, _register_m); 711 | } 712 | 713 | void cynes::CPU::op_dex() { 714 | _register_x--; 715 | 716 | set_status(Flag::Z, !_register_x); 717 | set_status(Flag::N, _register_x & 0x80); 718 | } 719 | 720 | void cynes::CPU::op_dey() { 721 | _register_y--; 722 | 723 | set_status(Flag::Z, !_register_y); 724 | set_status(Flag::N, _register_y & 0x80); 725 | } 726 | 727 | void cynes::CPU::op_eor() { 728 | _register_a ^= _register_m; 729 | 730 | set_status(Flag::Z, !_register_a); 731 | set_status(Flag::N, _register_a & 0x80); 732 | } 733 | 734 | void cynes::CPU::op_inc() { 735 | _nes.write(_target_address, _register_m); 736 | 737 | _register_m++; 738 | 739 | set_status(Flag::Z, !_register_m); 740 | set_status(Flag::N, _register_m & 0x80); 741 | 742 | _nes.write(_target_address, _register_m); 743 | } 744 | 745 | void cynes::CPU::op_inx() { 746 | _register_x++; 747 | 748 | set_status(Flag::Z, !_register_x); 749 | set_status(Flag::N, _register_x & 0x80); 750 | } 751 | 752 | void cynes::CPU::op_iny() { 753 | _register_y++; 754 | 755 | set_status(Flag::Z, !_register_y); 756 | set_status(Flag::N, _register_y & 0x80); 757 | } 758 | 759 | void cynes::CPU::op_isc() { 760 | _nes.write(_target_address, _register_m); 761 | 762 | _register_m++; 763 | 764 | uint8_t value = _register_m; 765 | 766 | _register_m ^= 0xFF; 767 | 768 | uint16_t result = _register_a + _register_m + (get_status(Flag::C) ? 0x01 : 0x00); 769 | 770 | set_status(Flag::C, result & 0x0100); 771 | set_status(Flag::V, ~(_register_a ^ _register_m) & (_register_a ^ result) & 0x80); 772 | 773 | _register_a = result & 0x00FF; 774 | 775 | set_status(Flag::Z, !_register_a); 776 | set_status(Flag::N, _register_a & 0x80); 777 | 778 | _nes.write(_target_address, value); 779 | } 780 | 781 | void cynes::CPU::op_jam() { 782 | _frozen = true; 783 | } 784 | 785 | void cynes::CPU::op_jmp() { 786 | _program_counter = _target_address; 787 | } 788 | 789 | void cynes::CPU::op_jsr() { 790 | _nes.read(_program_counter); 791 | 792 | _program_counter--; 793 | 794 | _nes.write(0x100 | _stack_pointer--, _program_counter >> 8); 795 | _nes.write(0x100 | _stack_pointer--, _program_counter & 0x00FF); 796 | 797 | _program_counter = _target_address; 798 | } 799 | 800 | void cynes::CPU::op_lar() { 801 | set_status(Flag::C, _register_a & 0x01); 802 | 803 | _register_a >>= 1; 804 | 805 | set_status(Flag::Z, !_register_a); 806 | set_status(Flag::N, _register_a & 0x80); 807 | } 808 | 809 | void cynes::CPU::op_las() { 810 | uint8_t result = _register_m & _stack_pointer; 811 | 812 | _register_a = result; 813 | _register_x = result; 814 | _stack_pointer = result; 815 | } 816 | 817 | void cynes::CPU::op_lax() { 818 | _register_a = _register_m; 819 | _register_x = _register_m; 820 | 821 | set_status(Flag::Z, !_register_m); 822 | set_status(Flag::N, _register_m & 0x80); 823 | } 824 | 825 | void cynes::CPU::op_lda() { 826 | _register_a = _register_m; 827 | 828 | set_status(Flag::Z, !_register_a); 829 | set_status(Flag::N, _register_a & 0x80); 830 | } 831 | 832 | void cynes::CPU::op_ldx() { 833 | _register_x = _register_m; 834 | 835 | set_status(Flag::Z, !_register_x); 836 | set_status(Flag::N, _register_x & 0x80); 837 | } 838 | 839 | void cynes::CPU::op_ldy() { 840 | _register_y = _register_m; 841 | 842 | set_status(Flag::Z, !_register_y); 843 | set_status(Flag::N, _register_y & 0x80); 844 | } 845 | 846 | void cynes::CPU::op_lsr() { 847 | _nes.write(_target_address, _register_m); 848 | 849 | set_status(Flag::C, _register_m & 0x01); 850 | 851 | _register_m >>= 1; 852 | 853 | set_status(Flag::Z, !_register_m); 854 | set_status(Flag::N, _register_m & 0x80); 855 | 856 | _nes.write(_target_address, _register_m); 857 | } 858 | 859 | void cynes::CPU::op_lxa() { 860 | _register_a = _register_m; 861 | _register_x = _register_m; 862 | 863 | set_status(Flag::Z, !_register_a); 864 | set_status(Flag::N, _register_a & 0x80); 865 | } 866 | 867 | void cynes::CPU::op_nop() {} 868 | 869 | void cynes::CPU::op_ora() { 870 | _register_a |= _register_m; 871 | 872 | set_status(Flag::Z, !_register_a); 873 | set_status(Flag::N, _register_a & 0x80); 874 | } 875 | 876 | void cynes::CPU::op_pha() { 877 | _nes.write(0x100 | _stack_pointer--, _register_a); 878 | } 879 | 880 | void cynes::CPU::op_php() { 881 | _nes.write(0x100 | _stack_pointer--, _status | Flag::B | Flag::U); 882 | } 883 | 884 | void cynes::CPU::op_pla() { 885 | _stack_pointer++; 886 | _nes.read(_program_counter); 887 | _register_a = _nes.read(0x100 | _stack_pointer); 888 | 889 | set_status(Flag::Z, !_register_a); 890 | set_status(Flag::N, _register_a & 0x80); 891 | } 892 | 893 | void cynes::CPU::op_plp() { 894 | _stack_pointer++; 895 | _nes.read(_program_counter); 896 | _status = _nes.read(0x100 | _stack_pointer) & 0xCF; 897 | } 898 | 899 | void cynes::CPU::op_ral() { 900 | bool carry = _register_a & 0x80; 901 | 902 | _register_a = (get_status(Flag::C) ? 0x01 : 0x00) | (_register_a << 1); 903 | 904 | set_status(Flag::C, carry); 905 | set_status(Flag::Z, !_register_a); 906 | set_status(Flag::N, _register_a & 0x80); 907 | } 908 | 909 | void cynes::CPU::op_rar() { 910 | bool carry = _register_a & 0x01; 911 | 912 | _register_a = (get_status(Flag::C) ? 0x80 : 0x00) | (_register_a >> 1); 913 | 914 | set_status(Flag::C, carry); 915 | set_status(Flag::Z, !_register_a); 916 | set_status(Flag::N, _register_a & 0x80); 917 | } 918 | 919 | void cynes::CPU::op_rla() { 920 | _nes.write(_target_address, _register_m); 921 | 922 | bool carry = _register_m & 0x80; 923 | 924 | _register_m = (get_status(Flag::C) ? 0x01 : 0x00) | (_register_m << 1); 925 | _register_a &= _register_m; 926 | 927 | set_status(Flag::C, carry); 928 | set_status(Flag::Z, !_register_a); 929 | set_status(Flag::N, _register_a & 0x80); 930 | 931 | _nes.write(_target_address, _register_m); 932 | } 933 | 934 | void cynes::CPU::op_rol() { 935 | _nes.write(_target_address, _register_m); 936 | 937 | bool carry = _register_m & 0x80; 938 | 939 | _register_m = (get_status(Flag::C) ? 0x01 : 0x00) | (_register_m << 1); 940 | 941 | set_status(Flag::C, carry); 942 | set_status(Flag::Z, !_register_m); 943 | set_status(Flag::N, _register_m & 0x80); 944 | 945 | _nes.write(_target_address, _register_m); 946 | } 947 | 948 | void cynes::CPU::op_ror() { 949 | _nes.write(_target_address, _register_m); 950 | 951 | bool carry = _register_m & 0x01; 952 | 953 | _register_m = (get_status(Flag::C) ? 0x80 : 0x00) | (_register_m >> 1); 954 | 955 | set_status(Flag::C, carry); 956 | set_status(Flag::Z, !_register_m); 957 | set_status(Flag::N, _register_m & 0x80); 958 | 959 | _nes.write(_target_address, _register_m); 960 | } 961 | 962 | void cynes::CPU::op_rra() { 963 | _nes.write(_target_address, _register_m); 964 | 965 | uint8_t carry = _register_m & 0x01; 966 | 967 | _register_m = (get_status(Flag::C) ? 0x80 : 0x00) | (_register_m >> 1); 968 | 969 | uint16_t result = _register_a + _register_m + carry; 970 | 971 | set_status(Flag::C, result & 0x0100); 972 | set_status(Flag::V, ~(_register_a ^ _register_m) & (_register_a ^ result) & 0x80); 973 | 974 | _register_a = result & 0x00FF; 975 | 976 | set_status(Flag::Z, !_register_a); 977 | set_status(Flag::N, _register_a & 0x80); 978 | 979 | _nes.write(_target_address, _register_m); 980 | } 981 | 982 | void cynes::CPU::op_rti() { 983 | _stack_pointer++; 984 | _nes.read(_program_counter); 985 | _status = _nes.read(0x100 | _stack_pointer) & 0xCF; 986 | _program_counter = _nes.read(0x100 | ++_stack_pointer); 987 | _program_counter |= _nes.read(0x100 | ++_stack_pointer) << 8; 988 | } 989 | 990 | void cynes::CPU::op_rts() { 991 | _stack_pointer++; 992 | 993 | _nes.read(_program_counter); 994 | _nes.read(_program_counter); 995 | 996 | _program_counter = _nes.read(0x100 | _stack_pointer); 997 | _program_counter |= _nes.read(0x100 | ++_stack_pointer) << 8; 998 | _program_counter++; 999 | } 1000 | 1001 | void cynes::CPU::op_sax() { 1002 | _nes.write(_target_address, _register_a & _register_x); 1003 | } 1004 | 1005 | void cynes::CPU::op_sbc() { 1006 | _register_m ^= 0xFF; 1007 | 1008 | uint16_t result = _register_a + _register_m + (get_status(Flag::C) ? 0x01 : 0x00); 1009 | 1010 | set_status(Flag::C, result & 0xFF00); 1011 | set_status(Flag::V, ~(_register_a ^ _register_m) & (_register_a ^ result) & 0x80); 1012 | 1013 | _register_a = result & 0x00FF; 1014 | 1015 | set_status(Flag::Z, !_register_a); 1016 | set_status(Flag::N, _register_a & 0x80); 1017 | } 1018 | 1019 | void cynes::CPU::op_sbx() { 1020 | _register_x &= _register_a; 1021 | 1022 | set_status(Flag::C, _register_x >= _register_m); 1023 | set_status(Flag::Z, _register_x == _register_m); 1024 | 1025 | _register_x -= _register_m; 1026 | 1027 | set_status(Flag::N, _register_x & 0x80); 1028 | } 1029 | 1030 | void cynes::CPU::op_sec() { 1031 | set_status(Flag::C, true); 1032 | } 1033 | 1034 | void cynes::CPU::op_sed() { 1035 | set_status(Flag::D, true); 1036 | } 1037 | 1038 | void cynes::CPU::op_sei() { 1039 | set_status(Flag::I, true); 1040 | } 1041 | 1042 | void cynes::CPU::op_sha() { 1043 | _nes.write(_target_address, _register_a & _register_x & (uint8_t(_target_address >> 8) + 1)); 1044 | } 1045 | 1046 | void cynes::CPU::op_shx() { 1047 | uint8_t address_high = 1 + (_target_address >> 8); 1048 | 1049 | _nes.write(((_register_x & address_high) << 8) | (_target_address & 0xFF), _register_x & address_high); 1050 | } 1051 | 1052 | void cynes::CPU::op_shy() { 1053 | uint8_t address_high = 1 + (_target_address >> 8); 1054 | 1055 | _nes.write(((_register_y & address_high) << 8) | (_target_address & 0xFF), _register_y & address_high); 1056 | } 1057 | 1058 | void cynes::CPU::op_slo() { 1059 | _nes.write(_target_address, _register_m); 1060 | 1061 | set_status(Flag::C, _register_m & 0x80); 1062 | 1063 | _register_m <<= 1; 1064 | _register_a |= _register_m; 1065 | 1066 | set_status(Flag::Z, !_register_a); 1067 | set_status(Flag::N, _register_a & 0x80); 1068 | 1069 | _nes.write(_target_address, _register_m); 1070 | } 1071 | 1072 | void cynes::CPU::op_sre() { 1073 | _nes.write(_target_address, _register_m); 1074 | 1075 | set_status(Flag::C, _register_m & 0x01); 1076 | 1077 | _register_m >>= 1; 1078 | _register_a ^= _register_m; 1079 | 1080 | set_status(Flag::Z, !_register_a); 1081 | set_status(Flag::N, _register_a & 0x80); 1082 | 1083 | _nes.write(_target_address, _register_m); 1084 | } 1085 | 1086 | void cynes::CPU::op_sta() { 1087 | _nes.write(_target_address, _register_a); 1088 | } 1089 | 1090 | void cynes::CPU::op_stx() { 1091 | _nes.write(_target_address, _register_x); 1092 | } 1093 | 1094 | void cynes::CPU::op_sty() { 1095 | _nes.write(_target_address, _register_y); 1096 | } 1097 | 1098 | void cynes::CPU::op_tas() { 1099 | _stack_pointer = _register_a & _register_x; 1100 | 1101 | _nes.write(_target_address, _stack_pointer & (uint8_t(_target_address >> 8) + 1)); 1102 | } 1103 | 1104 | void cynes::CPU::op_tax() { 1105 | _register_x = _register_a; 1106 | 1107 | set_status(Flag::Z, !_register_x); 1108 | set_status(Flag::N, _register_x & 0x80); 1109 | } 1110 | 1111 | void cynes::CPU::op_tay() { 1112 | _register_y = _register_a; 1113 | 1114 | set_status(Flag::Z, !_register_y); 1115 | set_status(Flag::N, _register_y & 0x80); 1116 | } 1117 | 1118 | void cynes::CPU::op_tsx() { 1119 | _register_x = _stack_pointer; 1120 | 1121 | set_status(Flag::Z, !_register_x); 1122 | set_status(Flag::N, _register_x & 0x80); 1123 | } 1124 | 1125 | void cynes::CPU::op_txa() { 1126 | _register_a = _register_x; 1127 | 1128 | set_status(Flag::Z, !_register_a); 1129 | set_status(Flag::N, _register_a & 0x80); 1130 | } 1131 | 1132 | void cynes::CPU::op_txs() { 1133 | _stack_pointer = _register_x; 1134 | } 1135 | 1136 | void cynes::CPU::op_tya() { 1137 | _register_a = _register_y; 1138 | 1139 | set_status(Flag::Z, !_register_a); 1140 | set_status(Flag::N, _register_a & 0x80); 1141 | } 1142 | 1143 | void cynes::CPU::op_usb() { 1144 | _register_m ^= 0xFF; 1145 | 1146 | uint16_t result = _register_a + _register_m + (get_status(Flag::C) ? 0x01 : 0x00); 1147 | 1148 | set_status(Flag::C, result & 0x0100); 1149 | set_status(Flag::V, ~(_register_a ^ _register_m) & (_register_a ^ result) & 0x80); 1150 | 1151 | _register_a = result & 0x00FF; 1152 | 1153 | set_status(Flag::Z, !_register_a); 1154 | set_status(Flag::N, _register_a & 0x80); 1155 | } 1156 | --------------------------------------------------------------------------------