├── favicon.ico ├── .gitmodules ├── saltynes_icon_128.jpg ├── saltynes_icon_16.jpg ├── ChangeLog ├── .gitignore ├── init_web.sh ├── src ├── build_date.cc ├── Color.h ├── Mapper003.cc ├── Parameters.cc ├── Raster.cc ├── Globals.cc ├── Mapper011.cc ├── Mapper002.cc ├── Logger.cc ├── base64.h ├── sha256sum.h ├── Memory.cc ├── NameTable.cc ├── Mapper007.cc ├── Misc.cc ├── ChannelNoise.cc ├── Color.cc ├── ChannelTriangle.cc ├── base64.cc ├── main.cc ├── InputHandler.cc ├── Mapper009.cc ├── ChannelDM.cc ├── ChannelSquare.cc ├── SaltyNES.cc ├── Tile.cc ├── Mapper004.cc ├── Mapper018.cc ├── PaletteTable.cc ├── Mapper001.cc ├── NES.cc ├── CpuInfo.cc ├── sha256sum.cc ├── MapperDefault.cc └── ByteBuffer.cc ├── make_desktop.sh ├── make_web.sh ├── README.md ├── CMakeLists.txt ├── static ├── styles.css ├── no_more_jquery.js └── setup.js └── index.html /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workhorsy/SaltyNES/HEAD/favicon.ico -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "emsdk"] 2 | path = emsdk 3 | url = https://github.com/juj/emsdk.git 4 | -------------------------------------------------------------------------------- /saltynes_icon_128.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workhorsy/SaltyNES/HEAD/saltynes_icon_128.jpg -------------------------------------------------------------------------------- /saltynes_icon_16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/workhorsy/SaltyNES/HEAD/saltynes_icon_16.jpg -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | * Release 0.1.0 2 | * Fixed Bug #6: Set minimum version requirements 3 | * Fixed Bug #5: added CMake as a building tool 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | roms/ 3 | build*/ 4 | index.wasm 5 | static/index.js 6 | saltynes 7 | 8 | Makefile 9 | 10 | SaltyNES.js 11 | SaltyNES.wasm 12 | SaltyNES.wasm.pre 13 | SaltyNES.wast 14 | SaltyNES 15 | -------------------------------------------------------------------------------- /init_web.sh: -------------------------------------------------------------------------------- 1 | 2 | git submodule init 3 | git submodule update 4 | 5 | SDK_VER="latest" 6 | 7 | cd emsdk 8 | ./emsdk update-tags 9 | ./emsdk install $SDK_VER 10 | ./emsdk activate $SDK_VER 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/build_date.cc: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | std::string get_build_date() { 6 | std::stringstream ss; 7 | ss << "Build date: " << __DATE__ << ", " << __TIME__; 8 | return ss.str(); 9 | } 10 | -------------------------------------------------------------------------------- /make_desktop.sh: -------------------------------------------------------------------------------- 1 | # Stop and exit on error 2 | set -e 3 | 4 | touch src/build_date.cc 5 | 6 | if [ "$1" == "debug" ]; then 7 | echo "Building debug version" 8 | BUILD_DIR="build_desktop_debug" 9 | BUILD_TYPE=Debug 10 | else 11 | echo "Building release version by default" 12 | BUILD_DIR="build_desktop_release" 13 | BUILD_TYPE=Release 14 | fi 15 | 16 | if [ ! -d $BUILD_DIR ]; then 17 | mkdir $BUILD_DIR 18 | fi 19 | 20 | cd $BUILD_DIR 21 | cmake .. -DMY_TYPE=$BUILD_TYPE 22 | make -j 4 23 | -------------------------------------------------------------------------------- /make_web.sh: -------------------------------------------------------------------------------- 1 | 2 | # Stop and exit on error 3 | set -e 4 | 5 | touch src/build_date.cc 6 | 7 | if [ "$1" == "debug" ]; then 8 | echo "Building debug version" 9 | BUILD_DIR="build_emscripten_debug" 10 | BUILD_TYPE=Debug 11 | else 12 | echo "Building release version by default" 13 | BUILD_DIR="build_emscripten_release" 14 | BUILD_TYPE=Release 15 | fi 16 | 17 | # Setup compiler build flags 18 | if [ ! -f ./emsdk/emsdk_env.sh ]; then 19 | echo "File emsdk_env.sh not found, run init_web.sh first" 20 | exit 1 21 | fi 22 | source ./emsdk/emsdk_env.sh --build=Release 23 | 24 | if [ ! -d $BUILD_DIR ]; then 25 | mkdir $BUILD_DIR 26 | fi 27 | 28 | cd $BUILD_DIR 29 | emcmake cmake .. -DMY_TYPE=$BUILD_TYPE 30 | make -j 1 31 | -------------------------------------------------------------------------------- /src/Color.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright 1995-2007 Sun Microsystems, Inc. All Rights Reserved. 4 | This code was ported from the Java library java.awt.Color to C++. 5 | It is licenced under GPL V2 with the classpath exception. 6 | http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/Color.java 7 | */ 8 | 9 | #ifndef _CPP_COLOR_H_ 10 | #define _CPP_COLOR_H_ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | class Color { 18 | public: 19 | static std::array* RGBtoHSB(int r, int g, int b, std::array* hsbvals); 20 | static int HSBtoRGB(float hue, float saturation, float brightness); 21 | }; 22 | 23 | #endif // _CPP_COLOR_H_ 24 | -------------------------------------------------------------------------------- /src/Mapper003.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | Mapper003::Mapper003() : MapperDefault() { 12 | 13 | } 14 | 15 | shared_ptr Mapper003::Init(shared_ptr nes) { 16 | this->base_init(nes); 17 | return shared_from_this(); 18 | } 19 | 20 | void Mapper003::write(int address, uint16_t value) { 21 | if(address < 0x8000) { 22 | // Let the base mapper take care of it. 23 | this->base_write(address, value); 24 | } else { 25 | // This is a VROM bank select command. 26 | // Swap in the given VROM bank at 0x0000: 27 | int bank = (value % (nes->getRom()->getVromBankCount() / 2)) * 2; 28 | loadVromBank(bank, 0x0000); 29 | loadVromBank(bank + 1, 0x1000); 30 | load8kVromBank(value * 2, 0x0000); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Parameters.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | string Parameters::p1_up = "VK_UP"; 12 | string Parameters::p1_down = "VK_DOWN"; 13 | string Parameters::p1_left = "VK_LEFT"; 14 | string Parameters::p1_right = "VK_RIGHT"; 15 | string Parameters::p1_a = "VK_Z"; 16 | string Parameters::p1_b = "VK_X"; 17 | string Parameters::p1_start = "VK_ENTER"; 18 | string Parameters::p1_select = "VK_CONTROL"; 19 | 20 | string Parameters::p2_up = "VK_NUMPAD8"; 21 | string Parameters::p2_down = "VK_NUMPAD2"; 22 | string Parameters::p2_left = "VK_NUMPAD4"; 23 | string Parameters::p2_right = "VK_NUMPAD6"; 24 | string Parameters::p2_a = "VK_NUMPAD7"; 25 | string Parameters::p2_b = "VK_NUMPAD9"; 26 | string Parameters::p2_start = "VK_NUMPAD1"; 27 | string Parameters::p2_select = "VK_NUMPAD3"; 28 | -------------------------------------------------------------------------------- /src/Raster.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | Raster::Raster(vector* data, int w, int h) { 12 | this->data = data; 13 | width = w; 14 | height = h; 15 | } 16 | 17 | Raster::Raster(int w, int h) { 18 | data = new vector(w * h, 0); 19 | width = w; 20 | height = h; 21 | } 22 | 23 | void Raster::drawTile(Raster* srcRaster, int srcx, int srcy, int dstx, int dsty, int w, int h) { 24 | vector* src = srcRaster->data; 25 | int src_index; 26 | int dst_index; 27 | int tmp; 28 | 29 | for(int y = 0; y < h; ++y) { 30 | 31 | src_index = (srcy + y) * srcRaster->width + srcx; 32 | dst_index = (dsty + y) * width + dstx; 33 | 34 | for(int x = 0; x < w; ++x) { 35 | 36 | if((tmp = (*src)[src_index]) != 0) { 37 | (*data)[dst_index] = tmp; 38 | } 39 | 40 | ++src_index; 41 | ++dst_index; 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Globals.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | SDL_Window* Globals::g_window = nullptr; 12 | SDL_Renderer* Globals::g_renderer = nullptr; 13 | SDL_Texture* Globals::g_screen = nullptr; 14 | 15 | double Globals::CPU_FREQ_NTSC = 1789772.5; 16 | double Globals::CPU_FREQ_PAL = 1773447.4; 17 | int Globals::preferredFrameRate = 60; 18 | 19 | // Microseconds per frame: 20 | const double Globals::MS_PER_FRAME = 1000000.0 / 60.0; 21 | // What value to flush memory with on power-up: 22 | uint16_t Globals::memoryFlushValue = 0xFF; 23 | 24 | bool Globals::disableSprites = false; 25 | bool Globals::palEmulation = false; 26 | bool Globals::enableSound = true; 27 | 28 | std::map Globals::keycodes; //Java key codes 29 | std::map Globals::controls; //vNES controls codes 30 | std::map Globals::joysticks; 31 | bool Globals::is_windows = false; 32 | -------------------------------------------------------------------------------- /src/Mapper011.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | Mapper011::Mapper011() : MapperDefault() { 12 | 13 | } 14 | 15 | shared_ptr Mapper011::Init(shared_ptr nes) { 16 | this->base_init(nes); 17 | return shared_from_this(); 18 | } 19 | 20 | void Mapper011::write(int address, uint16_t value) { 21 | if (address < 0x8000) { 22 | // Let the base mapper take care of it 23 | this->base_write(address, value); 24 | } else { 25 | // Swap in the given PRG-ROM bank: 26 | int prgbank1 = ((value & 0xF) * 2) % this->nes->rom->romCount; 27 | int prgbank2 = ((value & 0xF) * 2 + 1) % this->nes->rom->romCount; 28 | 29 | loadRomBank(prgbank1, 0x8000); 30 | loadRomBank(prgbank2, 0xC000); 31 | 32 | if (this->nes->rom->vromCount > 0) { 33 | // Swap in the given VROM bank at 0x0000: 34 | int bank = ((value >> 4) * 2) % (this->nes->rom->vromCount); 35 | loadVromBank(bank, 0x0000); 36 | loadVromBank(bank + 1, 0x1000); 37 | } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | A NES emulator in WebAssembly. Try it live: http://workhorsy.org/SaltyNES/ 3 | 4 | Forked from the NaCl C++ version: https://github.com/workhorsylegacy/SaltyNESLegacy 5 | 6 | Which is based on vNES: https://github.com/WorkhorsyForks/vNES 7 | 8 | # Build requirements 9 | cmake 3.4.3 or greater 10 | 11 | python 2 12 | 13 | SDL2 14 | 15 | # Build and run in browser 16 | ```bash 17 | ./init_web.sh 18 | ./make_web.sh 19 | python -m SimpleHTTPServer 9999 20 | ``` 21 | 22 | # Build and run in desktop 23 | ```bash 24 | ./make_desktop.sh 25 | ./SaltyNES game.nes 26 | ``` 27 | 28 | TODO 29 | * Remove the mutex, or replace it with std::mutex 30 | * see if smb3 and punchout work in vnes 31 | * save memory to localstorage/indexeddb 32 | * tetris has no backgrounds 33 | 34 | * Try using opengl for the screen to see if it makes painting faster. 35 | * make fps show in html 36 | * Make it so the emulator can be restarted without reloading the page 37 | * Add gamepad support 38 | * make the emulator easy to embed in other web apps by having hooks for all gui buttons 39 | 40 | * Make it not cache the wasm file, even after shift reload 41 | * Is there a way to check if running asm.js instead of WebAssembly? 42 | * Shrink wasm file by removing complex libraries 43 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.3) 2 | project (SaltyNES) 3 | 4 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) 5 | set(CMAKE_BUILD_TYPE_INIT "Release") 6 | set(CMAKE_BUILD_TYPE "Release") 7 | set(CMAKE_CXX_STANDARD 14) 8 | 9 | file(GLOB SOURCES "src/*.cc") 10 | message(STATUS "system name: " ${CMAKE_SYSTEM_NAME} ", build: " ${MY_TYPE}) 11 | 12 | if ({MY_TYPE} MATCHES "Debug") 13 | set(MORE_FLAGS_NATIVE="-g -DDEBUG") 14 | set(MORE_FLAGS_WEB="-O0 -DDEBUG -g4 -s SAFE_HEAP=1 -s ASSERTIONS=1 -s SAFE_HEAP_LOG=1 -s STACK_OVERFLOW_CHECK=2") # add additional debug emscripten flags ? 15 | endif () 16 | 17 | if ({MY_TYPE} MATCHES "Release") 18 | set(MORE_FLAGS_NATIVE="") 19 | set(MORE_FLAGS_WEB="-O3") 20 | endif () 21 | 22 | if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") 23 | set(CMAKE_C_COMPILER "emcc") 24 | set(EMCC_LINKER_FLAGS "--bind -s WASM=1 -DWEB=true -std=c++14 -s USE_SDL=2 " ${MORE_FLAGS_WEB}) 25 | set(CMAKE_CXX_FLAGS "${EMCC_LINKER_FLAGS}") 26 | endif () 27 | 28 | 29 | if (NOT ${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") 30 | find_package(sdl2 REQUIRED) 31 | set(CMAKE_CXX_FLAGS "-O3 -std=c++14 -lSDL2 -lSDL2_mixer -DDESKTOP=true" ${MORE_FLAGS_NATIVE}) 32 | endif () 33 | 34 | add_executable(SaltyNES ${SOURCES}) 35 | target_link_libraries(SaltyNES ${SDL2_LIBRARIES}) 36 | -------------------------------------------------------------------------------- /src/Mapper002.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | Mapper002::Mapper002() : MapperDefault() { 12 | 13 | } 14 | 15 | shared_ptr Mapper002::Init(shared_ptr nes) { 16 | this->base_init(nes); 17 | return shared_from_this(); 18 | } 19 | 20 | void Mapper002::write(int address, uint16_t value) { 21 | if(address < 0x8000) { 22 | // Let the base mapper take care of it. 23 | this->base_write(address, value); 24 | } else { 25 | // This is a ROM bank select command. 26 | // Swap in the given ROM bank at 0x8000: 27 | loadRomBank(value, 0x8000); 28 | } 29 | } 30 | 31 | void Mapper002::loadROM(shared_ptr rom) { 32 | if(!rom->isValid()) { 33 | //System.out.println("UNROM: Invalid ROM! Unable to load."); 34 | return; 35 | } 36 | 37 | //System.out.println("UNROM: loading ROM.."); 38 | 39 | // Load PRG-ROM: 40 | loadRomBank(0, 0x8000); 41 | loadRomBank(rom->getRomBankCount() - 1, 0xC000); 42 | 43 | // Load CHR-ROM: 44 | loadCHRROM(); 45 | 46 | // Do Reset-Interrupt: 47 | //nes.getCpu().doResetInterrupt(); 48 | nes->getCpu()->requestIrq(CPU::IRQ_RESET); 49 | } 50 | -------------------------------------------------------------------------------- /static/styles.css: -------------------------------------------------------------------------------- 1 | 2 | *, *::before, *::after { 3 | box-sizing: border-box; 4 | } 5 | 6 | * { 7 | font-family: Helvetica, Arial, sans-serif; 8 | } 9 | 10 | body { 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | #screen { 16 | /* Don't blur the screen when resized */ 17 | image-rendering: -moz-crisp-edges; 18 | image-rendering: pixelated; 19 | -ms-interpolation-mode: nearest-neighbor; 20 | 21 | width: 256px; 22 | height: 240px; 23 | 24 | border: 0px none; 25 | background-color: black; 26 | } 27 | 28 | #status { 29 | font-weight: bold; 30 | color: rgb(120, 120, 120); 31 | } 32 | 33 | #control_holder { 34 | text-align: right; 35 | position: absolute; 36 | top: 0; 37 | right: 0; 38 | } 39 | 40 | #screen_holder { 41 | text-align: center; 42 | } 43 | 44 | #game_holder { 45 | text-align: center; 46 | } 47 | 48 | #output { 49 | width: 100%; 50 | height: 200px; 51 | margin: 0 auto; 52 | margin-top: 10px; 53 | border-left: 0px; 54 | border-right: 0px; 55 | padding-left: 0px; 56 | padding-right: 0px; 57 | display: block; 58 | background-color: black; 59 | color: white; 60 | font-family: 'Lucida Console', Monaco, monospace; 61 | outline: none; 62 | } 63 | 64 | #footer { 65 | padding: 20px; 66 | } 67 | 68 | .ui_button { 69 | height: 44px; 70 | margin: 10px; 71 | } 72 | 73 | .ui_select { 74 | height: 44px; 75 | } 76 | 77 | .ui_checkbox { 78 | height: 44px; 79 | width: 44px; 80 | } 81 | -------------------------------------------------------------------------------- /src/Logger.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | A NES emulator in WebAssembly. Based on vNES. 4 | Licensed under GPLV3 or later 5 | Hosted at: https://github.com/workhorsy/SaltyNES 6 | */ 7 | 8 | #include "SaltyNES.h" 9 | 10 | #include 11 | 12 | using namespace std; 13 | 14 | 15 | string Logger::_file_name; 16 | ofstream* Logger::_log = nullptr; 17 | size_t Logger::_length = 0; 18 | size_t Logger::_log_number = 0; 19 | bool Logger::_is_on = false; 20 | 21 | void Logger::init(string file_name) { 22 | if(!Logger::_is_on) return; 23 | 24 | // Create the first log file 25 | _file_name = file_name; 26 | _log_number = 0; 27 | create_log_file(); 28 | } 29 | 30 | void Logger::create_log_file() { 31 | if(!Logger::_is_on) return; 32 | 33 | // Create the next numbered log name 34 | stringstream out; 35 | out << _file_name << _log_number; 36 | 37 | // Create the file 38 | if(_log) 39 | _log->close(); 40 | _log = new ofstream(); 41 | _log->open(out.str().c_str()); 42 | } 43 | 44 | void Logger::write(string message) { 45 | if(!Logger::_is_on) return; 46 | 47 | // Write the message to the log 48 | (*_log) << message; 49 | _length += message.length(); 50 | } 51 | 52 | void Logger::flush() { 53 | if(!Logger::_is_on) return; 54 | 55 | _log->flush(); 56 | 57 | // If the log file is too big, make a new one 58 | if(_length >= 10000000) { 59 | _length = 0; 60 | ++_log_number; 61 | create_log_file(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/base64.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | This base64 implementation has been modified to conform with newer C++ 4 | style guidelines. The original can be found at: 5 | https://github.com/ReneNyffenegger/development_misc/tree/master/base64 6 | 7 | 8 | Copyright (C) 2004-2008 René Nyffenegger 9 | 10 | This source code is provided 'as-is', without any express or implied 11 | warranty. In no event will the author be held liable for any damages 12 | arising from the use of this software. 13 | 14 | Permission is granted to anyone to use this software for any purpose, 15 | including commercial applications, and to alter it and redistribute it 16 | freely, subject to the following restrictions: 17 | 18 | 1. The origin of this source code must not be misrepresented; you must not 19 | claim that you wrote the original source code. If you use this source code 20 | in a product, an acknowledgment in the product documentation would be 21 | appreciated but is not required. 22 | 23 | 2. Altered source versions must be plainly marked as such, and must not be 24 | misrepresented as being the original source code. 25 | 26 | 3. This notice may not be removed or altered from any source distribution. 27 | 28 | René Nyffenegger rene.nyffenegger@adp-gmbh.ch 29 | 30 | */ 31 | 32 | #ifndef _BASE64_H_ 33 | #define _BASE64_H_ 34 | 35 | #include 36 | 37 | std::string base64_encode(unsigned char const* , unsigned int len); 38 | std::string base64_decode(std::string const& s); 39 | 40 | #endif // _BASE64_H_ 41 | -------------------------------------------------------------------------------- /src/sha256sum.h: -------------------------------------------------------------------------------- 1 | /* The MIT License 2 | 3 | Copyright (C) 2011 Zilong Tan (labytan@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | #ifndef _ULIB_SHA256_H_ 27 | #define _ULIB_SHA256_H_ 28 | 29 | #include 30 | 31 | #define SHA256_HASH_SIZE 32 /* 256 bit */ 32 | #define SHA256_HASH_WORDS 8 33 | 34 | struct _SHA256Context { 35 | uint64_t totalLength; 36 | uint32_t hash[SHA256_HASH_WORDS]; 37 | uint32_t bufferLength; 38 | union { 39 | uint32_t words[16]; 40 | uint8_t bytes[64]; 41 | } buffer; 42 | }; 43 | typedef struct _SHA256Context SHA256Context; 44 | 45 | #ifdef __cplusplus 46 | extern "C" { 47 | #endif 48 | 49 | void SHA256Init(SHA256Context * sc); 50 | 51 | void SHA256Update(SHA256Context * sc, const void *data, uint32_t len); 52 | 53 | void SHA256Final(SHA256Context * sc, uint8_t hash[SHA256_HASH_SIZE]); 54 | 55 | #ifdef __cplusplus 56 | } 57 | #endif 58 | 59 | #endif // _ULIB_SHA256_H_ 60 | -------------------------------------------------------------------------------- /src/Memory.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | 12 | Memory::Memory() : enable_shared_from_this() { 13 | } 14 | 15 | shared_ptr Memory::Init(shared_ptr nes, size_t byteCount) { 16 | this->nes = nes; 17 | this->mem = vector(byteCount, 0); 18 | return shared_from_this(); 19 | } 20 | 21 | Memory::~Memory() { 22 | nes = nullptr; 23 | } 24 | 25 | void Memory::stateLoad(ByteBuffer* buf) { 26 | buf->readByteArray(&mem); 27 | } 28 | 29 | void Memory::stateSave(ByteBuffer* buf) { 30 | buf->putByteArray(&mem); 31 | } 32 | 33 | void Memory::reset() { 34 | std::fill(mem.begin(), mem.end(), 0); 35 | } 36 | 37 | size_t Memory::getMemSize() { 38 | return mem.size(); 39 | } 40 | 41 | void Memory::write(size_t address, uint16_t value) { 42 | mem[address] = value; 43 | } 44 | 45 | uint16_t Memory::load(size_t address) { 46 | return mem[address]; 47 | } 48 | 49 | void Memory::dump(string file) { 50 | dump(file, 0, mem.size()); 51 | } 52 | 53 | void Memory::dump(string file, size_t offset, size_t length) { 54 | auto ch = vector(length); 55 | for(size_t i=0; i(mem[offset + i]); 57 | } 58 | 59 | try{ 60 | ofstream writer(file.c_str(), ios::out|ios::binary); 61 | writer.write(ch.data(), ch.size()); 62 | writer.close(); 63 | printf("Memory dumped to file \"%s\".\n", file.c_str()); 64 | 65 | }catch(exception& ioe) { 66 | printf("%s\n", "Memory dump to file: IO Error!"); 67 | } 68 | } 69 | 70 | void Memory::write(size_t address, array* array, size_t length) { 71 | if(address+length > mem.size()) 72 | return; 73 | array_copy(array, 0, &mem, address, length); 74 | } 75 | 76 | void Memory::write(size_t address, array* array, size_t arrayoffset, size_t length) { 77 | if(address+length > mem.size()) 78 | return; 79 | array_copy(array, arrayoffset, &mem, address, length); 80 | } 81 | -------------------------------------------------------------------------------- /src/NameTable.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | NameTable::NameTable() { 12 | this->name = ""; 13 | this->tile.fill(0); 14 | this->attrib.fill(0); 15 | this->width = 32; 16 | this->height = 32; 17 | } 18 | 19 | NameTable::NameTable(string name) { 20 | this->name = name; 21 | this->tile.fill(0); 22 | this->attrib.fill(0); 23 | this->width = 32; 24 | this->height = 32; 25 | } 26 | 27 | NameTable::~NameTable() { 28 | } 29 | 30 | uint16_t NameTable::getTileIndex(int x, int y) { 31 | return tile[y * width + x]; 32 | } 33 | 34 | uint16_t NameTable::getAttrib(int x, int y) { 35 | return attrib[y * width + x]; 36 | } 37 | 38 | void NameTable::writeTileIndex(int index, int value) { 39 | tile[index] = static_cast(value); 40 | } 41 | 42 | void NameTable::writeAttrib(int index, int value) { 43 | int basex, basey; 44 | int add; 45 | int tx, ty; 46 | //int attindex; 47 | basex = index % 8; 48 | basey = index / 8; 49 | basex *= 4; 50 | basey *= 4; 51 | 52 | for(int sqy = 0; sqy < 2; ++sqy) { 53 | for(int sqx = 0; sqx < 2; ++sqx) { 54 | add = (value >> (2 * (sqy * 2 + sqx))) & 3; 55 | for(int y = 0; y < 2; ++y) { 56 | for(int x = 0; x < 2; ++x) { 57 | tx = basex + sqx * 2 + x; 58 | ty = basey + sqy * 2 + y; 59 | //attindex = ty * width + tx; 60 | attrib[ty * width + tx] = static_cast((add << 2) & 12); 61 | ////System.out.println("x="+tx+" y="+ty+" value="+attrib[ty*width+tx]+" index="+attindex); 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | void NameTable::stateSave(ByteBuffer* buf) { 69 | for(int i = 0; i < width * height; ++i) { 70 | if(tile[i] > 255)//System.out.println(">255!!"); 71 | { 72 | buf->putByte(static_cast(tile[i])); 73 | } 74 | } 75 | for(int i = 0; i < width * height; ++i) { 76 | buf->putByte(static_cast(attrib[i])); 77 | } 78 | } 79 | 80 | void NameTable::stateLoad(ByteBuffer* buf) { 81 | for(int i = 0; i < width * height; ++i) { 82 | tile[i] = buf->readByte(); 83 | } 84 | for(int i = 0; i < width * height; ++i) { 85 | attrib[i] = buf->readByte(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Mapper007.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | Mapper007::Mapper007() : MapperDefault() { 12 | 13 | } 14 | 15 | shared_ptr Mapper007::Init(shared_ptr nes) { 16 | currentOffset = 0; 17 | currentMirroring = -1; 18 | 19 | // Get ref to ROM: 20 | shared_ptr rom = nes->getRom(); 21 | 22 | // Read out all PRG rom: 23 | int bc = rom->getRomBankCount(); 24 | prgrom = vector(bc * 16384, 0); 25 | for(int i = 0; i < bc; ++i) { 26 | array_copy(rom->getRomBank(i), 0, &prgrom, i * 16384, 16384); 27 | } 28 | 29 | this->base_init(nes); 30 | return shared_from_this(); 31 | } 32 | 33 | uint16_t Mapper007::load(int address) { 34 | if(address < 0x8000) { 35 | // Register read 36 | return this->base_load(address); 37 | } else { 38 | if((address + currentOffset) >= 262144) { 39 | return prgrom[(address + currentOffset) - 262144]; 40 | } else { 41 | return prgrom[address + currentOffset]; 42 | } 43 | } 44 | } 45 | 46 | void Mapper007::write(int address, uint16_t value) { 47 | if(address < 0x8000) { 48 | // Let the base mapper take care of it. 49 | this->base_write(address, value); 50 | } else { 51 | // Set PRG offset: 52 | currentOffset = ((value & 0xF) - 1) << 15; 53 | 54 | // Set mirroring: 55 | if(currentMirroring != (value & 0x10)) { 56 | 57 | currentMirroring = value & 0x10; 58 | if(currentMirroring == 0) { 59 | nes->getPpu()->setMirroring(ROM::SINGLESCREEN_MIRRORING); 60 | } else { 61 | nes->getPpu()->setMirroring(ROM::SINGLESCREEN_MIRRORING2); 62 | } 63 | 64 | } 65 | 66 | } 67 | } 68 | 69 | void Mapper007::mapperInternalStateLoad(ByteBuffer* buf) { 70 | this->base_mapperInternalStateLoad(buf); 71 | // Check version: 72 | if(buf->readByte() == 1) { 73 | currentMirroring = buf->readByte(); 74 | currentOffset = buf->readInt(); 75 | } 76 | } 77 | 78 | void Mapper007::mapperInternalStateSave(ByteBuffer* buf) { 79 | this->base_mapperInternalStateSave(buf); 80 | 81 | // Version: 82 | buf->putByte(static_cast(1)); 83 | 84 | // State: 85 | buf->putByte(static_cast(currentMirroring)); 86 | buf->putInt(currentOffset); 87 | } 88 | 89 | void Mapper007::reset() { 90 | this->base_reset(); 91 | currentOffset = 0; 92 | currentMirroring = -1; 93 | } 94 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SaltyNES: A NES emulator in WebAssembly 7 | 8 | 9 | 10 | 11 | 12 |

SaltyNES: A NES emulator in WebAssembly

13 | 14 |
15 |
16 | 17 | 18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 | 32 | 33 |
Loading ...
34 | 35 |
36 | 37 |
38 |
39 | 40 |
41 | 49 | 50 |
51 | 52 |

Controls

