├── 6502 ├── 6502Emulator │ ├── CMakeLists.txt │ └── src │ │ └── olcNes_PPU_Backgrounds.cpp └── 6502Lib │ ├── CMakeLists.txt │ └── src │ └── public │ ├── Bus.cpp │ ├── Bus.h │ ├── Cartridge.cpp │ ├── Cartridge.h │ ├── Mapper.cpp │ ├── Mapper.h │ ├── Mapper_000.cpp │ ├── Mapper_000.h │ ├── Mapper_001.cpp │ ├── Mapper_001.h │ ├── Mapper_002.cpp │ ├── Mapper_002.h │ ├── Mapper_003.cpp │ ├── Mapper_003.h │ ├── Mapper_004.cpp │ ├── Mapper_004.h │ ├── Mapper_066.cpp │ ├── Mapper_066.h │ ├── Nes2A03.cpp │ ├── Nes2A03.h │ ├── Nes2C02.cpp │ ├── Nes2C02.h │ ├── Nes6502.cpp │ ├── Nes6502.h │ ├── olcPGEX_Sound.h │ └── olcPixelGameEngine.h ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .vscode ├── c_cpp_properties.json └── settings.json ├── CMakeLists.txt ├── LICENSE ├── make_VS2019.bat ├── readme.md ├── readme6502.md └── rom ├── 23.nes ├── Adventure Island 3 (USA).nes ├── Adventure Island Classic (Europe).nes ├── Adventure Island Part II, The (Europe).nes ├── Castlevania2.nes ├── Doraemon (J).nes ├── Double Dragon II - The Revenge (USA) (Rev A).nes ├── Dragon Power (USA).nes ├── Duck Hunt (JUE).nes ├── Gumshoe.nes ├── Mega Man 3 (USA).nes ├── Mega Man 6 (USA).nes ├── Metal Max (Japan).nes ├── SolomonsKey.nes ├── Zoda's Revenge - StarTropics II (USA).nes ├── chenlong.nes ├── contra.nes ├── dazhuankuai.nes ├── duola.nes ├── emocheng.nes ├── maxituan.nes ├── mmc1_a12.nes ├── nestest.nes ├── rexuezuqiu.nes ├── smb.nes ├── smb4.nes ├── 热血新纪录.nes ├── 热血时代剧中文版.nes ├── 热血曲棍球.nes ├── 热血格斗中文版.nes ├── 热血物语中文版.nes ├── 热血硬派.nes ├── 热血篮球中文版.nes ├── 热血足球cn.nes ├── 热血足球联盟cn.nes ├── 热血进行曲中文版.nes └── 热血高校中文版.nes /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: 6502Emulator 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally 16 | # well on Windows or Mac. You can convert this to a matrix build if you need 17 | # cross-platform coverage. 18 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | with: 24 | fetch-depth: 0 25 | - name: Configure CMake 26 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 27 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 28 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 29 | - name: 安装mesa-common-dev 30 | run: sudo apt-get install mesa-common-dev && 31 | sudo apt-get install libgl1-mesa-dev libglu1-mesa-dev 32 | - name: 安装alssa 33 | run: sudo apt update && sudo apt-get install alsa-base alsa-utils alsa-source libasound2-dev 34 | - name: Build 35 | # Build your program with the given configuration 36 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} 37 | - name: 编译make 38 | run: cd build && make -j$(nproc) 39 | - uses: actions/upload-artifact@master 40 | with: 41 | name: 导出二进制文件 42 | path: ./build/6502/6502Emulator/6502Emulator 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/6502/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/bin/gcc" 10 | } 11 | ], 12 | "version": 4 13 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "C_Cpp.clang_format_fallbackStyle": "google" 3 | } -------------------------------------------------------------------------------- /6502/6502Emulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project( 6502Emulator ) 4 | 5 | set( CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DM6502_DEBUG" ) #so we can add the DEBUG preprocessor define and other flags to stay in debug mode - see https://cmake.org/Wiki/CMake_Useful_Variables#Compilers_and_Tools 6 | set( CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DM6502_DEBUG" ) 7 | set(CMAKE_CXX_STANDARD 17) 8 | 9 | if(MSVC) 10 | add_compile_options(/MP) #Use multiple processors when building 11 | add_compile_options(/W4 /wd4201 ) #Warning level 4, all warnings are errors 12 | else() 13 | add_compile_options(-W -Wall ) #All Warnings, all warnings are errors 14 | endif() 15 | 16 | set (M6502_SOURCES 17 | "src/olcNes_PPU_Backgrounds.cpp" 18 | ) 19 | 20 | source_group("src" FILES ${M6502_SOURCES}) 21 | 22 | add_executable( 6502Emulator ${M6502_SOURCES} ) 23 | add_dependencies( 6502Emulator M6502Lib ) 24 | target_link_libraries(6502Emulator M6502Lib) 25 | 26 | if (${APPLE}) 27 | 28 | ######################################################### 29 | # FIND CARBON 30 | ######################################################### 31 | FIND_LIBRARY(CARBON_LIBRARY Carbon) 32 | target_link_libraries(6502Emulator ${CARBON_LIBRARY}) 33 | 34 | ######################################################### 35 | # FIND PNG 36 | ######################################################### 37 | find_package(PNG REQUIRED) 38 | include_directories(${PNG_INCLUDE_DIRS}) 39 | link_directories(${PNG_LIBRARY_DIRS}) 40 | add_definitions(${PNG_DEFINITIONS}) 41 | if(NOT PNG_FOUND) 42 | message(ERROR " PNG not found!") 43 | endif(NOT PNG_FOUND) 44 | target_link_libraries(6502Emulator ${PNG_LIBRARIES}) 45 | 46 | ######################################################### 47 | # FIND GLUT 48 | ######################################################### 49 | find_package(GLUT REQUIRED) 50 | include_directories(${GLUT_INCLUDE_DIRS}) 51 | link_directories(${GLUT_LIBRARY_DIRS}) 52 | add_definitions(${GLUT_DEFINITIONS}) 53 | if(NOT GLUT_FOUND) 54 | message(ERROR " GLUT not found!") 55 | endif(NOT GLUT_FOUND) 56 | target_link_libraries(6502Emulator ${GLUT_LIBRARIES}) 57 | 58 | ######################################################### 59 | # FIND OPENGL 60 | ######################################################### 61 | find_package(OpenGL REQUIRED) 62 | include_directories(${OpenGL_INCLUDE_DIRS}) 63 | link_directories(${OpenGL_LIBRARY_DIRS}) 64 | add_definitions(${OpenGL_DEFINITIONS}) 65 | if(NOT OPENGL_FOUND) 66 | message(ERROR " OPENGL not found!") 67 | endif(NOT OPENGL_FOUND) 68 | target_link_libraries(6502Emulator ${OPENGL_LIBRARIES}) 69 | else() 70 | target_LINK_LIBRARIES(6502Emulator -lasound -lX11 -lGL -lpthread -lpng -lstdc++fs -std=c++17) 71 | endif() 72 | 73 | -------------------------------------------------------------------------------- /6502/6502Emulator/src/olcNes_PPU_Backgrounds.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #include 20 | #include 21 | #include 22 | 23 | #include "Bus.h" 24 | 25 | #define OLC_PGE_APPLICATION 26 | #include "olcPixelGameEngine.h" 27 | 28 | #define OLC_PGEX_SOUND 29 | #include "olcPGEX_Sound.h" 30 | 31 | class Demo_olcNES : public olc::PixelGameEngine { 32 | public: 33 | Demo_olcNES() { sAppName = "olcNES Sound Demonstration"; } 34 | 35 | private: 36 | // The NES 37 | Bus nes; 38 | std::shared_ptr cart; 39 | bool bEmulationRun = true; 40 | float fResidualTime = 0.0f; 41 | 42 | uint8_t nSelectedPalette = 0x00; 43 | 44 | std::list audio[4]; 45 | float fAccumulatedTime = 0.0f; 46 | 47 | private: 48 | // Support Utilities 49 | std::map mapAsm; 50 | 51 | std::string hex(uint32_t n, uint8_t d) { 52 | std::string s(d, '0'); 53 | for (int i = d - 1; i >= 0; i--, n >>= 4) 54 | s[i] = "0123456789ABCDEF"[n & 0xF]; 55 | return s; 56 | } 57 | 58 | void DrawRam(int x, int y, uint16_t nAddr, int nRows, int nColumns) { 59 | int nRamX = x, nRamY = y; 60 | for (int row = 0; row < nRows; row++) { 61 | std::string sOffset = "$" + hex(nAddr, 4) + ":"; 62 | for (int col = 0; col < nColumns; col++) { 63 | sOffset += " " + hex(nes.cpuRead(nAddr, true), 2); 64 | nAddr += 1; 65 | } 66 | DrawString(nRamX, nRamY, sOffset); 67 | nRamY += 10; 68 | } 69 | } 70 | 71 | void DrawCpu(int x, int y) { 72 | std::string status = "STATUS: "; 73 | DrawString(x, y, "STATUS:", olc::WHITE); 74 | DrawString(x + 64, y, "N", 75 | nes.cpu.status & Nes6502::N ? olc::GREEN : olc::RED); 76 | DrawString(x + 80, y, "V", 77 | nes.cpu.status & Nes6502::V ? olc::GREEN : olc::RED); 78 | DrawString(x + 96, y, "-", 79 | nes.cpu.status & Nes6502::U ? olc::GREEN : olc::RED); 80 | DrawString(x + 112, y, "B", 81 | nes.cpu.status & Nes6502::B ? olc::GREEN : olc::RED); 82 | DrawString(x + 128, y, "D", 83 | nes.cpu.status & Nes6502::D ? olc::GREEN : olc::RED); 84 | DrawString(x + 144, y, "I", 85 | nes.cpu.status & Nes6502::I ? olc::GREEN : olc::RED); 86 | DrawString(x + 160, y, "Z", 87 | nes.cpu.status & Nes6502::Z ? olc::GREEN : olc::RED); 88 | DrawString(x + 178, y, "C", 89 | nes.cpu.status & Nes6502::C ? olc::GREEN : olc::RED); 90 | DrawString(x, y + 10, "PC: $" + hex(nes.cpu.pc, 4)); 91 | DrawString( 92 | x, y + 20, 93 | "A: $" + hex(nes.cpu.a, 2) + " [" + std::to_string(nes.cpu.a) + "]"); 94 | DrawString( 95 | x, y + 30, 96 | "X: $" + hex(nes.cpu.x, 2) + " [" + std::to_string(nes.cpu.x) + "]"); 97 | DrawString( 98 | x, y + 40, 99 | "Y: $" + hex(nes.cpu.y, 2) + " [" + std::to_string(nes.cpu.y) + "]"); 100 | DrawString(x, y + 50, "Stack P: $" + hex(nes.cpu.stkp, 4)); 101 | } 102 | 103 | void DrawCode(int x, int y, int nLines) { 104 | auto it_a = mapAsm.find(nes.cpu.pc); 105 | int nLineY = (nLines >> 1) * 10 + y; 106 | if (it_a != mapAsm.end()) { 107 | DrawString(x, nLineY, (*it_a).second, olc::CYAN); 108 | while (nLineY < (nLines * 10) + y) { 109 | nLineY += 10; 110 | if (++it_a != mapAsm.end()) { 111 | DrawString(x, nLineY, (*it_a).second); 112 | } 113 | } 114 | } 115 | 116 | it_a = mapAsm.find(nes.cpu.pc); 117 | nLineY = (nLines >> 1) * 10 + y; 118 | if (it_a != mapAsm.end()) { 119 | while (nLineY > y) { 120 | nLineY -= 10; 121 | if (--it_a != mapAsm.end()) { 122 | DrawString(x, nLineY, (*it_a).second); 123 | } 124 | } 125 | } 126 | } 127 | 128 | void DrawAudio(int channel, int x, int y) { 129 | FillRect(x, y, 120, 120, olc::BLACK); 130 | int i = 0; 131 | for (auto s : audio[channel]) { 132 | Draw(x + i, y + (s >> (channel == 2 ? 5 : 4)), olc::YELLOW); 133 | i++; 134 | } 135 | } 136 | 137 | // This function is called by the underlying sound hardware 138 | // which runs in a different thread. It is automatically 139 | // synchronised with the sample rate of the sound card, and 140 | // expects a single "sample" to be returned, whcih ultimately 141 | // makes its way to your speakers, and then your ears, for that 142 | // lovely 8-bit bliss... but, that means we've some thread 143 | // handling to deal with, since we want both the PGE thread 144 | // and the sound system thread to interact with the emulator. 145 | 146 | static Demo_olcNES 147 | *pInstance; // Static variable that will hold a pointer to "this" 148 | 149 | static float SoundOut(int nChannel, float fGlobalTime, float fTimeStep) { 150 | if (nChannel == 0) { 151 | while (!pInstance->nes.clock()) { 152 | } 153 | return static_cast( 154 | pInstance->nes.dAudioSample); // dAudioSample是最后的播放数据 155 | } else 156 | return 0.0f; 157 | } 158 | 159 | bool OnUserCreate() override { 160 | // Load the cartridge 161 | cart = std::make_shared("./rom/smb.nes"); 162 | 163 | if (!cart->ImageValid()) return false; 164 | 165 | // Insert into NES 166 | nes.insertCartridge(cart); 167 | 168 | // Extract dissassembly 169 | // mapAsm = nes.cpu.disassemble(0x0000, 0xFFFF); 170 | 171 | for (int i = 0; i < 4; i++) { 172 | for (int j = 0; j < 120; j++) audio[i].push_back(0); 173 | } 174 | 175 | // Reset NES 176 | nes.reset(); 177 | 178 | // Initialise PGEX sound system, and give it a function to 179 | // call which returns a sound sample on demand 180 | pInstance = this; 181 | nes.SetSampleFrequency(44100); 182 | olc::SOUND::InitialiseAudio(44100, 1, 8, 512); 183 | olc::SOUND::SetUserSynthFunction(SoundOut); 184 | return true; 185 | } 186 | 187 | // We must play nicely now with the sound hardware, so unload 188 | // it when the application terminates 189 | bool OnUserDestroy() override { 190 | olc::SOUND::DestroyAudio(); 191 | return true; 192 | } 193 | 194 | bool OnUserUpdate(float fElapsedTime) override { 195 | #ifdef __APPLE__ 196 | { EmulatorUpdateWithoutAudio(fElapsedTime); } 197 | #else 198 | { EmulatorUpdateWithAudio(fElapsedTime); } 199 | #endif 200 | return true; 201 | } 202 | 203 | // This performs an emulation update but synced to audio, so it cant 204 | // perform stepping through code or frames. Essentially, it runs 205 | // the emulation in real time now, so only accepts "controller" input 206 | // and updates the display 207 | bool EmulatorUpdateWithAudio(float fElapsedTime) { 208 | // Sample audio channel output roughly once per frame 209 | fAccumulatedTime += fElapsedTime; 210 | if (fAccumulatedTime >= 1.0f / 60.0f) { 211 | fAccumulatedTime -= (1.0f / 60.0f); 212 | audio[0].pop_front(); 213 | audio[0].push_back(nes.apu.pulse1_visual); // 可视化音频 214 | audio[1].pop_front(); 215 | audio[1].push_back(nes.apu.pulse2_visual); 216 | audio[2].pop_front(); 217 | audio[2].push_back(nes.apu.noise_visual); 218 | } 219 | 220 | Clear(olc::DARK_BLUE); 221 | 222 | // Handle input for controller in port #1 223 | nes.controller[0] = 0x00; 224 | nes.controller[0] |= GetKey(olc::Key::K).bHeld ? 0x80 : 0x00; // A Button 225 | nes.controller[0] |= GetKey(olc::Key::J).bHeld ? 0x40 : 0x00; // B Button 226 | nes.controller[0] |= GetKey(olc::Key::Y).bHeld ? 0x20 : 0x00; // Select 227 | nes.controller[0] |= GetKey(olc::Key::T).bHeld ? 0x10 : 0x00; // Start 228 | nes.controller[0] |= GetKey(olc::Key::W).bHeld ? 0x08 : 0x00; 229 | nes.controller[0] |= GetKey(olc::Key::S).bHeld ? 0x04 : 0x00; 230 | nes.controller[0] |= GetKey(olc::Key::A).bHeld ? 0x02 : 0x00; 231 | nes.controller[0] |= GetKey(olc::Key::D).bHeld ? 0x01 : 0x00; 232 | 233 | if (GetKey(olc::Key::BACK).bPressed) nes.reset(); 234 | if (GetKey(olc::Key::P).bPressed) (++nSelectedPalette) &= 0x07; 235 | 236 | // 存储游戏状态 237 | if (GetKey(olc::Key::F1).bPressed) { 238 | std::ofstream ofs; 239 | ofs.open("./save_0.dat", std::ofstream::binary); 240 | ofs.write(reinterpret_cast(&nes), sizeof(nes)); 241 | ofs.close(); 242 | } 243 | 244 | // 还原游戏状态 245 | if (GetKey(olc::Key::F2).bPressed) { 246 | std::ifstream ifs; 247 | ifs.open("./save_0.dat", std::ofstream::binary); 248 | ifs.read(reinterpret_cast(&nes), sizeof(nes)); 249 | ifs.close(); 250 | } 251 | 252 | DrawCpu(516, 2); 253 | // DrawCode(516, 72, 26); 254 | 255 | // Draw OAM Contents (first 26 out of 64) 256 | // ====================================== 257 | for (int i = 0; i < 26; i++) { 258 | std::string s = hex(i, 2) + ": (" + 259 | std::to_string(nes.ppu.pOAM[i * 4 + 3]) + ", " + 260 | std::to_string(nes.ppu.pOAM[i * 4 + 0]) + ") " + 261 | "ID: " + hex(nes.ppu.pOAM[i * 4 + 1], 2) + 262 | +" AT: " + hex(nes.ppu.pOAM[i * 4 + 2], 2); 263 | DrawString(516, 72 + i * 10, s); 264 | // DrawSprite(516 + 231, 72 + i * 10, &nes.ppu.GetSpriteTitle(516 + 265 | // 231, 72 + i * 10, nes.ppu.pOAM[i * 4 + 1], nes.ppu.pOAM[i * 4 + 266 | // 2], nSelectedPalette, i)); 267 | } 268 | 269 | // Draw AUDIO Channels 270 | // DrawAudio(0, 520, 72); 271 | // DrawAudio(1, 644, 72); 272 | // DrawAudio(2, 520, 196); 273 | // DrawAudio(3, 644, 196); 274 | 275 | // Draw Palettes & Pattern Tables 276 | // ============================================== 277 | const int nSwatchSize = 6; 278 | for (int p = 0; p < 8; p++) // For each palette 279 | for (int s = 0; s < 4; s++) // For each index 280 | FillRect(516 + p * (nSwatchSize * 5) + s * nSwatchSize, 340, 281 | nSwatchSize, nSwatchSize, 282 | nes.ppu.GetColourFromPaletteRam(p, s)); 283 | 284 | // Draw selection reticule around selected palette 285 | DrawRect(516 + nSelectedPalette * (nSwatchSize * 5) - 1, 339, 286 | (nSwatchSize * 4), nSwatchSize, olc::WHITE); 287 | 288 | // Generate Pattern Tables 289 | DrawSprite(516, 348, &nes.ppu.GetPatternTable(0, nSelectedPalette)); 290 | DrawSprite(648, 348, &nes.ppu.GetPatternTable(1, nSelectedPalette)); 291 | 292 | // Draw rendered output 293 | // ======================================================== 294 | DrawSprite(0, 0, &nes.ppu.GetScreen(), 2); 295 | return true; 296 | } 297 | 298 | // This performs emulation with no audio synchronisation, so it is just 299 | // as before, in all the previous videos 300 | bool EmulatorUpdateWithoutAudio(float fElapsedTime) { 301 | Clear(olc::DARK_BLUE); 302 | 303 | // Handle input for controller in port #1 304 | nes.controller[0] = 0x00; 305 | nes.controller[0] |= GetKey(olc::Key::K).bHeld ? 0x80 : 0x00; // A Button 306 | nes.controller[0] |= GetKey(olc::Key::J).bHeld ? 0x40 : 0x00; // B Button 307 | nes.controller[0] |= GetKey(olc::Key::Y).bHeld ? 0x20 : 0x00; // Select 308 | nes.controller[0] |= GetKey(olc::Key::T).bHeld ? 0x10 : 0x00; // Start 309 | nes.controller[0] |= GetKey(olc::Key::W).bHeld ? 0x08 : 0x00; 310 | nes.controller[0] |= GetKey(olc::Key::S).bHeld ? 0x04 : 0x00; 311 | nes.controller[0] |= GetKey(olc::Key::A).bHeld ? 0x02 : 0x00; 312 | nes.controller[0] |= GetKey(olc::Key::D).bHeld ? 0x01 : 0x00; 313 | 314 | if (GetKey(olc::Key::SPACE).bPressed) bEmulationRun = !bEmulationRun; 315 | if (GetKey(olc::Key::BACK).bPressed) nes.reset(); 316 | if (GetKey(olc::Key::P).bPressed) (++nSelectedPalette) &= 0x07; 317 | 318 | if (bEmulationRun) { 319 | if (fResidualTime > 0.0f) 320 | fResidualTime -= fElapsedTime; 321 | else { 322 | fResidualTime += (1.0f / 60.0f) - fElapsedTime; 323 | do { 324 | nes.clock(); 325 | } while (!nes.ppu.frame_complete); 326 | nes.ppu.frame_complete = false; 327 | } 328 | } else { 329 | // Emulate code step-by-step 330 | if (GetKey(olc::Key::C).bPressed) { 331 | // Clock enough times to execute a whole CPU instruction 332 | do { 333 | nes.clock(); 334 | } while (!nes.cpu.complete()); 335 | // CPU clock runs slower than system clock, so it may be 336 | // complete for additional system clock cycles. Drain 337 | // those out 338 | do { 339 | nes.clock(); 340 | } while (nes.cpu.complete()); 341 | } 342 | 343 | // Emulate one whole frame 344 | if (GetKey(olc::Key::F).bPressed) { 345 | // Clock enough times to draw a single frame 346 | do { 347 | nes.clock(); 348 | } while (!nes.ppu.frame_complete); 349 | // Use residual clock cycles to complete current instruction 350 | do { 351 | nes.clock(); 352 | } while (!nes.cpu.complete()); 353 | // Reset frame completion flag 354 | nes.ppu.frame_complete = false; 355 | } 356 | // 存储游戏状态 357 | if (GetKey(olc::Key::Z).bPressed) { 358 | std::ofstream ofs; 359 | ofs.open("./save_0.dat", std::ofstream::binary); 360 | ofs.write(reinterpret_cast(&nes), sizeof(nes)); 361 | ofs.close(); 362 | } 363 | } 364 | 365 | DrawCpu(516, 2); 366 | // DrawCode(516, 72, 26); 367 | 368 | // Draw OAM Contents (first 26 out of 64) 369 | // ====================================== 370 | for (int i = 0; i < 26; i++) { 371 | std::string s = hex(i, 2) + ": (" + 372 | std::to_string(nes.ppu.pOAM[i * 4 + 3]) + ", " + 373 | std::to_string(nes.ppu.pOAM[i * 4 + 0]) + ") " + 374 | "ID: " + hex(nes.ppu.pOAM[i * 4 + 1], 2) + 375 | +" AT: " + hex(nes.ppu.pOAM[i * 4 + 2], 2); 376 | DrawString(516, 72 + i * 10, s); 377 | } 378 | 379 | // Draw Palettes & Pattern Tables 380 | // ============================================== 381 | const int nSwatchSize = 6; 382 | for (int p = 0; p < 8; p++) // For each palette 383 | for (int s = 0; s < 4; s++) // For each index 384 | FillRect(516 + p * (nSwatchSize * 5) + s * nSwatchSize, 340, 385 | nSwatchSize, nSwatchSize, 386 | nes.ppu.GetColourFromPaletteRam(p, s)); 387 | 388 | // Draw selection reticule around selected palette 389 | DrawRect(516 + nSelectedPalette * (nSwatchSize * 5) - 1, 339, 390 | (nSwatchSize * 4), nSwatchSize, olc::WHITE); 391 | 392 | // Generate Pattern Tables 393 | DrawSprite(516, 348, &nes.ppu.GetPatternTable(0, nSelectedPalette)); 394 | DrawSprite(648, 348, &nes.ppu.GetPatternTable(1, nSelectedPalette)); 395 | 396 | // Draw rendered output 397 | // ======================================================== 398 | DrawSprite(0, 0, &nes.ppu.GetScreen(), 2); 399 | return true; 400 | } 401 | }; 402 | 403 | // Provide implementation for our static pointer 404 | Demo_olcNES *Demo_olcNES::pInstance = nullptr; 405 | 406 | int main() { 407 | Demo_olcNES demo; 408 | demo.Construct(780, 480, 2, 2); 409 | demo.Start(); 410 | return 0; 411 | } 412 | -------------------------------------------------------------------------------- /6502/6502Lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project( M6502Lib ) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | 7 | if(MSVC) 8 | add_compile_options(/MP) #Use multiple processors when building 9 | add_compile_options(/W4 /wd4201) #Warning level 4, all warnings are errors 10 | else() 11 | add_compile_options(-W -Wall) #All Warnings, all warnings are errors 12 | endif() 13 | 14 | set (M6502_SOURCES 15 | "src/public/Bus.h" 16 | "src/public/Bus.cpp" 17 | "src/public/Cartridge.h" 18 | "src/public/Cartridge.cpp" 19 | "src/public/Mapper.h" 20 | "src/public/Mapper.cpp" 21 | "src/public/Mapper_000.h" 22 | "src/public/Mapper_000.cpp" 23 | "src/public/Mapper_001.h" 24 | "src/public/Mapper_001.cpp" 25 | "src/public/Mapper_002.h" 26 | "src/public/Mapper_002.cpp" 27 | "src/public/Mapper_003.h" 28 | "src/public/Mapper_003.cpp" 29 | "src/public/Mapper_004.h" 30 | "src/public/Mapper_004.cpp" 31 | "src/public/Mapper_066.h" 32 | "src/public/Mapper_066.cpp" 33 | "src/public/Nes2C02.h" 34 | "src/public/Nes2C02.cpp" 35 | "src/public/Nes6502.h" 36 | "src/public/Nes6502.cpp" 37 | "src/public/olcPGEX_Sound.h" 38 | "src/public/olcPixelGameEngine.h" 39 | "src/public/Nes2A03.cpp" 40 | "src/public/Nes2A03.h" 41 | ) 42 | 43 | source_group("src" FILES ${M6502_SOURCES}) 44 | 45 | add_library( M6502Lib ${M6502_SOURCES} ) 46 | 47 | target_include_directories ( M6502Lib PUBLIC "${PROJECT_SOURCE_DIR}/src/public") 48 | target_include_directories ( M6502Lib PRIVATE "${PROJECT_SOURCE_DIR}/src/private") 49 | 50 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Bus.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #include "Bus.h" 20 | 21 | Bus::Bus() { 22 | // 在nes最初启动时,连接cpu到bus 23 | cpu.ConnectBus(this); 24 | } 25 | 26 | Bus::~Bus() {} 27 | 28 | void Bus::SetSampleFrequency(uint32_t sample_rate) { 29 | // sample_rate是声音采样率,这个采样率决定一个声音数据的发生周期 30 | dAudioTimePerSystemSample = 1.0 / static_cast(sample_rate); 31 | dAudioTimePerNESClock = 32 | 1.0 / 5369318.0; // 1 / ppu的时钟频率 = ppu的时钟周期 33 | } 34 | 35 | void Bus::cpuWrite(uint16_t addr, uint8_t data) { 36 | if (cart->cpuWrite(addr, data)) { 37 | // cartridge进行首次判断 38 | // cartridge可以根據需要將內容映射到其他任何地址 39 | // 通过这种方式,可以扩展硬件外设,比如枪。 40 | } else if (addr >= 0x0000 && addr <= 0x1FFF) { 41 | // 系统内存地址 42 | // 只有一共8kb,但是只有2kb可以用 43 | // 其他地址是2kb的镜像,使用&0x07ff运算,只使用2kb 44 | cpuRam[addr & 0x07FF] = data; 45 | } else if (addr >= 0x2000 && addr <= 0x3FFF) { 46 | // PPU地址 47 | // ppu只有8个寄存器,在0x2000 - 48 | // 0x3fff这个范围内,都是这8个寄存器的重复镜像数据 49 | // 所以直接使用&0x0007获取正确地址即可 50 | ppu.cpuWrite(addr & 0x0007, data); 51 | } else if ((addr >= 0x4000 && addr <= 0x4013) || addr == 0x4015 || 52 | addr == 0x4017) { 53 | // APU地址 54 | // apu的几个寄存器地址 0x4000 - 0x4013 0x4015 0x4017 55 | apu.cpuWrite(addr, data); 56 | } else if (addr == 0x4014) { 57 | // 0x4014地址是DMA传送开始标志 58 | // 当此地址被写入数据时,说明发生DMA传送 59 | // CPU周期将停止,将OAM寄存器中的数据传送到PPU中,64个精灵,直到传送结束 60 | dma_page = data; 61 | dma_addr = 0x00; 62 | dma_transfer = true; 63 | } else if (addr >= 0x4016 && addr <= 0x4017) { 64 | // 控制寄存器地址 65 | // 0x4016 - 0x4017是控制寄存器地址,cpu通过读取此地址中的数据 66 | // 获取控制命令 67 | controller_state[addr & 0x0001] = controller[addr & 0x0001]; 68 | } 69 | } 70 | 71 | uint8_t Bus::cpuRead(uint16_t addr, bool bReadOnly) { 72 | uint8_t data = 0x00; 73 | if (cart->cpuRead(addr, data)) { 74 | // Cartridge 地址 75 | } else if (addr >= 0x0000 && addr <= 0x1FFF) { 76 | // 系统内存地址RAM, 每2kb都是镜像 2kb = 0x07FF 77 | data = cpuRam[addr & 0x07FF]; 78 | } else if (addr >= 0x2000 && addr <= 0x3FFF) { 79 | // PPU地址 每8个字节都是镜像数据 80 | data = ppu.cpuRead(addr & 0x0007, bReadOnly); 81 | } else if (addr == 0x4015) { 82 | // APU读寄存器 83 | data = apu.cpuRead(addr); 84 | } else if (addr >= 0x4016 && addr <= 0x4017) { 85 | // 读取控制命令数据 86 | data = (controller_state[addr & 0x0001] & 0x80) > 0; 87 | controller_state[addr & 0x0001] <<= 1; 88 | } 89 | 90 | return data; 91 | } 92 | 93 | void Bus::insertCartridge(const std::shared_ptr &cartridge) { 94 | // 插入卡带, 95 | // 将卡带连接到bus和ppu 96 | this->cart = cartridge; 97 | ppu.ConnectCartridge(cartridge); 98 | } 99 | 100 | void Bus::reset() { 101 | cart->reset(); 102 | cpu.reset(); 103 | ppu.reset(); 104 | nSystemClockCounter = 0; 105 | dma_page = 0x00; 106 | dma_addr = 0x00; 107 | dma_data = 0x00; 108 | dma_dummy = true; 109 | dma_transfer = false; 110 | } 111 | 112 | bool Bus::clock() { 113 | // 时钟, 模拟器的核心 114 | // 整个模拟器的运行"节奏" 115 | // 其中,ppu时钟通常用来决定整个模拟器速度 116 | ppu.clock(); 117 | 118 | // APU时钟 119 | apu.clock(); 120 | 121 | // CPU的时钟周期比PPU和APU的慢3倍,也就是说,PPU和APU每执行3次,CPU执行一次 122 | // 这是NES模拟器中规定的 123 | // 使用nSystemClockCounter来记录ppu的时钟周期. 除3取余即可得到cpu时钟 124 | if (nSystemClockCounter % 3 == 0) { 125 | // 判断是否发生DMA传送(将OAM数据传送到PPU中) 126 | if (dma_transfer) { 127 | // TODO(tiansongyu): 可能存在cpu的周期bug 128 | // 这里虽然在cpu的周期中,但实际cpu不进行clock, 129 | // 此时DMA占用cpu周期 130 | // 通常占用513或514个周期 131 | // (等待写入完成时为 1 个等待状态周期,如果在奇数 CPU 周期中为 + 1,则为 132 | // 256 个交替读 / 写周期。) dma_dummy的默认是true 133 | if (dma_dummy) { 134 | // 执行周期必须是偶数,需要等待一个cpu周期 135 | if (nSystemClockCounter % 2 == 1) { 136 | // DMA开始传送 137 | dma_dummy = false; 138 | } 139 | } else { 140 | if (nSystemClockCounter % 2 == 0) { 141 | // 偶数周期从cpu中读取数据, 142 | // dma_page存储着 0x(dma_page)xx数据 143 | // xx通常从0开始 ,0xFF结束, 144 | // 64个精灵,每个精灵4个字节,所以是256个字节,也就是0xFF大小 145 | // dma_page是地址的高位,dma_addr是地址的低位 146 | // dma_data即为从cpu中读取的数据 147 | dma_data = cpuRead(dma_page << 8 | dma_addr); 148 | } else { 149 | // 奇数周期,将数据写到PPU中 150 | ppu.pOAM[dma_addr] = dma_data; 151 | // 需要增加dma的地址偏移 152 | dma_addr++; 153 | // dma_addr是一个字节的数据 154 | // 当dma_addr大于0xFF时,会产生溢出 155 | // dma_addr = 0 说明此时DMA传送停止 156 | // 将标志寄存器更改 157 | if (dma_addr == 0x00) { 158 | dma_transfer = false; 159 | dma_dummy = true; 160 | } 161 | } 162 | } 163 | } else { 164 | // 没有发生DMA传送时,CPU时钟正常进行。 165 | cpu.clock(); 166 | } 167 | } 168 | 169 | // 音频同步 170 | bool bAudioSampleReady = false; 171 | // dAudioTimePerNESClock = 1.0 / 5369318.0; // 172 | dAudioTime += dAudioTimePerNESClock; 173 | if (dAudioTime >= dAudioTimePerSystemSample) { 174 | // dAudioTimePerSystemSample = 1.0 / (double)sample_rate; 175 | // 当运行的时间大于一次采样周期的时候,进行一次发声。。。。这个速度是极快的 176 | dAudioTime -= dAudioTimePerSystemSample; 177 | // 获取需要发出的声音信息 178 | dAudioSample = apu.GetOutputSample(); 179 | bAudioSampleReady = true; 180 | } 181 | if (ppu.nmi) { 182 | // PPU的中断通常发生在vertical blanking period,垂直消隐期间 183 | // 也就是scanline大于251?? 的时候,属于在屏幕的下方, 184 | // 此时cpu需要进行下一帧的名称表的准备 185 | // 不可能在绘制的时间进行准备,只能等到绘制结束,否则会产生很多问题 186 | // 唯一的机会就是在垂直消隐期间进行,也是nes设计巧妙之处 187 | ppu.nmi = false; 188 | cpu.nmi(); 189 | } 190 | 191 | // Check if cartridge is requesting IRQ 192 | if (cart->GetMapper()->irqState()) { 193 | cart->GetMapper()->irqClear(); 194 | cpu.irq(); 195 | } 196 | 197 | nSystemClockCounter++; 198 | // 如果准备好声音数据,开始发声。。 199 | return bAudioSampleReady; 200 | } 201 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Bus.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifndef __6502_6502LIB_SRC_PUBLIC_BUS_H_ 20 | #define __6502_6502LIB_SRC_PUBLIC_BUS_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "Cartridge.h" 27 | #include "Nes2A03.h" 28 | #include "Nes2C02.h" 29 | #include "Nes6502.h" 30 | 31 | class Bus { 32 | public: 33 | Bus(); 34 | ~Bus(); 35 | 36 | public: // 总线 37 | // 6502 CPU 38 | Nes6502 cpu; 39 | // 2C02 PPU图形处理器。。。显卡。。。 40 | Nes2C02 ppu; 41 | // 2A03 APU 声卡。。。 42 | Nes2A03 apu; 43 | std::shared_ptr cart; 44 | // CPU中2KB的内存RAM 45 | uint8_t cpuRam[2048]; 46 | // 控制器数据暂存位置 47 | uint8_t controller[2]; 48 | 49 | public: 50 | // 设置声音发声频率 51 | // sample_rate是声音采样率,这个采样率决定一个声音数据的发生周期 52 | void SetSampleFrequency(uint32_t sample_rate); 53 | double dAudioSample = 0.0; 54 | 55 | private: 56 | double dAudioTime = 0.0; 57 | double dAudioGlobalTime = 0.0; 58 | double dAudioTimePerNESClock = 0.0; 59 | double dAudioTimePerSystemSample = 0.0f; 60 | 61 | public: 62 | // 总线中负责cpu的读写 63 | void cpuWrite(uint16_t addr, uint8_t data); 64 | uint8_t cpuRead(uint16_t addr, bool bReadOnly = false); 65 | 66 | private: 67 | // ppu apu 经过的总共周期 68 | uint32_t nSystemClockCounter = 0; 69 | // 控制寄存器 70 | uint8_t controller_state[2]; 71 | 72 | private: 73 | // 游戏精灵的传送使用DMA机制,精灵一共有64个,每个精灵大小为4个字节 74 | // DMA传送开始时,CPU停止clock,改为DMA传送或者读取一次数据,每个周期还是只能进行读或者写一次操作 75 | // dma_page和dma_addr组成一个16位的CPU地址, 76 | // 通过DMA机制,将CPU中的数据,一个字节一个字节的送到PPU中 77 | // 由于开始时需要在CPU的偶数周期进行第一次读,所以需要513或者514个周期 78 | uint8_t dma_page = 0x00; 79 | uint8_t dma_addr = 0x00; 80 | uint8_t dma_data = 0x00; 81 | 82 | // DMA传输需要精确传送。原则上需要 83 | // 512个周期来读取和写入256字节的OAM内存,一个 84 | // 先读后写。但是,CPU需要处于“偶数”状态 85 | // 时钟周期,因此可能需要一个虚拟的空闲周期 86 | bool dma_dummy = true; 87 | 88 | // DMA传送发生标志 89 | bool dma_transfer = false; 90 | 91 | public: // 系统接口 92 | // 将卡带智能指针对象连接到内部总线 93 | void insertCartridge(const std::shared_ptr &cartridge); 94 | // 重置系统 95 | void reset(); 96 | // 系统clock 97 | bool clock(); 98 | }; 99 | #endif // __6502_6502LIB_SRC_PUBLIC_BUS_H_ 100 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Cartridge.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | 20 | #include "Cartridge.h" 21 | 22 | #include 23 | 24 | #include 25 | Cartridge::Cartridge(const std::string &sFileName) { 26 | // iNES 格式文件头 27 | // nes文件格式wiki:https://wiki.nesdev.com/w/index.php?title=INES#Name_of_file_format 28 | // iNES0.7 iNES NES2.0 29 | struct sHeader { 30 | char name[4]; 31 | uint8_t prg_rom_chunks; 32 | uint8_t chr_rom_chunks; 33 | uint8_t mapper1; 34 | uint8_t mapper2; 35 | uint8_t prg_ram_size; 36 | uint8_t tv_system1; 37 | uint8_t tv_system2; 38 | char unused[5]; 39 | } header; 40 | 41 | bImageValid = false; 42 | 43 | std::ifstream ifs; 44 | ifs.open(sFileName, std::ifstream::binary); 45 | if (ifs.is_open()) { 46 | // 读iNES文件头 47 | ifs.read(reinterpret_cast(&header), sizeof(sHeader)); 48 | // 如果有mapper1,则调到512字节开始读取文件内容 49 | if (header.mapper1 & 0x04) ifs.seekg(512, std::ios_base::cur); 50 | 51 | // 读取这个iNES文件所使用的mapperid号 52 | nMapperID = ((header.mapper2 >> 4) << 4) | (header.mapper1 >> 4); 53 | hw_mirror = (header.mapper1 & 0x01) ? VERTICAL : HORIZONTAL; 54 | 55 | // 这里默认是iNES文件格式,后续再添加iNES0.7 iNES2.0 // TODO 56 | uint8_t nFileType = 1; 57 | if ((header.mapper2 & 0x0C) == 0x08) nFileType = 2; 58 | 59 | if (nFileType == 0) { 60 | // TODO(tiansongyu) iNES0.7 61 | } 62 | 63 | if (nFileType == 1) { 64 | nPRGBanks = header.prg_rom_chunks; 65 | vPRGMemory.resize(nPRGBanks * 16384); 66 | ifs.read(reinterpret_cast(vPRGMemory.data()), vPRGMemory.size()); 67 | 68 | nCHRBanks = header.chr_rom_chunks; 69 | if (nCHRBanks == 0) { 70 | // 根据nCHRBanks个数,分配character rom内存 71 | vCHRMemory.resize(8192); 72 | } else { 73 | // 分配character rom内存 74 | vCHRMemory.resize(nCHRBanks * 8192); 75 | } 76 | ifs.read(reinterpret_cast(vCHRMemory.data()), vCHRMemory.size()); 77 | } 78 | 79 | if (nFileType == 2) { 80 | nPRGBanks = ((header.prg_ram_size & 0x07) << 8) | header.prg_rom_chunks; 81 | vPRGMemory.resize(nPRGBanks * 16384); 82 | ifs.read(reinterpret_cast(vPRGMemory.data()), vPRGMemory.size()); 83 | 84 | nCHRBanks = ((header.prg_ram_size & 0x38) << 8) | header.chr_rom_chunks; 85 | vCHRMemory.resize(nCHRBanks * 8192); 86 | ifs.read((char *)vCHRMemory.data(), vCHRMemory.size()); 87 | } 88 | 89 | // 选择对应的映射器 90 | switch (nMapperID) { 91 | case 0: 92 | pMapper = std::make_shared(nPRGBanks, nCHRBanks); 93 | break; 94 | case 1: 95 | pMapper = std::make_shared(nPRGBanks, nCHRBanks); 96 | break; 97 | case 2: 98 | pMapper = std::make_shared(nPRGBanks, nCHRBanks); 99 | break; 100 | case 3: 101 | pMapper = std::make_shared(nPRGBanks, nCHRBanks); 102 | break; 103 | case 4: 104 | pMapper = std::make_shared(nPRGBanks, nCHRBanks); 105 | break; 106 | case 66: 107 | pMapper = std::make_shared(nPRGBanks, nCHRBanks); 108 | break; 109 | default: 110 | std::cout << "can not find the correct mapper" << std::endl; 111 | break; 112 | } 113 | printf("MapperID is %d \n", nMapperID); 114 | bImageValid = true; 115 | ifs.close(); 116 | } 117 | } 118 | 119 | Cartridge::~Cartridge() {} 120 | 121 | bool Cartridge::ImageValid() { return bImageValid; } 122 | bool Cartridge::cpuRead(uint16_t addr, uint8_t &data) { 123 | uint32_t mapped_addr = 0; 124 | // cpu读取卡带中的内容 125 | 126 | if (pMapper->cpuMapRead(addr, mapped_addr, data)) { 127 | if (mapped_addr == 0xFFFFFFFF) { 128 | // Mapper has actually set the data value, for example cartridge based RAM 129 | return true; 130 | } else { 131 | // Mapper has produced an offset into cartridge bank memory 132 | data = vPRGMemory[mapped_addr]; 133 | } 134 | return true; 135 | } else 136 | return false; 137 | } 138 | 139 | bool Cartridge::cpuWrite(uint16_t addr, uint8_t data) { 140 | uint32_t mapped_addr = 0; 141 | if (pMapper->cpuMapWrite(addr, mapped_addr, data)) { 142 | if (mapped_addr == 0xFFFFFFFF) { 143 | // Mapper has actually set the data value, for example cartridge based RAM 144 | return true; 145 | } else { 146 | // Mapper has produced an offset into cartridge bank memory 147 | vPRGMemory[mapped_addr] = data; 148 | } 149 | return true; 150 | } else 151 | return false; 152 | } 153 | 154 | bool Cartridge::ppuRead(uint16_t addr, uint8_t &data) { 155 | uint32_t mapped_addr = 0; 156 | if (pMapper->ppuMapRead(addr, mapped_addr)) { 157 | data = vCHRMemory[mapped_addr]; 158 | return true; 159 | } else 160 | return false; 161 | } 162 | 163 | bool Cartridge::ppuWrite(uint16_t addr, uint8_t data) { 164 | uint32_t mapped_addr = 0; 165 | if (pMapper->ppuMapWrite(addr, mapped_addr)) { 166 | vCHRMemory[mapped_addr] = data; 167 | return true; 168 | } else 169 | return false; 170 | } 171 | 172 | void Cartridge::reset() { 173 | // 卡带重置,会重置mapper 174 | 175 | if (pMapper != nullptr) pMapper->reset(); 176 | } 177 | 178 | MIRROR Cartridge::Mirror() { 179 | MIRROR m = pMapper->mirror(); 180 | if (m == MIRROR::HARDWARE) { 181 | // Mirror configuration was defined 182 | // in hardware via soldering 183 | return hw_mirror; 184 | } else { 185 | // Mirror configuration can be 186 | // dynamically set via mapper 187 | return m; 188 | } 189 | } 190 | 191 | std::shared_ptr Cartridge::GetMapper() { return pMapper; } -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Cartridge.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifndef _6502_6502LIB_SRC_PUBLIC_CARTRIDGE_H_ 20 | #define _6502_6502LIB_SRC_PUBLIC_CARTRIDGE_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "Mapper_000.h" 29 | // 待添加不同种类的mapper 30 | #include "Mapper_001.h" 31 | #include "Mapper_002.h" 32 | #include "Mapper_003.h" 33 | #include "Mapper_004.h" 34 | #include "Mapper_066.h" 35 | 36 | class Cartridge { 37 | public: 38 | explicit Cartridge(const std::string &sFileName); 39 | ~Cartridge(); 40 | 41 | public: 42 | bool ImageValid(); 43 | std::shared_ptr GetMapper(); 44 | 45 | private: 46 | bool bImageValid = false; 47 | MIRROR hw_mirror = HORIZONTAL; 48 | 49 | uint8_t nMapperID = 0; 50 | uint8_t nPRGBanks = 0; 51 | uint8_t nCHRBanks = 0; 52 | 53 | std::vector vPRGMemory; 54 | std::vector vCHRMemory; 55 | 56 | std::shared_ptr pMapper; 57 | 58 | public: 59 | // 与总线通信 60 | bool cpuRead(uint16_t addr, uint8_t &data); 61 | bool cpuWrite(uint16_t addr, uint8_t data); 62 | 63 | // 与PPU通信总线 64 | bool ppuRead(uint16_t addr, uint8_t &data); 65 | bool ppuWrite(uint16_t addr, uint8_t data); 66 | 67 | // 重置卡带 68 | void reset(); 69 | 70 | MIRROR Mirror(); 71 | }; 72 | #endif // _6502_6502LIB_SRC_PUBLIC_CARTRIDGE_H_ 73 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper.cpp: -------------------------------------------------------------------------------- 1 | // Copyright [2020-2022] 2 | 3 | #include "Mapper.h" 4 | 5 | Mapper::Mapper(uint8_t prgBanks, uint8_t chrBanks) { 6 | nPRGBanks = prgBanks; 7 | nCHRBanks = chrBanks; 8 | 9 | // reset(); 10 | } 11 | 12 | Mapper::~Mapper() {} 13 | 14 | MIRROR Mapper::mirror() { return MIRROR::HARDWARE; } 15 | 16 | bool Mapper::irqState() { return false; } 17 | 18 | void Mapper::irqClear() {} 19 | 20 | void Mapper::scanline() {} 21 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper.h: -------------------------------------------------------------------------------- 1 | // Copyright [2020-2022] 2 | 3 | #pragma once 4 | #include 5 | 6 | enum MIRROR { 7 | HARDWARE, 8 | HORIZONTAL, 9 | VERTICAL, 10 | ONESCREEN_LO, 11 | ONESCREEN_HI, 12 | }; 13 | 14 | class Mapper { 15 | public: 16 | Mapper(uint8_t prgBanks, uint8_t chrBanks); 17 | ~Mapper(); 18 | 19 | public: 20 | // Transform CPU bus address into PRG ROM offset 21 | virtual bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, 22 | uint8_t &data) = 0; 23 | virtual bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 24 | uint8_t data = 0) = 0; 25 | // Transform PPU bus address into CHR ROM offset 26 | virtual bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) = 0; 27 | virtual bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) = 0; 28 | 29 | virtual void reset() = 0; 30 | 31 | // Get Mirror mode if mapper is in control 32 | virtual MIRROR mirror(); 33 | 34 | // IRQ Interface 35 | virtual bool irqState(); 36 | virtual void irqClear(); 37 | 38 | // Scanline Counting 39 | virtual void scanline(); 40 | 41 | protected: 42 | // These are stored locally as many of the mappers require this information 43 | uint8_t nPRGBanks = 0; 44 | uint8_t nCHRBanks = 0; 45 | }; 46 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_000.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifdef __GNUC__ 20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真 21 | // 关闭 警告:unused parameter 22 | #pragma GCC diagnostic ignored "-Wtype-limits" 23 | #pragma GCC diagnostic ignored "-Wunused-parameter" 24 | #endif 25 | 26 | #include "Mapper_000.h" 27 | 28 | Mapper_000::Mapper_000(uint8_t prgBanks, uint8_t chrBanks) 29 | : Mapper(prgBanks, chrBanks) { 30 | reset(); 31 | } 32 | 33 | Mapper_000::~Mapper_000() {} 34 | 35 | void Mapper_000::reset() {} 36 | 37 | bool Mapper_000::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, 38 | uint8_t &data) { 39 | // if PRGROM is 16KB 40 | // CPU Address Bus PRG ROM 41 | // 0x8000 -> 0xBFFF: Map 0x0000 -> 0x3FFF 42 | // 0xC000 -> 0xFFFF: Mirror 0x0000 -> 0x3FFF 43 | // if PRGROM is 32KB 44 | // CPU Address Bus PRG ROM 45 | // 0x8000 -> 0xFFFF: Map 0x0000 -> 0x7FFF 46 | 47 | // PRGROM分为16KB和32KB两种情况 48 | // 如果16KB,则其实只有0x3FFF使用到,从0x4000 -> 0x7FFF等同于0x0000 -> 0x3FFF 49 | if (addr >= 0x8000 && addr <= 0xFFFF) { 50 | mapped_addr = addr & (nPRGBanks > 1 ? 0x7FFF : 0x3FFF); 51 | return true; 52 | } 53 | 54 | return false; 55 | } 56 | 57 | bool Mapper_000::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 58 | uint8_t data) { 59 | // data没有使用,map中没有寄存器,cpu不会向map写入数据 60 | if (addr >= 0x8000 && addr <= 0xFFFF) { 61 | mapped_addr = addr & (nPRGBanks > 1 ? 0x7FFF : 0x3FFF); 62 | return true; 63 | } 64 | 65 | return false; 66 | } 67 | 68 | bool Mapper_000::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) { 69 | // There is no mapping required for PPU 70 | // PPU Address Bus CHR ROM 71 | // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF 72 | if (addr >= 0x0000 && addr <= 0x1FFF) { 73 | mapped_addr = addr; 74 | return true; 75 | } 76 | 77 | return false; 78 | } 79 | 80 | bool Mapper_000::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) { 81 | if (addr >= 0x0000 && addr <= 0x1FFF) { 82 | if (nCHRBanks == 0) { 83 | // Treat as RAM 84 | mapped_addr = addr; 85 | return true; 86 | } 87 | } 88 | 89 | return false; 90 | } 91 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_000.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifndef _6502_6502LIB_SRC_PUBLIC_MAPPER_000_H_ 20 | #define _6502_6502LIB_SRC_PUBLIC_MAPPER_000_H_ 21 | #include "Mapper.h" 22 | 23 | class Mapper_000 : public Mapper { 24 | public: 25 | Mapper_000(uint8_t prgBanks, uint8_t chrBanks); 26 | ~Mapper_000(); 27 | 28 | public: 29 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; 30 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 31 | uint8_t data = 0) override; 32 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; 33 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; 34 | void reset() override; 35 | 36 | // No local equipment required 37 | }; 38 | #endif // _6502_6502LIB_SRC_PUBLIC_MAPPER_000_H_ 39 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_001.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifdef __GNUC__ 20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真 21 | // 关闭 警告:unused parameter 22 | 23 | #pragma GCC diagnostic ignored "-Wtype-limits" 24 | #pragma GCC diagnostic ignored "-Wunused-parameter" 25 | #endif 26 | #include "Mapper_001.h" 27 | 28 | Mapper_001::Mapper_001(uint8_t prgBanks, uint8_t chrBanks) 29 | : Mapper(prgBanks, chrBanks) { 30 | vRAMStatic.resize(32 * 1024); 31 | } 32 | 33 | Mapper_001::~Mapper_001() {} 34 | 35 | bool Mapper_001::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, 36 | uint8_t &data) { 37 | if (addr >= 0x6000 && addr <= 0x7FFF) { 38 | // 0x6000 -> 0x7FFF 是mapper中的ram 39 | // Read is from static ram on cartridge 40 | mapped_addr = 0xFFFFFFFF; 41 | 42 | // Read data from RAM 43 | data = vRAMStatic[addr & 0x1FFF]; 44 | 45 | // Signal mapper has handled request 46 | return true; 47 | } 48 | 49 | if (addr >= 0x8000) { 50 | if (nControlRegister & 0b01000) { 51 | // 16K Mode 52 | if (addr >= 0x8000 && addr <= 0xBFFF) { 53 | mapped_addr = nPRGBankSelect16Lo * 0x4000 + (addr & 0x3FFF); 54 | return true; 55 | } 56 | 57 | if (addr >= 0xC000 && addr <= 0xFFFF) { 58 | mapped_addr = nPRGBankSelect16Hi * 0x4000 + (addr & 0x3FFF); 59 | return true; 60 | } 61 | } else { 62 | // 32K Mode 63 | mapped_addr = nPRGBankSelect32 * 0x8000 + (addr & 0x7FFF); 64 | return true; 65 | } 66 | } 67 | 68 | return false; 69 | } 70 | 71 | bool Mapper_001::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 72 | uint8_t data) { 73 | if (addr >= 0x6000 && addr <= 0x7FFF) { 74 | // Write is to static ram on cartridge 75 | mapped_addr = 0xFFFFFFFF; 76 | 77 | // Write data to RAM 78 | vRAMStatic[addr & 0x1FFF] = data; 79 | 80 | // Signal mapper has handled request 81 | return true; 82 | } 83 | 84 | if (addr >= 0x8000) { 85 | // 与几乎所有其他映射器不同,MMC1 通过串行端口进行配置以减少引脚数。 86 | // CPU $8000 - $FFFF 连接到一个公共移位寄存器。将位 7 设置($80 到 87 | // $FF)的值写入 $8000 - $FFFF 中的任何地址, 88 | // 会将移位寄存器清除到其初始状态。要更改寄存器的值,CPU 会在第 7 89 | // 位清除并在第 0 位中写入所需值的位进行五次写入。 在前四次写入时,MMC1 将位 90 | // 0 ​​移入移位寄存器。在第五次写入时,MMC1 将第 0 91 | // 位和移位寄存器的内容复制到由地址的第 14 和 13 位选择的内部寄存器中, 92 | // 然后清除移位寄存器。只有在第五次写入时地址才重要,即使如此,地址的第 14 93 | // 位和第 13 位也很重要, 因为映射寄存器未完全解码,如PPU 寄存器。第 5 94 | // 次写入后,移位寄存器会自动清零,因此不需要写入第 7 95 | // 位的移位寄存器来重置它。 96 | 97 | // 当 CPU 在连续周期写入串行端口时,MMC1 将忽略除第一个之外的所有写入。当 98 | // 6502 执行读 - 修改 - 写(RMW) 指令时会发生这种情况,例如 DEC 和 99 | // ROR,通过写回旧值然后在下一个周期写入新值。至少Bill &Ted's Excellence 100 | // Adventure通过在包含 $FF 的 ROM 位置执行 INC 来重置 MMC1;MMC1 看到写回的 101 | // $FF 并忽略在下一个周期写入的 $00。[1] 这样做的原因是 MMC1 102 | // 具有显式逻辑来忽略另一个写周期之后的任何写周期。写入的位置无关紧要,例如,即使在写入 103 | // $7fff 之后的一个周期内写入 $8000 也会被 MMC1 忽略,实际上,6502 104 | // 处理器无法做到这一点。 if (data & 0x80) // 0b10000000 105 | if (data & 0x80) { 106 | // MSB is set, so reset serial loading 107 | // 7 位 0 108 | // ---- ---- 109 | // Rxxx xxxD 110 | // | | 111 | // | +- 要移入移位寄存器的数据位,LSB 在前 112 | // +--------- 1:复位移位寄存器并用(Control OR $0C)写控制, 113 | // 将 PRG ROM 锁定在 $C000-$FFFF 到最后一个银行。 114 | nLoadRegister = 0x00; 115 | nLoadRegisterCount = 0; 116 | nControlRegister = nControlRegister | 0x0C; 117 | } else { 118 | // Load data in serially into load register 119 | // It arrives LSB first, so implant this at 120 | // bit 5. After 5 writes, the register is ready 121 | 122 | // ; 123 | // ; 124 | // Sets the switchable PRG ROM bank to the value of A.; 125 | // ; 126 | // A MMC1_SR MMC1_PB 127 | // setPRGBank:; 128 | // 000edcba 10000 Start with an empty shift register(SR).The 1 is used 129 | // sta $E000; 130 | // 000edcba->a1000 to detect when the SR has become full.lsr a; 131 | // > 0000edcb a1000 132 | // sta $E000; 133 | // 0000edcb->ba100 134 | // lsr a; 135 | // > 00000edc ba100 136 | // sta $E000; 137 | // 00000edc->cba10 138 | // lsr a; 139 | // > 000000ed cba10 140 | // sta $E000; 141 | // 000000ed->dcba1 Once a 1 is shifted into the last position, the SR is 142 | // full.lsr a; > 0000000e dcba1 sta $E000; 0000000e dcba1->edcba 143 | // A write with the SR full copies D0 and the SR to a bank register; 144 | // 10000($E000 - $FFFF means PRG bank number) and then clears the SR.rts 145 | 146 | nLoadRegister >>= 1; 147 | nLoadRegister |= (data & 0x01) << 4; 148 | nLoadRegisterCount++; 149 | 150 | if (nLoadRegisterCount == 5) { 151 | // Get Mapper Target Register, by examining 152 | // bits 13 & 14 of the address 153 | uint8_t nTargetRegister = (addr >> 13) & 0x03; 154 | // 0x8000 - 0x9FFF 155 | if (nTargetRegister == 0) { 156 | // Set Control Register 157 | nControlRegister = nLoadRegister & 0x1F; 158 | 159 | switch (nControlRegister & 0x03) { 160 | case 0: 161 | mirrormode = ONESCREEN_LO; 162 | break; 163 | case 1: 164 | mirrormode = ONESCREEN_HI; 165 | break; 166 | case 2: 167 | mirrormode = VERTICAL; 168 | break; 169 | case 3: 170 | mirrormode = HORIZONTAL; 171 | break; 172 | } 173 | } else if (nTargetRegister == 1) { // 0xA000 - 0xBFFF 174 | // Set CHR Bank Lo 175 | if (nControlRegister & 0b10000) { 176 | // 4K CHR Bank at PPU 0x0000 177 | nCHRBankSelect4Lo = nLoadRegister & 0x1F; 178 | } else { 179 | // 8K CHR Bank at PPU 0x0000 180 | nCHRBankSelect8 = nLoadRegister & 0x1E; 181 | } 182 | } else if (nTargetRegister == 2) { // 0xC000 - 0xDFFF 183 | // Set CHR Bank Hi 184 | if (nControlRegister & 0b10000) { 185 | // 4K CHR Bank at PPU 0x1000 186 | nCHRBankSelect4Hi = nLoadRegister & 0x1F; 187 | } 188 | } else if (nTargetRegister == 3) { // 0xE000 - 0xFFFF 189 | // Configure PRG Banks 190 | uint8_t nPRGMode = (nControlRegister >> 2) & 0x03; 191 | 192 | if (nPRGMode == 0 || nPRGMode == 1) { 193 | // Set 32K PRG Bank at CPU 0x8000 194 | nPRGBankSelect32 = (nLoadRegister & 0x0E) >> 1; 195 | } else if (nPRGMode == 2) { 196 | // Fix 16KB PRG Bank at CPU 0x8000 to First Bank 197 | nPRGBankSelect16Lo = 0; 198 | // Set 16KB PRG Bank at CPU 0xC000 199 | nPRGBankSelect16Hi = nLoadRegister & 0x0F; // ????? 200 | } else if (nPRGMode == 3) { 201 | // Set 16KB PRG Bank at CPU 0x8000 202 | nPRGBankSelect16Lo = nLoadRegister & 0x0F; // ????? 203 | // Fix 16KB PRG Bank at CPU 0xC000 to Last Bank 204 | nPRGBankSelect16Hi = nPRGBanks - 1; 205 | } 206 | } 207 | 208 | // 5 bits were written, and decoded, so 209 | // reset load register 210 | nLoadRegister = 0x00; 211 | nLoadRegisterCount = 0; 212 | } 213 | } 214 | } 215 | 216 | // Mapper has handled write, but do not update ROMs 217 | return false; 218 | } 219 | 220 | bool Mapper_001::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) { 221 | if (addr < 0x2000) { 222 | if (nCHRBanks == 0) { 223 | mapped_addr = addr; 224 | return true; 225 | } else { 226 | if (nControlRegister & 0b10000) { 227 | // 4K CHR Bank Mode 228 | if (addr >= 0x0000 && addr <= 0x0FFF) { 229 | mapped_addr = nCHRBankSelect4Lo * 0x1000 + (addr & 0x0FFF); 230 | return true; 231 | } 232 | 233 | if (addr >= 0x1000 && addr <= 0x1FFF) { 234 | mapped_addr = nCHRBankSelect4Hi * 0x1000 + (addr & 0x0FFF); 235 | return true; 236 | } 237 | } else { 238 | // 8K CHR Bank Mode 239 | mapped_addr = nCHRBankSelect8 * 0x2000 + (addr & 0x1FFF); 240 | return true; 241 | } 242 | } 243 | } 244 | 245 | return false; 246 | } 247 | 248 | bool Mapper_001::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) { 249 | if (addr < 0x2000) { 250 | if (nCHRBanks == 0) { 251 | mapped_addr = addr; 252 | return true; 253 | } 254 | 255 | return true; 256 | } else 257 | return false; 258 | } 259 | 260 | void Mapper_001::reset() { 261 | nControlRegister = 0x1C; 262 | nLoadRegister = 0x00; 263 | nLoadRegisterCount = 0x00; 264 | 265 | nCHRBankSelect4Lo = 0; 266 | nCHRBankSelect4Hi = 0; 267 | nCHRBankSelect8 = 0; 268 | 269 | nPRGBankSelect32 = 0; 270 | nPRGBankSelect16Lo = 0; 271 | nPRGBankSelect16Hi = nPRGBanks - 1; 272 | } 273 | 274 | MIRROR Mapper_001::mirror() { return mirrormode; } 275 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_001.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifndef _6502_6502LIB_SRC_PUBLIC_MAPPER_001_H_ 20 | #define _6502_6502LIB_SRC_PUBLIC_MAPPER_001_H_ 21 | #include 22 | 23 | #include "Mapper.h" 24 | 25 | class Mapper_001 : public Mapper { 26 | public: 27 | Mapper_001(uint8_t prgBanks, uint8_t chrBanks); 28 | ~Mapper_001(); 29 | 30 | public: 31 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; 32 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 33 | uint8_t data = 0) override; 34 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; 35 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; 36 | void reset() override; 37 | 38 | MIRROR mirror(); 39 | 40 | private: 41 | uint8_t nCHRBankSelect4Lo = 0x00; 42 | uint8_t nCHRBankSelect4Hi = 0x00; 43 | uint8_t nCHRBankSelect8 = 0x00; 44 | 45 | uint8_t nPRGBankSelect16Lo = 0x00; 46 | uint8_t nPRGBankSelect16Hi = 0x00; 47 | uint8_t nPRGBankSelect32 = 0x00; 48 | 49 | uint8_t nLoadRegister = 0x00; 50 | uint8_t nLoadRegisterCount = 0x00; 51 | uint8_t nControlRegister = 0x00; 52 | 53 | // No local equipment required 54 | 55 | MIRROR mirrormode = MIRROR::HORIZONTAL; 56 | 57 | std::vector vRAMStatic; 58 | }; 59 | #endif // _6502_6502LIB_SRC_PUBLIC_MAPPER_001_H_ 60 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_002.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifdef __GNUC__ 20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真 21 | // 关闭 警告:unused parameter 22 | #pragma GCC diagnostic ignored "-Wtype-limits" 23 | #pragma GCC diagnostic ignored "-Wunused-parameter" 24 | #endif 25 | #include "Mapper_002.h" 26 | 27 | Mapper_002::Mapper_002(uint8_t prgBanks, uint8_t chrBanks) 28 | : Mapper(prgBanks, chrBanks) { 29 | reset(); 30 | } 31 | 32 | Mapper_002::~Mapper_002() {} 33 | 34 | void Mapper_002::reset() { 35 | regLo = 0x00; 36 | regHi = nPRGBanks - 1; 37 | } 38 | 39 | bool Mapper_002::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, 40 | uint8_t &data) { 41 | if (addr >= 0x8000 && addr <= 0xBFFF) { 42 | mapped_addr = regLo * 0x4000 + (addr & 0x3FFF); 43 | return true; 44 | } else if (addr >= 0xC000 && addr <= 0xFFFF) { 45 | mapped_addr = regHi * 0x4000 + (addr & 0x3FFF); 46 | return true; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | bool Mapper_002::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 53 | uint8_t data) { 54 | if (addr >= 0x8000 && addr <= 0xFFFF) { 55 | regLo = data & 0x0F; 56 | } 57 | return false; 58 | } 59 | 60 | bool Mapper_002::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) { 61 | // There is no mapping required for PPU 62 | // PPU Address Bus CHR ROM 63 | // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF 64 | if (addr >= 0x0000 && addr <= 0x1FFF) { 65 | mapped_addr = addr; 66 | return true; 67 | } 68 | 69 | return false; 70 | } 71 | 72 | bool Mapper_002::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) { 73 | if (addr >= 0x0000 && addr <= 0x1FFF) { 74 | if (nCHRBanks == 0) { 75 | // Treat as RAM 76 | mapped_addr = addr; 77 | return true; 78 | } 79 | } 80 | 81 | return false; 82 | } 83 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_002.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | 20 | #pragma once 21 | #include "Mapper.h" 22 | 23 | class Mapper_002 : public Mapper { 24 | public: 25 | Mapper_002(uint8_t prgBanks, uint8_t chrBanks); 26 | ~Mapper_002(); 27 | 28 | public: 29 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; 30 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 31 | uint8_t data = 0) override; 32 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; 33 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; 34 | void reset() override; 35 | // No local equipment required 36 | 37 | private: 38 | uint8_t regLo = 0x00; 39 | uint8_t regHi = 0x00; 40 | }; 41 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_003.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifdef __GNUC__ 20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真 21 | // 关闭 警告:unused parameter 22 | #pragma GCC diagnostic ignored "-Wtype-limits" 23 | #pragma GCC diagnostic ignored "-Wunused-parameter" 24 | #endif 25 | #include "Mapper_003.h" 26 | 27 | Mapper_003::Mapper_003(uint8_t prgBanks, uint8_t chrBanks) 28 | : Mapper(prgBanks, chrBanks) { 29 | reset(); 30 | } 31 | 32 | Mapper_003::~Mapper_003() {} 33 | 34 | void Mapper_003::reset() { 35 | regHi = 0x00; 36 | regLo = 0x00; 37 | } 38 | 39 | bool Mapper_003::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, 40 | uint8_t &data) { 41 | // 存在ROM为16KB和32KB两种情况 42 | if (addr >= 0x8000 && addr <= 0xFFFF) { 43 | if (nPRGBanks == 1) // 16KB 44 | mapped_addr = addr & 0x3FFF; 45 | else if (nPRGBanks == 2) // 32KB 46 | mapped_addr = addr & 0x7FFF; 47 | return true; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | bool Mapper_003::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 54 | uint8_t data) { 55 | // 修改寄存器regLo的值 56 | if (addr >= 0x8000 && addr <= 0xFFFF) { 57 | regLo = data & 0b00000011; 58 | } 59 | return false; 60 | } 61 | 62 | bool Mapper_003::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) { 63 | // There is no mapping required for PPU 64 | // PPU Address Bus CHR ROM 65 | // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF 66 | 67 | // mapper_3 68 | // PPU $0000-$1FFF: 8 KB switchable CHR ROM bank 69 | if (addr >= 0x0000 && addr <= 0x1FFF) { 70 | mapped_addr = regLo * 0x2000 + addr; 71 | return true; 72 | } 73 | 74 | return false; 75 | } 76 | 77 | bool Mapper_003::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) { 78 | return false; 79 | } 80 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_003.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | 20 | #pragma once 21 | #include "Mapper.h" 22 | 23 | class Mapper_003 : public Mapper { 24 | public: 25 | Mapper_003(uint8_t prgBanks, uint8_t chrBanks); 26 | ~Mapper_003(); 27 | 28 | public: 29 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; 30 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 31 | uint8_t data = 0) override; 32 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; 33 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; 34 | void reset() override; 35 | 36 | private: 37 | uint8_t regHi = 0x00; 38 | uint8_t regLo = 0x00; 39 | // No local equipment required 40 | }; 41 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_004.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2022 tiansongyu 2 | // 3 | // This file is part of 6502Emulator. 4 | // 5 | // 6502Emulator is free GameEngine: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // 6502Emulator is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with 6502Emulator. If not, see . 17 | // 18 | // 6502Emulator is actively maintained and developed! 19 | #ifdef __GNUC__ 20 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真 21 | // 关闭 警告:unused parameter 22 | #pragma GCC diagnostic ignored "-Wtype-limits" 23 | #pragma GCC diagnostic ignored "-Wunused-parameter" 24 | #endif 25 | #include "Mapper_004.h" 26 | 27 | #include 28 | 29 | Mapper_004::Mapper_004(uint8_t prgBanks, uint8_t chrBanks) 30 | : Mapper(prgBanks, chrBanks) { 31 | vRAMStatic.resize(32 * 1024); 32 | } 33 | 34 | Mapper_004::~Mapper_004() {} 35 | 36 | bool Mapper_004::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, 37 | uint8_t &data) { 38 | if (addr >= 0x6000 && addr <= 0x7FFF) { 39 | // 0x6000 -> 0x7FFF 是mapper中的ram 40 | // Read is from static ram on cartridge 41 | mapped_addr = 0xFFFFFFFF; 42 | 43 | // Read data from RAM 44 | data = vRAMStatic[addr & 0x1FFF]; 45 | 46 | // Signal mapper has handled request 47 | return true; 48 | } 49 | if (addr >= 0x8000 && addr <= 0x9FFF) { 50 | mapped_addr = pPRGBank[0] + (addr & 0x1FFF); 51 | return true; 52 | } 53 | 54 | if (addr >= 0xA000 && addr <= 0xBFFF) { 55 | mapped_addr = pPRGBank[1] + (addr & 0x1FFF); 56 | return true; 57 | } 58 | 59 | if (addr >= 0xC000 && addr <= 0xDFFF) { 60 | mapped_addr = pPRGBank[2] + (addr & 0x1FFF); 61 | return true; 62 | } 63 | 64 | if (addr >= 0xE000 && addr <= 0xFFFF) { 65 | mapped_addr = pPRGBank[3] + (addr & 0x1FFF); 66 | return true; 67 | } 68 | return false; 69 | } 70 | 71 | bool Mapper_004::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 72 | uint8_t data) { 73 | if (addr >= 0x6000 && addr <= 0x7FFF) { 74 | // Write is to static ram on cartridge 75 | mapped_addr = 0xFFFFFFFF; 76 | 77 | // Write data to RAM 78 | vRAMStatic[addr & 0x1FFF] = data; 79 | 80 | // Signal mapper has handled request 81 | return true; 82 | } 83 | if (addr >= 0x8000 && addr <= 0x9FFF) { 84 | // Bank Select 85 | // 判断为偶数时 86 | if (!(addr & 0x0001)) { 87 | // nTargetRegister = data & 0x07; 88 | // bPRGBankMode = (data & 0x40); 89 | // bCHRInversion = (data & 0x80); 90 | nBankSelectRegister = data; 91 | } else { 92 | // Update target register 93 | pRegister[nBankSelectRegister & 0x07] = data; 94 | 95 | // Update Pointer Table 96 | // R0 and R1 ignore the bottom bit, 97 | // as the value written still counts banks in 1KB units but odd 98 | // numbered banks can't be selected. 99 | 100 | if (nBankSelectRegister & 0x80) { 101 | pCHRBank[0] = pRegister[2] * 0x0400; 102 | pCHRBank[1] = pRegister[3] * 0x0400; 103 | pCHRBank[2] = pRegister[4] * 0x0400; 104 | pCHRBank[3] = pRegister[5] * 0x0400; 105 | pCHRBank[4] = (pRegister[0] & 0xFE) * 0x0400; 106 | pCHRBank[5] = pRegister[0] * 0x0400 + 0x0400; 107 | pCHRBank[6] = (pRegister[1] & 0xFE) * 0x0400; 108 | pCHRBank[7] = pRegister[1] * 0x0400 + 0x0400; 109 | } else { 110 | pCHRBank[0] = (pRegister[0] & 0xFE) * 0x0400; 111 | pCHRBank[1] = pRegister[0] * 0x0400 + 0x0400; 112 | pCHRBank[2] = (pRegister[1] & 0xFE) * 0x0400; 113 | pCHRBank[3] = pRegister[1] * 0x0400 + 0x0400; 114 | pCHRBank[4] = pRegister[2] * 0x0400; 115 | pCHRBank[5] = pRegister[3] * 0x0400; 116 | pCHRBank[6] = pRegister[4] * 0x0400; 117 | pCHRBank[7] = pRegister[5] * 0x0400; 118 | } 119 | // R6 and R7 will ignore the top two bits, as the MMC3 has only 6 120 | // PRG ROM address lines. Some romhacks rely on an 8-bit extension 121 | // of R6/7 for oversized PRG-ROM, but this is deliberately not 122 | // supported by many emulators. See iNES Mapper 004 below. 123 | if (nBankSelectRegister & 0x40) { 124 | pPRGBank[2] = (pRegister[6] & 0x3F) * 0x2000; 125 | pPRGBank[0] = (nPRGBanks * 2 - 2) * 0x2000; 126 | } else { 127 | pPRGBank[0] = (pRegister[6] & 0x3F) * 0x2000; 128 | pPRGBank[2] = (nPRGBanks * 2 - 2) * 0x2000; 129 | } 130 | 131 | pPRGBank[1] = (pRegister[7] & 0x3F) * 0x2000; 132 | pPRGBank[3] = (nPRGBanks * 2 - 1) * 0x2000; 133 | } 134 | 135 | return false; 136 | } 137 | 138 | if (addr >= 0xA000 && addr <= 0xBFFF) { 139 | if (!(addr & 0x0001)) { 140 | // Mirroring 141 | // 这个寄存器有什么TM的用??????? 142 | // 这不是直接赋值了吗,有个屁用 143 | if (data & 0x01) 144 | mirrormode = MIRROR::HORIZONTAL; 145 | else 146 | mirrormode = MIRROR::VERTICAL; 147 | } else { 148 | // PRG Ram Protect 149 | // TODO(tiansongyu) 150 | } 151 | return false; 152 | } 153 | 154 | if (addr >= 0xC000 && addr <= 0xDFFF) { 155 | if (!(addr & 0x0001)) { 156 | nIrqLatch = data; 157 | } else { 158 | nIrqCounter = 0x0000; 159 | } 160 | return false; 161 | } 162 | 163 | if (addr >= 0xE000 && addr <= 0xFFFF) { 164 | if (!(addr & 0x0001)) { 165 | bIRQEnable = false; 166 | bIRQActive = false; 167 | } else { 168 | bIRQEnable = true; 169 | } 170 | return false; 171 | } 172 | 173 | return false; 174 | } 175 | 176 | bool Mapper_004::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) { 177 | if (addr >= 0x0000 && addr <= 0x03FF) { 178 | mapped_addr = pCHRBank[0] + (addr & 0x03FF); 179 | return true; 180 | } 181 | 182 | if (addr >= 0x0400 && addr <= 0x07FF) { 183 | mapped_addr = pCHRBank[1] + (addr & 0x03FF); 184 | return true; 185 | } 186 | 187 | if (addr >= 0x0800 && addr <= 0x0BFF) { 188 | mapped_addr = pCHRBank[2] + (addr & 0x03FF); 189 | return true; 190 | } 191 | 192 | if (addr >= 0x0C00 && addr <= 0x0FFF) { 193 | mapped_addr = pCHRBank[3] + (addr & 0x03FF); 194 | return true; 195 | } 196 | 197 | if (addr >= 0x1000 && addr <= 0x13FF) { 198 | mapped_addr = pCHRBank[4] + (addr & 0x03FF); 199 | return true; 200 | } 201 | 202 | if (addr >= 0x1400 && addr <= 0x17FF) { 203 | mapped_addr = pCHRBank[5] + (addr & 0x03FF); 204 | return true; 205 | } 206 | 207 | if (addr >= 0x1800 && addr <= 0x1BFF) { 208 | mapped_addr = pCHRBank[6] + (addr & 0x03FF); 209 | return true; 210 | } 211 | 212 | if (addr >= 0x1C00 && addr <= 0x1FFF) { 213 | mapped_addr = pCHRBank[7] + (addr & 0x03FF); 214 | return true; 215 | } 216 | 217 | return false; 218 | } 219 | 220 | bool Mapper_004::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) { 221 | return false; 222 | } 223 | 224 | void Mapper_004::reset() { 225 | nBankSelectRegister = 0x00; 226 | nBankData = 0x00; 227 | nPrgRamProtect = 0x00; 228 | nIrqCounter = 0x00; 229 | nIrqLatch = 0x00; 230 | 231 | mirrormode = MIRROR::HORIZONTAL; 232 | 233 | for (int i = 0; i < 4; i++) pPRGBank[i] = 0; 234 | for (int i = 0; i < 8; i++) { 235 | pCHRBank[i] = 0; 236 | pRegister[i] = 0; 237 | } 238 | 239 | pPRGBank[0] = 0 * 0x2000; 240 | pPRGBank[1] = 1 * 0x2000; 241 | pPRGBank[2] = (nPRGBanks * 2 - 2) * 0x2000; 242 | pPRGBank[3] = (nPRGBanks * 2 - 1) * 0x2000; 243 | } 244 | 245 | MIRROR Mapper_004::mirror() { return mirrormode; } 246 | 247 | bool Mapper_004::irqState() { return bIRQActive; } 248 | 249 | void Mapper_004::irqClear() { bIRQActive = false; } 250 | 251 | void Mapper_004::scanline() { 252 | if (nIrqCounter == 0) { 253 | nIrqCounter = nIrqLatch; 254 | } else 255 | nIrqCounter--; 256 | if (nIrqCounter == 0 && bIRQEnable) { 257 | bIRQActive = true; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_004.h: -------------------------------------------------------------------------------- 1 | // Copyright [2020-2022] 2 | 3 | #pragma once 4 | #include 5 | 6 | #include "Mapper.h" 7 | 8 | class Mapper_004 : public Mapper { 9 | public: 10 | Mapper_004(uint8_t prgBanks, uint8_t chrBanks); 11 | ~Mapper_004(); 12 | 13 | public: 14 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; 15 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 16 | uint8_t data = 0) override; 17 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; 18 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; 19 | void reset() override; 20 | 21 | MIRROR mirror(); 22 | 23 | // IRQ Interface 24 | bool irqState(); 25 | void irqClear(); 26 | 27 | // Scanline Counting 28 | void scanline(); 29 | 30 | private: 31 | uint8_t nBankSelectRegister = 0x00; 32 | uint8_t nBankData = 0x00; 33 | uint8_t nPrgRamProtect = 0x00; 34 | uint8_t nIrqCounter = 0x00; 35 | // nIrqLatch指的是发生中断时,刷新界面的行数 36 | uint8_t nIrqLatch = 0x00; 37 | uint32_t pRegister[8]; 38 | uint32_t pCHRBank[8]; 39 | uint32_t pPRGBank[4]; 40 | 41 | bool bIRQEnable = false; 42 | bool bIRQActive = false; 43 | 44 | // No local equipment required 45 | 46 | MIRROR mirrormode = MIRROR::HORIZONTAL; 47 | 48 | std::vector vRAMStatic; 49 | }; 50 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_066.cpp: -------------------------------------------------------------------------------- 1 | // Copyright [2020-2022] 2 | #ifdef __GNUC__ 3 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真 4 | // 关闭 警告:unused parameter 5 | #pragma GCC diagnostic ignored "-Wtype-limits" 6 | #pragma GCC diagnostic ignored "-Wunused-parameter" 7 | 8 | #endif 9 | #include "Mapper_066.h" 10 | 11 | Mapper_066::Mapper_066(uint8_t prgBanks, uint8_t chrBanks) 12 | : Mapper(prgBanks, chrBanks) { 13 | reset(); 14 | } 15 | 16 | Mapper_066::~Mapper_066() {} 17 | 18 | void Mapper_066::reset() { 19 | regHi = 0x00; 20 | regLo = 0x00; 21 | } 22 | 23 | bool Mapper_066::cpuMapRead(uint16_t addr, uint32_t &mapped_addr, 24 | uint8_t &data) { 25 | // CPU $8000-$FFFF: 32 KB switchable PRG ROM bank 26 | // PPU $0000-$1FFF: 8 KB switchable CHR ROM bank 27 | 28 | if (addr >= 0x8000 && addr <= 0xFFFF) { 29 | mapped_addr = regHi * 0x8000 + (addr & 0x7FFF); 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | bool Mapper_066::cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 37 | uint8_t data) { 38 | // 7 bit 0 39 | // ---- ---- 40 | // xxPP xxCC 41 | // || || 42 | // || ++- Select 8 KB CHR ROM bank for PPU $0000-$1FFF 43 | // ++------ Select 32 KB PRG ROM bank for CPU $8000-$FFFF 44 | if (addr >= 0x8000 && addr <= 0xFFFF) { 45 | regLo = data & 0b00000011; 46 | regHi = (data & 0b00110000) >> 4; 47 | return true; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | bool Mapper_066::ppuMapRead(uint16_t addr, uint32_t &mapped_addr) { 54 | // There is no mapping required for PPU 55 | // PPU Address Bus CHR ROM 56 | // 0x0000 -> 0x1FFF: Map 0x0000 -> 0x1FFF 57 | if (addr >= 0x0000 && addr <= 0x1FFF) { 58 | mapped_addr = regLo * 0x2000 + addr; 59 | return true; 60 | } 61 | 62 | return false; 63 | } 64 | 65 | bool Mapper_066::ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) { 66 | return false; 67 | } 68 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Mapper_066.h: -------------------------------------------------------------------------------- 1 | // Copyright [2020-2022] 2 | 3 | #pragma once 4 | #include "Mapper.h" 5 | 6 | class Mapper_066 : public Mapper { 7 | public: 8 | Mapper_066(uint8_t prgBanks, uint8_t chrBanks); 9 | ~Mapper_066(); 10 | 11 | public: 12 | bool cpuMapRead(uint16_t addr, uint32_t &mapped_addr, uint8_t &data) override; 13 | bool cpuMapWrite(uint16_t addr, uint32_t &mapped_addr, 14 | uint8_t data = 0) override; 15 | bool ppuMapRead(uint16_t addr, uint32_t &mapped_addr) override; 16 | bool ppuMapWrite(uint16_t addr, uint32_t &mapped_addr) override; 17 | void reset() override; 18 | 19 | private: 20 | uint8_t regHi = 0x00; 21 | uint8_t regLo = 0x00; 22 | // No local equipment required 23 | }; 24 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Nes2A03.cpp: -------------------------------------------------------------------------------- 1 | // Copyright [2020-2022] 2 | 3 | #include "Nes2A03.h" 4 | 5 | uint8_t Nes2A03::length_table[] = { 6 | 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 7 | 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30}; 8 | 9 | Nes2A03::Nes2A03() { noise_seq.sequence = 0xDBDB; } 10 | 11 | Nes2A03::~Nes2A03() {} 12 | 13 | void Nes2A03::cpuWrite(uint16_t addr, uint8_t data) { 14 | switch (addr) { 15 | case 0x4000: 16 | switch ((data & 0xC0) >> 6) { 17 | case 0x00: 18 | pulse1_seq.new_sequence = 0b01000000; 19 | pulse1_osc.dutycycle = 0.125; // 占空比体积 20 | break; 21 | case 0x01: 22 | pulse1_seq.new_sequence = 0b01100000; 23 | pulse1_osc.dutycycle = 0.250; 24 | break; 25 | case 0x02: 26 | pulse1_seq.new_sequence = 0b01111000; 27 | pulse1_osc.dutycycle = 0.500; 28 | break; 29 | case 0x03: 30 | pulse1_seq.new_sequence = 0b10011111; 31 | pulse1_osc.dutycycle = 0.750; 32 | break; 33 | } 34 | pulse1_seq.sequence = pulse1_seq.new_sequence; // 更新占空比体积 35 | pulse1_halt = (data & 0x20); // envelope loop / length counter halt 36 | pulse1_env.volume = (data & 0x0F); // 音量 37 | pulse1_env.disable = (data & 0x10); // constant volume 38 | break; 39 | 40 | case 0x4001: 41 | pulse1_sweep.enabled = data & 0x80; // Enabled flag 42 | pulse1_sweep.period = 43 | (data & 0x70) >> 4; // The divider's period is P + 1 half-frames 44 | pulse1_sweep.down = data & 0x08; // 启用标志 Enabled flag 45 | pulse1_sweep.shift = 46 | data & 0x07; // 移位计数器 Shift count (number of bits) 47 | pulse1_sweep.reload = true; // ? 48 | break; 49 | 50 | case 0x4002: 51 | pulse1_seq.reload = 52 | (pulse1_seq.reload & 0xFF00) | data; // Pulse 1 timer Low 8 bits 53 | break; 54 | 55 | case 0x4003: 56 | pulse1_seq.reload = (uint16_t)((data & 0x07)) << 8 | 57 | (pulse1_seq.reload & 0x00FF); // timer High 3 bits 58 | pulse1_seq.timer = pulse1_seq.reload; // seq被赋值 59 | pulse1_seq.sequence = pulse1_seq.new_sequence; // 更新占空比体积 60 | pulse1_lc.counter = 61 | length_table[(data & 0xF8) >> 3]; // Pulse 2 length counter load 62 | pulse1_env.start = true; // ? 63 | break; 64 | 65 | case 0x4004: 66 | switch ((data & 0xC0) >> 6) { 67 | case 0x00: 68 | pulse2_seq.new_sequence = 0b01000000; 69 | pulse2_osc.dutycycle = 0.125; 70 | break; 71 | case 0x01: 72 | pulse2_seq.new_sequence = 0b01100000; 73 | pulse2_osc.dutycycle = 0.250; 74 | break; 75 | case 0x02: 76 | pulse2_seq.new_sequence = 0b01111000; 77 | pulse2_osc.dutycycle = 0.500; 78 | break; 79 | case 0x03: 80 | pulse2_seq.new_sequence = 0b10011111; 81 | pulse2_osc.dutycycle = 0.750; 82 | break; 83 | } 84 | pulse2_seq.sequence = pulse2_seq.new_sequence; 85 | pulse2_halt = (data & 0x20); 86 | pulse2_env.volume = (data & 0x0F); 87 | pulse2_env.disable = (data & 0x10); 88 | break; 89 | 90 | case 0x4005: 91 | pulse2_sweep.enabled = data & 0x80; 92 | pulse2_sweep.period = (data & 0x70) >> 4; 93 | pulse2_sweep.down = data & 0x08; 94 | pulse2_sweep.shift = data & 0x07; 95 | pulse2_sweep.reload = true; 96 | break; 97 | 98 | case 0x4006: 99 | pulse2_seq.reload = (pulse2_seq.reload & 0xFF00) | data; 100 | break; 101 | 102 | case 0x4007: 103 | pulse2_seq.reload = 104 | (uint16_t)((data & 0x07)) << 8 | (pulse2_seq.reload & 0x00FF); 105 | pulse2_seq.timer = pulse2_seq.reload; 106 | pulse2_seq.sequence = pulse2_seq.new_sequence; 107 | pulse2_lc.counter = length_table[(data & 0xF8) >> 3]; 108 | pulse2_env.start = true; 109 | 110 | break; 111 | 112 | case 0x4008: 113 | break; 114 | 115 | case 0x400C: 116 | noise_env.volume = (data & 0x0F); // , volume/envelope 117 | noise_env.disable = (data & 0x10); // , constant volume 118 | noise_halt = (data & 0x20); // Envelope loop / length counter halt 119 | break; 120 | 121 | case 0x400E: 122 | // Loop noise ??? 123 | switch (data & 0x0F) { // noise period (P) 124 | case 0x00: 125 | noise_seq.reload = 0; 126 | break; 127 | case 0x01: 128 | noise_seq.reload = 4; 129 | break; 130 | case 0x02: 131 | noise_seq.reload = 8; 132 | break; 133 | case 0x03: 134 | noise_seq.reload = 16; 135 | break; 136 | case 0x04: 137 | noise_seq.reload = 32; 138 | break; 139 | case 0x05: 140 | noise_seq.reload = 64; 141 | break; 142 | case 0x06: 143 | noise_seq.reload = 96; 144 | break; 145 | case 0x07: 146 | noise_seq.reload = 128; 147 | break; 148 | case 0x08: 149 | noise_seq.reload = 160; 150 | break; 151 | case 0x09: 152 | noise_seq.reload = 202; 153 | break; 154 | case 0x0A: 155 | noise_seq.reload = 254; 156 | break; 157 | case 0x0B: 158 | noise_seq.reload = 380; 159 | break; 160 | case 0x0C: 161 | noise_seq.reload = 508; 162 | break; 163 | case 0x0D: 164 | noise_seq.reload = 1016; 165 | break; 166 | case 0x0E: 167 | noise_seq.reload = 2034; 168 | break; 169 | case 0x0F: 170 | noise_seq.reload = 4068; 171 | break; 172 | } 173 | break; 174 | 175 | case 0x4015: // APU STATUS 176 | pulse1_enable = data & 0x01; // 脉冲通道1启用判断 177 | pulse2_enable = data & 0x02; // 脉冲通道2启用判断 178 | noise_enable = data & 0x04; // 噪声 启用判断 179 | // 还有DMC和三角启用判断,这里没有使用 180 | break; 181 | 182 | case 0x400F: 183 | pulse1_env.start = true; 184 | pulse2_env.start = true; 185 | noise_env.start = true; 186 | noise_lc.counter = 187 | length_table[(data & 0xF8) >> 188 | 3]; // 长度计数器负载 (L) Length counter load 189 | break; 190 | } 191 | } 192 | 193 | uint8_t Nes2A03::cpuRead(uint16_t addr) { 194 | uint8_t data = 0x00; 195 | 196 | if (addr == 0x4015) { 197 | // data |= (pulse1_lc.counter > 0) ? 0x01 : 0x00; 198 | // data |= (pulse2_lc.counter > 0) ? 0x02 : 0x00; 199 | // data |= (noise_lc.counter > 0) ? 0x04 : 0x00; 200 | } 201 | 202 | return data; 203 | } 204 | 205 | void Nes2A03::clock() { 206 | // Depending on the frame count, we set a flag to tell 207 | // us where we are in the sequence. Essentially, changes 208 | // to notes only occur at these intervals, meaning, in a 209 | // way, this is responsible for ensuring musical time is 210 | // maintained. 211 | bool bQuarterFrameClock = false; 212 | bool bHalfFrameClock = false; 213 | 214 | dGlobalTime += (0.3333333333 / 1789773); 215 | 216 | if (clock_counter % 6 == 0) { 217 | frame_clock_counter++; 218 | 219 | // 4-Step Sequence Mode 220 | if (frame_clock_counter == 3729) { 221 | bQuarterFrameClock = true; 222 | } 223 | 224 | if (frame_clock_counter == 7457) { 225 | bQuarterFrameClock = true; 226 | bHalfFrameClock = true; 227 | } 228 | 229 | if (frame_clock_counter == 11186) { 230 | bQuarterFrameClock = true; 231 | } 232 | 233 | if (frame_clock_counter == 14916) { 234 | bQuarterFrameClock = true; 235 | bHalfFrameClock = true; 236 | frame_clock_counter = 0; 237 | } 238 | 239 | // Update functional units 240 | 241 | // Quater frame "beats" adjust the volume envelope 242 | if (bQuarterFrameClock) { 243 | pulse1_env.clock(pulse1_halt); 244 | pulse2_env.clock(pulse2_halt); 245 | noise_env.clock(noise_halt); 246 | } 247 | 248 | // Half frame "beats" adjust the note length and 249 | // frequency sweepers 250 | if (bHalfFrameClock) { 251 | pulse1_lc.clock(pulse1_enable, pulse1_halt); 252 | pulse2_lc.clock(pulse2_enable, pulse2_halt); 253 | noise_lc.clock(noise_enable, noise_halt); 254 | pulse1_sweep.clock(pulse1_seq.reload, 0); 255 | pulse2_sweep.clock(pulse2_seq.reload, 1); 256 | } 257 | 258 | // if (bUseRawMode) 259 | { 260 | // Update Pulse1 Channel ================================ 261 | pulse1_seq.clock(pulse1_enable, [](uint32_t &s) { 262 | // Shift right by 1 bit, wrapping around 263 | s = ((s & 0x0001) << 7) | ((s & 0x00FE) >> 1); 264 | }); 265 | 266 | // pulse1_sample = (double)pulse1_seq.output; 267 | } 268 | // else 269 | { 270 | pulse1_osc.frequency = 271 | 1789773.0 / (16.0 * static_cast(pulse1_seq.reload + 1)); 272 | pulse1_osc.amplitude = static_cast(pulse1_env.output - 1) / 16.0f; 273 | pulse1_sample = pulse1_osc.sample(dGlobalTime); 274 | 275 | if (pulse1_lc.counter > 0 && pulse1_seq.timer >= 8 && 276 | !pulse1_sweep.mute && pulse1_env.output > 2) 277 | pulse1_output += (pulse1_sample - pulse1_output) * 0.5; 278 | else 279 | pulse1_output = 0; 280 | } 281 | 282 | // if (bUseRawMode) 283 | { 284 | // Update Pulse1 Channel ================================ 285 | pulse2_seq.clock(pulse2_enable, [](uint32_t &s) { 286 | // Shift right by 1 bit, wrapping around 287 | s = ((s & 0x0001) << 7) | ((s & 0x00FE) >> 1); 288 | }); 289 | 290 | // pulse2_sample = (double)pulse2_seq.output; 291 | } 292 | // else 293 | { 294 | pulse2_osc.frequency = 295 | 1789773.0 / (16.0 * static_cast(pulse2_seq.reload + 1)); 296 | pulse2_osc.amplitude = static_cast(pulse2_env.output - 1) / 16.0; 297 | pulse2_sample = pulse2_osc.sample(dGlobalTime); 298 | 299 | if (pulse2_lc.counter > 0 && pulse2_seq.timer >= 8 && 300 | !pulse2_sweep.mute && pulse2_env.output > 2) 301 | pulse2_output += (pulse2_sample - pulse2_output) * 0.5; 302 | else 303 | pulse2_output = 0; 304 | } 305 | 306 | noise_seq.clock(noise_enable, [](uint32_t &s) { 307 | s = (((s & 0x0001) ^ ((s & 0x0002) >> 1)) << 14) | ((s & 0x7FFF) >> 1); 308 | }); 309 | 310 | if (noise_lc.counter > 0 && noise_seq.timer >= 8) { 311 | noise_output = static_cast(noise_seq.output) * 312 | (static_cast(noise_env.output - 1) / 16.0); 313 | } 314 | 315 | if (!pulse1_enable) pulse1_output = 0; 316 | if (!pulse2_enable) pulse2_output = 0; 317 | if (!noise_enable) noise_output = 0; 318 | } 319 | 320 | // Frequency sweepers change at high frequency 321 | pulse1_sweep.track(pulse1_seq.reload); 322 | pulse2_sweep.track(pulse2_seq.reload); 323 | 324 | pulse1_visual = (pulse1_enable && pulse1_env.output > 1 && !pulse1_sweep.mute) 325 | ? pulse1_seq.reload 326 | : 2047; 327 | pulse2_visual = (pulse2_enable && pulse2_env.output > 1 && !pulse2_sweep.mute) 328 | ? pulse2_seq.reload 329 | : 2047; 330 | noise_visual = 331 | (noise_enable && noise_env.output > 1) ? noise_seq.reload : 2047; 332 | 333 | clock_counter++; 334 | } 335 | 336 | double Nes2A03::GetOutputSample() { 337 | if (bUseRawMode) { 338 | return (pulse1_sample - 0.5) * 0.5 + (pulse2_sample - 0.5) * 0.5; 339 | } else { 340 | return ((1.0 * pulse1_output) - 0.8) * 0.1 + 341 | ((1.0 * pulse2_output) - 0.8) * 0.1 + 342 | ((2.0 * (noise_output - 0.5))) * 0.1; 343 | } 344 | } 345 | 346 | void Nes2A03::reset() {} 347 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Nes2A03.h: -------------------------------------------------------------------------------- 1 | /* 2 | olc::NES - APU 3 | "Thanks Dad for believing computers were gonna be a big deal..." - 4 | javidx9 5 | 6 | License (OLC-3) 7 | ~~~~~~~~~~~~~~~ 8 | 9 | Copyright 2018-2019 OneLoneCoder.com 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions 13 | are met: 14 | 15 | 1. Redistributions or derivations of source code must retain the above 16 | copyright notice, this list of conditions and the following disclaimer. 17 | 18 | 2. Redistributions or derivative works in binary form must reproduce 19 | the above copyright notice. This list of conditions and the following 20 | disclaimer must be reproduced in the documentation and/or other 21 | materials provided with the distribution. 22 | 23 | 3. Neither the name of the copyright holder nor the names of its 24 | contributors may be used to endorse or promote products derived 25 | from this software without specific prior written permission. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 31 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 | 39 | 40 | Relevant Video: https://youtu.be/xdzOvpYPmGE 41 | 42 | Links 43 | ~~~~~ 44 | YouTube: https://www.youtube.com/javidx9 45 | https://www.youtube.com/javidx9extra 46 | Discord: https://discord.gg/WhwHUMV 47 | Twitter: https://www.twitter.com/javidx9 48 | Twitch: https://www.twitch.tv/javidx9 49 | GitHub: https://www.github.com/onelonecoder 50 | Patreon: https://www.patreon.com/javidx9 51 | Homepage: https://www.onelonecoder.com 52 | 53 | Author 54 | ~~~~~~ 55 | David Barr, aka javidx9, �OneLoneCoder 2019 56 | */ 57 | 58 | /* 59 | 60 | IMPORTANT !!!! 61 | 62 | THIS CLASS IS VERY UNFINISHED 63 | 64 | */ 65 | #pragma once 66 | 67 | #include 68 | #include 69 | 70 | class Nes2A03 { 71 | public: 72 | Nes2A03(); 73 | ~Nes2A03(); 74 | 75 | public: 76 | void cpuWrite(uint16_t addr, uint8_t data); 77 | uint8_t cpuRead(uint16_t addr); 78 | void clock(); 79 | void reset(); 80 | 81 | double GetOutputSample(); 82 | 83 | private: 84 | uint32_t frame_clock_counter = 0; 85 | uint32_t clock_counter = 0; 86 | bool bUseRawMode = false; 87 | 88 | private: 89 | static uint8_t length_table[]; 90 | 91 | private: 92 | // Sequencer Module 93 | // ~~~~~~~~~~~~~~~~ 94 | // The purpose of the sequencer is to output a '1' after a given 95 | // interval. It does this by counting down from a start value, 96 | // when that value is < 0, it gets reset, and an internal "rotary" 97 | // buffer is shifted. The nature of ths shifted pattern is different 98 | // depending upon the channel, or module that requires sequencing. 99 | // For example, the square wave channels simply rotate the preset 100 | // sequence, but the noise channel needs to generate pseudo-random 101 | // outputs originating from the preset sequence. 102 | // 103 | // Consider a square wave channel. A preset sequence of 01010101 104 | // will output a 1 more freqently than 00010001, assuming we 105 | // always output the LSB. The speed of this output is also 106 | // governed by the timer counting down. The frequency is higher 107 | // for small timer values, and lower for larger. Increasing 108 | // the frequency of the output potentially increases the 109 | // audible frequency. In fact, this is how the pulse channels 110 | // fundamentally work. A "duty cycle" shape is loaded into the 111 | // sequencer and the timer is used to vary the pitch, yielding 112 | // notes. 113 | 114 | struct sequencer { 115 | uint32_t sequence = 0x00000000; 116 | uint32_t new_sequence = 0x00000000; 117 | uint16_t timer = 0x0000; 118 | uint16_t reload = 0x0000; 119 | uint8_t output = 0x00; 120 | 121 | // Pass in a lambda function to manipulate the sequence as required 122 | // by the owner of this sequencer module 123 | uint8_t clock(bool bEnable, std::function funcManip) { 124 | if (bEnable) { 125 | timer--; 126 | if (timer == 0xFFFF) { 127 | timer = reload; 128 | funcManip(sequence); 129 | output = sequence & 0x00000001; 130 | } 131 | } 132 | return output; 133 | } 134 | }; 135 | 136 | struct lengthcounter { 137 | uint8_t counter = 0x00; 138 | uint8_t clock(bool bEnable, bool bHalt) { 139 | if (!bEnable) 140 | counter = 0; 141 | else if (counter > 0 && !bHalt) 142 | counter--; 143 | return counter; 144 | } 145 | }; 146 | 147 | struct envelope { 148 | void clock(bool bLoop) { 149 | if (!start) { 150 | if (divider_count == 0) { 151 | divider_count = volume; 152 | 153 | if (decay_count == 0) { 154 | if (bLoop) { 155 | decay_count = 15; 156 | } 157 | } else 158 | decay_count--; 159 | } else 160 | divider_count--; 161 | } else { 162 | start = false; 163 | decay_count = 15; 164 | divider_count = volume; 165 | } 166 | 167 | if (disable) { 168 | output = volume; 169 | } else { 170 | output = decay_count; 171 | } 172 | } 173 | 174 | bool start = false; 175 | bool disable = false; 176 | uint16_t divider_count = 0; 177 | uint16_t volume = 0; 178 | uint16_t output = 0; 179 | uint16_t decay_count = 0; 180 | }; 181 | 182 | struct oscpulse { // 脉冲 183 | double frequency = 0; 184 | double dutycycle = 0; 185 | double amplitude = 1; 186 | double pi = 3.14159; 187 | double harmonics = 20; 188 | 189 | double sample(double t) { 190 | double a = 0; 191 | double b = 0; 192 | double p = dutycycle * 2.0 * pi; 193 | 194 | auto approxsin = [](float t) { 195 | float j = t * 0.15915; 196 | j = j - static_cast(j); 197 | return 20.785f * j * (j - 0.5) * (j - 1.0f); 198 | }; 199 | 200 | for (double n = 1; n < harmonics; n++) { 201 | double c = n * frequency * 2.0 * pi * t; 202 | a += -approxsin(c) / n; 203 | b += -approxsin(c - p * n) / n; 204 | 205 | // a += -sin(c) / n; 206 | // b += -sin(c - p * n) / n; 207 | } 208 | 209 | return (2.0 * amplitude / pi) * (a - b); 210 | } 211 | }; 212 | 213 | struct sweeper { 214 | bool enabled = false; 215 | bool down = false; 216 | bool reload = false; 217 | uint8_t shift = 0x00; 218 | uint8_t timer = 0x00; 219 | uint8_t period = 0x00; 220 | uint16_t change = 0; 221 | bool mute = false; 222 | 223 | void track(uint16_t target) { 224 | if (enabled) { 225 | change = target >> shift; 226 | mute = (target < 8) || (target > 0x7FF); 227 | } 228 | } 229 | 230 | bool clock(uint16_t &target, bool channel) { 231 | bool changed = false; 232 | if (timer == 0 && enabled && shift > 0 && !mute) { 233 | if (target >= 8 && change < 0x07FF) { 234 | if (down) { 235 | target -= change - channel; 236 | } else { 237 | target += change; 238 | } 239 | changed = true; 240 | } 241 | } 242 | 243 | // if (enabled) 244 | { 245 | if (timer == 0 || reload) { 246 | timer = period; 247 | reload = false; 248 | } else 249 | timer--; 250 | 251 | mute = (target < 8) || (target > 0x7FF); 252 | } 253 | 254 | return changed; 255 | } 256 | }; 257 | 258 | double dGlobalTime = 0.0; 259 | 260 | // Square Wave Pulse Channel 1 261 | bool pulse1_enable = false; 262 | bool pulse1_halt = false; 263 | double pulse1_sample = 0.0; 264 | double pulse1_output = 0.0; 265 | sequencer pulse1_seq; 266 | oscpulse pulse1_osc; 267 | envelope pulse1_env; 268 | lengthcounter pulse1_lc; 269 | sweeper pulse1_sweep; 270 | 271 | // Square Wave Pulse Channel 2 272 | bool pulse2_enable = false; 273 | bool pulse2_halt = false; 274 | double pulse2_sample = 0.0; 275 | double pulse2_output = 0.0; 276 | sequencer pulse2_seq; 277 | oscpulse pulse2_osc; 278 | envelope pulse2_env; 279 | lengthcounter pulse2_lc; 280 | sweeper pulse2_sweep; 281 | 282 | // Noise Channel 283 | bool noise_enable = false; 284 | bool noise_halt = false; 285 | envelope noise_env; 286 | lengthcounter noise_lc; 287 | sequencer noise_seq; 288 | double noise_sample = 0; 289 | double noise_output = 0; 290 | 291 | public: 292 | uint16_t pulse1_visual = 0; 293 | uint16_t pulse2_visual = 0; 294 | uint16_t noise_visual = 0; 295 | uint16_t triangle_visual = 0; 296 | }; 297 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Nes2C02.h: -------------------------------------------------------------------------------- 1 | // Copyright [2020-2022] 2 | 3 | #pragma once 4 | #include 5 | #include 6 | 7 | #include "Cartridge.h" 8 | #include "olcPixelGameEngine.h" 9 | class Nes2C02 { 10 | public: 11 | Nes2C02(); 12 | ~Nes2C02(); 13 | 14 | private: 15 | uint8_t tblName[2][1024]; // 名称表 16 | uint8_t tblPattern[2][4096]; // 模式表 17 | uint8_t tblPalette[32]; // 调色板 18 | 19 | private: 20 | olc::Pixel palScreen[0x40]; // 颜色 21 | olc::Sprite sprScreen = olc::Sprite(256, 240); // 显示器 22 | olc::Sprite sprNameTable[2] = { 23 | olc::Sprite(256, 240), olc::Sprite(256, 240)}; // 读取需要显示的名称表 24 | olc::Sprite sprPatternTable[2] = {olc::Sprite(128, 128), 25 | olc::Sprite(128, 128)}; // 需要显示的模式表 26 | olc::Sprite sprSpriteTitle[26]; // 0-26个OAM中的精灵 27 | 28 | public: 29 | // 调试函数(打印各种信息) 30 | olc::Sprite &GetScreen(); // 屏幕 31 | olc::Sprite &GetNameTable(uint8_t i); // 获取名称表 32 | olc::Sprite &GetPatternTable(uint8_t i, uint8_t palette); // 获取模式表 33 | // olc::Sprite &GetSpriteTitle(uint8_t x, // 打印部分精灵OAM信息 34 | // uint8_t y, uint8_t title, uint8_t attr, 35 | // uint8_t palette, int i); 36 | 37 | olc::Pixel &GetColourFromPaletteRam(uint8_t palette, 38 | uint8_t pixel); // 获取调色后的pixel 39 | 40 | bool frame_complete = false; // 用来判断帧的绘制是否完成 41 | 42 | private: 43 | union { 44 | struct { 45 | uint8_t unused : 5; 46 | uint8_t sprite_overflow : 1; 47 | uint8_t sprite_zero_hit : 1; 48 | uint8_t vertical_blank : 1; 49 | }; 50 | 51 | uint8_t reg; 52 | } status; // 状态寄存器 53 | 54 | union { 55 | struct { 56 | uint8_t grayscale : 1; 57 | uint8_t render_background_left : 1; 58 | uint8_t render_sprites_left : 1; 59 | uint8_t render_background : 1; 60 | uint8_t render_sprites : 1; 61 | uint8_t enhance_red : 1; 62 | uint8_t enhance_green : 1; 63 | uint8_t enhance_blue : 1; 64 | }; 65 | 66 | uint8_t reg; 67 | } mask; // 掩码寄存器 68 | 69 | union PPUCTRL { 70 | struct { 71 | uint8_t nametable_x : 1; 72 | uint8_t nametable_y : 1; 73 | uint8_t increment_mode : 1; 74 | uint8_t pattern_sprite : 1; 75 | uint8_t pattern_background : 1; 76 | uint8_t sprite_size : 1; 77 | uint8_t slave_mode : 1; // unused 78 | uint8_t enable_nmi : 1; 79 | }; 80 | 81 | uint8_t reg; 82 | } control; // 控制寄存器 83 | 84 | union loopy_register { 85 | // Credit to Loopy for working this out :D 86 | struct { 87 | uint16_t coarse_x : 5; // 0 - 31 nametable的 x 轴坐标 88 | uint16_t coarse_y : 5; // 0 - 31 nametable的 y 轴坐标 89 | uint16_t nametable_x : 1; // 90 | uint16_t nametable_y : 1; 91 | uint16_t fine_y : 3; // 0- 8 用来记录此 pixel 绘制的y 坐标, 92 | uint16_t unused : 1; 93 | }; 94 | 95 | uint16_t reg = 0x0000; 96 | }; // ppu中的绘制寄存器 97 | // 将活动“指针”地址插入nametable以提取背景磁贴信息 98 | loopy_register vram_addr; // Active "pointer" address into nametable to 99 | // extract background tile info 100 | // 在不同时间将信息临时存储到“指针”中 101 | loopy_register tram_addr; // Temporary store of information to be 102 | // "transferred" into "pointer" at various times 103 | 104 | // 像素水平偏移 105 | uint8_t fine_x = 0x00; 106 | 107 | // ppu 内部寄存器 108 | uint8_t address_latch = 0x00; 109 | uint8_t ppu_data_buffer = 0x00; 110 | 111 | // 像素“点”位置信息 112 | int16_t scanline = 0; // 用来记录扫描的行数 113 | int16_t cycle = 0; // 记录扫描点的列数 114 | 115 | // 背景像素渲染信息 116 | uint8_t bg_next_tile_id = 0x00; 117 | uint8_t bg_next_tile_attrib = 0x00; 118 | uint8_t bg_next_tile_lsb = 0x00; 119 | uint8_t bg_next_tile_msb = 0x00; 120 | uint16_t bg_shifter_pattern_lo = 0x0000; 121 | uint16_t bg_shifter_pattern_hi = 0x0000; 122 | uint16_t bg_shifter_attrib_lo = 0x0000; 123 | uint16_t bg_shifter_attrib_hi = 0x0000; 124 | 125 | // 前景"精灵"渲染信息 126 | // OAM是PPU内部的附加内存。 127 | // 未通过任何总线连接。它 128 | // 存储在下一帧上绘制64个8x8(或8x16)瓷砖。 129 | struct sObjectAttributeEntry { 130 | uint8_t y; // 精灵在名称表的Y轴坐标 Y 1- 256 131 | uint8_t id; // 精灵在模式表中的ID号,一共两张表, 132 | uint8_t attribute; // 存放该精灵的title属性 133 | uint8_t x; // 精灵在名称表的Y轴坐标 X 1-240 134 | } OAM[64]; 135 | 136 | // 当CPU手动通信时存储地址的寄存器 137 | // 通过PPU寄存器使用OAM。不常用,因为它 138 | // 非常慢,使用256字节的DMA传输。 139 | uint8_t oam_addr = 0x00; 140 | 141 | sObjectAttributeEntry spriteScanline[8]; // 在一行中,存放的最多8个精灵 142 | uint8_t sprite_count; // 在一行中的精灵数量 143 | uint8_t 144 | sprite_shifter_pattern_lo[8]; // 下一行将要绘制的精灵像素的低8个字节 信息 145 | uint8_t sprite_shifter_pattern_hi 146 | [8]; // 下一行将要绘制的精灵像素的高8个字节 信息 147 | // 这两个字节相加的结果组成一个8x8bit的像素图片 148 | // 0号精灵命中标志 149 | bool bSpriteZeroHitPossible = false; 150 | bool bSpriteZeroBeingRendered = false; 151 | bool odd_frame = false; 152 | 153 | public: 154 | // 上面的OAM可以方便地打包使用,但是DMA 155 | // 机制需要访问它,以便一次编写一个byte 156 | // 将OAM强转为字节指针,方便DMA直接传输 157 | uint8_t *pOAM = reinterpret_cast(OAM); 158 | 159 | public: 160 | // PPU与主线CPU进行通信函数 161 | uint8_t cpuRead(uint16_t addr, bool rdonly = false); 162 | void cpuWrite(uint16_t addr, uint8_t data); 163 | 164 | // 与PPU内部总线进行通信函数 165 | // 其中包含一些镜像地址需要转换 166 | uint8_t ppuRead(uint16_t addr, bool rdonly = false); 167 | void ppuWrite(uint16_t addr, uint8_t data); 168 | 169 | private: 170 | // 卡带 171 | std::shared_ptr cart; 172 | 173 | public: 174 | // 外部接口 175 | void ConnectCartridge(const std::shared_ptr &cartridge); 176 | void clock(); 177 | void reset(); 178 | bool nmi = false; 179 | }; 180 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Nes6502.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Nes6502 - An emulation of the 6502/2A03 processor 3 | "Thanks Dad for believing computers were gonna be a big deal..." - 4 | javidx9 5 | 6 | License (OLC-3) 7 | ~~~~~~~~~~~~~~~ 8 | 9 | Copyright 2018-2019 OneLoneCoder.com 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions 13 | are met: 14 | 15 | 1. Redistributions or derivations of source code must retain the above 16 | copyright notice, this list of conditions and the following disclaimer. 17 | 18 | 2. Redistributions or derivative works in binary form must reproduce 19 | the above copyright notice. This list of conditions and the following 20 | disclaimer must be reproduced in the documentation and/or other 21 | materials provided with the distribution. 22 | 23 | 3. Neither the name of the copyright holder nor the names of its 24 | contributors may be used to endorse or promote products derived 25 | from this software without specific prior written permission. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 31 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 | 39 | Background 40 | ~~~~~~~~~~ 41 | I love this microprocessor. It was at the heart of two of my favourite 42 | machines, the BBC Micro, and the Nintendo Entertainment System, as well 43 | as countless others in that era. I learnt to program on the Model B, and 44 | I learnt to love games on the NES, so in many ways, this processor is 45 | why I am the way I am today. 46 | 47 | In February 2019, I decided to undertake a selfish personal project and 48 | build a NES emulator. Ive always wanted to, and as such I've avoided 49 | looking at source code for such things. This made making this a real 50 | personal challenge. I know its been done countless times, and very 51 | likely in far more clever and accurate ways than mine, but I'm proud of this. 52 | 53 | Datasheet: http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf 54 | 55 | Files: Nes6502.h, Nes6502.cpp 56 | 57 | Relevant Video: 58 | 59 | Links 60 | ~~~~~ 61 | YouTube: https://www.youtube.com/javidx9 62 | https://www.youtube.com/javidx9extra 63 | Discord: https://discord.gg/WhwHUMV 64 | Twitter: https://www.twitter.com/javidx9 65 | Twitch: https://www.twitch.tv/javidx9 66 | GitHub: https://www.github.com/onelonecoder 67 | Patreon: https://www.patreon.com/javidx9 68 | Homepage: https://www.onelonecoder.com 69 | 70 | Author 71 | ~~~~~~ 72 | David Barr, aka javidx9, �OneLoneCoder 2019 73 | */ 74 | 75 | #include "Nes6502.h" 76 | 77 | #include "Bus.h" 78 | 79 | // Constructor 80 | Nes6502::Nes6502() { 81 | // Assembles the translation table. It's big, it's ugly, but it yields a 82 | // convenient way to emulate the 6502. I'm certain there are some "code-golf" 83 | // strategies to reduce this but I've deliberately kept it verbose for study 84 | // and alteration 85 | 86 | // It is 16x16 entries. This gives 256 instructions. It is arranged to that 87 | // the bottom 4 bits of the instruction choose the column, and the top 4 bits 88 | // choose the row. 89 | 90 | // For convenience to get function pointers to members of this class, I'm 91 | // using this or else it will be much much larger :D 92 | 93 | // The table is one big initialiser list of initialiser lists... 94 | using a = Nes6502; 95 | lookup = { 96 | {"BRK", &a::BRK, &a::IMM, 7}, {"ORA", &a::ORA, &a::IZX, 6}, 97 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 98 | {"???", &a::NOP, &a::IMP, 3}, {"ORA", &a::ORA, &a::ZP0, 3}, 99 | {"ASL", &a::ASL, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5}, 100 | {"PHP", &a::PHP, &a::IMP, 3}, {"ORA", &a::ORA, &a::IMM, 2}, 101 | {"ASL", &a::ASL, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2}, 102 | {"???", &a::NOP, &a::IMP, 4}, {"ORA", &a::ORA, &a::ABS, 4}, 103 | {"ASL", &a::ASL, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6}, 104 | {"BPL", &a::BPL, &a::REL, 2}, {"ORA", &a::ORA, &a::IZY, 5}, 105 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 106 | {"???", &a::NOP, &a::IMP, 4}, {"ORA", &a::ORA, &a::ZPX, 4}, 107 | {"ASL", &a::ASL, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6}, 108 | {"CLC", &a::CLC, &a::IMP, 2}, {"ORA", &a::ORA, &a::ABY, 4}, 109 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7}, 110 | {"???", &a::NOP, &a::IMP, 4}, {"ORA", &a::ORA, &a::ABX, 4}, 111 | {"ASL", &a::ASL, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7}, 112 | {"JSR", &a::JSR, &a::ABS, 6}, {"AND", &a::AND, &a::IZX, 6}, 113 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 114 | {"BIT", &a::BIT, &a::ZP0, 3}, {"AND", &a::AND, &a::ZP0, 3}, 115 | {"ROL", &a::ROL, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5}, 116 | {"PLP", &a::PLP, &a::IMP, 4}, {"AND", &a::AND, &a::IMM, 2}, 117 | {"ROL", &a::ROL, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2}, 118 | {"BIT", &a::BIT, &a::ABS, 4}, {"AND", &a::AND, &a::ABS, 4}, 119 | {"ROL", &a::ROL, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6}, 120 | {"BMI", &a::BMI, &a::REL, 2}, {"AND", &a::AND, &a::IZY, 5}, 121 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 122 | {"???", &a::NOP, &a::IMP, 4}, {"AND", &a::AND, &a::ZPX, 4}, 123 | {"ROL", &a::ROL, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6}, 124 | {"SEC", &a::SEC, &a::IMP, 2}, {"AND", &a::AND, &a::ABY, 4}, 125 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7}, 126 | {"???", &a::NOP, &a::IMP, 4}, {"AND", &a::AND, &a::ABX, 4}, 127 | {"ROL", &a::ROL, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7}, 128 | {"RTI", &a::RTI, &a::IMP, 6}, {"EOR", &a::EOR, &a::IZX, 6}, 129 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 130 | {"???", &a::NOP, &a::IMP, 3}, {"EOR", &a::EOR, &a::ZP0, 3}, 131 | {"LSR", &a::LSR, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5}, 132 | {"PHA", &a::PHA, &a::IMP, 3}, {"EOR", &a::EOR, &a::IMM, 2}, 133 | {"LSR", &a::LSR, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2}, 134 | {"JMP", &a::JMP, &a::ABS, 3}, {"EOR", &a::EOR, &a::ABS, 4}, 135 | {"LSR", &a::LSR, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6}, 136 | {"BVC", &a::BVC, &a::REL, 2}, {"EOR", &a::EOR, &a::IZY, 5}, 137 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 138 | {"???", &a::NOP, &a::IMP, 4}, {"EOR", &a::EOR, &a::ZPX, 4}, 139 | {"LSR", &a::LSR, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6}, 140 | {"CLI", &a::CLI, &a::IMP, 2}, {"EOR", &a::EOR, &a::ABY, 4}, 141 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7}, 142 | {"???", &a::NOP, &a::IMP, 4}, {"EOR", &a::EOR, &a::ABX, 4}, 143 | {"LSR", &a::LSR, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7}, 144 | {"RTS", &a::RTS, &a::IMP, 6}, {"ADC", &a::ADC, &a::IZX, 6}, 145 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 146 | {"???", &a::NOP, &a::IMP, 3}, {"ADC", &a::ADC, &a::ZP0, 3}, 147 | {"ROR", &a::ROR, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5}, 148 | {"PLA", &a::PLA, &a::IMP, 4}, {"ADC", &a::ADC, &a::IMM, 2}, 149 | {"ROR", &a::ROR, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2}, 150 | {"JMP", &a::JMP, &a::IND, 5}, {"ADC", &a::ADC, &a::ABS, 4}, 151 | {"ROR", &a::ROR, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6}, 152 | {"BVS", &a::BVS, &a::REL, 2}, {"ADC", &a::ADC, &a::IZY, 5}, 153 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 154 | {"???", &a::NOP, &a::IMP, 4}, {"ADC", &a::ADC, &a::ZPX, 4}, 155 | {"ROR", &a::ROR, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6}, 156 | {"SEI", &a::SEI, &a::IMP, 2}, {"ADC", &a::ADC, &a::ABY, 4}, 157 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7}, 158 | {"???", &a::NOP, &a::IMP, 4}, {"ADC", &a::ADC, &a::ABX, 4}, 159 | {"ROR", &a::ROR, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7}, 160 | {"???", &a::NOP, &a::IMP, 2}, {"STA", &a::STA, &a::IZX, 6}, 161 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 6}, 162 | {"STY", &a::STY, &a::ZP0, 3}, {"STA", &a::STA, &a::ZP0, 3}, 163 | {"STX", &a::STX, &a::ZP0, 3}, {"???", &a::XXX, &a::IMP, 3}, 164 | {"DEY", &a::DEY, &a::IMP, 2}, {"???", &a::NOP, &a::IMP, 2}, 165 | {"TXA", &a::TXA, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2}, 166 | {"STY", &a::STY, &a::ABS, 4}, {"STA", &a::STA, &a::ABS, 4}, 167 | {"STX", &a::STX, &a::ABS, 4}, {"???", &a::XXX, &a::IMP, 4}, 168 | {"BCC", &a::BCC, &a::REL, 2}, {"STA", &a::STA, &a::IZY, 6}, 169 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 6}, 170 | {"STY", &a::STY, &a::ZPX, 4}, {"STA", &a::STA, &a::ZPX, 4}, 171 | {"STX", &a::STX, &a::ZPY, 4}, {"???", &a::XXX, &a::IMP, 4}, 172 | {"TYA", &a::TYA, &a::IMP, 2}, {"STA", &a::STA, &a::ABY, 5}, 173 | {"TXS", &a::TXS, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 5}, 174 | {"???", &a::NOP, &a::IMP, 5}, {"STA", &a::STA, &a::ABX, 5}, 175 | {"???", &a::XXX, &a::IMP, 5}, {"???", &a::XXX, &a::IMP, 5}, 176 | {"LDY", &a::LDY, &a::IMM, 2}, {"LDA", &a::LDA, &a::IZX, 6}, 177 | {"LDX", &a::LDX, &a::IMM, 2}, {"???", &a::XXX, &a::IMP, 6}, 178 | {"LDY", &a::LDY, &a::ZP0, 3}, {"LDA", &a::LDA, &a::ZP0, 3}, 179 | {"LDX", &a::LDX, &a::ZP0, 3}, {"???", &a::XXX, &a::IMP, 3}, 180 | {"TAY", &a::TAY, &a::IMP, 2}, {"LDA", &a::LDA, &a::IMM, 2}, 181 | {"TAX", &a::TAX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2}, 182 | {"LDY", &a::LDY, &a::ABS, 4}, {"LDA", &a::LDA, &a::ABS, 4}, 183 | {"LDX", &a::LDX, &a::ABS, 4}, {"???", &a::XXX, &a::IMP, 4}, 184 | {"BCS", &a::BCS, &a::REL, 2}, {"LDA", &a::LDA, &a::IZY, 5}, 185 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 5}, 186 | {"LDY", &a::LDY, &a::ZPX, 4}, {"LDA", &a::LDA, &a::ZPX, 4}, 187 | {"LDX", &a::LDX, &a::ZPY, 4}, {"???", &a::XXX, &a::IMP, 4}, 188 | {"CLV", &a::CLV, &a::IMP, 2}, {"LDA", &a::LDA, &a::ABY, 4}, 189 | {"TSX", &a::TSX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 4}, 190 | {"LDY", &a::LDY, &a::ABX, 4}, {"LDA", &a::LDA, &a::ABX, 4}, 191 | {"LDX", &a::LDX, &a::ABY, 4}, {"???", &a::XXX, &a::IMP, 4}, 192 | {"CPY", &a::CPY, &a::IMM, 2}, {"CMP", &a::CMP, &a::IZX, 6}, 193 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 194 | {"CPY", &a::CPY, &a::ZP0, 3}, {"CMP", &a::CMP, &a::ZP0, 3}, 195 | {"DEC", &a::DEC, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5}, 196 | {"INY", &a::INY, &a::IMP, 2}, {"CMP", &a::CMP, &a::IMM, 2}, 197 | {"DEX", &a::DEX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 2}, 198 | {"CPY", &a::CPY, &a::ABS, 4}, {"CMP", &a::CMP, &a::ABS, 4}, 199 | {"DEC", &a::DEC, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6}, 200 | {"BNE", &a::BNE, &a::REL, 2}, {"CMP", &a::CMP, &a::IZY, 5}, 201 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 202 | {"???", &a::NOP, &a::IMP, 4}, {"CMP", &a::CMP, &a::ZPX, 4}, 203 | {"DEC", &a::DEC, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6}, 204 | {"CLD", &a::CLD, &a::IMP, 2}, {"CMP", &a::CMP, &a::ABY, 4}, 205 | {"NOP", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7}, 206 | {"???", &a::NOP, &a::IMP, 4}, {"CMP", &a::CMP, &a::ABX, 4}, 207 | {"DEC", &a::DEC, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7}, 208 | {"CPX", &a::CPX, &a::IMM, 2}, {"SBC", &a::SBC, &a::IZX, 6}, 209 | {"???", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 210 | {"CPX", &a::CPX, &a::ZP0, 3}, {"SBC", &a::SBC, &a::ZP0, 3}, 211 | {"INC", &a::INC, &a::ZP0, 5}, {"???", &a::XXX, &a::IMP, 5}, 212 | {"INX", &a::INX, &a::IMP, 2}, {"SBC", &a::SBC, &a::IMM, 2}, 213 | {"NOP", &a::NOP, &a::IMP, 2}, {"???", &a::SBC, &a::IMP, 2}, 214 | {"CPX", &a::CPX, &a::ABS, 4}, {"SBC", &a::SBC, &a::ABS, 4}, 215 | {"INC", &a::INC, &a::ABS, 6}, {"???", &a::XXX, &a::IMP, 6}, 216 | {"BEQ", &a::BEQ, &a::REL, 2}, {"SBC", &a::SBC, &a::IZY, 5}, 217 | {"???", &a::XXX, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 8}, 218 | {"???", &a::NOP, &a::IMP, 4}, {"SBC", &a::SBC, &a::ZPX, 4}, 219 | {"INC", &a::INC, &a::ZPX, 6}, {"???", &a::XXX, &a::IMP, 6}, 220 | {"SED", &a::SED, &a::IMP, 2}, {"SBC", &a::SBC, &a::ABY, 4}, 221 | {"NOP", &a::NOP, &a::IMP, 2}, {"???", &a::XXX, &a::IMP, 7}, 222 | {"???", &a::NOP, &a::IMP, 4}, {"SBC", &a::SBC, &a::ABX, 4}, 223 | {"INC", &a::INC, &a::ABX, 7}, {"???", &a::XXX, &a::IMP, 7}, 224 | }; 225 | } 226 | 227 | Nes6502::~Nes6502() { 228 | // Destructor - has nothing to do 229 | } 230 | 231 | /////////////////////////////////////////////////////////////////////////////// 232 | // BUS CONNECTIVITY 233 | 234 | // Reads an 8-bit byte from the bus, located at the specified 16-bit address 235 | uint8_t Nes6502::read(uint16_t a) { 236 | // In normal operation "read only" is set to false. This may seem odd. Some 237 | // devices on the bus may change state when they are read from, and this 238 | // is intentional under normal circumstances. However the disassembler will 239 | // want to read the data at an address without changing the state of the 240 | // devices on the bus 241 | return bus->cpuRead(a, false); 242 | } 243 | 244 | // Writes a byte to the bus at the specified address 245 | void Nes6502::write(uint16_t a, uint8_t d) { bus->cpuWrite(a, d); } 246 | 247 | /////////////////////////////////////////////////////////////////////////////// 248 | // EXTERNAL INPUTS 249 | 250 | // Forces the 6502 into a known state. This is hard-wired inside the CPU. The 251 | // registers are set to 0x00, the status register is cleared except for unused 252 | // bit which remains at 1. An absolute address is read from location 0xFFFC 253 | // which contains a second address that the program counter is set to. This 254 | // allows the programmer to jump to a known and programmable location in the 255 | // memory to start executing from. Typically the programmer would set the value 256 | // at location 0xFFFC at compile time. 257 | void Nes6502::reset() { 258 | // Get address to set program counter to 259 | addr_abs = 0xFFFC; 260 | uint16_t lo = read(addr_abs + 0); 261 | uint16_t hi = read(addr_abs + 1); 262 | 263 | // Set it 264 | pc = (hi << 8) | lo; 265 | 266 | // Reset internal registers 267 | a = 0; 268 | x = 0; 269 | y = 0; 270 | stkp = 0xFD; 271 | status = 0x00 | U; 272 | 273 | // Clear internal helper variables 274 | addr_rel = 0x0000; 275 | addr_abs = 0x0000; 276 | fetched = 0x00; 277 | 278 | // Reset takes time 279 | cycles = 8; 280 | } 281 | 282 | // Interrupt requests are a complex operation and only happen if the 283 | // "disable interrupt" flag is 0. IRQs can happen at any time, but 284 | // you dont want them to be destructive to the operation of the running 285 | // program. Therefore the current instruction is allowed to finish 286 | // (which I facilitate by doing the whole thing when cycles == 0) and 287 | // then the current program counter is stored on the stack. Then the 288 | // current status register is stored on the stack. When the routine 289 | // that services the interrupt has finished, the status register 290 | // and program counter can be restored to how they where before it 291 | // occurred. This is impemented by the "RTI" instruction. Once the IRQ 292 | // has happened, in a similar way to a reset, a programmable address 293 | // is read form hard coded location 0xFFFE, which is subsequently 294 | // set to the program counter. 295 | void Nes6502::irq() { 296 | // If interrupts are allowed 297 | if (GetFlag(I) == 0) { 298 | // Push the program counter to the stack. It's 16-bits dont 299 | // forget so that takes two pushes 300 | write(0x0100 + stkp, (pc >> 8) & 0x00FF); 301 | stkp--; 302 | write(0x0100 + stkp, pc & 0x00FF); 303 | stkp--; 304 | 305 | // Then Push the status register to the stack 306 | SetFlag(B, 0); 307 | SetFlag(U, 1); 308 | SetFlag(I, 1); 309 | write(0x0100 + stkp, status); 310 | stkp--; 311 | 312 | // Read new program counter location from fixed address 313 | addr_abs = 0xFFFE; 314 | uint16_t lo = read(addr_abs + 0); 315 | uint16_t hi = read(addr_abs + 1); 316 | pc = (hi << 8) | lo; 317 | 318 | // IRQs take time 319 | cycles = 7; 320 | } 321 | } 322 | 323 | // A Non-Maskable Interrupt cannot be ignored. It behaves in exactly the 324 | // same way as a regular IRQ, but reads the new program counter address 325 | // form location 0xFFFA. 326 | void Nes6502::nmi() { 327 | write(0x0100 + stkp, (pc >> 8) & 0x00FF); 328 | stkp--; 329 | write(0x0100 + stkp, pc & 0x00FF); 330 | stkp--; 331 | 332 | SetFlag(B, 0); 333 | SetFlag(U, 1); 334 | SetFlag(I, 1); 335 | write(0x0100 + stkp, status); 336 | stkp--; 337 | 338 | addr_abs = 0xFFFA; 339 | uint16_t lo = read(addr_abs + 0); 340 | uint16_t hi = read(addr_abs + 1); 341 | pc = (hi << 8) | lo; 342 | 343 | cycles = 8; 344 | } 345 | 346 | // Perform one clock cycles worth of emulation 347 | void Nes6502::clock() { 348 | // Each instruction requires a variable number of clock cycles to execute. 349 | // In my emulation, I only care about the final result and so I perform 350 | // the entire computation in one hit. In hardware, each clock cycle would 351 | // perform "microcode" style transformations of the CPUs state. 352 | // 353 | // To remain compliant with connected devices, it's important that the 354 | // emulation also takes "time" in order to execute instructions, so I 355 | // implement that delay by simply counting down the cycles required by 356 | // the instruction. When it reaches 0, the instruction is complete, and 357 | // the next one is ready to be executed. 358 | if (cycles == 0) { 359 | // Read next instruction byte. This 8-bit value is used to index 360 | // the translation table to get the relevant information about 361 | // how to implement the instruction 362 | opcode = read(pc); 363 | 364 | #ifdef LOGMODE 365 | uint16_t log_pc = pc; 366 | #endif 367 | 368 | // Always set the unused status flag bit to 1 369 | SetFlag(U, true); 370 | 371 | // Increment program counter, we read the opcode byte 372 | pc++; 373 | 374 | // Get Starting number of cycles 375 | cycles = lookup[opcode].cycles; 376 | 377 | // Perform fetch of intermmediate data using the 378 | // required addressing mode 379 | uint8_t additional_cycle1 = (this->*lookup[opcode].addrmode)(); 380 | 381 | // Perform operation 382 | uint8_t additional_cycle2 = (this->*lookup[opcode].operate)(); 383 | 384 | // The addressmode and opcode may have altered the number 385 | // of cycles this instruction requires before its completed 386 | cycles += (additional_cycle1 & additional_cycle2); 387 | 388 | // Always set the unused status flag bit to 1 389 | SetFlag(U, true); 390 | 391 | #ifdef LOGMODE 392 | // This logger dumps every cycle the entire processor state for analysis. 393 | // This can be used for debugging the emulation, but has little utility 394 | // during emulation. Its also very slow, so only use if you have to. 395 | if (logfile == nullptr) logfile = fopen("Nes6502.txt", "wt"); 396 | if (logfile != nullptr) { 397 | fprintf(logfile, 398 | "%10d:%02d PC:%04X %s A:%02X X:%02X Y:%02X %s%s%s%s%s%s%s%s " 399 | "STKP:%02X\n", 400 | clock_count, 0, log_pc, "XXX", a, x, y, GetFlag(N) ? "N" : ".", 401 | GetFlag(V) ? "V" : ".", GetFlag(U) ? "U" : ".", 402 | GetFlag(B) ? "B" : ".", GetFlag(D) ? "D" : ".", 403 | GetFlag(I) ? "I" : ".", GetFlag(Z) ? "Z" : ".", 404 | GetFlag(C) ? "C" : ".", stkp); 405 | } 406 | #endif 407 | } 408 | 409 | // Increment global clock count - This is actually unused unless logging is 410 | // enabled but I've kept it in because its a handy watch variable for 411 | // debugging 412 | clock_count++; 413 | 414 | // Decrement the number of cycles remaining for this instruction 415 | cycles--; 416 | } 417 | 418 | /////////////////////////////////////////////////////////////////////////////// 419 | // FLAG FUNCTIONS 420 | 421 | // Returns the value of a specific bit of the status register 422 | uint8_t Nes6502::GetFlag(FLAGS6502 f) { return ((status & f) > 0) ? 1 : 0; } 423 | 424 | // Sets or clears a specific bit of the status register 425 | void Nes6502::SetFlag(FLAGS6502 f, bool v) { 426 | if (v) 427 | status |= f; 428 | else 429 | status &= ~f; 430 | } 431 | 432 | /////////////////////////////////////////////////////////////////////////////// 433 | // ADDRESSING MODES 434 | 435 | // The 6502 can address between 0x0000 - 0xFFFF. The high byte is often referred 436 | // to as the "page", and the low byte is the offset into that page. This implies 437 | // there are 256 pages, each containing 256 bytes. 438 | // 439 | // Several addressing modes have the potential to require an additional clock 440 | // cycle if they cross a page boundary. This is combined with several 441 | // instructions that enable this additional clock cycle. So each addressing 442 | // function returns a flag saying it has potential, as does each instruction. If 443 | // both instruction and address function return 1, then an additional clock 444 | // cycle is required. 445 | 446 | // Address Mode: Implied 447 | // There is no additional data required for this instruction. The instruction 448 | // does something very simple like like sets a status bit. However, we will 449 | // target the accumulator, for instructions like PHA 450 | uint8_t Nes6502::IMP() { 451 | fetched = a; 452 | return 0; 453 | } 454 | 455 | // Address Mode: Immediate 456 | // The instruction expects the next byte to be used as a value, so we'll prep 457 | // the read address to point to the next byte 458 | uint8_t Nes6502::IMM() { 459 | addr_abs = pc++; 460 | return 0; 461 | } 462 | 463 | // Address Mode: Zero Page 464 | // To save program bytes, zero page addressing allows you to absolutely address 465 | // a location in first 0xFF bytes of address range. Clearly this only requires 466 | // one byte instead of the usual two. 467 | uint8_t Nes6502::ZP0() { 468 | addr_abs = read(pc); 469 | pc++; 470 | addr_abs &= 0x00FF; 471 | return 0; 472 | } 473 | 474 | // Address Mode: Zero Page with X Offset 475 | // Fundamentally the same as Zero Page addressing, but the contents of the X 476 | // Register is added to the supplied single byte address. This is useful for 477 | // iterating through ranges within the first page. 478 | uint8_t Nes6502::ZPX() { 479 | addr_abs = (read(pc) + x); 480 | pc++; 481 | addr_abs &= 0x00FF; 482 | return 0; 483 | } 484 | 485 | // Address Mode: Zero Page with Y Offset 486 | // Same as above but uses Y Register for offset 487 | uint8_t Nes6502::ZPY() { 488 | addr_abs = (read(pc) + y); 489 | pc++; 490 | addr_abs &= 0x00FF; 491 | return 0; 492 | } 493 | 494 | // Address Mode: Relative 495 | // This address mode is exclusive to branch instructions. The address 496 | // must reside within -128 to +127 of the branch instruction, i.e. 497 | // you cant directly branch to any address in the addressable range. 498 | uint8_t Nes6502::REL() { 499 | addr_rel = read(pc); 500 | pc++; 501 | if (addr_rel & 0x80) addr_rel |= 0xFF00; 502 | return 0; 503 | } 504 | 505 | // Address Mode: Absolute 506 | // A full 16-bit address is loaded and used 507 | uint8_t Nes6502::ABS() { 508 | uint16_t lo = read(pc); 509 | pc++; 510 | uint16_t hi = read(pc); 511 | pc++; 512 | 513 | addr_abs = (hi << 8) | lo; 514 | 515 | return 0; 516 | } 517 | 518 | // Address Mode: Absolute with X Offset 519 | // Fundamentally the same as absolute addressing, but the contents of the X 520 | // Register is added to the supplied two byte address. If the resulting address 521 | // changes the page, an additional clock cycle is required 522 | uint8_t Nes6502::ABX() { 523 | uint16_t lo = read(pc); 524 | pc++; 525 | uint16_t hi = read(pc); 526 | pc++; 527 | 528 | addr_abs = (hi << 8) | lo; 529 | addr_abs += x; 530 | 531 | if ((addr_abs & 0xFF00) != (hi << 8)) 532 | return 1; 533 | else 534 | return 0; 535 | } 536 | 537 | // Address Mode: Absolute with Y Offset 538 | // Fundamentally the same as absolute addressing, but the contents of the Y 539 | // Register is added to the supplied two byte address. If the resulting address 540 | // changes the page, an additional clock cycle is required 541 | uint8_t Nes6502::ABY() { 542 | uint16_t lo = read(pc); 543 | pc++; 544 | uint16_t hi = read(pc); 545 | pc++; 546 | 547 | addr_abs = (hi << 8) | lo; 548 | addr_abs += y; 549 | 550 | if ((addr_abs & 0xFF00) != (hi << 8)) 551 | return 1; 552 | else 553 | return 0; 554 | } 555 | 556 | // Note: The next 3 address modes use indirection (aka Pointers!) 557 | 558 | // Address Mode: Indirect 559 | // The supplied 16-bit address is read to get the actual 16-bit address. This is 560 | // instruction is unusual in that it has a bug in the hardware! To emulate its 561 | // function accurately, we also need to emulate this bug. If the low byte of the 562 | // supplied address is 0xFF, then to read the high byte of the actual address 563 | // we need to cross a page boundary. This doesnt actually work on the chip as 564 | // designed, instead it wraps back around in the same page, yielding an 565 | // invalid actual address 566 | uint8_t Nes6502::IND() { 567 | uint16_t ptr_lo = read(pc); 568 | pc++; 569 | uint16_t ptr_hi = read(pc); 570 | pc++; 571 | 572 | uint16_t ptr = (ptr_hi << 8) | ptr_lo; 573 | 574 | if (ptr_lo == 0x00FF) { // Simulate page boundary hardware bug 575 | addr_abs = (read(ptr & 0xFF00) << 8) | read(ptr + 0); 576 | } else { // Behave normally 577 | addr_abs = (read(ptr + 1) << 8) | read(ptr + 0); 578 | } 579 | 580 | return 0; 581 | } 582 | 583 | // Address Mode: Indirect X 584 | // The supplied 8-bit address is offset by X Register to index 585 | // a location in page 0x00. The actual 16-bit address is read 586 | // from this location 587 | uint8_t Nes6502::IZX() { 588 | uint16_t t = read(pc); 589 | pc++; 590 | 591 | uint16_t lo = read((uint16_t)(t + (uint16_t)x) & 0x00FF); 592 | uint16_t hi = read((uint16_t)(t + (uint16_t)x + 1) & 0x00FF); 593 | 594 | addr_abs = (hi << 8) | lo; 595 | 596 | return 0; 597 | } 598 | 599 | // Address Mode: Indirect Y 600 | // The supplied 8-bit address indexes a location in page 0x00. From 601 | // here the actual 16-bit address is read, and the contents of 602 | // Y Register is added to it to offset it. If the offset causes a 603 | // change in page then an additional clock cycle is required. 604 | uint8_t Nes6502::IZY() { 605 | uint16_t t = read(pc); 606 | pc++; 607 | 608 | uint16_t lo = read(t & 0x00FF); 609 | uint16_t hi = read((t + 1) & 0x00FF); 610 | 611 | addr_abs = (hi << 8) | lo; 612 | addr_abs += y; 613 | 614 | if ((addr_abs & 0xFF00) != (hi << 8)) 615 | return 1; 616 | else 617 | return 0; 618 | } 619 | 620 | // This function sources the data used by the instruction into 621 | // a convenient numeric variable. Some instructions dont have to 622 | // fetch data as the source is implied by the instruction. For example 623 | // "INX" increments the X register. There is no additional data 624 | // required. For all other addressing modes, the data resides at 625 | // the location held within addr_abs, so it is read from there. 626 | // Immediate adress mode exploits this slightly, as that has 627 | // set addr_abs = pc + 1, so it fetches the data from the 628 | // next byte for example "LDA $FF" just loads the accumulator with 629 | // 256, i.e. no far reaching memory fetch is required. "fetched" 630 | // is a variable global to the CPU, and is set by calling this 631 | // function. It also returns it for convenience. 632 | uint8_t Nes6502::fetch() { 633 | if (!(lookup[opcode].addrmode == &Nes6502::IMP)) fetched = read(addr_abs); 634 | return fetched; 635 | } 636 | 637 | /////////////////////////////////////////////////////////////////////////////// 638 | // INSTRUCTION IMPLEMENTATIONS 639 | 640 | // Note: Ive started with the two most complicated instructions to emulate, 641 | // which ironically is addition and subtraction! Ive tried to include a detailed 642 | // explanation as to why they are so complex, yet so fundamental. Im also NOT 643 | // going to do this through the explanation of 1 and 2's complement. 644 | 645 | // Instruction: Add with Carry In 646 | // Function: A = A + M + C 647 | // Flags Out: C, V, N, Z 648 | // 649 | // Explanation: 650 | // The purpose of this function is to add a value to the accumulator and a carry 651 | // bit. If the result is > 255 there is an overflow setting the carry bit. Ths 652 | // allows you to chain together ADC instructions to add numbers larger than 653 | // 8-bits. This in itself is simple, however the 6502 supports the concepts of 654 | // Negativity/Positivity and Signed Overflow. 655 | // 656 | // 10000100 = 128 + 4 = 132 in normal circumstances, we know this as unsigned 657 | // and it allows us to represent numbers between 0 and 255 (given 8 bits). The 658 | // 6502 can also interpret this word as something else if we assume those 8 bits 659 | // represent the range -128 to +127, i.e. it has become signed. 660 | // 661 | // Since 132 > 127, it effectively wraps around, through -128, to -124. This 662 | // wraparound is called overflow, and this is a useful to know as it indicates 663 | // that the calculation has gone outside the permissable range, and therefore no 664 | // longer makes numeric sense. 665 | // 666 | // Note the implementation of ADD is the same in binary, this is just about how 667 | // the numbers are represented, so the word 10000100 can be both -124 and 132 668 | // depending upon the context the programming is using it in. We can prove this! 669 | // 670 | // 10000100 = 132 or -124 671 | // +00010001 = + 17 + 17 672 | // ======== === === See, both are valid additions, but our 673 | // interpretation of 10010101 = 149 or -107 the context changes the 674 | // value, not the hardware! 675 | // 676 | // In principle under the -128 to 127 range: 677 | // 10000000 = -128, 11111111 = -1, 00000000 = 0, 00000000 = +1, 01111111 = +127 678 | // therefore negative numbers have the most significant set, positive numbers do 679 | // not 680 | // 681 | // To assist us, the 6502 can set the overflow flag, if the result of the 682 | // addition has wrapped around. V <- ~(A^M) & A^(A+M+C) :D lol, let's work out 683 | // why! 684 | // 685 | // Let's suppose we have A = 30, M = 10 and C = 0 686 | // A = 30 = 00011110 687 | // M = 10 = 00001010+ 688 | // RESULT = 40 = 00101000 689 | // 690 | // Here we have not gone out of range. The resulting significant bit has not 691 | // changed. So let's make a truth table to understand when overflow has 692 | // occurred. Here I take the MSB of each component, where R is RESULT. 693 | // 694 | // A M R | V | A^R | A^M |~(A^M) | 695 | // 0 0 0 | 0 | 0 | 0 | 1 | 696 | // 0 0 1 | 1 | 1 | 0 | 1 | 697 | // 0 1 0 | 0 | 0 | 1 | 0 | 698 | // 0 1 1 | 0 | 1 | 1 | 0 | so V = ~(A^M) & (A^R) 699 | // 1 0 0 | 0 | 1 | 1 | 0 | 700 | // 1 0 1 | 0 | 0 | 1 | 0 | 701 | // 1 1 0 | 1 | 1 | 0 | 1 | 702 | // 1 1 1 | 0 | 0 | 0 | 1 | 703 | // 704 | // We can see how the above equation calculates V, based on A, M and R. V was 705 | // chosen based on the following hypothesis: 706 | // Positive Number + Positive Number = Negative Result -> Overflow 707 | // Negative Number + Negative Number = Positive Result -> Overflow 708 | // Positive Number + Negative Number = Either Result -> Cannot Overflow 709 | // Positive Number + Positive Number = Positive Result -> OK! No Overflow 710 | // Negative Number + Negative Number = Negative Result -> OK! NO Overflow 711 | 712 | uint8_t Nes6502::ADC() { 713 | // Grab the data that we are adding to the accumulator 714 | fetch(); 715 | 716 | // Add is performed in 16-bit domain for emulation to capture any 717 | // carry bit, which will exist in bit 8 of the 16-bit word 718 | temp = (uint16_t)a + (uint16_t)fetched + (uint16_t)GetFlag(C); 719 | 720 | // The carry flag out exists in the high byte bit 0 721 | SetFlag(C, temp > 255); 722 | 723 | // The Zero flag is set if the result is 0 724 | SetFlag(Z, (temp & 0x00FF) == 0); 725 | 726 | // The signed Overflow flag is set based on all that up there! :D 727 | SetFlag( 728 | V, (~((uint16_t)a ^ (uint16_t)fetched) & ((uint16_t)a ^ (uint16_t)temp)) & 729 | 0x0080); 730 | 731 | // The negative flag is set to the most significant bit of the result 732 | SetFlag(N, temp & 0x80); 733 | 734 | // Load the result into the accumulator (it's 8-bit dont forget!) 735 | a = temp & 0x00FF; 736 | 737 | // This instruction has the potential to require an additional clock cycle 738 | return 1; 739 | } 740 | 741 | // Instruction: Subtraction with Borrow In 742 | // Function: A = A - M - (1 - C) 743 | // Flags Out: C, V, N, Z 744 | // 745 | // Explanation: 746 | // Given the explanation for ADC above, we can reorganise our data 747 | // to use the same computation for addition, for subtraction by multiplying 748 | // the data by -1, i.e. make it negative 749 | // 750 | // A = A - M - (1 - C) -> A = A + -1 * (M - (1 - C)) -> A = A + (-M + 1 + C) 751 | // 752 | // To make a signed positive number negative, we can invert the bits and add 1 753 | // (OK, I lied, a little bit of 1 and 2s complement :P) 754 | // 755 | // 5 = 00000101 756 | // -5 = 11111010 + 00000001 = 11111011 (or 251 in our 0 to 255 range) 757 | // 758 | // The range is actually unimportant, because if I take the value 15, and add 759 | // 251 to it, given we wrap around at 256, the result is 10, so it has 760 | // effectively subtracted 5, which was the original intention. (15 + 251) % 256 761 | // = 10 762 | // 763 | // Note that the equation above used (1-C), but this got converted to + 1 + C. 764 | // This means we already have the +1, so all we need to do is invert the bits 765 | // of M, the data(!) therfore we can simply add, exactly the same way we did 766 | // before. 767 | 768 | uint8_t Nes6502::SBC() { 769 | fetch(); 770 | 771 | // Operating in 16-bit domain to capture carry out 772 | 773 | // We can invert the bottom 8 bits with bitwise xor 774 | uint16_t value = ((uint16_t)fetched) ^ 0x00FF; 775 | 776 | // Notice this is exactly the same as addition from here! 777 | temp = (uint16_t)a + value + (uint16_t)GetFlag(C); 778 | SetFlag(C, temp & 0xFF00); 779 | SetFlag(Z, ((temp & 0x00FF) == 0)); 780 | SetFlag(V, (temp ^ (uint16_t)a) & (temp ^ value) & 0x0080); 781 | SetFlag(N, temp & 0x0080); 782 | a = temp & 0x00FF; 783 | return 1; 784 | } 785 | 786 | // OK! Complicated operations are done! the following are much simpler 787 | // and conventional. The typical order of events is: 788 | // 1) Fetch the data you are working with 789 | // 2) Perform calculation 790 | // 3) Store the result in desired place 791 | // 4) Set Flags of the status register 792 | // 5) Return if instruction has potential to require additional 793 | // clock cycle 794 | 795 | // Instruction: Bitwise Logic AND 796 | // Function: A = A & M 797 | // Flags Out: N, Z 798 | uint8_t Nes6502::AND() { 799 | fetch(); 800 | a = a & fetched; 801 | SetFlag(Z, a == 0x00); 802 | SetFlag(N, a & 0x80); 803 | return 1; 804 | } 805 | 806 | // Instruction: Arithmetic Shift Left 807 | // Function: A = C <- (A << 1) <- 0 808 | // Flags Out: N, Z, C 809 | uint8_t Nes6502::ASL() { 810 | fetch(); 811 | temp = (uint16_t)fetched << 1; 812 | SetFlag(C, (temp & 0xFF00) > 0); 813 | SetFlag(Z, (temp & 0x00FF) == 0x00); 814 | SetFlag(N, temp & 0x80); 815 | if (lookup[opcode].addrmode == &Nes6502::IMP) 816 | a = temp & 0x00FF; 817 | else 818 | write(addr_abs, temp & 0x00FF); 819 | return 0; 820 | } 821 | 822 | // Instruction: Branch if Carry Clear 823 | // Function: if(C == 0) pc = address 824 | uint8_t Nes6502::BCC() { 825 | if (GetFlag(C) == 0) { 826 | cycles++; 827 | addr_abs = pc + addr_rel; 828 | 829 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++; 830 | 831 | pc = addr_abs; 832 | } 833 | return 0; 834 | } 835 | 836 | // Instruction: Branch if Carry Set 837 | // Function: if(C == 1) pc = address 838 | uint8_t Nes6502::BCS() { 839 | if (GetFlag(C) == 1) { 840 | cycles++; 841 | addr_abs = pc + addr_rel; 842 | 843 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++; 844 | 845 | pc = addr_abs; 846 | } 847 | return 0; 848 | } 849 | 850 | // Instruction: Branch if Equal 851 | // Function: if(Z == 1) pc = address 852 | uint8_t Nes6502::BEQ() { 853 | if (GetFlag(Z) == 1) { 854 | cycles++; 855 | addr_abs = pc + addr_rel; 856 | 857 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++; 858 | 859 | pc = addr_abs; 860 | } 861 | return 0; 862 | } 863 | 864 | uint8_t Nes6502::BIT() { 865 | fetch(); 866 | temp = a & fetched; 867 | SetFlag(Z, (temp & 0x00FF) == 0x00); 868 | SetFlag(N, fetched & (1 << 7)); 869 | SetFlag(V, fetched & (1 << 6)); 870 | return 0; 871 | } 872 | 873 | // Instruction: Branch if Negative 874 | // Function: if(N == 1) pc = address 875 | uint8_t Nes6502::BMI() { 876 | if (GetFlag(N) == 1) { 877 | cycles++; 878 | addr_abs = pc + addr_rel; 879 | 880 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++; 881 | 882 | pc = addr_abs; 883 | } 884 | return 0; 885 | } 886 | 887 | // Instruction: Branch if Not Equal 888 | // Function: if(Z == 0) pc = address 889 | uint8_t Nes6502::BNE() { 890 | if (GetFlag(Z) == 0) { 891 | cycles++; 892 | addr_abs = pc + addr_rel; 893 | 894 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++; 895 | 896 | pc = addr_abs; 897 | } 898 | return 0; 899 | } 900 | 901 | // Instruction: Branch if Positive 902 | // Function: if(N == 0) pc = address 903 | uint8_t Nes6502::BPL() { 904 | if (GetFlag(N) == 0) { 905 | cycles++; 906 | addr_abs = pc + addr_rel; 907 | 908 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++; 909 | 910 | pc = addr_abs; 911 | } 912 | return 0; 913 | } 914 | 915 | // Instruction: Break 916 | // Function: Program Sourced Interrupt 917 | uint8_t Nes6502::BRK() { 918 | pc++; 919 | 920 | SetFlag(I, 1); 921 | write(0x0100 + stkp, (pc >> 8) & 0x00FF); 922 | stkp--; 923 | write(0x0100 + stkp, pc & 0x00FF); 924 | stkp--; 925 | 926 | SetFlag(B, 1); 927 | write(0x0100 + stkp, status); 928 | stkp--; 929 | SetFlag(B, 0); 930 | 931 | pc = (uint16_t)read(0xFFFE) | ((uint16_t)read(0xFFFF) << 8); 932 | return 0; 933 | } 934 | 935 | // Instruction: Branch if Overflow Clear 936 | // Function: if(V == 0) pc = address 937 | uint8_t Nes6502::BVC() { 938 | if (GetFlag(V) == 0) { 939 | cycles++; 940 | addr_abs = pc + addr_rel; 941 | 942 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++; 943 | 944 | pc = addr_abs; 945 | } 946 | return 0; 947 | } 948 | 949 | // Instruction: Branch if Overflow Set 950 | // Function: if(V == 1) pc = address 951 | uint8_t Nes6502::BVS() { 952 | if (GetFlag(V) == 1) { 953 | cycles++; 954 | addr_abs = pc + addr_rel; 955 | 956 | if ((addr_abs & 0xFF00) != (pc & 0xFF00)) cycles++; 957 | 958 | pc = addr_abs; 959 | } 960 | return 0; 961 | } 962 | 963 | // Instruction: Clear Carry Flag 964 | // Function: C = 0 965 | uint8_t Nes6502::CLC() { 966 | SetFlag(C, false); 967 | return 0; 968 | } 969 | 970 | // Instruction: Clear Decimal Flag 971 | // Function: D = 0 972 | uint8_t Nes6502::CLD() { 973 | SetFlag(D, false); 974 | return 0; 975 | } 976 | 977 | // Instruction: Disable Interrupts / Clear Interrupt Flag 978 | // Function: I = 0 979 | uint8_t Nes6502::CLI() { 980 | SetFlag(I, false); 981 | return 0; 982 | } 983 | 984 | // Instruction: Clear Overflow Flag 985 | // Function: V = 0 986 | uint8_t Nes6502::CLV() { 987 | SetFlag(V, false); 988 | return 0; 989 | } 990 | 991 | // Instruction: Compare Accumulator 992 | // Function: C <- A >= M Z <- (A - M) == 0 993 | // Flags Out: N, C, Z 994 | uint8_t Nes6502::CMP() { 995 | fetch(); 996 | temp = (uint16_t)a - (uint16_t)fetched; 997 | SetFlag(C, a >= fetched); 998 | SetFlag(Z, (temp & 0x00FF) == 0x0000); 999 | SetFlag(N, temp & 0x0080); 1000 | return 1; 1001 | } 1002 | 1003 | // Instruction: Compare X Register 1004 | // Function: C <- X >= M Z <- (X - M) == 0 1005 | // Flags Out: N, C, Z 1006 | uint8_t Nes6502::CPX() { 1007 | fetch(); 1008 | temp = (uint16_t)x - (uint16_t)fetched; 1009 | SetFlag(C, x >= fetched); 1010 | SetFlag(Z, (temp & 0x00FF) == 0x0000); 1011 | SetFlag(N, temp & 0x0080); 1012 | return 0; 1013 | } 1014 | 1015 | // Instruction: Compare Y Register 1016 | // Function: C <- Y >= M Z <- (Y - M) == 0 1017 | // Flags Out: N, C, Z 1018 | uint8_t Nes6502::CPY() { 1019 | fetch(); 1020 | temp = (uint16_t)y - (uint16_t)fetched; 1021 | SetFlag(C, y >= fetched); 1022 | SetFlag(Z, (temp & 0x00FF) == 0x0000); 1023 | SetFlag(N, temp & 0x0080); 1024 | return 0; 1025 | } 1026 | 1027 | // Instruction: Decrement Value at Memory Location 1028 | // Function: M = M - 1 1029 | // Flags Out: N, Z 1030 | uint8_t Nes6502::DEC() { 1031 | fetch(); 1032 | temp = fetched - 1; 1033 | write(addr_abs, temp & 0x00FF); 1034 | SetFlag(Z, (temp & 0x00FF) == 0x0000); 1035 | SetFlag(N, temp & 0x0080); 1036 | return 0; 1037 | } 1038 | 1039 | // Instruction: Decrement X Register 1040 | // Function: X = X - 1 1041 | // Flags Out: N, Z 1042 | uint8_t Nes6502::DEX() { 1043 | x--; 1044 | SetFlag(Z, x == 0x00); 1045 | SetFlag(N, x & 0x80); 1046 | return 0; 1047 | } 1048 | 1049 | // Instruction: Decrement Y Register 1050 | // Function: Y = Y - 1 1051 | // Flags Out: N, Z 1052 | uint8_t Nes6502::DEY() { 1053 | y--; 1054 | SetFlag(Z, y == 0x00); 1055 | SetFlag(N, y & 0x80); 1056 | return 0; 1057 | } 1058 | 1059 | // Instruction: Bitwise Logic XOR 1060 | // Function: A = A xor M 1061 | // Flags Out: N, Z 1062 | uint8_t Nes6502::EOR() { 1063 | fetch(); 1064 | a = a ^ fetched; 1065 | SetFlag(Z, a == 0x00); 1066 | SetFlag(N, a & 0x80); 1067 | return 1; 1068 | } 1069 | 1070 | // Instruction: Increment Value at Memory Location 1071 | // Function: M = M + 1 1072 | // Flags Out: N, Z 1073 | uint8_t Nes6502::INC() { 1074 | fetch(); 1075 | temp = fetched + 1; 1076 | write(addr_abs, temp & 0x00FF); 1077 | SetFlag(Z, (temp & 0x00FF) == 0x0000); 1078 | SetFlag(N, temp & 0x0080); 1079 | return 0; 1080 | } 1081 | 1082 | // Instruction: Increment X Register 1083 | // Function: X = X + 1 1084 | // Flags Out: N, Z 1085 | uint8_t Nes6502::INX() { 1086 | x++; 1087 | SetFlag(Z, x == 0x00); 1088 | SetFlag(N, x & 0x80); 1089 | return 0; 1090 | } 1091 | 1092 | // Instruction: Increment Y Register 1093 | // Function: Y = Y + 1 1094 | // Flags Out: N, Z 1095 | uint8_t Nes6502::INY() { 1096 | y++; 1097 | SetFlag(Z, y == 0x00); 1098 | SetFlag(N, y & 0x80); 1099 | return 0; 1100 | } 1101 | 1102 | // Instruction: Jump To Location 1103 | // Function: pc = address 1104 | uint8_t Nes6502::JMP() { 1105 | pc = addr_abs; 1106 | return 0; 1107 | } 1108 | 1109 | // Instruction: Jump To Sub-Routine 1110 | // Function: Push current pc to stack, pc = address 1111 | uint8_t Nes6502::JSR() { 1112 | pc--; 1113 | 1114 | write(0x0100 + stkp, (pc >> 8) & 0x00FF); 1115 | stkp--; 1116 | write(0x0100 + stkp, pc & 0x00FF); 1117 | stkp--; 1118 | 1119 | pc = addr_abs; 1120 | return 0; 1121 | } 1122 | 1123 | // Instruction: Load The Accumulator 1124 | // Function: A = M 1125 | // Flags Out: N, Z 1126 | uint8_t Nes6502::LDA() { 1127 | fetch(); 1128 | a = fetched; 1129 | SetFlag(Z, a == 0x00); 1130 | SetFlag(N, a & 0x80); 1131 | return 1; 1132 | } 1133 | 1134 | // Instruction: Load The X Register 1135 | // Function: X = M 1136 | // Flags Out: N, Z 1137 | uint8_t Nes6502::LDX() { 1138 | fetch(); 1139 | x = fetched; 1140 | SetFlag(Z, x == 0x00); 1141 | SetFlag(N, x & 0x80); 1142 | return 1; 1143 | } 1144 | 1145 | // Instruction: Load The Y Register 1146 | // Function: Y = M 1147 | // Flags Out: N, Z 1148 | uint8_t Nes6502::LDY() { 1149 | fetch(); 1150 | y = fetched; 1151 | SetFlag(Z, y == 0x00); 1152 | SetFlag(N, y & 0x80); 1153 | return 1; 1154 | } 1155 | 1156 | uint8_t Nes6502::LSR() { 1157 | fetch(); 1158 | SetFlag(C, fetched & 0x0001); 1159 | temp = fetched >> 1; 1160 | SetFlag(Z, (temp & 0x00FF) == 0x0000); 1161 | SetFlag(N, temp & 0x0080); 1162 | if (lookup[opcode].addrmode == &Nes6502::IMP) 1163 | a = temp & 0x00FF; 1164 | else 1165 | write(addr_abs, temp & 0x00FF); 1166 | return 0; 1167 | } 1168 | 1169 | uint8_t Nes6502::NOP() { 1170 | switch (opcode) { 1171 | case 0x1C: 1172 | case 0x3C: 1173 | case 0x5C: 1174 | case 0x7C: 1175 | case 0xDC: 1176 | case 0xFC: 1177 | return 1; 1178 | break; 1179 | } 1180 | return 0; 1181 | } 1182 | 1183 | // Instruction: Bitwise Logic OR 1184 | // Function: A = A | M 1185 | // Flags Out: N, Z 1186 | uint8_t Nes6502::ORA() { 1187 | fetch(); 1188 | a = a | fetched; 1189 | SetFlag(Z, a == 0x00); 1190 | SetFlag(N, a & 0x80); 1191 | return 1; 1192 | } 1193 | 1194 | // Instruction: Push Accumulator to Stack 1195 | // Function: A -> stack 1196 | uint8_t Nes6502::PHA() { 1197 | write(0x0100 + stkp, a); 1198 | stkp--; 1199 | return 0; 1200 | } 1201 | 1202 | // Instruction: Push Status Register to Stack 1203 | // Function: status -> stack 1204 | // Note: Break flag is set to 1 before push 1205 | uint8_t Nes6502::PHP() { 1206 | write(0x0100 + stkp, status | B | U); 1207 | SetFlag(B, 0); 1208 | SetFlag(U, 0); 1209 | stkp--; 1210 | return 0; 1211 | } 1212 | 1213 | // Instruction: Pop Accumulator off Stack 1214 | // Function: A <- stack 1215 | // Flags Out: N, Z 1216 | uint8_t Nes6502::PLA() { 1217 | stkp++; 1218 | a = read(0x0100 + stkp); 1219 | SetFlag(Z, a == 0x00); 1220 | SetFlag(N, a & 0x80); 1221 | return 0; 1222 | } 1223 | 1224 | // Instruction: Pop Status Register off Stack 1225 | // Function: Status <- stack 1226 | uint8_t Nes6502::PLP() { 1227 | stkp++; 1228 | status = read(0x0100 + stkp); 1229 | SetFlag(U, 1); 1230 | return 0; 1231 | } 1232 | 1233 | uint8_t Nes6502::ROL() { 1234 | fetch(); 1235 | temp = (uint16_t)(fetched << 1) | GetFlag(C); 1236 | SetFlag(C, temp & 0xFF00); 1237 | SetFlag(Z, (temp & 0x00FF) == 0x0000); 1238 | SetFlag(N, temp & 0x0080); 1239 | if (lookup[opcode].addrmode == &Nes6502::IMP) 1240 | a = temp & 0x00FF; 1241 | else 1242 | write(addr_abs, temp & 0x00FF); 1243 | return 0; 1244 | } 1245 | 1246 | uint8_t Nes6502::ROR() { 1247 | fetch(); 1248 | temp = (uint16_t)(GetFlag(C) << 7) | (fetched >> 1); 1249 | SetFlag(C, fetched & 0x01); 1250 | SetFlag(Z, (temp & 0x00FF) == 0x00); 1251 | SetFlag(N, temp & 0x0080); 1252 | if (lookup[opcode].addrmode == &Nes6502::IMP) 1253 | a = temp & 0x00FF; 1254 | else 1255 | write(addr_abs, temp & 0x00FF); 1256 | return 0; 1257 | } 1258 | 1259 | uint8_t Nes6502::RTI() { 1260 | stkp++; 1261 | status = read(0x0100 + stkp); 1262 | status &= ~B; 1263 | status &= ~U; 1264 | 1265 | stkp++; 1266 | pc = (uint16_t)read(0x0100 + stkp); 1267 | stkp++; 1268 | pc |= (uint16_t)read(0x0100 + stkp) << 8; 1269 | return 0; 1270 | } 1271 | 1272 | uint8_t Nes6502::RTS() { 1273 | stkp++; 1274 | pc = (uint16_t)read(0x0100 + stkp); 1275 | stkp++; 1276 | pc |= (uint16_t)read(0x0100 + stkp) << 8; 1277 | 1278 | pc++; 1279 | return 0; 1280 | } 1281 | 1282 | // Instruction: Set Carry Flag 1283 | // Function: C = 1 1284 | uint8_t Nes6502::SEC() { 1285 | SetFlag(C, true); 1286 | return 0; 1287 | } 1288 | 1289 | // Instruction: Set Decimal Flag 1290 | // Function: D = 1 1291 | uint8_t Nes6502::SED() { 1292 | SetFlag(D, true); 1293 | return 0; 1294 | } 1295 | 1296 | // Instruction: Set Interrupt Flag / Enable Interrupts 1297 | // Function: I = 1 1298 | uint8_t Nes6502::SEI() { 1299 | SetFlag(I, true); 1300 | return 0; 1301 | } 1302 | 1303 | // Instruction: Store Accumulator at Address 1304 | // Function: M = A 1305 | uint8_t Nes6502::STA() { 1306 | write(addr_abs, a); 1307 | return 0; 1308 | } 1309 | 1310 | // Instruction: Store X Register at Address 1311 | // Function: M = X 1312 | uint8_t Nes6502::STX() { 1313 | write(addr_abs, x); 1314 | return 0; 1315 | } 1316 | 1317 | // Instruction: Store Y Register at Address 1318 | // Function: M = Y 1319 | uint8_t Nes6502::STY() { 1320 | write(addr_abs, y); 1321 | return 0; 1322 | } 1323 | 1324 | // Instruction: Transfer Accumulator to X Register 1325 | // Function: X = A 1326 | // Flags Out: N, Z 1327 | uint8_t Nes6502::TAX() { 1328 | x = a; 1329 | SetFlag(Z, x == 0x00); 1330 | SetFlag(N, x & 0x80); 1331 | return 0; 1332 | } 1333 | 1334 | // Instruction: Transfer Accumulator to Y Register 1335 | // Function: Y = A 1336 | // Flags Out: N, Z 1337 | uint8_t Nes6502::TAY() { 1338 | y = a; 1339 | SetFlag(Z, y == 0x00); 1340 | SetFlag(N, y & 0x80); 1341 | return 0; 1342 | } 1343 | 1344 | // Instruction: Transfer Stack Pointer to X Register 1345 | // Function: X = stack pointer 1346 | // Flags Out: N, Z 1347 | uint8_t Nes6502::TSX() { 1348 | x = stkp; 1349 | SetFlag(Z, x == 0x00); 1350 | SetFlag(N, x & 0x80); 1351 | return 0; 1352 | } 1353 | 1354 | // Instruction: Transfer X Register to Accumulator 1355 | // Function: A = X 1356 | // Flags Out: N, Z 1357 | uint8_t Nes6502::TXA() { 1358 | a = x; 1359 | SetFlag(Z, a == 0x00); 1360 | SetFlag(N, a & 0x80); 1361 | return 0; 1362 | } 1363 | 1364 | // Instruction: Transfer X Register to Stack Pointer 1365 | // Function: stack pointer = X 1366 | uint8_t Nes6502::TXS() { 1367 | stkp = x; 1368 | return 0; 1369 | } 1370 | 1371 | // Instruction: Transfer Y Register to Accumulator 1372 | // Function: A = Y 1373 | // Flags Out: N, Z 1374 | uint8_t Nes6502::TYA() { 1375 | a = y; 1376 | SetFlag(Z, a == 0x00); 1377 | SetFlag(N, a & 0x80); 1378 | return 0; 1379 | } 1380 | 1381 | // This function captures illegal opcodes 1382 | uint8_t Nes6502::XXX() { return 0; } 1383 | 1384 | /////////////////////////////////////////////////////////////////////////////// 1385 | // HELPER FUNCTIONS 1386 | 1387 | bool Nes6502::complete() { return cycles == 0; } 1388 | 1389 | // This is the disassembly function. Its workings are not required for 1390 | // emulation. It is merely a convenience function to turn the binary instruction 1391 | // code into human readable form. Its included as part of the emulator because 1392 | // it can take advantage of many of the CPUs internal operations to do this. 1393 | std::map Nes6502::disassemble(uint16_t nStart, 1394 | uint16_t nStop) { 1395 | uint32_t addr = nStart; 1396 | uint8_t value = 0x00, lo = 0x00, hi = 0x00; 1397 | std::map mapLines; 1398 | uint16_t line_addr = 0; 1399 | 1400 | // A convenient utility to convert variables into 1401 | // hex strings because "modern C++"'s method with 1402 | // streams is atrocious 1403 | auto hex = [](uint32_t n, uint8_t d) { 1404 | std::string s(d, '0'); 1405 | for (int i = d - 1; i >= 0; i--, n >>= 4) 1406 | s[i] = "0123456789ABCDEF"[n & 0xF]; 1407 | return s; 1408 | }; 1409 | 1410 | // Starting at the specified address we read an instruction 1411 | // byte, which in turn yields information from the lookup table 1412 | // as to how many additional bytes we need to read and what the 1413 | // addressing mode is. I need this info to assemble human readable 1414 | // syntax, which is different depending upon the addressing mode 1415 | 1416 | // As the instruction is decoded, a std::string is assembled 1417 | // with the readable output 1418 | while (addr <= (uint32_t)nStop) { 1419 | line_addr = addr; 1420 | 1421 | // Prefix line with instruction address 1422 | std::string sInst = "$" + hex(addr, 4) + ": "; 1423 | 1424 | // Read instruction, and get its readable name 1425 | uint8_t opcode = bus->cpuRead(addr, true); 1426 | addr++; 1427 | sInst += lookup[opcode].name + " "; 1428 | 1429 | // Get oprands from desired locations, and form the 1430 | // instruction based upon its addressing mode. These 1431 | // routines mimmick the actual fetch routine of the 1432 | // 6502 in order to get accurate data as part of the 1433 | // instruction 1434 | if (lookup[opcode].addrmode == &Nes6502::IMP) { 1435 | sInst += " {IMP}"; 1436 | } else if (lookup[opcode].addrmode == &Nes6502::IMM) { 1437 | value = bus->cpuRead(addr, true); 1438 | addr++; 1439 | sInst += "#$" + hex(value, 2) + " {IMM}"; 1440 | } else if (lookup[opcode].addrmode == &Nes6502::ZP0) { 1441 | lo = bus->cpuRead(addr, true); 1442 | addr++; 1443 | hi = 0x00; 1444 | sInst += "$" + hex(lo, 2) + " {ZP0}"; 1445 | } else if (lookup[opcode].addrmode == &Nes6502::ZPX) { 1446 | lo = bus->cpuRead(addr, true); 1447 | addr++; 1448 | hi = 0x00; 1449 | sInst += "$" + hex(lo, 2) + ", X {ZPX}"; 1450 | } else if (lookup[opcode].addrmode == &Nes6502::ZPY) { 1451 | lo = bus->cpuRead(addr, true); 1452 | addr++; 1453 | hi = 0x00; 1454 | sInst += "$" + hex(lo, 2) + ", Y {ZPY}"; 1455 | } else if (lookup[opcode].addrmode == &Nes6502::IZX) { 1456 | lo = bus->cpuRead(addr, true); 1457 | addr++; 1458 | hi = 0x00; 1459 | sInst += "($" + hex(lo, 2) + ", X) {IZX}"; 1460 | } else if (lookup[opcode].addrmode == &Nes6502::IZY) { 1461 | lo = bus->cpuRead(addr, true); 1462 | addr++; 1463 | hi = 0x00; 1464 | sInst += "($" + hex(lo, 2) + "), Y {IZY}"; 1465 | } else if (lookup[opcode].addrmode == &Nes6502::ABS) { 1466 | lo = bus->cpuRead(addr, true); 1467 | addr++; 1468 | hi = bus->cpuRead(addr, true); 1469 | addr++; 1470 | sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + " {ABS}"; 1471 | } else if (lookup[opcode].addrmode == &Nes6502::ABX) { 1472 | lo = bus->cpuRead(addr, true); 1473 | addr++; 1474 | hi = bus->cpuRead(addr, true); 1475 | addr++; 1476 | sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + ", X {ABX}"; 1477 | } else if (lookup[opcode].addrmode == &Nes6502::ABY) { 1478 | lo = bus->cpuRead(addr, true); 1479 | addr++; 1480 | hi = bus->cpuRead(addr, true); 1481 | addr++; 1482 | sInst += "$" + hex((uint16_t)(hi << 8) | lo, 4) + ", Y {ABY}"; 1483 | } else if (lookup[opcode].addrmode == &Nes6502::IND) { 1484 | lo = bus->cpuRead(addr, true); 1485 | addr++; 1486 | hi = bus->cpuRead(addr, true); 1487 | addr++; 1488 | sInst += "($" + hex((uint16_t)(hi << 8) | lo, 4) + ") {IND}"; 1489 | } else if (lookup[opcode].addrmode == &Nes6502::REL) { 1490 | value = bus->cpuRead(addr, true); 1491 | addr++; 1492 | sInst += "$" + hex(value, 2) + " [$" + hex(addr + (int8_t)value, 4) + 1493 | "] {REL}"; 1494 | } 1495 | 1496 | // Add the formed string to a std::map, using the instruction's 1497 | // address as the key. This makes it convenient to look for later 1498 | // as the instructions are variable in length, so a straight up 1499 | // incremental index is not sufficient. 1500 | mapLines[line_addr] = sInst; 1501 | } 1502 | 1503 | return mapLines; 1504 | } 1505 | 1506 | // End of File - Jx9 1507 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/Nes6502.h: -------------------------------------------------------------------------------- 1 | /* 2 | Nes6502 - An emulation of the 6502/2A03 processor 3 | "Thanks Dad for believing computers were gonna be a big deal..." - 4 | javidx9 5 | 6 | License (OLC-3) 7 | ~~~~~~~~~~~~~~~ 8 | 9 | Copyright 2018-2019 OneLoneCoder.com 10 | 11 | Redistribution and use in source and binary forms, with or without 12 | modification, are permitted provided that the following conditions 13 | are met: 14 | 15 | 1. Redistributions or derivations of source code must retain the above 16 | copyright notice, this list of conditions and the following disclaimer. 17 | 18 | 2. Redistributions or derivative works in binary form must reproduce 19 | the above copyright notice. This list of conditions and the following 20 | disclaimer must be reproduced in the documentation and/or other 21 | materials provided with the distribution. 22 | 23 | 3. Neither the name of the copyright holder nor the names of its 24 | contributors may be used to endorse or promote products derived 25 | from this software without specific prior written permission. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 31 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 | 39 | Background 40 | ~~~~~~~~~~ 41 | I love this microprocessor. It was at the heart of two of my favourite 42 | machines, the BBC Micro, and the Nintendo Entertainment System, as well 43 | as countless others in that era. I learnt to program on the Model B, and 44 | I learnt to love games on the NES, so in many ways, this processor is 45 | why I am the way I am today. 46 | 47 | In February 2019, I decided to undertake a selfish personal project and 48 | build a NES emulator. Ive always wanted to, and as such I've avoided 49 | looking at source code for such things. This made making this a real 50 | personal challenge. I know its been done countless times, and very 51 | likely in far more clever and accurate ways than mine, but I'm proud of this. 52 | 53 | Datasheet: http://archive.6502.org/datasheets/rockwell_r650x_r651x.pdf 54 | 55 | Files: Nes6502.h, Nes6502.cpp 56 | 57 | Relevant Video: https://youtu.be/8XmxKPJDGU0 58 | 59 | Links 60 | ~~~~~ 61 | YouTube: https://www.youtube.com/javidx9 62 | https://www.youtube.com/javidx9extra 63 | Discord: https://discord.gg/WhwHUMV 64 | Twitter: https://www.twitter.com/javidx9 65 | Twitch: https://www.twitch.tv/javidx9 66 | GitHub: https://www.github.com/onelonecoder 67 | Patreon: https://www.patreon.com/javidx9 68 | Homepage: https://www.onelonecoder.com 69 | 70 | Author 71 | ~~~~~~ 72 | David Barr, aka javidx9, �OneLoneCoder 2019 73 | */ 74 | 75 | #pragma once 76 | 77 | // With little modification, reliance upon the stdlib can 78 | // be removed entirely if required. 79 | 80 | // Ths is required for translation table and disassembler. The table 81 | // could be implemented straight up as an array, but I used a vector. 82 | #include 83 | 84 | // These are required for disassembler. If you dont require disassembly 85 | // then just remove the function. 86 | #include 87 | #include 88 | 89 | // Emulation Behaviour Logging ====================================== 90 | // Uncomment this to create a logfile entry for each clock tick of 91 | // the CPU. Beware: this slows down emulation considerably and 92 | // generates extremely large files. I recommend "glogg" to view the 93 | // data as it is designed to handle enormous files. 94 | // 95 | // #define LOGMODE // <- Uncomment me to enable logging! 96 | 97 | #ifdef LOGMODE 98 | #include 99 | #endif 100 | 101 | // Forward declaration of generic communications bus class to 102 | // prevent circular inclusions 103 | class Bus; 104 | 105 | // The 6502 Emulation Class. This is it! 106 | class Nes6502 { 107 | public: 108 | Nes6502(); 109 | ~Nes6502(); 110 | 111 | public: 112 | // CPU Core registers, exposed as public here for ease of access from external 113 | // examinors. This is all the 6502 has. 114 | uint8_t a = 0x00; // Accumulator Register 115 | uint8_t x = 0x00; // X Register 116 | uint8_t y = 0x00; // Y Register 117 | uint8_t stkp = 0x00; // Stack Pointer (points to location on bus) 118 | uint16_t pc = 0x0000; // Program Counter 119 | uint8_t status = 0x00; // Status Register 120 | 121 | // External event functions. In hardware these represent pins that are 122 | // asserted to produce a change in state. 123 | void reset(); // Reset Interrupt - Forces CPU into known state 124 | void 125 | irq(); // Interrupt Request - Executes an instruction at a specific location 126 | void 127 | nmi(); // Non-Maskable Interrupt Request - As above, but cannot be disabled 128 | void clock(); // Perform one clock cycle's worth of update 129 | 130 | // Indicates the current instruction has completed by returning true. This is 131 | // a utility function to enable "step-by-step" execution, without manually 132 | // clocking every cycle 133 | bool complete(); 134 | 135 | // Link this CPU to a communications bus 136 | void ConnectBus(Bus *n) { bus = n; } 137 | 138 | // Produces a map of strings, with keys equivalent to instruction start 139 | // locations in memory, for the specified address range 140 | std::map disassemble(uint16_t nStart, uint16_t nStop); 141 | 142 | // The status register stores 8 flags. Ive enumerated these here for ease 143 | // of access. You can access the status register directly since its public. 144 | // The bits have different interpretations depending upon the context and 145 | // instruction being executed. 146 | enum FLAGS6502 { 147 | C = (1 << 0), // Carry Bit 148 | Z = (1 << 1), // Zero 149 | I = (1 << 2), // Disable Interrupts 150 | D = (1 << 3), // Decimal Mode (unused in this implementation) 151 | B = (1 << 4), // Break 152 | U = (1 << 5), // Unused 153 | V = (1 << 6), // Overflow 154 | N = (1 << 7), // Negative 155 | }; 156 | 157 | private: 158 | // Convenience functions to access status register 159 | uint8_t GetFlag(FLAGS6502 f); 160 | void SetFlag(FLAGS6502 f, bool v); 161 | 162 | // Assisstive variables to facilitate emulation 163 | uint8_t fetched = 0x00; // Represents the working input value to the ALU 164 | uint16_t temp = 0x0000; // A convenience variable used everywhere 165 | uint16_t addr_abs = 0x0000; // All used memory addresses end up in here 166 | uint16_t addr_rel = 0x00; // Represents absolute address following a branch 167 | uint8_t opcode = 0x00; // Is the instruction byte 168 | uint8_t cycles = 0; // Counts how many cycles the instruction has remaining 169 | uint32_t clock_count = 0; // A global accumulation of the number of clocks 170 | 171 | // Linkage to the communications bus 172 | Bus *bus = nullptr; 173 | uint8_t read(uint16_t a); 174 | void write(uint16_t a, uint8_t d); 175 | 176 | // The read location of data can come from two sources, a memory address, or 177 | // its immediately available as part of the instruction. This function decides 178 | // depending on address mode of instruction byte 179 | uint8_t fetch(); 180 | 181 | // This structure and the following vector are used to compile and store 182 | // the opcode translation table. The 6502 can effectively have 256 183 | // different instructions. Each of these are stored in a table in numerical 184 | // order so they can be looked up easily, with no decoding required. 185 | // Each table entry holds: 186 | // Pneumonic : A textual representation of the instruction (used for 187 | // disassembly) Opcode Function: A function pointer to the implementation of 188 | // the opcode Opcode Address Mode : A function pointer to the 189 | // implementation of the 190 | // addressing mechanism used by the 191 | // instruction Cycle Count : An integer that represents the base number of 192 | // clock cycles the CPU requires to perform the instruction 193 | 194 | struct INSTRUCTION { 195 | std::string name; 196 | uint8_t (Nes6502::*operate)(void) = nullptr; 197 | uint8_t (Nes6502::*addrmode)(void) = nullptr; 198 | uint8_t cycles = 0; 199 | }; 200 | 201 | std::vector lookup; 202 | 203 | private: 204 | // Addressing Modes ============================================= 205 | // The 6502 has a variety of addressing modes to access data in 206 | // memory, some of which are direct and some are indirect (like 207 | // pointers in C++). Each opcode contains information about which 208 | // addressing mode should be employed to facilitate the 209 | // instruction, in regards to where it reads/writes the data it 210 | // uses. The address mode changes the number of bytes that 211 | // makes up the full instruction, so we implement addressing 212 | // before executing the instruction, to make sure the program 213 | // counter is at the correct location, the instruction is 214 | // primed with the addresses it needs, and the number of clock 215 | // cycles the instruction requires is calculated. These functions 216 | // may adjust the number of cycles required depending upon where 217 | // and how the memory is accessed, so they return the required 218 | // adjustment. 219 | 220 | uint8_t IMP(); 221 | uint8_t IMM(); 222 | uint8_t ZP0(); 223 | uint8_t ZPX(); 224 | uint8_t ZPY(); 225 | uint8_t REL(); 226 | uint8_t ABS(); 227 | uint8_t ABX(); 228 | uint8_t ABY(); 229 | uint8_t IND(); 230 | uint8_t IZX(); 231 | uint8_t IZY(); 232 | 233 | private: 234 | // Opcodes ====================================================== 235 | // There are 56 "legitimate" opcodes provided by the 6502 CPU. I 236 | // have not modelled "unofficial" opcodes. As each opcode is 237 | // defined by 1 byte, there are potentially 256 possible codes. 238 | // Codes are not used in a "switch case" style on a processor, 239 | // instead they are repsonisble for switching individual parts of 240 | // CPU circuits on and off. The opcodes listed here are official, 241 | // meaning that the functionality of the chip when provided with 242 | // these codes is as the developers intended it to be. Unofficial 243 | // codes will of course also influence the CPU circuitry in 244 | // interesting ways, and can be exploited to gain additional 245 | // functionality! 246 | // 247 | // These functions return 0 normally, but some are capable of 248 | // requiring more clock cycles when executed under certain 249 | // conditions combined with certain addressing modes. If that is 250 | // the case, they return 1. 251 | // 252 | // I have included detailed explanations of each function in 253 | // the class implementation file. Note they are listed in 254 | // alphabetical order here for ease of finding. 255 | 256 | uint8_t ADC(); 257 | uint8_t AND(); 258 | uint8_t ASL(); 259 | uint8_t BCC(); 260 | uint8_t BCS(); 261 | uint8_t BEQ(); 262 | uint8_t BIT(); 263 | uint8_t BMI(); 264 | uint8_t BNE(); 265 | uint8_t BPL(); 266 | uint8_t BRK(); 267 | uint8_t BVC(); 268 | uint8_t BVS(); 269 | uint8_t CLC(); 270 | uint8_t CLD(); 271 | uint8_t CLI(); 272 | uint8_t CLV(); 273 | uint8_t CMP(); 274 | uint8_t CPX(); 275 | uint8_t CPY(); 276 | uint8_t DEC(); 277 | uint8_t DEX(); 278 | uint8_t DEY(); 279 | uint8_t EOR(); 280 | uint8_t INC(); 281 | uint8_t INX(); 282 | uint8_t INY(); 283 | uint8_t JMP(); 284 | uint8_t JSR(); 285 | uint8_t LDA(); 286 | uint8_t LDX(); 287 | uint8_t LDY(); 288 | uint8_t LSR(); 289 | uint8_t NOP(); 290 | uint8_t ORA(); 291 | uint8_t PHA(); 292 | uint8_t PHP(); 293 | uint8_t PLA(); 294 | uint8_t PLP(); 295 | uint8_t ROL(); 296 | uint8_t ROR(); 297 | uint8_t RTI(); 298 | uint8_t RTS(); 299 | uint8_t SBC(); 300 | uint8_t SEC(); 301 | uint8_t SED(); 302 | uint8_t SEI(); 303 | uint8_t STA(); 304 | uint8_t STX(); 305 | uint8_t STY(); 306 | uint8_t TAX(); 307 | uint8_t TAY(); 308 | uint8_t TSX(); 309 | uint8_t TXA(); 310 | uint8_t TXS(); 311 | uint8_t TYA(); 312 | 313 | // I capture all "unofficial" opcodes with this function. It is 314 | // functionally identical to a NOP 315 | uint8_t XXX(); 316 | 317 | #ifdef LOGMODE 318 | 319 | private: 320 | FILE *logfile = nullptr; 321 | #endif 322 | }; 323 | 324 | // End of File - Jx9 325 | -------------------------------------------------------------------------------- /6502/6502Lib/src/public/olcPGEX_Sound.h: -------------------------------------------------------------------------------- 1 | /* 2 | olcPGEX_Sound.h 3 | 4 | +-------------------------------------------------------------+ 5 | | OneLoneCoder Pixel Game Engine Extension | 6 | | Sound - v0.3 | 7 | +-------------------------------------------------------------+ 8 | 9 | What is this? 10 | ~~~~~~~~~~~~~ 11 | This is an extension to the olcPixelGameEngine, which provides 12 | sound generation and wave playing routines. 13 | 14 | Special Thanks: 15 | ~~~~~~~~~~~~~~~ 16 | Slavka - For entire non-windows system back end! 17 | Gorbit99 - Testing, Bug Fixes 18 | Cyberdroid - Testing, Bug Fixes 19 | Dragoneye - Testing 20 | Puol - Testing 21 | 22 | License (OLC-3) 23 | ~~~~~~~~~~~~~~~ 24 | 25 | Copyright 2018 - 2019 OneLoneCoder.com 26 | 27 | Redistribution and use in source and binary forms, with or without 28 | modification, are permitted provided that the following conditions 29 | are met: 30 | 31 | 1. Redistributions or derivations of source code must retain the above 32 | copyright notice, this list of conditions and the following disclaimer. 33 | 34 | 2. Redistributions or derivative works in binary form must reproduce 35 | the above copyright notice. This list of conditions and the following 36 | disclaimer must be reproduced in the documentation and/or other 37 | materials provided with the distribution. 38 | 39 | 3. Neither the name of the copyright holder nor the names of its 40 | contributors may be used to endorse or promote products derived 41 | from this software without specific prior written permission. 42 | 43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 44 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 45 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 46 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 47 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 48 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 49 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 50 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 51 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 52 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 53 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 54 | 55 | Links 56 | ~~~~~ 57 | YouTube: https://www.youtube.com/javidx9 58 | Discord: https://discord.gg/WhwHUMV 59 | Twitter: https://www.twitter.com/javidx9 60 | Twitch: https://www.twitch.tv/javidx9 61 | GitHub: https://www.github.com/onelonecoder 62 | Homepage: https://www.onelonecoder.com 63 | Patreon: https://www.patreon.com/javidx9 64 | 65 | Author 66 | ~~~~~~ 67 | David Barr, aka javidx9, �OneLoneCoder 2019 68 | */ 69 | 70 | #ifdef __GNUC__ 71 | // 关闭 警告:由于数据类型范围限制,比较结果永远为真 72 | // 关闭 警告:unused parameter 73 | #pragma GCC diagnostic ignored "-Wtype-limits" 74 | #pragma GCC diagnostic ignored "-Wunused-parameter" 75 | 76 | #endif 77 | 78 | #ifndef OLC_PGEX_SOUND_H 79 | #define OLC_PGEX_SOUND_H 80 | 81 | #include 82 | #include 83 | #include 84 | #include 85 | #undef min 86 | #undef max 87 | 88 | // Choose a default sound backend 89 | #if !defined(USE_ALSA) && !defined(USE_OPENAL) && !defined(USE_WINDOWS) 90 | #ifdef __linux__ 91 | #define USE_ALSA 92 | #endif 93 | 94 | #ifdef __EMSCRIPTEN__ 95 | #define USE_OPENAL 96 | #endif 97 | 98 | #ifdef _WIN32 99 | #define USE_WINDOWS 100 | #endif 101 | 102 | #endif 103 | 104 | #ifdef USE_ALSA 105 | #define ALSA_PCM_NEW_HW_PARAMS_API 106 | #include 107 | #endif 108 | 109 | #ifdef USE_OPENAL 110 | #include 111 | #include 112 | 113 | #include 114 | #endif 115 | #include 116 | 117 | #pragma pack(push, 1) 118 | typedef struct { 119 | uint16_t wFormatTag; 120 | uint16_t nChannels; 121 | uint32_t nSamplesPerSec; 122 | uint32_t nAvgBytesPerSec; 123 | uint16_t nBlockAlign; 124 | uint16_t wBitsPerSample; 125 | uint16_t cbSize; 126 | } OLC_WAVEFORMATEX; 127 | #pragma pack(pop) 128 | 129 | namespace olc { 130 | // Container class for Advanced 2D Drawing functions 131 | class SOUND : public olc::PGEX { 132 | // A representation of an affine transform, used to rotate, scale, offset & 133 | // shear space 134 | public: 135 | class AudioSample { 136 | public: 137 | AudioSample(); 138 | AudioSample(std::string sWavFile, olc::ResourcePack *pack = nullptr); 139 | olc::rcode LoadFromFile(std::string sWavFile, 140 | olc::ResourcePack *pack = nullptr); 141 | 142 | public: 143 | OLC_WAVEFORMATEX wavHeader; 144 | float *fSample = nullptr; 145 | long nSamples = 0; 146 | int nChannels = 0; 147 | bool bSampleValid = false; 148 | }; 149 | 150 | struct sCurrentlyPlayingSample { 151 | int nAudioSampleID = 0; 152 | long nSamplePosition = 0; 153 | bool bFinished = false; 154 | bool bLoop = false; 155 | bool bFlagForStop = false; 156 | }; 157 | 158 | static std::list listActiveSamples; 159 | 160 | public: 161 | static bool InitialiseAudio(unsigned int nSampleRate = 44100, 162 | unsigned int nChannels = 1, 163 | unsigned int nBlocks = 8, 164 | unsigned int nBlockSamples = 512); 165 | static bool DestroyAudio(); 166 | static void SetUserSynthFunction( 167 | std::function func); 168 | static void SetUserFilterFunction( 169 | std::function func); 170 | 171 | public: 172 | static int LoadAudioSample(std::string sWavFile, 173 | olc::ResourcePack *pack = nullptr); 174 | static void PlaySample(int id, bool bLoop = false); 175 | static void StopSample(int id); 176 | static void StopAll(); 177 | static float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); 178 | 179 | private: 180 | #ifdef USE_WINDOWS // Windows specific sound management 181 | static void CALLBACK waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, 182 | DWORD dwParam2); 183 | static unsigned int m_nSampleRate; 184 | static unsigned int m_nChannels; 185 | static unsigned int m_nBlockCount; 186 | static unsigned int m_nBlockSamples; 187 | static unsigned int m_nBlockCurrent; 188 | static short *m_pBlockMemory; 189 | static WAVEHDR *m_pWaveHeaders; 190 | static HWAVEOUT m_hwDevice; 191 | static std::atomic m_nBlockFree; 192 | static std::condition_variable m_cvBlockNotZero; 193 | static std::mutex m_muxBlockNotZero; 194 | #endif 195 | 196 | #ifdef USE_ALSA 197 | static snd_pcm_t *m_pPCM; 198 | static unsigned int m_nSampleRate; 199 | static unsigned int m_nChannels; 200 | static unsigned int m_nBlockSamples; 201 | static short *m_pBlockMemory; 202 | #endif 203 | 204 | #ifdef USE_OPENAL 205 | static std::queue m_qAvailableBuffers; 206 | static ALuint *m_pBuffers; 207 | static ALuint m_nSource; 208 | static ALCdevice *m_pDevice; 209 | static ALCcontext *m_pContext; 210 | static unsigned int m_nSampleRate; 211 | static unsigned int m_nChannels; 212 | static unsigned int m_nBlockCount; 213 | static unsigned int m_nBlockSamples; 214 | static short *m_pBlockMemory; 215 | #endif 216 | 217 | static void AudioThread(); 218 | static std::thread m_AudioThread; 219 | static std::atomic m_bAudioThreadActive; 220 | static std::atomic m_fGlobalTime; 221 | static std::function funcUserSynth; 222 | static std::function funcUserFilter; 223 | }; 224 | } // namespace olc 225 | 226 | // Implementation, platform-independent 227 | 228 | #ifdef OLC_PGEX_SOUND 229 | #undef OLC_PGEX_SOUND 230 | 231 | namespace olc { 232 | SOUND::AudioSample::AudioSample() {} 233 | 234 | SOUND::AudioSample::AudioSample(std::string sWavFile, olc::ResourcePack *pack) { 235 | LoadFromFile(sWavFile, pack); 236 | } 237 | 238 | olc::rcode SOUND::AudioSample::LoadFromFile(std::string sWavFile, 239 | olc::ResourcePack *pack) { 240 | auto ReadWave = [&](std::istream &is) { 241 | char dump[4]; 242 | is.read(dump, sizeof(char) * 4); // Read "RIFF" 243 | if (strncmp(dump, "RIFF", 4) != 0) return olc::FAIL; 244 | is.read(dump, sizeof(char) * 4); // Not Interested 245 | is.read(dump, sizeof(char) * 4); // Read "WAVE" 246 | if (strncmp(dump, "WAVE", 4) != 0) return olc::FAIL; 247 | 248 | // Read Wave description chunk 249 | is.read(dump, sizeof(char) * 4); // Read "fmt " 250 | unsigned int nHeaderSize = 0; 251 | is.read((char *)&nHeaderSize, sizeof(unsigned int)); // Not Interested 252 | is.read( 253 | (char *)&wavHeader, 254 | nHeaderSize); // sizeof(WAVEFORMATEX)); // Read Wave Format Structure 255 | // chunk Note the -2, because the structure has 2 bytes 256 | // to indicate its own size which are not in the wav file 257 | 258 | // Just check if wave format is compatible with olcPGE 259 | if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) 260 | return olc::FAIL; 261 | 262 | // Search for audio data chunk 263 | uint32_t nChunksize = 0; 264 | is.read(dump, sizeof(char) * 4); // Read chunk header 265 | is.read((char *)&nChunksize, sizeof(uint32_t)); // Read chunk size 266 | while (strncmp(dump, "data", 4) != 0) { 267 | // Not audio data, so just skip it 268 | // std::fseek(f, nChunksize, SEEK_CUR); 269 | is.seekg(nChunksize, std::istream::cur); 270 | is.read(dump, sizeof(char) * 4); 271 | is.read((char *)&nChunksize, sizeof(uint32_t)); 272 | } 273 | 274 | // Finally got to data, so read it all in and convert to float samples 275 | nSamples = 276 | nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); 277 | nChannels = wavHeader.nChannels; 278 | 279 | // Create floating point buffer to hold audio sample 280 | fSample = new float[nSamples * nChannels]; 281 | float *pSample = fSample; 282 | 283 | // Read in audio data and normalise 284 | for (long i = 0; i < nSamples; i++) { 285 | for (int c = 0; c < nChannels; c++) { 286 | short s = 0; 287 | if (!is.eof()) { 288 | is.read((char *)&s, sizeof(short)); 289 | 290 | *pSample = (float)s / (float)(SHRT_MAX); 291 | pSample++; 292 | } 293 | } 294 | } 295 | 296 | // All done, flag sound as valid 297 | bSampleValid = true; 298 | return olc::OK; 299 | }; 300 | 301 | if (pack != nullptr) { 302 | // olc::ResourcePack::sEntry entry = pack->GetStreamBuffer(sWavFile); 303 | // std::istream is(&entry); 304 | // return ReadWave(is); 305 | std::cout << "hello" << std::endl; 306 | } else { 307 | // Read from file 308 | std::ifstream ifs(sWavFile, std::ifstream::binary); 309 | if (ifs.is_open()) { 310 | return ReadWave(ifs); 311 | } else 312 | return olc::FAIL; 313 | } 314 | return olc::OK; 315 | } 316 | 317 | // This vector holds all loaded sound samples in memory 318 | std::vector vecAudioSamples; 319 | 320 | // This structure represents a sound that is currently playing. It only 321 | // holds the sound ID and where this instance of it is up to for its 322 | // current playback 323 | 324 | void SOUND::SetUserSynthFunction(std::function func) { 325 | funcUserSynth = func; 326 | } 327 | 328 | void SOUND::SetUserFilterFunction( 329 | std::function func) { 330 | funcUserFilter = func; 331 | } 332 | 333 | // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID 334 | // number is returned if successful, otherwise -1 335 | int SOUND::LoadAudioSample(std::string sWavFile, olc::ResourcePack *pack) { 336 | olc::SOUND::AudioSample a(sWavFile, pack); 337 | if (a.bSampleValid) { 338 | vecAudioSamples.push_back(a); 339 | return (unsigned int)vecAudioSamples.size(); 340 | } else 341 | return -1; 342 | } 343 | 344 | // Add sample 'id' to the mixers sounds to play list 345 | void SOUND::PlaySample(int id, bool bLoop) { 346 | olc::SOUND::sCurrentlyPlayingSample a; 347 | a.nAudioSampleID = id; 348 | a.nSamplePosition = 0; 349 | a.bFinished = false; 350 | a.bFlagForStop = false; 351 | a.bLoop = bLoop; 352 | SOUND::listActiveSamples.push_back(a); 353 | } 354 | 355 | void SOUND::StopSample(int id) { 356 | // Find first occurence of sample id 357 | auto s = std::find_if(listActiveSamples.begin(), listActiveSamples.end(), 358 | [&](const olc::SOUND::sCurrentlyPlayingSample &s) { 359 | return s.nAudioSampleID == id; 360 | }); 361 | if (s != listActiveSamples.end()) s->bFlagForStop = true; 362 | } 363 | 364 | void SOUND::StopAll() { 365 | for (auto &s : listActiveSamples) { 366 | s.bFlagForStop = true; 367 | } 368 | } 369 | 370 | float SOUND::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) { 371 | // Accumulate sample for this channel 372 | float fMixerSample = 0.0f; 373 | 374 | for (auto &s : listActiveSamples) { 375 | if (m_bAudioThreadActive) { 376 | if (s.bFlagForStop) { 377 | s.bLoop = false; 378 | s.bFinished = true; 379 | } else { 380 | // Calculate sample position 381 | s.nSamplePosition += roundf((float)vecAudioSamples[s.nAudioSampleID - 1] 382 | .wavHeader.nSamplesPerSec * 383 | fTimeStep); 384 | 385 | // If sample position is valid add to the mix 386 | if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) 387 | fMixerSample += 388 | vecAudioSamples[s.nAudioSampleID - 1] 389 | .fSample[(s.nSamplePosition * 390 | vecAudioSamples[s.nAudioSampleID - 1].nChannels) + 391 | nChannel]; 392 | else { 393 | if (s.bLoop) { 394 | s.nSamplePosition = 0; 395 | } else 396 | s.bFinished = true; // Else sound has completed 397 | } 398 | } 399 | } else 400 | return 0.0f; 401 | } 402 | 403 | // If sounds have completed then remove them 404 | listActiveSamples.remove_if( 405 | [](const sCurrentlyPlayingSample &s) { return s.bFinished; }); 406 | 407 | // The users application might be generating sound, so grab that if it exists 408 | if (funcUserSynth != nullptr) 409 | fMixerSample += funcUserSynth(nChannel, fGlobalTime, fTimeStep); 410 | 411 | // Return the sample via an optional user override to filter the sound 412 | if (funcUserFilter != nullptr) 413 | return funcUserFilter(nChannel, fGlobalTime, fMixerSample); 414 | else 415 | return fMixerSample; 416 | } 417 | 418 | std::thread SOUND::m_AudioThread; 419 | std::atomic SOUND::m_bAudioThreadActive{false}; 420 | std::atomic SOUND::m_fGlobalTime{0.0f}; 421 | std::list SOUND::listActiveSamples; 422 | std::function SOUND::funcUserSynth = nullptr; 423 | std::function SOUND::funcUserFilter = nullptr; 424 | } // namespace olc 425 | 426 | // Implementation, Windows-specific 427 | #ifdef USE_WINDOWS 428 | #pragma comment(lib, "winmm.lib") 429 | 430 | namespace olc { 431 | bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, 432 | unsigned int nBlocks, unsigned int nBlockSamples) { 433 | // Initialise Sound Engine 434 | m_bAudioThreadActive = false; 435 | m_nSampleRate = nSampleRate; 436 | m_nChannels = nChannels; 437 | m_nBlockCount = nBlocks; 438 | m_nBlockSamples = nBlockSamples; 439 | m_nBlockFree = m_nBlockCount; 440 | m_nBlockCurrent = 0; 441 | m_pBlockMemory = nullptr; 442 | m_pWaveHeaders = nullptr; 443 | 444 | // Device is available 445 | WAVEFORMATEX waveFormat; 446 | waveFormat.wFormatTag = WAVE_FORMAT_PCM; 447 | waveFormat.nSamplesPerSec = m_nSampleRate; 448 | waveFormat.wBitsPerSample = sizeof(short) * 8; 449 | waveFormat.nChannels = m_nChannels; 450 | waveFormat.nBlockAlign = 451 | (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; 452 | waveFormat.nAvgBytesPerSec = 453 | waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; 454 | waveFormat.cbSize = 0; 455 | 456 | listActiveSamples.clear(); 457 | 458 | // Open Device if valid 459 | if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, 460 | (DWORD_PTR)SOUND::waveOutProc, (DWORD_PTR)0, 461 | CALLBACK_FUNCTION) != S_OK) 462 | return DestroyAudio(); 463 | 464 | // Allocate Wave|Block Memory 465 | m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; 466 | if (m_pBlockMemory == nullptr) return DestroyAudio(); 467 | ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); 468 | 469 | m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; 470 | if (m_pWaveHeaders == nullptr) return DestroyAudio(); 471 | ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); 472 | 473 | // Link headers to block memory 474 | for (unsigned int n = 0; n < m_nBlockCount; n++) { 475 | m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); 476 | m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); 477 | } 478 | 479 | m_bAudioThreadActive = true; 480 | m_AudioThread = std::thread(&SOUND::AudioThread); 481 | 482 | // Start the ball rolling with the sound delivery thread 483 | std::unique_lock lm(m_muxBlockNotZero); 484 | m_cvBlockNotZero.notify_one(); 485 | return true; 486 | } 487 | 488 | // Stop and clean up audio system 489 | bool SOUND::DestroyAudio() { 490 | m_bAudioThreadActive = false; 491 | m_AudioThread.join(); 492 | return false; 493 | } 494 | 495 | // Handler for soundcard request for more data 496 | void CALLBACK SOUND::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, 497 | DWORD dwParam2) { 498 | if (uMsg != WOM_DONE) return; 499 | m_nBlockFree++; 500 | std::unique_lock lm(m_muxBlockNotZero); 501 | m_cvBlockNotZero.notify_one(); 502 | } 503 | 504 | // Audio thread. This loop responds to requests from the soundcard to fill 505 | // 'blocks' with audio data. If no requests are available it goes dormant until 506 | // the sound card is ready for more data. The block is fille by the "user" in 507 | // some manner and then issued to the soundcard. 508 | void SOUND::AudioThread() { 509 | m_fGlobalTime = 0.0f; 510 | static float fTimeStep = 1.0f / (float)m_nSampleRate; 511 | 512 | // Goofy hack to get maximum integer for a type at run-time 513 | short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; 514 | float fMaxSample = (float)nMaxSample; 515 | short nPreviousSample = 0; 516 | 517 | while (m_bAudioThreadActive) { 518 | // Wait for block to become available 519 | if (m_nBlockFree == 0) { 520 | std::unique_lock lm(m_muxBlockNotZero); 521 | while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly 522 | m_cvBlockNotZero.wait(lm); 523 | } 524 | 525 | // Block is here, so use it 526 | m_nBlockFree--; 527 | 528 | // Prepare block for processing 529 | if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) 530 | waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], 531 | sizeof(WAVEHDR)); 532 | 533 | short nNewSample = 0; 534 | int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; 535 | 536 | auto clip = [](float fSample, float fMax) { 537 | if (fSample >= 0.0) 538 | return fmin(fSample, fMax); 539 | else 540 | return fmax(fSample, -fMax); 541 | }; 542 | 543 | for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) { 544 | // User Process 545 | for (unsigned int c = 0; c < m_nChannels; c++) { 546 | nNewSample = 547 | (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * 548 | fMaxSample); 549 | m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; 550 | nPreviousSample = nNewSample; 551 | } 552 | 553 | m_fGlobalTime = m_fGlobalTime + fTimeStep; 554 | } 555 | 556 | // Send block to sound device 557 | waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], 558 | sizeof(WAVEHDR)); 559 | waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); 560 | m_nBlockCurrent++; 561 | m_nBlockCurrent %= m_nBlockCount; 562 | } 563 | } 564 | 565 | unsigned int SOUND::m_nSampleRate = 0; 566 | unsigned int SOUND::m_nChannels = 0; 567 | unsigned int SOUND::m_nBlockCount = 0; 568 | unsigned int SOUND::m_nBlockSamples = 0; 569 | unsigned int SOUND::m_nBlockCurrent = 0; 570 | short *SOUND::m_pBlockMemory = nullptr; 571 | WAVEHDR *SOUND::m_pWaveHeaders = nullptr; 572 | HWAVEOUT SOUND::m_hwDevice; 573 | std::atomic SOUND::m_nBlockFree = 0; 574 | std::condition_variable SOUND::m_cvBlockNotZero; 575 | std::mutex SOUND::m_muxBlockNotZero; 576 | } // namespace olc 577 | 578 | #elif defined(USE_ALSA) 579 | 580 | namespace olc { 581 | bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, 582 | unsigned int nBlocks, unsigned int nBlockSamples) { 583 | // Initialise Sound Engine 584 | m_bAudioThreadActive = false; 585 | m_nSampleRate = nSampleRate; // 周期 586 | m_nChannels = nChannels; // 声道 587 | m_nBlockSamples = nBlockSamples; // 一个块的大小 588 | m_pBlockMemory = nullptr; 589 | 590 | // Open PCM stream 591 | int rc = snd_pcm_open(&m_pPCM, "default", SND_PCM_STREAM_PLAYBACK, 0); 592 | if (rc < 0) return DestroyAudio(); 593 | 594 | // Prepare the parameter structure and set default parameters 595 | snd_pcm_hw_params_t *params; // PCM指针 596 | snd_pcm_hw_params_alloca(¶ms); // 分配PCM指针内存 597 | snd_pcm_hw_params_any(m_pPCM, params); // 绑定句柄和PCM指针 598 | 599 | // Set other parameters 600 | snd_pcm_hw_params_set_format( 601 | m_pPCM, params, 602 | SND_PCM_FORMAT_S16_LE); // 设定PCM数据格式 每个数据是16bit 603 | snd_pcm_hw_params_set_rate(m_pPCM, params, m_nSampleRate, 0); // 采样率 604 | snd_pcm_hw_params_set_channels(m_pPCM, params, m_nChannels); // 声道,这里是1 605 | snd_pcm_hw_params_set_period_size(m_pPCM, params, m_nBlockSamples, 606 | 0); // 周期的大小,这里为512 607 | snd_pcm_hw_params_set_periods(m_pPCM, params, nBlocks, 608 | 0); // 周期数量 这里为8 609 | 610 | // Save these parameters 611 | rc = snd_pcm_hw_params(m_pPCM, params); // 保存参数 612 | if (rc < 0) return DestroyAudio(); 613 | 614 | listActiveSamples.clear(); // 关闭正在播放的设备??? 615 | 616 | // Allocate Wave|Block Memory 617 | m_pBlockMemory = 618 | new short[m_nBlockSamples]; // 一个周期的大小为512 ,每个是16bit(2个字节) 619 | if (m_pBlockMemory == nullptr) return DestroyAudio(); 620 | std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 621 | 0); // 初始化播放字节 622 | 623 | // Unsure if really needed, helped prevent underrun on my setup 624 | snd_pcm_start(m_pPCM); // 准备数据完成,开始播放m_pPCM 625 | for (unsigned int i = 0; i < nBlocks; i++) 626 | rc = snd_pcm_writei( 627 | m_pPCM, m_pBlockMemory, 628 | 512); // 循环播放 8 个块 ,每个块为512个大小,当然,每个数据是16bit 629 | 630 | snd_pcm_start(m_pPCM); // 准备数据完成,开始播放m_pPCM 631 | m_bAudioThreadActive = true; // 打开播放进程标志 632 | m_AudioThread = std::thread(&SOUND::AudioThread); // 初始化音频播放进程 633 | 634 | return true; 635 | } 636 | 637 | // Stop and clean up audio system 638 | bool SOUND::DestroyAudio() { 639 | m_bAudioThreadActive = false; 640 | m_AudioThread.join(); 641 | snd_pcm_drain(m_pPCM); 642 | snd_pcm_close(m_pPCM); 643 | return false; 644 | } 645 | 646 | // Audio thread. This loop responds to requests from the soundcard to fill 647 | // 'blocks' with audio data. If no requests are available it goes dormant until 648 | // the sound card is ready for more data. The block is fille by the "user" in 649 | // some manner and then issued to the soundcard. 650 | void SOUND::AudioThread() { 651 | m_fGlobalTime = 0.0f; 652 | static float fTimeStep = 1.0f / (float)m_nSampleRate; 653 | 654 | // Goofy hack to get maximum integer for a type at run-time 655 | short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; 656 | float fMaxSample = (float)nMaxSample; 657 | // short nPreviousSample = 0; 658 | 659 | while (m_bAudioThreadActive) { 660 | short nNewSample = 0; 661 | 662 | auto clip = [](float fSample, float fMax) { 663 | if (fSample >= 0.0) 664 | return fmin(fSample, fMax); 665 | else 666 | return fmax(fSample, -fMax); 667 | }; 668 | 669 | for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) { 670 | // User Process 671 | for (unsigned int c = 0; c < m_nChannels; c++) { 672 | nNewSample = 673 | (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * 674 | fMaxSample); 675 | m_pBlockMemory[n + c] = nNewSample; 676 | // nPreviousSample = nNewSample; 677 | } 678 | 679 | m_fGlobalTime = m_fGlobalTime + fTimeStep; 680 | } 681 | 682 | // Send block to sound device 683 | snd_pcm_uframes_t nLeft = m_nBlockSamples; 684 | short *pBlockPos = m_pBlockMemory; 685 | while (nLeft > 0) { 686 | int rc = snd_pcm_writei(m_pPCM, pBlockPos, nLeft); 687 | if (rc > 0) { 688 | pBlockPos += rc * m_nChannels; // 消耗了多少 short 的音频, 一直播放 689 | nLeft -= rc; 690 | } 691 | if (rc == -EAGAIN) continue; 692 | if (rc == 693 | -EPIPE) // an underrun occured, prepare the device for more data 694 | snd_pcm_prepare(m_pPCM); 695 | } 696 | } 697 | } 698 | 699 | snd_pcm_t *SOUND::m_pPCM = nullptr; 700 | unsigned int SOUND::m_nSampleRate = 0; 701 | unsigned int SOUND::m_nChannels = 0; 702 | unsigned int SOUND::m_nBlockSamples = 0; 703 | short *SOUND::m_pBlockMemory = nullptr; 704 | } // namespace olc 705 | 706 | #elif defined(USE_OPENAL) 707 | 708 | namespace olc { 709 | bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, 710 | unsigned int nBlocks, unsigned int nBlockSamples) { 711 | // Initialise Sound Engine 712 | m_bAudioThreadActive = false; 713 | m_nSampleRate = nSampleRate; 714 | m_nChannels = nChannels; 715 | m_nBlockCount = nBlocks; 716 | m_nBlockSamples = nBlockSamples; 717 | m_pBlockMemory = nullptr; 718 | 719 | // Open the device and create the context 720 | m_pDevice = alcOpenDevice(NULL); 721 | if (m_pDevice) { 722 | m_pContext = alcCreateContext(m_pDevice, NULL); 723 | alcMakeContextCurrent(m_pContext); 724 | } else 725 | return DestroyAudio(); 726 | 727 | // Allocate memory for sound data 728 | alGetError(); 729 | m_pBuffers = new ALuint[m_nBlockCount]; 730 | alGenBuffers(m_nBlockCount, m_pBuffers); 731 | alGenSources(1, &m_nSource); 732 | 733 | for (unsigned int i = 0; i < m_nBlockCount; i++) 734 | m_qAvailableBuffers.push(m_pBuffers[i]); 735 | 736 | listActiveSamples.clear(); 737 | 738 | // Allocate Wave|Block Memory 739 | m_pBlockMemory = new short[m_nBlockSamples]; 740 | if (m_pBlockMemory == nullptr) return DestroyAudio(); 741 | std::fill(m_pBlockMemory, m_pBlockMemory + m_nBlockSamples, 0); 742 | 743 | m_bAudioThreadActive = true; 744 | m_AudioThread = std::thread(&SOUND::AudioThread); 745 | return true; 746 | } 747 | 748 | // Stop and clean up audio system 749 | bool SOUND::DestroyAudio() { 750 | m_bAudioThreadActive = false; 751 | m_AudioThread.join(); 752 | 753 | alDeleteBuffers(m_nBlockCount, m_pBuffers); 754 | delete[] m_pBuffers; 755 | alDeleteSources(1, &m_nSource); 756 | 757 | alcMakeContextCurrent(NULL); 758 | alcDestroyContext(m_pContext); 759 | alcCloseDevice(m_pDevice); 760 | return false; 761 | } 762 | 763 | // Audio thread. This loop responds to requests from the soundcard to fill 764 | // 'blocks' with audio data. If no requests are available it goes dormant until 765 | // the sound card is ready for more data. The block is fille by the "user" in 766 | // some manner and then issued to the soundcard. 767 | void SOUND::AudioThread() { 768 | m_fGlobalTime = 0.0f; 769 | static float fTimeStep = 1.0f / (float)m_nSampleRate; 770 | 771 | // Goofy hack to get maximum integer for a type at run-time 772 | short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; 773 | float fMaxSample = (float)nMaxSample; 774 | short nPreviousSample = 0; 775 | 776 | std::vector vProcessed; 777 | 778 | while (m_bAudioThreadActive) { 779 | ALint nState, nProcessed; 780 | alGetSourcei(m_nSource, AL_SOURCE_STATE, &nState); 781 | alGetSourcei(m_nSource, AL_BUFFERS_PROCESSED, &nProcessed); 782 | 783 | // Add processed buffers to our queue 784 | vProcessed.resize(nProcessed); 785 | alSourceUnqueueBuffers(m_nSource, nProcessed, vProcessed.data()); 786 | for (ALint nBuf : vProcessed) m_qAvailableBuffers.push(nBuf); 787 | 788 | // Wait until there is a free buffer (ewww) 789 | if (m_qAvailableBuffers.empty()) continue; 790 | 791 | short nNewSample = 0; 792 | 793 | auto clip = [](float fSample, float fMax) { 794 | if (fSample >= 0.0) 795 | return fmin(fSample, fMax); 796 | else 797 | return fmax(fSample, -fMax); 798 | }; 799 | 800 | for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) { 801 | // User Process 802 | for (unsigned int c = 0; c < m_nChannels; c++) { 803 | nNewSample = 804 | (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * 805 | fMaxSample); 806 | m_pBlockMemory[n + c] = nNewSample; 807 | nPreviousSample = nNewSample; 808 | } 809 | 810 | m_fGlobalTime = m_fGlobalTime + fTimeStep; 811 | } 812 | 813 | // Fill OpenAL data buffer 814 | alBufferData(m_qAvailableBuffers.front(), 815 | m_nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, 816 | m_pBlockMemory, 2 * m_nBlockSamples, m_nSampleRate); 817 | // Add it to the OpenAL queue 818 | alSourceQueueBuffers(m_nSource, 1, &m_qAvailableBuffers.front()); 819 | // Remove it from ours 820 | m_qAvailableBuffers.pop(); 821 | 822 | // If it's not playing for some reason, change that 823 | if (nState != AL_PLAYING) alSourcePlay(m_nSource); 824 | } 825 | } 826 | 827 | std::queue SOUND::m_qAvailableBuffers; 828 | ALuint *SOUND::m_pBuffers = nullptr; 829 | ALuint SOUND::m_nSource = 0; 830 | ALCdevice *SOUND::m_pDevice = nullptr; 831 | ALCcontext *SOUND::m_pContext = nullptr; 832 | unsigned int SOUND::m_nSampleRate = 0; 833 | unsigned int SOUND::m_nChannels = 0; 834 | unsigned int SOUND::m_nBlockCount = 0; 835 | unsigned int SOUND::m_nBlockSamples = 0; 836 | short *SOUND::m_pBlockMemory = nullptr; 837 | } // namespace olc 838 | 839 | #else // Some other platform 840 | 841 | namespace olc { 842 | bool SOUND::InitialiseAudio(unsigned int nSampleRate, unsigned int nChannels, 843 | unsigned int nBlocks, unsigned int nBlockSamples) { 844 | return true; 845 | } 846 | 847 | // Stop and clean up audio system 848 | bool SOUND::DestroyAudio() { return false; } 849 | 850 | // Audio thread. This loop responds to requests from the soundcard to fill 851 | // 'blocks' with audio data. If no requests are available it goes dormant until 852 | // the sound card is ready for more data. The block is fille by the "user" in 853 | // some manner and then issued to the soundcard. 854 | void SOUND::AudioThread() {} 855 | } // namespace olc 856 | 857 | #endif 858 | #endif 859 | #endif // OLC_PGEX_SOUND -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.7) 2 | 3 | project( 6502 ) 4 | 5 | # Turn on the ability to create folders to organize projects (.vcproj) 6 | # It creates "CMakePredefinedTargets" folder by default and adds CMake 7 | # defined projects like INSTALL.vcproj and ZERO_CHECK.vcproj 8 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 9 | 10 | # Sub-directories where more CMakeLists.txt exist 11 | #add_subdirectory(6502/6502Test) 12 | add_subdirectory(6502/6502Lib) 13 | add_subdirectory(6502/6502Emulator) 14 | 15 | file(COPY ./rom DESTINATION ./6502/6502Emulator/) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License (OLC-3) 2 | Copyright 2018, 2019, 2020, 2021 OneLoneCoder.com 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions or derivations of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | Redistributions or derivative works in binary form must reproduce the above copyright notice. This list of conditions and the following disclaimer must be reproduced in the documentation and/or other materials provided with the distribution. 9 | 10 | Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /make_VS2019.bat: -------------------------------------------------------------------------------- 1 | md build 2 | cd build 3 | cmake -G "Visual Studio 16 2019" -A x64 ..\ 4 | PAUSE -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # NES 模拟器(Nintendo Entertainment System emulator) 2 | 3 | 适用于Windows、UNIX、MAC平台的开源NES模拟器,为NES模拟器学习者提供与原NES机器高度相似的运行逻辑展示,源码并没有对运行速度进行优化,这也是为了保证高度还原NES运行机制的妥协。 4 | 5 | 该项目旨在重现NES游戏机的运行机制,而不注重性能,如果想要学习性能强大的模拟器,可以参照以下模拟器源码,·[fceux](https://github.com/TASEmulators/fceux)、[jsnes(js)](https://github.com/bfirsh/jsnes)、[nes(go)](https://github.com/fogleman/nes)、[SimpleNES](https://github.com/amhndu/SimpleNES)等源码,这些模拟器皆具有较好的性能,但整体代码结构并不利于NES模拟器的学习,因为其中做了大量的性能优化,导致模拟器的运行机制与原机器大相径庭,所以本仓库源码更利于理解NES。 6 | 7 | ### 控制器按键 8 | 9 | | Nintendo | Emulator | 10 | | --------------------- | ----------- | 11 | | Up, Down, Left, Right | W A S D | 12 | | Start | T | 13 | | Select | Y | 14 | | A | K | 15 | | B | J | 16 | | Reset | R | 17 | 18 | ### 功能键 19 | 20 | | function | key | 21 | |-----------------|-----------| 22 | | save game state | F1 | 23 | | load game state | F2 | 24 | | reset game | Backspace | 25 | | change palette | P | 26 | 27 | ## 实现的模块 28 | - BUS 29 | - Cartridge(卡带) 30 | - Mapper(0\1\2\3\4\66) 31 | - 2A03(apu) 32 | - 2C02(ppu) 33 | - [6502(cpu)](https://github.com/tiansongyu/6502Emulator/blob/master/readme6502.md) 34 | 35 | ## 编译环境配置 36 | 37 | ### linux环境 38 | 39 | 安装cmake 40 | ``` bash 41 | # ubuntu 42 | # 安装opengl、alsa 43 | sudo apt install cmake mesa-common-dev libgl1-mesa-dev \ 44 | libglu1-mesa-dev alsa-base alsa-utils alsa-source libasound2-dev libpng-dev -y 45 | 46 | ``` 47 | ### windows 48 | 49 | 安装vs2019 50 | ## 编译 51 | 52 | ### linux环境 53 | ``` bash 54 | git clone git@github.com:tiansongyu/6502Emulator.git 55 | cd 6502Emulator 56 | mkdir build 57 | cd build 58 | cmake -DCMAKE_BUILD_TYPE=Release .. 59 | cd 6502/6502Emulator/ 60 | make -j12 61 | ./6502Emulator 62 | ``` 63 | ### Windows环境 64 | ``` bash 65 | git clone git@github.com:tiansongyu/6502Emulator.git 66 | cd 6502Emulator 67 | make_VS2019.bat 68 | ``` 69 | 70 | ## 可游玩游戏 71 | 72 | 每个游戏对应一个Mapper,只要实现游戏对应的Mapper,就可以游玩 73 | 本模拟器实现的mapper有 74 | - Mapper0 (NROM) 75 | - Mapper1 (MMC1) 76 | - Mapper2 (UxROM) 77 | - Mapper3 ( CNROM ) 78 | - Mapper4 (MMC3) 最复杂的一种Mapper,目前可能在存在中断问题,没有解决,metal max 中断不正确 79 | - Mapper66 (GxROM) 80 | 81 | 查看如下游戏列表,查看对应的Mapper,可以用来判断是否可游玩 82 | * [NES Mapper List](http://tuxnes.sourceforge.net/nesmapper.txt) 83 | 84 | 0,1,2,3,4为任天堂自己生产的mapper,已经可以游玩大部分游戏,如想实现其他Mapper 85 | ,可以查看[Mapper资料](https://wiki.nesdev.org/w/index.php?title=Mapper),并查看本仓库源码,可以找到实现其他mapper的方法。 86 | 87 | ## 参考文档资料 88 | 89 | * [NES Reference Guide (Wiki)](https://wiki.nesdev.org/w/index.php?title=NES_reference_guide) 90 | * [6502官网](http://www.6502.org/) 91 | * [6502 CPU Reference](http://www.obelisk.me.uk/6502/reference.html) 92 | * [6502 CPU Instruction](https://www.masswerk.at/6502/6502_instruction_set.html) 93 | * [6502 在线指令实现](https://www.masswerk.at/6502/assembler.html) 94 | * [NES Documentation (PDF)](http://nesdev.com/NESDoc.pdf) 95 | 96 | 97 | ### 其他开发工具 98 | * [NES Mapper List](http://tuxnes.sourceforge.net/nesmapper.txt) 99 | * [NES文件下载](https://wowroms.com/en/roms/nintendo-entertainment-system/) 100 | 101 | ### 未实现功能 102 | 103 | - [6502 指令实现情况](https://trello.com/b/ll6HPTJ0/6502emulator) 104 | - [NES模拟器功能实现情况](https://trello.com/b/hB8YJmU6/nes%E6%A8%A1%E6%8B%9F%E5%99%A8) 105 | -------------------------------------------------------------------------------- /readme6502.md: -------------------------------------------------------------------------------- 1 | # 6052emulator 6502芯片模拟记录 2 | 3 | ## 使用方法 4 | 1. **安装vs2019、cmake v3.20+** 5 | 2. **下载或clone本库源码** 6 | 3. **执行make_VS2019.bat命令,会使用cmake自动生成vs2019工程,找到sln文件,进入工程(vs2019关闭警告作为错误选项)**(vs2017 vs2016 vs2015等皆可使用本源码,需要修改**make_VS2019.bat**中的`cmake -G "Visual Studio 16 2019" -A x64 ..\ -Dgtest_force_shared_crt=on` 指令,查询相关资料,修改为对应的vs版本工程) 7 | 8 | 9 | ### 工程划分 10 | 本项目分四个工程: 11 | - 6502Emulator用UI界面的方式呈现6502寄存器的状态 12 | - gtest是谷歌的测试框架,属于Lib库 13 | - M6502Lib是6502CPU的指令实现 14 | - M6502Test是单元测试代码 15 | 16 | 17 | 工程包括分个模块。一个是6502CPU实现模块,另外一个是使用[Google test](https://github.com/google/googletest/)工具进行单元测试,用来测试实现的指令。 18 | 本工程只包括6502指令实现,可以进行执行所有逻辑、算数等指令(WIP),如果需要学习6502有关实现,可以查阅6502应用相关资料,进行应用实现。 19 | 20 | ## 参考资料 21 | - **http://www.obelisk.me.uk/6502/** 22 | - **http://www.6502.org/** 23 | - **https://www.c64-wiki.com/wiki/Addressing_mode** 24 | 25 | 1. 基本架构 描述了处理器的一些基本细节。 26 | 2. 寄存器 遍历每个内部寄存器及其使用。 27 | 3. 指令 提供了整个指令集的摘要。 28 | 4. 寻址 描述了6502存储器的每种寻址模式。 29 | 5. 算法 包含基本的6502编码示例。 30 | 6. 参考 详细描述了完整的指令集。 31 | 32 | 33 | 34 | 35 | 36 | ## 基本架构 37 | 38 | 6502微处理器是一个相对简单的8位CPU,只有几个内部寄存器能够通过其16位地址总线寻址最多64Kb的存储器。处理器是低位字节序的,并且地址优先存储在存储器中的最低有效字节之内。 39 | 40 | 存储器的第一个256字节页面($0000-$00FF)被称为“零页面”,是许多特殊寻址模式的焦点,这些寻址模式导致指令更短(和更快)或允许间接访问存储器。内存的第二页($0100-$ 01FF)是为系统堆栈保留的,不能重定位。 41 | 42 | 存储器映射中唯一保留的其他位置是存储器$FFFA至$FFFF的最后6个字节,必须使用不可屏蔽中断处理程序($FFFA/B)的地址,上电复位位置($FFFC/D)和BRK/中断请求处理程序($ FFFE/F)。 43 | 44 | 6502对硬件设备没有任何特殊支持,因此必须将它们映射到内存区域,以便与硬件锁存器交换数据。 45 | 46 | 47 | ## 寄存器 48 | 49 | ### 寄存器 50 | 与同一时期的其他处理器相比,6502仅具有少量的寄存器。由于算法必须有效利用寄存器和存储器,因此编程尤其具有挑战性。 51 | 52 | ### 程序计数器 53 | 程序计数器是一个16位寄存器,它指向要执行的下一条指令。执行指令后,程序计数器的值会自动修改。 54 | 55 | 可以通过执行跳转,相对分支或对另一个存储器地址的子例程调用,或从子例程或中断返回来修改程序计数器的值。 56 | 57 | ### 堆栈指针 58 | 处理器支持位于$0100和$01FF之间的256字节堆栈。堆栈指针是一个8位寄存器,它保存堆栈中下一个空闲位置的低8位。堆栈的位置是固定的,不能移动。 59 | 60 | 将字节压入堆栈会导致堆栈指针递减。相反,拉字节会导致其递增。 61 | 62 | CPU不会检测到堆栈是否因过多的推入或拉出操作而溢出,并且很可能导致程序崩溃。 63 | ### 累加器 64 | 8位累加器用于所有算术和逻辑运算(增量和减量除外)。累加器的内容可以存储或从内存或堆栈中检索。 65 | 66 | 大多数复杂的操作将需要使用累加器进行算术运算,并且对它的使用进行有效的优化是时间关键型例程的关键功能。 67 | ### 索引寄存器X 68 | 8位索引寄存器最常用于保存计数器或偏移量以访问存储器。可以将X寄存器的值加载并保存在内存中,然后将其与保存在内存中或递增或递减的值进行比较。 69 | 70 | X寄存器具有一项特殊功能。它可用于获取堆栈指针的副本或更改其值。 71 | ### 索引寄存器Y 72 | Y寄存器与X寄存器的相似之处在于,它可用于保持计数器或偏移量存储器访问,并支持同一组存储器负载,保存和比较操作以及递增和递减操作。它没有特殊功能。 73 | ### 处理器状态 74 | 在执行指令时,会设置或清除一组处理器标志,以记录操作结果。该标志和一些其他控制标志保存在特殊状态寄存器中。每个标志在寄存器中都有一个位。 75 | 76 | 存在用于测试各个位的值,设置或清除其中一些以及将整个位压入或拉出堆栈的指令。 77 | 78 | - Carry Flag 携带标志 79 | 如果最后一次操作导致结果的第7位发生溢出或由于第0位引起下溢,则进位标志将置位。在算术,比较和逻辑移位期间会设置此条件。可以使用“设置进位标志”(SEC)指令进行显式设置,并通过“清除进位标志”(CLC)进行清除。 80 | - Zero Flag 零标志 81 | 82 | 如果最后一个操作的结果为零,则设置零标志。 83 | - Interrupt Disable 中断禁止 84 | 如果程序执行了“设置中断禁用”(SEI)指令,则将设置中断禁用标志。设置该标志后,处理器将不会响应来自设备的中断,直到通过“清除中断禁用”(CLI)指令将其清除为止。 85 | - Decimal Mode 十进制模式 86 | 当设置了十进制模式标志时,处理器将在加法和减法期间遵守二进制编码的十进制(BCD)算术规则。可以使用“设置十进制标志”(SED)显式设置该标志,并使用“清除十进制标志”(CLD)清除该标志。 87 | - Break Command 中断命令 88 | 当执行了BRK指令并且产生了一个中断来对其进行处理时,中断命令位置1 。 89 | - Overflow Flag 溢出标志 90 | 如果结果产生了无效的2的补码结果(例如,加到正数而最后得到负数:64 + 64 => -128),则在算术运算期间将设置溢出标志。通过查看第6位和第7位之间以及第7位和进位标志之间的进位确定。 91 | - Negative Flag 负标志 92 | 如果最后一个操作的结果的第7位设置为1,则设置负标志。 93 | 94 | ### 已实现指令 95 | [点击这里进入trello看板](https://trello.com/b/ll6HPTJ0/) 96 | 97 | -------------------------------------------------------------------------------- /rom/23.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/23.nes -------------------------------------------------------------------------------- /rom/Adventure Island 3 (USA).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Adventure Island 3 (USA).nes -------------------------------------------------------------------------------- /rom/Adventure Island Classic (Europe).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Adventure Island Classic (Europe).nes -------------------------------------------------------------------------------- /rom/Adventure Island Part II, The (Europe).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Adventure Island Part II, The (Europe).nes -------------------------------------------------------------------------------- /rom/Castlevania2.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Castlevania2.nes -------------------------------------------------------------------------------- /rom/Doraemon (J).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Doraemon (J).nes -------------------------------------------------------------------------------- /rom/Double Dragon II - The Revenge (USA) (Rev A).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Double Dragon II - The Revenge (USA) (Rev A).nes -------------------------------------------------------------------------------- /rom/Dragon Power (USA).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Dragon Power (USA).nes -------------------------------------------------------------------------------- /rom/Duck Hunt (JUE).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Duck Hunt (JUE).nes -------------------------------------------------------------------------------- /rom/Gumshoe.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Gumshoe.nes -------------------------------------------------------------------------------- /rom/Mega Man 3 (USA).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Mega Man 3 (USA).nes -------------------------------------------------------------------------------- /rom/Mega Man 6 (USA).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Mega Man 6 (USA).nes -------------------------------------------------------------------------------- /rom/Metal Max (Japan).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Metal Max (Japan).nes -------------------------------------------------------------------------------- /rom/SolomonsKey.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/SolomonsKey.nes -------------------------------------------------------------------------------- /rom/Zoda's Revenge - StarTropics II (USA).nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/Zoda's Revenge - StarTropics II (USA).nes -------------------------------------------------------------------------------- /rom/chenlong.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/chenlong.nes -------------------------------------------------------------------------------- /rom/contra.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/contra.nes -------------------------------------------------------------------------------- /rom/dazhuankuai.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/dazhuankuai.nes -------------------------------------------------------------------------------- /rom/duola.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/duola.nes -------------------------------------------------------------------------------- /rom/emocheng.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/emocheng.nes -------------------------------------------------------------------------------- /rom/maxituan.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/maxituan.nes -------------------------------------------------------------------------------- /rom/mmc1_a12.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/mmc1_a12.nes -------------------------------------------------------------------------------- /rom/nestest.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/nestest.nes -------------------------------------------------------------------------------- /rom/rexuezuqiu.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/rexuezuqiu.nes -------------------------------------------------------------------------------- /rom/smb.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/smb.nes -------------------------------------------------------------------------------- /rom/smb4.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/smb4.nes -------------------------------------------------------------------------------- /rom/热血新纪录.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血新纪录.nes -------------------------------------------------------------------------------- /rom/热血时代剧中文版.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血时代剧中文版.nes -------------------------------------------------------------------------------- /rom/热血曲棍球.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血曲棍球.nes -------------------------------------------------------------------------------- /rom/热血格斗中文版.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血格斗中文版.nes -------------------------------------------------------------------------------- /rom/热血物语中文版.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血物语中文版.nes -------------------------------------------------------------------------------- /rom/热血硬派.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血硬派.nes -------------------------------------------------------------------------------- /rom/热血篮球中文版.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血篮球中文版.nes -------------------------------------------------------------------------------- /rom/热血足球cn.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血足球cn.nes -------------------------------------------------------------------------------- /rom/热血足球联盟cn.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血足球联盟cn.nes -------------------------------------------------------------------------------- /rom/热血进行曲中文版.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血进行曲中文版.nes -------------------------------------------------------------------------------- /rom/热血高校中文版.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiansongyu/6502Emulator/ef63565e489c9ceba428695d08d393eeaea1338d/rom/热血高校中文版.nes --------------------------------------------------------------------------------