├── nestest.nes ├── Docs └── design.png ├── UI ├── MenuBase.h ├── NotificationManager.h └── Notification.h ├── Utils ├── handler.h ├── enums.h ├── typedefs.h ├── structs.h ├── constants.h ├── functions.h ├── Queue.h ├── AudioFilters.h └── Array.h ├── Audio └── Audio.h ├── main.cpp ├── Makefile ├── CMakeLists.txt ├── Mapper ├── Mapper0.h ├── Mapper3.h ├── Mapper3.cpp ├── Mapper2.h ├── Mapper0.cpp ├── Mapper2.cpp ├── Mapper1.h ├── MapperBase.h └── Mapper1.cpp ├── .gitignore ├── README.md ├── Cartridge ├── Cartridge.cpp └── Cartridge.h ├── LICENSE ├── APU ├── APU.h ├── AudioWaves.h └── APU.cpp ├── Controller ├── Controller.h └── Controller.cpp ├── PPU ├── PPU.h └── PPU.cpp ├── Bus ├── Bus.h ├── Bus.cpp ├── PPUBus.h └── PPUBus.cpp ├── System ├── System.h └── System.cpp ├── Display ├── Display.h └── Display.cpp ├── CPU ├── CPU.h └── CPU.cpp └── State └── StateManagement.h /nestest.nes: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozanarmagan/NESEmulator/HEAD/nestest.nes -------------------------------------------------------------------------------- /Docs/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ozanarmagan/NESEmulator/HEAD/Docs/design.png -------------------------------------------------------------------------------- /UI/MenuBase.h: -------------------------------------------------------------------------------- 1 | #ifndef MENUBASE_H 2 | #define MENUBASE_H 3 | 4 | 5 | 6 | class MenuBase 7 | { 8 | public: 9 | 10 | private: 11 | int currentChoice = 0; 12 | 13 | }; 14 | 15 | 16 | 17 | 18 | #endif -------------------------------------------------------------------------------- /Utils/handler.h: -------------------------------------------------------------------------------- 1 | #ifndef HANDLER_H 2 | #define HANDLER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "constants.h" 8 | #include "typedefs.h" 9 | #include "enums.h" 10 | #include "structs.h" 11 | #include "functions.h" 12 | 13 | #define RUN_FOREVER while(1) 14 | 15 | 16 | 17 | #endif -------------------------------------------------------------------------------- /Audio/Audio.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_H 2 | #define AUDIO_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | namespace nesemulator 9 | { 10 | namespace Audio 11 | { 12 | inline void addToQueue(float sample) { SDL_QueueAudio(1,&sample,sizeof(float)); }; 13 | } 14 | } 15 | 16 | 17 | 18 | #endif -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "System/System.h" 3 | 4 | 5 | int main(int argc,char** argv) 6 | { 7 | if(argc < 2) 8 | std::cout << "No NES File to read!"; 9 | 10 | std::string fn = std::string(argv[1]); 11 | 12 | nesemulator::NES nes(fn); 13 | 14 | nes.log(); 15 | 16 | 17 | nes.start(); 18 | 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /Utils/enums.h: -------------------------------------------------------------------------------- 1 | #ifndef ENUMS_H 2 | #define ENUMS_H 3 | 4 | 5 | namespace nesemulator 6 | { 7 | enum class MIRRORING { // Mirroring mode of the cartridge 8 | HORIZONTAL,VERTICAL,FOURSCREENS,ONSCREENLOWER,ONSCREENHIGHER 9 | }; 10 | 11 | struct OBJECT_ATTRIBUTE // PPU OAM Object Attribute 12 | { 13 | BYTE y,id,attribute,x; 14 | }; 15 | } 16 | 17 | #endif // ! -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CCXXFLAGS = -lSDL2 -lSDL2main -lSDL2_TTF 3 | LDFLAGS = -O3 -std=c++17 4 | 5 | SRC = $(wildcard */*.cpp) *.cpp 6 | OBJ = $(SRC:.cc=.o) 7 | 8 | ifeq ($(OS),Windows_NT) 9 | EXEC = nesemulator.exe 10 | else 11 | EXEC = nesemulator.out 12 | endif 13 | 14 | all: $(EXEC) 15 | 16 | $(EXEC): $(OBJ) 17 | $(CXX) $(LDFLAGS) -o $(EXEC) $(OBJ) $(LBLIBS) $(CCXXFLAGS) 18 | 19 | clean: 20 | rm -rf $(OBJ) $(EXEC) -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | project(nesemulator VERSION 1.0.0 LANGUAGES C CXX) 4 | 5 | find_package(SDL2 REQUIRED) 6 | include_directories(${SDL2_INCLUDE_DIRS}) 7 | 8 | file(GLOB source_files CONFIGURE_DEPENDS "./*/*.cpp") 9 | 10 | add_executable(nesemulator ${source_files}) 11 | 12 | if(MSVC) 13 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /std:c++latest") 14 | else() 15 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) 16 | endif() 17 | 18 | target_link_libraries(nesemulator ${SDL2_LIBRARIES}) -------------------------------------------------------------------------------- /Utils/typedefs.h: -------------------------------------------------------------------------------- 1 | #ifndef TYPEDEFS_H 2 | #define TYPEDEFS_H 3 | 4 | 5 | namespace nesemulator 6 | { 7 | class CPU; 8 | // Type declerations 9 | typedef uint8_t BYTE; 10 | typedef uint16_t ADDRESS; 11 | typedef uint16_t AUDIOINT; 12 | typedef uint32_t PIXEL; 13 | typedef uint32_t ADDRESS32; 14 | typedef uint64_t TIMER; 15 | typedef uint8_t FLAG; 16 | typedef uint32_t HERTZ; 17 | 18 | using OPEXEC = void; 19 | using OPEXEC_PTR = OPEXEC (CPU::*)(ADDRESS source); 20 | using ADDRESSING_MODE = ADDRESS(CPU::*)(); 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /Mapper/Mapper0.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPPER0_H 2 | #define MAPPER0_H 3 | 4 | 5 | #include "MapperBase.h" 6 | 7 | namespace nesemulator 8 | { 9 | class Mapper0 : public MapperBase 10 | { 11 | public: 12 | Mapper0(Cartridge* cart); 13 | BYTE MapReadCpu(ADDRESS address) override; 14 | void MapWriteCpu(ADDRESS address,BYTE value) override; 15 | BYTE MapReadPpu(ADDRESS address) override; 16 | void MapWritePpu(ADDRESS address,BYTE value) override; 17 | private: 18 | }; 19 | 20 | } 21 | 22 | 23 | #endif 24 | 25 | 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.slo 2 | *.lo 3 | *.o 4 | *.ttf 5 | *.obj 6 | *.gch 7 | *.pch 8 | *.so 9 | *.dylib 10 | *.dll 11 | *.mod 12 | *.smod 13 | *.lai 14 | *.la 15 | *.a 16 | *.lib 17 | *.exe 18 | *.out 19 | *.bin 20 | *.nes 21 | *.log 22 | */*.log 23 | */*.exe 24 | **/cmake-build-debug 25 | **/CMakeCache.txt 26 | **/cmake_install.cmake 27 | **/install_manifest.txt 28 | **/CMakeFiles/ 29 | **/CTestTestfile.cmake 30 | **/*.cbp 31 | **/CMakeScripts 32 | **/compile_commands.json 33 | */build/* 34 | build/* 35 | build/release/* 36 | build/release-cpp11/* 37 | build/debug-cpp11/* 38 | build/debug/* 39 | build/run/* 40 | build/logs/& 41 | .bash/ 42 | .vscode 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NESEmulator 2 | A NES console emulator in C++ with SDL2. 3 | 4 | It works with Mapper 0,1,2,3 games for now, but the emulator is still very WIP. 5 | 6 | ## Controllers 7 | 8 | | Emulator Buttons | Keyboard | 9 | |------------------|-------------| 10 | | START | S | 11 | | SELECT | A | 12 | | A | X | 13 | | B | C | 14 | | UP | UP ARROW | 15 | | DOWN | DOWN ARROW | 16 | | LEFT | LEFT ARROW | 17 | | RIGHT | RIGHT ARROW | 18 | 19 | 20 | ## Important Note 21 | 22 | This documentation is very temporary and going to be updated soon with all details. 23 | -------------------------------------------------------------------------------- /Cartridge/Cartridge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "Cartridge.h" 3 | 4 | namespace nesemulator 5 | { 6 | namespace 7 | { 8 | std::string bTohex(uint32_t n, uint8_t d) 9 | { 10 | std::string s(d, '0'); 11 | for (int i = d - 1; i >= 0; i--, n >>= 4) 12 | s[i] = "0123456789ABCDEF"[n & 0xF]; 13 | return s; 14 | }; 15 | } 16 | 17 | std::ostream& operator<<(std::ostream& stream,Cartridge& cart) 18 | { 19 | stream << "CARTRIDGE INFO:\n" << "Number of PRG ROM : " << bTohex(cart.getPRGNum(),2) << std::endl; 20 | stream << "Number of CHR ROM : " << bTohex(cart.getCHRNum(),2) << std::endl << "Mapper ID: " << bTohex(cart.mapperID,2) << std::endl; 21 | return stream; 22 | } 23 | } -------------------------------------------------------------------------------- /Mapper/Mapper3.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPPER3_H 2 | #define MAPPER3_H 3 | 4 | 5 | #include "MapperBase.h" 6 | 7 | namespace nesemulator 8 | { 9 | class Mapper3 : public MapperBase 10 | { 11 | public: 12 | Mapper3(Cartridge* cart) : MapperBase(cart) { }; 13 | BYTE MapReadCpu(ADDRESS address) override; 14 | void MapWriteCpu(ADDRESS address,BYTE value) override; 15 | BYTE MapReadPpu(ADDRESS address) override; 16 | void MapWritePpu(ADDRESS address,BYTE value) override; 17 | private: 18 | BYTE selectedCHRbank = 0x00; // Selected CHR bank on Cartridge 19 | friend class NES; 20 | }; 21 | } 22 | 23 | 24 | 25 | #endif 26 | 27 | 28 | -------------------------------------------------------------------------------- /Mapper/Mapper3.cpp: -------------------------------------------------------------------------------- 1 | #include "Mapper3.h" 2 | 3 | 4 | 5 | namespace nesemulator 6 | { 7 | BYTE Mapper3::MapReadCpu(ADDRESS address) 8 | { 9 | if(address >= 0x8000 && address <= 0xFFFF) 10 | return cartridge->getPRGData((address - 0x8000) % (cartridge->getPRGNum() * 16 * 1024)); 11 | else 12 | return 0x00; 13 | } 14 | 15 | 16 | void Mapper3::MapWriteCpu(ADDRESS address,BYTE value) 17 | { 18 | selectedCHRbank = value & 0x03; 19 | } 20 | 21 | BYTE Mapper3::MapReadPpu(ADDRESS address) 22 | { 23 | return cartridge->getCHRData(selectedCHRbank * 0x2000 + address); 24 | } 25 | 26 | void Mapper3::MapWritePpu(ADDRESS address,BYTE value) 27 | { } 28 | } -------------------------------------------------------------------------------- /Mapper/Mapper2.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPPER2_H 2 | #define MAPPER2_H 3 | 4 | 5 | #include "MapperBase.h" 6 | 7 | namespace nesemulator 8 | { 9 | class Mapper2 : public MapperBase 10 | { 11 | public: 12 | Mapper2(Cartridge* cart); 13 | BYTE MapReadCpu(ADDRESS address) override; 14 | void MapWriteCpu(ADDRESS address,BYTE value) override; 15 | BYTE MapReadPpu(ADDRESS address) override; 16 | void MapWritePpu(ADDRESS address,BYTE value) override; 17 | private: 18 | BYTE lowerBankOffset = 0x00; // Offset for first 16KB switchable PRG ROM 19 | BYTE higherBankOffset = 0x00; // Offset for lasat 16KB fixed PRG ROM 20 | friend class NES; 21 | }; 22 | } 23 | 24 | #endif 25 | 26 | 27 | -------------------------------------------------------------------------------- /Utils/structs.h: -------------------------------------------------------------------------------- 1 | #ifndef STRUCTS_H 2 | #define STRUCTS_H 3 | 4 | #include "typedefs.h" 5 | #include "../APU/AudioWaves.h" 6 | 7 | namespace nesemulator 8 | { 9 | 10 | struct FileHeader // iNES File Header 11 | { 12 | char name[4]; // Validator for iNES files and versions 13 | BYTE PRGROMChunks; 14 | BYTE CHRROMChunks; 15 | BYTE mapper1; 16 | BYTE mapper2; 17 | BYTE PRGRAMSize; 18 | BYTE TVSystem1; 19 | BYTE TVSystem2; 20 | BYTE unused[5]; 21 | }; 22 | 23 | // CPU Instruction 24 | struct INSTRUCTION 25 | { 26 | OPEXEC_PTR operation; 27 | ADDRESSING_MODE addr; 28 | }; 29 | 30 | 31 | 32 | struct PIXEL_RGB // Pixel struct for display 33 | { 34 | BYTE r,g,b; 35 | }; 36 | 37 | } 38 | 39 | 40 | #endif -------------------------------------------------------------------------------- /Mapper/Mapper0.cpp: -------------------------------------------------------------------------------- 1 | #include "Mapper0.h" 2 | 3 | namespace nesemulator 4 | { 5 | Mapper0::Mapper0(Cartridge* cart) : MapperBase(cart) 6 | { } 7 | 8 | BYTE Mapper0::MapReadCpu(ADDRESS address) 9 | { 10 | if(address >= 0x8000 && address <= 0xFFFF) 11 | return cartridge->getPRGData((address - 0x8000) % (cartridge->getPRGNum() * 16 * 1024)); 12 | else 13 | return 0x00; 14 | } 15 | 16 | void Mapper0::MapWriteCpu(ADDRESS address,BYTE value) 17 | { 18 | 19 | } 20 | 21 | 22 | 23 | BYTE Mapper0::MapReadPpu(ADDRESS address) 24 | { 25 | return cartridge->getCHRData(address); 26 | } 27 | 28 | 29 | void Mapper0::MapWritePpu(ADDRESS address,BYTE value) 30 | { 31 | if(cartridge->getCHRNum() == 0) 32 | cartridge->setCHRData(address,value); 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Utils/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_H 2 | #define CONSTANTS_H 3 | 4 | namespace nesemulator 5 | { 6 | // Display size (Render size downsampled or upsampled to this size for displaying the frame) 7 | static constexpr int DISPLAY_WIDTH = 1024; 8 | static constexpr int DISPLAY_HEIGHT = 960; 9 | // Actual Render size 10 | static constexpr int RENDER_WIDTH = 256; 11 | static constexpr int RENDER_HEIGHT = 240; 12 | // Debug frame size 13 | static constexpr int DEBUG_WIDTH = 270; 14 | static constexpr int DEBUG_HEIGHT = 270; 15 | 16 | 17 | static constexpr double PI = 3.14159265359; 18 | 19 | 20 | // Maximum state count to store 21 | static constexpr int STATE_COUNT = 3; 22 | // Default state buffer size 23 | static constexpr int DEFAULT_STATE_BUFFER = 2048 * 1024; 24 | 25 | // Audio samples per frame 26 | static constexpr int SAMPLE_PER_FRAME = 8192; 27 | } 28 | 29 | #endif 30 | 31 | -------------------------------------------------------------------------------- /Utils/functions.h: -------------------------------------------------------------------------------- 1 | #ifndef FUNCTIONS_H 2 | #define FUNCTIONS_H 3 | 4 | #include "typedefs.h" 5 | 6 | namespace nesemulator 7 | { 8 | // Helper functions for bitwise operations 9 | static constexpr void SET_BIT(BYTE& byte,const BYTE& bit) { (byte) |= (1<<(bit));} 10 | static constexpr void CLEAR_BIT(BYTE& byte,const BYTE& bit) { (byte) &= ~((1)<<(bit));} 11 | static constexpr bool CHECK_BIT(const BYTE& byte,const BYTE& bit) { return (byte) & (1<<(bit));} 12 | 13 | static constexpr void reverseByte(BYTE* byte) 14 | { 15 | BYTE temp = 0x00; 16 | if(*byte & 0x01) temp |= 0x80; 17 | if(*byte & 0x02) temp |= 0x40; 18 | if(*byte & 0x04) temp |= 0x20; 19 | if(*byte & 0x08) temp |= 0x10; 20 | if(*byte & 0x10) temp |= 0x08; 21 | if(*byte & 0x20) temp |= 0x04; 22 | if(*byte & 0x40) temp |= 0x02; 23 | if(*byte & 0x80) temp |= 0x01; 24 | *byte = temp; 25 | } // Function to reverse a byte (binary) 26 | } 27 | #endif -------------------------------------------------------------------------------- /Mapper/Mapper2.cpp: -------------------------------------------------------------------------------- 1 | #include "Mapper2.h" 2 | 3 | namespace nesemulator 4 | { 5 | Mapper2::Mapper2(Cartridge* cart) : MapperBase(cart) 6 | { 7 | higherBankOffset = cartridge->getPRGNum() - 1; 8 | } 9 | 10 | 11 | 12 | BYTE Mapper2::MapReadCpu(ADDRESS address) 13 | { 14 | if(address < 0xC000) 15 | return cartridge->getPRGData(lowerBankOffset * 0x4000 + (address - 0x8000)); 16 | else 17 | return cartridge->getPRGData(higherBankOffset * 0x4000 + (address - 0xC000)); 18 | } 19 | 20 | 21 | void Mapper2::MapWriteCpu(ADDRESS address,BYTE value) 22 | { 23 | if(address >= 0x8000 && address <= 0xFFFF) 24 | lowerBankOffset = value & 0x0F; 25 | } 26 | 27 | 28 | BYTE Mapper2::MapReadPpu(ADDRESS address) 29 | { 30 | return cartridge->getCHRData(address); 31 | } 32 | 33 | 34 | void Mapper2::MapWritePpu(ADDRESS address,BYTE value) 35 | { 36 | if(cartridge->getCHRNum() == 0) 37 | cartridge->setCHRData(address,value); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /Mapper/Mapper1.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPPER1_H 2 | #define MAPPER1_H 3 | 4 | #include "MapperBase.h" 5 | #include "../Cartridge/Cartridge.h" 6 | 7 | namespace nesemulator 8 | { 9 | class Mapper1 : public MapperBase 10 | { 11 | public: 12 | Mapper1(Cartridge *cart); 13 | BYTE MapReadCpu(ADDRESS address) override; 14 | void MapWriteCpu(ADDRESS address,BYTE value) override; 15 | BYTE MapReadPpu(ADDRESS address) override; 16 | void MapWritePpu(ADDRESS address,BYTE value) override; 17 | private: 18 | void resetRegisters(); 19 | void updatePRG(); 20 | BYTE PRGRAM[8*1024]; 21 | friend class NES; 22 | struct { 23 | BYTE controlReg = 0x00,tempReg = 0x00,tempRegNum = 0x00; 24 | } REGISTERS; 25 | BYTE CHR0 = 0x00; 26 | BYTE CHR1 = 0x00; 27 | BYTE CHRFull = 0x00; 28 | BYTE PRG0 = 0x00; 29 | BYTE PRG1 = 0x00; 30 | BYTE PRGFull = 0x00; 31 | }; 32 | } 33 | 34 | 35 | #endif -------------------------------------------------------------------------------- /Utils/Queue.h: -------------------------------------------------------------------------------- 1 | #ifndef QUEUE_H 2 | #define QUEUE_H 3 | 4 | 5 | template 6 | class Queue 7 | { 8 | public: 9 | Queue() { head = new T[capacity]; }; 10 | ~Queue() { delete [] head;} 11 | void insert(const T& val); 12 | T pop(); 13 | const size_t size() const { return total; }; 14 | private: 15 | T* head; 16 | size_t total = 0; 17 | size_t capacity = 100000; 18 | void increaseCapacity(); 19 | }; 20 | 21 | 22 | template 23 | void Queue::insert(const T& val) 24 | { 25 | if(++total == capacity) 26 | increaseCapacity(); 27 | head[total - 1] = val; 28 | } 29 | 30 | template 31 | T Queue::pop() 32 | { 33 | T val = head[0]; 34 | for(int i = 1;i < total;i++) 35 | head[i - 1] = head[i]; 36 | total--; 37 | return val; 38 | } 39 | 40 | 41 | template 42 | void Queue::increaseCapacity() 43 | { 44 | auto oldHead = head; 45 | capacity += 10000; 46 | head = new T[capacity]; 47 | delete [] oldHead; 48 | } 49 | 50 | #endif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ozan Armağan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /APU/APU.h: -------------------------------------------------------------------------------- 1 | #ifndef APU_H 2 | #define APU_H 3 | 4 | #include "../Utils/handler.h" 5 | #include "AudioWaves.h" 6 | #include "../Utils/AudioFilters.h" 7 | 8 | namespace nesemulator 9 | { 10 | class APU 11 | { 12 | public: 13 | APU(); 14 | void writeToMemory(ADDRESS address,BYTE value); 15 | void tick(); 16 | float output(); 17 | private: 18 | void halfTick(); 19 | void quarterTick(); 20 | PulseWave pulse1 = PulseWave(1); 21 | PulseWave pulse2 = PulseWave(0); 22 | TriangleWave triangle; 23 | NoiseWave noise; 24 | DMC dmc; 25 | float pulseTable[31]; 26 | float tndTable[203]; 27 | TIMER clock; 28 | FLAG incomingFrameCounterReset; 29 | BYTE countToFrameCounterReset; 30 | AUDIOINT frameCounter = 0; 31 | enum class FRAMECOUNTERMODE { MODE4,MODE5} frameCounterMode; 32 | HighPassFilter high1; 33 | HighPassFilter high2; 34 | LowPassFilter low; 35 | friend class NES; 36 | }; 37 | } 38 | 39 | 40 | 41 | #endif -------------------------------------------------------------------------------- /Mapper/MapperBase.h: -------------------------------------------------------------------------------- 1 | #ifndef MAPPER_H 2 | #define MAPPER_H 3 | 4 | #include "../Utils/handler.h" 5 | #include "../Cartridge/Cartridge.h" 6 | 7 | 8 | namespace nesemulator 9 | { 10 | /** 11 | This is an abstract base class for implementation of each mapper 12 | All the mappers MUST be derived from this class 13 | */ 14 | class MapperBase 15 | { 16 | public: 17 | MapperBase(Cartridge *cartridge_): cartridge(cartridge_) { }; 18 | virtual BYTE MapReadCpu(ADDRESS address) = 0; 19 | virtual void MapWriteCpu(ADDRESS address,BYTE value) = 0; 20 | virtual BYTE MapReadPpu(ADDRESS address) = 0; 21 | virtual void MapWritePpu(ADDRESS address,BYTE value) = 0; 22 | void setCartridge(Cartridge *cart) { cartridge = cart; }; 23 | void setMirroring(MIRRORING mirrorirngType) { mirroring = mirrorirngType; }; 24 | MIRRORING getMirroring() { return mirroring; }; 25 | protected: 26 | friend class NES; 27 | Cartridge *cartridge; 28 | MIRRORING mirroring = MIRRORING::HORIZONTAL; 29 | }; 30 | } 31 | 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /Controller/Controller.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLLER_H 2 | #define CONTROLLER_H 3 | 4 | 5 | #include 6 | #include "../Utils/handler.h" 7 | #include "../Bus/Bus.h" 8 | 9 | namespace nesemulator 10 | { 11 | /** 12 | * Controller class to store current controller inputs 13 | * 14 | */ 15 | class Controller 16 | { 17 | public: 18 | Controller(Bus* bus); 19 | bool getKeyStatus(int index) { return keys[index]; }; 20 | void setKeys(const BYTE* keys_) { keys = keys_; }; 21 | void setJoyButton(int id,int which); 22 | void clearJoyButton(int id,int which); 23 | void setJoyAxis(int id,int value,int which); 24 | const bool& getSaveButtonStatus() { return saveStateButton; } 25 | const bool& getLoadButtonStatus() { return loadStateButton; } 26 | void setKey(int id); 27 | void clearKey(int id); 28 | void handleInput(); 29 | private: 30 | void getKeyState() {const BYTE* keys_ = SDL_GetKeyboardState(NULL);keys = keys_;}; 31 | friend class NES; 32 | const BYTE *keys; //SDL Keys 33 | Bus* bus; 34 | BYTE controllerStatus[2] = {0x00, 0x00}; 35 | bool saveStateButton; 36 | bool loadStateButton; 37 | SDL_GameController *contorller1 = nullptr; 38 | SDL_GameController *contorller2 = nullptr; 39 | }; 40 | 41 | } 42 | 43 | #endif -------------------------------------------------------------------------------- /Cartridge/Cartridge.h: -------------------------------------------------------------------------------- 1 | #ifndef CARTRIDGE_H 2 | #define CARTRIDGE_H 3 | 4 | #include "../Utils/handler.h" 5 | 6 | 7 | 8 | namespace nesemulator 9 | { 10 | /** 11 | * Cartridge class to store ROM data 12 | */ 13 | class Cartridge 14 | { 15 | public: 16 | BYTE getPRGNum() { return PRGNum; }; 17 | BYTE getCHRNum() { return CHRNum; }; 18 | BYTE getPRGData(ADDRESS32 address) { return PRGmemory.get()[address]; }; 19 | BYTE getCHRData(ADDRESS32 address) { return CHRmemory.get()[address]; }; 20 | BYTE getMapperID() { return mapperID; }; 21 | void setPRGData(ADDRESS address,BYTE value) { PRGmemory.get()[address] = value; }; 22 | void setCHRData(ADDRESS address,BYTE value) { CHRmemory.get()[address] = value; }; 23 | void setMapperID(int ID) { mapperID = ID; }; 24 | void loadPRGData(BYTE* PRGHead) { PRGmemory = std::unique_ptr(PRGHead); }; 25 | void loadCHRData(BYTE* CHRHead) { CHRmemory = std::unique_ptr(CHRHead); }; 26 | void setPRGNum(BYTE _PRGNum) { PRGNum = _PRGNum;}; 27 | void setCHRNum(BYTE _CHRNum) { CHRNum = _CHRNum;}; 28 | friend std::ostream& operator<<(std::ostream& stream,Cartridge& cart); 29 | private: 30 | friend class NES; 31 | std::unique_ptr PRGmemory; 32 | std::unique_ptr CHRmemory; 33 | BYTE mapperID; 34 | BYTE PRGNum; 35 | BYTE CHRNum; 36 | }; 37 | } 38 | 39 | #endif -------------------------------------------------------------------------------- /PPU/PPU.h: -------------------------------------------------------------------------------- 1 | #ifndef PPU_H 2 | #define PPU_H 3 | 4 | 5 | //#define PPUDEBUG 6 | 7 | #include "../Utils/handler.h" 8 | #include "../Bus/Bus.h" 9 | #include "../Cartridge/Cartridge.h" 10 | #include "../Bus/PPUBus.h" 11 | #include "../Display/Display.h" 12 | #include "../Utils/Array.h" 13 | 14 | #ifdef PPUDEBUG 15 | 16 | #include 17 | 18 | #endif 19 | 20 | 21 | 22 | 23 | namespace nesemulator 24 | { 25 | class PPU 26 | { 27 | public: 28 | PPU(Display* display,Bus* mem,Cartridge* cartridge,PPUBus* ppuBus) : display(display),mainBus(mem),cartridge(cartridge),ppuBus(ppuBus) { 29 | #ifdef PPUDEBUG 30 | ppuLog = fopen("./Logs/ppu.log","w+"); 31 | #endif 32 | }; 33 | void tick(); 34 | const FLAG inline isFrameDone() { return frameDone; }; 35 | void clearFrameDone() { frameDone = false; }; 36 | friend std::ostream& operator<<(std::ostream &out,PPU &ppu); // For logging stuff 37 | #ifdef DEBUG 38 | void getPatternTable(); 39 | #endif 40 | private: 41 | friend class NES; 42 | Bus* mainBus; 43 | Cartridge* cartridge; 44 | PPUBus* ppuBus; 45 | Display* display; 46 | BYTE PPU_BUFFER; 47 | short int col = 0,row = 0; 48 | FLAG frameDone = false; 49 | Array nextRowSprites; 50 | void shift(); 51 | void frameReset(); 52 | void setSpritesForNextRow(); 53 | void loadSpritesForNextRow(); 54 | void setBackgroundPixel(); 55 | void setForegroundPixel(); 56 | void getFinalPixel(); 57 | void loadShifters(); 58 | BYTE pixel = 0x00; 59 | BYTE palette = 0x00; 60 | FLAG spriteZeroIndicator = false; 61 | FLAG spriteZero = false; 62 | FLAG odd = false; 63 | #ifdef PPUDEBUG 64 | FILE* ppuLog; 65 | void log(); 66 | #endif 67 | 68 | }; 69 | } 70 | 71 | #endif -------------------------------------------------------------------------------- /Bus/Bus.h: -------------------------------------------------------------------------------- 1 | #ifndef BUS_H 2 | #define BUS_H 3 | 4 | #include 5 | 6 | #include "../Utils/handler.h" 7 | #include "PPUBus.h" 8 | #include "../Cartridge/Cartridge.h" 9 | #include "../Mapper/MapperBase.h" 10 | #include "../APU/APU.h" 11 | 12 | namespace nesemulator 13 | { 14 | /** 15 | * Main bus of the emulator 16 | * 17 | * Maps addresses of read and write requests from CPU according to memory map of the console 18 | * Operates DMA process for PPU 19 | * Stores internal RAM of 2KB 20 | */ 21 | class Bus 22 | { 23 | public: 24 | Bus(PPUBus& ppuBus,APU& apu) : ppuBus(ppuBus),apu(apu) { }; 25 | BYTE readFromMemory(ADDRESS address); 26 | void writeToMemory(ADDRESS address,BYTE value); 27 | void clearMemoryBlock(ADDRESS start,ADDRESS end); 28 | void setMapper(std::shared_ptr _mapper) { mapper = _mapper; }; 29 | void setControllerMemory(BYTE controllers[2]) { controllerCache[0] = controllers[0]; controllerCache[1] = controllerCache[1]; }; 30 | bool getDMAStatus() const { return DMA; }; 31 | void print(int end = 20); 32 | friend class NES; 33 | private: 34 | void DMAReadNext(){ DMA_data = readFromMemory((DMA_high << 8) | (DMA_low)); }; 35 | void writeOAM() { ppuBus.writeOAM(DMA_low++, DMA_data); }; 36 | BYTE memory[2048]; // CPU RAM 37 | BYTE controllerCache[2]; // Temporary cache to write in every frame 38 | BYTE controllerMemory[2]; // Bytes for controllers 39 | PPUBus& ppuBus; 40 | APU& apu; 41 | std::shared_ptr mapper; 42 | bool DMA = false,DMA_dummy = true; 43 | BYTE DMA_low = 0x00; 44 | BYTE DMA_high = 0x00; 45 | BYTE DMA_data = 0x00; 46 | }; 47 | } 48 | 49 | 50 | #endif -------------------------------------------------------------------------------- /System/System.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include "../Utils/handler.h" 5 | #include "../State/StateManagement.h" 6 | #include "../Cartridge/Cartridge.h" 7 | #include "../Mapper/MapperBase.h" 8 | #include "../Mapper/Mapper0.h" 9 | #include "../Mapper/Mapper1.h" 10 | #include "../Mapper/Mapper2.h" 11 | #include "../Mapper/Mapper3.h" 12 | #include "../Bus/Bus.h" 13 | #include "../Bus/PPUBus.h" 14 | #include "../PPU/PPU.h" 15 | #include "../CPU/CPU.h" 16 | #include "../APU/APU.h" 17 | #include "../Display/Display.h" 18 | #include "../Controller/Controller.h" 19 | #include "../Audio/Audio.h" 20 | #include "../Utils/Queue.h" 21 | 22 | 23 | 24 | namespace nesemulator 25 | { 26 | /** 27 | * Wrapper and master class for the emulator 28 | * 29 | * Controls main loop of the system 30 | */ 31 | class NES 32 | { 33 | public: 34 | NES(); 35 | NES(std::string fileName); 36 | void insertNESFile(std::string fileName); 37 | void start(); 38 | void reset(); 39 | void log(); 40 | private: 41 | Cartridge cartridge; 42 | std::shared_ptr mapper; 43 | PPUBus ppuBus; 44 | Bus bus; 45 | PPU ppu; 46 | APU apu; 47 | CPU cpu; 48 | Display display; 49 | void setMapper(); 50 | void mainLoop(); 51 | void tick(); 52 | void DMA(); 53 | TIMER clock = 0; 54 | SDL_Event events; 55 | Controller controller; 56 | double innerClock = 0; 57 | inline static double cyclesPerSample = 121.53; 58 | void loadState(int index); 59 | void saveCurrentState(); 60 | void loadState(); 61 | std::string fileName; 62 | bool hasSaved = false,hasLoaded = false; 63 | }; 64 | } -------------------------------------------------------------------------------- /Utils/AudioFilters.h: -------------------------------------------------------------------------------- 1 | #ifndef FILTER_H 2 | #define FILTER_H 3 | 4 | #include "handler.h" 5 | 6 | namespace nesemulator 7 | { 8 | class FilterBase 9 | { 10 | public: 11 | FilterBase(HERTZ freq) { RC = 1.0 / ( 2 * PI * double(freq)); dT = 1.0 / 44100.0; }; 12 | virtual double filter(double input) = 0; 13 | protected: 14 | double RC,dT; 15 | }; 16 | 17 | 18 | /* 19 | From wikipedia, 20 | function lowpass(real[0..n] x, real dt, real RC) 21 | var real[0..n] y 22 | var real α := dt / (RC + dt) 23 | y[0] := α * x[0] 24 | for i from 1 to n 25 | y[i] := α * x[i] + (1-α) * y[i-1] 26 | return y 27 | 28 | https://en.wikipedia.org/wiki/Low-pass_filter 29 | */ 30 | class LowPassFilter : FilterBase 31 | { 32 | public: 33 | LowPassFilter(HERTZ freq) : FilterBase(freq) { a = dT / (dT + RC); }; 34 | virtual double filter(double input) final { prevY = prevY + a * (input - prevX); prevX = input; return prevY; }; 35 | private: 36 | double a; 37 | double prevX = 0; 38 | double prevY = 0; 39 | }; 40 | 41 | 42 | /* 43 | From wikipedia, 44 | function highpass(real[1..n] x, real dt, real RC) 45 | var real[1..n] y 46 | var real α := RC / (RC + dt) 47 | y[1] := x[1] 48 | for i from 2 to n 49 | y[i] := α × y[i−1] + α × (x[i] − x[i−1]) 50 | return y 51 | 52 | https://en.wikipedia.org/wiki/High-pass_filter 53 | */ 54 | class HighPassFilter : FilterBase 55 | { 56 | public: 57 | HighPassFilter(HERTZ freq) : FilterBase(freq) { a = RC / (dT + RC); }; 58 | virtual double filter(double input) final { prevY = a * (prevY + input - prevX); prevX = input; return prevY; }; 59 | private: 60 | double a; 61 | double prevX = 0; 62 | double prevY = 0; 63 | }; 64 | } 65 | 66 | 67 | 68 | #endif -------------------------------------------------------------------------------- /Display/Display.h: -------------------------------------------------------------------------------- 1 | #ifndef DISPLAY_H 2 | #define DISPLAY_H 3 | 4 | #define SDL_MAIN_HANDLED 5 | #include "../Utils/handler.h" 6 | #include "../Controller/Controller.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../UI/NotificationManager.h" 13 | 14 | namespace nesemulator 15 | { 16 | /** 17 | * Display class to store pixels of frames and display it with SDL2 library utilities 18 | * 19 | */ 20 | class Display 21 | { 22 | public: 23 | Display(SDL_Event* event,Controller& controller); 24 | ~Display() { SDL_DestroyTexture(texture); SDL_DestroyWindow(window); SDL_DestroyRenderer(renderer); }; 25 | void init(); //Init display 26 | void initDebug(); // Init debug window 27 | void setPixel(int x, int y, PIXEL_RGB pixelColors); // Set pixel in the pixel matrix 28 | void frameDone(); // Transfer next frame to current frame 29 | void renderFrame(); // Render current frame 30 | ui::NotificationManager& getNotificationManager() { return notificationManager; }; 31 | #ifdef DEBUG 32 | void renderDebugFrame(); // Render Debug Frame 33 | void setPixelDebug(int x, int y, PIXEL_RGB colors); // Set pixel for debug window 34 | #endif 35 | private: 36 | PIXEL currentFrame[RENDER_WIDTH * RENDER_HEIGHT]; // Pixel array of current frame 37 | PIXEL nextFrame[RENDER_WIDTH * RENDER_HEIGHT]; // Pixel array of next frame 38 | #ifdef DEBUG 39 | PIXEL debug[DEBUG_WIDTH * DEBUG_HEIGHT]; // Pixel array of debug frame 40 | #endif 41 | SDL_Window *window; 42 | SDL_Renderer *renderer; 43 | SDL_Texture *texture; 44 | SDL_Window *debugWindow; 45 | SDL_Renderer *Drenderer; 46 | SDL_Texture *Dtexture; 47 | SDL_Event *eventPtr; 48 | Controller& controller; 49 | TTF_Font *font; 50 | TIMER interval0 = 0,interval1 = 0; //Interval between frames to track FPS 51 | int pitch = RENDER_WIDTH * sizeof(PIXEL); 52 | ui::NotificationManager notificationManager; 53 | }; 54 | } 55 | 56 | #endif -------------------------------------------------------------------------------- /UI/NotificationManager.h: -------------------------------------------------------------------------------- 1 | #ifndef NOTIFICATIONMANAGER_H 2 | #define NOTIFICATIONMANAGER_H 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | #include "../Utils/handler.h" 12 | #include "Notification.h" 13 | 14 | namespace nesemulator 15 | { 16 | namespace ui 17 | { 18 | 19 | class NotificationManager 20 | { 21 | public: 22 | NotificationManager(SDL_Renderer* renderer, TTF_Font* font); 23 | 24 | void addNotification(const std::string& message, const SDL_Color& color, TIMER durationInMilliseconds); 25 | void removeAllNotifications(); 26 | void update(); 27 | void render(); 28 | void setRenderer(SDL_Renderer* renderer){ this->renderer = renderer; }; 29 | void setFont(TTF_Font* font){ this->font = font; }; 30 | 31 | private: 32 | std::vector notifications; 33 | SDL_Renderer* renderer; 34 | TTF_Font* font; 35 | TIMER lastSystemTime; 36 | }; 37 | 38 | inline NotificationManager::NotificationManager(SDL_Renderer* renderer, TTF_Font* font) 39 | { 40 | this->renderer = renderer; 41 | this->font = font; 42 | lastSystemTime = SDL_GetTicks64(); 43 | } 44 | 45 | inline void NotificationManager::addNotification(const std::string& message, const SDL_Color& color, TIMER duration) 46 | { 47 | notifications.push_back(Notification(message, duration, color, font, renderer)); 48 | } 49 | 50 | inline void NotificationManager::removeAllNotifications() 51 | { 52 | notifications.clear(); 53 | } 54 | 55 | inline void NotificationManager::update() 56 | { 57 | auto temp = SDL_GetTicks64(); 58 | for (auto& notification : notifications) 59 | { 60 | notification.update(temp - lastSystemTime); 61 | if(notification.isFinished()) 62 | notifications.erase(std::remove(notifications.begin(), notifications.end(), notification), notifications.end()); 63 | } 64 | 65 | lastSystemTime = temp; 66 | } 67 | 68 | inline void NotificationManager::render() 69 | { 70 | auto deltaHeight = 0; 71 | for (auto& notification : notifications) 72 | { 73 | notification.draw(0, deltaHeight); 74 | deltaHeight += 8; 75 | } 76 | } 77 | 78 | 79 | 80 | } 81 | 82 | } 83 | 84 | 85 | 86 | 87 | #endif -------------------------------------------------------------------------------- /Bus/Bus.cpp: -------------------------------------------------------------------------------- 1 | #include "Bus.h" 2 | #include 3 | #include 4 | 5 | namespace nesemulator 6 | { 7 | namespace{ 8 | std::string bTohex(uint32_t n, uint8_t d) 9 | { 10 | std::string s(d, '0'); 11 | for (int i = d - 1; i >= 0; i--, n >>= 4) 12 | s[i] = "0123456789ABCDEF"[n & 0xF]; 13 | return s; 14 | }; 15 | } 16 | 17 | BYTE Bus::readFromMemory(ADDRESS address) 18 | { 19 | if(address >= 0x0000 && address <= 0x1FFF) 20 | return memory[address & 0x07FF]; // Bitwise and because of mirroring 21 | else if(address >= 0x2000 && address <= 0x3FFF) 22 | return ppuBus.readFromMemory_mainBus(address & 0x0007); // Same for PPU 23 | else if(address >= 0x4016 && address <= 0x4017) 24 | { 25 | BYTE data = (controllerMemory[address & 0x0001] & 0x80) > 0; 26 | address &= 0x0001; 27 | controllerMemory[address] <<= 1; 28 | return data; 29 | } 30 | else if(address >= 0x4800 && address <= 0xFFFF) 31 | return mapper->MapReadCpu(address); 32 | else 33 | return 0; 34 | } 35 | 36 | void Bus::writeToMemory(ADDRESS address,BYTE value) 37 | { 38 | if(address >= 0x0000 && address <= 0x1FFF) 39 | memory[address & 0x07FF] = value; 40 | else if(address >= 0x2000 && address <= 0x3FFF) 41 | ppuBus.writeToMemory_mainBus(address & 0x0007,value); 42 | else if((address >= 0x4000 && address <= 0x4013) || address == 0x4015 || address == 0x4017) 43 | apu.writeToMemory(address,value); 44 | else if(address == 0x4014) // DMA Activated 45 | { 46 | DMA = true; 47 | DMA_low = 0x00; 48 | DMA_high = value; 49 | } 50 | else if(address >= 0x4016 && address <= 0x4017) 51 | controllerMemory[address & 0x0001] = controllerCache[address & 0x0001]; 52 | else if(address >= 0x4800 && address <= 0xFFFF) 53 | mapper->MapWriteCpu(address,value); 54 | } 55 | 56 | void Bus::clearMemoryBlock(ADDRESS start,ADDRESS end) // For debugging, not necessary 57 | { 58 | for(ADDRESS i = start;i < end + 1;i++) 59 | if(i >= 0x0000 && i <= 0x1FFF) 60 | memory[i & 0x07FF] = 0x00; 61 | } 62 | 63 | void Bus::print(int end) 64 | { 65 | std::cout << "Bus: " << std::endl; 66 | for(int i = 0;i < end;i++) 67 | { 68 | std::cout << std::hex << bTohex(memory[i],2) << " "; 69 | if(i != 0 && i % 8 == 0) 70 | std::cout << std::endl; 71 | } 72 | std::cout << std::endl; 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /Mapper/Mapper1.cpp: -------------------------------------------------------------------------------- 1 | #include "Mapper1.h" 2 | 3 | namespace nesemulator 4 | { 5 | 6 | Mapper1::Mapper1(Cartridge* cart) : MapperBase(cart) 7 | { 8 | REGISTERS.controlReg = 0x1C; 9 | PRG1 = cartridge->getPRGNum() - 1; 10 | } 11 | 12 | 13 | void Mapper1::resetRegisters() 14 | { 15 | REGISTERS.controlReg |= 0x0C; 16 | REGISTERS.tempReg = 0x00; 17 | REGISTERS.tempRegNum = 0x00; 18 | updatePRG(); 19 | } 20 | 21 | 22 | BYTE Mapper1::MapReadCpu(ADDRESS address) 23 | { 24 | if(address < 0x8000) 25 | return PRGRAM[address % 8192]; 26 | else if(address <= 0xFFFF) 27 | { 28 | if(REGISTERS.controlReg & 0x08) 29 | { 30 | if(address < 0xC000) 31 | return cartridge->getPRGData(PRG0 * 0x4000 + (address & 0x3FFF)); 32 | else 33 | return cartridge->getPRGData(PRG1 * 0x4000 + (address & 0x3FFF)); 34 | } 35 | else 36 | return cartridge->getPRGData(PRGFull * 0x8000 + (address & 0x7FFF)); 37 | } 38 | else 39 | return 0x00; 40 | } 41 | 42 | 43 | void Mapper1::updatePRG() 44 | { 45 | BYTE mode = (REGISTERS.controlReg >> 2) & 0x03; 46 | if(mode < 2) 47 | PRGFull = (REGISTERS.tempReg & 0x0E) >> 1; 48 | else if(mode == 2) 49 | { 50 | PRG0 = 0x00; 51 | PRG1 = REGISTERS.tempReg & 0x0F; 52 | } 53 | else 54 | { 55 | PRG0 = REGISTERS.tempReg & 0x0F; 56 | PRG1 = cartridge->getPRGNum() - 1; 57 | } 58 | } 59 | 60 | 61 | 62 | void Mapper1::MapWriteCpu(ADDRESS address,BYTE value) 63 | { 64 | if(address < 0x8000) 65 | PRGRAM[address % 8192] = value; 66 | else 67 | { 68 | if(value & 0x80) // Reset if MSB set to 1 69 | resetRegisters(); 70 | else 71 | { 72 | REGISTERS.tempReg = ((value & 0x01) << 4) | (REGISTERS.tempReg >> 1); 73 | if(++REGISTERS.tempRegNum == 5) 74 | { 75 | if(address < 0xA000) 76 | { 77 | REGISTERS.controlReg = REGISTERS.tempReg & 0x1F; 78 | switch (REGISTERS.controlReg & 0x03) 79 | { 80 | case 0: setMirroring(MIRRORING::ONSCREENLOWER) ;break; 81 | case 1: setMirroring(MIRRORING::ONSCREENHIGHER);break; 82 | case 2: setMirroring(MIRRORING::VERTICAL) ;break; 83 | case 3: setMirroring(MIRRORING::HORIZONTAL) ;break; 84 | } 85 | } 86 | else if(address < 0xC000) 87 | { 88 | if(REGISTERS.controlReg & 0x10) 89 | CHR0 = REGISTERS.tempReg & 0x1F; 90 | else 91 | CHRFull = REGISTERS.tempReg & 0x1E; 92 | } 93 | else if(address < 0xE000) 94 | CHR1 = ((REGISTERS.controlReg & 0x10) ? (REGISTERS.tempReg & 0x1F): CHR1); 95 | else 96 | updatePRG(); 97 | REGISTERS.tempReg = 0x00; 98 | REGISTERS.tempRegNum = 0; 99 | } 100 | } 101 | } 102 | } 103 | 104 | 105 | 106 | BYTE Mapper1::MapReadPpu(ADDRESS address) 107 | { 108 | if(cartridge->getCHRNum() == 0) 109 | return cartridge->getCHRData(address); 110 | if(REGISTERS.controlReg & 0x10) 111 | { 112 | if(address < 0x1000) 113 | return cartridge->getCHRData(CHR0 * 4096 + (address & 0x0FFF)); 114 | else 115 | return cartridge->getCHRData(CHR1 * 4096 + (address & 0x0FFF)); 116 | } 117 | else 118 | return cartridge->getCHRData(CHRFull * 8192 + (address & 0x1FFF)); 119 | return 0x00; 120 | } 121 | 122 | 123 | void Mapper1::MapWritePpu(ADDRESS address,BYTE value) 124 | { 125 | if(cartridge->getCHRNum() == 0) 126 | cartridge->setCHRData(address,value); 127 | 128 | } 129 | } -------------------------------------------------------------------------------- /Bus/PPUBus.h: -------------------------------------------------------------------------------- 1 | #ifndef PPUBUS_H 2 | #define PPUBUS_H 3 | 4 | #include 5 | 6 | #include "../Utils/handler.h" 7 | #include "../Mapper/MapperBase.h" 8 | #include "../Utils/Array.h" 9 | 10 | namespace nesemulator 11 | { 12 | /** 13 | * Bus of PPU to move and store data for PPU operations and requests from main bus to read and write to PPU memory 14 | * 15 | */ 16 | class PPUBus 17 | { 18 | public: 19 | PPUBus(); 20 | BYTE readFromMemory_mainBus(ADDRESS address); 21 | void writeToMemory_mainBus(ADDRESS address,BYTE value); 22 | BYTE readFromMemory(ADDRESS address); 23 | void writeToMemory(ADDRESS address,BYTE value); 24 | void setMapper(std::shared_ptr _mapper); 25 | bool getNMI() const {return NMI; }; 26 | void setNMI(bool val) { NMI = val; }; 27 | void writeOAM(BYTE address,BYTE value) { ((BYTE*)OAM)[address] = value; }; 28 | BYTE getOAMOffset() const { return OAM_offset; }; 29 | friend class PPU; 30 | friend class Bus; 31 | private: 32 | BYTE nameTables[4096]; 33 | BYTE palettes[32]; 34 | PIXEL_RGB colors[64]; 35 | std::shared_ptr mapper; 36 | bool addressToggle = false; 37 | BYTE ppuBuffer = 0x00; 38 | bool NMI = false; 39 | BYTE BG_PIXEL = 0x00; 40 | BYTE BG_PALETTE = 0x00; 41 | BYTE FG_PIXEL = 0x00; 42 | BYTE FG_PALETTE = 0x00; 43 | BYTE FG_PRIORITY = 0x00; 44 | friend class NES; 45 | union 46 | { 47 | struct 48 | { 49 | FLAG unused : 5; 50 | FLAG SPRT_OVERFLOW : 1; 51 | FLAG SPRT_ZERO_HIT : 1; 52 | FLAG VBLANK : 1; 53 | }; 54 | BYTE combined; 55 | } PPUSTATUS; 56 | 57 | union 58 | { 59 | struct 60 | { 61 | FLAG GRAY_SCALE : 1; 62 | FLAG RENDER_BCKGRND_LEFT : 1; 63 | FLAG RENDER_SPRTS_LEFT : 1; 64 | FLAG RENDER_BCKGRD : 1; 65 | FLAG RENDER_SPRTS : 1; 66 | FLAG ENCHANCED_R : 1; 67 | FLAG ENCHANCED_G : 1; 68 | FLAG ENCHANCED_B : 1; 69 | }; 70 | BYTE combined; 71 | } PPUMASK; 72 | 73 | union 74 | { 75 | struct 76 | { 77 | FLAG NAMETABLE_X : 1; 78 | FLAG NAMETABLE_Y : 1; 79 | FLAG INCREMENT_MODE : 1; 80 | FLAG PATTERN_SPRT : 1; 81 | FLAG PATTERN_BCKGRND : 1; 82 | FLAG SPRT_SIZE : 1; 83 | FLAG SLAVE : 1; 84 | FLAG ENABLE_NMI : 1; 85 | }; 86 | BYTE combined; 87 | } PPUCTRL; 88 | 89 | struct{ 90 | BYTE BG_NEXT_ID = 0x00; 91 | BYTE BG_NEXT_LOW = 0x00; 92 | BYTE BG_NEXT_HIGH = 0x00; 93 | BYTE BG_NEXT_ATTR = 0x00; 94 | } BG_RENDER_FETCH; 95 | 96 | 97 | union SHIFTER 98 | { 99 | struct { 100 | ADDRESS NEXT : 8; 101 | ADDRESS CURRENT : 8; 102 | }; 103 | ADDRESS combined = 0x0000; 104 | } BG_SHIFTER_PATTERN_LOW; 105 | 106 | SHIFTER BG_SHIFTER_PATTERN_HIGH; 107 | 108 | SHIFTER BG_SHIFTER_ATTR_LOW; 109 | 110 | SHIFTER BG_SHIFTER_ATTR_HIGH; 111 | union LOOPY 112 | { 113 | struct 114 | { 115 | ADDRESS CO_X : 5; 116 | ADDRESS CO_Y : 5; 117 | ADDRESS NT_X : 1; 118 | ADDRESS NT_Y : 1; 119 | ADDRESS FINE_Y : 3; 120 | ADDRESS UNUSED : 1; 121 | }; 122 | ADDRESS combined = 0x0000; 123 | }; 124 | 125 | LOOPY vRAM; 126 | LOOPY tempRAM; 127 | BYTE FINE_X = 0x00; 128 | 129 | OBJECT_ATTRIBUTE OAM[64]; 130 | BYTE OAM_offset = 0x00; 131 | 132 | Array SPRT_SHIFTER_LOW = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 133 | Array SPRT_SHIFTER_HIGH = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; 134 | BYTE SPRT_PATTERN_LOW = 0x00,SPRT_PATTERN_HIGH = 0x00; 135 | ADDRESS SPRT_PATTERN_ADDR_L = 0x0000; 136 | }; 137 | 138 | } 139 | 140 | #endif -------------------------------------------------------------------------------- /Utils/Array.h: -------------------------------------------------------------------------------- 1 | #ifndef ARRAY_H 2 | #define ARRAY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | class Array 11 | { 12 | public: 13 | Array(); 14 | Array(Array &other); 15 | Array(std::initializer_list list); 16 | Array(T* rawArray,const size_t& size); 17 | int inline getSize() const { return size; }; 18 | void clear(); 19 | void add(const T element); 20 | void insert(const T element,int index); 21 | T& operator[](int i) { return head[i]; }; 22 | inline T& last() { return head[size - 1]; }; 23 | inline T& first() {return *head; }; 24 | bool isEmpty() const { return size == 0; }; 25 | void operator=(const Array& other); 26 | const T* getRaw() { return head; } 27 | ~Array(); 28 | class iterator : public std::iterator 29 | { 30 | public: 31 | using diffType = typename std::iterator::difference_type; 32 | iterator() : ptr(nullptr) { }; 33 | iterator(T* rhs) : ptr(rhs) { }; 34 | iterator(const T& rhs) : ptr(rhs) { }; 35 | inline iterator& operator+=(diffType rhs) { ptr += rhs; return *this; }; 36 | inline iterator& operator-=(diffType rhs) { ptr -= rhs; return *this; }; 37 | inline T& operator*() const { return *ptr; }; 38 | inline T& operator->() const { return ptr; }; 39 | inline T& operator[](diffType rhs) const { return ptr[rhs]; }; 40 | inline iterator& operator++() { ptr++; return *this; }; 41 | inline iterator& operator--() { ptr--; return *this; }; 42 | inline iterator& operator++(int) { iterator temp(*this);++ptr; return temp; }; 43 | inline iterator& operator--(int) { iterator temp(*this);--ptr; return temp; }; 44 | inline diffType operator-(const iterator& rhs) const { return ptr - rhs.ptr; }; 45 | inline friend iterator operator+(diffType lhs, const iterator& rhs) { return iterator(lhs + rhs.ptr); }; 46 | inline friend iterator operator-(diffType lhs, const iterator& rhs) { return iterator(lhs - rhs.ptr); }; 47 | 48 | inline bool operator==(const iterator& rhs) const { return ptr == rhs.ptr; }; 49 | inline bool operator!=(const iterator& rhs) const { return ptr != rhs.ptr; }; 50 | inline bool operator<(const iterator& rhs) const { return ptr < rhs.ptr; }; 51 | inline bool operator>(const iterator& rhs) const { return ptr > rhs.ptr; }; 52 | inline bool operator<=(const iterator& rhs) const { return ptr <= rhs.ptr; }; 53 | inline bool operator>=(const iterator& rhs) const { return ptr >= rhs.ptr; }; 54 | private: 55 | T* ptr; 56 | }; 57 | inline iterator begin() { return iterator(head); }; 58 | inline iterator end() { return iterator(head + size); }; 59 | private: 60 | T* head; 61 | size_t size,capacity; 62 | bool isFull() const { return size == capacity; }; 63 | void increaseCapacity(); 64 | }; 65 | 66 | 67 | 68 | template 69 | Array::Array() : capacity(10000),size(0) 70 | { 71 | head = new T[capacity]; 72 | } 73 | 74 | template 75 | Array::Array(Array& other) : capacity(other.getCapacity()),size(other.getSize()) 76 | { 77 | head = new T[other.getCapacity()]; 78 | for(int i = 0;i < other.getSize();i++) 79 | head[i] = other[i]; 80 | } 81 | 82 | template 83 | Array::Array(std::initializer_list list) : capacity(10000),size(0) 84 | { 85 | head = new T[capacity]; 86 | for(auto element : list) 87 | add(element); 88 | } 89 | 90 | 91 | 92 | 93 | 94 | template 95 | void Array::add(T element) 96 | { 97 | head[size++] = element; 98 | if(isFull()) 99 | increaseCapacity(); 100 | } 101 | 102 | 103 | template 104 | void Array::insert(T element,int index) 105 | { 106 | if(index >= size || index < 0) 107 | return; 108 | for(int i = size;i > index;i--) 109 | head[i] = head[i-1]; 110 | head[index] = element; 111 | size++; 112 | if(isFull()) 113 | increaseCapacity(); 114 | } 115 | 116 | 117 | 118 | template 119 | void Array::clear() 120 | { 121 | std::memset(head, 0xFF, size * sizeof(T)); 122 | size = 0; 123 | } 124 | 125 | 126 | 127 | 128 | 129 | template 130 | Array::~Array() 131 | { 132 | delete [] head; 133 | } 134 | 135 | template 136 | Array::Array(T* rawArray,const size_t& size) 137 | { 138 | head = rawArray; 139 | this->size = size; 140 | this->capacity = size; 141 | increaseCapacity(); 142 | } 143 | 144 | template 145 | void Array::increaseCapacity() 146 | { 147 | auto oldHead = head; 148 | head = new T[capacity * 2]; 149 | capacity *= 2; 150 | for(int i = 0;i < size;i++) 151 | head[i] = oldHead[i]; 152 | } 153 | 154 | template 155 | void Array::operator=(const Array& other) 156 | { 157 | delete [] head; 158 | head = new T[other.capacity]; 159 | size = other.size; 160 | for(int i = 0;i < other.size;i++) 161 | head[i] = other.head[i]; 162 | } 163 | 164 | 165 | 166 | 167 | #endif -------------------------------------------------------------------------------- /Display/Display.cpp: -------------------------------------------------------------------------------- 1 | #include "Display.h" 2 | 3 | 4 | namespace nesemulator 5 | { 6 | 7 | Display::Display(SDL_Event* event,Controller& controller) : eventPtr(event),controller(controller),notificationManager(renderer,font) 8 | { 9 | if(SDL_Init(SDL_INIT_EVERYTHING) != 0) 10 | std::cout << "ERROR: SDL Couldn't initialized!\n"; 11 | init(); 12 | 13 | SDL_AudioSpec audiospec; 14 | 15 | audiospec.freq = 44100; 16 | audiospec.format = AUDIO_F32SYS; 17 | audiospec.samples = 735; 18 | audiospec.channels = 1; 19 | audiospec.callback = NULL; 20 | 21 | 22 | auto audio_open = SDL_OpenAudio(&audiospec,NULL); 23 | 24 | if(audio_open < 0) 25 | std::cout << "ERROR! " << SDL_GetError() << std::endl; 26 | 27 | std::cout << "Audio Device Initialized\n"; 28 | std::cout << "Device Name: " << SDL_GetAudioDeviceName(audio_open,0) << std::endl; 29 | std::cout << "Frequency: " << audiospec.freq << std::endl; 30 | std::cout << "Samples: " << audiospec.samples << std::endl; 31 | std::cout << "Channels: " << (int)audiospec.channels << std::endl; 32 | 33 | SDL_PauseAudio(0); 34 | 35 | 36 | #ifdef DEBUG 37 | initDebug(); 38 | #endif 39 | 40 | } 41 | 42 | void Display::init() 43 | { 44 | window = SDL_CreateWindow("NES Emulator - OzanArmagan", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, DISPLAY_WIDTH, DISPLAY_HEIGHT, 0); 45 | 46 | TTF_Init(); 47 | 48 | if(!window) 49 | { 50 | std::cout << "Failed to create window\n"; 51 | std::cout << "SDL2 Error: " << SDL_GetError() << "\n"; 52 | return; 53 | } 54 | else 55 | std::cout << "SDL Window created!\n", 56 | 57 | //SDL_SetWindowBordered(window,SDL_FALSE); 58 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); 59 | 60 | TTF_Font* font = TTF_OpenFont("./sans.ttf",36); 61 | 62 | notificationManager.setRenderer(renderer); 63 | notificationManager.setFont(font); 64 | 65 | texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, RENDER_WIDTH, RENDER_HEIGHT); 66 | 67 | SDL_RenderSetLogicalSize(renderer, RENDER_WIDTH, RENDER_HEIGHT); 68 | 69 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); 70 | 71 | } 72 | 73 | 74 | void Display::setPixel(int x, int y,PIXEL_RGB pixelColors) 75 | { 76 | PIXEL pixel = 0; 77 | 78 | pixel |= 255 << 24; 79 | pixel |= (PIXEL)pixelColors.r << 16; 80 | pixel |= (PIXEL)pixelColors.g << 8; 81 | pixel |= (PIXEL)pixelColors.b << 0; 82 | 83 | nextFrame[x + y * RENDER_WIDTH - 1] = pixel; 84 | } 85 | 86 | void Display::renderFrame() 87 | { 88 | 89 | SDL_RenderClear(renderer); 90 | SDL_UpdateTexture(texture, NULL, currentFrame, RENDER_WIDTH * sizeof(PIXEL)); 91 | SDL_RenderCopy(renderer, texture, NULL, NULL); 92 | 93 | notificationManager.render(); 94 | 95 | notificationManager.update(); 96 | 97 | 98 | SDL_RenderPresent(renderer); 99 | 100 | interval1 = interval0; 101 | interval0 = SDL_GetTicks(); 102 | 103 | SDL_SetWindowTitle(window,(std::string("NES Emulator FPS: " + std::to_string(1000.0 / (interval0 - interval1)))).c_str()); 104 | 105 | while(SDL_PollEvent(eventPtr)) 106 | { 107 | switch (eventPtr->type) 108 | { 109 | case SDL_QUIT: 110 | std::cout << "LOG: Quit has called! Good bye!\n"; 111 | exit(1); 112 | break; 113 | case SDL_CONTROLLERBUTTONDOWN: 114 | controller.setJoyButton(eventPtr->cbutton.button,eventPtr->cbutton.which); 115 | break; 116 | case SDL_CONTROLLERBUTTONUP: 117 | controller.clearJoyButton(eventPtr->cbutton.button,eventPtr->cbutton.which); 118 | break; 119 | case SDL_KEYDOWN: 120 | controller.setKey(eventPtr->key.keysym.scancode); 121 | break; 122 | case SDL_KEYUP: 123 | controller.clearKey(eventPtr->key.keysym.scancode); 124 | break; 125 | default: 126 | break; 127 | } 128 | } 129 | 130 | 131 | } 132 | 133 | void Display::frameDone() 134 | { 135 | for(int i = 0;i < RENDER_WIDTH * RENDER_HEIGHT;i++) 136 | currentFrame[i] = nextFrame[i]; 137 | } 138 | 139 | 140 | #ifdef DEBUG 141 | void Display::initDebug() 142 | { 143 | debugWindow = SDL_CreateWindow("DEBUG WINDOW", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, DEBUG_WIDTH * 2 , DEBUG_HEIGHT * 2 , 0); 144 | 145 | if(!debugWindow) 146 | { 147 | std::cout << "Failed to create debug window\n"; 148 | std::cout << "SDL2 Error: " << SDL_GetError() << "\n"; 149 | return; 150 | } 151 | else 152 | std::cout << "Debug Window created!\n", 153 | Drenderer = SDL_CreateRenderer(debugWindow, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); 154 | 155 | Dtexture = SDL_CreateTexture(Drenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, DEBUG_WIDTH, DEBUG_HEIGHT); 156 | 157 | SDL_RenderSetLogicalSize(Drenderer, DEBUG_WIDTH, DEBUG_HEIGHT); 158 | 159 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear"); 160 | 161 | 162 | for(int i = 0;i < DEBUG_HEIGHT;i++) 163 | for(int j = 0;j < DEBUG_WIDTH;j++) 164 | setPixelDebug(i,j,{84,84,84}); 165 | } 166 | 167 | #endif 168 | 169 | #ifdef DEBUG 170 | void Display::renderDebugFrame() 171 | { 172 | SDL_UpdateTexture(Dtexture, NULL, debug, DEBUG_WIDTH * sizeof(PIXEL)); 173 | SDL_RenderCopy(Drenderer, Dtexture, NULL, NULL); 174 | SDL_RenderPresent(Drenderer); 175 | 176 | } 177 | 178 | void Display::setPixelDebug(int x, int y,PIXEL_RGB colors) 179 | { 180 | PIXEL pixel = 0; 181 | 182 | pixel |= 255 << 24; 183 | pixel |= (PIXEL)colors.r << 16; 184 | pixel |= (PIXEL)colors.g << 8; 185 | pixel |= (PIXEL)colors.b << 0; 186 | 187 | debug[x + y * DEBUG_WIDTH] = pixel; 188 | } 189 | #endif 190 | } 191 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /Controller/Controller.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "Controller.h" 5 | 6 | namespace nesemulator 7 | { 8 | Controller::Controller(Bus* bus) : bus(bus) 9 | { 10 | if(SDL_NumJoysticks() > 0) 11 | { 12 | contorller1 = SDL_GameControllerOpen(0); 13 | std::cout << "Found Game Controller:\n"; 14 | std::cout << "Name: " << SDL_GameControllerNameForIndex(0) << std::endl; 15 | 16 | } 17 | if(SDL_NumJoysticks() > 1) 18 | { 19 | contorller2 = SDL_GameControllerOpen(1); 20 | std::cout << "Found Game Controller:\n"; 21 | std::cout << "Name: " << SDL_GameControllerNameForIndex(1) << std::endl; 22 | } 23 | } 24 | 25 | 26 | 27 | void Controller::setJoyButton(int id,int which) 28 | { 29 | switch(id) 30 | { 31 | case SDL_CONTROLLER_BUTTON_A: 32 | controllerStatus[which] |= 0x80; 33 | break; 34 | case SDL_CONTROLLER_BUTTON_B: 35 | controllerStatus[which] |= 0x40; 36 | break; 37 | case SDL_CONTROLLER_BUTTON_BACK: 38 | controllerStatus[which] |= 0x20; 39 | break; 40 | case SDL_CONTROLLER_BUTTON_START: 41 | controllerStatus[which] |= 0x10; 42 | break; 43 | case SDL_CONTROLLER_BUTTON_DPAD_UP: 44 | controllerStatus[which] |= 0x08; 45 | break; 46 | case SDL_CONTROLLER_BUTTON_DPAD_DOWN: 47 | controllerStatus[which] |= 0x04; 48 | break; 49 | case SDL_CONTROLLER_BUTTON_DPAD_LEFT: 50 | controllerStatus[which] |= 0x02; 51 | break; 52 | case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: 53 | controllerStatus[which] |= 0x01; 54 | break; 55 | } 56 | bus->setControllerMemory(controllerStatus); 57 | } 58 | 59 | void Controller::clearJoyButton(int id,int which) 60 | { 61 | switch(id) 62 | { 63 | case SDL_CONTROLLER_BUTTON_A: 64 | controllerStatus[which] &= 0x7F; 65 | break; 66 | case SDL_CONTROLLER_BUTTON_B: 67 | controllerStatus[which] &= 0xBF; 68 | break; 69 | case SDL_CONTROLLER_BUTTON_BACK: 70 | controllerStatus[which] &= 0xD0; 71 | break; 72 | case SDL_CONTROLLER_BUTTON_START: 73 | controllerStatus[which] &= 0xE0; 74 | break; 75 | case SDL_CONTROLLER_BUTTON_DPAD_UP: 76 | controllerStatus[which] &= 0xF7; 77 | break; 78 | case SDL_CONTROLLER_BUTTON_DPAD_DOWN: 79 | controllerStatus[which] &= 0xFB; 80 | break; 81 | case SDL_CONTROLLER_BUTTON_DPAD_LEFT: 82 | controllerStatus[which] &= 0xFD; 83 | break; 84 | case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: 85 | controllerStatus[which] &= 0xFE; 86 | break; 87 | } 88 | bus->setControllerMemory(controllerStatus); 89 | } 90 | 91 | void Controller::setJoyAxis(int id,int value,int which) 92 | { 93 | switch(id) 94 | { 95 | case SDL_CONTROLLER_AXIS_LEFTY: 96 | break; 97 | case SDL_CONTROLLER_AXIS_LEFTX: 98 | controllerStatus[which] |= (value > 20000) ? 0x02 : 0x00; 99 | controllerStatus[which] |= (value < -20000) ? 0x01 : 0x00; 100 | break; 101 | default: 102 | break; 103 | } 104 | bus->setControllerMemory(controllerStatus); 105 | } 106 | 107 | void Controller::setKey(int id) 108 | { 109 | switch(id) 110 | { 111 | case SDL_SCANCODE_X: 112 | controllerStatus[0] |= 0x80; 113 | break; 114 | case SDL_SCANCODE_C: 115 | controllerStatus[0] |= 0x40; 116 | break; 117 | case SDL_SCANCODE_A: 118 | controllerStatus[0] |= 0x20; 119 | break; 120 | case SDL_SCANCODE_S: 121 | controllerStatus[0] |= 0x10; 122 | break; 123 | case SDL_SCANCODE_UP: 124 | controllerStatus[0] |= 0x08; 125 | break; 126 | case SDL_SCANCODE_DOWN: 127 | controllerStatus[0] |= 0x04; 128 | break; 129 | case SDL_SCANCODE_LEFT: 130 | controllerStatus[0] |= 0x02; 131 | break; 132 | case SDL_SCANCODE_RIGHT: 133 | controllerStatus[0] |= 0x01; 134 | break; 135 | case SDL_SCANCODE_T: 136 | saveStateButton = true; 137 | break; 138 | case SDL_SCANCODE_Y: 139 | loadStateButton = true; 140 | break; 141 | } 142 | bus->setControllerMemory(controllerStatus); 143 | } 144 | 145 | void Controller::clearKey(int id) 146 | { 147 | switch(id) 148 | { 149 | case SDL_SCANCODE_X: 150 | controllerStatus[0] &= 0x7F; 151 | break; 152 | case SDL_SCANCODE_C: 153 | controllerStatus[0] &= 0xBF; 154 | break; 155 | case SDL_SCANCODE_A: 156 | controllerStatus[0] &= 0xD0; 157 | break; 158 | case SDL_SCANCODE_S: 159 | controllerStatus[0] &= 0xE0; 160 | break; 161 | case SDL_SCANCODE_UP: 162 | controllerStatus[0] &= 0xF7; 163 | break; 164 | case SDL_SCANCODE_DOWN: 165 | controllerStatus[0] &= 0xFB; 166 | break; 167 | case SDL_SCANCODE_LEFT: 168 | controllerStatus[0] &= 0xFD; 169 | break; 170 | case SDL_SCANCODE_RIGHT: 171 | controllerStatus[0] &= 0xFE; 172 | break; 173 | case SDL_SCANCODE_T: 174 | saveStateButton = false; 175 | break; 176 | case SDL_SCANCODE_Y: 177 | loadStateButton = false; 178 | break; 179 | } 180 | bus->setControllerMemory(controllerStatus); 181 | } 182 | } -------------------------------------------------------------------------------- /UI/Notification.h: -------------------------------------------------------------------------------- 1 | #ifndef NOTIFICATION 2 | #define NOTIFICATION 3 | 4 | 5 | #include 6 | #include 7 | 8 | #include "../Utils/handler.h" 9 | 10 | 11 | namespace nesemulator 12 | { 13 | namespace ui 14 | { 15 | 16 | /* SDL Notification colors with RGB values as array */ 17 | namespace notificationColors 18 | { 19 | constexpr SDL_Color info = { 64, 255, 208, 255 }; 20 | constexpr SDL_Color warning = { 255, 255, 0, 255 }; 21 | constexpr SDL_Color error = { 255, 0, 0, 255 }; 22 | constexpr SDL_Color success = { 0, 255, 0, 255 }; 23 | constexpr SDL_Color white = { 255, 255, 255, 255 }; 24 | constexpr SDL_Color black = { 0, 0, 0, 255 }; 25 | constexpr SDL_Color red = { 255, 0, 0, 255 }; 26 | constexpr SDL_Color green = { 0, 255, 0, 255 }; 27 | constexpr SDL_Color blue = { 0, 0, 255, 255 }; 28 | constexpr SDL_Color yellow = { 255, 255, 0, 255 }; 29 | constexpr SDL_Color cyan = { 0, 255, 255, 255 }; 30 | constexpr SDL_Color magenta = { 255, 0, 255, 255 }; 31 | } 32 | 33 | 34 | 35 | /* Class to create notifications using SDL_ttf with durations */ 36 | class Notification 37 | { 38 | public: 39 | Notification(std::string _text, TIMER _duration); 40 | Notification(std::string _text, TIMER _duration, SDL_Color _color, TTF_Font* _font, SDL_Renderer* _renderer); 41 | Notification(std::string _text, TIMER _duration, TTF_Font* _font, SDL_Renderer* _renderer); 42 | void draw(int x = 0, int y = 0); 43 | void update(TIMER delta); 44 | bool isFinished() const { return finished; }; 45 | void setFinished(bool _finished) { finished = _finished; }; 46 | void setText(std::string _text) { text = _text; }; 47 | void setDuration(TIMER _duration) { duration = _duration; }; 48 | void setColor(int _r, int _g, int _b) { color.r = _r; color.g = _g; color.b = _b; }; 49 | void setAlpha(int _alpha) { color.a = _alpha; }; 50 | void setFont(TTF_Font* _font) { font = _font; }; 51 | void setRenderer(SDL_Renderer* _renderer) { renderer = _renderer; }; 52 | void setTextSize(int _size) { textSize = _size; }; 53 | void setTextStyle(int _style) { textStyle = _style; }; 54 | void setTextOutline(int _outline) { textOutline = _outline; }; 55 | void setTextOutlineColor(int _r, int _g, int _b) { textOutlineR = _r; textOutlineG = _g; textOutlineB = _b; }; 56 | void setTextShadow(int _shadow) { textShadow = _shadow; }; 57 | void setTextShadowColor(int _r, int _g, int _b) { textShadowR = _r; textShadowG = _g; textShadowB = _b; }; 58 | void setTextShadowOffset(int _x, int _y) { textShadowX = _x; textShadowY = _y; }; 59 | void setTextShadowBlur(int _blur) { textShadowBlur = _blur; }; 60 | void setTextShadowBlurRadius(int _radius) { textShadow = _radius; }; 61 | void setTextShadowSpread(int _spread) { textShadowSpread = _spread; }; 62 | void setTextShadowQuality(int _quality) { textShadowQuality = _quality; }; 63 | void setTextShadowThickness(int _thickness) { textShadowThickness = _thickness; }; 64 | void setTextShadowType(int _type) { textShadowType = _type; }; 65 | bool operator==(const Notification& other) const; 66 | private: 67 | std::string text; 68 | int64_t duration; 69 | SDL_Color color; 70 | TTF_Font* font; 71 | SDL_Renderer* renderer; 72 | int textSize; 73 | int textStyle; 74 | int textOutline; 75 | int textOutlineR; 76 | int textOutlineG; 77 | int textOutlineB; 78 | int textShadow; 79 | int textShadowR; 80 | int textShadowG; 81 | int textShadowB; 82 | int textShadowX; 83 | int textShadowY; 84 | int textShadowBlur; 85 | int textShadowBlurRadius; 86 | int textShadowSpread; 87 | int textShadowQuality; 88 | int textShadowThickness; 89 | int textShadowType; 90 | bool finished; 91 | 92 | }; 93 | 94 | inline Notification::Notification(std::string _text, TIMER _duration) 95 | { 96 | text = _text; 97 | duration = _duration; 98 | finished = false; 99 | } 100 | 101 | inline Notification::Notification(std::string _text, TIMER _duration, SDL_Color _color, TTF_Font* _font, SDL_Renderer* _renderer) 102 | { 103 | text = _text; 104 | duration = _duration; 105 | finished = false; 106 | font = _font; 107 | renderer = _renderer; 108 | color = _color; 109 | } 110 | 111 | 112 | inline Notification::Notification(std::string _text, TIMER _duration, TTF_Font* _font, SDL_Renderer* _renderer) 113 | { 114 | text = _text; 115 | duration = _duration; 116 | finished = false; 117 | font = _font; 118 | renderer = _renderer; 119 | } 120 | 121 | 122 | inline void Notification::draw(int x,int y) 123 | { 124 | SDL_Surface* surface = TTF_RenderText_Blended(font, text.c_str(), color); 125 | SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); 126 | SDL_Rect rect = {x, y, surface->w / 5, surface->h / 5}; 127 | SDL_RenderCopy(renderer, texture, NULL, &rect); 128 | SDL_FreeSurface(surface); 129 | SDL_DestroyTexture(texture); 130 | } 131 | 132 | inline void Notification::update(TIMER delta) 133 | { 134 | duration -= delta; 135 | if(duration <= 0) 136 | finished = true; 137 | } 138 | 139 | 140 | 141 | inline bool Notification::operator==(const Notification& other) const 142 | { 143 | return (text == other.text && duration == other.duration && font == other.font && renderer == other.renderer && textSize == other.textSize && textStyle == other.textStyle && textOutline == other.textOutline && textOutlineR == other.textOutlineR && textOutlineG == other.textOutlineG && textOutlineB == other.textOutlineB && textShadow == other.textShadow && textShadowR == other.textShadowR && textShadowG == other.textShadowG && textShadowB == other.textShadowB && textShadowX == other.textShadowX && textShadowY == other.textShadowY && textShadowBlur == other.textShadowBlur && textShadowBlurRadius == other.textShadowBlurRadius && textShadowSpread == other.textShadowSpread && textShadowQuality == other.textShadowQuality && textShadowThickness == other.textShadowThickness && textShadowType == other.textShadowType && finished == other.finished); 144 | } 145 | } 146 | 147 | } 148 | 149 | 150 | #endif -------------------------------------------------------------------------------- /CPU/CPU.h: -------------------------------------------------------------------------------- 1 | #ifndef CPU_H 2 | #define CPU_H 3 | 4 | //#define CPUDEBUG 5 | 6 | #include "../Utils/handler.h" 7 | #include "../Bus/Bus.h" 8 | 9 | #ifdef CPUDEBUG 10 | 11 | #include 12 | 13 | #endif 14 | 15 | 16 | 17 | 18 | 19 | 20 | namespace nesemulator 21 | { 22 | /** 23 | This is a solid emulation of 6502 Proccessor. 24 | 25 | CPU EMULATION TYPE : Jump Table Based 26 | EXPLANATION : 27 | Each addressing mode (ADDRESSING_MODE) and operation (OPEXEC) is a function 28 | and they are forming INSTRUCTION struct for each OPCODE 29 | Since there is 256 OPCODE but 6502 using only 151 of them,remaining 30 | OPCODEs are illegal and they literally do nothing (NOP) 31 | */ 32 | class CPU 33 | { 34 | public: 35 | CPU(Bus& bus); 36 | void tick(); 37 | void reset(); 38 | void NMI(); 39 | void IRQ(); 40 | friend std::ostream& operator<<(std::ostream &out,CPU &cpu); 41 | private: 42 | friend class NES; 43 | #ifdef CPUDEBUG 44 | void logToFile(); 45 | FILE* logFile; 46 | #endif 47 | 48 | /* ACCUMULATOR REGISTER */ 49 | BYTE A = 0x00; 50 | /* INDEX-X REGISTER */ 51 | BYTE X = 0x00; 52 | /* INDEX-Y REGISTER */ 53 | BYTE Y = 0x00; 54 | /* STACK POINTER */ 55 | BYTE SP = 0xFF; 56 | /** 57 | * STATUS REGITERS to store FLAGs 58 | * 59 | */ 60 | union 61 | { 62 | struct 63 | { 64 | FLAG CARRY : 1, ZERO : 1, INTERRUPT : 1, DECIMAL : 1, BREAK : 1, UNUSED : 1, OVERFLOW : 1, NEGATIVE : 1; 65 | }; 66 | BYTE combined; 67 | } STATUS; 68 | 69 | /** 70 | * Instruction table of register 71 | */ 72 | INSTRUCTION table[256]; 73 | /** 74 | * Cycle count of each operation in the instruction table 75 | */ 76 | static constexpr BYTE cycleCounts[256] = { // Cycle count for each operation 77 | 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6, 78 | 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, 79 | 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6, 80 | 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, 81 | 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6, 82 | 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, 83 | 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6, 84 | 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, 85 | 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, 86 | 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5, 87 | 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4, 88 | 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4, 89 | 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6, 90 | 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, 91 | 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6, 92 | 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7, 93 | }; 94 | static const ADDRESS IRQVECTOR_H = 0xFFFF; // IRQ Vector 95 | static const ADDRESS IRQVECTOR_L = 0xFFFE; 96 | static const ADDRESS RSTVECTOR_H = 0xFFFD; // Reset Vector 97 | static const ADDRESS RSTVECTOR_L = 0xFFFC; 98 | static const ADDRESS NMIVECTOR_H = 0xFFFB; // NMI Vector 99 | static const ADDRESS NMIVECTOR_L = 0xFFFA; 100 | 101 | ADDRESS programCounter = 0x0000; 102 | 103 | /* Remaining cycles of last executed operation */ 104 | BYTE cycleRemaining = 0; 105 | /* Some operations may take more time... */ 106 | BYTE additionalCycle0 = 0,additionalCycle1 = 0; 107 | 108 | BYTE currentOpCode; 109 | 110 | INSTRUCTION currentInstruction; 111 | 112 | /* Bus object to read and write */ 113 | Bus& bus; 114 | 115 | /* CPU Clock */ 116 | TIMER clock = 0; 117 | 118 | ADDRESS source; 119 | 120 | 121 | void push(BYTE value); 122 | 123 | BYTE pop(); 124 | /*------------------------OPERATIONS------------------------*/ 125 | 126 | 127 | OPEXEC ADC(ADDRESS source); 128 | 129 | OPEXEC AND(ADDRESS source); 130 | 131 | OPEXEC ASL(ADDRESS source); 132 | OPEXEC BCC(ADDRESS source); 133 | OPEXEC BCS(ADDRESS source); 134 | OPEXEC BEQ(ADDRESS source); 135 | OPEXEC BIT(ADDRESS source); 136 | 137 | OPEXEC BMI(ADDRESS source); 138 | 139 | OPEXEC BNE(ADDRESS source); 140 | 141 | OPEXEC BPL(ADDRESS source); 142 | 143 | OPEXEC BRK(ADDRESS source); 144 | OPEXEC BVC(ADDRESS source); 145 | 146 | OPEXEC BVS(ADDRESS source); 147 | OPEXEC CLC(ADDRESS source); 148 | 149 | OPEXEC CLD(ADDRESS source); 150 | 151 | OPEXEC CLI(ADDRESS source); 152 | 153 | OPEXEC CLV(ADDRESS source); 154 | 155 | OPEXEC CMP(ADDRESS source); 156 | 157 | OPEXEC CPX(ADDRESS source); 158 | 159 | OPEXEC CPY(ADDRESS source); 160 | OPEXEC DEC(ADDRESS source); 161 | OPEXEC DEX(ADDRESS source); 162 | 163 | OPEXEC DEY(ADDRESS source); 164 | 165 | OPEXEC EOR(ADDRESS source); 166 | 167 | OPEXEC INC(ADDRESS source); 168 | 169 | OPEXEC INX_OP(ADDRESS source); 170 | OPEXEC INY_OP(ADDRESS source); 171 | 172 | OPEXEC JMP(ADDRESS source); 173 | 174 | OPEXEC JSR(ADDRESS source); 175 | 176 | OPEXEC LDA(ADDRESS source); 177 | 178 | OPEXEC LDX(ADDRESS source); 179 | 180 | OPEXEC LDY(ADDRESS source); 181 | 182 | OPEXEC LSR(ADDRESS source); 183 | 184 | 185 | OPEXEC NOP(ADDRESS source); 186 | 187 | OPEXEC ORA(ADDRESS source); 188 | OPEXEC PHA(ADDRESS source); 189 | OPEXEC PHP(ADDRESS source); 190 | 191 | OPEXEC PLA(ADDRESS source); 192 | 193 | OPEXEC PLP(ADDRESS source); 194 | 195 | OPEXEC ROL(ADDRESS source); 196 | 197 | OPEXEC ROR(ADDRESS source); 198 | 199 | 200 | OPEXEC RTI(ADDRESS source); 201 | 202 | OPEXEC RTS(ADDRESS source); 203 | OPEXEC SBC(ADDRESS source); 204 | 205 | OPEXEC SEC(ADDRESS source); 206 | OPEXEC SED(ADDRESS source); 207 | 208 | OPEXEC SEI(ADDRESS source); 209 | 210 | OPEXEC STA(ADDRESS source); 211 | 212 | OPEXEC STX(ADDRESS source); 213 | 214 | OPEXEC STY(ADDRESS source); 215 | OPEXEC TAX(ADDRESS source); 216 | 217 | OPEXEC TAY(ADDRESS source); 218 | 219 | OPEXEC TSX(ADDRESS source); 220 | 221 | OPEXEC TXA(ADDRESS source); 222 | 223 | OPEXEC TYA(ADDRESS source); 224 | OPEXEC TXS(ADDRESS source); 225 | OPEXEC ILLEGAL(ADDRESS source); 226 | 227 | /*------------ADDRESSING MODES--------*/ 228 | 229 | ADDRESS IMM(); // IMMEDIATE 230 | 231 | ADDRESS ABS(); // ABSOLUTE 232 | 233 | ADDRESS ZER(); // ZERO PAGE 234 | 235 | ADDRESS ZEX(); // INDEXED-X ZERO PAGE 236 | 237 | ADDRESS ZEY(); // INDEXED-Y ZERO PAGE 238 | 239 | ADDRESS ABX(); // INDEXED-X ABSOLUTE 240 | 241 | ADDRESS ABY(); // INDEXED-Y ABSOLUTE 242 | 243 | ADDRESS IMP(); // IMPLIED 244 | 245 | ADDRESS REL(); // RELATIVE 246 | 247 | ADDRESS INX(); // INDEXED-X INDIRECT 248 | 249 | ADDRESS INY(); // INDEXED-Y INDIRECT 250 | 251 | ADDRESS ABI(); // ABSOLUTE INDIRECT 252 | 253 | 254 | 255 | }; 256 | 257 | } 258 | 259 | #endif -------------------------------------------------------------------------------- /Bus/PPUBus.cpp: -------------------------------------------------------------------------------- 1 | #include "PPUBus.h" 2 | 3 | 4 | namespace nesemulator 5 | { 6 | PPUBus::PPUBus() 7 | { 8 | colors[0x00] = {84, 84, 84}; 9 | colors[0x01] = {0, 30, 116}; 10 | colors[0x02] = {8, 16, 144}; 11 | colors[0x03] = {48, 0, 136}; 12 | colors[0x04] = {68, 0, 100}; 13 | colors[0x05] = {92, 0, 48}; 14 | colors[0x06] = {84, 4, 0}; 15 | colors[0x07] = {60, 24, 0}; 16 | colors[0x08] = {32, 42, 0}; 17 | colors[0x09] = {8, 58, 0}; 18 | colors[0x0A] = {0, 64, 0}; 19 | colors[0x0B] = {0, 60, 0}; 20 | colors[0x0C] = {0, 50, 60}; 21 | colors[0x0D] = {0, 0, 0}; 22 | colors[0x0E] = {0, 0, 0}; 23 | colors[0x0F] = {0, 0, 0}; 24 | colors[0x10] = {152, 150, 152}; 25 | colors[0x11] = {8, 76, 196}; 26 | colors[0x12] = {48, 50, 236}; 27 | colors[0x13] = {92, 30, 228}; 28 | colors[0x14] = {136, 20, 176}; 29 | colors[0x15] = {160, 20, 100}; 30 | colors[0x16] = {152, 34, 32}; 31 | colors[0x17] = {120, 60, 0}; 32 | colors[0x18] = {84, 90, 0}; 33 | colors[0x19] = {40, 114, 0}; 34 | colors[0x1A] = {8, 124, 0}; 35 | colors[0x1B] = {0, 118, 40}; 36 | colors[0x1C] = {0, 102, 120}; 37 | colors[0x1D] = {0, 0, 0}; 38 | colors[0x1E] = {0, 0, 0}; 39 | colors[0x1F] = {0, 0, 0}; 40 | colors[0x20] = {236, 238, 236}; 41 | colors[0x21] = {76, 154, 236}; 42 | colors[0x22] = {120, 124, 236}; 43 | colors[0x23] = {176, 98, 236}; 44 | colors[0x24] = {228, 84, 236}; 45 | colors[0x25] = {236, 88, 180}; 46 | colors[0x26] = {236, 106, 100}; 47 | colors[0x27] = {212, 136, 32}; 48 | colors[0x28] = {160, 170, 0}; 49 | colors[0x29] = {116, 196, 0}; 50 | colors[0x2A] = {76, 208, 32}; 51 | colors[0x2B] = {56, 204, 108}; 52 | colors[0x2C] = {56, 180, 204}; 53 | colors[0x2D] = {60, 60, 60}; 54 | colors[0x2E] = {0, 0, 0}; 55 | colors[0x2F] = {0, 0, 0}; 56 | colors[0x30] = {236, 238, 236}; 57 | colors[0x31] = {168, 204, 236}; 58 | colors[0x32] = {188, 188, 236}; 59 | colors[0x33] = {212, 178, 236}; 60 | colors[0x34] = {236, 174, 236}; 61 | colors[0x35] = {236, 174, 212}; 62 | colors[0x36] = {236, 180, 176}; 63 | colors[0x37] = {228, 196, 144}; 64 | colors[0x38] = {204, 210, 120}; 65 | colors[0x39] = {180, 222, 120}; 66 | colors[0x3A] = {168, 226, 144}; 67 | colors[0x3B] = {152, 226, 180}; 68 | colors[0x3C] = {160, 214, 228}; 69 | colors[0x3D] = {160, 162, 160}; 70 | colors[0x3E] = {0, 0, 0}; 71 | colors[0x3F] = {0, 0, 0}; 72 | 73 | 74 | PPUSTATUS.combined = 0x00; 75 | PPUCTRL.combined = 0x00; 76 | PPUMASK.combined = 0x00; 77 | 78 | vRAM.combined = 0x0000; 79 | tempRAM.combined = 0x0000; 80 | 81 | BG_RENDER_FETCH.BG_NEXT_ATTR = 0x00; 82 | BG_RENDER_FETCH.BG_NEXT_HIGH = 0x00; 83 | BG_RENDER_FETCH.BG_NEXT_LOW = 0x00; 84 | BG_RENDER_FETCH.BG_NEXT_ID = 0x00; 85 | 86 | BG_SHIFTER_ATTR_HIGH.combined = 0x0000; 87 | BG_SHIFTER_ATTR_LOW.combined = 0x0000; 88 | BG_SHIFTER_PATTERN_HIGH.combined = 0x0000; 89 | BG_SHIFTER_PATTERN_LOW.combined = 0x0000; 90 | 91 | ppuBuffer = 0x00; 92 | 93 | } 94 | 95 | 96 | void PPUBus::setMapper(std::shared_ptr _mapper) 97 | { 98 | mapper = _mapper; 99 | } 100 | 101 | 102 | BYTE PPUBus::readFromMemory(ADDRESS address) 103 | { 104 | address &= 0x3FFF; 105 | if(address >= 0x0000 && address <= 0x1FFF) 106 | return mapper->MapReadPpu(address); 107 | else if(address >= 0x2000 && address <= 0x3EFF) 108 | { 109 | address &= 0x0FFF; 110 | if(mapper->getMirroring() == MIRRORING::VERTICAL) 111 | { 112 | return nameTables[address % 2048]; 113 | } 114 | else if(mapper->getMirroring() == MIRRORING::HORIZONTAL) 115 | { 116 | if(address <= 0x07FF) 117 | return nameTables[address % 1024]; 118 | else 119 | return nameTables[(address % 1024) + 1024]; 120 | } 121 | else if(mapper->getMirroring() == MIRRORING::FOURSCREENS) 122 | return nameTables[address % 4096]; 123 | else if(mapper->getMirroring() == MIRRORING::ONSCREENLOWER) 124 | return nameTables[address % 1024]; 125 | else if(mapper->getMirroring() == MIRRORING::ONSCREENHIGHER) 126 | return nameTables[(address % 1024) + 1024]; 127 | else 128 | return 0x00; 129 | } 130 | else if(address >= 0x3F00 && address <= 0x3FFF) 131 | { 132 | address &= 0x001F; 133 | if(address == 0x0010) address = 0x0000; 134 | if(address == 0x0014) address = 0x0004; 135 | if(address == 0x0018) address = 0x0008; 136 | if(address == 0x001C) address = 0x000C; 137 | return palettes[address]; 138 | } 139 | 140 | return 0x00; 141 | } 142 | 143 | 144 | void PPUBus::writeToMemory(ADDRESS address,BYTE value) 145 | { 146 | address &= 0x3FFF; 147 | if(address >= 0x0000 && address <= 0x1FFF) 148 | mapper->MapWritePpu(address,value); 149 | else if(address >= 0x2000 && address <= 0x3EFF) 150 | { 151 | address &= 0x0FFF; 152 | if(mapper->getMirroring() == MIRRORING::VERTICAL) 153 | { 154 | nameTables[address % 2048] = value; 155 | } 156 | else if(mapper->getMirroring() == MIRRORING::HORIZONTAL) 157 | { 158 | if(address <= 0x07FF) 159 | nameTables[address % 1024] = value; 160 | else 161 | nameTables[(address % 1024) + 1024] = value; 162 | } 163 | else if(mapper->getMirroring() == MIRRORING::FOURSCREENS) 164 | nameTables[address % 4096] = value; 165 | else if(mapper->getMirroring() == MIRRORING::ONSCREENLOWER) 166 | nameTables[address % 1024] = value; 167 | else if(mapper->getMirroring() == MIRRORING::ONSCREENHIGHER) 168 | nameTables[(address % 1024) + 1024] = value; 169 | } 170 | else if(address >= 0x3F00 && address <= 0x3FFF) 171 | { 172 | address &= 0x001F; 173 | if(address == 0x0010) address = 0x0000; 174 | if(address == 0x0014) address = 0x0004; 175 | if(address == 0x0018) address = 0x0008; 176 | if(address == 0x001C) address = 0x000C; 177 | palettes[address] = value; 178 | } 179 | } 180 | 181 | 182 | BYTE PPUBus::readFromMemory_mainBus(ADDRESS address) 183 | { 184 | if(address == 0x0000) 185 | { 186 | return 0x00; 187 | } 188 | else if(address == 0x0001) 189 | { 190 | return OAM_offset; 191 | } 192 | else if(address == 0x0002) 193 | { 194 | BYTE temp = (PPUSTATUS.combined & 0xE0) | (ppuBuffer & 0x1F); 195 | PPUSTATUS.VBLANK = 0; 196 | addressToggle = false; 197 | 198 | return temp; 199 | } 200 | else if(address == 0x0003) 201 | { } 202 | else if(address == 0x0004) 203 | return ((BYTE*)&OAM)[OAM_offset]; 204 | else if(address == 0x0005) 205 | { } 206 | else if(address == 0x0006) 207 | { } 208 | else if(address == 0x0007) 209 | { 210 | BYTE data = ppuBuffer; 211 | ppuBuffer = readFromMemory(vRAM.combined); 212 | 213 | if(vRAM.combined >= 0x3F00) return ppuBuffer; 214 | vRAM.combined += (PPUCTRL.INCREMENT_MODE ? 32 : 1); 215 | return data; 216 | } 217 | 218 | 219 | return 0x00; 220 | 221 | } 222 | 223 | 224 | void PPUBus::writeToMemory_mainBus(ADDRESS address,BYTE value) 225 | { 226 | if(address == 0x0000) 227 | { 228 | PPUCTRL.combined = value; 229 | tempRAM.NT_X = PPUCTRL.NAMETABLE_X; 230 | tempRAM.NT_Y = PPUCTRL.NAMETABLE_Y; 231 | } 232 | else if(address == 0x0001) 233 | PPUMASK.combined = value; 234 | else if(address == 0x0002) 235 | { 236 | 237 | } 238 | else if(address == 0x0003) 239 | OAM_offset = value; 240 | else if(address == 0x0004) 241 | ((BYTE*)OAM)[OAM_offset] = value; 242 | else if(address == 0x0005) 243 | { 244 | if(!addressToggle) 245 | { 246 | FINE_X = value & 0x07; 247 | tempRAM.CO_X = value >> 3; 248 | addressToggle = true; 249 | } 250 | else 251 | { 252 | tempRAM.FINE_Y = value & 0x07; 253 | tempRAM.CO_Y = value >> 3; 254 | addressToggle = false; 255 | } 256 | } 257 | else if(address == 0x0006) 258 | { 259 | if(!addressToggle) 260 | { 261 | tempRAM.combined = (ADDRESS)((value & 0x3F) << 8) | (tempRAM.combined & 0x00FF); 262 | addressToggle = true; 263 | } 264 | else 265 | { 266 | tempRAM.combined = (tempRAM.combined & 0xFF00) | value; 267 | vRAM = tempRAM; 268 | addressToggle = false; 269 | } 270 | 271 | } 272 | else if(address == 0x0007) 273 | { 274 | writeToMemory(vRAM.combined,value); 275 | vRAM.combined += (PPUCTRL.INCREMENT_MODE ? 32 : 1); 276 | } 277 | } 278 | 279 | } -------------------------------------------------------------------------------- /APU/AudioWaves.h: -------------------------------------------------------------------------------- 1 | #ifndef WAVES_H 2 | #define WAVES_H 3 | 4 | #include "../Utils/handler.h" 5 | 6 | namespace nesemulator 7 | { 8 | struct ENVELOPE 9 | { 10 | FLAG start; 11 | FLAG loop; 12 | FLAG constantVolume; 13 | BYTE divider; 14 | BYTE volume; 15 | BYTE decay; 16 | 17 | void tick() 18 | { 19 | if(start) 20 | { 21 | start = false; 22 | decay = 15; 23 | divider = volume; 24 | } 25 | else 26 | { 27 | if(divider == 0) 28 | { 29 | divider = volume; 30 | if(decay == 0) 31 | { 32 | if(loop) 33 | decay = 15; 34 | } 35 | else 36 | decay--; 37 | } 38 | else 39 | divider--; 40 | } 41 | } 42 | 43 | AUDIOINT output() 44 | { 45 | return constantVolume ? volume : decay; 46 | } 47 | }; 48 | 49 | 50 | struct SWEEPER 51 | { 52 | SWEEPER(AUDIOINT& timerRef,FLAG& pulse1,AUDIOINT& period) : timer(timerRef),pulse1(pulse1),period(period) { }; 53 | FLAG& pulse1; 54 | FLAG enabled; 55 | FLAG negative; 56 | FLAG reload; 57 | FLAG silenceChannel; 58 | BYTE dividerPeriod; 59 | AUDIOINT targetPeriod; 60 | BYTE shiftCount; 61 | BYTE divider; 62 | AUDIOINT& period; 63 | AUDIOINT& timer; 64 | 65 | void operator=(SWEEPER& other) 66 | { 67 | pulse1 = other.pulse1; 68 | enabled = other.enabled; 69 | negative = other.negative; 70 | reload = other.reload; 71 | silenceChannel = other.silenceChannel; 72 | dividerPeriod = other.dividerPeriod; 73 | targetPeriod = other.targetPeriod; 74 | shiftCount = other.shiftCount; 75 | divider = other.divider; 76 | period = other.period; 77 | timer = other.timer; 78 | } 79 | 80 | void calculateTargetPeriod() 81 | { 82 | AUDIOINT change = period >> shiftCount; 83 | if(negative) 84 | { 85 | change = -change; 86 | if(pulse1) 87 | change -= 1; 88 | } 89 | 90 | targetPeriod = period + change; 91 | 92 | silenceChannel = (period < 8 || targetPeriod > 0x7FF) ? true : false; 93 | } 94 | 95 | void tick() 96 | { 97 | calculateTargetPeriod(); 98 | if(divider == 0 && enabled && !silenceChannel && shiftCount != 0) 99 | { 100 | period = targetPeriod; 101 | 102 | } 103 | if(divider == 0 || reload) 104 | { 105 | divider = dividerPeriod; 106 | reload = false; 107 | } 108 | else 109 | divider--; 110 | } 111 | }; 112 | 113 | 114 | struct LENGTHCOUNTER 115 | { 116 | FLAG enabled; 117 | FLAG halt; 118 | BYTE value; 119 | 120 | 121 | static constexpr BYTE lengthTable[32] = {10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30}; 122 | void disable() 123 | { 124 | enabled = false; 125 | value = 0; 126 | } 127 | 128 | void enable() 129 | { 130 | enabled = true; 131 | } 132 | 133 | void tick() 134 | { 135 | if(value > 0 && !halt) 136 | value--; 137 | } 138 | }; 139 | 140 | 141 | struct PulseWave 142 | { 143 | PulseWave(FLAG isPulse1) : pulse1(isPulse1) { }; 144 | FLAG pulse1; 145 | FLAG enabled; 146 | AUDIOINT timer; 147 | AUDIOINT period; 148 | 149 | void tick() 150 | { 151 | if(timer == 0) 152 | { 153 | timer = period; 154 | sequencerTick(); 155 | } 156 | else 157 | timer--; 158 | } 159 | 160 | ENVELOPE envelope; 161 | SWEEPER sweeper = SWEEPER(timer,pulse1,period); 162 | LENGTHCOUNTER lengthCounter; 163 | 164 | static constexpr BYTE dutySequenceTable[4][8] = { 165 | {0, 1, 0, 0, 0, 0, 0, 0}, 166 | {0, 1, 1, 0, 0, 0, 0, 0}, 167 | {0, 1, 1, 1, 1, 0, 0, 0}, 168 | {1, 0, 0, 1, 1, 1, 1, 1} 169 | }; 170 | BYTE sequencer = 0; 171 | BYTE sequencerMode; 172 | void sequencerTick() 173 | { 174 | sequencer++; 175 | sequencer = sequencer % 8; 176 | } 177 | 178 | AUDIOINT output() 179 | { 180 | sweeper.calculateTargetPeriod(); 181 | if(dutySequenceTable[sequencerMode][sequencer] == 0 || sweeper.silenceChannel || lengthCounter.value == 0 || enabled == false) 182 | return 0x00; 183 | else 184 | return envelope.output(); 185 | } 186 | }; 187 | 188 | 189 | struct TriangleWave 190 | { 191 | FLAG enabled; 192 | AUDIOINT timer; 193 | AUDIOINT period; 194 | 195 | void tick() 196 | { 197 | if(timer == 0) 198 | { 199 | timer = period; 200 | sequencerTick(); 201 | } 202 | else 203 | timer--; 204 | } 205 | 206 | 207 | LENGTHCOUNTER lengthCounter; 208 | 209 | FLAG linearCounterReload; 210 | FLAG linearCounterControl; 211 | BYTE linearCounter; 212 | BYTE linerCounterReloadValue; 213 | 214 | void linearCounterTick() 215 | { 216 | if(linearCounterReload) 217 | linearCounter = linerCounterReloadValue; 218 | else if(linearCounter > 0) 219 | linearCounter--; 220 | 221 | if(!linearCounterControl) 222 | linearCounterReload = false; 223 | } 224 | 225 | static constexpr BYTE sequenceTable[32] = {15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; 226 | 227 | BYTE sequencerIndex; 228 | 229 | void sequencerTick() 230 | { 231 | if(lengthCounter.value > 0 && linearCounter > 0) 232 | { 233 | sequencerIndex++; 234 | sequencerIndex = sequencerIndex % 32; 235 | } 236 | } 237 | 238 | AUDIOINT output() 239 | { 240 | // if(linearCounter == 0 || lengthCounter.value == 0 || enabled == false) 241 | // return 0x00; 242 | // else 243 | return sequenceTable[sequencerIndex]; 244 | } 245 | }; 246 | 247 | 248 | struct NoiseWave 249 | { 250 | 251 | enum class NOISEMODE{ MODE1,MODE6}; 252 | NOISEMODE mode; 253 | FLAG enabled; 254 | AUDIOINT timer; 255 | AUDIOINT period; 256 | 257 | void tick() 258 | { 259 | if(timer == 0) 260 | { 261 | timer = period; 262 | shifterTick(); 263 | } 264 | else 265 | timer--; 266 | } 267 | 268 | static constexpr AUDIOINT noiseTable[16] = {4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068}; 269 | 270 | ENVELOPE envelope; 271 | 272 | LENGTHCOUNTER lengthCounter; 273 | 274 | AUDIOINT shifter = 0x0001; 275 | 276 | void shifterTick() 277 | { 278 | BYTE feedBack = (shifter & 0x1) ^ (shifter >> ((mode == NOISEMODE::MODE6) ? 6 : 1) & 0x1); 279 | 280 | shifter >>= 1; 281 | 282 | if(feedBack) 283 | shifter |= feedBack << 14; 284 | } 285 | 286 | AUDIOINT output() 287 | { 288 | if(!(shifter & 0x0001) || lengthCounter.value == 0 || enabled == false) 289 | return 0x00; 290 | else 291 | return envelope.output(); 292 | } 293 | }; 294 | 295 | struct DMC 296 | { 297 | FLAG enabled; 298 | FLAG IRQEnabled; 299 | FLAG IRQ; 300 | FLAG loop; 301 | FLAG bufferEmpty; 302 | FLAG silenceChannel; 303 | FLAG stall; 304 | ADDRESS sampleAddress; 305 | ADDRESS currentAddresss; 306 | AUDIOINT period; 307 | AUDIOINT timer; 308 | AUDIOINT sampleLength; 309 | AUDIOINT bytesRemaining; 310 | BYTE outputLoad; 311 | 312 | static constexpr AUDIOINT periodTable[] = {428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54}; 313 | 314 | void tick() 315 | { 316 | if(timer == 0) 317 | { 318 | timer = period - 1; 319 | } 320 | else 321 | timer--; 322 | } 323 | 324 | AUDIOINT output() 325 | { 326 | return outputLoad; 327 | } 328 | 329 | }; 330 | } 331 | 332 | 333 | #endif -------------------------------------------------------------------------------- /APU/APU.cpp: -------------------------------------------------------------------------------- 1 | #include "APU.h" 2 | 3 | 4 | 5 | namespace nesemulator 6 | { 7 | APU::APU() : high1(90),high2(440),low(14000) 8 | { 9 | for(int i = 0;i < 31;i++) 10 | pulseTable[i] = 95.52 / (8128.0 / (float)i + 100); 11 | for(int i = 0;i < 203;i++) 12 | tndTable[i] = 163.67 / (24329.0 / (float)i + 100); 13 | } 14 | 15 | 16 | void APU::quarterTick() 17 | { 18 | pulse1.envelope.tick(); 19 | pulse2.envelope.tick(); 20 | triangle.linearCounterTick(); 21 | noise.envelope.tick(); 22 | } 23 | 24 | void APU::halfTick() 25 | { 26 | quarterTick(); 27 | 28 | pulse1.lengthCounter.tick(); 29 | pulse2.lengthCounter.tick(); 30 | triangle.lengthCounter.tick(); 31 | noise.lengthCounter.tick(); 32 | 33 | pulse1.sweeper.tick(); 34 | pulse2.sweeper.tick(); 35 | } 36 | 37 | void APU::tick() 38 | { 39 | pulse1.sweeper.calculateTargetPeriod(); 40 | pulse2.sweeper.calculateTargetPeriod(); 41 | if(incomingFrameCounterReset) 42 | { 43 | if(--countToFrameCounterReset == 0) 44 | { 45 | incomingFrameCounterReset = false; 46 | frameCounter = 0; 47 | if(frameCounterMode == FRAMECOUNTERMODE::MODE5) 48 | halfTick(); 49 | } 50 | else 51 | countToFrameCounterReset--; 52 | } 53 | 54 | triangle.tick(); 55 | 56 | if(clock % 2 == 0) // APU clocks in every-other-CPU-cycle 57 | { 58 | 59 | 60 | 61 | if(frameCounter == 3729 || frameCounter == 11186) 62 | quarterTick(); 63 | if((frameCounterMode == FRAMECOUNTERMODE::MODE5 && frameCounter == 18641) || (frameCounterMode == FRAMECOUNTERMODE::MODE4 && frameCounter == 14915) || frameCounter == 7457) 64 | halfTick(); 65 | if((frameCounterMode == FRAMECOUNTERMODE::MODE5 && frameCounter == 18641) || (frameCounterMode == FRAMECOUNTERMODE::MODE4 && frameCounter == 14915)) 66 | frameCounter = 0; 67 | else 68 | frameCounter++; 69 | 70 | pulse1.tick(); 71 | pulse2.tick(); 72 | noise.tick(); 73 | dmc.tick(); 74 | } 75 | 76 | clock++; 77 | } 78 | 79 | 80 | 81 | void APU::writeToMemory(ADDRESS address,BYTE value) 82 | { 83 | switch (address) 84 | { 85 | case 0x4000: 86 | pulse1.envelope.volume = value & 0x0F; 87 | pulse1.envelope.constantVolume = (value & 0x10) ? true : false; 88 | pulse1.lengthCounter.halt = (value & 0x20) ? true : false; 89 | pulse1.envelope.loop = (value & 0x20) ? true : false; 90 | pulse1.sequencerMode = (value & 0xC0) >> 6; 91 | break; 92 | case 0x4001: 93 | pulse1.sweeper.enabled = (value & 0x80) ? true : false; 94 | pulse1.sweeper.dividerPeriod = (value & 0x70) >> 4; 95 | pulse1.sweeper.negative = (value & 0x08) ? true : false; 96 | pulse1.sweeper.shiftCount = value & 0x07; 97 | pulse1.sweeper.reload = true; 98 | break; 99 | case 0x4002: 100 | pulse1.period = (pulse1.period & 0xFF00) | (AUDIOINT)value; 101 | pulse1.sweeper.calculateTargetPeriod(); 102 | break; 103 | case 0x4003: 104 | if(pulse1.enabled) 105 | pulse1.lengthCounter.value = LENGTHCOUNTER::lengthTable[(value & 0xF8) >> 3]; 106 | pulse1.period = (pulse1.period & 0x00FF) | ((AUDIOINT)((value & 0x07)) << 8); 107 | pulse1.sweeper.calculateTargetPeriod(); 108 | pulse1.sequencer = 0; 109 | pulse1.envelope.start = true; 110 | break; 111 | case 0x4004: 112 | pulse2.envelope.volume = value & 0x0F; 113 | pulse2.envelope.constantVolume = (value & 0x10) ? true : false; 114 | pulse2.lengthCounter.halt = (value & 0x20) ? true : false; 115 | pulse2.envelope.loop = (value & 0x20) ? true : false; 116 | pulse2.sequencerMode = (value & 0xC0) >> 6; 117 | break; 118 | case 0x4005: 119 | pulse2.sweeper.enabled = (value & 0x80) ? true : false; 120 | pulse2.sweeper.dividerPeriod = (value & 0x70) >> 4; 121 | pulse2.sweeper.negative = (value & 0x08) ? true : false; 122 | pulse2.sweeper.shiftCount = value & 0x07; 123 | pulse2.sweeper.reload = true; 124 | break; 125 | case 0x4006: 126 | pulse2.period = (pulse2.period & 0xFF00) | (AUDIOINT)value; 127 | pulse2.sweeper.calculateTargetPeriod(); 128 | break; 129 | case 0x4007: 130 | if(pulse2.enabled) 131 | pulse2.lengthCounter.value = LENGTHCOUNTER::lengthTable[(value & 0xF8) >> 3]; 132 | pulse2.period = (pulse2.period & 0x00FF) | ((AUDIOINT)((value & 0x07)) << 8); 133 | pulse2.sweeper.calculateTargetPeriod(); 134 | pulse2.sequencer = 0; 135 | pulse2.envelope.start = true; 136 | break; 137 | case 0x4008: 138 | triangle.lengthCounter.halt = (value & 0x80) ? true : false; 139 | triangle.linearCounterControl = (value & 0x80) ? true : false; 140 | triangle.linerCounterReloadValue = value & 0x7F; 141 | break; 142 | case 0x400A: 143 | triangle.period = (triangle.period & 0xFF00) | (AUDIOINT)value; 144 | break; 145 | case 0x400B: 146 | if(triangle.enabled) 147 | triangle.lengthCounter.value = LENGTHCOUNTER::lengthTable[(value & 0xF8) >> 3]; 148 | triangle.period = (triangle.period & 0x00FF) | ((AUDIOINT)((value & 0x07)) << 8); 149 | triangle.linearCounterReload = true; 150 | break; 151 | case 0x400C: 152 | noise.lengthCounter.halt = (value & 0x20) ? true : false; 153 | noise.envelope.loop = (value & 0x20) ? true : false; 154 | noise.envelope.constantVolume = (value & 0x10) ? true : false; 155 | noise.envelope.volume = value & 0x0F; 156 | break; 157 | case 0x400E: 158 | noise.mode = (value & 0x80) ? NoiseWave::NOISEMODE::MODE6 : NoiseWave::NOISEMODE::MODE1; 159 | noise.period = NoiseWave::noiseTable[value & 0x0F]; 160 | break; 161 | case 0x400F: 162 | if(noise.enabled) 163 | noise.lengthCounter.value = LENGTHCOUNTER::lengthTable[(value & 0xF8) >> 3]; 164 | noise.envelope.start = true; 165 | break; 166 | case 0x4010: 167 | dmc.IRQEnabled = (value & 0x80) ? true : false; 168 | dmc.loop = (value & 0x40) ? true : false; 169 | dmc.period = DMC::periodTable[(value & 0x0F)]; 170 | break; 171 | case 0x4011: 172 | dmc.outputLoad = value & 0x7F; 173 | break; 174 | case 0x4012: 175 | dmc.sampleAddress = 0xC000 + ((AUDIOINT)(value) * 64); 176 | break; 177 | case 0x4013: 178 | dmc.sampleLength = (AUDIOINT)(value) * 16; 179 | break; 180 | case 0x4015: 181 | if((value & 0x01) == 0) 182 | { 183 | pulse1.enabled = false; 184 | pulse1.lengthCounter.value = 0; 185 | } 186 | else 187 | pulse1.enabled = true; 188 | 189 | if((value & 0x02) == 0) 190 | { 191 | pulse2.enabled = false; 192 | pulse2.lengthCounter.value = 0; 193 | } 194 | else 195 | pulse2.enabled = true; 196 | 197 | if((value & 0x04) == 0) 198 | { 199 | triangle.enabled = false; 200 | triangle.lengthCounter.value = 0; 201 | } 202 | else 203 | triangle.enabled = true; 204 | 205 | if((value & 0x08) == 0) 206 | { 207 | noise.enabled = false; 208 | noise.lengthCounter.value = 0; 209 | } 210 | else 211 | noise.enabled = true; 212 | 213 | if((value & 0x10) == 0) 214 | { 215 | dmc.enabled = false; 216 | dmc.bytesRemaining = 0; 217 | } 218 | else 219 | { 220 | dmc.enabled = true; 221 | if(dmc.bytesRemaining == 0) 222 | { 223 | dmc.currentAddresss = dmc.sampleAddress; 224 | dmc.bytesRemaining = dmc.sampleLength; 225 | } 226 | } 227 | 228 | dmc.IRQ = false; 229 | break; 230 | case 0x4017: 231 | frameCounterMode = (value & 0x80) ? FRAMECOUNTERMODE::MODE5 : FRAMECOUNTERMODE::MODE4; 232 | incomingFrameCounterReset = true; 233 | countToFrameCounterReset = 3 + ((clock % 2) ? 1 : 0); 234 | break; 235 | default: 236 | break; 237 | } 238 | } 239 | 240 | 241 | float APU::output() 242 | { 243 | float output = pulseTable[pulse1.output() + pulse2.output()] + tndTable[3 * triangle.output() + 2 * noise.output()]; 244 | 245 | output = high1.filter(output); 246 | output = high2.filter(output); 247 | output = low.filter(output); 248 | return output; 249 | } 250 | } -------------------------------------------------------------------------------- /State/StateManagement.h: -------------------------------------------------------------------------------- 1 | #ifndef STATE_MANAGEMENT_H 2 | #define STATE_MANAGEMENT_H 3 | 4 | #include 5 | #include 6 | 7 | #include "../Utils/handler.h" 8 | #include "../APU/AudioWaves.h" 9 | #include "../Utils/AudioFilters.h" 10 | #include "../Utils/Array.h" 11 | 12 | namespace nesemulator 13 | { 14 | 15 | namespace StateManagement 16 | { 17 | // Struct to store cartridge data for states 18 | struct CartridgeState 19 | { 20 | size_t PRGROMSize; 21 | size_t CHRROMSize; 22 | std::unique_ptr PRGROM; 23 | std::unique_ptr CHRROM; 24 | void initCartridge(const size_t& PRGROMSize,const size_t& CHRROMSize) 25 | { 26 | this->PRGROMSize = PRGROMSize; 27 | this->CHRROMSize = CHRROMSize; 28 | PRGROM = std::unique_ptr(new BYTE[PRGROMSize]); 29 | CHRROM = std::unique_ptr(new BYTE[CHRROMSize]); 30 | } 31 | 32 | void loadPRGData(BYTE* PRGROM) 33 | { 34 | std::memcpy((char*)this->PRGROM.get(),(char*)PRGROM,PRGROMSize); 35 | } 36 | 37 | void loadCHRData(BYTE* CHRROM) 38 | { 39 | std::memcpy((char*)this->CHRROM.get(),(char*)CHRROM,CHRROMSize); 40 | } 41 | }; 42 | 43 | 44 | // Struct to store CPU state 45 | struct CPUState 46 | { 47 | BYTE A; 48 | BYTE X; 49 | BYTE Y; 50 | BYTE SP; 51 | BYTE STATUS; 52 | TIMER clock; 53 | ADDRESS programCounter; 54 | BYTE cycleRemaining; 55 | BYTE additionalCycle0,additionalCycle1; 56 | BYTE currentOpCode; 57 | INSTRUCTION currentInstruction; 58 | }; 59 | 60 | struct APUState 61 | { 62 | PulseWave pulse1 = PulseWave(1); 63 | PulseWave pulse2 = PulseWave(0); 64 | TriangleWave triangle; 65 | NoiseWave noise; 66 | DMC dmc; 67 | TIMER clock; 68 | FLAG incomingFrameCounterReset; 69 | BYTE countToFrameCounterReset; 70 | AUDIOINT frameCounter; 71 | BYTE frameCounterMode; 72 | HighPassFilter high1 = HighPassFilter(90); 73 | HighPassFilter high2 = HighPassFilter(440); 74 | LowPassFilter low = LowPassFilter(14000); 75 | }; 76 | 77 | struct BusState 78 | { 79 | BYTE memory[2048]; 80 | BYTE controllerCache[2]; 81 | BYTE controllerMemory[2]; 82 | bool DMA,DMA_dummy; 83 | BYTE DMA_low; 84 | BYTE DMA_high; 85 | BYTE DMA_data; 86 | }; 87 | 88 | struct PPUBusState 89 | { 90 | BYTE nameTables[4096]; 91 | BYTE palettes[32]; 92 | PIXEL_RGB colors[64]; 93 | bool addressToggle = false; 94 | BYTE ppuBuffer; 95 | bool NMI = false; 96 | BYTE BG_PIXEL; 97 | BYTE BG_PALETTE; 98 | BYTE FG_PIXEL; 99 | BYTE FG_PALETTE; 100 | BYTE FG_PRIORITY; 101 | BYTE PPUSTATUS; 102 | BYTE PPUMASK; 103 | BYTE PPUCTRL; 104 | BYTE BG_NEXT_ID; // BG_RENDER_FETCH 105 | BYTE BG_NEXT_LOW; 106 | BYTE BG_NEXT_HIGH; 107 | BYTE BG_NEXT_ATTR; 108 | ADDRESS BG_SHIFTER_PATTERN_LOW; 109 | ADDRESS BG_SHIFTER_PATTERN_HIGH; 110 | ADDRESS BG_SHIFTER_ATTR_LOW; 111 | ADDRESS BG_SHIFTER_ATTR_HIGH; 112 | ADDRESS LOOPY; 113 | ADDRESS vRAM; 114 | ADDRESS tempRAM; 115 | BYTE FINE_X; 116 | OBJECT_ATTRIBUTE OAM[64]; 117 | BYTE OAM_offset; 118 | Array SPRT_SHIFTER_LOW; 119 | Array SPRT_SHIFTER_HIGH; 120 | BYTE SPRT_PATTERN_LOW,SPRT_PATTERN_HIGH; 121 | ADDRESS SPRT_PATTERN_ADDR_L; 122 | }; 123 | 124 | 125 | namespace Mapper 126 | { 127 | struct MapperState 128 | { 129 | 130 | }; 131 | 132 | struct Mapper1State : MapperState 133 | { 134 | BYTE PRGRAM[8*1024]; 135 | struct { 136 | BYTE controlReg,tempReg,tempRegNum; 137 | } REGISTERS; 138 | BYTE CHR0; 139 | BYTE CHR1; 140 | BYTE CHRFull; 141 | BYTE PRG0; 142 | BYTE PRG1; 143 | BYTE PRGFull; 144 | }; 145 | 146 | struct Mapper2State : MapperState 147 | { 148 | BYTE lowerBankOffset; 149 | BYTE higherBankOffset; 150 | }; 151 | 152 | struct Mapper3State : MapperState 153 | { 154 | BYTE selectedCHRbank; 155 | }; 156 | } 157 | 158 | 159 | struct PPUState 160 | { 161 | short int col,row; 162 | FLAG frameDone; 163 | Array nextRowSprites; 164 | BYTE pixel; 165 | BYTE palette; 166 | FLAG spriteZeroIndicator; 167 | FLAG spriteZero; 168 | FLAG odd; 169 | }; 170 | 171 | 172 | 173 | struct State 174 | { 175 | std::string fileName; 176 | short mapperID; 177 | BYTE mirroringMode; /* 0 = horizontal, 1 = vertical */ 178 | TIMER systemTime; 179 | double innerClock; 180 | CartridgeState cartridge; 181 | CPUState cpu; 182 | APUState apu; 183 | BusState bus; 184 | PPUBusState ppuBus; 185 | BYTE controllerState[2]; 186 | std::shared_ptr mapper; 187 | PPUState ppu; 188 | void writeToFile(const std::string& fileName) 189 | { 190 | std::ofstream file(fileName, std::ios::binary); 191 | 192 | file.write((char*)&mapperID, sizeof(short)); 193 | file.write((char*)&mirroringMode, 1); 194 | file.write((char*)&systemTime, sizeof(TIMER)); 195 | file.write((char*)&innerClock, sizeof(double)); 196 | 197 | file.write((char*)&apu,sizeof(APUState)); 198 | file.write((char*)&cpu,sizeof(CPUState)); 199 | file.write((char*)&bus,sizeof(BusState)); 200 | 201 | switch (mapperID) 202 | { 203 | case 0: break; 204 | case 1: file.write((char*)mapper.get(),sizeof(Mapper::Mapper1State)); break; 205 | case 2: file.write((char*)mapper.get(),sizeof(Mapper::Mapper2State)); break; 206 | case 3: file.write((char*)mapper.get(),sizeof(Mapper::Mapper3State)); break; 207 | default: 208 | break; 209 | } 210 | 211 | file.write((char*)&ppu.col, sizeof(short)); 212 | file.write((char*)&ppu.row, sizeof(short)); 213 | file.write((char*)&ppu.frameDone, 1); 214 | file.write((char*)&ppu.odd, 1); 215 | file.write((char*)&ppu.palette, 1); 216 | file.write((char*)&ppu.pixel, 1); 217 | file.write((char*)&ppu.spriteZero, 1); 218 | file.write((char*)&ppu.spriteZeroIndicator, 1); 219 | int size = ppu.nextRowSprites.getSize(); 220 | file.write((char*)&size,sizeof(int)); 221 | file.write((char*)ppu.nextRowSprites.getRaw(),sizeof(OBJECT_ATTRIBUTE) * size); 222 | 223 | file.write((char*)&ppuBus.addressToggle, sizeof(bool)); 224 | file.write((char*)&ppuBus.BG_NEXT_ATTR, 1); 225 | file.write((char*)&ppuBus.BG_NEXT_HIGH, 1); 226 | file.write((char*)&ppuBus.BG_NEXT_ID, 1); 227 | file.write((char*)&ppuBus.BG_NEXT_LOW, 1); 228 | file.write((char*)&ppuBus.BG_PALETTE, 1); 229 | file.write((char*)&ppuBus.BG_PIXEL, 1); 230 | file.write((char*)&ppuBus.BG_SHIFTER_ATTR_HIGH, sizeof(ADDRESS)); 231 | file.write((char*)&ppuBus.BG_SHIFTER_ATTR_LOW, sizeof(ADDRESS)); 232 | file.write((char*)&ppuBus.BG_SHIFTER_PATTERN_HIGH, sizeof(ADDRESS)); 233 | file.write((char*)&ppuBus.BG_SHIFTER_PATTERN_LOW, sizeof(ADDRESS)); 234 | file.write((char*)&ppuBus.colors, sizeof(PIXEL_RGB) * 64); 235 | file.write((char*)&ppuBus.FG_PALETTE, 1); 236 | file.write((char*)&ppuBus.FG_PIXEL, 1); 237 | file.write((char*)&ppuBus.FG_PRIORITY, 1); 238 | file.write((char*)&ppuBus.FINE_X, 1); 239 | file.write((char*)&ppuBus.nameTables, 4096); 240 | file.write((char*)&ppuBus.NMI, sizeof(bool)); 241 | file.write((char*)&ppuBus.OAM, sizeof(OBJECT_ATTRIBUTE) * 64); 242 | file.write((char*)&ppuBus.OAM_offset, 1); 243 | file.write((char*)&ppuBus.palettes, 32); 244 | file.write((char*)&ppuBus.ppuBuffer, 1); 245 | file.write((char*)&ppuBus.PPUCTRL, 1); 246 | file.write((char*)&ppuBus.PPUMASK, 1); 247 | file.write((char*)&ppuBus.PPUSTATUS, 1); 248 | file.write((char*)&ppuBus.SPRT_PATTERN_ADDR_L, sizeof(ADDRESS)); 249 | file.write((char*)&ppuBus.SPRT_PATTERN_HIGH, 1); 250 | file.write((char*)&ppuBus.SPRT_PATTERN_LOW, 1); 251 | file.write((char*)&ppuBus.tempRAM, sizeof(ADDRESS)); 252 | file.write((char*)&ppuBus.vRAM, sizeof(ADDRESS)); 253 | size = ppuBus.SPRT_SHIFTER_HIGH.getSize(); 254 | file.write((char*)&size,sizeof(int)); 255 | file.write((char*)ppuBus.SPRT_SHIFTER_HIGH.getRaw(), size); 256 | size = ppuBus.SPRT_SHIFTER_LOW.getSize(); 257 | file.write((char*)&size,sizeof(int)); 258 | file.write((char*)ppuBus.SPRT_SHIFTER_LOW.getRaw(), size); 259 | 260 | file.write((char*)&cartridge.PRGROMSize,sizeof(size_t)); 261 | file.write((char*)cartridge.PRGROM.get(), cartridge.PRGROMSize); 262 | file.write((char*)&cartridge.CHRROMSize,sizeof(size_t)); 263 | file.write((char*)cartridge.CHRROM.get(), cartridge.CHRROMSize); 264 | } 265 | 266 | void readFromFile(const std::string& fileName) 267 | { 268 | std::ifstream file(fileName, std::ios::binary); 269 | 270 | file.read((char*)&mapperID, sizeof(short)); 271 | file.read((char*)&mirroringMode, 1); 272 | file.read((char*)&systemTime, sizeof(TIMER)); 273 | file.read((char*)&innerClock, sizeof(double)); 274 | 275 | file.read((char*)&apu,sizeof(APUState)); 276 | file.read((char*)&cpu,sizeof(CPUState)); 277 | file.read((char*)&bus,sizeof(BusState)); 278 | 279 | switch (mapperID) 280 | { 281 | case 0: break; 282 | case 1: mapper = std::make_shared(); file.read((char*)mapper.get(),sizeof(Mapper::Mapper1State)); break; 283 | case 2: mapper = std::make_shared(); file.read((char*)mapper.get(),sizeof(Mapper::Mapper2State)); break; 284 | case 3: mapper = std::make_shared(); file.read((char*)mapper.get(),sizeof(Mapper::Mapper3State)); break; 285 | default: 286 | break; 287 | } 288 | 289 | 290 | 291 | file.read((char*)&ppu.col, sizeof(short)); 292 | file.read((char*)&ppu.row, sizeof(short)); 293 | file.read((char*)&ppu.frameDone, 1); 294 | file.read((char*)&ppu.odd, 1); 295 | file.read((char*)&ppu.palette, 1); 296 | file.read((char*)&ppu.pixel, 1); 297 | file.read((char*)&ppu.spriteZero, 1); 298 | file.read((char*)&ppu.spriteZeroIndicator, 1); 299 | int size; 300 | file.read((char*)&size,sizeof(int)); 301 | OBJECT_ATTRIBUTE* arr = new OBJECT_ATTRIBUTE[size]; 302 | file.read((char*)arr,sizeof(OBJECT_ATTRIBUTE) * size); 303 | ppu.nextRowSprites = Array(arr,size); 304 | file.read((char*)&ppuBus.addressToggle, sizeof(bool)); 305 | file.read((char*)&ppuBus.BG_NEXT_ATTR, 1); 306 | file.read((char*)&ppuBus.BG_NEXT_HIGH, 1); 307 | file.read((char*)&ppuBus.BG_NEXT_ID, 1); 308 | file.read((char*)&ppuBus.BG_NEXT_LOW, 1); 309 | file.read((char*)&ppuBus.BG_PALETTE, 1); 310 | file.read((char*)&ppuBus.BG_PIXEL, 1); 311 | file.read((char*)&ppuBus.BG_SHIFTER_ATTR_HIGH, sizeof(ADDRESS)); 312 | file.read((char*)&ppuBus.BG_SHIFTER_ATTR_LOW, sizeof(ADDRESS)); 313 | file.read((char*)&ppuBus.BG_SHIFTER_PATTERN_HIGH, sizeof(ADDRESS)); 314 | file.read((char*)&ppuBus.BG_SHIFTER_PATTERN_LOW, sizeof(ADDRESS)); 315 | file.read((char*)&ppuBus.colors, sizeof(PIXEL_RGB) * 64); 316 | file.read((char*)&ppuBus.FG_PALETTE, 1); 317 | file.read((char*)&ppuBus.FG_PIXEL, 1); 318 | file.read((char*)&ppuBus.FG_PRIORITY, 1); 319 | file.read((char*)&ppuBus.FINE_X, 1); 320 | file.read((char*)&ppuBus.nameTables, 4096); 321 | file.read((char*)&ppuBus.NMI, sizeof(bool)); 322 | file.read((char*)&ppuBus.OAM, sizeof(OBJECT_ATTRIBUTE) * 64); 323 | file.read((char*)&ppuBus.OAM_offset, 1); 324 | file.read((char*)&ppuBus.palettes, 32); 325 | file.read((char*)&ppuBus.ppuBuffer, 1); 326 | file.read((char*)&ppuBus.PPUCTRL, 1); 327 | file.read((char*)&ppuBus.PPUMASK, 1); 328 | file.read((char*)&ppuBus.PPUSTATUS, 1); 329 | file.read((char*)&ppuBus.SPRT_PATTERN_ADDR_L, sizeof(ADDRESS)); 330 | file.read((char*)&ppuBus.SPRT_PATTERN_HIGH, 1); 331 | file.read((char*)&ppuBus.SPRT_PATTERN_LOW, 1); 332 | file.read((char*)&ppuBus.tempRAM, sizeof(ADDRESS)); 333 | file.read((char*)&ppuBus.vRAM, sizeof(ADDRESS)); 334 | 335 | file.read((char*)&size,sizeof(int)); 336 | BYTE* bArr = new BYTE[size]; 337 | file.read((char*)bArr, size); 338 | ppuBus.SPRT_SHIFTER_HIGH = Array(bArr,size); 339 | file.read((char*)&size,sizeof(int)); 340 | bArr = new BYTE[size]; 341 | file.read((char*)bArr, size); 342 | ppuBus.SPRT_SHIFTER_LOW = Array(bArr,size); 343 | 344 | 345 | file.read((char*)&cartridge.PRGROMSize,sizeof(size_t)); 346 | cartridge.PRGROM = std::unique_ptr(new BYTE[cartridge.PRGROMSize]); 347 | file.read((char*)cartridge.PRGROM.get(), cartridge.PRGROMSize); 348 | file.read((char*)&cartridge.CHRROMSize,sizeof(size_t)); 349 | cartridge.CHRROM = std::unique_ptr(new BYTE[cartridge.CHRROMSize]); 350 | file.read((char*)cartridge.CHRROM.get(), cartridge.CHRROMSize); 351 | } 352 | }; 353 | } 354 | 355 | } 356 | 357 | #endif 358 | -------------------------------------------------------------------------------- /PPU/PPU.cpp: -------------------------------------------------------------------------------- 1 | #include "PPU.h" 2 | 3 | namespace nesemulator 4 | { 5 | // Shifters are stores for which pixels and palettes going to be used for next 8 pixels 6 | // While every bit of low and high pattern shifter representing 1 pixel (LSB and MSB), 7 | // Palette (Attribute) actually depends on only which tile is going to be rendered 8 | // So it is going to be same for next 8 pixels,but we also put them in a shifter and rotate 9 | // To make things easier while rendering 10 | // First 8 bits of shifters represents pixel going to be rendered in this tile and (higher 8 bits) 11 | // Other 8 bits of shifter represents pixels going to be rendered in next tile (lower 8 bits) 12 | // X X X X X X X X X Y Y Y Y Y Y Y Y Y <- one of shifters represented (16 bit) 13 | // Current tile Next tile 14 | void PPU::loadShifters() 15 | { 16 | ppuBus->BG_SHIFTER_PATTERN_LOW.NEXT = ppuBus->BG_RENDER_FETCH.BG_NEXT_LOW; 17 | ppuBus->BG_SHIFTER_PATTERN_HIGH.NEXT = ppuBus->BG_RENDER_FETCH.BG_NEXT_HIGH; 18 | ppuBus->BG_SHIFTER_ATTR_LOW.NEXT = (ppuBus->BG_RENDER_FETCH.BG_NEXT_ATTR & 0x01) ? 0xFF : 0x00; 19 | ppuBus->BG_SHIFTER_ATTR_HIGH.NEXT = (ppuBus->BG_RENDER_FETCH.BG_NEXT_ATTR & 0x02) ? 0xFF : 0x00; 20 | } 21 | 22 | 23 | 24 | // Shift shifters if currently rendering 25 | void PPU::shift() 26 | { 27 | if(ppuBus->PPUMASK.RENDER_BCKGRD == 1) 28 | { 29 | ppuBus->BG_SHIFTER_PATTERN_LOW.combined <<= 1; 30 | ppuBus->BG_SHIFTER_PATTERN_HIGH.combined <<= 1; 31 | ppuBus->BG_SHIFTER_ATTR_LOW.combined <<= 1; 32 | ppuBus->BG_SHIFTER_ATTR_HIGH.combined <<= 1; 33 | } 34 | 35 | if(ppuBus->PPUMASK.RENDER_SPRTS && col >= 1 && col <= 257) 36 | for(int i = 0;i < nextRowSprites.getSize();i++) 37 | { 38 | if(nextRowSprites[i].x > 0) nextRowSprites[i].x--; 39 | else 40 | { 41 | ppuBus->SPRT_SHIFTER_LOW[i] <<= 1; 42 | ppuBus->SPRT_SHIFTER_HIGH[i] <<= 1; 43 | } 44 | } 45 | } 46 | 47 | 48 | 49 | /* This function used to reset PPU register and Sprite Shifters before every frame render */ 50 | void PPU::frameReset() 51 | { 52 | ppuBus->PPUSTATUS.VBLANK = 0; 53 | ppuBus->PPUSTATUS.SPRT_ZERO_HIT = 0; 54 | ppuBus->PPUSTATUS.SPRT_OVERFLOW = 0; 55 | for(auto element : ppuBus->SPRT_SHIFTER_LOW) 56 | element = 0; 57 | for(auto element : ppuBus->SPRT_SHIFTER_HIGH) 58 | element = 0; 59 | 60 | } 61 | 62 | 63 | /* Load Sprites Array with first 8 sprites to occur in next row (scanline) */ 64 | void PPU::setSpritesForNextRow() 65 | { 66 | nextRowSprites.clear(); 67 | for(auto element : ppuBus->SPRT_SHIFTER_LOW) 68 | element = 0; 69 | for(auto element : ppuBus->SPRT_SHIFTER_HIGH) 70 | element = 0; 71 | 72 | spriteZeroIndicator = false; 73 | 74 | for(int i = 0;i < 64 && nextRowSprites.getSize() < 9;i++) 75 | { 76 | ADDRESS offset = ((ADDRESS)row - ((ADDRESS)ppuBus->OAM[i].y)); // Get offset to current row 77 | if(offset >= 0 && offset < (ppuBus->PPUCTRL.SPRT_SIZE ? 16 : 8)) 78 | { 79 | if(nextRowSprites.getSize() < 8) 80 | { 81 | // If we are going to render 0th sprite next row that means sprite 0 might render 82 | if(i == 0) 83 | spriteZeroIndicator = true; 84 | nextRowSprites.add(ppuBus->OAM[i]); 85 | } 86 | 87 | } 88 | } 89 | ppuBus->PPUSTATUS.SPRT_OVERFLOW = (nextRowSprites.getSize() > 8) ? 1 : 0; // Check sprite overflow 90 | } 91 | 92 | 93 | /* Find which parts of found sprites to be rendered for next row and also find their orientation depending on their attribute */ 94 | void PPU::loadSpritesForNextRow() 95 | { 96 | for(int i = 0;i < nextRowSprites.getSize();i++) 97 | { 98 | ppuBus->SPRT_PATTERN_LOW = 0x00;ppuBus->SPRT_PATTERN_HIGH = 0x00; 99 | ppuBus->SPRT_PATTERN_ADDR_L = 0x0000; 100 | 101 | // Check Vertical orientation of sprite first according to sprite size (8x8 or 8x16). 102 | // Because Vertical flip changes which bytes of sprite are going to be read 103 | // Besides horizontal flip only changes order of that particular byte 104 | if(ppuBus->PPUCTRL.SPRT_SIZE == 0) 105 | { 106 | if(!(nextRowSprites[i].attribute & 0x80)) 107 | ppuBus->SPRT_PATTERN_ADDR_L = (ppuBus->PPUCTRL.PATTERN_SPRT << 12) | (nextRowSprites[i].id << 4) | (row - nextRowSprites[i].y); 108 | 109 | else 110 | ppuBus->SPRT_PATTERN_ADDR_L = (ppuBus->PPUCTRL.PATTERN_SPRT << 12) | (nextRowSprites[i].id << 4) | (7 - row + nextRowSprites[i].y); 111 | } 112 | else 113 | { 114 | if(!(nextRowSprites[i].attribute & 0x80)) 115 | { 116 | if(row - nextRowSprites[i].y < 8) 117 | ppuBus->SPRT_PATTERN_ADDR_L = ((ppuBus->PPUCTRL.PATTERN_SPRT & 0x01) << 12) | ((nextRowSprites[i].id & 0xFE) << 4) | ((row - nextRowSprites[i].y) & 0x07); 118 | else 119 | ppuBus->SPRT_PATTERN_ADDR_L = ((ppuBus->PPUCTRL.PATTERN_SPRT & 0x01) << 12) | (((nextRowSprites[i].id & 0xFE) + 1) << 4) | ((row - nextRowSprites[i].y) & 0x07); 120 | } 121 | else 122 | { 123 | if(row - nextRowSprites[i].y < 8) 124 | ppuBus->SPRT_PATTERN_ADDR_L = ((ppuBus->PPUCTRL.PATTERN_SPRT & 0x01) << 12) | (((nextRowSprites[i].id & 0xFE) + 1) << 4) | ((7 - row + nextRowSprites[i].y) & 0x07); 125 | else 126 | ppuBus->SPRT_PATTERN_ADDR_L = ((ppuBus->PPUCTRL.PATTERN_SPRT & 0x01) << 12) | ((nextRowSprites[i].id & 0xFE) << 4) | ((7 - row + nextRowSprites[i].y) & 0x07); 127 | } 128 | } 129 | 130 | ppuBus->SPRT_PATTERN_LOW = ppuBus->readFromMemory(ppuBus->SPRT_PATTERN_ADDR_L); 131 | ppuBus->SPRT_PATTERN_HIGH = ppuBus->readFromMemory(ppuBus->SPRT_PATTERN_ADDR_L + 8); 132 | 133 | if(nextRowSprites[i].attribute & 0x40) // Horizontal Flip orientation 134 | { 135 | reverseByte(&ppuBus->SPRT_PATTERN_LOW); 136 | reverseByte(&ppuBus->SPRT_PATTERN_HIGH); 137 | } 138 | 139 | ppuBus->SPRT_SHIFTER_LOW[i] = ppuBus->SPRT_PATTERN_LOW; 140 | ppuBus->SPRT_SHIFTER_HIGH[i] = ppuBus->SPRT_PATTERN_HIGH; 141 | } 142 | } 143 | 144 | void PPU::setBackgroundPixel() 145 | { 146 | if(ppuBus->PPUMASK.RENDER_BCKGRD) // Check background 147 | { 148 | // set BG Pixel and PG Palette according to offset in current tile (FINE_X) 149 | BYTE pL = (ppuBus->BG_SHIFTER_PATTERN_LOW.combined & (0x8000 >> ppuBus->FINE_X)) ? 1 : 0; 150 | BYTE pH = (ppuBus->BG_SHIFTER_PATTERN_HIGH.combined & (0x8000 >> ppuBus->FINE_X)) ? 1 : 0; 151 | 152 | ppuBus->BG_PIXEL = (pH << 1) | pL; 153 | 154 | pL = (ppuBus->BG_SHIFTER_ATTR_LOW.combined & (0x8000 >> ppuBus->FINE_X)) ? 1 : 0; 155 | pH = (ppuBus->BG_SHIFTER_ATTR_HIGH.combined & (0x8000 >> ppuBus->FINE_X)) ? 1 : 0; 156 | 157 | ppuBus->BG_PALETTE = (pH << 1) | pL; 158 | 159 | } 160 | else 161 | { 162 | ppuBus->BG_PIXEL = 0x00; 163 | ppuBus->BG_PALETTE = 0x00; 164 | } 165 | } 166 | 167 | 168 | void PPU::setForegroundPixel() 169 | { 170 | ppuBus->FG_PALETTE = 0x00; 171 | ppuBus->FG_PIXEL = 0x00; 172 | ppuBus->FG_PRIORITY = 0x00; 173 | 174 | if(ppuBus->PPUMASK.RENDER_SPRTS) // Check foreground 175 | { 176 | spriteZero = false; 177 | for(int i = 0;i < nextRowSprites.getSize(); i++) 178 | { 179 | if(nextRowSprites[i].x == 0) 180 | { 181 | BYTE pL = (ppuBus->SPRT_SHIFTER_LOW[i] & 0x80) > 0; 182 | BYTE pH = (ppuBus->SPRT_SHIFTER_HIGH[i] & 0x80) > 0; 183 | ppuBus->FG_PIXEL = (pH << 1) | pL; 184 | 185 | ppuBus->FG_PALETTE = (nextRowSprites[i].attribute & 0x03) + 0x04; 186 | ppuBus->FG_PRIORITY = (nextRowSprites[i].attribute & 0x20) == 0; 187 | 188 | // If this is the first sprite (sprite-zero) going to be rendered set Flag 189 | if(ppuBus->FG_PIXEL != 0) 190 | { 191 | if(i == 0) 192 | spriteZero = true; 193 | break; 194 | } 195 | } 196 | } 197 | } 198 | } 199 | 200 | 201 | 202 | // Now after a long proccess we have both background and foreground pixel for current pixel 203 | // All we need to is compare their priority now 204 | // This priority depending on which one is zero and which one is not 205 | // If both of them are zero that means current pixel actually transparent 206 | // But if both of them are set than which one is going to be rendered selected according to 207 | // priority flag of foreground pixel that read from attiribute of sprite in OAM 208 | // Here is a table for that (Not value 1 is symbolic and represents non-zero value) 209 | // ------------------------------- 210 | // | BG | FG | result | 211 | // |-----------------------------| 212 | // | 0 | 0 |TRANSPARENT| 213 | // |-----------------------------| 214 | // | 1 | 0 | BG | 215 | // |-----------------------------| 216 | // | 0 | 1 | fG | 217 | // |-----------------------------| 218 | // | 1 | 1 |FG_PRIORITY| 219 | // ------------------------------- 220 | void PPU::getFinalPixel() 221 | { 222 | if(ppuBus->BG_PIXEL == 0x00 && ppuBus->FG_PIXEL == 0x00) 223 | { 224 | pixel = 0x00; 225 | palette = 0x00; 226 | } 227 | else if(ppuBus->BG_PIXEL == 0x00 && ppuBus->FG_PIXEL > 0x00) 228 | { 229 | pixel = ppuBus->FG_PIXEL; 230 | palette = ppuBus->FG_PALETTE; 231 | } 232 | else if(ppuBus->BG_PIXEL > 0x00 && ppuBus->FG_PIXEL == 0x00) 233 | { 234 | pixel = ppuBus->BG_PIXEL; 235 | palette = ppuBus->BG_PALETTE; 236 | } 237 | else if(ppuBus->BG_PIXEL > 0x00 && ppuBus->FG_PIXEL > 0x00) 238 | { 239 | pixel = ((ppuBus->FG_PRIORITY) ? ppuBus->FG_PIXEL : ppuBus->BG_PIXEL); 240 | palette = ((ppuBus->FG_PRIORITY) ? ppuBus->FG_PALETTE : ppuBus->BG_PALETTE); 241 | 242 | if(spriteZero && spriteZeroIndicator && col < 258) 243 | if(ppuBus->PPUMASK.RENDER_BCKGRD & ppuBus->PPUMASK.RENDER_SPRTS) 244 | if(~(ppuBus->PPUMASK.RENDER_BCKGRND_LEFT | ppuBus->PPUMASK.RENDER_SPRTS_LEFT)) 245 | if(col > 8) 246 | ppuBus->PPUSTATUS.SPRT_ZERO_HIT = 1; 247 | else if(col > 0) 248 | ppuBus->PPUSTATUS.SPRT_ZERO_HIT = 1; 249 | 250 | } 251 | } 252 | 253 | 254 | void PPU::tick() 255 | { 256 | 257 | #ifdef PPUDEBUG 258 | log(); 259 | #endif 260 | 261 | 262 | 263 | if(row >= -1 && row < 240) // If currently rendering visible screen rows 264 | { 265 | if(row == 0 && col == 0 && odd & (ppuBus->PPUMASK.RENDER_BCKGRD || ppuBus->PPUMASK.RENDER_SPRTS)) // Skip odd frame becase this is technically is last pixel of last row 266 | col = 1; 267 | if(row == -1 && col == 1) // Reset registers and vblank for next frame 268 | frameReset(); 269 | if((col >= 2 && col < 258) || (col >= 321 && col < 338)) 270 | { 271 | shift(); // To update both backgorund and foreground shifters 272 | 273 | 274 | // While we are rendering current tile,actually also we are fetching next tile in every step 275 | // In start of current tile (1st pixel of tile) -> Get ID of next tile 276 | // In 3rd pixel of current tile -> fetch attirubte (palette) of next tile from attribute table which is in the end of every nametable 277 | // In 5th pixel of current tile -> fetch LSBs of next tile 278 | // In 7th pixel of current tile -> fetch MSBs of next tile 279 | // In last pixel of current tile -> Increase number of tiles rendered (coarse X) 280 | // NOTE: As you may notice every fetch (pattern ID,Attribute,LSB,MSB) takes 2 PPU cycles 281 | int a = (col - 1) % 8; 282 | 283 | if(a == 0) // Get Next tile ID and load shifters for next tile 284 | { 285 | loadShifters(); 286 | 287 | ppuBus->BG_RENDER_FETCH.BG_NEXT_ID = ppuBus->readFromMemory(0x2000 | (ppuBus->vRAM.combined & 0x0FFF)); 288 | } 289 | else if(a == 2) // Get Next tile Attribute from Attribute Table 290 | { 291 | ppuBus->BG_RENDER_FETCH.BG_NEXT_ATTR = ppuBus->readFromMemory(0x23C0 | (ppuBus->vRAM.NT_Y << 11) 292 | | (ppuBus->vRAM.NT_X << 10) 293 | | ((ppuBus->vRAM.CO_Y >> 2) << 3) 294 | | (ppuBus->vRAM.CO_X >> 2)); 295 | if(ppuBus->vRAM.CO_Y & 0x02) ppuBus->BG_RENDER_FETCH.BG_NEXT_ATTR >>= 4; 296 | if(ppuBus->vRAM.CO_X & 0x02) ppuBus->BG_RENDER_FETCH.BG_NEXT_ATTR >>= 2; 297 | ppuBus->BG_RENDER_FETCH.BG_NEXT_ATTR &= 0x03; 298 | } 299 | else if(a == 4) // Get Next tile LSBs 300 | ppuBus->BG_RENDER_FETCH.BG_NEXT_LOW = ppuBus->readFromMemory((ppuBus->PPUCTRL.PATTERN_BCKGRND << 12) 301 | + ((ADDRESS)ppuBus->BG_RENDER_FETCH.BG_NEXT_ID << 4) 302 | + (ppuBus->vRAM.FINE_Y)); 303 | else if(a == 6) // Get Next tile MSBs 304 | ppuBus->BG_RENDER_FETCH.BG_NEXT_HIGH = ppuBus->readFromMemory((ppuBus->PPUCTRL.PATTERN_BCKGRND << 12) 305 | + ((ADDRESS)ppuBus->BG_RENDER_FETCH.BG_NEXT_ID << 4) 306 | + (ppuBus->vRAM.FINE_Y) + 8); 307 | else if(a == 7) // Load shifters 308 | if(ppuBus->PPUMASK.RENDER_BCKGRD || ppuBus->PPUMASK.RENDER_SPRTS) 309 | { 310 | if(ppuBus->vRAM.CO_X == 31) // 32th CO_X means end of the row 311 | { 312 | ppuBus->vRAM.CO_X = 0; 313 | ppuBus->vRAM.NT_X = ~ppuBus->vRAM.NT_X; 314 | } 315 | else 316 | ppuBus->vRAM.CO_X++; 317 | } 318 | } 319 | if(col == 256) 320 | if(ppuBus->PPUMASK.RENDER_BCKGRD || ppuBus->PPUMASK.RENDER_SPRTS) 321 | { 322 | if(ppuBus->vRAM.FINE_Y < 7) 323 | ppuBus->vRAM.FINE_Y++; 324 | else 325 | { 326 | ppuBus->vRAM.FINE_Y = 0; 327 | if(ppuBus->vRAM.CO_Y == 29) 328 | { 329 | ppuBus->vRAM.CO_Y = 0; 330 | ppuBus->vRAM.NT_Y = ~ppuBus->vRAM.NT_Y; 331 | } 332 | else if(ppuBus->vRAM.CO_Y == 31) 333 | ppuBus->vRAM.CO_Y = 0; 334 | else 335 | ppuBus->vRAM.CO_Y++; 336 | } 337 | } 338 | if(col == 257) 339 | { 340 | loadShifters(); 341 | if(ppuBus->PPUMASK.RENDER_BCKGRD || ppuBus->PPUMASK.RENDER_SPRTS) 342 | { 343 | ppuBus->vRAM.CO_X = ppuBus->tempRAM.CO_X; 344 | ppuBus->vRAM.NT_X = ppuBus->tempRAM.NT_X; 345 | } 346 | } 347 | if(col == 338 || col == 340) 348 | ppuBus->BG_RENDER_FETCH.BG_NEXT_ID = ppuBus->readFromMemory(0x2000 | (ppuBus->vRAM.combined & 0x0FFF)); 349 | if(row == -1 && col >= 280 && col < 305) 350 | if(ppuBus->PPUMASK.RENDER_BCKGRD || ppuBus->PPUMASK.RENDER_SPRTS) 351 | { 352 | ppuBus->vRAM.CO_Y = ppuBus->tempRAM.CO_Y; 353 | ppuBus->vRAM.NT_Y = ppuBus->tempRAM.NT_Y; 354 | ppuBus->vRAM.FINE_Y = ppuBus->tempRAM.FINE_Y; 355 | } 356 | 357 | if(col == 257 && row >= 0) 358 | setSpritesForNextRow(); 359 | 360 | if(col == 340) 361 | loadSpritesForNextRow(); 362 | } 363 | 364 | if(row == 241 && col == 1) 365 | { 366 | ppuBus->PPUSTATUS.VBLANK = 1; // Vertical blank has activated 367 | if(ppuBus->PPUCTRL.ENABLE_NMI) 368 | ppuBus->setNMI(true); 369 | } 370 | 371 | setBackgroundPixel(); 372 | 373 | setForegroundPixel(); 374 | 375 | getFinalPixel(); 376 | 377 | 378 | if(row < 240 && col < 257 && row > -1 && col > 0) 379 | display->setPixel(col, row, ppuBus->colors[ ppuBus->readFromMemory(0x3F00 + (palette << 2) + pixel) & 0x3F]); 380 | 381 | if(++col >= 341) 382 | { 383 | col = 0; 384 | if(++row >= 261) 385 | { 386 | row = -1; 387 | display->frameDone(); 388 | odd = !odd; 389 | } 390 | } 391 | 392 | } 393 | 394 | 395 | std::ostream& operator<<(std::ostream &out,PPU &ppu) 396 | { 397 | out << std::dec; 398 | out << "\nPPU COL : " << ppu.col <<" ROW : " << ppu.row; 399 | out << std::endl; 400 | 401 | return out; 402 | } 403 | 404 | 405 | #ifdef DEBUG 406 | void PPU::getPatternTable() 407 | { 408 | for(ADDRESS tileY = 0; tileY < 16; tileY++) 409 | for(ADDRESS tileX = 0; tileX < 16; tileX++) 410 | { 411 | ADDRESS offset = tileY * 256 + tileX * 16; 412 | for(BYTE row = 0;row < 8;row++) 413 | { 414 | BYTE lsb_1 = ppuBus->readFromMemory(0x1000 + offset + row + 0x0000); 415 | BYTE msb_1 = ppuBus->readFromMemory(0x1000 + offset + row + 0x0008); 416 | BYTE lsb_0 = ppuBus->readFromMemory(offset + row + 0x0000); 417 | BYTE msb_0 = ppuBus->readFromMemory(offset + row + 0x0008); 418 | for(BYTE col = 0;col < 8;col++) 419 | { 420 | BYTE pixel0 = (lsb_0 & 0x01) + (msb_0 & 0x01); 421 | BYTE pixel1 = (lsb_1 & 0x01) + (msb_1 & 0x01); 422 | lsb_1 >>= 1; 423 | msb_1 >>= 1; 424 | lsb_0 >>= 1; 425 | msb_0 >>= 1; 426 | display->setPixelDebug(tileX * 8 + (7 - col) + 142, (tileY * 8 + 5) + row, ppuBus->colors[ ppuBus->readFromMemory(0x3F00 + pixel1) & 0x3F]); 427 | display->setPixelDebug(tileX * 8 + (7 - col), (tileY * 8 + 5) + row, ppuBus->colors[ ppuBus->readFromMemory(0x3F00 + pixel0) & 0x3F]); 428 | } 429 | } 430 | } 431 | } 432 | #endif 433 | 434 | #ifdef PPUDEBUG 435 | 436 | void PPU::log() 437 | { 438 | fprintf(ppuLog,"VRAM:%04X TRAM:%04X TILE_ID:%02X SC:%d CY:%d SHIFTER:%04X MSB:%02X LSB:%02X\n",ppuBus->vRAM.combined,ppuBus->tempRAM.combined,ppuBus->BG_RENDER_FETCH.BG_NEXT_ID,row,col,ppuBus->BG_SHIFTER_PATTERN_HIGH.combined,ppuBus->BG_RENDER_FETCH.BG_NEXT_HIGH,ppuBus->BG_RENDER_FETCH.BG_NEXT_LOW); 439 | } 440 | #endif 441 | } 442 | 443 | 444 | 445 | -------------------------------------------------------------------------------- /System/System.cpp: -------------------------------------------------------------------------------- 1 | #include "System.h" 2 | 3 | namespace nesemulator 4 | { 5 | namespace 6 | { 7 | std::string bTohex(uint32_t n, uint8_t d) 8 | { 9 | std::string s(d, '0'); 10 | for (int i = d - 1; i >= 0; i--, n >>= 4) 11 | s[i] = "0123456789ABCDEF"[n & 0xF]; 12 | return s; 13 | }; 14 | } 15 | 16 | 17 | NES::NES() : cartridge(),apu(),ppuBus(),bus(ppuBus,apu),ppu(&display,&bus,&cartridge,&ppuBus),cpu(bus),controller(&bus),display(&events,controller) 18 | { 19 | } 20 | 21 | NES::NES(std::string fileName) : cartridge(),apu(),ppuBus(),bus(ppuBus,apu),ppu(&display,&bus,&cartridge,&ppuBus),cpu(bus),controller(&bus),display(&events,controller) 22 | { 23 | insertNESFile(fileName); 24 | } 25 | 26 | void NES::insertNESFile(std::string fileName) 27 | { 28 | std::ifstream file(fileName,std::ios::binary); 29 | FileHeader header; 30 | if(file.is_open()) 31 | { 32 | // Read header first 33 | file.read((char*)&header.name,4); 34 | file.read((char*)&header.PRGROMChunks,1); 35 | file.read((char*)&header.CHRROMChunks,1); 36 | file.read((char*)&header.mapper1,1); 37 | file.read((char*)&header.mapper2,1); 38 | file.read((char*)&header.PRGRAMSize,1); 39 | file.read((char*)&header.TVSystem1,1); 40 | file.read((char*)&header.TVSystem2,1); 41 | file.read((char*)&header.unused,5); 42 | if(header.mapper1 & 0x04) 43 | file.seekg(512,std::ios_base::cur); 44 | BYTE mapperID = ((header.mapper2 >> 4) << 4) | (header.mapper1 >> 4); 45 | bool isNES2 = (header.mapper2 & 0x0C) == 0x08; 46 | if(!isNES2) 47 | { 48 | std::cout << "NES File Type : 1.0" << std::endl; 49 | cartridge.setPRGNum(header.PRGROMChunks); 50 | BYTE* PRGmem = new BYTE[header.PRGROMChunks * 16384]; 51 | file.read((char*) PRGmem,header.PRGROMChunks * 16384); 52 | cartridge.loadPRGData(PRGmem); 53 | cartridge.setCHRNum(header.CHRROMChunks); 54 | BYTE* CHRmem; 55 | if(header.CHRROMChunks == 0) // If it is CHR RAM 56 | { 57 | cartridge.setCHRNum(1); 58 | CHRmem = new BYTE[8192]; 59 | } 60 | else 61 | { 62 | CHRmem = new BYTE[header.CHRROMChunks * 8192]; 63 | cartridge.setCHRNum(header.CHRROMChunks); 64 | } 65 | file.read((char*) CHRmem,(header.CHRROMChunks != 0 ? header.CHRROMChunks : 1) * 8192); 66 | cartridge.loadCHRData(CHRmem); 67 | } 68 | else 69 | { 70 | std::cout << "NES File Type : 2.0" << std::endl; 71 | cartridge.setPRGNum(((header.PRGRAMSize & 0x07) << 8) | header.PRGROMChunks); 72 | BYTE* PRGmem = new BYTE[cartridge.getPRGNum() * 16384]; 73 | file.read((char*) PRGmem,cartridge.getPRGNum() * 16384); 74 | cartridge.loadPRGData(PRGmem); 75 | cartridge.setCHRNum(((header.PRGRAMSize & 0x38) << 8) | header.CHRROMChunks); 76 | BYTE* CHRmem = new BYTE[cartridge.getCHRNum() * 8192]; 77 | file.read((char*) CHRmem,cartridge.getCHRNum() * 8192); 78 | cartridge.loadCHRData(CHRmem); 79 | } 80 | std::cout << "Mapper ID:" << mapperID; 81 | cartridge.setMapperID(mapperID); 82 | setMapper(); 83 | mapper->setMirroring((header.mapper1 & 0x01) ? MIRRORING::VERTICAL : MIRRORING::HORIZONTAL); 84 | file.close(); 85 | } 86 | else 87 | { 88 | std::cout << "FATAL ERROR: File couldn't read"; 89 | } 90 | } 91 | 92 | 93 | void NES::setMapper() 94 | { 95 | switch (cartridge.getMapperID()) 96 | { 97 | case 0:mapper = std::make_shared(&cartridge);break; 98 | case 1:mapper = std::make_shared(&cartridge);break; 99 | case 2:mapper = std::make_shared(&cartridge);break; 100 | case 3:mapper = std::make_shared(&cartridge);break; 101 | default: 102 | break; 103 | } 104 | 105 | bus.setMapper(mapper); 106 | ppuBus.setMapper(mapper); 107 | } 108 | 109 | 110 | 111 | void NES::log() 112 | { 113 | 114 | bus.print(); 115 | std::cout << cartridge; 116 | } 117 | 118 | 119 | void NES::start() 120 | { 121 | cpu.reset(); 122 | std::cout << "\nLOG: " << "Main Loop Has Started!\n"; 123 | 124 | mainLoop(); 125 | } 126 | 127 | 128 | void NES::mainLoop() 129 | { 130 | RUN_FOREVER 131 | { 132 | while(SDL_GetQueuedAudioSize(1) < SAMPLE_PER_FRAME) 133 | { 134 | tick(); 135 | if(innerClock >= cyclesPerSample) 136 | { 137 | Audio::addToQueue(apu.output()); 138 | innerClock -= cyclesPerSample; 139 | } 140 | innerClock += 1.0; 141 | } 142 | display.renderFrame(); 143 | 144 | if(controller.getSaveButtonStatus() && !hasSaved) 145 | saveCurrentState(); 146 | if(controller.getLoadButtonStatus() && !hasLoaded) 147 | loadState(); 148 | if(hasSaved && !controller.getSaveButtonStatus()) 149 | hasSaved = false; 150 | if(hasLoaded && !controller.getLoadButtonStatus()) 151 | hasLoaded = false; 152 | 153 | 154 | #ifdef DEBUG 155 | ppu.getPatternTable(); 156 | display.renderDebugFrame(); 157 | #endif 158 | } 159 | } 160 | 161 | 162 | void NES::tick() 163 | { 164 | ppu.tick(); 165 | 166 | 167 | if(clock % 3 == 0) // PPU of NES is 3x faster than CPU in clock-speed wise,so there happens a CPU tick for every 3x PPU tick (which also means a system tick) 168 | { 169 | apu.tick(); 170 | if(bus.getDMAStatus()) // Check DMA status from main bus before every CPU tick 171 | DMA(); 172 | else 173 | cpu.tick(); 174 | } 175 | 176 | if(ppuBus.getNMI()) // If a Non-maskable Interrupt about to happen 177 | { 178 | cpu.NMI(); 179 | ppuBus.setNMI(false); 180 | } 181 | 182 | 183 | clock++; 184 | } 185 | 186 | 187 | 188 | 189 | 190 | void NES::DMA() // Direct Memory Access 191 | { 192 | if(bus.DMA_dummy) // Because of limitations of NES hardware,first odd CPU clock in start of DMA is a dummy-write 193 | { 194 | if(clock % 2 == 1) 195 | bus.DMA_dummy = false; 196 | } 197 | else // AFter dummy-write,DMA reads address from bus for next write in every even CPU cycle and writes to PPU Bus OAM via main bus in every odd CPU cycle 198 | { 199 | if(clock % 2 == 0) 200 | bus.DMAReadNext(); 201 | else 202 | { 203 | bus.writeOAM(); 204 | if(bus.DMA_low == 0x00) // DMA is done after writing 256 bytes (which is equal to approximately 1 CPU page) 205 | { 206 | bus.DMA = false; 207 | bus.DMA_dummy = true; 208 | } 209 | } 210 | } 211 | } 212 | 213 | void NES::saveCurrentState() 214 | { 215 | display.getNotificationManager().addNotification("Saving current state...",ui::notificationColors::info, 3000); 216 | StateManagement::State state; 217 | state.cartridge.initCartridge(cartridge.getPRGNum() * 16384, cartridge.getCHRNum() * 8192); 218 | state.cartridge.loadPRGData(cartridge.PRGmemory.get()); 219 | state.cartridge.loadCHRData(cartridge.CHRmemory.get()); 220 | state.apu.clock = apu.clock; 221 | state.apu.countToFrameCounterReset = apu.countToFrameCounterReset; 222 | state.apu.dmc = apu.dmc; 223 | state.apu.frameCounter = apu.frameCounter; 224 | state.apu.frameCounterMode = apu.frameCounterMode == APU::FRAMECOUNTERMODE::MODE4 ? 0 : 1; 225 | state.apu.high1 = apu.high1; 226 | state.apu.high2 = apu.high2; 227 | state.apu.incomingFrameCounterReset = apu.incomingFrameCounterReset; 228 | state.apu.low = apu.low; 229 | state.apu.noise = apu.noise; 230 | state.apu.pulse1 = apu.pulse1; 231 | state.apu.pulse2 = apu.pulse2; 232 | state.apu.triangle = apu.triangle; 233 | 234 | state.cpu.A = cpu.A; 235 | state.cpu.additionalCycle0 = cpu.additionalCycle0; 236 | state.cpu.additionalCycle1 = cpu.additionalCycle1; 237 | state.cpu.clock = cpu.clock; 238 | state.cpu.currentInstruction = cpu.currentInstruction; 239 | state.cpu.currentOpCode = cpu.currentOpCode; 240 | state.cpu.cycleRemaining = cpu.cycleRemaining; 241 | state.cpu.programCounter = cpu.programCounter; 242 | state.cpu.SP = cpu.SP; 243 | state.cpu.STATUS = cpu.STATUS.combined; 244 | state.cpu.X = cpu.X; 245 | state.cpu.Y = cpu.Y; 246 | 247 | std::memcpy(state.bus.controllerCache,bus.controllerCache,2); 248 | std::memcpy(state.bus.controllerMemory,bus.controllerMemory,2); 249 | state.bus.DMA = bus.DMA; 250 | state.bus.DMA_data = bus.DMA_data; 251 | state.bus.DMA_dummy = bus.DMA_dummy; 252 | state.bus.DMA_high = bus.DMA_high; 253 | state.bus.DMA_low = bus.DMA_low; 254 | std::memcpy(state.bus.memory,bus.memory,2048); 255 | 256 | 257 | std::memcpy(state.controllerState,controller.controllerStatus,2); 258 | 259 | state.innerClock = innerClock; 260 | state.systemTime = clock; 261 | 262 | state.ppu.col = ppu.col; 263 | state.ppu.frameDone = ppu.frameDone; 264 | state.ppu.nextRowSprites = ppu.nextRowSprites; 265 | state.ppu.odd = ppu.odd; 266 | state.ppu.palette = ppu.palette; 267 | state.ppu.pixel = ppu.pixel; 268 | state.ppu.row = ppu.row; 269 | state.ppu.spriteZero = ppu.spriteZero; 270 | state.ppu.spriteZeroIndicator = ppu.spriteZeroIndicator; 271 | 272 | 273 | state.ppuBus.addressToggle = ppuBus.addressToggle; 274 | state.ppuBus.BG_NEXT_ATTR = ppuBus.BG_RENDER_FETCH.BG_NEXT_ATTR; 275 | state.ppuBus.BG_NEXT_HIGH = ppuBus.BG_RENDER_FETCH.BG_NEXT_HIGH; 276 | state.ppuBus.BG_NEXT_ID = ppuBus.BG_RENDER_FETCH.BG_NEXT_ID; 277 | state.ppuBus.BG_NEXT_LOW = ppuBus.BG_RENDER_FETCH.BG_NEXT_LOW; 278 | state.ppuBus.BG_PALETTE = ppuBus.BG_PALETTE; 279 | state.ppuBus.BG_PIXEL = ppuBus.BG_PIXEL; 280 | state.ppuBus.BG_SHIFTER_ATTR_HIGH = ppuBus.BG_SHIFTER_ATTR_HIGH.combined; 281 | state.ppuBus.BG_SHIFTER_ATTR_LOW = ppuBus.BG_SHIFTER_ATTR_LOW.combined; 282 | state.ppuBus.BG_SHIFTER_PATTERN_HIGH = ppuBus.BG_SHIFTER_PATTERN_HIGH.combined; 283 | state.ppuBus.BG_SHIFTER_PATTERN_LOW = ppuBus.BG_SHIFTER_PATTERN_LOW.combined; 284 | state.ppuBus.FG_PALETTE = ppuBus.FG_PALETTE; 285 | state.ppuBus.FG_PIXEL = ppuBus.FG_PIXEL; 286 | state.ppuBus.FG_PRIORITY = ppuBus.FG_PRIORITY; 287 | state.ppuBus.FINE_X = ppuBus.FINE_X; 288 | state.ppuBus.vRAM = ppuBus.vRAM.combined; 289 | state.ppuBus.tempRAM = ppuBus.tempRAM.combined; 290 | std::memcpy(state.ppuBus.nameTables,ppuBus.nameTables,4096); 291 | std::memcpy(state.ppuBus.palettes,ppuBus.palettes,32); 292 | std::memcpy(state.ppuBus.OAM,ppuBus.OAM,64 * sizeof(OBJECT_ATTRIBUTE)); 293 | state.ppuBus.NMI = ppuBus.NMI; 294 | state.ppuBus.OAM_offset = ppuBus.OAM_offset; 295 | state.ppuBus.PPUCTRL = ppuBus.PPUCTRL.combined; 296 | state.ppuBus.PPUMASK = ppuBus.PPUMASK.combined; 297 | state.ppuBus.PPUSTATUS = ppuBus.PPUSTATUS.combined; 298 | state.ppuBus.SPRT_PATTERN_ADDR_L = ppuBus.SPRT_PATTERN_ADDR_L; 299 | state.ppuBus.SPRT_PATTERN_HIGH = ppuBus.SPRT_PATTERN_HIGH; 300 | state.ppuBus.ppuBuffer = ppuBus.ppuBuffer; 301 | state.ppuBus.SPRT_SHIFTER_HIGH = ppuBus.SPRT_SHIFTER_HIGH; 302 | state.ppuBus.SPRT_SHIFTER_LOW = ppuBus.SPRT_SHIFTER_LOW; 303 | 304 | 305 | state.mirroringMode = mapper->getMirroring() == MIRRORING::VERTICAL ? 1 : 0; 306 | switch(cartridge.getMapperID()) 307 | { 308 | case 0: 309 | state.mapperID = 0; 310 | break; 311 | case 1: 312 | state.mapperID = 1; 313 | state.mapper = std::make_shared(); 314 | std::memcpy(static_cast(state.mapper.get())->PRGRAM,static_cast(mapper.get())->PRGRAM,8*1024); 315 | static_cast(state.mapper.get())->REGISTERS.controlReg = static_cast(mapper.get())->REGISTERS.controlReg; 316 | static_cast(state.mapper.get())->REGISTERS.tempReg = static_cast(mapper.get())->REGISTERS.tempReg; 317 | static_cast(state.mapper.get())->REGISTERS.tempRegNum = static_cast(mapper.get())->REGISTERS.tempRegNum; 318 | static_cast(state.mapper.get())->CHR0 = static_cast(mapper.get())->CHR0; 319 | static_cast(state.mapper.get())->CHR1 = static_cast(mapper.get())->CHR1; 320 | static_cast(state.mapper.get())->CHRFull = static_cast(mapper.get())->CHRFull; 321 | static_cast(state.mapper.get())->PRG0 = static_cast(mapper.get())->PRG0; 322 | static_cast(state.mapper.get())->PRG1 = static_cast(mapper.get())->PRG1; 323 | static_cast(state.mapper.get())->PRGFull = static_cast(mapper.get())->PRGFull; 324 | break; 325 | case 2: 326 | state.mapperID = 2; 327 | state.mapper = std::make_shared(); 328 | static_cast(state.mapper.get())->lowerBankOffset = static_cast(mapper.get())->lowerBankOffset; 329 | static_cast(state.mapper.get())->higherBankOffset = static_cast(mapper.get())->higherBankOffset; 330 | break; 331 | case 3: 332 | state.mapperID = 3; 333 | state.mapper = std::make_shared(); 334 | static_cast(state.mapper.get())->selectedCHRbank = static_cast(mapper.get())->selectedCHRbank; 335 | break; 336 | default: 337 | break; 338 | } 339 | 340 | state.writeToFile("save.bin"); 341 | hasSaved = true; 342 | } 343 | 344 | void NES::loadState() 345 | { 346 | StateManagement::State state; 347 | state.readFromFile("save.bin"); 348 | display.getNotificationManager().addNotification("Loaded state",ui::notificationColors::warning, 3000); 349 | apu.clock = state.apu.clock; 350 | apu.countToFrameCounterReset = state.apu.countToFrameCounterReset; 351 | apu.dmc = state.apu.dmc; 352 | apu.frameCounter = state.apu.frameCounter; 353 | apu.frameCounterMode = state.apu.frameCounterMode ? APU::FRAMECOUNTERMODE::MODE5 : APU::FRAMECOUNTERMODE::MODE4; 354 | apu.high1 = state.apu.high1; 355 | apu.high2 = state.apu.high2; 356 | apu.incomingFrameCounterReset = state.apu.incomingFrameCounterReset; 357 | apu.low = state.apu.low; 358 | apu.noise =state.apu.noise; 359 | apu.pulse1 =state.apu.pulse1; 360 | apu.pulse2 =state.apu.pulse2; 361 | apu.triangle =state.apu.triangle; 362 | 363 | cpu.A = state.cpu.A; 364 | cpu.additionalCycle0 = state.cpu.additionalCycle0; 365 | cpu.additionalCycle1 = state.cpu.additionalCycle1; 366 | cpu.clock = state.cpu.clock; 367 | cpu.currentInstruction = cpu.table[state.cpu.currentOpCode]; 368 | cpu.currentOpCode = state.cpu.currentOpCode; 369 | cpu.cycleRemaining = state.cpu.cycleRemaining; 370 | cpu.programCounter = state.cpu.programCounter; 371 | cpu.SP = state.cpu.SP; 372 | cpu.STATUS.combined = state.cpu.STATUS; 373 | cpu.X = state.cpu.X; 374 | cpu.Y = state.cpu.Y; 375 | 376 | std::memcpy(bus.controllerCache,state.bus.controllerCache,2); 377 | std::memcpy(bus.controllerMemory,state.bus.controllerMemory,2); 378 | bus.DMA = state.bus.DMA; 379 | bus.DMA_data = state.bus.DMA_data; 380 | bus.DMA_dummy = state.bus.DMA_dummy; 381 | bus.DMA_high = state.bus.DMA_high; 382 | bus.DMA_low = state.bus.DMA_low; 383 | std::memcpy(bus.memory,state.bus.memory,2048); 384 | 385 | 386 | std::memcpy(controller.controllerStatus,state.controllerState,2); 387 | 388 | innerClock = state.innerClock; 389 | clock = state.systemTime; 390 | 391 | ppu.col = state.ppu.col; 392 | ppu.frameDone = state.ppu.frameDone; 393 | ppu.nextRowSprites = state.ppu.nextRowSprites; 394 | ppu.odd = state.ppu.odd; 395 | ppu.palette = state.ppu.palette; 396 | ppu.pixel = state.ppu.pixel; 397 | ppu.row = state.ppu.row; 398 | ppu.spriteZero = state.ppu.spriteZero; 399 | ppu.spriteZeroIndicator = state.ppu.spriteZeroIndicator; 400 | 401 | 402 | ppuBus.addressToggle = state.ppuBus.addressToggle; 403 | ppuBus.BG_RENDER_FETCH.BG_NEXT_ATTR = state.ppuBus.BG_NEXT_ATTR; 404 | ppuBus.BG_RENDER_FETCH.BG_NEXT_HIGH = state.ppuBus.BG_NEXT_HIGH; 405 | ppuBus.BG_RENDER_FETCH.BG_NEXT_ID = state.ppuBus.BG_NEXT_ID; 406 | ppuBus.BG_RENDER_FETCH.BG_NEXT_LOW = state.ppuBus.BG_NEXT_LOW; 407 | ppuBus.BG_PALETTE = state.ppuBus.BG_PALETTE; 408 | ppuBus.BG_PIXEL = state.ppuBus.BG_PIXEL; 409 | ppuBus.BG_SHIFTER_ATTR_HIGH.combined = state.ppuBus.BG_SHIFTER_ATTR_HIGH; 410 | ppuBus.BG_SHIFTER_ATTR_LOW.combined = state.ppuBus.BG_SHIFTER_ATTR_LOW; 411 | ppuBus.BG_SHIFTER_PATTERN_HIGH.combined = state.ppuBus.BG_SHIFTER_PATTERN_HIGH; 412 | ppuBus.BG_SHIFTER_PATTERN_LOW.combined = state.ppuBus.BG_SHIFTER_PATTERN_LOW; 413 | ppuBus.FG_PALETTE = state.ppuBus.FG_PALETTE; 414 | ppuBus.FG_PIXEL = state.ppuBus.FG_PIXEL; 415 | ppuBus.FG_PRIORITY = state.ppuBus.FG_PRIORITY; 416 | ppuBus.FINE_X = state.ppuBus.FINE_X; 417 | ppuBus.vRAM.combined = state.ppuBus.vRAM; 418 | ppuBus.tempRAM.combined = state.ppuBus.tempRAM; 419 | std::memcpy(ppuBus.nameTables,state.ppuBus.nameTables,4096); 420 | std::memcpy(ppuBus.palettes,state.ppuBus.palettes,32); 421 | std::memcpy(ppuBus.OAM,state.ppuBus.OAM,64 * sizeof(OBJECT_ATTRIBUTE)); 422 | ppuBus.NMI = state.ppuBus.NMI; 423 | ppuBus.OAM_offset = state.ppuBus.OAM_offset; 424 | ppuBus.PPUCTRL.combined = state.ppuBus.PPUCTRL; 425 | ppuBus.PPUMASK.combined = state.ppuBus.PPUMASK; 426 | ppuBus.PPUSTATUS.combined = state.ppuBus.PPUSTATUS; 427 | ppuBus.SPRT_PATTERN_ADDR_L = state.ppuBus.SPRT_PATTERN_ADDR_L; 428 | ppuBus.SPRT_PATTERN_HIGH = state.ppuBus.SPRT_PATTERN_HIGH; 429 | ppuBus.ppuBuffer = state.ppuBus.ppuBuffer; 430 | ppuBus.SPRT_SHIFTER_HIGH = state.ppuBus.SPRT_SHIFTER_HIGH; 431 | ppuBus.SPRT_SHIFTER_LOW = state.ppuBus.SPRT_SHIFTER_LOW; 432 | 433 | cartridge.setMapperID(state.mapperID); 434 | setMapper(); 435 | 436 | switch(cartridge.getMapperID()) 437 | { 438 | case 1: 439 | std::memcpy(static_cast(mapper.get())->PRGRAM,static_cast(state.mapper.get())->PRGRAM,8*1024); 440 | static_cast(mapper.get())->REGISTERS.controlReg = static_cast(state.mapper.get())->REGISTERS.controlReg; 441 | static_cast(mapper.get())->REGISTERS.tempReg = static_cast(state.mapper.get())->REGISTERS.tempReg; 442 | static_cast(mapper.get())->REGISTERS.tempRegNum = static_cast(state.mapper.get())->REGISTERS.tempRegNum; 443 | static_cast(mapper.get())->CHR0 = static_cast(state.mapper.get())->CHR0; 444 | static_cast(mapper.get())->CHR1 = static_cast(state.mapper.get())->CHR1; 445 | static_cast(mapper.get())->CHRFull = static_cast(state.mapper.get())->CHRFull; 446 | static_cast(mapper.get())->PRG0 = static_cast(state.mapper.get())->PRG0; 447 | static_cast(mapper.get())->PRG1 = static_cast(state.mapper.get())->PRG1; 448 | static_cast(mapper.get())->PRGFull = static_cast(state.mapper.get())->PRGFull; 449 | break; 450 | case 2: 451 | static_cast(mapper.get())->lowerBankOffset = static_cast(state.mapper.get())->lowerBankOffset; 452 | static_cast(mapper.get())->higherBankOffset = static_cast(state.mapper.get())->higherBankOffset; 453 | break; 454 | case 3: 455 | static_cast(mapper.get())->selectedCHRbank = static_cast(state.mapper.get())->selectedCHRbank; 456 | break; 457 | default: 458 | break; 459 | } 460 | 461 | mapper->setMirroring(state.mirroringMode == 0 ? MIRRORING::HORIZONTAL : MIRRORING::VERTICAL); 462 | 463 | cartridge.CHRNum = state.cartridge.CHRROMSize / 8192; 464 | cartridge.PRGNum = state.cartridge.PRGROMSize / 16384; 465 | 466 | cartridge.PRGmemory = std::move(state.cartridge.PRGROM); 467 | cartridge.CHRmemory = std::move(state.cartridge.CHRROM); 468 | 469 | hasLoaded = true; 470 | } 471 | 472 | } 473 | 474 | 475 | -------------------------------------------------------------------------------- /CPU/CPU.cpp: -------------------------------------------------------------------------------- 1 | #include "CPU.h" 2 | #include 3 | 4 | namespace nesemulator 5 | { 6 | constexpr BYTE CPU::cycleCounts[]; 7 | 8 | namespace // Helper for logging 9 | { 10 | std::string bTohex(uint32_t n, uint8_t d) 11 | { 12 | std::string s(d, '0'); 13 | for (int i = d - 1; i >= 0; i--, n >>= 4) 14 | s[i] = "0123456789ABCDEF"[n & 0xF]; 15 | return s; 16 | }; 17 | } 18 | 19 | 20 | /** 21 | * Construct a new CPU::CPU object and fill instruction table with related operations and addressing modes 22 | * 23 | * @param mem Bus object to connect 24 | */ 25 | CPU::CPU(Bus& mem) : bus(mem) { 26 | 27 | #ifdef CPUDEBUG 28 | logFile = fopen("./Logs/cpu.log","w+"); 29 | #endif 30 | 31 | clock = 0; 32 | programCounter = 0; 33 | 34 | // Fill with NOP for empty OPCODES 35 | INSTRUCTION temp; 36 | temp.operation = &CPU::NOP; 37 | temp.addr = &CPU::IMP; 38 | for(int i = 0;i < 256;i++) 39 | table[i] = temp; 40 | 41 | // Fill table now 42 | temp.addr = &CPU::IMM; 43 | temp.operation = &CPU::ADC; 44 | table[0x69] = temp; 45 | temp.addr = &CPU::ABS; 46 | temp.operation = &CPU::ADC; 47 | table[0x6D] = temp; 48 | temp.addr = &CPU::ZER; 49 | temp.operation = &CPU::ADC; 50 | table[0x65] = temp; 51 | temp.addr = &CPU::INX; 52 | temp.operation = &CPU::ADC; 53 | table[0x61] = temp; 54 | temp.addr = &CPU::INY; 55 | temp.operation = &CPU::ADC; 56 | table[0x71] = temp; 57 | temp.addr = &CPU::ZEX; 58 | temp.operation = &CPU::ADC; 59 | table[0x75] = temp; 60 | temp.addr = &CPU::ABX; 61 | temp.operation = &CPU::ADC; 62 | table[0x7D] = temp; 63 | temp.addr = &CPU::ABY; 64 | temp.operation = &CPU::ADC; 65 | table[0x79] = temp; 66 | temp.addr = &CPU::IMP; 67 | temp.operation = &CPU::ASL; 68 | table[0x0A] = temp; 69 | temp.addr = &CPU::IMP; 70 | temp.operation = &CPU::LSR; 71 | table[0x4A] = temp; 72 | temp.addr = &CPU::IMM; 73 | temp.operation = &CPU::AND; 74 | table[0x29] = temp; 75 | temp.addr = &CPU::ABS; 76 | temp.operation = &CPU::AND; 77 | table[0x2D] = temp; 78 | temp.addr = &CPU::ZER; 79 | temp.operation = &CPU::AND; 80 | table[0x25] = temp; 81 | temp.addr = &CPU::INX; 82 | temp.operation = &CPU::AND; 83 | table[0x21] = temp; 84 | temp.addr = &CPU::INY; 85 | temp.operation = &CPU::AND; 86 | table[0x31] = temp; 87 | temp.addr = &CPU::ZEX; 88 | temp.operation = &CPU::AND; 89 | table[0x35] = temp; 90 | temp.addr = &CPU::ABX; 91 | temp.operation = &CPU::AND; 92 | table[0x3D] = temp; 93 | temp.addr = &CPU::ABY; 94 | temp.operation = &CPU::AND; 95 | table[0x39] = temp; 96 | temp.addr = &CPU::ABS; 97 | temp.operation = &CPU::ASL; 98 | table[0x0E] = temp; 99 | temp.addr = &CPU::ZER; 100 | temp.operation = &CPU::ASL; 101 | table[0x06] = temp; 102 | temp.addr = &CPU::ZEX; 103 | temp.operation = &CPU::ASL; 104 | table[0x16] = temp; 105 | temp.addr = &CPU::ABX; 106 | temp.operation = &CPU::ASL; 107 | table[0x1E] = temp; 108 | temp.addr = &CPU::REL; 109 | temp.operation = &CPU::BCC; 110 | table[0x90] = temp; 111 | temp.addr = &CPU::REL; 112 | temp.operation = &CPU::BCS; 113 | table[0xB0] = temp; 114 | temp.addr = &CPU::REL; 115 | temp.operation = &CPU::BEQ; 116 | table[0xF0] = temp; 117 | temp.addr = &CPU::ABS; 118 | temp.operation = &CPU::BIT; 119 | table[0x2C] = temp; 120 | temp.addr = &CPU::ZER; 121 | temp.operation = &CPU::BIT; 122 | table[0x24] = temp; 123 | temp.addr = &CPU::REL; 124 | temp.operation = &CPU::BMI; 125 | table[0x30] = temp; 126 | temp.addr = &CPU::REL; 127 | temp.operation = &CPU::BNE; 128 | table[0xD0] = temp; 129 | temp.addr = &CPU::REL; 130 | temp.operation = &CPU::BPL; 131 | table[0x10] = temp; 132 | temp.addr = &CPU::IMP; 133 | temp.operation = &CPU::BRK; 134 | table[0x00] = temp; 135 | temp.addr = &CPU::REL; 136 | temp.operation = &CPU::BVC; 137 | table[0x50] = temp; 138 | temp.addr = &CPU::REL; 139 | temp.operation = &CPU::BVS; 140 | table[0x70] = temp; 141 | temp.addr = &CPU::IMP; 142 | temp.operation = &CPU::CLC; 143 | table[0x18] = temp; 144 | temp.addr = &CPU::IMP; 145 | temp.operation = &CPU::CLD; 146 | table[0xD8] = temp; 147 | temp.addr = &CPU::IMP; 148 | temp.operation = &CPU::CLI; 149 | table[0x58] = temp; 150 | temp.addr = &CPU::IMP; 151 | temp.operation = &CPU::CLV; 152 | table[0xB8] = temp; 153 | temp.addr = &CPU::IMM; 154 | temp.operation = &CPU::CMP; 155 | table[0xC9] = temp; 156 | temp.addr = &CPU::ABS; 157 | temp.operation = &CPU::CMP; 158 | table[0xCD] = temp; 159 | temp.addr = &CPU::ZER; 160 | temp.operation = &CPU::CMP; 161 | table[0xC5] = temp; 162 | temp.addr = &CPU::INX; 163 | temp.operation = &CPU::CMP; 164 | table[0xC1] = temp; 165 | temp.addr = &CPU::INY; 166 | temp.operation = &CPU::CMP; 167 | table[0xD1] = temp; 168 | temp.addr = &CPU::ZEX; 169 | temp.operation = &CPU::CMP; 170 | table[0xD5] = temp; 171 | temp.addr = &CPU::ABX; 172 | temp.operation = &CPU::CMP; 173 | table[0xDD] = temp; 174 | temp.addr = &CPU::ABY; 175 | temp.operation = &CPU::CMP; 176 | table[0xD9] = temp; 177 | temp.addr = &CPU::IMM; 178 | temp.operation = &CPU::CPX; 179 | table[0xE0] = temp; 180 | temp.addr = &CPU::ABS; 181 | temp.operation = &CPU::CPX; 182 | table[0xEC] = temp; 183 | temp.addr = &CPU::ZER; 184 | temp.operation = &CPU::CPX; 185 | table[0xE4] = temp; 186 | temp.addr = &CPU::IMM; 187 | temp.operation = &CPU::CPY; 188 | table[0xC0] = temp; 189 | temp.addr = &CPU::ABS; 190 | temp.operation = &CPU::CPY; 191 | table[0xCC] = temp; 192 | temp.addr = &CPU::ZER; 193 | temp.operation = &CPU::CPY; 194 | table[0xC4] = temp; 195 | temp.addr = &CPU::ABS; 196 | temp.operation = &CPU::DEC; 197 | table[0xCE] = temp; 198 | temp.addr = &CPU::ZER; 199 | temp.operation = &CPU::DEC; 200 | table[0xC6] = temp; 201 | temp.addr = &CPU::ZEX; 202 | temp.operation = &CPU::DEC; 203 | table[0xD6] = temp; 204 | temp.addr = &CPU::ABX; 205 | temp.operation = &CPU::DEC; 206 | table[0xDE] = temp; 207 | temp.addr = &CPU::IMP; 208 | temp.operation = &CPU::DEX; 209 | table[0xCA] = temp; 210 | temp.addr = &CPU::IMP; 211 | temp.operation = &CPU::DEY; 212 | table[0x88] = temp; 213 | temp.addr = &CPU::IMM; 214 | temp.operation = &CPU::EOR; 215 | table[0x49] = temp; 216 | temp.addr = &CPU::ABS; 217 | temp.operation = &CPU::EOR; 218 | table[0x4D] = temp; 219 | temp.addr = &CPU::ZER; 220 | temp.operation = &CPU::EOR; 221 | table[0x45] = temp; 222 | temp.addr = &CPU::INX; 223 | temp.operation = &CPU::EOR; 224 | table[0x41] = temp; 225 | temp.addr = &CPU::INY; 226 | temp.operation = &CPU::EOR; 227 | table[0x51] = temp; 228 | temp.addr = &CPU::ZEX; 229 | temp.operation = &CPU::EOR; 230 | table[0x55] = temp; 231 | temp.addr = &CPU::ABX; 232 | temp.operation = &CPU::EOR; 233 | table[0x5D] = temp; 234 | temp.addr = &CPU::ABY; 235 | temp.operation = &CPU::EOR; 236 | table[0x59] = temp; 237 | temp.addr = &CPU::ABS; 238 | temp.operation = &CPU::INC; 239 | table[0xEE] = temp; 240 | temp.addr = &CPU::ZER; 241 | temp.operation = &CPU::INC; 242 | table[0xE6] = temp; 243 | temp.addr = &CPU::ZEX; 244 | temp.operation = &CPU::INC; 245 | table[0xF6] = temp; 246 | temp.addr = &CPU::ABX; 247 | temp.operation = &CPU::INC; 248 | table[0xFE] = temp; 249 | temp.addr = &CPU::IMP; 250 | temp.operation = &CPU::INX_OP; 251 | table[0xE8] = temp; 252 | temp.addr = &CPU::IMP; 253 | temp.operation = &CPU::INY_OP; 254 | table[0xC8] = temp; 255 | temp.addr = &CPU::ABS; 256 | temp.operation = &CPU::JMP; 257 | table[0x4C] = temp; 258 | temp.addr = &CPU::ABI; 259 | temp.operation = &CPU::JMP; 260 | table[0x6C] = temp; 261 | temp.addr = &CPU::ABS; 262 | temp.operation = &CPU::JSR; 263 | table[0x20] = temp; 264 | temp.addr = &CPU::IMM; 265 | temp.operation = &CPU::LDA; 266 | table[0xA9] = temp; 267 | temp.addr = &CPU::ABS; 268 | temp.operation = &CPU::LDA; 269 | table[0xAD] = temp; 270 | temp.addr = &CPU::ZER; 271 | temp.operation = &CPU::LDA; 272 | table[0xA5] = temp; 273 | temp.addr = &CPU::INX; 274 | temp.operation = &CPU::LDA; 275 | table[0xA1] = temp; 276 | temp.addr = &CPU::INY; 277 | temp.operation = &CPU::LDA; 278 | table[0xB1] = temp; 279 | temp.addr = &CPU::ZEX; 280 | temp.operation = &CPU::LDA; 281 | table[0xB5] = temp; 282 | temp.addr = &CPU::ABX; 283 | temp.operation = &CPU::LDA; 284 | table[0xBD] = temp; 285 | temp.addr = &CPU::ABY; 286 | temp.operation = &CPU::LDA; 287 | table[0xB9] = temp; 288 | temp.addr = &CPU::IMM; 289 | temp.operation = &CPU::LDX; 290 | table[0xA2] = temp; 291 | temp.addr = &CPU::ABS; 292 | temp.operation = &CPU::LDX; 293 | table[0xAE] = temp; 294 | temp.addr = &CPU::ZER; 295 | temp.operation = &CPU::LDX; 296 | table[0xA6] = temp; 297 | temp.addr = &CPU::ABY; 298 | temp.operation = &CPU::LDX; 299 | table[0xBE] = temp; 300 | temp.addr = &CPU::ZEY; 301 | temp.operation = &CPU::LDX; 302 | table[0xB6] = temp; 303 | temp.addr = &CPU::IMM; 304 | temp.operation = &CPU::LDY; 305 | table[0xA0] = temp; 306 | temp.addr = &CPU::ABS; 307 | temp.operation = &CPU::LDY; 308 | table[0xAC] = temp; 309 | temp.addr = &CPU::ZER; 310 | temp.operation = &CPU::LDY; 311 | table[0xA4] = temp; 312 | temp.addr = &CPU::ZEX; 313 | temp.operation = &CPU::LDY; 314 | table[0xB4] = temp; 315 | temp.addr = &CPU::ABX; 316 | temp.operation = &CPU::LDY; 317 | table[0xBC] = temp; 318 | temp.addr = &CPU::ABS; 319 | temp.operation = &CPU::LSR; 320 | table[0x4E] = temp; 321 | temp.addr = &CPU::ZER; 322 | temp.operation = &CPU::LSR; 323 | table[0x46] = temp; 324 | temp.addr = &CPU::ZEX; 325 | temp.operation = &CPU::LSR; 326 | table[0x56] = temp; 327 | temp.addr = &CPU::ABX; 328 | temp.operation = &CPU::LSR; 329 | table[0x5E] = temp; 330 | temp.addr = &CPU::IMP; 331 | temp.operation = &CPU::NOP; 332 | table[0xEA] = temp; 333 | temp.addr = &CPU::IMM; 334 | temp.operation = &CPU::ORA; 335 | table[0x09] = temp; 336 | temp.addr = &CPU::ABS; 337 | temp.operation = &CPU::ORA; 338 | table[0x0D] = temp; 339 | temp.addr = &CPU::ZER; 340 | temp.operation = &CPU::ORA; 341 | table[0x05] = temp; 342 | temp.addr = &CPU::INX; 343 | temp.operation = &CPU::ORA; 344 | table[0x01] = temp; 345 | temp.addr = &CPU::INY; 346 | temp.operation = &CPU::ORA; 347 | table[0x11] = temp; 348 | temp.addr = &CPU::ZEX; 349 | temp.operation = &CPU::ORA; 350 | table[0x15] = temp; 351 | temp.addr = &CPU::ABX; 352 | temp.operation = &CPU::ORA; 353 | table[0x1D] = temp; 354 | temp.addr = &CPU::ABY; 355 | temp.operation = &CPU::ORA; 356 | table[0x19] = temp; 357 | temp.addr = &CPU::IMP; 358 | temp.operation = &CPU::PHA; 359 | table[0x48] = temp; 360 | temp.addr = &CPU::IMP; 361 | temp.operation = &CPU::PHP; 362 | table[0x08] = temp; 363 | temp.addr = &CPU::IMP; 364 | temp.operation = &CPU::PLA; 365 | table[0x68] = temp; 366 | temp.addr = &CPU::IMP; 367 | temp.operation = &CPU::PLP; 368 | table[0x28] = temp; 369 | temp.addr = &CPU::ABS; 370 | temp.operation = &CPU::ROL; 371 | table[0x2E] = temp; 372 | temp.addr = &CPU::ZER; 373 | temp.operation = &CPU::ROL; 374 | table[0x26] = temp; 375 | temp.addr = &CPU::ZEX; 376 | temp.operation = &CPU::ROL; 377 | table[0x36] = temp; 378 | temp.addr = &CPU::ABX; 379 | temp.operation = &CPU::ROL; 380 | table[0x3E] = temp; 381 | temp.addr = &CPU::IMP; 382 | temp.operation = &CPU::ROL; 383 | table[0x2A] = temp; 384 | temp.addr = &CPU::ABS; 385 | temp.operation = &CPU::ROR; 386 | table[0x6E] = temp; 387 | temp.addr = &CPU::ZER; 388 | temp.operation = &CPU::ROR; 389 | table[0x66] = temp; 390 | temp.addr = &CPU::ZEX; 391 | temp.operation = &CPU::ROR; 392 | table[0x76] = temp; 393 | temp.addr = &CPU::ABX; 394 | temp.operation = &CPU::ROR; 395 | table[0x7E] = temp; 396 | temp.addr = &CPU::IMP; 397 | temp.operation = &CPU::ROR; 398 | table[0x6A] = temp; 399 | temp.addr = &CPU::IMP; 400 | temp.operation = &CPU::RTI; 401 | table[0x40] = temp; 402 | temp.addr = &CPU::IMP; 403 | temp.operation = &CPU::RTS; 404 | table[0x60] = temp; 405 | temp.addr = &CPU::IMM; 406 | temp.operation = &CPU::SBC; 407 | table[0xE9] = temp; 408 | temp.addr = &CPU::ABS; 409 | temp.operation = &CPU::SBC; 410 | table[0xED] = temp; 411 | temp.addr = &CPU::ZER; 412 | temp.operation = &CPU::SBC; 413 | table[0xE5] = temp; 414 | temp.addr = &CPU::INX; 415 | temp.operation = &CPU::SBC; 416 | table[0xE1] = temp; 417 | temp.addr = &CPU::INY; 418 | temp.operation = &CPU::SBC; 419 | table[0xF1] = temp; 420 | temp.addr = &CPU::ZEX; 421 | temp.operation = &CPU::SBC; 422 | table[0xF5] = temp; 423 | temp.addr = &CPU::ABX; 424 | temp.operation = &CPU::SBC; 425 | table[0xFD] = temp; 426 | temp.addr = &CPU::ABY; 427 | temp.operation = &CPU::SBC; 428 | table[0xF9] = temp; 429 | temp.addr = &CPU::IMP; 430 | temp.operation = &CPU::SEC; 431 | table[0x38] = temp; 432 | temp.addr = &CPU::IMP; 433 | temp.operation = &CPU::SED; 434 | table[0xF8] = temp; 435 | temp.addr = &CPU::IMP; 436 | temp.operation = &CPU::SEI; 437 | table[0x78] = temp; 438 | temp.addr = &CPU::ABS; 439 | temp.operation = &CPU::STA; 440 | table[0x8D] = temp; 441 | temp.addr = &CPU::ZER; 442 | temp.operation = &CPU::STA; 443 | table[0x85] = temp; 444 | temp.addr = &CPU::INX; 445 | temp.operation = &CPU::STA; 446 | table[0x81] = temp; 447 | temp.addr = &CPU::INY; 448 | temp.operation = &CPU::STA; 449 | table[0x91] = temp; 450 | temp.addr = &CPU::ZEX; 451 | temp.operation = &CPU::STA; 452 | table[0x95] = temp; 453 | temp.addr = &CPU::ABX; 454 | temp.operation = &CPU::STA; 455 | table[0x9D] = temp; 456 | temp.addr = &CPU::ABY; 457 | temp.operation = &CPU::STA; 458 | table[0x99] = temp; 459 | temp.addr = &CPU::ABS; 460 | temp.operation = &CPU::STX; 461 | table[0x8E] = temp; 462 | temp.addr = &CPU::ZER; 463 | temp.operation = &CPU::STX; 464 | table[0x86] = temp; 465 | temp.addr = &CPU::ZEY; 466 | temp.operation = &CPU::STX; 467 | table[0x96] = temp; 468 | temp.addr = &CPU::ABS; 469 | temp.operation = &CPU::STY; 470 | table[0x8C] = temp; 471 | temp.addr = &CPU::ZER; 472 | temp.operation = &CPU::STY; 473 | table[0x84] = temp; 474 | temp.addr = &CPU::ZEX; 475 | temp.operation = &CPU::STY; 476 | table[0x94] = temp; 477 | temp.addr = &CPU::IMP; 478 | temp.operation = &CPU::TAX; 479 | table[0xAA] = temp; 480 | temp.addr = &CPU::IMP; 481 | temp.operation = &CPU::TAY; 482 | table[0xA8] = temp; 483 | temp.addr = &CPU::IMP; 484 | temp.operation = &CPU::TSX; 485 | table[0xBA] = temp; 486 | temp.addr = &CPU::IMP; 487 | temp.operation = &CPU::TXA; 488 | table[0x8A] = temp; 489 | temp.addr = &CPU::IMP; 490 | temp.operation = &CPU::TXS; 491 | table[0x9A] = temp; 492 | temp.addr = &CPU::IMP; 493 | temp.operation = &CPU::TYA; 494 | table[0x98] = temp; 495 | }; 496 | 497 | 498 | /** 499 | * Push value to the stack 500 | * 501 | * @param value to push 502 | */ 503 | void CPU::push(BYTE value) 504 | { 505 | bus.writeToMemory(0x0100 + SP--,value); 506 | } 507 | 508 | /** 509 | * Pop value from stack 510 | * 511 | * @return BYTE to pop 512 | */ 513 | BYTE CPU::pop() 514 | { 515 | SP++; 516 | return bus.readFromMemory(0x0100 + SP); 517 | } 518 | 519 | /** 520 | * Ticks CPU for one cycle if there is no extra cycle remaining for last operation 521 | * Fetch,decode and execute next operation 522 | */ 523 | void CPU::tick() 524 | { 525 | 526 | if(bus.getDMAStatus()) 527 | return; 528 | 529 | if(cycleRemaining == 0) 530 | { 531 | 532 | #ifdef CPUDEBUG 533 | logToFile(); 534 | #endif 535 | 536 | currentOpCode = bus.readFromMemory(programCounter++); // Fetch 537 | 538 | currentInstruction = table[currentOpCode]; // Decode 539 | 540 | 541 | cycleRemaining += CPU::cycleCounts[currentOpCode]; 542 | 543 | source = (this->*currentInstruction.addr)(); 544 | 545 | 546 | 547 | (this->*currentInstruction.operation)(source); // Execute 548 | 549 | 550 | cycleRemaining += (additionalCycle0 & additionalCycle1); 551 | 552 | additionalCycle0 = 0x00; 553 | additionalCycle1 = 0x00; 554 | } 555 | 556 | clock++; 557 | 558 | cycleRemaining--; 559 | } 560 | 561 | 562 | 563 | 564 | /** 565 | * Helper function to print details of CPU 566 | * 567 | * @param out std::ostream to insert 568 | * @param cpu CPU object 569 | * @return std::ostream& 570 | */ 571 | std::ostream& operator<<(std::ostream &out,CPU &cpu) // For logging 572 | { 573 | out << "REGISTERS: " << std::endl; 574 | out << "A: " << bTohex(cpu.A,2) << std::endl << "X: " << bTohex(cpu.X,2) << std::endl << "Y: " << bTohex(cpu.Y,2) << std::endl << "SP: " << bTohex(cpu.SP,2) << std::endl; 575 | out << "PROGRAM clock: " << bTohex(cpu.programCounter,4) << std::endl; 576 | 577 | return out; 578 | } 579 | 580 | 581 | /** 582 | * Function to reset CPU 583 | * 584 | */ 585 | void CPU::reset() 586 | { 587 | programCounter = 0xFFFC; 588 | BYTE low,high; 589 | low = bus.readFromMemory(programCounter); 590 | high = bus.readFromMemory(programCounter + 1); 591 | 592 | programCounter = (high << 8) | low; 593 | 594 | A = 0; 595 | X = 0; 596 | Y = 0; 597 | SP = 0xFD; 598 | 599 | STATUS.CARRY = 0; 600 | STATUS.OVERFLOW = 0; 601 | STATUS.ZERO = 0; 602 | STATUS.NEGATIVE = 0; 603 | STATUS.BREAK = 0; 604 | STATUS.INTERRUPT = 1; 605 | STATUS.DECIMAL = 0; 606 | 607 | clock = 0; 608 | 609 | cycleRemaining = 8; 610 | } 611 | 612 | 613 | 614 | /** 615 | * Non-Maskable Interrupt 616 | * 617 | * Set BREAK and INTERRUPT flags 618 | * Push current PROGRAM COUNTER and STATUS registers to the stack 619 | * Set PROGRAM COUNTER to the value of NMIVECTOR address 620 | */ 621 | void CPU::NMI() 622 | { 623 | STATUS.BREAK = 0; 624 | STATUS.UNUSED = 1; 625 | STATUS.INTERRUPT = 1; 626 | push((programCounter >> 8) & 0xFF); 627 | push(programCounter & 0xFF); 628 | push(STATUS.combined); 629 | 630 | 631 | programCounter = (bus.readFromMemory(NMIVECTOR_H) << 8) | bus.readFromMemory(NMIVECTOR_L); 632 | 633 | cycleRemaining = 8; 634 | } 635 | 636 | /** 637 | * Interrupt Request 638 | * 639 | * If INTERRUPT flag not set in STATUS register; 640 | * Set BREAK and INTERRUPT flags 641 | * Push current PROGRAM COUNTER and STATUS registers to the stack 642 | * Set PROGRAM COUNTER to the value of NMIVECTOR address 643 | */ 644 | void CPU::IRQ() 645 | { 646 | if(STATUS.INTERRUPT == 0) 647 | { 648 | STATUS.BREAK = 0; 649 | STATUS.UNUSED = 1; 650 | STATUS.INTERRUPT = 1; 651 | push((programCounter >> 8) & 0xFF); 652 | push(programCounter & 0xFF); 653 | push(STATUS.combined); 654 | 655 | programCounter = (bus.readFromMemory(IRQVECTOR_H) << 8) | bus.readFromMemory(IRQVECTOR_L); 656 | 657 | cycleRemaining = 7; 658 | } 659 | } 660 | 661 | 662 | /** 663 | * Immediate Addressing Mode 664 | * 665 | * Get address from address pointed by PROGRAM COUNTER directly 666 | * 667 | */ 668 | ADDRESS CPU::IMM() 669 | { 670 | return programCounter++; 671 | }; // IMMEDIATE 672 | 673 | /** 674 | * Absoulte Addressing Mode 675 | * 676 | * Use value of address pointed by PROGRAM COUNTER to obtain address of next operation 677 | * 678 | */ 679 | ADDRESS CPU::ABS() 680 | { 681 | ADDRESS addrLower = bus.readFromMemory(programCounter++), 682 | addrHigher = bus.readFromMemory(programCounter++); 683 | return (addrHigher << 8) | addrLower; 684 | }; // ABSOLUTE 685 | 686 | /** 687 | * Zero-Indexed Addressing Mode 688 | * 689 | * Use value of address pointed by PROGRAM COUNTER as low 8-bits of a CPU zero-page address 690 | */ 691 | ADDRESS CPU::ZER() 692 | { return bus.readFromMemory(programCounter++) % 256; }; // ZERO PAGE 693 | 694 | /** 695 | * Zero-Indexed X Addressing Mode 696 | * 697 | * Use value of address pointed by PROGRAM COUNTER plus X register as low 8-bits of a CPU zero-page address 698 | */ 699 | ADDRESS CPU::ZEX() 700 | { return (bus.readFromMemory(programCounter++) + X) % 256; }; // INDEXED-X ZERO PAGE 701 | 702 | /** 703 | * Zero-Indexed Y Addressing Mode 704 | * 705 | * Use value of address pointed by PROGRAM COUNTER plus Y register as low 8-bits of a CPU zero-page address 706 | */ 707 | ADDRESS CPU::ZEY() 708 | { return (bus.readFromMemory(programCounter++) + Y) % 256; }; // INDEXED-Y ZERO PAGE 709 | 710 | 711 | /** 712 | * Absoule + X Addressing Mode 713 | * 714 | * 715 | * Get absolute addressing and sum with X register 716 | */ 717 | ADDRESS CPU::ABX() 718 | { ADDRESS addrLower = bus.readFromMemory(programCounter++), 719 | addrHigher = bus.readFromMemory(programCounter++),absolute; 720 | absolute = (addrHigher << 8) | addrLower; 721 | absolute += X; 722 | if((absolute & 0xFF00) != (addrHigher << 8)) 723 | additionalCycle0 = 1; 724 | return absolute; 725 | }; 726 | 727 | /** 728 | * Absoule + Y Addressing Mode 729 | * 730 | * 731 | * Get absolute addressing and sum with Y register 732 | */ 733 | ADDRESS CPU::ABY() 734 | { 735 | ADDRESS addrLower = bus.readFromMemory(programCounter++), 736 | addrHigher = bus.readFromMemory(programCounter++),absolute; 737 | absolute = (addrHigher << 8) | addrLower; 738 | absolute += Y; 739 | if((absolute & 0xFF00) != (addrHigher << 8)) 740 | additionalCycle0 = 1; 741 | return absolute; 742 | }; 743 | 744 | /** 745 | * Implied Addressing Mode 746 | * 747 | * No address needed for next operation 748 | */ 749 | ADDRESS CPU::IMP() 750 | { return 0; }; // IMPLIED 751 | 752 | /** 753 | * Relative Addressing Mode 754 | * 755 | * Use value stored in address pointed by PROGRAM COUNTER to be offset for current value of PROGRAM COUNTER 756 | */ 757 | ADDRESS CPU::REL() 758 | { 759 | ADDRESS offset = bus.readFromMemory(programCounter++); 760 | if(offset & 0x80) offset |= 0xFF00; 761 | return programCounter + offset; 762 | }; // RELATIVE 763 | 764 | 765 | 766 | ADDRESS CPU::INX() 767 | { 768 | ADDRESS temp = bus.readFromMemory(programCounter++); 769 | ADDRESS lower = bus.readFromMemory((temp + (ADDRESS)X) % 256),higher = bus.readFromMemory((temp + (ADDRESS)X + 1) % 256); 770 | return (higher << 8) | lower; 771 | }; 772 | 773 | ADDRESS CPU::INY() 774 | { 775 | ADDRESS temp = bus.readFromMemory(programCounter++); 776 | ADDRESS lower = bus.readFromMemory(temp % 256),higher = bus.readFromMemory((temp + 1) % 256); 777 | return ((higher << 8) | lower) + Y; 778 | }; 779 | 780 | ADDRESS CPU::ABI() 781 | { 782 | ADDRESS addressLower = bus.readFromMemory(programCounter++); 783 | ADDRESS addressHigher = bus.readFromMemory(programCounter++); 784 | ADDRESS abs = (addressHigher << 8) | addressLower; 785 | ADDRESS result = 0x0000; 786 | if(addressLower == 0x00FF) result = (bus.readFromMemory(abs & 0xFF00) << 8) | bus.readFromMemory(abs + 0); 787 | else result = ((bus.readFromMemory(abs + 1) << 8) | bus.readFromMemory(abs + 0)); 788 | return result; 789 | }; // ABSOLUTE INDIRECT 790 | 791 | 792 | 793 | 794 | OPEXEC CPU::ADC(ADDRESS source) 795 | { 796 | BYTE data = bus.readFromMemory(source); 797 | ADDRESS temp = (ADDRESS) data + (ADDRESS) A + (ADDRESS)(STATUS.CARRY ? 1 : 0); 798 | STATUS.CARRY = temp > 255 ? 1 : 0; 799 | 800 | STATUS.ZERO = (temp & 0x00FF) == 0 ? 1 : 0; 801 | 802 | STATUS.OVERFLOW = ((~((ADDRESS)A ^ (ADDRESS) data) & ((ADDRESS)A ^ (ADDRESS)temp)) & 0x0080) ? 1 : 0; 803 | 804 | STATUS.NEGATIVE = temp & 0x0080 ? 1 : 0; 805 | 806 | A = temp & 0x00FF; 807 | additionalCycle1++; 808 | }; 809 | 810 | OPEXEC CPU::AND(ADDRESS source) 811 | { 812 | A = A & bus.readFromMemory(source); 813 | STATUS.ZERO = (A == 0) ? 1 : 0; 814 | STATUS.NEGATIVE = (A & 0x80) ? 1 : 0; 815 | additionalCycle1++; 816 | }; 817 | 818 | OPEXEC CPU::ASL(ADDRESS source) 819 | { 820 | ADDRESS data = (ADDRESS)((currentInstruction.addr == &CPU::IMP) ? A : bus.readFromMemory(source)); 821 | data <<= 1; 822 | STATUS.CARRY = (data & 0xFF00) ? 1 : 0; 823 | STATUS.ZERO = (data & 0x00FF) == 0 ? 1 : 0; 824 | STATUS.NEGATIVE = (data & 0x80) ? 1 : 0; 825 | if(currentInstruction.addr == &CPU::IMP) 826 | A = data % 256; 827 | else 828 | bus.writeToMemory(source,data % 256); 829 | }; 830 | 831 | 832 | OPEXEC CPU::BCC(ADDRESS source) 833 | { 834 | if(!STATUS.CARRY) 835 | { 836 | cycleRemaining++; 837 | programCounter = source; 838 | if ((source & 0xFF00) != (programCounter & 0xFF00)) 839 | cycleRemaining++; 840 | } 841 | }; 842 | 843 | OPEXEC CPU::BCS(ADDRESS source) 844 | { 845 | if(STATUS.CARRY) 846 | { 847 | cycleRemaining++; 848 | programCounter = source; 849 | if ((source & 0xFF00) != (programCounter & 0xFF00)) 850 | cycleRemaining++; 851 | } 852 | }; 853 | 854 | OPEXEC CPU::BEQ(ADDRESS source) 855 | { 856 | if(STATUS.ZERO) 857 | { 858 | cycleRemaining++; 859 | programCounter = source; 860 | if ((source & 0xFF00) != (programCounter & 0xFF00)) 861 | cycleRemaining++; 862 | } 863 | }; 864 | 865 | OPEXEC CPU::BIT(ADDRESS source) 866 | { 867 | ADDRESS data = bus.readFromMemory(source); 868 | STATUS.ZERO = ((ADDRESS)(A & data) & 0x00FF) == 0x00 ? 1 : 0; 869 | STATUS.NEGATIVE = (data & (1 << 7)) ? 1 : 0; 870 | STATUS.OVERFLOW = (data & (1 << 6)) ? 1 : 0; 871 | }; 872 | 873 | OPEXEC CPU::BMI(ADDRESS source) 874 | { 875 | if(STATUS.NEGATIVE) 876 | { 877 | cycleRemaining++; 878 | programCounter = source; 879 | if ((source & 0xFF00) != (programCounter & 0xFF00)) 880 | cycleRemaining++; 881 | } 882 | }; 883 | 884 | OPEXEC CPU::BNE(ADDRESS source) 885 | { 886 | if(!STATUS.ZERO) 887 | { 888 | cycleRemaining++; 889 | programCounter = source; 890 | if ((source & 0xFF00) != (programCounter & 0xFF00)) 891 | cycleRemaining++; 892 | } 893 | }; 894 | 895 | OPEXEC CPU::BPL(ADDRESS source) 896 | { 897 | if(!STATUS.NEGATIVE) 898 | { 899 | cycleRemaining++; 900 | programCounter = source; 901 | if ((source & 0xFF00) != (programCounter & 0xFF00)) 902 | cycleRemaining++; 903 | } 904 | }; 905 | 906 | OPEXEC CPU::BRK(ADDRESS source) 907 | { 908 | STATUS.BREAK = 1; 909 | STATUS.INTERRUPT = 1; 910 | programCounter++; 911 | push((programCounter >> 8) & 0xFF); 912 | push(programCounter & 0xFF); 913 | push(STATUS.combined); 914 | STATUS.BREAK = 0; 915 | programCounter = bus.readFromMemory(IRQVECTOR_L) | (bus.readFromMemory(IRQVECTOR_H) << 8); 916 | }; 917 | 918 | OPEXEC CPU::BVC(ADDRESS source) 919 | { 920 | if(!STATUS.OVERFLOW) 921 | { 922 | cycleRemaining++; 923 | programCounter = source; 924 | if ((source & 0xFF00) != (programCounter & 0xFF00)) 925 | cycleRemaining++; 926 | } 927 | }; 928 | 929 | OPEXEC CPU::BVS(ADDRESS source) 930 | { 931 | if(STATUS.OVERFLOW) 932 | { 933 | cycleRemaining++; 934 | programCounter = source; 935 | if ((source & 0xFF00) != (programCounter & 0xFF00)) 936 | cycleRemaining++; 937 | } 938 | }; 939 | 940 | OPEXEC CPU::CLC(ADDRESS source) 941 | { 942 | STATUS.CARRY = 0; 943 | }; 944 | 945 | OPEXEC CPU::CLD(ADDRESS source) 946 | { 947 | STATUS.DECIMAL = 0; 948 | }; 949 | 950 | OPEXEC CPU::CLI(ADDRESS source) 951 | { 952 | STATUS.INTERRUPT = 0; 953 | }; 954 | 955 | OPEXEC CPU::CLV(ADDRESS source) 956 | { 957 | STATUS.OVERFLOW = 0; 958 | }; 959 | 960 | OPEXEC CPU::CMP(ADDRESS source) 961 | { 962 | BYTE data = bus.readFromMemory(source); 963 | ADDRESS temp = (ADDRESS)A - (ADDRESS)data; 964 | STATUS.CARRY = (A >= data) ? 1 : 0; 965 | STATUS.NEGATIVE = (0x0080 & temp) ? 1 : 0; 966 | STATUS.ZERO = (temp & 0x00FF) == 0x0000 ? 1 : 0; 967 | additionalCycle1++; 968 | }; 969 | 970 | OPEXEC CPU::CPX(ADDRESS source) 971 | { 972 | BYTE data = bus.readFromMemory(source); 973 | ADDRESS temp = (ADDRESS)X - (ADDRESS)data; 974 | STATUS.CARRY = (X >= data) ? 1 : 0; 975 | STATUS.NEGATIVE = (0x0080 & temp) ? 1 : 0; 976 | STATUS.ZERO = (temp & 0x00FF) == 0x0000 ? 1 : 0; 977 | additionalCycle1++; 978 | }; 979 | 980 | OPEXEC CPU::CPY(ADDRESS source) 981 | { 982 | BYTE data = bus.readFromMemory(source); 983 | ADDRESS temp = (ADDRESS)Y - (ADDRESS)data; 984 | STATUS.CARRY = (Y >= data) ? 1 : 0; 985 | STATUS.NEGATIVE = (0x0080 & temp) ? 1 : 0; 986 | STATUS.ZERO = (temp & 0x00FF) == 0x0000 ? 1 : 0; 987 | additionalCycle1++; 988 | }; 989 | 990 | OPEXEC CPU::DEC(ADDRESS source) 991 | { 992 | BYTE data = bus.readFromMemory(source); 993 | ADDRESS temp = (ADDRESS) data - 1; 994 | STATUS.NEGATIVE = (temp & 0x0080) ? 1 : 0; 995 | STATUS.ZERO = (temp & 0x00FF) == 0x0000 ? 1 : 0; 996 | bus.writeToMemory(source,temp & 0x00FF); 997 | }; 998 | 999 | OPEXEC CPU::DEX(ADDRESS source) 1000 | { 1001 | BYTE data = X - 1; 1002 | STATUS.NEGATIVE = (data & 0x80) ? 1 : 0; 1003 | STATUS.ZERO = !data; 1004 | X = data; 1005 | }; 1006 | 1007 | OPEXEC CPU::DEY(ADDRESS source) 1008 | { 1009 | BYTE data = Y - 1; 1010 | STATUS.NEGATIVE = (data & 0x80) ? 1 : 0; 1011 | STATUS.ZERO = !data; 1012 | Y = data; 1013 | }; 1014 | 1015 | OPEXEC CPU::EOR(ADDRESS source) 1016 | { 1017 | BYTE data = A ^ bus.readFromMemory(source); 1018 | STATUS.NEGATIVE = (data & 0x80) ? 1 : 0; 1019 | STATUS.ZERO = data == 0 ? 1 : 0; 1020 | A = data; 1021 | }; 1022 | 1023 | OPEXEC CPU::INC(ADDRESS source) 1024 | { 1025 | BYTE data = (bus.readFromMemory(source) + 1); 1026 | STATUS.ZERO = (data == 0x00) ? 1 : 0; 1027 | STATUS.NEGATIVE = (data & 0x80) ? 1 : 0; 1028 | bus.writeToMemory(source,data); 1029 | }; 1030 | 1031 | OPEXEC CPU::INX_OP(ADDRESS source) 1032 | { 1033 | X = (X + 1); 1034 | STATUS.ZERO = (X == 0x00) ? 1 : 0; 1035 | STATUS.NEGATIVE = (X & 0x80) ? 1 : 0; 1036 | }; 1037 | 1038 | OPEXEC CPU::INY_OP(ADDRESS source) 1039 | { 1040 | Y = (Y + 1); 1041 | STATUS.ZERO = (Y == 0x00) ? 1 : 0; 1042 | STATUS.NEGATIVE = (Y & 0x80) ? 1 : 0; 1043 | }; 1044 | 1045 | OPEXEC CPU::JMP(ADDRESS source) 1046 | { 1047 | programCounter = source; 1048 | }; 1049 | 1050 | OPEXEC CPU::JSR(ADDRESS source) 1051 | { 1052 | programCounter--; 1053 | push((programCounter >> 8) & 0x00FF); 1054 | push(programCounter & 0x00FF); 1055 | programCounter = source; 1056 | }; 1057 | 1058 | OPEXEC CPU::LDA(ADDRESS source) 1059 | { 1060 | A = bus.readFromMemory(source); 1061 | STATUS.ZERO = (A == 0x00) ? 1 : 0; 1062 | STATUS.NEGATIVE = (A & 0x80) ? 1 : 0; 1063 | additionalCycle1++; 1064 | }; 1065 | 1066 | OPEXEC CPU::LDX(ADDRESS source) 1067 | { 1068 | X = bus.readFromMemory(source); 1069 | STATUS.ZERO = (X == 0) ? 1 : 0; 1070 | STATUS.NEGATIVE = (X & 0x80) ? 1 : 0; 1071 | additionalCycle1++; 1072 | }; 1073 | 1074 | OPEXEC CPU::LDY(ADDRESS source) 1075 | { 1076 | Y = bus.readFromMemory(source); 1077 | STATUS.ZERO = (Y == 0) ? 1 : 0; 1078 | STATUS.NEGATIVE = (Y & 0x80) ? 1 : 0; 1079 | additionalCycle1++; 1080 | }; 1081 | 1082 | OPEXEC CPU::LSR(ADDRESS source) 1083 | { 1084 | ADDRESS data = currentInstruction.addr == &CPU::IMP ? A : bus.readFromMemory(source); 1085 | STATUS.CARRY = data & 0x0001; 1086 | data >>= 1; 1087 | STATUS.ZERO = (data & 0x00FF) == 0 ? 1 : 0; 1088 | STATUS.NEGATIVE = 0; 1089 | if(currentInstruction.addr == &CPU::IMP) 1090 | A = data & 0x00FF; 1091 | else 1092 | bus.writeToMemory(source,data); 1093 | }; 1094 | 1095 | 1096 | OPEXEC CPU::NOP(ADDRESS source) 1097 | { }; 1098 | 1099 | OPEXEC CPU::ORA(ADDRESS source) 1100 | { 1101 | A = bus.readFromMemory(source) | A; 1102 | STATUS.ZERO = (A == 0x00) ? 1 : 0; 1103 | STATUS.NEGATIVE = (A & 0x80) ? 1 : 0; 1104 | }; 1105 | 1106 | OPEXEC CPU::PHA(ADDRESS source) 1107 | { 1108 | push(A); 1109 | }; 1110 | 1111 | OPEXEC CPU::PHP(ADDRESS source) 1112 | { 1113 | STATUS.BREAK = 1; 1114 | STATUS.UNUSED = 1; 1115 | push(STATUS.combined); 1116 | STATUS.BREAK = 0; 1117 | STATUS.UNUSED = 0; 1118 | }; 1119 | 1120 | OPEXEC CPU::PLA(ADDRESS source) 1121 | { 1122 | A = pop(); 1123 | STATUS.ZERO = (A == 0x00) ? 1 : 0; 1124 | STATUS.NEGATIVE = (A & 0x80) ? 1 : 0; 1125 | }; 1126 | 1127 | OPEXEC CPU::PLP(ADDRESS source) 1128 | { 1129 | STATUS.combined = pop(); 1130 | }; 1131 | 1132 | OPEXEC CPU::ROL(ADDRESS source) 1133 | { 1134 | ADDRESS data = currentInstruction.addr == &CPU::IMP ? A : bus.readFromMemory(source); 1135 | bool temp = data & 0x80; 1136 | data <<= 1; 1137 | if(STATUS.CARRY) data |= 0x01; 1138 | STATUS.CARRY = temp ? 1 : 0; 1139 | STATUS.ZERO = (data == 0x00) ? 1 : 0; 1140 | STATUS.NEGATIVE = (data & 0x80) ? 1 : 0; 1141 | if(currentInstruction.addr == &CPU::IMP) 1142 | A = data; 1143 | else 1144 | bus.writeToMemory(source,data); 1145 | }; 1146 | 1147 | OPEXEC CPU::ROR(ADDRESS source) 1148 | { 1149 | BYTE data = currentInstruction.addr == &CPU::IMP ? A : bus.readFromMemory(source); 1150 | bool temp = CHECK_BIT(data,0); 1151 | STATUS.NEGATIVE = STATUS.CARRY ? 1 : 0; 1152 | data >>= 1; 1153 | if(STATUS.CARRY) SET_BIT(data,7); 1154 | STATUS.CARRY = temp ? 1 : 0; 1155 | STATUS.ZERO = data == 0x00 ? 1 : 0; 1156 | if(currentInstruction.addr == &CPU::IMP) 1157 | A = data; 1158 | else 1159 | bus.writeToMemory(source,data); 1160 | }; 1161 | 1162 | 1163 | OPEXEC CPU::RTI(ADDRESS source) 1164 | { 1165 | BYTE low,high; 1166 | 1167 | STATUS.combined = pop(); 1168 | STATUS.BREAK = 0; 1169 | STATUS.UNUSED = 0; 1170 | low = pop(); 1171 | high = pop(); 1172 | 1173 | programCounter = (high << 8) | low; 1174 | }; 1175 | 1176 | OPEXEC CPU::RTS(ADDRESS source) 1177 | { 1178 | BYTE low,high; 1179 | 1180 | programCounter = pop(); 1181 | high = pop(); 1182 | 1183 | programCounter |= (ADDRESS)(high) << 8; 1184 | programCounter++; 1185 | 1186 | }; 1187 | 1188 | OPEXEC CPU::SBC(ADDRESS source) 1189 | { 1190 | BYTE data = bus.readFromMemory(source); 1191 | 1192 | ADDRESS val = (ADDRESS) data ^ 0x00FF; 1193 | 1194 | ADDRESS temp = (ADDRESS) A + val + (ADDRESS)(STATUS.CARRY ? 1 : 0); 1195 | STATUS.CARRY = temp & 0xFF00 ? 1 : 0; 1196 | 1197 | STATUS.ZERO = (temp & 0x00FF) == 0 ? 1 : 0; 1198 | 1199 | STATUS.OVERFLOW = ((temp ^ (ADDRESS) A) & (temp ^ val) & 0x0080) ? 1 : 0; 1200 | 1201 | STATUS.NEGATIVE = temp & 0x0080 ? 1 : 0; 1202 | 1203 | A = temp & 0x00FF; 1204 | additionalCycle1++; 1205 | }; 1206 | 1207 | OPEXEC CPU::SEC(ADDRESS source) 1208 | { 1209 | STATUS.CARRY = 1; 1210 | }; 1211 | 1212 | OPEXEC CPU::SED(ADDRESS source) 1213 | { 1214 | STATUS.DECIMAL = 1; 1215 | }; 1216 | 1217 | OPEXEC CPU::SEI(ADDRESS source) 1218 | { 1219 | STATUS.INTERRUPT = 1; 1220 | }; 1221 | 1222 | OPEXEC CPU::STA(ADDRESS source) 1223 | { 1224 | bus.writeToMemory(source,A); 1225 | }; 1226 | 1227 | OPEXEC CPU::STX(ADDRESS source) 1228 | { 1229 | bus.writeToMemory(source,X); 1230 | }; 1231 | 1232 | OPEXEC CPU::STY(ADDRESS source) 1233 | { 1234 | bus.writeToMemory(source,Y); 1235 | }; 1236 | 1237 | OPEXEC CPU::TAX(ADDRESS source) 1238 | { 1239 | X = A; 1240 | STATUS.ZERO = (X == 0x00) ? 1 : 0; 1241 | STATUS.NEGATIVE = (X & 0x80) ? 1 : 0; 1242 | }; 1243 | 1244 | OPEXEC CPU::TAY(ADDRESS source) 1245 | { 1246 | Y = A; 1247 | STATUS.ZERO = (Y == 0x00) ? 1 : 0; 1248 | STATUS.NEGATIVE = (Y & 0x80) ? 1 : 0; 1249 | }; 1250 | 1251 | OPEXEC CPU::TSX(ADDRESS source) 1252 | { 1253 | X = SP; 1254 | STATUS.ZERO = (X == 0x00) ? 1 : 0; 1255 | STATUS.NEGATIVE = (X & 0x80) ? 1 : 0; 1256 | }; 1257 | 1258 | OPEXEC CPU::TXA(ADDRESS source) 1259 | { 1260 | A = X; 1261 | STATUS.ZERO = (A== 0x00) ? 1 : 0; 1262 | STATUS.NEGATIVE = (A & 0x80) ? 1 : 0; 1263 | }; 1264 | 1265 | OPEXEC CPU::TYA(ADDRESS source) 1266 | { 1267 | A = Y; 1268 | STATUS.ZERO = (A== 0x00) ? 1 : 0; 1269 | STATUS.NEGATIVE = (A & 0x80) ? 1 : 0; 1270 | }; 1271 | 1272 | OPEXEC CPU::TXS(ADDRESS source) 1273 | { 1274 | SP = X; 1275 | }; 1276 | 1277 | OPEXEC CPU::ILLEGAL(ADDRESS source) 1278 | { 1279 | 1280 | }; 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | #ifdef CPUDEBUG 1294 | 1295 | void CPU::logToFile() 1296 | { 1297 | std::string op = ""; 1298 | if(currentInstruction.operation == &CPU::AND) 1299 | op = "AND"; 1300 | else if(currentInstruction.operation == &CPU::ADC) 1301 | op = "ADC"; 1302 | else if(currentInstruction.operation == &CPU::ASL) 1303 | op = "ASL"; 1304 | else if(currentInstruction.operation == &CPU::BCC) 1305 | op = "BCC"; 1306 | else if(currentInstruction.operation == &CPU::BCS) 1307 | op = "BCS"; 1308 | else if(currentInstruction.operation == &CPU::BEQ) 1309 | op = "BEQ"; 1310 | else if(currentInstruction.operation == &CPU::BIT) 1311 | op = "BIT"; 1312 | else if(currentInstruction.operation == &CPU::BMI) 1313 | op = "BMI"; 1314 | else if(currentInstruction.operation == &CPU::BNE) 1315 | op = "BNE"; 1316 | else if(currentInstruction.operation == &CPU::BPL) 1317 | op = "BPL"; 1318 | else if(currentInstruction.operation == &CPU::BRK) 1319 | op = "BRK"; 1320 | else if(currentInstruction.operation == &CPU::BVC) 1321 | op = "BC"; 1322 | else if(currentInstruction.operation == &CPU::BVS) 1323 | op = "BVS"; 1324 | else if(currentInstruction.operation == &CPU::CLC) 1325 | op = "CLC"; 1326 | else if(currentInstruction.operation == &CPU::CLD) 1327 | op = "CLD"; 1328 | else if(currentInstruction.operation == &CPU::CLI) 1329 | op = "CLI"; 1330 | else if(currentInstruction.operation == &CPU::CLV) 1331 | op = "CLV"; 1332 | else if(currentInstruction.operation == &CPU::CMP) 1333 | op = "CMP"; 1334 | else if(currentInstruction.operation == &CPU::CPX) 1335 | op = "CPX"; 1336 | else if(currentInstruction.operation == &CPU::CPY) 1337 | op = "CPY"; 1338 | else if(currentInstruction.operation == &CPU::DEC) 1339 | op = "DEC"; 1340 | else if(currentInstruction.operation == &CPU::DEX) 1341 | op = "DEX"; 1342 | else if(currentInstruction.operation == &CPU::DEY) 1343 | op = "DEY"; 1344 | else if(currentInstruction.operation == &CPU::EOR) 1345 | op = "EOR"; 1346 | else if(currentInstruction.operation == &CPU::ILLEGAL) 1347 | op = "ILLEGAL"; 1348 | else if(currentInstruction.operation == &CPU::INC) 1349 | op = "INC"; 1350 | else if(currentInstruction.operation == &CPU::INX_OP) 1351 | op = "INX"; 1352 | else if(currentInstruction.operation == &CPU::INY_OP) 1353 | op = "INY"; 1354 | else if(currentInstruction.operation == &CPU::JMP) 1355 | op = "JMP"; 1356 | else if(currentInstruction.operation == &CPU::JSR) 1357 | op = "JSR"; 1358 | else if(currentInstruction.operation == &CPU::LDA) 1359 | op = "LDA"; 1360 | else if(currentInstruction.operation == &CPU::LDX) 1361 | op = "LDX"; 1362 | else if(currentInstruction.operation == &CPU::LDY) 1363 | op = "LDY"; 1364 | else if(currentInstruction.operation == &CPU::LSR) 1365 | op = "LSR"; 1366 | else if(currentInstruction.operation == &CPU::NOP) 1367 | op = "NOP"; 1368 | else if(currentInstruction.operation == &CPU::ORA) 1369 | op = "ORA"; 1370 | else if(currentInstruction.operation == &CPU::PHA) 1371 | op = "PHA"; 1372 | else if(currentInstruction.operation == &CPU::PHP) 1373 | op = "PHP"; 1374 | else if(currentInstruction.operation == &CPU::PLA) 1375 | op = "PLA"; 1376 | else if(currentInstruction.operation == &CPU::PLP) 1377 | op = "PLP"; 1378 | else if(currentInstruction.operation == &CPU::ROL) 1379 | op = "ROL"; 1380 | else if(currentInstruction.operation == &CPU::ROR) 1381 | op = "ROR"; 1382 | else if(currentInstruction.operation == &CPU::RTI) 1383 | op = "RTI"; 1384 | else if(currentInstruction.operation == &CPU::RTS) 1385 | op = "RTS"; 1386 | else if(currentInstruction.operation == &CPU::SBC) 1387 | op = "SBC"; 1388 | else if(currentInstruction.operation == &CPU::SEC) 1389 | op = "SEC"; 1390 | else if(currentInstruction.operation == &CPU::SED) 1391 | op = "SED"; 1392 | else if(currentInstruction.operation == &CPU::SEI) 1393 | op = "SEI"; 1394 | else if(currentInstruction.operation == &CPU::STA) 1395 | op = "STA"; 1396 | else if(currentInstruction.operation == &CPU::STX) 1397 | op = "STX"; 1398 | else if(currentInstruction.operation == &CPU::STY) 1399 | op = "STY"; 1400 | else if(currentInstruction.operation == &CPU::TAX) 1401 | op = "TAX"; 1402 | else if(currentInstruction.operation == &CPU::TAY) 1403 | op = "TAY"; 1404 | else if(currentInstruction.operation == &CPU::TSX) 1405 | op = "TSX"; 1406 | else if(currentInstruction.operation == &CPU::TXA) 1407 | op = "TXA"; 1408 | else if(currentInstruction.operation == &CPU::TXS) 1409 | op = "TXS"; 1410 | else if(currentInstruction.operation == &CPU::TYA) 1411 | op = "TYA"; 1412 | fprintf(logFile,"PC:%04X A:%02X X:%02X Y:%02X %d%d%d%d%d%d%d%d SP:%02X OP:%-3s $:%04X\n",programCounter,A,X,Y,STATUS.CARRY,STATUS.ZERO,STATUS.INTERRUPT,STATUS.DECIMAL,STATUS.BREAK,STATUS.UNUSED,STATUS.OVERFLOW,STATUS.NEGATIVE,SP,op.c_str(),source); 1413 | } 1414 | #endif 1415 | } 1416 | --------------------------------------------------------------------------------