├── docs ├── Contra1.png ├── F1_race.png ├── Ikki(J).png ├── Journey.png ├── Athena(J).png ├── Lifeporce.png ├── MapleStory.png ├── mega_man.png ├── Castlevania.png ├── Donkey_kong.png ├── CircusCharlie.png ├── Communication.png ├── CrazyClimber(J).png ├── StarLuster(J).png ├── SuperMarioBros.png ├── Senjou_no_Ookami(J).png └── CHANGELOG.md ├── .devcontainer ├── build-image.sh ├── devcontainer.json └── nes-dockerfile ├── .gitee ├── ISSUE_TEMPLATE.zh-CN.md └── PULL_REQUEST_TEMPLATE.zh-CN.md ├── sdl ├── sdl2 │ ├── CMakeLists.txt │ ├── xmake.lua │ ├── port │ │ ├── nes_conf.h │ │ └── nes_port.c │ └── main.c └── sdl3 │ ├── xmake.lua │ ├── port │ ├── nes_conf.h │ └── nes_port.c │ └── main.c ├── .github └── workflows │ ├── issue-translator.yml │ ├── windows.yml │ ├── macos.yml │ ├── linux.yml │ └── release.yml ├── .gitignore ├── port ├── nes_conf.h └── nes_port.c ├── src ├── nes_mapper │ ├── nes_mapper0.c │ ├── nes_mapper180.c │ ├── nes_mapper94.c │ ├── nes_mapper2.c │ ├── nes_mapper7.c │ ├── nes_mapper3.c │ ├── nes_mapper117.c │ └── nes_mapper1.c ├── nes_default.c ├── nes_mapper.c ├── nes_rom.c ├── nes_ppu.c ├── nes.c └── nes_apu.c ├── inc ├── nes_log.h ├── nes.h ├── nes_default.h ├── nes_cpu.h ├── nes_rom.h ├── nes_ppu.h ├── nes_apu.h └── nes_mapper.h ├── README_zh.md ├── README.md └── LICENSE /docs/Contra1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Contra1.png -------------------------------------------------------------------------------- /docs/F1_race.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/F1_race.png -------------------------------------------------------------------------------- /docs/Ikki(J).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Ikki(J).png -------------------------------------------------------------------------------- /docs/Journey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Journey.png -------------------------------------------------------------------------------- /docs/Athena(J).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Athena(J).png -------------------------------------------------------------------------------- /docs/Lifeporce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Lifeporce.png -------------------------------------------------------------------------------- /docs/MapleStory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/MapleStory.png -------------------------------------------------------------------------------- /docs/mega_man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/mega_man.png -------------------------------------------------------------------------------- /docs/Castlevania.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Castlevania.png -------------------------------------------------------------------------------- /docs/Donkey_kong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Donkey_kong.png -------------------------------------------------------------------------------- /.devcontainer/build-image.sh: -------------------------------------------------------------------------------- 1 | docker build -f nes-dockerfile -t nes.osprey.io/nes-build-run . 2 | -------------------------------------------------------------------------------- /docs/CircusCharlie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/CircusCharlie.png -------------------------------------------------------------------------------- /docs/Communication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Communication.png -------------------------------------------------------------------------------- /docs/CrazyClimber(J).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/CrazyClimber(J).png -------------------------------------------------------------------------------- /docs/StarLuster(J).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/StarLuster(J).png -------------------------------------------------------------------------------- /docs/SuperMarioBros.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/SuperMarioBros.png -------------------------------------------------------------------------------- /docs/Senjou_no_Ookami(J).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PeakRacing/nes/HEAD/docs/Senjou_no_Ookami(J).png -------------------------------------------------------------------------------- /.gitee/ISSUE_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | ### 该问题是怎么引起的? 2 | 3 | 4 | 5 | ### 重现步骤 6 | 7 | 8 | 9 | ### 报错信息 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitee/PULL_REQUEST_TEMPLATE.zh-CN.md: -------------------------------------------------------------------------------- 1 | ### 相关的Issue 2 | 3 | 4 | ### 原因(目的、解决的问题等) 5 | 6 | 7 | ### 描述(做了什么,变更了什么) 8 | 9 | 10 | ### 测试用例(新增、改动、可能影响的功能) 11 | 12 | -------------------------------------------------------------------------------- /sdl/sdl2/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(nes) 4 | 5 | find_package(SDL2 CONFIG REQUIRED) 6 | include_directories(${SDL2_INCLUDE_DIRS}) 7 | 8 | set(NES_DIRS "../..") 9 | 10 | file(GLOB_RECURSE SRCS 11 | ${NES_DIRS}/src/*.c 12 | port/*.c 13 | main.c 14 | ) 15 | list(APPEND INCS 16 | ${NES_DIRS}/inc 17 | port 18 | ) 19 | include_directories(${INCS}) 20 | 21 | add_executable(${PROJECT_NAME} ${SRCS}) 22 | target_link_libraries(${PROJECT_NAME} 23 | PRIVATE SDL2::SDL2 24 | ) 25 | -------------------------------------------------------------------------------- /.github/workflows/issue-translator.yml: -------------------------------------------------------------------------------- 1 | name: 'issue-translator' 2 | on: 3 | issue_comment: 4 | types: [created] 5 | issues: 6 | types: [opened] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: usthe/issues-translate-action@v2.7 13 | with: 14 | IS_MODIFY_TITLE: false 15 | # 非必须,决定是否需要修改issue标题内容 16 | # 若是true,则机器人账户@Issues-translate-bot必须拥有修改此仓库issue权限。可以通过邀请@Issues-translate-bot加入仓库协作者实现。 17 | # CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿 18 | # 非必须,自定义机器人翻译的前缀开始内容。 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.a 21 | *.la 22 | *.lo 23 | 24 | # Shared objects (inc. Windows DLLs) 25 | *.so 26 | *.so.* 27 | *.dylib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | *.i*86 34 | *.x86_64 35 | *.hex 36 | 37 | # Debug files 38 | *.dSYM/ 39 | *.su 40 | *.idb 41 | *.pdb 42 | 43 | # Kernel Module Compile Results 44 | *.mod* 45 | *.cmd 46 | .tmp_versions/ 47 | modules.order 48 | Module.symvers 49 | Mkfile.old 50 | dkms.conf 51 | 52 | 53 | .vscode 54 | .xmake 55 | 56 | *.nes 57 | *.NES 58 | **/build 59 | vsxmake2022 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ([中文](# 更新日志)) 2 | 3 | # Changelog 4 | 5 | ## master (unreleased) 6 | 7 | 8 | 9 | ## v0.0.2 10 | 11 | ### ADD: 12 | 13 | - APU 14 | - Mapper support 3,7,94,117,180 15 | - Merge threads 16 | 17 | ### FIX: 18 | 19 | - Background drawing mirroring error 20 | 21 | ### DEL: 22 | 23 | - Delete llvm 24 | 25 | 26 | 27 | ## v0.0.1 28 | 29 | The first beta version, which already supports CUP, PPU, mapper0 2, is already playable Super Mario, Contra, etc 30 | 31 | 32 | 33 | 34 | 35 | # ([英文](# Changelog)) 36 | 37 | # 更新日志 38 | 39 | ## master (开发中) 40 | 41 | 42 | 43 | ## v0.0.2 44 | 45 | ### 新增: 46 | 47 | - APU 48 | - mapper 支持 3,7,94,117,180 49 | - 合并线程 50 | 51 | ### 修复: 52 | 53 | - 背景绘制镜像错误 54 | 55 | ### 删除: 56 | 57 | - 去掉llvm使用 58 | 59 | 60 | 61 | ## v0.0.1 62 | 63 | 第一个浏览版,已支持CUP,PPU,mapper0 2,已可玩超级玛丽,魂斗罗等 64 | -------------------------------------------------------------------------------- /sdl/sdl3/xmake.lua: -------------------------------------------------------------------------------- 1 | set_project("nes") 2 | set_xmakever("3.0.0") 3 | add_rules("mode.debug", "mode.release") 4 | 5 | if is_mode("debug") then 6 | set_symbols("debug") 7 | set_optimize("none") 8 | else 9 | set_strip("all") 10 | set_symbols("hidden") 11 | set_optimize("fastest") 12 | end 13 | 14 | set_warnings("allextra") 15 | set_languages("c11") 16 | 17 | if is_plat("wasm") then 18 | add_requires("emscripten") 19 | set_toolchains("emcc@emscripten") 20 | end 21 | 22 | -- TODO 23 | -- if is_host("windows") then 24 | -- elseif is_host("linux") then 25 | -- elseif is_host("macosx") then 26 | -- else 27 | -- end 28 | 29 | -- [[ add SDL3 ]] 30 | add_requires("libsdl3") 31 | add_packages("libsdl3") 32 | 33 | target("nes", function () 34 | set_kind("binary") 35 | 36 | local nes_dir = "../.." 37 | add_includedirs(nes_dir .. "/inc") 38 | add_files(nes_dir .. "/src/**.c") 39 | 40 | add_includedirs("port") 41 | add_files("port/*.c") 42 | 43 | add_files("main.c") 44 | 45 | end) 46 | -------------------------------------------------------------------------------- /sdl/sdl2/xmake.lua: -------------------------------------------------------------------------------- 1 | set_project("nes") 2 | set_xmakever("3.0.0") 3 | add_rules("mode.debug", "mode.release") 4 | 5 | if is_mode("debug") then 6 | set_symbols("debug") 7 | set_optimize("none") 8 | else 9 | set_strip("all") 10 | set_symbols("hidden") 11 | set_optimize("fastest") 12 | end 13 | 14 | set_warnings("allextra") 15 | set_languages("c11") 16 | 17 | if is_plat("wasm") then 18 | add_requires("emscripten") 19 | set_toolchains("emcc@emscripten") 20 | end 21 | 22 | -- TODO 23 | -- if is_host("windows") then 24 | -- elseif is_host("linux") then 25 | -- elseif is_host("macosx") then 26 | -- else 27 | -- end 28 | 29 | -- [[ add SDL2 ]] 30 | add_requires("libsdl2", {configs = {sdlmain = false}}) 31 | add_packages("libsdl2") 32 | 33 | target("nes", function () 34 | set_kind("binary") 35 | 36 | local nes_dir = "../.." 37 | add_includedirs(nes_dir .. "/inc") 38 | add_files(nes_dir .. "/src/**.c") 39 | 40 | add_includedirs("port") 41 | add_files("port/*.c") 42 | 43 | add_files("main.c") 44 | 45 | end) 46 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Windows 3 | run-name: Windows Build 4 | 5 | on: 6 | push: 7 | branches: [ "master" ] 8 | paths-ignore: 9 | - '.github/workflows/release.yml' 10 | - 'docs/**' 11 | - 'README.md' 12 | - 'README_zh.md' 13 | pull_request: 14 | branches: [ "master" ] 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | os: [windows-latest] 21 | arch: [x64] 22 | runs-on: ${{ matrix.os }} 23 | concurrency: 24 | group: ${{ matrix.os }}-${{ matrix.arch }} 25 | cancel-in-progress: true 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up MSVC 29 | uses: ilammy/msvc-dev-cmd@v1 30 | - name: prepare software 31 | uses: xmake-io/github-action-setup-xmake@v1 32 | with: 33 | xmake-version: latest 34 | - name: build sdl2-nes 35 | run: | 36 | xrepo update-repo 37 | cd ./sdl/sdl2 38 | xmake -v -y 39 | cp build/windows/${{ matrix.arch }}/release/nes.exe ../../nes-windows-${{ matrix.arch }}.exe 40 | cd ../../ 41 | - name: build sdl3-nes 42 | run: | 43 | xrepo update-repo 44 | cd ./sdl/sdl3 45 | xmake -v -y 46 | # cp build/windows/${{ matrix.arch }}/release/nes.exe ../../nes-windows-${{ matrix.arch }}.exe 47 | cd ../../ 48 | - uses: actions/upload-artifact@v4 49 | with: 50 | name: nes-windows-${{ matrix.arch }}.exe 51 | path: nes-windows-${{ matrix.arch }}.exe 52 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Macos 3 | run-name: Macos Build 4 | 5 | on: 6 | push: 7 | branches: [ "master" ] 8 | paths-ignore: 9 | - '.github/workflows/release.yml' 10 | - 'docs/**' 11 | - 'README.md' 12 | - 'README_zh.md' 13 | pull_request: 14 | branches: [ "master" ] 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | os: [macos-latest] 21 | arch: [arm64] 22 | runs-on: ${{ matrix.os }} 23 | concurrency: 24 | group: ${{ matrix.os }}-${{ matrix.arch }} 25 | cancel-in-progress: true 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: prepare software 29 | run: | 30 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 31 | brew update 32 | brew install sdl2 xmake 33 | xmake update 34 | - name: build sdl2-nes 35 | run: | 36 | xrepo update-repo 37 | cd ./sdl/sdl2 38 | xmake -v -y 39 | cp build/macosx/${{ matrix.arch }}/release/nes ../../nes-macos-${{ matrix.arch }}.AppImage 40 | cd ../../ 41 | - name: build sdl3-nes 42 | run: | 43 | xrepo update-repo 44 | cd ./sdl/sdl3 45 | xmake -v -y 46 | # cp build/macosx/${{ matrix.arch }}/release/nes ../../nes-macos-${{ matrix.arch }}.AppImage 47 | cd ../../ 48 | - uses: actions/upload-artifact@v4 49 | with: 50 | name: nes-macos-${{ matrix.arch }}.AppImage 51 | path: nes-macos-${{ matrix.arch }}.AppImage 52 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osprey nes", 3 | 4 | // Default path to open when attaching to a new container. 5 | "workspaceFolder": "/home/osprey/work", 6 | "image":"nes.osprey.io/nes-build-run", 7 | 8 | // Set *default* container specific settings.json values on container create. 9 | "customizations": { 10 | "vscode": { 11 | "settings": { 12 | "terminal.integrated.defaultProfile.linux": "bash", 13 | "cmake.automaticReconfigure": false, 14 | "cmake.configureOnEdit": false 15 | }, 16 | "extensions": [ 17 | // Put your favourite extensions here 18 | // "mhutchie.git-graph", 19 | // "eamodio.gitlens", 20 | // "ms-vscode.cmake-tools", 21 | // "ms-vscode.cpptools", 22 | // "ms-vscode.cpptools-extension-pack", 23 | ] 24 | } 25 | }, 26 | 27 | // An array port numbers to forward 28 | "forwardPorts": [], 29 | // "postCreateCommand": "echo 'hello container'", 30 | // Container user VS Code should use when connecting 31 | "remoteUser": "osprey", 32 | // Set environment variables for VS Code and sub-processes 33 | "containerEnv": { 34 | "MY_VARIABLE": "some-value", 35 | // "DISPLAY": ":0" 36 | }, 37 | 38 | "runArgs": [ 39 | "--network=host" 40 | ], 41 | "privileged": true, 42 | "mounts": [ 43 | // for audio device 44 | "source=/dev/snd,target=/dev/snd,type=bind,consistency=cached", 45 | // for video 46 | "source=/tmp/.X11-unix,target=/tmp/.X11-unix,type=bind,consistency=cached" 47 | ] 48 | } -------------------------------------------------------------------------------- /port/nes_conf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #define NES_ENABLE_SOUND (1) /* enable sound */ 23 | #define NES_USE_SRAM (0) /* use SRAM */ 24 | 25 | #define NES_FRAME_SKIP (0) /* skip frames */ 26 | /* Color depth: 27 | * - 16: RGB565 28 | * - 32: ARGB8888 29 | */ 30 | #define NES_COLOR_DEPTH (32) /* color depth */ 31 | #define NES_COLOR_SWAP (0) /* swap color channels */ 32 | #define NES_RAM_LACK (0) /* lack of RAM */ 33 | 34 | #define NES_USE_FS (1) /* use file system */ 35 | /* 36 | * - NES_LOG_LEVEL_NONE Do not log anything. 37 | * - NES_LOG_LEVEL_ERROR Log error. 38 | * - NES_LOG_LEVEL_WARN Log warning. 39 | * - NES_LOG_LEVEL_INFO Log infomation. 40 | * - NES_LOG_LEVEL_DEBUG Log debug. 41 | */ 42 | #define NES_LOG_LEVEL NES_LOG_LEVEL_INFO 43 | 44 | /* log */ 45 | #define nes_log_printf(format,...) printf(format, ##__VA_ARGS__) 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /sdl/sdl2/port/nes_conf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #define NES_ENABLE_SOUND (1) /* enable sound */ 23 | #define NES_USE_SRAM (0) /* use SRAM */ 24 | 25 | #define NES_FRAME_SKIP (0) /* skip frames */ 26 | /* Color depth: 27 | * - 16: RGB565 28 | * - 32: ARGB8888 29 | */ 30 | #define NES_COLOR_DEPTH (32) /* color depth */ 31 | #define NES_COLOR_SWAP (0) /* swap color channels */ 32 | #define NES_RAM_LACK (0) /* lack of RAM */ 33 | 34 | #define NES_USE_FS (1) /* use file system */ 35 | /* 36 | * - NES_LOG_LEVEL_NONE Do not log anything. 37 | * - NES_LOG_LEVEL_ERROR Log error. 38 | * - NES_LOG_LEVEL_WARN Log warning. 39 | * - NES_LOG_LEVEL_INFO Log infomation. 40 | * - NES_LOG_LEVEL_DEBUG Log debug. 41 | */ 42 | #define NES_LOG_LEVEL NES_LOG_LEVEL_INFO 43 | 44 | /* log */ 45 | #define nes_log_printf(format,...) printf(format, ##__VA_ARGS__) 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /sdl/sdl3/port/nes_conf.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #define NES_ENABLE_SOUND (1) /* enable sound */ 23 | #define NES_USE_SRAM (0) /* use SRAM */ 24 | 25 | #define NES_FRAME_SKIP (0) /* skip frames */ 26 | /* Color depth: 27 | * - 16: RGB565 28 | * - 32: ARGB8888 29 | */ 30 | #define NES_COLOR_DEPTH (32) /* color depth */ 31 | #define NES_COLOR_SWAP (0) /* swap color channels */ 32 | #define NES_RAM_LACK (0) /* lack of RAM */ 33 | 34 | #define NES_USE_FS (1) /* use file system */ 35 | /* 36 | * - NES_LOG_LEVEL_NONE Do not log anything. 37 | * - NES_LOG_LEVEL_ERROR Log error. 38 | * - NES_LOG_LEVEL_WARN Log warning. 39 | * - NES_LOG_LEVEL_INFO Log infomation. 40 | * - NES_LOG_LEVEL_DEBUG Log debug. 41 | */ 42 | #define NES_LOG_LEVEL NES_LOG_LEVEL_INFO 43 | 44 | /* log */ 45 | #define nes_log_printf(format,...) printf(format, ##__VA_ARGS__) 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | -------------------------------------------------------------------------------- /src/nes_mapper/nes_mapper0.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | #include "nes_mapper.h" 19 | 20 | /* https://www.nesdev.org/wiki/NROM */ 21 | 22 | static void nes_mapper_init(nes_t* nes){ 23 | // $6000-$7FFF: Family Basic only: PRG RAM, mirrored as necessary to fill entire 8 KiB window, write protectable with an external switch. 24 | 25 | // CPU $8000-$BFFF: First 16 KB of ROM. 26 | nes_load_prgrom_16k(nes, 0, 0); 27 | // CPU $C000-$FFFF: Last 16 KB of ROM (NROM-256) or mirror of $8000-$BFFF (NROM-128). 28 | nes_load_prgrom_16k(nes, 1, nes->nes_rom.prg_rom_size - 1); // PRG-ROM 16k or 32k, set mirror. 29 | // CHR capacity: 8 KiB ROM (DIP-28 standard pinout) but most emulators support RAM. 30 | nes_load_chrrom_8k(nes, 0, 0); 31 | } 32 | 33 | static void nes_mapper_write(nes_t* nes, uint16_t write_addr, uint8_t data){ 34 | (void)nes; 35 | (void)write_addr; 36 | (void)data; 37 | } 38 | 39 | int nes_mapper0_init(nes_t* nes){ 40 | nes->nes_mapper.mapper_init = nes_mapper_init; 41 | nes->nes_mapper.mapper_write = nes_mapper_write; 42 | return 0; 43 | } 44 | 45 | -------------------------------------------------------------------------------- /sdl/sdl2/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | 20 | int main(int argc, char** argv){ 21 | nes_t* nes = nes_init(); 22 | if (argc == 2){ 23 | const char* nes_file_path = argv[1]; 24 | size_t nes_file_path_len = strlen(nes_file_path); 25 | if (nes_memcmp(nes_file_path+nes_file_path_len-4,".nes",4)==0 || nes_memcmp(nes_file_path+nes_file_path_len-4,".NES",4)==0){ 26 | NES_LOG_INFO("nes_file_path:%s\n",nes_file_path); 27 | int ret = nes_load_file(nes, nes_file_path); 28 | if (ret){ 29 | NES_LOG_ERROR("nes load file fail\n"); 30 | goto error; 31 | } 32 | nes_run(nes); 33 | nes_unload_file(nes); 34 | nes_deinit(nes); 35 | return 0; 36 | }else{ 37 | NES_LOG_ERROR("Please enter xxx.nes\n"); 38 | goto error; 39 | } 40 | }else{ 41 | NES_LOG_ERROR("Please enter the nes file path\n"); 42 | goto error; 43 | } 44 | error: 45 | nes_deinit(nes); 46 | getchar(); 47 | return -1; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /sdl/sdl3/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | 20 | int main(int argc, char** argv){ 21 | nes_t* nes = nes_init(); 22 | if (argc == 2){ 23 | const char* nes_file_path = argv[1]; 24 | size_t nes_file_path_len = strlen(nes_file_path); 25 | if (nes_memcmp(nes_file_path+nes_file_path_len-4,".nes",4)==0 || nes_memcmp(nes_file_path+nes_file_path_len-4,".NES",4)==0){ 26 | NES_LOG_INFO("nes_file_path:%s\n",nes_file_path); 27 | int ret = nes_load_file(nes, nes_file_path); 28 | if (ret){ 29 | NES_LOG_ERROR("nes load file fail\n"); 30 | goto error; 31 | } 32 | nes_run(nes); 33 | nes_unload_file(nes); 34 | nes_deinit(nes); 35 | return 0; 36 | }else{ 37 | NES_LOG_ERROR("Please enter xxx.nes\n"); 38 | goto error; 39 | } 40 | }else{ 41 | NES_LOG_ERROR("Please enter the nes file path\n"); 42 | goto error; 43 | } 44 | error: 45 | nes_deinit(nes); 46 | getchar(); 47 | return -1; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/nes_mapper/nes_mapper180.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | /* https://www.nesdev.org/wiki/INES_Mapper_180 */ 20 | 21 | static void nes_mapper_init(nes_t* nes){ 22 | // CPU $8000-$BFFF: 16 KB PRG ROM bank, fixed to the first bank 23 | nes_load_prgrom_16k(nes, 0, 0); 24 | // CPU $C000-$FFFF: 16 KB switchable PRG ROM bank 25 | nes_load_prgrom_16k(nes, 1, 0); 26 | // CHR capacity: 8 KiB ROM. 27 | nes_load_chrrom_8k(nes, 0, 0); 28 | } 29 | 30 | /* 31 | 7 bit 0 32 | ---- ---- 33 | xxxx xPPP 34 | ||| 35 | +++-- Select 16 KB PRG ROM bank for CPU $C000-$FFFF 36 | */ 37 | typedef struct { 38 | uint8_t P:3; 39 | uint8_t :5; 40 | }bank_select_t; 41 | 42 | static void nes_mapper_write(nes_t* nes, uint16_t address, uint8_t date) { 43 | (void)address; 44 | const bank_select_t* bank_select = (bank_select_t*)&date; 45 | nes_load_prgrom_16k(nes, 1, bank_select->P); 46 | } 47 | 48 | 49 | 50 | int nes_mapper180_init(nes_t* nes){ 51 | nes->nes_mapper.mapper_init = nes_mapper_init; 52 | nes->nes_mapper.mapper_write = nes_mapper_write; 53 | return 0; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/linux.yml: -------------------------------------------------------------------------------- 1 | 2 | name: linux 3 | run-name: Linux Build 4 | 5 | on: 6 | push: 7 | branches: [ "master" ] 8 | paths-ignore: 9 | - '.github/workflows/release.yml' 10 | - 'docs/**' 11 | - 'README.md' 12 | - 'README_zh.md' 13 | pull_request: 14 | branches: [ "master" ] 15 | 16 | jobs: 17 | build: 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest] 21 | arch: [x86_64] 22 | runs-on: ${{ matrix.os }} 23 | concurrency: 24 | group: ${{ matrix.os }}-${{ matrix.arch }} 25 | cancel-in-progress: true 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: prepare software 29 | run: | 30 | sudo add-apt-repository ppa:xmake-io/xmake -y 31 | # sudo dpkg --add-architecture i386 32 | sudo apt-get update -y 33 | # sudo apt-get install -y lib32z1 libc6:i386 libgcc1:i386 libstdc++5:i386 libstdc++6:i386 34 | sudo apt-get install -y git make gcc p7zip-full libsdl2-dev xmake 35 | sudo apt-get upgrade -y 36 | xmake update 37 | - name: build sdl2-nes 38 | run: | 39 | xrepo update-repo 40 | cd ./sdl/sdl2 41 | xmake -v -y 42 | cp build/linux/${{ matrix.arch }}/release/nes ../../nes-linux-${{ matrix.arch }}.bin 43 | cd ../../ 44 | - name: build sdl3-nes 45 | run: | 46 | xrepo update-repo 47 | cd ./sdl/sdl3 48 | xmake -v -y 49 | # cp build/linux/${{ matrix.arch }}/release/nes ../../nes-linux-${{ matrix.arch }}.bin 50 | cd ../../ 51 | 52 | - uses: actions/upload-artifact@v4 53 | with: 54 | name: nes-linux-${{ matrix.arch }}.bin 55 | path: nes-linux-${{ matrix.arch }}.bin 56 | -------------------------------------------------------------------------------- /src/nes_mapper/nes_mapper94.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | /* https://www.nesdev.org/wiki/INES_Mapper_094 */ 20 | 21 | static void nes_mapper_init(nes_t* nes){ 22 | // CPU $8000-$BFFF: 16 KB switchable PRG ROM bank. 23 | nes_load_prgrom_16k(nes, 0, 0); 24 | // CPU $C000-$FFFF: 16 KB PRG ROM bank, fixed to the last bank. 25 | nes_load_prgrom_16k(nes, 1, nes->nes_rom.prg_rom_size - 1); 26 | // CHR capacity: 8 KiB ROM. 27 | nes_load_chrrom_8k(nes, 0, 0); 28 | } 29 | 30 | /* 31 | 7 bit 0 32 | ---- ---- 33 | xxxP PPxx 34 | | || 35 | +-++--- Select 16 KB PRG ROM bank for CPU $8000-$BFFF 36 | */ 37 | typedef struct { 38 | uint8_t :2; 39 | uint8_t P:3; 40 | uint8_t :3; 41 | }bank_select_t; 42 | 43 | static void nes_mapper_write(nes_t* nes, uint16_t address, uint8_t date) { 44 | (void)address; 45 | const bank_select_t* bank_select = (bank_select_t*)&date; 46 | nes_load_prgrom_16k(nes, 0, bank_select->P); 47 | } 48 | 49 | int nes_mapper94_init(nes_t* nes){ 50 | nes->nes_mapper.mapper_init = nes_mapper_init; 51 | nes->nes_mapper.mapper_write = nes_mapper_write; 52 | return 0; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/nes_mapper/nes_mapper2.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | /* https://www.nesdev.org/wiki/UxROM */ 20 | 21 | static void nes_mapper_init(nes_t* nes){ 22 | // CPU $8000-$BFFF: 16 KB switchable PRG ROM bank. 23 | nes_load_prgrom_16k(nes, 0, 0); 24 | // CPU $C000-$FFFF: 16 KB PRG ROM bank, fixed to the last bank. 25 | nes_load_prgrom_16k(nes, 1, nes->nes_rom.prg_rom_size - 1); 26 | // CHR capacity: 8 KiB ROM. 27 | nes_load_chrrom_8k(nes, 0, 0); 28 | } 29 | 30 | /* 31 | 7 bit 0 32 | ---- ---- 33 | xxxx pPPP 34 | |||| 35 | ++++- Select 16 KB PRG ROM bank for CPU $8000-$BFFF 36 | (UNROM uses bits 2-0; UOROM uses bits 3-0) 37 | */ 38 | typedef struct { 39 | uint8_t P:4; 40 | uint8_t :4; 41 | }bank_select_t; 42 | 43 | static void nes_mapper_write(nes_t* nes, uint16_t address, uint8_t date) { 44 | (void)address; 45 | const bank_select_t* bank_select = (bank_select_t*)&date; 46 | nes_load_prgrom_16k(nes, 0, bank_select->P); 47 | } 48 | 49 | 50 | 51 | int nes_mapper2_init(nes_t* nes){ 52 | nes->nes_mapper.mapper_init = nes_mapper_init; 53 | nes->nes_mapper.mapper_write = nes_mapper_write; 54 | return 0; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/nes_mapper/nes_mapper7.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | /* https://www.nesdev.org/wiki/AxROM */ 20 | static void nes_mapper_init(nes_t* nes){ 21 | // CPU $8000-$FFFF: 32 KB switchable PRG ROM bank 22 | nes_load_prgrom_32k(nes, 0, 0); 23 | // CHR capacity: 8 KiB ROM. 24 | nes_load_chrrom_8k(nes, 0, 0); 25 | } 26 | 27 | /* 28 | Bank select ($8000-$FFFF) 29 | 7 bit 0 30 | ---- ---- 31 | xxxM xPPP 32 | | ||| 33 | | +++- Select 32 KB PRG ROM bank for CPU $8000-$FFFF 34 | +------ Select 1 KB VRAM page for all 4 nametables 35 | */ 36 | typedef struct { 37 | uint8_t P:3; 38 | uint8_t :1; 39 | uint8_t M:1; 40 | uint8_t :3; 41 | }bank_select_t; 42 | 43 | static void nes_mapper_write(nes_t* nes, uint16_t address, uint8_t date) { 44 | (void)address; 45 | const bank_select_t* bank_select = (bank_select_t*)&date; 46 | nes_load_prgrom_32k(nes, 0, bank_select->P); 47 | nes_ppu_screen_mirrors(nes, bank_select->M?NES_MIRROR_ONE_SCREEN1:NES_MIRROR_ONE_SCREEN0); 48 | } 49 | 50 | int nes_mapper7_init(nes_t* nes){ 51 | nes->nes_mapper.mapper_init = nes_mapper_init; 52 | nes->nes_mapper.mapper_write = nes_mapper_write; 53 | return 0; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /src/nes_mapper/nes_mapper3.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | /* https://www.nesdev.org/wiki/INES_Mapper_003 */ 20 | static void nes_mapper_init(nes_t* nes){ 21 | // CPU $8000-$BFFF: First 16 KB of ROM. 22 | nes_load_prgrom_16k(nes, 0, 0); 23 | // CPU $C000-$FFFF: Last 16 KB of ROM or mirror of $8000-$BFFF. 24 | nes_load_prgrom_16k(nes, 1, nes->nes_rom.prg_rom_size - 1); // PRG ROM size: 16 KiB or 32 KiB, set mirror. 25 | // CHR capacity: 8 KiB ROM. 26 | nes_load_chrrom_8k(nes, 0, 0); 27 | } 28 | 29 | /* 30 | PPU $0000-$1FFF: 8 KB switchable CHR ROM bank 31 | 7 bit 0 32 | ---- ---- 33 | cccc ccCC 34 | |||| |||| 35 | ++++-++++- Select 8 KB CHR ROM bank for PPU $0000-$1FFF 36 | CNROM only implements the lowest 2 bits, capping it at 32 KiB CHR. Other boards may implement 4 or more bits for larger CHR. 37 | */ 38 | static void nes_mapper_write(nes_t* nes, uint16_t address, uint8_t date) { 39 | (void)address; 40 | const uint8_t bank = (date % nes->nes_rom.chr_rom_size); 41 | nes_load_chrrom_8k(nes, 0, bank); 42 | } 43 | 44 | int nes_mapper3_init(nes_t* nes){ 45 | nes->nes_mapper.mapper_init = nes_mapper_init; 46 | nes->nes_mapper.mapper_write = nes_mapper_write; 47 | return 0; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/nes_mapper/nes_mapper117.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | /* https://www.nesdev.org/wiki/INES_Mapper_177 */ 20 | static void nes_mapper_init(nes_t* nes){ 21 | // CPU $8000-$FFFF: 32 KB switchable PRG ROM bank 22 | nes_load_prgrom_32k(nes, 0, 0); 23 | // CHR capacity: 8 KiB ROM. 24 | nes_load_chrrom_8k(nes, 0, 0); 25 | } 26 | 27 | /* 28 | 7 bit 0 29 | --------- 30 | $8000-FFFF: ..MP PPPP 31 | |+-++++- Select 32 KiB PRG-ROM bank at CPU $8000-$FFFF 32 | +------- Select nametable mirroring 33 | 0: Vertical 34 | 1: Horizontal 35 | */ 36 | typedef struct { 37 | uint8_t P:5; 38 | uint8_t M:1; 39 | uint8_t :2; 40 | }bank_select_t; 41 | 42 | static void nes_mapper_write(nes_t* nes, uint16_t address, uint8_t date) { 43 | (void)address; 44 | const bank_select_t* bank_select = (bank_select_t*)&date; 45 | nes_load_prgrom_32k(nes, 0, bank_select->P); 46 | nes_ppu_screen_mirrors(nes, bank_select->M?NES_MIRROR_HORIZONTAL:NES_MIRROR_VERTICAL); 47 | } 48 | 49 | int nes_mapper117_init(nes_t* nes){ 50 | nes->nes_mapper.mapper_init = nes_mapper_init; 51 | nes->nes_mapper.mapper_write = nes_mapper_write; 52 | return 0; 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/nes_default.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes_default.h" 18 | 19 | #ifndef _MSC_VER 20 | #include 21 | 22 | 23 | /* memory */ 24 | NES_WEAK void *nes_malloc(int num){ 25 | return malloc(num); 26 | } 27 | 28 | NES_WEAK void nes_free(void *address){ 29 | free(address); 30 | } 31 | 32 | NES_WEAK void *nes_memcpy(void *str1, const void *str2, size_t n){ 33 | return memcpy(str1, str2, n); 34 | } 35 | 36 | NES_WEAK void *nes_memset(void *str, int c, size_t n){ 37 | return memset(str,c,n); 38 | } 39 | 40 | NES_WEAK int nes_memcmp(const void *str1, const void *str2, size_t n){ 41 | return memcmp(str1,str2,n); 42 | } 43 | 44 | #if (NES_USE_FS == 1) 45 | /* io */ 46 | NES_WEAK FILE *nes_fopen(const char * filename, const char * mode ){ 47 | return fopen(filename,mode); 48 | } 49 | 50 | NES_WEAK size_t nes_fread(void *ptr, size_t size, size_t nmemb, FILE *stream){ 51 | return fread(ptr, size, nmemb,stream); 52 | } 53 | 54 | NES_WEAK size_t nes_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream){ 55 | return fwrite(ptr, size, nmemb,stream); 56 | } 57 | 58 | NES_WEAK int nes_fseek(FILE *stream, long int offset, int whence){ 59 | return fseek(stream,offset,whence); 60 | } 61 | 62 | NES_WEAK int nes_fclose(FILE *stream ){ 63 | return fclose(stream); 64 | } 65 | #endif 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /inc/nes_log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #include "nes_default.h" 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | /* 25 | * The color for terminal (foreground) 26 | * BLACK 30 27 | * RED 31 28 | * GREEN 32 29 | * YELLOW 33 30 | * BLUE 34 31 | * PURPLE 35 32 | * CYAN 36 33 | * WHITE 37 34 | */ 35 | 36 | #define NES_LOG_LEVEL_NONE 0 /* Do not log anything. */ 37 | #define NES_LOG_LEVEL_ERROR 1 /* Log error. */ 38 | #define NES_LOG_LEVEL_WARN 2 /* Log warning. */ 39 | #define NES_LOG_LEVEL_INFO 3 /* Log infomation. */ 40 | #define NES_LOG_LEVEL_DEBUG 4 /* Log debug. */ 41 | 42 | #ifndef NES_LOG_LEVEL 43 | # define NES_LOG_LEVEL NES_LOG_LEVEL_INFO 44 | #endif 45 | 46 | # if NES_LOG_LEVEL >= NES_LOG_LEVEL_ERROR 47 | # define NES_LOG_ERROR(format, ...) nes_log_printf("\033[31m[ERROR][%s:%d(%s)]: \033[0m" format, __FILE__, __LINE__, __func__, ##__VA_ARGS__) 48 | # else 49 | # define NES_LOG_ERROR(format, ...) do {}while(0) 50 | # endif 51 | 52 | # if NES_LOG_LEVEL >= NES_LOG_LEVEL_WARN 53 | # define NES_LOG_WARN(format, ...) nes_log_printf("\033[33m[WARN][%s:%d(%s)]: \033[0m" format, __FILE__, __LINE__, __func__, ##__VA_ARGS__) 54 | # else 55 | # define NES_LOG_WARN(format, ...) do {}while(0) 56 | # endif 57 | 58 | # if NES_LOG_LEVEL >= NES_LOG_LEVEL_INFO 59 | # define NES_LOG_INFO(format, ...) nes_log_printf("\033[32m[INFO][%s:%d(%s)]: \033[0m" format, __FILE__, __LINE__, __func__, ##__VA_ARGS__) 60 | # else 61 | # define NES_LOG_INFO(format, ...) do {}while(0) 62 | # endif 63 | 64 | # if NES_LOG_LEVEL >= NES_LOG_LEVEL_DEBUG 65 | # define NES_LOG_DEBUG(format, ...) nes_log_printf("\033[0m[DEBUG][%s:%d(%s)]: \033[0m" format, __FILE__, __LINE__, __func__, ##__VA_ARGS__) 66 | # else 67 | # define NES_LOG_DEBUG(format, ...) do {}while(0) 68 | # endif 69 | 70 | #ifdef __cplusplus 71 | } 72 | #endif 73 | -------------------------------------------------------------------------------- /.devcontainer/nes-dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 as builder 2 | 3 | MAINTAINER Osprey 4 | 5 | #docker build -f nes-dockerfile -t nes.osprey.io/nes-build-run . 6 | 7 | # docker start nes-build 8 | # docker attach nes-build 9 | # docker exec -it nes-build /bin/bash 10 | 11 | 12 | # 创建用户 cmd:id 13 | ARG USERNAME=osprey 14 | ARG USER_UID=1000 15 | ARG USER_GID=$USER_UID 16 | 17 | RUN groupadd --gid $USER_GID $USERNAME \ 18 | && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ 19 | && apt-get update \ 20 | && apt-get install -y sudo \ 21 | && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ 22 | && chmod 0440 /etc/sudoers.d/$USERNAME 23 | 24 | RUN apt-get update 25 | # for cmd: add-apt-repository 26 | RUN apt -y install software-properties-common dirmngr apt-transport-https lsb-release ca-certificates 27 | RUN add-apt-repository ppa:xmake-io/xmake -y && apt-get update && apt-get install -y xmake 28 | RUN apt-get install -y p7zip-full libsdl2-dev 29 | 30 | RUN apt-get install -y git make gcc 31 | # option 32 | RUN apt-get install -y nano iputils-ping 33 | # setting the password of root to 123456 34 | RUN echo 'root:123456' | chpasswd 35 | 36 | USER $USERNAME 37 | ENV HOME=/home/$USERNAME 38 | 39 | RUN mkdir /home/$USERNAME/work 40 | 41 | WORKDIR /home/$USERNAME/work 42 | 43 | RUN git clone https://gitee.com/PeakRacing/nes && cd nes && xmake 44 | 45 | 46 | ########################################### 47 | FROM ubuntu:18.04 as prod 48 | 49 | # 50 | ENV TZ=Asia/Shanghai 51 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 52 | 53 | # 创建用户 cmd:id 54 | ARG USERNAME=osprey 55 | ARG USER_UID=1000 56 | ARG USER_GID=$USER_UID 57 | 58 | RUN groupadd --gid $USER_GID $USERNAME \ 59 | && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \ 60 | && apt-get update \ 61 | && apt-get install -y sudo \ 62 | && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME \ 63 | && chmod 0440 /etc/sudoers.d/$USERNAME 64 | 65 | RUN apt-get update 66 | RUN apt-get install -y p7zip-full libsdl2-dev 67 | 68 | USER $USERNAME 69 | ENV HOME=/home/$USERNAME 70 | 71 | RUN mkdir /home/$USERNAME/work 72 | WORKDIR /home/$USERNAME/work 73 | RUN echo "PS1='\[\e[0;33m\]\u@\h\[\e[0m\]:\[\e[0;34m\]\w\[\e[0m\]\$ '" >> /home/$USERNAME/.bashrc 74 | 75 | COPY --from=0 /home/$USERNAME/work/nes/build/linux/x86_64/release/nes . 76 | COPY contra.nes . 77 | COPY SuperMarioBrosWorld.nes . 78 | #CMD ["~/work/nes contra.nes"] 79 | 80 | # run cmd: 81 | # docker run --name=nes-test --rm -it -v /tmp/.X11-unix:/tmp/.X11-unix -v /dev/snd:/dev/snd --privileged -e DISPLAY=unix$DISPLAY nes.osprey.io/nes-build-run /bin/bash -c "./nes contra.nes" 82 | 83 | -------------------------------------------------------------------------------- /inc/nes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #include "nes_default.h" 19 | #include "nes_log.h" 20 | #include "nes_rom.h" 21 | #include "nes_cpu.h" 22 | #include "nes_ppu.h" 23 | #include "nes_apu.h" 24 | #include "nes_mapper.h" 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | #define NES_VERSION_MAJOR 0 31 | #define NES_VERSION_MINOR 0 32 | #define NES_VERSION_PATCH 1 33 | 34 | #define STRINGIFY_HELPER(x) #x 35 | #define NES_VERSION_STRING STRINGIFY_HELPER(NES_VERSION_MAJOR) "." STRINGIFY_HELPER(NES_VERSION_MINOR) "." STRINGIFY_HELPER(NES_VERSION_PATCH) 36 | 37 | #define NES_NAME "NES" 38 | 39 | #define NES_URL "https://github.com/PeakRacing/nes" 40 | 41 | #define NES_WIDTH 256 42 | #define NES_HEIGHT 240 43 | 44 | // https://www.nesdev.org/wiki/Cycle_reference_chart 45 | #define NES_CPU_CLOCK_FREQ (1789773) // 21.47~ MHz ÷ 12 = 1.789773 MHz 46 | 47 | // https://www.nesdev.org/w/images/default/4/4f/Ppu.svg 48 | #define NES_PPU_CPU_CLOCKS (113) // 341 × 4 ÷ 12 = 113 2⁄3 49 | 50 | #define NES_OK (0) 51 | #define NES_ERROR (-1) 52 | 53 | typedef struct nes{ 54 | uint8_t nes_quit; 55 | #if (NES_FRAME_SKIP != 0) 56 | uint8_t nes_frame_skip_count; 57 | #endif 58 | uint16_t scanline; 59 | nes_rom_info_t nes_rom; 60 | nes_cpu_t nes_cpu; 61 | nes_ppu_t nes_ppu; 62 | #if (NES_ENABLE_SOUND==1) 63 | nes_apu_t nes_apu; 64 | #endif 65 | nes_mapper_t nes_mapper; 66 | nes_color_t nes_draw_data[NES_DRAW_SIZE]; 67 | } nes_t; 68 | 69 | 70 | nes_t* nes_init(void); 71 | int nes_deinit(nes_t *nes); 72 | 73 | void nes_run(nes_t* nes); 74 | 75 | #if (NES_USE_FS == 1) 76 | int nes_load_file(nes_t* nes, const char* file_path); 77 | int nes_unload_file(nes_t* nes); 78 | #endif 79 | 80 | int nes_load_rom(nes_t* nes, const uint8_t* nes_rom); 81 | int nes_unload_rom(nes_t* nes); 82 | 83 | int nes_initex(nes_t* nes); 84 | int nes_deinitex(nes_t* nes); 85 | void nes_frame(nes_t* nes); 86 | 87 | #ifdef __cplusplus 88 | } 89 | #endif 90 | 91 | -------------------------------------------------------------------------------- /port/nes_port.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "nes.h" 17 | 18 | /* memory */ 19 | void *nes_malloc(int num){ 20 | return malloc(num); 21 | } 22 | 23 | void nes_free(void *address){ 24 | free(address); 25 | } 26 | 27 | void *nes_memcpy(void *str1, const void *str2, size_t n){ 28 | return memcpy(str1, str2, n); 29 | } 30 | 31 | void *nes_memset(void *str, int c, size_t n){ 32 | return memset(str,c,n); 33 | } 34 | 35 | int nes_memcmp(const void *str1, const void *str2, size_t n){ 36 | return memcmp(str1,str2,n); 37 | } 38 | 39 | #if (NES_USE_FS == 1) 40 | /* io */ 41 | FILE *nes_fopen(const char * filename, const char * mode ){ 42 | return fopen(filename,mode); 43 | } 44 | 45 | size_t nes_fread(void *ptr, size_t size, size_t nmemb, FILE *stream){ 46 | return fread(ptr, size, nmemb,stream); 47 | } 48 | 49 | size_t nes_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream){ 50 | return fwrite(ptr, size, nmemb,stream); 51 | } 52 | 53 | int nes_fseek(FILE *stream, long int offset, int whence){ 54 | return fseek(stream,offset,whence); 55 | } 56 | 57 | int nes_fclose(FILE *stream ){ 58 | return fclose(stream); 59 | } 60 | #endif 61 | 62 | int nes_log_printf(const char *format, ...){ 63 | va_list args; 64 | va_start(args, format); 65 | vprintf(format, args); 66 | va_end(args); 67 | 68 | size_t len; 69 | va_list args; 70 | if (luat_log_level_cur > level) return; 71 | char log_printf_buff[LOGLOG_SIZE] = {0}; 72 | va_start(args, format); 73 | len = vsnprintf_(log_printf_buff, LOGLOG_SIZE - 2, format, args); 74 | va_end(args); 75 | if (len > 0) { 76 | log_printf_buff[len] = '\n'; 77 | luat_log_write(log_printf_buff, len + 1); 78 | } 79 | 80 | } 81 | 82 | #if (NES_ENABLE_SOUND == 1) 83 | 84 | int nes_sound_output(uint8_t *buffer, size_t len){ 85 | return 0; 86 | } 87 | #endif 88 | 89 | int nes_initex(nes_t *nes){ 90 | return 0; 91 | } 92 | 93 | int nes_deinitex(nes_t *nes){ 94 | return 0; 95 | } 96 | 97 | int nes_draw(int x1, int y1, int x2, int y2, nes_color_t* color_data){ 98 | return 0; 99 | } 100 | 101 | #define FRAMES_PER_SECOND 1000/60 102 | 103 | void nes_frame(nes_t* nes){ 104 | } 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/nes_mapper.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | /* load 8k PRG-ROM */ 20 | void nes_load_prgrom_8k(nes_t* nes,uint8_t des, uint16_t src) { 21 | nes->nes_cpu.prg_banks[des] = nes->nes_rom.prg_rom + 8 * 1024 * src; 22 | } 23 | 24 | /* load 16k PRG-ROM */ 25 | void nes_load_prgrom_16k(nes_t* nes,uint8_t des, uint16_t src) { 26 | nes->nes_cpu.prg_banks[des * 2] = nes->nes_rom.prg_rom + 8 * 1024 * src * 2; 27 | nes->nes_cpu.prg_banks[des * 2 + 1] = nes->nes_rom.prg_rom + 8 * 1024 * (src * 2 + 1); 28 | } 29 | 30 | /* load 32k PRG-ROM */ 31 | void nes_load_prgrom_32k(nes_t* nes,uint8_t des, uint16_t src) { 32 | (void)des; 33 | nes->nes_cpu.prg_banks[0] = nes->nes_rom.prg_rom + 8 * 1024 * src * 4; 34 | nes->nes_cpu.prg_banks[1] = nes->nes_rom.prg_rom + 8 * 1024 * (src * 4 + 1); 35 | nes->nes_cpu.prg_banks[2] = nes->nes_rom.prg_rom + 8 * 1024 * (src * 4 + 2); 36 | nes->nes_cpu.prg_banks[3] = nes->nes_rom.prg_rom + 8 * 1024 * (src * 4 + 3); 37 | } 38 | 39 | /* load 1k CHR-ROM */ 40 | void nes_load_chrrom_1k(nes_t* nes,uint8_t des, uint8_t src) { 41 | nes->nes_ppu.pattern_table[des] = nes->nes_rom.chr_rom + 1024 * src; 42 | } 43 | 44 | /* load 4k CHR-ROM */ 45 | void nes_load_chrrom_4k(nes_t* nes,uint8_t des, uint8_t src) { 46 | for (size_t i = 0; i < 4; i++){ 47 | nes->nes_ppu.pattern_table[des * 4 + i] = nes->nes_rom.chr_rom + 1024 * (src * 4 + i); 48 | } 49 | } 50 | 51 | /* load 8k CHR-ROM */ 52 | void nes_load_chrrom_8k(nes_t* nes,uint8_t des, uint8_t src) { 53 | for (size_t i = 0; i < 8; i++){ 54 | nes->nes_ppu.pattern_table[des + i] = nes->nes_rom.chr_rom + 1024 * (src * 8 + i); 55 | } 56 | } 57 | 58 | #define NES_CASE_LOAD_MAPPER(mapper_id) case mapper_id: return nes_mapper##mapper_id##_init(nes) 59 | 60 | int nes_load_mapper(nes_t* nes){ 61 | switch (nes->nes_rom.mapper_number){ 62 | NES_CASE_LOAD_MAPPER(0); 63 | NES_CASE_LOAD_MAPPER(1); 64 | NES_CASE_LOAD_MAPPER(2); 65 | NES_CASE_LOAD_MAPPER(3); 66 | // NES_CASE_LOAD_MAPPER(4); 67 | NES_CASE_LOAD_MAPPER(7); 68 | NES_CASE_LOAD_MAPPER(94); 69 | NES_CASE_LOAD_MAPPER(117); 70 | NES_CASE_LOAD_MAPPER(180); 71 | default : 72 | NES_LOG_ERROR("mapper:%03d is unsupported\n",nes->nes_rom.mapper_number); 73 | return NES_ERROR; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /inc/nes_default.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "nes_conf.h" 23 | 24 | #ifdef __cplusplus 25 | extern "C" { 26 | #endif 27 | 28 | #if defined(__ARMCC_VERSION) 29 | #define NES_WEAK __attribute__((weak)) 30 | #elif defined(__IAR_SYSTEMS_ICC__) 31 | #define NES_WEAK __weak 32 | #elif defined(__GNUC__) 33 | #define NES_WEAK __attribute__((weak)) 34 | #elif defined(_MSC_VER) 35 | #define NES_WEAK 36 | #else 37 | #define NES_WEAK __attribute__((weak)) 38 | #endif 39 | 40 | #ifndef NES_ENABLE_SOUND 41 | #define NES_ENABLE_SOUND (0) 42 | #endif 43 | 44 | #ifndef NES_USE_FS 45 | #define NES_USE_FS (0) 46 | #endif 47 | 48 | #ifndef NES_FRAME_SKIP 49 | #define NES_FRAME_SKIP (0) 50 | #endif 51 | 52 | #ifndef NES_RAM_LACK 53 | #define NES_RAM_LACK (0) 54 | #endif 55 | 56 | #if (NES_RAM_LACK == 1) 57 | #define NES_DRAW_SIZE (NES_WIDTH * NES_HEIGHT / 2) 58 | #else 59 | #define NES_DRAW_SIZE (NES_WIDTH * NES_HEIGHT) 60 | #endif 61 | 62 | #ifndef NES_COLOR_SWAP 63 | #define NES_COLOR_SWAP (0) 64 | #endif 65 | 66 | /* Color depth: 67 | * - 16: RGB565 68 | * - 32: ARGB8888 69 | */ 70 | #ifndef NES_COLOR_DEPTH 71 | #define NES_COLOR_DEPTH (32) 72 | #endif 73 | 74 | #if (NES_COLOR_DEPTH == 32) 75 | #define nes_color_t uint32_t 76 | #elif (NES_COLOR_DEPTH == 16) 77 | #define nes_color_t uint16_t 78 | #else 79 | #error "no supprt color depth" 80 | #endif 81 | 82 | /* memory */ 83 | void *nes_malloc(int num); 84 | void nes_free(void *address); 85 | void *nes_memcpy(void *str1, const void *str2, size_t n); 86 | void *nes_memset(void *str, int c, size_t n); 87 | int nes_memcmp(const void *str1, const void *str2, size_t n); 88 | 89 | #if (NES_USE_FS == 1) 90 | 91 | /* io */ 92 | FILE *nes_fopen(const char * filename, const char * mode ); 93 | size_t nes_fread(void *ptr, size_t size, size_t nmemb, FILE *stream); 94 | size_t nes_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 95 | int nes_fseek(FILE *stream, long int offset, int whence); 96 | int nes_fclose(FILE *stream ); 97 | 98 | #endif 99 | 100 | int nes_draw(int x1, int y1, int x2, int y2, nes_color_t* color_data); 101 | int nes_sound_output(uint8_t *buffer, size_t len); 102 | 103 | #ifdef __cplusplus 104 | } 105 | #endif 106 | -------------------------------------------------------------------------------- /inc/nes_cpu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | 23 | // https://www.nesdev.org/wiki/CPU_memory_map 24 | #define NES_CPU_RAM_SIZE 0x800 /* 2KB */ 25 | 26 | #define NES_VERCTOR_NMI 0xFFFA /* NMI vector (NMI=not maskable interupts) */ 27 | #define NES_VERCTOR_RESET 0xFFFC /* Reset vector */ 28 | #define NES_VERCTOR_IRQBRK 0xFFFE /* IRQ vector */ 29 | 30 | struct nes; 31 | typedef struct nes nes_t; 32 | 33 | /* 34 | Bit No. 15 14 13 12 11 10 9 8 35 | A1 B1 Select1 Start1 Up1 Down1 Left1 Right1 36 | Bit No. 7 6 5 4 3 2 1 0 37 | A2 B2 Select2 Start2 Up2 Down2 Left2 Right2 38 | */ 39 | typedef struct nes_joypad{ 40 | uint8_t offset1; 41 | uint8_t offset2; 42 | uint8_t mask; 43 | union { 44 | struct { 45 | uint8_t R2:1; 46 | uint8_t L2:1; 47 | uint8_t D2:1; 48 | uint8_t U2:1; 49 | uint8_t ST2:1; 50 | uint8_t SE2:1; 51 | uint8_t B2:1; 52 | uint8_t A2:1; 53 | uint8_t R1:1; 54 | uint8_t L1:1; 55 | uint8_t D1:1; 56 | uint8_t U1:1; 57 | uint8_t ST1:1; 58 | uint8_t SE1:1; 59 | uint8_t B1:1; 60 | uint8_t A1:1; 61 | }; 62 | uint16_t joypad; 63 | }; 64 | } nes_joypad_t; 65 | 66 | // https://www.nesdev.org/wiki/CPU_registers 67 | typedef struct nes_cpu{ 68 | /* CPU registers */ 69 | uint8_t A; /* Accumulator */ 70 | uint8_t X; /* Indexes X */ 71 | uint8_t Y; /* Indexes Y */ 72 | uint8_t SP; /* Stack Pointer */ 73 | uint16_t PC; /* Program Counter */ 74 | union { 75 | struct { 76 | uint8_t C:1; /* carry flag (1 on unsigned overflow) */ 77 | uint8_t Z:1; /* zero flag (1 when all bits of a result are 0) */ 78 | uint8_t I:1; /* IRQ flag (when 1, no interupts will occur (exceptions are IRQs forced by BRK and NMIs)) */ 79 | uint8_t D:1; /* decimal flag (1 when CPU in BCD mode) */ 80 | uint8_t B:1; /* break flag (1 when interupt was caused by a BRK) */ 81 | uint8_t U:1; /* unused (always 1) */ 82 | uint8_t V:1; /* overflow flag (1 on signed overflow) */ 83 | uint8_t N:1; /* negative flag (1 when result is negative) */ 84 | }; 85 | uint8_t P; /* Status Register */ 86 | }; 87 | uint8_t irq_counter; 88 | uint8_t irq_nmi; 89 | uint8_t opcode; 90 | uint16_t cycles; 91 | uint8_t cpu_ram[NES_CPU_RAM_SIZE]; 92 | uint8_t* prg_banks[4]; /* 4 bank ( 8Kb * 4 ) = 32KB */ 93 | nes_joypad_t joypad; 94 | } nes_cpu_t; 95 | 96 | void nes_cpu_init(nes_t *nes); 97 | void nes_cpu_reset(nes_t* nes); 98 | 99 | void nes_opcode(nes_t* nes,uint16_t ticks); 100 | 101 | #ifdef __cplusplus 102 | } 103 | #endif 104 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+" 7 | 8 | jobs: 9 | release: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | 16 | - name: Create Release 17 | id: create_release 18 | uses: softprops/action-gh-release@v2 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | tag_name: ${{ github.ref }} 23 | name: Release ${{ github.ref }} 24 | body: | 25 | [CHANGELOG](https://github.com/PeakRacing/nes/blob/master/docs/CHANGELOG.md#Changelog) 26 | [更新日志](https://github.com/PeakRacing/nes/blob/master/docs/CHANGELOG.md#更新日志) 27 | draft: false 28 | prerelease: false 29 | 30 | macos: 31 | needs: release 32 | strategy: 33 | matrix: 34 | os: [macos-latest] 35 | arch: [arm64] 36 | runs-on: ${{ matrix.os }} 37 | concurrency: 38 | group: ${{ matrix.os }}-${{ matrix.arch }} 39 | cancel-in-progress: true 40 | steps: 41 | - uses: actions/checkout@v4 42 | - name: prepare software 43 | run: | 44 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 45 | brew update 46 | brew install make sdl2 xmake 47 | xmake update 48 | - name: build 49 | run: | 50 | xrepo update-repo 51 | cd ./sdl/sdl2 52 | xmake -v -y 53 | cp build/macosx/${{ matrix.arch }}/release/nes ../../nes-macos-${{ matrix.arch }}.AppImage 54 | cd ../../ 55 | 56 | - name: Publish file to release 57 | uses: svenstaro/upload-release-action@v2 58 | env: 59 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 60 | with: 61 | repo_token: ${{ secrets.GITHUB_TOKEN }} 62 | file_glob: true 63 | file: nes-macos-${{ matrix.arch }}.AppImage 64 | tag: ${{ github.ref }} 65 | overwrite: true 66 | 67 | linux: 68 | needs: release 69 | strategy: 70 | matrix: 71 | os: [ubuntu-latest] 72 | arch: [x86_64] 73 | runs-on: ${{ matrix.os }} 74 | concurrency: 75 | group: ${{ matrix.os }}-${{ matrix.arch }} 76 | cancel-in-progress: true 77 | steps: 78 | - uses: actions/checkout@v4 79 | - name: prepare software 80 | run: | 81 | sudo add-apt-repository ppa:xmake-io/xmake -y 82 | # sudo dpkg --add-architecture i386 83 | sudo apt-get update -y 84 | # sudo apt-get install -y lib32z1 libc6:i386 libgcc1:i386 libstdc++5:i386 libstdc++6:i386 85 | sudo apt-get install -y git make gcc p7zip-full libsdl2-dev xmake 86 | sudo apt-get upgrade -y 87 | xmake update 88 | - name: build 89 | run: | 90 | xrepo update-repo 91 | cd ./sdl/sdl2 92 | xmake -v -y 93 | cp build/linux/${{ matrix.arch }}/release/nes ../../nes-linux-${{ matrix.arch }}.bin 94 | cd ../../ 95 | 96 | - name: Publish file to release 97 | uses: svenstaro/upload-release-action@v2 98 | env: 99 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 100 | with: 101 | repo_token: ${{ secrets.GITHUB_TOKEN }} 102 | file_glob: true 103 | file: nes-linux-${{ matrix.arch }}.bin 104 | tag: ${{ github.ref }} 105 | overwrite: true 106 | 107 | windows: 108 | needs: release 109 | strategy: 110 | matrix: 111 | os: [windows-latest] 112 | arch: [x64] 113 | runs-on: ${{ matrix.os }} 114 | concurrency: 115 | group: ${{ matrix.os }}-${{ matrix.arch }} 116 | cancel-in-progress: true 117 | steps: 118 | - uses: actions/checkout@v4 119 | - name: Set up MSVC 120 | uses: ilammy/msvc-dev-cmd@v1 121 | - name: prepare software 122 | uses: xmake-io/github-action-setup-xmake@v1 123 | with: 124 | xmake-version: latest 125 | - name: build 126 | run: | 127 | xrepo update-repo 128 | cd ./sdl/sdl2 129 | xmake -v -y 130 | cp build/windows/${{ matrix.arch }}/release/nes.exe ../../nes-windows-${{ matrix.arch }}.exe 131 | cd ../../ 132 | 133 | - name: Publish file to release 134 | uses: svenstaro/upload-release-action@v2 135 | env: 136 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 137 | with: 138 | repo_token: ${{ secrets.GITHUB_TOKEN }} 139 | file_glob: true 140 | file: nes-windows-${{ matrix.arch }}.exe 141 | tag: ${{ github.ref }} 142 | overwrite: true 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | [**English**](./README.md) | **中文** 2 | 3 | 4 | 5 | # nes 模拟器 6 | 7 | [![Pull Requests Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)[![first-timers-only Friendly](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](http://www.firsttimersonly.com/) 8 | 9 | ![github license](https://img.shields.io/github/license/PeakRacing/nes)[![Latest Release](https://img.shields.io/github/v/release/PeakRacing/nes?label=Release&logo=github)](https://github.com/PeakRacing/nes/releases/latest)![Windows](https://github.com/PeakRacing/nes/actions/workflows/windows.yml/badge.svg?branch=master)![Linux](https://github.com/PeakRacing/nes/actions/workflows/linux.yml/badge.svg?branch=master)![Macos](https://github.com/PeakRacing/nes/actions/workflows/macos.yml/badge.svg?branch=master) 10 | 11 | 12 | 13 | github: [PeakRacing/nes: A NES emulator in C (github.com)](https://github.com/PeakRacing/nes) (推荐) 14 | 15 | gitee: [nes: c语言实现的nes模拟器 (gitee.com)](https://gitee.com/PeakRacing/nes) (由于同步问题可能导致更新不及时) 16 | 17 | ## 介绍 18 | ​ C语言实现的nes模拟器,要求C语言标准: **C11** 以上 19 | 20 | ​ **注意:本仓库仅为nes模拟器,不提供游戏本体!!!** 21 | 22 | 23 | 24 | **平台支持:** 25 | 26 | - [x] Windows 27 | 28 | - [x] Linux 29 | 30 | - [x] MacOS 31 | 32 | **模拟器支持情况:** 33 | 34 | - [x] CPU 35 | 36 | - [x] PPU 37 | 38 | - [x] APU 39 | 40 | **mapper 支持:** 41 | 42 | ​ 0, 2, 3, 7, 94, 117, 180 43 | 44 | ## 软件架构 45 | ​ 示例基于SDL进行图像声音输出,没有特殊依赖,您可自行移植至任意硬件 46 | 47 | 48 | ## 编译教程 49 | 50 | ### 编译准备 51 | 52 | #### Windows: 53 | 54 | ​ 安装MSVC([Visual Studio 2022](https://visualstudio.microsoft.com/zh-hans/vs/)) 55 | 56 | ​ 安装 [xmake](https://github.com/xmake-io/xmake) 57 | 58 | #### Linux(Ubuntu): 59 | 60 | ```shell 61 | sudo add-apt-repository ppa:xmake-io/xmake -y 62 | sudo apt-get update -y 63 | sudo apt-get install -y git make gcc p7zip-full libsdl2-dev xmake 64 | ``` 65 | 66 | #### Macox: 67 | 68 | ```shell 69 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 70 | brew update 71 | brew install make gcc sdl2 xmake 72 | ``` 73 | 74 | ### 编译方法 75 | 76 | ​ 克隆本仓库,直接执行 `xmake` 编译即可 77 | 78 | ​ **注意:**本项目同时支持sdl2和sdl3, 使用sdl2就在sdl/sdl2下执行`xmake` 79 | 80 | ​ 81 | 82 | ## 下载地址 83 | 84 | github: https://github.com/PeakRacing/nes/releases 85 | 86 | gitee: https://gitee.com/PeakRacing/nes/releases 87 | 88 | 89 | 90 | ## 使用说明 91 | 92 | ​ Linux或Macos下输入 `./nes xxx.nes` 加载要运行的游戏 93 | 94 | ​ Windows下输入 `.\nes.exe xxx.nes` 加载要运行的游戏 95 | 96 | ## 按键映射 97 | 98 | | 手柄 | 上 | 下 | 左 | 左 | 选择 | 开始 | A | B | 99 | | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--: | 100 | | P1 | `W` | `S` | `A` | `D` | `V` | `B` | `J` | `K` | 101 | | P2 | `↑` | `↓` | `←` | `→` | `1` | `2` | `5` | `6` | 102 | 103 | **注意:P2使用数字键盘(小键盘)** 104 | 105 | ## 移植说明 106 | 107 | ​ `inc` 和 `src` 目录下的源码无需修改,只需要修改`port`目录下的三个文件 `nes_conf.h` `nes_port.c` `nes_port.h` 108 | 109 | - `nes_conf.h`为配置文件,根据自己需求配置即可,如需打印额外定义 nes_log_printf 的实现 110 | - `nes_port.c`为主要移植文件,需要根据需求进行移植 111 | 112 | 113 | 114 | ​ **注意:如果移植的目标平台性能羸弱、空间较小等特别预留了一些宏配置:** 115 | 116 | - 可以将`NES_ENABLE_SOUND`设置为0关闭apu以增加运行速度 117 | - 可以将`NES_RAM_LACK`设置为1使用半屏刷新以减少ram消耗(运行速度会降低) 118 | - 可以自行配置`NES_FRAME_SKIP`进行跳帧 119 | - 如果为嵌入式平台使用spi 8字节传输时颜色异常配置`NES_COLOR_SWAP`可进行大小端切换 120 | 121 | 122 | 123 | ​ **另外,APU合成使用了单浮点数计算,代码在nes_apu.c中,可自行优化单浮点计算加速或者不使用单浮点计算以加速运行速度** 124 | 125 | ## 运行展示 126 | 127 | **mapper 0:** 128 | 129 | | ![Super Mario Bros](./docs/SuperMarioBros.png) | ![F1_race](./docs/F1_race.png) | ![Star Luster (J)](./docs/StarLuster(J).png) | ![Ikki (J)](./docs/Ikki(J).png) | 130 | | :--------------------------------------------: | :----------------------------: | :------------------------------------------: | ------------------------------- | 131 | | ![Circus Charlie](./docs/CircusCharlie.png) | | | | 132 | 133 | **mapper 2:** 134 | 135 | 136 | | ![Contra1](./docs/Contra1.png) | ![Castlevania](./docs/Castlevania.png) | ![Journey](./docs/Journey.png) | ![Lifeporce](./docs/Lifeporce.png) | 137 | | :------------------------------: | :------------------------------------: | :----------------------------: | ---------------------------------- | 138 | | ![mega_man](./docs/mega_man.png) | ![Athena (J)](./docs/Athena(J).png) | | | 139 | 140 | **mapper 3:** 141 | 142 | | ![contra](./docs/MapleStory.png) | ![Donkey_kong](./docs/Donkey_kong.png) | 143 | | :------------------------------: | :------------------------------------: | 144 | 145 | 146 | 147 | **mapper 94:** 148 | 149 | ![Senjou no Ookami](./docs/Senjou_no_Ookami(J).png) 150 | 151 | **mapper 180:** 152 | 153 | ![Crazy Climber](./docs/CrazyClimber(J).png) 154 | 155 | ## 交流群 156 | 157 | ​ **非技术支持,仅作为兴趣交流** 158 | 159 | ![Communication](./docs/Communication.png) 160 | 161 | ## 文献参考 162 | 163 | https://www.nesdev.org/ 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/nes_mapper/nes_mapper1.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | #include "nes_mapper.h" 19 | 20 | /* https://www.nesdev.org/wiki/MMC1 */ 21 | 22 | 23 | typedef struct { 24 | uint8_t shift; 25 | union { 26 | struct { 27 | uint8_t M:2; 28 | uint8_t P:2; 29 | uint8_t C:1; 30 | uint8_t :3; 31 | }control; 32 | uint8_t control_byte; 33 | }; 34 | } mapper1_register_t; 35 | 36 | static mapper1_register_t mapper_register = {0}; 37 | 38 | static void nes_mapper_init(nes_t* nes){ 39 | // CPU $6000-$7FFF: 8 KB PRG-RAM bank, (optional) 40 | 41 | // CPU $8000-$BFFF: 16 KB PRG-ROM bank, either switchable or fixed to the first bank 42 | nes_load_prgrom_16k(nes, 0, 0); 43 | // CPU $C000-$FFFF: 16 KB PRG-ROM bank, either fixed to the last bank or switchable 44 | nes_load_prgrom_16k(nes, 1, nes->nes_rom.prg_rom_size - 1); 45 | 46 | // PPU $0000-$0FFF: 4 KB switchable CHR bank 47 | // PPU $1000-$1FFF: 4 KB switchable CHR bank 48 | 49 | // CHR capacity: 50 | nes_load_chrrom_8k(nes, 0, 0); 51 | 52 | nes_memset(&mapper_register, 0x00, sizeof(mapper1_register_t)); 53 | mapper_register.shift = 0x10; 54 | } 55 | 56 | static inline void nes_mapper_write_control(nes_t* nes, uint8_t data) { 57 | mapper_register.control_byte = data; 58 | nes_ppu_screen_mirrors(nes, mapper_register.control.M); 59 | } 60 | /* 61 | CHR bank 0 (internal, $A000-$BFFF) 62 | 4bit0 63 | ----- 64 | CCCCC 65 | ||||| 66 | +++++- Select 4 KB or 8 KB CHR bank at PPU $0000 (low bit ignored in 8 KB mode) 67 | */ 68 | static inline void nes_mapper_write_chrbank0(nes_t* nes) { 69 | if (mapper_register.control.C) { 70 | nes_load_chrrom_4k(nes, 0, mapper_register.shift); 71 | } else { 72 | nes_load_chrrom_8k(nes, 0, (mapper_register.shift & (uint8_t)0x0E)); 73 | } 74 | } 75 | /* 76 | CHR bank 1 (internal, $C000-$DFFF) 77 | 4bit0 78 | ----- 79 | CCCCC 80 | ||||| 81 | +++++- Select 4 KB CHR bank at PPU $1000 (ignored in 8 KB mode) 82 | */ 83 | static inline void nes_mapper_write_chrbank1(nes_t* nes) { 84 | if (mapper_register.control.C) 85 | nes_load_chrrom_4k(nes, 1, mapper_register.shift); 86 | } 87 | /* 88 | PRG bank (internal, $E000-$FFFF) 89 | 4bit0 90 | ----- 91 | RPPPP 92 | ||||| 93 | |++++- Select 16 KB PRG-ROM bank (low bit ignored in 32 KB mode) 94 | +----- MMC1B and later: PRG-RAM chip enable (0: enabled; 1: disabled; ignored on MMC1A) 95 | MMC1A: Bit 3 bypasses fixed bank logic in 16K mode (0: fixed bank affects A17-A14; 96 | 1: fixed bank affects A16-A14 and bit 3 directly controls A17) 97 | */ 98 | static inline void nes_mapper_write_prgbank(nes_t* nes) { 99 | const uint8_t bankid = mapper_register.shift & (uint8_t)0x0F; 100 | switch (mapper_register.control.P){ 101 | case 0: case 1: 102 | // 32KB mode - switch both 16KB banks together 103 | nes_load_prgrom_32k(nes, 0, bankid & (uint8_t)0x0E); 104 | break; 105 | case 2: 106 | // Fix first bank at $8000 and switch 16KB bank at $C000 107 | nes_load_prgrom_16k(nes, 0, 0); 108 | nes_load_prgrom_16k(nes, 1, bankid); 109 | break; 110 | case 3: 111 | // Fix last bank at $C000 and switch 16KB bank at $8000 112 | nes_load_prgrom_16k(nes, 0, bankid); 113 | nes_load_prgrom_16k(nes, 1, nes->nes_rom.prg_rom_size - 1); 114 | break; 115 | } 116 | } 117 | 118 | static inline void nes_mapper_write_register(nes_t* nes, uint16_t address) { 119 | switch ((address & 0x7FFF) >> 13){ 120 | case 0: 121 | nes_mapper_write_control(nes, mapper_register.shift); 122 | break; 123 | case 1: 124 | nes_mapper_write_chrbank0(nes); 125 | break; 126 | case 2: 127 | nes_mapper_write_chrbank1(nes); 128 | break; 129 | case 3: 130 | nes_mapper_write_prgbank(nes); 131 | break; 132 | } 133 | } 134 | /* 135 | Load register ($8000-$FFFF) 136 | 7 bit 0 137 | ---- ---- 138 | Rxxx xxxD 139 | | | 140 | | +- Data bit to be shifted into shift register, LSB first 141 | +--------- A write with bit set will reset shift register 142 | and write Control with (Control OR $0C), 143 | locking PRG-ROM at $C000-$FFFF to the last bank. 144 | */ 145 | static void nes_mapper_write(nes_t* nes, uint16_t write_addr, uint8_t data){ 146 | if (data & (uint8_t)0x80){ 147 | mapper_register.shift = 0x10; // reset shift register 148 | nes_mapper_write_control(nes, mapper_register.control.P); 149 | }else { 150 | const uint8_t finished = mapper_register.shift & 1; 151 | mapper_register.shift >>= 1; 152 | mapper_register.shift |= (data & 1) << 4; 153 | if (finished) { 154 | nes_mapper_write_register(nes, write_addr); 155 | mapper_register.shift = 0x10; 156 | } 157 | } 158 | } 159 | 160 | int nes_mapper1_init(nes_t* nes){ 161 | nes->nes_mapper.mapper_init = nes_mapper_init; 162 | nes->nes_mapper.mapper_write = nes_mapper_write; 163 | return 0; 164 | } 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **English** | [中文](./README_zh.md) 2 | 3 | 4 | 5 | # nes simulator 6 | 7 | [![Pull Requests Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat)](http://makeapullrequest.com)[![first-timers-only Friendly](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](http://www.firsttimersonly.com/) 8 | 9 | ![github license](https://img.shields.io/github/license/PeakRacing/nes)[![Latest Release](https://img.shields.io/github/v/release/PeakRacing/nes?label=Release&logo=github)](https://github.com/PeakRacing/nes/releases/latest)![Windows](https://github.com/PeakRacing/nes/actions/workflows/windows.yml/badge.svg?branch=master)![Linux](https://github.com/PeakRacing/nes/actions/workflows/linux.yml/badge.svg?branch=master)![Macos](https://github.com/PeakRacing/nes/actions/workflows/macos.yml/badge.svg?branch=master) 10 | 11 | 12 | 13 | github: [PeakRacing/nes: A NES emulator in C (github.com)](https://github.com/PeakRacing/nes) (recommend) 14 | 15 | gitee: [nes: c语言实现的nes模拟器 (gitee.com)](https://gitee.com/PeakRacing/nes) (updates may not be timely due to synchronization issues) 16 | 17 | ## Introduction 18 | ​ The nes simulator implemented in C , requires `C11` or above 19 | 20 | ​ **attention:This repository is only for the nes simulator and does not provide the game !!!** 21 | 22 | 23 | 24 | **Platform support:** 25 | 26 | - [x] Windows 27 | 28 | - [x] Linux 29 | 30 | - [x] MacOS 31 | 32 | **Simulator support:** 33 | 34 | - [x] CPU 35 | 36 | - [x] PPU 37 | 38 | - [x] APU 39 | 40 | **mapper support:** 41 | 42 | ​ 0, 2, 3, 7, 94, 117, 180 43 | 44 | ## Software Architecture 45 | ​ The example is based on SDL for image and sound output, without special dependencies, and you can port to any hardware by yourself 46 | 47 | 48 | ## Compile Tutorial 49 | 50 | ​ clone repository,install [xmake](https://github.com/xmake-io/xmake),execute `xmake` directly to compile 51 | 52 | ### Compile Preparation 53 | 54 | #### Windows: 55 | 56 | ​ install MSVC([Visual Studio 2022](https://visualstudio.microsoft.com/zh-hans/vs/)) 57 | 58 | ​ install [xmake](https://github.com/xmake-io/xmake) 59 | 60 | #### Linux(Ubuntu): 61 | 62 | ```shell 63 | sudo add-apt-repository ppa:xmake-io/xmake -y 64 | sudo apt-get update -y 65 | sudo apt-get install -y git make gcc p7zip-full libsdl2-dev xmake 66 | ``` 67 | 68 | #### Macox: 69 | 70 | ```shell 71 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 72 | brew update 73 | brew install make gcc sdl2 xmake 74 | ``` 75 | 76 | ### Compilation Method 77 | 78 | ​ clone repository,execute `xmake` directly to compile 79 | 80 | ​ **Note:** This project supports both SDL2 and SDL3, use SDL2 to execute 'xmake' under SDL/SDL2 81 | 82 | 83 | 84 | ## Download link 85 | 86 | github: https://github.com/PeakRacing/nes/releases 87 | 88 | gitee: https://gitee.com/PeakRacing/nes/releases 89 | 90 | 91 | 92 | ## Instructions 93 | 94 | ​ on linux or macos enter `./nes xxx.nes` load the game to run 95 | 96 | ​ on windows enter `.\nes.exe xxx.nes` load the game to run 97 | 98 | ## Key mapping 99 | 100 | | joystick | up | down | left | right | select | start | A | B | 101 | | :------: | :--: | :--: | :--: | :---: | :----: | :---: | :--: | :--: | 102 | | P1 | `W` | `S` | `A` | `D` | `V` | `B` | `J` | `K` | 103 | | P2 | `↑` | `↓` | `←` | `→` | `1` | `2` | `5` | `6` | 104 | 105 | **Note: P2 uses numberic keypad** 106 | 107 | ## Transplant instructions 108 | 109 | ​ The source code in the `inc`and `src` directories does not need to be modified, only the three files in the `port` directory `nes_conf.h` `nes_port.c` `nes_port.h` 110 | 111 | - `nes_conf.h` is a configuration file, configure according to your needs, such as printing extra definitions, the implementation of `nes_log_printf` 112 | - `nes_port.c` is the main porting file, which needs to be ported according to the needs 113 | 114 | 115 | 116 | ​ **Note: If the target platform for migration has weak performance and small space, some macro configurations are specially reserved:** 117 | 118 | - `NES_ENABLE_SOUND` can be set to 0 to turn off the APU to increase the running speed 119 | - `NES_RAM_LACK` can be set to 1, using a half-screen refresh to reduce RAM consumption (running at a slower speed) 120 | - You can configure `NES_FRAME_SKIP` to skip frames 121 | - If SPI 8-byte transmission is used for embedded platforms, the color anomaly configuration `NES_COLOR_SWAP` can be used to switch the large and small ends 122 | 123 | 124 | 125 | ​ **In addition, the APU synthesis uses single floating-point calculations, and the code can optimize the single-floating-point calculation acceleration or not use single-floating-point calculations to speed up the operation in `nes_apu.c`** 126 | 127 | ## Showcase 128 | 129 | **mapper 0:** 130 | 131 | | ![Super Mario Bros](./docs/SuperMarioBros.png) | ![F1_race](./docs/F1_race.png) | ![Star Luster (J)](./docs/StarLuster(J).png) | ![Ikki (J)](./docs/Ikki(J).png) | 132 | | :--------------------------------------------: | :----------------------------: | :------------------------------------------: | ------------------------------- | 133 | | ![Circus Charlie](./docs/CircusCharlie.png) | | | | 134 | 135 | **mapper 2:** 136 | 137 | 138 | | ![Contra1](./docs/Contra1.png) | ![Castlevania](./docs/Castlevania.png) | ![Journey](./docs/Journey.png) | ![Lifeporce](./docs/Lifeporce.png) | 139 | | :------------------------------: | :------------------------------------: | :----------------------------: | ---------------------------------- | 140 | | ![mega_man](./docs/mega_man.png) | ![Athena (J)](./docs/Athena(J).png) | | | 141 | 142 | **mapper 3:** 143 | 144 | | ![contra](./docs/MapleStory.png) | ![Donkey_kong](./docs/Donkey_kong.png) | 145 | | :------------------------------: | :------------------------------------: | 146 | 147 | 148 | 149 | **mapper 94:** 150 | 151 | ![Senjou no Ookami](./docs/Senjou_no_Ookami(J).png) 152 | 153 | **mapper 180:** 154 | 155 | ![Crazy Climber](./docs/CrazyClimber(J).png) 156 | 157 | ## Discussion group 158 | 159 | ​ **Non-technical support, only for the purpose of interest exchange.** 160 | 161 | ![Communication](./docs/Communication.png) 162 | 163 | ## Literature reference 164 | 165 | https://www.nesdev.org/ 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /src/nes_rom.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | #if (NES_USE_FS == 1) 20 | int nes_load_file(nes_t* nes, const char* file_path ){ 21 | nes_header_ines_t nes_header_info = {0}; 22 | 23 | void* nes_file = nes_fopen(file_path, "rb"); 24 | if (nes_file == NULL){ 25 | NES_LOG_ERROR("nes_load_file: failed to open file %s\n", file_path); 26 | goto error; 27 | } 28 | #if (NES_USE_SRAM == 1) 29 | nes->nes_rom.sram = (uint8_t*)nes_malloc(SRAM_SIZE); 30 | if (nes->nes_rom.sram == NULL) { 31 | goto error; 32 | } 33 | nes_memset(nes->nes_rom.sram, 0x00, SRAM_SIZE); 34 | #endif 35 | if (nes_fread(&nes_header_info, sizeof(nes_header_info), 1, nes_file)) { 36 | if (nes_memcmp(nes_header_info.identification, "NES\x1a", 4)){ 37 | goto error; 38 | } 39 | if (nes_header_info.trainer){ 40 | #if (NES_USE_SRAM == 1) 41 | if (nes_fread(nes->nes_rom.sram, TRAINER_SIZE, 1, nes_file)==0){ 42 | goto error; 43 | } 44 | #else 45 | nes_fseek(nes_file, TRAINER_SIZE, SEEK_CUR); 46 | #endif 47 | } 48 | if (nes_header_info.identifier==2){ //NES 2.0 49 | nes_header_nes2_t* nes2_header_info = (nes_header_nes2_t*)&nes_header_info; 50 | nes->nes_rom.prg_rom_size = ((nes2_header_info->prg_rom_size_m << 8) & 0xF00) | nes2_header_info->prg_rom_size_l; 51 | nes->nes_rom.chr_rom_size = ((nes2_header_info->chr_rom_size_m << 8) & 0xF00) | nes2_header_info->chr_rom_size_l; 52 | nes->nes_rom.mapper_number = ((nes2_header_info->mapper_number_h << 8) & 0xF00) | ((nes2_header_info->mapper_number_m << 4) & 0xF0) | (nes2_header_info->mapper_number_l & 0x0F); 53 | 54 | }else{ //INES 55 | nes_header_ines_t* ines_header_info = (nes_header_ines_t*)&nes_header_info; 56 | nes->nes_rom.prg_rom_size = ines_header_info->prg_rom_size; 57 | nes->nes_rom.chr_rom_size = ines_header_info->chr_rom_size; 58 | nes->nes_rom.mapper_number = ines_header_info->mapper_number_l | ines_header_info->mapper_number_h << 4; 59 | } 60 | nes->nes_rom.mirroring_type = nes_header_info.mirroring; 61 | nes->nes_rom.four_screen = nes_header_info.four_screen; 62 | nes->nes_rom.save_ram = nes_header_info.save; 63 | nes->nes_rom.prg_rom = (uint8_t*)nes_malloc(PRG_ROM_UNIT_SIZE * nes->nes_rom.prg_rom_size); 64 | if (nes->nes_rom.prg_rom == NULL) { 65 | goto error; 66 | } 67 | if (nes_fread(nes->nes_rom.prg_rom, PRG_ROM_UNIT_SIZE, nes->nes_rom.prg_rom_size, nes_file)==0){ 68 | goto error; 69 | } 70 | nes->nes_rom.chr_rom = (uint8_t*)nes_malloc(CHR_ROM_UNIT_SIZE * (nes->nes_rom.chr_rom_size ? nes->nes_rom.chr_rom_size : 1)); 71 | if (nes->nes_rom.chr_rom == NULL) { 72 | goto error; 73 | } 74 | if (nes->nes_rom.chr_rom_size){ 75 | if (nes_fread(nes->nes_rom.chr_rom, CHR_ROM_UNIT_SIZE, (nes->nes_rom.chr_rom_size), nes_file)==0){ 76 | goto error; 77 | } 78 | } 79 | }else{ 80 | goto error; 81 | } 82 | nes_fclose(nes_file); 83 | nes_cpu_init(nes); 84 | #if (NES_ENABLE_SOUND==1) 85 | nes_apu_init(nes); 86 | #endif 87 | nes_ppu_init(nes); 88 | if(nes_load_mapper(nes)){ 89 | goto error; 90 | } 91 | nes->nes_mapper.mapper_init(nes); 92 | return NES_OK; 93 | error: 94 | if (nes_file){ 95 | nes_fclose(nes_file); 96 | } 97 | if (nes){ 98 | nes_unload_file(nes); 99 | } 100 | return NES_ERROR; 101 | 102 | } 103 | 104 | 105 | int nes_unload_file(nes_t* nes){ 106 | if (nes->nes_rom.prg_rom){ 107 | nes_free(nes->nes_rom.prg_rom); 108 | } 109 | if (nes->nes_rom.chr_rom){ 110 | nes_free(nes->nes_rom.chr_rom); 111 | } 112 | if (nes->nes_rom.sram){ 113 | nes_free(nes->nes_rom.sram); 114 | } 115 | return NES_OK; 116 | } 117 | 118 | #endif 119 | 120 | int nes_load_rom(nes_t* nes, const uint8_t* nes_rom){ 121 | nes_header_ines_t* nes_header_info = (nes_header_ines_t*)nes_rom; 122 | #if (NES_USE_SRAM == 1) 123 | nes->nes_rom.sram = (uint8_t*)nes_malloc(SRAM_SIZE); 124 | #endif 125 | if ( nes_memcmp( nes_header_info->identification, "NES\x1a", 4 )){ 126 | goto error; 127 | } 128 | uint8_t* nes_bin = (uint8_t*)nes_rom + sizeof(nes_header_ines_t); 129 | if (nes_header_info->trainer){ 130 | #if (NES_USE_SRAM == 1) 131 | #else 132 | #endif 133 | nes_bin += TRAINER_SIZE; 134 | } 135 | 136 | if (nes_header_info->identifier==2){ //NES 2.0 137 | nes_header_nes2_t* nes2_header_info = (nes_header_nes2_t*)nes_header_info; 138 | nes->nes_rom.prg_rom_size = ((nes2_header_info->prg_rom_size_m << 8) & 0xF00) | nes2_header_info->prg_rom_size_l; 139 | nes->nes_rom.chr_rom_size = ((nes2_header_info->chr_rom_size_m << 8) & 0xF00) | nes2_header_info->chr_rom_size_l; 140 | nes->nes_rom.mapper_number = ((nes2_header_info->mapper_number_h << 8) & 0xF00) | ((nes2_header_info->mapper_number_m << 4) & 0xF0) | (nes2_header_info->mapper_number_l & 0x0F); 141 | 142 | }else{ //INES 143 | nes_header_ines_t* ines_header_info = (nes_header_ines_t*)nes_header_info; 144 | nes->nes_rom.prg_rom_size = ines_header_info->prg_rom_size; 145 | nes->nes_rom.chr_rom_size = ines_header_info->chr_rom_size; 146 | nes->nes_rom.mapper_number = ines_header_info->mapper_number_l | ines_header_info->mapper_number_h << 4; 147 | } 148 | 149 | nes->nes_rom.mirroring_type = (nes_header_info->mirroring); 150 | nes->nes_rom.four_screen = (nes_header_info->four_screen); 151 | nes->nes_rom.save_ram = (nes_header_info->save); 152 | 153 | nes->nes_rom.prg_rom = nes_bin; 154 | nes_bin += PRG_ROM_UNIT_SIZE * nes->nes_rom.prg_rom_size; 155 | 156 | if (nes->nes_rom.chr_rom_size){ 157 | nes->nes_rom.chr_rom = nes_bin; 158 | } 159 | nes_cpu_init(nes); 160 | #if (NES_ENABLE_SOUND==1) 161 | nes_apu_init(nes); 162 | #endif 163 | nes_ppu_init(nes); 164 | if(nes_load_mapper(nes)){ 165 | return NES_ERROR; 166 | } 167 | nes->nes_mapper.mapper_init(nes); 168 | return NES_OK; 169 | error: 170 | if (nes){ 171 | nes_unload_rom(nes); 172 | } 173 | return NES_ERROR; 174 | } 175 | 176 | int nes_unload_rom(nes_t* nes){ 177 | (void)nes; 178 | return NES_OK; 179 | } 180 | -------------------------------------------------------------------------------- /src/nes_ppu.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | static inline uint8_t nes_read_ppu_memory(nes_t* nes){ 20 | const uint16_t address = nes->nes_ppu.v_reg & (uint16_t)0x3FFF; 21 | const uint8_t index = address >> 10; 22 | const uint16_t offset = address & (uint16_t)0x3FF; 23 | if (address < (uint16_t)0x3F00) {// BANK 24 | uint8_t data = nes->nes_ppu.buffer; 25 | nes->nes_ppu.buffer = nes->nes_ppu.chr_banks[index][offset]; 26 | return data; 27 | } else {// 调色板 28 | nes->nes_ppu.buffer = nes->nes_ppu.chr_banks[index][offset]; 29 | return nes->nes_ppu.palette_indexes[address & (uint16_t)0x1f]; 30 | } 31 | } 32 | 33 | static inline void nes_write_ppu_memory(nes_t* nes,uint8_t data){ 34 | const uint16_t address = nes->nes_ppu.v_reg & (uint16_t)0x3FFF; 35 | if (address < (uint16_t)0x3F00) {// BANK 36 | nes->nes_ppu.chr_banks[(uint8_t)(address >> 10)][(uint16_t)(address & (uint16_t)0x3FF)] = data; 37 | } else {// 调色板 38 | if ((uint8_t)address & 0x03) { 39 | nes->nes_ppu.palette_indexes[(uint8_t)address & 0x1f] = data & 0x3F; 40 | } else { 41 | const uint8_t offset = (uint8_t)address & 0x0f; 42 | nes->nes_ppu.palette_indexes[offset] = nes->nes_ppu.palette_indexes[offset | 0x10] = data & 0x3F; 43 | } 44 | } 45 | } 46 | 47 | // https://www.nesdev.org/wiki/PPU_registers 48 | uint8_t nes_read_ppu_register(nes_t* nes,uint16_t address){ 49 | uint8_t data = 0; 50 | switch (address & (uint16_t)0x07){ 51 | case 2://Status ($2002) < read 52 | // w: <- 0 53 | data = nes->nes_ppu.ppu_status; 54 | nes->nes_ppu.STATUS_V = 0; 55 | nes->nes_ppu.w = 0; 56 | break; 57 | case 4://OAM data ($2004) <> read/write 58 | data = nes->nes_ppu.oam_data[nes->nes_ppu.oam_addr]; 59 | break; 60 | case 7://Data ($2007) <> read/write 61 | data = nes_read_ppu_memory(nes); 62 | nes->nes_ppu.v_reg += (uint16_t)((nes->nes_ppu.CTRL_I) ? 32 : 1); 63 | break; 64 | default : // ($2000 $2001 $2003 $2005 $2006) 65 | // NES_LOG_DEBUG("nes_read_ppu_register error %04X\n",address); 66 | // break; 67 | return nes->nes_ppu.oam_addr; 68 | } 69 | // NES_LOG_DEBUG("nes_read_ppu_register %04X %02X\n",address,data); 70 | return data; 71 | } 72 | 73 | void nes_write_ppu_register(nes_t* nes,uint16_t address, uint8_t data){ 74 | // NES_LOG_DEBUG("nes_write_ppu_register %04X %02X\n",address,data); 75 | switch (address & (uint16_t)0x07){ 76 | case 0://Controller ($2000) > write 77 | // t: ....GH.. ........ <- d: ......GH 78 | // <- d: ABCDEF.. 79 | nes->nes_ppu.ppu_ctrl = data; 80 | nes->nes_ppu.t.nametable = nes->nes_ppu.CTRL_N; 81 | break; 82 | case 1://Mask ($2001) > write 83 | nes->nes_ppu.ppu_mask = data; 84 | break; 85 | case 3://OAM address ($2003) > write 86 | nes->nes_ppu.oam_addr = data; 87 | break; 88 | case 4://OAM data ($2004) <> read/write 89 | nes->nes_ppu.oam_data[nes->nes_ppu.oam_addr++] = data; 90 | break; 91 | case 5://Scroll ($2005) >> write x2 92 | if (nes->nes_ppu.w) { // w is 1 93 | // t: FGH..AB CDE..... <- d: ABCDEFGH 94 | // w: <- 0 95 | nes->nes_ppu.t.fine_y = (data & 0x07); 96 | nes->nes_ppu.t.coarse_y = (data & 0xF8)>>3; 97 | nes->nes_ppu.w = 0; 98 | } else { // w is 0 99 | // t: ....... ...ABCDE <- d: ABCDE... 100 | // x: FGH <- d: .....FGH 101 | // w: <- 1 102 | nes->nes_ppu.t.coarse_x = (data & 0xF8)>>3; 103 | nes->nes_ppu.x = (data & 0x07); 104 | nes->nes_ppu.w = 1; 105 | } 106 | break; 107 | case 6://Address ($2006) >> write x2 108 | if (nes->nes_ppu.w) { // w is 1 109 | // t: ....... ABCDEFGH <- d: ABCDEFGH 110 | // v: <...all bits...> <- t: <...all bits...> 111 | // w: <- 0 112 | nes->nes_ppu.t_reg = (nes->nes_ppu.t_reg & (uint16_t)0xFF00) | (uint16_t)data; 113 | nes->nes_ppu.v_reg = nes->nes_ppu.t_reg; 114 | nes->nes_ppu.w = 0; 115 | } else { // w is 0 116 | // t: ..CDEFGH ........ <- d: ..CDEFGH 117 | // <- d: AB...... 118 | // t: .Z...... ........ <- 0 (bit Z is cleared) 119 | // w: <- 1 120 | nes->nes_ppu.t_reg = (nes->nes_ppu.t_reg & (uint16_t)0xFF) | (((uint16_t)data & 0x3F) << 8); 121 | nes->nes_ppu.w = 1; 122 | } 123 | break; 124 | case 7://Data ($2007) <> read/write 125 | nes_write_ppu_memory(nes,data); 126 | nes->nes_ppu.v_reg += (uint16_t)((nes->nes_ppu.CTRL_I) ? 32 : 1); 127 | break; 128 | default : 129 | NES_LOG_DEBUG("nes_write_ppu_register error %04X %02X\n",address,data); 130 | break; 131 | } 132 | } 133 | 134 | static const uint8_t nes_mirror_table[NES_MIRROR_COUNT][4] ={ 135 | { 0, 1, 2, 3 }, // NES_MIRROR_FOUR_SCREEN 136 | { 0, 0, 1, 1 }, // NES_MIRROR_HORIZONTAL 137 | { 0, 1, 0, 1 }, // NES_MIRROR_VERTICAL 138 | { 0, 0, 0, 0 }, // NES_MIRROR_ONE_SCREEN0 139 | { 1, 1, 1, 1 }, // NES_MIRROR_ONE_SCREEN1 140 | { 0, 0, 0, 1 }, // NES_MIRROR_MAPPER 141 | }; 142 | 143 | 144 | void nes_ppu_screen_mirrors(nes_t *nes,nes_mirror_type_t mirror_type){ 145 | if (mirror_type == NES_MIRROR_AUTO){ 146 | if (nes->nes_rom.four_screen) { // four_screen 147 | mirror_type = NES_MIRROR_FOUR_SCREEN; 148 | } else if (nes->nes_rom.mirroring_type) { // Vertical 149 | mirror_type = NES_MIRROR_VERTICAL; 150 | } else { // Horizontal 151 | mirror_type = NES_MIRROR_HORIZONTAL; 152 | } 153 | } 154 | nes->nes_ppu.name_table[0] = nes->nes_ppu.ppu_vram[nes_mirror_table[mirror_type][0]]; 155 | nes->nes_ppu.name_table[1] = nes->nes_ppu.ppu_vram[nes_mirror_table[mirror_type][1]]; 156 | nes->nes_ppu.name_table[2] = nes->nes_ppu.ppu_vram[nes_mirror_table[mirror_type][2]]; 157 | nes->nes_ppu.name_table[3] = nes->nes_ppu.ppu_vram[nes_mirror_table[mirror_type][3]]; 158 | // mirrors 159 | nes->nes_ppu.name_table_mirrors[0] = nes->nes_ppu.name_table[0]; 160 | nes->nes_ppu.name_table_mirrors[1] = nes->nes_ppu.name_table[1]; 161 | nes->nes_ppu.name_table_mirrors[2] = nes->nes_ppu.name_table[2]; 162 | nes->nes_ppu.name_table_mirrors[3] = nes->nes_ppu.name_table[3]; 163 | } 164 | 165 | void nes_ppu_init(nes_t *nes){ 166 | nes_ppu_screen_mirrors(nes,NES_MIRROR_AUTO); 167 | } 168 | 169 | -------------------------------------------------------------------------------- /inc/nes_rom.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #define SRAM_SIZE (0x2000) /* 8K */ 23 | 24 | #define TRAINER_SIZE (0x200) /* 512 */ 25 | #define PRG_ROM_UNIT_SIZE (0x4000) /* 16K */ 26 | #define CHR_ROM_UNIT_SIZE (0x2000) /* 8K */ 27 | 28 | struct nes; 29 | typedef struct nes nes_t; 30 | 31 | /* INES: https://www.nesdev.org/wiki/INES */ 32 | typedef struct { 33 | uint8_t identification[4]; /* 0-3 Constant $4E $45 $53 $1A (ASCII "NES" followed by MS-DOS end-of-file) */ 34 | uint8_t prg_rom_size; /* 4 Size of PRG ROM in 16 KB units */ 35 | uint8_t chr_rom_size; /* 5 Size of CHR ROM in 8 KB units (value 0 means the board uses CHR RAM) */ 36 | struct { 37 | uint8_t mirroring:1; /* D0 Nametable arrangement: 0: vertical arrangement ("horizontal mirrored") (CIRAM A10 = PPU A11) 38 | 1: horizontal arrangement ("vertically mirrored") (CIRAM A10 = PPU A10) */ 39 | uint8_t save:1; /* D1 Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory */ 40 | uint8_t trainer:1; /* D2 512-byte trainer at $7000-$71FF (stored before PRG data) */ 41 | uint8_t four_screen:1; /* D3 Alternative nametable layout */ 42 | uint8_t mapper_number_l:4; /* D4-7 Lower nybble of mapper number */ 43 | }; 44 | struct { 45 | uint8_t unisystem:1; /* D0 VS Unisystem */ 46 | uint8_t playchoice_10:1; /* D1 PlayChoice-10 (8 KB of Hint Screen data stored after CHR data) */ 47 | uint8_t identifier:2; /* D2-3 If equal to 2, flags 8-15 are in NES 2.0 format */ 48 | uint8_t mapper_number_h:4; /* D4-7 Upper nybble of mapper number */ 49 | }; 50 | uint8_t prg_ram_size; /* PRG RAM size */ 51 | struct { 52 | uint8_t tv_system:1; /* D0 TV system (0: NTSC; 1: PAL) */ 53 | uint8_t :7; 54 | }; 55 | struct { 56 | uint8_t tv_system_ex:2; /* D0-1 TV system (0: NTSC; 2: PAL; 1/3: dual compatible) */ 57 | uint8_t :2; 58 | uint8_t prg_rom:1; /* D4 PRG RAM ($6000-$7FFF) (0: present; 1: not present) */ 59 | uint8_t board_conflicts:1; /* D5 0: Board has no bus conflicts; 1: Board has bus conflicts */ 60 | uint8_t :2; 61 | }; 62 | uint8_t Reserved[5]; /* 11-15 Reserved */ 63 | } nes_header_ines_t; 64 | 65 | /* NES 2.0: https://wiki.nesdev.org/w/index.php/NES_2.0 */ 66 | typedef struct { 67 | uint8_t identification[4]; /* 0-3 Identification String. Must be "NES". */ 68 | uint8_t prg_rom_size_l; /* 4 PRG-ROM size LSB */ 69 | uint8_t chr_rom_size_l; /* 5 CHR-ROM size LSB */ 70 | struct { 71 | uint8_t mirroring:1; /* D0 Hard-wired nametable mirroring type 0: Horizontal or mapper-controlled 1: Vertical */ 72 | uint8_t save:1; /* D1 "Battery" and other non-volatile memory 0: Not present 1: Present */ 73 | uint8_t trainer:1; /* D2 512-byte Trainer 0: Not present 1: Present between Header and PRG-ROM data */ 74 | uint8_t four_screen:1; /* D3 Hard-wired four-screen mode 0: No 1: Yes */ 75 | uint8_t mapper_number_l:4; /* D4-7 Mapper Number D0..D3 */ 76 | }; 77 | struct { 78 | uint8_t console_type:2; /* D0-1 Console type 0: Nintendo Entertainment System/Family Computer 1: Nintendo Vs. System 2: Nintendo Playchoice 10 3: Extended Console Type */ 79 | uint8_t identifier:2; /* D2-3 NES 2.0 identifier */ 80 | uint8_t mapper_number_m:4; /* D4-7 Mapper Number D4..D7 */ 81 | }; 82 | struct { 83 | uint8_t mapper_number_h:4; /* D0-3 Mapper number D8..D11 */ 84 | uint8_t submapper:4; /* D4-7 Submapper number */ 85 | }; /* Mapper MSB/Submapper */ 86 | struct { 87 | uint8_t prg_rom_size_m:4; /* D0-3 PRG-ROM size MSB */ 88 | uint8_t chr_rom_size_m:4; /* D4-7 CHR-ROM size MSB */ 89 | }; /* PRG-ROM/CHR-ROM size MSB */ 90 | struct { 91 | uint8_t prg_ram_size_m:4; /* D0-3 PRG-RAM (volatile) shift count */ 92 | uint8_t eeprom_size_m:4; /* D4-7 PRG-NVRAM/EEPROM (non-volatile) shift count */ 93 | }; /* PRG-RAM/EEPROM size 94 | If the shift count is zero, there is no PRG-(NV)RAM. 95 | If the shift count is non-zero, the actual size is 96 | "64 << shift count" bytes, i.e. 8192 bytes for a shift count of 7. */ 97 | struct { 98 | uint8_t chr_ram_size_m:4; /* D0-3 CHR-RAM size (volatile) shift count */ 99 | uint8_t chr_nvram_size_m:4; /* D4-7 CHR-NVRAM size (non-volatile) shift count */ 100 | }; /* CHR-RAM size 101 | If the shift count is zero, there is no CHR-(NV)RAM. 102 | If the shift count is non-zero, the actual size is 103 | "64 << shift count" bytes, i.e. 8192 bytes for a shift count of 7. */ 104 | struct { 105 | uint8_t timing_mode :2; /* D0-1 CPU/PPU timing mode 106 | 0: RP2C02 ("NTSC NES") 107 | 1: RP2C07 ("Licensed PAL NES") 108 | 2: Multiple-region 109 | 3: UMC 6527P ("Dendy") */ 110 | uint8_t :6; 111 | }; /* CPU/PPU Timing */ 112 | struct { 113 | uint8_t ppu_type:4; /* D0-3 Vs. PPU Type */ 114 | uint8_t hardware_type:4; /* D4-7 Vs. Hardware Type */ 115 | }; /* When Byte 7 AND 3 =1: Vs. System Type 116 | When Byte 7 AND 3 =3: Extended Console Type */ 117 | struct { 118 | uint8_t miscellaneous_number:2; /* D0-1 Number of miscellaneous ROMs present */ 119 | uint8_t :6; 120 | }; /* Miscellaneous ROMs */ 121 | struct { 122 | uint8_t expansion_device:6; /* D0-5 Default Expansion Device */ 123 | uint8_t :2; 124 | }; /* Default Expansion Device */ 125 | } nes_header_nes2_t; 126 | 127 | typedef struct nes_rom_info{ 128 | uint16_t prg_rom_size; 129 | uint16_t chr_rom_size; 130 | uint8_t* prg_rom; 131 | uint8_t* chr_rom; 132 | uint8_t* sram; 133 | uint16_t mapper_number; /* Mapper Number */ 134 | uint8_t mirroring_type; /* 0: Horizontal or mapper-controlled 1: Vertical */ 135 | uint8_t four_screen; /* 0: No 1: Yes */ 136 | uint8_t save_ram; /* 0: Not present 1: Present */ 137 | } nes_rom_info_t; 138 | 139 | #ifdef __cplusplus 140 | } 141 | #endif 142 | 143 | -------------------------------------------------------------------------------- /inc/nes_ppu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #define NES_PPU_VRAM_SIZE 0x1000 /* 4KB */ 23 | #define NES_PPU_OAM_SIZE 0x100 /* 256B */ 24 | 25 | typedef enum { 26 | NES_MIRROR_FOUR_SCREEN , 27 | NES_MIRROR_HORIZONTAL , 28 | NES_MIRROR_VERTICAL , 29 | NES_MIRROR_ONE_SCREEN0 , 30 | NES_MIRROR_ONE_SCREEN1 , 31 | NES_MIRROR_MAPPER , 32 | NES_MIRROR_COUNT , 33 | NES_MIRROR_AUTO , 34 | }nes_mirror_type_t; 35 | 36 | struct nes; 37 | typedef struct nes nes_t; 38 | 39 | // https://www.nesdev.org/wiki/PPU_OAM 40 | typedef struct{ 41 | uint8_t y; /* Y position of top of sprite */ 42 | union { 43 | struct { 44 | uint8_t pattern_8x16:1; /* Bank ($0000 or $1000) of tiles */ 45 | uint8_t tile_index_8x16 :7; /* Tile number of top of sprite (0 to 254; bottom half gets the next tile) */ 46 | }; 47 | uint8_t tile_index_number; /* Tile index number */ 48 | }; 49 | union { 50 | struct { 51 | uint8_t sprite_palette:2; /* Palette (4 to 7) of sprite */ 52 | uint8_t :3; /* nimplemented (read 0) */ 53 | uint8_t priority :1; /* Priority (0: in front of background; 1: behind background) */ 54 | uint8_t flip_h :1; /* Flip sprite horizontally */ 55 | uint8_t flip_v :1; /* Flip sprite vertically */ 56 | }; 57 | uint8_t attributes; /* Attributes */ 58 | }; 59 | uint8_t x; /* X position of left side of sprite. */ 60 | } sprite_info_t; 61 | 62 | // https://www.nesdev.org/wiki/PPU_registers 63 | typedef struct nes_ppu{ 64 | union { 65 | struct { 66 | uint8_t CTRL_N:2; /* Base nametable address (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00) */ 67 | uint8_t CTRL_I:1; /* VRAM address increment per CPU read/write of PPUDATA (0: add 1, going across; 1: add 32, going down) */ 68 | uint8_t CTRL_S:1; /* Sprite pattern table address for 8x8 sprites (0: $0000; 1: $1000; ignored in 8x16 mode) */ 69 | uint8_t CTRL_B:1; /* Background pattern table address (0: $0000; 1: $1000) */ 70 | uint8_t CTRL_H:1; /* Sprite size (0: 8x8 pixels; 1: 8x16 pixels – see PPU OAM#Byte 1) */ 71 | uint8_t CTRL_P:1; /* (0: read backdrop from EXT pins; 1: output color on EXT pins) */ 72 | uint8_t CTRL_V:1; /* Generate an NMI at the start of the vertical blanking interval (0: off; 1: on) */ 73 | }; 74 | uint8_t ppu_ctrl; 75 | }; 76 | union { 77 | struct { 78 | uint8_t MASK_Gr:1; /* Greyscale (0: normal color, 1: produce a greyscale display) */ 79 | uint8_t MASK_m:1; /* 1: Show background in leftmost 8 pixels of screen, 0: Hide */ 80 | uint8_t MASK_M:1; /* 1: Show sprites in leftmost 8 pixels of screen, 0: Hide */ 81 | uint8_t MASK_b:1; /* 1: Show background */ 82 | uint8_t MASK_s:1; /* 1: Show sprites */ 83 | uint8_t MASK_R:1; /* Emphasize red (green on PAL/Dendy) */ 84 | uint8_t MASK_G:1; /* Emphasize green (red on PAL/Dendy) */ 85 | uint8_t MASK_B:1; /* Emphasize blue */ 86 | }; 87 | uint8_t ppu_mask; 88 | }; 89 | union { 90 | struct { 91 | uint8_t :5; 92 | uint8_t STATUS_O:1; /* Sprite overflow. The intent was for this flag to be set 93 | whenever more than eight sprites appear on a scanline, but a 94 | hardware bug causes the actual behavior to be more complicated 95 | and generate false positives as well as false negatives; see 96 | PPU sprite evaluation. This flag is set during sprite 97 | evaluation and cleared at dot 1 (the second dot) of the 98 | pre-render line. */ 99 | uint8_t STATUS_S:1; /* Sprite 0 Hit. Set when a nonzero pixel of sprite 0 overlaps 100 | a nonzero background pixel; cleared at dot 1 of the pre-render 101 | line. Used for raster timing. */ 102 | uint8_t STATUS_V:1; /* Vertical blank has started (0: not in vblank; 1: in vblank). 103 | Set at dot 1 of line 241 (the line *after* the post-render 104 | line); cleared after reading $2002 and at dot 1 of the 105 | pre-render line. */ 106 | }; 107 | uint8_t ppu_status; 108 | }; 109 | struct { 110 | uint8_t x:3; /* Fine X scroll (3 bits) */ 111 | uint8_t w:1; /* First or second write toggle (1 bit) */ 112 | uint8_t :4;// 可利用做xxx标志位 113 | }; 114 | uint8_t oam_addr; /* OAM read/write address */ 115 | uint8_t buffer; /* PPU internal buffer */ 116 | union { 117 | struct{ // Scroll 118 | uint16_t coarse_x : 5; 119 | uint16_t coarse_y : 5; 120 | uint16_t nametable : 2; 121 | uint16_t fine_y : 3; 122 | uint16_t : 1; 123 | }v; 124 | uint16_t v_reg; /* Current VRAM address (15 bits) */ 125 | }; 126 | union { 127 | struct{ // Scroll 128 | uint16_t coarse_x : 5; 129 | uint16_t coarse_y : 5; 130 | uint16_t nametable : 2; 131 | uint16_t fine_y : 3; 132 | uint16_t : 1; 133 | }t; 134 | uint16_t t_reg; /* Temporary VRAM address (15 bits); can also be thought of as the address of the top left onscreen tile. */ 135 | }; 136 | uint8_t ppu_vram[4][NES_PPU_VRAM_SIZE/4]; // 4K: 0x1000 = 0x400 * 4 137 | union { 138 | sprite_info_t sprite_info[NES_PPU_OAM_SIZE/4]; 139 | uint8_t oam_data[NES_PPU_OAM_SIZE]; /* OAM data read/write 140 | The OAM (Object Attribute Memory) is internal memory inside the PPU that contains a display list of up to 64 sprites, 141 | where each sprite's information occupies 4 bytes.*/ 142 | }; 143 | 144 | uint8_t palette_indexes[0x20]; /* $3F00-$3F1F Palette RAM indexes */ 145 | union { 146 | struct { 147 | nes_color_t background_palette[0x10]; 148 | nes_color_t sprite_palette[0x10]; 149 | }; 150 | nes_color_t palette[0x20]; 151 | }; 152 | union { 153 | struct { 154 | uint8_t* pattern_table[8]; 155 | uint8_t* name_table[4]; 156 | uint8_t* name_table_mirrors[4]; 157 | }; 158 | uint8_t* chr_banks[16]; /* 16k chr_banks,without background_palette and sprite_palette 159 | 0 - 3 pattern_table_0 4k 160 | 4 - 7 pattern_table_1 4k 161 | 8 name_table_0 1k 162 | 9 name_table_1 1k 163 | 10 name_table_2 1k 164 | 11 name_table_3 1k 165 | 12-15 mirrors */ 166 | }; 167 | } nes_ppu_t; 168 | 169 | void nes_ppu_init(nes_t *nes); 170 | void nes_ppu_screen_mirrors(nes_t *nes,nes_mirror_type_t mirror_type); 171 | 172 | uint8_t nes_read_ppu_register(nes_t *nes,uint16_t address); 173 | void nes_write_ppu_register(nes_t *nes,uint16_t address, uint8_t data); 174 | 175 | #ifdef __cplusplus 176 | } 177 | #endif 178 | -------------------------------------------------------------------------------- /sdl/sdl2/port/nes_port.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "nes.h" 17 | 18 | #include 19 | 20 | /* memory */ 21 | void *nes_malloc(int num){ 22 | return SDL_malloc(num); 23 | } 24 | 25 | void nes_free(void *address){ 26 | SDL_free(address); 27 | } 28 | 29 | void *nes_memcpy(void *str1, const void *str2, size_t n){ 30 | return SDL_memcpy(str1, str2, n); 31 | } 32 | 33 | void *nes_memset(void *str, int c, size_t n){ 34 | return SDL_memset(str,c,n); 35 | } 36 | 37 | int nes_memcmp(const void *str1, const void *str2, size_t n){ 38 | return SDL_memcmp(str1,str2,n); 39 | } 40 | 41 | #if (NES_USE_FS == 1) 42 | /* io */ 43 | FILE *nes_fopen(const char * filename, const char * mode ){ 44 | return fopen(filename,mode); 45 | } 46 | 47 | size_t nes_fread(void *ptr, size_t size, size_t nmemb, FILE *stream){ 48 | return fread(ptr, size, nmemb,stream); 49 | } 50 | 51 | size_t nes_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream){ 52 | return fwrite(ptr, size, nmemb,stream); 53 | } 54 | 55 | int nes_fseek(FILE *stream, long int offset, int whence){ 56 | return fseek(stream,offset,whence); 57 | } 58 | 59 | int nes_fclose(FILE *stream ){ 60 | return fclose(stream); 61 | } 62 | #endif 63 | 64 | static SDL_Window *window = NULL; 65 | static SDL_Renderer *renderer = NULL; 66 | static SDL_Texture *framebuffer = NULL; 67 | 68 | static void sdl_event(nes_t *nes) { 69 | SDL_Event event; 70 | if (SDL_PollEvent(&event)){ 71 | switch (event.type) { 72 | case SDL_KEYDOWN: 73 | switch (event.key.keysym.scancode){ 74 | case 26://W 75 | nes->nes_cpu.joypad.U1 = 1; 76 | break; 77 | case 22://S 78 | nes->nes_cpu.joypad.D1 = 1; 79 | break; 80 | case 4://A 81 | nes->nes_cpu.joypad.L1 = 1; 82 | break; 83 | case 7://D 84 | nes->nes_cpu.joypad.R1 = 1; 85 | break; 86 | case 13://J 87 | nes->nes_cpu.joypad.A1 = 1; 88 | break; 89 | case 14://K 90 | nes->nes_cpu.joypad.B1 = 1; 91 | break; 92 | case 25://V 93 | nes->nes_cpu.joypad.SE1 = 1; 94 | break; 95 | case 5://B 96 | nes->nes_cpu.joypad.ST1 = 1; 97 | break; 98 | case 82://↑ 99 | nes->nes_cpu.joypad.U2 = 1; 100 | break; 101 | case 81://↓ 102 | nes->nes_cpu.joypad.D2 = 1; 103 | break; 104 | case 80://← 105 | nes->nes_cpu.joypad.L2 = 1; 106 | break; 107 | case 79://→ 108 | nes->nes_cpu.joypad.R2 = 1; 109 | break; 110 | case 93://5 111 | nes->nes_cpu.joypad.A2 = 1; 112 | break; 113 | case 94://6 114 | nes->nes_cpu.joypad.B2 = 1; 115 | break; 116 | case 89://1 117 | nes->nes_cpu.joypad.SE2 = 1; 118 | break; 119 | case 90://2 120 | nes->nes_cpu.joypad.ST2 = 1; 121 | break; 122 | default: 123 | break; 124 | } 125 | break; 126 | case SDL_KEYUP: 127 | switch (event.key.keysym.scancode){ 128 | case 26://W 129 | nes->nes_cpu.joypad.U1 = 0; 130 | break; 131 | case 22://S 132 | nes->nes_cpu.joypad.D1 = 0; 133 | break; 134 | case 4://A 135 | nes->nes_cpu.joypad.L1 = 0; 136 | break; 137 | case 7://D 138 | nes->nes_cpu.joypad.R1 = 0; 139 | break; 140 | case 13://J 141 | nes->nes_cpu.joypad.A1 = 0; 142 | break; 143 | case 14://K 144 | nes->nes_cpu.joypad.B1 = 0; 145 | break; 146 | case 25://V 147 | nes->nes_cpu.joypad.SE1 = 0; 148 | break; 149 | case 5://B 150 | nes->nes_cpu.joypad.ST1 = 0; 151 | break; 152 | case 82://↑ 153 | nes->nes_cpu.joypad.U2 = 0; 154 | break; 155 | case 81://↓ 156 | nes->nes_cpu.joypad.D2 = 0; 157 | break; 158 | case 80://← 159 | nes->nes_cpu.joypad.L2 = 0; 160 | break; 161 | case 79://→ 162 | nes->nes_cpu.joypad.R2 = 0; 163 | break; 164 | case 93://5 165 | nes->nes_cpu.joypad.A2 = 0; 166 | break; 167 | case 94://6 168 | nes->nes_cpu.joypad.B2 = 0; 169 | break; 170 | case 89://1 171 | nes->nes_cpu.joypad.SE2 = 0; 172 | break; 173 | case 90://2 174 | nes->nes_cpu.joypad.ST2 = 0; 175 | break; 176 | default: 177 | break; 178 | } 179 | break; 180 | case SDL_QUIT: 181 | nes_deinit(nes); 182 | return; 183 | } 184 | } 185 | } 186 | 187 | #if (NES_ENABLE_SOUND == 1) 188 | 189 | static SDL_AudioDeviceID nes_audio_device; 190 | #define SDL_AUDIO_NUM_CHANNELS (1) 191 | 192 | 193 | static uint8_t apu_output = 0; 194 | static void AudioCallback(void* userdata, Uint8* stream, int len) { 195 | (void)len; 196 | nes_t *nes = (nes_t*)userdata; 197 | if (apu_output){ 198 | nes_memcpy(stream, &nes->nes_apu.sample_buffer , NES_APU_SAMPLE_PER_SYNC); 199 | apu_output = 0; 200 | } 201 | } 202 | 203 | int nes_sound_output(uint8_t *buffer, size_t len){ 204 | (void)buffer; 205 | (void)len; 206 | apu_output = 1; 207 | return 0; 208 | } 209 | #endif 210 | 211 | int nes_initex(nes_t *nes){ 212 | if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_JOYSTICK| SDL_INIT_TIMER)) { 213 | SDL_Log("Can not init video, %s", SDL_GetError()); 214 | return -1; 215 | } 216 | window = SDL_CreateWindow( 217 | NES_NAME, 218 | SDL_WINDOWPOS_UNDEFINED, 219 | SDL_WINDOWPOS_UNDEFINED, 220 | NES_WIDTH * 2, NES_HEIGHT * 2, // 二倍分辨率 221 | SDL_WINDOW_SHOWN|SDL_WINDOW_ALLOW_HIGHDPI 222 | ); 223 | if (window == NULL) { 224 | SDL_Log("Can not create window, %s", SDL_GetError()); 225 | return -1; 226 | } 227 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE); 228 | framebuffer = SDL_CreateTexture(renderer, 229 | SDL_PIXELFORMAT_ARGB8888, 230 | SDL_TEXTUREACCESS_STREAMING, 231 | NES_WIDTH, 232 | NES_HEIGHT); 233 | #if (NES_ENABLE_SOUND == 1) 234 | SDL_AudioSpec desired = { 235 | .freq = NES_APU_SAMPLE_RATE, 236 | .format = AUDIO_S8, 237 | .channels = SDL_AUDIO_NUM_CHANNELS, 238 | .samples = NES_APU_SAMPLE_PER_SYNC, 239 | .callback = AudioCallback, 240 | .userdata = nes 241 | }; 242 | nes_audio_device = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desired, NULL, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); 243 | if (!nes_audio_device) { 244 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError()); 245 | } 246 | SDL_PauseAudioDevice(nes_audio_device, SDL_FALSE); 247 | #endif 248 | return 0; 249 | } 250 | 251 | int nes_deinitex(nes_t *nes){ 252 | (void)nes; 253 | SDL_DestroyTexture(framebuffer); 254 | SDL_DestroyRenderer(renderer); 255 | SDL_DestroyWindow(window); 256 | SDL_Quit(); 257 | return 0; 258 | } 259 | 260 | int nes_draw(int x1, int y1, int x2, int y2, nes_color_t* color_data){ 261 | if (!framebuffer){ 262 | return -1; 263 | } 264 | SDL_Rect rect; 265 | rect.x = x1; 266 | rect.y = y1; 267 | rect.w = x2 - x1 + 1; 268 | rect.h = y2 - y1 + 1; 269 | SDL_UpdateTexture(framebuffer, &rect, color_data, rect.w * 4); 270 | return 0; 271 | } 272 | 273 | #define FRAMES_PER_SECOND 1000/60 274 | 275 | void nes_frame(nes_t* nes){ 276 | SDL_RenderCopy(renderer, framebuffer, NULL, NULL); 277 | SDL_RenderPresent(renderer); 278 | sdl_event(nes); 279 | SDL_Delay(FRAMES_PER_SECOND); 280 | } 281 | -------------------------------------------------------------------------------- /sdl/sdl3/port/nes_port.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "nes.h" 17 | 18 | #include 19 | 20 | /* memory */ 21 | void *nes_malloc(int num){ 22 | return SDL_malloc(num); 23 | } 24 | 25 | void nes_free(void *address){ 26 | SDL_free(address); 27 | } 28 | 29 | void *nes_memcpy(void *str1, const void *str2, size_t n){ 30 | return SDL_memcpy(str1, str2, n); 31 | } 32 | 33 | void *nes_memset(void *str, int c, size_t n){ 34 | return SDL_memset(str,c,n); 35 | } 36 | 37 | int nes_memcmp(const void *str1, const void *str2, size_t n){ 38 | return SDL_memcmp(str1,str2,n); 39 | } 40 | 41 | #if (NES_USE_FS == 1) 42 | /* io */ 43 | FILE *nes_fopen(const char * filename, const char * mode ){ 44 | return fopen(filename,mode); 45 | } 46 | 47 | size_t nes_fread(void *ptr, size_t size, size_t nmemb, FILE *stream){ 48 | return fread(ptr, size, nmemb,stream); 49 | } 50 | 51 | size_t nes_fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream){ 52 | return fwrite(ptr, size, nmemb,stream); 53 | } 54 | 55 | int nes_fseek(FILE *stream, long int offset, int whence){ 56 | return fseek(stream,offset,whence); 57 | } 58 | 59 | int nes_fclose(FILE *stream ){ 60 | return fclose(stream); 61 | } 62 | #endif 63 | 64 | static SDL_Window *window = NULL; 65 | static SDL_Renderer *renderer = NULL; 66 | static SDL_Texture *framebuffer = NULL; 67 | 68 | static void sdl_event(nes_t *nes) { 69 | SDL_Event event; 70 | if (SDL_PollEvent(&event)){ 71 | switch (event.type) { 72 | case SDL_EVENT_KEY_DOWN: 73 | switch (event.key.scancode){ 74 | case 26://W 75 | nes->nes_cpu.joypad.U1 = 1; 76 | break; 77 | case 22://S 78 | nes->nes_cpu.joypad.D1 = 1; 79 | break; 80 | case 4://A 81 | nes->nes_cpu.joypad.L1 = 1; 82 | break; 83 | case 7://D 84 | nes->nes_cpu.joypad.R1 = 1; 85 | break; 86 | case 13://J 87 | nes->nes_cpu.joypad.A1 = 1; 88 | break; 89 | case 14://K 90 | nes->nes_cpu.joypad.B1 = 1; 91 | break; 92 | case 25://V 93 | nes->nes_cpu.joypad.SE1 = 1; 94 | break; 95 | case 5://B 96 | nes->nes_cpu.joypad.ST1 = 1; 97 | break; 98 | case 82://↑ 99 | nes->nes_cpu.joypad.U2 = 1; 100 | break; 101 | case 81://↓ 102 | nes->nes_cpu.joypad.D2 = 1; 103 | break; 104 | case 80://← 105 | nes->nes_cpu.joypad.L2 = 1; 106 | break; 107 | case 79://→ 108 | nes->nes_cpu.joypad.R2 = 1; 109 | break; 110 | case 93://5 111 | nes->nes_cpu.joypad.A2 = 1; 112 | break; 113 | case 94://6 114 | nes->nes_cpu.joypad.B2 = 1; 115 | break; 116 | case 89://1 117 | nes->nes_cpu.joypad.SE2 = 1; 118 | break; 119 | case 90://2 120 | nes->nes_cpu.joypad.ST2 = 1; 121 | break; 122 | default: 123 | break; 124 | } 125 | break; 126 | case SDL_EVENT_KEY_UP: 127 | switch (event.key.scancode){ 128 | case 26://W 129 | nes->nes_cpu.joypad.U1 = 0; 130 | break; 131 | case 22://S 132 | nes->nes_cpu.joypad.D1 = 0; 133 | break; 134 | case 4://A 135 | nes->nes_cpu.joypad.L1 = 0; 136 | break; 137 | case 7://D 138 | nes->nes_cpu.joypad.R1 = 0; 139 | break; 140 | case 13://J 141 | nes->nes_cpu.joypad.A1 = 0; 142 | break; 143 | case 14://K 144 | nes->nes_cpu.joypad.B1 = 0; 145 | break; 146 | case 25://V 147 | nes->nes_cpu.joypad.SE1 = 0; 148 | break; 149 | case 5://B 150 | nes->nes_cpu.joypad.ST1 = 0; 151 | break; 152 | case 82://↑ 153 | nes->nes_cpu.joypad.U2 = 0; 154 | break; 155 | case 81://↓ 156 | nes->nes_cpu.joypad.D2 = 0; 157 | break; 158 | case 80://← 159 | nes->nes_cpu.joypad.L2 = 0; 160 | break; 161 | case 79://→ 162 | nes->nes_cpu.joypad.R2 = 0; 163 | break; 164 | case 93://5 165 | nes->nes_cpu.joypad.A2 = 0; 166 | break; 167 | case 94://6 168 | nes->nes_cpu.joypad.B2 = 0; 169 | break; 170 | case 89://1 171 | nes->nes_cpu.joypad.SE2 = 0; 172 | break; 173 | case 90://2 174 | nes->nes_cpu.joypad.ST2 = 0; 175 | break; 176 | default: 177 | break; 178 | } 179 | break; 180 | case SDL_EVENT_QUIT: 181 | nes_deinit(nes); 182 | return; 183 | } 184 | } 185 | } 186 | 187 | #if (NES_ENABLE_SOUND == 1) 188 | 189 | #define SDL_AUDIO_NUM_CHANNELS (1) 190 | static SDL_AudioStream* nes_audio_stream = NULL; 191 | 192 | static uint8_t apu_output = 0; 193 | static void AudioCallback(void* userdata, SDL_AudioStream* astream, int additional_amount, int total_amount) { 194 | (void)total_amount; 195 | nes_t *nes = (nes_t*)userdata; 196 | static int total = NES_APU_SAMPLE_PER_SYNC; 197 | if (apu_output){ 198 | uint8_t samples[441] = {0}; 199 | uint8_t* nes_sample_buffer = &nes->nes_apu.sample_buffer; 200 | int StreamSend = SDL_min(additional_amount, total); 201 | 202 | nes_memcpy(samples, (StreamSend == additional_amount)? nes_sample_buffer : (nes_sample_buffer)+additional_amount, StreamSend); 203 | SDL_PutAudioStreamData(astream, samples, StreamSend); 204 | 205 | total -= StreamSend; 206 | if (total == 0){ 207 | total = NES_APU_SAMPLE_PER_SYNC; 208 | apu_output = 0; 209 | } 210 | } 211 | } 212 | 213 | int nes_sound_output(uint8_t *buffer, size_t len){ 214 | (void)buffer; 215 | (void)len; 216 | apu_output = 1; 217 | return 0; 218 | } 219 | #endif 220 | 221 | int nes_initex(nes_t *nes){ 222 | SDL_SetAppMetadata(NES_NAME, NES_VERSION_STRING, NES_URL); 223 | if (!SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_JOYSTICK| SDL_INIT_EVENTS)) { 224 | SDL_Log("Can not init video, %s", SDL_GetError()); 225 | return -1; 226 | } 227 | if (!SDL_CreateWindowAndRenderer(NES_NAME,NES_WIDTH * 2, NES_HEIGHT * 2, // 二倍分辨率 228 | SDL_WINDOW_OCCLUDED|SDL_WINDOW_HIGH_PIXEL_DENSITY, 229 | &window,&renderer)) { 230 | SDL_Log("Can not create window, %s", SDL_GetError()); 231 | return -1; 232 | } 233 | framebuffer = SDL_CreateTexture(renderer, 234 | SDL_PIXELFORMAT_ARGB8888, 235 | SDL_TEXTUREACCESS_STREAMING, 236 | NES_WIDTH, 237 | NES_HEIGHT); 238 | #if (NES_ENABLE_SOUND == 1) 239 | SDL_AudioSpec spec = { 240 | .freq = NES_APU_SAMPLE_RATE, 241 | .format = SDL_AUDIO_S8, 242 | .channels = SDL_AUDIO_NUM_CHANNELS, 243 | }; 244 | nes_audio_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &spec, AudioCallback, nes); 245 | if (!nes_audio_stream) { 246 | SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't open audio: %s\n", SDL_GetError()); 247 | } 248 | SDL_ResumeAudioStreamDevice(nes_audio_stream); 249 | #endif 250 | return 0; 251 | } 252 | 253 | int nes_deinitex(nes_t *nes){ 254 | (void)nes; 255 | SDL_DestroyTexture(framebuffer); 256 | SDL_DestroyRenderer(renderer); 257 | SDL_DestroyWindow(window); 258 | SDL_Quit(); 259 | return 0; 260 | } 261 | 262 | int nes_draw(int x1, int y1, int x2, int y2, nes_color_t* color_data){ 263 | if (!framebuffer){ 264 | return -1; 265 | } 266 | SDL_Rect rect; 267 | rect.x = x1; 268 | rect.y = y1; 269 | rect.w = x2 - x1 + 1; 270 | rect.h = y2 - y1 + 1; 271 | SDL_UpdateTexture(framebuffer, &rect, color_data, rect.w * 4); 272 | return 0; 273 | } 274 | 275 | #define FRAMES_PER_SECOND 1000/60 276 | 277 | void nes_frame(nes_t* nes){ 278 | SDL_RenderTexture(renderer, framebuffer, NULL, NULL); 279 | SDL_RenderPresent(renderer); 280 | sdl_event(nes); 281 | SDL_Delay(FRAMES_PER_SECOND); 282 | } 283 | 284 | 285 | -------------------------------------------------------------------------------- /inc/nes_apu.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #define NES_APU_SAMPLE_RATE (44100) 23 | #define NES_APU_SAMPLE_PER_SYNC (NES_APU_SAMPLE_RATE/60) 24 | 25 | struct nes; 26 | typedef struct nes nes_t; 27 | 28 | // https://www.nesdev.org/wiki/APU 29 | // https://www.nesdev.org/apu_ref.txt 30 | 31 | // https://www.nesdev.org/wiki/APU#Pulse_($4000-$4007) 32 | typedef struct { 33 | union { 34 | struct { 35 | uint8_t envelope_lowers:4; /* Sets the direct volume if constant, otherwise controls the rate which the envelope lowers. 36 | VVVV: 0000=silence 1111=maximum */ 37 | uint8_t constant_volume:1; /* If C is set the volume will be a constant. If clear, an envelope will be used, 38 | starting at volume 15 and lowering to 0 over time. */ 39 | uint8_t len_counter_halt:1; /* 1 = Infinite play, 0 = One-shot. If 1, the length counter will be frozen at its current value, 40 | and the envelope will repeat forever. 41 | The length counter and envelope units are clocked by the frame counter. 42 | If the length counter's current value is 0 the channel will be silenced whether or not this bit is set. 43 | When using a one-shot envelope, the length counter should be loaded with a time longer than the length of the envelope to prevent it from being cut off early. 44 | When looping, after reaching 0 the envelope will restart at volume 15 at its next period. */ 45 | uint8_t duty:2; /* The width of the pulse is controlled by the duty bits in $4000/$4004. See APU Pulse for details. 46 | DD: 00=12.5% 01=25% 10=50% 11=75% */ 47 | }; /* $4000/$4004 */ 48 | uint8_t control0; 49 | }; 50 | union { 51 | struct { 52 | uint8_t shift:3; /* Shift count (number of bits). 53 | If SSS is 0, then behaves like E=0. */ 54 | uint8_t negate:1; /* Negate flag 55 | 0: add to period, sweeping toward lower frequencies 56 | 1: subtract from period, sweeping toward higher frequencies */ 57 | uint8_t period:3; /* The divider's period is P + 1 half-frames */ 58 | uint8_t enabled:1; /* Enabled flag */ 59 | }; /* $4001 Sweep unit Side effects:Sets the reload flag */ 60 | uint8_t control1; 61 | }; 62 | 63 | uint8_t timer_low; /* Timer low (T) Low 8 bits of raw period */ 64 | union { 65 | struct { 66 | uint8_t timer_high:3; /* timer high High 3 bits of raw period */ 67 | uint8_t len_counter_load:5; /* Length counter load */ 68 | }; 69 | uint8_t control3; 70 | }; 71 | uint8_t length_counter; 72 | uint8_t sweep_reload; 73 | uint8_t envelope_restart; 74 | uint16_t cur_period; 75 | uint8_t sweep_divider; 76 | uint8_t envelope_divider; 77 | uint8_t envelope_volume; 78 | uint8_t sample_buffer[NES_APU_SAMPLE_PER_SYNC]; 79 | uint16_t sample_index; 80 | float seq_local_old; 81 | } pulse_t; 82 | 83 | // https://www.nesdev.org/wiki/APU#Triangle_($4008-$400B) 84 | typedef struct { 85 | union { 86 | struct { 87 | uint8_t linear_counter_load:7; /* This reload value will be applied to the linear counter on the next frame counter tick, but only if its reload flag is set. 88 | A write to $400B is needed to raise the reload flag. 89 | After a frame counter tick applies the load value R, the reload flag will only be cleared if C is also clear, otherwise it will continually reload (i.e. halt). */ 90 | uint8_t len_counter_halt:1; /* This bit controls both the length counter and linear counter at the same time. 91 | When set this will stop the length counter in the same way as for the pulse/noise channels. 92 | When set it prevents the linear counter's internal reload flag from clearing, which effectively halts it if $400B is written after setting C. 93 | The linear counter silences the channel after a specified time with a resolution of 240Hz in NTSC (see frame counter below). 94 | Because both the length and linear counters are be enabled at the same time, whichever has a longer setting is redundant. 95 | See APU Triangle for more linear counter details. */ 96 | }; 97 | uint8_t control0; 98 | }; 99 | 100 | uint8_t timer_low; /* Timer low Low 8 bits of raw period */ 101 | union { 102 | struct { 103 | uint8_t timer_high:3; /* timer high High 3 bits of raw period */ 104 | uint8_t len_counter_load:5; /* Length counter load */ 105 | }; 106 | uint8_t control3; 107 | }; 108 | uint8_t length_counter; 109 | uint8_t linear_counter; 110 | uint8_t linear_restart; 111 | uint16_t cur_period; 112 | uint8_t sample_buffer[NES_APU_SAMPLE_PER_SYNC]; 113 | uint16_t sample_index; 114 | float seq_local_old; 115 | } triangle_t; 116 | 117 | // https://www.nesdev.org/wiki/APU#Noise_($400C-$400F) 118 | typedef struct { 119 | union { 120 | struct { 121 | uint8_t volume_envelope:4; /* volume/envelope (V) VVVV: 0000=silence 1111=maximum */ 122 | uint8_t constant_volume:1; /* constant volume (C) */ 123 | uint8_t len_counter_halt:1; /* length counter halt (L) */ 124 | }; // Envelope loop 125 | uint8_t control0; 126 | }; 127 | union { 128 | struct { 129 | uint8_t noise_period:4; /* noise period (P) Period */ 130 | uint8_t :3; 131 | uint8_t loop_noise:1; /* Loop noise (L) Tone mode enable */ 132 | }; 133 | uint8_t control2; 134 | }; 135 | union { 136 | struct { 137 | uint8_t :3; 138 | uint8_t len_counter_load:5; /* Length counter load (L) */ 139 | }; 140 | uint8_t control3; 141 | }; 142 | union { 143 | struct { 144 | uint16_t lfsr_d0:1; 145 | uint16_t lfsr_d1:1; 146 | uint16_t :4; 147 | uint16_t lfsr_d6:1; 148 | uint16_t :9; 149 | }; 150 | uint16_t lfsr; 151 | }; 152 | uint8_t length_counter; 153 | uint8_t envelope_restart; 154 | uint8_t envelope_divider; 155 | uint8_t envelope_volume; 156 | uint8_t sample_buffer[NES_APU_SAMPLE_PER_SYNC]; 157 | uint16_t sample_index; 158 | } noise_t; 159 | 160 | typedef struct { 161 | union { 162 | struct { 163 | uint8_t frequency:4; /* frequency (R) */ 164 | uint8_t :2; 165 | uint8_t loop:1; /* loop (L) */ 166 | uint8_t irq_enable:1; /* IRQ enable (I) */ 167 | }; 168 | uint8_t control0; 169 | }; 170 | union { 171 | struct { 172 | uint8_t load_counter:7; /* Load counter (D) */ 173 | uint8_t :1; 174 | }; 175 | uint8_t control1; 176 | }; 177 | uint8_t sample_address; /* Sample address (A) */ 178 | uint8_t sample_length; /* Sample length (L) */ 179 | uint8_t sample_buffer[NES_APU_SAMPLE_PER_SYNC]; 180 | uint16_t sample_index; 181 | } dmc_t; 182 | 183 | // https://www.nesdev.org/wiki/APU#Registers 184 | typedef struct nes_apu{ 185 | pulse_t pulse1; /* $4000–$4003 First pulse wave */ 186 | pulse_t pulse2; /* $4004–$4007 Second pulse wave */ 187 | triangle_t triangle; /* $4008–$400B Triangle wave */ 188 | noise_t noise; /* $400C–$400F Noise */ 189 | dmc_t dmc; /* $4010–$4013 DMC (sample playback). */ 190 | union { 191 | struct { 192 | uint8_t status_pulse1:1; /* write:Enable pulse1 channels read:length counter of pulse1 */ 193 | uint8_t status_pulse2:1; /* write:Enable pulse2 channels read:length counter of pulse2 */ 194 | uint8_t status_triangle:1; /* write:Enable triangle channels read:length counter of triangle */ 195 | uint8_t status_noise:1; /* write:Enable noise channels read:length counter of noise */ 196 | uint8_t status_dmc:1; /* write:Enable dmc channels read:dmc active */ 197 | uint8_t :1; 198 | uint8_t frame_interrupt:1; /* frame interrupt (F) only read */ 199 | uint8_t dmc_interrupt:1; /* DMC interrupt (I) only read */ 200 | }; 201 | uint8_t status; /* Status ($4015) 202 | The status register is used to enable and disable individual channels, 203 | control the DMC, and can read the status of length counters and APU interrupts. */ 204 | }; 205 | 206 | union { 207 | struct { 208 | uint8_t :6; 209 | uint8_t irq_inhibit_flag:1; /* IRQ inhibit flag (I)*/ 210 | uint8_t mode:1; /* Mode (M, 0 = 4-step, 1 = 5-step) */ 211 | }; 212 | uint8_t frame_counter; // Frame Counter ($4017) 213 | }; 214 | 215 | uint64_t clock_count; 216 | // sample_buffer: pulse1 pulse2 triangle noise dmc output 217 | uint8_t sample_buffer[NES_APU_SAMPLE_PER_SYNC]; 218 | uint16_t sample_index; 219 | uint64_t cpu_lock_count; 220 | uint64_t sample_local_start; 221 | uint64_t sample_local_end; 222 | } nes_apu_t; 223 | 224 | void nes_apu_init(nes_t *nes); 225 | void nes_apu_frame(nes_t *nes); 226 | uint8_t nes_read_apu_register(nes_t *nes,uint16_t address); 227 | void nes_write_apu_register(nes_t* nes,uint16_t address,uint8_t data); 228 | 229 | #ifdef __cplusplus 230 | } 231 | #endif 232 | 233 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/nes.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | //https://www.nesdev.org/pal.txt 20 | 21 | static nes_color_t nes_palette[]={ 22 | #if (NES_COLOR_DEPTH == 32) // ARGB8888 23 | 0xFF757575, 0xFF271B8F, 0xFF0000AB, 0xFF47009F, 0xFF8F0077, 0xFFB0013, 0xFFA70000, 0xFF7F0B00,0xFF432F00, 0xFF004700, 0xFF005100, 0xFF003F17, 0xFF1B3F5F, 0xFF000000, 0xFF000000, 0xFF000000, 24 | 0xFFBCBCBC, 0xFF0073EF, 0xFF233BEF, 0xFF8300F3, 0xFFBF00BF, 0xFF7005B, 0xFFDB2B00, 0xFFCB4F0F,0xFF8B7300, 0xFF009700, 0xFF00AB00, 0xFF00933B, 0xFF00838B, 0xFF000000, 0xFF000000, 0xFF000000, 25 | 0xFFFFFFFF, 0xFF3FBFFF, 0xFF5F97FF, 0xFFA78BFD, 0xFFF77BFF, 0xFFF77B7, 0xFFFF7763, 0xFFFF9B3B,0xFFF3BF3F, 0xFF83D313, 0xFF4FDF4B, 0xFF58F898, 0xFF00EBDB, 0xFF000000, 0xFF000000, 0xFF000000, 26 | 0xFFFFFFFF, 0xFFABE7FF, 0xFFC7D7FF, 0xFFD7CBFF, 0xFFFFC7FF, 0xFFFC7DB, 0xFFFFBFB3, 0xFFFFDBAB,0xFFFFE7A3, 0xFFE3FFA3, 0xFFABF3BF, 0xFFB3FFCF, 0xFF9FFFF3, 0xFF000000, 0xFF000000, 0xFF000000, 27 | #elif (NES_COLOR_DEPTH == 16) 28 | #if (NES_COLOR_SWAP == 0) // RGB565 29 | 0x73AE, 0x20D1, 0x0015, 0x4013, 0x880E, 0x0802, 0xA000, 0x7840,0x4160, 0x0220, 0x0280, 0x01E2, 0x19EB, 0x0000, 0x0000, 0x0000, 30 | 0xBDF7, 0x039D, 0x21DD, 0x801E, 0xB817, 0x000B, 0xD940, 0xCA61,0x8B80, 0x04A0, 0x0540, 0x0487, 0x0411, 0x0000, 0x0000, 0x0000, 31 | 0xFFFF, 0x3DFF, 0x5CBF, 0xA45F, 0xF3DF, 0x0BB6, 0xFBAC, 0xFCC7,0xF5E7, 0x8682, 0x4EE9, 0x5FD3, 0x075B, 0x0000, 0x0000, 0x0000, 32 | 0xFFFF, 0xAF3F, 0xC6BF, 0xD65F, 0xFE3F, 0x0E3B, 0xFDF6, 0xFED5,0xFF34, 0xE7F4, 0xAF97, 0xB7F9, 0x9FFE, 0x0000, 0x0000, 0x0000, 33 | #else // RGB565_SWAP 34 | 0xAE73, 0xD120, 0x1500, 0x1340, 0x0E88, 0x0208, 0x00A0, 0x4078,0x6041, 0x2002, 0x8002, 0xE201, 0xEB19, 0x0000, 0x0000, 0x0000, 35 | 0xF7BD, 0x9D03, 0xDD21, 0x1E80, 0x17B8, 0x0B00, 0x40D9, 0x61CA,0x808B, 0xA004, 0x4005, 0x8704, 0x1104, 0x0000, 0x0000, 0x0000, 36 | 0xFFFF, 0xFF3D, 0xBF5C, 0x5FA4, 0xDFF3, 0xB60B, 0xACFB, 0xC7FC,0xE7F5, 0x8286, 0xE94E, 0xD35F, 0x5B07, 0x0000, 0x0000, 0x0000, 37 | 0xFFFF, 0x3FAF, 0xBFC6, 0x5FD6, 0x3FFE, 0x3B0E, 0xF6FD, 0xD5FE,0x34FF, 0xF4E7, 0x97AF, 0xF9B7, 0xFE9F, 0x0000, 0x0000, 0x0000, 38 | #endif /* NES_COLOR_SWAP */ 39 | #endif /* NES_COLOR_DEPTH */ 40 | }; 41 | 42 | nes_t* nes_init(void){ 43 | nes_t* nes = (nes_t *)nes_malloc(sizeof(nes_t)); 44 | if (nes == NULL) { 45 | return NULL; 46 | } 47 | nes_memset(nes, 0, sizeof(nes_t)); 48 | nes_initex(nes); 49 | return nes; 50 | } 51 | 52 | int nes_deinit(nes_t *nes){ 53 | nes->nes_quit = 1; 54 | nes_deinitex(nes); 55 | if (nes){ 56 | nes_free(nes); 57 | nes = NULL; 58 | } 59 | return NES_OK; 60 | } 61 | 62 | static inline void nes_palette_generate(nes_t* nes){ 63 | for (uint8_t i = 0; i < 32; i++) { 64 | nes->nes_ppu.palette[i] = nes_palette[nes->nes_ppu.palette_indexes[i]]; 65 | } 66 | for (uint8_t i = 1; i < 8; i++){ 67 | nes->nes_ppu.palette[4 * i] = nes->nes_ppu.palette[0]; 68 | } 69 | } 70 | 71 | static void nes_render_background_line(nes_t* nes,uint16_t scanline,nes_color_t* draw_data){ 72 | (void)scanline; 73 | uint8_t p = 0; 74 | int8_t m = 7 - nes->nes_ppu.x; 75 | const uint8_t dx = (const uint8_t)nes->nes_ppu.v.coarse_x; 76 | const uint8_t dy = (const uint8_t)nes->nes_ppu.v.fine_y; 77 | const uint8_t tile_y = (const uint8_t)nes->nes_ppu.v.coarse_y; 78 | uint8_t nametable_id = (uint8_t)nes->nes_ppu.v.nametable; 79 | for (uint8_t tile_x = dx; tile_x < 32; tile_x++){ 80 | const uint8_t pattern_id = nes->nes_ppu.name_table[nametable_id][tile_x + (tile_y << 5)]; 81 | // if (pattern_id == 0x40){ 82 | // uint8_t tile_x1 = (tile_x + 1) & 0x1F; 83 | // NES_LOG_DEBUG("scanline:%d pattern_id:0x%02x dx:%d dy:%d tile_x:%d tile_y:%d\n",scanline,pattern_id,dx,dy,tile_x,tile_y); 84 | // NES_LOG_DEBUG("tile_x1:%d\n",tile_x1); 85 | // } 86 | // if (scanline == 170 && tile_x == 10) 87 | // { 88 | // printf("scanline:%d pattern_id:0x%02x dx:%d dy:%d tile_x:%d tile_y:%d\n",scanline,pattern_id,dx,dy,tile_x,tile_y); 89 | // } 90 | 91 | const uint8_t* bit0_p = nes->nes_ppu.pattern_table[nes->nes_ppu.CTRL_B ? 4 : 0] + pattern_id * 16; 92 | const uint8_t* bit1_p = bit0_p + 8; 93 | const uint8_t bit0 = bit0_p[dy]; 94 | const uint8_t bit1 = bit1_p[dy]; 95 | const uint8_t attribute = nes->nes_ppu.name_table[nametable_id][960 + ((tile_y >> 2) << 3) + (tile_x >> 2)]; 96 | // 1:D4-D5/D6-D7 0:D0-D1/D2-D3 97 | // 1:D2-D3/D6-D7 0:D0-D1/D4-D5 98 | const uint8_t high_bit = ((attribute >> (((tile_y & 2) << 1) | (tile_x & 2))) & 3) << 2; 99 | for (; m >= 0; m--){ 100 | uint8_t low_bit = ((bit0 >> m) & 0x01) | ((bit1 >> m)<<1 & 0x02); 101 | uint8_t palette_index = (high_bit & 0x0c) | low_bit; 102 | draw_data[p++] = nes->nes_ppu.background_palette[palette_index]; 103 | } 104 | m = 7; 105 | } 106 | nametable_id ^= 1; 107 | for (uint8_t tile_x = 0; tile_x <= dx; tile_x++){ 108 | const uint8_t pattern_id = nes->nes_ppu.name_table[nametable_id][tile_x + (tile_y << 5)]; 109 | const uint8_t* bit0_p = nes->nes_ppu.pattern_table[nes->nes_ppu.CTRL_B ? 4 : 0] + pattern_id * 16; 110 | const uint8_t* bit1_p = bit0_p + 8; 111 | const uint8_t bit0 = bit0_p[dy]; 112 | const uint8_t bit1 = bit1_p[dy]; 113 | const uint8_t attribute = nes->nes_ppu.name_table[nametable_id][960 + ((tile_y >> 2) << 3) + (tile_x >> 2)]; 114 | // 1:D4-D5/D6-D7 0:D0-D1/D2-D3 115 | // 1:D2-D3/D6-D7 0:D0-D1/D4-D5 116 | const uint8_t high_bit = ((attribute >> (((tile_y & 2) << 1) | (tile_x & 2))) & 3) << 2; 117 | uint8_t skew = 0; 118 | if (tile_x == dx){ 119 | if (nes->nes_ppu.x){ 120 | skew = 8 - nes->nes_ppu.x; 121 | }else 122 | break; 123 | } 124 | for (; m >= skew; m--){ 125 | const uint8_t low_bit = ((bit0 >> m) & 0x01) | ((bit1 >> m)<<1 & 0x02); 126 | const uint8_t palette_index = (high_bit & 0x0c) | low_bit; 127 | draw_data[p++] = nes->nes_ppu.background_palette[palette_index]; 128 | } 129 | m = 7; 130 | } 131 | } 132 | 133 | static void nes_render_sprite_line(nes_t* nes,uint16_t scanline,nes_color_t* draw_data){ 134 | const nes_color_t background_color = nes->nes_ppu.background_palette[0]; 135 | uint8_t sprite[8] = {0}; 136 | uint8_t sprite_numbers = 0; 137 | const uint8_t sprite_size = nes->nes_ppu.CTRL_H?16:8; 138 | 139 | // 遍历显示的精灵和检测是否精灵溢出 140 | for (uint8_t i = 0; i < 64; i++){ 141 | if (nes->nes_ppu.sprite_info[i].y >= 0xEF){ 142 | continue; 143 | } 144 | uint8_t sprite_y = (uint8_t)(nes->nes_ppu.sprite_info[i].y + 1); 145 | if (scanline < sprite_y || scanline >= sprite_y + sprite_size){ 146 | continue; 147 | } 148 | if (sprite_numbers==8){ 149 | nes->nes_ppu.STATUS_O = 1; 150 | break; 151 | } 152 | sprite[sprite_numbers++]=i; 153 | } 154 | // 显示精灵 155 | for (uint8_t sprite_number = sprite_numbers; sprite_number > 0; sprite_number--){ 156 | const uint8_t sprite_id = sprite[sprite_number-1]; 157 | const sprite_info_t sprite_info = nes->nes_ppu.sprite_info[sprite_id]; 158 | const uint8_t sprite_y = (uint8_t)(sprite_info.y + 1); 159 | const uint8_t* sprite_bit0_p = nes->nes_ppu.pattern_table[nes->nes_ppu.CTRL_H?((sprite_info.pattern_8x16)?4:0):(nes->nes_ppu.CTRL_S?4:0)] \ 160 | + (nes->nes_ppu.CTRL_H?(sprite_info.tile_index_8x16 << 1 ):(sprite_info.tile_index_number)) * 16; 161 | const uint8_t* sprite_bit1_p = sprite_bit0_p + 8; 162 | 163 | uint8_t dy = (uint8_t)(scanline - sprite_y); 164 | 165 | if (nes->nes_ppu.CTRL_H){ 166 | if (sprite_info.flip_v){ 167 | if (dy < 8){ 168 | sprite_bit0_p +=16; 169 | sprite_bit1_p +=16; 170 | dy = sprite_size - dy - 1 -8; 171 | }else{ 172 | dy = sprite_size - dy - 1; 173 | } 174 | }else{ 175 | if (dy > 7){ 176 | sprite_bit0_p +=16; 177 | sprite_bit1_p +=16; 178 | dy-=8; 179 | } 180 | } 181 | }else{ 182 | if (sprite_info.flip_v){ 183 | dy = sprite_size - dy - 1; 184 | } 185 | } 186 | 187 | const uint8_t sprite_bit0 = sprite_bit0_p[dy]; 188 | const uint8_t sprite_bit1 = sprite_bit1_p[dy]; 189 | #if (NES_FRAME_SKIP != 0) 190 | if(nes->nes_frame_skip_count == 0) 191 | #endif 192 | { 193 | uint8_t p = sprite_info.x; 194 | if (sprite_info.flip_h){ 195 | for (int8_t m = 0; m <= 7; m++){ 196 | const uint8_t low_bit = ((sprite_bit0 >> m) & 0x01) | ((sprite_bit1 >> m)<<1 & 0x02); 197 | const uint8_t palette_index = (sprite_info.sprite_palette << 2) | low_bit; 198 | if (palette_index%4 != 0){ 199 | if (sprite_info.priority){ 200 | if (draw_data[p] == background_color){ 201 | draw_data[p] = nes->nes_ppu.sprite_palette[palette_index]; 202 | } 203 | }else{ 204 | draw_data[p] = nes->nes_ppu.sprite_palette[palette_index]; 205 | } 206 | } 207 | if (p == 255) 208 | break; 209 | p++; 210 | } 211 | }else{ 212 | for (int8_t m = 7; m >= 0; m--){ 213 | const uint8_t low_bit = ((sprite_bit0 >> m) & 0x01) | ((sprite_bit1 >> m)<<1 & 0x02); 214 | const uint8_t palette_index = (sprite_info.sprite_palette << 2) | low_bit; 215 | if (palette_index%4 != 0){ 216 | if (sprite_info.priority){ 217 | if (draw_data[p] == background_color){ 218 | draw_data[p] = nes->nes_ppu.sprite_palette[palette_index]; 219 | } 220 | }else{ 221 | draw_data[p] = nes->nes_ppu.sprite_palette[palette_index]; 222 | } 223 | } 224 | if (p == 255) 225 | break; 226 | p++; 227 | } 228 | } 229 | } 230 | // 检测精灵0命中 231 | if (sprite_id==0){ 232 | const uint8_t sprite_date = sprite_bit0 | sprite_bit1; 233 | if (sprite_date && nes->nes_ppu.MASK_b && nes->nes_ppu.STATUS_S == 0){ 234 | // printf("scanline:%d x:%d MASK_m:%d MASK_M:%d\n", 235 | // scanline,nes->nes_ppu.x,nes->nes_ppu.MASK_m,nes->nes_ppu.MASK_M); 236 | const uint8_t nametable_id = (uint8_t)nes->nes_ppu.v.nametable; 237 | const uint8_t tile_x = (nes->nes_ppu.sprite_info[0].x) >> 3; 238 | const uint8_t tile_y = (uint8_t)(scanline >> 3); 239 | const uint8_t pattern_id = nes->nes_ppu.name_table[nametable_id][tile_x + (tile_y << 5)]; 240 | const uint8_t* bit0_p = nes->nes_ppu.pattern_table[nes->nes_ppu.CTRL_B ? 4 : 0] + pattern_id * 16; 241 | const uint8_t* bit1_p = bit0_p + 8; 242 | const uint8_t background_date = bit0_p[dy] | bit1_p[dy] << 1; 243 | if (sprite_date & background_date){ 244 | nes->nes_ppu.STATUS_S = 1; 245 | // printf("scanline:%d sprite_bit0:%d sprite_bit1:%d sprite_date:%d bit0_p:%d bit1_p:%d background_date:%d \n", 246 | // scanline,sprite_bit0,sprite_bit1,sprite_date,bit0_p[dy],bit1_p[dy],background_date); 247 | } 248 | } 249 | } 250 | 251 | } 252 | } 253 | 254 | // https://www.nesdev.org/wiki/PPU_rendering 255 | 256 | 257 | // static void nes_background_pattern_test(nes_t* nes){ 258 | // nes_palette_generate(nes); 259 | // nes_memset(nes->nes_draw_data, nes->nes_ppu.background_palette[0], sizeof(nes_color_t) * NES_DRAW_SIZE); 260 | 261 | // uint8_t nametable_id = 0; 262 | // for (uint8_t j = 0; j < 16 * 8; j++){ 263 | // uint16_t p = j*NES_WIDTH; 264 | // uint8_t tile_y = j/8; 265 | // uint8_t dy = j%8; 266 | // int8_t m = 7; 267 | // for (uint8_t i = 0; i < 16; i++){ 268 | // uint8_t tile_x = i; 269 | // const uint8_t pattern_id = tile_y*16 + tile_x; 270 | // const uint8_t* bit0_p = nes->nes_ppu.pattern_table[1 ? 4 : 0] + pattern_id * 16; 271 | // const uint8_t* bit1_p = bit0_p + 8; 272 | // const uint8_t bit0 = bit0_p[dy]; 273 | // const uint8_t bit1 = bit1_p[dy]; 274 | // const uint8_t attribute = nes->nes_ppu.name_table[nametable_id][960 + ((tile_y >> 2) << 3) + (tile_x >> 2)]; 275 | // const uint8_t high_bit = ((attribute >> (((tile_y & 2) << 1) | (tile_x & 2))) & 3) << 2; 276 | // for (; m >= 0; m--){ 277 | // uint8_t low_bit = ((bit0 >> m) & 0x01) | ((bit1 >> m)<<1 & 0x02); 278 | // uint8_t palette_index = (high_bit & 0x0c) | low_bit; 279 | // nes->nes_draw_data[p++] = nes->nes_ppu.background_palette[palette_index]; 280 | // } 281 | // m = 7; 282 | // } 283 | // } 284 | // nes_draw(0, 0, NES_WIDTH-1, NES_HEIGHT-1, nes->nes_draw_data); 285 | // nes_frame(nes); 286 | // } 287 | 288 | void nes_run(nes_t* nes){ 289 | NES_LOG_DEBUG("mapper:%03d\n",nes->nes_rom.mapper_number); 290 | NES_LOG_DEBUG("prg_rom_size:%d*16kB\n",nes->nes_rom.prg_rom_size); 291 | NES_LOG_DEBUG("chr_rom_size:%d*8kB\n",nes->nes_rom.chr_rom_size); 292 | NES_LOG_DEBUG("mirroring_type:%d\n",nes->nes_rom.mirroring_type); 293 | NES_LOG_DEBUG("four_screen:%d\n",nes->nes_rom.four_screen); 294 | // NES_LOG_DEBUG("save_ram:%d\n",nes->nes_rom.save_ram); 295 | 296 | nes_cpu_reset(nes); 297 | uint64_t frame_cnt = 0; 298 | 299 | while (!nes->nes_quit){ 300 | // NES_LOG_DEBUG("frame_cnt:%d\n",frame_cnt); 301 | frame_cnt++; 302 | #if (NES_FRAME_SKIP != 0) 303 | if(nes->nes_frame_skip_count == 0) 304 | #endif 305 | { 306 | nes_palette_generate(nes); 307 | } 308 | if (nes->nes_ppu.MASK_b == 0){ 309 | #if (NES_FRAME_SKIP != 0) 310 | if(nes->nes_frame_skip_count == 0) 311 | #endif 312 | { 313 | nes_memset(nes->nes_draw_data, nes->nes_ppu.background_palette[0], sizeof(nes_color_t) * NES_DRAW_SIZE); 314 | } 315 | } 316 | #if (NES_ENABLE_SOUND==1) 317 | nes_apu_frame(nes); 318 | #endif 319 | // https://www.nesdev.org/wiki/PPU_rendering#Visible_scanlines_(0-239) 320 | for(nes->scanline = 0; nes->scanline < NES_HEIGHT; nes->scanline++) { // 0-239 Visible frame 321 | if (nes->nes_ppu.MASK_b){ 322 | #if (NES_FRAME_SKIP != 0) 323 | if (nes->nes_frame_skip_count == 0) 324 | #endif 325 | { 326 | #if (NES_RAM_LACK == 1) 327 | nes_render_background_line(nes, nes->scanline, nes->nes_draw_data + nes->scanline%(NES_HEIGHT/2) * NES_WIDTH); 328 | #else 329 | nes_render_background_line(nes, nes->scanline, nes->nes_draw_data + nes->scanline * NES_WIDTH); 330 | #endif 331 | } 332 | } 333 | if (nes->nes_ppu.MASK_s){ 334 | #if (NES_RAM_LACK == 1) 335 | nes_render_sprite_line(nes, nes->scanline,nes->nes_draw_data + nes->scanline%(NES_HEIGHT/2) * NES_WIDTH); 336 | #else 337 | nes_render_sprite_line(nes, nes-> scanline,nes->nes_draw_data + nes->scanline * NES_WIDTH); 338 | #endif 339 | } 340 | nes_opcode(nes,85); // ppu cycles: 85*3=255 341 | // https://www.nesdev.org/wiki/PPU_scrolling#Wrapping_around 342 | if (nes->nes_ppu.MASK_b){ 343 | // https://www.nesdev.org/wiki/PPU_scrolling#At_dot_256_of_each_scanline 344 | if ((nes->nes_ppu.v.fine_y) < 7) { 345 | nes->nes_ppu.v.fine_y++; 346 | }else { 347 | nes->nes_ppu.v.fine_y = 0; 348 | uint8_t y = (uint8_t)(nes->nes_ppu.v.coarse_y); 349 | if (y == 29) { 350 | y = 0; 351 | nes->nes_ppu.v_reg ^= 0x0800; 352 | }else if (y == 31) { 353 | y = 0; 354 | }else { 355 | y++; 356 | } 357 | nes->nes_ppu.v.coarse_y = y; 358 | } 359 | // https://www.nesdev.org/wiki/PPU_scrolling#At_dot_257_of_each_scanline 360 | // v: ....A.. ...BCDEF <- t: ....A.. ...BCDEF 361 | nes->nes_ppu.v_reg = (nes->nes_ppu.v_reg & (uint16_t)0xFBE0) | (nes->nes_ppu.t_reg & (uint16_t)0x041F); 362 | } 363 | nes_opcode(nes,NES_PPU_CPU_CLOCKS-85); 364 | #if (NES_ENABLE_SOUND==1) 365 | if (nes->scanline % 66 == 65) nes_apu_frame(nes); 366 | #endif 367 | #if (NES_RAM_LACK == 1) 368 | #if (NES_FRAME_SKIP != 0) 369 | if(nes->nes_frame_skip_count == 0) 370 | #endif 371 | { 372 | if (nes->scanline == NES_HEIGHT/2-1){ 373 | nes_draw(0, 0, NES_WIDTH-1, NES_HEIGHT/2-1, nes->nes_draw_data); 374 | }else if(nes->scanline == NES_HEIGHT-1){ 375 | nes_draw(0, NES_HEIGHT/2, NES_WIDTH-1, NES_HEIGHT-1, nes->nes_draw_data); 376 | } 377 | } 378 | #endif 379 | } 380 | #if (NES_RAM_LACK == 0) 381 | #if (NES_FRAME_SKIP != 0) 382 | if(nes->nes_frame_skip_count == 0) 383 | #endif 384 | { 385 | nes_draw(0, 0, NES_WIDTH-1, NES_HEIGHT-1, nes->nes_draw_data); 386 | } 387 | #endif 388 | nes_opcode(nes,NES_PPU_CPU_CLOCKS); //240 Post-render line 389 | 390 | nes->nes_ppu.STATUS_V = 1;// Set VBlank flag (241 line) 391 | if (nes->nes_ppu.CTRL_V) { 392 | nes->nes_cpu.irq_nmi=1; 393 | } 394 | 395 | for(uint8_t i = 0; i < 20; i++){ // 241-260行 垂直空白行 x20 396 | nes_opcode(nes,NES_PPU_CPU_CLOCKS); 397 | } 398 | nes->nes_ppu.ppu_status = 0; // Clear:VBlank,Sprite 0,Overflow 399 | nes_opcode(nes,NES_PPU_CPU_CLOCKS); // Pre-render scanline (-1 or 261) 400 | 401 | if (nes->nes_ppu.MASK_b){ 402 | // https://www.nesdev.org/wiki/PPU_scrolling#During_dots_280_to_304_of_the_pre-render_scanline_(end_of_vblank) 403 | // v: GHIA.BC DEF..... <- t: GHIA.BC DEF..... 404 | nes->nes_ppu.v_reg = (nes->nes_ppu.v_reg & (uint16_t)0x841F) | (nes->nes_ppu.t_reg & (uint16_t)0x7BE0); 405 | } 406 | nes_frame(nes); 407 | #if (NES_FRAME_SKIP != 0) 408 | if ( ++nes->nes_frame_skip_count > NES_FRAME_SKIP){ 409 | nes->nes_frame_skip_count = 0; 410 | } 411 | #endif 412 | } 413 | } 414 | 415 | -------------------------------------------------------------------------------- /src/nes_apu.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "nes.h" 18 | 19 | #if (NES_ENABLE_SOUND == 1) 20 | 21 | // https://www.nesdev.org/wiki/APU_Length_Counter 22 | static const uint8_t length_counter_table[32] = { 23 | /* | 0 1 2 3 4 5 6 7 8 9 A B C D E F 24 | ---------+---------------------------------------------------------------- */ 25 | /*00-0F*/ 0x0A,0xFE,0x14,0x02,0x28,0x04,0x50,0x06,0xA0,0x08,0x3C,0x0A,0x0E,0x0C,0x1A,0x0E, 26 | /*10-1F*/ 0x0C,0x10,0x18,0x12,0x30,0x14,0x60,0x16,0xC0,0x18,0x48,0x1A,0x10,0x1C,0x20,0x1E, 27 | }; 28 | 29 | static const uint8_t apu_pulse_wave[4][8] = { 30 | {0, 1, 0, 0, 0, 0, 0, 0}, 31 | {0, 1, 1, 0, 0, 0, 0, 0}, 32 | {0, 1, 1, 1, 1, 0, 0, 0}, 33 | {1, 0, 0, 1, 1, 1, 1, 1} 34 | }; 35 | 36 | static const uint8_t apu_triangle_wave[32] = { 37 | 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 38 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 39 | }; 40 | 41 | 42 | static inline void nes_apu_pulse_sweep(pulse_t* pulse, uint8_t period_one){ 43 | if (pulse->sweep_divider == 0 && pulse->enabled && pulse->shift){ 44 | if (pulse->cur_period >= 8 && pulse->cur_period <= 0x7ff){ 45 | if (pulse->negate){ 46 | pulse->cur_period = pulse->cur_period - (pulse->cur_period >> pulse->shift) - period_one; 47 | }else{ 48 | pulse->cur_period = pulse->cur_period + (pulse->cur_period >> pulse->shift); 49 | } 50 | } 51 | } 52 | if (pulse->sweep_reload || (pulse->sweep_divider == 0)){ //扫描单元重新开始 53 | pulse->sweep_reload = 0; 54 | pulse->sweep_divider = pulse->period; 55 | }else{ 56 | pulse->sweep_divider--; 57 | } 58 | } 59 | 60 | static inline void nes_apu_length_counter_and_sweep(nes_t* nes){ 61 | // length_counter 62 | if (!nes->nes_apu.pulse1.len_counter_halt && nes->nes_apu.pulse1.length_counter){ 63 | nes->nes_apu.pulse1.length_counter--; 64 | } 65 | if (!nes->nes_apu.pulse2.len_counter_halt && nes->nes_apu.pulse2.length_counter){ 66 | nes->nes_apu.pulse2.length_counter--; 67 | } 68 | if (!nes->nes_apu.triangle.len_counter_halt && nes->nes_apu.triangle.length_counter){ 69 | nes->nes_apu.triangle.length_counter--; 70 | } 71 | if (!nes->nes_apu.noise.len_counter_halt && nes->nes_apu.noise.length_counter){ 72 | nes->nes_apu.noise.length_counter--; 73 | } 74 | // sweep 75 | nes_apu_pulse_sweep(&nes->nes_apu.pulse1,1); 76 | nes_apu_pulse_sweep(&nes->nes_apu.pulse2,0); 77 | } 78 | 79 | static inline void nes_apu_pulse_envelopes(pulse_t* pulse){ 80 | if (pulse->envelope_restart){//包络重新开始 81 | pulse->envelope_restart = 0; 82 | pulse->envelope_divider = pulse->envelope_lowers; 83 | pulse->envelope_volume = 15; 84 | }else{ 85 | if (pulse->envelope_divider){ 86 | pulse->envelope_divider--; 87 | }else{ 88 | pulse->envelope_divider = pulse->envelope_lowers; 89 | if (pulse->envelope_volume){ 90 | pulse->envelope_volume--; 91 | }else{ 92 | if (pulse->len_counter_halt){ 93 | pulse->envelope_volume = 15; 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | static inline void nes_apu_noise_envelopes(noise_t* noise){ 101 | if (noise->envelope_restart){//包络重新开始 102 | noise->envelope_restart = 0; 103 | noise->envelope_divider = noise->volume_envelope; 104 | noise->envelope_volume = 15; 105 | }else{ 106 | if (noise->envelope_divider){ 107 | noise->envelope_divider--; 108 | }else{ 109 | noise->envelope_divider = noise->volume_envelope; 110 | if (noise->envelope_volume){ 111 | noise->envelope_volume--; 112 | }else{ 113 | if (noise->len_counter_halt){ 114 | noise->envelope_volume = 15; 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | static inline void nes_apu_triangle_linear_counter(triangle_t* triangle){ 122 | if (triangle->linear_restart){ 123 | triangle->linear_counter = triangle->linear_counter_load; 124 | }else if (triangle->linear_counter){ 125 | triangle->linear_counter--; 126 | } 127 | if (!triangle->len_counter_halt) 128 | triangle->linear_restart = 0; 129 | } 130 | 131 | static inline void nes_apu_envelopes_and_linear_counter(nes_t *nes){ 132 | nes_apu_pulse_envelopes(&nes->nes_apu.pulse1); 133 | nes_apu_pulse_envelopes(&nes->nes_apu.pulse2); 134 | nes_apu_triangle_linear_counter(&nes->nes_apu.triangle); 135 | nes_apu_noise_envelopes(&nes->nes_apu.noise); 136 | } 137 | 138 | // https://www.nesdev.org/wiki/APU_Pulse 139 | static void nes_apu_play_pulse(nes_apu_t* apu,uint8_t pulse_id){ 140 | pulse_t* pulse = (pulse_id==1)? &apu->pulse1: &apu->pulse2; 141 | uint8_t volume,enabled = (pulse_id==1)? apu->status_pulse1: apu->status_pulse2; 142 | for (uint64_t sample_local = apu->sample_local_start; sample_local <= apu->sample_local_end; sample_local++){ 143 | uint8_t mute = 0; 144 | if (pulse->sample_index >= NES_APU_SAMPLE_PER_SYNC){ 145 | break; 146 | } 147 | if ((!enabled) || (pulse->length_counter == 0)){ 148 | mute = 1; 149 | pulse->sample_buffer[pulse->sample_index++] = 0; 150 | pulse->seq_local_old = 0; 151 | continue; 152 | }else if (pulse->cur_period <= 7 || pulse->cur_period >= 0x800) 153 | mute = 1; 154 | const uint64_t cpu_local = sample_local* NES_CPU_CLOCK_FREQ / NES_APU_SAMPLE_RATE; 155 | const uint64_t cpu_local_diff = cpu_local - apu->cpu_lock_count; 156 | // fpulse = fCPU/(16*(t+1)) 157 | const float seq_diff = cpu_local_diff * 1.0f / (16 * (pulse->cur_period + 1)); 158 | const float seq_local = (seq_diff + pulse->seq_local_old) - (int)(seq_diff + pulse->seq_local_old); 159 | if (mute){ 160 | volume = 0; 161 | }else if (pulse->constant_volume){ 162 | volume = pulse->envelope_lowers; 163 | }else{ 164 | volume = pulse->envelope_volume; 165 | } 166 | pulse->sample_buffer[pulse->sample_index++] = apu_pulse_wave[pulse->duty][(int)(seq_local * 8)] * volume; 167 | if (sample_local == (uint64_t)((apu->clock_count + 1) * NES_APU_SAMPLE_RATE / 240)){ 168 | pulse->seq_local_old = seq_local; 169 | } 170 | } 171 | } 172 | 173 | // https://www.nesdev.org/wiki/APU_Triangle 174 | static void nes_apu_play_triangle(nes_apu_t* apu){ 175 | triangle_t* triangle = &apu->triangle; 176 | for (uint64_t sample_local = apu->sample_local_start; sample_local <= apu->sample_local_end; sample_local++){ 177 | if (triangle->sample_index >= NES_APU_SAMPLE_PER_SYNC){ 178 | break; 179 | } 180 | if ((!apu->status_triangle) || (triangle->length_counter == 0) || (triangle->linear_counter == 0)){ 181 | triangle->sample_buffer[triangle->sample_index++] = 0; 182 | triangle->seq_local_old = 0; 183 | continue; 184 | } 185 | const uint64_t cpu_local = sample_local* NES_CPU_CLOCK_FREQ / NES_APU_SAMPLE_RATE; 186 | const uint64_t cpu_local_diff = cpu_local - apu->cpu_lock_count; 187 | const float seq_diff = cpu_local_diff * 1.0f / (16 * (triangle->cur_period + 1)); 188 | const float seq_local = (seq_diff + triangle->seq_local_old) - (int)(seq_diff + triangle->seq_local_old); 189 | uint8_t volume = apu_triangle_wave[(int)(seq_local * 32)]; 190 | triangle->sample_buffer[triangle->sample_index++] = volume; 191 | if (sample_local == (uint64_t)((apu->clock_count + 1) * NES_APU_SAMPLE_RATE / 240)){ 192 | triangle->seq_local_old = seq_local; 193 | } 194 | } 195 | } 196 | 197 | // https://www.nesdev.org/wiki/APU_Noise 198 | static void nes_apu_play_noise(nes_apu_t* apu){ 199 | noise_t* noise = &apu->noise; 200 | uint8_t volume; 201 | uint64_t cpu_local_old = apu->cpu_lock_count; 202 | for (uint64_t sample_local = apu->sample_local_start; sample_local <= apu->sample_local_end; sample_local++){ 203 | if (noise->sample_index >= NES_APU_SAMPLE_PER_SYNC){ 204 | break; 205 | } 206 | if ((!apu->status_noise) || (noise->length_counter == 0)){ 207 | noise->sample_buffer[noise->sample_index++] = 0; 208 | continue; 209 | } 210 | const uint64_t cpu_local = sample_local* NES_CPU_CLOCK_FREQ / NES_APU_SAMPLE_RATE; 211 | const uint64_t lfsr_count = (cpu_local / (noise->noise_period + 1)) - (cpu_local_old / (noise->noise_period + 1)); 212 | cpu_local_old = cpu_local; 213 | if (noise->loop_noise){ //短模式 214 | for (uint64_t t = 0; t < lfsr_count; t++){ 215 | noise->lfsr = (noise->lfsr >> 1) | ((uint16_t)((noise->lfsr_d0 ^ noise->lfsr_d6) << 14)); 216 | } 217 | }else{ //长模式 218 | for (uint64_t t = 0; t < lfsr_count; t++){ 219 | noise->lfsr = (noise->lfsr >> 1) | ((uint16_t)((noise->lfsr_d0 ^ noise->lfsr_d1) << 14)); 220 | } 221 | } 222 | if (noise->constant_volume){ 223 | volume = noise->volume_envelope; 224 | }else{ 225 | volume = noise->envelope_volume; 226 | } 227 | noise->sample_buffer[noise->sample_index++] = (uint8_t)(noise->lfsr_d0 * volume); 228 | } 229 | } 230 | 231 | // https://www.nesdev.org/wiki/APU_DMC 232 | static void nes_apu_play_dmc(nes_apu_t* apu){ 233 | 234 | for (uint64_t sample_local = apu->sample_local_start; sample_local <= apu->sample_local_end; sample_local++){ 235 | 236 | } 237 | } 238 | 239 | static inline void nes_apu_play(nes_t* nes){ 240 | nes->nes_apu.cpu_lock_count = nes->nes_apu.clock_count * NES_CPU_CLOCK_FREQ / 240; 241 | nes->nes_apu.sample_local_start = nes->nes_apu.clock_count * NES_APU_SAMPLE_RATE / 240 + 1; 242 | nes->nes_apu.sample_local_end = (nes->nes_apu.clock_count + 1) * NES_APU_SAMPLE_RATE / 240; 243 | nes_apu_play_pulse(&nes->nes_apu,1); 244 | nes_apu_play_pulse(&nes->nes_apu,2); 245 | nes_apu_play_triangle(&nes->nes_apu); 246 | nes_apu_play_noise(&nes->nes_apu); 247 | nes_apu_play_dmc(&nes->nes_apu); 248 | 249 | if (nes->nes_apu.clock_count % 4 == 3){ 250 | // https://www.nesdev.org/wiki/APU_Mixer 251 | nes_memset(nes->nes_apu.sample_buffer, 0, NES_APU_SAMPLE_PER_SYNC); 252 | for (int t = 0; t <= NES_APU_SAMPLE_PER_SYNC - 1; t++){ 253 | // 这里采用线性近似方法 https://www.nesdev.org/wiki/APU_Mixer#Linear_Approximation 254 | float volume_total = 0.00752f * (nes->nes_apu.pulse1.sample_buffer[t] + nes->nes_apu.pulse2.sample_buffer[t]); 255 | volume_total += 0.00851f * nes->nes_apu.triangle.sample_buffer[t] + 0.00494f * nes->nes_apu.noise.sample_buffer[t] + 0.00335f * nes->nes_apu.dmc.sample_buffer[t]; 256 | nes->nes_apu.sample_buffer[t] = (uint8_t)(volume_total * 256); 257 | } 258 | nes_memset(nes->nes_apu.pulse1.sample_buffer, 0, NES_APU_SAMPLE_PER_SYNC); 259 | nes_memset(nes->nes_apu.pulse2.sample_buffer, 0, NES_APU_SAMPLE_PER_SYNC); 260 | nes_memset(nes->nes_apu.triangle.sample_buffer, 0, NES_APU_SAMPLE_PER_SYNC); 261 | nes_memset(nes->nes_apu.noise.sample_buffer, 0, NES_APU_SAMPLE_PER_SYNC); 262 | nes_memset(nes->nes_apu.dmc.sample_buffer, 0, NES_APU_SAMPLE_PER_SYNC); 263 | 264 | nes->nes_apu.pulse1.sample_index = 0; 265 | nes->nes_apu.pulse2.sample_index = 0; 266 | nes->nes_apu.triangle.sample_index = 0; 267 | nes->nes_apu.noise.sample_index = 0; 268 | nes->nes_apu.dmc.sample_index = 0; 269 | 270 | nes_sound_output(nes->nes_apu.sample_buffer, NES_APU_SAMPLE_PER_SYNC); 271 | } 272 | } 273 | 274 | extern void nes_cpu_irq(nes_t* nes); 275 | static inline void nes_apu_frame_irq(nes_t *nes){ 276 | if (nes->nes_apu.irq_inhibit_flag==0){ 277 | nes->nes_apu.frame_interrupt = 1; 278 | nes_cpu_irq(nes); 279 | } 280 | } 281 | 282 | /* 283 | https://www.nesdev.org/wiki/APU#Frame_Counter_($4017) 284 | https://www.nesdev.org/wiki/APU_Frame_Counter 285 | 286 | mode 0: mode 1: function 287 | --------- ----------- ----------------------------- 288 | - - - f - - - - - IRQ (if bit 6 is clear) 289 | - l - l - l - - l Length counter and sweep 290 | e e e e e e e - e Envelope and linear counter 291 | */ 292 | void nes_apu_frame(nes_t* nes){ 293 | if(nes->nes_apu.mode){// 5 step mode 294 | switch(nes->nes_apu.clock_count % 5){ 295 | case 0: 296 | nes_apu_envelopes_and_linear_counter(nes); 297 | break; 298 | case 1: 299 | nes_apu_length_counter_and_sweep(nes); 300 | nes_apu_envelopes_and_linear_counter(nes); 301 | break; 302 | case 2: 303 | nes_apu_envelopes_and_linear_counter(nes); 304 | break; 305 | case 4: 306 | nes_apu_length_counter_and_sweep(nes); 307 | nes_apu_envelopes_and_linear_counter(nes); 308 | break; 309 | } 310 | }else{ // 4 step mode 311 | switch(nes->nes_apu.clock_count % 4){ 312 | case 0: 313 | nes_apu_envelopes_and_linear_counter(nes); 314 | break; 315 | case 1: 316 | nes_apu_length_counter_and_sweep(nes); 317 | nes_apu_envelopes_and_linear_counter(nes); 318 | break; 319 | case 2: 320 | nes_apu_envelopes_and_linear_counter(nes); 321 | break; 322 | case 3: 323 | nes_apu_frame_irq(nes); 324 | nes_apu_length_counter_and_sweep(nes); 325 | nes_apu_envelopes_and_linear_counter(nes); 326 | break; 327 | default: 328 | break; 329 | } 330 | } 331 | nes_apu_play(nes); 332 | nes->nes_apu.clock_count++; 333 | } 334 | 335 | void nes_apu_init(nes_t *nes){ 336 | nes->nes_apu.status = 0; 337 | nes->nes_apu.noise.lfsr = 1; 338 | } 339 | 340 | uint8_t nes_read_apu_register(nes_t *nes,uint16_t address){ 341 | uint8_t data = 0; 342 | if(address==0x4015){ 343 | data=nes->nes_apu.status&0xc0; 344 | 345 | if (nes->nes_apu.pulse1.length_counter) data |= 1; 346 | if (nes->nes_apu.pulse2.length_counter) data |= (1 << 1); 347 | if (nes->nes_apu.triangle.length_counter) data |= (1 << 2); 348 | if (nes->nes_apu.noise.length_counter) data |= (1 << 3); 349 | if (nes->nes_apu.dmc.load_counter) data |= (1 << 4); 350 | 351 | nes->nes_apu.frame_interrupt = 0; 352 | }else{ 353 | NES_LOG_DEBUG("nes_read apu %04X %02X\n",address,data); 354 | } 355 | return data; 356 | } 357 | 358 | void nes_write_apu_register(nes_t* nes,uint16_t address,uint8_t data){ 359 | switch(address){ 360 | // Pulse ($4000–$4007) 361 | // Pulse0 ($4000–$4003) 362 | case 0x4000: 363 | nes->nes_apu.pulse1.control0=data; 364 | break; 365 | case 0x4001: 366 | nes->nes_apu.pulse1.control1=data; 367 | nes->nes_apu.pulse1.sweep_reload=1; 368 | break; 369 | case 0x4002: 370 | nes->nes_apu.pulse1.timer_low=data; 371 | nes->nes_apu.pulse1.cur_period=nes->nes_apu.pulse1.timer_high<<8|nes->nes_apu.pulse1.timer_low; 372 | break; 373 | case 0x4003: 374 | nes->nes_apu.pulse1.control3=data; 375 | if (nes->nes_apu.status_pulse1){ 376 | nes->nes_apu.pulse1.length_counter = length_counter_table[nes->nes_apu.pulse1.len_counter_load]; 377 | } 378 | nes->nes_apu.pulse1.cur_period=nes->nes_apu.pulse1.timer_high<<8|nes->nes_apu.pulse1.timer_low; 379 | nes->nes_apu.pulse1.envelope_restart = 1; 380 | nes->nes_apu.pulse1.seq_local_old = 0; 381 | break; 382 | // Pulse1 ($4004–$4007) 383 | case 0x4004: 384 | nes->nes_apu.pulse2.control0=data; 385 | break; 386 | case 0x4005: 387 | nes->nes_apu.pulse2.control1=data; 388 | nes->nes_apu.pulse2.sweep_reload=1; 389 | break; 390 | case 0x4006: 391 | nes->nes_apu.pulse2.timer_low=data; 392 | break; 393 | case 0x4007: 394 | nes->nes_apu.pulse2.control3=data; 395 | if (nes->nes_apu.status_pulse2){ 396 | nes->nes_apu.pulse2.length_counter = length_counter_table[nes->nes_apu.pulse2.len_counter_load]; 397 | } 398 | nes->nes_apu.pulse2.cur_period=nes->nes_apu.pulse2.timer_high<<8|nes->nes_apu.pulse2.timer_low; 399 | nes->nes_apu.pulse2.envelope_restart = 1; 400 | nes->nes_apu.pulse2.seq_local_old = 0; 401 | break; 402 | // Triangle ($4008–$400B) 403 | case 0x4008: 404 | nes->nes_apu.triangle.control0=data; 405 | break; 406 | // case 0x4009: 407 | // break; 408 | case 0x400A: 409 | nes->nes_apu.triangle.timer_low=data; 410 | nes->nes_apu.triangle.cur_period=nes->nes_apu.triangle.timer_high<<8|nes->nes_apu.triangle.timer_low; 411 | break; 412 | case 0x400B: 413 | nes->nes_apu.triangle.control3=data; 414 | if (nes->nes_apu.status_triangle){ 415 | nes->nes_apu.triangle.length_counter = length_counter_table[nes->nes_apu.triangle.len_counter_load]; 416 | } 417 | nes->nes_apu.triangle.cur_period=nes->nes_apu.triangle.timer_high<<8|nes->nes_apu.triangle.timer_low; 418 | nes->nes_apu.triangle.linear_restart = 1; 419 | break; 420 | // Noise ($400C–$400F) 421 | case 0x400C: 422 | nes->nes_apu.noise.control0=data; 423 | break; 424 | // case 0x400D: 425 | // break; 426 | case 0x400E: 427 | nes->nes_apu.noise.control2=data; 428 | break; 429 | case 0x400F: 430 | nes->nes_apu.noise.control3=data; 431 | if (nes->nes_apu.status_noise){ 432 | nes->nes_apu.noise.length_counter = length_counter_table[nes->nes_apu.noise.len_counter_load]; 433 | } 434 | nes->nes_apu.noise.envelope_restart = 1; 435 | break; 436 | // DMC ($4010–$4013) 437 | case 0x4010: 438 | nes->nes_apu.dmc.control0=data; 439 | break; 440 | case 0x4011: 441 | nes->nes_apu.dmc.control1=data; 442 | break; 443 | case 0x4012: 444 | nes->nes_apu.dmc.sample_address=data; 445 | break; 446 | case 0x4013: 447 | nes->nes_apu.dmc.sample_length=data; 448 | break; 449 | // case 0x4014: 450 | // break; 451 | // Status ($4015) https://www.nesdev.org/wiki/APU#Status_($4015) 452 | case 0x4015: 453 | nes->nes_apu.status=data; 454 | if (nes->nes_apu.status_pulse1==0){ 455 | nes->nes_apu.pulse1.length_counter=0; 456 | } 457 | if (nes->nes_apu.status_pulse2==0){ 458 | nes->nes_apu.pulse2.length_counter=0; 459 | } 460 | if (nes->nes_apu.status_triangle==0){ 461 | nes->nes_apu.triangle.length_counter=0; 462 | } 463 | if (nes->nes_apu.status_noise==0){ 464 | nes->nes_apu.noise.length_counter=0; 465 | } 466 | // nes->nes_apu.dmc_interrupt = 0; 467 | break; 468 | case 0x4017: 469 | nes->nes_apu.frame_counter=data; 470 | if (nes->nes_apu.irq_inhibit_flag){ 471 | nes->nes_apu.frame_interrupt = 0; 472 | } 473 | if (nes->nes_apu.mode){ 474 | nes_apu_length_counter_and_sweep(nes); 475 | nes_apu_envelopes_and_linear_counter(nes); 476 | } 477 | break; 478 | default: 479 | NES_LOG_DEBUG("nes_write apu %04X %02X\n",address,data); 480 | break; 481 | } 482 | } 483 | 484 | #endif 485 | 486 | 487 | 488 | -------------------------------------------------------------------------------- /inc/nes_mapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright PeakRacing 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | struct nes; 23 | typedef struct nes nes_t; 24 | 25 | /* https://www.nesdev.org/wiki/Mapper */ 26 | typedef struct { 27 | void (*mapper_init)(nes_t* nes); 28 | void (*mapper_deinit)(nes_t* nes); 29 | void (*mapper_write)(nes_t* nes, uint16_t write_addr, uint8_t data); 30 | void (*mapper_sram)(nes_t* nes, uint16_t write_addr, uint8_t data); 31 | void (*mapper_apu)(nes_t* nes, uint16_t write_addr, uint8_t data); 32 | uint8_t (*mapper_read_apu)(nes_t* nes, uint16_t write_addr); 33 | /* Callback at VSync */ 34 | void (*mapper_vsync)(nes_t* nes); 35 | /* Callback at HSync */ 36 | void (*mapper_hsync)(nes_t* nes); 37 | /* Callback at PPU read/write */ 38 | void (*mapper_ppu)(nes_t* nes, uint16_t write_addr); 39 | /* Callback at Rendering Screen 1:BG, 0:Sprite */ 40 | void (*mapper_render_screen)(nes_t* nes, uint8_t mode); 41 | void* mapper_register; 42 | void* mapper_data; 43 | } nes_mapper_t; 44 | 45 | /* prg rom */ 46 | void nes_load_prgrom_8k(nes_t* nes,uint8_t des, uint16_t src); 47 | void nes_load_prgrom_16k(nes_t* nes,uint8_t des, uint16_t src); 48 | void nes_load_prgrom_32k(nes_t* nes,uint8_t des, uint16_t src); 49 | 50 | /* chr rom */ 51 | void nes_load_chrrom_1k(nes_t* nes,uint8_t des, uint8_t src); 52 | void nes_load_chrrom_4k(nes_t* nes,uint8_t des, uint8_t src); 53 | void nes_load_chrrom_8k(nes_t* nes,uint8_t des, uint8_t src); 54 | 55 | /* mapper */ 56 | int nes_load_mapper(nes_t* nes); 57 | 58 | /* iNES 1.0 mapper 0~255 */ 59 | int nes_mapper0_init(nes_t* nes); 60 | int nes_mapper1_init(nes_t* nes); 61 | int nes_mapper2_init(nes_t* nes); 62 | int nes_mapper3_init(nes_t* nes); 63 | int nes_mapper4_init(nes_t* nes); 64 | int nes_mapper5_init(nes_t* nes); 65 | int nes_mapper6_init(nes_t* nes); 66 | int nes_mapper7_init(nes_t* nes); 67 | int nes_mapper8_init(nes_t* nes); 68 | int nes_mapper9_init(nes_t* nes); 69 | int nes_mapper10_init(nes_t* nes); 70 | int nes_mapper11_init(nes_t* nes); 71 | int nes_mapper12_init(nes_t* nes); 72 | int nes_mapper13_init(nes_t* nes); 73 | int nes_mapper14_init(nes_t* nes); 74 | int nes_mapper15_init(nes_t* nes); 75 | int nes_mapper16_init(nes_t* nes); 76 | int nes_mapper17_init(nes_t* nes); 77 | int nes_mapper18_init(nes_t* nes); 78 | int nes_mapper19_init(nes_t* nes); 79 | int nes_mapper20_init(nes_t* nes); 80 | int nes_mapper21_init(nes_t* nes); 81 | int nes_mapper22_init(nes_t* nes); 82 | int nes_mapper23_init(nes_t* nes); 83 | int nes_mapper24_init(nes_t* nes); 84 | int nes_mapper25_init(nes_t* nes); 85 | int nes_mapper26_init(nes_t* nes); 86 | int nes_mapper27_init(nes_t* nes); 87 | int nes_mapper28_init(nes_t* nes); 88 | int nes_mapper29_init(nes_t* nes); 89 | int nes_mapper30_init(nes_t* nes); 90 | int nes_mapper31_init(nes_t* nes); 91 | int nes_mapper32_init(nes_t* nes); 92 | int nes_mapper33_init(nes_t* nes); 93 | int nes_mapper34_init(nes_t* nes); 94 | int nes_mapper35_init(nes_t* nes); 95 | int nes_mapper36_init(nes_t* nes); 96 | int nes_mapper37_init(nes_t* nes); 97 | int nes_mapper38_init(nes_t* nes); 98 | int nes_mapper39_init(nes_t* nes); 99 | int nes_mapper40_init(nes_t* nes); 100 | int nes_mapper41_init(nes_t* nes); 101 | int nes_mapper42_init(nes_t* nes); 102 | int nes_mapper43_init(nes_t* nes); 103 | int nes_mapper44_init(nes_t* nes); 104 | int nes_mapper45_init(nes_t* nes); 105 | int nes_mapper46_init(nes_t* nes); 106 | int nes_mapper47_init(nes_t* nes); 107 | int nes_mapper48_init(nes_t* nes); 108 | int nes_mapper49_init(nes_t* nes); 109 | int nes_mapper50_init(nes_t* nes); 110 | int nes_mapper51_init(nes_t* nes); 111 | int nes_mapper52_init(nes_t* nes); 112 | int nes_mapper53_init(nes_t* nes); 113 | int nes_mapper54_init(nes_t* nes); 114 | int nes_mapper55_init(nes_t* nes); 115 | int nes_mapper56_init(nes_t* nes); 116 | int nes_mapper57_init(nes_t* nes); 117 | int nes_mapper58_init(nes_t* nes); 118 | int nes_mapper59_init(nes_t* nes); 119 | int nes_mapper60_init(nes_t* nes); 120 | int nes_mapper61_init(nes_t* nes); 121 | int nes_mapper62_init(nes_t* nes); 122 | int nes_mapper63_init(nes_t* nes); 123 | int nes_mapper64_init(nes_t* nes); 124 | int nes_mapper65_init(nes_t* nes); 125 | int nes_mapper66_init(nes_t* nes); 126 | int nes_mapper67_init(nes_t* nes); 127 | int nes_mapper68_init(nes_t* nes); 128 | int nes_mapper69_init(nes_t* nes); 129 | int nes_mapper70_init(nes_t* nes); 130 | int nes_mapper71_init(nes_t* nes); 131 | int nes_mapper72_init(nes_t* nes); 132 | int nes_mapper73_init(nes_t* nes); 133 | int nes_mapper74_init(nes_t* nes); 134 | int nes_mapper75_init(nes_t* nes); 135 | int nes_mapper76_init(nes_t* nes); 136 | int nes_mapper77_init(nes_t* nes); 137 | int nes_mapper78_init(nes_t* nes); 138 | int nes_mapper79_init(nes_t* nes); 139 | int nes_mapper80_init(nes_t* nes); 140 | int nes_mapper81_init(nes_t* nes); 141 | int nes_mapper82_init(nes_t* nes); 142 | int nes_mapper83_init(nes_t* nes); 143 | int nes_mapper84_init(nes_t* nes); 144 | int nes_mapper85_init(nes_t* nes); 145 | int nes_mapper86_init(nes_t* nes); 146 | int nes_mapper87_init(nes_t* nes); 147 | int nes_mapper88_init(nes_t* nes); 148 | int nes_mapper89_init(nes_t* nes); 149 | int nes_mapper90_init(nes_t* nes); 150 | int nes_mapper91_init(nes_t* nes); 151 | int nes_mapper92_init(nes_t* nes); 152 | int nes_mapper93_init(nes_t* nes); 153 | int nes_mapper94_init(nes_t* nes); 154 | int nes_mapper95_init(nes_t* nes); 155 | int nes_mapper96_init(nes_t* nes); 156 | int nes_mapper97_init(nes_t* nes); 157 | int nes_mapper98_init(nes_t* nes); 158 | int nes_mapper99_init(nes_t* nes); 159 | int nes_mapper100_init(nes_t* nes); 160 | int nes_mapper101_init(nes_t* nes); 161 | int nes_mapper102_init(nes_t* nes); 162 | int nes_mapper103_init(nes_t* nes); 163 | int nes_mapper104_init(nes_t* nes); 164 | int nes_mapper105_init(nes_t* nes); 165 | int nes_mapper106_init(nes_t* nes); 166 | int nes_mapper107_init(nes_t* nes); 167 | int nes_mapper108_init(nes_t* nes); 168 | int nes_mapper109_init(nes_t* nes); 169 | int nes_mapper110_init(nes_t* nes); 170 | int nes_mapper111_init(nes_t* nes); 171 | int nes_mapper112_init(nes_t* nes); 172 | int nes_mapper113_init(nes_t* nes); 173 | int nes_mapper114_init(nes_t* nes); 174 | int nes_mapper115_init(nes_t* nes); 175 | int nes_mapper116_init(nes_t* nes); 176 | int nes_mapper117_init(nes_t* nes); 177 | int nes_mapper118_init(nes_t* nes); 178 | int nes_mapper119_init(nes_t* nes); 179 | int nes_mapper120_init(nes_t* nes); 180 | int nes_mapper121_init(nes_t* nes); 181 | int nes_mapper122_init(nes_t* nes); 182 | int nes_mapper123_init(nes_t* nes); 183 | int nes_mapper124_init(nes_t* nes); 184 | int nes_mapper125_init(nes_t* nes); 185 | int nes_mapper126_init(nes_t* nes); 186 | int nes_mapper127_init(nes_t* nes); 187 | int nes_mapper128_init(nes_t* nes); 188 | int nes_mapper129_init(nes_t* nes); 189 | int nes_mapper130_init(nes_t* nes); 190 | int nes_mapper131_init(nes_t* nes); 191 | int nes_mapper132_init(nes_t* nes); 192 | int nes_mapper133_init(nes_t* nes); 193 | int nes_mapper134_init(nes_t* nes); 194 | int nes_mapper135_init(nes_t* nes); 195 | int nes_mapper136_init(nes_t* nes); 196 | int nes_mapper137_init(nes_t* nes); 197 | int nes_mapper138_init(nes_t* nes); 198 | int nes_mapper139_init(nes_t* nes); 199 | int nes_mapper140_init(nes_t* nes); 200 | int nes_mapper141_init(nes_t* nes); 201 | int nes_mapper142_init(nes_t* nes); 202 | int nes_mapper143_init(nes_t* nes); 203 | int nes_mapper144_init(nes_t* nes); 204 | int nes_mapper145_init(nes_t* nes); 205 | int nes_mapper146_init(nes_t* nes); 206 | int nes_mapper147_init(nes_t* nes); 207 | int nes_mapper148_init(nes_t* nes); 208 | int nes_mapper149_init(nes_t* nes); 209 | int nes_mapper150_init(nes_t* nes); 210 | int nes_mapper151_init(nes_t* nes); 211 | int nes_mapper152_init(nes_t* nes); 212 | int nes_mapper153_init(nes_t* nes); 213 | int nes_mapper154_init(nes_t* nes); 214 | int nes_mapper155_init(nes_t* nes); 215 | int nes_mapper156_init(nes_t* nes); 216 | int nes_mapper157_init(nes_t* nes); 217 | int nes_mapper158_init(nes_t* nes); 218 | int nes_mapper159_init(nes_t* nes); 219 | int nes_mapper160_init(nes_t* nes); 220 | int nes_mapper161_init(nes_t* nes); 221 | int nes_mapper162_init(nes_t* nes); 222 | int nes_mapper163_init(nes_t* nes); 223 | int nes_mapper164_init(nes_t* nes); 224 | int nes_mapper165_init(nes_t* nes); 225 | int nes_mapper166_init(nes_t* nes); 226 | int nes_mapper167_init(nes_t* nes); 227 | int nes_mapper168_init(nes_t* nes); 228 | int nes_mapper169_init(nes_t* nes); 229 | int nes_mapper170_init(nes_t* nes); 230 | int nes_mapper171_init(nes_t* nes); 231 | int nes_mapper172_init(nes_t* nes); 232 | int nes_mapper173_init(nes_t* nes); 233 | int nes_mapper174_init(nes_t* nes); 234 | int nes_mapper175_init(nes_t* nes); 235 | int nes_mapper176_init(nes_t* nes); 236 | int nes_mapper177_init(nes_t* nes); 237 | int nes_mapper178_init(nes_t* nes); 238 | int nes_mapper179_init(nes_t* nes); 239 | int nes_mapper180_init(nes_t* nes); 240 | int nes_mapper181_init(nes_t* nes); 241 | int nes_mapper182_init(nes_t* nes); 242 | int nes_mapper183_init(nes_t* nes); 243 | int nes_mapper184_init(nes_t* nes); 244 | int nes_mapper185_init(nes_t* nes); 245 | int nes_mapper186_init(nes_t* nes); 246 | int nes_mapper187_init(nes_t* nes); 247 | int nes_mapper188_init(nes_t* nes); 248 | int nes_mapper189_init(nes_t* nes); 249 | int nes_mapper190_init(nes_t* nes); 250 | int nes_mapper191_init(nes_t* nes); 251 | int nes_mapper192_init(nes_t* nes); 252 | int nes_mapper193_init(nes_t* nes); 253 | int nes_mapper194_init(nes_t* nes); 254 | int nes_mapper195_init(nes_t* nes); 255 | int nes_mapper196_init(nes_t* nes); 256 | int nes_mapper197_init(nes_t* nes); 257 | int nes_mapper198_init(nes_t* nes); 258 | int nes_mapper199_init(nes_t* nes); 259 | int nes_mapper200_init(nes_t* nes); 260 | int nes_mapper201_init(nes_t* nes); 261 | int nes_mapper202_init(nes_t* nes); 262 | int nes_mapper203_init(nes_t* nes); 263 | int nes_mapper204_init(nes_t* nes); 264 | int nes_mapper205_init(nes_t* nes); 265 | int nes_mapper206_init(nes_t* nes); 266 | int nes_mapper207_init(nes_t* nes); 267 | int nes_mapper208_init(nes_t* nes); 268 | int nes_mapper209_init(nes_t* nes); 269 | int nes_mapper210_init(nes_t* nes); 270 | int nes_mapper211_init(nes_t* nes); 271 | int nes_mapper212_init(nes_t* nes); 272 | int nes_mapper213_init(nes_t* nes); 273 | int nes_mapper214_init(nes_t* nes); 274 | int nes_mapper215_init(nes_t* nes); 275 | int nes_mapper216_init(nes_t* nes); 276 | int nes_mapper217_init(nes_t* nes); 277 | int nes_mapper218_init(nes_t* nes); 278 | int nes_mapper219_init(nes_t* nes); 279 | int nes_mapper220_init(nes_t* nes); 280 | int nes_mapper221_init(nes_t* nes); 281 | int nes_mapper222_init(nes_t* nes); 282 | int nes_mapper223_init(nes_t* nes); 283 | int nes_mapper224_init(nes_t* nes); 284 | int nes_mapper225_init(nes_t* nes); 285 | int nes_mapper226_init(nes_t* nes); 286 | int nes_mapper227_init(nes_t* nes); 287 | int nes_mapper228_init(nes_t* nes); 288 | int nes_mapper229_init(nes_t* nes); 289 | int nes_mapper230_init(nes_t* nes); 290 | int nes_mapper231_init(nes_t* nes); 291 | int nes_mapper232_init(nes_t* nes); 292 | int nes_mapper233_init(nes_t* nes); 293 | int nes_mapper234_init(nes_t* nes); 294 | int nes_mapper235_init(nes_t* nes); 295 | int nes_mapper236_init(nes_t* nes); 296 | int nes_mapper237_init(nes_t* nes); 297 | int nes_mapper238_init(nes_t* nes); 298 | int nes_mapper239_init(nes_t* nes); 299 | int nes_mapper240_init(nes_t* nes); 300 | int nes_mapper241_init(nes_t* nes); 301 | int nes_mapper242_init(nes_t* nes); 302 | int nes_mapper243_init(nes_t* nes); 303 | int nes_mapper244_init(nes_t* nes); 304 | int nes_mapper245_init(nes_t* nes); 305 | int nes_mapper246_init(nes_t* nes); 306 | int nes_mapper247_init(nes_t* nes); 307 | int nes_mapper248_init(nes_t* nes); 308 | int nes_mapper249_init(nes_t* nes); 309 | int nes_mapper250_init(nes_t* nes); 310 | int nes_mapper251_init(nes_t* nes); 311 | int nes_mapper252_init(nes_t* nes); 312 | int nes_mapper253_init(nes_t* nes); 313 | int nes_mapper254_init(nes_t* nes); 314 | int nes_mapper255_init(nes_t* nes); 315 | /* NES 2.0 mappers 256~511 */ 316 | int nes_mapper256_init(nes_t* nes); 317 | int nes_mapper257_init(nes_t* nes); 318 | int nes_mapper258_init(nes_t* nes); 319 | int nes_mapper259_init(nes_t* nes); 320 | int nes_mapper260_init(nes_t* nes); 321 | int nes_mapper261_init(nes_t* nes); 322 | int nes_mapper262_init(nes_t* nes); 323 | int nes_mapper263_init(nes_t* nes); 324 | int nes_mapper264_init(nes_t* nes); 325 | int nes_mapper265_init(nes_t* nes); 326 | int nes_mapper266_init(nes_t* nes); 327 | int nes_mapper267_init(nes_t* nes); 328 | int nes_mapper268_init(nes_t* nes); 329 | int nes_mapper269_init(nes_t* nes); 330 | int nes_mapper270_init(nes_t* nes); 331 | int nes_mapper271_init(nes_t* nes); 332 | int nes_mapper272_init(nes_t* nes); 333 | int nes_mapper273_init(nes_t* nes); 334 | int nes_mapper274_init(nes_t* nes); 335 | int nes_mapper275_init(nes_t* nes); 336 | int nes_mapper276_init(nes_t* nes); 337 | int nes_mapper277_init(nes_t* nes); 338 | int nes_mapper278_init(nes_t* nes); 339 | int nes_mapper279_init(nes_t* nes); 340 | int nes_mapper280_init(nes_t* nes); 341 | int nes_mapper281_init(nes_t* nes); 342 | int nes_mapper282_init(nes_t* nes); 343 | int nes_mapper283_init(nes_t* nes); 344 | int nes_mapper284_init(nes_t* nes); 345 | int nes_mapper285_init(nes_t* nes); 346 | int nes_mapper286_init(nes_t* nes); 347 | int nes_mapper287_init(nes_t* nes); 348 | int nes_mapper288_init(nes_t* nes); 349 | int nes_mapper289_init(nes_t* nes); 350 | int nes_mapper290_init(nes_t* nes); 351 | int nes_mapper291_init(nes_t* nes); 352 | int nes_mapper292_init(nes_t* nes); 353 | int nes_mapper293_init(nes_t* nes); 354 | int nes_mapper294_init(nes_t* nes); 355 | int nes_mapper295_init(nes_t* nes); 356 | int nes_mapper296_init(nes_t* nes); 357 | int nes_mapper297_init(nes_t* nes); 358 | int nes_mapper298_init(nes_t* nes); 359 | int nes_mapper299_init(nes_t* nes); 360 | int nes_mapper300_init(nes_t* nes); 361 | int nes_mapper301_init(nes_t* nes); 362 | int nes_mapper302_init(nes_t* nes); 363 | int nes_mapper303_init(nes_t* nes); 364 | int nes_mapper304_init(nes_t* nes); 365 | int nes_mapper305_init(nes_t* nes); 366 | int nes_mapper306_init(nes_t* nes); 367 | int nes_mapper307_init(nes_t* nes); 368 | int nes_mapper308_init(nes_t* nes); 369 | int nes_mapper309_init(nes_t* nes); 370 | int nes_mapper310_init(nes_t* nes); 371 | int nes_mapper311_init(nes_t* nes); 372 | int nes_mapper312_init(nes_t* nes); 373 | int nes_mapper313_init(nes_t* nes); 374 | int nes_mapper314_init(nes_t* nes); 375 | int nes_mapper315_init(nes_t* nes); 376 | int nes_mapper316_init(nes_t* nes); 377 | int nes_mapper317_init(nes_t* nes); 378 | int nes_mapper318_init(nes_t* nes); 379 | int nes_mapper319_init(nes_t* nes); 380 | int nes_mapper320_init(nes_t* nes); 381 | int nes_mapper321_init(nes_t* nes); 382 | int nes_mapper322_init(nes_t* nes); 383 | int nes_mapper323_init(nes_t* nes); 384 | int nes_mapper324_init(nes_t* nes); 385 | int nes_mapper325_init(nes_t* nes); 386 | int nes_mapper326_init(nes_t* nes); 387 | int nes_mapper327_init(nes_t* nes); 388 | int nes_mapper328_init(nes_t* nes); 389 | int nes_mapper329_init(nes_t* nes); 390 | int nes_mapper330_init(nes_t* nes); 391 | int nes_mapper331_init(nes_t* nes); 392 | int nes_mapper332_init(nes_t* nes); 393 | int nes_mapper333_init(nes_t* nes); 394 | int nes_mapper334_init(nes_t* nes); 395 | int nes_mapper335_init(nes_t* nes); 396 | int nes_mapper336_init(nes_t* nes); 397 | int nes_mapper337_init(nes_t* nes); 398 | int nes_mapper338_init(nes_t* nes); 399 | int nes_mapper339_init(nes_t* nes); 400 | int nes_mapper340_init(nes_t* nes); 401 | int nes_mapper341_init(nes_t* nes); 402 | int nes_mapper342_init(nes_t* nes); 403 | int nes_mapper343_init(nes_t* nes); 404 | int nes_mapper344_init(nes_t* nes); 405 | int nes_mapper345_init(nes_t* nes); 406 | int nes_mapper346_init(nes_t* nes); 407 | int nes_mapper347_init(nes_t* nes); 408 | int nes_mapper348_init(nes_t* nes); 409 | int nes_mapper349_init(nes_t* nes); 410 | int nes_mapper350_init(nes_t* nes); 411 | int nes_mapper351_init(nes_t* nes); 412 | int nes_mapper352_init(nes_t* nes); 413 | int nes_mapper353_init(nes_t* nes); 414 | int nes_mapper354_init(nes_t* nes); 415 | int nes_mapper355_init(nes_t* nes); 416 | int nes_mapper356_init(nes_t* nes); 417 | int nes_mapper357_init(nes_t* nes); 418 | int nes_mapper358_init(nes_t* nes); 419 | int nes_mapper359_init(nes_t* nes); 420 | int nes_mapper360_init(nes_t* nes); 421 | int nes_mapper361_init(nes_t* nes); 422 | int nes_mapper362_init(nes_t* nes); 423 | int nes_mapper363_init(nes_t* nes); 424 | int nes_mapper364_init(nes_t* nes); 425 | int nes_mapper365_init(nes_t* nes); 426 | int nes_mapper366_init(nes_t* nes); 427 | int nes_mapper367_init(nes_t* nes); 428 | int nes_mapper368_init(nes_t* nes); 429 | int nes_mapper369_init(nes_t* nes); 430 | int nes_mapper370_init(nes_t* nes); 431 | int nes_mapper371_init(nes_t* nes); 432 | int nes_mapper372_init(nes_t* nes); 433 | int nes_mapper373_init(nes_t* nes); 434 | int nes_mapper374_init(nes_t* nes); 435 | int nes_mapper375_init(nes_t* nes); 436 | int nes_mapper376_init(nes_t* nes); 437 | int nes_mapper377_init(nes_t* nes); 438 | int nes_mapper378_init(nes_t* nes); 439 | int nes_mapper379_init(nes_t* nes); 440 | int nes_mapper380_init(nes_t* nes); 441 | int nes_mapper381_init(nes_t* nes); 442 | int nes_mapper382_init(nes_t* nes); 443 | int nes_mapper383_init(nes_t* nes); 444 | int nes_mapper384_init(nes_t* nes); 445 | int nes_mapper385_init(nes_t* nes); 446 | int nes_mapper386_init(nes_t* nes); 447 | int nes_mapper387_init(nes_t* nes); 448 | int nes_mapper388_init(nes_t* nes); 449 | int nes_mapper389_init(nes_t* nes); 450 | int nes_mapper390_init(nes_t* nes); 451 | int nes_mapper391_init(nes_t* nes); 452 | int nes_mapper392_init(nes_t* nes); 453 | int nes_mapper393_init(nes_t* nes); 454 | int nes_mapper394_init(nes_t* nes); 455 | int nes_mapper395_init(nes_t* nes); 456 | int nes_mapper396_init(nes_t* nes); 457 | int nes_mapper397_init(nes_t* nes); 458 | int nes_mapper398_init(nes_t* nes); 459 | int nes_mapper399_init(nes_t* nes); 460 | int nes_mapper400_init(nes_t* nes); 461 | int nes_mapper401_init(nes_t* nes); 462 | int nes_mapper402_init(nes_t* nes); 463 | int nes_mapper403_init(nes_t* nes); 464 | int nes_mapper404_init(nes_t* nes); 465 | int nes_mapper405_init(nes_t* nes); 466 | int nes_mapper406_init(nes_t* nes); 467 | int nes_mapper407_init(nes_t* nes); 468 | int nes_mapper408_init(nes_t* nes); 469 | int nes_mapper409_init(nes_t* nes); 470 | int nes_mapper410_init(nes_t* nes); 471 | int nes_mapper411_init(nes_t* nes); 472 | int nes_mapper412_init(nes_t* nes); 473 | int nes_mapper413_init(nes_t* nes); 474 | int nes_mapper414_init(nes_t* nes); 475 | int nes_mapper415_init(nes_t* nes); 476 | int nes_mapper416_init(nes_t* nes); 477 | int nes_mapper417_init(nes_t* nes); 478 | int nes_mapper418_init(nes_t* nes); 479 | int nes_mapper419_init(nes_t* nes); 480 | int nes_mapper420_init(nes_t* nes); 481 | int nes_mapper421_init(nes_t* nes); 482 | int nes_mapper422_init(nes_t* nes); 483 | int nes_mapper423_init(nes_t* nes); 484 | int nes_mapper424_init(nes_t* nes); 485 | int nes_mapper425_init(nes_t* nes); 486 | int nes_mapper426_init(nes_t* nes); 487 | int nes_mapper427_init(nes_t* nes); 488 | int nes_mapper428_init(nes_t* nes); 489 | int nes_mapper429_init(nes_t* nes); 490 | int nes_mapper430_init(nes_t* nes); 491 | int nes_mapper431_init(nes_t* nes); 492 | int nes_mapper432_init(nes_t* nes); 493 | int nes_mapper433_init(nes_t* nes); 494 | int nes_mapper434_init(nes_t* nes); 495 | int nes_mapper435_init(nes_t* nes); 496 | int nes_mapper436_init(nes_t* nes); 497 | int nes_mapper437_init(nes_t* nes); 498 | int nes_mapper438_init(nes_t* nes); 499 | int nes_mapper439_init(nes_t* nes); 500 | int nes_mapper440_init(nes_t* nes); 501 | int nes_mapper441_init(nes_t* nes); 502 | int nes_mapper442_init(nes_t* nes); 503 | int nes_mapper443_init(nes_t* nes); 504 | int nes_mapper444_init(nes_t* nes); 505 | int nes_mapper445_init(nes_t* nes); 506 | int nes_mapper446_init(nes_t* nes); 507 | int nes_mapper447_init(nes_t* nes); 508 | int nes_mapper448_init(nes_t* nes); 509 | int nes_mapper449_init(nes_t* nes); 510 | int nes_mapper450_init(nes_t* nes); 511 | int nes_mapper451_init(nes_t* nes); 512 | int nes_mapper452_init(nes_t* nes); 513 | int nes_mapper453_init(nes_t* nes); 514 | int nes_mapper454_init(nes_t* nes); 515 | int nes_mapper455_init(nes_t* nes); 516 | int nes_mapper456_init(nes_t* nes); 517 | int nes_mapper457_init(nes_t* nes); 518 | int nes_mapper458_init(nes_t* nes); 519 | int nes_mapper459_init(nes_t* nes); 520 | int nes_mapper460_init(nes_t* nes); 521 | int nes_mapper461_init(nes_t* nes); 522 | int nes_mapper462_init(nes_t* nes); 523 | int nes_mapper463_init(nes_t* nes); 524 | int nes_mapper464_init(nes_t* nes); 525 | int nes_mapper465_init(nes_t* nes); 526 | int nes_mapper466_init(nes_t* nes); 527 | int nes_mapper467_init(nes_t* nes); 528 | int nes_mapper468_init(nes_t* nes); 529 | int nes_mapper469_init(nes_t* nes); 530 | int nes_mapper470_init(nes_t* nes); 531 | int nes_mapper471_init(nes_t* nes); 532 | int nes_mapper472_init(nes_t* nes); 533 | int nes_mapper473_init(nes_t* nes); 534 | int nes_mapper474_init(nes_t* nes); 535 | int nes_mapper475_init(nes_t* nes); 536 | int nes_mapper476_init(nes_t* nes); 537 | int nes_mapper477_init(nes_t* nes); 538 | int nes_mapper478_init(nes_t* nes); 539 | int nes_mapper479_init(nes_t* nes); 540 | int nes_mapper480_init(nes_t* nes); 541 | int nes_mapper481_init(nes_t* nes); 542 | int nes_mapper482_init(nes_t* nes); 543 | int nes_mapper483_init(nes_t* nes); 544 | int nes_mapper484_init(nes_t* nes); 545 | int nes_mapper485_init(nes_t* nes); 546 | int nes_mapper486_init(nes_t* nes); 547 | int nes_mapper487_init(nes_t* nes); 548 | int nes_mapper488_init(nes_t* nes); 549 | int nes_mapper489_init(nes_t* nes); 550 | int nes_mapper490_init(nes_t* nes); 551 | int nes_mapper491_init(nes_t* nes); 552 | int nes_mapper492_init(nes_t* nes); 553 | int nes_mapper493_init(nes_t* nes); 554 | int nes_mapper494_init(nes_t* nes); 555 | int nes_mapper495_init(nes_t* nes); 556 | int nes_mapper496_init(nes_t* nes); 557 | int nes_mapper497_init(nes_t* nes); 558 | int nes_mapper498_init(nes_t* nes); 559 | int nes_mapper499_init(nes_t* nes); 560 | int nes_mapper500_init(nes_t* nes); 561 | int nes_mapper501_init(nes_t* nes); 562 | int nes_mapper502_init(nes_t* nes); 563 | int nes_mapper503_init(nes_t* nes); 564 | int nes_mapper504_init(nes_t* nes); 565 | int nes_mapper505_init(nes_t* nes); 566 | int nes_mapper506_init(nes_t* nes); 567 | int nes_mapper507_init(nes_t* nes); 568 | int nes_mapper508_init(nes_t* nes); 569 | int nes_mapper509_init(nes_t* nes); 570 | int nes_mapper510_init(nes_t* nes); 571 | int nes_mapper511_init(nes_t* nes); 572 | 573 | #ifdef __cplusplus 574 | } 575 | #endif 576 | 577 | --------------------------------------------------------------------------------