53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
Direction:WASD
B / A:J / K
Start / Select:Enter / Shift
67 | 68 | 69 | 70 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/Misc.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | vector* Misc::_rnd = nullptr; 12 | int Misc::nextRnd = 0; 13 | float Misc::rndret = 0; 14 | 15 | vector* Misc::rnd() { 16 | if(_rnd == nullptr) { 17 | _rnd = new vector(100000); 18 | for(size_t i = 0; i < _rnd->size(); ++i) { 19 | (*_rnd)[i] = rand_float(); 20 | 21 | } 22 | } 23 | return _rnd; 24 | } 25 | 26 | string Misc::hex8(int i) { 27 | string s = intToHexString(i); 28 | while(s.length() < 2) { 29 | s = "0" + s; 30 | } 31 | return toUpperCase(s); 32 | } 33 | 34 | string Misc::hex16(int i) { 35 | string s = intToHexString(i); 36 | while(s.length() < 4) { 37 | s = "0" + s; 38 | } 39 | return toUpperCase(s); 40 | } 41 | 42 | string Misc::binN(int num, int N) { 43 | char* c = new char[N]; 44 | for(int i = 0; i < N; ++i) { 45 | c[N - i - 1] = (num & 0x1) == 1 ? '1' : '0'; 46 | num >>= 1; 47 | } 48 | return string(c); 49 | } 50 | 51 | string Misc::bin8(int num) { 52 | return binN(num, 8); 53 | } 54 | 55 | string Misc::bin16(int num) { 56 | return binN(num, 16); 57 | } 58 | 59 | string Misc::binStr(uint32_t value, int bitcount) { 60 | string ret = ""; 61 | for(int i = 0; i < bitcount; ++i) { 62 | ret = ((value & (1 << i)) != 0 ? "1" : "0") + ret; 63 | } 64 | return ret; 65 | } 66 | 67 | string Misc::pad(string str, string padStr, int length) { 68 | while(static_cast(str.length()) < length) { 69 | str += padStr; 70 | } 71 | return str; 72 | } 73 | 74 | float Misc::random() { 75 | rndret = (*rnd())[nextRnd]; 76 | ++nextRnd; 77 | if(nextRnd >= static_cast(rnd()->size())) { 78 | nextRnd = static_cast(rand_float() * (rnd()->size() - 1)); 79 | } 80 | return rndret; 81 | } 82 | 83 | string Misc::from_vector_to_hex_string(array* data) { 84 | const size_t BYTE_LEN = 4; 85 | stringstream out; 86 | for(size_t i=0; isize(); ++i) { 87 | out << std::hex << setfill('0') << std::setw(BYTE_LEN) << static_cast((*data)[i]); 88 | } 89 | return out.str(); 90 | } 91 | 92 | vector* Misc::from_hex_string_to_vector(string data) { 93 | const size_t BYTE_LEN = 4; 94 | const size_t VECTOR_SIZE = data.length() / BYTE_LEN; 95 | vector* retval = new vector(VECTOR_SIZE, 0); 96 | 97 | uint16_t value = 0; 98 | stringstream in; 99 | size_t j = 0; 100 | for(size_t i=0; i> value; 104 | (*retval)[i] = value; 105 | j += BYTE_LEN; 106 | } 107 | 108 | return retval; 109 | } 110 | -------------------------------------------------------------------------------- /src/ChannelNoise.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | 12 | ChannelNoise::ChannelNoise() { 13 | } 14 | 15 | ChannelNoise::ChannelNoise(shared_ptr papu) { 16 | this->papu = papu; 17 | 18 | _isEnabled = false; 19 | envDecayDisable = false; 20 | envDecayLoopEnable = false; 21 | lengthCounterEnable = false; 22 | envReset = false; 23 | shiftNow = false; 24 | lengthCounter = 0; 25 | progTimerCount = 0; 26 | progTimerMax = 0; 27 | envDecayRate = 0; 28 | envDecayCounter = 0; 29 | envVolume = 0; 30 | masterVolume = 0; 31 | shiftReg = 1 << 14; 32 | randomBit = 0; 33 | randomMode = 0; 34 | sampleValue = 0; 35 | accValue = 0; 36 | accCount = 1; 37 | tmp = 0; 38 | } 39 | 40 | ChannelNoise::~ChannelNoise() { 41 | papu = nullptr; 42 | } 43 | 44 | void ChannelNoise::clockLengthCounter() { 45 | if(lengthCounterEnable && lengthCounter > 0) { 46 | --lengthCounter; 47 | if(lengthCounter == 0) { 48 | updateSampleValue(); 49 | } 50 | } 51 | } 52 | 53 | void ChannelNoise::clockEnvDecay() { 54 | if(envReset) { 55 | // Reset envelope: 56 | envReset = false; 57 | envDecayCounter = envDecayRate + 1; 58 | envVolume = 0xF; 59 | } else if(--envDecayCounter <= 0) { 60 | // Normal handling: 61 | envDecayCounter = envDecayRate + 1; 62 | if(envVolume > 0) { 63 | --envVolume; 64 | } else { 65 | envVolume = envDecayLoopEnable ? 0xF : 0; 66 | } 67 | } 68 | 69 | masterVolume = envDecayDisable ? envDecayRate : envVolume; 70 | updateSampleValue(); 71 | } 72 | 73 | void ChannelNoise::updateSampleValue() { 74 | if(_isEnabled && lengthCounter > 0) { 75 | sampleValue = randomBit * masterVolume; 76 | } 77 | } 78 | 79 | void ChannelNoise::writeReg(int address, int value) { 80 | if(address == 0x400C) { 81 | // Volume/Envelope decay: 82 | envDecayDisable = ((value & 0x10) != 0); 83 | envDecayRate = value & 0xF; 84 | envDecayLoopEnable = ((value & 0x20) != 0); 85 | lengthCounterEnable = ((value & 0x20) == 0); 86 | masterVolume = envDecayDisable ? envDecayRate : envVolume; 87 | 88 | } else if(address == 0x400E) { 89 | 90 | // Programmable timer: 91 | progTimerMax = papu->getNoiseWaveLength(value & 0xF); 92 | randomMode = value >> 7; 93 | 94 | } else if(address == 0x400F) { 95 | 96 | // Length counter 97 | lengthCounter = papu->getLengthMax(value & 248); 98 | envReset = true; 99 | 100 | } 101 | 102 | // Update: 103 | //updateSampleValue(); 104 | } 105 | 106 | void ChannelNoise::setEnabled(bool value) { 107 | _isEnabled = value; 108 | if(!value) { 109 | lengthCounter = 0; 110 | } 111 | updateSampleValue(); 112 | } 113 | 114 | bool ChannelNoise::isEnabled() { 115 | return _isEnabled; 116 | } 117 | 118 | int ChannelNoise::getLengthStatus() { 119 | return ((lengthCounter == 0 || !_isEnabled) ? 0 : 1); 120 | } 121 | 122 | void ChannelNoise::reset() { 123 | progTimerCount = 0; 124 | progTimerMax = 0; 125 | _isEnabled = false; 126 | lengthCounter = 0; 127 | lengthCounterEnable = false; 128 | envDecayDisable = false; 129 | envDecayLoopEnable = false; 130 | shiftNow = false; 131 | envDecayRate = 0; 132 | envDecayCounter = 0; 133 | envVolume = 0; 134 | masterVolume = 0; 135 | shiftReg = 1; 136 | randomBit = 0; 137 | randomMode = 0; 138 | sampleValue = 0; 139 | tmp = 0; 140 | } 141 | -------------------------------------------------------------------------------- /src/Color.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright 1995-2007 Sun Microsystems, Inc. All Rights Reserved. 4 | This code was ported from the Java library java.awt.Color to C++. 5 | It is licenced under GPL V2 with the classpath exception. 6 | http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/awt/Color.java 7 | */ 8 | 9 | #include "Color.h" 10 | 11 | std::array* Color::RGBtoHSB(int r, int g, int b, std::array* hsbvals) { 12 | assert(hsbvals != nullptr); 13 | float hue, saturation, brightness; 14 | 15 | int cmax = (r > g) ? r : g; 16 | if(b > cmax) cmax = b; 17 | int cmin = (r < g) ? r : g; 18 | if(b < cmin) cmin = b; 19 | 20 | brightness = static_cast(cmax) / 255.0f; 21 | if(cmax != 0) 22 | saturation = static_cast(cmax - cmin) / static_cast(cmax); 23 | else 24 | saturation = 0; 25 | if(saturation == 0) { 26 | hue = 0; 27 | } else { 28 | float redc = static_cast(cmax - r) / static_cast(cmax - cmin); 29 | float greenc = static_cast(cmax - g) / static_cast(cmax - cmin); 30 | float bluec = static_cast(cmax - b) / static_cast(cmax - cmin); 31 | if(r == cmax) 32 | hue = bluec - greenc; 33 | else if(g == cmax) 34 | hue = 2.0f + redc - bluec; 35 | else 36 | hue = 4.0f + greenc - redc; 37 | hue = hue / 6.0f; 38 | if(hue < 0) 39 | hue = hue + 1.0f; 40 | } 41 | (*hsbvals)[0] = hue; 42 | (*hsbvals)[1] = saturation; 43 | (*hsbvals)[2] = brightness; 44 | return hsbvals; 45 | } 46 | 47 | int Color::HSBtoRGB(float hue, float saturation, float brightness) { 48 | int r = 0, g = 0, b = 0; 49 | if(saturation == 0) { 50 | r = g = b = static_cast(brightness * 255.0f + 0.5f); 51 | } else { 52 | float h = (hue - static_cast(floor(hue))) * 6.0f; 53 | float f = h - static_cast(floor(h)); 54 | float p = brightness * (1.0f - saturation); 55 | float q = brightness * (1.0f - saturation * f); 56 | float t = brightness * (1.0f - (saturation * (1.0f - f))); 57 | switch (static_cast(h)) { 58 | case 0: 59 | r = static_cast(brightness * 255.0f + 0.5f); 60 | g = static_cast(t * 255.0f + 0.5f); 61 | b = static_cast(p * 255.0f + 0.5f); 62 | break; 63 | case 1: 64 | r = static_cast(q * 255.0f + 0.5f); 65 | g = static_cast(brightness * 255.0f + 0.5f); 66 | b = static_cast(p * 255.0f + 0.5f); 67 | break; 68 | case 2: 69 | r = static_cast(p * 255.0f + 0.5f); 70 | g = static_cast(brightness * 255.0f + 0.5f); 71 | b = static_cast(t * 255.0f + 0.5f); 72 | break; 73 | case 3: 74 | r = static_cast(p * 255.0f + 0.5f); 75 | g = static_cast(q * 255.0f + 0.5f); 76 | b = static_cast(brightness * 255.0f + 0.5f); 77 | break; 78 | case 4: 79 | r = static_cast(t * 255.0f + 0.5f); 80 | g = static_cast(p * 255.0f + 0.5f); 81 | b = static_cast(brightness * 255.0f + 0.5f); 82 | break; 83 | case 5: 84 | r = static_cast(brightness * 255.0f + 0.5f); 85 | g = static_cast(p * 255.0f + 0.5f); 86 | b = static_cast(q * 255.0f + 0.5f); 87 | break; 88 | } 89 | } 90 | return 0xff000000 | (r << 16) | (g << 8) | (b << 0); 91 | } 92 | -------------------------------------------------------------------------------- /src/ChannelTriangle.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | ChannelTriangle::ChannelTriangle() { 12 | } 13 | 14 | ChannelTriangle::ChannelTriangle(shared_ptr papu) { 15 | this->papu = papu; 16 | this->_isEnabled = false; 17 | this->sampleCondition = false; 18 | this->lengthCounterEnable = false; 19 | this->lcHalt = false; 20 | this->lcControl = false; 21 | this->progTimerCount = 0; 22 | this->progTimerMax = 0; 23 | this->triangleCounter = 0; 24 | this->lengthCounter = 0; 25 | this->linearCounter = 0; 26 | this->lcLoadValue = 0; 27 | this->sampleValue = 0; 28 | this->tmp = 0; 29 | } 30 | 31 | ChannelTriangle::~ChannelTriangle() { 32 | papu = nullptr; 33 | } 34 | 35 | void ChannelTriangle::clockLengthCounter() { 36 | if(lengthCounterEnable && lengthCounter > 0) { 37 | --lengthCounter; 38 | if(lengthCounter == 0) { 39 | updateSampleCondition(); 40 | } 41 | } 42 | } 43 | 44 | void ChannelTriangle::clockLinearCounter() { 45 | if(lcHalt) { 46 | 47 | // Load: 48 | linearCounter = lcLoadValue; 49 | updateSampleCondition(); 50 | 51 | } else if(linearCounter > 0) { 52 | 53 | // Decrement: 54 | --linearCounter; 55 | updateSampleCondition(); 56 | 57 | } 58 | 59 | if(!lcControl) { 60 | 61 | // Clear halt flag: 62 | lcHalt = false; 63 | 64 | } 65 | 66 | } 67 | 68 | int ChannelTriangle::getLengthStatus() { 69 | return ((lengthCounter == 0 || !_isEnabled) ? 0 : 1); 70 | } 71 | /* 72 | int ChannelTriangle::readReg(int address) { 73 | return 0; 74 | } 75 | */ 76 | void ChannelTriangle::writeReg(int address, int value) { 77 | if(address == 0x4008) { 78 | 79 | // New values for linear counter: 80 | lcControl = (value & 0x80) != 0; 81 | lcLoadValue = value & 0x7F; 82 | 83 | // Length counter enable: 84 | lengthCounterEnable = !lcControl; 85 | 86 | } else if(address == 0x400A) { 87 | 88 | // Programmable timer: 89 | progTimerMax &= 0x700; 90 | progTimerMax |= value; 91 | 92 | } else if(address == 0x400B) { 93 | 94 | // Programmable timer, length counter 95 | progTimerMax &= 0xFF; 96 | progTimerMax |= ((value & 0x07) << 8); 97 | lengthCounter = papu->getLengthMax(value & 0xF8); 98 | lcHalt = true; 99 | 100 | } 101 | 102 | updateSampleCondition(); 103 | 104 | } 105 | 106 | void ChannelTriangle::clockProgrammableTimer(int nCycles) { 107 | if(progTimerMax > 0) { 108 | progTimerCount += nCycles; 109 | while(progTimerMax > 0 && progTimerCount >= progTimerMax) { 110 | progTimerCount -= progTimerMax; 111 | if(_isEnabled && lengthCounter > 0 && linearCounter > 0) { 112 | clockTriangleGenerator(); 113 | } 114 | } 115 | } 116 | 117 | } 118 | 119 | void ChannelTriangle::clockTriangleGenerator() { 120 | ++triangleCounter; 121 | triangleCounter &= 0x1F; 122 | } 123 | 124 | void ChannelTriangle::setEnabled(bool value) { 125 | _isEnabled = value; 126 | if(!value) { 127 | lengthCounter = 0; 128 | } 129 | updateSampleCondition(); 130 | } 131 | 132 | bool ChannelTriangle::isEnabled() { 133 | return _isEnabled; 134 | } 135 | 136 | void ChannelTriangle::updateSampleCondition() { 137 | sampleCondition = 138 | _isEnabled && 139 | progTimerMax > 7 && 140 | linearCounter > 0 && 141 | lengthCounter > 0; 142 | } 143 | 144 | void ChannelTriangle::reset() { 145 | progTimerCount = 0; 146 | progTimerMax = 0; 147 | triangleCounter = 0; 148 | _isEnabled = false; 149 | sampleCondition = false; 150 | lengthCounter = 0; 151 | lengthCounterEnable = false; 152 | linearCounter = 0; 153 | lcLoadValue = 0; 154 | lcHalt = true; 155 | lcControl = false; 156 | tmp = 0; 157 | sampleValue = 0xF; 158 | } 159 | -------------------------------------------------------------------------------- /src/base64.cc: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | This base64 implementation has been modified to conform with newer C++ 4 | style guidelines. The original can be found at: 5 | https://github.com/ReneNyffenegger/development_misc/tree/master/base64 6 | 7 | Copyright (C) 2004-2008 René Nyffenegger 8 | 9 | This source code is provided 'as-is', without any express or implied 10 | warranty. In no event will the author be held liable for any damages 11 | arising from the use of this software. 12 | 13 | Permission is granted to anyone to use this software for any purpose, 14 | including commercial applications, and to alter it and redistribute it 15 | freely, subject to the following restrictions: 16 | 17 | 1. The origin of this source code must not be misrepresented; you must not 18 | claim that you wrote the original source code. If you use this source code 19 | in a product, an acknowledgment in the product documentation would be 20 | appreciated but is not required. 21 | 22 | 2. Altered source versions must be plainly marked as such, and must not be 23 | misrepresented as being the original source code. 24 | 25 | 3. This notice may not be removed or altered from any source distribution. 26 | 27 | René Nyffenegger rene.nyffenegger@adp-gmbh.ch 28 | 29 | */ 30 | 31 | #include "base64.h" 32 | 33 | static const std::string base64_chars = 34 | "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 35 | "abcdefghijklmnopqrstuvwxyz" 36 | "0123456789+/"; 37 | 38 | 39 | static inline bool is_base64(unsigned char c) { 40 | return (isalnum(c) || (c == '+') || (c == '/')); 41 | } 42 | 43 | std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { 44 | std::string ret; 45 | int i = 0; 46 | int j = 0; 47 | unsigned char char_array_3[3]; 48 | unsigned char char_array_4[4]; 49 | 50 | while(in_len--) { 51 | char_array_3[i++] = *(bytes_to_encode++); 52 | if(i == 3) { 53 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 54 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 55 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 56 | char_array_4[3] = char_array_3[2] & 0x3f; 57 | 58 | for(i = 0; (i <4) ; i++) 59 | ret += base64_chars[char_array_4[i]]; 60 | i = 0; 61 | } 62 | } 63 | 64 | if(i) 65 | { 66 | for(j = i; j < 3; j++) 67 | char_array_3[j] = '\0'; 68 | 69 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 70 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 71 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 72 | char_array_4[3] = char_array_3[2] & 0x3f; 73 | 74 | for(j = 0; (j < i + 1); j++) 75 | ret += base64_chars[char_array_4[j]]; 76 | 77 | while((i++ < 3)) 78 | ret += '='; 79 | 80 | } 81 | 82 | return ret; 83 | 84 | } 85 | 86 | std::string base64_decode(std::string const& encoded_string) { 87 | int in_len = encoded_string.size(); 88 | int i = 0; 89 | int j = 0; 90 | int in_ = 0; 91 | unsigned char char_array_4[4], char_array_3[3]; 92 | std::string ret; 93 | 94 | while(in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { 95 | char_array_4[i++] = encoded_string[in_]; in_++; 96 | if(i ==4) { 97 | for(i = 0; i <4; i++) 98 | char_array_4[i] = base64_chars.find(char_array_4[i]); 99 | 100 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 101 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 102 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 103 | 104 | for(i = 0; (i < 3); i++) 105 | ret += char_array_3[i]; 106 | i = 0; 107 | } 108 | } 109 | 110 | if(i) { 111 | for(j = i; j <4; j++) 112 | char_array_4[j] = 0; 113 | 114 | for(j = 0; j <4; j++) 115 | char_array_4[j] = base64_chars.find(char_array_4[j]); 116 | 117 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 118 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 119 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 120 | 121 | for(j = 0; (j < i - 1); j++) ret += char_array_3[j]; 122 | } 123 | 124 | return ret; 125 | } 126 | -------------------------------------------------------------------------------- /static/no_more_jquery.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Matthew Brennan Jones 2 | // This software uses a MIT style license 3 | // https://github.com/SoftwareAddictionShow/no-more-jquery 4 | "use strict"; 5 | 6 | // Great website for reasons not to use jquery: 7 | // http://youmightnotneedjquery.com 8 | 9 | let style = document.createElement('style'); 10 | style.type = 'text/css'; 11 | style.innerHTML = '.hidden { display: none !important; }'; 12 | document.getElementsByTagName('head')[0].appendChild(style); 13 | 14 | function $(selector) { 15 | if(! selector || selector.length === 0) { 16 | return null; 17 | } else if (selector[0] === '#') { 18 | return document.querySelector(selector); 19 | } else if(selector[0] === '.') { 20 | return document.querySelectorAll(selector); 21 | } else { 22 | return document.getElementsByTagName(selector); 23 | } 24 | 25 | return null; 26 | } 27 | 28 | function hide(selector) { 29 | let elements = document.querySelectorAll(selector); 30 | for (let i=0; i new_value; 129 | let diff_value = is_bigger ? old_value - new_value : new_value - old_value; 130 | let start_time = null; 131 | 132 | let stepTime = function(timestamp) { 133 | if (start_time === null) { 134 | start_time = timestamp; 135 | } 136 | let elapsed_time = timestamp - start_time; 137 | let percent = elapsed_time / target_time; 138 | if (percent >= 1.0) { 139 | percent = 1.0; 140 | } 141 | 142 | let trans_value = 0; 143 | if (is_bigger) { 144 | trans_value = old_value - (diff_value * percent); 145 | } else { 146 | trans_value= old_value + (diff_value * percent); 147 | } 148 | cb(trans_value); 149 | if (percent !== 1.0) { 150 | window.requestAnimationFrame(stepTime); 151 | } 152 | }; 153 | window.requestAnimationFrame(stepTime); 154 | } 155 | -------------------------------------------------------------------------------- /src/main.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | A NES emulator in WebAssembly. Based on vNES. 4 | Licensed under GPLV3 or later 5 | Hosted at: https://github.com/workhorsy/SaltyNES 6 | */ 7 | 8 | #include "SaltyNES.h" 9 | 10 | std::string get_build_date(); 11 | 12 | using namespace std; 13 | 14 | SaltyNES salty_nes; 15 | vector g_game_data; 16 | string g_game_file_name; 17 | 18 | void set_is_windows() { 19 | Globals::is_windows = true; 20 | } 21 | 22 | bool toggle_sound() { 23 | shared_ptr papu = salty_nes.nes->papu; 24 | papu->_is_muted = ! papu->_is_muted; 25 | return !papu->_is_muted; 26 | } 27 | 28 | void on_emultor_start() { 29 | salty_nes.init(); 30 | salty_nes.load_rom(g_game_file_name, &g_game_data, nullptr); 31 | salty_nes.run(); 32 | } 33 | 34 | void on_emultor_loop() { 35 | if (salty_nes.nes) { 36 | salty_nes.nes->getCpu()->emulate_frame(); 37 | 38 | if (salty_nes.nes->getCpu()->stopRunning) { 39 | #ifdef WEB 40 | emscripten_cancel_main_loop(); 41 | #endif 42 | } 43 | } 44 | } 45 | 46 | void start_main_loop() { 47 | #ifdef DESKTOP 48 | while (! salty_nes.nes->getCpu()->stopRunning) { 49 | on_emultor_loop(); 50 | } 51 | #endif 52 | 53 | #ifdef WEB 54 | // Tell the web app that everything is loaded 55 | EM_ASM_ARGS({ 56 | onReady(); 57 | }, 0); 58 | 59 | emscripten_set_main_loop(on_emultor_loop, 0, true); 60 | #endif 61 | 62 | // Cleanup the SDL resources then exit 63 | SDL_Quit(); 64 | } 65 | 66 | void set_game_data_size(size_t size) { 67 | g_game_data.resize(size); 68 | std::fill(g_game_data.begin(), g_game_data.end(), 0); 69 | } 70 | 71 | void set_game_data_index(size_t index, uint8_t data) { 72 | g_game_data[index] = data; 73 | } 74 | 75 | void set_game_data_from_file(string file_name) { 76 | ifstream reader(file_name.c_str(), ios::in|ios::binary); 77 | if(reader.fail()) { 78 | fprintf(stderr, "Error while loading rom '%s': %s\n", file_name.c_str(), strerror(errno)); 79 | exit(1); 80 | } 81 | 82 | reader.seekg(0, ios::end); 83 | size_t length = reader.tellg(); 84 | reader.seekg(0, ios::beg); 85 | assert(length > 0); 86 | g_game_data.resize(length); 87 | reader.read(reinterpret_cast(g_game_data.data()), g_game_data.size()); 88 | reader.close(); 89 | g_game_file_name = file_name; 90 | } 91 | 92 | #ifdef WEB 93 | 94 | EMSCRIPTEN_BINDINGS(Wrappers) { 95 | emscripten::function("set_game_data_size", &set_game_data_size); 96 | emscripten::function("set_game_data_index", &set_game_data_index); 97 | emscripten::function("on_emultor_start", &on_emultor_start); 98 | emscripten::function("toggle_sound", &toggle_sound); 99 | emscripten::function("set_is_windows", &set_is_windows); 100 | }; 101 | 102 | #endif 103 | 104 | int main(int argc, char* argv[]) { 105 | printf("%s\n", "SaltyNES is a NES emulator in WebAssembly"); 106 | printf("%s\n", "SaltyNES (C) 2012-2017 Matthew Brennan Jones "); 107 | printf("%s\n", "vNES 2.14 (C) 2006-2011 Jamie Sanders thatsanderskid.com"); 108 | printf("%s\n", "This program is licensed under GPLV3 or later"); 109 | printf("%s\n", "https://github.com/workhorsy/SaltyNES"); 110 | printf("%s\n", get_build_date().c_str()); 111 | 112 | // Make sure there is a rom file name 113 | #ifdef DESKTOP 114 | if (argc < 2) { 115 | fprintf(stderr, "No rom file argument provided. Exiting ...\n"); 116 | return -1; 117 | } 118 | set_game_data_from_file(argv[1]); 119 | #endif 120 | #ifdef WEB 121 | g_game_file_name = "rom_from_browser.nes"; 122 | #endif 123 | 124 | // Initialize SDL 125 | if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK) != 0) { 126 | fprintf(stderr, "Could not initialize SDL: %s\n", SDL_GetError()); 127 | return -1; 128 | } 129 | 130 | // Create a SDL window 131 | Globals::g_window = SDL_CreateWindow( 132 | "SaltyNES", 133 | 0, 0, 256, 240, 134 | 0 135 | ); 136 | if (! Globals::g_window) { 137 | fprintf(stderr, "Couldn't create a window: %s\n", SDL_GetError()); 138 | return -1; 139 | } 140 | 141 | // Create a SDL renderer 142 | Globals::g_renderer = SDL_CreateRenderer( 143 | Globals::g_window, 144 | -1, 145 | SDL_RENDERER_ACCELERATED 146 | ); 147 | if (! Globals::g_renderer) { 148 | fprintf(stderr, "Couldn't create a renderer: %s\n", SDL_GetError()); 149 | return -1; 150 | } 151 | 152 | // Create the SDL screen 153 | Globals::g_screen = SDL_CreateTexture(Globals::g_renderer, 154 | SDL_PIXELFORMAT_BGR888, SDL_TEXTUREACCESS_STATIC, 256, 240); 155 | if (! Globals::g_screen) { 156 | fprintf(stderr, "Couldn't create a teture: %s\n", SDL_GetError()); 157 | return -1; 158 | } 159 | 160 | #ifdef DESKTOP 161 | on_emultor_start(); 162 | #endif 163 | start_main_loop(); 164 | return 0; 165 | } 166 | -------------------------------------------------------------------------------- /src/InputHandler.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | 10 | #include "SaltyNES.h" 11 | 12 | const float InputHandler::AXES_DEAD_ZONE = 0.2; 13 | const string InputHandler::KEYS[] = { "up", "down", "right", "left", "start", "select", "a", "b" }; 14 | const size_t InputHandler::KEYS_LENGTH = 8; 15 | bool InputHandler::_is_configuring_gamepad = false; 16 | string InputHandler::_configuring_gamepad_button = ""; 17 | 18 | InputHandler::InputHandler(int id) : 19 | _id(id), 20 | _keys(255), 21 | _map(InputHandler::NUM_KEYS), enable_shared_from_this() { 22 | 23 | _is_gamepad_connected = false; 24 | _is_gamepad_used = false; 25 | _is_keyboard_used = false; 26 | 27 | _is_input_pressed["up"] = false; 28 | _is_input_pressed["down"] = false; 29 | _is_input_pressed["right"] = false; 30 | _is_input_pressed["left"] = false; 31 | _is_input_pressed["start"] = false; 32 | _is_input_pressed["select"] = false; 33 | _is_input_pressed["a"] = false; 34 | _is_input_pressed["b"] = false; 35 | } 36 | 37 | InputHandler::~InputHandler() { 38 | 39 | } 40 | 41 | uint16_t InputHandler::getKeyState(int padKey) { 42 | return static_cast(_keys[_map[padKey]] ? 0x41 : 0x40); 43 | } 44 | 45 | void InputHandler::mapKey(int padKey, int kbKeycode) { 46 | _map[padKey] = kbKeycode; 47 | } 48 | 49 | void InputHandler::poll_for_key_events() { 50 | // Check for keyboard input 51 | int numberOfKeys; 52 | const uint8_t* keystate = SDL_GetKeyboardState(&numberOfKeys); 53 | 54 | _keys[_map[InputHandler::KEY_UP]] = keystate[SDL_SCANCODE_W]; 55 | _keys[_map[InputHandler::KEY_DOWN]] = keystate[SDL_SCANCODE_S]; 56 | _keys[_map[InputHandler::KEY_RIGHT]] = keystate[SDL_SCANCODE_D]; 57 | _keys[_map[InputHandler::KEY_LEFT]] = keystate[SDL_SCANCODE_A]; 58 | _keys[_map[InputHandler::KEY_START]] = keystate[SDL_SCANCODE_RETURN]; 59 | _keys[_map[InputHandler::KEY_SELECT]] = keystate[SDL_SCANCODE_RSHIFT]; 60 | _keys[_map[InputHandler::KEY_B]] = keystate[SDL_SCANCODE_J]; 61 | _keys[_map[InputHandler::KEY_A]] = keystate[SDL_SCANCODE_K]; 62 | 63 | bool is_using_keyboard = ( 64 | keystate[SDL_SCANCODE_W] | 65 | keystate[SDL_SCANCODE_S] | 66 | keystate[SDL_SCANCODE_D] | 67 | keystate[SDL_SCANCODE_A] | 68 | keystate[SDL_SCANCODE_RETURN] | 69 | keystate[SDL_SCANCODE_RSHIFT] | 70 | keystate[SDL_SCANCODE_J] | 71 | keystate[SDL_SCANCODE_K]) != 0; 72 | 73 | // Check for gamepad input 74 | if (! is_using_keyboard) { 75 | for (auto const& pair : Globals::joysticks) { 76 | int id = pair.first; 77 | SDL_Joystick* joy = pair.second; 78 | //printf("????????????? joy id: %d\n", id); 79 | 80 | if (joy != nullptr && SDL_JoystickGetAttached(joy)) { 81 | //printf("????????????? joy attached i: %d\n", SDL_JoystickGetAttached(joy)); 82 | 83 | if (Globals::is_windows) { 84 | _keys[_map[InputHandler::KEY_START]] = SDL_JoystickGetButton(joy, 9); 85 | _keys[_map[InputHandler::KEY_SELECT]] = SDL_JoystickGetButton(joy, 8); 86 | _keys[_map[InputHandler::KEY_B]] = SDL_JoystickGetButton(joy, 0); 87 | _keys[_map[InputHandler::KEY_A]] = SDL_JoystickGetButton(joy, 1); 88 | _keys[_map[InputHandler::KEY_UP]] = SDL_JoystickGetButton(joy, 12); 89 | _keys[_map[InputHandler::KEY_DOWN]] = SDL_JoystickGetButton(joy, 13); 90 | _keys[_map[InputHandler::KEY_RIGHT]] = SDL_JoystickGetButton(joy, 15); 91 | _keys[_map[InputHandler::KEY_LEFT]] = SDL_JoystickGetButton(joy, 14); 92 | } else { 93 | _keys[_map[InputHandler::KEY_START]] = SDL_JoystickGetButton(joy, 7); 94 | _keys[_map[InputHandler::KEY_SELECT]] = SDL_JoystickGetButton(joy, 6); 95 | _keys[_map[InputHandler::KEY_B]] = SDL_JoystickGetButton(joy, 0); 96 | _keys[_map[InputHandler::KEY_A]] = SDL_JoystickGetButton(joy, 1); 97 | _keys[_map[InputHandler::KEY_UP]] = SDL_JoystickGetButton(joy, 13); 98 | _keys[_map[InputHandler::KEY_DOWN]] = SDL_JoystickGetButton(joy, 14); 99 | _keys[_map[InputHandler::KEY_RIGHT]] = SDL_JoystickGetButton(joy, 12); 100 | _keys[_map[InputHandler::KEY_LEFT]] = SDL_JoystickGetButton(joy, 11); 101 | } 102 | } 103 | } 104 | } 105 | 106 | // Can't hold both left & right or up & down at same time: 107 | if(_keys[_map[InputHandler::KEY_LEFT]]) { 108 | _keys[_map[InputHandler::KEY_RIGHT]] = false; 109 | } else if(_keys[_map[InputHandler::KEY_RIGHT]]) { 110 | _keys[_map[InputHandler::KEY_LEFT]] = false; 111 | } 112 | 113 | if(_keys[_map[InputHandler::KEY_UP]]) { 114 | _keys[_map[InputHandler::KEY_DOWN]] = false; 115 | } else if(_keys[_map[InputHandler::KEY_DOWN]]) { 116 | _keys[_map[InputHandler::KEY_UP]] = false; 117 | } 118 | } 119 | 120 | void InputHandler::reset() { 121 | size_t size = _keys.size(); 122 | _keys.clear(); 123 | _keys.resize(size); 124 | } 125 | -------------------------------------------------------------------------------- /src/Mapper009.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | Mapper009::Mapper009() : MapperDefault() { 12 | 13 | } 14 | 15 | shared_ptr Mapper009::Init(shared_ptr nes) { 16 | latchLo = 0; 17 | latchHi = 0; 18 | latchLoVal1 = 0; 19 | latchLoVal2 = 0; 20 | latchHiVal1 = 0; 21 | latchHiVal2 = 0; 22 | 23 | reset(); 24 | 25 | this->base_init(nes); 26 | return shared_from_this(); 27 | } 28 | 29 | void Mapper009::write(int address, uint16_t value) { 30 | if(address < 0x8000) { 31 | 32 | // Handle normally. 33 | this->base_write(address, value); 34 | 35 | } else { 36 | 37 | // MMC2 write. 38 | 39 | value &= 0xFF; 40 | address &= 0xF000; 41 | switch (address >> 12) { 42 | case 0xA: { 43 | 44 | // Select 8k ROM bank at 0x8000 45 | load8kRomBank(value, 0x8000); 46 | return; 47 | 48 | } 49 | case 0xB: { 50 | 51 | // Select 4k VROM bank at 0x0000, $FD mode 52 | latchLoVal1 = value; 53 | if(latchLo == 0xFD) { 54 | loadVromBank(value, 0x0000); 55 | } 56 | return; 57 | 58 | } 59 | case 0xC: { 60 | 61 | // Select 4k VROM bank at 0x0000, $FE mode 62 | latchLoVal2 = value; 63 | if(latchLo == 0xFE) { 64 | loadVromBank(value, 0x0000); 65 | } 66 | return; 67 | 68 | } 69 | case 0xD: { 70 | 71 | // Select 4k VROM bank at 0x1000, $FD mode 72 | latchHiVal1 = value; 73 | if(latchHi == 0xFD) { 74 | loadVromBank(value, 0x1000); 75 | } 76 | return; 77 | 78 | } 79 | case 0xE: { 80 | 81 | // Select 4k VROM bank at 0x1000, $FE mode 82 | latchHiVal2 = value; 83 | if(latchHi == 0xFE) { 84 | loadVromBank(value, 0x1000); 85 | } 86 | return; 87 | 88 | } 89 | case 0xF: { 90 | 91 | // Select mirroring 92 | if((value & 0x1) == 0) { 93 | 94 | // Vertical mirroring 95 | nes->getPpu()->setMirroring(ROM::VERTICAL_MIRRORING); 96 | 97 | } else { 98 | 99 | // Horizontal mirroring 100 | nes->getPpu()->setMirroring(ROM::HORIZONTAL_MIRRORING); 101 | 102 | } 103 | return; 104 | } 105 | } 106 | } 107 | } 108 | 109 | void Mapper009::loadROM(shared_ptr rom) { 110 | //System.out.println("Loading ROM."); 111 | 112 | if(!rom->isValid()) { 113 | //System.out.println("MMC2: Invalid ROM! Unable to load."); 114 | return; 115 | } 116 | 117 | // Get number of 8K banks: 118 | int num_8k_banks = rom->getRomBankCount() * 2; 119 | 120 | // Load PRG-ROM: 121 | load8kRomBank(0, 0x8000); 122 | load8kRomBank(num_8k_banks - 3, 0xA000); 123 | load8kRomBank(num_8k_banks - 2, 0xC000); 124 | load8kRomBank(num_8k_banks - 1, 0xE000); 125 | 126 | // Load CHR-ROM: 127 | loadCHRROM(); 128 | 129 | // Load Battery RAM (if present): 130 | loadBatteryRam(); 131 | 132 | // Do Reset-Interrupt: 133 | nes->getCpu()->requestIrq(CPU::IRQ_RESET); 134 | } 135 | 136 | void Mapper009::latchAccess(int address) { 137 | if((address & 0x1FF0) == 0x0FD0 && latchLo != 0xFD) { 138 | // Set $FD mode 139 | loadVromBank(latchLoVal1, 0x0000); 140 | latchLo = 0xFD; 141 | //System.out.println("LO FD"); 142 | } else if((address & 0x1FF0) == 0x0FE0 && latchLo != 0xFE) { 143 | // Set $FE mode 144 | loadVromBank(latchLoVal2, 0x0000); 145 | latchLo = 0xFE; 146 | //System.out.println("LO FE"); 147 | } else if((address & 0x1FF0) == 0x1FD0 && latchHi != 0xFD) { 148 | // Set $FD mode 149 | loadVromBank(latchHiVal1, 0x1000); 150 | latchHi = 0xFD; 151 | //System.out.println("HI FD"); 152 | } else if((address & 0x1FF0) == 0x1FE0 && latchHi != 0xFE) { 153 | // Set $FE mode 154 | loadVromBank(latchHiVal2, 0x1000); 155 | latchHi = 0xFE; 156 | //System.out.println("HI FE"); 157 | } 158 | } 159 | 160 | void Mapper009::mapperInternalStateLoad(ByteBuffer* buf) { 161 | this->base_mapperInternalStateLoad(buf); 162 | 163 | // Check version: 164 | if(buf->readByte() == 1) { 165 | 166 | latchLo = buf->readByte(); 167 | latchHi = buf->readByte(); 168 | latchLoVal1 = buf->readByte(); 169 | latchLoVal2 = buf->readByte(); 170 | latchHiVal1 = buf->readByte(); 171 | latchHiVal2 = buf->readByte(); 172 | 173 | } 174 | } 175 | 176 | void Mapper009::mapperInternalStateSave(ByteBuffer* buf) { 177 | this->base_mapperInternalStateSave(buf); 178 | 179 | // Version: 180 | buf->putByte(static_cast(1)); 181 | 182 | // State: 183 | buf->putByte(static_cast(latchLo)); 184 | buf->putByte(static_cast(latchHi)); 185 | buf->putByte(static_cast(latchLoVal1)); 186 | buf->putByte(static_cast(latchLoVal2)); 187 | buf->putByte(static_cast(latchHiVal1)); 188 | buf->putByte(static_cast(latchHiVal2)); 189 | } 190 | 191 | void Mapper009::reset() { 192 | // Set latch to $FE mode: 193 | latchLo = 0xFE; 194 | latchHi = 0xFE; 195 | latchLoVal1 = 0; 196 | latchLoVal2 = 4; 197 | latchHiVal1 = 0; 198 | latchHiVal2 = 0; 199 | } 200 | -------------------------------------------------------------------------------- /src/ChannelDM.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | ChannelDM::ChannelDM() { 12 | } 13 | 14 | ChannelDM::ChannelDM(shared_ptr papu) { 15 | this->papu = papu; 16 | 17 | this->_isEnabled = false; 18 | this->hasSample = false; 19 | this->irqGenerated = false; 20 | this->playMode = 0; 21 | this->dmaFrequency = 0; 22 | this->dmaCounter = 0; 23 | this->deltaCounter = 0; 24 | this->playStartAddress = 0; 25 | this->playAddress = 0; 26 | this->playLength = 0; 27 | this->playLengthCounter = 0; 28 | this->shiftCounter = 0; 29 | this->reg4012 = 0; 30 | this->reg4013 = 0; 31 | this->status = 0; 32 | this->sample = 0; 33 | this->dacLsb = 0; 34 | this->data = 0; 35 | } 36 | 37 | ChannelDM::~ChannelDM() { 38 | papu = nullptr; 39 | } 40 | 41 | void ChannelDM::clockDmc() { 42 | 43 | // Only alter DAC value if the sample buffer has data: 44 | if(hasSample) { 45 | 46 | if((data & 1) == 0) { 47 | 48 | // Decrement delta: 49 | if(deltaCounter > 0) { 50 | deltaCounter--; 51 | } 52 | 53 | } else { 54 | 55 | // Increment delta: 56 | if(deltaCounter < 63) { 57 | ++deltaCounter; 58 | } 59 | 60 | } 61 | 62 | // Update sample value: 63 | sample = _isEnabled ? (deltaCounter << 1) + dacLsb : 0; 64 | 65 | // Update shift register: 66 | data >>= 1; 67 | 68 | } 69 | 70 | dmaCounter--; 71 | if(dmaCounter <= 0) { 72 | 73 | // No more sample bits. 74 | hasSample = false; 75 | endOfSample(); 76 | dmaCounter = 8; 77 | 78 | } 79 | 80 | if(irqGenerated) { 81 | papu->nes->cpu->requestIrq(CPU::IRQ_NORMAL); 82 | } 83 | 84 | } 85 | 86 | void ChannelDM::endOfSample() { 87 | if(playLengthCounter == 0 && playMode == MODE_LOOP) { 88 | 89 | // Start from beginning of sample: 90 | playAddress = playStartAddress; 91 | playLengthCounter = playLength; 92 | 93 | } 94 | 95 | if(playLengthCounter > 0) { 96 | 97 | // Fetch next sample: 98 | nextSample(); 99 | 100 | if(playLengthCounter == 0) { 101 | 102 | // Last byte of sample fetched, generate IRQ: 103 | if(playMode == MODE_IRQ) { 104 | 105 | // Generate IRQ: 106 | irqGenerated = true; 107 | 108 | } 109 | 110 | } 111 | 112 | } 113 | 114 | } 115 | 116 | void ChannelDM::nextSample() { 117 | // Fetch byte: 118 | data = papu->getNes()->getMemoryMapper()->load(playAddress); 119 | papu->getNes()->cpu->haltCycles(4); 120 | 121 | --playLengthCounter; 122 | ++playAddress; 123 | if(playAddress > 0xFFFF) { 124 | playAddress = 0x8000; 125 | } 126 | 127 | hasSample = true; 128 | } 129 | 130 | void ChannelDM::writeReg(int address, int value) { 131 | if(address == 0x4010) { 132 | // Play mode, DMA Frequency 133 | if((value >> 6) == 0) { 134 | playMode = MODE_NORMAL; 135 | } else if(((value >> 6) & 1) == 1) { 136 | playMode = MODE_LOOP; 137 | } else if((value >> 6) == 2) { 138 | playMode = MODE_IRQ; 139 | } 140 | 141 | if((value & 0x80) == 0) { 142 | irqGenerated = false; 143 | } 144 | 145 | dmaFrequency = papu->getDmcFrequency(value & 0xF); 146 | 147 | } else if(address == 0x4011) { 148 | // Delta counter load register: 149 | deltaCounter = (value >> 1) & 63; 150 | dacLsb = value & 1; 151 | if(papu->userEnableDmc) { 152 | sample = ((deltaCounter << 1) + dacLsb); // update sample value 153 | } 154 | 155 | } else if(address == 0x4012) { 156 | // DMA address load register 157 | playStartAddress = (value << 6) | 0x0C000; 158 | playAddress = playStartAddress; 159 | reg4012 = value; 160 | 161 | } else if(address == 0x4013) { 162 | // Length of play code 163 | playLength = (value << 4) + 1; 164 | playLengthCounter = playLength; 165 | reg4013 = value; 166 | 167 | } else if(address == 0x4015) { 168 | // DMC/IRQ Status 169 | if(((value >> 4) & 1) == 0) { 170 | // Disable: 171 | playLengthCounter = 0; 172 | } else { 173 | // Restart: 174 | playAddress = playStartAddress; 175 | playLengthCounter = playLength; 176 | } 177 | irqGenerated = false; 178 | } 179 | } 180 | 181 | void ChannelDM::setEnabled(bool value) { 182 | if((!_isEnabled) && value) { 183 | playLengthCounter = playLength; 184 | } 185 | _isEnabled = value; 186 | 187 | } 188 | 189 | bool ChannelDM::isEnabled() { 190 | return _isEnabled; 191 | } 192 | 193 | int ChannelDM::getLengthStatus() { 194 | return ((playLengthCounter == 0 || !_isEnabled) ? 0 : 1); 195 | } 196 | 197 | int ChannelDM::getIrqStatus() { 198 | return (irqGenerated ? 1 : 0); 199 | } 200 | 201 | void ChannelDM::reset() { 202 | _isEnabled = false; 203 | irqGenerated = false; 204 | playMode = MODE_NORMAL; 205 | dmaFrequency = 0; 206 | dmaCounter = 0; 207 | deltaCounter = 0; 208 | playStartAddress = 0; 209 | playAddress = 0; 210 | playLength = 0; 211 | playLengthCounter = 0; 212 | status = 0; 213 | sample = 0; 214 | dacLsb = 0; 215 | shiftCounter = 0; 216 | reg4012 = 0; 217 | reg4013 = 0; 218 | data = 0; 219 | } 220 | -------------------------------------------------------------------------------- /src/ChannelSquare.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | const int ChannelSquare::dutyLookup[32] = { 12 | 0, 1, 0, 0, 0, 0, 0, 0, 13 | 0, 1, 1, 0, 0, 0, 0, 0, 14 | 0, 1, 1, 1, 1, 0, 0, 0, 15 | 1, 0, 0, 1, 1, 1, 1, 1}; 16 | 17 | const int ChannelSquare::impLookup[32] = { 18 | 1, -1, 0, 0, 0, 0, 0, 0, 19 | 1, 0, -1, 0, 0, 0, 0, 0, 20 | 1, 0, 0, 0, -1, 0, 0, 0, 21 | -1, 0, 1, 0, 0, 0, 0, 0}; 22 | 23 | ChannelSquare::ChannelSquare() { 24 | } 25 | 26 | ChannelSquare::ChannelSquare(shared_ptr papu, bool square1) { 27 | this->papu = papu; 28 | sqr1 = square1; 29 | _isEnabled = false; 30 | lengthCounterEnable = false; 31 | sweepActive = false; 32 | envDecayDisable = false; 33 | envDecayLoopEnable = false; 34 | envReset = false; 35 | sweepCarry = false; 36 | updateSweepPeriod = false; 37 | progTimerCount = 0; 38 | progTimerMax = 0; 39 | lengthCounter = 0; 40 | squareCounter = 0; 41 | sweepCounter = 0; 42 | sweepCounterMax = 0; 43 | sweepMode = 0; 44 | sweepShiftAmount = 0; 45 | envDecayRate = 0; 46 | envDecayCounter = 0; 47 | envVolume = 0; 48 | masterVolume = 0; 49 | dutyMode = 0; 50 | sweepResult = 0; 51 | sampleValue = 0; 52 | vol = 0; 53 | } 54 | 55 | ChannelSquare::~ChannelSquare() { 56 | papu = nullptr; 57 | } 58 | 59 | void ChannelSquare::clockLengthCounter() { 60 | if(lengthCounterEnable && lengthCounter > 0) { 61 | --lengthCounter; 62 | if(lengthCounter == 0) { 63 | updateSampleValue(); 64 | } 65 | } 66 | 67 | } 68 | 69 | void ChannelSquare::clockEnvDecay() { 70 | if(envReset) { 71 | // Reset envelope: 72 | envReset = false; 73 | envDecayCounter = envDecayRate + 1; 74 | envVolume = 0xF; 75 | } else if((--envDecayCounter) <= 0) { 76 | // Normal handling: 77 | envDecayCounter = envDecayRate + 1; 78 | if(envVolume > 0) { 79 | --envVolume; 80 | } else { 81 | envVolume = envDecayLoopEnable ? 0xF : 0; 82 | } 83 | } 84 | 85 | masterVolume = envDecayDisable ? envDecayRate : envVolume; 86 | updateSampleValue(); 87 | } 88 | 89 | void ChannelSquare::clockSweep() { 90 | if(--sweepCounter <= 0) { 91 | 92 | sweepCounter = sweepCounterMax + 1; 93 | if(sweepActive && sweepShiftAmount > 0 && progTimerMax > 7) { 94 | 95 | // Calculate result from shifter: 96 | sweepCarry = false; 97 | if(sweepMode == 0) { 98 | progTimerMax += (progTimerMax >> sweepShiftAmount); 99 | if(progTimerMax > 4095) { 100 | progTimerMax = 4095; 101 | sweepCarry = true; 102 | } 103 | } else { 104 | progTimerMax = progTimerMax - ((progTimerMax >> sweepShiftAmount) - (sqr1 ? 1 : 0)); 105 | } 106 | 107 | } 108 | 109 | } 110 | 111 | if(updateSweepPeriod) { 112 | updateSweepPeriod = false; 113 | sweepCounter = sweepCounterMax + 1; 114 | } 115 | 116 | } 117 | 118 | void ChannelSquare::updateSampleValue() { 119 | 120 | if(_isEnabled && lengthCounter > 0 && progTimerMax > 7) { 121 | 122 | if(sweepMode == 0 && (progTimerMax + (progTimerMax >> sweepShiftAmount)) > 4095) { 123 | //if(sweepCarry) { 124 | 125 | sampleValue = 0; 126 | 127 | } else { 128 | 129 | sampleValue = masterVolume * dutyLookup[(dutyMode << 3) + squareCounter]; 130 | 131 | } 132 | 133 | } else { 134 | 135 | sampleValue = 0; 136 | 137 | } 138 | 139 | } 140 | 141 | void ChannelSquare::writeReg(int address, int value) { 142 | 143 | int addrAdd = (sqr1 ? 0 : 4); 144 | if(address == 0x4000 + addrAdd) { 145 | 146 | // Volume/Envelope decay: 147 | envDecayDisable = ((value & 0x10) != 0); 148 | envDecayRate = value & 0xF; 149 | envDecayLoopEnable = ((value & 0x20) != 0); 150 | dutyMode = (value >> 6) & 0x3; 151 | lengthCounterEnable = ((value & 0x20) == 0); 152 | masterVolume = envDecayDisable ? envDecayRate : envVolume; 153 | updateSampleValue(); 154 | 155 | } else if(address == 0x4001 + addrAdd) { 156 | 157 | // Sweep: 158 | sweepActive = ((value & 0x80) != 0); 159 | sweepCounterMax = ((value >> 4) & 7); 160 | sweepMode = (value >> 3) & 1; 161 | sweepShiftAmount = value & 7; 162 | updateSweepPeriod = true; 163 | 164 | } else if(address == 0x4002 + addrAdd) { 165 | 166 | // Programmable timer: 167 | progTimerMax &= 0x700; 168 | progTimerMax |= value; 169 | 170 | } else if(address == 0x4003 + addrAdd) { 171 | 172 | // Programmable timer, length counter 173 | progTimerMax &= 0xFF; 174 | progTimerMax |= ((value & 0x7) << 8); 175 | 176 | if(_isEnabled) { 177 | lengthCounter = papu->getLengthMax(value & 0xF8); 178 | } 179 | 180 | envReset = true; 181 | 182 | } 183 | 184 | } 185 | 186 | void ChannelSquare::setEnabled(bool value) { 187 | _isEnabled = value; 188 | if(!value) { 189 | lengthCounter = 0; 190 | } 191 | updateSampleValue(); 192 | } 193 | 194 | bool ChannelSquare::isEnabled() { 195 | return _isEnabled; 196 | } 197 | 198 | int ChannelSquare::getLengthStatus() { 199 | return ((lengthCounter == 0 || !_isEnabled) ? 0 : 1); 200 | } 201 | 202 | void ChannelSquare::reset() { 203 | progTimerCount = 0; 204 | progTimerMax = 0; 205 | lengthCounter = 0; 206 | squareCounter = 0; 207 | sweepCounter = 0; 208 | sweepCounterMax = 0; 209 | sweepMode = 0; 210 | sweepShiftAmount = 0; 211 | envDecayRate = 0; 212 | envDecayCounter = 0; 213 | envVolume = 0; 214 | masterVolume = 0; 215 | dutyMode = 0; 216 | vol = 0; 217 | 218 | _isEnabled = false; 219 | lengthCounterEnable = false; 220 | sweepActive = false; 221 | sweepCarry = false; 222 | envDecayDisable = false; 223 | envDecayLoopEnable = false; 224 | } 225 | -------------------------------------------------------------------------------- /src/SaltyNES.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | 10 | #include "SaltyNES.h" 11 | 12 | SaltyNES::SaltyNES() { 13 | samplerate = 0; 14 | progress = 0; 15 | nes = nullptr; 16 | } 17 | 18 | SaltyNES::~SaltyNES() { 19 | stop(); 20 | 21 | _rom_name.clear(); 22 | } 23 | 24 | void SaltyNES::init() { 25 | initKeyCodes(); 26 | readParams(); 27 | 28 | Globals::memoryFlushValue = 0x00; // make SMB1 hacked version work. 29 | 30 | auto joy1 = make_shared(0); 31 | auto joy2 = make_shared(1); 32 | nes = make_shared()->Init(joy1, joy2); 33 | nes->enableSound(true); 34 | nes->reset(); 35 | } 36 | 37 | void SaltyNES::load_rom(string rom_name, vector* rom_data, array* save_ram) { 38 | _rom_name = rom_name; 39 | nes->load_rom_from_data(rom_name, rom_data, save_ram); 40 | } 41 | 42 | void SaltyNES::run() { 43 | // Can start painting: 44 | 45 | if(nes->rom->isValid()) { 46 | // Start emulation: 47 | //System.out.println("vNES is now starting the processor."); 48 | nes->getCpu()->start(); 49 | } else { 50 | // ROM file was invalid. 51 | printf("vNES was unable to find (%s).\n", _rom_name.c_str()); 52 | } 53 | } 54 | 55 | void SaltyNES::stop() { 56 | if (nes) { 57 | nes->stopEmulation(); 58 | //System.out.println("vNES has stopped the processor."); 59 | nes->getPapu()->stop(); 60 | } 61 | } 62 | 63 | void SaltyNES::readParams() { 64 | /* Controller Setup for Player 1 */ 65 | Globals::controls["p1_up"] = Parameters::p1_up; 66 | Globals::controls["p1_down"] = Parameters::p1_down; 67 | Globals::controls["p1_left"] = Parameters::p1_left; 68 | Globals::controls["p1_right"] = Parameters::p1_right; 69 | Globals::controls["p1_a"] = Parameters::p1_a; 70 | Globals::controls["p1_b"] = Parameters::p1_b; 71 | Globals::controls["p1_select"] = Parameters::p1_select; 72 | Globals::controls["p1_start"] = Parameters::p1_start; 73 | 74 | /* Controller Setup for Player 2 */ 75 | Globals::controls["p2_up"] = Parameters::p2_up; 76 | Globals::controls["p2_down"] = Parameters::p2_down; 77 | Globals::controls["p2_left"] = Parameters::p2_left; 78 | Globals::controls["p2_right"] = Parameters::p2_right; 79 | Globals::controls["p2_a"] = Parameters::p2_a; 80 | Globals::controls["p2_b"] = Parameters::p2_b; 81 | Globals::controls["p2_select"] = Parameters::p2_select; 82 | Globals::controls["p2_start"] = Parameters::p2_start; 83 | } 84 | 85 | void SaltyNES::initKeyCodes() { 86 | Globals::keycodes["VK_SPACE"] = 32; 87 | Globals::keycodes["VK_PAGE_UP"] = 33; 88 | Globals::keycodes["VK_PAGE_DOWN"] = 34; 89 | Globals::keycodes["VK_END"] = 35; 90 | Globals::keycodes["VK_HOME"] = 36; 91 | Globals::keycodes["VK_DELETE"] = 127; 92 | Globals::keycodes["VK_INSERT"] = 155; 93 | Globals::keycodes["VK_LEFT"] = 37; 94 | Globals::keycodes["VK_UP"] = 38; 95 | Globals::keycodes["VK_RIGHT"] = 39; 96 | Globals::keycodes["VK_DOWN"] = 40; 97 | Globals::keycodes["VK_0"] = 48; 98 | Globals::keycodes["VK_1"] = 49; 99 | Globals::keycodes["VK_2"] = 50; 100 | Globals::keycodes["VK_3"] = 51; 101 | Globals::keycodes["VK_4"] = 52; 102 | Globals::keycodes["VK_5"] = 53; 103 | Globals::keycodes["VK_6"] = 54; 104 | Globals::keycodes["VK_7"] = 55; 105 | Globals::keycodes["VK_8"] = 56; 106 | Globals::keycodes["VK_9"] = 57; 107 | Globals::keycodes["VK_A"] = 65; 108 | Globals::keycodes["VK_B"] = 66; 109 | Globals::keycodes["VK_C"] = 67; 110 | Globals::keycodes["VK_D"] = 68; 111 | Globals::keycodes["VK_E"] = 69; 112 | Globals::keycodes["VK_F"] = 70; 113 | Globals::keycodes["VK_G"] = 71; 114 | Globals::keycodes["VK_H"] = 72; 115 | Globals::keycodes["VK_I"] = 73; 116 | Globals::keycodes["VK_J"] = 74; 117 | Globals::keycodes["VK_K"] = 75; 118 | Globals::keycodes["VK_L"] = 76; 119 | Globals::keycodes["VK_M"] = 77; 120 | Globals::keycodes["VK_N"] = 78; 121 | Globals::keycodes["VK_O"] = 79; 122 | Globals::keycodes["VK_P"] = 80; 123 | Globals::keycodes["VK_Q"] = 81; 124 | Globals::keycodes["VK_R"] = 82; 125 | Globals::keycodes["VK_S"] = 83; 126 | Globals::keycodes["VK_T"] = 84; 127 | Globals::keycodes["VK_U"] = 85; 128 | Globals::keycodes["VK_V"] = 86; 129 | Globals::keycodes["VK_W"] = 87; 130 | Globals::keycodes["VK_X"] = 88; 131 | Globals::keycodes["VK_Y"] = 89; 132 | Globals::keycodes["VK_Z"] = 90; 133 | Globals::keycodes["VK_NUMPAD0"] = 96; 134 | Globals::keycodes["VK_NUMPAD1"] = 97; 135 | Globals::keycodes["VK_NUMPAD2"] = 98; 136 | Globals::keycodes["VK_NUMPAD3"] = 99; 137 | Globals::keycodes["VK_NUMPAD4"] = 100; 138 | Globals::keycodes["VK_NUMPAD5"] = 101; 139 | Globals::keycodes["VK_NUMPAD6"] = 102; 140 | Globals::keycodes["VK_NUMPAD7"] = 103; 141 | Globals::keycodes["VK_NUMPAD8"] = 104; 142 | Globals::keycodes["VK_NUMPAD9"] = 105; 143 | Globals::keycodes["VK_MULTIPLY"] = 106; 144 | Globals::keycodes["VK_ADD"] = 107; 145 | Globals::keycodes["VK_SUBTRACT"] = 109; 146 | Globals::keycodes["VK_DECIMAL"] = 110; 147 | Globals::keycodes["VK_DIVIDE"] = 111; 148 | Globals::keycodes["VK_BACK_SPACE"] = 8; 149 | Globals::keycodes["VK_TAB"] = 9; 150 | Globals::keycodes["VK_ENTER"] = 10; 151 | Globals::keycodes["VK_SHIFT"] = 16; 152 | Globals::keycodes["VK_CONTROL"] = 17; 153 | Globals::keycodes["VK_ALT"] = 18; 154 | Globals::keycodes["VK_PAUSE"] = 19; 155 | Globals::keycodes["VK_ESCAPE"] = 27; 156 | Globals::keycodes["VK_OPEN_BRACKET"] = 91; 157 | Globals::keycodes["VK_BACK_SLASH"] = 92; 158 | Globals::keycodes["VK_CLOSE_BRACKET"] = 93; 159 | Globals::keycodes["VK_SEMICOLON"] = 59; 160 | Globals::keycodes["VK_QUOTE"] = 222; 161 | Globals::keycodes["VK_COMMA"] = 44; 162 | Globals::keycodes["VK_MINUS"] = 45; 163 | Globals::keycodes["VK_PERIOD"] = 46; 164 | Globals::keycodes["VK_SLASH"] = 47; 165 | } 166 | -------------------------------------------------------------------------------- /src/Tile.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | 10 | #include "SaltyNES.h" 11 | 12 | 13 | Tile::Tile() { 14 | // Tile data: 15 | pix.fill(0); 16 | fbIndex = 0; 17 | tIndex = 0; 18 | x = 0; 19 | y = 0; 20 | w = 0; 21 | h = 0; 22 | incX = 0; 23 | incY = 0; 24 | palIndex = 0; 25 | tpri = 0; 26 | c = 0; 27 | initialized = false; 28 | opaque.fill(false); 29 | } 30 | 31 | void Tile::setBuffer(vector* scanline) { 32 | for(y = 0; y < 8; ++y) { 33 | setScanline(y, (*scanline)[y], (*scanline)[y + 8]); 34 | } 35 | } 36 | 37 | void Tile::setScanline(int sline, uint16_t b1, uint16_t b2) { 38 | initialized = true; 39 | tIndex = sline << 3; 40 | for(x = 0; x < 8; ++x) { 41 | pix[tIndex + x] = ((b1 >> (7 - x)) & 1) + (((b2 >> (7 - x)) & 1) << 1); 42 | if(pix[tIndex + x] == 0) { 43 | opaque[sline] = false; 44 | } 45 | } 46 | } 47 | 48 | void Tile::renderSimple(int dx, int dy, vector* fBuffer, int palAdd, int* palette) { 49 | tIndex = 0; 50 | fbIndex = (dy << 8) + dx; 51 | for(y = 8; y != 0; --y) { 52 | for(x = 8; x != 0; --x) { 53 | palIndex = pix[tIndex]; 54 | if(palIndex != 0) { 55 | (*fBuffer)[fbIndex] = palette[palIndex + palAdd]; 56 | } 57 | ++fbIndex; 58 | ++tIndex; 59 | } 60 | fbIndex -= 8; 61 | fbIndex += 256; 62 | } 63 | } 64 | 65 | void Tile::renderSmall(int dx, int dy, vector* buffer, int palAdd, int* palette) { 66 | tIndex = 0; 67 | fbIndex = (dy << 8) + dx; 68 | for(y = 0; y < 4; ++y) { 69 | for(x = 0; x < 4; ++x) { 70 | 71 | c = (palette[pix[tIndex] + palAdd] >> 2) & 0x003F3F3F; 72 | c += (palette[pix[tIndex + 1] + palAdd] >> 2) & 0x003F3F3F; 73 | c += (palette[pix[tIndex + 8] + palAdd] >> 2) & 0x003F3F3F; 74 | c += (palette[pix[tIndex + 9] + palAdd] >> 2) & 0x003F3F3F; 75 | (*buffer)[fbIndex] = c; 76 | ++fbIndex; 77 | tIndex += 2; 78 | } 79 | tIndex += 8; 80 | fbIndex += 252; 81 | } 82 | 83 | } 84 | 85 | void Tile::render(int srcx1, int srcy1, int srcx2, int srcy2, int dx, int dy, array* fBuffer, int palAdd, array* palette, bool flipHorizontal, bool flipVertical, int pri, array* priTable) { 86 | if(dx < -7 || dx >= 256 || dy < -7 || dy >= 240) { 87 | return; 88 | } 89 | 90 | w = srcx2 - srcx1; 91 | h = srcy2 - srcy1; 92 | 93 | if(dx < 0) { 94 | srcx1 -= dx; 95 | } 96 | if(dx + srcx2 >= 256) { 97 | srcx2 = 256 - dx; 98 | } 99 | 100 | if(dy < 0) { 101 | srcy1 -= dy; 102 | } 103 | if(dy + srcy2 >= 240) { 104 | srcy2 = 240 - dy; 105 | } 106 | 107 | if(!flipHorizontal && !flipVertical) { 108 | 109 | fbIndex = (dy << 8) + dx; 110 | tIndex = 0; 111 | for(y = 0; y < 8; ++y) { 112 | for(x = 0; x < 8; ++x) { 113 | if(x >= srcx1 && x < srcx2 && y >= srcy1 && y < srcy2) { 114 | palIndex = pix[tIndex]; 115 | tpri = (*priTable)[fbIndex]; 116 | if(palIndex != 0 && pri <= (tpri & 0xFF)) { 117 | (*fBuffer)[fbIndex] = (*palette)[palIndex + palAdd]; 118 | tpri = (tpri & 0xF00) | pri; 119 | (*priTable)[fbIndex] = tpri; 120 | } 121 | } 122 | ++fbIndex; 123 | ++tIndex; 124 | } 125 | fbIndex -= 8; 126 | fbIndex += 256; 127 | } 128 | 129 | } else if(flipHorizontal && !flipVertical) { 130 | 131 | fbIndex = (dy << 8) + dx; 132 | tIndex = 7; 133 | for(y = 0; y < 8; ++y) { 134 | for(x = 0; x < 8; ++x) { 135 | if(x >= srcx1 && x < srcx2 && y >= srcy1 && y < srcy2) { 136 | palIndex = pix[tIndex]; 137 | tpri = (*priTable)[fbIndex]; 138 | if(palIndex != 0 && pri <= (tpri & 0xFF)) { 139 | (*fBuffer)[fbIndex] = (*palette)[palIndex + palAdd]; 140 | tpri = (tpri & 0xF00) | pri; 141 | (*priTable)[fbIndex] = tpri; 142 | } 143 | } 144 | ++fbIndex; 145 | --tIndex; 146 | } 147 | fbIndex -= 8; 148 | fbIndex += 256; 149 | tIndex += 16; 150 | } 151 | 152 | } else if(flipVertical && !flipHorizontal) { 153 | 154 | fbIndex = (dy << 8) + dx; 155 | tIndex = 56; 156 | for(y = 0; y < 8; ++y) { 157 | for(x = 0; x < 8; ++x) { 158 | if(x >= srcx1 && x < srcx2 && y >= srcy1 && y < srcy2) { 159 | palIndex = pix[tIndex]; 160 | tpri = (*priTable)[fbIndex]; 161 | if(palIndex != 0 && pri <= (tpri & 0xFF)) { 162 | (*fBuffer)[fbIndex] = (*palette)[palIndex + palAdd]; 163 | tpri = (tpri & 0xF00) | pri; 164 | (*priTable)[fbIndex] = tpri; 165 | } 166 | } 167 | ++fbIndex; 168 | ++tIndex; 169 | } 170 | fbIndex -= 8; 171 | fbIndex += 256; 172 | tIndex -= 16; 173 | } 174 | 175 | } else { 176 | 177 | fbIndex = (dy << 8) + dx; 178 | tIndex = 63; 179 | for(y = 0; y < 8; ++y) { 180 | for(x = 0; x < 8; ++x) { 181 | if(x >= srcx1 && x < srcx2 && y >= srcy1 && y < srcy2) { 182 | palIndex = pix[tIndex]; 183 | tpri = (*priTable)[fbIndex]; 184 | if(palIndex != 0 && pri <= (tpri & 0xFF)) { 185 | (*fBuffer)[fbIndex] = (*palette)[palIndex + palAdd]; 186 | tpri = (tpri & 0xF00) | pri; 187 | (*priTable)[fbIndex] = tpri; 188 | } 189 | } 190 | ++fbIndex; 191 | --tIndex; 192 | } 193 | fbIndex -= 8; 194 | fbIndex += 256; 195 | } 196 | 197 | } 198 | } 199 | 200 | bool Tile::isTransparent(int x, int y) { 201 | return (pix[(y << 3) + x] == 0); 202 | } 203 | 204 | void Tile::dumpData(string file) { 205 | try { 206 | 207 | ofstream writer(file.c_str(), ios::out|ios::binary); 208 | string chunk; 209 | for(int y = 0; y < 8; ++y) { 210 | for(int x = 0; x < 8; ++x) { 211 | chunk = Misc::hex8(pix[(y << 3) + x]).substr(1); 212 | writer.write(chunk.c_str(), chunk.length()); 213 | } 214 | chunk = "\r\n"; 215 | writer.write(chunk.c_str(), chunk.length()); 216 | } 217 | 218 | writer.close(); 219 | //System.out.println("Tile data dumped to file "+file); 220 | 221 | } catch (exception& e) { 222 | //System.out.println("Unable to dump tile to file."); 223 | // e.printStackTrace(); 224 | } 225 | } 226 | 227 | void Tile::stateSave(ByteBuffer* buf) { 228 | buf->putBoolean(initialized); 229 | for(int i = 0; i < 8; ++i) { 230 | buf->putBoolean(opaque[i]); 231 | } 232 | for(int i = 0; i < 64; ++i) { 233 | buf->putByte(static_cast(pix[i])); 234 | } 235 | } 236 | 237 | void Tile::stateLoad(ByteBuffer* buf) { 238 | initialized = buf->readBoolean(); 239 | for(int i = 0; i < 8; ++i) { 240 | opaque[i] = buf->readBoolean(); 241 | } 242 | for(int i = 0; i < 64; ++i) { 243 | pix[i] = buf->readByte(); 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/Mapper004.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | 12 | Mapper004::Mapper004() : MapperDefault() { 13 | 14 | } 15 | 16 | shared_ptr Mapper004::Init(shared_ptr nes) { 17 | prgAddressChanged = false; 18 | this->base_init(nes); 19 | return shared_from_this(); 20 | } 21 | 22 | void Mapper004::mapperInternalStateLoad(ByteBuffer* buf) { 23 | this->base_mapperInternalStateLoad(buf); 24 | 25 | // Check version: 26 | if(buf->readByte() == 1) { 27 | command = buf->readInt(); 28 | prgAddressSelect = buf->readInt(); 29 | chrAddressSelect = buf->readInt(); 30 | pageNumber = buf->readInt(); 31 | irqCounter = buf->readInt(); 32 | irqLatchValue = buf->readInt(); 33 | irqEnable = buf->readInt(); 34 | prgAddressChanged = buf->readBoolean(); 35 | } 36 | } 37 | 38 | void Mapper004::mapperInternalStateSave(ByteBuffer* buf) { 39 | this->base_mapperInternalStateSave(buf); 40 | 41 | // Version: 42 | buf->putByte(static_cast(1)); 43 | 44 | // State: 45 | buf->putInt(command); 46 | buf->putInt(prgAddressSelect); 47 | buf->putInt(chrAddressSelect); 48 | buf->putInt(pageNumber); 49 | buf->putInt(irqCounter); 50 | buf->putInt(irqLatchValue); 51 | buf->putInt(irqEnable); 52 | buf->putBoolean(prgAddressChanged); 53 | 54 | } 55 | 56 | void Mapper004::write(int address, uint16_t value) { 57 | if(address < 0x8000) { 58 | // Normal memory write. 59 | this->base_write(address, value); 60 | return; 61 | } 62 | 63 | if(address == 0x8000) { 64 | // Command/Address Select register 65 | command = value & 7; 66 | int tmp = (value >> 6) & 1; 67 | if(tmp != prgAddressSelect) { 68 | prgAddressChanged = true; 69 | } 70 | prgAddressSelect = tmp; 71 | chrAddressSelect = (value >> 7) & 1; 72 | } else if(address == 0x8001) { 73 | // Page number for command 74 | executeCommand(command, value); 75 | } else if(address == 0xA000) { 76 | // Mirroring select 77 | if((value & 1) != 0) { 78 | nes->getPpu()->setMirroring(ROM::HORIZONTAL_MIRRORING); 79 | } else { 80 | nes->getPpu()->setMirroring(ROM::VERTICAL_MIRRORING); 81 | } 82 | } else if(address == 0xA001) { 83 | // SaveRAM Toggle 84 | nes->getRom()->setSaveState((value & 1) != 0); 85 | } else if(address == 0xC000) { 86 | // IRQ Counter register 87 | irqCounter = value; 88 | //nes.ppu.mapperIrqCounter = 0; 89 | } else if(address == 0xC001) { 90 | // IRQ Latch register 91 | irqLatchValue = value; 92 | } else if(address == 0xE000) { 93 | // IRQ Control Reg 0 (disable) 94 | //irqCounter = irqLatchValue; 95 | irqEnable = 0; 96 | } else if(address == 0xE001) { 97 | // IRQ Control Reg 1 (enable) 98 | irqEnable = 1; 99 | } else { 100 | // Not a MMC3 register. 101 | // The game has probably crashed, 102 | // since it tries to write to ROM.. 103 | // IGNORE. 104 | } 105 | } 106 | 107 | void Mapper004::executeCommand(int cmd, int arg) { 108 | if(cmd == CMD_SEL_2_1K_VROM_0000) { 109 | 110 | // Select 2 1KB VROM pages at 0x0000: 111 | if(chrAddressSelect == 0) { 112 | load1kVromBank(arg, 0x0000); 113 | load1kVromBank(arg + 1, 0x0400); 114 | } else { 115 | load1kVromBank(arg, 0x1000); 116 | load1kVromBank(arg + 1, 0x1400); 117 | } 118 | 119 | } else if(cmd == CMD_SEL_2_1K_VROM_0800) { 120 | 121 | // Select 2 1KB VROM pages at 0x0800: 122 | if(chrAddressSelect == 0) { 123 | load1kVromBank(arg, 0x0800); 124 | load1kVromBank(arg + 1, 0x0C00); 125 | } else { 126 | load1kVromBank(arg, 0x1800); 127 | load1kVromBank(arg + 1, 0x1C00); 128 | } 129 | 130 | } else if(cmd == CMD_SEL_1K_VROM_1000) { 131 | 132 | // Select 1K VROM Page at 0x1000: 133 | if(chrAddressSelect == 0) { 134 | load1kVromBank(arg, 0x1000); 135 | } else { 136 | load1kVromBank(arg, 0x0000); 137 | } 138 | 139 | } else if(cmd == CMD_SEL_1K_VROM_1400) { 140 | 141 | // Select 1K VROM Page at 0x1400: 142 | if(chrAddressSelect == 0) { 143 | load1kVromBank(arg, 0x1400); 144 | } else { 145 | load1kVromBank(arg, 0x0400); 146 | } 147 | 148 | } else if(cmd == CMD_SEL_1K_VROM_1800) { 149 | 150 | // Select 1K VROM Page at 0x1800: 151 | if(chrAddressSelect == 0) { 152 | load1kVromBank(arg, 0x1800); 153 | } else { 154 | load1kVromBank(arg, 0x0800); 155 | } 156 | 157 | } else if(cmd == CMD_SEL_1K_VROM_1C00) { 158 | 159 | // Select 1K VROM Page at 0x1C00: 160 | if(chrAddressSelect == 0) { 161 | load1kVromBank(arg, 0x1C00); 162 | } else { 163 | load1kVromBank(arg, 0x0C00); 164 | } 165 | 166 | } else if(cmd == CMD_SEL_ROM_PAGE1) { 167 | 168 | //Globals.println("cmd=SEL_ROM_PAGE1"); 169 | if(prgAddressChanged) { 170 | //Globals.println("PRG Address has changed."); 171 | // Load the two hardwired banks: 172 | if(prgAddressSelect == 0) { 173 | load8kRomBank(((nes->getRom()->getRomBankCount() - 1) * 2), 0xC000); 174 | } else { 175 | 176 | load8kRomBank(((nes->getRom()->getRomBankCount() - 1) * 2), 0x8000); 177 | } 178 | prgAddressChanged = false; 179 | } 180 | 181 | // Select first switchable ROM page: 182 | //Globals.println("prgAddressSelect = "+prgAddressSelect+" arg="+arg); 183 | if(prgAddressSelect == 0) { 184 | load8kRomBank(arg, 0x8000); 185 | } else { 186 | load8kRomBank(arg, 0xC000); 187 | } 188 | 189 | } else if(cmd == CMD_SEL_ROM_PAGE2) { 190 | 191 | //Globals.println("cmd=SEL_ROM_PAGE2"); 192 | //Globals.println("prgAddressSelect = "+prgAddressSelect+" arg="+arg); 193 | 194 | // Select second switchable ROM page: 195 | load8kRomBank(arg, 0xA000); 196 | 197 | // hardwire appropriate bank: 198 | if(prgAddressChanged) { 199 | //Globals.println("PRG Address has changed."); 200 | // Load the two hardwired banks: 201 | if(prgAddressSelect == 0) { 202 | load8kRomBank(((nes->getRom()->getRomBankCount() - 1) * 2), 0xC000); 203 | } else { 204 | 205 | load8kRomBank(((nes->getRom()->getRomBankCount() - 1) * 2), 0x8000); 206 | } 207 | prgAddressChanged = false; 208 | } 209 | } 210 | } 211 | 212 | void Mapper004::loadROM(shared_ptr rom) { 213 | //System.out.println("Loading ROM."); 214 | 215 | if(!rom->isValid()) { 216 | //System.out.println("MMC3: Invalid ROM! Unable to load."); 217 | return; 218 | } 219 | 220 | // Load hardwired PRG banks (0xC000 and 0xE000): 221 | load8kRomBank(((nes->getRom()->getRomBankCount() - 1) * 2), 0xC000); 222 | load8kRomBank(((nes->getRom()->getRomBankCount() - 1) * 2) + 1, 0xE000); 223 | 224 | // Load swappable PRG banks (0x8000 and 0xA000): 225 | load8kRomBank(0, 0x8000); 226 | load8kRomBank(1, 0xA000); 227 | 228 | // Load CHR-ROM: 229 | loadCHRROM(); 230 | 231 | // Load Battery RAM (if present): 232 | loadBatteryRam(); 233 | 234 | // Do Reset-Interrupt: 235 | //nes.getCpu().doResetInterrupt(); 236 | nes->getCpu()->requestIrq(CPU::IRQ_RESET); 237 | } 238 | 239 | void Mapper004::clockIrqCounter() { 240 | if(irqEnable == 1) { 241 | --irqCounter; 242 | if(irqCounter < 0) { 243 | 244 | // Trigger IRQ: 245 | //nes.getCpu().doIrq(); 246 | nes->getCpu()->requestIrq(CPU::IRQ_NORMAL); 247 | irqCounter = irqLatchValue; 248 | 249 | } 250 | } 251 | } 252 | 253 | void Mapper004::reset() { 254 | command = 0; 255 | prgAddressSelect = 0; 256 | chrAddressSelect = 0; 257 | pageNumber = 0; 258 | irqCounter = 0; 259 | irqLatchValue = 0; 260 | irqEnable = 0; 261 | prgAddressChanged = false; 262 | } 263 | -------------------------------------------------------------------------------- /src/Mapper018.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | Mapper018::Mapper018() : MapperDefault() { 12 | 13 | } 14 | 15 | shared_ptr Mapper018::Init(shared_ptr nes) { 16 | irq_counter = 0; 17 | irq_latch = 0; 18 | irq_enabled = false; 19 | //regs[] = new int[11]; 20 | //num_8k_banks; 21 | patch = 0; 22 | 23 | reset(); 24 | 25 | this->base_init(nes); 26 | return shared_from_this(); 27 | } 28 | 29 | void Mapper018::mapperInternalStateLoad(ByteBuffer* buf) { 30 | this->base_mapperInternalStateLoad(buf); 31 | 32 | if (buf->readByte() == 1) { 33 | irq_counter = buf->readInt(); 34 | irq_latch = buf->readInt(); 35 | irq_enabled = buf->readBoolean(); 36 | } 37 | } 38 | 39 | void Mapper018::mapperInternalStateSave(ByteBuffer* buf) { 40 | this->base_mapperInternalStateSave(buf); 41 | 42 | // Version: 43 | buf->putByte((short) 1); 44 | 45 | buf->putInt(irq_counter); 46 | buf->putInt(irq_latch); 47 | buf->putBoolean(irq_enabled); 48 | } 49 | 50 | void Mapper018::write(int address, short value) { 51 | 52 | if (address < 0x8000) { 53 | this->base_write(address, value); 54 | 55 | } else { 56 | 57 | switch (address) { 58 | case 0x8000: 59 | { 60 | regs[0] = (regs[0] & 0xF0) | (value & 0x0F); 61 | load8kRomBank(regs[0], 0x8000); 62 | } 63 | break; 64 | 65 | case 0x8001: 66 | { 67 | regs[0] = (regs[0] & 0x0F) | ((value & 0x0F) << 4); 68 | load8kRomBank(regs[0], 0x8000); 69 | } 70 | break; 71 | 72 | case 0x8002: 73 | { 74 | regs[1] = (regs[1] & 0xF0) | (value & 0x0F); 75 | load8kRomBank(regs[1], 0xA000); 76 | } 77 | break; 78 | 79 | case 0x8003: 80 | { 81 | regs[1] = (regs[1] & 0x0F) | ((value & 0x0F) << 4); 82 | load8kRomBank(regs[1], 0xA000); 83 | } 84 | break; 85 | 86 | case 0x9000: 87 | { 88 | regs[2] = (regs[2] & 0xF0) | (value & 0x0F); 89 | load8kRomBank(regs[2], 0xC000); 90 | } 91 | break; 92 | 93 | case 0x9001: 94 | { 95 | regs[2] = (regs[2] & 0x0F) | ((value & 0x0F) << 4); 96 | load8kRomBank(regs[2], 0xC000); 97 | } 98 | break; 99 | 100 | case 0xA000: 101 | { 102 | regs[3] = (regs[3] & 0xF0) | (value & 0x0F); 103 | load1kVromBank(regs[3], 0x0000); 104 | } 105 | break; 106 | 107 | case 0xA001: 108 | { 109 | regs[3] = (regs[3] & 0x0F) | ((value & 0x0F) << 4); 110 | load1kVromBank(regs[3], 0x0000); 111 | } 112 | break; 113 | 114 | case 0xA002: 115 | { 116 | regs[4] = (regs[4] & 0xF0) | (value & 0x0F); 117 | load1kVromBank(regs[4], 0x0400); 118 | } 119 | break; 120 | 121 | case 0xA003: 122 | { 123 | regs[4] = (regs[4] & 0x0F) | ((value & 0x0F) << 4); 124 | load1kVromBank(regs[4], 0x0400); 125 | } 126 | break; 127 | 128 | case 0xB000: 129 | { 130 | regs[5] = (regs[5] & 0xF0) | (value & 0x0F); 131 | load1kVromBank(regs[5], 0x0800); 132 | } 133 | break; 134 | 135 | case 0xB001: 136 | { 137 | regs[5] = (regs[5] & 0x0F) | ((value & 0x0F) << 4); 138 | load1kVromBank(regs[5], 0x0800); 139 | } 140 | break; 141 | 142 | case 0xB002: 143 | { 144 | regs[6] = (regs[6] & 0xF0) | (value & 0x0F); 145 | load1kVromBank(regs[6], 0x0C00); 146 | } 147 | break; 148 | 149 | case 0xB003: 150 | { 151 | regs[6] = (regs[6] & 0x0F) | ((value & 0x0F) << 4); 152 | load1kVromBank(regs[6], 0x0C00); 153 | } 154 | break; 155 | 156 | case 0xC000: 157 | { 158 | regs[7] = (regs[7] & 0xF0) | (value & 0x0F); 159 | load1kVromBank(regs[7], 0x1000); 160 | } 161 | break; 162 | 163 | case 0xC001: 164 | { 165 | regs[7] = (regs[7] & 0x0F) | ((value & 0x0F) << 4); 166 | load1kVromBank(regs[7], 0x1000); 167 | } 168 | break; 169 | 170 | case 0xC002: 171 | { 172 | regs[8] = (regs[8] & 0xF0) | (value & 0x0F); 173 | load1kVromBank(regs[8], 0x1400); 174 | } 175 | break; 176 | 177 | case 0xC003: 178 | { 179 | regs[8] = (regs[8] & 0x0F) | ((value & 0x0F) << 4); 180 | load1kVromBank(regs[8], 0x1400); 181 | } 182 | break; 183 | 184 | case 0xD000: 185 | { 186 | regs[9] = (regs[9] & 0xF0) | (value & 0x0F); 187 | load1kVromBank(regs[9], 0x1800); 188 | } 189 | break; 190 | 191 | case 0xD001: 192 | { 193 | regs[9] = (regs[9] & 0x0F) | ((value & 0x0F) << 4); 194 | load1kVromBank(regs[9], 0x1800); 195 | } 196 | break; 197 | 198 | case 0xD002: 199 | { 200 | regs[10] = (regs[10] & 0xF0) | (value & 0x0F); 201 | load1kVromBank(regs[10], 0x1C00); 202 | } 203 | break; 204 | 205 | case 0xD003: 206 | { 207 | regs[10] = (regs[10] & 0x0F) | ((value & 0x0F) << 4); 208 | load1kVromBank(regs[10], 0x1C00); 209 | } 210 | break; 211 | 212 | case 0xE000: 213 | { 214 | irq_latch = (irq_latch & 0xFFF0) | (value & 0x0F); 215 | } 216 | break; 217 | 218 | case 0xE001: 219 | { 220 | irq_latch = (irq_latch & 0xFF0F) | ((value & 0x0F) << 4); 221 | } 222 | break; 223 | 224 | case 0xE002: 225 | { 226 | irq_latch = (irq_latch & 0xF0FF) | ((value & 0x0F) << 8); 227 | } 228 | break; 229 | 230 | case 0xE003: 231 | { 232 | irq_latch = (irq_latch & 0x0FFF) | ((value & 0x0F) << 12); 233 | } 234 | break; 235 | 236 | case 0xF000: 237 | { 238 | irq_counter = irq_latch; 239 | } 240 | break; 241 | 242 | case 0xF001: 243 | { 244 | irq_enabled = (value & 0x01) != 0; 245 | } 246 | break; 247 | 248 | case 0xF002: 249 | { 250 | value &= 0x03; 251 | 252 | if (value == 0) { 253 | nes->getPpu()->setMirroring(ROM::HORIZONTAL_MIRRORING); 254 | } else if (value == 1) { 255 | nes->getPpu()->setMirroring(ROM::VERTICAL_MIRRORING); 256 | } else { 257 | nes->getPpu()->setMirroring(ROM::SINGLESCREEN_MIRRORING); 258 | } 259 | 260 | } 261 | break; 262 | } 263 | 264 | } 265 | 266 | } 267 | 268 | void Mapper018::loadROM(ROM* rom) { 269 | //System.out.println("Loading ROM."); 270 | 271 | if (! rom->isValid()) { 272 | printf("VRC2: Invalid ROM! Unable to load."); 273 | return; 274 | } 275 | 276 | // Get number of 8K banks: 277 | int num_8k_banks = rom->getRomBankCount() * 2; 278 | 279 | // Load PRG-ROM: 280 | load8kRomBank(0, 0x8000); 281 | load8kRomBank(1, 0xA000); 282 | load8kRomBank(num_8k_banks - 2, 0xC000); 283 | load8kRomBank(num_8k_banks - 1, 0xE000); 284 | 285 | // Load CHR-ROM: 286 | loadCHRROM(); 287 | 288 | loadBatteryRam(); 289 | 290 | // Do Reset-Interrupt: 291 | nes->getCpu()->requestIrq(CPU::IRQ_RESET); 292 | } 293 | 294 | int Mapper018::syncH(int scanline) { 295 | if (irq_enabled) { 296 | if (irq_counter <= 113) { 297 | irq_counter = (patch == 1) ? 114 : 0; 298 | irq_enabled = false; 299 | return 3; 300 | } else { 301 | irq_counter -= 113; 302 | } 303 | } 304 | 305 | return 0; 306 | } 307 | 308 | void Mapper018::reset() { 309 | regs[0] = 0; 310 | regs[1] = 1; 311 | regs[2] = num_8k_banks - 2; 312 | regs[3] = num_8k_banks - 1; 313 | regs[4] = 0; 314 | regs[5] = 0; 315 | regs[6] = 0; 316 | regs[7] = 0; 317 | regs[8] = 0; 318 | regs[9] = 0; 319 | regs[10] = 0; 320 | 321 | // IRQ Settings 322 | irq_enabled = false; 323 | irq_latch = 0; 324 | irq_counter = 0; 325 | } 326 | -------------------------------------------------------------------------------- /src/PaletteTable.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | 10 | #include "SaltyNES.h" 11 | 12 | 13 | int PaletteTable::curTable[64] = {0}; 14 | int PaletteTable::origTable[64] = {0}; 15 | int PaletteTable::emphTable[8][64] = {{0}}; 16 | 17 | PaletteTable::PaletteTable() : enable_shared_from_this() { 18 | } 19 | 20 | shared_ptr PaletteTable::Init() { 21 | currentEmph = -1; 22 | currentHue = 0; 23 | currentSaturation = 0; 24 | currentLightness = 0; 25 | currentContrast = 0; 26 | return shared_from_this(); 27 | } 28 | 29 | // Load the NTSC palette: 30 | bool PaletteTable::loadNTSCPalette() { 31 | loadDefaultPalette(); 32 | return true; 33 | } 34 | 35 | void PaletteTable::makeTables() { 36 | int r, g, b, col; 37 | 38 | // Calculate a table for each possible emphasis setting: 39 | for(int emph = 0; emph < 8; ++emph) { 40 | 41 | // Determine color component factors: 42 | float rFactor = 1.0f, gFactor = 1.0f, bFactor = 1.0f; 43 | if((emph & 1) != 0) { 44 | rFactor = 0.75f; 45 | bFactor = 0.75f; 46 | } 47 | if((emph & 2) != 0) { 48 | rFactor = 0.75f; 49 | gFactor = 0.75f; 50 | } 51 | if((emph & 4) != 0) { 52 | gFactor = 0.75f; 53 | bFactor = 0.75f; 54 | } 55 | 56 | // Calculate table: 57 | for(int i = 0; i < 64; ++i) { 58 | col = origTable[i]; 59 | r = static_cast(getRed(col) * rFactor); 60 | g = static_cast(getGreen(col) * gFactor); 61 | b = static_cast(getBlue(col) * bFactor); 62 | emphTable[emph][i] = getRgb(r, g, b); 63 | } 64 | } 65 | } 66 | 67 | void PaletteTable::setEmphasis(int emph) { 68 | if(emph != currentEmph) { 69 | currentEmph = emph; 70 | for(int i = 0; i < 64; ++i) { 71 | curTable[i] = emphTable[emph][i]; 72 | } 73 | updatePalette(); 74 | } 75 | } 76 | 77 | int PaletteTable::getEntry(int yiq) { 78 | return curTable[yiq]; 79 | } 80 | 81 | int PaletteTable::RGBtoHSL(int r, int g, int b) { 82 | auto buffer = array(); 83 | array* hsbvals = Color::RGBtoHSB(b, g, r, &buffer); 84 | (*hsbvals)[0] -= floor((*hsbvals)[0]); 85 | 86 | int ret = 0; 87 | ret |= (static_cast((*hsbvals)[0] * 255.0) << 16); 88 | ret |= (static_cast((*hsbvals)[1] * 255.0) << 8); 89 | ret |= static_cast((*hsbvals)[2] * 255.0); 90 | 91 | return ret; 92 | } 93 | 94 | int PaletteTable::RGBtoHSL(int rgb) { 95 | return RGBtoHSL((rgb >> 16) & 0xFF, (rgb >> 8) & 0xFF, (rgb) & 0xFF); 96 | } 97 | 98 | int PaletteTable::HSLtoRGB(int h, int s, int l) { 99 | return Color::HSBtoRGB(h / 255.0f, s / 255.0f, l / 255.0f); 100 | } 101 | 102 | int PaletteTable::HSLtoRGB(int hsl) { 103 | float h, s, l; 104 | h = static_cast(((hsl >> 16) & 0xFF) / 255.0); 105 | s = static_cast(((hsl >> 8) & 0xFF) / 255.0); 106 | l = static_cast(((hsl) & 0xFF) / 255.0); 107 | return Color::HSBtoRGB(h, s, l); 108 | } 109 | 110 | int PaletteTable::getHue(int hsl) { 111 | return (hsl >> 16) & 0xFF; 112 | } 113 | 114 | int PaletteTable::getSaturation(int hsl) { 115 | return (hsl >> 8) & 0xFF; 116 | } 117 | 118 | int PaletteTable::getLightness(int hsl) { 119 | return hsl & 0xFF; 120 | } 121 | 122 | int PaletteTable::getRed(int rgb) { 123 | return (rgb >> 16) & 0xFF; 124 | } 125 | 126 | int PaletteTable::getGreen(int rgb) { 127 | return (rgb >> 8) & 0xFF; 128 | } 129 | 130 | int PaletteTable::getBlue(int rgb) { 131 | return rgb & 0xFF; 132 | } 133 | 134 | void PaletteTable::setRed(int* rgb, int r) { 135 | *rgb |= (16 << (r & 0xFF)); 136 | } 137 | 138 | void PaletteTable::setGreen(int* rgb, int g) { 139 | *rgb |= (8 << (g & 0xFF)); 140 | } 141 | 142 | void PaletteTable::setBlue(int* rgb, int b) { 143 | *rgb |= ((b & 0xFF)); 144 | } 145 | 146 | int PaletteTable::getRgb(int r, int g, int b) { 147 | return ((r << 16) | (g << 8) | (b)); 148 | } 149 | 150 | void PaletteTable::updatePalette() { 151 | updatePalette(currentHue, currentSaturation, currentLightness, currentContrast); 152 | } 153 | 154 | // Change palette colors. 155 | // Arguments should be set to 0 to keep the original value. 156 | void PaletteTable::updatePalette(int hueAdd, int saturationAdd, int lightnessAdd, int contrastAdd) { 157 | int hsl, rgb; 158 | int h, s, l; 159 | int r, g, b; 160 | 161 | if(contrastAdd > 0) { 162 | contrastAdd *= 4; 163 | } 164 | for(int i = 0; i < 64; ++i) { 165 | 166 | hsl = RGBtoHSL(emphTable[currentEmph][i]); 167 | h = getHue(hsl) + hueAdd; 168 | s = static_cast(getSaturation(hsl) * (1.0 + saturationAdd / 256.0f)); 169 | l = getLightness(hsl); 170 | 171 | if(h < 0) { 172 | h += 255; 173 | } 174 | if(s < 0) { 175 | s = 0; 176 | } 177 | if(l < 0) { 178 | l = 0; 179 | } 180 | 181 | if(h > 255) { 182 | h -= 255; 183 | } 184 | if(s > 255) { 185 | s = 255; 186 | } 187 | if(l > 255) { 188 | l = 255; 189 | } 190 | 191 | rgb = HSLtoRGB(h, s, l); 192 | 193 | r = getRed(rgb); 194 | g = getGreen(rgb); 195 | b = getBlue(rgb); 196 | 197 | r = 128 + lightnessAdd + static_cast((r - 128) * (1.0 + contrastAdd / 256.0f)); 198 | g = 128 + lightnessAdd + static_cast((g - 128) * (1.0 + contrastAdd / 256.0f)); 199 | b = 128 + lightnessAdd + static_cast((b - 128) * (1.0 + contrastAdd / 256.0f)); 200 | 201 | if(r < 0) { 202 | r = 0; 203 | } 204 | if(g < 0) { 205 | g = 0; 206 | } 207 | if(b < 0) { 208 | b = 0; 209 | } 210 | 211 | if(r > 255) { 212 | r = 255; 213 | } 214 | if(g > 255) { 215 | g = 255; 216 | } 217 | if(b > 255) { 218 | b = 255; 219 | } 220 | 221 | rgb = getRgb(r, g, b); 222 | curTable[i] = rgb; 223 | 224 | } 225 | 226 | currentHue = hueAdd; 227 | currentSaturation = saturationAdd; 228 | currentLightness = lightnessAdd; 229 | currentContrast = contrastAdd; 230 | } 231 | 232 | void PaletteTable::loadDefaultPalette() { 233 | origTable[ 0] = getRgb(124, 124, 124); 234 | origTable[ 1] = getRgb(0, 0, 252); 235 | origTable[ 2] = getRgb(0, 0, 188); 236 | origTable[ 3] = getRgb(68, 40, 188); 237 | origTable[ 4] = getRgb(148, 0, 132); 238 | origTable[ 5] = getRgb(168, 0, 32); 239 | origTable[ 6] = getRgb(168, 16, 0); 240 | origTable[ 7] = getRgb(136, 20, 0); 241 | origTable[ 8] = getRgb(80, 48, 0); 242 | origTable[ 9] = getRgb(0, 120, 0); 243 | origTable[10] = getRgb(0, 104, 0); 244 | origTable[11] = getRgb(0, 88, 0); 245 | origTable[12] = getRgb(0, 64, 88); 246 | origTable[13] = getRgb(0, 0, 0); 247 | origTable[14] = getRgb(0, 0, 0); 248 | origTable[15] = getRgb(0, 0, 0); 249 | origTable[16] = getRgb(188, 188, 188); 250 | origTable[17] = getRgb(0, 120, 248); 251 | origTable[18] = getRgb(0, 88, 248); 252 | origTable[19] = getRgb(104, 68, 252); 253 | origTable[20] = getRgb(216, 0, 204); 254 | origTable[21] = getRgb(228, 0, 88); 255 | origTable[22] = getRgb(248, 56, 0); 256 | origTable[23] = getRgb(228, 92, 16); 257 | origTable[24] = getRgb(172, 124, 0); 258 | origTable[25] = getRgb(0, 184, 0); 259 | origTable[26] = getRgb(0, 168, 0); 260 | origTable[27] = getRgb(0, 168, 68); 261 | origTable[28] = getRgb(0, 136, 136); 262 | origTable[29] = getRgb(0, 0, 0); 263 | origTable[30] = getRgb(0, 0, 0); 264 | origTable[31] = getRgb(0, 0, 0); 265 | origTable[32] = getRgb(248, 248, 248); 266 | origTable[33] = getRgb(60, 188, 252); 267 | origTable[34] = getRgb(104, 136, 252); 268 | origTable[35] = getRgb(152, 120, 248); 269 | origTable[36] = getRgb(248, 120, 248); 270 | origTable[37] = getRgb(248, 88, 152); 271 | origTable[38] = getRgb(248, 120, 88); 272 | origTable[39] = getRgb(252, 160, 68); 273 | origTable[40] = getRgb(248, 184, 0); 274 | origTable[41] = getRgb(184, 248, 24); 275 | origTable[42] = getRgb(88, 216, 84); 276 | origTable[43] = getRgb(88, 248, 152); 277 | origTable[44] = getRgb(0, 232, 216); 278 | origTable[45] = getRgb(120, 120, 120); 279 | origTable[46] = getRgb(0, 0, 0); 280 | origTable[47] = getRgb(0, 0, 0); 281 | origTable[48] = getRgb(252, 252, 252); 282 | origTable[49] = getRgb(164, 228, 252); 283 | origTable[50] = getRgb(184, 184, 248); 284 | origTable[51] = getRgb(216, 184, 248); 285 | origTable[52] = getRgb(248, 184, 248); 286 | origTable[53] = getRgb(248, 164, 192); 287 | origTable[54] = getRgb(240, 208, 176); 288 | origTable[55] = getRgb(252, 224, 168); 289 | origTable[56] = getRgb(248, 216, 120); 290 | origTable[57] = getRgb(216, 248, 120); 291 | origTable[58] = getRgb(184, 248, 184); 292 | origTable[59] = getRgb(184, 248, 216); 293 | origTable[60] = getRgb(0, 252, 252); 294 | origTable[61] = getRgb(216, 216, 16); 295 | origTable[62] = getRgb(0, 0, 0); 296 | origTable[63] = getRgb(0, 0, 0); 297 | 298 | setEmphasis(0); 299 | makeTables(); 300 | } 301 | 302 | void PaletteTable::reset() { 303 | currentEmph = 0; 304 | currentHue = 0; 305 | currentSaturation = 0; 306 | currentLightness = 0; 307 | setEmphasis(0); 308 | updatePalette(); 309 | } 310 | -------------------------------------------------------------------------------- /src/Mapper001.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | Mapper001::Mapper001() : MapperDefault() { 12 | 13 | } 14 | 15 | shared_ptr Mapper001::Init(shared_ptr nes) { 16 | // Register 0: 17 | mirroring = 0; 18 | oneScreenMirroring = 0; 19 | prgSwitchingArea = 1; 20 | prgSwitchingSize = 1; 21 | vromSwitchingSize = 0; 22 | 23 | // Register 1: 24 | romSelectionReg0 = 0; 25 | 26 | // Register 2: 27 | romSelectionReg1 = 0; 28 | 29 | // Register 3: 30 | romBankSelect = 0; 31 | 32 | // 5-bit buffer: 33 | regBuffer = 0; 34 | regBufferCounter = 0; 35 | 36 | this->base_init(nes); 37 | return shared_from_this(); 38 | } 39 | 40 | void Mapper001::mapperInternalStateLoad(ByteBuffer* buf) { 41 | 42 | // Check version: 43 | if(buf->readByte() == 1) { 44 | 45 | // Reg 0: 46 | mirroring = buf->readInt(); 47 | oneScreenMirroring = buf->readInt(); 48 | prgSwitchingArea = buf->readInt(); 49 | prgSwitchingSize = buf->readInt(); 50 | vromSwitchingSize = buf->readInt(); 51 | 52 | // Reg 1: 53 | romSelectionReg0 = buf->readInt(); 54 | 55 | // Reg 2: 56 | romSelectionReg1 = buf->readInt(); 57 | 58 | // Reg 3: 59 | romBankSelect = buf->readInt(); 60 | 61 | // 5-bit buffer: 62 | regBuffer = buf->readInt(); 63 | regBufferCounter = buf->readInt(); 64 | 65 | } 66 | 67 | } 68 | 69 | void Mapper001::mapperInternalStateSave(ByteBuffer* buf) { 70 | // Version: 71 | buf->putByte(static_cast(1)); 72 | 73 | // Reg 0: 74 | buf->putInt(mirroring); 75 | buf->putInt(oneScreenMirroring); 76 | buf->putInt(prgSwitchingArea); 77 | buf->putInt(prgSwitchingSize); 78 | buf->putInt(vromSwitchingSize); 79 | 80 | // Reg 1: 81 | buf->putInt(romSelectionReg0); 82 | 83 | // Reg 2: 84 | buf->putInt(romSelectionReg1); 85 | 86 | // Reg 3: 87 | buf->putInt(romBankSelect); 88 | 89 | // 5-bit buffer: 90 | buf->putInt(regBuffer); 91 | buf->putInt(regBufferCounter); 92 | 93 | } 94 | 95 | void Mapper001::write(int address, uint16_t value) { 96 | // Writes to addresses other than MMC registers are handled by NoMapper. 97 | if(address < 0x8000) { 98 | this->base_write(address, value); 99 | return; 100 | } 101 | 102 | ////System.out.println("MMC Write. Reg="+(getRegNumber(address))+" Value="+value); 103 | 104 | // See what should be done with the written value: 105 | if((value & 128) != 0) { 106 | 107 | // Reset buffering: 108 | regBufferCounter = 0; 109 | regBuffer = 0; 110 | 111 | // Reset register: 112 | if(getRegNumber(address) == 0) { 113 | 114 | prgSwitchingArea = 1; 115 | prgSwitchingSize = 1; 116 | 117 | } 118 | 119 | } else { 120 | 121 | // Continue buffering: 122 | //regBuffer = (regBuffer & (0xFF-(1<getPpu()->setMirroring(ROM::SINGLESCREEN_MIRRORING); 154 | } else { 155 | // Not overridden by SingleScreen mirroring. 156 | ////System.out.println("MMC1: Setting Normal Mirroring. value="+mirroring); 157 | nes->getPpu()->setMirroring((mirroring & 1) != 0 ? ROM::HORIZONTAL_MIRRORING : ROM::VERTICAL_MIRRORING); 158 | } 159 | } 160 | 161 | // PRG Switching Area; 162 | prgSwitchingArea = (value >> 2) & 1; 163 | 164 | // PRG Switching Size: 165 | prgSwitchingSize = (value >> 3) & 1; 166 | 167 | // VROM Switching Size: 168 | vromSwitchingSize = (value >> 4) & 1; 169 | 170 | } else if(reg == 1) { 171 | 172 | // ROM selection: 173 | romSelectionReg0 = (value >> 4) & 1; 174 | 175 | // Check whether the cart has VROM: 176 | if(nes->getRom()->getVromBankCount() > 0) { 177 | 178 | // Select VROM bank at 0x0000: 179 | if(vromSwitchingSize == 0) { 180 | 181 | // Swap 8kB VROM: 182 | ////System.out.println("Swapping 8k VROM, bank="+(value&0xF)+" romSelReg="+romSelectionReg0); 183 | if(romSelectionReg0 == 0) { 184 | load8kVromBank((value & 0xF), 0x0000); 185 | } else { 186 | load8kVromBank(nes->getRom()->getVromBankCount() / 2 + (value & 0xF), 0x0000); 187 | } 188 | 189 | } else { 190 | 191 | // Swap 4kB VROM: 192 | ////System.out.println("ROMSELREG0 = "+romSelectionReg0); 193 | ////System.out.println("Swapping 4k VROM at 0x0000, bank="+(value&0xF)); 194 | 195 | if(romSelectionReg0 == 0) { 196 | loadVromBank((value & 0xF), 0x0000); 197 | } else { 198 | loadVromBank(nes->getRom()->getVromBankCount() / 2 + (value & 0xF), 0x0000); 199 | } 200 | 201 | } 202 | 203 | } 204 | 205 | } else if(reg == 2) { 206 | 207 | // ROM selection: 208 | romSelectionReg1 = (value >> 4) & 1; 209 | 210 | // Check whether the cart has VROM: 211 | if(nes->getRom()->getVromBankCount() > 0) { 212 | 213 | // Select VROM bank at 0x1000: 214 | if(vromSwitchingSize == 1) { 215 | 216 | // Swap 4kB of VROM: 217 | ////System.out.println("ROMSELREG1 = "+romSelectionReg1); 218 | ////System.out.println("Swapping 4k VROM at 0x1000, bank="+(value&0xF)); 219 | if(romSelectionReg1 == 0) { 220 | loadVromBank((value & 0xF), 0x1000); 221 | } else { 222 | loadVromBank(nes->getRom()->getVromBankCount() / 2 + (value & 0xF), 0x1000); 223 | } 224 | 225 | } 226 | 227 | } 228 | 229 | } else { 230 | 231 | // Select ROM bank: 232 | // ------------------------- 233 | tmp = value & 0xF; 234 | int bank; 235 | int baseBank = 0; 236 | int bankCount = nes->getRom()->getRomBankCount(); 237 | 238 | if(bankCount >= 32) { 239 | 240 | // 1024 kB cart 241 | if(vromSwitchingSize == 0) { 242 | if(romSelectionReg0 == 1) { 243 | baseBank = 16; 244 | } 245 | } else { 246 | baseBank = (romSelectionReg0 | (romSelectionReg1 << 1)) << 3; 247 | } 248 | 249 | } else if(bankCount >= 16) { 250 | 251 | // 512 kB cart 252 | if(romSelectionReg0 == 1) { 253 | baseBank = 8; 254 | } 255 | 256 | } 257 | 258 | if(prgSwitchingSize == 0) { 259 | 260 | // 32kB 261 | bank = baseBank + (value & 0xF); 262 | load32kRomBank(bank, 0x8000); 263 | 264 | } else { 265 | 266 | // 16kB 267 | bank = baseBank * 2 + (value & 0xF); 268 | if(prgSwitchingArea == 0) { 269 | loadRomBank(bank, 0xC000); 270 | } else { 271 | loadRomBank(bank, 0x8000); 272 | } 273 | 274 | } 275 | 276 | // ------------------------- 277 | 278 | } 279 | 280 | } 281 | 282 | // Returns the register number from the address written to: 283 | int Mapper001::getRegNumber(int address) { 284 | if(address >= 0x8000 && address <= 0x9FFF) { 285 | return 0; 286 | } else if(address >= 0xA000 && address <= 0xBFFF) { 287 | return 1; 288 | } else if(address >= 0xC000 && address <= 0xDFFF) { 289 | return 2; 290 | } else { 291 | return 3; 292 | } 293 | } 294 | 295 | void Mapper001::loadROM(shared_ptr rom) { 296 | //System.out.println("Loading ROM."); 297 | 298 | if(!rom->isValid()) { 299 | //System.out.println("MMC1: Invalid ROM! Unable to load."); 300 | return; 301 | } 302 | 303 | // Load PRG-ROM: 304 | loadRomBank(0, 0x8000); // First ROM bank.. 305 | loadRomBank(rom->getRomBankCount() - 1, 0xC000); // ..and last ROM bank. 306 | 307 | // Load CHR-ROM: 308 | loadCHRROM(); 309 | 310 | // Load Battery RAM (if present): 311 | loadBatteryRam(); 312 | 313 | // Do Reset-Interrupt: 314 | //nes->getCpu().doResetInterrupt(); 315 | nes->getCpu()->requestIrq(CPU::IRQ_RESET); 316 | 317 | } 318 | 319 | void Mapper001::reset() { 320 | regBuffer = 0; 321 | regBufferCounter = 0; 322 | 323 | // Register 0: 324 | mirroring = 0; 325 | oneScreenMirroring = 0; 326 | prgSwitchingArea = 1; 327 | prgSwitchingSize = 1; 328 | vromSwitchingSize = 0; 329 | 330 | // Register 1: 331 | romSelectionReg0 = 0; 332 | 333 | // Register 2: 334 | romSelectionReg1 = 0; 335 | 336 | // Register 3: 337 | romBankSelect = 0; 338 | 339 | } 340 | 341 | void Mapper001::switchLowHighPrgRom(int oldSetting) { 342 | // not yet. 343 | assert(oldSetting != -1); 344 | } 345 | 346 | void Mapper001::switch16to32() { 347 | // not yet. 348 | } 349 | 350 | void Mapper001::switch32to16() { 351 | // not yet. 352 | } 353 | -------------------------------------------------------------------------------- /src/NES.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | NES::NES() : enable_shared_from_this() { 12 | } 13 | 14 | shared_ptr NES::Init(shared_ptr joy1, shared_ptr joy2) { 15 | _joy1 = joy1; 16 | _joy2 = joy2; 17 | 18 | this->_is_paused = false; 19 | this->_isRunning = false; 20 | 21 | // Create memory: 22 | cpuMem = make_shared()->Init(shared_from_this(), 0x10000); // Main memory (internal to CPU) 23 | ppuMem = make_shared()->Init(shared_from_this(), 0x8000); // VRAM memory (internal to PPU) 24 | sprMem = make_shared()->Init(shared_from_this(), 0x100); // Sprite RAM (internal to PPU) 25 | 26 | // Create system units: 27 | cpu = make_shared()->Init(shared_from_this()); 28 | palTable = make_shared()->Init(); 29 | ppu = make_shared()->Init(shared_from_this()); 30 | papu = make_shared()->Init(shared_from_this()); 31 | memMapper = nullptr; 32 | rom = nullptr; 33 | 34 | // Init sound registers: 35 | for(int i = 0; i < 0x14; ++i) { 36 | if(i == 0x10) { 37 | papu->writeReg(0x4010, static_cast(0x10)); 38 | } else { 39 | papu->writeReg(0x4000 + i, static_cast(0)); 40 | } 41 | } 42 | 43 | // Grab Controller Setting for Player 1: 44 | this->_joy1->mapKey(InputHandler::KEY_A, Globals::keycodes[Globals::controls["p1_a"]]); 45 | this->_joy1->mapKey(InputHandler::KEY_B, Globals::keycodes[Globals::controls["p1_b"]]); 46 | this->_joy1->mapKey(InputHandler::KEY_START, Globals::keycodes[Globals::controls["p1_start"]]); 47 | this->_joy1->mapKey(InputHandler::KEY_SELECT, Globals::keycodes[Globals::controls["p1_select"]]); 48 | this->_joy1->mapKey(InputHandler::KEY_UP, Globals::keycodes[Globals::controls["p1_up"]]); 49 | this->_joy1->mapKey(InputHandler::KEY_DOWN, Globals::keycodes[Globals::controls["p1_down"]]); 50 | this->_joy1->mapKey(InputHandler::KEY_LEFT, Globals::keycodes[Globals::controls["p1_left"]]); 51 | this->_joy1->mapKey(InputHandler::KEY_RIGHT, Globals::keycodes[Globals::controls["p1_right"]]); 52 | 53 | // Grab Controller Setting for Player 2: 54 | this->_joy2->mapKey(InputHandler::KEY_A, Globals::keycodes[Globals::controls["p2_a"]]); 55 | this->_joy2->mapKey(InputHandler::KEY_B, Globals::keycodes[Globals::controls["p2_b"]]); 56 | this->_joy2->mapKey(InputHandler::KEY_START, Globals::keycodes[Globals::controls["p2_start"]]); 57 | this->_joy2->mapKey(InputHandler::KEY_SELECT, Globals::keycodes[Globals::controls["p2_select"]]); 58 | this->_joy2->mapKey(InputHandler::KEY_UP, Globals::keycodes[Globals::controls["p2_up"]]); 59 | this->_joy2->mapKey(InputHandler::KEY_DOWN, Globals::keycodes[Globals::controls["p2_down"]]); 60 | this->_joy2->mapKey(InputHandler::KEY_LEFT, Globals::keycodes[Globals::controls["p2_left"]]); 61 | this->_joy2->mapKey(InputHandler::KEY_RIGHT, Globals::keycodes[Globals::controls["p2_right"]]); 62 | 63 | // Load NTSC palette: 64 | if(!palTable->loadNTSCPalette()) { 65 | //System.out.println("Unable to load palette file. Using default."); 66 | palTable->loadDefaultPalette(); 67 | } 68 | 69 | // Initialize units: 70 | cpu->init(); 71 | ppu->init(); 72 | 73 | // Enable sound: 74 | enableSound(true); 75 | 76 | // Clear CPU memory: 77 | clearCPUMemory(); 78 | 79 | return shared_from_this(); 80 | } 81 | 82 | NES::~NES() { 83 | } 84 | 85 | void NES::dumpRomMemory(ofstream* writer) { 86 | //ofstream writer("rom_mem_cpp.txt", ios::out|ios::binary); 87 | for(size_t i = 0;irom.size(); ++i) { 88 | for(size_t j = 0;jrom[i].size(); ++j) { 89 | stringstream out; 90 | out << "@" << j << " " << rom->rom[i][j] << "\n"; 91 | writer->write(out.str().c_str(), out.str().length()); 92 | } 93 | } 94 | //writer.close(); 95 | //exit(0); 96 | } 97 | 98 | void NES::dumpCPUMemory(ofstream* writer) { 99 | //ofstream writer("cpu_mem_cpp.txt", ios::out|ios::binary); 100 | for(size_t i = 0;imem.size(); ++i) { 101 | stringstream out; 102 | out << "-" << i << " " << cpuMem->mem[i] << "\n"; 103 | writer->write(out.str().c_str(), out.str().length()); 104 | } 105 | //writer.close(); 106 | //exit(0); 107 | } 108 | 109 | bool NES::stateLoad(ByteBuffer* buf) { 110 | bool continueEmulation = false; 111 | bool success; 112 | 113 | // Pause emulation: 114 | continueEmulation = true; 115 | stopEmulation(); 116 | 117 | // Check version: 118 | if(buf->readByte() == 1) { 119 | 120 | // Let units load their state from the buffer: 121 | cpuMem->stateLoad(buf); 122 | ppuMem->stateLoad(buf); 123 | sprMem->stateLoad(buf); 124 | cpu->stateLoad(buf); 125 | memMapper->stateLoad(buf); 126 | ppu->stateLoad(buf); 127 | success = true; 128 | 129 | } else { 130 | 131 | //System.out.println("State file has wrong format. version="+buf->readByte(0)); 132 | success = false; 133 | 134 | } 135 | 136 | // Continue emulation: 137 | if(continueEmulation) { 138 | startEmulation(); 139 | } 140 | 141 | return success; 142 | } 143 | 144 | void NES::stateSave(ByteBuffer* buf) { 145 | bool continueEmulation = isRunning(); 146 | stopEmulation(); 147 | 148 | // Version: 149 | buf->putByte(static_cast(1)); 150 | 151 | // Let units save their state: 152 | cpuMem->stateSave(buf); 153 | ppuMem->stateSave(buf); 154 | sprMem->stateSave(buf); 155 | cpu->stateSave(buf); 156 | memMapper->stateSave(buf); 157 | ppu->stateSave(buf); 158 | 159 | // Continue emulation: 160 | if(continueEmulation) { 161 | startEmulation(); 162 | } 163 | 164 | } 165 | 166 | bool NES::isRunning() { 167 | return _isRunning; 168 | } 169 | 170 | void NES::startEmulation() { 171 | if(Globals::enableSound && !papu->isRunning()) { 172 | papu->lock_mutex(); 173 | papu->synchronized_start(); 174 | papu->unlock_mutex(); 175 | } 176 | { 177 | if(rom != nullptr && rom->isValid()) { 178 | _isRunning = true; 179 | } 180 | } 181 | } 182 | 183 | void NES::stopEmulation() { 184 | _isRunning = false; 185 | cpu->stop(); 186 | 187 | if(Globals::enableSound && papu->isRunning()) { 188 | papu->stop(); 189 | } 190 | } 191 | 192 | void NES::clearCPUMemory() { 193 | uint16_t flushval = Globals::memoryFlushValue; 194 | for(int i = 0; i < 0x2000; ++i) { 195 | cpuMem->mem[i] = flushval; 196 | } 197 | for(int p = 0; p < 4; ++p) { 198 | int i = p * 0x800; 199 | cpuMem->mem[i + 0x008] = 0xF7; 200 | cpuMem->mem[i + 0x009] = 0xEF; 201 | cpuMem->mem[i + 0x00A] = 0xDF; 202 | cpuMem->mem[i + 0x00F] = 0xBF; 203 | } 204 | } 205 | 206 | void NES::setGameGenieState(bool enable) { 207 | if(memMapper != nullptr) { 208 | memMapper->setGameGenieState(enable); 209 | } 210 | } 211 | 212 | // Returns CPU object. 213 | shared_ptr NES::getCpu() { 214 | return cpu; 215 | } 216 | 217 | // Returns PPU object. 218 | shared_ptr NES::getPpu() { 219 | return ppu; 220 | } 221 | 222 | // Returns pAPU object. 223 | shared_ptr NES::getPapu() { 224 | return papu; 225 | } 226 | 227 | // Returns CPU Memory. 228 | shared_ptr NES::getCpuMemory() { 229 | return cpuMem; 230 | } 231 | 232 | // Returns PPU Memory. 233 | shared_ptr NES::getPpuMemory() { 234 | return ppuMem; 235 | } 236 | 237 | // Returns Sprite Memory. 238 | shared_ptr NES::getSprMemory() { 239 | return sprMem; 240 | } 241 | 242 | // Returns the currently loaded ROM. 243 | shared_ptr NES::getRom() { 244 | return rom; 245 | } 246 | 247 | // Returns the memory mapper. 248 | shared_ptr NES::getMemoryMapper() { 249 | return memMapper; 250 | } 251 | 252 | bool NES::load_rom_from_data(string rom_name, vector* data, array* save_ram) { 253 | // Can't load ROM while still running. 254 | if(_isRunning) { 255 | stopEmulation(); 256 | } 257 | 258 | { 259 | // Load ROM file: 260 | 261 | rom = make_shared()->Init(shared_from_this()); 262 | rom->load_from_data(rom_name, data, save_ram); 263 | 264 | if(rom->isValid()) { 265 | 266 | // The CPU will load 267 | // the ROM into the CPU 268 | // and PPU memory. 269 | 270 | reset(); 271 | 272 | memMapper = rom->createMapper(); 273 | cpu->setMapper(memMapper); 274 | memMapper->loadROM(rom); 275 | ppu->setMirroring(rom->getMirroringType()); 276 | } 277 | return rom->isValid(); 278 | } 279 | } 280 | 281 | // Resets the system. 282 | void NES::reset() { 283 | if(rom != nullptr) { 284 | rom->closeRom(); 285 | } 286 | if(memMapper != nullptr) { 287 | memMapper->reset(); 288 | } 289 | 290 | cpuMem->reset(); 291 | ppuMem->reset(); 292 | sprMem->reset(); 293 | 294 | clearCPUMemory(); 295 | 296 | cpu->reset(); 297 | cpu->init(); 298 | ppu->reset(); 299 | palTable->reset(); 300 | papu->reset(); 301 | 302 | // shared_ptr joy1 = gui->getJoy1(); 303 | // if(joy1 != nullptr) { 304 | // joy1->reset(); 305 | // } 306 | } 307 | 308 | // Enable or disable sound playback. 309 | void NES::enableSound(bool enable) { 310 | bool wasRunning = isRunning(); 311 | if(wasRunning) { 312 | stopEmulation(); 313 | } 314 | 315 | if(enable) { 316 | papu->lock_mutex(); 317 | papu->synchronized_start(); 318 | papu->unlock_mutex(); 319 | } else { 320 | papu->stop(); 321 | } 322 | 323 | //System.out.println("** SOUND ENABLE = "+enable+" **"); 324 | Globals::enableSound = enable; 325 | 326 | if(wasRunning) { 327 | startEmulation(); 328 | } 329 | } 330 | /* 331 | void NES::setFramerate(int rate) { 332 | Globals::preferredFrameRate = rate; 333 | Globals::frameTime = 1000000 / rate; 334 | 335 | papu->lock_mutex(); 336 | papu->synchronized_setSampleRate(papu->getSampleRate(), false); 337 | papu->unlock_mutex(); 338 | } 339 | */ 340 | -------------------------------------------------------------------------------- /static/setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | A NES emulator in WebAssembly. Based on vNES. 4 | Licensed under GPLV3 or later 5 | Hosted at: https://github.com/workhorsy/SaltyNES 6 | */ 7 | 8 | 9 | let g_zoom = 1; 10 | let g_mouse_move_timeout = null; 11 | 12 | let statusElement = $('#status'); 13 | let progressElement = $('#progress'); 14 | var Module = null; 15 | 16 | function onReady() { 17 | show('#select_game'); 18 | show('#fileupload'); 19 | show('#screen'); 20 | hide('#progress'); 21 | Module.setStatus(''); 22 | } 23 | 24 | function play_game(game_data) { 25 | Module.set_game_data_size(game_data.length); 26 | 27 | for (let i=0; i { 99 | if (response.status >= 200 && response.status < 400) 100 | return response.arrayBuffer() 101 | throw new Error('Network error: ' + response.statusText + ', ' + response.status); 102 | }) 103 | } 104 | 105 | let main = (function() { 106 | Module = { 107 | preRun: [], 108 | postRun: [], 109 | print: (function() { 110 | let element = $('#output'); 111 | if (element) element.value = ''; // clear browser cache 112 | return function(text) { 113 | if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); 114 | // These replacements are necessary if you render to raw HTML 115 | //text = text.replace(/&/g, "&"); 116 | //text = text.replace(//g, ">"); 118 | //text = text.replace('\n', '
', 'g'); 119 | console.log(text); 120 | if (element) { 121 | element.value += text + "\n"; 122 | element.scrollTop = element.scrollHeight; // focus on bottom 123 | } 124 | }; 125 | })(), 126 | printErr: function(text) { 127 | if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); 128 | if (0) { // XXX disabled for safety typeof dump == 'function') { 129 | dump(text + '\n'); // fast, straight to the real console 130 | } else { 131 | console.error(text); 132 | } 133 | }, 134 | canvas: (function() { 135 | let screen = $('#screen'); 136 | 137 | // As a default initial behavior, pop up an alert when webgl context is lost. To make your 138 | // application robust, you may want to override this behavior before shipping! 139 | // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 140 | screen.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); 141 | 142 | return screen; 143 | })(), 144 | setStatus: function(text) { 145 | if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' }; 146 | if (text === Module.setStatus.text) return; 147 | let m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/); 148 | let now = Date.now(); 149 | if (m && now - Date.now() < 30) return; // if this is a progress update, skip it if too soon 150 | if (m) { 151 | text = m[1]; 152 | } 153 | statusElement.innerHTML = text; 154 | }, 155 | totalDependencies: 0, 156 | monitorRunDependencies: function(left) { 157 | this.totalDependencies = Math.max(this.totalDependencies, left); 158 | Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.'); 159 | } 160 | }; 161 | 162 | window.onerror = function(event) { 163 | // TODO: do not warn on ok events like simulating an infinite loop or exitStatus 164 | Module.setStatus('Exception thrown, see JavaScript console'); 165 | Module.setStatus = function(text) { 166 | if (text) Module.printErr('[post-exception status] ' + text); 167 | }; 168 | }; 169 | 170 | document.addEventListener('mousemove', function() { 171 | // Show the cursor when the mouse moves 172 | document.body.style.cursor = ''; 173 | $('#screen').style.cursor = ''; 174 | 175 | // Just return if not fullscreen 176 | const is_full_screen = ( 177 | document.fullscreen || 178 | document.webkitIsFullScreen || 179 | document.mozFullScreen 180 | ); 181 | if (! is_full_screen) { 182 | return; 183 | } 184 | 185 | // Clear the previous mouse timeout 186 | if (g_mouse_move_timeout) { 187 | clearTimeout(g_mouse_move_timeout); 188 | g_mouse_move_timeout = null; 189 | } 190 | 191 | // Hide the cursor after 3 seconds 192 | g_mouse_move_timeout = setTimeout(function() { 193 | document.body.style.cursor = 'none'; 194 | $('#screen').style.cursor = 'none'; 195 | g_mouse_move_timeout = null; 196 | }, 3000); 197 | }, false); 198 | 199 | $('#button_toggle_console').addEventListener('click', function() { 200 | if (is_hidden('#output')) { 201 | show('#output'); 202 | this.value = "Console On"; 203 | } else { 204 | hide('#output'); 205 | this.value = "Console Off"; 206 | } 207 | }, false); 208 | 209 | $('#button_toggle_sound').addEventListener('click', function() { 210 | if (Module.toggle_sound()) { 211 | this.value = "Sound On"; 212 | } else { 213 | this.value = "Sound Off"; 214 | } 215 | }, false); 216 | 217 | $('#button_full_screen').addEventListener('click', function() { 218 | // Go full screen 219 | Module.requestFullscreen( 220 | false, 221 | false 222 | ); 223 | g_zoom = 1; 224 | 225 | // Set the background color of the screen holder elements to black. 226 | // This will prevent the white line on the bottom of the screen in Chrome. 227 | let holder = $('#screen_holder'); 228 | let children = holder.children; 229 | for (let i = 0; i < children.length; ++i) { 230 | children[i].style.backgroundColor = 'black'; 231 | } 232 | 233 | }, false); 234 | 235 | $('#button_zoom_in').addEventListener('click', function() { 236 | g_zoom++; 237 | let style = $('#screen').style; 238 | style.width = (g_zoom * 256) + 'px'; 239 | style.height = (g_zoom * 240) + 'px'; 240 | }, false); 241 | 242 | $('#button_zoom_out').addEventListener('click', function() { 243 | if (g_zoom > 1) g_zoom--; 244 | let style = $('#screen').style; 245 | style.width = (g_zoom * 256) + 'px'; 246 | style.height = (g_zoom * 240) + 'px'; 247 | }, false); 248 | 249 | $('#select_game').addEventListener('change', function(event) { 250 | hide('#select_game'); 251 | hide('#fileupload'); 252 | Module.setStatus('Downloading ...'); 253 | 254 | // Save the file name in the module args 255 | let file_name = $('#select_game').value; 256 | Module.arguments = [ file_name ]; 257 | fetchOctetStream(file_name) 258 | .then(data => { 259 | let game_data = new Uint8Array(data); 260 | play_game(game_data); 261 | }) 262 | .catch((error) => { 263 | show('#select_game'); 264 | show('#fileupload'); 265 | Module.setStatus(error.toString()) 266 | }) 267 | }, false); 268 | 269 | $('#fileupload').addEventListener('change', function(event) { 270 | hide('#select_game'); 271 | hide('#fileupload'); 272 | 273 | let fileReader = new FileReader(); 274 | fileReader.onload = function() { 275 | let game_data = new Uint8Array(this.result); 276 | play_game(game_data); 277 | }; 278 | fileReader.readAsArrayBuffer(this.files[0]); 279 | }, false); 280 | 281 | $('#screen').addEventListener('contextmenu', function(event) { 282 | event.preventDefault(); 283 | }, false); 284 | 285 | // Load the large files and show progress 286 | documentOnReady(() => { 287 | downloadAndLoadScript("SaltyNES.wasm", "application/octet-binary", function() { 288 | downloadAndLoadScript("SaltyNES.js", "text/javascript", function() { 289 | if (navigator.userAgent.includes('windows')) { 290 | Module.set_is_windows(); 291 | } 292 | }); 293 | }); 294 | }); 295 | 296 | }); 297 | 298 | if (! ('WebAssembly' in window)) { 299 | document.body.innerHTML = "

This browser does not support WebAssembly.

"; 300 | } else { 301 | main(); 302 | } 303 | -------------------------------------------------------------------------------- /src/CpuInfo.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | // Opdata array: 12 | bool CpuInfo::isOp = false; 13 | array CpuInfo::opdata; 14 | 15 | // Instruction names: 16 | const array CpuInfo::instname = {{ 17 | "ADC", 18 | "AND", 19 | "ASL", 20 | "BCC", 21 | "BCS", 22 | "BEQ", 23 | "BIT", 24 | "BMI", 25 | "BNE", 26 | "BPL", 27 | "BRK", 28 | "BVC", 29 | "BVS", 30 | "CLC", 31 | "CLD", 32 | "CLI", 33 | "CLV", 34 | "CMP", 35 | "CPX", 36 | "CPY", 37 | "DEC", 38 | "DEX", 39 | "DEY", 40 | "EOR", 41 | "INC", 42 | "INX", 43 | "INY", 44 | "JMP", 45 | "JSR", 46 | "LDA", 47 | "LDX", 48 | "LDY", 49 | "LSR", 50 | "NOP", 51 | "ORA", 52 | "PHA", 53 | "PHP", 54 | "PLA", 55 | "PLP", 56 | "ROL", 57 | "ROR", 58 | "RTI", 59 | "RTS", 60 | "SBC", 61 | "SEC", 62 | "SED", 63 | "SEI", 64 | "STA", 65 | "STX", 66 | "STY", 67 | "TAX", 68 | "TAY", 69 | "TSX", 70 | "TXA", 71 | "TXS", 72 | "TYA" 73 | }}; 74 | 75 | // Address mode descriptions: 76 | const array CpuInfo::addrDesc = {{ 77 | "Zero Page ", 78 | "Relative ", 79 | "Implied ", 80 | "Absolute ", 81 | "Accumulator ", 82 | "Immediate ", 83 | "Zero Page,X ", 84 | "Zero Page,Y ", 85 | "Absolute,X ", 86 | "Absolute,Y ", 87 | "Preindexed Indirect ", 88 | "Postindexed Indirect", 89 | "Indirect Absolute " 90 | }}; 91 | 92 | const array CpuInfo::cycTable = { 93 | /*0x00*/7, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 4, 4, 6, 6, 94 | /*0x10*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, 95 | /*0x20*/ 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 4, 4, 6, 6, 96 | /*0x30*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, 97 | /*0x40*/ 6, 6, 2, 8, 3, 3, 5, 5, 3, 2, 2, 2, 3, 4, 6, 6, 98 | /*0x50*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, 99 | /*0x60*/ 6, 6, 2, 8, 3, 3, 5, 5, 4, 2, 2, 2, 5, 4, 6, 6, 100 | /*0x70*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, 101 | /*0x80*/ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, 102 | /*0x90*/ 2, 6, 2, 6, 4, 4, 4, 4, 2, 5, 2, 5, 5, 5, 5, 5, 103 | /*0xA0*/ 2, 6, 2, 6, 3, 3, 3, 3, 2, 2, 2, 2, 4, 4, 4, 4, 104 | /*0xB0*/ 2, 5, 2, 5, 4, 4, 4, 4, 2, 4, 2, 4, 4, 4, 4, 4, 105 | /*0xC0*/ 2, 6, 2, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, 106 | /*0xD0*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7, 107 | /*0xE0*/ 2, 6, 3, 8, 3, 3, 5, 5, 2, 2, 2, 2, 4, 4, 6, 6, 108 | /*0xF0*/ 2, 5, 2, 8, 4, 4, 6, 6, 2, 4, 2, 7, 4, 4, 7, 7 109 | }; 110 | 111 | // Instruction types: 112 | // -------------------------------- // 113 | const int CpuInfo::INS_ADC = 0; 114 | const int CpuInfo::INS_AND = 1; 115 | const int CpuInfo::INS_ASL = 2; 116 | const int CpuInfo::INS_BCC = 3; 117 | const int CpuInfo::INS_BCS = 4; 118 | const int CpuInfo::INS_BEQ = 5; 119 | const int CpuInfo::INS_BIT = 6; 120 | const int CpuInfo::INS_BMI = 7; 121 | const int CpuInfo::INS_BNE = 8; 122 | const int CpuInfo::INS_BPL = 9; 123 | const int CpuInfo::INS_BRK = 10; 124 | const int CpuInfo::INS_BVC = 11; 125 | const int CpuInfo::INS_BVS = 12; 126 | const int CpuInfo::INS_CLC = 13; 127 | const int CpuInfo::INS_CLD = 14; 128 | const int CpuInfo::INS_CLI = 15; 129 | const int CpuInfo::INS_CLV = 16; 130 | const int CpuInfo::INS_CMP = 17; 131 | const int CpuInfo::INS_CPX = 18; 132 | const int CpuInfo::INS_CPY = 19; 133 | const int CpuInfo::INS_DEC = 20; 134 | const int CpuInfo::INS_DEX = 21; 135 | const int CpuInfo::INS_DEY = 22; 136 | const int CpuInfo::INS_EOR = 23; 137 | const int CpuInfo::INS_INC = 24; 138 | const int CpuInfo::INS_INX = 25; 139 | const int CpuInfo::INS_INY = 26; 140 | const int CpuInfo::INS_JMP = 27; 141 | const int CpuInfo::INS_JSR = 28; 142 | const int CpuInfo::INS_LDA = 29; 143 | const int CpuInfo::INS_LDX = 30; 144 | const int CpuInfo::INS_LDY = 31; 145 | const int CpuInfo::INS_LSR = 32; 146 | const int CpuInfo::INS_NOP = 33; 147 | const int CpuInfo::INS_ORA = 34; 148 | const int CpuInfo::INS_PHA = 35; 149 | const int CpuInfo::INS_PHP = 36; 150 | const int CpuInfo::INS_PLA = 37; 151 | const int CpuInfo::INS_PLP = 38; 152 | const int CpuInfo::INS_ROL = 39; 153 | const int CpuInfo::INS_ROR = 40; 154 | const int CpuInfo::INS_RTI = 41; 155 | const int CpuInfo::INS_RTS = 42; 156 | const int CpuInfo::INS_SBC = 43; 157 | const int CpuInfo::INS_SEC = 44; 158 | const int CpuInfo::INS_SED = 45; 159 | const int CpuInfo::INS_SEI = 46; 160 | const int CpuInfo::INS_STA = 47; 161 | const int CpuInfo::INS_STX = 48; 162 | const int CpuInfo::INS_STY = 49; 163 | const int CpuInfo::INS_TAX = 50; 164 | const int CpuInfo::INS_TAY = 51; 165 | const int CpuInfo::INS_TSX = 52; 166 | const int CpuInfo::INS_TXA = 53; 167 | const int CpuInfo::INS_TXS = 54; 168 | const int CpuInfo::INS_TYA = 55; 169 | const int CpuInfo::INS_DUMMY = 56; // dummy instruction used for 'halting' the processor some cycles 170 | // -------------------------------- // 171 | // Addressing modes: 172 | const int CpuInfo::ADDR_ZP = 0; 173 | const int CpuInfo::ADDR_REL = 1; 174 | const int CpuInfo::ADDR_IMP = 2; 175 | const int CpuInfo::ADDR_ABS = 3; 176 | const int CpuInfo::ADDR_ACC = 4; 177 | const int CpuInfo::ADDR_IMM = 5; 178 | const int CpuInfo::ADDR_ZPX = 6; 179 | const int CpuInfo::ADDR_ZPY = 7; 180 | const int CpuInfo::ADDR_ABSX = 8; 181 | const int CpuInfo::ADDR_ABSY = 9; 182 | const int CpuInfo::ADDR_PREIDXIND = 10; 183 | const int CpuInfo::ADDR_POSTIDXIND = 11; 184 | const int CpuInfo::ADDR_INDABS = 12; 185 | 186 | array CpuInfo::getInstNames() { 187 | return instname; 188 | } 189 | 190 | string CpuInfo::getInstName(size_t inst) { 191 | if(inst < instname.size()) { 192 | return instname[inst]; 193 | } else { 194 | return "???"; 195 | } 196 | } 197 | 198 | array CpuInfo::getAddressModeNames() { 199 | return addrDesc; 200 | } 201 | 202 | string CpuInfo::getAddressModeName(int addrMode) { 203 | if(addrMode >= 0 && addrMode < static_cast(addrDesc.size())) { 204 | return addrDesc[addrMode]; 205 | } 206 | return "???"; 207 | } 208 | 209 | void CpuInfo::initOpData() { 210 | if(isOp) 211 | return; 212 | 213 | isOp = true; 214 | 215 | // Set all to invalid instruction (to detect crashes): 216 | opdata.fill(0xFF); 217 | 218 | // Now fill in all valid opcodes: 219 | 220 | // ADC: 221 | setOp(INS_ADC, 0x69, ADDR_IMM, 2, 2); 222 | setOp(INS_ADC, 0x65, ADDR_ZP, 2, 3); 223 | setOp(INS_ADC, 0x75, ADDR_ZPX, 2, 4); 224 | setOp(INS_ADC, 0x6D, ADDR_ABS, 3, 4); 225 | setOp(INS_ADC, 0x7D, ADDR_ABSX, 3, 4); 226 | setOp(INS_ADC, 0x79, ADDR_ABSY, 3, 4); 227 | setOp(INS_ADC, 0x61, ADDR_PREIDXIND, 2, 6); 228 | setOp(INS_ADC, 0x71, ADDR_POSTIDXIND, 2, 5); 229 | 230 | // AND: 231 | setOp(INS_AND, 0x29, ADDR_IMM, 2, 2); 232 | setOp(INS_AND, 0x25, ADDR_ZP, 2, 3); 233 | setOp(INS_AND, 0x35, ADDR_ZPX, 2, 4); 234 | setOp(INS_AND, 0x2D, ADDR_ABS, 3, 4); 235 | setOp(INS_AND, 0x3D, ADDR_ABSX, 3, 4); 236 | setOp(INS_AND, 0x39, ADDR_ABSY, 3, 4); 237 | setOp(INS_AND, 0x21, ADDR_PREIDXIND, 2, 6); 238 | setOp(INS_AND, 0x31, ADDR_POSTIDXIND, 2, 5); 239 | 240 | // ASL: 241 | setOp(INS_ASL, 0x0A, ADDR_ACC, 1, 2); 242 | setOp(INS_ASL, 0x06, ADDR_ZP, 2, 5); 243 | setOp(INS_ASL, 0x16, ADDR_ZPX, 2, 6); 244 | setOp(INS_ASL, 0x0E, ADDR_ABS, 3, 6); 245 | setOp(INS_ASL, 0x1E, ADDR_ABSX, 3, 7); 246 | 247 | // BCC: 248 | setOp(INS_BCC, 0x90, ADDR_REL, 2, 2); 249 | 250 | // BCS: 251 | setOp(INS_BCS, 0xB0, ADDR_REL, 2, 2); 252 | 253 | // BEQ: 254 | setOp(INS_BEQ, 0xF0, ADDR_REL, 2, 2); 255 | 256 | // BIT: 257 | setOp(INS_BIT, 0x24, ADDR_ZP, 2, 3); 258 | setOp(INS_BIT, 0x2C, ADDR_ABS, 3, 4); 259 | 260 | // BMI: 261 | setOp(INS_BMI, 0x30, ADDR_REL, 2, 2); 262 | 263 | // BNE: 264 | setOp(INS_BNE, 0xD0, ADDR_REL, 2, 2); 265 | 266 | // BPL: 267 | setOp(INS_BPL, 0x10, ADDR_REL, 2, 2); 268 | 269 | // BRK: 270 | setOp(INS_BRK, 0x00, ADDR_IMP, 1, 7); 271 | 272 | // BVC: 273 | setOp(INS_BVC, 0x50, ADDR_REL, 2, 2); 274 | 275 | // BVS: 276 | setOp(INS_BVS, 0x70, ADDR_REL, 2, 2); 277 | 278 | // CLC: 279 | setOp(INS_CLC, 0x18, ADDR_IMP, 1, 2); 280 | 281 | // CLD: 282 | setOp(INS_CLD, 0xD8, ADDR_IMP, 1, 2); 283 | 284 | // CLI: 285 | setOp(INS_CLI, 0x58, ADDR_IMP, 1, 2); 286 | 287 | // CLV: 288 | setOp(INS_CLV, 0xB8, ADDR_IMP, 1, 2); 289 | 290 | // CMP: 291 | setOp(INS_CMP, 0xC9, ADDR_IMM, 2, 2); 292 | setOp(INS_CMP, 0xC5, ADDR_ZP, 2, 3); 293 | setOp(INS_CMP, 0xD5, ADDR_ZPX, 2, 4); 294 | setOp(INS_CMP, 0xCD, ADDR_ABS, 3, 4); 295 | setOp(INS_CMP, 0xDD, ADDR_ABSX, 3, 4); 296 | setOp(INS_CMP, 0xD9, ADDR_ABSY, 3, 4); 297 | setOp(INS_CMP, 0xC1, ADDR_PREIDXIND, 2, 6); 298 | setOp(INS_CMP, 0xD1, ADDR_POSTIDXIND, 2, 5); 299 | 300 | // CPX: 301 | setOp(INS_CPX, 0xE0, ADDR_IMM, 2, 2); 302 | setOp(INS_CPX, 0xE4, ADDR_ZP, 2, 3); 303 | setOp(INS_CPX, 0xEC, ADDR_ABS, 3, 4); 304 | 305 | // CPY: 306 | setOp(INS_CPY, 0xC0, ADDR_IMM, 2, 2); 307 | setOp(INS_CPY, 0xC4, ADDR_ZP, 2, 3); 308 | setOp(INS_CPY, 0xCC, ADDR_ABS, 3, 4); 309 | 310 | // DEC: 311 | setOp(INS_DEC, 0xC6, ADDR_ZP, 2, 5); 312 | setOp(INS_DEC, 0xD6, ADDR_ZPX, 2, 6); 313 | setOp(INS_DEC, 0xCE, ADDR_ABS, 3, 6); 314 | setOp(INS_DEC, 0xDE, ADDR_ABSX, 3, 7); 315 | 316 | // DEX: 317 | setOp(INS_DEX, 0xCA, ADDR_IMP, 1, 2); 318 | 319 | // DEY: 320 | setOp(INS_DEY, 0x88, ADDR_IMP, 1, 2); 321 | 322 | // EOR: 323 | setOp(INS_EOR, 0x49, ADDR_IMM, 2, 2); 324 | setOp(INS_EOR, 0x45, ADDR_ZP, 2, 3); 325 | setOp(INS_EOR, 0x55, ADDR_ZPX, 2, 4); 326 | setOp(INS_EOR, 0x4D, ADDR_ABS, 3, 4); 327 | setOp(INS_EOR, 0x5D, ADDR_ABSX, 3, 4); 328 | setOp(INS_EOR, 0x59, ADDR_ABSY, 3, 4); 329 | setOp(INS_EOR, 0x41, ADDR_PREIDXIND, 2, 6); 330 | setOp(INS_EOR, 0x51, ADDR_POSTIDXIND, 2, 5); 331 | 332 | // INC: 333 | setOp(INS_INC, 0xE6, ADDR_ZP, 2, 5); 334 | setOp(INS_INC, 0xF6, ADDR_ZPX, 2, 6); 335 | setOp(INS_INC, 0xEE, ADDR_ABS, 3, 6); 336 | setOp(INS_INC, 0xFE, ADDR_ABSX, 3, 7); 337 | 338 | // INX: 339 | setOp(INS_INX, 0xE8, ADDR_IMP, 1, 2); 340 | 341 | // INY: 342 | setOp(INS_INY, 0xC8, ADDR_IMP, 1, 2); 343 | 344 | // JMP: 345 | setOp(INS_JMP, 0x4C, ADDR_ABS, 3, 3); 346 | setOp(INS_JMP, 0x6C, ADDR_INDABS, 3, 5); 347 | 348 | // JSR: 349 | setOp(INS_JSR, 0x20, ADDR_ABS, 3, 6); 350 | 351 | // LDA: 352 | setOp(INS_LDA, 0xA9, ADDR_IMM, 2, 2); 353 | setOp(INS_LDA, 0xA5, ADDR_ZP, 2, 3); 354 | setOp(INS_LDA, 0xB5, ADDR_ZPX, 2, 4); 355 | setOp(INS_LDA, 0xAD, ADDR_ABS, 3, 4); 356 | setOp(INS_LDA, 0xBD, ADDR_ABSX, 3, 4); 357 | setOp(INS_LDA, 0xB9, ADDR_ABSY, 3, 4); 358 | setOp(INS_LDA, 0xA1, ADDR_PREIDXIND, 2, 6); 359 | setOp(INS_LDA, 0xB1, ADDR_POSTIDXIND, 2, 5); 360 | 361 | 362 | // LDX: 363 | setOp(INS_LDX, 0xA2, ADDR_IMM, 2, 2); 364 | setOp(INS_LDX, 0xA6, ADDR_ZP, 2, 3); 365 | setOp(INS_LDX, 0xB6, ADDR_ZPY, 2, 4); 366 | setOp(INS_LDX, 0xAE, ADDR_ABS, 3, 4); 367 | setOp(INS_LDX, 0xBE, ADDR_ABSY, 3, 4); 368 | 369 | // LDY: 370 | setOp(INS_LDY, 0xA0, ADDR_IMM, 2, 2); 371 | setOp(INS_LDY, 0xA4, ADDR_ZP, 2, 3); 372 | setOp(INS_LDY, 0xB4, ADDR_ZPX, 2, 4); 373 | setOp(INS_LDY, 0xAC, ADDR_ABS, 3, 4); 374 | setOp(INS_LDY, 0xBC, ADDR_ABSX, 3, 4); 375 | 376 | // LSR: 377 | setOp(INS_LSR, 0x4A, ADDR_ACC, 1, 2); 378 | setOp(INS_LSR, 0x46, ADDR_ZP, 2, 5); 379 | setOp(INS_LSR, 0x56, ADDR_ZPX, 2, 6); 380 | setOp(INS_LSR, 0x4E, ADDR_ABS, 3, 6); 381 | setOp(INS_LSR, 0x5E, ADDR_ABSX, 3, 7); 382 | 383 | // NOP: 384 | setOp(INS_NOP, 0xEA, ADDR_IMP, 1, 2); 385 | 386 | // ORA: 387 | setOp(INS_ORA, 0x09, ADDR_IMM, 2, 2); 388 | setOp(INS_ORA, 0x05, ADDR_ZP, 2, 3); 389 | setOp(INS_ORA, 0x15, ADDR_ZPX, 2, 4); 390 | setOp(INS_ORA, 0x0D, ADDR_ABS, 3, 4); 391 | setOp(INS_ORA, 0x1D, ADDR_ABSX, 3, 4); 392 | setOp(INS_ORA, 0x19, ADDR_ABSY, 3, 4); 393 | setOp(INS_ORA, 0x01, ADDR_PREIDXIND, 2, 6); 394 | setOp(INS_ORA, 0x11, ADDR_POSTIDXIND, 2, 5); 395 | 396 | // PHA: 397 | setOp(INS_PHA, 0x48, ADDR_IMP, 1, 3); 398 | 399 | // PHP: 400 | setOp(INS_PHP, 0x08, ADDR_IMP, 1, 3); 401 | 402 | // PLA: 403 | setOp(INS_PLA, 0x68, ADDR_IMP, 1, 4); 404 | 405 | // PLP: 406 | setOp(INS_PLP, 0x28, ADDR_IMP, 1, 4); 407 | 408 | // ROL: 409 | setOp(INS_ROL, 0x2A, ADDR_ACC, 1, 2); 410 | setOp(INS_ROL, 0x26, ADDR_ZP, 2, 5); 411 | setOp(INS_ROL, 0x36, ADDR_ZPX, 2, 6); 412 | setOp(INS_ROL, 0x2E, ADDR_ABS, 3, 6); 413 | setOp(INS_ROL, 0x3E, ADDR_ABSX, 3, 7); 414 | 415 | // ROR: 416 | setOp(INS_ROR, 0x6A, ADDR_ACC, 1, 2); 417 | setOp(INS_ROR, 0x66, ADDR_ZP, 2, 5); 418 | setOp(INS_ROR, 0x76, ADDR_ZPX, 2, 6); 419 | setOp(INS_ROR, 0x6E, ADDR_ABS, 3, 6); 420 | setOp(INS_ROR, 0x7E, ADDR_ABSX, 3, 7); 421 | 422 | // RTI: 423 | setOp(INS_RTI, 0x40, ADDR_IMP, 1, 6); 424 | 425 | // RTS: 426 | setOp(INS_RTS, 0x60, ADDR_IMP, 1, 6); 427 | 428 | // SBC: 429 | setOp(INS_SBC, 0xE9, ADDR_IMM, 2, 2); 430 | setOp(INS_SBC, 0xE5, ADDR_ZP, 2, 3); 431 | setOp(INS_SBC, 0xF5, ADDR_ZPX, 2, 4); 432 | setOp(INS_SBC, 0xED, ADDR_ABS, 3, 4); 433 | setOp(INS_SBC, 0xFD, ADDR_ABSX, 3, 4); 434 | setOp(INS_SBC, 0xF9, ADDR_ABSY, 3, 4); 435 | setOp(INS_SBC, 0xE1, ADDR_PREIDXIND, 2, 6); 436 | setOp(INS_SBC, 0xF1, ADDR_POSTIDXIND, 2, 5); 437 | 438 | // SEC: 439 | setOp(INS_SEC, 0x38, ADDR_IMP, 1, 2); 440 | 441 | // SED: 442 | setOp(INS_SED, 0xF8, ADDR_IMP, 1, 2); 443 | 444 | // SEI: 445 | setOp(INS_SEI, 0x78, ADDR_IMP, 1, 2); 446 | 447 | // STA: 448 | setOp(INS_STA, 0x85, ADDR_ZP, 2, 3); 449 | setOp(INS_STA, 0x95, ADDR_ZPX, 2, 4); 450 | setOp(INS_STA, 0x8D, ADDR_ABS, 3, 4); 451 | setOp(INS_STA, 0x9D, ADDR_ABSX, 3, 5); 452 | setOp(INS_STA, 0x99, ADDR_ABSY, 3, 5); 453 | setOp(INS_STA, 0x81, ADDR_PREIDXIND, 2, 6); 454 | setOp(INS_STA, 0x91, ADDR_POSTIDXIND, 2, 6); 455 | 456 | // STX: 457 | setOp(INS_STX, 0x86, ADDR_ZP, 2, 3); 458 | setOp(INS_STX, 0x96, ADDR_ZPY, 2, 4); 459 | setOp(INS_STX, 0x8E, ADDR_ABS, 3, 4); 460 | 461 | // STY: 462 | setOp(INS_STY, 0x84, ADDR_ZP, 2, 3); 463 | setOp(INS_STY, 0x94, ADDR_ZPX, 2, 4); 464 | setOp(INS_STY, 0x8C, ADDR_ABS, 3, 4); 465 | 466 | // TAX: 467 | setOp(INS_TAX, 0xAA, ADDR_IMP, 1, 2); 468 | 469 | // TAY: 470 | setOp(INS_TAY, 0xA8, ADDR_IMP, 1, 2); 471 | 472 | // TSX: 473 | setOp(INS_TSX, 0xBA, ADDR_IMP, 1, 2); 474 | 475 | // TXA: 476 | setOp(INS_TXA, 0x8A, ADDR_IMP, 1, 2); 477 | 478 | // TXS: 479 | setOp(INS_TXS, 0x9A, ADDR_IMP, 1, 2); 480 | 481 | // TYA: 482 | setOp(INS_TYA, 0x98, ADDR_IMP, 1, 2); 483 | } 484 | 485 | void CpuInfo::setOp(int inst, int op, int addr, int size, int cycles) { 486 | opdata[op] = 487 | ((inst & 0xFF)) | 488 | ((addr & 0xFF) << 8) | 489 | ((size & 0xFF) << 16) | 490 | ((cycles & 0xFF) << 24); 491 | } 492 | -------------------------------------------------------------------------------- /src/sha256sum.cc: -------------------------------------------------------------------------------- 1 | /* The MIT License 2 | 3 | Copyright (C) 2011 Zilong Tan (tzlloch@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 20 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 21 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | */ 25 | 26 | /* 27 | * Original code is derived from the author: 28 | * Allan Saddi 29 | */ 30 | 31 | #include 32 | #include 33 | #include "sha256sum.h" 34 | 35 | #define ROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) 36 | #define ROTR(x, n) (((x) >> (n)) | ((x) << (32 - (n)))) 37 | 38 | #define Ch(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) 39 | #define Maj(x, y, z) (((x) & ((y) | (z))) | ((y) & (z))) 40 | #define SIGMA0(x) (ROTR((x), 2) ^ ROTR((x), 13) ^ ROTR((x), 22)) 41 | #define SIGMA1(x) (ROTR((x), 6) ^ ROTR((x), 11) ^ ROTR((x), 25)) 42 | #define sigma0(x) (ROTR((x), 7) ^ ROTR((x), 18) ^ ((x) >> 3)) 43 | #define sigma1(x) (ROTR((x), 17) ^ ROTR((x), 19) ^ ((x) >> 10)) 44 | 45 | #define DO_ROUND() { \ 46 | t1 = h + SIGMA1(e) + Ch(e, f, g) + *(Kp++) + *(W++); \ 47 | t2 = SIGMA0(a) + Maj(a, b, c); \ 48 | h = g; \ 49 | g = f; \ 50 | f = e; \ 51 | e = d + t1; \ 52 | d = c; \ 53 | c = b; \ 54 | b = a; \ 55 | a = t1 + t2; \ 56 | } 57 | 58 | static const uint32_t K[64] = { 59 | 0x428a2f98L, 0x71374491L, 0xb5c0fbcfL, 0xe9b5dba5L, 60 | 0x3956c25bL, 0x59f111f1L, 0x923f82a4L, 0xab1c5ed5L, 61 | 0xd807aa98L, 0x12835b01L, 0x243185beL, 0x550c7dc3L, 62 | 0x72be5d74L, 0x80deb1feL, 0x9bdc06a7L, 0xc19bf174L, 63 | 0xe49b69c1L, 0xefbe4786L, 0x0fc19dc6L, 0x240ca1ccL, 64 | 0x2de92c6fL, 0x4a7484aaL, 0x5cb0a9dcL, 0x76f988daL, 65 | 0x983e5152L, 0xa831c66dL, 0xb00327c8L, 0xbf597fc7L, 66 | 0xc6e00bf3L, 0xd5a79147L, 0x06ca6351L, 0x14292967L, 67 | 0x27b70a85L, 0x2e1b2138L, 0x4d2c6dfcL, 0x53380d13L, 68 | 0x650a7354L, 0x766a0abbL, 0x81c2c92eL, 0x92722c85L, 69 | 0xa2bfe8a1L, 0xa81a664bL, 0xc24b8b70L, 0xc76c51a3L, 70 | 0xd192e819L, 0xd6990624L, 0xf40e3585L, 0x106aa070L, 71 | 0x19a4c116L, 0x1e376c08L, 0x2748774cL, 0x34b0bcb5L, 72 | 0x391c0cb3L, 0x4ed8aa4aL, 0x5b9cca4fL, 0x682e6ff3L, 73 | 0x748f82eeL, 0x78a5636fL, 0x84c87814L, 0x8cc70208L, 74 | 0x90befffaL, 0xa4506cebL, 0xbef9a3f7L, 0xc67178f2L 75 | }; 76 | 77 | #ifndef RUNTIME_ENDIAN 78 | 79 | #ifdef WORDS_BIGENDIAN 80 | 81 | #define BYTESWAP(x) (x) 82 | #define BYTESWAP64(x) (x) 83 | 84 | #else /* WORDS_BIGENDIAN */ 85 | 86 | #define BYTESWAP(x) ((ROTR((x), 8) & 0xff00ff00L) | \ 87 | (ROTL((x), 8) & 0x00ff00ffL)) 88 | #define BYTESWAP64(x) _byteswap64(x) 89 | 90 | static inline uint64_t _byteswap64(uint64_t x) 91 | { 92 | uint32_t a = x >> 32; 93 | uint32_t b = static_cast(x); 94 | return (static_cast(BYTESWAP(b)) << 32) | static_cast(BYTESWAP(a)); 95 | } 96 | 97 | #endif /* WORDS_BIGENDIAN */ 98 | 99 | #else /* !RUNTIME_ENDIAN */ 100 | 101 | static int littleEndian; 102 | 103 | #define BYTESWAP(x) _byteswap(x) 104 | #define BYTESWAP64(x) _byteswap64(x) 105 | 106 | #define _BYTESWAP(x) ((ROTR((x), 8) & 0xff00ff00L) | \ 107 | (ROTL((x), 8) & 0x00ff00ffL)) 108 | #define _BYTESWAP64(x) __byteswap64(x) 109 | 110 | static inline uint64_t __byteswap64(uint64_t x) 111 | { 112 | uint32_t a = x >> 32; 113 | uint32_t b = (uint32_t) x; 114 | return ((uint64_t) _BYTESWAP(b) << 32) | (uint64_t) _BYTESWAP(a); 115 | } 116 | 117 | static inline uint32_t _byteswap(uint32_t x) 118 | { 119 | if(!littleEndian) 120 | return x; 121 | else 122 | return _BYTESWAP(x); 123 | } 124 | 125 | static inline uint64_t _byteswap64(uint64_t x) 126 | { 127 | if(!littleEndian) 128 | return x; 129 | else 130 | return _BYTESWAP64(x); 131 | } 132 | 133 | static inline void setEndian(void) 134 | { 135 | union { 136 | uint32_t w; 137 | uint8_t b[4]; 138 | } endian; 139 | 140 | endian.w = 1L; 141 | littleEndian = endian.b[0] != 0; 142 | } 143 | 144 | #endif /* !RUNTIME_ENDIAN */ 145 | 146 | static const uint8_t padding[64] = { 147 | 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 148 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 149 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 150 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 151 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 152 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 153 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 154 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 155 | }; 156 | 157 | void SHA256Init(SHA256Context * sc) 158 | { 159 | #ifdef RUNTIME_ENDIAN 160 | setEndian(); 161 | #endif /* RUNTIME_ENDIAN */ 162 | 163 | sc->totalLength = 0LL; 164 | sc->hash[0] = 0x6a09e667L; 165 | sc->hash[1] = 0xbb67ae85L; 166 | sc->hash[2] = 0x3c6ef372L; 167 | sc->hash[3] = 0xa54ff53aL; 168 | sc->hash[4] = 0x510e527fL; 169 | sc->hash[5] = 0x9b05688cL; 170 | sc->hash[6] = 0x1f83d9abL; 171 | sc->hash[7] = 0x5be0cd19L; 172 | sc->bufferLength = 0L; 173 | } 174 | 175 | static void burnStack(int size) 176 | { 177 | char buf[128]; 178 | 179 | memset(buf, 0, sizeof(buf)); 180 | size -= sizeof(buf); 181 | if(size > 0) 182 | burnStack(size); 183 | } 184 | 185 | static void SHA256Guts(SHA256Context * sc, const uint32_t * cbuf) 186 | { 187 | uint32_t buf[64]; 188 | uint32_t *W, *W2, *W7, *W15, *W16; 189 | uint32_t a, b, c, d, e, f, g, h; 190 | uint32_t t1, t2; 191 | const uint32_t *Kp; 192 | int i; 193 | 194 | W = buf; 195 | 196 | for(i = 15; i >= 0; i--) { 197 | *(W++) = BYTESWAP(*cbuf); 198 | cbuf++; 199 | } 200 | 201 | W16 = &buf[0]; 202 | W15 = &buf[1]; 203 | W7 = &buf[9]; 204 | W2 = &buf[14]; 205 | 206 | for(i = 47; i >= 0; i--) { 207 | *(W++) = sigma1(*W2) + *(W7++) + sigma0(*W15) + *(W16++); 208 | W2++; 209 | W15++; 210 | } 211 | 212 | a = sc->hash[0]; 213 | b = sc->hash[1]; 214 | c = sc->hash[2]; 215 | d = sc->hash[3]; 216 | e = sc->hash[4]; 217 | f = sc->hash[5]; 218 | g = sc->hash[6]; 219 | h = sc->hash[7]; 220 | 221 | Kp = K; 222 | W = buf; 223 | 224 | #ifndef SHA256_UNROLL 225 | #define SHA256_UNROLL 1 226 | #endif /* !SHA256_UNROLL */ 227 | 228 | #if SHA256_UNROLL == 1 229 | for(i = 63; i >= 0; i--) 230 | DO_ROUND(); 231 | #elif SHA256_UNROLL == 2 232 | for(i = 31; i >= 0; i--) { 233 | DO_ROUND(); 234 | DO_ROUND(); 235 | } 236 | #elif SHA256_UNROLL == 4 237 | for(i = 15; i >= 0; i--) { 238 | DO_ROUND(); 239 | DO_ROUND(); 240 | DO_ROUND(); 241 | DO_ROUND(); 242 | } 243 | #elif SHA256_UNROLL == 8 244 | for(i = 7; i >= 0; i--) { 245 | DO_ROUND(); 246 | DO_ROUND(); 247 | DO_ROUND(); 248 | DO_ROUND(); 249 | DO_ROUND(); 250 | DO_ROUND(); 251 | DO_ROUND(); 252 | DO_ROUND(); 253 | } 254 | #elif SHA256_UNROLL == 16 255 | for(i = 3; i >= 0; i--) { 256 | DO_ROUND(); 257 | DO_ROUND(); 258 | DO_ROUND(); 259 | DO_ROUND(); 260 | DO_ROUND(); 261 | DO_ROUND(); 262 | DO_ROUND(); 263 | DO_ROUND(); 264 | DO_ROUND(); 265 | DO_ROUND(); 266 | DO_ROUND(); 267 | DO_ROUND(); 268 | DO_ROUND(); 269 | DO_ROUND(); 270 | DO_ROUND(); 271 | DO_ROUND(); 272 | } 273 | #elif SHA256_UNROLL == 32 274 | for(i = 1; i >= 0; i--) { 275 | DO_ROUND(); 276 | DO_ROUND(); 277 | DO_ROUND(); 278 | DO_ROUND(); 279 | DO_ROUND(); 280 | DO_ROUND(); 281 | DO_ROUND(); 282 | DO_ROUND(); 283 | DO_ROUND(); 284 | DO_ROUND(); 285 | DO_ROUND(); 286 | DO_ROUND(); 287 | DO_ROUND(); 288 | DO_ROUND(); 289 | DO_ROUND(); 290 | DO_ROUND(); 291 | DO_ROUND(); 292 | DO_ROUND(); 293 | DO_ROUND(); 294 | DO_ROUND(); 295 | DO_ROUND(); 296 | DO_ROUND(); 297 | DO_ROUND(); 298 | DO_ROUND(); 299 | DO_ROUND(); 300 | DO_ROUND(); 301 | DO_ROUND(); 302 | DO_ROUND(); 303 | DO_ROUND(); 304 | DO_ROUND(); 305 | DO_ROUND(); 306 | DO_ROUND(); 307 | } 308 | #elif SHA256_UNROLL == 64 309 | DO_ROUND(); 310 | DO_ROUND(); 311 | DO_ROUND(); 312 | DO_ROUND(); 313 | DO_ROUND(); 314 | DO_ROUND(); 315 | DO_ROUND(); 316 | DO_ROUND(); 317 | DO_ROUND(); 318 | DO_ROUND(); 319 | DO_ROUND(); 320 | DO_ROUND(); 321 | DO_ROUND(); 322 | DO_ROUND(); 323 | DO_ROUND(); 324 | DO_ROUND(); 325 | DO_ROUND(); 326 | DO_ROUND(); 327 | DO_ROUND(); 328 | DO_ROUND(); 329 | DO_ROUND(); 330 | DO_ROUND(); 331 | DO_ROUND(); 332 | DO_ROUND(); 333 | DO_ROUND(); 334 | DO_ROUND(); 335 | DO_ROUND(); 336 | DO_ROUND(); 337 | DO_ROUND(); 338 | DO_ROUND(); 339 | DO_ROUND(); 340 | DO_ROUND(); 341 | DO_ROUND(); 342 | DO_ROUND(); 343 | DO_ROUND(); 344 | DO_ROUND(); 345 | DO_ROUND(); 346 | DO_ROUND(); 347 | DO_ROUND(); 348 | DO_ROUND(); 349 | DO_ROUND(); 350 | DO_ROUND(); 351 | DO_ROUND(); 352 | DO_ROUND(); 353 | DO_ROUND(); 354 | DO_ROUND(); 355 | DO_ROUND(); 356 | DO_ROUND(); 357 | DO_ROUND(); 358 | DO_ROUND(); 359 | DO_ROUND(); 360 | DO_ROUND(); 361 | DO_ROUND(); 362 | DO_ROUND(); 363 | DO_ROUND(); 364 | DO_ROUND(); 365 | DO_ROUND(); 366 | DO_ROUND(); 367 | DO_ROUND(); 368 | DO_ROUND(); 369 | DO_ROUND(); 370 | DO_ROUND(); 371 | DO_ROUND(); 372 | DO_ROUND(); 373 | #else 374 | #error "SHA256_UNROLL must be 1, 2, 4, 8, 16, 32, or 64!" 375 | #endif 376 | 377 | sc->hash[0] += a; 378 | sc->hash[1] += b; 379 | sc->hash[2] += c; 380 | sc->hash[3] += d; 381 | sc->hash[4] += e; 382 | sc->hash[5] += f; 383 | sc->hash[6] += g; 384 | sc->hash[7] += h; 385 | } 386 | 387 | void SHA256Update(SHA256Context * sc, const void *data, uint32_t len) 388 | { 389 | uint32_t bufferBytesLeft; 390 | uint32_t bytesToCopy; 391 | int needBurn = 0; 392 | 393 | if(sc->bufferLength) { 394 | bufferBytesLeft = 64L - sc->bufferLength; 395 | 396 | bytesToCopy = bufferBytesLeft; 397 | if(bytesToCopy > len) 398 | bytesToCopy = len; 399 | 400 | memcpy(&sc->buffer.bytes[sc->bufferLength], data, bytesToCopy); 401 | 402 | sc->totalLength += bytesToCopy * 8L; 403 | 404 | sc->bufferLength += bytesToCopy; 405 | data = reinterpret_cast(data) + bytesToCopy; 406 | len -= bytesToCopy; 407 | 408 | if(sc->bufferLength == 64L) { 409 | SHA256Guts(sc, sc->buffer.words); 410 | needBurn = 1; 411 | sc->bufferLength = 0L; 412 | } 413 | } 414 | 415 | while(len > 63L) { 416 | sc->totalLength += 512L; 417 | 418 | SHA256Guts(sc, reinterpret_cast(data)); 419 | needBurn = 1; 420 | 421 | data = reinterpret_cast(data) + 64L; 422 | len -= 64L; 423 | } 424 | 425 | if(len) { 426 | memcpy(&sc->buffer.bytes[sc->bufferLength], data, len); 427 | 428 | sc->totalLength += len * 8L; 429 | 430 | sc->bufferLength += len; 431 | } 432 | 433 | if(needBurn) 434 | burnStack(sizeof(uint32_t[74]) + sizeof(uint32_t *[6]) + 435 | sizeof(int)); 436 | } 437 | 438 | void SHA256Final(SHA256Context * sc, uint8_t hash[SHA256_HASH_SIZE]) 439 | { 440 | uint32_t bytesToPad; 441 | uint64_t lengthPad; 442 | int i; 443 | 444 | bytesToPad = 120L - sc->bufferLength; 445 | if(bytesToPad > 64L) 446 | bytesToPad -= 64L; 447 | 448 | lengthPad = BYTESWAP64(sc->totalLength); 449 | 450 | SHA256Update(sc, padding, bytesToPad); 451 | SHA256Update(sc, &lengthPad, 8L); 452 | 453 | if(hash) { 454 | for(i = 0; i < SHA256_HASH_WORDS; i++) { 455 | *reinterpret_cast(hash) = BYTESWAP(sc->hash[i]); 456 | hash += 4; 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /src/MapperDefault.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | #include "SaltyNES.h" 10 | 11 | 12 | MapperDefault::MapperDefault() : enable_shared_from_this() { 13 | } 14 | 15 | shared_ptr MapperDefault::Init(shared_ptr nes) { 16 | cpuMem = nullptr; 17 | ppuMem = nullptr; 18 | cpuMemArray = nullptr; 19 | rom = nullptr; 20 | cpu = nullptr; 21 | ppu = nullptr; 22 | cpuMemSize = 0; 23 | joy1StrobeState = 0; 24 | joy2StrobeState = 0; 25 | joypadLastWrite = 0; 26 | mousePressed = false; 27 | gameGenieActive = false; 28 | mouseX = 0; 29 | mouseY = 0; 30 | tmp = 0; 31 | 32 | this->base_init(nes); 33 | return shared_from_this(); 34 | } 35 | 36 | MapperDefault::~MapperDefault() { 37 | nes = nullptr; 38 | cpuMem = nullptr; 39 | ppuMem = nullptr; 40 | rom = nullptr; 41 | cpu = nullptr; 42 | ppu = nullptr; 43 | } 44 | 45 | void MapperDefault::write(int address, uint16_t value) { 46 | base_write(address, value); 47 | } 48 | 49 | void MapperDefault::base_init(shared_ptr nes) { 50 | this->nes = nes; 51 | this->cpuMem = nes->getCpuMemory(); 52 | this->cpuMemArray = &(cpuMem->mem); 53 | this->ppuMem = nes->getPpuMemory(); 54 | this->rom = nes->getRom(); 55 | this->cpu = nes->getCpu(); 56 | this->ppu = nes->getPpu(); 57 | 58 | cpuMemSize = cpuMem->getMemSize(); 59 | joypadLastWrite = -1; 60 | 61 | } 62 | 63 | void MapperDefault::stateLoad(ByteBuffer* buf) { 64 | // Check version: 65 | if(buf->readByte() == 1) { 66 | 67 | // Joypad stuff: 68 | joy1StrobeState = buf->readInt(); 69 | joy2StrobeState = buf->readInt(); 70 | joypadLastWrite = buf->readInt(); 71 | 72 | // Mapper specific stuff: 73 | base_mapperInternalStateLoad(buf); 74 | 75 | } 76 | } 77 | 78 | void MapperDefault::stateSave(ByteBuffer* buf) { 79 | // Version: 80 | buf->putByte(static_cast(1)); 81 | 82 | // Joypad stuff: 83 | buf->putInt(joy1StrobeState); 84 | buf->putInt(joy2StrobeState); 85 | buf->putInt(joypadLastWrite); 86 | 87 | // Mapper specific stuff: 88 | base_mapperInternalStateSave(buf); 89 | } 90 | 91 | void MapperDefault::base_mapperInternalStateLoad(ByteBuffer* buf) { 92 | buf->putByte(static_cast(joy1StrobeState)); 93 | buf->putByte(static_cast(joy2StrobeState)); 94 | buf->putByte(static_cast(joypadLastWrite)); 95 | 96 | } 97 | 98 | void MapperDefault::base_mapperInternalStateSave(ByteBuffer* buf) { 99 | joy1StrobeState = buf->readByte(); 100 | joy2StrobeState = buf->readByte(); 101 | joypadLastWrite = buf->readByte(); 102 | } 103 | 104 | void MapperDefault::setGameGenieState(bool enable) { 105 | gameGenieActive = enable; 106 | } 107 | 108 | bool MapperDefault::getGameGenieState() { 109 | return gameGenieActive; 110 | } 111 | 112 | void MapperDefault::base_write(int address, uint16_t value) { 113 | if(address < 0x2000) { 114 | 115 | // Mirroring of RAM: 116 | cpuMem->mem[address & 0x7FF] = value; 117 | 118 | } else if(address > 0x4017) { 119 | 120 | cpuMem->mem[address] = value; 121 | if(address >= 0x6000 && address < 0x8000) { 122 | 123 | // Write to SaveRAM. Store in file: 124 | if(rom != nullptr) { 125 | rom->writeBatteryRam(address, value); 126 | } 127 | 128 | } 129 | 130 | } else if(address > 0x2007 && address < 0x4000) { 131 | 132 | regWrite(0x2000 + (address & 0x7), value); 133 | 134 | } else { 135 | 136 | regWrite(address, value); 137 | 138 | } 139 | 140 | } 141 | 142 | void MapperDefault::writelow(int address, uint16_t value) { 143 | if(address < 0x2000) { 144 | // Mirroring of RAM: 145 | cpuMem->mem[address & 0x7FF] = value; 146 | 147 | } else if(address > 0x4017) { 148 | cpuMem->mem[address] = value; 149 | 150 | } else if(address > 0x2007 && address < 0x4000) { 151 | regWrite(0x2000 + (address & 0x7), value); 152 | 153 | } else { 154 | regWrite(address, value); 155 | } 156 | 157 | } 158 | 159 | uint16_t MapperDefault::load(int address) { 160 | return base_load(address); 161 | } 162 | 163 | uint16_t MapperDefault::base_load(int address) { 164 | // Wrap around: 165 | address &= 0xFFFF; 166 | 167 | // Check address range: 168 | if(address > 0x4017) { 169 | 170 | // ROM: 171 | return (*cpuMemArray)[address]; 172 | 173 | } else if(address >= 0x2000) { 174 | // I/O Ports. 175 | return regLoad(address); 176 | } else { 177 | // RAM (mirrored) 178 | return (*cpuMemArray)[address & 0x7FF]; 179 | } 180 | } 181 | 182 | uint16_t MapperDefault::regLoad(int address) { 183 | switch (address >> 12) { // use fourth nibble (0xF000) 184 | case 0: { 185 | break; 186 | } 187 | case 1: { 188 | break; 189 | } 190 | case 2: { 191 | // Fall through to case 3 192 | } 193 | case 3: { 194 | 195 | // PPU Registers 196 | switch (address & 0x7) { 197 | case 0x0: { 198 | 199 | // 0x2000: 200 | // PPU Control Register 1. 201 | // (the value is stored both 202 | // in main memory and in the 203 | // PPU as flags): 204 | // (not in the real NES) 205 | return cpuMem->mem[0x2000]; 206 | 207 | } 208 | case 0x1: { 209 | 210 | // 0x2001: 211 | // PPU Control Register 2. 212 | // (the value is stored both 213 | // in main memory and in the 214 | // PPU as flags): 215 | // (not in the real NES) 216 | return cpuMem->mem[0x2001]; 217 | 218 | } 219 | case 0x2: { 220 | 221 | // 0x2002: 222 | // PPU Status Register. 223 | // The value is stored in 224 | // main memory in addition 225 | // to as flags in the PPU. 226 | // (not in the real NES) 227 | return ppu->readStatusRegister(); 228 | 229 | } 230 | case 0x3: { 231 | return 0; 232 | } 233 | case 0x4: { 234 | 235 | // 0x2004: 236 | // Sprite Memory read. 237 | return ppu->sramLoad(); 238 | 239 | } 240 | case 0x5: { 241 | return 0; 242 | } 243 | case 0x6: { 244 | return 0; 245 | } 246 | case 0x7: { 247 | 248 | // 0x2007: 249 | // VRAM read: 250 | return ppu->vramLoad(); 251 | 252 | } 253 | } 254 | break; 255 | 256 | } 257 | case 4: { 258 | 259 | 260 | // Sound+Joypad registers 261 | 262 | switch (address - 0x4015) { 263 | case 0: { 264 | 265 | // 0x4015: 266 | // Sound channel enable, DMC Status 267 | return nes->getPapu()->readReg(); 268 | 269 | } 270 | case 1: { 271 | 272 | // 0x4016: 273 | // Joystick 1 + Strobe 274 | return joy1Read(); 275 | 276 | } 277 | case 2: { 278 | 279 | // 0x4017: 280 | // Joystick 2 + Strobe 281 | if(mousePressed && nes->ppu != nullptr && nes->ppu->get_screen_buffer() != nullptr) { 282 | 283 | // Check for white pixel nearby: 284 | 285 | int sx, sy, ex, ey, w; 286 | sx = max(0, mouseX - 4); 287 | ex = min(256, mouseX + 4); 288 | sy = max(0, mouseY - 4); 289 | ey = min(240, mouseY + 4); 290 | w = 0; 291 | 292 | for(int y = sy; y < ey; ++y) { 293 | for(int x = sx; x < ex; ++x) { 294 | if(((*nes->ppu->get_screen_buffer())[(y << 8) + x] & 0xFFFFFF) == 0xFFFFFF) { 295 | w = 0x1 << 3; 296 | break; 297 | } 298 | } 299 | } 300 | 301 | w |= (mousePressed ? (0x1 << 4) : 0); 302 | return static_cast(joy2Read() | w); 303 | 304 | } else { 305 | return joy2Read(); 306 | } 307 | 308 | } 309 | } 310 | 311 | break; 312 | 313 | } 314 | } 315 | 316 | return 0; 317 | 318 | } 319 | 320 | void MapperDefault::regWrite(int address, uint16_t value) { 321 | switch (address) { 322 | case 0x2000: { 323 | 324 | // PPU Control register 1 325 | cpuMem->write(address, value); 326 | ppu->updateControlReg1(value); 327 | break; 328 | 329 | } 330 | case 0x2001: { 331 | 332 | // PPU Control register 2 333 | cpuMem->write(address, value); 334 | ppu->updateControlReg2(value); 335 | break; 336 | 337 | } 338 | case 0x2003: { 339 | 340 | // Set Sprite RAM address: 341 | ppu->writeSRAMAddress(value); 342 | break; 343 | 344 | } 345 | case 0x2004: { 346 | 347 | // Write to Sprite RAM: 348 | ppu->sramWrite(value); 349 | break; 350 | 351 | } 352 | case 0x2005: { 353 | 354 | // Screen Scroll offsets: 355 | ppu->scrollWrite(value); 356 | break; 357 | 358 | } 359 | case 0x2006: { 360 | 361 | // Set VRAM address: 362 | ppu->writeVRAMAddress(value); 363 | break; 364 | 365 | } 366 | case 0x2007: { 367 | 368 | // Write to VRAM: 369 | ppu->vramWrite(value); 370 | break; 371 | 372 | } 373 | case 0x4014: { 374 | 375 | // Sprite Memory DMA Access 376 | ppu->sramDMA(value); 377 | break; 378 | 379 | } 380 | case 0x4015: { 381 | 382 | // Sound Channel Switch, DMC Status 383 | nes->getPapu()->writeReg(address, value); 384 | break; 385 | 386 | } 387 | case 0x4016: { 388 | 389 | ////System.out.println("joy strobe write "+value); 390 | 391 | // Joystick 1 + Strobe 392 | if(value == 0 && joypadLastWrite == 1) { 393 | ////System.out.println("Strobes reset."); 394 | joy1StrobeState = 0; 395 | joy2StrobeState = 0; 396 | } 397 | joypadLastWrite = value; 398 | break; 399 | 400 | } 401 | case 0x4017: { 402 | 403 | // Sound channel frame sequencer: 404 | nes->papu->writeReg(address, value); 405 | break; 406 | 407 | } 408 | default: { 409 | 410 | // Sound registers 411 | ////System.out.println("write to sound reg"); 412 | if(address >= 0x4000 && address <= 0x4017) { 413 | nes->getPapu()->writeReg(address, value); 414 | } 415 | break; 416 | 417 | } 418 | } 419 | 420 | } 421 | 422 | uint16_t MapperDefault::joy1Read() { 423 | uint16_t ret = 0; 424 | shared_ptr in = nes->_joy1; 425 | 426 | switch (joy1StrobeState) { 427 | case 0: 428 | ret = in->getKeyState(InputHandler::KEY_A); 429 | break; 430 | case 1: 431 | ret = in->getKeyState(InputHandler::KEY_B); 432 | break; 433 | case 2: 434 | ret = in->getKeyState(InputHandler::KEY_SELECT); 435 | break; 436 | case 3: 437 | ret = in->getKeyState(InputHandler::KEY_START); 438 | break; 439 | case 4: 440 | ret = in->getKeyState(InputHandler::KEY_UP); 441 | break; 442 | case 5: 443 | ret = in->getKeyState(InputHandler::KEY_DOWN); 444 | break; 445 | case 6: 446 | ret = in->getKeyState(InputHandler::KEY_LEFT); 447 | break; 448 | case 7: 449 | ret = in->getKeyState(InputHandler::KEY_RIGHT); 450 | break; 451 | case 8: 452 | case 9: 453 | case 10: 454 | case 11: 455 | case 12: 456 | case 13: 457 | case 14: 458 | case 15: 459 | case 16: 460 | case 17: 461 | case 18: 462 | ret = static_cast(0); 463 | break; 464 | case 19: 465 | ret = static_cast(1); 466 | break; 467 | default: 468 | ret = 0; 469 | } 470 | 471 | ++joy1StrobeState; 472 | if(joy1StrobeState == 24) { 473 | joy1StrobeState = 0; 474 | } 475 | 476 | return ret; 477 | } 478 | 479 | uint16_t MapperDefault::joy2Read() { 480 | uint16_t ret = 0; 481 | shared_ptr in = nes->_joy2; 482 | 483 | switch (joy2StrobeState) { 484 | case 0: 485 | ret = in->getKeyState(InputHandler::KEY_A); 486 | break; 487 | case 1: 488 | ret = in->getKeyState(InputHandler::KEY_B); 489 | break; 490 | case 2: 491 | ret = in->getKeyState(InputHandler::KEY_SELECT); 492 | break; 493 | case 3: 494 | ret = in->getKeyState(InputHandler::KEY_START); 495 | break; 496 | case 4: 497 | ret = in->getKeyState(InputHandler::KEY_UP); 498 | break; 499 | case 5: 500 | ret = in->getKeyState(InputHandler::KEY_DOWN); 501 | break; 502 | case 6: 503 | ret = in->getKeyState(InputHandler::KEY_LEFT); 504 | break; 505 | case 7: 506 | ret = in->getKeyState(InputHandler::KEY_RIGHT); 507 | break; 508 | case 8: 509 | case 9: 510 | case 10: 511 | case 11: 512 | case 12: 513 | case 13: 514 | case 14: 515 | case 15: 516 | case 16: 517 | case 17: 518 | case 18: 519 | ret = static_cast(0); 520 | break; 521 | case 19: 522 | ret = static_cast(1); 523 | break; 524 | default: 525 | ret = 0; 526 | } 527 | 528 | ++joy2StrobeState; 529 | if(joy2StrobeState == 24) { 530 | joy2StrobeState = 0; 531 | } 532 | 533 | return ret; 534 | } 535 | 536 | void MapperDefault::loadROM(shared_ptr rom) { 537 | if(!rom->isValid() || rom->getRomBankCount() < 1) { 538 | //System.out.println("NoMapper: Invalid ROM! Unable to load."); 539 | return; 540 | } 541 | 542 | // Load ROM into memory: 543 | loadPRGROM(); 544 | 545 | // Load CHR-ROM: 546 | loadCHRROM(); 547 | 548 | // Load Battery RAM (if present): 549 | loadBatteryRam(); 550 | 551 | // Reset IRQ: 552 | //nes->getCpu().doResetInterrupt(); 553 | nes->getCpu()->requestIrq(CPU::IRQ_RESET); 554 | 555 | } 556 | 557 | void MapperDefault::loadPRGROM() { 558 | if(rom->getRomBankCount() > 1) { 559 | // Load the two first banks into memory. 560 | loadRomBank(0, 0x8000); 561 | loadRomBank(1, 0xC000); 562 | } else { 563 | // Load the one bank into both memory locations: 564 | loadRomBank(0, 0x8000); 565 | loadRomBank(0, 0xC000); 566 | } 567 | } 568 | 569 | void MapperDefault::loadCHRROM() { 570 | ////System.out.println("Loading CHR ROM.."); 571 | 572 | if(rom->getVromBankCount() > 0) { 573 | if(rom->getVromBankCount() == 1) { 574 | loadVromBank(0, 0x0000); 575 | loadVromBank(0, 0x1000); 576 | } else { 577 | loadVromBank(0, 0x0000); 578 | loadVromBank(1, 0x1000); 579 | } 580 | } else { 581 | //System.out.println("There aren't any CHR-ROM banks.."); 582 | } 583 | } 584 | 585 | void MapperDefault::loadBatteryRam() { 586 | if(rom->batteryRam) { 587 | array* ram = rom->getBatteryRam(); 588 | if(ram != nullptr && ram->size() == 0x2000) { 589 | array_copy(ram, 0, &nes->cpuMem->mem, 0x6000, 0x2000); 590 | } 591 | } 592 | } 593 | 594 | void MapperDefault::loadRomBank(int bank, int address) { 595 | // Loads a ROM bank into the specified address. 596 | bank %= rom->getRomBankCount(); 597 | //array* data = rom->getRomBank(bank); 598 | //cpuMem->write(address,data,data.length); 599 | array_copy(rom->getRomBank(bank), 0, &cpuMem->mem, address, 16384); 600 | 601 | } 602 | 603 | void MapperDefault::loadVromBank(int bank, int address) { 604 | if(rom->getVromBankCount() == 0) { 605 | return; 606 | } 607 | ppu->triggerRendering(); 608 | 609 | array_copy(rom->getVromBank(bank % rom->getVromBankCount()), 0, &nes->ppuMem->mem, address, 4096); 610 | 611 | array* vromTile = rom->getVromBankTiles(bank % rom->getVromBankCount()); 612 | array_copy(vromTile, 0, &ppu->ptTile, address >> 4, 256); 613 | } 614 | 615 | void MapperDefault::load32kRomBank(int bank, int address) { 616 | loadRomBank((bank * 2) % rom->getRomBankCount(), address); 617 | loadRomBank((bank * 2 + 1) % rom->getRomBankCount(), address + 16384); 618 | } 619 | 620 | void MapperDefault::load8kVromBank(int bank4kStart, int address) { 621 | if(rom->getVromBankCount() == 0) { 622 | return; 623 | } 624 | ppu->triggerRendering(); 625 | 626 | loadVromBank((bank4kStart) % rom->getVromBankCount(), address); 627 | loadVromBank((bank4kStart + 1) % rom->getVromBankCount(), address + 4096); 628 | } 629 | 630 | void MapperDefault::load1kVromBank(int bank1k, int address) { 631 | if(rom->getVromBankCount() == 0) { 632 | return; 633 | } 634 | ppu->triggerRendering(); 635 | 636 | int bank4k = (bank1k / 4) % rom->getVromBankCount(); 637 | int bankoffset = (bank1k % 4) * 1024; 638 | array_copy(rom->getVromBank(bank4k), 0, &nes->ppuMem->mem, bankoffset, 1024); 639 | 640 | // Update tiles: 641 | array* vromTile = rom->getVromBankTiles(bank4k); 642 | int baseIndex = address >> 4; 643 | for(int i = 0; i < 64; ++i) { 644 | ppu->ptTile[baseIndex + i] = (*vromTile)[((bank1k % 4) << 6) + i]; 645 | } 646 | } 647 | 648 | void MapperDefault::load2kVromBank(int bank2k, int address) { 649 | if(rom->getVromBankCount() == 0) { 650 | return; 651 | } 652 | ppu->triggerRendering(); 653 | 654 | int bank4k = (bank2k / 2) % rom->getVromBankCount(); 655 | int bankoffset = (bank2k % 2) * 2048; 656 | array_copy(rom->getVromBank(bank4k), bankoffset, &nes->ppuMem->mem, address, 2048); 657 | 658 | // Update tiles: 659 | array* vromTile = rom->getVromBankTiles(bank4k); 660 | int baseIndex = address >> 4; 661 | for(int i = 0; i < 128; ++i) { 662 | ppu->ptTile[baseIndex + i] = (*vromTile)[((bank2k % 2) << 7) + i]; 663 | } 664 | } 665 | 666 | void MapperDefault::load8kRomBank(int bank8k, int address) { 667 | int bank16k = (bank8k / 2) % rom->getRomBankCount(); 668 | int offset = (bank8k % 2) * 8192; 669 | 670 | array* bank = rom->getRomBank(bank16k); 671 | cpuMem->write(address, bank, offset, 8192); 672 | } 673 | 674 | void MapperDefault::clockIrqCounter() { 675 | // Does nothing. This is used by the MMC3 mapper. 676 | } 677 | 678 | void MapperDefault::latchAccess(int address) { 679 | // Does nothing. This is used by MMC2. 680 | assert(address > -1); 681 | } 682 | 683 | int MapperDefault::syncV() { 684 | return 0; 685 | } 686 | 687 | int MapperDefault::syncH(int scanline) { 688 | assert(scanline); 689 | return 0; 690 | } 691 | 692 | void MapperDefault::setMouseState(bool pressed, int x, int y) { 693 | mousePressed = pressed; 694 | mouseX = x; 695 | mouseY = y; 696 | } 697 | 698 | void MapperDefault::reset() { 699 | this->base_reset(); 700 | } 701 | 702 | void MapperDefault::base_reset() { 703 | this->joy1StrobeState = 0; 704 | this->joy2StrobeState = 0; 705 | this->joypadLastWrite = 0; 706 | this->mousePressed = false; 707 | } 708 | -------------------------------------------------------------------------------- /src/ByteBuffer.cc: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2012-2017 Matthew Brennan Jones 3 | Copyright (c) 2006-2011 Jamie Sanders 4 | A NES emulator in WebAssembly. Based on vNES. 5 | Licensed under GPLV3 or later 6 | Hosted at: https://github.com/workhorsy/SaltyNES 7 | */ 8 | 9 | 10 | #include "SaltyNES.h" 11 | 12 | 13 | 14 | ByteBuffer::ByteBuffer(size_t size, const int byteOrdering) { 15 | if(size < 1) { 16 | size = 1; 17 | } 18 | this->buf = vector(size, 0); 19 | this->byteOrder = byteOrdering; 20 | curPos = 0; 21 | hasBeenErrors = false; 22 | } 23 | 24 | ByteBuffer::ByteBuffer(vector* content, const int byteOrdering) { 25 | try { 26 | this->buf = vector(content->size(), 0); 27 | for(size_t i = 0; i < content->size(); ++i) { 28 | buf[i] = static_cast((*content)[i] & 255); 29 | } 30 | this->byteOrder = byteOrdering; 31 | curPos = 0; 32 | hasBeenErrors = false; 33 | } catch (exception& e) { 34 | //System.out.println("ByteBuffer: Couldn't create buffer from empty array."); 35 | } 36 | } 37 | 38 | void ByteBuffer::setExpandable(bool exp) { 39 | expandable = exp; 40 | } 41 | 42 | void ByteBuffer::setExpandBy(size_t expBy) { 43 | 44 | if(expBy > 1024) { 45 | this->expandBy = expBy; 46 | } 47 | 48 | } 49 | 50 | void ByteBuffer::setByteOrder(int byteOrder) { 51 | 52 | if(byteOrder >= 0 && byteOrder < 2) { 53 | this->byteOrder = byteOrder; 54 | } 55 | 56 | } 57 | 58 | uint8_t* ByteBuffer::getBytes() { 59 | return reinterpret_cast(this->buf.data()); 60 | } 61 | 62 | size_t ByteBuffer::getSize() { 63 | return this->buf.size(); 64 | } 65 | 66 | size_t ByteBuffer::getPos() { 67 | return curPos; 68 | } 69 | 70 | void ByteBuffer::error() { 71 | hasBeenErrors = true; 72 | printf("Not in range!\n"); 73 | } 74 | 75 | bool ByteBuffer::hasHadErrors() { 76 | return hasBeenErrors; 77 | } 78 | 79 | void ByteBuffer::clear() { 80 | curPos = 0; 81 | std::fill(buf.begin(), buf.end(), 0); 82 | } 83 | 84 | void ByteBuffer::fill(uint8_t value) { 85 | std::fill(buf.begin(), buf.end(), value); 86 | } 87 | 88 | bool ByteBuffer::fillRange(size_t start, size_t length, uint8_t value) { 89 | if(inRange(start, length)) { 90 | for(size_t i = start; i < (start + length); ++i) { 91 | buf[i] = value; 92 | } 93 | return true; 94 | } else { 95 | error(); 96 | return false; 97 | } 98 | } 99 | 100 | void ByteBuffer::resize(size_t length) { 101 | buf.resize(length); 102 | } 103 | 104 | void ByteBuffer::resizeToCurrentPos() { 105 | resize(curPos); 106 | } 107 | 108 | void ByteBuffer::expand() { 109 | expand(expandBy); 110 | } 111 | 112 | void ByteBuffer::expand(size_t byHowMuch) { 113 | resize(buf.size() + byHowMuch); 114 | } 115 | 116 | void ByteBuffer::goTo(size_t position) { 117 | if(inRange(position)) { 118 | curPos = position; 119 | } else { 120 | error(); 121 | } 122 | } 123 | 124 | void ByteBuffer::move(size_t howFar) { 125 | curPos += howFar; 126 | if(!inRange(curPos)) { 127 | curPos = buf.size() - 1; 128 | } 129 | } 130 | 131 | bool ByteBuffer::inRange(size_t pos) { 132 | if(pos < buf.size()) { 133 | return true; 134 | } else { 135 | if(expandable) { 136 | expand(max(pos + 1 - buf.size(), expandBy)); 137 | return true; 138 | } else { 139 | return false; 140 | } 141 | } 142 | } 143 | 144 | bool ByteBuffer::inRange(size_t pos, size_t length) { 145 | if(pos + (length - 1) < buf.size()) { 146 | return true; 147 | } else { 148 | if(expandable) { 149 | expand(max(pos + length - buf.size(), expandBy)); 150 | return true; 151 | } else { 152 | return false; 153 | } 154 | } 155 | } 156 | 157 | bool ByteBuffer::putBoolean(bool b) { 158 | bool ret = putBoolean(b, curPos); 159 | move(1); 160 | return ret; 161 | } 162 | 163 | bool ByteBuffer::putBoolean(bool b, size_t pos) { 164 | if(b) { 165 | return putByte(static_cast(1), pos); 166 | } else { 167 | return putByte(static_cast(0), pos); 168 | } 169 | } 170 | 171 | bool ByteBuffer::putByte(uint16_t var) { 172 | if(inRange(curPos, 1)) { 173 | buf[curPos] = var; 174 | move(1); 175 | return true; 176 | } else { 177 | error(); 178 | return false; 179 | } 180 | } 181 | 182 | bool ByteBuffer::putByte(uint16_t var, size_t pos) { 183 | if(inRange(pos, 1)) { 184 | buf[pos] = var; 185 | return true; 186 | } else { 187 | error(); 188 | return false; 189 | } 190 | } 191 | 192 | bool ByteBuffer::putShort(uint16_t var) { 193 | bool ret = putShort(var, curPos); 194 | if(ret) { 195 | move(2); 196 | } 197 | return ret; 198 | } 199 | 200 | bool ByteBuffer::putShort(uint16_t var, size_t pos) { 201 | if(inRange(pos, 2)) { 202 | if(this->byteOrder == BO_BIG_ENDIAN) { 203 | buf[pos + 0] = static_cast((var >> 8) & 255); 204 | buf[pos + 1] = static_cast((var) & 255); 205 | } else { 206 | buf[pos + 1] = static_cast((var >> 8) & 255); 207 | buf[pos + 0] = static_cast((var) & 255); 208 | } 209 | return true; 210 | } else { 211 | error(); 212 | return false; 213 | } 214 | } 215 | 216 | bool ByteBuffer::putInt(int var) { 217 | bool ret = putInt(var, curPos); 218 | if(ret) { 219 | move(4); 220 | } 221 | return ret; 222 | } 223 | 224 | bool ByteBuffer::putInt(int var, size_t pos) { 225 | if(inRange(pos, 4)) { 226 | if(this->byteOrder == BO_BIG_ENDIAN) { 227 | buf[pos + 0] = static_cast((var >> 24) & 255); 228 | buf[pos + 1] = static_cast((var >> 16) & 255); 229 | buf[pos + 2] = static_cast((var >> 8) & 255); 230 | buf[pos + 3] = static_cast(var & 255); 231 | } else { 232 | buf[pos + 3] = static_cast((var >> 24) & 255); 233 | buf[pos + 2] = static_cast((var >> 16) & 255); 234 | buf[pos + 1] = static_cast((var >> 8) & 255); 235 | buf[pos + 0] = static_cast(var & 255); 236 | } 237 | return true; 238 | } else { 239 | error(); 240 | return false; 241 | } 242 | } 243 | 244 | bool ByteBuffer::putString(string var) { 245 | bool ret = putString(var, curPos); 246 | if(ret) { 247 | move(2 * var.length()); 248 | } 249 | return ret; 250 | } 251 | 252 | bool ByteBuffer::putString(string var, size_t pos) { 253 | const char* charArr = reinterpret_cast(var.c_str()); 254 | uint16_t theChar; 255 | if(inRange(pos, var.length() * 2)) { 256 | for(size_t i = 0; i < var.length(); ++i) { 257 | theChar = static_cast(charArr[i]); 258 | buf[pos + 0] = static_cast((theChar >> 8) & 255); 259 | buf[pos + 1] = static_cast(theChar & 255); 260 | pos += 2; 261 | } 262 | return true; 263 | } else { 264 | error(); 265 | return false; 266 | } 267 | } 268 | 269 | bool ByteBuffer::putChar(char var) { 270 | bool ret = putChar(var, curPos); 271 | if(ret) { 272 | move(2); 273 | } 274 | return ret; 275 | } 276 | 277 | bool ByteBuffer::putChar(char var, size_t pos) { 278 | int tmp = var; 279 | if(inRange(pos, 2)) { 280 | if(byteOrder == BO_BIG_ENDIAN) { 281 | buf[pos + 0] = static_cast((tmp >> 8) & 255); 282 | buf[pos + 1] = static_cast(tmp & 255); 283 | } else { 284 | buf[pos + 1] = static_cast((tmp >> 8) & 255); 285 | buf[pos + 0] = static_cast(tmp & 255); 286 | } 287 | return true; 288 | } else { 289 | error(); 290 | return false; 291 | } 292 | } 293 | 294 | bool ByteBuffer::putCharAscii(char var) { 295 | bool ret = putCharAscii(var, curPos); 296 | if(ret) { 297 | move(1); 298 | } 299 | return ret; 300 | } 301 | 302 | bool ByteBuffer::putCharAscii(char var, size_t pos) { 303 | if(inRange(pos)) { 304 | buf[pos] = static_cast(var); 305 | return true; 306 | } else { 307 | error(); 308 | return false; 309 | } 310 | } 311 | 312 | bool ByteBuffer::putStringAscii(string var) { 313 | bool ret = putStringAscii(var, curPos); 314 | if(ret) { 315 | move(var.length()); 316 | } 317 | return ret; 318 | } 319 | 320 | bool ByteBuffer::putStringAscii(string var, size_t pos) { 321 | const char* charArr = reinterpret_cast(var.c_str()); 322 | if(inRange(pos, var.length())) { 323 | for(size_t i = 0; i < var.length(); ++i) { 324 | buf[pos] = static_cast(charArr[i]); 325 | ++pos; 326 | } 327 | return true; 328 | } else { 329 | error(); 330 | return false; 331 | } 332 | } 333 | 334 | bool ByteBuffer::putByteArray(vector* arr) { 335 | if(arr == nullptr) { 336 | return false; 337 | } 338 | if(buf.size() - curPos < arr->size()) { 339 | resize(curPos + arr->size()); 340 | } 341 | for(size_t i = 0; i < arr->size(); ++i) { 342 | buf[curPos + i] = static_cast((*arr)[i]); 343 | } 344 | curPos += arr->size(); 345 | return true; 346 | } 347 | 348 | bool ByteBuffer::readByteArray(vector* arr) { 349 | if(arr == nullptr) { 350 | return false; 351 | } 352 | if(buf.size() - curPos < arr->size()) { 353 | return false; 354 | } 355 | for(size_t i = 0; i < arr->size(); ++i) { 356 | (*arr)[i] = static_cast(buf[curPos + i] & 0xFF); 357 | } 358 | curPos += arr->size(); 359 | return true; 360 | } 361 | 362 | bool ByteBuffer::putShortArray(vector* arr) { 363 | if(arr == nullptr) { 364 | return false; 365 | } 366 | if(buf.size() - curPos < arr->size() * 2) { 367 | resize(curPos + arr->size() * 2); 368 | } 369 | if(byteOrder == BO_BIG_ENDIAN) { 370 | for(size_t i = 0; i < arr->size(); ++i) { 371 | buf[curPos + 0] = static_cast(((*arr)[i] >> 8) & 255); 372 | buf[curPos + 1] = static_cast(((*arr)[i]) & 255); 373 | curPos += 2; 374 | } 375 | } else { 376 | for(size_t i = 0; i < arr->size(); ++i) { 377 | buf[curPos + 1] = static_cast(((*arr)[i] >> 8) & 255); 378 | buf[curPos + 0] = static_cast(((*arr)[i]) & 255); 379 | curPos += 2; 380 | } 381 | } 382 | return true; 383 | } 384 | 385 | string ByteBuffer::toString() { 386 | char* strBuf = new char(buf.size()-1); 387 | uint16_t tmp; 388 | for(size_t i = 0; i < (buf.size() - 1); i += 2) { 389 | tmp = static_cast((buf[i] << 8) | (buf[i + 1])); 390 | strBuf[i] = static_cast(tmp); 391 | } 392 | return string(strBuf); 393 | } 394 | 395 | string ByteBuffer::toStringAscii() { 396 | char* strBuf = new char(buf.size()-1); 397 | for(size_t i = 0; i < buf.size(); ++i) { 398 | strBuf[i] = static_cast(buf[i]); 399 | } 400 | return string(strBuf); 401 | } 402 | 403 | bool ByteBuffer::readBoolean() { 404 | bool ret = readBoolean(curPos); 405 | move(1); 406 | return ret; 407 | } 408 | 409 | bool ByteBuffer::readBoolean(size_t pos) { 410 | return readByte(pos) == 1; 411 | } 412 | 413 | uint16_t ByteBuffer::readByte() { 414 | uint16_t ret = readByte(curPos); 415 | move(1); 416 | return ret; 417 | } 418 | 419 | uint16_t ByteBuffer::readByte(size_t pos) { 420 | if(inRange(pos)) { 421 | return buf[pos]; 422 | } else { 423 | error(); 424 | throw "ArrayIndexOutOfBoundsException"; 425 | } 426 | } 427 | 428 | uint16_t ByteBuffer::readShort() { 429 | uint16_t ret = readShort(curPos); 430 | move(2); 431 | return ret; 432 | } 433 | 434 | uint16_t ByteBuffer::readShort(size_t pos) { 435 | if(inRange(pos, 2)) { 436 | if(this->byteOrder == BO_BIG_ENDIAN) { 437 | return static_cast((buf[pos] << 8) | (buf[pos + 1])); 438 | } else { 439 | return static_cast((buf[pos + 1] << 8) | (buf[pos])); 440 | } 441 | } else { 442 | error(); 443 | throw "ArrayIndexOutOfBoundsException"; 444 | } 445 | } 446 | 447 | int ByteBuffer::readInt() { 448 | int ret = readInt(curPos); 449 | move(4); 450 | return ret; 451 | } 452 | 453 | int ByteBuffer::readInt(size_t pos) { 454 | int ret = 0; 455 | if(inRange(pos, 4)) { 456 | if(this->byteOrder == BO_BIG_ENDIAN) { 457 | ret |= (buf[pos + 0] << 24); 458 | ret |= (buf[pos + 1] << 16); 459 | ret |= (buf[pos + 2] << 8); 460 | ret |= (buf[pos + 3]); 461 | } else { 462 | ret |= (buf[pos + 3] << 24); 463 | ret |= (buf[pos + 2] << 16); 464 | ret |= (buf[pos + 1] << 8); 465 | ret |= (buf[pos + 0]); 466 | } 467 | return ret; 468 | } else { 469 | error(); 470 | throw "ArrayIndexOutOfBoundsException"; 471 | } 472 | } 473 | 474 | char ByteBuffer::readChar() { 475 | char ret = readChar(curPos); 476 | move(2); 477 | return ret; 478 | } 479 | 480 | char ByteBuffer::readChar(size_t pos) { 481 | if(inRange(pos, 2)) { 482 | return static_cast(readShort(pos)); 483 | } else { 484 | error(); 485 | throw "ArrayIndexOutOfBoundsException"; 486 | } 487 | } 488 | 489 | char ByteBuffer::readCharAscii() { 490 | char ret = readCharAscii(curPos); 491 | move(1); 492 | return ret; 493 | } 494 | 495 | char ByteBuffer::readCharAscii(size_t pos) { 496 | if(inRange(pos, 1)) { 497 | return static_cast(readByte(pos) & 255); 498 | } else { 499 | error(); 500 | throw "ArrayIndexOutOfBoundsException"; 501 | } 502 | } 503 | 504 | string ByteBuffer::readString(size_t length) { 505 | if(length > 0) { 506 | string ret = readString(curPos, length); 507 | move(ret.length() * 2); 508 | return ret; 509 | } else { 510 | return string(""); 511 | } 512 | } 513 | 514 | string ByteBuffer::readString(size_t pos, size_t length) { 515 | char* tmp; 516 | if(inRange(pos, length * 2)) { 517 | tmp = new char[length]; 518 | for(size_t i = 0; i < length; ++i) { 519 | tmp[i] = readChar(pos + i * 2); 520 | } 521 | return string(tmp); 522 | } else { 523 | throw "ArrayIndexOutOfBoundsException"; 524 | } 525 | } 526 | 527 | string ByteBuffer::readStringWithShortLength() { 528 | string ret = readStringWithShortLength(curPos); 529 | move(ret.length() * 2 + 2); 530 | return ret; 531 | } 532 | 533 | string ByteBuffer::readStringWithShortLength(size_t pos) { 534 | uint16_t len; 535 | if(inRange(pos, 2)) { 536 | len = readShort(pos); 537 | if(len > 0) { 538 | return readString(pos + 2, len); 539 | } else { 540 | return string(""); 541 | } 542 | } else { 543 | throw "ArrayIndexOutOfBoundsException"; 544 | } 545 | } 546 | 547 | string ByteBuffer::readStringAscii(size_t length) { 548 | string ret = readStringAscii(curPos, length); 549 | move(ret.length()); 550 | return ret; 551 | } 552 | 553 | string ByteBuffer::readStringAscii(size_t pos, size_t length) { 554 | char* tmp; 555 | if(inRange(pos, length) && length > 0) { 556 | tmp = new char[length]; 557 | for(size_t i = 0; i < length; ++i) { 558 | tmp[i] = readCharAscii(pos + i); 559 | } 560 | return string(tmp); 561 | } else { 562 | throw "ArrayIndexOutOfBoundsException"; 563 | } 564 | } 565 | 566 | string ByteBuffer::readStringAsciiWithShortLength() { 567 | string ret = readStringAsciiWithShortLength(curPos); 568 | move(ret.length() + 2); 569 | return ret; 570 | } 571 | 572 | string ByteBuffer::readStringAsciiWithShortLength(size_t pos) { 573 | uint16_t len; 574 | if(inRange(pos, 2)) { 575 | len = readShort(pos); 576 | if(len > 0) { 577 | return readStringAscii(pos + 2, len); 578 | } else { 579 | return string(""); 580 | } 581 | } else { 582 | throw "ArrayIndexOutOfBoundsException"; 583 | } 584 | } 585 | /* 586 | vector* ByteBuffer::expandShortArray(vector* array, size_t size) { 587 | vector* newArr = new vector(array->size() + size, 0); 588 | for(size_t i=0; isize(); ++i) 589 | (*newArr)[i] = (*array)[i]; 590 | } 591 | 592 | void ByteBuffer::crop() { 593 | if(curPos > 0) { 594 | if(curPos < buf.size()) { 595 | buf.resize(curPos); 596 | } 597 | } else { 598 | //System.out.println("Could not crop buffer, as the current position is 0. The buffer may not be empty."); 599 | } 600 | } 601 | 602 | ByteBuffer* ByteBuffer::asciiEncode(ByteBuffer* buf) { 603 | 604 | vector* data = &buf->buf; 605 | vector* enc = new vector(buf->getSize() * 2, 0); 606 | 607 | size_t encpos = 0; 608 | int tmp; 609 | for(size_t i = 0; i < data->size(); ++i) { 610 | 611 | tmp = (*data)[i]; 612 | (*enc)[encpos] = static_cast((65 + tmp) & 0xF); 613 | (*enc)[encpos + 1] = static_cast((65 + (tmp >> 4)) & 0xF); 614 | encpos += 2; 615 | 616 | } 617 | return new ByteBuffer(enc, ByteBuffer::BO_BIG_ENDIAN); 618 | 619 | } 620 | 621 | ByteBuffer* ByteBuffer::asciiDecode(ByteBuffer* buf) { 622 | return nullptr; 623 | } 624 | 625 | void ByteBuffer::saveToZipFile(File f, ByteBuffer* buf) { 626 | 627 | try { 628 | 629 | FileOutputStream fOut = new FileOutputStream(f); 630 | ZipOutputStream zipOut = new ZipOutputStream(fOut); 631 | zipOut.putNextEntry(new ZipEntry("contents")); 632 | zipOut.write(buf->getBytes()); 633 | zipOut.closeEntry(); 634 | zipOut.close(); 635 | fOut.close(); 636 | //System.out.println("Buffer was successfully saved to "+f.getPath()); 637 | 638 | } catch (Exception e) { 639 | 640 | //System.out.println("Unable to save buffer to file "+f.getPath()); 641 | e.printStackTrace(); 642 | 643 | } 644 | 645 | } 646 | 647 | static ByteBuffer* ByteBuffer::readFromZipFile(File f) { 648 | 649 | try { 650 | 651 | FileInputStream in = new FileInputStream(f); 652 | ZipInputStream zipIn = new ZipInputStream(in); 653 | int len, curlen, read; 654 | 655 | ZipFile zip = new ZipFile(f); 656 | ZipEntry entry = zip.getEntry("contents"); 657 | len = (int) entry.getSize(); 658 | //System.out.println("Len = "+len); 659 | 660 | curlen = 0; 661 | uint8_t* buf = new uint8_t[len]; 662 | zipIn.getNextEntry(); 663 | while(curlen < len) { 664 | read = zipIn.read(buf, curlen, len - curlen); 665 | if(read >= 0) { 666 | curlen += read; 667 | } else { 668 | // end of file. 669 | break; 670 | } 671 | } 672 | zipIn.closeEntry(); 673 | zipIn.close(); 674 | in.close(); 675 | zip.close(); 676 | return new ByteBuffer(buf, ByteBuffer.BO_BIG_ENDIAN); 677 | 678 | } catch (Exception e) { 679 | //System.out.println("Unable to load buffer from file "+f.getPath()); 680 | e.printStackTrace(); 681 | } 682 | 683 | // fail: 684 | return nullptr; 685 | 686 | } 687 | */ 688 | --------------------------------------------------------------------------------