From ad0faa2a4222ac6db6dd4b041b27d1765930756c Mon Sep 17 00:00:00 2001 From: goyuken Date: Sat, 4 Jan 2014 20:40:03 +0000 Subject: [PATCH] quicknes import @ d2901940e566841c7c35dd92ad7556fb59c753a4 from https://github.com/kode54/QuickNES_Core.git only changes: add one pair of missing parens (https://github.com/kode54/QuickNES_Core/issues/3) and commented out HAVE_ZLIB_H also includes new vs10 solution which may or may not build --- quicknes/fex/Binary_Extractor.cpp | 77 + quicknes/fex/Binary_Extractor.h | 26 + quicknes/fex/Data_Reader.cpp | 774 ++++++++++ quicknes/fex/Data_Reader.h | 265 ++++ quicknes/fex/File_Extractor.cpp | 341 +++++ quicknes/fex/File_Extractor.h | 191 +++ quicknes/fex/Gzip_Extractor.cpp | 98 ++ quicknes/fex/Gzip_Extractor.h | 34 + quicknes/fex/Gzip_Reader.cpp | 85 ++ quicknes/fex/Gzip_Reader.h | 46 + quicknes/fex/Rar_Extractor.cpp | 197 +++ quicknes/fex/Rar_Extractor.h | 43 + quicknes/fex/Zip7_Extractor.cpp | 290 ++++ quicknes/fex/Zip7_Extractor.h | 36 + quicknes/fex/Zip_Extractor.cpp | 390 +++++ quicknes/fex/Zip_Extractor.h | 45 + quicknes/fex/Zlib_Inflater.cpp | 257 ++++ quicknes/fex/Zlib_Inflater.h | 70 + quicknes/fex/blargg_common.cpp | 51 + quicknes/fex/blargg_common.h | 217 +++ quicknes/fex/blargg_config.h | 37 + quicknes/fex/blargg_endian.h | 185 +++ quicknes/fex/blargg_errors.cpp | 113 ++ quicknes/fex/blargg_errors.h | 80 ++ quicknes/fex/blargg_source.h | 121 ++ quicknes/fex/fex.cpp | 321 +++++ quicknes/fex/fex.h | 206 +++ quicknes/libquicknes/libquicknes.sln | 20 + quicknes/libquicknes/libquicknes.vcxproj | 113 ++ .../libquicknes/libquicknes.vcxproj.filters | 148 ++ quicknes/libretro/Makefile | 216 +++ quicknes/libretro/jni/Android.mk | 75 + quicknes/libretro/jni/Application.mk | 2 + quicknes/libretro/libretro.cpp | 267 ++++ quicknes/libretro/libretro.h | 758 ++++++++++ quicknes/libretro/link.T | 5 + quicknes/nes_emu/Blip_Buffer.cpp | 412 ++++++ quicknes/nes_emu/Blip_Buffer.h | 354 +++++ quicknes/nes_emu/Blip_Synth.h | 208 +++ quicknes/nes_emu/Effects_Buffer.cpp | 518 +++++++ quicknes/nes_emu/Effects_Buffer.h | 93 ++ quicknes/nes_emu/Mapper_Fme07.cpp | 215 +++ quicknes/nes_emu/Mapper_Fme7.cpp | 215 +++ quicknes/nes_emu/Mapper_Mmc5.cpp | 155 ++ quicknes/nes_emu/Mapper_Namco106.cpp | 213 +++ quicknes/nes_emu/Mapper_Vrc6.cpp | 262 ++++ quicknes/nes_emu/Multi_Buffer.cpp | 213 +++ quicknes/nes_emu/Multi_Buffer.h | 175 +++ quicknes/nes_emu/Nes_Apu.cpp | 380 +++++ quicknes/nes_emu/Nes_Apu.h | 177 +++ quicknes/nes_emu/Nes_Apu_State.cpp | 125 ++ quicknes/nes_emu/Nes_Blitter.cpp | 113 ++ quicknes/nes_emu/Nes_Blitter.h | 45 + quicknes/nes_emu/Nes_Buffer.cpp | 201 +++ quicknes/nes_emu/Nes_Buffer.h | 68 + quicknes/nes_emu/Nes_Cart.cpp | 268 ++++ quicknes/nes_emu/Nes_Cart.h | 93 ++ quicknes/nes_emu/Nes_Core.cpp | 570 ++++++++ quicknes/nes_emu/Nes_Core.h | 117 ++ quicknes/nes_emu/Nes_Cpu.cpp | 1250 +++++++++++++++++ quicknes/nes_emu/Nes_Cpu.h | 128 ++ quicknes/nes_emu/Nes_Effects_Buffer.cpp | 84 ++ quicknes/nes_emu/Nes_Effects_Buffer.h | 38 + quicknes/nes_emu/Nes_Emu.cpp | 494 +++++++ quicknes/nes_emu/Nes_Emu.h | 266 ++++ quicknes/nes_emu/Nes_File.cpp | 226 +++ quicknes/nes_emu/Nes_File.h | 128 ++ quicknes/nes_emu/Nes_Film.cpp | 531 +++++++ quicknes/nes_emu/Nes_Film.h | 214 +++ quicknes/nes_emu/Nes_Film_Data.cpp | 273 ++++ quicknes/nes_emu/Nes_Film_Data.h | 86 ++ quicknes/nes_emu/Nes_Film_Packer.cpp | 220 +++ quicknes/nes_emu/Nes_Film_Packer.h | 32 + quicknes/nes_emu/Nes_Fme07_Apu.cpp | 122 ++ quicknes/nes_emu/Nes_Fme07_Apu.h | 133 ++ quicknes/nes_emu/Nes_Fme7_Apu.cpp | 120 ++ quicknes/nes_emu/Nes_Fme7_Apu.h | 135 ++ quicknes/nes_emu/Nes_Mapper.cpp | 216 +++ quicknes/nes_emu/Nes_Mapper.h | 234 +++ quicknes/nes_emu/Nes_Mmc1.cpp | 123 ++ quicknes/nes_emu/Nes_Mmc3.cpp | 252 ++++ quicknes/nes_emu/Nes_Namco_Apu.cpp | 149 ++ quicknes/nes_emu/Nes_Namco_Apu.h | 104 ++ quicknes/nes_emu/Nes_Nonlinearizer.cpp | 157 +++ quicknes/nes_emu/Nes_Nonlinearizer.h | 44 + quicknes/nes_emu/Nes_Oscs.cpp | 545 +++++++ quicknes/nes_emu/Nes_Oscs.h | 150 ++ quicknes/nes_emu/Nes_Ppu.cpp | 669 +++++++++ quicknes/nes_emu/Nes_Ppu.h | 141 ++ quicknes/nes_emu/Nes_Ppu_Bg.h | 69 + quicknes/nes_emu/Nes_Ppu_Core.cpp | 419 ++++++ quicknes/nes_emu/Nes_Ppu_Impl.cpp | 483 +++++++ quicknes/nes_emu/Nes_Ppu_Impl.h | 198 +++ quicknes/nes_emu/Nes_Ppu_Rendering.cpp | 487 +++++++ quicknes/nes_emu/Nes_Ppu_Rendering.h | 63 + quicknes/nes_emu/Nes_Ppu_Sprites.h | 132 ++ quicknes/nes_emu/Nes_Recorder.cpp | 472 +++++++ quicknes/nes_emu/Nes_Recorder.h | 142 ++ quicknes/nes_emu/Nes_Rewinder.cpp | 324 +++++ quicknes/nes_emu/Nes_Rewinder.h | 95 ++ quicknes/nes_emu/Nes_Rom.cpp | 216 +++ quicknes/nes_emu/Nes_Rom.h | 86 ++ quicknes/nes_emu/Nes_Snapshot.cpp | 329 +++++ quicknes/nes_emu/Nes_Snapshot.h | 165 +++ quicknes/nes_emu/Nes_State.cpp | 293 ++++ quicknes/nes_emu/Nes_State.h | 142 ++ quicknes/nes_emu/Nes_Vrc6.cpp | 231 +++ quicknes/nes_emu/Nes_Vrc6.h | 86 ++ quicknes/nes_emu/Nes_Vrc6_Apu.cpp | 217 +++ quicknes/nes_emu/Nes_Vrc6_Apu.h | 99 ++ quicknes/nes_emu/Nonlinear_Buffer.cpp | 189 +++ quicknes/nes_emu/Nonlinear_Buffer.h | 65 + quicknes/nes_emu/Nonlinear_Effects_Buffer.cpp | 69 + quicknes/nes_emu/Nonlinear_Effects_Buffer.h | 33 + quicknes/nes_emu/abstract_file.cpp | 308 ++++ quicknes/nes_emu/abstract_file.h | 162 +++ quicknes/nes_emu/apu_snapshot.cpp | 126 ++ quicknes/nes_emu/apu_snapshot.h | 75 + quicknes/nes_emu/apu_state.cpp | 132 ++ quicknes/nes_emu/apu_state.h | 79 ++ quicknes/nes_emu/blargg_common.h | 164 +++ quicknes/nes_emu/blargg_config.h | 33 + quicknes/nes_emu/blargg_endian.h | 157 +++ quicknes/nes_emu/blargg_source.h | 76 + quicknes/nes_emu/misc_mappers.cpp | 207 +++ quicknes/nes_emu/nes_cpu_io.h | 144 ++ quicknes/nes_emu/nes_data.cpp | 75 + quicknes/nes_emu/nes_data.h | 174 +++ quicknes/nes_emu/nes_mappers.cpp | 117 ++ quicknes/nes_emu/nes_ntsc.h | 199 +++ quicknes/nes_emu/nes_ntsc_impl.h | 520 +++++++ quicknes/nes_emu/nes_util.cpp | 211 +++ quicknes/nes_emu/nes_util.h | 90 ++ quicknes/nes_emu/optional_mappers.cpp | 194 +++ 134 files changed, 27001 insertions(+) create mode 100644 quicknes/fex/Binary_Extractor.cpp create mode 100644 quicknes/fex/Binary_Extractor.h create mode 100644 quicknes/fex/Data_Reader.cpp create mode 100644 quicknes/fex/Data_Reader.h create mode 100644 quicknes/fex/File_Extractor.cpp create mode 100644 quicknes/fex/File_Extractor.h create mode 100644 quicknes/fex/Gzip_Extractor.cpp create mode 100644 quicknes/fex/Gzip_Extractor.h create mode 100644 quicknes/fex/Gzip_Reader.cpp create mode 100644 quicknes/fex/Gzip_Reader.h create mode 100644 quicknes/fex/Rar_Extractor.cpp create mode 100644 quicknes/fex/Rar_Extractor.h create mode 100644 quicknes/fex/Zip7_Extractor.cpp create mode 100644 quicknes/fex/Zip7_Extractor.h create mode 100644 quicknes/fex/Zip_Extractor.cpp create mode 100644 quicknes/fex/Zip_Extractor.h create mode 100644 quicknes/fex/Zlib_Inflater.cpp create mode 100644 quicknes/fex/Zlib_Inflater.h create mode 100644 quicknes/fex/blargg_common.cpp create mode 100644 quicknes/fex/blargg_common.h create mode 100644 quicknes/fex/blargg_config.h create mode 100644 quicknes/fex/blargg_endian.h create mode 100644 quicknes/fex/blargg_errors.cpp create mode 100644 quicknes/fex/blargg_errors.h create mode 100644 quicknes/fex/blargg_source.h create mode 100644 quicknes/fex/fex.cpp create mode 100644 quicknes/fex/fex.h create mode 100644 quicknes/libquicknes/libquicknes.sln create mode 100644 quicknes/libquicknes/libquicknes.vcxproj create mode 100644 quicknes/libquicknes/libquicknes.vcxproj.filters create mode 100644 quicknes/libretro/Makefile create mode 100644 quicknes/libretro/jni/Android.mk create mode 100644 quicknes/libretro/jni/Application.mk create mode 100644 quicknes/libretro/libretro.cpp create mode 100644 quicknes/libretro/libretro.h create mode 100644 quicknes/libretro/link.T create mode 100644 quicknes/nes_emu/Blip_Buffer.cpp create mode 100644 quicknes/nes_emu/Blip_Buffer.h create mode 100644 quicknes/nes_emu/Blip_Synth.h create mode 100644 quicknes/nes_emu/Effects_Buffer.cpp create mode 100644 quicknes/nes_emu/Effects_Buffer.h create mode 100644 quicknes/nes_emu/Mapper_Fme07.cpp create mode 100644 quicknes/nes_emu/Mapper_Fme7.cpp create mode 100644 quicknes/nes_emu/Mapper_Mmc5.cpp create mode 100644 quicknes/nes_emu/Mapper_Namco106.cpp create mode 100644 quicknes/nes_emu/Mapper_Vrc6.cpp create mode 100644 quicknes/nes_emu/Multi_Buffer.cpp create mode 100644 quicknes/nes_emu/Multi_Buffer.h create mode 100644 quicknes/nes_emu/Nes_Apu.cpp create mode 100644 quicknes/nes_emu/Nes_Apu.h create mode 100644 quicknes/nes_emu/Nes_Apu_State.cpp create mode 100644 quicknes/nes_emu/Nes_Blitter.cpp create mode 100644 quicknes/nes_emu/Nes_Blitter.h create mode 100644 quicknes/nes_emu/Nes_Buffer.cpp create mode 100644 quicknes/nes_emu/Nes_Buffer.h create mode 100644 quicknes/nes_emu/Nes_Cart.cpp create mode 100644 quicknes/nes_emu/Nes_Cart.h create mode 100644 quicknes/nes_emu/Nes_Core.cpp create mode 100644 quicknes/nes_emu/Nes_Core.h create mode 100644 quicknes/nes_emu/Nes_Cpu.cpp create mode 100644 quicknes/nes_emu/Nes_Cpu.h create mode 100644 quicknes/nes_emu/Nes_Effects_Buffer.cpp create mode 100644 quicknes/nes_emu/Nes_Effects_Buffer.h create mode 100644 quicknes/nes_emu/Nes_Emu.cpp create mode 100644 quicknes/nes_emu/Nes_Emu.h create mode 100644 quicknes/nes_emu/Nes_File.cpp create mode 100644 quicknes/nes_emu/Nes_File.h create mode 100644 quicknes/nes_emu/Nes_Film.cpp create mode 100644 quicknes/nes_emu/Nes_Film.h create mode 100644 quicknes/nes_emu/Nes_Film_Data.cpp create mode 100644 quicknes/nes_emu/Nes_Film_Data.h create mode 100644 quicknes/nes_emu/Nes_Film_Packer.cpp create mode 100644 quicknes/nes_emu/Nes_Film_Packer.h create mode 100644 quicknes/nes_emu/Nes_Fme07_Apu.cpp create mode 100644 quicknes/nes_emu/Nes_Fme07_Apu.h create mode 100644 quicknes/nes_emu/Nes_Fme7_Apu.cpp create mode 100644 quicknes/nes_emu/Nes_Fme7_Apu.h create mode 100644 quicknes/nes_emu/Nes_Mapper.cpp create mode 100644 quicknes/nes_emu/Nes_Mapper.h create mode 100644 quicknes/nes_emu/Nes_Mmc1.cpp create mode 100644 quicknes/nes_emu/Nes_Mmc3.cpp create mode 100644 quicknes/nes_emu/Nes_Namco_Apu.cpp create mode 100644 quicknes/nes_emu/Nes_Namco_Apu.h create mode 100644 quicknes/nes_emu/Nes_Nonlinearizer.cpp create mode 100644 quicknes/nes_emu/Nes_Nonlinearizer.h create mode 100644 quicknes/nes_emu/Nes_Oscs.cpp create mode 100644 quicknes/nes_emu/Nes_Oscs.h create mode 100644 quicknes/nes_emu/Nes_Ppu.cpp create mode 100644 quicknes/nes_emu/Nes_Ppu.h create mode 100644 quicknes/nes_emu/Nes_Ppu_Bg.h create mode 100644 quicknes/nes_emu/Nes_Ppu_Core.cpp create mode 100644 quicknes/nes_emu/Nes_Ppu_Impl.cpp create mode 100644 quicknes/nes_emu/Nes_Ppu_Impl.h create mode 100644 quicknes/nes_emu/Nes_Ppu_Rendering.cpp create mode 100644 quicknes/nes_emu/Nes_Ppu_Rendering.h create mode 100644 quicknes/nes_emu/Nes_Ppu_Sprites.h create mode 100644 quicknes/nes_emu/Nes_Recorder.cpp create mode 100644 quicknes/nes_emu/Nes_Recorder.h create mode 100644 quicknes/nes_emu/Nes_Rewinder.cpp create mode 100644 quicknes/nes_emu/Nes_Rewinder.h create mode 100644 quicknes/nes_emu/Nes_Rom.cpp create mode 100644 quicknes/nes_emu/Nes_Rom.h create mode 100644 quicknes/nes_emu/Nes_Snapshot.cpp create mode 100644 quicknes/nes_emu/Nes_Snapshot.h create mode 100644 quicknes/nes_emu/Nes_State.cpp create mode 100644 quicknes/nes_emu/Nes_State.h create mode 100644 quicknes/nes_emu/Nes_Vrc6.cpp create mode 100644 quicknes/nes_emu/Nes_Vrc6.h create mode 100644 quicknes/nes_emu/Nes_Vrc6_Apu.cpp create mode 100644 quicknes/nes_emu/Nes_Vrc6_Apu.h create mode 100644 quicknes/nes_emu/Nonlinear_Buffer.cpp create mode 100644 quicknes/nes_emu/Nonlinear_Buffer.h create mode 100644 quicknes/nes_emu/Nonlinear_Effects_Buffer.cpp create mode 100644 quicknes/nes_emu/Nonlinear_Effects_Buffer.h create mode 100644 quicknes/nes_emu/abstract_file.cpp create mode 100644 quicknes/nes_emu/abstract_file.h create mode 100644 quicknes/nes_emu/apu_snapshot.cpp create mode 100644 quicknes/nes_emu/apu_snapshot.h create mode 100644 quicknes/nes_emu/apu_state.cpp create mode 100644 quicknes/nes_emu/apu_state.h create mode 100644 quicknes/nes_emu/blargg_common.h create mode 100644 quicknes/nes_emu/blargg_config.h create mode 100644 quicknes/nes_emu/blargg_endian.h create mode 100644 quicknes/nes_emu/blargg_source.h create mode 100644 quicknes/nes_emu/misc_mappers.cpp create mode 100644 quicknes/nes_emu/nes_cpu_io.h create mode 100644 quicknes/nes_emu/nes_data.cpp create mode 100644 quicknes/nes_emu/nes_data.h create mode 100644 quicknes/nes_emu/nes_mappers.cpp create mode 100644 quicknes/nes_emu/nes_ntsc.h create mode 100644 quicknes/nes_emu/nes_ntsc_impl.h create mode 100644 quicknes/nes_emu/nes_util.cpp create mode 100644 quicknes/nes_emu/nes_util.h create mode 100644 quicknes/nes_emu/optional_mappers.cpp diff --git a/quicknes/fex/Binary_Extractor.cpp b/quicknes/fex/Binary_Extractor.cpp new file mode 100644 index 0000000000..7940c0a0b9 --- /dev/null +++ b/quicknes/fex/Binary_Extractor.cpp @@ -0,0 +1,77 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Binary_Extractor.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// TODO: could close file once data has been read into memory + +static File_Extractor* new_binary() +{ + return BLARGG_NEW Binary_Extractor; +} + +fex_type_t_ const fex_bin_type [1] = {{ + "", + &new_binary, + "file", + NULL +}}; + +Binary_Extractor::Binary_Extractor() : + File_Extractor( fex_bin_type ) +{ } + +Binary_Extractor::~Binary_Extractor() +{ + close(); +} + +blargg_err_t Binary_Extractor::open_path_v() +{ + set_name( arc_path() ); + return blargg_ok; +} + +blargg_err_t Binary_Extractor::open_v() +{ + set_name( arc_path() ); + set_info( arc().remain(), 0, 0 ); + return blargg_ok; +} + +void Binary_Extractor::close_v() +{ } + +blargg_err_t Binary_Extractor::next_v() +{ + return blargg_ok; +} + +blargg_err_t Binary_Extractor::rewind_v() +{ + return open_path_v(); +} + +blargg_err_t Binary_Extractor::stat_v() +{ + RETURN_ERR( open_arc_file() ); + RETURN_ERR( arc().seek( 0 ) ); + return open_v(); +} + +blargg_err_t Binary_Extractor::extract_v( void* p, int n ) +{ + return arc().read( p, n ); +} diff --git a/quicknes/fex/Binary_Extractor.h b/quicknes/fex/Binary_Extractor.h new file mode 100644 index 0000000000..d8cca642f6 --- /dev/null +++ b/quicknes/fex/Binary_Extractor.h @@ -0,0 +1,26 @@ +// Presents a single file as an "archive" of just that file. + +// File_Extractor 1.0.0 +#ifndef BINARY_EXTRACTOR_H +#define BINARY_EXTRACTOR_H + +#include "File_Extractor.h" + +class Binary_Extractor : public File_Extractor { +public: + Binary_Extractor(); + virtual ~Binary_Extractor(); + +protected: + virtual blargg_err_t open_path_v(); + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + + virtual blargg_err_t stat_v(); + virtual blargg_err_t extract_v( void*, int ); +}; + +#endif diff --git a/quicknes/fex/Data_Reader.cpp b/quicknes/fex/Data_Reader.cpp new file mode 100644 index 0000000000..7f4a9ed842 --- /dev/null +++ b/quicknes/fex/Data_Reader.cpp @@ -0,0 +1,774 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Data_Reader.h" + +#include "blargg_endian.h" +#include +#include + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Data_Reader + +blargg_err_t Data_Reader::read( void* p, int n ) +{ + assert( n >= 0 ); + + if ( n < 0 ) + return blargg_err_caller; + + if ( n <= 0 ) + return blargg_ok; + + if ( n > remain() ) + return blargg_err_file_eof; + + blargg_err_t err = read_v( p, n ); + if ( !err ) + remain_ -= n; + + return err; +} + +blargg_err_t Data_Reader::read_avail( void* p, int* n_ ) +{ + assert( *n_ >= 0 ); + + int n = min( (BOOST::uint64_t)(*n_), remain() ); + *n_ = 0; + + if ( n < 0 ) + return blargg_err_caller; + + if ( n <= 0 ) + return blargg_ok; + + blargg_err_t err = read_v( p, n ); + if ( !err ) + { + remain_ -= n; + *n_ = n; + } + + return err; +} + +blargg_err_t Data_Reader::read_avail( void* p, long* n ) +{ + int i = STATIC_CAST(int, *n); + blargg_err_t err = read_avail( p, &i ); + *n = i; + return err; +} + +blargg_err_t Data_Reader::skip_v( int count ) +{ + char buf [512]; + while ( count ) + { + int n = min( count, (int) sizeof buf ); + count -= n; + RETURN_ERR( read_v( buf, n ) ); + } + return blargg_ok; +} + +blargg_err_t Data_Reader::skip( int n ) +{ + assert( n >= 0 ); + + if ( n < 0 ) + return blargg_err_caller; + + if ( n <= 0 ) + return blargg_ok; + + if ( n > remain() ) + return blargg_err_file_eof; + + blargg_err_t err = skip_v( n ); + if ( !err ) + remain_ -= n; + + return err; +} + + +// File_Reader + +blargg_err_t File_Reader::seek( BOOST::uint64_t n ) +{ + assert( n >= 0 ); + + if ( n < 0 ) + return blargg_err_caller; + + if ( n == tell() ) + return blargg_ok; + + if ( n > size() ) + return blargg_err_file_eof; + + blargg_err_t err = seek_v( n ); + if ( !err ) + set_tell( n ); + + return err; +} + +blargg_err_t File_Reader::skip_v( BOOST::uint64_t n ) +{ + return seek_v( tell() + n ); +} + + +// Subset_Reader + +Subset_Reader::Subset_Reader( Data_Reader* dr, BOOST::uint64_t size ) : + in( dr ) +{ + set_remain( min( size, dr->remain() ) ); +} + +blargg_err_t Subset_Reader::read_v( void* p, int s ) +{ + return in->read( p, s ); +} + + +// Remaining_Reader + +Remaining_Reader::Remaining_Reader( void const* h, int size, Data_Reader* r ) : + in( r ) +{ + header = h; + header_remain = size; + + set_remain( size + r->remain() ); +} + +blargg_err_t Remaining_Reader::read_v( void* out, int count ) +{ + int first = min( count, header_remain ); + if ( first ) + { + memcpy( out, header, first ); + header = STATIC_CAST(char const*, header) + first; + header_remain -= first; + } + + return in->read( STATIC_CAST(char*, out) + first, count - first ); +} + + +// Mem_File_Reader + +Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : + begin( STATIC_CAST(const char*, p) ) +{ + set_size( s ); +} + +blargg_err_t Mem_File_Reader::read_v( void* p, int s ) +{ + memcpy( p, begin + tell(), s ); + return blargg_ok; +} + +blargg_err_t Mem_File_Reader::seek_v( int ) +{ + return blargg_ok; +} + + +// Callback_Reader + +Callback_Reader::Callback_Reader( callback_t c, BOOST::uint64_t s, void* d ) : + callback( c ), + user_data( d ) +{ + set_remain( s ); +} + +blargg_err_t Callback_Reader::read_v( void* out, int count ) +{ + return callback( user_data, out, count ); +} + + +// Callback_File_Reader + +Callback_File_Reader::Callback_File_Reader( callback_t c, BOOST::uint64_t s, void* d ) : + callback( c ), + user_data( d ) +{ + set_size( s ); +} + +blargg_err_t Callback_File_Reader::read_v( void* out, int count ) +{ + return callback( user_data, out, count, tell() ); +} + +blargg_err_t Callback_File_Reader::seek_v( int ) +{ + return blargg_ok; +} + +static const BOOST::uint8_t mask_tab[6]={0x80,0xE0,0xF0,0xF8,0xFC,0xFE}; + +static const BOOST::uint8_t val_tab[6]={0,0xC0,0xE0,0xF0,0xF8,0xFC}; + +size_t utf8_char_len_from_header( char p_c ) +{ + BOOST::uint8_t c = (BOOST::uint8_t)p_c; + + size_t cnt = 0; + for(;;) + { + if ( ( p_c & mask_tab[cnt] ) == val_tab[cnt] ) break; + if ( ++cnt >= 6 ) return 0; + } + + return cnt + 1; +} + +size_t utf8_decode_char( const char *p_utf8, unsigned & wide, size_t mmax ) +{ + const BOOST::uint8_t * utf8 = ( const BOOST::uint8_t* )p_utf8; + + if ( mmax == 0 ) + { + wide = 0; + return 0; + } + + if ( utf8[0] < 0x80 ) + { + wide = utf8[0]; + return utf8[0]>0 ? 1 : 0; + } + if ( mmax > 6 ) mmax = 6; + wide = 0; + + unsigned res=0; + unsigned n; + unsigned cnt=0; + for(;;) + { + if ( ( *utf8 & mask_tab[cnt] ) == val_tab[cnt] ) break; + if ( ++cnt >= mmax ) return 0; + } + cnt++; + + if ( cnt==2 && !( *utf8 & 0x1E ) ) return 0; + + if ( cnt == 1 ) + res = *utf8; + else + res = ( 0xFF >> ( cnt + 1 ) ) & *utf8; + + for ( n = 1; n < cnt; n++ ) + { + if ( ( utf8[n] & 0xC0 ) != 0x80 ) + return 0; + if ( !res && n == 2 && !( ( utf8[n] & 0x7F ) >> ( 7 - cnt ) ) ) + return 0; + + res = ( res << 6 ) | ( utf8[n] & 0x3F ); + } + + wide = res; + + return cnt; +} + +size_t utf8_encode_char( unsigned wide, char * target ) +{ + size_t count; + + if ( wide < 0x80 ) + count = 1; + else if ( wide < 0x800 ) + count = 2; + else if ( wide < 0x10000 ) + count = 3; + else if ( wide < 0x200000 ) + count = 4; + else if ( wide < 0x4000000 ) + count = 5; + else if ( wide <= 0x7FFFFFFF ) + count = 6; + else + return 0; + + if ( target == 0 ) + return count; + + switch ( count ) + { + case 6: + target[5] = 0x80 | ( wide & 0x3F ); + wide = wide >> 6; + wide |= 0x4000000; + case 5: + target[4] = 0x80 | ( wide & 0x3F ); + wide = wide >> 6; + wide |= 0x200000; + case 4: + target[3] = 0x80 | ( wide & 0x3F ); + wide = wide >> 6; + wide |= 0x10000; + case 3: + target[2] = 0x80 | ( wide & 0x3F ); + wide = wide >> 6; + wide |= 0x800; + case 2: + target[1] = 0x80 | ( wide & 0x3F ); + wide = wide >> 6; + wide |= 0xC0; + case 1: + target[0] = wide; + } + + return count; +} + +size_t utf16_encode_char( unsigned cur_wchar, blargg_wchar_t * out ) +{ + if ( cur_wchar < 0x10000 ) + { + if ( out ) *out = (blargg_wchar_t) cur_wchar; return 1; + } + else if ( cur_wchar < ( 1 << 20 ) ) + { + unsigned c = cur_wchar - 0x10000; + //MSDN: + //The first (high) surrogate is a 16-bit code value in the range U+D800 to U+DBFF. The second (low) surrogate is a 16-bit code value in the range U+DC00 to U+DFFF. Using surrogates, Unicode can support over one million characters. For more details about surrogates, refer to The Unicode Standard, version 2.0. + if ( out ) + { + out[0] = ( blargg_wchar_t )( 0xD800 | ( 0x3FF & ( c >> 10 ) ) ); + out[1] = ( blargg_wchar_t )( 0xDC00 | ( 0x3FF & c ) ) ; + } + return 2; + } + else + { + if ( out ) *out = '?'; return 1; + } +} + +size_t utf16_decode_char( const blargg_wchar_t * p_source, unsigned * p_out, size_t p_source_length ) +{ + if ( p_source_length == 0 ) return 0; + else if ( p_source_length == 1 ) + { + *p_out = p_source[0]; + return 1; + } + else + { + size_t retval = 0; + unsigned decoded = p_source[0]; + if ( decoded != 0 ) + { + retval = 1; + if ( ( decoded & 0xFC00 ) == 0xD800 ) + { + unsigned low = p_source[1]; + if ( ( low & 0xFC00 ) == 0xDC00 ) + { + decoded = 0x10000 + ( ( ( decoded & 0x3FF ) << 10 ) | ( low & 0x3FF ) ); + retval = 2; + } + } + } + *p_out = decoded; + return retval; + } +} + +// Converts wide-character path to UTF-8. Free result with free(). Only supported on Windows. +char* blargg_to_utf8( const blargg_wchar_t* wpath ) +{ + if ( wpath == NULL ) + return NULL; + + size_t needed = 0; + size_t mmax = blargg_wcslen( wpath ); + if ( mmax <= 0 ) + return NULL; + + size_t ptr = 0; + while ( ptr < mmax ) + { + unsigned wide = 0; + size_t char_len = utf16_decode_char( wpath + ptr, &wide, mmax - ptr ); + if ( !char_len ) break; + ptr += char_len; + needed += utf8_encode_char( wide, 0 ); + } + if ( needed <= 0 ) + return NULL; + + char* path = (char*) calloc( needed + 1, 1 ); + if ( path == NULL ) + return NULL; + + ptr = 0; + size_t actual = 0; + while ( ptr < mmax && actual < needed ) + { + unsigned wide = 0; + size_t char_len = utf16_decode_char( wpath + ptr, &wide, mmax - ptr ); + if ( !char_len ) break; + ptr += char_len; + actual += utf8_encode_char( wide, path + actual ); + } + + if ( actual == 0 ) + { + free( path ); + return NULL; + } + + assert( actual == needed ); + return path; +} + +// Converts UTF-8 path to wide-character. Free result with free() Only supported on Windows. +blargg_wchar_t* blargg_to_wide( const char* path ) +{ + if ( path == NULL ) + return NULL; + + size_t mmax = strlen( path ); + if ( mmax <= 0 ) + return NULL; + + size_t needed = 0; + size_t ptr = 0; + while ( ptr < mmax ) + { + unsigned wide = 0; + size_t char_len = utf8_decode_char( path + ptr, wide, mmax - ptr ); + if ( !char_len ) break; + ptr += char_len; + needed += utf16_encode_char( wide, 0 ); + } + if ( needed <= 0 ) + return NULL; + + blargg_wchar_t* wpath = (blargg_wchar_t*) calloc( needed + 1, sizeof *wpath ); + if ( wpath == NULL ) + return NULL; + + ptr = 0; + size_t actual = 0; + while ( ptr < mmax && actual < needed ) + { + unsigned wide = 0; + size_t char_len = utf8_decode_char( path + ptr, wide, mmax - ptr ); + if ( !char_len ) break; + ptr += char_len; + actual += utf16_encode_char( wide, wpath + actual ); + } + if ( actual == 0 ) + { + free( wpath ); + return NULL; + } + + assert( actual == needed ); + return wpath; +} + +#ifdef _WIN32 + +static FILE* blargg_fopen( const char path [], const char mode [] ) +{ + FILE* file = NULL; + blargg_wchar_t* wmode = NULL; + blargg_wchar_t* wpath = NULL; + + wpath = blargg_to_wide( path ); + if ( wpath ) + { + wmode = blargg_to_wide( mode ); + if ( wmode ) + file = _wfopen( wpath, wmode ); + } + + // Save and restore errno in case free() clears it + int saved_errno = errno; + free( wmode ); + free( wpath ); + errno = saved_errno; + + return file; +} + +#else + +static inline FILE* blargg_fopen( const char path [], const char mode [] ) +{ + return fopen( path, mode ); +} + +#endif + + +// Std_File_Reader + +Std_File_Reader::Std_File_Reader() +{ + file_ = NULL; +} + +Std_File_Reader::~Std_File_Reader() +{ + close(); +} + +static blargg_err_t blargg_fopen( FILE** out, const char path [] ) +{ + errno = 0; + *out = blargg_fopen( path, "rb" ); + if ( !*out ) + { + #ifdef ENOENT + if ( errno == ENOENT ) + return blargg_err_file_missing; + #endif + #ifdef ENOMEM + if ( errno == ENOMEM ) + return blargg_err_memory; + #endif + return blargg_err_file_read; + } + + return blargg_ok; +} + +static blargg_err_t blargg_fsize( FILE* f, long* out ) +{ + if ( fseek( f, 0, SEEK_END ) ) + return blargg_err_file_io; + + *out = ftell( f ); + if ( *out < 0 ) + return blargg_err_file_io; + + if ( fseek( f, 0, SEEK_SET ) ) + return blargg_err_file_io; + + return blargg_ok; +} + +blargg_err_t Std_File_Reader::open( const char path [] ) +{ + close(); + + FILE* f; + RETURN_ERR( blargg_fopen( &f, path ) ); + + long s; + blargg_err_t err = blargg_fsize( f, &s ); + if ( err ) + { + fclose( f ); + return err; + } + + file_ = f; + set_size( s ); + + return blargg_ok; +} + +void Std_File_Reader::make_unbuffered() +{ + if ( setvbuf( STATIC_CAST(FILE*, file_), NULL, _IONBF, 0 ) ) + check( false ); // shouldn't fail, but OK if it does +} + +blargg_err_t Std_File_Reader::read_v( void* p, int s ) +{ + if ( (size_t) s != fread( p, 1, s, STATIC_CAST(FILE*, file_) ) ) + { + // Data_Reader's wrapper should prevent EOF + check( !feof( STATIC_CAST(FILE*, file_) ) ); + + return blargg_err_file_io; + } + + return blargg_ok; +} + +#ifdef __CELLOS_LV2__ +int +fseeko(FILE *stream, off_t pos, int whence) +{ + return fseek(stream, (long)pos, whence); +} +#endif + +blargg_err_t Std_File_Reader::seek_v( BOOST::uint64_t n ) +{ +#ifdef _WIN32 + if ( fseek( STATIC_CAST(FILE*, file_), n, SEEK_SET ) ) +#else + if ( fseeko( STATIC_CAST(FILE*, file_), n, SEEK_SET ) ) +#endif + { + // Data_Reader's wrapper should prevent EOF + check( !feof( STATIC_CAST(FILE*, file_) ) ); + + return blargg_err_file_io; + } + + return blargg_ok; +} + +void Std_File_Reader::close() +{ + if ( file_ ) + { + fclose( STATIC_CAST(FILE*, file_) ); + file_ = NULL; + } +} + + +// Gzip_File_Reader + +#ifndef __LIBRETRO__ +#ifdef HAVE_ZLIB_H + +#include "zlib.h" + +static const char* get_gzip_eof( const char path [], long* eof ) +{ + FILE* file; + RETURN_ERR( blargg_fopen( &file, path ) ); + + int const h_size = 4; + unsigned char h [h_size]; + + // read four bytes to ensure that we can seek to -4 later + if ( fread( h, 1, h_size, file ) != (size_t) h_size || h[0] != 0x1F || h[1] != 0x8B ) + { + // Not gzipped + if ( ferror( file ) ) + return blargg_err_file_io; + + if ( fseek( file, 0, SEEK_END ) ) + return blargg_err_file_io; + + *eof = ftell( file ); + if ( *eof < 0 ) + return blargg_err_file_io; + } + else + { + // Gzipped; get uncompressed size from end + if ( fseek( file, -h_size, SEEK_END ) ) + return blargg_err_file_io; + + if ( fread( h, 1, h_size, file ) != (size_t) h_size ) + return blargg_err_file_io; + + *eof = get_le32( h ); + } + + if ( fclose( file ) ) + check( false ); + + return blargg_ok; +} + +Gzip_File_Reader::Gzip_File_Reader() +{ + file_ = NULL; +} + +Gzip_File_Reader::~Gzip_File_Reader() +{ + close(); +} + +blargg_err_t Gzip_File_Reader::open( const char path [] ) +{ + close(); + + long s; + RETURN_ERR( get_gzip_eof( path, &s ) ); + + file_ = gzopen( path, "rb" ); + if ( !file_ ) + return blargg_err_file_read; + + set_size( s ); + return blargg_ok; +} + +static blargg_err_t convert_gz_error( gzFile file ) +{ + int err; + gzerror( file, &err ); + + switch ( err ) + { + case Z_STREAM_ERROR: break; + case Z_DATA_ERROR: return blargg_err_file_corrupt; + case Z_MEM_ERROR: return blargg_err_memory; + case Z_BUF_ERROR: break; + } + return blargg_err_internal; +} + +blargg_err_t Gzip_File_Reader::read_v( void* p, int s ) +{ + int result = gzread( (gzFile) file_, p, s ); + if ( result != s ) + { + if ( result < 0 ) + return convert_gz_error( (gzFile) file_ ); + + return blargg_err_file_corrupt; + } + + return blargg_ok; +} + +blargg_err_t Gzip_File_Reader::seek_v( int n ) +{ + if ( gzseek( (gzFile) file_, n, SEEK_SET ) < 0 ) + return convert_gz_error( (gzFile) file_ ); + + return blargg_ok; +} + +void Gzip_File_Reader::close() +{ + if ( file_ ) + { + if ( gzclose( (gzFile) file_ ) ) + check( false ); + file_ = NULL; + } +} +#endif + +#endif diff --git a/quicknes/fex/Data_Reader.h b/quicknes/fex/Data_Reader.h new file mode 100644 index 0000000000..1cf92bd139 --- /dev/null +++ b/quicknes/fex/Data_Reader.h @@ -0,0 +1,265 @@ +// Lightweight interface for reading data from byte stream + +// File_Extractor 1.0.0 +#ifndef DATA_READER_H +#define DATA_READER_H + +#include "blargg_common.h" + +/* Some functions accept a long instead of int for convenience where caller has +a long due to some other interface, and would otherwise have to get a warning, +or cast it (and verify that it wasn't outside the range of an int). + +To really support huge (>2GB) files, long isn't a solution, since there's no +guarantee it's more than 32 bits. We'd need to use long long (if available), or +something compiler-specific, and change all places file sizes or offsets are +used. */ + +// Supports reading and finding out how many bytes are remaining +class Data_Reader { +public: + + // Reads min(*n,remain()) bytes and sets *n to this number, thus trying to read more + // tham remain() bytes doesn't result in error, just *n being set to remain(). + blargg_err_t read_avail( void* p, int* n ); + blargg_err_t read_avail( void* p, long* n ); + + // Reads exactly n bytes, or returns error if they couldn't ALL be read. + // Reading past end of file results in blargg_err_file_eof. + blargg_err_t read( void* p, int n ); + + // Number of bytes remaining until end of file + BOOST::uint64_t remain() const { return remain_; } + + // Reads and discards n bytes. Skipping past end of file results in blargg_err_file_eof. + blargg_err_t skip( int n ); + + virtual ~Data_Reader() { } + +private: + // noncopyable + Data_Reader( const Data_Reader& ); + Data_Reader& operator = ( const Data_Reader& ); + +// Derived interface +protected: + Data_Reader() : remain_( 0 ) { } + + // Sets remain + void set_remain( BOOST::uint64_t n ) { assert( n >= 0 ); remain_ = n; } + + // Do same as read(). Guaranteed that 0 < n <= remain(). Value of remain() is updated + // AFTER this call succeeds, not before. set_remain() should NOT be called from this. + virtual blargg_err_t read_v( void*, int n ) BLARGG_PURE( { (void)n; return blargg_ok; } ) + + // Do same as skip(). Guaranteed that 0 < n <= remain(). Default just reads data + // and discards it. Value of remain() is updated AFTER this call succeeds, not + // before. set_remain() should NOT be called from this. + virtual blargg_err_t skip_v( int n ); + +// Implementation +public: + BLARGG_DISABLE_NOTHROW + +private: + BOOST::uint64_t remain_; +}; + + +// Supports seeking in addition to Data_Reader operations +class File_Reader : public Data_Reader { +public: + + // Size of file + BOOST::uint64_t size() const { return size_; } + + // Current position in file + BOOST::uint64_t tell() const { return size_ - remain(); } + + // Goes to new position + blargg_err_t seek( BOOST::uint64_t ); + +// Derived interface +protected: + // Sets size and resets position + void set_size( BOOST::uint64_t n ) { size_ = n; Data_Reader::set_remain( n ); } + void set_size( int n ) { set_size( STATIC_CAST(BOOST::uint64_t, n) ); } + void set_size( long n ) { set_size( STATIC_CAST(BOOST::uint64_t, n) ); } + + // Sets reported position + void set_tell( BOOST::uint64_t i ) { assert( 0 <= i && i <= size_ ); Data_Reader::set_remain( size_ - i ); } + + // Do same as seek(). Guaranteed that 0 <= n <= size(). Value of tell() is updated + // AFTER this call succeeds, not before. set_* functions should NOT be called from this. + virtual blargg_err_t seek_v( BOOST::uint64_t n ) BLARGG_PURE( { (void)n; return blargg_ok; } ) + +// Implementation +protected: + File_Reader() : size_( 0 ) { } + + virtual blargg_err_t skip_v( BOOST::uint64_t ); + +private: + BOOST::uint64_t size_; + + void set_remain(); // avoid accidental use of set_remain +}; + + +// Reads from file on disk +class Std_File_Reader : public File_Reader { +public: + + // Opens file + blargg_err_t open( const char path [] ); + + // Closes file if one was open + void close(); + + // Switches to unbuffered mode. Useful if buffering is already being + // done at a higher level. + void make_unbuffered(); + +// Implementation +public: + Std_File_Reader(); + virtual ~Std_File_Reader(); + +protected: + virtual blargg_err_t read_v( void*, int ); + virtual blargg_err_t seek_v( BOOST::uint64_t ); + +private: + void* file_; +}; + + +// Treats range of memory as a file +class Mem_File_Reader : public File_Reader { +public: + + Mem_File_Reader( const void* begin, long size ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + virtual blargg_err_t seek_v( int ); + +private: + const char* const begin; +}; + + +// Allows only count bytes to be read from reader passed +class Subset_Reader : public Data_Reader { +public: + + Subset_Reader( Data_Reader*, BOOST::uint64_t count ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + +private: + Data_Reader* const in; +}; + + +// Joins already-read header and remaining data into original file. +// Meant for cases where you've already read header and don't want +// to seek and re-read data (for efficiency). +class Remaining_Reader : public Data_Reader { +public: + + Remaining_Reader( void const* header, int header_size, Data_Reader* ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + +private: + Data_Reader* const in; + void const* header; + int header_remain; +}; + + +// Invokes callback function to read data +extern "C" { // necessary to be usable from C + typedef const char* (*callback_reader_func_t)( + void* user_data, // Same value passed to constructor + void* out, // Buffer to place data into + int count // Number of bytes to read + ); +} +class Callback_Reader : public Data_Reader { +public: + typedef callback_reader_func_t callback_t; + Callback_Reader( callback_t, BOOST::uint64_t size, void* user_data ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + +private: + callback_t const callback; + void* const user_data; +}; + + +// Invokes callback function to read data +extern "C" { // necessary to be usable from C + typedef const char* (*callback_file_reader_func_t)( + void* user_data, // Same value passed to constructor + void* out, // Buffer to place data into + int count, // Number of bytes to read + BOOST::uint64_t pos // Position in file to read from + ); +} +class Callback_File_Reader : public File_Reader { +public: + typedef callback_file_reader_func_t callback_t; + Callback_File_Reader( callback_t, BOOST::uint64_t size, void* user_data ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + virtual blargg_err_t seek_v( int ); + +private: + callback_t const callback; + void* const user_data; +}; + + +#ifdef HAVE_ZLIB_H + +// Reads file compressed with gzip (or uncompressed) +class Gzip_File_Reader : public File_Reader { +public: + + // Opens possibly gzipped file + blargg_err_t open( const char path [] ); + + // Closes file if one was open + void close(); + +// Implementation +public: + Gzip_File_Reader(); + ~Gzip_File_Reader(); + +protected: + virtual blargg_err_t read_v( void*, int ); + virtual blargg_err_t seek_v( int ); + +private: + // void* so "zlib.h" doesn't have to be included here + void* file_; +}; +#endif + +char* blargg_to_utf8( const blargg_wchar_t* ); +blargg_wchar_t* blargg_to_wide( const char* ); + +#endif diff --git a/quicknes/fex/File_Extractor.cpp b/quicknes/fex/File_Extractor.cpp new file mode 100644 index 0000000000..68b061fa91 --- /dev/null +++ b/quicknes/fex/File_Extractor.cpp @@ -0,0 +1,341 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "File_Extractor.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +File_Extractor::fex_t( fex_type_t t ) : + type_( t ) +{ + own_file_ = NULL; + + close_(); +} + +// Open + +blargg_err_t File_Extractor::set_path( const char* path ) +{ + if ( !path ) + path = ""; + + RETURN_ERR( path_.resize( strlen( path ) + 1 ) ); + memcpy( path_.begin(), path, path_.size() ); + return blargg_ok; +} + +blargg_err_t File_Extractor::open( const char path [] ) +{ + close(); + + RETURN_ERR( set_path( path ) ); + + blargg_err_t err = open_path_v(); + if ( err ) + close(); + else + opened_ = true; + + return err; +} + +blargg_err_t File_Extractor::open_path_v() +{ + RETURN_ERR( open_arc_file() ); + + return open_v(); +} + +inline +static void make_unbuffered( Std_File_Reader* r ) +{ + r->make_unbuffered(); +} + +inline +static void make_unbuffered( void* ) +{ } + +blargg_err_t File_Extractor::open_arc_file( bool unbuffered ) +{ + if ( reader_ ) + return blargg_ok; + + FEX_FILE_READER* in = BLARGG_NEW FEX_FILE_READER; + CHECK_ALLOC( in ); + + blargg_err_t err = in->open( arc_path() ); + if ( err ) + { + delete in; + } + else + { + reader_ = in; + own_file(); + if ( unbuffered ) + make_unbuffered( in ); + } + + return err; +} + +blargg_err_t File_Extractor::open( File_Reader* input, const char* path ) +{ + close(); + + RETURN_ERR( set_path( path ) ); + + RETURN_ERR( input->seek( 0 ) ); + + reader_ = input; + blargg_err_t err = open_v(); + if ( err ) + close(); + else + opened_ = true; + + return err; +} + +// Close + +void File_Extractor::close() +{ + close_v(); + close_(); +} + +void File_Extractor::close_() +{ + delete own_file_; + own_file_ = NULL; + + tell_ = 0; + reader_ = NULL; + opened_ = false; + + path_.clear(); + clear_file(); +} + +File_Extractor::~fex_t() +{ + check( !opened() ); // fails if derived destructor didn't call close() + + delete own_file_; +} + +// Scanning + +void File_Extractor::clear_file() +{ + name_ = NULL; + wname_ = NULL; + done_ = true; + stat_called = false; + data_ptr_ = NULL; + + set_info( 0 ); + own_data_.clear(); + clear_file_v(); +} + +void File_Extractor::set_name( const char new_name [], const blargg_wchar_t* new_wname ) +{ + name_ = new_name; + wname_ = new_wname; + done_ = false; +} + +void File_Extractor::set_info( BOOST::uint64_t new_size, unsigned date, unsigned crc ) +{ + size_ = new_size; + date_ = (date != 0xFFFFFFFF ? date : 0); + crc32_ = crc; + set_remain( new_size ); +} + +blargg_err_t File_Extractor::next_() +{ + tell_++; + clear_file(); + + blargg_err_t err = next_v(); + if ( err ) + clear_file(); + + return err; +} + +blargg_err_t File_Extractor::next() +{ + assert( !done() ); + return next_(); +} + +blargg_err_t File_Extractor::rewind() +{ + assert( opened() ); + + tell_ = 0; + clear_file(); + + blargg_err_t err = rewind_v(); + if ( err ) + clear_file(); + + return err; +} + +blargg_err_t File_Extractor::stat() +{ + assert( !done() ); + + if ( !stat_called ) + { + RETURN_ERR( stat_v() ); + stat_called = true; + } + return blargg_ok; +} + +// Tell/seek + +int const pos_offset = 1; + +fex_pos_t File_Extractor::tell_arc() const +{ + assert( opened() ); + + fex_pos_t pos = tell_arc_v(); + assert( pos >= 0 ); + + return pos + pos_offset; +} + +blargg_err_t File_Extractor::seek_arc( fex_pos_t pos ) +{ + assert( opened() ); + assert( pos != 0 ); + + clear_file(); + + blargg_err_t err = seek_arc_v( pos - pos_offset ); + if ( err ) + clear_file(); + + return err; +} + +fex_pos_t File_Extractor::tell_arc_v() const +{ + return tell_; +} + +blargg_err_t File_Extractor::seek_arc_v( fex_pos_t pos ) +{ + // >= because seeking to current file should always reset read pointer etc. + if ( tell_ >= pos ) + RETURN_ERR( rewind() ); + + while ( tell_ < pos ) + { + RETURN_ERR( next_() ); + + if ( done() ) + { + assert( false ); + return blargg_err_caller; + } + } + + assert( tell_ == pos ); + + return blargg_ok; +} + +// Extraction + +blargg_err_t File_Extractor::rewind_file() +{ + RETURN_ERR( stat() ); + + if ( tell() > 0 ) + { + if ( data_ptr_ ) + { + set_remain( size() ); + } + else + { + RETURN_ERR( seek_arc( tell_arc() ) ); + RETURN_ERR( stat() ); + } + } + + return blargg_ok; +} + +blargg_err_t File_Extractor::data( const void** data_out ) +{ + assert( !done() ); + + *data_out = NULL; + if ( !data_ptr_ ) + { + BOOST::uint64_t old_tell = tell(); + + RETURN_ERR( rewind_file() ); + + void const* ptr; + RETURN_ERR( data_v( &ptr ) ); + data_ptr_ = ptr; + + // Now that data is in memory, we can seek by simply setting remain + set_remain( size() - old_tell ); + } + + *data_out = data_ptr_; + return blargg_ok; +} + +blargg_err_t File_Extractor::data_v( void const** out ) +{ + RETURN_ERR( own_data_.resize( size() ) ); + *out = own_data_.begin(); + + blargg_err_t err = extract_v( own_data_.begin(), own_data_.size() ); + if ( err ) + own_data_.clear(); + + return err; +} + +blargg_err_t File_Extractor::extract_v( void* out, int count ) +{ + void const* p; + RETURN_ERR( data( &p ) ); + memcpy( out, STATIC_CAST(char const*,p) + (size() - remain()), count ); + + return blargg_ok; +} + +blargg_err_t File_Extractor::read_v( void* out, int count ) +{ + if ( data_ptr_ ) + return File_Extractor::extract_v( out, count ); + + return extract_v( out, count ); +} diff --git a/quicknes/fex/File_Extractor.h b/quicknes/fex/File_Extractor.h new file mode 100644 index 0000000000..3855bf69c1 --- /dev/null +++ b/quicknes/fex/File_Extractor.h @@ -0,0 +1,191 @@ +// Compressed file archive interface + +// File_Extractor 1.0.0 +#ifndef FILE_EXTRACTOR_H +#define FILE_EXTRACTOR_H + +#include "blargg_common.h" +#include "Data_Reader.h" +#include "fex.h" + +struct fex_t : private Data_Reader { +public: + virtual ~fex_t(); + +// Open/close + + // Opens archive from custom data source. Keeps pointer until close(). + blargg_err_t open( File_Reader* input, const char* path = NULL ); + + // Takes ownership of File_Reader* passed to open(), so that close() + // will delete it. + void own_file() { own_file_ = reader_; } + + // See fex.h + blargg_err_t open( const char path [] ); + fex_type_t type() const { return type_; } + void close(); + +// Scanning + + // See fex.h + bool done() const { return done_; } + blargg_err_t next(); + blargg_err_t rewind(); + fex_pos_t tell_arc() const; + blargg_err_t seek_arc( fex_pos_t ); + +// Info + + // See fex.h + const char* name() const { return name_; } + const blargg_wchar_t* wname() const { return wname_; } + blargg_err_t stat(); + BOOST::uint64_t size() const { assert( stat_called ); return size_; } + unsigned int dos_date() const { return date_; } + unsigned int crc32() const { return crc32_; } + +// Extraction + + // Data_Reader to current file's data, so standard Data_Reader interface can + // be used, rather than having to treat archives specially. stat() must have + // been called. + Data_Reader& reader() { assert( stat_called ); return *this; } + + // See fex.h + blargg_err_t data( const void** data_out ); + BOOST::uint64_t tell() const { return size_ - remain(); } + +// Derived interface +protected: + + // Sets type of object + fex_t( fex_type_t ); + + // Path to archive file, or "" if none supplied + const char* arc_path() const { return path_.begin(); } + + // Opens archive file if it's not already. If unbuffered is true, opens file + // without any buffering. + blargg_err_t open_arc_file( bool unbuffered = false ); + + // Archive file + File_Reader& arc() const { return *reader_; } + + // Sets current file name + void set_name( const char name [], const blargg_wchar_t* wname = NULL ); + + // Sets current file information + void set_info( BOOST::uint64_t size, unsigned date = 0, unsigned crc = 0 ); + +// User overrides + + // Overrides must do indicated task. Non-pure functions have reasonable default + // implementation. Overrides should avoid calling public functions like + // next() and rewind(). + + // Open archive using file_path(). OK to delay actual file opening until later. + // Default just calls open_arc_file(), then open_v(). + virtual blargg_err_t open_path_v(); + + // Open archive using file() for source data. If unsupported, return error. + virtual blargg_err_t open_v() BLARGG_PURE( ; ) + + // Go to next file in archive and call set_name() and optionally set_info() + virtual blargg_err_t next_v() BLARGG_PURE( ; ) + + // Go back to first file in archive + virtual blargg_err_t rewind_v() BLARGG_PURE( ; ) + + // Close archive. Called even if open_path_v() or open_v() return unsuccessfully. + virtual void close_v() BLARGG_PURE( ; ) + + // Clear any fields related to current file + virtual void clear_file_v() { } + + // Call set_info() if not already called by next_v() + virtual blargg_err_t stat_v() { return blargg_ok; } + + // Return value that allows later return to this file. Result must be >= 0. + virtual fex_pos_t tell_arc_v() const; + + // Return to previously saved position + virtual blargg_err_t seek_arc_v( fex_pos_t ); + + // One or both of the following must be overridden + + // Provide pointer to data for current file in archive + virtual blargg_err_t data_v( const void** out ); + + // Extract next n bytes + virtual blargg_err_t extract_v( void* out, int n ); + +// Implementation +public: + BLARGG_DISABLE_NOTHROW + +private: + fex_type_t const type_; + + // Archive file + blargg_vector path_; + File_Reader* reader_; + File_Reader* own_file_; + bool opened_; + + // Position in archive + fex_pos_t tell_; // only used by default implementation of tell/seek + bool done_; + + // Info for current file in archive + const char* name_; + const blargg_wchar_t* wname_; + unsigned date_; + unsigned crc32_; + BOOST::uint64_t size_; + bool stat_called; + + // Current file contents + void const* data_ptr_; // NULL if not read into memory + blargg_vector own_data_; + + bool opened() const { return opened_; } + void clear_file(); + void close_(); + blargg_err_t set_path( const char* path ); + blargg_err_t rewind_file(); + blargg_err_t next_(); + + // Data_Reader overrides + // TODO: override skip_v? + virtual blargg_err_t read_v( void* out, int n ); +}; + +struct fex_type_t_ +{ + const char* extension; + File_Extractor* (*new_fex)(); + const char* name; + blargg_err_t (*init)(); // Called by fex_init(). Can be NULL. +}; + +extern const fex_type_t_ + fex_7z_type [1], + fex_gz_type [1], + fex_rar_type [1], + fex_zip_type [1], + fex_bin_type [1]; + +inline blargg_err_t File_Extractor::open_v() { return blargg_ok; } +inline blargg_err_t File_Extractor::next_v() { return blargg_ok; } +inline blargg_err_t File_Extractor::rewind_v() { return blargg_ok; } +inline void File_Extractor::close_v() { } + +// Default to Std_File_Reader for archive access +#ifndef FEX_FILE_READER + #define FEX_FILE_READER Std_File_Reader +#elif defined (FEX_FILE_READER_INCLUDE) + #include FEX_FILE_READER_INCLUDE +#endif + +#endif diff --git a/quicknes/fex/Gzip_Extractor.cpp b/quicknes/fex/Gzip_Extractor.cpp new file mode 100644 index 0000000000..6946de27da --- /dev/null +++ b/quicknes/fex/Gzip_Extractor.cpp @@ -0,0 +1,98 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Gzip_Extractor.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// TODO: could close file once data has been read into memory + +static blargg_err_t init_gzip_file() +{ + get_crc_table(); // initialize zlib's CRC-32 tables + return blargg_ok; +} + +static File_Extractor* new_gzip() +{ + return BLARGG_NEW Gzip_Extractor; +} + +fex_type_t_ const fex_gz_type [1] = {{ + ".gz", + &new_gzip, + "gzipped file", + &init_gzip_file +}}; + +Gzip_Extractor::Gzip_Extractor() : + File_Extractor( fex_gz_type ) +{ } + +Gzip_Extractor::~Gzip_Extractor() +{ + close(); +} + +blargg_err_t Gzip_Extractor::open_path_v() +{ + // skip opening file + return open_v(); +} + +blargg_err_t Gzip_Extractor::stat_v() +{ + RETURN_ERR( open_arc_file( true ) ); + if ( !gr.opened() || gr.tell() != 0 ) + RETURN_ERR( gr.open( &arc() ) ); + + set_info( gr.remain(), 0, gr.crc32() ); + return blargg_ok; +} + +blargg_err_t Gzip_Extractor::open_v() +{ + // Remove .gz suffix + size_t len = strlen( arc_path() ); + if ( fex_has_extension( arc_path(), ".gz" ) ) + len -= 3; + + RETURN_ERR( name.resize( len + 1 ) ); + memcpy( name.begin(), arc_path(), name.size() ); + name [name.size() - 1] = '\0'; + + set_name( name.begin() ); + return blargg_ok; +} + +void Gzip_Extractor::close_v() +{ + name.clear(); + gr.close(); +} + +blargg_err_t Gzip_Extractor::next_v() +{ + return blargg_ok; +} + +blargg_err_t Gzip_Extractor::rewind_v() +{ + set_name( name.begin() ); + return blargg_ok; +} + +blargg_err_t Gzip_Extractor::extract_v( void* p, int n ) +{ + return gr.read( p, n ); +} diff --git a/quicknes/fex/Gzip_Extractor.h b/quicknes/fex/Gzip_Extractor.h new file mode 100644 index 0000000000..02044d0c78 --- /dev/null +++ b/quicknes/fex/Gzip_Extractor.h @@ -0,0 +1,34 @@ +// Presents a gzipped file as an "archive" of just that file. +// Also handles non-gzipped files. + +// File_Extractor 1.0.0 +#ifndef GZIP_EXTRACTOR_H +#define GZIP_EXTRACTOR_H + +#include "File_Extractor.h" +#include "Gzip_Reader.h" + +class Gzip_Extractor : public File_Extractor { +public: + Gzip_Extractor(); + virtual ~Gzip_Extractor(); + +protected: + virtual blargg_err_t open_path_v(); + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + + virtual blargg_err_t stat_v(); + virtual blargg_err_t extract_v( void*, int ); + +private: + Gzip_Reader gr; + blargg_vector name; + + void set_info_(); +}; + +#endif diff --git a/quicknes/fex/Gzip_Reader.cpp b/quicknes/fex/Gzip_Reader.cpp new file mode 100644 index 0000000000..464beee7e4 --- /dev/null +++ b/quicknes/fex/Gzip_Reader.cpp @@ -0,0 +1,85 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Gzip_Reader.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2006-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Gzip_Reader::Gzip_Reader() +{ + close(); +} + +Gzip_Reader::~Gzip_Reader() +{ } + +static blargg_err_t gzip_reader_read( void* file, void* out, int* count ) +{ + return STATIC_CAST(File_Reader*,file)->read_avail( out, count ); +} + +blargg_err_t Gzip_Reader::calc_size() +{ + size_ = in->size(); + crc32_ = 0; + if ( inflater.deflated() ) + { + byte trailer [8]; + int old_pos = in->tell(); + RETURN_ERR( in->seek( size_ - sizeof trailer ) ); + RETURN_ERR( in->read( trailer, sizeof trailer ) ); + RETURN_ERR( in->seek( old_pos ) ); + crc32_ = get_le32( trailer + 0 ); + + unsigned n = get_le32( trailer + 4 ); + if ( n > INT_MAX ) + return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "gzip larger than 2GB" ); + + size_ = n; + } + return blargg_ok; +} + +blargg_err_t Gzip_Reader::open( File_Reader* new_in ) +{ + close(); + + in = new_in; + RETURN_ERR( in->seek( 0 ) ); + RETURN_ERR( inflater.begin( gzip_reader_read, new_in ) ); + RETURN_ERR( inflater.set_mode( inflater.mode_auto ) ); + RETURN_ERR( calc_size() ); + set_remain( size_ ); + + return blargg_ok; +} + +void Gzip_Reader::close() +{ + in = NULL; + inflater.end(); +} + +blargg_err_t Gzip_Reader::read_v( void* out, int count ) +{ + assert( in ); + int actual = count; + RETURN_ERR( inflater.read( out, &actual ) ); + + if ( actual != count ) + return blargg_err_file_corrupt; + + return blargg_ok; +} diff --git a/quicknes/fex/Gzip_Reader.h b/quicknes/fex/Gzip_Reader.h new file mode 100644 index 0000000000..b514772faa --- /dev/null +++ b/quicknes/fex/Gzip_Reader.h @@ -0,0 +1,46 @@ +// Transparently decompresses gzip files, as well as uncompressed + +// File_Extractor 1.0.0 +#ifndef GZIP_READER_H +#define GZIP_READER_H + +#include "Data_Reader.h" +#include "Zlib_Inflater.h" + +class Gzip_Reader : public Data_Reader { +public: + // Keeps pointer to reader until close(). If + blargg_err_t open( File_Reader* ); + + // True if file is open + bool opened() const { return in != NULL; } + + // Frees memory + void close(); + + // True if file is compressed + bool deflated() const { return inflater.deflated(); } + + // CRC-32 of data, of 0 if unavailable + unsigned int crc32() const { return crc32_; } + + // Number of bytes read since opening + BOOST::uint64_t tell() const { return size_ - remain(); } + +public: + Gzip_Reader(); + virtual ~Gzip_Reader(); + +protected: + virtual blargg_err_t read_v( void*, int ); + +private: + File_Reader* in; + unsigned crc32_; + int size_; + Zlib_Inflater inflater; + + blargg_err_t calc_size(); +}; + +#endif diff --git a/quicknes/fex/Rar_Extractor.cpp b/quicknes/fex/Rar_Extractor.cpp new file mode 100644 index 0000000000..410586464e --- /dev/null +++ b/quicknes/fex/Rar_Extractor.cpp @@ -0,0 +1,197 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "blargg_common.h" + +#if FEX_ENABLE_RAR + +#include "Rar_Extractor.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +static blargg_err_t init_rar() +{ + unrar_init(); + return blargg_ok; +} + +static File_Extractor* new_rar() +{ + return BLARGG_NEW Rar_Extractor; +} + +fex_type_t_ const fex_rar_type [1] = {{ + ".rar", + &new_rar, + "RAR archive", + &init_rar +}}; + +blargg_err_t Rar_Extractor::convert_err( unrar_err_t err ) +{ + blargg_err_t reader_err = reader.err; + reader.err = blargg_ok; + if ( reader_err ) + check( err == unrar_next_err ); + + switch ( err ) + { + case unrar_ok: return blargg_ok; + case unrar_err_memory: return blargg_err_memory; + case unrar_err_open: return blargg_err_file_read; + case unrar_err_not_arc: return blargg_err_file_type; + case unrar_err_corrupt: return blargg_err_file_corrupt; + case unrar_err_io: return blargg_err_file_io; + case unrar_err_arc_eof: return blargg_err_internal; + case unrar_err_encrypted: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR encryption not supported" ); + case unrar_err_segmented: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR segmentation not supported" ); + case unrar_err_huge: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "Huge RAR files not supported" ); + case unrar_err_old_algo: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "Old RAR compression not supported" ); + case unrar_err_new_algo: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR uses unknown newer compression" ); + case unrar_next_err: break; + default: + check( false ); // unhandled RAR error + } + + if ( reader_err ) + return reader_err; + + check( false ); + return BLARGG_ERR( BLARGG_ERR_INTERNAL, "RAR archive" ); +} + +static inline unrar_err_t handle_err( Rar_Extractor::read_callback_t* h, blargg_err_t err ) +{ + if ( !err ) + return unrar_ok; + + h->err = err; + return unrar_next_err; +} + +extern "C" +{ + static unrar_err_t my_unrar_read( void* data, void* out, int* count, unrar_pos_t pos ) + { + // TODO: 64-bit file support + + Rar_Extractor::read_callback_t* h = STATIC_CAST(Rar_Extractor::read_callback_t*,data); + if ( h->pos != pos ) + { + blargg_err_t err = h->in->seek( pos ); + if ( err ) + return handle_err( h, err ); + + h->pos = pos; + } + + blargg_err_t err = h->in->read_avail( out, count ); + if ( err ) + return handle_err( h, err ); + + h->pos += *count; + + return unrar_ok; + } +} + +Rar_Extractor::Rar_Extractor() : + File_Extractor( fex_rar_type ) +{ + unrar = NULL; +} + +Rar_Extractor::~Rar_Extractor() +{ + close(); +} + +blargg_err_t Rar_Extractor::open_v() +{ + reader.pos = 0; + reader.in = &arc(); + reader.err = blargg_ok; + + RETURN_ERR( arc().seek( 0 ) ); + RETURN_ERR( convert_err( unrar_open_custom( &unrar, &my_unrar_read, &reader ) ) ); + return skip_unextractables(); +} + +void Rar_Extractor::close_v() +{ + unrar_close( unrar ); + + unrar = NULL; + reader.in = NULL; +} + +blargg_err_t Rar_Extractor::skip_unextractables() +{ + while ( !unrar_done( unrar ) && unrar_try_extract( unrar ) ) + RETURN_ERR( next_raw() ); + + if ( !unrar_done( unrar ) ) + { + unrar_info_t const* info = unrar_info( unrar ); + + set_name( info->name, (info->name_w && *info->name_w) ? info->name_w : NULL ); + set_info( info->size, info->dos_date, (info->is_crc32 ? info->crc : 0) ); + } + + return blargg_ok; +} + +blargg_err_t Rar_Extractor::next_raw() +{ + return convert_err( unrar_next( unrar ) ); +} + +blargg_err_t Rar_Extractor::next_v() +{ + RETURN_ERR( next_raw() ); + return skip_unextractables(); +} + +blargg_err_t Rar_Extractor::rewind_v() +{ + RETURN_ERR( convert_err( unrar_rewind( unrar ) ) ); + return skip_unextractables(); +} + +fex_pos_t Rar_Extractor::tell_arc_v() const +{ + return unrar_tell( unrar ); +} + +blargg_err_t Rar_Extractor::seek_arc_v( fex_pos_t pos ) +{ + RETURN_ERR( convert_err( unrar_seek( unrar, pos ) ) ); + return skip_unextractables(); +} + +blargg_err_t Rar_Extractor::data_v( void const** out ) +{ + return convert_err( unrar_extract_mem( unrar, out ) ); +} + +blargg_err_t Rar_Extractor::extract_v( void* out, int count ) +{ + // We can read entire file directly into user buffer + if ( count == size() ) + return convert_err( unrar_extract( unrar, out, count ) ); + + // This will call data_v() and copy from that buffer for us + return File_Extractor::extract_v( out, count ); +} + +#endif diff --git a/quicknes/fex/Rar_Extractor.h b/quicknes/fex/Rar_Extractor.h new file mode 100644 index 0000000000..a2dbf39e93 --- /dev/null +++ b/quicknes/fex/Rar_Extractor.h @@ -0,0 +1,43 @@ +// RAR archive extractor + +// File_Extractor 1.0.0 +#ifndef RAR_EXTRACTOR_H +#define RAR_EXTRACTOR_H + +#include "File_Extractor.h" +#include "unrar/unrar.h" + +class Rar_Extractor : public File_Extractor { +public: + Rar_Extractor(); + virtual ~Rar_Extractor(); + + struct read_callback_t + { + const char* err; + BOOST::uint64_t pos; + File_Reader* in; + }; + +protected: + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + virtual fex_pos_t tell_arc_v() const; + virtual blargg_err_t seek_arc_v( fex_pos_t ); + + virtual blargg_err_t data_v( void const** ); + virtual blargg_err_t extract_v( void*, int ); + +private: + unrar_t* unrar; + read_callback_t reader; + + blargg_err_t convert_err( unrar_err_t ); + blargg_err_t skip_unextractables(); + blargg_err_t next_raw(); +}; + +#endif diff --git a/quicknes/fex/Zip7_Extractor.cpp b/quicknes/fex/Zip7_Extractor.cpp new file mode 100644 index 0000000000..65c935a0fc --- /dev/null +++ b/quicknes/fex/Zip7_Extractor.cpp @@ -0,0 +1,290 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Zip7_Extractor.h" + +extern "C" { +#include "7z_C/7z.h" +#include "7z_C/7zAlloc.h" +#include "7z_C/7zCrc.h" +} + +#include + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +static ISzAlloc zip7_alloc = { SzAlloc, SzFree }; +static ISzAlloc zip7_alloc_temp = { SzAllocTemp, SzFreeTemp }; + +struct Zip7_Extractor_Impl : + ISeekInStream +{ + CLookToRead look; + CSzArEx db; + + // SzExtract state + UInt32 block_index; + Byte* buf; + size_t buf_size; + + File_Reader* in; + const char* in_err; +}; + +extern "C" +{ + // 7-zip callbacks pass an ISeekInStream* for data, so we must cast it + // back to ISeekInStream* FIRST, then cast to our Impl structure + + static SRes zip7_read_( void* vstream, void* out, size_t* size ) + { + assert( out && size ); + ISeekInStream* stream = STATIC_CAST(ISeekInStream*,vstream); + Zip7_Extractor_Impl* impl = STATIC_CAST(Zip7_Extractor_Impl*,stream); + + long lsize = *size; + blargg_err_t err = impl->in->read_avail( out, &lsize ); + if ( err ) + { + *size = 0; + impl->in_err = err; + return SZ_ERROR_READ; + } + + *size = lsize; + return SZ_OK; + } + + static SRes zip7_seek_( void* vstream, Int64* pos, ESzSeek mode ) + { + ISeekInStream* stream = STATIC_CAST(ISeekInStream*,vstream); + Zip7_Extractor_Impl* impl = STATIC_CAST(Zip7_Extractor_Impl*,stream); + + if ( mode == SZ_SEEK_CUR ) + { + assert( *pos == 0 ); // only used to find the archive start position + *pos = impl->in->tell(); + return SZ_OK; + } + + if ( mode == SZ_SEEK_END ) + { + assert( *pos == 0 ); // only used to find file length + *pos = impl->in->size(); + return SZ_OK; + } + + assert( mode == SZ_SEEK_SET ); + blargg_err_t err = impl->in->seek( *pos ); + if ( err ) + { + // don't set in_err in this case, since it might be benign + if ( err == blargg_err_file_eof ) + return SZ_ERROR_INPUT_EOF; + + impl->in_err = err; + return SZ_ERROR_READ; + } + + return SZ_OK; + } +} + +blargg_err_t Zip7_Extractor::zip7_err( int err ) +{ + // TODO: ignore in_err in some cases? unsure about which error to use + blargg_err_t in_err = impl->in_err; + impl->in_err = NULL; + if ( in_err ) + { + check( err != SZ_OK ); + return in_err; + } + + switch ( err ) + { + case SZ_OK: return blargg_ok; + case SZ_ERROR_MEM: return blargg_err_memory; + case SZ_ERROR_READ: return blargg_err_file_io; + case SZ_ERROR_CRC: + case SZ_ERROR_DATA: + case SZ_ERROR_INPUT_EOF: + case SZ_ERROR_ARCHIVE: return blargg_err_file_corrupt; + case SZ_ERROR_UNSUPPORTED: return blargg_err_file_feature; + case SZ_ERROR_NO_ARCHIVE: return blargg_err_file_type; + } + + return blargg_err_generic; +} + +static blargg_err_t init_7z() +{ + static bool inited; + if ( !inited ) + { + inited = true; + CrcGenerateTable(); + } + return blargg_ok; +} + +static File_Extractor* new_7z() +{ + return BLARGG_NEW Zip7_Extractor; +} + +fex_type_t_ const fex_7z_type [1] = {{ + ".7z", + &new_7z, + "7-zip archive", + &init_7z +}}; + +Zip7_Extractor::Zip7_Extractor() : + File_Extractor( fex_7z_type ) +{ + impl = NULL; +} + +Zip7_Extractor::~Zip7_Extractor() +{ + close(); +} + +blargg_err_t Zip7_Extractor::open_v() +{ + RETURN_ERR( init_7z() ); + + if ( !impl ) + { + impl = (Zip7_Extractor_Impl*) malloc( sizeof *impl ); + CHECK_ALLOC( impl ); + } + + impl->in = &arc(); + impl->block_index = (UInt32) -1; + impl->buf = NULL; + impl->buf_size = 0; + + LookToRead_CreateVTable( &impl->look, false ); + impl->ISeekInStream::Read = zip7_read_; + impl->ISeekInStream::Seek = zip7_seek_; + impl->look.realStream = impl; + LookToRead_Init( &impl->look ); + + SzArEx_Init( &impl->db ); + + impl->in_err = NULL; + RETURN_ERR( zip7_err( SzArEx_Open( &impl->db, &impl->look.s, + &zip7_alloc, &zip7_alloc_temp ) ) ); + + return seek_arc_v( 0 ); +} + +void Zip7_Extractor::close_v() +{ + if ( impl ) + { + if ( impl->in ) + { + impl->in = NULL; + SzArEx_Free( &impl->db, &zip7_alloc ); + } + IAlloc_Free( &zip7_alloc, impl->buf ); + free( impl ); + impl = NULL; + } +} + +blargg_err_t Zip7_Extractor::next_v() +{ + while ( ++index < (int) impl->db.db.NumFiles ) + { + CSzFileItem const& item = impl->db.db.Files [index]; + if ( !item.IsDir ) + { + unsigned long date = 0; + if ( item.MTimeDefined ) + { + const UInt64 epoch = ((UInt64)0x019db1de << 32) + 0xd53e8000; + /* 0x019db1ded53e8000ULL: 1970-01-01 00:00:00 (UTC) */ + struct tm tm; + + UInt64 time = ((UInt64)item.MTime.High << 32) + item.MTime.Low - epoch; + time /= 1000000; + + time_t _time = time; + +#ifdef _MSC_VER + localtime_s( &tm, &_time ); +#else + localtime_r( &_time, &tm ); +#endif + + date = ( tm.tm_sec >> 1 ) & 0x1F | + (( tm.tm_min & 0x3F ) << 5 ) | + (( tm.tm_hour & 0x1F ) << 11 ) | + (( tm.tm_mday & 0x1F ) << 16 ) | + (( ( tm.tm_mon + 1 ) & 0x0F ) << 21 ) | + (( ( tm.tm_year - 80 ) & 0x7F ) << 25 ); + } + + size_t name_length = SzArEx_GetFileNameUtf16( &impl->db, index, 0 ); + name16.resize( name_length ); + SzArEx_GetFileNameUtf16( &impl->db, index, ( UInt16 * ) name16.begin() ); + char * temp = blargg_to_utf8( name16.begin() ); + if ( !temp ) temp = ""; + size_t utf8_length = strlen( temp ); + name8.resize( utf8_length + 1 ); + memcpy( name8.begin(), temp, utf8_length + 1 ); + free( temp ); + set_name( name8.begin(), name16.begin() ); + set_info( item.Size, 0, (item.CrcDefined ? item.Crc : 0) ); + break; + } + } + + return blargg_ok; +} + +blargg_err_t Zip7_Extractor::rewind_v() +{ + return seek_arc_v( 0 ); +} + +fex_pos_t Zip7_Extractor::tell_arc_v() const +{ + return index; +} + +blargg_err_t Zip7_Extractor::seek_arc_v( fex_pos_t pos ) +{ + assert( 0 <= pos && pos <= (int) impl->db.db.NumFiles ); + + index = pos - 1; + return next_v(); +} + +blargg_err_t Zip7_Extractor::data_v( void const** out ) +{ + impl->in_err = NULL; + size_t offset = 0; + size_t count = 0; + RETURN_ERR( zip7_err( SzArEx_Extract( &impl->db, &impl->look.s, index, + &impl->block_index, &impl->buf, &impl->buf_size, + &offset, &count, &zip7_alloc, &zip7_alloc_temp ) ) ); + assert( count == (size_t) size() ); + + *out = impl->buf + offset; + return blargg_ok; +} diff --git a/quicknes/fex/Zip7_Extractor.h b/quicknes/fex/Zip7_Extractor.h new file mode 100644 index 0000000000..b533a12819 --- /dev/null +++ b/quicknes/fex/Zip7_Extractor.h @@ -0,0 +1,36 @@ +// 7-zip archive extractor + +// File_Extractor 1.0.0 +#ifndef ZIP7_EXTRACTOR_H +#define ZIP7_EXTRACTOR_H + +#include "File_Extractor.h" + +struct Zip7_Extractor_Impl; + +class Zip7_Extractor : public File_Extractor { +public: + Zip7_Extractor(); + virtual ~Zip7_Extractor(); + +protected: + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + virtual fex_pos_t tell_arc_v() const; + virtual blargg_err_t seek_arc_v( fex_pos_t ); + + virtual blargg_err_t data_v( void const** out ); + +private: + Zip7_Extractor_Impl* impl; + int index; + blargg_vector name8; + blargg_vector name16; + + blargg_err_t zip7_err( int err ); +}; + +#endif diff --git a/quicknes/fex/Zip_Extractor.cpp b/quicknes/fex/Zip_Extractor.cpp new file mode 100644 index 0000000000..fd08d102ee --- /dev/null +++ b/quicknes/fex/Zip_Extractor.cpp @@ -0,0 +1,390 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Zip_Extractor.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +/* To avoid copying filename string from catalog, I terminate it by modifying +catalog data. This potentially requires moving the first byte of the type +of the next entry elsewhere; I move it to the first byte of made_by. Kind +of hacky, but I'd rather not have to allocate memory for a copy of it. */ + +#include "blargg_source.h" + +/* Reads this much from end of file when first opening. Only this much is +searched for the end catalog entry. If whole catalog is within this data, +nothing more needs to be read on open. */ +int const end_read_size = 8 * 1024; + +/* Reads are are made using file offset that's a multiple of this, +increasing performance. */ +int const disk_block_size = 4 * 1024; + +// Read buffer used for extracting file data +int const read_buf_size = 16 * 1024; + +struct header_t +{ + char type [4]; + byte vers [2]; + byte flags [2]; + byte method [2]; + byte date [4]; + byte crc [4]; + byte raw_size [4]; + byte size [4]; + byte filename_len [2]; + byte extra_len [2]; + char filename [2]; // [filename_len] + //char extra [extra_len]; +}; +int const header_size = 30; + +struct entry_t +{ + char type [4]; + byte made_by [2]; + byte vers [2]; + byte flags [2]; + byte method [2]; + byte date [4]; + byte crc [4]; + byte raw_size [4]; + byte size [4]; + byte filename_len [2]; + byte extra_len [2]; + byte comment_len [2]; + byte disk [2]; + byte int_attrib [2]; + byte ext_attrib [4]; + byte file_offset [4]; + char filename [2]; // [filename_len] + //char extra [extra_len]; + //char comment [comment_len]; +}; +int const entry_size = 46; + +struct end_entry_t +{ + char type [4]; + byte disk [2]; + byte first_disk [2]; + byte disk_entry_count [2]; + byte entry_count [2]; + byte dir_size [4]; + byte dir_offset [4]; + byte comment_len [2]; + char comment [2]; // [comment_len] +}; +int const end_entry_size = 22; + +static blargg_err_t init_zip() +{ + get_crc_table(); // initialize zlib's CRC-32 tables + return blargg_ok; +} + +static File_Extractor* new_zip() +{ + return BLARGG_NEW Zip_Extractor; +} + +fex_type_t_ const fex_zip_type [1] = {{ + ".zip", + &new_zip, + "ZIP archive", + &init_zip +}}; + +Zip_Extractor::Zip_Extractor() : + File_Extractor( fex_zip_type ) +{ + Zip_Extractor::clear_file_v(); + + // If these fail, structures had extra padding inserted by compiler + assert( offsetof (header_t,filename) == header_size ); + assert( offsetof (entry_t,filename) == entry_size ); + assert( offsetof (end_entry_t,comment) == end_entry_size ); +} + +Zip_Extractor::~Zip_Extractor() +{ + close(); +} + +blargg_err_t Zip_Extractor::open_path_v() +{ + RETURN_ERR( open_arc_file( true ) ); + return File_Extractor::open_path_v(); +} + +inline +void Zip_Extractor::reorder_entry_header( int offset ) +{ + catalog [offset + 0] = 0; + catalog [offset + 4] = 'P'; +} + +blargg_err_t Zip_Extractor::open_v() +{ + if ( arc().size() < end_entry_size ) + return blargg_err_file_type; + + // Read final end_read_size bytes of file + BOOST::uint64_t file_pos = max( (BOOST::uint64_t) 0, arc().size() - end_read_size ); + file_pos -= file_pos % disk_block_size; + RETURN_ERR( catalog.resize( arc().size() - file_pos ) ); + RETURN_ERR( arc().seek( file_pos ) ); + RETURN_ERR( arc().read( catalog.begin(), catalog.size() ) ); + + // Find end-of-catalog entry + BOOST::uint64_t end_pos = catalog.size() - end_entry_size; + while ( end_pos >= 0 && memcmp( &catalog [end_pos], "PK\5\6", 4 ) ) + end_pos--; + if ( end_pos < 0 ) + return blargg_err_file_type; + end_entry_t const& end_entry = (end_entry_t&) catalog [end_pos]; + end_pos += file_pos; + + // some idiotic zip compressors add data to end of zip without setting comment len +// check( arc().size() == end_pos + end_entry_size + get_le16( end_entry.comment_len ) ); + + // Find file offset of beginning of catalog + catalog_begin = get_le32( end_entry.dir_offset ); + int catalog_size = end_pos - catalog_begin; + if ( catalog_size < 0 ) + return blargg_err_file_corrupt; + catalog_size += end_entry_size; + + // See if catalog is entirely contained in bytes already read + BOOST::uint64_t begin_offset = catalog_begin - file_pos; + if ( begin_offset >= 0 ) + memmove( catalog.begin(), &catalog [begin_offset], catalog_size ); + + RETURN_ERR( catalog.resize( catalog_size ) ); + if ( begin_offset < 0 ) + { + // Catalog begins before bytes read, so it needs to be read + RETURN_ERR( arc().seek( catalog_begin ) ); + RETURN_ERR( arc().read( catalog.begin(), catalog.size() ) ); + } + + // First entry in catalog should be a file or end of archive + if ( memcmp( catalog.begin(), "PK\1\2", 4 ) && memcmp( catalog.begin(), "PK\5\6", 4 ) ) + return blargg_err_file_type; + + reorder_entry_header( 0 ); + return rewind_v(); +} + +void Zip_Extractor::close_v() +{ + catalog.clear(); +} + +// Scanning + +inline +static bool is_normal_file( entry_t const& e, unsigned len ) +{ + int last_char = (len ? e.filename [len - 1] : '/'); + bool is_dir = (last_char == '/' || last_char == '\\'); + if ( is_dir && get_le32( e.size ) == 0 ) + return false; + check( !is_dir ); + + // Mac OS X puts meta-information in separate files with normal extensions, + // so they must be filtered out or caller will mistake them for normal files. + if ( e.made_by[1] == 3 ) + { + const char* dir = strrchr( e.filename, '/' ); + if ( dir ) + dir++; + else + dir = e.filename; + + if ( *dir == '.' ) + return false; + + if ( !strcmp( dir, "Icon\x0D" ) ) + return false; + } + + return true; +} + +blargg_err_t Zip_Extractor::update_info( bool advance_first ) +{ + while ( 1 ) + { + entry_t& e = (entry_t&) catalog [catalog_pos]; + + if ( memcmp( e.type, "\0K\1\2P", 5 ) && memcmp( e.type, "PK\1\2", 4 ) ) + { + check( !memcmp( e.type, "\0K\5\6P", 5 ) ); + break; + } + + unsigned len = get_le16( e.filename_len ); + int next_offset = catalog_pos + entry_size + len + get_le16( e.extra_len ) + + get_le16( e.comment_len ); + if ( (unsigned) next_offset > catalog.size() - end_entry_size ) + return blargg_err_file_corrupt; + + if ( catalog [next_offset] == 'P' ) + reorder_entry_header( next_offset ); + + if ( !advance_first ) + { + e.filename [len] = 0; // terminate name + + if ( is_normal_file( e, len ) ) + { + set_name( e.filename ); + set_info( get_le32( e.size ), get_le32( e.date ), get_le32( e.crc ) ); + break; + } + } + + catalog_pos = next_offset; + advance_first = false; + } + + return blargg_ok; +} + +blargg_err_t Zip_Extractor::next_v() +{ + return update_info( true ); +} + +blargg_err_t Zip_Extractor::rewind_v() +{ + return seek_arc_v( 0 ); +} + +fex_pos_t Zip_Extractor::tell_arc_v() const +{ + return catalog_pos; +} + +blargg_err_t Zip_Extractor::seek_arc_v( fex_pos_t pos ) +{ + assert( 0 <= pos && (size_t) pos <= catalog.size() - end_entry_size ); + + catalog_pos = pos; + return update_info( false ); +} + +// Reading + +void Zip_Extractor::clear_file_v() +{ + buf.end(); +} + +blargg_err_t Zip_Extractor::inflater_read( void* data, void* out, int* count ) +{ + Zip_Extractor& self = *STATIC_CAST(Zip_Extractor*,data); + + if ( *count > self.raw_remain ) + *count = self.raw_remain; + + self.raw_remain -= *count; + + return self.arc().read( out, *count ); +} + +blargg_err_t Zip_Extractor::fill_buf( int offset, int buf_size, int initial_read ) +{ + raw_remain = arc().size() - offset; + RETURN_ERR( arc().seek( offset ) ); + return buf.begin( inflater_read, this, buf_size, initial_read ); +} + +blargg_err_t Zip_Extractor::first_read( int count ) +{ + entry_t const& e = (entry_t&) catalog [catalog_pos]; + + // Determine compression + { + int method = get_le16( e.method ); + if ( (method && method != Z_DEFLATED) || get_le16( e.vers ) > 20 ) + return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "compression method" ); + file_deflated = (method != 0); + } + + int raw_size = get_le32( e.raw_size ); + + int file_offset = get_le32( e.file_offset ); + int align = file_offset % disk_block_size; + { + // read header + int buf_size = 3 * disk_block_size - 1 + raw_size; // space for all raw data + buf_size -= buf_size % disk_block_size; + int initial_read = buf_size; + if ( !file_deflated || count < size() ) + { + buf_size = read_buf_size; + initial_read = disk_block_size * 2; + } + // TODO: avoid re-reading if buffer already has data we want? + RETURN_ERR( fill_buf( file_offset - align, buf_size, initial_read ) ); + } + header_t const& h = (header_t&) buf.data() [align]; + if ( buf.filled() < align + header_size || memcmp( h.type, "PK\3\4", 4 ) ) + return blargg_err_file_corrupt; + + // CRCs of header and file data + correct_crc = get_le32( h.crc ); + if ( !correct_crc ) + correct_crc = get_le32( e.crc ); + check( correct_crc == get_le32( e.crc ) ); // catalog CRC should match + crc = ::crc32( 0, NULL, 0 ); + + // Data offset + int data_offset = file_offset + header_size + + get_le16( h.filename_len ) + get_le16( h.extra_len ); + if ( data_offset + raw_size > catalog_begin ) + return blargg_err_file_corrupt; + + // Refill buffer if there's lots of extra data after header + int buf_offset = data_offset - file_offset + align; + if ( buf_offset > buf.filled() ) + { + // TODO: this will almost never occur, making it a good place for bugs + buf_offset = data_offset % disk_block_size; + RETURN_ERR( fill_buf( data_offset - buf_offset, read_buf_size, disk_block_size ) ); + } + + raw_remain = raw_size - (buf.filled() - buf_offset); + return buf.set_mode( (file_deflated ? buf.mode_raw_deflate : buf.mode_copy), buf_offset ); +} + +blargg_err_t Zip_Extractor::extract_v( void* out, int count ) +{ + if ( tell() == 0 ) + RETURN_ERR( first_read( count ) ); + + int actual = count; + RETURN_ERR( buf.read( out, &actual ) ); + if ( actual < count ) + return blargg_err_file_corrupt; + + crc = ::crc32( crc, (byte const*) out, count ); + if ( count == reader().remain() && crc != correct_crc ) + return blargg_err_file_corrupt; + + return blargg_ok; +} diff --git a/quicknes/fex/Zip_Extractor.h b/quicknes/fex/Zip_Extractor.h new file mode 100644 index 0000000000..46ced04b7d --- /dev/null +++ b/quicknes/fex/Zip_Extractor.h @@ -0,0 +1,45 @@ +// ZIP archive extractor. Only supports deflation and store (no compression). + +// File_Extractor 1.0.0 +#ifndef ZIP_EXTRACTOR_H +#define ZIP_EXTRACTOR_H + +#include "File_Extractor.h" +#include "Zlib_Inflater.h" + +class Zip_Extractor : public File_Extractor { +public: + Zip_Extractor(); + virtual ~Zip_Extractor(); + +protected: + virtual blargg_err_t open_path_v(); + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual void clear_file_v(); + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + virtual fex_pos_t tell_arc_v() const; + virtual blargg_err_t seek_arc_v( fex_pos_t ); + + virtual blargg_err_t extract_v( void*, int ); + +private: + blargg_vector catalog; + int catalog_begin; // offset of first catalog entry in file (to detect corruption) + int catalog_pos; // position of current entry in catalog + BOOST::uint64_t raw_remain; // bytes remaining to be read from zip file for current file + unsigned crc; // ongoing CRC of extracted bytes + unsigned correct_crc; + bool file_deflated; + Zlib_Inflater buf; + + blargg_err_t fill_buf( int offset, int buf_size, int initial_read ); + blargg_err_t update_info( bool advance_first ); + blargg_err_t first_read( int count ); + void reorder_entry_header( int offset ); + static blargg_err_t inflater_read( void* data, void* out, int* count ); +}; + +#endif diff --git a/quicknes/fex/Zlib_Inflater.cpp b/quicknes/fex/Zlib_Inflater.cpp new file mode 100644 index 0000000000..ac77ca5153 --- /dev/null +++ b/quicknes/fex/Zlib_Inflater.cpp @@ -0,0 +1,257 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Zlib_Inflater.h" + +/* Copyright (C) 2006-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const block_size = 4096; + +static const char* get_zlib_err( int code ) +{ + assert( code != Z_OK ); + switch ( code ) + { + case Z_MEM_ERROR: return blargg_err_memory; + case Z_DATA_ERROR: return blargg_err_file_corrupt; + // TODO: handle more error codes + } + + const char* str = zError( code ); + if ( !str ) + str = BLARGG_ERR( BLARGG_ERR_GENERIC, "problem unzipping data" ); + + return str; +} + +void Zlib_Inflater::end() +{ + if ( deflated_ ) + { + deflated_ = false; + if ( inflateEnd( &zbuf ) ) + check( false ); + } + buf.clear(); + + static z_stream const empty = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + memcpy( &zbuf, &empty, sizeof zbuf ); +} + +Zlib_Inflater::Zlib_Inflater() +{ + deflated_ = false; + end(); // initialize things +} + +Zlib_Inflater::~Zlib_Inflater() +{ + end(); +} + +blargg_err_t Zlib_Inflater::fill_buf( int count ) +{ + byte* out = buf.end() - count; + RETURN_ERR( callback( user_data, out, &count ) ); + zbuf.avail_in = count; + zbuf.next_in = out; + return blargg_ok; +} + +blargg_err_t Zlib_Inflater::begin( callback_t new_callback, void* new_user_data, + int new_buf_size, int initial_read ) +{ + callback = new_callback; + user_data = new_user_data; + + end(); + + // TODO: decide whether using different size on alloc failure is a good idea + //RETURN_ERR( buf.resize( new_buf_size ? new_buf_size : 4 * block_size ) ); + if ( new_buf_size && buf.resize( new_buf_size ) ) + { + ACK_FAILURE(); + new_buf_size = 0; + } + + if ( !new_buf_size ) + { + RETURN_ERR( buf.resize( 4 * block_size ) ); + initial_read = 0; + } + + // Fill buffer with some data, less than normal buffer size since caller might + // just be examining beginning of file. + return fill_buf( initial_read ? initial_read : block_size ); +} + +blargg_err_t Zlib_Inflater::set_mode( mode_t mode, int data_offset ) +{ + zbuf.next_in += data_offset; + zbuf.avail_in -= data_offset; + + if ( mode == mode_auto ) + { + // examine buffer for gzip header + mode = mode_copy; + unsigned const min_gzip_size = 2 + 8 + 8; + if ( zbuf.avail_in >= min_gzip_size && + zbuf.next_in [0] == 0x1F && zbuf.next_in [1] == 0x8B ) + mode = mode_ungz; + } + + if ( mode != mode_copy ) + { + int wb = MAX_WBITS + 16; // have zlib handle gzip header + if ( mode == mode_raw_deflate ) + wb = -MAX_WBITS; + + int zerr = inflateInit2( &zbuf, wb ); + if ( zerr ) + { + zbuf.next_in = NULL; + return get_zlib_err( zerr ); + } + + deflated_ = true; + } + return blargg_ok; +} + +/* +// Reads/inflates entire stream. All input must be in buffer, and count must be total +// of all output. +blargg_err_t read_all( void* out, int count ); + + +// zlib automatically applies this optimization (uses inflateFast) +// TODO: remove +blargg_err_t Zlib_Inflater::read_all( void* out, int count ) +{ + if ( deflated_ ) + { + zbuf.next_out = (Bytef*) out; + zbuf.avail_out = count; + + int err = inflate( &zbuf, Z_FINISH ); + + if ( zbuf.avail_out || err != Z_STREAM_END ) + return blargg_err_file_corrupt; + } + else + { + if ( zbuf.avail_in < count ) + return blargg_err_file_corrupt; + + memcpy( out, zbuf.next_in, count ); + + zbuf.next_in += count; + zbuf.avail_in -= count; + } + + return blargg_ok; +} +*/ + +blargg_err_t Zlib_Inflater::read( void* out, int* count_io ) +{ + int remain = *count_io; + if ( remain && zbuf.next_in ) + { + if ( deflated_ ) + { + zbuf.next_out = (Bytef*) out; + zbuf.avail_out = remain; + + while ( 1 ) + { + uInt old_avail_in = zbuf.avail_in; + int err = inflate( &zbuf, Z_NO_FLUSH ); + if ( err == Z_STREAM_END ) + { + remain = zbuf.avail_out; + end(); + break; // no more data to inflate + } + + if ( err && (err != Z_BUF_ERROR || old_avail_in) ) + return get_zlib_err( err ); + + if ( !zbuf.avail_out ) + { + remain = 0; + break; // requested number of bytes inflated + } + + if ( zbuf.avail_in ) + { + // inflate() should never leave input if there's still space for output + check( false ); + return blargg_err_file_corrupt; + } + + RETURN_ERR( fill_buf( buf.size() ) ); + if ( !zbuf.avail_in ) + return blargg_err_file_corrupt; // stream didn't end but there's no more data + } + } + else + { + while ( 1 ) + { + // copy buffered data + if ( zbuf.avail_in ) + { + long count = zbuf.avail_in; + if ( count > remain ) + count = remain; + memcpy( out, zbuf.next_in, count ); + zbuf.total_out += count; + out = (char*) out + count; + remain -= count; + zbuf.next_in += count; + zbuf.avail_in -= count; + } + + if ( !zbuf.avail_in && zbuf.next_in < buf.end() ) + { + end(); + break; + } + + // read large request directly + if ( remain + zbuf.total_out % block_size >= buf.size() ) + { + int count = remain; + RETURN_ERR( callback( user_data, out, &count ) ); + zbuf.total_out += count; + out = (char*) out + count; + remain -= count; + + if ( remain ) + { + end(); + break; + } + } + + if ( !remain ) + break; + + RETURN_ERR( fill_buf( buf.size() - zbuf.total_out % block_size ) ); + } + } + } + *count_io -= remain; + return blargg_ok; +} diff --git a/quicknes/fex/Zlib_Inflater.h b/quicknes/fex/Zlib_Inflater.h new file mode 100644 index 0000000000..8dedfe8e2a --- /dev/null +++ b/quicknes/fex/Zlib_Inflater.h @@ -0,0 +1,70 @@ +// Simplifies use of zlib for inflating data + +// File_Extractor 1.0.0 +#ifndef ZLIB_INFLATER_H +#define ZLIB_INFLATER_H + +#include "blargg_common.h" +#include "Data_Reader.h" +#include + +class Zlib_Inflater { +public: + + // Reads at most min(*count,bytes_until_eof()) bytes into *out and set *count + // to that number, or returns error if that many can't be read. + typedef blargg_err_t (*callback_t)( void* user_data, void* out, int* count ); + + // Begins by setting callback and filling buffer. Default buffer is 16K and + // filled to 4K, or specify buf_size and initial_read for custom buffer size + // and how much to read initially. + blargg_err_t begin( callback_t, void* user_data, + int buf_size = 0, int initial_read = 0 ); + + // Data read into buffer by begin() + const unsigned char* data() const { return zbuf.next_in; } + int filled() const { return zbuf.avail_in; } + + // Begins inflation using specified mode. Using mode_auto selects between + // mode_copy and mode_ungz by examining first two bytes of buffer. Use + // buf_offset to specify where data begins in buffer, in case there is + // header data that should be skipped. + enum mode_t { mode_copy, mode_ungz, mode_raw_deflate, mode_auto }; + blargg_err_t set_mode( mode_t, int buf_offset = 0 ); + + // True if set_mode() has been called with mode_ungz or mode_raw_deflate + bool deflated() const { return deflated_; } + + // Reads/inflates at most *count_io bytes into *out and sets *count_io to actual + // number of bytes read (less than requested if end of data was reached). + // Buffers source data internally, even in copy mode, so input file can be + // unbuffered without sacrificing performance. + blargg_err_t read( void* out, int* count_io ); + + // Total number of bytes read since begin() + int tell() const { return zbuf.total_out; } + + // Ends inflation and frees memory + void end(); + +private: + // noncopyable + Zlib_Inflater( const Zlib_Inflater& ); + Zlib_Inflater& operator = ( const Zlib_Inflater& ); + +// Implementation +public: + Zlib_Inflater(); + ~Zlib_Inflater(); + +private: + z_stream_s zbuf; + blargg_vector buf; + bool deflated_; + callback_t callback; + void* user_data; + + blargg_err_t fill_buf( int count ); +}; + +#endif diff --git a/quicknes/fex/blargg_common.cpp b/quicknes/fex/blargg_common.cpp new file mode 100644 index 0000000000..ab86e64573 --- /dev/null +++ b/quicknes/fex/blargg_common.cpp @@ -0,0 +1,51 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "blargg_common.h" + +/* Copyright (C) 2008-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void blargg_vector_::init() +{ + begin_ = NULL; + size_ = 0; +} + +void blargg_vector_::clear() +{ + void* p = begin_; + begin_ = NULL; + size_ = 0; + free( p ); +} + +blargg_err_t blargg_vector_::resize_( size_t n, size_t elem_size ) +{ + if ( n != size_ ) + { + if ( n == 0 ) + { + // Simpler to handle explicitly. Realloc will handle a size of 0, + // but then we have to avoid raising an error for a NULL return. + clear(); + } + else + { + void* p = realloc( begin_, n * elem_size ); + CHECK_ALLOC( p ); + begin_ = p; + size_ = n; + } + } + return blargg_ok; +} diff --git a/quicknes/fex/blargg_common.h b/quicknes/fex/blargg_common.h new file mode 100644 index 0000000000..ac29f929cd --- /dev/null +++ b/quicknes/fex/blargg_common.h @@ -0,0 +1,217 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// File_Extractor 1.0.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include +#include + +typedef const char* blargg_err_t; // 0 on success, otherwise error string + +#ifdef _WIN32 +typedef wchar_t blargg_wchar_t; +#else +typedef uint16_t blargg_wchar_t; +#endif + +inline size_t blargg_wcslen( const blargg_wchar_t* str ) +{ + size_t length = 0; + while ( *str++ ) length++; + return length; +} + +// Success; no error +blargg_err_t const blargg_ok = 0; + +// BLARGG_RESTRICT: equivalent to C99's restrict, where supported +#if __GNUC__ >= 3 || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +#if __cplusplus >= 199711 + #define BLARGG_MUTABLE mutable +#else + #define BLARGG_MUTABLE +#endif + +/* BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant). +I don't just use 'abcd' because that's implementation-dependent. */ +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000 + (b&0xFF)*0x10000 + (c&0xFF)*0x100 + (d&0xFF)) + +/* BLARGG_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +Can be used at file, function, or class scope. */ +#ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) __LINE__ fails when /Zl is specified + #define BLARGG_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) +#else + // Others fail when declaring same function multiple times in class, + // so differentiate them by line + #define BLARGG_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) +#endif + +/* Pure virtual functions cause a vtable entry to a "called pure virtual" +error handler, requiring linkage to the C++ runtime library. This macro is +used in place of the "= 0", and simply expands to its argument. During +development, it expands to "= 0", allowing detection of missing overrides. */ +#define BLARGG_PURE( def ) def + +/* My code depends on ASCII anywhere a character or string constant is +compared with data read from a file, and anywhere file data is read and +treated as a string. */ +#if '\n'!=0x0A || ' '!=0x20 || '0'!=0x30 || 'A'!=0x41 || 'a'!=0x61 + #error "ASCII character set required" +#endif + +/* My code depends on int being at least 32 bits. Almost everything these days +uses at least 32-bit ints, so it's hard to even find a system with 16-bit ints +to test with. The issue can't be gotten around by using a suitable blargg_int +everywhere either, because int is often converted to implicitly when doing +arithmetic on smaller types. */ +#if UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" +#endif + +// In case compiler doesn't support these properly. Used rarely. +#define STATIC_CAST(T,expr) static_cast (expr) +#define CONST_CAST( T,expr) const_cast (expr) + +// User configuration can override the above macros if necessary +#include "blargg_config.h" + +/* BLARGG_DEPRECATED [_TEXT] for any declarations/text to be removed in a +future version. In GCC, we can let the compiler warn. In other compilers, +we strip it out unless BLARGG_LEGACY is true. */ +#if BLARGG_LEGACY + // Allow old client code to work without warnings + #define BLARGG_DEPRECATED_TEXT( text ) text + #define BLARGG_DEPRECATED( text ) text +#elif __GNUC__ >= 4 + // In GCC, we can mark declarations and let the compiler warn + #define BLARGG_DEPRECATED_TEXT( text ) text + #define BLARGG_DEPRECATED( text ) __attribute__ ((deprecated)) text +#else + // By default, deprecated items are removed, to avoid use in new code + #define BLARGG_DEPRECATED_TEXT( text ) + #define BLARGG_DEPRECATED( text ) +#endif + +/* BOOST::int8_t, BOOST::int32_t, etc. +I used BOOST since I originally was going to allow use of the boost library +for prividing the definitions. If I'm defining them, they must be scoped or +else they could conflict with the standard ones at global scope. Even if +HAVE_STDINT_H isn't defined, I can't assume the typedefs won't exist at +global scope already. */ +#if defined (HAVE_STDINT_H) || \ + UCHAR_MAX != 0xFF || USHRT_MAX != 0xFFFF || UINT_MAX != 0xFFFFFFFF + #include + #define BOOST +#else + struct BOOST + { + typedef signed char int8_t; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef int int32_t; + typedef unsigned int uint32_t; + typedef __int64 int64_t; + typedef unsigned __int64 uint64_t; + }; +#endif + +/* My code is not written with exceptions in mind, so either uses new (nothrow) +OR overrides operator new in my classes. The former is best since clients +creating objects will get standard exceptions on failure, but that causes it +to require the standard C++ library. So, when the client is using the C +interface, I override operator new to use malloc. */ + +// BLARGG_DISABLE_NOTHROW is put inside classes +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if NULL can be returned + #if __cplusplus >= 199711 || __GNUC__ >= 3 || _MSC_VER >= 1300 + #define BLARGG_THROWS_NOTHING throw () + #else + #define BLARGG_THROWS_NOTHING + #endif + + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS_NOTHING { return malloc( s ); }\ + void operator delete( void* p ) BLARGG_THROWS_NOTHING { free( p ); } + + #define BLARGG_NEW new +#else + // BLARGG_NEW is used in place of new in library code + #include + #define BLARGG_NEW new (std::nothrow) +#endif + + class blargg_vector_ { + protected: + void* begin_; + size_t size_; + void init(); + blargg_err_t resize_( size_t n, size_t elem_size ); + public: + size_t size() const { return size_; } + void clear(); + }; + +// Very lightweight vector for POD types (no constructor/destructor) +template +class blargg_vector : public blargg_vector_ { + union T_must_be_pod { T t; }; // fails if T is not POD +public: + blargg_vector() { init(); } + ~blargg_vector() { clear(); } + + blargg_err_t resize( size_t n ) { return resize_( n, sizeof (T) ); } + + T* begin() { return static_cast (begin_); } + const T* begin() const { return static_cast (begin_); } + + T* end() { return static_cast (begin_) + size_; } + const T* end() const { return static_cast (begin_) + size_; } + + T& operator [] ( size_t n ) + { + assert( n < size_ ); + return static_cast (begin_) [n]; + } + + const T& operator [] ( size_t n ) const + { + assert( n < size_ ); + return static_cast (begin_) [n]; + } +}; + +// Callback function with user data. +// blargg_callback set_callback; // for user, this acts like... +// void set_callback( T func, void* user_data = NULL ); // ...this +// To call function, do set_callback.f( .. set_callback.data ... ); +template +struct blargg_callback +{ + T f; + void* data; + blargg_callback() { f = NULL; } + void operator () ( T callback, void* user_data = NULL ) { f = callback; data = user_data; } +}; + +BLARGG_DEPRECATED( typedef signed int blargg_long; ) +BLARGG_DEPRECATED( typedef unsigned int blargg_ulong; ) +#if BLARGG_LEGACY + #define BOOST_STATIC_ASSERT BLARGG_STATIC_ASSERT +#endif + +#endif diff --git a/quicknes/fex/blargg_config.h b/quicknes/fex/blargg_config.h new file mode 100644 index 0000000000..73d9613f48 --- /dev/null +++ b/quicknes/fex/blargg_config.h @@ -0,0 +1,37 @@ +// Library configuration. Modify this file as necessary. + +// File_Extractor 1.0.0 +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment a #define line below to have effect described. +// #define HAVE_ZLIB_H + +// Enable RAR archive support. Doing so adds extra licensing restrictions +// to this library (see unrar/readme.txt for more information). +#define FEX_ENABLE_RAR 1 + +// Accept file paths encoded as UTF-8. Currently only affects Windows, +// as Unix/Linux/Mac OS X already use UTF-8 paths. +#define BLARGG_UTF8_PATHS 1 + +// Enable support for as building DLL on Windows. +//#define BLARGG_BUILD_DLL 1 + +// Support only the listed archive types. Remove any you don't need. +/* +#define FEX_TYPE_LIST \ + fex_7z_type,\ + fex_gz_type,\ + fex_rar_type,\ + fex_zip_type, +*/ + +#define HAVE_STDINT_H + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/quicknes/fex/blargg_endian.h b/quicknes/fex/blargg_endian.h new file mode 100644 index 0000000000..d012871f21 --- /dev/null +++ b/quicknes/fex/blargg_endian.h @@ -0,0 +1,185 @@ +// CPU Byte Order Utilities + +// File_Extractor 1.0.0 +#ifndef BLARGG_ENDIAN_H +#define BLARGG_ENDIAN_H + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (__i386__) || defined (__x86_64__) || defined (_M_IX86) || defined (_M_X64) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) || \ + defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + #define BLARGG_CPU_RISC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#elif !defined (__mips__) + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() +{ + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif +} + +inline unsigned get_le16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 8 | + (unsigned) ((unsigned char const*) p) [1]; +} + +inline unsigned get_le32( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [3] << 24 | + (unsigned) ((unsigned char const*) p) [2] << 16 | + (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be32( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 24 | + (unsigned) ((unsigned char const*) p) [1] << 16 | + (unsigned) ((unsigned char const*) p) [2] << 8 | + (unsigned) ((unsigned char const*) p) [3]; +} + +inline void set_le16( void* p, unsigned n ) +{ + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be16( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} + +inline void set_le32( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) n; + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); +} + +inline void set_be32( void* p, unsigned n ) +{ + ((unsigned char*) p) [3] = (unsigned char) n; + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known + #if BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t const*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t const*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t const*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t const*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + + #if BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + #if defined (__MWERKS__) + #define GET_LE16( addr ) (__lhbrx( addr, 0 )) + #define GET_LE32( addr ) (__lwbrx( addr, 0 )) + #define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 )) + #define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 )) + #elif defined (__GNUC__) + #define GET_LE16( addr ) ({unsigned short ppc_lhbrx_; __asm__ volatile( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr) : "memory" ); ppc_lhbrx_;}) + #define GET_LE32( addr ) ({unsigned short ppc_lwbrx_; __asm__ volatile( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr) : "memory" ); ppc_lwbrx_;}) + #define SET_LE16( addr, in ) ({__asm__ volatile( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) : "memory" );}) + #define SET_LE32( addr, in ) ({__asm__ volatile( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) : "memory" );}) + #endif + #endif + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, unsigned n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, unsigned n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t const* p ) { return GET_LE16( p ); } +inline unsigned get_le( BOOST::uint32_t const* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t const* p ) { return GET_BE16( p ); } +inline unsigned get_be( BOOST::uint32_t const* p ) { return GET_BE32( p ); } + +#endif diff --git a/quicknes/fex/blargg_errors.cpp b/quicknes/fex/blargg_errors.cpp new file mode 100644 index 0000000000..025ea50078 --- /dev/null +++ b/quicknes/fex/blargg_errors.cpp @@ -0,0 +1,113 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "blargg_errors.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +blargg_err_def_t blargg_err_generic = BLARGG_ERR_GENERIC; +blargg_err_def_t blargg_err_memory = BLARGG_ERR_MEMORY; +blargg_err_def_t blargg_err_caller = BLARGG_ERR_CALLER; +blargg_err_def_t blargg_err_internal = BLARGG_ERR_INTERNAL; +blargg_err_def_t blargg_err_limitation = BLARGG_ERR_LIMITATION; + +blargg_err_def_t blargg_err_file_missing = BLARGG_ERR_FILE_MISSING; +blargg_err_def_t blargg_err_file_read = BLARGG_ERR_FILE_READ; +blargg_err_def_t blargg_err_file_write = BLARGG_ERR_FILE_WRITE; +blargg_err_def_t blargg_err_file_io = BLARGG_ERR_FILE_IO; +blargg_err_def_t blargg_err_file_full = BLARGG_ERR_FILE_FULL; +blargg_err_def_t blargg_err_file_eof = BLARGG_ERR_FILE_EOF; + +blargg_err_def_t blargg_err_file_type = BLARGG_ERR_FILE_TYPE; +blargg_err_def_t blargg_err_file_feature = BLARGG_ERR_FILE_FEATURE; +blargg_err_def_t blargg_err_file_corrupt = BLARGG_ERR_FILE_CORRUPT; + +const char* blargg_err_str( blargg_err_t err ) +{ + if ( !err ) + return ""; + + if ( *err == BLARGG_ERR_TYPE("")[0] ) + return err + 1; + + return err; +} + +bool blargg_is_err_type( blargg_err_t err, const char type [] ) +{ + if ( err ) + { + // True if first strlen(type) characters of err match type + char const* p = err; + while ( *type && *type == *p ) + { + type++; + p++; + } + + if ( !*type ) + return true; + } + + return false; +} + +const char* blargg_err_details( blargg_err_t err ) +{ + const char* p = err; + if ( !p ) + { + p = ""; + } + else if ( *p == BLARGG_ERR_TYPE("")[0] ) + { + while ( *p && *p != ';' ) + p++; + + // Skip ; and space after it + if ( *p ) + { + p++; + + check( *p == ' ' ); + if ( *p ) + p++; + } + } + return p; +} + +int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const codes [] ) +{ + if ( !err ) + return 0; + + while ( codes->str && !blargg_is_err_type( err, codes->str ) ) + codes++; + + return codes->code; +} + +blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const codes [] ) +{ + if ( !code ) + return blargg_ok; + + while ( codes->str && codes->code != code ) + codes++; + + if ( !codes->str ) + return blargg_err_generic; + + return codes->str; +} diff --git a/quicknes/fex/blargg_errors.h b/quicknes/fex/blargg_errors.h new file mode 100644 index 0000000000..2b6dd3c05f --- /dev/null +++ b/quicknes/fex/blargg_errors.h @@ -0,0 +1,80 @@ +// Error strings and conversion functions + +// File_Extractor 1.0.0 +#ifndef BLARGG_ERRORS_H +#define BLARGG_ERRORS_H + +#ifndef BLARGG_COMMON_H + #include "blargg_common.h" +#endif + +typedef const char blargg_err_def_t []; + +// Basic errors +extern blargg_err_def_t blargg_err_generic; +extern blargg_err_def_t blargg_err_memory; +extern blargg_err_def_t blargg_err_caller; +extern blargg_err_def_t blargg_err_internal; +extern blargg_err_def_t blargg_err_limitation; + +// File low-level +extern blargg_err_def_t blargg_err_file_missing; // not found +extern blargg_err_def_t blargg_err_file_read; +extern blargg_err_def_t blargg_err_file_write; +extern blargg_err_def_t blargg_err_file_io; +extern blargg_err_def_t blargg_err_file_full; +extern blargg_err_def_t blargg_err_file_eof; + +// File high-level +extern blargg_err_def_t blargg_err_file_type; // wrong file type +extern blargg_err_def_t blargg_err_file_feature; +extern blargg_err_def_t blargg_err_file_corrupt; + +// C string describing error, or "" if err == NULL +const char* blargg_err_str( blargg_err_t err ); + +// True iff error is of given type, or false if err == NULL +bool blargg_is_err_type( blargg_err_t, const char type [] ); + +// Details of error without describing main cause, or "" if err == NULL +const char* blargg_err_details( blargg_err_t err ); + +// Converts error string to integer code using mapping table. Calls blargg_is_err_type() +// for each str and returns code on first match. Returns 0 if err == NULL. +struct blargg_err_to_code_t { + const char* str; + int code; +}; +int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const [] ); + +// Converts error code back to string. If code == 0, returns NULL. If not in table, +// returns blargg_err_generic. +blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const [] ); + +// Generates error string literal with details of cause +#define BLARGG_ERR( type, str ) (type "; " str) + +// Extra space to make it clear when blargg_err_str() isn't called to get +// printable version of error. At some point, I might prefix error strings +// with a code, to speed conversion to a code. +#define BLARGG_ERR_TYPE( str ) " " str + +// Error types to pass to BLARGG_ERR macro +#define BLARGG_ERR_GENERIC BLARGG_ERR_TYPE( "operation failed" ) +#define BLARGG_ERR_MEMORY BLARGG_ERR_TYPE( "out of memory" ) +#define BLARGG_ERR_CALLER BLARGG_ERR_TYPE( "internal usage bug" ) +#define BLARGG_ERR_INTERNAL BLARGG_ERR_TYPE( "internal bug" ) +#define BLARGG_ERR_LIMITATION BLARGG_ERR_TYPE( "exceeded limitation" ) + +#define BLARGG_ERR_FILE_MISSING BLARGG_ERR_TYPE( "file not found" ) +#define BLARGG_ERR_FILE_READ BLARGG_ERR_TYPE( "couldn't open file" ) +#define BLARGG_ERR_FILE_WRITE BLARGG_ERR_TYPE( "couldn't modify file" ) +#define BLARGG_ERR_FILE_IO BLARGG_ERR_TYPE( "read/write error" ) +#define BLARGG_ERR_FILE_FULL BLARGG_ERR_TYPE( "disk full" ) +#define BLARGG_ERR_FILE_EOF BLARGG_ERR_TYPE( "truncated file" ) + +#define BLARGG_ERR_FILE_TYPE BLARGG_ERR_TYPE( "wrong file type" ) +#define BLARGG_ERR_FILE_FEATURE BLARGG_ERR_TYPE( "unsupported file feature" ) +#define BLARGG_ERR_FILE_CORRUPT BLARGG_ERR_TYPE( "corrupt file" ) + +#endif diff --git a/quicknes/fex/blargg_source.h b/quicknes/fex/blargg_source.h new file mode 100644 index 0000000000..cd3065b4fc --- /dev/null +++ b/quicknes/fex/blargg_source.h @@ -0,0 +1,121 @@ +/* Included at the beginning of library source files, AFTER all other #include +lines. Sets up helpful macros and services used in my source code. Since this +is only "active" in my source code, I don't have to worry about polluting the +global namespace with unprefixed names. */ + +// File_Extractor 1.0.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +#ifndef BLARGG_COMMON_H // optimization only + #include "blargg_common.h" +#endif +#include "blargg_errors.h" + +#include /* memcpy(), memset(), memmove() */ +#include /* offsetof() */ + +/* The following four macros are for debugging only. Some or all might be +defined to do nothing, depending on the circumstances. Described is what +happens when a particular macro is defined to do something. When defined to +do nothing, the macros do NOT evaluate their argument(s). */ + +/* If expr is false, prints file and line number, then aborts program. Meant +for checking internal state and consistency. A failed assertion indicates a bug +in MY code. + +void assert( bool expr ); */ +#include + +/* If expr is false, prints file and line number, then aborts program. Meant +for checking caller-supplied parameters and operations that are outside the +control of the module. A failed requirement probably indicates a bug in YOUR +code. + +void require( bool expr ); */ +#undef require +#define require( expr ) assert( expr ) + +/* Like printf() except output goes to debugging console/file. + +void dprintf( const char format [], ... ); */ +static inline void blargg_dprintf_( const char [], ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +/* If expr is false, prints file and line number to debug console/log, then +continues execution normally. Meant for flagging potential problems or things +that should be looked into, but that aren't serious problems. + +void check( bool expr ); */ +#undef check +#define check( expr ) ((void) 0) + +/* If expr yields non-NULL error string, returns it from current function, +otherwise continues normally. */ +#undef RETURN_ERR +#define RETURN_ERR( expr ) \ + do {\ + blargg_err_t blargg_return_err_ = (expr);\ + if ( blargg_return_err_ )\ + return blargg_return_err_;\ + } while ( 0 ) + +/* If ptr is NULL, returns out-of-memory error, otherwise continues normally. */ +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) \ + do {\ + if ( !(ptr) )\ + return blargg_err_memory;\ + } while ( 0 ) + +/* The usual min/max functions for built-in types. */ + +template T min( T x, T y ) { return x < y ? x : y; } +template T max( T x, T y ) { return x > y ? x : y; } + +#define BLARGG_DEF_MIN_MAX( type ) + +BLARGG_DEF_MIN_MAX( int ) +BLARGG_DEF_MIN_MAX( unsigned ) +BLARGG_DEF_MIN_MAX( long ) +BLARGG_DEF_MIN_MAX( unsigned long ) +BLARGG_DEF_MIN_MAX( float ) +BLARGG_DEF_MIN_MAX( double ) +#if __WORDSIZE != 64 +BLARGG_DEF_MIN_MAX( BOOST::uint64_t ) +#endif + +// typedef unsigned char byte; +typedef unsigned char blargg_byte; +#undef byte +#define byte blargg_byte + +#ifndef BLARGG_EXPORT + #if defined (_WIN32) && BLARGG_BUILD_DLL + #define BLARGG_EXPORT __declspec(dllexport) + #elif defined (__GNUC__) + // can always set visibility, even when not building DLL + #define BLARGG_EXPORT __attribute__ ((visibility ("default"))) + #else + #define BLARGG_EXPORT + #endif +#endif + +#if BLARGG_LEGACY + #define BLARGG_CHECK_ALLOC CHECK_ALLOC + #define BLARGG_RETURN_ERR RETURN_ERR +#endif + +// Called after failed operation when overall operation may still complete OK. +// Only used by unit testing framework. +#undef ACK_FAILURE +#define ACK_FAILURE() ((void)0) + +/* BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf etc. +and check */ +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/quicknes/fex/fex.cpp b/quicknes/fex/fex.cpp new file mode 100644 index 0000000000..8a7dee6a47 --- /dev/null +++ b/quicknes/fex/fex.cpp @@ -0,0 +1,321 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "fex.h" + +#include "File_Extractor.h" +#include "blargg_endian.h" +#include +#include + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + + +//// Types + +BLARGG_EXPORT const fex_type_t* fex_type_list( void ) +{ + static fex_type_t const fex_type_list_ [] = + { + #ifdef FEX_TYPE_LIST + FEX_TYPE_LIST + #else + // Modify blargg_config.h to change type list, NOT this file + fex_7z_type, + fex_gz_type, + #if FEX_ENABLE_RAR + fex_rar_type, + #endif + fex_zip_type, + #endif + fex_bin_type, + NULL + }; + + return fex_type_list_; +} + +BLARGG_EXPORT fex_err_t fex_init( void ) +{ + static bool inited; + if ( !inited ) + { + for ( fex_type_t const* t = fex_type_list(); *t != NULL; ++t ) + { + if ( (*t)->init ) + RETURN_ERR( (*t)->init() ); + } + inited = true; + } + return blargg_ok; +} + +BLARGG_EXPORT const char* fex_identify_header( void const* header ) +{ + unsigned four = get_be32( header ); + switch ( four ) + { + case 0x52457E5E: + case 0x52617221: return ".rar"; + + case 0x377ABCAF: return ".7z"; + + case 0x504B0304: + case 0x504B0506: return ".zip"; + + case 0x53495421: return ".sit"; + case 0x41724301: return ".arc"; + case 0x4D534346: return ".cab"; + case 0x5A4F4F20: return ".zoo"; + } + + unsigned three = four >> 8; + switch ( three ) + { + case 0x425A68: return ".bz2"; + } + + unsigned two = four >> 16; + switch ( two ) + { + case 0x1F8B: return ".gz"; + case 0x60EA: return ".arj"; + } + + unsigned skip_first_two = four & 0xFFFF; + if ( skip_first_two == 0x2D6C ) + return ".lha"; + + return ""; +} + +static int fex_has_extension_( const char str [], const char suffix [], size_t str_len ) +{ + size_t suffix_len = strlen( suffix ); + if ( str_len >= suffix_len ) + { + str += str_len - suffix_len; + while ( *str && tolower( (unsigned char) *str ) == *suffix ) + { + str++; + suffix++; + } + } + return *suffix == 0; +} + +BLARGG_EXPORT int fex_has_extension( const char str [], const char suffix [] ) +{ + return fex_has_extension_( str, suffix, strlen( str ) ); +} + +static int is_archive_extension( const char str [] ) +{ + static const char exts [] [6] = { + ".7z", + ".arc", + ".arj", + ".bz2", + ".cab", + ".dmg", + ".gz", + ".lha", + ".lz", + ".lzh", + ".lzma", + ".lzo", + ".lzx", + ".pea", + ".rar", + ".sit", + ".sitx", + ".tgz", + ".tlz", + ".z", + ".zip", + ".zoo", + "" + }; + + size_t str_len = strlen( str ); + const char (*ext) [6] = exts; + for ( ; **ext; ext++ ) + { + if ( fex_has_extension_( str, *ext, str_len ) ) + return 1; + } + return 0; +} + +BLARGG_EXPORT fex_type_t fex_identify_extension( const char str [] ) +{ + size_t str_len = strlen( str ); + for ( fex_type_t const* types = fex_type_list(); *types; types++ ) + { + if ( fex_has_extension_( str, (*types)->extension, str_len ) ) + { + // Avoid treating known archive type as binary + if ( *(*types)->extension || !is_archive_extension( str ) ) + return *types; + } + } + return NULL; +} + +BLARGG_EXPORT fex_err_t fex_identify_file( fex_type_t* type_out, const char path [] ) +{ + *type_out = NULL; + + fex_type_t type = fex_identify_extension( path ); + + // Unsupported extension? + if ( !type ) + return blargg_ok; // reject + + // Unknown/no extension? + if ( !*(type->extension) ) + { + // Examine header + FEX_FILE_READER in; + RETURN_ERR( in.open( path ) ); + if ( in.remain() >= fex_identify_header_size ) + { + char h [fex_identify_header_size]; + RETURN_ERR( in.read( h, sizeof h ) ); + + type = fex_identify_extension( fex_identify_header( h ) ); + } + } + + *type_out = type; + return blargg_ok; +} + +BLARGG_EXPORT fex_err_t fex_open_type( fex_t** fe_out, const char path [], fex_type_t type ) +{ + *fe_out = NULL; + + if ( !type ) + return blargg_err_file_type; + + fex_t* fe = type->new_fex(); + CHECK_ALLOC( fe ); + + fex_err_t err = fe->open( path ); + if ( err ) + { + delete fe; + return err; + } + + *fe_out = fe; + return blargg_ok; +} + +BLARGG_EXPORT fex_err_t fex_open( fex_t** fe_out, const char path [] ) +{ + *fe_out = NULL; + + fex_type_t type; + RETURN_ERR( fex_identify_file( &type, path ) ); + + return fex_open_type( fe_out, path, type ); +} + + +//// Wide paths + +char* fex_wide_to_path( const blargg_wchar_t* wide ) +{ + return blargg_to_utf8( wide ); +} + +void fex_free_path( char* path ) +{ + free( path ); +} + + +//// Errors + +#define ENTRY( name ) { blargg_err_##name, fex_err_##name } +static blargg_err_to_code_t const fex_codes [] = +{ + ENTRY( generic ), + ENTRY( memory ), + ENTRY( caller ), + ENTRY( internal ), + ENTRY( limitation ), + + ENTRY( file_missing ), + ENTRY( file_read ), + ENTRY( file_io ), + ENTRY( file_eof ), + + ENTRY( file_type ), + ENTRY( file_feature ), + ENTRY( file_corrupt ), + + { 0, -1 } +}; +#undef ENTRY + +static int err_code( fex_err_t err ) +{ + return blargg_err_to_code( err, fex_codes ); +} + +BLARGG_EXPORT int fex_err_code( fex_err_t err ) +{ + int code = err_code( err ); + return (code >= 0 ? code : fex_err_generic); +} + +BLARGG_EXPORT fex_err_t fex_code_to_err( int code ) +{ + return blargg_code_to_err( code, fex_codes ); +} + +BLARGG_EXPORT const char* fex_err_details( fex_err_t err ) +{ + // If we don't have error code assigned, return entire string + return (err_code( err ) >= 0 ? blargg_err_details( err ) : blargg_err_str( err )); +} + + +//// Wrappers + +BLARGG_EXPORT fex_err_t fex_read( fex_t* fe, void* out, int count ) +{ + RETURN_ERR( fe->stat() ); + return fe->reader().read( out, count ); +} + +BLARGG_EXPORT void fex_close ( fex_t* fe ) { delete fe; } +BLARGG_EXPORT fex_type_t fex_type ( const fex_t* fe ) { return fe->type(); } +BLARGG_EXPORT int fex_done ( const fex_t* fe ) { return fe->done(); } +BLARGG_EXPORT const char* fex_name ( const fex_t* fe ) { return fe->name(); } +BLARGG_EXPORT const blargg_wchar_t* fex_wname ( const fex_t* fe ) { return fe->wname(); } +BLARGG_EXPORT uint64_t fex_size ( const fex_t* fe ) { return fe->size(); } +BLARGG_EXPORT unsigned fex_dos_date ( const fex_t* fe ) { return fe->dos_date(); } +BLARGG_EXPORT unsigned fex_crc32 ( const fex_t* fe ) { return fe->crc32(); } +BLARGG_EXPORT fex_err_t fex_stat ( fex_t* fe ) { return fe->stat(); } +BLARGG_EXPORT fex_err_t fex_next ( fex_t* fe ) { return fe->next(); } +BLARGG_EXPORT fex_err_t fex_rewind ( fex_t* fe ) { return fe->rewind(); } +BLARGG_EXPORT uint64_t fex_tell ( const fex_t* fe ) { return fe->tell(); } +BLARGG_EXPORT fex_pos_t fex_tell_arc ( const fex_t* fe ) { return fe->tell_arc(); } +BLARGG_EXPORT fex_err_t fex_seek_arc ( fex_t* fe, fex_pos_t pos ) { return fe->seek_arc( pos ); } +BLARGG_EXPORT const char* fex_type_extension ( fex_type_t t ) { return t->extension; } +BLARGG_EXPORT const char* fex_type_name ( fex_type_t t ) { return t->name; } +BLARGG_EXPORT fex_err_t fex_data ( fex_t* fe, const void** data_out ) { return fe->data( data_out ); } +BLARGG_EXPORT const char* fex_err_str ( fex_err_t err ) { return blargg_err_str( err ); } diff --git a/quicknes/fex/fex.h b/quicknes/fex/fex.h new file mode 100644 index 0000000000..7a85485915 --- /dev/null +++ b/quicknes/fex/fex.h @@ -0,0 +1,206 @@ +/** Uniform access to zip, gzip, 7-zip, and RAR compressed archives \file */ + +/* File_Extractor 1.0.0 */ +#ifndef FEX_H +#define FEX_H + +#include +#include + +#include "blargg_common.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/** First parameter of most functions is fex_t*, or const fex_t* if nothing is +changed. Once one of these functions returns an error, the archive should not +be used any further, other than to close it. One exception is +fex_error_file_eof; the archive may still be used after this. */ +typedef struct fex_t fex_t; + +/** Pointer to error, or NULL if function was successful. See error functions +below. */ +#ifndef fex_err_t /* (#ifndef allows better testing of library) */ + typedef const char* fex_err_t; +#endif + + +/**** File types ****/ + +/** Archive file type identifier. Can also hold NULL. */ +typedef const struct fex_type_t_* fex_type_t; + +/** Array of supported types, with NULL at end */ +const fex_type_t* fex_type_list( void ); + +/** Name of this archive type, e.g. "ZIP archive", "file" */ +const char* fex_type_name( fex_type_t ); + +/** Usual file extension for type, e.g. ".zip", ".7z". For binary file type, +returns "", since it can open any file. */ +const char* fex_type_extension( fex_type_t ); + + +/**** Wide-character file paths ****/ + +/** Converts wide-character path to form suitable for use with fex functions. */ +char* fex_wide_to_path( const blargg_wchar_t* wide ); + +/** Frees converted path. OK to pass NULL. */ +void fex_free_path( char* ); + + +/**** Identification ****/ + +/** True if str ends in extension. If extension is "", always returns true. +Converts str to lowercase before comparison, so extension should ALREADY be +lowercase (i.e. pass ".zip", NOT ".ZIP"). */ +int fex_has_extension( const char str [], const char extension [] ); + +/** Determines type based on first fex_identify_header_size bytes of file. +Returns usual file extension this should have (e.g. ".zip", ".gz", etc.). +Returns "" if file header is not recognized. */ +const char* fex_identify_header( const void* header ); +enum { fex_identify_header_size = 16 }; + +/** Determines type based on extension of a file path, or just a lone extension +(must include '.', e.g. ".zip", NOT just "zip"). Returns NULL if extension is +for an unsupported type (e.g. ".lzh"). */ +fex_type_t fex_identify_extension( const char path_or_extension [] ); + +/** Determines type based on filename extension and/or file header. Sets *out +to determined type, or NULL if type is not supported. */ +fex_err_t fex_identify_file( fex_type_t* out, const char path [] ); + +/** Type of an already-opened archive */ +fex_type_t fex_type( const fex_t* ); + + +/**** Open/close ****/ + +/** Initializes static tables used by library. Automatically called by +fex_open(). OK to call more than once. */ +fex_err_t fex_init( void ); + +/** Opens archive and points *out at it. If error, sets *out to NULL. */ +fex_err_t fex_open( fex_t** out, const char path [] ); + +/** Opens archive of specified type and sets *out. Returns error if file is not +of that archive type. If error, sets *out to NULL. */ +fex_err_t fex_open_type( fex_t** out, const char path [], fex_type_t ); + +/** Closes archive and frees memory. OK to pass NULL. */ +void fex_close( fex_t* ); + + +/**** Scanning ****/ + +/** True if at end of archive. Must be called after fex_open() or fex_rewind(), +as an archive might contain no files. */ +int fex_done( const fex_t* ); + +/** Goes to next file in archive. If there are no more files, fex_done() will +now return true. */ +fex_err_t fex_next( fex_t* ); + +/** Goes back to first file in archive, as if it were just opened with +fex_open() */ +fex_err_t fex_rewind( fex_t* ); + +/** Saved position in archive. Can also store zero. */ +typedef uint64_t fex_pos_t; + +/** Position of current file in archive. Never returns zero. */ +fex_pos_t fex_tell_arc( const fex_t* ); + +/** Returns to file at previously-saved position */ +fex_err_t fex_seek_arc( fex_t*, fex_pos_t ); + + +/**** Info ****/ + +/** Name of current file */ +const char* fex_name( const fex_t* ); + +/** Wide-character name of current file, or NULL if unavailable */ +const blargg_wchar_t* fex_wname( const fex_t* ); + +/** Makes further information available for file */ +fex_err_t fex_stat( fex_t* ); + +/** Size of current file. fex_stat() or fex_data() must have been called. */ +uint64_t fex_size( const fex_t* ); + +/** Modification date of current file (MS-DOS format), or 0 if unavailable. +fex_stat() must have been called. */ +unsigned int fex_dos_date( const fex_t* ); + +/** CRC-32 checksum of current file's contents, or 0 if unavailable. Doesn't +require calculation; simply gets it from file's header. fex_stat() must have +been called. */ +unsigned int fex_crc32( const fex_t* ); + + +/**** Extraction ****/ + +/** Reads n bytes from current file. Reading past end of file results in +fex_err_file_eof. */ +fex_err_t fex_read( fex_t*, void* out, int n ); + +/** Number of bytes read from current file */ +uint64_t fex_tell( const fex_t* ); + +/** Points *out at current file's data in memory. Pointer is valid until +fex_next(), fex_rewind(), fex_seek_arc(), or fex_close() is called. Pointer +must NOT be freed(); library frees it automatically. If error, sets *out to +NULL. */ +fex_err_t fex_data( fex_t*, const void** out ); + + +/**** Errors ****/ + +/** Error string associated with err. Returns "" if err is NULL. Returns err +unchanged if it isn't a fex_err_t returned by library. */ +const char* fex_err_str( fex_err_t err ); + +/** Details of error beyond main cause, or "" if none or err is NULL. Returns +err unchanged if it isn't a fex_err_t returned by library. */ +const char* fex_err_details( fex_err_t err ); + +/** Numeric code corresponding to err. Returns fex_ok if err is NULL. Returns +fex_err_generic if err isn't a fex_err_t returned by library. */ +int fex_err_code( fex_err_t err ); + +enum { + fex_ok = 0,/**< Successful call. Guaranteed to be zero. */ + fex_err_generic = 0x01,/**< Error of unspecified type */ + fex_err_memory = 0x02,/**< Out of memory */ + fex_err_caller = 0x03,/**< Caller called function with bad args */ + fex_err_internal = 0x04,/**< Internal problem, bug, etc. */ + fex_err_limitation = 0x05,/**< Exceeded program limit */ + + fex_err_file_missing = 0x20,/**< File not found at specified path */ + fex_err_file_read = 0x21,/**< Couldn't open file for reading */ + fex_err_file_io = 0x23,/**< Read/write error */ + fex_err_file_eof = 0x25,/**< Tried to read past end of file */ + + fex_err_file_type = 0x30,/**< File is of wrong type */ + fex_err_file_feature = 0x32,/**< File requires unsupported feature */ + fex_err_file_corrupt = 0x33 /**< File is corrupt */ +}; + +/** fex_err_t corresponding to numeric code. Note that this might not recover +the original fex_err_t before it was converted to a numeric code; in +particular, fex_err_details(fex_code_to_err(code)) will be "" in most cases. */ +fex_err_t fex_code_to_err( int code ); + + +/* Deprecated */ +typedef fex_t File_Extractor; + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/quicknes/libquicknes/libquicknes.sln b/quicknes/libquicknes/libquicknes.sln new file mode 100644 index 0000000000..30daf499fa --- /dev/null +++ b/quicknes/libquicknes/libquicknes.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libquicknes", "libquicknes.vcxproj", "{F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}.Debug|Win32.ActiveCfg = Debug|Win32 + {F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}.Debug|Win32.Build.0 = Debug|Win32 + {F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}.Release|Win32.ActiveCfg = Release|Win32 + {F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/quicknes/libquicknes/libquicknes.vcxproj b/quicknes/libquicknes/libquicknes.vcxproj new file mode 100644 index 0000000000..1b2d084d71 --- /dev/null +++ b/quicknes/libquicknes/libquicknes.vcxproj @@ -0,0 +1,113 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {F07F76D3-08E6-4EBC-82F9-53FF90ABD9A9} + libquicknes + + + + DynamicLibrary + true + MultiByte + + + DynamicLibrary + false + true + MultiByte + + + + + + + + + + + + + + + + Level3 + Disabled + _WINDLL;%(PreprocessorDefinitions);BLARGG_SOURCE_BEGIN="blargg_common.h";BLARGG_ENABLE_OPTIMIZER="blargg_common.h" + 4244;4800;4804;4996 + $(ProjectDir)\.. + + + true + + + + + Level3 + MaxSpeed + true + true + _WINDLL;%(PreprocessorDefinitions);BLARGG_SOURCE_BEGIN="blargg_common.h";BLARGG_ENABLE_OPTIMIZER="blargg_common.h" + 4244;4800;4804;4996 + $(ProjectDir)\.. + + + true + true + true + + + + + + \ No newline at end of file diff --git a/quicknes/libquicknes/libquicknes.vcxproj.filters b/quicknes/libquicknes/libquicknes.vcxproj.filters new file mode 100644 index 0000000000..e4c5c91b74 --- /dev/null +++ b/quicknes/libquicknes/libquicknes.vcxproj.filters @@ -0,0 +1,148 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {ba7f113f-5234-44b7-a84c-35ad9dd39db2} + + + {704585a9-2165-422f-8457-b6c5ffe9179d} + + + {109f60b9-f5f8-4f85-807d-b0c977848674} + + + {278454e6-01bf-4140-8707-bc2a4512c2ac} + + + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\nes_emu + + + Source Files\fex + + + Source Files\fex + + + Source Files\fex + + + \ No newline at end of file diff --git a/quicknes/libretro/Makefile b/quicknes/libretro/Makefile new file mode 100644 index 0000000000..8117af8704 --- /dev/null +++ b/quicknes/libretro/Makefile @@ -0,0 +1,216 @@ +DEBUG = 0 + +ifeq ($(platform),) +platform = unix +ifeq ($(shell uname -a),) + platform = win +else ifneq ($(findstring MINGW,$(shell uname -a)),) + platform = win +else ifneq ($(findstring Darwin,$(shell uname -a)),) + platform = osx +else ifneq ($(findstring win,$(shell uname -a)),) + platform = win +endif +endif + +# system platform +system_platform = unix +ifeq ($(shell uname -a),) +EXE_EXT = .exe + system_platform = win +else ifneq ($(findstring Darwin,$(shell uname -a)),) + system_platform = osx +else ifneq ($(findstring MINGW,$(shell uname -a)),) + system_platform = win +endif + +LIBRETRO_DIR := libretro +TARGET_NAME := quicknes + +ifeq ($(platform), unix) + TARGET := $(TARGET_NAME)_libretro.so + fpic := -fPIC + SHARED := -shared -Wl,-version-script=link.T -Wl,-no-undefined +else ifeq ($(platform), osx) + TARGET := $(TARGET_NAME)_libretro.dylib + fpic := -fPIC + SHARED := -dynamiclib +else ifeq ($(platform), ios) + TARGET := $(TARGET_NAME)_libretro_ios.dylib + fpic := -fPIC + SHARED := -dynamiclib + + CC = clang -arch armv7 -isysroot $(IOSSDK) -miphoneos-version-min=5.0 + CXX = clang++ -arch armv7 -isysroot $(IOSSDK) -miphoneos-version-min=5.0 + PLATFORM_DEFINES += -DIOS -miphoneos-version-min=5.0 +else ifeq ($(platform), qnx) + TARGET := $(TARGET_NAME)_libretro_qnx.so + fpic := -fPIC + SHARED := -shared -Wl,--version-script=link.T + + CC = qcc -Vgcc_ntoarmv7le + CXX = QCC -Vgcc_ntoarmv7le + AR = QCC -Vgcc_ntoarmv7le + PLATFORM_DEFINES += -D__BLACKBERRY_QNX__ +else ifeq ($(platform), ps3) + TARGET := $(TARGET_NAME)_libretro_ps3.a + CXX = $(CELL_SDK)/host-win32/ppu/bin/ppu-lv2-g++.exe + AR = $(CELL_SDK)/host-win32/ppu/bin/ppu-lv2-ar.exe + PLATFORM_DEFINES := -D__CELLOS_LV2__ + STATIC_LINKING = 1 +else ifeq ($(platform), sncps3) + TARGET := $(TARGET_NAME)_libretro_ps3.a + CC = $(CELL_SDK)/host-win32/sn/bin/ps3ppusnc.exe + CXX = $(CELL_SDK)/host-win32/sn/bin/ps3ppusnc.exe + AR = $(CELL_SDK)/host-win32/sn/bin/ps3snarl.exe + PLATFORM_DEFINES := -D__CELLOS_LV2__ + STATIC_LINKING = 1 +else ifeq ($(platform), psl1ght) + TARGET := $(TARGET_NAME)_libretro_psl1ght.a + CC = $(PS3DEV)/ppu/bin/ppu-gcc$(EXE_EXT) + CXX = $(PS3DEV)/ppu/bin/ppu-g++$(EXE_EXT) + AR = $(PS3DEV)/ppu/bin/ppu-ar$(EXE_EXT) + PLATFORM_DEFINES := -D__CELLOS_LV2__ + STATIC_LINKING = 1 +else ifeq ($(platform), xenon) + TARGET := $(TARGET_NAME)_libretro_xenon360.a + CC = xenon-gcc$(EXE_EXT) + CXX = xenon-g++$(EXE_EXT) + AR = xenon-ar$(EXE_EXT) + PLATFORM_DEFINES := -D__LIBXENON__ + STATIC_LINKING = 1 +else ifeq ($(platform), ngc) + TARGET := $(TARGET_NAME)_libretro_ngc.a + CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT) + CXX = $(DEVKITPPC)/bin/powerpc-eabi-g++$(EXE_EXT) + AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT) + PLATFORM_DEFINES += -DGEKKO -DHW_DOL -mrvl -mcpu=750 -meabi -mhard-float + STATIC_LINKING = 1 +else ifeq ($(platform), wii) + TARGET := $(TARGET_NAME)_libretro_wii.a + CC = $(DEVKITPPC)/bin/powerpc-eabi-gcc$(EXE_EXT) + CXX = $(DEVKITPPC)/bin/powerpc-eabi-g++$(EXE_EXT) + AR = $(DEVKITPPC)/bin/powerpc-eabi-ar$(EXE_EXT) + PLATFORM_DEFINES += -DGEKKO -DHW_RVL -mrvl -mcpu=750 -meabi -mhard-float + STATIC_LINKING = 1 +else ifneq (,$(findstring armv,$(platform))) + TARGET := $(TARGET_NAME)_libretro.so + fpic := -fPIC + SHARED := -shared -Wl,-version-script=link.T -Wl,-no-undefined + CC = gcc +ifneq (,$(findstring cortexa8,$(platform))) + PLATFORM_DEFINES += -marm -mcpu=cortex-a8 +else ifneq (,$(findstring cortexa9,$(platform))) + PLATFORM_DEFINES += -marm -mcpu=cortex-a9 +endif + PLATFORM_DEFINES += -marm +ifneq (,$(findstring neon,$(platform))) + PLATFORM_DEFINES += -mfpu=neon + HAVE_NEON = 1 +endif +ifneq (,$(findstring softfloat,$(platform))) + PLATFORM_DEFINES += -mfloat-abi=softfp +else ifneq (,$(findstring hardfloat,$(platform))) + PLATFORM_DEFINES += -mfloat-abi=hard +endif + PLATFORM_DEFINES += -DARM +else + TARGET := $(TARGET_NAME)_libretro.dll + CC = gcc + CXX = g++ + SHARED := -shared -static-libgcc -static-libstdc++ -Wl,-no-undefined -Wl,-version-script=link.T +endif + +all: $(TARGET) + +ifeq ($(DEBUG), 1) + CFLAGS += -O0 -g + CXXFLAGS += -O0 -g +else + CFLAGS += -O3 + CXXFLAGS += -O3 +endif + +EMU_DIR := ../nes_emu + +CXXSRCS := \ + $(EMU_DIR)/abstract_file.cpp \ + $(EMU_DIR)/apu_state.cpp \ + $(EMU_DIR)/Blip_Buffer.cpp \ + $(EMU_DIR)/Effects_Buffer.cpp \ + $(EMU_DIR)/Mapper_Fme7.cpp \ + $(EMU_DIR)/Mapper_Mmc5.cpp \ + $(EMU_DIR)/Mapper_Namco106.cpp \ + $(EMU_DIR)/Mapper_Vrc6.cpp \ + $(EMU_DIR)/misc_mappers.cpp \ + $(EMU_DIR)/Multi_Buffer.cpp \ + $(EMU_DIR)/Nes_Apu.cpp \ + $(EMU_DIR)/Nes_Buffer.cpp \ + $(EMU_DIR)/Nes_Cart.cpp \ + $(EMU_DIR)/Nes_Core.cpp \ + $(EMU_DIR)/Nes_Cpu.cpp \ + $(EMU_DIR)/nes_data.cpp \ + $(EMU_DIR)/Nes_Effects_Buffer.cpp \ + $(EMU_DIR)/Nes_Emu.cpp \ + $(EMU_DIR)/Nes_File.cpp \ + $(EMU_DIR)/Nes_Film.cpp \ + $(EMU_DIR)/Nes_Film_Data.cpp \ + $(EMU_DIR)/Nes_Film_Packer.cpp \ + $(EMU_DIR)/Nes_Fme7_Apu.cpp \ + $(EMU_DIR)/Nes_Mapper.cpp \ + $(EMU_DIR)/nes_mappers.cpp \ + $(EMU_DIR)/Nes_Mmc1.cpp \ + $(EMU_DIR)/Nes_Mmc3.cpp \ + $(EMU_DIR)/Nes_Namco_Apu.cpp \ + $(EMU_DIR)/Nes_Oscs.cpp \ + $(EMU_DIR)/Nes_Ppu.cpp \ + $(EMU_DIR)/Nes_Ppu_Impl.cpp \ + $(EMU_DIR)/Nes_Ppu_Rendering.cpp \ + $(EMU_DIR)/Nes_Recorder.cpp \ + $(EMU_DIR)/Nes_State.cpp \ + $(EMU_DIR)/nes_util.cpp \ + $(EMU_DIR)/Nes_Vrc6_Apu.cpp \ + libretro.cpp + +LIBSRCS := \ + ../fex/Data_Reader.cpp \ + ../fex/blargg_errors.cpp \ + ../fex/blargg_common.cpp + +CXXOBJ := $(CXXSRCS:.cpp=.o) $(LIBSRCS:.cpp=.o) + +OBJS := $(CXXOBJ) + +DEFINES := -D__LIBRETRO__ $(PLATFORM_DEFINES) -Wall -Wno-multichar -Wno-unused-variable -Wno-sign-compare -DNDEBUG \ + -DSTD_AUTO_FILE_WRITER=Std_File_Writer \ + -DSTD_AUTO_FILE_READER=Std_File_Reader \ + -DSTD_AUTO_FILE_COMP_READER=Std_File_Reader \ + -DSTD_AUTO_FILE_COMP_WRITER=Std_File_Writer + +CFLAGS += $(fpic) $(DEFINES) -std=gnu99 +CXXFLAGS += $(fpic) $(DEFINES) + +INCDIRS := -I$(EMU_DIR) -I. -I.. + +$(TARGET): $(OBJS) +ifeq ($(STATIC_LINKING), 1) + $(AR) rcs $@ $(OBJS) +else + $(CXX) -o $@ $(SHARED) $(OBJS) $(LDFLAGS) $(LIBS) +endif + +%.o: %.cpp + $(CXX) -c -o $@ $< $(CXXFLAGS) $(INCDIRS) + +%.o: %.c + $(CXX) -c -o $@ $< $(CXXFLAGS) $(INCDIRS) + +clean-objs: + rm -f $(OBJS) + +clean: + rm -f $(OBJS) + rm -f $(TARGET) + +.PHONY: clean clean-objs + diff --git a/quicknes/libretro/jni/Android.mk b/quicknes/libretro/jni/Android.mk new file mode 100644 index 0000000000..f74e2569fb --- /dev/null +++ b/quicknes/libretro/jni/Android.mk @@ -0,0 +1,75 @@ +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +ifeq ($(TARGET_ARCH),arm) +LOCAL_CFLAGS += -DANDROID_ARM +LOCAL_ARM_MODE := arm +endif + +ifeq ($(TARGET_ARCH),x86) +LOCAL_CFLAGS += -DANDROID_X86 +endif + +ifeq ($(TARGET_ARCH),mips) +LOCAL_CFLAGS += -DANDROID_MIPS +endif + +LOCAL_MODULE := libretro + +EMU_DIR := ../../nes_emu +LIBRETRO_DIR := ../ + +CXXSRCS := \ + $(EMU_DIR)/abstract_file.cpp \ + $(EMU_DIR)/apu_state.cpp \ + $(EMU_DIR)/Blip_Buffer.cpp \ + $(EMU_DIR)/Effects_Buffer.cpp \ + $(EMU_DIR)/Mapper_Fme7.cpp \ + $(EMU_DIR)/Mapper_Mmc5.cpp \ + $(EMU_DIR)/Mapper_Namco106.cpp \ + $(EMU_DIR)/Mapper_Vrc6.cpp \ + $(EMU_DIR)/misc_mappers.cpp \ + $(EMU_DIR)/Multi_Buffer.cpp \ + $(EMU_DIR)/Nes_Apu.cpp \ + $(EMU_DIR)/Nes_Buffer.cpp \ + $(EMU_DIR)/Nes_Cart.cpp \ + $(EMU_DIR)/Nes_Core.cpp \ + $(EMU_DIR)/Nes_Cpu.cpp \ + $(EMU_DIR)/nes_data.cpp \ + $(EMU_DIR)/Nes_Effects_Buffer.cpp \ + $(EMU_DIR)/Nes_Emu.cpp \ + $(EMU_DIR)/Nes_File.cpp \ + $(EMU_DIR)/Nes_Film.cpp \ + $(EMU_DIR)/Nes_Film_Data.cpp \ + $(EMU_DIR)/Nes_Film_Packer.cpp \ + $(EMU_DIR)/Nes_Fme7_Apu.cpp \ + $(EMU_DIR)/Nes_Mapper.cpp \ + $(EMU_DIR)/nes_mappers.cpp \ + $(EMU_DIR)/Nes_Mmc1.cpp \ + $(EMU_DIR)/Nes_Mmc3.cpp \ + $(EMU_DIR)/Nes_Namco_Apu.cpp \ + $(EMU_DIR)/Nes_Oscs.cpp \ + $(EMU_DIR)/Nes_Ppu.cpp \ + $(EMU_DIR)/Nes_Ppu_Impl.cpp \ + $(EMU_DIR)/Nes_Ppu_Rendering.cpp \ + $(EMU_DIR)/Nes_Recorder.cpp \ + $(EMU_DIR)/Nes_State.cpp \ + $(EMU_DIR)/nes_util.cpp \ + $(EMU_DIR)/Nes_Vrc6_Apu.cpp \ + $(LIBRETRO_DIR)/libretro.cpp + +LIBSRCS := \ + $(LIBRETRO_DIR)/../fex/Data_Reader.cpp \ + $(LIBRETRO_DIR)/../fex/blargg_errors.cpp \ + $(LIBRETRO_DIR)/../fex/blargg_common.cpp + +LOCAL_SRC_FILES = $(CXXSRCS) $(LIBSRCS) +LOCAL_CXXFLAGS = -DANDROID -D__LIBRETRO__ -Wall -Wno-multichar -Wno-unused-variable -Wno-sign-compare -DNDEBUG \ + -DSTD_AUTO_FILE_WRITER=Std_File_Writer \ + -DSTD_AUTO_FILE_READER=Std_File_Reader \ + -DSTD_AUTO_FILE_COMP_READER=Std_File_Reader \ + -DSTD_AUTO_FILE_COMP_WRITER=Std_File_Writer +LOCAL_C_INCLUDES = $(LIBRETRO_DIR) $(EMU_DIR) $(EMU_DIR)/.. + +include $(BUILD_SHARED_LIBRARY) diff --git a/quicknes/libretro/jni/Application.mk b/quicknes/libretro/jni/Application.mk new file mode 100644 index 0000000000..e235f0da7d --- /dev/null +++ b/quicknes/libretro/jni/Application.mk @@ -0,0 +1,2 @@ +APP_STL := gnustl_static +APP_ABI := all diff --git a/quicknes/libretro/libretro.cpp b/quicknes/libretro/libretro.cpp new file mode 100644 index 0000000000..f631de8e5d --- /dev/null +++ b/quicknes/libretro/libretro.cpp @@ -0,0 +1,267 @@ +#include "libretro.h" +#include +#include +#include +#include +#include +#include "Nes_Emu.h" +#include "fex/Data_Reader.h" +#include "abstract_file.h" + +static Nes_Emu *emu; + +void retro_init(void) +{ + delete emu; + emu = new Nes_Emu; +} + +void retro_deinit(void) +{ + delete emu; + emu = 0; +} + +unsigned retro_api_version(void) +{ + return RETRO_API_VERSION; +} + +void retro_set_controller_port_device(unsigned, unsigned) +{ +} + +void retro_get_system_info(struct retro_system_info *info) +{ + memset(info, 0, sizeof(*info)); + info->library_name = "QuickNES"; + info->library_version = "v1"; + info->need_fullpath = false; + info->valid_extensions = "nes"; // Anything is fine, we don't care. +} + +void retro_get_system_av_info(struct retro_system_av_info *info) +{ + const retro_system_timing timing = { Nes_Emu::frame_rate, 44100.0 }; + info->timing = timing; + + const retro_game_geometry geom = { + Nes_Emu::image_width, + Nes_Emu::image_height, + Nes_Emu::image_width, + Nes_Emu::image_height, + 4.0 / 3.0, + }; + info->geometry = geom; +} + +static retro_video_refresh_t video_cb; +static retro_audio_sample_t audio_cb; +static retro_audio_sample_batch_t audio_batch_cb; +static retro_environment_t environ_cb; +static retro_input_poll_t input_poll_cb; +static retro_input_state_t input_state_cb; + +void retro_set_environment(retro_environment_t cb) +{ + environ_cb = cb; +} + +void retro_set_audio_sample(retro_audio_sample_t cb) +{ + audio_cb = cb; +} + +void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) +{ + audio_batch_cb = cb; +} + +void retro_set_input_poll(retro_input_poll_t cb) +{ + input_poll_cb = cb; +} + +void retro_set_input_state(retro_input_state_t cb) +{ + input_state_cb = cb; +} + +void retro_set_video_refresh(retro_video_refresh_t cb) +{ + video_cb = cb; +} + +void retro_reset(void) +{ + if (emu) + emu->reset(); +} + +#define JOY_A 1 +#define JOY_B 2 +#define JOY_SELECT 4 +#define JOY_START 8 +#define JOY_UP 0x10 +#define JOY_DOWN 0x20 +#define JOY_LEFT 0x40 +#define JOY_RIGHT 0x80 + +typedef struct +{ + unsigned retro; + unsigned nes; +} keymap; + +static const keymap bindmap[] = { + { RETRO_DEVICE_ID_JOYPAD_A, JOY_A }, + { RETRO_DEVICE_ID_JOYPAD_B, JOY_B }, + { RETRO_DEVICE_ID_JOYPAD_SELECT, JOY_SELECT }, + { RETRO_DEVICE_ID_JOYPAD_START, JOY_START }, + { RETRO_DEVICE_ID_JOYPAD_UP, JOY_UP }, + { RETRO_DEVICE_ID_JOYPAD_DOWN, JOY_DOWN }, + { RETRO_DEVICE_ID_JOYPAD_LEFT, JOY_LEFT }, + { RETRO_DEVICE_ID_JOYPAD_RIGHT, JOY_RIGHT }, +}; + +static void update_input(int pads[2]) +{ + pads[0] = pads[1] = 0; + input_poll_cb(); + + for (unsigned p = 0; p < 2; p++) + for (unsigned bind = 0; bind < sizeof(bindmap) / sizeof(bindmap[0]); bind++) + pads[p] |= input_state_cb(p, RETRO_DEVICE_JOYPAD, 0, bindmap[bind].retro) ? bindmap[bind].nes : 0; +} + +void retro_run(void) +{ + int pads[2] = {0}; + update_input(pads); + + emu->emulate_frame(pads[0], pads[1]); + const Nes_Emu::frame_t &frame = emu->frame(); + + static uint32_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height]; + + const uint8_t *in_pixels = frame.pixels; + uint32_t *out_pixels = video_buffer; + + for (unsigned h = 0; h < Nes_Emu::image_height; + h++, in_pixels += frame.pitch, out_pixels += Nes_Emu::image_width) + { + for (unsigned w = 0; w < Nes_Emu::image_width; w++) + { + unsigned col = frame.palette[in_pixels[w]]; + const Nes_Emu::rgb_t& rgb = emu->nes_colors[col]; + unsigned r = rgb.red; + unsigned g = rgb.green; + unsigned b = rgb.blue; + out_pixels[w] = (r << 16) | (g << 8) | (b << 0); + } + } + + video_cb(video_buffer, Nes_Emu::image_width, Nes_Emu::image_height, + Nes_Emu::image_width * sizeof(uint32_t)); + + // Mono -> Stereo. + int16_t samples[2048]; + long read_samples = emu->read_samples(samples, 2048); + int16_t out_samples[4096]; + for (long i = 0; i < read_samples; i++) + out_samples[(i << 1)] = out_samples[(i << 1) + 1] = samples[i]; + + audio_batch_cb(out_samples, read_samples); +} + +bool retro_load_game(const struct retro_game_info *info) +{ + if (!emu) + return false; + + enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; + if (!environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &fmt)) + { + fprintf(stderr, "XRGB8888 is not supported.\n"); + return false; + } + + emu->set_sample_rate(44100); + emu->set_equalizer(Nes_Emu::nes_eq); + emu->set_palette_range(0); + + static uint8_t video_buffer[Nes_Emu::image_width * Nes_Emu::image_height]; + emu->set_pixels(video_buffer, Nes_Emu::image_width); + + Mem_File_Reader reader(info->data, info->size); + return !emu->load_ines(reader); +} + +void retro_unload_game(void) +{ + emu->close(); +} + +unsigned retro_get_region(void) +{ + return RETRO_REGION_NTSC; +} + +bool retro_load_game_special(unsigned, const struct retro_game_info *, size_t) +{ + return false; +} + +size_t retro_serialize_size(void) +{ + Mem_Writer writer; + if (emu->save_state(writer)) + return 0; + + return writer.size(); +} + +bool retro_serialize(void *data, size_t size) +{ + Mem_Writer writer(data, size); + return !emu->save_state(writer); +} + +bool retro_unserialize(const void *data, size_t size) +{ + Mem_File_Reader reader(data, size); + return !emu->load_state(reader); +} + +void *retro_get_memory_data(unsigned id) +{ + switch (id) + { + case RETRO_MEMORY_SAVE_RAM: + return emu->high_mem(); + case RETRO_MEMORY_SYSTEM_RAM: + return emu->low_mem(); + default: + return 0; + } +} + +size_t retro_get_memory_size(unsigned id) +{ + switch (id) + { + case RETRO_MEMORY_SAVE_RAM: + return Nes_Emu::high_mem_size; + case RETRO_MEMORY_SYSTEM_RAM: + return Nes_Emu::low_mem_size; + default: + return 0; + } +} + +void retro_cheat_reset(void) +{} + +void retro_cheat_set(unsigned, bool, const char *) +{} + diff --git a/quicknes/libretro/libretro.h b/quicknes/libretro/libretro.h new file mode 100644 index 0000000000..b02855ae1f --- /dev/null +++ b/quicknes/libretro/libretro.h @@ -0,0 +1,758 @@ +#ifndef LIBRETRO_H__ +#define LIBRETRO_H__ + +#include +#include +#include + +// Hack applied for MSVC when compiling in C89 mode as it isn't C99 compliant. +#ifdef __cplusplus +extern "C" { +#else +#if defined(_MSC_VER) && !defined(SN_TARGET_PS3) && !defined(__cplusplus) +#define bool unsigned char +#define true 1 +#define false 0 +#else +#include +#endif +#endif + +// Used for checking API/ABI mismatches that can break libretro implementations. +// It is not incremented for compatible changes to the API. +#define RETRO_API_VERSION 1 + +// Libretro's fundamental device abstractions. +#define RETRO_DEVICE_MASK 0xff +#define RETRO_DEVICE_NONE 0 + +// The JOYPAD is called RetroPad. It is essentially a Super Nintendo controller, +// but with additional L2/R2/L3/R3 buttons, similar to a PS1 DualShock. +#define RETRO_DEVICE_JOYPAD 1 + +// The mouse is a simple mouse, similar to Super Nintendo's mouse. +// X and Y coordinates are reported relatively to last poll (poll callback). +// It is up to the libretro implementation to keep track of where the mouse pointer is supposed to be on the screen. +// The frontend must make sure not to interfere with its own hardware mouse pointer. +#define RETRO_DEVICE_MOUSE 2 + +// KEYBOARD device lets one poll for raw key pressed. +// It is poll based, so input callback will return with the current pressed state. +#define RETRO_DEVICE_KEYBOARD 3 + +// Lightgun X/Y coordinates are reported relatively to last poll, similar to mouse. +#define RETRO_DEVICE_LIGHTGUN 4 + +// The ANALOG device is an extension to JOYPAD (RetroPad). +// Similar to DualShock it adds two analog sticks. +// This is treated as a separate device type as it returns values in the full analog range +// of [-0x8000, 0x7fff]. Positive X axis is right. Positive Y axis is down. +// Only use ANALOG type when polling for analog values of the axes. +#define RETRO_DEVICE_ANALOG 5 + +// Abstracts the concept of a pointing mechanism, e.g. touch. +// This allows libretro to query in absolute coordinates where on the screen a mouse (or something similar) is being placed. +// For a touch centric device, coordinates reported are the coordinates of the press. +// +// Coordinates in X and Y are reported as: +// [-0x7fff, 0x7fff]: -0x7fff corresponds to the far left/top of the screen, +// and 0x7fff corresponds to the far right/bottom of the screen. +// The "screen" is here defined as area that is passed to the frontend and later displayed on the monitor. +// The frontend is free to scale/resize this screen as it sees fit, however, +// (X, Y) = (-0x7fff, -0x7fff) will correspond to the top-left pixel of the game image, etc. +// +// To check if the pointer coordinates are valid (e.g. a touch display actually being touched), +// PRESSED returns 1 or 0. +// If using a mouse, PRESSED will usually correspond to the left mouse button. +// PRESSED will only return 1 if the pointer is inside the game screen. +// +// For multi-touch, the index variable can be used to successively query more presses. +// If index = 0 returns true for _PRESSED, coordinates can be extracted +// with _X, _Y for index = 0. One can then query _PRESSED, _X, _Y with index = 1, and so on. +// Eventually _PRESSED will return false for an index. No further presses are registered at this point. +#define RETRO_DEVICE_POINTER 6 + +// These device types are specializations of the base types above. +// They should only be used in retro_set_controller_type() to inform libretro implementations +// about use of a very specific device type. +// +// In input state callback, however, only the base type should be used in the 'device' field. +#define RETRO_DEVICE_JOYPAD_MULTITAP ((1 << 8) | RETRO_DEVICE_JOYPAD) +#define RETRO_DEVICE_LIGHTGUN_SUPER_SCOPE ((1 << 8) | RETRO_DEVICE_LIGHTGUN) +#define RETRO_DEVICE_LIGHTGUN_JUSTIFIER ((2 << 8) | RETRO_DEVICE_LIGHTGUN) +#define RETRO_DEVICE_LIGHTGUN_JUSTIFIERS ((3 << 8) | RETRO_DEVICE_LIGHTGUN) + +// Buttons for the RetroPad (JOYPAD). +// The placement of these is equivalent to placements on the Super Nintendo controller. +// L2/R2/L3/R3 buttons correspond to the PS1 DualShock. +#define RETRO_DEVICE_ID_JOYPAD_B 0 +#define RETRO_DEVICE_ID_JOYPAD_Y 1 +#define RETRO_DEVICE_ID_JOYPAD_SELECT 2 +#define RETRO_DEVICE_ID_JOYPAD_START 3 +#define RETRO_DEVICE_ID_JOYPAD_UP 4 +#define RETRO_DEVICE_ID_JOYPAD_DOWN 5 +#define RETRO_DEVICE_ID_JOYPAD_LEFT 6 +#define RETRO_DEVICE_ID_JOYPAD_RIGHT 7 +#define RETRO_DEVICE_ID_JOYPAD_A 8 +#define RETRO_DEVICE_ID_JOYPAD_X 9 +#define RETRO_DEVICE_ID_JOYPAD_L 10 +#define RETRO_DEVICE_ID_JOYPAD_R 11 +#define RETRO_DEVICE_ID_JOYPAD_L2 12 +#define RETRO_DEVICE_ID_JOYPAD_R2 13 +#define RETRO_DEVICE_ID_JOYPAD_L3 14 +#define RETRO_DEVICE_ID_JOYPAD_R3 15 + +// Index / Id values for ANALOG device. +#define RETRO_DEVICE_INDEX_ANALOG_LEFT 0 +#define RETRO_DEVICE_INDEX_ANALOG_RIGHT 1 +#define RETRO_DEVICE_ID_ANALOG_X 0 +#define RETRO_DEVICE_ID_ANALOG_Y 1 + +// Id values for MOUSE. +#define RETRO_DEVICE_ID_MOUSE_X 0 +#define RETRO_DEVICE_ID_MOUSE_Y 1 +#define RETRO_DEVICE_ID_MOUSE_LEFT 2 +#define RETRO_DEVICE_ID_MOUSE_RIGHT 3 + +// Id values for LIGHTGUN types. +#define RETRO_DEVICE_ID_LIGHTGUN_X 0 +#define RETRO_DEVICE_ID_LIGHTGUN_Y 1 +#define RETRO_DEVICE_ID_LIGHTGUN_TRIGGER 2 +#define RETRO_DEVICE_ID_LIGHTGUN_CURSOR 3 +#define RETRO_DEVICE_ID_LIGHTGUN_TURBO 4 +#define RETRO_DEVICE_ID_LIGHTGUN_PAUSE 5 +#define RETRO_DEVICE_ID_LIGHTGUN_START 6 + +// Id values for POINTER. +#define RETRO_DEVICE_ID_POINTER_X 0 +#define RETRO_DEVICE_ID_POINTER_Y 1 +#define RETRO_DEVICE_ID_POINTER_PRESSED 2 + +// Returned from retro_get_region(). +#define RETRO_REGION_NTSC 0 +#define RETRO_REGION_PAL 1 + +// Passed to retro_get_memory_data/size(). +// If the memory type doesn't apply to the implementation NULL/0 can be returned. +#define RETRO_MEMORY_MASK 0xff + +// Regular save ram. This ram is usually found on a game cartridge, backed up by a battery. +// If save game data is too complex for a single memory buffer, +// the SYSTEM_DIRECTORY environment callback can be used. +#define RETRO_MEMORY_SAVE_RAM 0 + +// Some games have a built-in clock to keep track of time. +// This memory is usually just a couple of bytes to keep track of time. +#define RETRO_MEMORY_RTC 1 + +// System ram lets a frontend peek into a game systems main RAM. +#define RETRO_MEMORY_SYSTEM_RAM 2 + +// Video ram lets a frontend peek into a game systems video RAM (VRAM). +#define RETRO_MEMORY_VIDEO_RAM 3 + +// Special memory types. +#define RETRO_MEMORY_SNES_BSX_RAM ((1 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_SNES_BSX_PRAM ((2 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_SNES_SUFAMI_TURBO_A_RAM ((3 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_SNES_SUFAMI_TURBO_B_RAM ((4 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_SNES_GAME_BOY_RAM ((5 << 8) | RETRO_MEMORY_SAVE_RAM) +#define RETRO_MEMORY_SNES_GAME_BOY_RTC ((6 << 8) | RETRO_MEMORY_RTC) + +// Special game types passed into retro_load_game_special(). +// Only used when multiple ROMs are required. +#define RETRO_GAME_TYPE_BSX 0x101 +#define RETRO_GAME_TYPE_BSX_SLOTTED 0x102 +#define RETRO_GAME_TYPE_SUFAMI_TURBO 0x103 +#define RETRO_GAME_TYPE_SUPER_GAME_BOY 0x104 + +// Keysyms used for ID in input state callback when polling RETRO_KEYBOARD. +enum retro_key +{ + RETROK_UNKNOWN = 0, + RETROK_FIRST = 0, + RETROK_BACKSPACE = 8, + RETROK_TAB = 9, + RETROK_CLEAR = 12, + RETROK_RETURN = 13, + RETROK_PAUSE = 19, + RETROK_ESCAPE = 27, + RETROK_SPACE = 32, + RETROK_EXCLAIM = 33, + RETROK_QUOTEDBL = 34, + RETROK_HASH = 35, + RETROK_DOLLAR = 36, + RETROK_AMPERSAND = 38, + RETROK_QUOTE = 39, + RETROK_LEFTPAREN = 40, + RETROK_RIGHTPAREN = 41, + RETROK_ASTERISK = 42, + RETROK_PLUS = 43, + RETROK_COMMA = 44, + RETROK_MINUS = 45, + RETROK_PERIOD = 46, + RETROK_SLASH = 47, + RETROK_0 = 48, + RETROK_1 = 49, + RETROK_2 = 50, + RETROK_3 = 51, + RETROK_4 = 52, + RETROK_5 = 53, + RETROK_6 = 54, + RETROK_7 = 55, + RETROK_8 = 56, + RETROK_9 = 57, + RETROK_COLON = 58, + RETROK_SEMICOLON = 59, + RETROK_LESS = 60, + RETROK_EQUALS = 61, + RETROK_GREATER = 62, + RETROK_QUESTION = 63, + RETROK_AT = 64, + RETROK_LEFTBRACKET = 91, + RETROK_BACKSLASH = 92, + RETROK_RIGHTBRACKET = 93, + RETROK_CARET = 94, + RETROK_UNDERSCORE = 95, + RETROK_BACKQUOTE = 96, + RETROK_a = 97, + RETROK_b = 98, + RETROK_c = 99, + RETROK_d = 100, + RETROK_e = 101, + RETROK_f = 102, + RETROK_g = 103, + RETROK_h = 104, + RETROK_i = 105, + RETROK_j = 106, + RETROK_k = 107, + RETROK_l = 108, + RETROK_m = 109, + RETROK_n = 110, + RETROK_o = 111, + RETROK_p = 112, + RETROK_q = 113, + RETROK_r = 114, + RETROK_s = 115, + RETROK_t = 116, + RETROK_u = 117, + RETROK_v = 118, + RETROK_w = 119, + RETROK_x = 120, + RETROK_y = 121, + RETROK_z = 122, + RETROK_DELETE = 127, + + RETROK_KP0 = 256, + RETROK_KP1 = 257, + RETROK_KP2 = 258, + RETROK_KP3 = 259, + RETROK_KP4 = 260, + RETROK_KP5 = 261, + RETROK_KP6 = 262, + RETROK_KP7 = 263, + RETROK_KP8 = 264, + RETROK_KP9 = 265, + RETROK_KP_PERIOD = 266, + RETROK_KP_DIVIDE = 267, + RETROK_KP_MULTIPLY = 268, + RETROK_KP_MINUS = 269, + RETROK_KP_PLUS = 270, + RETROK_KP_ENTER = 271, + RETROK_KP_EQUALS = 272, + + RETROK_UP = 273, + RETROK_DOWN = 274, + RETROK_RIGHT = 275, + RETROK_LEFT = 276, + RETROK_INSERT = 277, + RETROK_HOME = 278, + RETROK_END = 279, + RETROK_PAGEUP = 280, + RETROK_PAGEDOWN = 281, + + RETROK_F1 = 282, + RETROK_F2 = 283, + RETROK_F3 = 284, + RETROK_F4 = 285, + RETROK_F5 = 286, + RETROK_F6 = 287, + RETROK_F7 = 288, + RETROK_F8 = 289, + RETROK_F9 = 290, + RETROK_F10 = 291, + RETROK_F11 = 292, + RETROK_F12 = 293, + RETROK_F13 = 294, + RETROK_F14 = 295, + RETROK_F15 = 296, + + RETROK_NUMLOCK = 300, + RETROK_CAPSLOCK = 301, + RETROK_SCROLLOCK = 302, + RETROK_RSHIFT = 303, + RETROK_LSHIFT = 304, + RETROK_RCTRL = 305, + RETROK_LCTRL = 306, + RETROK_RALT = 307, + RETROK_LALT = 308, + RETROK_RMETA = 309, + RETROK_LMETA = 310, + RETROK_LSUPER = 311, + RETROK_RSUPER = 312, + RETROK_MODE = 313, + RETROK_COMPOSE = 314, + + RETROK_HELP = 315, + RETROK_PRINT = 316, + RETROK_SYSREQ = 317, + RETROK_BREAK = 318, + RETROK_MENU = 319, + RETROK_POWER = 320, + RETROK_EURO = 321, + RETROK_UNDO = 322, + + RETROK_LAST, + + RETROK_DUMMY = INT_MAX // Ensure sizeof(enum) == sizeof(int) +}; + +enum retro_mod +{ + RETROKMOD_NONE = 0x0000, + + RETROKMOD_SHIFT = 0x01, + RETROKMOD_CTRL = 0x02, + RETROKMOD_ALT = 0x04, + RETROKMOD_META = 0x08, + + RETROKMOD_NUMLOCK = 0x10, + RETROKMOD_CAPSLOCK = 0x20, + RETROKMOD_SCROLLOCK = 0x40, + + RETROKMOD_DUMMY = INT_MAX // Ensure sizeof(enum) == sizeof(int) +}; + +// If set, this call is not part of the public libretro API yet. It can change or be removed at any time. +#define RETRO_ENVIRONMENT_EXPERIMENTAL 0x10000 + +// Environment commands. +#define RETRO_ENVIRONMENT_SET_ROTATION 1 // const unsigned * -- + // Sets screen rotation of graphics. + // Is only implemented if rotation can be accelerated by hardware. + // Valid values are 0, 1, 2, 3, which rotates screen by 0, 90, 180, 270 degrees + // counter-clockwise respectively. + // +#define RETRO_ENVIRONMENT_GET_OVERSCAN 2 // bool * -- + // Boolean value whether or not the implementation should use overscan, or crop away overscan. + // +#define RETRO_ENVIRONMENT_GET_CAN_DUPE 3 // bool * -- + // Boolean value whether or not frontend supports frame duping, + // passing NULL to video frame callback. + // +// Environ 4, 5 are no longer supported (GET_VARIABLE / SET_VARIABLES), and reserved to avoid possible ABI clash. +#define RETRO_ENVIRONMENT_SET_MESSAGE 6 // const struct retro_message * -- + // Sets a message to be displayed in implementation-specific manner for a certain amount of 'frames'. + // Should not be used for trivial messages, which should simply be logged to stderr. +#define RETRO_ENVIRONMENT_SHUTDOWN 7 // N/A (NULL) -- + // Requests the frontend to shutdown. + // Should only be used if game has a specific + // way to shutdown the game from a menu item or similar. + // +#define RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL 8 + // const unsigned * -- + // Gives a hint to the frontend how demanding this implementation + // is on a system. E.g. reporting a level of 2 means + // this implementation should run decently on all frontends + // of level 2 and up. + // + // It can be used by the frontend to potentially warn + // about too demanding implementations. + // + // The levels are "floating", but roughly defined as: + // 0: Low-powered embedded devices such as Raspberry Pi + // 1: 6th generation consoles, such as Wii/Xbox 1, and phones, tablets, etc. + // 2: 7th generation consoles, such as PS3/360, with sub-par CPUs. + // 3: Modern desktop/laptops with reasonably powerful CPUs. + // 4: High-end desktops with very powerful CPUs. + // + // This function can be called on a per-game basis, + // as certain games an implementation can play might be + // particularily demanding. + // If called, it should be called in retro_load_game(). + // +#define RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY 9 + // const char ** -- + // Returns the "system" directory of the frontend. + // This directory can be used to store system specific ROMs such as BIOSes, configuration data, etc. + // The returned value can be NULL. + // If so, no such directory is defined, + // and it's up to the implementation to find a suitable directory. + // +#define RETRO_ENVIRONMENT_SET_PIXEL_FORMAT 10 + // const enum retro_pixel_format * -- + // Sets the internal pixel format used by the implementation. + // The default pixel format is RETRO_PIXEL_FORMAT_0RGB1555. + // This pixel format however, is deprecated (see enum retro_pixel_format). + // If the call returns false, the frontend does not support this pixel format. + // This function should be called inside retro_load_game() or retro_get_system_av_info(). + // +#define RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS 11 + // const struct retro_input_descriptor * -- + // Sets an array of retro_input_descriptors. + // It is up to the frontend to present this in a usable way. + // The array is terminated by retro_input_descriptor::description being set to NULL. + // This function can be called at any time, but it is recommended to call it as early as possible. +#define RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK 12 + // const struct retro_keyboard_callback * -- + // Sets a callback function used to notify core about keyboard events. + // +#define RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE 13 + // const struct retro_disk_control_callback * -- + // Sets an interface which frontend can use to eject and insert disk images. + // This is used for games which consist of multiple images and must be manually + // swapped out by the user (e.g. PSX). +#define RETRO_ENVIRONMENT_SET_HW_RENDER (14 | RETRO_ENVIRONMENT_EXPERIMENTAL) + // struct retro_hw_render_callback * -- + // NOTE: This call is currently very experimental, and should not be considered part of the public API. + // The interface could be changed or removed at any time. + // Sets an interface to let a libretro core render with hardware acceleration. + // Should be called in retro_load_game(). + // If successful, libretro cores will be able to render to a frontend-provided framebuffer. + // The size of this framebuffer will be at least as large as max_width/max_height provided in get_av_info(). + // If HW rendering is used, pass only RETRO_HW_FRAME_BUFFER_VALID or NULL to retro_video_refresh_t. +#define RETRO_ENVIRONMENT_GET_VARIABLE 15 + // struct retro_variable * -- + // Interface to aquire user-defined information from environment + // that cannot feasibly be supported in a multi-system way. + // 'key' should be set to a key which has already been set by SET_VARIABLES. + // 'data' will be set to a value or NULL. + // +#define RETRO_ENVIRONMENT_SET_VARIABLES 16 + // const struct retro_variable * -- + // Allows an implementation to signal the environment + // which variables it might want to check for later using GET_VARIABLE. + // This allows the frontend to present these variables to a user dynamically. + // This should be called as early as possible (ideally in retro_set_environment). + // + // 'data' points to an array of retro_variable structs terminated by a { NULL, NULL } element. + // retro_variable::key should be namespaced to not collide with other implementations' keys. E.g. A core called 'foo' should use keys named as 'foo_option'. + // retro_variable::value should contain a human readable description of the key as well as a '|' delimited list of expected values. + // The number of possible options should be very limited, i.e. it should be feasible to cycle through options without a keyboard. + // First entry should be treated as a default. + // + // Example entry: + // { "foo_option", "Speed hack coprocessor X; false|true" } + // + // Text before first ';' is description. This ';' must be followed by a space, and followed by a list of possible values split up with '|'. + // Only strings are operated on. The possible values will generally be displayed and stored as-is by the frontend. + // +#define RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE 17 + // bool * -- + // Result is set to true if some variables are updated by + // frontend since last call to RETRO_ENVIRONMENT_GET_VARIABLE. + // Variables should be queried with GET_VARIABLE. + +// Pass this to retro_video_refresh_t if rendering to hardware. +// Passing NULL to retro_video_refresh_t is still a frame dupe as normal. +#define RETRO_HW_FRAME_BUFFER_VALID ((void*)-1) + +// Invalidates the current HW context. +// If called, all GPU resources must be reinitialized. +// Usually called when frontend reinits video driver. +// Also called first time video driver is initialized, allowing libretro core to init resources. +typedef void (*retro_hw_context_reset_t)(void); +// Gets current framebuffer which is to be rendered to. Could change every frame potentially. +typedef uintptr_t (*retro_hw_get_current_framebuffer_t)(void); + +// Get a symbol from HW context. +typedef void (*retro_proc_address_t)(void); +typedef retro_proc_address_t (*retro_hw_get_proc_address_t)(const char *sym); + +enum retro_hw_context_type +{ + RETRO_HW_CONTEXT_NONE = 0, + RETRO_HW_CONTEXT_OPENGL, // OpenGL 2.x. Latest version available before 3.x+. + RETRO_HW_CONTEXT_OPENGLES2, // GLES 2.0 + + RETRO_HW_CONTEXT_DUMMY = INT_MAX +}; + +struct retro_hw_render_callback +{ + enum retro_hw_context_type context_type; // Which API to use. Set by libretro core. + retro_hw_context_reset_t context_reset; // Set by libretro core. + retro_hw_get_current_framebuffer_t get_current_framebuffer; // Set by frontend. + retro_hw_get_proc_address_t get_proc_address; // Set by frontend. + bool depth; // Set if render buffers should have depth component attached. +}; + +// Callback type passed in RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK. Called by the frontend in response to keyboard events. +// down is set if the key is being pressed, or false if it is being released. +// keycode is the RETROK value of the char. +// character is the text character of the pressed key. (UTF-32). +// key_modifiers is a set of RETROKMOD values or'ed together. +typedef void (*retro_keyboard_event_t)(bool down, unsigned keycode, uint32_t character, uint16_t key_modifiers); + +struct retro_keyboard_callback +{ + retro_keyboard_event_t callback; +}; + +// Callbacks for RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE. +// Should be set for implementations which can swap out multiple disk images in runtime. +// If the implementation can do this automatically, it should strive to do so. +// However, there are cases where the user must manually do so. +// +// Overview: To swap a disk image, eject the disk image with set_eject_state(true). +// Set the disk index with set_image_index(index). Insert the disk again with set_eject_state(false). + +// If ejected is true, "ejects" the virtual disk tray. +// When ejected, the disk image index can be set. +typedef bool (*retro_set_eject_state_t)(bool ejected); +// Gets current eject state. The initial state is 'not ejected'. +typedef bool (*retro_get_eject_state_t)(void); +// Gets current disk index. First disk is index 0. +// If return value is >= get_num_images(), no disk is currently inserted. +typedef unsigned (*retro_get_image_index_t)(void); +// Sets image index. Can only be called when disk is ejected. +// The implementation supports setting "no disk" by using an index >= get_num_images(). +typedef bool (*retro_set_image_index_t)(unsigned index); +// Gets total number of images which are available to use. +typedef unsigned (*retro_get_num_images_t)(void); +// +// Replaces the disk image associated with index. +// Arguments to pass in info have same requirements as retro_load_game(). +// Virtual disk tray must be ejected when calling this. +// Replacing a disk image with info = NULL will remove the disk image from the internal list. +// As a result, calls to get_image_index() can change. +// +// E.g. replace_image_index(1, NULL), and previous get_image_index() returned 4 before. +// Index 1 will be removed, and the new index is 3. +struct retro_game_info; +typedef bool (*retro_replace_image_index_t)(unsigned index, const struct retro_game_info *info); +// Adds a new valid index (get_num_images()) to the internal disk list. +// This will increment subsequent return values from get_num_images() by 1. +// This image index cannot be used until a disk image has been set with replace_image_index. +typedef bool (*retro_add_image_index_t)(void); + +struct retro_disk_control_callback +{ + retro_set_eject_state_t set_eject_state; + retro_get_eject_state_t get_eject_state; + + retro_get_image_index_t get_image_index; + retro_set_image_index_t set_image_index; + retro_get_num_images_t get_num_images; + + retro_replace_image_index_t replace_image_index; + retro_add_image_index_t add_image_index; +}; + +enum retro_pixel_format +{ + // 0RGB1555, native endian. 0 bit must be set to 0. + // This pixel format is default for compatibility concerns only. + // If a 15/16-bit pixel format is desired, consider using RGB565. + RETRO_PIXEL_FORMAT_0RGB1555 = 0, + + // XRGB8888, native endian. X bits are ignored. + RETRO_PIXEL_FORMAT_XRGB8888 = 1, + + // RGB565, native endian. This pixel format is the recommended format to use if a 15/16-bit format is desired + // as it is the pixel format that is typically available on a wide range of low-power devices. + // It is also natively supported in APIs like OpenGL ES. + RETRO_PIXEL_FORMAT_RGB565 = 2, + + // Ensure sizeof() == sizeof(int). + RETRO_PIXEL_FORMAT_UNKNOWN = INT_MAX +}; + +struct retro_message +{ + const char *msg; // Message to be displayed. + unsigned frames; // Duration in frames of message. +}; + +// Describes how the libretro implementation maps a libretro input bind +// to its internal input system through a human readable string. +// This string can be used to better let a user configure input. +struct retro_input_descriptor +{ + // Associates given parameters with a description. + unsigned port; + unsigned device; + unsigned index; + unsigned id; + + const char *description; // Human readable description for parameters. + // The pointer must remain valid until retro_unload_game() is called. +}; + +struct retro_system_info +{ + // All pointers are owned by libretro implementation, and pointers must remain valid until retro_deinit() is called. + + const char *library_name; // Descriptive name of library. Should not contain any version numbers, etc. + const char *library_version; // Descriptive version of core. + + const char *valid_extensions; // A string listing probably rom extensions the core will be able to load, separated with pipe. + // I.e. "bin|rom|iso". + // Typically used for a GUI to filter out extensions. + + bool need_fullpath; // If true, retro_load_game() is guaranteed to provide a valid pathname in retro_game_info::path. + // ::data and ::size are both invalid. + // If false, ::data and ::size are guaranteed to be valid, but ::path might not be valid. + // This is typically set to true for libretro implementations that must load from file. + // Implementations should strive for setting this to false, as it allows the frontend to perform patching, etc. + + bool block_extract; // If true, the frontend is not allowed to extract any archives before loading the real ROM. + // Necessary for certain libretro implementations that load games from zipped archives. +}; + +struct retro_game_geometry +{ + unsigned base_width; // Nominal video width of game. + unsigned base_height; // Nominal video height of game. + unsigned max_width; // Maximum possible width of game. + unsigned max_height; // Maximum possible height of game. + + float aspect_ratio; // Nominal aspect ratio of game. If aspect_ratio is <= 0.0, + // an aspect ratio of base_width / base_height is assumed. + // A frontend could override this setting if desired. +}; + +struct retro_system_timing +{ + double fps; // FPS of video content. + double sample_rate; // Sampling rate of audio. +}; + +struct retro_system_av_info +{ + struct retro_game_geometry geometry; + struct retro_system_timing timing; +}; + +struct retro_variable +{ + const char *key; // Variable to query in RETRO_ENVIRONMENT_GET_VARIABLE. + // If NULL, obtains the complete environment string if more complex parsing is necessary. + // The environment string is formatted as key-value pairs delimited by semicolons as so: + // "key1=value1;key2=value2;..." + const char *value; // Value to be obtained. If key does not exist, it is set to NULL. +}; + +struct retro_game_info +{ + const char *path; // Path to game, UTF-8 encoded. Usually used as a reference. + // May be NULL if rom was loaded from stdin or similar. + // retro_system_info::need_fullpath guaranteed that this path is valid. + const void *data; // Memory buffer of loaded game. Will be NULL if need_fullpath was set. + size_t size; // Size of memory buffer. + const char *meta; // String of implementation specific meta-data. +}; + +// Callbacks +// +// Environment callback. Gives implementations a way of performing uncommon tasks. Extensible. +typedef bool (*retro_environment_t)(unsigned cmd, void *data); + +// Render a frame. Pixel format is 15-bit 0RGB1555 native endian unless changed (see RETRO_ENVIRONMENT_SET_PIXEL_FORMAT). +// Width and height specify dimensions of buffer. +// Pitch specifices length in bytes between two lines in buffer. +// For performance reasons, it is highly recommended to have a frame that is packed in memory, i.e. pitch == width * byte_per_pixel. +// Certain graphic APIs, such as OpenGL ES, do not like textures that are not packed in memory. +typedef void (*retro_video_refresh_t)(const void *data, unsigned width, unsigned height, size_t pitch); + +// Renders a single audio frame. Should only be used if implementation generates a single sample at a time. +// Format is signed 16-bit native endian. +typedef void (*retro_audio_sample_t)(int16_t left, int16_t right); +// Renders multiple audio frames in one go. One frame is defined as a sample of left and right channels, interleaved. +// I.e. int16_t buf[4] = { l, r, l, r }; would be 2 frames. +// Only one of the audio callbacks must ever be used. +typedef size_t (*retro_audio_sample_batch_t)(const int16_t *data, size_t frames); + +// Polls input. +typedef void (*retro_input_poll_t)(void); +// Queries for input for player 'port'. device will be masked with RETRO_DEVICE_MASK. +// Specialization of devices such as RETRO_DEVICE_JOYPAD_MULTITAP that have been set with retro_set_controller_port_device() +// will still use the higher level RETRO_DEVICE_JOYPAD to request input. +typedef int16_t (*retro_input_state_t)(unsigned port, unsigned device, unsigned index, unsigned id); + +// Sets callbacks. retro_set_environment() is guaranteed to be called before retro_init(). +// The rest of the set_* functions are guaranteed to have been called before the first call to retro_run() is made. +void retro_set_environment(retro_environment_t); +void retro_set_video_refresh(retro_video_refresh_t); +void retro_set_audio_sample(retro_audio_sample_t); +void retro_set_audio_sample_batch(retro_audio_sample_batch_t); +void retro_set_input_poll(retro_input_poll_t); +void retro_set_input_state(retro_input_state_t); + +// Library global initialization/deinitialization. +void retro_init(void); +void retro_deinit(void); + +// Must return RETRO_API_VERSION. Used to validate ABI compatibility when the API is revised. +unsigned retro_api_version(void); + +// Gets statically known system info. Pointers provided in *info must be statically allocated. +// Can be called at any time, even before retro_init(). +void retro_get_system_info(struct retro_system_info *info); + +// Gets information about system audio/video timings and geometry. +// Can be called only after retro_load_game() has successfully completed. +// NOTE: The implementation of this function might not initialize every variable if needed. +// E.g. geom.aspect_ratio might not be initialized if core doesn't desire a particular aspect ratio. +void retro_get_system_av_info(struct retro_system_av_info *info); + +// Sets device to be used for player 'port'. +void retro_set_controller_port_device(unsigned port, unsigned device); + +// Resets the current game. +void retro_reset(void); + +// Runs the game for one video frame. +// During retro_run(), input_poll callback must be called at least once. +// +// If a frame is not rendered for reasons where a game "dropped" a frame, +// this still counts as a frame, and retro_run() should explicitly dupe a frame if GET_CAN_DUPE returns true. +// In this case, the video callback can take a NULL argument for data. +void retro_run(void); + +// Returns the amount of data the implementation requires to serialize internal state (save states). +// Beetween calls to retro_load_game() and retro_unload_game(), the returned size is never allowed to be larger than a previous returned value, to +// ensure that the frontend can allocate a save state buffer once. +size_t retro_serialize_size(void); + +// Serializes internal state. If failed, or size is lower than retro_serialize_size(), it should return false, true otherwise. +bool retro_serialize(void *data, size_t size); +bool retro_unserialize(const void *data, size_t size); + +void retro_cheat_reset(void); +void retro_cheat_set(unsigned index, bool enabled, const char *code); + +// Loads a game. +bool retro_load_game(const struct retro_game_info *game); + +// Loads a "special" kind of game. Should not be used except in extreme cases. +bool retro_load_game_special( + unsigned game_type, + const struct retro_game_info *info, size_t num_info +); + +// Unloads a currently loaded game. +void retro_unload_game(void); + +// Gets region of game. +unsigned retro_get_region(void); + +// Gets region of memory. +void *retro_get_memory_data(unsigned id); +size_t retro_get_memory_size(unsigned id); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/quicknes/libretro/link.T b/quicknes/libretro/link.T new file mode 100644 index 0000000000..b0c262db9e --- /dev/null +++ b/quicknes/libretro/link.T @@ -0,0 +1,5 @@ +{ + global: retro_*; + local: *; +}; + diff --git a/quicknes/nes_emu/Blip_Buffer.cpp b/quicknes/nes_emu/Blip_Buffer.cpp new file mode 100644 index 0000000000..e106889b04 --- /dev/null +++ b/quicknes/nes_emu/Blip_Buffer.cpp @@ -0,0 +1,412 @@ + +// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ + +#include "Blip_Buffer.h" + +#include +#include +#include +#include +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int const buffer_extra = blip_widest_impulse_ + 2; + +Blip_Buffer::Blip_Buffer() +{ + factor_ = LONG_MAX; + offset_ = 0; + buffer_ = 0; + buffer_size_ = 0; + sample_rate_ = 0; + reader_accum = 0; + bass_shift = 0; + clock_rate_ = 0; + bass_freq_ = 16; + length_ = 0; + + // assumptions code makes about implementation-defined features + #ifndef NDEBUG + // right shift of negative value preserves sign + buf_t_ i = -0x7FFFFFFE; + assert( (i >> 1) == -0x3FFFFFFF ); + + // casting to short truncates to 16 bits and sign-extends + i = 0x18000; + assert( (short) i == -0x8000 ); + #endif +} + +Blip_Buffer::~Blip_Buffer() +{ + free( buffer_ ); +} + +void Blip_Buffer::clear( int entire_buffer ) +{ + offset_ = 0; + reader_accum = 0; + if ( buffer_ ) + { + long count = (entire_buffer ? buffer_size_ : samples_avail()); + memset( buffer_, 0, (count + buffer_extra) * sizeof (buf_t_) ); + } +} + +Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec ) +{ + // start with maximum length that resampled time can represent + long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - buffer_extra - 64; + if ( msec != blip_max_length ) + { + long s = (new_rate * (msec + 1) + 999) / 1000; + if ( s < new_size ) + new_size = s; + else + assert( 0 ); // fails if requested buffer length exceeds limit + } + + if ( buffer_size_ != new_size ) + { + void* p = realloc( buffer_, (new_size + buffer_extra) * sizeof *buffer_ ); + if ( !p ) + return "Out of memory"; + buffer_ = (buf_t_*) p; + } + + buffer_size_ = new_size; + + // update things based on the sample rate + sample_rate_ = new_rate; + length_ = new_size * 1000 / new_rate - 1; + if ( msec ) + assert( length_ == msec ); // ensure length is same as that passed in + if ( clock_rate_ ) + clock_rate( clock_rate_ ); + bass_freq( bass_freq_ ); + + clear(); + + return 0; // success +} + +blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const +{ + double ratio = (double) sample_rate_ / clock_rate; + long factor = (long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 ); + assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large + return (blip_resampled_time_t) factor; +} + +void Blip_Buffer::bass_freq( int freq ) +{ + bass_freq_ = freq; + int shift = 31; + if ( freq > 0 ) + { + shift = 13; + long f = (freq << 16) / sample_rate_; + while ( (f >>= 1) && --shift ) { } + } + bass_shift = shift; +} + +void Blip_Buffer::end_frame( blip_time_t t ) +{ + offset_ += t * factor_; + assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length +} + +void Blip_Buffer::remove_silence( long count ) +{ + assert( count <= samples_avail() ); // tried to remove more samples than available + offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} + +long Blip_Buffer::count_samples( blip_time_t t ) const +{ + unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY; + unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY; + return (long) (last_sample - first_sample); +} + +blip_time_t Blip_Buffer::count_clocks( long count ) const +{ + if ( count > buffer_size_ ) + count = buffer_size_; + blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; + return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_); +} + +void Blip_Buffer::remove_samples( long count ) +{ + if ( count ) + { + remove_silence( count ); + + // copy remaining samples to beginning and clear old samples + long remain = samples_avail() + buffer_extra; + memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ ); + memset( buffer_ + remain, 0, count * sizeof *buffer_ ); + } +} + +// Blip_Synth_ + +Blip_Synth_::Blip_Synth_( short* p, int w ) : + impulses( p ), + width( w ) +{ + volume_unit_ = 0.0; + kernel_unit = 0; + buf = 0; + last_amp = 0; + delta_factor = 0; +} + +// TODO: apparently this is defined elsewhere too +#define pi my_pi +static double const pi = 3.1415926535897932384626433832795029; + +static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff ) +{ + if ( cutoff >= 0.999 ) + cutoff = 0.999; + + if ( treble < -300.0 ) + treble = -300.0; + if ( treble > 5.0 ) + treble = 5.0; + + double const maxh = 4096.0; + double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) ); + double const pow_a_n = pow( rolloff, maxh - maxh * cutoff ); + double const to_angle = pi / 2 / maxh / oversample; + for ( int i = 0; i < count; i++ ) + { + double angle = ((i - count) * 2 + 1) * to_angle; + double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle ); + double cos_nc_angle = cos( maxh * cutoff * angle ); + double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle ); + double cos_angle = cos( angle ); + + c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle; + double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle); + double b = 2.0 - cos_angle - cos_angle; + double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle; + + out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d + } +} + +void blip_eq_t::generate( float* out, int count ) const +{ + // lower cutoff freq for narrow kernels with their wider transition band + // (8 points->1.49, 16 points->1.15) + double oversample = blip_res * 2.25 / count + 0.85; + double half_rate = sample_rate * 0.5; + if ( cutoff_freq ) + oversample = half_rate / cutoff_freq; + double cutoff = rolloff_freq * oversample / half_rate; + + gen_sinc( out, count, blip_res * oversample, treble, cutoff ); + + // apply (half of) hamming window + double to_fraction = pi / (count - 1); + for ( int i = count; i--; ) + out [i] *= 0.54 - 0.46 * cos( i * to_fraction ); +} + +void Blip_Synth_::adjust_impulse() +{ + // sum pairs for each phase and add error correction to end of first half + int const size = impulses_size(); + for ( int p = blip_res; p-- >= blip_res / 2; ) + { + int p2 = blip_res - 2 - p; + long error = kernel_unit; + for ( int i = 1; i < size; i += blip_res ) + { + error -= impulses [i + p ]; + error -= impulses [i + p2]; + } + if ( p == p2 ) + error /= 2; // phase = 0.5 impulse uses same half for both sides + impulses [size - blip_res + p] += error; + //printf( "error: %ld\n", error ); + } + + //for ( int i = blip_res; i--; printf( "\n" ) ) + // for ( int j = 0; j < width / 2; j++ ) + // printf( "%5ld,", impulses [j * blip_res + i + 1] ); +} + +void Blip_Synth_::treble_eq( blip_eq_t const& eq ) +{ + float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2]; + + int const half_size = blip_res / 2 * (width - 1); + eq.generate( &fimpulse [blip_res], half_size ); + + int i; + + // need mirror slightly past center for calculation + for ( i = blip_res; i--; ) + fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i]; + + // starts at 0 + for ( i = 0; i < blip_res; i++ ) + fimpulse [i] = 0.0f; + + // find rescale factor + double total = 0.0; + for ( i = 0; i < half_size; i++ ) + total += fimpulse [blip_res + i]; + + //double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB + //double const base_unit = 37888.0; // allows treble to +5 dB + double const base_unit = 32768.0; // necessary for blip_unscaled to work + double rescale = base_unit / 2 / total; + kernel_unit = (long) base_unit; + + // integrate, first difference, rescale, convert to int + double sum = 0.0; + double next = 0.0; + int const impulses_size = this->impulses_size(); + for ( i = 0; i < impulses_size; i++ ) + { + impulses [i] = (short) floor( (next - sum) * rescale + 0.5 ); + sum += fimpulse [i]; + next += fimpulse [i + blip_res]; + } + adjust_impulse(); + + // volume might require rescaling + double vol = volume_unit_; + if ( vol ) + { + volume_unit_ = 0.0; + volume_unit( vol ); + } +} + +void Blip_Synth_::volume_unit( double new_unit ) +{ + if ( new_unit != volume_unit_ ) + { + // use default eq if it hasn't been set yet + if ( !kernel_unit ) + treble_eq( -8.0 ); + + volume_unit_ = new_unit; + double factor = new_unit * (1L << blip_sample_bits) / kernel_unit; + + if ( factor > 0.0 ) + { + int shift = 0; + + // if unit is really small, might need to attenuate kernel + while ( factor < 2.0 ) + { + shift++; + factor *= 2.0; + } + + if ( shift ) + { + kernel_unit >>= shift; + assert( kernel_unit > 0 ); // fails if volume unit is too low + + // keep values positive to avoid round-towards-zero of sign-preserving + // right shift for negative values + long offset = 0x8000 + (1 << (shift - 1)); + long offset2 = 0x8000 >> shift; + for ( int i = impulses_size(); i--; ) + impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2); + adjust_impulse(); + } + } + delta_factor = (int) floor( factor + 0.5 ); + //printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit ); + } +} + +long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, int stereo ) +{ + long count = samples_avail(); + if ( count > max_samples ) + count = max_samples; + + if ( count ) + { + int const sample_shift = blip_sample_bits - 16; + int const bass_shift = this->bass_shift; + long accum = reader_accum; + buf_t_* in = buffer_; + + if ( !stereo ) + { + for ( long n = count; n--; ) + { + long s = accum >> sample_shift; + accum -= accum >> bass_shift; + accum += *in++; + *out++ = (blip_sample_t) s; + + // clamp sample + if ( (blip_sample_t) s != s ) + out [-1] = (blip_sample_t) (0x7FFF - (s >> 24)); + } + } + else + { + for ( long n = count; n--; ) + { + long s = accum >> sample_shift; + accum -= accum >> bass_shift; + accum += *in++; + *out = (blip_sample_t) s; + out += 2; + + // clamp sample + if ( (blip_sample_t) s != s ) + out [-2] = (blip_sample_t) (0x7FFF - (s >> 24)); + } + } + + reader_accum = accum; + remove_samples( count ); + } + return count; +} + +void Blip_Buffer::mix_samples( blip_sample_t const* in, long count ) +{ + buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2; + + int const sample_shift = blip_sample_bits - 16; + int prev = 0; + while ( count-- ) + { + long s = (long) *in++ << sample_shift; + *out += s - prev; + prev = s; + ++out; + } + *out -= prev; +} + diff --git a/quicknes/nes_emu/Blip_Buffer.h b/quicknes/nes_emu/Blip_Buffer.h new file mode 100644 index 0000000000..49a156a3f6 --- /dev/null +++ b/quicknes/nes_emu/Blip_Buffer.h @@ -0,0 +1,354 @@ + +// Band-limited sound synthesis and buffering + +// Blip_Buffer 0.4.0 + +#ifndef BLIP_BUFFER_H +#define BLIP_BUFFER_H + +// Time unit at source clock rate +typedef long blip_time_t; + +// Output samples are 16-bit signed, with a range of -32768 to 32767 +typedef short blip_sample_t; +enum { blip_sample_max = 32767 }; + +class Blip_Buffer { +public: + typedef const char* blargg_err_t; + + // Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults + // to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there + // isn't enough memory, returns error without affecting current buffer setup. + blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 ); + + // Set number of source time units per second + void clock_rate( long ); + + // End current time frame of specified duration and make its samples available + // (along with any still-unread samples) for reading with read_samples(). Begins + // a new time frame at the end of the current frame. + void end_frame( blip_time_t time ); + + // Read at most 'max_samples' out of buffer into 'dest', removing them from from + // the buffer. Returns number of samples actually read and removed. If stereo is + // true, increments 'dest' one extra time after writing each sample, to allow + // easy interleving of two channels into a stereo output buffer. + long read_samples( blip_sample_t* dest, long max_samples, int stereo = 0 ); + +// Additional optional features + + // Current output sample rate + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // Number of source time units per second + long clock_rate() const; + + // Set frequency high-pass filter frequency, where higher values reduce bass more + void bass_freq( int frequency ); + + // Number of samples delay from synthesis to samples read out + int output_latency() const; + + // Remove all available samples and clear buffer to silence. If 'entire_buffer' is + // false, just clears out any samples waiting rather than the entire buffer. + void clear( int entire_buffer = 1 ); + + // Number of samples available for reading with read_samples() + long samples_avail() const; + + // Remove 'count' samples from those waiting to be read + void remove_samples( long count ); + +// Experimental features + + // Number of raw samples that can be mixed within frame of specified duration. + long count_samples( blip_time_t duration ) const; + + // Mix 'count' samples from 'buf' into buffer. + void mix_samples( blip_sample_t const* buf, long count ); + + // Count number of clocks needed until 'count' samples will be available. + // If buffer can't even hold 'count' samples, returns number of clocks until + // buffer becomes full. + blip_time_t count_clocks( long count ) const; + + // not documented yet + typedef unsigned long blip_resampled_time_t; + void remove_silence( long count ); + blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; } + blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; } + blip_resampled_time_t clock_rate_factor( long clock_rate ) const; +public: + Blip_Buffer(); + ~Blip_Buffer(); + + // Deprecated + typedef blip_resampled_time_t resampled_time_t; + blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); } + blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); } +private: + // noncopyable + Blip_Buffer( const Blip_Buffer& ); + Blip_Buffer& operator = ( const Blip_Buffer& ); +public: + typedef long buf_t_; + unsigned long factor_; + blip_resampled_time_t offset_; + buf_t_* buffer_; + long buffer_size_; +private: + long reader_accum; + int bass_shift; + long sample_rate_; + long clock_rate_; + int bass_freq_; + int length_; + friend class Blip_Reader; +}; + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +// Number of bits in resample ratio fraction. Higher values give a more accurate ratio +// but reduce maximum buffer size. +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in +// noticeable broadband noise when synthesizing high frequency square waves. +// Affects size of Blip_Synth objects since they store the waveform directly. +#ifndef BLIP_PHASE_BITS + #define BLIP_PHASE_BITS 6 +#endif + + // Internal + typedef unsigned long blip_resampled_time_t; + int const blip_widest_impulse_ = 16; + int const blip_res = 1 << BLIP_PHASE_BITS; + class blip_eq_t; + + class Blip_Synth_ { + double volume_unit_; + short* const impulses; + int const width; + long kernel_unit; + int impulses_size() const { return blip_res / 2 * width + 1; } + void adjust_impulse(); + public: + Blip_Buffer* buf; + int last_amp; + int delta_factor; + + Blip_Synth_( short* impulses, int width ); + void treble_eq( blip_eq_t const& ); + void volume_unit( double ); + }; + +// Quality level. Start with blip_good_quality. +const int blip_med_quality = 8; +const int blip_good_quality = 12; +const int blip_high_quality = 16; + +// Range specifies the greatest expected change in amplitude. Calculate it +// by finding the difference between the maximum and minimum expected +// amplitudes (max - min). +template +class Blip_Synth { +public: + // Set overall volume of waveform + void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); } + + // Configure low-pass filter (see notes.txt) + void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); } + + // Get/set Blip_Buffer used for output + Blip_Buffer* output() const { return impl.buf; } + void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; } + + // Update amplitude of waveform at given time. Using this requires a separate + // Blip_Synth for each waveform. + void update( blip_time_t time, int amplitude ); + +// Low-level interface + + // Add an amplitude transition of specified delta, optionally into specified buffer + // rather than the one set with output(). Delta can be positive or negative. + // The actual change in amplitude is delta * (volume / range) + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); } + + // Works directly in terms of fractional output samples. Contact author for more. + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + + // Same as offset(), except code is inlined for higher performance + void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const { + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t t, int delta ) const { + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); + } + +public: + Blip_Synth() : impl( impulses, quality ) { } +private: + typedef short imp_t; + imp_t impulses [blip_res * (quality / 2) + 1]; + Blip_Synth_ impl; +}; + +// Low-pass equalization parameters +class blip_eq_t { +public: + // Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce + // treble, small positive values (0 to 5.0) increase treble. + blip_eq_t( double treble_db = 0 ); + + // See notes.txt + blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 ); + +private: + double treble; + long rolloff_freq; + long sample_rate; + long cutoff_freq; + void generate( float* out, int count ) const; + friend class Blip_Synth_; +}; + +int const blip_sample_bits = 30; + +// Optimized inline sample reader for custom sample formats and mixing of Blip_Buffer samples +class Blip_Reader { +public: + // Begin reading samples from buffer. Returns value to pass to next() (can + // be ignored if default bass_freq is acceptable). + int begin( Blip_Buffer& ); + + // Current sample + long read() const { return accum >> (blip_sample_bits - 16); } + + // Current raw sample in full internal resolution + long read_raw() const { return accum; } + + // Advance to next sample + void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); } + + // End reading samples from buffer. The number of samples read must now be removed + // using Blip_Buffer::remove_samples(). + void end( Blip_Buffer& b ) { b.reader_accum = accum; } + +private: + const Blip_Buffer::buf_t_* buf; + long accum; +}; + + +// End of public interface + + +#include + +// Compatibility with older version +const long blip_unscaled = 65535; +const int blip_low_quality = blip_med_quality; +const int blip_best_quality = blip_high_quality; + +#define BLIP_FWD( i ) { \ + long t0 = i0 * delta + buf [fwd + i]; \ + long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i]; \ + i0 = imp [blip_res * (i + 2)]; \ + buf [fwd + i] = t0; \ + buf [fwd + 1 + i] = t1; } + +#define BLIP_REV( r ) { \ + long t0 = i0 * delta + buf [rev - r]; \ + long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r]; \ + i0 = imp [blip_res * (r - 1)]; \ + buf [rev - r] = t0; \ + buf [rev + 1 - r] = t1; } + +template +inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + // Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the + // need for a longer buffer as set by set_sample_rate(). + assert( (long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ ); + delta *= impl.delta_factor; + int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1)); + imp_t const* imp = impulses + blip_res - phase; + long* buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY); + long i0 = *imp; + + int const fwd = (blip_widest_impulse_ - quality) / 2; + int const rev = fwd + quality - 2; + + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + int const mid = quality / 2 - 1; + long t0 = i0 * delta + buf [fwd + mid - 1]; + long t1 = imp [blip_res * mid] * delta + buf [fwd + mid]; + imp = impulses + phase; + i0 = imp [blip_res * mid]; + buf [fwd + mid - 1] = t0; + buf [fwd + mid] = t1; + } + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + + long t0 = i0 * delta + buf [rev]; + long t1 = *imp * delta + buf [rev + 1]; + buf [rev] = t0; + buf [rev + 1] = t1; +} + +#undef BLIP_FWD +#undef BLIP_REV + +template +void Blip_Synth::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const +{ + offset_resampled( t * buf->factor_ + buf->offset_, delta, buf ); +} + +template +void Blip_Synth::update( blip_time_t t, int amp ) +{ + int delta = amp - impl.last_amp; + impl.last_amp = amp; + offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf ); +} + +inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { } +inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) : + treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { } + +inline int Blip_Buffer::length() const { return length_; } +inline long Blip_Buffer::samples_avail() const { return (long) (offset_ >> BLIP_BUFFER_ACCURACY); } +inline long Blip_Buffer::sample_rate() const { return sample_rate_; } +inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; } +inline long Blip_Buffer::clock_rate() const { return clock_rate_; } +inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); } + +inline int Blip_Reader::begin( Blip_Buffer& blip_buf ) +{ + buf = blip_buf.buffer_; + accum = blip_buf.reader_accum; + return blip_buf.bass_shift; +} + +int const blip_max_length = 0; +int const blip_default_length = 250; + +#endif + diff --git a/quicknes/nes_emu/Blip_Synth.h b/quicknes/nes_emu/Blip_Synth.h new file mode 100644 index 0000000000..f2eb077b88 --- /dev/null +++ b/quicknes/nes_emu/Blip_Synth.h @@ -0,0 +1,208 @@ + +// Blip_Synth and Blip_Wave are waveform transition synthesizers for adding +// waveforms to a Blip_Buffer. + +// Blip_Buffer 0.3.4. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef BLIP_SYNTH_H +#define BLIP_SYNTH_H + +#ifndef BLIP_BUFFER_H + #include "Blip_Buffer.h" +#endif + +// Quality level. Higher levels are slower, and worse in a few cases. +// Use blip_good_quality as a starting point. +const int blip_low_quality = 1; +const int blip_med_quality = 2; +const int blip_good_quality = 3; +const int blip_high_quality = 4; + +// Blip_Synth is a transition waveform synthesizer which adds band-limited +// offsets (transitions) into a Blip_Buffer. For a simpler interface, use +// Blip_Wave (below). +// +// Range specifies the greatest expected offset that will occur. For a +// waveform that goes between +amp and -amp, range should be amp * 2 (half +// that if it only goes between +amp and 0). When range is large, a higher +// accuracy scheme is used; to force this even when range is small, pass +// the negative of range (i.e. -range). +template +class Blip_Synth { + BOOST_STATIC_ASSERT( 1 <= quality && quality <= 5 ); + BOOST_STATIC_ASSERT( -32768 <= range && range <= 32767 ); + enum { + abs_range = (range < 0) ? -range : range, + fine_mode = (range > 512 || range < 0), + width = (quality < 5 ? quality * 4 : Blip_Buffer::widest_impulse_), + res = 1 << blip_res_bits_, + impulse_size = width / 2 * (fine_mode + 1), + base_impulses_size = width / 2 * (res / 2 + 1), + fine_bits = (fine_mode ? (abs_range <= 64 ? 2 : abs_range <= 128 ? 3 : + abs_range <= 256 ? 4 : abs_range <= 512 ? 5 : abs_range <= 1024 ? 6 : + abs_range <= 2048 ? 7 : 8) : 0) + }; + blip_pair_t_ impulses [impulse_size * res * 2 + base_impulses_size]; + Blip_Impulse_ impulse; + void init() { impulse.init( impulses, width, res, fine_bits ); } +public: + Blip_Synth() { init(); } + Blip_Synth( double volume ) { init(); this->volume( volume ); } + + // Configure low-pass filter (see notes.txt). Not optimized for real-time control + void treble_eq( const blip_eq_t& eq ) { impulse.treble_eq( eq ); } + + // Set volume of a transition at amplitude 'range' by setting volume_unit + // to v / range + void volume( double v ) { impulse.volume_unit( v * (1.0 / abs_range) ); } + + // Set base volume unit of transitions, where 1.0 is a full swing between the + // positive and negative extremes. Not optimized for real-time control. + void volume_unit( double unit ) { impulse.volume_unit( unit ); } + + // Default Blip_Buffer used for output when none is specified for a given call + Blip_Buffer* output() const { return impulse.buf; } + void output( Blip_Buffer* b ) { impulse.buf = b; } + + // Add an amplitude offset (transition) with a magnitude of delta * volume_unit + // into the specified buffer (default buffer if none specified) at the + // specified source time. Delta can be positive or negative. To increase + // performance by inlining code at the call site, use offset_inline(). + void offset( blip_time_t, int delta, Blip_Buffer* ) const; + + void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const; + void offset_resampled( blip_resampled_time_t t, int o ) const { + offset_resampled( t, o, impulse.buf ); + } + void offset( blip_time_t t, int delta ) const { + offset( t, delta, impulse.buf ); + } + void offset_inline( blip_time_t time, int delta, Blip_Buffer* buf ) const { + offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); + } + void offset_inline( blip_time_t time, int delta ) const { + offset_inline( time, delta, impulse.buf ); + } +}; + +// Blip_Wave is a synthesizer for adding a *single* waveform to a Blip_Buffer. +// A wave is built from a series of delays and new amplitudes. This provides a +// simpler interface than Blip_Synth, nothing more. +template +class Blip_Wave { + Blip_Synth synth; + blip_time_t time_; + int last_amp; + void init() { time_ = 0; last_amp = 0; } +public: + // Start wave at time 0 and amplitude 0 + Blip_Wave() { init(); } + Blip_Wave( double volume ) { init(); this->volume( volume ); } + + // See Blip_Synth for description + void volume( double v ) { synth.volume( v ); } + void volume_unit( double v ) { synth.volume_unit( v ); } + void treble_eq( const blip_eq_t& eq){ synth.treble_eq( eq ); } + Blip_Buffer* output() const { return synth.output(); } + void output( Blip_Buffer* b ) { synth.output( b ); if ( !b ) time_ = last_amp = 0; } + + // Current time in frame + blip_time_t time() const { return time_; } + void time( blip_time_t t ) { time_ = t; } + + // Current amplitude of wave + int amplitude() const { return last_amp; } + void amplitude( int ); + + // Move forward by 't' time units + void delay( blip_time_t t ) { time_ += t; } + + // End time frame of specified duration. Localize time to new frame. + // If wave hadn't been run to end of frame, start it at beginning of new frame. + void end_frame( blip_time_t duration ) + { + time_ -= duration; + if ( time_ < 0 ) + time_ = 0; + } +}; + +// End of public interface + +template +void Blip_Wave::amplitude( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + synth.offset_inline( time_, delta ); +} + +template +inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ + typedef blip_pair_t_ pair_t; + + unsigned sample_index = (time >> BLIP_BUFFER_ACCURACY) & ~1; + assert(( "Blip_Synth/Blip_wave: Went past end of buffer", + sample_index < blip_buf->buffer_size_ )); + enum { const_offset = Blip_Buffer::widest_impulse_ / 2 - width / 2 }; + pair_t* buf = (pair_t*) &blip_buf->buffer_ [const_offset + sample_index]; + + enum { shift = BLIP_BUFFER_ACCURACY - blip_res_bits_ }; + enum { mask = res * 2 - 1 }; + const pair_t* imp = &impulses [((time >> shift) & mask) * impulse_size]; + + pair_t offset = impulse.offset * delta; + + if ( !fine_bits ) + { + // normal mode + for ( int n = width / 4; n; --n ) + { + pair_t t0 = buf [0] - offset; + pair_t t1 = buf [1] - offset; + + t0 += imp [0] * delta; + t1 += imp [1] * delta; + imp += 2; + + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + } + else + { + // fine mode + enum { sub_range = 1 << fine_bits }; + delta += sub_range / 2; + int delta2 = (delta & (sub_range - 1)) - sub_range / 2; + delta >>= fine_bits; + + for ( int n = width / 4; n; --n ) + { + pair_t t0 = buf [0] - offset; + pair_t t1 = buf [1] - offset; + + t0 += imp [0] * delta2; + t0 += imp [1] * delta; + + t1 += imp [2] * delta2; + t1 += imp [3] * delta; + + imp += 4; + + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + } +} + +template +void Blip_Synth::offset( blip_time_t time, int delta, Blip_Buffer* buf ) const { + offset_resampled( time * buf->factor_ + buf->offset_, delta, buf ); +} + +#endif + diff --git a/quicknes/nes_emu/Effects_Buffer.cpp b/quicknes/nes_emu/Effects_Buffer.cpp new file mode 100644 index 0000000000..2ce9be0b47 --- /dev/null +++ b/quicknes/nes_emu/Effects_Buffer.cpp @@ -0,0 +1,518 @@ + +// Game_Music_Emu 0.3.0. http://www.slack.net/~ant/ + +#include "Effects_Buffer.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +typedef long fixed_t; + +#define TO_FIXED( f ) fixed_t ((f) * (1L << 15) + 0.5) +#define FMUL( x, y ) (((x) * (y)) >> 15) + +const unsigned echo_size = 4096; +const unsigned echo_mask = echo_size - 1; +BOOST_STATIC_ASSERT( (echo_size & echo_mask) == 0 ); // must be power of 2 + +const unsigned reverb_size = 8192 * 2; +const unsigned reverb_mask = reverb_size - 1; +BOOST_STATIC_ASSERT( (reverb_size & reverb_mask) == 0 ); // must be power of 2 + +Effects_Buffer::config_t::config_t() +{ + pan_1 = -0.15f; + pan_2 = 0.15f; + reverb_delay = 88.0f; + reverb_level = 0.12f; + echo_delay = 61.0f; + echo_level = 0.10f; + delay_variance = 18.0f; + effects_enabled = false; +} + +void Effects_Buffer::set_depth( double d ) +{ + float f = (float) d; + config_t c; + c.pan_1 = -0.6f * f; + c.pan_2 = 0.6f * f; + c.reverb_delay = 880 * 0.1f; + c.echo_delay = 610 * 0.1f; + if ( f > 0.5 ) + f = 0.5; // TODO: more linear reduction of extreme reverb/echo + c.reverb_level = 0.5f * f; + c.echo_level = 0.30f * f; + c.delay_variance = 180 * 0.1f; + c.effects_enabled = (d > 0.0f); + config( c ); +} + +Effects_Buffer::Effects_Buffer( bool center_only ) : Multi_Buffer( 2 ) +{ + buf_count = center_only ? max_buf_count - 4 : max_buf_count; + + echo_buf = NULL; + echo_pos = 0; + + reverb_buf = NULL; + reverb_pos = 0; + + stereo_remain = 0; + effect_remain = 0; + effects_enabled = false; + set_depth( 0 ); +} + +Effects_Buffer::~Effects_Buffer() +{ + delete [] echo_buf; + delete [] reverb_buf; +} + +blargg_err_t Effects_Buffer::set_sample_rate( long rate, int msec ) +{ + if ( !echo_buf ) + { + echo_buf = BLARGG_NEW blip_sample_t [echo_size]; + CHECK_ALLOC( echo_buf ); + } + + if ( !reverb_buf ) + { + reverb_buf = BLARGG_NEW blip_sample_t [reverb_size]; + CHECK_ALLOC( reverb_buf ); + } + + for ( int i = 0; i < buf_count; i++ ) + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + + config( config_ ); + clear(); + + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Effects_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Effects_Buffer::bass_freq( int freq ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( freq ); +} + +void Effects_Buffer::clear() +{ + stereo_remain = 0; + effect_remain = 0; + if ( echo_buf ) + memset( echo_buf, 0, echo_size * sizeof *echo_buf ); + if ( reverb_buf ) + memset( reverb_buf, 0, reverb_size * sizeof *reverb_buf ); + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +inline int pin_range( int n, int max, int min = 0 ) +{ + if ( n < min ) + return min; + if ( n > max ) + return max; + return n; +} + +void Effects_Buffer::config( const config_t& cfg ) +{ + channels_changed(); + + // clear echo and reverb buffers + if ( !config_.effects_enabled && cfg.effects_enabled && echo_buf ) + { + memset( echo_buf, 0, echo_size * sizeof (blip_sample_t) ); + memset( reverb_buf, 0, reverb_size * sizeof (blip_sample_t) ); + } + + config_ = cfg; + + if ( config_.effects_enabled ) + { + // convert to internal format + + chans.pan_1_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_1 ); + chans.pan_1_levels [1] = TO_FIXED( 2 ) - chans.pan_1_levels [0]; + + chans.pan_2_levels [0] = TO_FIXED( 1 ) - TO_FIXED( config_.pan_2 ); + chans.pan_2_levels [1] = TO_FIXED( 2 ) - chans.pan_2_levels [0]; + + chans.reverb_level = TO_FIXED( config_.reverb_level ); + chans.echo_level = TO_FIXED( config_.echo_level ); + + int delay_offset = int (1.0 / 2000 * config_.delay_variance * sample_rate()); + + int reverb_sample_delay = int (1.0 / 1000 * config_.reverb_delay * sample_rate()); + chans.reverb_delay_l = pin_range( reverb_size - + (reverb_sample_delay - delay_offset) * 2, reverb_size - 2, 0 ); + chans.reverb_delay_r = pin_range( reverb_size + 1 - + (reverb_sample_delay + delay_offset) * 2, reverb_size - 1, 1 ); + + int echo_sample_delay = int (1.0 / 1000 * config_.echo_delay * sample_rate()); + chans.echo_delay_l = pin_range( echo_size - 1 - (echo_sample_delay - delay_offset), + echo_size - 1 ); + chans.echo_delay_r = pin_range( echo_size - 1 - (echo_sample_delay + delay_offset), + echo_size - 1 ); + + // set up outputs + for ( unsigned i = 0; i < chan_count; i++ ) + { + channel_t& o = channels [i]; + if ( i < 2 ) + { + o.center = &bufs [i]; + o.left = &bufs [3]; + o.right = &bufs [4]; + } + else + { + o.center = &bufs [2]; + o.left = &bufs [5]; + o.right = &bufs [6]; + } + } + + } + else + { + // set up outputs + for ( unsigned i = 0; i < chan_count; i++ ) + { + channel_t& o = channels [i]; + o.center = &bufs [0]; + o.left = &bufs [1]; + o.right = &bufs [2]; + } + } + + if ( buf_count < max_buf_count ) + { + for ( unsigned i = 0; i < chan_count; i++ ) + { + channel_t& o = channels [i]; + o.left = o.center; + o.right = o.center; + } + } +} + +void Effects_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].end_frame( clock_count ); + + if ( stereo && buf_count == max_buf_count ) + stereo_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + if ( effects_enabled || config_.effects_enabled ) + effect_remain = bufs [0].samples_avail() + bufs [0].output_latency(); + + effects_enabled = config_.effects_enabled; +} + +long Effects_Buffer::samples_avail() const +{ + return bufs [0].samples_avail() * 2; +} + +long Effects_Buffer::read_samples( blip_sample_t* out, long total_samples ) +{ + require( total_samples % 2 == 0 ); // count must be even + + long remain = bufs [0].samples_avail(); + if ( remain > (total_samples >> 1) ) + remain = (total_samples >> 1); + total_samples = remain; + while ( remain ) + { + int active_bufs = buf_count; + long count = remain; + + // optimizing mixing to skip any channels which had nothing added + if ( effect_remain ) + { + if ( count > effect_remain ) + count = effect_remain; + + if ( stereo_remain ) + { + mix_enhanced( out, count ); + } + else + { + mix_mono_enhanced( out, count ); + active_bufs = 3; + } + } + else if ( stereo_remain ) + { + mix_stereo( out, count ); + active_bufs = 3; + } + else + { + mix_mono( out, count ); + active_bufs = 1; + } + + out += count * 2; + remain -= count; + + stereo_remain -= count; + if ( stereo_remain < 0 ) + stereo_remain = 0; + + effect_remain -= count; + if ( effect_remain < 0 ) + effect_remain = 0; + + for ( int i = 0; i < buf_count; i++ ) + { + if ( i < active_bufs ) + bufs [i].remove_samples( count ); + else + bufs [i].remove_silence( count ); // keep time synchronized + } + } + + return total_samples * 2; +} + +void Effects_Buffer::mix_mono( blip_sample_t* out, long count ) +{ + Blip_Reader c; + int shift = c.begin( bufs [0] ); + + // unrolled loop + for ( long n = count >> 1; n--; ) + { + long cs0 = c.read(); + c.next( shift ); + + long cs1 = c.read(); + c.next( shift ); + + if ( (BOOST::int16_t) cs0 != cs0 ) + cs0 = 0x7FFF - (cs0 >> 24); + ((BOOST::uint32_t*) out) [0] = ((BOOST::uint16_t) cs0) | (cs0 << 16); + + if ( (BOOST::int16_t) cs1 != cs1 ) + cs1 = 0x7FFF - (cs1 >> 24); + ((BOOST::uint32_t*) out) [1] = ((BOOST::uint16_t) cs1) | (cs1 << 16); + out += 4; + } + + if ( count & 1 ) + { + int s = c.read(); + c.next( shift ); + out [0] = s; + out [1] = s; + if ( (BOOST::int16_t) s != s ) + { + s = 0x7FFF - (s >> 24); + out [0] = s; + out [1] = s; + } + } + + c.end( bufs [0] ); +} + +void Effects_Buffer::mix_stereo( blip_sample_t* out, long count ) +{ + Blip_Reader l; l.begin( bufs [1] ); + Blip_Reader r; r.begin( bufs [2] ); + Blip_Reader c; + int shift = c.begin( bufs [0] ); + + while ( count-- ) + { + int cs = c.read(); + c.next( shift ); + int left = cs + l.read(); + int right = cs + r.read(); + l.next( shift ); + r.next( shift ); + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + + c.end( bufs [0] ); + r.end( bufs [2] ); + l.end( bufs [1] ); +} + +void Effects_Buffer::mix_mono_enhanced( blip_sample_t* out, long count ) +{ + Blip_Reader sq1; sq1.begin( bufs [0] ); + Blip_Reader sq2; sq2.begin( bufs [1] ); + Blip_Reader center; + int shift = center.begin( bufs [2] ); + + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = sq1.read(); + int sum2_s = sq2.read(); + + sq1.next( shift ); + sq2.next( shift ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = center.read(); + center.next( shift ); + + int left = new_reverb_l + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + sq1.end( bufs [0] ); + sq2.end( bufs [1] ); + center.end( bufs [2] ); +} + +void Effects_Buffer::mix_enhanced( blip_sample_t* out, long count ) +{ + Blip_Reader l1; l1.begin( bufs [3] ); + Blip_Reader r1; r1.begin( bufs [4] ); + Blip_Reader l2; l2.begin( bufs [5] ); + Blip_Reader r2; r2.begin( bufs [6] ); + Blip_Reader sq1; sq1.begin( bufs [0] ); + Blip_Reader sq2; sq2.begin( bufs [1] ); + Blip_Reader center; + int shift = center.begin( bufs [2] ); + + int echo_pos = this->echo_pos; + int reverb_pos = this->reverb_pos; + + while ( count-- ) + { + int sum1_s = sq1.read(); + int sum2_s = sq2.read(); + + sq1.next( shift ); + sq2.next( shift ); + + int new_reverb_l = FMUL( sum1_s, chans.pan_1_levels [0] ) + + FMUL( sum2_s, chans.pan_2_levels [0] ) + l1.read() + + reverb_buf [(reverb_pos + chans.reverb_delay_l) & reverb_mask]; + + int new_reverb_r = FMUL( sum1_s, chans.pan_1_levels [1] ) + + FMUL( sum2_s, chans.pan_2_levels [1] ) + r1.read() + + reverb_buf [(reverb_pos + chans.reverb_delay_r) & reverb_mask]; + + l1.next( shift ); + r1.next( shift ); + + fixed_t reverb_level = chans.reverb_level; + reverb_buf [reverb_pos] = FMUL( new_reverb_l, reverb_level ); + reverb_buf [reverb_pos + 1] = FMUL( new_reverb_r, reverb_level ); + reverb_pos = (reverb_pos + 2) & reverb_mask; + + int sum3_s = center.read(); + center.next( shift ); + + int left = new_reverb_l + sum3_s + l2.read() + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_l) & echo_mask] ); + int right = new_reverb_r + sum3_s + r2.read() + FMUL( chans.echo_level, + echo_buf [(echo_pos + chans.echo_delay_r) & echo_mask] ); + + l2.next( shift ); + r2.next( shift ); + + echo_buf [echo_pos] = sum3_s; + echo_pos = (echo_pos + 1) & echo_mask; + + if ( (BOOST::int16_t) left != left ) + left = 0x7FFF - (left >> 24); + + out [0] = left; + out [1] = right; + + out += 2; + + if ( (BOOST::int16_t) right != right ) + out [-1] = 0x7FFF - (right >> 24); + } + this->reverb_pos = reverb_pos; + this->echo_pos = echo_pos; + + sq1.end( bufs [0] ); + sq2.end( bufs [1] ); + center.end( bufs [2] ); + l1.end( bufs [3] ); + r1.end( bufs [4] ); + l2.end( bufs [5] ); + r2.end( bufs [6] ); +} + diff --git a/quicknes/nes_emu/Effects_Buffer.h b/quicknes/nes_emu/Effects_Buffer.h new file mode 100644 index 0000000000..d16085c8a6 --- /dev/null +++ b/quicknes/nes_emu/Effects_Buffer.h @@ -0,0 +1,93 @@ + +// Multi-channel effects buffer with panning, echo and reverb + +// Game_Music_Emu 0.3.0 + +#ifndef EFFECTS_BUFFER_H +#define EFFECTS_BUFFER_H + +#include "Multi_Buffer.h" + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Effects_Buffer : public Multi_Buffer { +public: + // If center_only is true, only center buffers are created and + // less memory is used. + Effects_Buffer( bool center_only = false ); + + // Channel Effect Center Pan + // --------------------------------- + // 0,5 reverb pan_1 + // 1,6 reverb pan_2 + // 2,7 echo - + // 3 echo - + // 4 echo - + + // Channel configuration + struct config_t { + double pan_1; // -1.0 = left, 0.0 = center, 1.0 = right + double pan_2; + double echo_delay; // msec + double echo_level; // 0.0 to 1.0 + double reverb_delay; // msec + double delay_variance; // difference between left/right delays (msec) + double reverb_level; // 0.0 to 1.0 + bool effects_enabled; // if false, use optimized simple mixer + config_t(); + }; + + // Set configuration of buffer + virtual void config( const config_t& ); + void set_depth( double ); + +public: + ~Effects_Buffer(); + blargg_err_t set_sample_rate( long samples_per_sec, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool was_stereo = true ); + long read_samples( blip_sample_t*, long ); + long samples_avail() const; +private: + typedef long fixed_t; + + enum { max_buf_count = 7 }; + Blip_Buffer bufs [max_buf_count]; + enum { chan_count = 5 }; + channel_t channels [chan_count]; + config_t config_; + long stereo_remain; + long effect_remain; + int buf_count; + bool effects_enabled; + + blip_sample_t* reverb_buf; + blip_sample_t* echo_buf; + int reverb_pos; + int echo_pos; + + struct { + fixed_t pan_1_levels [2]; + fixed_t pan_2_levels [2]; + int echo_delay_l; + int echo_delay_r; + fixed_t echo_level; + int reverb_delay_l; + int reverb_delay_r; + fixed_t reverb_level; + } chans; + + void mix_mono( blip_sample_t*, long ); + void mix_stereo( blip_sample_t*, long ); + void mix_enhanced( blip_sample_t*, long ); + void mix_mono_enhanced( blip_sample_t*, long ); +}; + + inline Effects_Buffer::channel_t Effects_Buffer::channel( int i ) { + return channels [i % chan_count]; + } + +#endif + diff --git a/quicknes/nes_emu/Mapper_Fme07.cpp b/quicknes/nes_emu/Mapper_Fme07.cpp new file mode 100644 index 0000000000..d88f31d378 --- /dev/null +++ b/quicknes/nes_emu/Mapper_Fme07.cpp @@ -0,0 +1,215 @@ + +// Sunsoft FME-07 mapper + +// Nes_Emu 0.5.6. http://www.slack.net/~ant/libs/ + +#include "Nes_Mapper.h" + +#include "blargg_endian.h" +#include "Nes_Fme07_Apu.h" + +/* Copyright (C) 2005 Chris Moeller */ +/* Copyright (C) 2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +struct fme07_state_t +{ + // first 16 bytes in register order + BOOST::uint8_t regs [13]; + BOOST::uint8_t irq_mode; + BOOST::uint16_t irq_count; + + BOOST::uint8_t command; + BOOST::uint8_t irq_pending; + fme07_snapshot_t sound_state; // only used when saving/restoring state + + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (fme07_state_t) == 18 + sizeof (fme07_snapshot_t) ); + +void fme07_state_t::swap() +{ + set_le16( &irq_count, irq_count ); + for ( int i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ ) + set_le16( &sound_state.delays [i], sound_state.delays [i] ); +} + +class Mapper_Fme07 : public Nes_Mapper, fme07_state_t { + nes_time_t last_time; + Nes_Fme07_Apu sound; +public: + Mapper_Fme07() + { + fme07_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual int channel_count() const { return sound.osc_count; } + + virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + + virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + + virtual void reset_state() + { + regs [8] = 0x40; // wram disabled + irq_count = 0xFFFF; + sound.reset(); + } + + virtual void save_state( mapper_state_t& out ) + { + sound.save_snapshot( &sound_state ); + fme07_state_t::swap(); + Nes_Mapper::save_state( out ); + fme07_state_t::swap(); // to do: kind of hacky to swap in place + } + + virtual void read_state( mapper_state_t const& in ) + { + Nes_Mapper::read_state( in ); + fme07_state_t::swap(); + sound.load_snapshot( sound_state ); + } + + void write_register( int index, int data ); + + virtual void apply_mapping() + { + last_time = 0; + for ( int i = 0; i < sizeof regs; i++ ) + write_register( i, regs [i] ); + } + + virtual void run_until( nes_time_t end_time ) + { + int new_count = irq_count - (end_time - last_time); + last_time = end_time; + + if ( new_count <= 0 && (irq_mode & 0x81) == 0x81 ) + irq_pending = true; + + if ( irq_mode & 0x01 ) + irq_count = new_count & 0xFFFF; + } + + virtual nes_time_t next_irq( nes_time_t present ) + { + if ( irq_pending ) + return 0; + + if ( (irq_mode & 0x81) == 0x81 ) + return last_time + irq_count + 1; + + return no_irq; + } + + virtual void end_frame( nes_time_t end_time ) + { + if ( end_time > last_time ) + run_until( end_time ); + + last_time -= end_time; + assert( last_time >= 0 ); + + sound.end_frame( end_time ); + } + + void write_irq( nes_time_t, int index, int data ); + + virtual void write( nes_time_t time, nes_addr_t addr, int data ) + { + switch ( addr & 0xE000 ) + { + case 0x8000: + command = data & 0x0F; + break; + + case 0xA000: + if ( command < 0x0D ) + write_register( command, data ); + else + write_irq( time, command, data ); + break; + + case 0xC000: + sound.write_latch( data ); + break; + + case 0xE000: + sound.write_data( time, data ); + break; + } + } +}; + +void Mapper_Fme07::write_irq( nes_time_t time, int index, int data ) +{ + run_until( time ); + switch ( index ) + { + case 0x0D: + irq_mode = data; + if ( (irq_mode & 0x81) != 0x81 ) + irq_pending = false; + break; + + case 0x0E: + irq_count = (irq_count & 0xFF00) | data; + break; + + case 0x0F: + irq_count = data << 8 | (irq_count & 0xFF); + break; + } + + if ( (irq_mode & 0x81) == 0x81 ) + irq_changed(); +} + +void Mapper_Fme07::write_register( int index, int data ) +{ + regs [index] = data; + int prg_bank = index - 0x09; + if ( (unsigned) prg_bank < 3 ) // most common + { + set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data ); + } + else if ( index == 0x08 ) + { + enable_sram( (data & 0xC0) == 0xC0 ); + if ( !(data & 0xC0) ) + set_prg_bank( 0x6000, bank_8k, data & 0x3F ); + } + else if ( index < 0x08 ) + { + set_chr_bank( index * 0x400, bank_1k, data ); + } + else + { + assert( index == 0x0C ); + if ( data & 2 ) + mirror_single( data & 1 ); + else if ( data & 1 ) + mirror_horiz(); + else + mirror_vert(); + } +} + +void register_fme07_mapper(); +void register_fme07_mapper() +{ + register_mapper( 69 ); +} + diff --git a/quicknes/nes_emu/Mapper_Fme7.cpp b/quicknes/nes_emu/Mapper_Fme7.cpp new file mode 100644 index 0000000000..47e54e892e --- /dev/null +++ b/quicknes/nes_emu/Mapper_Fme7.cpp @@ -0,0 +1,215 @@ + +// Sunsoft FME-7 mapper + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ + +#include "Nes_Mapper.h" + +#include "blargg_endian.h" +#include "Nes_Fme7_Apu.h" + +/* Copyright (C) 2005 Chris Moeller */ +/* Copyright (C) 2005-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +struct fme7_state_t +{ + // first 16 bytes in register order + BOOST::uint8_t regs [13]; + BOOST::uint8_t irq_mode; + BOOST::uint16_t irq_count; + + BOOST::uint8_t command; + BOOST::uint8_t irq_pending; + fme7_apu_state_t sound_state; // only used when saving/restoring state + + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (fme7_state_t) == 18 + sizeof (fme7_apu_state_t) ); + +void fme7_state_t::swap() +{ + set_le16( &irq_count, irq_count ); + for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ ) + set_le16( &sound_state.delays [i], sound_state.delays [i] ); +} + +class Mapper_Fme7 : public Nes_Mapper, fme7_state_t { + nes_time_t last_time; + Nes_Fme7_Apu sound; +public: + Mapper_Fme7() + { + fme7_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual int channel_count() const { return sound.osc_count; } + + virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + + virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + + virtual void reset_state() + { + regs [8] = 0x40; // wram disabled + irq_count = 0xFFFF; + sound.reset(); + } + + virtual void save_state( mapper_state_t& out ) + { + sound.save_state( &sound_state ); + fme7_state_t::swap(); + Nes_Mapper::save_state( out ); + fme7_state_t::swap(); // to do: kind of hacky to swap in place + } + + virtual void read_state( mapper_state_t const& in ) + { + Nes_Mapper::read_state( in ); + fme7_state_t::swap(); + sound.load_state( sound_state ); + } + + void write_register( int index, int data ); + + virtual void apply_mapping() + { + last_time = 0; + for ( int i = 0; i < (int) sizeof regs; i++ ) + write_register( i, regs [i] ); + } + + virtual void run_until( nes_time_t end_time ) + { + int new_count = irq_count - (end_time - last_time); + last_time = end_time; + + if ( new_count <= 0 && (irq_mode & 0x81) == 0x81 ) + irq_pending = true; + + if ( irq_mode & 0x01 ) + irq_count = new_count & 0xFFFF; + } + + virtual nes_time_t next_irq( nes_time_t ) + { + if ( irq_pending ) + return 0; + + if ( (irq_mode & 0x81) == 0x81 ) + return last_time + irq_count + 1; + + return no_irq; + } + + virtual void end_frame( nes_time_t end_time ) + { + if ( end_time > last_time ) + run_until( end_time ); + + last_time -= end_time; + assert( last_time >= 0 ); + + sound.end_frame( end_time ); + } + + void write_irq( nes_time_t, int index, int data ); + + virtual void write( nes_time_t time, nes_addr_t addr, int data ) + { + switch ( addr & 0xE000 ) + { + case 0x8000: + command = data & 0x0F; + break; + + case 0xA000: + if ( command < 0x0D ) + write_register( command, data ); + else + write_irq( time, command, data ); + break; + + case 0xC000: + sound.write_latch( data ); + break; + + case 0xE000: + sound.write_data( time, data ); + break; + } + } +}; + +void Mapper_Fme7::write_irq( nes_time_t time, int index, int data ) +{ + run_until( time ); + switch ( index ) + { + case 0x0D: + irq_mode = data; + if ( (irq_mode & 0x81) != 0x81 ) + irq_pending = false; + break; + + case 0x0E: + irq_count = (irq_count & 0xFF00) | data; + break; + + case 0x0F: + irq_count = data << 8 | (irq_count & 0xFF); + break; + } + + if ( (irq_mode & 0x81) == 0x81 ) + irq_changed(); +} + +void Mapper_Fme7::write_register( int index, int data ) +{ + regs [index] = data; + int prg_bank = index - 0x09; + if ( (unsigned) prg_bank < 3 ) // most common + { + set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data ); + } + else if ( index == 0x08 ) + { + enable_sram( (data & 0xC0) == 0xC0 ); + if ( !(data & 0xC0) ) + set_prg_bank( 0x6000, bank_8k, data & 0x3F ); + } + else if ( index < 0x08 ) + { + set_chr_bank( index * 0x400, bank_1k, data ); + } + else + { + assert( index == 0x0C ); + if ( data & 2 ) + mirror_single( data & 1 ); + else if ( data & 1 ) + mirror_horiz(); + else + mirror_vert(); + } +} + +void register_fme7_mapper(); +void register_fme7_mapper() +{ + register_mapper( 69 ); +} + diff --git a/quicknes/nes_emu/Mapper_Mmc5.cpp b/quicknes/nes_emu/Mapper_Mmc5.cpp new file mode 100644 index 0000000000..ca610aeca1 --- /dev/null +++ b/quicknes/nes_emu/Mapper_Mmc5.cpp @@ -0,0 +1,155 @@ + +// NES MMC5 mapper, currently only tailored for Castlevania 3 (U) + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include "Nes_Core.h" +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +struct mmc5_state_t +{ + enum { reg_count = 0x30 }; + byte regs [0x30]; + byte irq_enabled; +}; +// to do: finalize state format +BOOST_STATIC_ASSERT( sizeof (mmc5_state_t) == 0x31 ); + +class Mapper_Mmc5 : public Nes_Mapper, mmc5_state_t { + nes_time_t irq_time; +public: + Mapper_Mmc5() + { + mmc5_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { + irq_time = no_irq; + regs [0x00] = 2; + regs [0x01] = 3; + regs [0x14] = 0x7f; + regs [0x15] = 0x7f; + regs [0x16] = 0x7f; + regs [0x17] = 0x7f; + } + + virtual void read_state( mapper_state_t const& in ) + { + Nes_Mapper::read_state( in ); + irq_time = no_irq; + } + + enum { regs_addr = 0x5100 }; + + virtual void apply_mapping(); + + virtual nes_time_t next_irq( nes_time_t ) + { + if ( irq_enabled & 0x80 ) + return irq_time; + + return no_irq; + } + + virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data ) + { + int reg = addr - regs_addr; + if ( (unsigned) reg < reg_count ) + { + regs [reg] = data; + switch ( reg ) + { + case 0x05: + mirror_manual( data & 3, data >> 2 & 3, + data >> 4 & 3, data >> 6 & 3 ); + break; + + case 0x15: + set_prg_bank( 0x8000, bank_16k, data >> 1 & 0x3f ); + break; + + case 0x16: + set_prg_bank( 0xC000, bank_8k, data & 0x7f ); + break; + + case 0x17: + set_prg_bank( 0xE000, bank_8k, data & 0x7f ); + break; + + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + set_chr_bank( ((reg >> 1 & 4) + (reg & 3)) * 0x400, bank_1k, data ); + break; + } + check( (regs [0x00] & 3) == 2 ); + check( (regs [0x01] & 3) == 3 ); + } + else if ( addr == 0x5203 ) + { + irq_time = no_irq; + if ( data && data < 240 ) + { + irq_time = (341 * 21 + 128 + (data * 341)) / 3; + if ( irq_time < time ) + irq_time = no_irq; + } + irq_changed(); + } + else if ( addr == 0x5204 ) + { + irq_enabled = data; + irq_changed(); + } + else + { + return false; + } + + return true; + } + + virtual void write( nes_time_t, nes_addr_t, int ) { } +}; + +void Mapper_Mmc5::apply_mapping() +{ + static unsigned char list [] = { + 0x05, 0x15, 0x16, 0x17, + 0x20, 0x21, 0x22, 0x23, + 0x28, 0x29, 0x2a, 0x2b + }; + + for ( int i = 0; i < (int) sizeof list; i++ ) + write_intercepted( 0, regs_addr + list [i], regs [list [i]] ); + intercept_writes( 0x5100, 0x200 ); +} + +void register_mmc5_mapper(); +void register_mmc5_mapper() +{ + register_mapper( 5 ); +} + diff --git a/quicknes/nes_emu/Mapper_Namco106.cpp b/quicknes/nes_emu/Mapper_Namco106.cpp new file mode 100644 index 0000000000..5039e54539 --- /dev/null +++ b/quicknes/nes_emu/Mapper_Namco106.cpp @@ -0,0 +1,213 @@ + +// Namco 106 mapper + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include "Nes_Namco_Apu.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// to do: CHR mapping and nametable handling needs work + +struct namco106_state_t +{ + BOOST::uint8_t regs [16]; + BOOST::uint16_t irq_ctr; + BOOST::uint8_t irq_pending; + BOOST::uint8_t unused1 [1]; +}; +BOOST_STATIC_ASSERT( sizeof (namco106_state_t) == 20 ); + +class Mapper_Namco106 : public Nes_Mapper, namco106_state_t { + Nes_Namco_Apu sound; + nes_time_t last_time; +public: + Mapper_Namco106() + { + namco106_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual int channel_count() const { return sound.osc_count; } + + virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + + virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + + void reset_state() + { + regs [12] = 0; + regs [13] = 1; + regs [14] = last_bank - 1; + sound.reset(); + } + + virtual void apply_mapping() + { + last_time = 0; + enable_sram(); + intercept_writes( 0x4800, 1 ); + intercept_reads ( 0x4800, 1 ); + + intercept_writes( 0x5000, 0x1000 ); + intercept_reads ( 0x5000, 0x1000 ); + + for ( int i = 0; i < (int) sizeof regs; i++ ) + write( 0, 0x8000 + i * 0x800, regs [i] ); + } + + virtual nes_time_t next_irq( nes_time_t time ) + { + if ( irq_pending ) + return time; + + if ( !(irq_ctr & 0x8000) ) + return no_irq; + + return 0x10000 - irq_ctr + last_time; + } + + virtual void run_until( nes_time_t end_time ) + { + long count = irq_ctr + (end_time - last_time); + if ( irq_ctr & 0x8000 ) + { + if ( count > 0xffff ) + { + count = 0xffff; + irq_pending = true; + } + } + else if ( count > 0x7fff ) + { + count = 0x7fff; + } + + irq_ctr = count; + last_time = end_time; + } + + virtual void end_frame( nes_time_t end_time ) + { + if ( end_time > last_time ) + run_until( end_time ); + last_time -= end_time; + assert( last_time >= 0 ); + sound.end_frame( end_time ); + } + + void write_bank( nes_addr_t, int data ); + void write_irq( nes_time_t, nes_addr_t, int data ); + + virtual int read( nes_time_t time, nes_addr_t addr ) + { + if ( addr == 0x4800 ) + return sound.read_data(); + + if ( addr == 0x5000 ) + { + irq_pending = false; + return irq_ctr & 0xff; + } + + if ( addr == 0x5800 ) + { + irq_pending = false; + return irq_ctr >> 8; + } + + return Nes_Mapper::read( time, addr ); + } + + virtual bool write_intercepted( nes_time_t time, nes_addr_t addr, int data ) + { + if ( addr == 0x4800 ) + { + sound.write_data( time, data ); + } + else if ( addr == 0x5000 ) + { + irq_ctr = (irq_ctr & 0xff00) | data; + irq_pending = false; + irq_changed(); + } + else if ( addr == 0x5800 ) + { + irq_ctr = (data << 8) | (irq_ctr & 0xff); + irq_pending = false; + irq_changed(); + } + else + { + return false; + } + + return true; + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + int reg = addr >> 11 & 0x0F; + regs [reg] = data; + + int prg_bank = reg - 0x0c; + if ( (unsigned) prg_bank < 3 ) + { + if ( prg_bank == 0 && (data & 0x40) ) + mirror_vert(); + set_prg_bank( 0x8000 | (prg_bank << bank_8k), bank_8k, data & 0x3F ); + } + else if ( reg < 8 ) + { + set_chr_bank( reg * 0x400, bank_1k, data ); + } + else if ( reg < 0x0c ) + { + mirror_manual( regs [8] & 1, regs [9] & 1, regs [10] & 1, regs [11] & 1 ); + } + else + { + sound.write_addr( data ); + } + } +}; + +void register_namco106_mapper(); +void register_namco106_mapper() +{ + register_mapper( 19 ); +} + +// in the most obscure place in case crappy linker is used +void register_optional_mappers(); +void register_optional_mappers() +{ + extern void register_misc_mappers(); + register_misc_mappers(); + + extern void register_vrc6_mapper(); + register_vrc6_mapper(); + + extern void register_mmc5_mapper(); + register_mmc5_mapper(); + + extern void register_fme7_mapper(); + register_fme7_mapper(); + + extern void register_namco106_mapper(); + register_namco106_mapper(); +} + diff --git a/quicknes/nes_emu/Mapper_Vrc6.cpp b/quicknes/nes_emu/Mapper_Vrc6.cpp new file mode 100644 index 0000000000..9cb2672c1c --- /dev/null +++ b/quicknes/nes_emu/Mapper_Vrc6.cpp @@ -0,0 +1,262 @@ + +// Konami VRC6 mapper + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include +#include "Nes_Vrc6_Apu.h" +#include "blargg_endian.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +struct vrc6_state_t +{ + // written registers + byte prg_16k_bank; + // could move sound regs int and out of vrc6_apu_state_t for state saving, + // allowing them to be stored here + byte old_sound_regs [3] [3]; // to do: eliminate this duplicate + byte mirroring; + byte prg_8k_bank; + byte chr_banks [8]; + byte irq_reload; + byte irq_mode; + + // internal state + BOOST::uint16_t next_time; + byte irq_pending; + byte unused; + + vrc6_apu_state_t sound_state; + + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (vrc6_state_t) == 26 + sizeof (vrc6_apu_state_t) ); + +void vrc6_state_t::swap() +{ + set_le16( &next_time, next_time ); + for ( unsigned i = 0; i < sizeof sound_state.delays / sizeof sound_state.delays [0]; i++ ) + set_le16( &sound_state.delays [i], sound_state.delays [i] ); +} + +class Mapper_Vrc6 : public Nes_Mapper, vrc6_state_t { + int swap_mask; + Nes_Vrc6_Apu sound; + enum { timer_period = 113 * 4 + 3 }; +public: + Mapper_Vrc6( int sm ) + { + swap_mask = sm; + vrc6_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual int channel_count() const { return sound.osc_count; } + + virtual void set_channel_buf( int i, Blip_Buffer* b ) { sound.osc_output( i, b ); } + + virtual void set_treble( blip_eq_t const& eq ) { sound.treble_eq( eq ); } + + virtual void reset_state() + { + prg_8k_bank = last_bank - 1; + sound.reset(); + } + + virtual void save_state( mapper_state_t& out ) + { + sound.save_state( &sound_state ); + vrc6_state_t::swap(); + Nes_Mapper::save_state( out ); + vrc6_state_t::swap(); // to do: kind of hacky to swap in place + } + + virtual void read_state( mapper_state_t const& in ); + + virtual void apply_mapping() + { + enable_sram(); + set_prg_bank( 0x8000, bank_16k, prg_16k_bank ); + set_prg_bank( 0xC000, bank_8k, prg_8k_bank ); + + for ( int i = 0; i < (int) sizeof chr_banks; i++ ) + set_chr_bank( i * 0x400, bank_1k, chr_banks [i] ); + + write_bank( 0xb003, mirroring ); + } + + void reset_timer( nes_time_t present ) + { + next_time = present + unsigned ((0x100 - irq_reload) * timer_period) / 4; + } + + virtual void run_until( nes_time_t end_time ) + { + if ( irq_mode & 2 ) + { + while ( next_time < end_time ) + { + //dprintf( "%d timer expired\n", next_time ); + irq_pending = true; + reset_timer( next_time ); + } + } + } + + virtual void end_frame( nes_time_t end_time ) + { + run_until( end_time ); + + // to do: next_time might go negative if IRQ is disabled + next_time -= end_time; + + sound.end_frame( end_time ); + } + + virtual nes_time_t next_irq( nes_time_t present ) + { + if ( irq_pending ) + return present; + + if ( irq_mode & 2 ) + return next_time + 1; + + return no_irq; + } + + void write_bank( nes_addr_t, int data ); + void write_irq( nes_time_t, nes_addr_t, int data ); + + virtual void write( nes_time_t time, nes_addr_t addr, int data ) + { + int osc = unsigned (addr - sound.base_addr) / sound.addr_step; + + if ( (addr + 1) & 2 ) // optionally swap 1 and 2 + addr ^= swap_mask; + + int reg = addr & 3; + if ( (unsigned) osc < sound.osc_count && reg < sound.reg_count ) + sound.write_osc( time, osc, reg, data ); + else if ( addr < 0xf000 ) + write_bank( addr, data ); + else + write_irq( time, addr, data ); + } +}; + +void Mapper_Vrc6::read_state( mapper_state_t const& in ) +{ + Nes_Mapper::read_state( in ); + vrc6_state_t::swap(); + + // to do: eliminate when format is updated + // old-style registers + static char zero [sizeof old_sound_regs] = { 0 }; + if ( 0 != memcmp( old_sound_regs, zero, sizeof zero ) ) + { + dprintf( "Using old VRC6 sound register format\n" ); + memcpy( sound_state.regs, old_sound_regs, sizeof sound_state.regs ); + memset( old_sound_regs, 0, sizeof old_sound_regs ); + } + + sound.load_state( sound_state ); +} + +void Mapper_Vrc6::write_irq( nes_time_t time, nes_addr_t addr, int data ) +{ + // IRQ + run_until( time ); + //dprintf( "%d VRC6 IRQ [%d] = %02X\n", time, addr & 3, data ); + switch ( addr & 3 ) + { + case 0: + irq_reload = data; + break; + + case 1: + irq_pending = false; + irq_mode = data; + if ( data & 2 ) + reset_timer( time ); + break; + + case 2: + irq_pending = false; + irq_mode = (irq_mode & ~2) | ((irq_mode << 1) & 2); + break; + } + irq_changed(); +} + +void Mapper_Vrc6::write_bank( nes_addr_t addr, int data ) +{ + switch ( addr & 0xf003 ) + { + case 0x8000: + prg_16k_bank = data; + set_prg_bank( 0x8000, bank_16k, data ); + break; + + case 0xb003: { + mirroring = data; + + //dprintf( "Change mirroring %d\n", data ); +// emu()->enable_sram( data & 0x80 ); // to do: needed? + int page = data >> 5 & 1; + if ( data & 8 ) + mirror_single( ((data >> 2) ^ page) & 1 ); + else if ( data & 4 ) + mirror_horiz( page ); + else + mirror_vert( page ); + break; + } + + case 0xc000: + prg_8k_bank = data; + set_prg_bank( 0xC000, bank_8k, data ); + break; + + default: + int bank = (addr >> 11 & 4) | (addr & 3); + if ( addr >= 0xd000 ) + { + //dprintf( "change chr bank %d\n", bank ); + chr_banks [bank] = data; + set_chr_bank( bank * 0x400, bank_1k, data ); + } + break; + } +} + +static Nes_Mapper* make_vrc6a() +{ + return BLARGG_NEW Mapper_Vrc6( 0 ); +} + +static Nes_Mapper* make_vrc6b() +{ + return BLARGG_NEW Mapper_Vrc6( 3 ); +} + +void register_vrc6_mapper(); +void register_vrc6_mapper() +{ + Nes_Mapper::register_mapper( 24, make_vrc6a ); + Nes_Mapper::register_mapper( 26, make_vrc6b ); +} + diff --git a/quicknes/nes_emu/Multi_Buffer.cpp b/quicknes/nes_emu/Multi_Buffer.cpp new file mode 100644 index 0000000000..f6cf2057d9 --- /dev/null +++ b/quicknes/nes_emu/Multi_Buffer.cpp @@ -0,0 +1,213 @@ + +// Blip_Buffer 0.4.0. http://www.slack.net/~ant/ + +#include "Multi_Buffer.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf ) +{ + length_ = 0; + sample_rate_ = 0; + channels_changed_count_ = 1; +} + +blargg_err_t Multi_Buffer::set_channel_count( int ) +{ + return 0; +} + +Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 ) +{ +} + +Mono_Buffer::~Mono_Buffer() +{ +} + +blargg_err_t Mono_Buffer::set_sample_rate( long rate, int msec ) +{ + RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); +} + +// Silent_Buffer + +Silent_Buffer::Silent_Buffer() : Multi_Buffer( 1 ) // 0 channels would probably confuse +{ + chan.left = NULL; + chan.center = NULL; + chan.right = NULL; +} + +// Mono_Buffer + +Mono_Buffer::channel_t Mono_Buffer::channel( int ) +{ + channel_t ch; + ch.center = &buf; + ch.left = &buf; + ch.right = &buf; + return ch; +} + +void Mono_Buffer::end_frame( blip_time_t t, bool ) +{ + buf.end_frame( t ); +} + +// Stereo_Buffer + +Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 ) +{ + chan.center = &bufs [0]; + chan.left = &bufs [1]; + chan.right = &bufs [2]; +} + +Stereo_Buffer::~Stereo_Buffer() +{ +} + +blargg_err_t Stereo_Buffer::set_sample_rate( long rate, int msec ) +{ + for ( int i = 0; i < buf_count; i++ ) + RETURN_ERR( bufs [i].set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( bufs [0].sample_rate(), bufs [0].length() ); +} + +void Stereo_Buffer::clock_rate( long rate ) +{ + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clock_rate( rate ); +} + +void Stereo_Buffer::bass_freq( int bass ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].bass_freq( bass ); +} + +void Stereo_Buffer::clear() +{ + stereo_added = false; + was_stereo = false; + for ( int i = 0; i < buf_count; i++ ) + bufs [i].clear(); +} + +void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo ) +{ + for ( unsigned i = 0; i < buf_count; i++ ) + bufs [i].end_frame( clock_count ); + + stereo_added |= stereo; +} + +long Stereo_Buffer::read_samples( blip_sample_t* out, long count ) +{ + require( !(count & 1) ); // count must be even + count = (unsigned) count / 2; + + long avail = bufs [0].samples_avail(); + if ( count > avail ) + count = avail; + if ( count ) + { + if ( stereo_added || was_stereo ) + { + mix_stereo( out, count ); + + bufs [0].remove_samples( count ); + bufs [1].remove_samples( count ); + bufs [2].remove_samples( count ); + } + else + { + mix_mono( out, count ); + + bufs [0].remove_samples( count ); + + bufs [1].remove_silence( count ); + bufs [2].remove_silence( count ); + } + + // to do: this might miss opportunities for optimization + if ( !bufs [0].samples_avail() ) { + was_stereo = stereo_added; + stereo_added = false; + } + } + + return count * 2; +} + +void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count ) +{ + Blip_Reader left; + Blip_Reader right; + Blip_Reader center; + + left.begin( bufs [1] ); + right.begin( bufs [2] ); + int bass = center.begin( bufs [0] ); + + while ( count-- ) + { + int c = center.read(); + long l = c + left.read(); + long r = c + right.read(); + center.next( bass ); + out [0] = l; + out [1] = r; + out += 2; + + if ( (BOOST::int16_t) l != l ) + out [-2] = 0x7FFF - (l >> 24); + + left.next( bass ); + right.next( bass ); + + if ( (BOOST::int16_t) r != r ) + out [-1] = 0x7FFF - (r >> 24); + } + + center.end( bufs [0] ); + right.end( bufs [2] ); + left.end( bufs [1] ); +} + +void Stereo_Buffer::mix_mono( blip_sample_t* out, long count ) +{ + Blip_Reader in; + int bass = in.begin( bufs [0] ); + + while ( count-- ) + { + long s = in.read(); + in.next( bass ); + out [0] = s; + out [1] = s; + out += 2; + + if ( (BOOST::int16_t) s != s ) { + s = 0x7FFF - (s >> 24); + out [-2] = s; + out [-1] = s; + } + } + + in.end( bufs [0] ); +} + diff --git a/quicknes/nes_emu/Multi_Buffer.h b/quicknes/nes_emu/Multi_Buffer.h new file mode 100644 index 0000000000..bff3ac99fa --- /dev/null +++ b/quicknes/nes_emu/Multi_Buffer.h @@ -0,0 +1,175 @@ + +// Multi-channel sound buffer interface, and basic mono and stereo buffers + +// Blip_Buffer 0.4.0 + +#ifndef MULTI_BUFFER_H +#define MULTI_BUFFER_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +// Interface to one or more Blip_Buffers mapped to one or more channels +// consisting of left, center, and right buffers. +class Multi_Buffer { +public: + Multi_Buffer( int samples_per_frame ); + virtual ~Multi_Buffer() { } + + // Set the number of channels available + virtual blargg_err_t set_channel_count( int ); + + // Get indexed channel, from 0 to channel count - 1 + struct channel_t { + Blip_Buffer* center; + Blip_Buffer* left; + Blip_Buffer* right; + }; + virtual channel_t channel( int index ) = 0; + + // See Blip_Buffer.h + virtual blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ) = 0; + virtual void clock_rate( long ) = 0; + virtual void bass_freq( int ) = 0; + virtual void clear() = 0; + long sample_rate() const; + + // Length of buffer, in milliseconds + int length() const; + + // See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo' + // if nothing was added to the left and right buffers of any channel for + // this time frame. + virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0; + + // Number of samples per output frame (1 = mono, 2 = stereo) + int samples_per_frame() const; + + // Count of changes to channel configuration. Incremented whenever + // a change is made to any of the Blip_Buffers for any channel. + unsigned channels_changed_count() { return channels_changed_count_; } + + // See Blip_Buffer.h + virtual long read_samples( blip_sample_t*, long ) = 0; + virtual long samples_avail() const = 0; + +protected: + void channels_changed() { channels_changed_count_++; } +private: + // noncopyable + Multi_Buffer( const Multi_Buffer& ); + Multi_Buffer& operator = ( const Multi_Buffer& ); + + unsigned channels_changed_count_; + long sample_rate_; + int length_; + int const samples_per_frame_; +}; + +// Uses a single buffer and outputs mono samples. +class Mono_Buffer : public Multi_Buffer { + Blip_Buffer buf; +public: + Mono_Buffer(); + ~Mono_Buffer(); + + // Buffer used for all channels + Blip_Buffer* center() { return &buf; } + + // See Multi_Buffer + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long samples_avail() const; + long read_samples( blip_sample_t*, long ); +}; + +// Uses three buffers (one for center) and outputs stereo sample pairs. +class Stereo_Buffer : public Multi_Buffer { +public: + Stereo_Buffer(); + ~Stereo_Buffer(); + + // Buffers used for all channels + Blip_Buffer* center() { return &bufs [0]; } + Blip_Buffer* left() { return &bufs [1]; } + Blip_Buffer* right() { return &bufs [2]; } + + // See Multi_Buffer + blargg_err_t set_sample_rate( long, int msec = blip_default_length ); + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int index ); + void end_frame( blip_time_t, bool added_stereo = true ); + + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + +private: + enum { buf_count = 3 }; + Blip_Buffer bufs [buf_count]; + channel_t chan; + bool stereo_added; + bool was_stereo; + + void mix_stereo( blip_sample_t*, long ); + void mix_mono( blip_sample_t*, long ); +}; + +// Silent_Buffer generates no samples, useful where no sound is wanted +class Silent_Buffer : public Multi_Buffer { + channel_t chan; +public: + Silent_Buffer(); + + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void clock_rate( long ) { } + void bass_freq( int ) { } + void clear() { } + channel_t channel( int ) { return chan; } + void end_frame( blip_time_t, bool unused = true ) { } + long samples_avail() const { return 0; } + long read_samples( blip_sample_t*, long ) { return 0; } +}; + + +// End of public interface + +inline blargg_err_t Multi_Buffer::set_sample_rate( long rate, int msec ) +{ + sample_rate_ = rate; + length_ = msec; + return 0; +} + +inline blargg_err_t Silent_Buffer::set_sample_rate( long rate, int msec ) +{ + return Multi_Buffer::set_sample_rate( rate, msec ); +} + +inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; } + +inline long Stereo_Buffer::samples_avail() const { return bufs [0].samples_avail() * 2; } + +inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int ) { return chan; } + +inline long Multi_Buffer::sample_rate() const { return sample_rate_; } + +inline int Multi_Buffer::length() const { return length_; } + +inline void Mono_Buffer::clock_rate( long rate ) { buf.clock_rate( rate ); } + +inline void Mono_Buffer::clear() { buf.clear(); } + +inline void Mono_Buffer::bass_freq( int freq ) { buf.bass_freq( freq ); } + +inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); } + +inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); } + +#endif + diff --git a/quicknes/nes_emu/Nes_Apu.cpp b/quicknes/nes_emu/Nes_Apu.cpp new file mode 100644 index 0000000000..d9d362d6cb --- /dev/null +++ b/quicknes/nes_emu/Nes_Apu.cpp @@ -0,0 +1,380 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int const amp_range = 15; + +Nes_Apu::Nes_Apu() : + square1( &square_synth ), + square2( &square_synth ) +{ + dmc.apu = this; + dmc.prg_reader = NULL; + irq_notifier_ = NULL; + + oscs [0] = &square1; + oscs [1] = &square2; + oscs [2] = ▵ + oscs [3] = &noise; + oscs [4] = &dmc; + + output( NULL ); + volume( 1.0 ); + reset( false ); +} + +Nes_Apu::~Nes_Apu() +{ +} + +void Nes_Apu::treble_eq( const blip_eq_t& eq ) +{ + square_synth.treble_eq( eq ); + triangle.synth.treble_eq( eq ); + noise.synth.treble_eq( eq ); + dmc.synth.treble_eq( eq ); +} + +void Nes_Apu::enable_nonlinear( double v ) +{ + dmc.nonlinear = true; + square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v ); + + const double tnd = 0.48 / 202 * nonlinear_tnd_gain(); + triangle.synth.volume( 3.0 * tnd ); + noise.synth.volume( 2.0 * tnd ); + dmc.synth.volume( tnd ); + + square1 .last_amp = 0; + square2 .last_amp = 0; + triangle.last_amp = 0; + noise .last_amp = 0; + dmc .last_amp = 0; +} + +void Nes_Apu::volume( double v ) +{ + dmc.nonlinear = false; + square_synth.volume( 0.1128 / amp_range * v ); + triangle.synth.volume( 0.12765 / amp_range * v ); + noise.synth.volume( 0.0741 / amp_range * v ); + dmc.synth.volume( 0.42545 / 127 * v ); +} + +void Nes_Apu::output( Blip_Buffer* buffer ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buffer ); +} + +void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac ) +{ + // to do: time pal frame periods exactly + frame_period = pal_mode ? 8314 : 7458; + dmc.pal_mode = pal_mode; + + square1.reset(); + square2.reset(); + triangle.reset(); + noise.reset(); + dmc.reset(); + + last_time = 0; + last_dmc_time = 0; + osc_enables = 0; + irq_flag = false; + earliest_irq_ = no_irq; + frame_delay = 1; + write_register( 0, 0x4017, 0x00 ); + write_register( 0, 0x4015, 0x00 ); + + for ( nes_addr_t addr = start_addr; addr <= 0x4013; addr++ ) + write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 ); + + dmc.dac = initial_dmc_dac; + if ( !dmc.nonlinear ) + triangle.last_amp = 15; + //if ( !dmc.nonlinear ) // to do: remove? + // dmc.last_amp = initial_dmc_dac; // prevent output transition +} + +void Nes_Apu::irq_changed() +{ + nes_time_t new_irq = dmc.next_irq; + if ( dmc.irq_flag | irq_flag ) { + new_irq = 0; + } + else if ( new_irq > next_irq ) { + new_irq = next_irq; + } + + if ( new_irq != earliest_irq_ ) { + earliest_irq_ = new_irq; + if ( irq_notifier_ ) + irq_notifier_( irq_data ); + } +} + +// frames + +void Nes_Apu::run_until( nes_time_t end_time ) +{ + require( end_time >= last_dmc_time ); + if ( end_time > next_dmc_read_time() ) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run( start, end_time ); + } +} + +void Nes_Apu::run_until_( nes_time_t end_time ) +{ + require( end_time >= last_time ); + + if ( end_time == last_time ) + return; + + if ( last_dmc_time < end_time ) + { + nes_time_t start = last_dmc_time; + last_dmc_time = end_time; + dmc.run( start, end_time ); + } + + while ( true ) + { + // earlier of next frame time or end time + nes_time_t time = last_time + frame_delay; + if ( time > end_time ) + time = end_time; + frame_delay -= time - last_time; + + // run oscs to present + square1.run( last_time, time ); + square2.run( last_time, time ); + triangle.run( last_time, time ); + noise.run( last_time, time ); + last_time = time; + + if ( time == end_time ) + break; // no more frames to run + + // take frame-specific actions + frame_delay = frame_period; + switch ( frame++ ) + { + case 0: + if ( !(frame_mode & 0xc0) ) { + next_irq = time + frame_period * 4 + 1; + irq_flag = true; + } + // fall through + case 2: + // clock length and sweep on frames 0 and 2 + square1.clock_length( 0x20 ); + square2.clock_length( 0x20 ); + noise.clock_length( 0x20 ); + triangle.clock_length( 0x80 ); // different bit for halt flag on triangle + + square1.clock_sweep( -1 ); + square2.clock_sweep( 0 ); + break; + + case 1: + // frame 1 is slightly shorter + frame_delay -= 2; + break; + + case 3: + frame = 0; + + // frame 3 is almost twice as long in mode 1 + if ( frame_mode & 0x80 ) + frame_delay += frame_period - 6; + break; + } + + // clock envelopes and linear counter every frame + triangle.clock_linear_counter(); + square1.clock_envelope(); + square2.clock_envelope(); + noise.clock_envelope(); + } +} + +template +inline void zero_apu_osc( T* osc, nes_time_t time ) +{ + Blip_Buffer* output = osc->output; + int last_amp = osc->last_amp; + osc->last_amp = 0; + if ( output && last_amp ) + osc->synth.offset( time, -last_amp, output ); +} + +void Nes_Apu::end_frame( nes_time_t end_time ) +{ + if ( end_time > last_time ) + run_until_( end_time ); + + if ( dmc.nonlinear ) + { + zero_apu_osc( &square1, last_time ); + zero_apu_osc( &square2, last_time ); + zero_apu_osc( &triangle, last_time ); + zero_apu_osc( &noise, last_time ); + zero_apu_osc( &dmc, last_time ); + } + + // make times relative to new frame + last_time -= end_time; + require( last_time >= 0 ); + + last_dmc_time -= end_time; + require( last_dmc_time >= 0 ); + + if ( next_irq != no_irq ) { + next_irq -= end_time; + assert( next_irq >= 0 ); + } + if ( dmc.next_irq != no_irq ) { + dmc.next_irq -= end_time; + assert( dmc.next_irq >= 0 ); + } + if ( earliest_irq_ != no_irq ) { + earliest_irq_ -= end_time; + if ( earliest_irq_ < 0 ) + earliest_irq_ = 0; + } +} + +// registers + +static const unsigned char length_table [0x20] = { + 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, + 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E, + 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, + 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E +}; + +void Nes_Apu::write_register( nes_time_t time, nes_addr_t addr, int data ) +{ + require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx) + require( (unsigned) data <= 0xff ); + + // Ignore addresses outside range + if ( addr < start_addr || end_addr < addr ) + return; + + run_until_( time ); + + if ( addr < 0x4014 ) + { + // Write to channel + int osc_index = (addr - start_addr) >> 2; + Nes_Osc* osc = oscs [osc_index]; + + int reg = addr & 3; + osc->regs [reg] = data; + osc->reg_written [reg] = true; + + if ( osc_index == 4 ) + { + // handle DMC specially + dmc.write_register( reg, data ); + } + else if ( reg == 3 ) + { + // load length counter + if ( (osc_enables >> osc_index) & 1 ) + osc->length_counter = length_table [(data >> 3) & 0x1f]; + + // reset square phase + if ( osc_index < 2 ) + ((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1; + } + } + else if ( addr == 0x4015 ) + { + // Channel enables + for ( int i = osc_count; i--; ) + if ( !((data >> i) & 1) ) + oscs [i]->length_counter = 0; + + bool recalc_irq = dmc.irq_flag; + dmc.irq_flag = false; + + int old_enables = osc_enables; + osc_enables = data; + if ( !(data & 0x10) ) { + dmc.next_irq = no_irq; + recalc_irq = true; + } + else if ( !(old_enables & 0x10) ) { + dmc.start(); // dmc just enabled + } + + if ( recalc_irq ) + irq_changed(); + } + else if ( addr == 0x4017 ) + { + // Frame mode + frame_mode = data; + + bool irq_enabled = !(data & 0x40); + irq_flag &= irq_enabled; + next_irq = no_irq; + + // mode 1 + frame_delay = (frame_delay & 1); + frame = 0; + + if ( !(data & 0x80) ) + { + // mode 0 + frame = 1; + frame_delay += frame_period; + if ( irq_enabled ) + next_irq = time + frame_delay + frame_period * 3; + } + + irq_changed(); + } +} + +int Nes_Apu::read_status( nes_time_t time ) +{ + run_until_( time - 1 ); + + int result = (dmc.irq_flag << 7) | (irq_flag << 6); + + for ( int i = 0; i < osc_count; i++ ) + if ( oscs [i]->length_counter ) + result |= 1 << i; + + run_until_( time ); + + if ( irq_flag ) { + irq_flag = false; + irq_changed(); + } + + return result; +} + diff --git a/quicknes/nes_emu/Nes_Apu.h b/quicknes/nes_emu/Nes_Apu.h new file mode 100644 index 0000000000..a004ff757e --- /dev/null +++ b/quicknes/nes_emu/Nes_Apu.h @@ -0,0 +1,177 @@ + +// NES 2A03 APU sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_APU_H +#define NES_APU_H + +typedef long nes_time_t; // CPU clock cycle count +typedef unsigned nes_addr_t; // 16-bit memory address + +#include "Nes_Oscs.h" + +struct apu_state_t; +class Nes_Buffer; + +class Nes_Apu { +public: + Nes_Apu(); + ~Nes_Apu(); + + // Set buffer to generate all sound into, or disable sound if NULL + void output( Blip_Buffer* ); + + // Set memory reader callback used by DMC oscillator to fetch samples. + // When callback is invoked, 'user_data' is passed unchanged as the + // first parameter. + void dmc_reader( int (*callback)( void* user_data, nes_addr_t ), void* user_data = NULL ); + + // All time values are the number of CPU clock cycles relative to the + // beginning of the current time frame. Before resetting the CPU clock + // count, call end_frame( last_cpu_time ). + + // Write to register (0x4000-0x4017, except 0x4014 and 0x4016) + enum { start_addr = 0x4000 }; + enum { end_addr = 0x4017 }; + void write_register( nes_time_t, nes_addr_t, int data ); + + // Read from status register at 0x4015 + enum { status_addr = 0x4015 }; + int read_status( nes_time_t ); + + // Run all oscillators up to specified time, end current time frame, then + // start a new time frame at time 0. Time frames have no effect on emulation + // and each can be whatever length is convenient. + void end_frame( nes_time_t ); + +// Additional optional features (can be ignored without any problem) + + // Reset internal frame counter, registers, and all oscillators. + // Use PAL timing if pal_timing is true, otherwise use NTSC timing. + // Set the DMC oscillator's initial DAC value to initial_dmc_dac without + // any audible click. + void reset( bool pal_timing = false, int initial_dmc_dac = 0 ); + + // Save/load exact emulation state + void save_state( apu_state_t* out ) const; + void load_state( apu_state_t const& ); + + // Set overall volume (default is 1.0) + void volume( double ); + + // Set treble equalization (see notes.txt) + void treble_eq( const blip_eq_t& ); + + // Set sound output of specific oscillator to buffer. If buffer is NULL, + // the specified oscillator is muted and emulation accuracy is reduced. + // The oscillators are indexed as follows: 0) Square 1, 1) Square 2, + // 2) Triangle, 3) Noise, 4) DMC. + enum { osc_count = 5 }; + void osc_output( int index, Blip_Buffer* buffer ); + + // Set IRQ time callback that is invoked when the time of earliest IRQ + // may have changed, or NULL to disable. When callback is invoked, + // 'user_data' is passed unchanged as the first parameter. + void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL ); + + // Get time that APU-generated IRQ will occur if no further register reads + // or writes occur. If IRQ is already pending, returns irq_waiting. If no + // IRQ will occur, returns no_irq. + enum { no_irq = LONG_MAX / 2 + 1 }; + enum { irq_waiting = 0 }; + nes_time_t earliest_irq( nes_time_t ) const; + + // Count number of DMC reads that would occur if 'run_until( t )' were executed. + // If last_read is not NULL, set *last_read to the earliest time that + // 'count_dmc_reads( time )' would result in the same result. + int count_dmc_reads( nes_time_t t, nes_time_t* last_read = NULL ) const; + + // Time when next DMC memory read will occur + nes_time_t next_dmc_read_time() const; + + // Run DMC until specified time, so that any DMC memory reads can be + // accounted for (i.e. inserting CPU wait states). + void run_until( nes_time_t ); + +// End of public interface. +private: + friend class Nes_Nonlinearizer; + void enable_nonlinear( double volume ); + static double nonlinear_tnd_gain() { return 0.75; } +private: + friend struct Nes_Dmc; + + // noncopyable + Nes_Apu( const Nes_Apu& ); + Nes_Apu& operator = ( const Nes_Apu& ); + + Nes_Osc* oscs [osc_count]; + Nes_Square square1; + Nes_Square square2; + Nes_Noise noise; + Nes_Triangle triangle; + Nes_Dmc dmc; + + nes_time_t last_time; // has been run until this time in current frame + nes_time_t last_dmc_time; + nes_time_t earliest_irq_; + nes_time_t next_irq; + int frame_period; + int frame_delay; // cycles until frame counter runs next + int frame; // current frame (0-3) + int osc_enables; + int frame_mode; + bool irq_flag; + void (*irq_notifier_)( void* user_data ); + void* irq_data; + Nes_Square::Synth square_synth; // shared by squares + + void irq_changed(); + void state_restored(); + void run_until_( nes_time_t ); + + // TODO: remove + friend class Nes_Core; +}; + +inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf ) +{ + assert( (unsigned) osc < osc_count ); + oscs [osc]->output = buf; +} + +inline nes_time_t Nes_Apu::earliest_irq( nes_time_t ) const +{ + return earliest_irq_; +} + +inline void Nes_Apu::dmc_reader( int (*func)( void*, nes_addr_t ), void* user_data ) +{ + dmc.prg_reader_data = user_data; + dmc.prg_reader = func; +} + +inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data ) +{ + irq_notifier_ = func; + irq_data = user_data; +} + +inline int Nes_Apu::count_dmc_reads( nes_time_t time, nes_time_t* last_read ) const +{ + return dmc.count_reads( time, last_read ); +} + +inline nes_time_t Nes_Dmc::next_read_time() const +{ + if ( length_counter == 0 ) + return Nes_Apu::no_irq; // not reading + + return apu->last_dmc_time + delay + long (bits_remain - 1) * period; +} + +inline nes_time_t Nes_Apu::next_dmc_read_time() const { return dmc.next_read_time(); } + +#endif + diff --git a/quicknes/nes_emu/Nes_Apu_State.cpp b/quicknes/nes_emu/Nes_Apu_State.cpp new file mode 100644 index 0000000000..5720090fda --- /dev/null +++ b/quicknes/nes_emu/Nes_Apu_State.cpp @@ -0,0 +1,125 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/ + +#include "Nes_Apu.h" + +#include "Nes_State.h" + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +template +struct apu_reflection +{ + #define REFLECT( apu, state ) (mode ? void (apu = state) : void (state = apu)) + + static void reflect_env( apu_state_t::env_t& state, Nes_Envelope& osc ) + { + REFLECT( state [0], osc.env_delay ); + REFLECT( state [1], osc.envelope ); + REFLECT( state [2], osc.reg_written [3] ); + } + + static void reflect_square( apu_state_t::square_t& state, Nes_Square& osc ) + { + reflect_env( state.env, osc ); + REFLECT( state.delay, osc.delay ); + REFLECT( state.length, osc.length_counter ); + REFLECT( state.phase, osc.phase ); + REFLECT( state.swp_delay, osc.sweep_delay ); + REFLECT( state.swp_reset, osc.reg_written [1] ); + } + + static void reflect_triangle( apu_state_t::triangle_t& state, Nes_Triangle& osc ) + { + REFLECT( state.delay, osc.delay ); + REFLECT( state.length, osc.length_counter ); + REFLECT( state.linear_counter, osc.linear_counter ); + REFLECT( state.linear_mode, osc.reg_written [3] ); + } + + static void reflect_noise( apu_state_t::noise_t& state, Nes_Noise& osc ) + { + reflect_env( state.env, osc ); + REFLECT( state.delay, osc.delay ); + REFLECT( state.length, osc.length_counter ); + REFLECT( state.shift_reg, osc.noise ); + } + + static void reflect_dmc( apu_state_t::dmc_t& state, Nes_Dmc& osc ) + { + REFLECT( state.delay, osc.delay ); + REFLECT( state.remain, osc.length_counter ); + REFLECT( state.buf, osc.buf ); + REFLECT( state.bits_remain, osc.bits_remain ); + REFLECT( state.bits, osc.bits ); + REFLECT( state.buf_empty, osc.buf_empty ); + REFLECT( state.silence, osc.silence ); + REFLECT( state.irq_flag, osc.irq_flag ); + if ( mode ) + state.addr = osc.address | 0x8000; + else + osc.address = state.addr & 0x7fff; + } +}; + +void Nes_Apu::get_state( apu_state_t* state ) const +{ + for ( int i = 0; i < osc_count * 4; i++ ) + state->w40xx [i] = oscs [i >> 2]->regs [i & 3]; + state->w40xx [0x11] = dmc.dac; + + state->w4015 = osc_enables; + state->w4017 = frame_mode; + state->delay = frame_delay; + state->step = frame; + state->irq_flag = irq_flag; + + typedef apu_reflection<1> refl; + Nes_Apu& apu = *(Nes_Apu*) this; // const_cast + refl::reflect_square ( state->square1, apu.square1 ); + refl::reflect_square ( state->square2, apu.square2 ); + refl::reflect_triangle( state->triangle, apu.triangle ); + refl::reflect_noise ( state->noise, apu.noise ); + refl::reflect_dmc ( state->dmc, apu.dmc ); +} + +void Nes_Apu::set_state( apu_state_t const& state ) +{ + reset(); + + write_register( 0, 0x4017, state.w4017 ); + write_register( 0, 0x4015, state.w4015 ); + + for ( int i = 0; i < osc_count * 4; i++ ) + { + int n = state.w40xx [i]; + oscs [i >> 2]->regs [i & 3] = n; + write_register( 0, 0x4000 + i, n ); + } + + frame_delay = state.delay; + frame = state.step; + irq_flag = state.irq_flag; + + typedef apu_reflection<0> refl; + apu_state_t& st = (apu_state_t&) state; // const_cast + refl::reflect_square ( st.square1, square1 ); + refl::reflect_square ( st.square2, square2 ); + refl::reflect_triangle( st.triangle, triangle ); + refl::reflect_noise ( st.noise, noise ); + refl::reflect_dmc ( st.dmc, dmc ); + dmc.recalc_irq(); + dmc.last_amp = dmc.dac; +} + diff --git a/quicknes/nes_emu/Nes_Blitter.cpp b/quicknes/nes_emu/Nes_Blitter.cpp new file mode 100644 index 0000000000..a5e5b37e0e --- /dev/null +++ b/quicknes/nes_emu/Nes_Blitter.cpp @@ -0,0 +1,113 @@ + +#include "Nes_Blitter.h" + +#include "blargg_endian.h" + +#include "blargg_source.h" + +#ifndef NES_BLITTER_OUT_DEPTH + #define NES_BLITTER_OUT_DEPTH 16 +#endif + +Nes_Blitter::Nes_Blitter() { ntsc = 0; } + +Nes_Blitter::~Nes_Blitter() { free( ntsc ); } + +blargg_err_t Nes_Blitter::init() +{ + assert( !ntsc ); + CHECK_ALLOC( ntsc = (nes_ntsc_emph_t*) malloc( sizeof *ntsc ) ); + static setup_t const s = { }; + setup_ = s; + setup_.ntsc = nes_ntsc_composite; + return setup( setup_ ); +} + +blargg_err_t Nes_Blitter::setup( setup_t const& s ) +{ + setup_ = s; + chunk_count = ((Nes_Emu::image_width - setup_.crop.left - setup_.crop.right) + 5) / 6; + height = Nes_Emu::image_height - setup_.crop.top - setup_.crop.bottom; + nes_ntsc_init_emph( ntsc, &setup_.ntsc ); + return 0; +} + +void Nes_Blitter::blit( Nes_Emu& emu, void* out, long out_pitch ) +{ + short const* palette = emu.frame().palette; + int burst_phase = (setup_.ntsc.merge_fields ? 0 : emu.frame().burst_phase); + long in_pitch = emu.frame().pitch; + unsigned char* in = emu.frame().pixels + setup_.crop.top * in_pitch + setup_.crop.left; + + for ( int n = height; n; --n ) + { + unsigned char* line_in = in; + in += in_pitch; + + BOOST::uint32_t* line_out = (BOOST::uint32_t*) out; + out = (char*) out + out_pitch; + + NES_NTSC_BEGIN_ROW( ntsc, burst_phase, + nes_ntsc_black, nes_ntsc_black, palette [*line_in] ); + + line_in [256] = 252; // loop reads 3 extra pixels, so set them to black + line_in [257] = 252; + line_in [258] = 252; + line_in++; + + burst_phase = (burst_phase + 1) % nes_ntsc_burst_count; + + // assemble two 16-bit pixels into a 32-bit int for better performance + #if BLARGG_BIG_ENDIAN + #define COMBINE_PIXELS right |= left << 16; + #else + #define COMBINE_PIXELS right <<= 16; right |= left; + #endif + + #define OUT_PIXEL( i ) \ + if ( !(i & 1) ) {\ + NES_NTSC_RGB_OUT( (i % 7), left, NES_BLITTER_OUT_DEPTH );\ + if ( i > 1 ) line_out [(i/2)-1] = right;\ + }\ + else {\ + NES_NTSC_RGB_OUT( (i % 7), right, NES_BLITTER_OUT_DEPTH );\ + COMBINE_PIXELS;\ + } + + for ( int n = chunk_count; n; --n ) + { + unsigned long left, right; + + NES_NTSC_COLOR_IN( 0, palette [line_in [0]] ); + OUT_PIXEL( 0 ); + OUT_PIXEL( 1 ); + + NES_NTSC_COLOR_IN( 1, palette [line_in [1]] ); + OUT_PIXEL( 2 ); + OUT_PIXEL( 3 ); + + NES_NTSC_COLOR_IN( 2, palette [line_in [2]] ); + OUT_PIXEL( 4 ); + OUT_PIXEL( 5 ); + OUT_PIXEL( 6 ); + + NES_NTSC_COLOR_IN( 0, palette [line_in [3]] ); + OUT_PIXEL( 7 ); + OUT_PIXEL( 8 ); + + NES_NTSC_COLOR_IN( 1, palette [line_in [4]] ); + OUT_PIXEL( 9 ); + OUT_PIXEL( 10); + + NES_NTSC_COLOR_IN( 2, palette [line_in [5]] ); + line_in += 6; + OUT_PIXEL( 11 ); + OUT_PIXEL( 12 ); + OUT_PIXEL( 13 ); + + line_out [6] = right; + line_out += 7; + } + } +} + diff --git a/quicknes/nes_emu/Nes_Blitter.h b/quicknes/nes_emu/Nes_Blitter.h new file mode 100644 index 0000000000..44e14146aa --- /dev/null +++ b/quicknes/nes_emu/Nes_Blitter.h @@ -0,0 +1,45 @@ + +// NTSC filter for use with Nes_Emu + +#ifndef NES_BLITTER_H +#define NES_BLITTER_H + +#include "nes_emu/Nes_Emu.h" +#include "nes_emu/nes_ntsc.h" + +class Nes_Blitter { +public: + Nes_Blitter(); + ~Nes_Blitter(); + + blargg_err_t init(); + + struct setup_t + { + nes_ntsc_setup_t ntsc; + struct { + // Number of NES source pixels to remove from each border. + // Resulting width will be rounded to multiple of 6 pixels due + // to internal limitations. + int left, top, right, bottom; + } crop; + }; + setup_t const& setup() const { return setup_; } + blargg_err_t setup( setup_t const& ); + + // size of output image generated by blit() + int out_width() const { return chunk_count * 14; } + int out_height() const { return height; } + + // Generate NTSC filtered image to 16-bit 565 RGB output pixels + void blit( Nes_Emu&, void* out, long pitch ); + +private: + setup_t setup_; + nes_ntsc_emph_t* ntsc; + int chunk_count; + int height; +}; + +#endif + diff --git a/quicknes/nes_emu/Nes_Buffer.cpp b/quicknes/nes_emu/Nes_Buffer.cpp new file mode 100644 index 0000000000..97d76888e3 --- /dev/null +++ b/quicknes/nes_emu/Nes_Buffer.cpp @@ -0,0 +1,201 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ + +#include "Nes_Buffer.h" + +#include "Nes_Apu.h" + +/* Library Copyright (C) 2003-2006 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +// Nes_Buffer + +Nes_Buffer::Nes_Buffer() : Multi_Buffer( 1 ) { } + +Nes_Buffer::~Nes_Buffer() { } + +Multi_Buffer* set_apu( Nes_Buffer* buf, Nes_Apu* apu ) +{ + buf->set_apu( apu ); + return buf; +} + +void Nes_Buffer::enable_nonlinearity( bool b ) +{ + if ( b ) + clear(); + + Nes_Apu* apu = nonlin.enable( b, &tnd ); + apu->osc_output( 0, &buf ); + apu->osc_output( 1, &buf ); +} + +blargg_err_t Nes_Buffer::set_sample_rate( long rate, int msec ) +{ + enable_nonlinearity( nonlin.enabled ); // reapply + RETURN_ERR( buf.set_sample_rate( rate, msec ) ); + RETURN_ERR( tnd.set_sample_rate( rate, msec ) ); + return Multi_Buffer::set_sample_rate( buf.sample_rate(), buf.length() ); +} + +void Nes_Buffer::clock_rate( long rate ) +{ + buf.clock_rate( rate ); + tnd.clock_rate( rate ); +} + +void Nes_Buffer::bass_freq( int freq ) +{ + buf.bass_freq( freq ); + tnd.bass_freq( freq ); +} + +void Nes_Buffer::clear() +{ + nonlin.clear(); + buf.clear(); + tnd.clear(); +} + +Nes_Buffer::channel_t Nes_Buffer::channel( int i ) +{ + channel_t c; + c.center = &buf; + if ( 2 <= i && i <= 4 ) + c.center = &tnd; // only use for triangle, noise, and dmc + c.left = c.center; + c.right = c.center; + return c; +} + +void Nes_Buffer::end_frame( blip_time_t length, bool ) +{ + buf.end_frame( length ); + tnd.end_frame( length ); +} + +long Nes_Buffer::samples_avail() const +{ + return buf.samples_avail(); +} + +long Nes_Buffer::read_samples( blip_sample_t* out, long count ) +{ + count = nonlin.make_nonlinear( tnd, count ); + if ( count ) + { + Blip_Reader lin; + Blip_Reader nonlin; + + int lin_bass = lin.begin( buf ); + int nonlin_bass = nonlin.begin( tnd ); + + for ( int n = count; n--; ) + { + int s = lin.read() + nonlin.read(); + lin.next( lin_bass ); + nonlin.next( nonlin_bass ); + *out++ = s; + + if ( (BOOST::int16_t) s != s ) + out [-1] = 0x7FFF - (s >> 24); + } + + lin.end( buf ); + nonlin.end( tnd ); + + buf.remove_samples( count ); + tnd.remove_samples( count ); + } + + return count; +} + +// Nes_Nonlinearizer + +Nes_Nonlinearizer::Nes_Nonlinearizer() +{ + apu = NULL; + enabled = true; + + float const gain = 0x7fff * 1.3f; + // don't use entire range, so any overflow will stay within table + int const range = (int) (table_size * Nes_Apu::nonlinear_tnd_gain()); + for ( int i = 0; i < table_size; i++ ) + { + int const offset = table_size - range; + int j = i - offset; + float n = 202.0f / (range - 1) * j; + float d = gain * 163.67f / (24329.0f / n + 100.0f); + int out = (int) d; +//out = j << (15 - table_bits); // make table linear for testing + assert( out < 0x8000 ); + table [j & (table_size - 1)] = out; + } +} + +Nes_Apu* Nes_Nonlinearizer::enable( bool b, Blip_Buffer* buf ) +{ + require( apu ); + apu->osc_output( 2, buf ); + apu->osc_output( 3, buf ); + apu->osc_output( 4, buf ); + enabled = b; + if ( b ) + apu->enable_nonlinear( 1.0 ); + else + apu->volume( 1.0 ); + return apu; +} + +#define ENTRY( s ) table [(s) >> (blip_sample_bits - table_bits - 1) & (table_size - 1)] + +long Nes_Nonlinearizer::make_nonlinear( Blip_Buffer& buf, long count ) +{ + require( apu ); + long avail = buf.samples_avail(); + if ( count > avail ) + count = avail; + if ( count && enabled ) + { + + Blip_Buffer::buf_t_* p = buf.buffer_; + long accum = this->accum; + long prev = this->prev; + for ( unsigned n = count; n; --n ) + { + long entry = ENTRY( accum ); + check( (entry >= 0) == (accum >= 0) ); + accum += *p; + *p++ = (entry - prev) << (blip_sample_bits - 16); + prev = entry; + } + + this->prev = prev; + this->accum = accum; + } + + return count; +} + +void Nes_Nonlinearizer::clear() +{ + accum = 0; + prev = ENTRY( 86016000 ); // avoid thump due to APU's triangle dc bias + // TODO: still results in slight clicks and thumps +} + diff --git a/quicknes/nes_emu/Nes_Buffer.h b/quicknes/nes_emu/Nes_Buffer.h new file mode 100644 index 0000000000..8e47a4d9ce --- /dev/null +++ b/quicknes/nes_emu/Nes_Buffer.h @@ -0,0 +1,68 @@ + +// NES non-linear audio buffer + +// Nes_Emu 0.7.0 + +#ifndef NES_BUFFER_H +#define NES_BUFFER_H + +#include "Multi_Buffer.h" +class Nes_Apu; + +class Nes_Nonlinearizer { +private: + enum { table_bits = 11 }; + enum { table_size = 1 << table_bits }; + BOOST::int16_t table [table_size]; + Nes_Apu* apu; + long accum; + long prev; + +public: + Nes_Nonlinearizer(); + bool enabled; + void clear(); + void set_apu( Nes_Apu* a ) { apu = a; } + Nes_Apu* enable( bool, Blip_Buffer* tnd ); + long make_nonlinear( Blip_Buffer& buf, long count ); +}; + +class Nes_Buffer : public Multi_Buffer { +public: + Nes_Buffer(); + ~Nes_Buffer(); + + // Setup APU for use with buffer, including setting its output to this buffer. + // If you're using Nes_Emu, this is automatically called for you. + void set_apu( Nes_Apu* apu ) { nonlin.set_apu( apu ); } + + // Enable/disable non-linear output + void enable_nonlinearity( bool = true ); + + // Blip_Buffer to output other sound chips to + Blip_Buffer* buffer() { return &buf; } + + // See Multi_Buffer.h + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + +#if 0 // What is this? + Multi_Buffer::sample_rate; +#endif + + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + +private: + Blip_Buffer buf; + Blip_Buffer tnd; + Nes_Nonlinearizer nonlin; + friend Multi_Buffer* set_apu( Nes_Buffer*, Nes_Apu* ); +}; + +#endif + diff --git a/quicknes/nes_emu/Nes_Cart.cpp b/quicknes/nes_emu/Nes_Cart.cpp new file mode 100644 index 0000000000..d006ec80f9 --- /dev/null +++ b/quicknes/nes_emu/Nes_Cart.cpp @@ -0,0 +1,268 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Cart.h" + +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +char const Nes_Cart::not_ines_file [] = "Not an iNES file"; + +Nes_Cart::Nes_Cart() +{ + prg_ = NULL; + chr_ = NULL; + clear(); +} + +Nes_Cart::~Nes_Cart() +{ + clear(); +} + +void Nes_Cart::clear() +{ + free( prg_ ); + prg_ = NULL; + + free( chr_ ); + chr_ = NULL; + + prg_size_ = 0; + chr_size_ = 0; + mapper = 0; +} + +long Nes_Cart::round_to_bank_size( long n ) +{ + n += bank_size - 1; + return n - n % bank_size; +} + +blargg_err_t Nes_Cart::resize_prg( long size ) +{ + if ( size != prg_size_ ) + { + // padding allows CPU to always read operands of instruction, which + // might go past end of data + void* p = realloc( prg_, round_to_bank_size( size ) + 2 ); + CHECK_ALLOC( p || !size ); + prg_ = (byte*) p; + prg_size_ = size; + } + return 0; +} + +blargg_err_t Nes_Cart::resize_chr( long size ) +{ + if ( size != chr_size_ ) + { + void* p = realloc( chr_, round_to_bank_size( size ) ); + CHECK_ALLOC( p || !size ); + chr_ = (byte*) p; + chr_size_ = size; + } + return 0; +} + +// iNES reading + +struct ines_header_t { + BOOST::uint8_t signature [4]; + BOOST::uint8_t prg_count; // number of 16K PRG banks + BOOST::uint8_t chr_count; // number of 8K CHR banks + BOOST::uint8_t flags; // MMMM FTBV Mapper low, Four-screen, Trainer, Battery, V mirror + BOOST::uint8_t flags2; // MMMM --XX Mapper high 4 bits + BOOST::uint8_t zero [8]; // if zero [7] is non-zero, treat flags2 as zero +}; +BOOST_STATIC_ASSERT( sizeof (ines_header_t) == 16 ); + +blargg_err_t Nes_Cart::load_ines( Auto_File_Reader in ) +{ + RETURN_ERR( in.open() ); + + ines_header_t h; + RETURN_ERR( in->read( &h, sizeof h ) ); + + if ( 0 != memcmp( h.signature, "NES\x1A", 4 ) ) + return not_ines_file; + + if ( h.zero [7] ) // handle header defaced by a fucking idiot's handle + h.flags2 = 0; + + set_mapper( h.flags, h.flags2 ); + + if ( h.flags & 0x04 ) // skip trainer + RETURN_ERR( in->skip( 512 ) ); + + RETURN_ERR( resize_prg( h.prg_count * 16 * 1024L ) ); + RETURN_ERR( resize_chr( h.chr_count * 8 * 1024L ) ); + + RETURN_ERR( in->read( prg(), prg_size() ) ); + RETURN_ERR( in->read( chr(), chr_size() ) ); + + return 0; +} + +// IPS patching + +// IPS patch file format (integers are big-endian): +// 5 "PATCH" +// n blocks +// +// normal block: +// 3 offset +// 2 size +// n data +// +// repeated byte block: +// 3 offset +// 2 0 +// 2 size +// 1 fill value +// +// end block (optional): +// 3 "EOF" +// +// A block can append data to the file by specifying an offset at the end of +// the current file data. + +typedef BOOST::uint8_t byte; +static blargg_err_t apply_ips_patch( Data_Reader& patch, byte** file, long* file_size ) +{ + byte signature [5]; + RETURN_ERR( patch.read( signature, sizeof signature ) ); + if ( memcmp( signature, "PATCH", sizeof signature ) ) + return "Not an IPS patch file"; + + while ( patch.remain() ) + { + // read offset + byte buf [6]; + RETURN_ERR( patch.read( buf, 3 ) ); + long offset = buf [0] * 0x10000 + buf [1] * 0x100 + buf [2]; + if ( offset == 0x454F46 ) // 'EOF' + break; + + // read size + RETURN_ERR( patch.read( buf, 2 ) ); + long size = buf [0] * 0x100 + buf [1]; + + // size = 0 signals a run of identical bytes + int fill = -1; + if ( size == 0 ) + { + RETURN_ERR( patch.read( buf, 3 ) ); + size = buf [0] * 0x100 + buf [1]; + fill = buf [2]; + } + + // expand file if new data is at exact end of file + if ( offset == *file_size ) + { + *file_size = offset + size; + void* p = realloc( *file, *file_size ); + CHECK_ALLOC( p ); + *file = (byte*) p; + } + + //dprintf( "Patch offset: 0x%04X, size: 0x%04X\n", (int) offset, (int) size ); + + if ( offset < 0 || *file_size < offset + size ) + return "IPS tried to patch past end of file"; + + // read/fill data + if ( fill < 0 ) + RETURN_ERR( patch.read( *file + offset, size ) ); + else + memset( *file + offset, fill, size ); + } + + return 0; +} + +blargg_err_t Nes_Cart::load_patched_ines( Auto_File_Reader in, Auto_File_Reader patch ) +{ + RETURN_ERR( in.open() ); + RETURN_ERR( patch.open() ); + + // read file into memory + long size = in->remain(); + byte* ines = (byte*) malloc( size ); + CHECK_ALLOC( ines ); + const char* err = in->read( ines, size ); + + // apply patch + if ( !err ) + err = apply_ips_patch( *patch, &ines, &size ); + + // load patched file + if ( !err ) + { + Mem_File_Reader patched( ines, size ); + err = load_ines( patched ); + } + + free( ines ); + + return err; +} + +blargg_err_t Nes_Cart::apply_ips_to_prg( Auto_File_Reader patch ) +{ + RETURN_ERR( patch.open() ); + + long size = prg_size(); + + byte* prg_copy = (byte*) malloc( size ); + CHECK_ALLOC( prg_copy ); + memcpy( prg_copy, prg(), size ); + + const char* err = apply_ips_patch( *patch, &prg_copy, &size ); + + if ( !err ) + { + resize_prg( size ); + memcpy( prg(), prg_copy, size ); + } + + free( prg_copy ); + + return err; +} + +blargg_err_t Nes_Cart::apply_ips_to_chr( Auto_File_Reader patch ) +{ + RETURN_ERR( patch.open() ); + + long size = chr_size(); + + byte* chr_copy = (byte*) malloc( size ); + CHECK_ALLOC( chr_copy ); + memcpy( chr_copy, chr(), size ); + + const char* err = apply_ips_patch( *patch, &chr_copy, &size ); + + if ( !err ) + { + resize_chr( size ); + memcpy( chr(), chr_copy, size ); + } + + free( chr_copy ); + + return err; +} diff --git a/quicknes/nes_emu/Nes_Cart.h b/quicknes/nes_emu/Nes_Cart.h new file mode 100644 index 0000000000..f00aac0979 --- /dev/null +++ b/quicknes/nes_emu/Nes_Cart.h @@ -0,0 +1,93 @@ + +// NES cartridge data (PRG, CHR, mapper) + +// Nes_Emu 0.7.0 + +#ifndef NES_CART_H +#define NES_CART_H + +#include "blargg_common.h" +#include "abstract_file.h" + +class Nes_Cart { + typedef BOOST::uint8_t byte; +public: + Nes_Cart(); + ~Nes_Cart(); + + // Load iNES file + blargg_err_t load_ines( Auto_File_Reader ); + static const char not_ines_file []; + + // Load iNES file and apply IPS patch + blargg_err_t load_patched_ines( Auto_File_Reader, Auto_File_Reader ips_patch ); + + // Apply IPS patches to specific parts + blargg_err_t apply_ips_to_prg( Auto_File_Reader ips_patch ); + blargg_err_t apply_ips_to_chr( Auto_File_Reader ips_patch ); + + // to do: support UNIF? + + // True if data is currently loaded + bool loaded() const { return prg_ != NULL; } + + // Free data + void clear(); + + // True if cartridge claims to have battery-backed memory + bool has_battery_ram() const; + + // Size of PRG data + long prg_size() const { return prg_size_; } + + // Size of CHR data + long chr_size() const { return chr_size_; } + + // Change size of PRG (code) data + blargg_err_t resize_prg( long ); + + // Change size of CHR (graphics) data + blargg_err_t resize_chr( long ); + + // Set mapper and information bytes. LSB and MSB are the standard iNES header + // bytes at offsets 6 and 7. + void set_mapper( int mapper_lsb, int mapper_msb ); + + unsigned mapper_data() const { return mapper; } + + // Initial mirroring setup + int mirroring() const { return mapper & 0x09; } + + // iNES mapper code + int mapper_code() const; + + // Pointer to beginning of PRG data + byte * prg() { return prg_; } + byte const* prg() const { return prg_; } + + // Pointer to beginning of CHR data + byte * chr() { return chr_; } + byte const* chr() const { return chr_; } + + // End of public interface +private: + enum { bank_size = 8 * 1024L }; // bank sizes must be a multiple of this + byte* prg_; + byte* chr_; + long prg_size_; + long chr_size_; + unsigned mapper; + long round_to_bank_size( long n ); +}; + +inline bool Nes_Cart::has_battery_ram() const { return mapper & 0x02; } + +inline void Nes_Cart::set_mapper( int mapper_lsb, int mapper_msb ) +{ + mapper = mapper_msb * 0x100 + mapper_lsb; +} + +inline int Nes_Cart::mapper_code() const { return ((mapper >> 8) & 0xf0) | ((mapper >> 4) & 0x0f); } + +#endif + diff --git a/quicknes/nes_emu/Nes_Core.cpp b/quicknes/nes_emu/Nes_Core.cpp new file mode 100644 index 0000000000..365cab4b2f --- /dev/null +++ b/quicknes/nes_emu/Nes_Core.cpp @@ -0,0 +1,570 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Core.h" + +#include +#include "Nes_Mapper.h" +#include "Nes_State.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +extern const char unsupported_mapper [] = "Unsupported mapper"; + +bool const wait_states_enabled = true; +bool const single_instruction_mode = false; // for debugging irq/nmi timing issues + +const int unmapped_fill = Nes_Cpu::page_wrap_opcode; + +unsigned const low_ram_size = 0x800; +unsigned const low_ram_end = 0x2000; +unsigned const sram_end = 0x8000; + +Nes_Core::Nes_Core() : ppu( this ) +{ + cart = NULL; + impl = NULL; + mapper = NULL; + memset( &nes, 0, sizeof nes ); + memset( &joypad, 0, sizeof joypad ); +} + +blargg_err_t Nes_Core::init() +{ + if ( !impl ) + { + CHECK_ALLOC( impl = BLARGG_NEW impl_t ); + impl->apu.dmc_reader( read_dmc, this ); + impl->apu.irq_notifier( apu_irq_changed, this ); + } + + return 0; +} + +void Nes_Core::close() +{ + // check that nothing modified unmapped page + #ifndef NDEBUG + //if ( cart && mem_differs( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page ) ) + // dprintf( "Unmapped code page was written to\n" ); + #endif + + cart = NULL; + delete mapper; + mapper = NULL; + + ppu.close_chr(); + + disable_rendering(); +} + +blargg_err_t Nes_Core::open( Nes_Cart const* new_cart ) +{ + close(); + + RETURN_ERR( init() ); + + mapper = Nes_Mapper::create( new_cart, this ); + if ( !mapper ) + return unsupported_mapper; + + RETURN_ERR( ppu.open_chr( new_cart->chr(), new_cart->chr_size() ) ); + + cart = new_cart; + memset( impl->unmapped_page, unmapped_fill, sizeof impl->unmapped_page ); + reset( true, true ); + return 0; +} + +Nes_Core::~Nes_Core() +{ + close(); + delete impl; +} + +void Nes_Core::save_state( Nes_State_* out ) const +{ + out->clear(); + + out->nes = nes; + out->nes_valid = true; + + *out->cpu = cpu::r; + out->cpu_valid = true; + + *out->joypad = joypad; + out->joypad_valid = true; + + impl->apu.save_state( out->apu ); + out->apu_valid = true; + + ppu.save_state( out ); + + memcpy( out->ram, cpu::low_mem, out->ram_size ); + out->ram_valid = true; + + out->sram_size = 0; + if ( sram_present ) + { + out->sram_size = sizeof impl->sram; + memcpy( out->sram, impl->sram, out->sram_size ); + } + + out->mapper->size = 0; + mapper->save_state( *out->mapper ); + out->mapper_valid = true; +} + +void Nes_Core::save_state( Nes_State* out ) const +{ + save_state( reinterpret_cast(out) ); +} + +void Nes_Core::load_state( Nes_State_ const& in ) +{ + require( cart ); + + disable_rendering(); + error_count = 0; + + if ( in.nes_valid ) + nes = in.nes; + + // always use frame count + ppu.burst_phase = 0; // avoids shimmer when seeking to same time over and over + nes.frame_count = in.nes.frame_count; + if ( (frame_count_t) nes.frame_count == invalid_frame_count ) + nes.frame_count = 0; + + if ( in.cpu_valid ) + cpu::r = *in.cpu; + + if ( in.joypad_valid ) + joypad = *in.joypad; + + if ( in.apu_valid ) + { + impl->apu.load_state( *in.apu ); + // prevent apu from running extra at beginning of frame + impl->apu.end_frame( -(int) nes.timestamp / ppu_overclock ); + } + else + { + impl->apu.reset(); + } + + ppu.load_state( in ); + + if ( in.ram_valid ) + memcpy( cpu::low_mem, in.ram, in.ram_size ); + + sram_present = false; + if ( in.sram_size ) + { + sram_present = true; + memcpy( impl->sram, in.sram, min( (int) in.sram_size, (int) sizeof impl->sram ) ); + enable_sram( true ); // mapper can override (read-only, unmapped, etc.) + } + + if ( in.mapper_valid ) // restore last since it might reconfigure things + mapper->load_state( *in.mapper ); +} + +void Nes_Core::enable_prg_6000() +{ + sram_writable = 0; + sram_readable = 0; + lrom_readable = 0x8000; +} + +void Nes_Core::enable_sram( bool b, bool read_only ) +{ + sram_writable = 0; + if ( b ) + { + if ( !sram_present ) + { + sram_present = true; + memset( impl->sram, 0xFF, impl->sram_size ); + } + sram_readable = sram_end; + if ( !read_only ) + sram_writable = sram_end; + cpu::map_code( 0x6000, impl->sram_size, impl->sram ); + } + else + { + sram_readable = 0; + for ( int i = 0; i < impl->sram_size; i += cpu::page_size ) + cpu::map_code( 0x6000 + i, cpu::page_size, impl->unmapped_page ); + } +} + +// Unmapped memory + +static nes_addr_t last_unmapped_addr; + +void Nes_Core::log_unmapped( nes_addr_t addr, int data ) +{ + #if !defined (NDEBUG) && 0 + if ( last_unmapped_addr != addr ) + { + last_unmapped_addr = addr; + if ( data < 0 ) + dprintf( "Read unmapped %04X\n", addr ); + else + dprintf( "Write unmapped %04X <- %02X\n", addr, data ); + } + #endif +} + +inline void Nes_Core::cpu_adjust_time( int n ) +{ + ppu_2002_time -= n; + cpu_time_offset += n; + cpu::reduce_limit( n ); +} + +// I/O and sound + +int Nes_Core::read_dmc( void* data, nes_addr_t addr ) +{ + Nes_Core* emu = (Nes_Core*) data; + int result = *emu->cpu::get_code( addr ); + if ( wait_states_enabled ) + emu->cpu_adjust_time( 4 ); + return result; +} + +void Nes_Core::apu_irq_changed( void* emu ) +{ + ((Nes_Core*) emu)->irq_changed(); +} + +void Nes_Core::write_io( nes_addr_t addr, int data ) +{ + // sprite dma + if ( addr == 0x4014 ) + { + ppu.dma_sprites( clock(), cpu::get_code( data * 0x100 ) ); + cpu_adjust_time( 513 ); + return; + } + + // joypad strobe + if ( addr == 0x4016 ) + { + // if strobe goes low, latch data + if ( joypad.w4016 & 1 & ~data ) + { + joypad_read_count++; + joypad.joypad_latches [0] = current_joypad [0]; + joypad.joypad_latches [1] = current_joypad [1]; + } + joypad.w4016 = data; + return; + } + + // apu + if ( unsigned (addr - impl->apu.start_addr) <= impl->apu.end_addr - impl->apu.start_addr ) + { + impl->apu.write_register( clock(), addr, data ); + if ( wait_states_enabled ) + { + if ( addr == 0x4010 || (addr == 0x4015 && (data & 0x10)) ) + { + impl->apu.run_until( clock() + 1 ); + event_changed(); + } + } + return; + } + + #ifndef NDEBUG + log_unmapped( addr, data ); + #endif +} + +int Nes_Core::read_io( nes_addr_t addr ) +{ + if ( (addr & 0xFFFE) == 0x4016 ) + { + // to do: to aid with recording, doesn't emulate transparent latch, + // so a game that held strobe at 1 and read $4016 or $4017 would not get + // the current A status as occurs on a NES + unsigned long result = joypad.joypad_latches [addr & 1]; + if ( !(joypad.w4016 & 1) ) + joypad.joypad_latches [addr & 1] = (result >> 1) | 0x80000000; + return result & 1; + } + + if ( addr == Nes_Apu::status_addr ) + return impl->apu.read_status( clock() ); + + #ifndef NDEBUG + log_unmapped( addr ); + #endif + + return addr >> 8; // simulate open bus +} + +// CPU + +const int irq_inhibit_mask = 0x04; + +nes_addr_t Nes_Core::read_vector( nes_addr_t addr ) +{ + byte const* p = cpu::get_code( addr ); + return p [1] * 0x100 + p [0]; +} + +void Nes_Core::reset( bool full_reset, bool erase_battery_ram ) +{ + require( cart ); + + if ( full_reset ) + { + cpu::reset( impl->unmapped_page ); + cpu_time_offset = -1; + clock_ = 0; + + // Low RAM + memset( cpu::low_mem, 0xFF, low_ram_size ); + cpu::low_mem [8] = 0xf7; + cpu::low_mem [9] = 0xef; + cpu::low_mem [10] = 0xdf; + cpu::low_mem [15] = 0xbf; + + // SRAM + lrom_readable = 0; + sram_present = false; + enable_sram( false ); + if ( !cart->has_battery_ram() || erase_battery_ram ) + memset( impl->sram, 0xFF, impl->sram_size ); + + joypad.joypad_latches [0] = 0; + joypad.joypad_latches [1] = 0; + + nes.frame_count = 0; + } + + // to do: emulate partial reset + + ppu.reset( full_reset ); + impl->apu.reset(); + + mapper->reset(); + + cpu::r.pc = read_vector( 0xFFFC ); + cpu::r.sp = 0xfd; + cpu::r.a = 0; + cpu::r.x = 0; + cpu::r.y = 0; + cpu::r.status = irq_inhibit_mask; + nes.timestamp = 0; + error_count = 0; +} + +void Nes_Core::vector_interrupt( nes_addr_t vector ) +{ + cpu::push_byte( cpu::r.pc >> 8 ); + cpu::push_byte( cpu::r.pc & 0xFF ); + cpu::push_byte( cpu::r.status | 0x20 ); // reserved bit is set + + cpu_adjust_time( 7 ); + cpu::r.status |= irq_inhibit_mask; + cpu::r.pc = read_vector( vector ); +} + +inline nes_time_t Nes_Core::earliest_irq( nes_time_t present ) +{ + return min( impl->apu.earliest_irq( present ), mapper->next_irq( present ) ); +} + +void Nes_Core::irq_changed() +{ + cpu_set_irq_time( earliest_irq( cpu_time() ) ); +} + +inline nes_time_t Nes_Core::ppu_frame_length( nes_time_t present ) +{ + nes_time_t t = ppu.frame_length(); + if ( t > present ) + return t; + + ppu.render_bg_until( clock() ); // to do: why this call to clock() rather than using present? + return ppu.frame_length(); +} + +inline nes_time_t Nes_Core::earliest_event( nes_time_t present ) +{ + // PPU frame + nes_time_t t = ppu_frame_length( present ); + + // DMC + if ( wait_states_enabled ) + t = min( t, impl->apu.next_dmc_read_time() + 1 ); + + // NMI + t = min( t, ppu.nmi_time() ); + + if ( single_instruction_mode ) + t = min( t, present + 1 ); + + return t; +} + +void Nes_Core::event_changed() +{ + cpu_set_end_time( earliest_event( cpu_time() ) ); +} + +#undef NES_EMU_CPU_HOOK +#ifndef NES_EMU_CPU_HOOK + #define NES_EMU_CPU_HOOK( cpu, end_time ) cpu::run( end_time ) +#endif + +nes_time_t Nes_Core::emulate_frame_() +{ + Nes_Cpu::result_t last_result = cpu::result_cycles; + int extra_instructions = 0; + while ( true ) + { + // Add DMC wait-states to CPU time + if ( wait_states_enabled ) + { + impl->apu.run_until( cpu_time() ); + clock_ = cpu_time_offset; + } + + nes_time_t present = cpu_time(); + if ( present >= ppu_frame_length( present ) ) + { + if ( ppu.nmi_time() <= present ) + { + // NMI will occur next, so delayed CLI and SEI don't need to be handled. + // If NMI will occur normally ($2000.7 and $2002.7 set), let it occur + // next frame, otherwise vector it now. + + if ( !(ppu.w2000 & 0x80 & ppu.r2002) ) + { + dprintf( "vectored NMI at end of frame\n" ); + vector_interrupt( 0xFFFA ); + present += 7; + } + return present; + } + + if ( extra_instructions > 2 ) + { + check( last_result != cpu::result_sei && last_result != cpu::result_cli ); + check( ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002) ); + return present; + } + + if ( last_result != cpu::result_cli && last_result != cpu::result_sei && + (ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002)) ) + return present; + + dprintf( "Executing extra instructions for frame\n" ); + extra_instructions++; // execute one more instruction + } + + // NMI + if ( present >= ppu.nmi_time() ) + { + ppu.acknowledge_nmi(); + vector_interrupt( 0xFFFA ); + last_result = cpu::result_cycles; // most recent sei/cli won't be delayed now + } + + // IRQ + nes_time_t irq_time = earliest_irq( present ); + cpu_set_irq_time( irq_time ); + if ( present >= irq_time && (!(cpu::r.status & irq_inhibit_mask) || + last_result == cpu::result_sei) ) + { + if ( last_result != cpu::result_cli ) + { + //dprintf( "%6d IRQ vectored\n", present ); + mapper->run_until( present ); + vector_interrupt( 0xFFFE ); + } + else + { + // CLI delays IRQ + cpu_set_irq_time( present + 1 ); + check( false ); // rare event + } + } + + // CPU + nes_time_t end_time = earliest_event( present ); + if ( extra_instructions ) + end_time = present + 1; + unsigned long cpu_error_count = cpu::error_count(); + last_result = NES_EMU_CPU_HOOK( cpu, end_time - cpu_time_offset - 1 ); + cpu_adjust_time( cpu::time() ); + clock_ = cpu_time_offset; + error_count += cpu::error_count() - cpu_error_count; + } +} + +nes_time_t Nes_Core::emulate_frame() +{ + require( cart ); + + joypad_read_count = 0; + + cpu_time_offset = ppu.begin_frame( nes.timestamp ) - 1; + ppu_2002_time = 0; + clock_ = cpu_time_offset; + + check( cpu_time() == (int) nes.timestamp / ppu_overclock ); + check( 1 && impl->apu.last_time == cpu_time() ); + + // TODO: clean this fucking mess up + impl->apu.run_until_( emulate_frame_() ); + clock_ = cpu_time_offset; + impl->apu.run_until_( cpu_time() ); + check( 2 && clock_ == cpu_time_offset ); + check( 3 && impl->apu.last_time == cpu_time() ); + + nes_time_t ppu_frame_length = ppu.frame_length(); + nes_time_t length = cpu_time(); + nes.timestamp = ppu.end_frame( length ); + mapper->end_frame( length ); + impl->apu.end_frame( ppu_frame_length ); + check( 4 && cpu_time() == length ); + + check( 5 && impl->apu.last_time == length - ppu_frame_length ); + + disable_rendering(); + nes.frame_count++; + + return ppu_frame_length; +} + +void Nes_Core::add_mapper_intercept( nes_addr_t addr, unsigned size, bool read, bool write ) +{ + require( addr >= 0x4000 ); + require( addr + size <= 0x10000 ); + int end = (addr + size + (page_size - 1)) >> page_bits; + for ( int page = addr >> page_bits; page < end; page++ ) + { + data_reader_mapped [page] |= read; + data_writer_mapped [page] |= write; + } +} + diff --git a/quicknes/nes_emu/Nes_Core.h b/quicknes/nes_emu/Nes_Core.h new file mode 100644 index 0000000000..b4dd759703 --- /dev/null +++ b/quicknes/nes_emu/Nes_Core.h @@ -0,0 +1,117 @@ + +// Internal NES emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_CORE_H +#define NES_CORE_H + +#include "blargg_common.h" +#include "Nes_Apu.h" +#include "Nes_Cpu.h" +#include "Nes_Ppu.h" +class Nes_Mapper; +class Nes_Cart; +class Nes_State; + +class Nes_Core : private Nes_Cpu { + typedef Nes_Cpu cpu; +public: + Nes_Core(); + ~Nes_Core(); + + blargg_err_t init(); + blargg_err_t open( Nes_Cart const* ); + void reset( bool full_reset = true, bool erase_battery_ram = false ); + blip_time_t emulate_frame(); + void close(); + + void save_state( Nes_State* ) const; + void save_state( Nes_State_* ) const; + void load_state( Nes_State_ const& ); + + void irq_changed(); + void event_changed(); + +public: private: friend class Nes_Emu; + + struct impl_t + { + enum { sram_size = 0x2000 }; + BOOST::uint8_t sram [sram_size]; + Nes_Apu apu; + + // extra byte allows CPU to always read operand of instruction, which + // might go past end of data + BOOST::uint8_t unmapped_page [::Nes_Cpu::page_size + 1]; + }; + impl_t* impl; // keep large arrays separate + unsigned long error_count; + bool sram_present; + +public: + unsigned long current_joypad [2]; + int joypad_read_count; + Nes_Cart const* cart; + Nes_Mapper* mapper; + nes_state_t nes; + Nes_Ppu ppu; + +private: + // noncopyable + Nes_Core( const Nes_Core& ); + Nes_Core& operator = ( const Nes_Core& ); + + // Timing + nes_time_t ppu_2002_time; + void disable_rendering() { clock_ = 0; } + nes_time_t earliest_irq( nes_time_t present ); + nes_time_t ppu_frame_length( nes_time_t present ); + nes_time_t earliest_event( nes_time_t present ); + + // APU and Joypad + joypad_state_t joypad; + int read_io( nes_addr_t ); + void write_io( nes_addr_t, int data ); + static int read_dmc( void* emu, nes_addr_t ); + static void apu_irq_changed( void* emu ); + + // CPU + unsigned sram_readable; + unsigned sram_writable; + unsigned lrom_readable; + nes_time_t clock_; + nes_time_t cpu_time_offset; + nes_time_t emulate_frame_(); + nes_addr_t read_vector( nes_addr_t ); + void vector_interrupt( nes_addr_t ); + static void log_unmapped( nes_addr_t addr, int data = -1 ); + void cpu_set_irq_time( nes_time_t t ) { cpu::set_irq_time_( t - 1 - cpu_time_offset ); } + void cpu_set_end_time( nes_time_t t ) { cpu::set_end_time_( t - 1 - cpu_time_offset ); } + nes_time_t cpu_time() const { return clock_ + 1; } + void cpu_adjust_time( int offset ); + +public: private: friend class Nes_Ppu; + void set_ppu_2002_time( nes_time_t t ) { ppu_2002_time = t - 1 - cpu_time_offset; } + +public: private: friend class Nes_Mapper; + void enable_prg_6000(); + void enable_sram( bool enabled, bool read_only = false ); + nes_time_t clock() const { return clock_; } + void add_mapper_intercept( nes_addr_t start, unsigned size, bool read, bool write ); + +public: private: friend class Nes_Cpu; + int cpu_read_ppu( nes_addr_t, nes_time_t ); + int cpu_read( nes_addr_t, nes_time_t ); + void cpu_write( nes_addr_t, int data, nes_time_t ); + void cpu_write_2007( int data ); + +private: + unsigned char data_reader_mapped [page_count + 1]; // extra entry for overflow + unsigned char data_writer_mapped [page_count + 1]; +}; + +int mem_differs( void const* p, int cmp, unsigned long s ); + +#endif + diff --git a/quicknes/nes_emu/Nes_Cpu.cpp b/quicknes/nes_emu/Nes_Cpu.cpp new file mode 100644 index 0000000000..09f10dd702 --- /dev/null +++ b/quicknes/nes_emu/Nes_Cpu.cpp @@ -0,0 +1,1250 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/nes-emu/ + +// TODO: remove +#if !defined (NDEBUG) && 0 + #pragma peephole on + #pragma global_optimizer on + #pragma optimization_level 4 + #pragma scheduling 604 + #undef BLARGG_ENABLE_OPTIMIZER +#endif + +#include "Nes_Cpu.h" + +#include +#include +#include "blargg_endian.h" + +#include "nes_cpu_io.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +inline void Nes_Cpu::set_code_page( int i, uint8_t const* p ) +{ + code_map [i] = p - (unsigned) i * page_size; +} + +void Nes_Cpu::reset( void const* unmapped_page ) +{ + r.status = 0; + r.sp = 0; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + + error_count_ = 0; + clock_count = 0; + clock_limit = 0; + irq_time_ = LONG_MAX / 2 + 1; + end_time_ = LONG_MAX / 2 + 1; + + assert( page_size == 0x800 ); // assumes this + set_code_page( 0, low_mem ); + set_code_page( 1, low_mem ); + set_code_page( 2, low_mem ); + set_code_page( 3, low_mem ); + for ( int i = 4; i < page_count + 1; i++ ) + set_code_page( i, (uint8_t*) unmapped_page ); + + #ifndef NDEBUG + blargg_verify_byte_order(); + #endif +} + +void Nes_Cpu::map_code( nes_addr_t start, unsigned size, const void* data ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= 0x10000 ); + + unsigned first_page = start / page_size; + for ( unsigned i = size / page_size; i--; ) + set_code_page( first_page + i, (uint8_t*) data + i * page_size ); +} + +// Note: 'addr' is evaulated more than once in the following macros, so it +// must not contain side-effects. + +//static void log_read( int opcode ) { LOG_FREQ( "read", 256, opcode ); } + +#define READ_LIKELY_PPU( addr ) (NES_CPU_READ_PPU( this, (addr), (clock_count) )) +#define READ( addr ) (NES_CPU_READ( this, (addr), (clock_count) )) +#define WRITE( addr, data ) {NES_CPU_WRITE( this, (addr), (data), (clock_count) );} + +#define READ_LOW( addr ) (low_mem [int (addr)]) +#define WRITE_LOW( addr, data ) (void) (READ_LOW( addr ) = (data)) + +#define READ_PROG( addr ) (code_map [(addr) >> page_bits] [addr]) +#define READ_PROG16( addr ) GET_LE16( &READ_PROG( addr ) ) + +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define PUSH( v ) ((sp = (sp - 1) | 0x100), WRITE_LOW( sp, v )) + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +int Nes_Cpu::read( nes_addr_t addr ) +{ + return READ( addr ); +} + +void Nes_Cpu::write( nes_addr_t addr, int value ) +{ + WRITE( addr, value ); +} + +#ifndef NES_CPU_GLUE_ONLY + +static const unsigned char clock_table [256] = { +// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 7,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,2,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 3,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 3,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 3,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F +}; + +Nes_Cpu::result_t Nes_Cpu::run( nes_time_t end ) +{ + set_end_time_( end ); + clock_count = 0; + + volatile result_t result = result_cycles; + +#if !BLARGG_CPU_CISC + long clock_count = this->clock_count; + uint8_t* const low_mem = this->low_mem; +#endif + + // registers + unsigned pc = r.pc; + int sp; + SET_SP( r.sp ); + int a = r.a; + int x = r.x; + int y = r.y; + + // status flags + + int const st_n = 0x80; + int const st_v = 0x40; + int const st_r = 0x20; + int const st_b = 0x10; + int const st_d = 0x08; + int const st_i = 0x04; + int const st_z = 0x02; + int const st_c = 0x01; + + #define IS_NEG (nz & 0x880) + + #define CALC_STATUS( out ) do { \ + out = status & (st_v | st_d | st_i); \ + out |= (c >> 8) & st_c; \ + if ( IS_NEG ) out |= st_n; \ + if ( !(nz & 0xFF) ) out |= st_z; \ + } while ( 0 ) + + #define SET_STATUS( in ) do { \ + status = in & (st_v | st_d | st_i); \ + c = in << 8; \ + nz = (in << 4) & 0x800; \ + nz |= ~in & st_z; \ + } while ( 0 ) + + int status; + int c; // carry set if (c & 0x100) != 0 + int nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x880) != 0 + { + int temp = r.status; + SET_STATUS( temp ); + } + + goto loop; +dec_clock_loop: + clock_count--; +loop: + + assert( (unsigned) GET_SP() < 0x100 ); + assert( (unsigned) a < 0x100 ); + assert( (unsigned) x < 0x100 ); + assert( (unsigned) y < 0x100 ); + + uint8_t const* page = code_map [pc >> page_bits]; + unsigned opcode = page [pc]; + pc++; + + if ( clock_count >= clock_limit ) + goto stop; + + clock_count += clock_table [opcode]; + unsigned data; + data = page [pc]; + + switch ( opcode ) + { + +// Macros + +#define GET_OPERAND( addr ) page [addr] +#define GET_OPERAND16( addr ) GET_LE16( &page [addr] ) + +//#define GET_OPERAND( addr ) READ_PROG( addr ) +//#define GET_OPERAND16( addr ) READ_PROG16( addr ) + +#define ADD_PAGE (pc++, data += 0x100 * GET_OPERAND( pc )); +#define GET_ADDR() GET_OPERAND16( pc ) + +#define HANDLE_PAGE_CROSSING( lsb ) clock_count += (lsb) >> 8; + +#define INC_DEC_XY( reg, n ) reg = uint8_t (nz = reg + n); goto loop; + +#define IND_Y(r,c) { \ + int temp = READ_LOW( data ) + y; \ + data = temp + 0x100 * READ_LOW( uint8_t (data + 1) ); \ + if (c) HANDLE_PAGE_CROSSING( temp ); \ + if (!(r) || (temp & 0x100)) \ + READ( data - ( temp & 0x100 ) ); \ + } + +#define IND_X { \ + int temp = data + x; \ + data = 0x100 * READ_LOW( uint8_t (temp + 1) ) + READ_LOW( uint8_t (temp) ); \ + } + +#define ARITH_ADDR_MODES( op ) \ +case op - 0x04: /* (ind,x) */ \ + IND_X \ + goto ptr##op; \ +case op + 0x0C: /* (ind),y */ \ + IND_Y(true,true) \ + goto ptr##op; \ +case op + 0x10: /* zp,X */ \ + data = uint8_t (data + x); \ +case op + 0x00: /* zp */ \ + data = READ_LOW( data ); \ + goto imm##op; \ +case op + 0x14: /* abs,Y */ \ + data += y; \ + goto ind##op; \ +case op + 0x18: /* abs,X */ \ + data += x; \ +ind##op: { \ + HANDLE_PAGE_CROSSING( data ); \ + int temp = data; \ + ADD_PAGE \ + if ( temp & 0x100 ) \ + READ( data - 0x100 ); \ + goto ptr##op; \ +} \ +case op + 0x08: /* abs */ \ + ADD_PAGE \ +ptr##op: \ + data = READ( data ); \ +case op + 0x04: /* imm */ \ +imm##op: \ + +#define ARITH_ADDR_MODES_PTR( op ) \ +case op - 0x04: /* (ind,x) */ \ + IND_X \ + goto imm##op; \ +case op + 0x0C: \ + IND_Y(false,false) \ + goto imm##op; \ +case op + 0x10: /* zp,X */ \ + data = uint8_t (data + x); \ + goto imm##op; \ +case op + 0x14: /* abs,Y */ \ + data += y; \ + goto ind##op; \ +case op + 0x18: /* abs,X */ \ + data += x; \ +ind##op: { \ + int temp = data; \ + ADD_PAGE \ + READ( data - ( temp & 0x100 ) ); \ + goto imm##op; \ +} \ +case op + 0x08: /* abs */ \ + ADD_PAGE \ +case op + 0x00: /* zp */ \ +imm##op: \ + +#define BRANCH( cond ) \ +{ \ + pc++; \ + int offset = (BOOST::int8_t) data; \ + int extra_clock = (pc & 0xFF) + offset; \ + if ( !(cond) ) goto dec_clock_loop; \ + pc += offset; \ + pc = BOOST::uint16_t( pc ); \ + clock_count += (extra_clock >> 8) & 1; \ + goto loop; \ +} + +// Often-Used + + case 0xB5: // LDA zp,x + data = uint8_t (data + x); + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + + case 0xD0: // BNE + BRANCH( (uint8_t) nz ); + + case 0x20: { // JSR + int temp = pc + 1; + pc = GET_OPERAND16( pc ); + WRITE_LOW( 0x100 | (sp - 1), temp >> 8 ); + sp = (sp - 2) | 0x100; + WRITE_LOW( sp, temp ); + goto loop; + } + + case 0x4C: // JMP abs + pc = GET_OPERAND16( pc ); + goto loop; + + case 0xE8: INC_DEC_XY( x, 1 ) // INX + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0xF0: // BEQ + BRANCH( !(uint8_t) nz ); + + case 0x95: // STA zp,x + data = uint8_t (data + x); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xC8: INC_DEC_XY( y, 1 ) // INY + + case 0xA8: // TAY + y = a; + case 0x98: // TYA + a = nz = y; + goto loop; + + case 0xAD:{// LDA abs + unsigned addr = GET_ADDR(); + pc += 2; + a = nz = READ_LIKELY_PPU( addr ); + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_LOW( sp ); + pc += READ_LOW( 0x100 | (sp - 0xFF) ) * 0x100; + sp = (sp - 0xFE) | 0x100; + goto loop; + + case 0x99: // STA abs,Y + data += y; + goto sta_ind_common; + + case 0x9D: // STA abs,X + data += x; + sta_ind_common: { + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + goto sta_ptr; + } + case 0x8D: // STA abs + ADD_PAGE + sta_ptr: + pc++; + WRITE( data, a ); + goto loop; + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + +#if 0 + case 0xA1: // LDA (ind,X) + IND_X + goto lda_ptr; + + case 0xB1: // LDA (ind),Y + IND_Y(true,true) + goto lda_ptr; + + case 0xB9: // LDA abs,Y + data += y; + goto lda_ind_common; + + case 0xBD: // LDA abs,X + data += x; + lda_ind_common: { + HANDLE_PAGE_CROSSING( data ); + int temp = data; + ADD_PAGE + if ( temp & 0x100 ) + READ( data - 0x100 ); + } + lda_ptr: + a = nz = READ( data ); + pc++; + goto loop; +#else + // optimization of most commonly used memory read instructions + + case 0xB9:// LDA abs,Y + data += y; + data -= x; + case 0xBD:{// LDA abs,X + pc++; + unsigned msb = GET_OPERAND( pc ); + data += x; + // indexed common + pc++; + HANDLE_PAGE_CROSSING( data ); + int temp = data; + data += msb * 0x100; + a = nz = READ_PROG( BOOST::uint16_t( data ) ); + if ( (unsigned) (data - 0x2000) >= 0x6000 ) + goto loop; + if ( temp & 0x100 ) + READ( data - 0x100 ); + a = nz = READ( data ); + goto loop; + } + + case 0xB1:{// LDA (ind),Y + unsigned msb = READ_LOW( (uint8_t) (data + 1) ); + data = READ_LOW( data ) + y; + // indexed common + pc++; + HANDLE_PAGE_CROSSING( data ); + int temp = data; + data += msb * 0x100; + a = nz = READ_PROG( BOOST::uint16_t( data ) ); + if ( (unsigned) (data - 0x2000) >= 0x6000 ) + goto loop; + if ( temp & 0x100 ) + READ( data - 0x100 ); + a = nz = READ( data ); + goto loop; + } + + case 0xA1: // LDA (ind,X) + IND_X + a = nz = READ( data ); + pc++; + goto loop; + +#endif + +// Branch + + case 0x50: // BVC + BRANCH( !(status & st_v) ) + + case 0x70: // BVS + BRANCH( status & st_v ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + +// Load/store + + case 0x94: // STY zp,x + data = uint8_t (data + x); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = uint8_t (data + y); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = uint8_t (data + y); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = uint8_t (data + x); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0x91: // STA (ind),Y + IND_Y(false,false) + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X + goto sta_ptr; + + case 0xBC: // LDY abs,X + data += x; + HANDLE_PAGE_CROSSING( data ); + case 0xAC:{// LDY abs + pc++; + unsigned addr = data + 0x100 * GET_OPERAND( pc ); + if ( data & 0x100 ) + READ( addr - 0x100 ); + pc++; + y = nz = READ( addr ); + goto loop; + } + + case 0xBE: // LDX abs,y + data += y; + HANDLE_PAGE_CROSSING( data ); + case 0xAE:{// LDX abs + pc++; + unsigned addr = data + 0x100 * GET_OPERAND( pc ); + pc++; + if ( data & 0x100 ) + READ( addr - 0x100 ); + x = nz = READ( addr ); + goto loop; + } + + { + int temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + unsigned addr = GET_ADDR(); + WRITE( addr, temp ); + pc += 2; + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + unsigned addr = GET_ADDR(); + pc++; + data = READ( addr ); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0xCC:{// CPY abs + unsigned addr = GET_ADDR(); + pc++; + data = READ( addr ); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + +// Logical + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + + case 0x2C:{// BIT abs + unsigned addr = GET_ADDR(); + pc += 2; + status &= ~st_v; + nz = READ_LIKELY_PPU( addr ); + status |= nz & st_v; + if ( a & nz ) + goto loop; + // result must be zero, even if N bit is set + nz = nz << 4 & 0x800; + goto loop; + } + + case 0x24: // BIT zp + nz = READ_LOW( data ); + pc++; + status &= ~st_v; + status |= nz & st_v; + if ( a & nz ) + goto loop; + // result must be zero, even if N bit is set + nz = nz << 4 & 0x800; + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + int carry = (c >> 8) & 1; + int ov = (a ^ 0x80) + carry + (BOOST::int8_t) data; // sign-extend + status &= ~st_v; + status |= (ov >> 2) & 0x40; + c = nz = a + data + carry; + pc++; + a = (uint8_t) nz; + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + lsr_a: + c = 0; + case 0x6A: // ROR A + nz = (c >> 1) & 0x80; // could use bit insert macro here + c = a << 8; + nz |= a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = (uint8_t) nz; + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + int temp = (c >> 8) & 1; + c = nz; + nz |= temp; + a = (uint8_t) nz; + goto loop; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: { + int temp = data; + ADD_PAGE + if ( opcode == 0x1E || opcode == 0x3E ) READ( data - ( temp & 0x100 ) ); + WRITE( data, temp = READ( data ) ); + nz = (c >> 8) & 1; + nz |= (c = temp << 1); + } + rotate_common: + pc++; + WRITE( data, (uint8_t) nz ); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + int temp = data; + ADD_PAGE + if ( opcode == 0x5E || opcode == 0x7E ) READ( data - ( temp & 0x100 ) ); + WRITE( data, temp = READ( data ) ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x76: // ROR zp,x + data = uint8_t (data + x); + goto ror_zp; + + case 0x56: // LSR zp,x + data = uint8_t (data + x); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = uint8_t (data + x); + goto rol_zp; + + case 0x16: // ASL zp,x + data = uint8_t (data + x); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = (c >> 8) & 1; + nz |= (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + + case 0xCA: INC_DEC_XY( x, -1 ) // DEX + + case 0x88: INC_DEC_XY( y, -1 ) // DEY + + case 0xF6: // INC zp,x + data = uint8_t (data + x); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = uint8_t (data + x); + case 0xC6: // DEC zp + nz = -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: { // INC abs,x + int temp = data + x; + data = x + GET_ADDR(); + READ( data - ( temp & 0x100 ) ); + goto inc_ptr; + } + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: { // DEC abs,x + int temp = data + x; + data = x + GET_ADDR(); + READ( data - ( temp & 0x100 ) ); + goto dec_ptr; + } + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = -1; + inc_common: { + int temp; + WRITE( data, temp = READ( data ) ); + nz += temp; + pc += 2; + WRITE( data, (uint8_t) nz ); + goto loop; + } + +// Transfer + + case 0xAA: // TAX + x = a; + case 0x8A: // TXA + a = nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + +// Stack + + case 0x48: // PHA + PUSH( a ); // verified + goto loop; + + case 0x68: // PLA + a = nz = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + goto loop; + + case 0x40: // RTI + { + int temp = READ_LOW( sp ); + pc = READ_LOW( 0x100 | (sp - 0xFF) ); + pc |= READ_LOW( 0x100 | (sp - 0xFE) ) * 0x100; + sp = (sp - 0xFD) | 0x100; + data = status; + SET_STATUS( temp ); + } + if ( !((data ^ status) & st_i) ) + goto loop; // I flag didn't change + i_flag_changed: + //dprintf( "%6d %s\n", time(), (status & st_i ? "SEI" : "CLI") ); + this->r.status = status; // update externally-visible I flag + // update clock_limit based on modified I flag + clock_limit = end_time_; + if ( end_time_ <= irq_time_ ) + goto loop; + if ( status & st_i ) + goto loop; + clock_limit = irq_time_; + goto loop; + + case 0x28:{// PLP + int temp = READ_LOW( sp ); + sp = (sp - 0xFF) | 0x100; + data = status; + SET_STATUS( temp ); + if ( !((data ^ status) & st_i) ) + goto loop; // I flag didn't change + if ( !(status & st_i) ) + goto handle_cli; + goto handle_sei; + } + + case 0x08: { // PHP + int temp; + CALC_STATUS( temp ); + PUSH( temp | st_b | st_r ); + goto loop; + } + + case 0x6C: // JMP (ind) + data = GET_ADDR(); + pc = READ( data ); + pc |= READ( (data & 0xFF00) | ((data + 1) & 0xFF) ) << 8; + goto loop; + + case 0x00: { // BRK + pc++; + WRITE_LOW( 0x100 | (sp - 1), pc >> 8 ); + WRITE_LOW( 0x100 | (sp - 2), pc ); + int temp; + CALC_STATUS( temp ); + sp = (sp - 3) | 0x100; + WRITE_LOW( sp, temp | st_b | st_r ); + pc = GET_LE16( &code_map [0xFFFE >> page_bits] [0xFFFE] ); + status |= st_i; + goto i_flag_changed; + } + +// Flags + + case 0x38: // SEC + c = ~0; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + status &= ~st_v; + goto loop; + + case 0xD8: // CLD + status &= ~st_d; + goto loop; + + case 0xF8: // SED + status |= st_d; + goto loop; + + case 0x58: // CLI + if ( !(status & st_i) ) + goto loop; + status &= ~st_i; + handle_cli: + //dprintf( "%6d CLI\n", time() ); + this->r.status = status; // update externally-visible I flag + if ( clock_count < end_time_ ) + { + assert( clock_limit == end_time_ ); + if ( end_time_ <= irq_time_ ) + goto loop; // irq is later + if ( clock_count >= irq_time_ ) + irq_time_ = clock_count + 1; // delay IRQ until after next instruction + clock_limit = irq_time_; + goto loop; + } + // execution is stopping now, so delayed CLI must be handled by caller + result = result_cli; + goto end; + + case 0x78: // SEI + if ( status & st_i ) + goto loop; + status |= st_i; + handle_sei: + //dprintf( "%6d SEI\n", time() ); + this->r.status = status; // update externally-visible I flag + clock_limit = end_time_; + if ( clock_count < irq_time_ ) + goto loop; + result = result_sei; // IRQ will occur now, even though I flag is set + goto end; + +// Unofficial + case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: { // SKW + data += x; + HANDLE_PAGE_CROSSING( data ); + int addr = GET_ADDR() + x; + if ( data & 0x100 ) + READ( addr - 0x100 ); + READ( addr ); + } + case 0x0C: // SKW + pc++; + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: // SKB + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + pc++; + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: // NOP + goto loop; + + ARITH_ADDR_MODES_PTR( 0xC7 ) // DCP + WRITE( data, nz = READ( data ) ); + nz = uint8_t( nz - 1 ); + WRITE( data, nz ); + pc++; + nz = a - nz; + c = ~nz; + nz &= 0xFF; + goto loop; + + ARITH_ADDR_MODES_PTR( 0xE7 ) // ISC + WRITE( data, nz = READ( data ) ); + nz = uint8_t( nz + 1 ); + WRITE( data, nz ); + data = nz ^ 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES_PTR( 0x27 ) { // RLA + WRITE( data, nz = READ( data ) ); + int temp = c; + c = nz << 1; + nz = uint8_t( c ) | ( ( temp >> 8 ) & 0x01 ); + WRITE( data, nz ); + pc++; + nz = a &= nz; + goto loop; + } + + ARITH_ADDR_MODES_PTR( 0x67 ) { // RRA + int temp; + WRITE( data, temp = READ( data ) ); + nz = ((c >> 1) & 0x80) | (temp >> 1); + WRITE( data, nz ); + data = nz; + c = temp << 8; + goto adc_imm; + } + + ARITH_ADDR_MODES_PTR( 0x07 ) // SLO + WRITE( data, nz = READ( data ) ); + c = nz << 1; + nz = uint8_t( c ); + WRITE( data, nz ); + nz = (a |= nz); + pc++; + goto loop; + + ARITH_ADDR_MODES_PTR( 0x47 ) // SRE + WRITE( data, nz = READ( data ) ); + c = nz << 8; + nz >>= 1; + WRITE( data, nz ); + nz = a ^= nz; + pc++; + goto loop; + + case 0x4B: // ALR + nz = (a &= data); + pc++; + goto lsr_a; + + case 0x0B: // ANC + case 0x2B: + nz = a &= data; + c = a << 1; + pc++; + goto loop; + + case 0x6B: // ARR + nz = a = uint8_t( ( ( data & a ) >> 1 ) | ( ( c >> 1 ) & 0x80 ) ); + c = a << 2; + status = ( status & ~st_v ) | ( ( a ^ a << 1 ) & st_v ); + pc++; + goto loop; + + case 0xAB: // LXA + a = data; + x = data; + nz = data; + pc++; + goto loop; + + case 0xA3: // LAX + IND_X + goto lax_ptr; + + case 0xB3: + IND_Y(true,true) + goto lax_ptr; + + case 0xB7: + data = uint8_t (data + y); + + case 0xA7: + data = READ_LOW( data ); + goto lax_imm; + + case 0xBF: { + data += y; + HANDLE_PAGE_CROSSING( data ); + int temp = data; + ADD_PAGE; + if ( temp & 0x100 ) + READ( data - 0x100 ); + goto lax_ptr; + } + + case 0xAF: + ADD_PAGE + + lax_ptr: + data = READ( data ); + lax_imm: + nz = x = a = data; + pc++; + goto loop; + + case 0x83: // SAX + IND_X + goto sax_imm; + + case 0x97: + data = uint8_t (data + y); + goto sax_imm; + + case 0x8F: + ADD_PAGE + + case 0x87: + sax_imm: + WRITE( data, a & x ); + pc++; + goto loop; + + case 0xCB: // SBX + data = ( a & x ) - data; + c = ( data <= 0xFF ) ? 0x100 : 0; + nz = x = uint8_t( data ); + pc++; + goto loop; + + case 0x93: // SHA (ind),Y + IND_Y(false,false) + pc++; + WRITE( data, uint8_t( a & x & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + + case 0x9F: { // SHA abs,Y + data += y; + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + pc++; + WRITE( data, uint8_t( a & x & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + } + + case 0x9E: { // SHX abs,Y + data += y; + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + pc++; + if ( !( temp & 0x100 ) ) + WRITE( data, uint8_t( x & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + } + + case 0x9C: { // SHY abs,X + data += x; + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + pc++; + if ( !( temp & 0x100) ) + WRITE( data, uint8_t( y & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + } + + case 0x9B: { // SHS abs,Y + data += y; + int temp = data; + ADD_PAGE + READ( data - ( temp & 0x100 ) ); + pc++; + SET_SP( a & x ); + WRITE( data, uint8_t( a & x & ( ( data >> 8 ) + 1 ) ) ); + goto loop; + } + + case 0xBB: { // LAS abs,Y + data += y; + HANDLE_PAGE_CROSSING( data ); + int temp = data; + ADD_PAGE + if ( temp & 0x100 ) + READ( data - 0x100 ); + pc++; + a = GET_SP(); + x = a &= READ( data ); + SET_SP( a ); + goto loop; + } + +// Unimplemented + + case page_wrap_opcode: // HLT + if ( pc > 0x10000 ) + { + // handle wrap-around (assumes caller has put page of HLT at 0x10000) + pc = (pc - 1) & 0xFFFF; + clock_count -= 2; + goto loop; + } + // fall through + default: + // skip over proper number of bytes + static unsigned char const row [8] = { 0x95, 0x95, 0x95, 0xd5, 0x95, 0x95, 0xd5, 0xf5 }; + int len = row [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; + if ( opcode == 0x9C ) + len = 3; + pc += len - 1; + error_count_++; + goto loop; + + //result = result_badop; // TODO: re-enable + goto stop; + } + + // If this fails then the case above is missing an opcode + assert( false ); + +stop: + pc--; +end: + + { + int temp; + CALC_STATUS( temp ); + r.status = temp; + } + + this->clock_count = clock_count; + r.pc = pc; + r.sp = GET_SP(); + r.a = a; + r.x = x; + r.y = y; + irq_time_ = LONG_MAX / 2 + 1; + + return result; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Cpu.h b/quicknes/nes_emu/Nes_Cpu.h new file mode 100644 index 0000000000..0e55169403 --- /dev/null +++ b/quicknes/nes_emu/Nes_Cpu.h @@ -0,0 +1,128 @@ + +// NES 6502 CPU emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_CPU_H +#define NES_CPU_H + +#include "blargg_common.h" + +typedef long nes_time_t; // clock cycle count +typedef unsigned nes_addr_t; // 16-bit address + +class Nes_Cpu { +public: + typedef BOOST::uint8_t uint8_t; + + // Clear registers, unmap memory, and map code pages to unmapped_page. + void reset( void const* unmapped_page = 0 ); + + // Map code memory (memory accessed via the program counter). Start and size + // must be multiple of page_size. + enum { page_bits = 11 }; + enum { page_count = 0x10000 >> page_bits }; + enum { page_size = 1L << page_bits }; + void map_code( nes_addr_t start, unsigned size, void const* code ); + + // Access memory as the emulated CPU does. + int read( nes_addr_t ); + void write( nes_addr_t, int data ); + uint8_t* get_code( nes_addr_t ); // non-const to allow debugger to modify code + + // Push a byte on the stack + void push_byte( int ); + + // NES 6502 registers. *Not* kept updated during a call to run(). + struct registers_t { + long pc; // more than 16 bits to allow overflow detection + BOOST::uint8_t a; + BOOST::uint8_t x; + BOOST::uint8_t y; + BOOST::uint8_t status; + BOOST::uint8_t sp; + }; + //registers_t r; + + // Reasons that run() returns + enum result_t { + result_cycles, // Requested number of cycles (or more) were executed + result_sei, // I flag just set and IRQ time would generate IRQ now + result_cli, // I flag just cleared but IRQ should occur *after* next instr + result_badop // unimplemented/illegal instruction + }; + + result_t run( nes_time_t end_time ); + + nes_time_t time() const { return clock_count; } + void reduce_limit( int offset ); + void set_end_time_( nes_time_t t ); + void set_irq_time_( nes_time_t t ); + unsigned long error_count() const { return error_count_; } + + // If PC exceeds 0xFFFF and encounters page_wrap_opcode, it will be silently wrapped. + enum { page_wrap_opcode = 0xF2 }; + + // One of the many opcodes that are undefined and stop CPU emulation. + enum { bad_opcode = 0xD2 }; + +private: + uint8_t const* code_map [page_count + 1]; + nes_time_t clock_limit; + nes_time_t clock_count; + nes_time_t irq_time_; + nes_time_t end_time_; + unsigned long error_count_; + + enum { irq_inhibit = 0x04 }; + void set_code_page( int, uint8_t const* ); + void update_clock_limit(); + +public: + registers_t r; + + // low_mem is a full page size so it can be mapped with code_map + uint8_t low_mem [page_size > 0x800 ? page_size : 0x800]; +}; + +inline BOOST::uint8_t* Nes_Cpu::get_code( nes_addr_t addr ) +{ + return (uint8_t*) code_map [addr >> page_bits] + addr; +} + +inline void Nes_Cpu::update_clock_limit() +{ + nes_time_t t = end_time_; + if ( t > irq_time_ && !(r.status & irq_inhibit) ) + t = irq_time_; + clock_limit = t; +} + +inline void Nes_Cpu::set_end_time_( nes_time_t t ) +{ + end_time_ = t; + update_clock_limit(); +} + +inline void Nes_Cpu::set_irq_time_( nes_time_t t ) +{ + irq_time_ = t; + update_clock_limit(); +} + +inline void Nes_Cpu::reduce_limit( int offset ) +{ + clock_limit -= offset; + end_time_ -= offset; + irq_time_ -= offset; +} + +inline void Nes_Cpu::push_byte( int data ) +{ + int sp = r.sp; + r.sp = (sp - 1) & 0xFF; + low_mem [0x100 + sp] = data; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Effects_Buffer.cpp b/quicknes/nes_emu/Nes_Effects_Buffer.cpp new file mode 100644 index 0000000000..27acb356e6 --- /dev/null +++ b/quicknes/nes_emu/Nes_Effects_Buffer.cpp @@ -0,0 +1,84 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/libs/ + +#include "Nes_Effects_Buffer.h" + +#include "Nes_Apu.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Nes_Effects_Buffer::Nes_Effects_Buffer() : + Effects_Buffer( true ) // nes never uses stereo channels +{ + config_t c; + c.effects_enabled = false; + config( c ); +} + +Nes_Effects_Buffer::~Nes_Effects_Buffer() { } + +Multi_Buffer* set_apu( Nes_Effects_Buffer* buf, Nes_Apu* apu ) +{ + buf->set_apu( apu ); + return buf; +} + +void Nes_Effects_Buffer::enable_nonlinearity( bool b ) +{ + if ( b ) + clear(); + Nes_Apu* apu = nonlin.enable( b, channel( 2 ).center ); + apu->osc_output( 0, channel( 0 ).center ); + apu->osc_output( 1, channel( 1 ).center ); +} + +void Nes_Effects_Buffer::config( const config_t& in ) +{ + config_t c = in; + if ( !c.effects_enabled ) + { + // effects must always be enabled to keep separate buffers, so + // set parameters to be equivalent to disabled + c.pan_1 = 0; + c.pan_2 = 0; + c.echo_level = 0; + c.reverb_level = 0; + c.effects_enabled = true; + } + Effects_Buffer::config( c ); +} + +blargg_err_t Nes_Effects_Buffer::set_sample_rate( long rate, int msec ) +{ + enable_nonlinearity( nonlin.enabled ); // reapply + return Effects_Buffer::set_sample_rate( rate, msec ); +} + +void Nes_Effects_Buffer::clear() +{ + nonlin.clear(); + Effects_Buffer::clear(); +} + +Nes_Effects_Buffer::channel_t Nes_Effects_Buffer::channel( int i ) +{ + return Effects_Buffer::channel( (2 <= i && i <= 4) ? 2 : i & 1 ); +} + +long Nes_Effects_Buffer::read_samples( blip_sample_t* out, long count ) +{ + count = 2 * nonlin.make_nonlinear( *channel( 2 ).center, count / 2 ); + return Effects_Buffer::read_samples( out, count ); +} + diff --git a/quicknes/nes_emu/Nes_Effects_Buffer.h b/quicknes/nes_emu/Nes_Effects_Buffer.h new file mode 100644 index 0000000000..1861114f1a --- /dev/null +++ b/quicknes/nes_emu/Nes_Effects_Buffer.h @@ -0,0 +1,38 @@ + +// Effects_Buffer with non-linear sound + +// Nes_Emu 0.7.0 + +#ifndef NES_EFFECTS_BUFFER_H +#define NES_EFFECTS_BUFFER_H + +#include "Nes_Buffer.h" +#include "Effects_Buffer.h" + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Nes_Effects_Buffer : public Effects_Buffer { +public: + Nes_Effects_Buffer(); + ~Nes_Effects_Buffer(); + + // Setup APU for use with buffer, including setting its output to this buffer. + // If you're using Nes_Emu, this is automatically called for you. + void set_apu( Nes_Apu* apu ) { nonlin.set_apu( apu ); } + + // Enable/disable non-linear output + void enable_nonlinearity( bool = true ); + + // See Effects_Buffer.h for reference + blargg_err_t set_sample_rate( long rate, int msec = blip_default_length ); + void config( const config_t& ); + void clear(); + channel_t channel( int ); + long read_samples( blip_sample_t*, long ); + +private: + Nes_Nonlinearizer nonlin; + friend Multi_Buffer* set_apu( Nes_Effects_Buffer*, Nes_Apu* ); +}; + +#endif + diff --git a/quicknes/nes_emu/Nes_Emu.cpp b/quicknes/nes_emu/Nes_Emu.cpp new file mode 100644 index 0000000000..5b2a1ddc6b --- /dev/null +++ b/quicknes/nes_emu/Nes_Emu.cpp @@ -0,0 +1,494 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Emu.h" + +#include +#include "Nes_State.h" +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int const sound_fade_size = 384; + +// Constants are manually duplicated in Nes_Emu so their value can be seen +// directly, rather than having to look in Nes_Ppu.h. "0 +" converts to int. +BOOST_STATIC_ASSERT( Nes_Emu::image_width == 0 + Nes_Ppu::image_width ); +BOOST_STATIC_ASSERT( Nes_Emu::image_height == 0 + Nes_Ppu::image_height ); + +Nes_Emu::equalizer_t const Nes_Emu::nes_eq = { -1.0, 80 }; +Nes_Emu::equalizer_t const Nes_Emu::famicom_eq = { -15.0, 80 }; +Nes_Emu::equalizer_t const Nes_Emu::tv_eq = { -12.0, 180 }; + +Nes_Emu::Nes_Emu() +{ + frame_ = &single_frame; + buffer_height_ = Nes_Ppu::buffer_height + 2; + default_sound_buf = NULL; + sound_buf = &silent_buffer; + sound_buf_changed_count = 0; + equalizer_ = nes_eq; + channel_count_ = 0; + sound_enabled = false; + host_pixels = NULL; + single_frame.pixels = 0; + single_frame.top = 0; + init_called = false; + set_palette_range( 0 ); + memset( single_frame.palette, 0, sizeof single_frame.palette ); +} + +Nes_Emu::~Nes_Emu() +{ + delete default_sound_buf; +} + +blargg_err_t Nes_Emu::init_() +{ + return emu.init(); +} + +inline blargg_err_t Nes_Emu::auto_init() +{ + if ( !init_called ) + { + RETURN_ERR( init_() ); + init_called = true; + } + return 0; +} + +inline void Nes_Emu::clear_sound_buf() +{ + fade_sound_out = false; + fade_sound_in = true; + sound_buf->clear(); +} + +// Emulation + +void Nes_Emu::close() +{ + if ( cart() ) + { + emu.close(); + private_cart.clear(); + } +} + +blargg_err_t Nes_Emu::set_cart( Nes_Cart const* new_cart ) +{ + close(); + RETURN_ERR( auto_init() ); + RETURN_ERR( emu.open( new_cart ) ); + + channel_count_ = Nes_Apu::osc_count + emu.mapper->channel_count(); + RETURN_ERR( sound_buf->set_channel_count( channel_count() ) ); + set_equalizer( equalizer_ ); + enable_sound( true ); + + reset(); + + return 0; +} + +void Nes_Emu::reset( bool full_reset, bool erase_battery_ram ) +{ + require( cart() ); + + clear_sound_buf(); + set_timestamp( 0 ); + emu.reset( full_reset, erase_battery_ram ); +} + +void Nes_Emu::set_palette_range( int begin, int end ) +{ + require( (unsigned) end <= 0x100 ); + // round up to alignment + emu.ppu.palette_begin = (begin + palette_alignment - 1) & ~(palette_alignment - 1); + host_palette_size = end - emu.ppu.palette_begin; + require( host_palette_size >= palette_alignment ); +} + +blargg_err_t Nes_Emu::emulate_frame( int joypad1, int joypad2 ) +{ + emu.current_joypad [0] = (joypad1 |= ~0xFF); + emu.current_joypad [1] = (joypad2 |= ~0xFF); + + emu.ppu.host_pixels = NULL; + + unsigned changed_count = sound_buf->channels_changed_count(); + bool new_enabled = (frame_ != NULL); + if ( sound_buf_changed_count != changed_count || sound_enabled != new_enabled ) + { + sound_buf_changed_count = changed_count; + sound_enabled = new_enabled; + enable_sound( sound_enabled ); + } + + frame_t* f = frame_; + if ( f ) + { + emu.ppu.max_palette_size = host_palette_size; + emu.ppu.host_palette = f->palette + emu.ppu.palette_begin; + // add black and white for emulator to use (unless emulator uses entire + // palette for frame) + f->palette [252] = 0x0F; + f->palette [254] = 0x30; + f->palette [255] = 0x0F; + if ( host_pixels ) + emu.ppu.host_pixels = (BOOST::uint8_t*) host_pixels + + emu.ppu.host_row_bytes * f->top; + + if ( sound_buf->samples_avail() ) + clear_sound_buf(); + + nes_time_t frame_len = emu.emulate_frame(); + sound_buf->end_frame( frame_len, false ); + + f = frame_; + f->sample_count = sound_buf->samples_avail(); + f->chan_count = sound_buf->samples_per_frame(); + f->palette_begin = emu.ppu.palette_begin; + f->palette_size = emu.ppu.palette_size; + f->joypad_read_count = emu.joypad_read_count; + f->burst_phase = emu.ppu.burst_phase; + f->pitch = emu.ppu.host_row_bytes; + f->pixels = emu.ppu.host_pixels + f->left; + } + else + { + emu.ppu.max_palette_size = 0; + emu.emulate_frame(); + } + + return 0; +} + +// Extras + +blargg_err_t Nes_Emu::load_ines( Auto_File_Reader in ) +{ + close(); + RETURN_ERR( private_cart.load_ines( in ) ); + return set_cart( &private_cart ); +} + +blargg_err_t Nes_Emu::save_battery_ram( Auto_File_Writer out ) +{ + RETURN_ERR( out.open() ); + return out->write( emu.impl->sram, emu.impl->sram_size ); +} + +blargg_err_t Nes_Emu::load_battery_ram( Auto_File_Reader in ) +{ + RETURN_ERR( in.open() ); + emu.sram_present = true; + return in->read( emu.impl->sram, emu.impl->sram_size ); +} + +void Nes_Emu::load_state( Nes_State_ const& in ) +{ + clear_sound_buf(); + emu.load_state( in ); +} + +void Nes_Emu::load_state( Nes_State const& in ) +{ + loading_state( in ); + load_state( STATIC_CAST(Nes_State_ const&,in) ); +} + +blargg_err_t Nes_Emu::load_state( Auto_File_Reader in ) +{ + Nes_State* state = BLARGG_NEW Nes_State; + CHECK_ALLOC( state ); + blargg_err_t err = state->read( in ); + if ( !err ) + load_state( *state ); + delete state; + return err; +} + +blargg_err_t Nes_Emu::save_state( Auto_File_Writer out ) const +{ + Nes_State* state = BLARGG_NEW Nes_State; + CHECK_ALLOC( state ); + save_state( state ); + blargg_err_t err = state->write( out ); + delete state; + return err; +} + +void Nes_Emu::write_chr( void const* p, long count, long offset ) +{ + require( (unsigned long) offset <= (unsigned long) chr_size() ); + long end = offset + count; + require( (unsigned long) end <= (unsigned long) chr_size() ); + memcpy( (byte*) chr_mem() + offset, p, count ); + emu.ppu.rebuild_chr( offset, end ); +} + +blargg_err_t Nes_Emu::set_sample_rate( long rate, class Nes_Buffer* buf ) +{ + extern Multi_Buffer* set_apu( class Nes_Buffer*, Nes_Apu* ); + RETURN_ERR( auto_init() ); + return set_sample_rate( rate, set_apu( buf, &emu.impl->apu ) ); +} + +blargg_err_t Nes_Emu::set_sample_rate( long rate, class Nes_Effects_Buffer* buf ) +{ + extern Multi_Buffer* set_apu( class Nes_Effects_Buffer*, Nes_Apu* ); + RETURN_ERR( auto_init() ); + return set_sample_rate( rate, set_apu( buf, &emu.impl->apu ) ); +} + +// Sound + +void Nes_Emu::set_frame_rate( double rate ) +{ + sound_buf->clock_rate( (long) (1789773 / 60.0 * rate) ); +} + +blargg_err_t Nes_Emu::set_sample_rate( long rate, Multi_Buffer* new_buf ) +{ + require( new_buf ); + RETURN_ERR( auto_init() ); + emu.impl->apu.volume( 1.0 ); // cancel any previous non-linearity + RETURN_ERR( new_buf->set_sample_rate( rate, 1200 / frame_rate ) ); + sound_buf = new_buf; + sound_buf_changed_count = 0; + if ( new_buf != default_sound_buf ) + { + delete default_sound_buf; + default_sound_buf = NULL; + } + set_frame_rate( frame_rate ); + return 0; +} + +blargg_err_t Nes_Emu::set_sample_rate( long rate ) +{ + if ( !default_sound_buf ) + CHECK_ALLOC( default_sound_buf = BLARGG_NEW Mono_Buffer ); + return set_sample_rate( rate, default_sound_buf ); +} + +void Nes_Emu::set_equalizer( equalizer_t const& eq ) +{ + equalizer_ = eq; + if ( cart() ) + { + blip_eq_t blip_eq( eq.treble, 0, sound_buf->sample_rate() ); + emu.impl->apu.treble_eq( blip_eq ); + emu.mapper->set_treble( blip_eq ); + sound_buf->bass_freq( equalizer_.bass ); + } +} + +void Nes_Emu::enable_sound( bool enabled ) +{ + if ( enabled ) + { + for ( int i = channel_count(); i-- > 0; ) + { + Blip_Buffer* buf = sound_buf->channel( i ).center; + int mapper_index = i - Nes_Apu::osc_count; + if ( mapper_index < 0 ) + emu.impl->apu.osc_output( i, buf ); + else + emu.mapper->set_channel_buf( mapper_index, buf ); + } + } + else + { + emu.impl->apu.output( NULL ); + for ( int i = channel_count() - Nes_Apu::osc_count; i-- > 0; ) + emu.mapper->set_channel_buf( i, NULL ); + } +} + +void Nes_Emu::fade_samples( blip_sample_t* p, int size, int step ) +{ + if ( size >= sound_fade_size ) + { + if ( step < 0 ) + p += size - sound_fade_size; + + int const shift = 15; + int mul = (1 - step) << (shift - 1); + step *= (1 << shift) / sound_fade_size; + + for ( int n = sound_fade_size; n--; ) + { + *p = (*p * mul) >> 15; + ++p; + mul += step; + } + } +} + +long Nes_Emu::read_samples( short* out, long out_size ) +{ + require( out_size >= sound_buf->samples_avail() ); + long count = sound_buf->read_samples( out, out_size ); + if ( fade_sound_in ) + { + fade_sound_in = false; + fade_samples( out, count, 1 ); + } + + if ( fade_sound_out ) + { + fade_sound_out = false; + fade_sound_in = true; // next buffer should be faded in + fade_samples( out, count, -1 ); + } + return count; +} + +Nes_Emu::rgb_t const Nes_Emu::nes_colors [color_table_size] = +{ + // generated with nes_ntsc default settings + {102,102,102},{ 0, 42,136},{ 20, 18,168},{ 59, 0,164}, + { 92, 0,126},{110, 0, 64},{108, 7, 0},{ 87, 29, 0}, + { 52, 53, 0},{ 12, 73, 0},{ 0, 82, 0},{ 0, 79, 8}, + { 0, 64, 78},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {174,174,174},{ 21, 95,218},{ 66, 64,254},{118, 39,255}, + {161, 27,205},{184, 30,124},{181, 50, 32},{153, 79, 0}, + {108,110, 0},{ 56,135, 0},{ 13,148, 0},{ 0,144, 50}, + { 0,124,142},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {254,254,254},{100,176,254},{147,144,254},{199,119,254}, + {243,106,254},{254,110,205},{254,130,112},{235,159, 35}, + {189,191, 0},{137,217, 0},{ 93,229, 48},{ 69,225,130}, + { 72,206,223},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {254,254,254},{193,224,254},{212,211,254},{233,200,254}, + {251,195,254},{254,197,235},{254,205,198},{247,217,166}, + {229,230,149},{208,240,151},{190,245,171},{180,243,205}, + {181,236,243},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + + {114, 83, 79},{ 0, 23,113},{ 32, 0,145},{ 71, 0,141}, + {104, 0,103},{122, 0, 41},{120, 0, 0},{ 99, 10, 0}, + { 64, 34, 0},{ 24, 54, 0},{ 0, 63, 0},{ 0, 60, 0}, + { 0, 45, 54},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {190,148,143},{ 37, 69,187},{ 83, 38,228},{134, 13,224}, + {177, 1,174},{200, 4, 92},{198, 24, 1},{170, 53, 0}, + {124, 84, 0},{ 73,109, 0},{ 30,122, 0},{ 6,118, 19}, + { 9, 98,110},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {254,222,215},{122,142,254},{168,110,254},{220, 85,254}, + {254, 72,247},{254, 76,164},{254, 96, 71},{254,125, 0}, + {210,157, 0},{158,183, 0},{114,195, 7},{ 90,191, 89}, + { 93,172,182},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {254,222,215},{214,190,233},{233,177,250},{254,166,248}, + {254,161,228},{254,163,194},{254,171,157},{254,183,125}, + {250,196,108},{229,206,110},{211,211,130},{201,210,164}, + {203,202,202},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 75,106, 64},{ 0, 46, 98},{ 0, 22,130},{ 32, 3,126}, + { 65, 0, 88},{ 82, 0, 26},{ 80, 11, 0},{ 59, 34, 0}, + { 24, 58, 0},{ 0, 77, 0},{ 0, 86, 0},{ 0, 83, 0}, + { 0, 68, 39},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {136,180,122},{ 0,101,166},{ 29, 69,208},{ 80, 44,203}, + {123, 32,153},{146, 36, 72},{144, 55, 0},{116, 84, 0}, + { 70,116, 0},{ 19,141, 0},{ 0,153, 0},{ 0,149, 0}, + { 0,130, 90},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {207,254,188},{ 51,183,233},{ 98,151,254},{150,126,254}, + {193,113,220},{217,117,137},{214,137, 45},{186,166, 0}, + {140,198, 0},{ 88,224, 0},{ 44,236, 0},{ 20,232, 63}, + { 23,213,155},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {207,254,188},{144,231,207},{163,218,224},{184,207,222}, + {201,202,201},{211,204,168},{210,212,130},{198,224, 99}, + {180,237, 81},{159,247, 83},{141,252,104},{131,251,137}, + {132,243,175},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 83, 83, 55},{ 0, 23, 89},{ 0, 0,121},{ 40, 0,117}, + { 73, 0, 79},{ 90, 0, 17},{ 88, 0, 0},{ 67, 10, 0}, + { 32, 34, 0},{ 0, 53, 0},{ 0, 63, 0},{ 0, 60, 0}, + { 0, 45, 30},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {147,148,110},{ 0, 69,154},{ 40, 38,196},{ 91, 12,191}, + {134, 0,141},{157, 4, 60},{155, 23, 0},{127, 52, 0}, + { 81, 84, 0},{ 30,109, 0},{ 0,121, 0},{ 0,117, 0}, + { 0, 98, 78},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {221,222,173},{ 65,142,217},{112,110,254},{164, 84,255}, + {208, 72,204},{231, 76,122},{229, 95, 29},{200,125, 0}, + {154,157, 0},{102,182, 0},{ 58,195, 0},{ 34,191, 47}, + { 37,171,140},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {221,222,173},{158,189,191},{177,176,208},{198,166,206}, + {216,161,185},{225,163,152},{224,171,114},{213,183, 83}, + {194,195, 66},{173,206, 68},{155,211, 88},{145,209,122}, + {146,201,159},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 87, 87,133},{ 0, 26,167},{ 5, 2,198},{ 44, 0,195}, + { 77, 0,157},{ 95, 0, 94},{ 93, 0, 25},{ 71, 14, 0}, + { 36, 38, 0},{ 0, 57, 0},{ 0, 66, 0},{ 0, 63, 38}, + { 0, 49,108},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {153,153,216},{ 0, 74,254},{ 46, 43,254},{ 97, 17,254}, + {140, 5,247},{164, 9,165},{161, 28, 74},{133, 57, 0}, + { 87, 89, 0},{ 36,114, 0},{ 0,126, 10},{ 0,122, 92}, + { 0,103,183},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {229,228,254},{ 74,148,254},{120,116,254},{172, 91,254}, + {216, 78,254},{239, 82,254},{237,102,166},{208,131, 89}, + {162,163, 46},{110,189, 51},{ 66,201,102},{ 42,197,184}, + { 45,178,254},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {229,228,254},{166,196,254},{185,183,254},{206,172,254}, + {224,167,254},{233,169,254},{232,177,252},{221,189,220}, + {202,202,203},{181,212,205},{163,217,226},{153,216,254}, + {154,208,254},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 90, 71, 97},{ 0, 11,130},{ 8, 0,162},{ 47, 0,158}, + { 80, 0,120},{ 98, 0, 58},{ 96, 0, 0},{ 74, 0, 0}, + { 39, 22, 0},{ 0, 42, 0},{ 0, 51, 0},{ 0, 48, 2}, + { 0, 33, 72},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {158,132,166},{ 4, 53,210},{ 50, 22,252},{101, 0,247}, + {144, 0,197},{168, 0,116},{165, 7, 25},{137, 36, 0}, + { 91, 68, 0},{ 40, 93, 0},{ 0,105, 0},{ 0,101, 42}, + { 0, 82,134},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {234,201,246},{ 79,121,254},{125, 89,254},{177, 63,254}, + {221, 51,254},{245, 55,195},{242, 74,102},{214,104, 24}, + {167,136, 0},{115,161, 0},{ 71,174, 37},{ 48,170,120}, + { 50,150,213},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {234,201,246},{171,168,254},{190,155,254},{211,145,254}, + {229,140,254},{239,142,225},{237,150,187},{226,162,156}, + {207,174,139},{186,185,141},{168,190,161},{159,188,195}, + {160,180,232},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 66, 85, 88},{ 0, 25,121},{ 0, 1,153},{ 23, 0,149}, + { 56, 0,111},{ 74, 0, 49},{ 72, 0, 0},{ 51, 12, 0}, + { 16, 36, 0},{ 0, 55, 0},{ 0, 65, 0},{ 0, 62, 0}, + { 0, 47, 63},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {125,151,154},{ 0, 72,198},{ 17, 40,240},{ 69, 15,235}, + {112, 3,185},{135, 7,104},{132, 26, 12},{104, 55, 0}, + { 59, 87, 0},{ 7,112, 0},{ 0,124, 0},{ 0,120, 30}, + { 0,101,121},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {192,225,230},{ 37,145,254},{ 83,114,254},{135, 88,254}, + {179, 76,254},{202, 80,179},{200, 99, 86},{171,129, 8}, + {125,160, 0},{ 73,186, 0},{ 29,198, 21},{ 5,194,104}, + { 8,175,197},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {192,225,230},{129,193,248},{148,180,254},{169,170,254}, + {187,165,242},{196,166,209},{195,174,171},{184,186,140}, + {165,199,123},{144,209,125},{126,214,145},{116,213,179}, + {118,205,216},{184,184,184},{ 0, 0, 0},{ 0, 0, 0}, + { 69, 69, 69},{ 0, 16,110},{ 0, 0,142},{ 33, 0,138}, + { 66, 0,100},{ 84, 0, 38},{ 82, 0, 0},{ 60, 3, 0}, + { 25, 27, 0},{ 0, 46, 0},{ 0, 56, 0},{ 0, 53, 0}, + { 0, 38, 51},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {134,134,134},{ 0, 64,187},{ 35, 32,228},{ 86, 7,223}, + {129, 0,174},{153, 0, 92},{150, 18, 1},{122, 47, 0}, + { 76, 79, 0},{ 25,104, 0},{ 0,116, 0},{ 0,112, 19}, + { 0, 93,110},{ 0, 0, 0},{ 0, 0, 0},{ 0, 0, 0}, + {207,207,207},{ 60,136,254},{107,104,254},{159, 79,254}, + {203, 66,248},{226, 70,165},{224, 90, 72},{195,119, 0}, + {149,151, 0},{ 97,177, 0},{ 53,189, 8},{ 29,185, 91}, + { 32,166,183},{ 79, 79, 79},{ 0, 0, 0},{ 0, 0, 0}, + {207,207,207},{148,178,229},{166,165,246},{188,155,244}, + {205,150,224},{215,152,190},{214,159,152},{202,171,121}, + {183,184,104},{162,195,106},{145,200,126},{135,198,160}, + {136,190,197},{184,184,184},{ 0, 0, 0},{ 0, 0, 0} +}; + diff --git a/quicknes/nes_emu/Nes_Emu.h b/quicknes/nes_emu/Nes_Emu.h new file mode 100644 index 0000000000..81d47f154c --- /dev/null +++ b/quicknes/nes_emu/Nes_Emu.h @@ -0,0 +1,266 @@ + +// NES video game console emulator with snapshot support + +// Nes_Emu 0.7.0 + +#ifndef NES_EMU_H +#define NES_EMU_H + +#include "blargg_common.h" +#include "Multi_Buffer.h" +#include "Nes_Cart.h" +#include "Nes_Core.h" +class Nes_State; + +// Register optional mappers included with Nes_Emu +void register_optional_mappers(); + +extern const char unsupported_mapper []; // returned when cartridge uses unsupported mapper + +class Nes_Emu { +public: + Nes_Emu(); + virtual ~Nes_Emu(); + +// Basic setup + + // Load iNES file into emulator and clear recording + blargg_err_t load_ines( Auto_File_Reader ); + + // Set sample rate for sound generation + blargg_err_t set_sample_rate( long ); + + // Size and depth of graphics buffer required for rendering. Note that this + // is larger than the actual image, with a temporary area around the edge + // that gets filled with junk. Its height is many times larger in Nes_Recorder + // to allow caching of multiple images. + enum { buffer_width = Nes_Ppu::buffer_width }; + int buffer_height() const { return buffer_height_; } + enum { bits_per_pixel = 8 }; + + // Set graphics buffer to render pixels to. Pixels points to top-left pixel and + // row_bytes is the number of bytes to get to the next line (positive or negative). + void set_pixels( void* pixels, long row_bytes ); + + // Size of image generated in graphics buffer + enum { image_width = 256 }; + enum { image_height = 240 }; + +// Basic emulation + + // Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image + // and sound are available for output using the accessors below. + virtual blargg_err_t emulate_frame( int joypad1, int joypad2 = 0 ); + + // Maximum size of palette that can be generated + enum { max_palette_size = 256 }; + + // Result of current frame + struct frame_t + { + int joypad_read_count; // number of times joypads were strobed (read) + int burst_phase; // NTSC burst phase for frame (0, 1, or 2) + + int sample_count; // number of samples (always a multiple of chan_count) + int chan_count; // 1: mono, 2: stereo + + int top; // top-left position of image in graphics buffer + enum { left = 8 }; + unsigned char* pixels; // pointer to top-left pixel of image + long pitch; // number of bytes to get to next row of image + + int palette_begin; // first host palette entry, as set by set_palette_range() + int palette_size; // number of entries used for current frame + short palette [max_palette_size]; // [palette_begin to palette_begin+palette_size-1] + }; + frame_t const& frame() const { return *frame_; } + + // Read samples for the current frame. Returns number of samples read into buffer. + // Currently all samples must be read in one call. + virtual long read_samples( short* out, long max_samples ); + +// Additional features + + // Use already-loaded cartridge. Retains pointer, so it must be kept around until + // closed. A cartridge can be shared among multiple emulators. After opening, + // cartridge's CHR data shouldn't be modified since a copy is cached internally. + blargg_err_t set_cart( Nes_Cart const* ); + + // Pointer to current cartridge, or NULL if none is loaded + Nes_Cart const* cart() const { return emu.cart; } + + // Free any memory and close cartridge, if one was currently open. A new cartridge + // must be opened before further emulation can take place. + void close(); + + // Emulate powering NES off and then back on. If full_reset is false, emulates + // pressing the reset button only, which doesn't affect memory, otherwise + // emulates powering system off then on. + virtual void reset( bool full_reset = true, bool erase_battery_ram = false ); + + // Number of undefined CPU instructions encountered. Cleared after reset() and + // load_state(). A non-zero value indicates that cartridge is probably + // incompatible. + unsigned long error_count() const { return emu.error_count; } + +// Sound + + // Set sample rate and use a custom sound buffer instead of the default + // mono buffer, i.e. Nes_Buffer, Effects_Buffer, etc.. + blargg_err_t set_sample_rate( long rate, Multi_Buffer* ); + + // Adjust effective frame rate by changing how many samples are generated each frame. + // Allows fine tuning of frame rate to improve synchronization. + void set_frame_rate( double rate ); + + // Number of sound channels for current cartridge + int channel_count() const { return channel_count_; } + + // Frequency equalizer parameters + struct equalizer_t { + double treble; // 5.0 = extra-crisp, -200.0 = muffled + long bass; // 0 = deep, 20000 = tinny + }; + + // Current frequency equalization + equalizer_t const& equalizer() const { return equalizer_; } + + // Change frequency equalization + void set_equalizer( equalizer_t const& ); + + // Equalizer presets + static equalizer_t const nes_eq; // NES + static equalizer_t const famicom_eq; // Famicom + static equalizer_t const tv_eq; // TV speaker + +// File save/load + + // Save emulator state + void save_state( Nes_State* s ) const { emu.save_state( s ); } + blargg_err_t save_state( Auto_File_Writer ) const; + + // Load state into emulator + void load_state( Nes_State const& ); + blargg_err_t load_state( Auto_File_Reader ); + + // True if current cartridge claims it uses battery-backed memory + bool has_battery_ram() const { return cart()->has_battery_ram(); } + + // Save current battery RAM + blargg_err_t save_battery_ram( Auto_File_Writer ); + + // Load battery RAM from file. Best called just after reset() or loading cartridge. + blargg_err_t load_battery_ram( Auto_File_Reader ); + +// Graphics + + // Number of frames generated per second + enum { frame_rate = 60 }; + + // Size of fixed NES color table (including the 8 color emphasis modes) + enum { color_table_size = 8 * 64 }; + + // NES color lookup table based on standard NTSC TV decoder. Use nes_ntsc.h to + // generate a palette with custom parameters. + struct rgb_t { unsigned char red, green, blue; }; + static rgb_t const nes_colors [color_table_size]; + + // Hide/show/enhance sprites. Sprite mode does not affect emulation accuracy. + enum sprite_mode_t { + sprites_hidden = 0, + sprites_visible = 8, // limit of 8 sprites per scanline as on NES (default) + sprites_enhanced = 64 // unlimited sprites per scanline (no flickering) + }; + void set_sprite_mode( sprite_mode_t n ) { emu.ppu.sprite_limit = n; } + + // Set range of host palette entries to use in graphics buffer; default uses + // all of them. Begin will be rounded up to next multiple of palette_alignment. + // Use frame().palette_begin to find the adjusted beginning entry used. + enum { palette_alignment = 64 }; + void set_palette_range( int begin, int end = 256 ); + +// Access to emulated memory, for viewer/cheater/debugger + + // CHR + byte const* chr_mem(); + long chr_size() const; + void write_chr( void const*, long count, long offset ); + + // Nametable + byte* nametable_mem() { return emu.ppu.impl->nt_ram; } + long nametable_size() const { return 0x1000; } + + // Built-in 2K memory + enum { low_mem_size = 0x800 }; + byte* low_mem() { return emu.low_mem; } + + // Optional 8K memory + enum { high_mem_size = 0x2000 }; + byte* high_mem() { return emu.impl->sram; } + + // End of public interface +public: + blargg_err_t set_sample_rate( long rate, class Nes_Buffer* ); + blargg_err_t set_sample_rate( long rate, class Nes_Effects_Buffer* ); + void irq_changed() { emu.irq_changed(); } +private: + friend class Nes_Recorder; + + frame_t* frame_; + int buffer_height_; + bool fade_sound_in; + bool fade_sound_out; + virtual blargg_err_t init_(); + + virtual void loading_state( Nes_State const& ) { } + void load_state( Nes_State_ const& ); + void save_state( Nes_State_* s ) const { emu.save_state( s ); } + int joypad_read_count() const { return emu.joypad_read_count; } + long timestamp() const { return emu.nes.frame_count; } + void set_timestamp( long t ) { emu.nes.frame_count = t; } + +private: + // noncopyable + Nes_Emu( const Nes_Emu& ); + Nes_Emu& operator = ( const Nes_Emu& ); + + // sound + Multi_Buffer* default_sound_buf; + Multi_Buffer* sound_buf; + unsigned sound_buf_changed_count; + Silent_Buffer silent_buffer; + equalizer_t equalizer_; + int channel_count_; + bool sound_enabled; + void enable_sound( bool ); + void clear_sound_buf(); + void fade_samples( blip_sample_t*, int size, int step ); + + char* host_pixels; + int host_palette_size; + frame_t single_frame; + Nes_Cart private_cart; + Nes_Core emu; // large; keep at end + + bool init_called; + blargg_err_t auto_init(); +}; + +inline void Nes_Emu::set_pixels( void* p, long n ) +{ + host_pixels = (char*) p + n; + emu.ppu.host_row_bytes = n; +} + +inline byte const* Nes_Emu::chr_mem() +{ + return cart()->chr_size() ? (byte*) cart()->chr() : emu.ppu.impl->chr_ram; +} + +inline long Nes_Emu::chr_size() const +{ + return cart()->chr_size() ? cart()->chr_size() : emu.ppu.chr_addr_size; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_File.cpp b/quicknes/nes_emu/Nes_File.cpp new file mode 100644 index 0000000000..f2f570e61e --- /dev/null +++ b/quicknes/nes_emu/Nes_File.cpp @@ -0,0 +1,226 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_File.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Nes_File_Writer + +Nes_File_Writer::Nes_File_Writer() +{ + write_remain = 0; + depth_ = 0; +} + +Nes_File_Writer::~Nes_File_Writer() +{ +} + +blargg_err_t Nes_File_Writer::begin( Auto_File_Writer dw, nes_tag_t tag ) +{ + require( !out ); + out = dw; + RETURN_ERR( out.open_comp() ); + return begin_group( tag ); +} + +blargg_err_t Nes_File_Writer::begin_group( nes_tag_t tag ) +{ + depth_++; + return write_header( tag, group_begin_size ); +} + +blargg_err_t Nes_File_Writer::write_header( nes_tag_t tag, long size ) +{ + nes_block_t h; + h.tag = tag; + h.size = size; + h.swap(); + return out->write( &h, sizeof h ); +} + +blargg_err_t Nes_File_Writer::write_block( nes_tag_t tag, void const* data, long size ) +{ + RETURN_ERR( write_block_header( tag, size ) ); + return write( data, size ); +} + +blargg_err_t Nes_File_Writer::write_block_header( nes_tag_t tag, long size ) +{ + require( !write_remain ); + write_remain = size; + return write_header( tag, size ); +} + +Nes_File_Writer::error_t Nes_File_Writer::write( void const* p, long s ) +{ + write_remain -= s; + require( write_remain >= 0 ); + return out->write( p, s ); +} + +blargg_err_t Nes_File_Writer::end() +{ + require( depth_ == 1 ); + return end_group(); +} + +blargg_err_t Nes_File_Writer::end_group() +{ + require( depth_ > 0 ); + depth_--; + return write_header( group_end_tag, 0 ); +} + +// Nes_File_Reader + +Nes_File_Reader::Nes_File_Reader() +{ + h.tag = 0; + h.size = 0; + block_type_ = invalid; + depth_ = -1; +} + +Nes_File_Reader::~Nes_File_Reader() +{ +} + +blargg_err_t Nes_File_Reader::read_block_data( void* p, long s ) +{ + long extra = remain(); + if ( s > extra ) + s = extra; + extra -= s; + RETURN_ERR( read( p, s ) ); + if ( extra ) + RETURN_ERR( skip( extra ) ); + return 0; +} + +blargg_err_t Nes_File_Reader::begin( Auto_File_Reader dr ) +{ + require( !in ); + RETURN_ERR( dr.open() ); + in = dr; + RETURN_ERR( read_header() ); + if ( block_type() != group_begin ) + return "File is wrong type"; + return enter_group(); +} + +blargg_err_t Nes_File_Reader::read_header() +{ + RETURN_ERR( in->read( &h, sizeof h ) ); + h.swap(); + block_type_ = data_block; + if ( h.size == group_begin_size ) + { + block_type_ = group_begin; + h.size = 0; + } + if ( (long) h.tag == group_end_tag ) + { + block_type_ = group_end; + h.tag = 0; + } + set_remain( h.size ); + return 0; +} + +blargg_err_t Nes_File_Reader::next_block() +{ + require( depth() >= 0 ); + switch ( block_type() ) + { + case group_end: + require( false ); + return "Tried to go past end of blocks"; + + case group_begin: { + int d = 1; + do + { + RETURN_ERR( skip( h.size ) ); + RETURN_ERR( read_header() ); + if ( block_type() == group_begin ) + d++; + if ( block_type() == group_end ) + d--; + } + while ( d > 0); + break; + } + + case data_block: + RETURN_ERR( skip( h.size ) ); + break; + + case invalid: + break; + } + return read_header(); +} + +blargg_err_t Nes_File_Reader::enter_group() +{ + require( block_type() == group_begin ); + block_type_ = invalid; // cause next_block() not to skip group + depth_++; + return 0; +} + +blargg_err_t Nes_File_Reader::exit_group() +{ + require( depth() > 0 ); + int d = 1; + while ( true ) + { + if ( block_type() == group_end ) + d--; + if ( block_type() == group_begin ) + d++; + if ( d == 0 ) + break; + RETURN_ERR( skip( h.size ) ); + RETURN_ERR( read_header() ); + } + + block_type_ = invalid; // cause next_block() to read past end block + depth_--; + return 0; +} + +blargg_err_t Nes_File_Reader::skip_v( int s ) +{ + require( block_type() == data_block ); + if ( (unsigned long) s > h.size ) + return "Tried to skip past end of data"; + h.size -= s; + set_remain( h.size ); + return in->skip( s ); +} + +blargg_err_t Nes_File_Reader::read_v( void* p, int n ) +{ + require( block_type() == data_block ); + if ( (unsigned long) n > h.size ) + n = h.size; + h.size -= n; + set_remain( h.size ); + return in->read( p, n ); +} diff --git a/quicknes/nes_emu/Nes_File.h b/quicknes/nes_emu/Nes_File.h new file mode 100644 index 0000000000..a077dc8e41 --- /dev/null +++ b/quicknes/nes_emu/Nes_File.h @@ -0,0 +1,128 @@ + +// NES block-oriented file access + +// Nes_Emu 0.7.0 + +#ifndef NES_FILE_H +#define NES_FILE_H + +#include "blargg_common.h" +#include "abstract_file.h" +#include "nes_data.h" + +// Writes a structured file +class Nes_File_Writer : public Data_Writer { +public: + Nes_File_Writer(); + ~Nes_File_Writer(); + + // Begin writing file with specified signature tag + blargg_err_t begin( Auto_File_Writer, nes_tag_t ); + + // Begin tagged group + blargg_err_t begin_group( nes_tag_t ); + + // Write tagged block + blargg_err_t write_block( nes_tag_t, void const*, long size ); + + // Write tagged block header. 'Size' bytes must be written before next block. + blargg_err_t write_block_header( nes_tag_t, long size ); + + // Write data to current block + error_t write( void const*, long ); + + // End tagged group + blargg_err_t end_group(); + + // End file + blargg_err_t end(); + +private: + Auto_File_Writer out; + long write_remain; + int depth_; + blargg_err_t write_header( nes_tag_t tag, long size ); +}; + +// Reads a structured file +class Nes_File_Reader : public Data_Reader { +public: + Nes_File_Reader(); + ~Nes_File_Reader(); + + // If true, verify checksums of any blocks that have them + void enable_checksums( bool = true ); + + // Begin reading file. Until next_block() is called, block_tag() yields tag for file. + blargg_err_t begin( Auto_File_Reader ); + + // Read header of next block in current group + blargg_err_t next_block(); + + // Type of current block + enum block_type_t { + data_block, + group_begin, + group_end, + invalid + }; + block_type_t block_type() const { return block_type_; } + + // Tag of current block + nes_tag_t block_tag() const { return h.tag; } + + // Read at most s bytes from block and skip any remaining bytes + blargg_err_t read_block_data( void*, long s ); + + // Read at most 's' bytes from current block and return number of bytes actually read + virtual blargg_err_t read_v( void*, int n ); + + // Skip 's' bytes in current block + virtual blargg_err_t skip_v( int s ); + + // Read first sub-block of current group block + blargg_err_t enter_group(); + + // Skip past current group + blargg_err_t exit_group(); + + // Current depth, where 0 is top-level in file and higher is deeper + int depth() const { return depth_; } + + // True if all data has been read + bool done() const { return depth() == 0 && block_type() == group_end; } +private: + Auto_File_Reader in; + nes_block_t h; + block_type_t block_type_; + int depth_; + blargg_err_t read_header(); +}; + +template +inline blargg_err_t read_nes_state( Nes_File_Reader& in, T* out ) +{ + blargg_err_t err = in.read_block_data( out, sizeof *out ); + out->swap(); + return err; +} + +template +inline blargg_err_t write_nes_state( Nes_File_Writer& out, T& in ) +{ + in.swap(); + blargg_err_t err = out.write_block( in.tag, &in, sizeof in ); + in.swap(); + return err; +} + +template +inline blargg_err_t write_nes_state( Nes_File_Writer& out, const T& in ) +{ + T copy = in; + copy.swap(); + return out.write_block( copy.tag, ©, sizeof copy ); +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Film.cpp b/quicknes/nes_emu/Nes_Film.cpp new file mode 100644 index 0000000000..ff258cbf6d --- /dev/null +++ b/quicknes/nes_emu/Nes_Film.cpp @@ -0,0 +1,531 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Film.h" + +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +nes_tag_t const joypad_data_tag = FOUR_CHAR('JOYP'); + +Nes_Film::Nes_Film() { clear( 60 * 60 ); } + +Nes_Film::~Nes_Film() { } + +void Nes_Film::clear( frame_count_t new_period ) +{ + period_ = new_period; + end_ = begin_ = -invalid_frame_count; + time_offset = 0; + has_joypad_sync_ = true; + has_second_joypad = false; + data.clear( new_period ); +} + +inline int Nes_Film::calc_block_count( frame_count_t new_end ) const +{ + // usually one block extra than absolutely needed + return (new_end - time_offset) / data.period() + 2; +} + +blargg_err_t Nes_Film::resize( frame_count_t new_end ) +{ + blargg_err_t err = data.resize( calc_block_count( new_end ) ); + if ( !err ) + end_ = new_end; + return err; +} + +inline int Nes_Film::calc_block( frame_count_t time, int* index_out ) const +{ + assert( time_offset <= time && time <= end() ); + frame_count_t rel = time - time_offset; + int block = rel / data.period(); + *index_out = rel - block * data.period(); + return block; +} + +Nes_Film::joypad_t Nes_Film::get_joypad( frame_count_t time ) const +{ + int index; + block_t const& b = data.read( calc_block( time, &index ) ); + joypad_t result = b.joypad0 [index]; + if ( b.joypads [1] ) + result |= b.joypads [1] [index] << 8; + + return result; +} + +blargg_err_t Nes_Film::set_joypad( frame_count_t time, joypad_t joypad ) +{ + int index; + int block = calc_block( time, &index ); + block_t* b = data.write( block ); + CHECK_ALLOC( b ); + b->joypad0 [index] = joypad & 0xFF; + + int joypad2 = joypad >> 8 & 0xFF; + if ( joypad2 && !b->joypads [1] ) + CHECK_ALLOC( b = data.alloc_joypad2( block ) ); + if ( b->joypads [1] ) + { + b->joypads [1] [index] = joypad2; + has_second_joypad = true; + } + return 0; +} + +blargg_err_t Nes_Film::record_frame( frame_count_t time, joypad_t joypad, Nes_State_** out ) +{ + if ( out ) + *out = 0; + + if ( !contains( time ) ) + { + require( blank() ); + clear(); + begin_ = end_ = time; + time_offset = time - time % period_; + } + + RETURN_ERR( resize( time + 1 ) ); + + RETURN_ERR( set_joypad( time, joypad ) ); + + // first check detects stale snapshot left after trimming film + if ( read_snapshot( time ).timestamp() > time || time == begin_ || time % period_ == 0 ) + { + Nes_State_* ss = modify_snapshot( time ); + CHECK_ALLOC( ss ); + if ( out ) + *out = ss; + if ( time != begin_ ) + ss->set_timestamp( invalid_frame_count ); // caller might not take snapshot + } + + return 0; +} + +Nes_State_ const* Nes_Film::nearest_snapshot( frame_count_t time ) const +{ + require( contains( time ) ); + + if ( time > end() ) + time = end(); + + for ( int i = snapshot_index( time ); i >= 0; i-- ) + { + Nes_State_ const& ss = snapshots( i ); + if ( ss.timestamp() <= time ) // invalid timestamp will always be greater + return &ss; + } + + return 0; +} + +frame_count_t Nes_Film::constrain( frame_count_t t ) const +{ + if ( t != invalid_frame_count && !blank() ) + { + if ( t < begin_ ) t = begin_; + if ( t > end_ ) t = end_; + } + return t; +} + +inline bool Nes_Film::contains_range( frame_count_t first, frame_count_t last ) const +{ + return begin_ <= first && first <= last && last <= end_; +} + +void Nes_Film::trim( frame_count_t first, frame_count_t last ) +{ + check( begin() <= first && first <= last && last <= end() ); + + // TODO: this routine was broken; check thoroughly + + if ( first > begin_ ) + begin_ = first; + + // preserve first snapshot, which might be before beginning + int first_block = (begin_ - time_offset) / data.period(); + if ( first_block > 0 ) + { + // TODO: pathological thrashing still possible + Nes_State_ const* ss = nearest_snapshot( begin_ ); + if ( ss ) + first_block = (ss->timestamp() - time_offset) / data.period(); + time_offset += first_block * data.period(); + } + + if ( begin_ <= last && last < end_ ) + end_ = last; + data.trim( first_block, calc_block_count( end_ ) ); + // be sure snapshot for beginning was preserved + assert( nearest_snapshot( begin_ ) ); +} + +// Nes_Film_Joypad_Scanner + +// Simplifies scanning joypad data +class Nes_Film_Joypad_Scanner { +public: + // Begin scanning range and set public members for first block + Nes_Film_Joypad_Scanner( frame_count_t first, frame_count_t last, Nes_Film const& ); + + int block; // block index + int offset; // offset in data + int count; // number of bytes + frame_count_t remain; // number of bytes remaining to scan + + // Pointer to temporary buffer of 'block_period' bytes. Cleared + // to zero before first use. + unsigned char* buf(); + + // Go to next block. False if no more blocks. + bool next(); + + ~Nes_Film_Joypad_Scanner(); +private: + Nes_Film& film; + unsigned char* buf_; + void recalc_count(); +}; + +inline unsigned char* Nes_Film_Joypad_Scanner::buf() +{ + if ( !buf_ ) + buf_ = (unsigned char*) calloc( 1, film.data.period() ); + return buf_; +} + +inline void Nes_Film_Joypad_Scanner::recalc_count() +{ + count = film.data.period() - offset; + if ( count > remain ) + count = remain; +} + +Nes_Film_Joypad_Scanner::Nes_Film_Joypad_Scanner( frame_count_t first, frame_count_t last, + Nes_Film const& f ) : film( *(Nes_Film*) &f ) +{ + buf_ = 0; + remain = last - first; + block = film.calc_block( first, &offset ); + recalc_count(); + film.data.joypad_only( true ); +} + +Nes_Film_Joypad_Scanner::~Nes_Film_Joypad_Scanner() +{ + film.data.joypad_only( false ); + free( buf_ ); +} + +bool Nes_Film_Joypad_Scanner::next() +{ + block++; + offset = 0; + remain -= count; + if ( remain <= 0 ) + return false; + recalc_count(); + return true; +} + +// Nes_Film_Writer + +blargg_err_t Nes_Film_Writer::end( Nes_Film const& film, frame_count_t first, + frame_count_t last, frame_count_t period ) +{ + RETURN_ERR( film.write_blocks( *this, first, last, period ) ); + return Nes_File_Writer::end(); +} + +blargg_err_t Nes_Film::write( Auto_File_Writer out, frame_count_t first, + frame_count_t last, frame_count_t period ) const +{ + Nes_Film_Writer writer; + RETURN_ERR( writer.begin( out ) ); + return writer.end( *this, first, last, (period ? period : this->period()) ); +} + +static blargg_err_t write_state( Nes_State_ const& ss, Nes_File_Writer& out ) +{ + RETURN_ERR( out.begin_group( state_file_tag ) ); + RETURN_ERR( ss.write_blocks( out ) ); + return out.end_group(); +} + +blargg_err_t Nes_Film::write_blocks( Nes_File_Writer& out, frame_count_t first, + frame_count_t last, frame_count_t period ) const +{ + require( contains_range( first, last ) ); + require( nearest_snapshot( first ) ); + frame_count_t first_snapshot = nearest_snapshot( first )->timestamp(); + assert( first_snapshot <= first ); + + // write info block + movie_info_t info; + memset( &info, 0, sizeof info ); + info.begin = first; + info.length = last - first; + info.extra = first - first_snapshot; + info.period = period; + info.has_joypad_sync = has_joypad_sync_; + info.joypad_count = 1; + if ( has_second_joypad ) + { + // Scan second joypad data for any blocks containing non-zero data + Nes_Film_Joypad_Scanner joypad( first, last, *this ); + do + { + block_t const& b = data.read( joypad.block ); + if ( b.joypads [1] && + mem_differs( &b.joypads [1] [joypad.offset], 0, joypad.count ) ) + { + info.joypad_count = 2; + break; + } + } + while ( joypad.next() ); + } + RETURN_ERR( write_nes_state( out, info ) ); + + // write joypad data + for ( int i = 0; i < info.joypad_count; i++ ) + { + Nes_Film_Joypad_Scanner joypad( first_snapshot, last, *this ); + RETURN_ERR( out.write_block_header( joypad_data_tag, joypad.remain ) ); + do + { + block_t const& b = data.read( joypad.block ); + byte const* data = b.joypads [i]; + if ( !data ) + CHECK_ALLOC( data = joypad.buf() ); + RETURN_ERR( out.write( &data [joypad.offset], joypad.count ) ); + } + while ( joypad.next() ); + } + + // write first state + int index = snapshot_index( first_snapshot ); + assert( snapshots( index ).timestamp() == first_snapshot ); + RETURN_ERR( write_state( snapshots( index ), out ) ); + + // write snapshots that fall within output periods + // TODO: thorougly verify this tricky algorithm + //dprintf( "last: %6d\n", last ); + int last_index = snapshot_index( last ); + frame_count_t time = first_snapshot + period; + for ( ; ++index <= last_index; ) + { + Nes_State_ const& ss = snapshots( index ); + frame_count_t t = ss.timestamp(); + if ( t != invalid_frame_count ) + { + while ( time + period <= t ) + time += period; + + if ( t >= time - period ) + { + time += period; + //dprintf( "time: %6d\n", t ); + RETURN_ERR( write_state( ss, out ) ); + } + } + } + + return 0; +} + +// Nes_Film_Reader + +blargg_err_t Nes_Film::read( Auto_File_Reader in ) +{ + Nes_Film_Reader reader; + RETURN_ERR( reader.begin( in, this ) ); + while ( !reader.done() ) + RETURN_ERR( reader.next_block() ); + return 0; +} + +Nes_Film_Reader::Nes_Film_Reader() +{ + film = 0; + info_ptr = 0; + joypad_count = 0; + film_initialized = false; + memset( &info_, 0, sizeof info_ ); +} + +Nes_Film_Reader::~Nes_Film_Reader() { } + +blargg_err_t Nes_Film_Reader::begin( Auto_File_Reader dr, Nes_Film* nf ) +{ + film = nf; + RETURN_ERR( Nes_File_Reader::begin( dr ) ); + if ( block_tag() != movie_file_tag ) + return "Not a movie file"; + return 0; +} + +blargg_err_t Nes_Film::begin_read( movie_info_t const& info ) +{ + begin_ = info.begin - info.extra; + end_ = info.begin + info.length; + time_offset = begin_ - begin_ % period_; + has_joypad_sync_ = info.has_joypad_sync; + assert( begin_ <= end_ ); + return resize( end_ ); +} + +blargg_err_t Nes_Film_Reader::customize() +{ + require( info_ptr ); + if ( film_initialized ) + return 0; + film_initialized = true; + film->clear(); + return film->begin_read( info_ ); +} + +blargg_err_t Nes_Film_Reader::next_block() +{ + blargg_err_t err = next_block_(); + if ( err ) + film->clear(); // don't leave film in inconsistent state when reading fails + return err; +} + +blargg_err_t Nes_Film_Reader::next_block_() +{ + for ( ; ; ) + { + RETURN_ERR( Nes_File_Reader::next_block() ); + switch ( depth() == 0 ? block_tag() : 0 ) + { + case movie_info_t::tag: + check( !info_ptr ); + RETURN_ERR( read_nes_state( *this, &info_ ) ); + info_ptr = &info_; + return 0; + + case joypad_data_tag: + RETURN_ERR( customize() ); + RETURN_ERR( film->read_joypad( *this, joypad_count++ ) ); + break; + + case state_file_tag: + RETURN_ERR( customize() ); + RETURN_ERR( film->read_snapshot( *this ) ); + break; + + default: + if ( done() ) + { + // at least first snapshot must have been read + check( film->read_snapshot( film->begin_ ).timestamp() != invalid_frame_count ); + film->begin_ += info_.extra; // bump back to claimed beginning +// todo: remove +#if !defined (NDEBUG) && 0 +FILE* out = fopen( "raw_block", "wb" ); +int block_count = (film->end() - film->time_offset) / film->data.period() + 1; +//for ( int i = 0; i < block_count; i++ ) +int i = (block_count > 1); + fwrite( &film->data.read( i ), offsetof (Nes_Film_Data::block_t,joypad0 [film->data.period()]), 1, out ); +fclose( out ); +#endif + } + return 0; + } + } +} + +blargg_err_t Nes_Film::read_joypad( Nes_File_Reader& in, int index ) +{ + check( index <= 1 ); + if ( index <= 1 ) + { + Nes_Film_Joypad_Scanner joypad( begin_, end_, *this ); + do + { + block_t* b = data.write( joypad.block ); + CHECK_ALLOC( b ); + byte* p = b->joypads [index]; + if ( !p ) + CHECK_ALLOC( p = joypad.buf() ); + p += joypad.offset; + RETURN_ERR( in.read( p, joypad.count ) ); + if ( !b->joypads [index] && mem_differs( p, 0, joypad.count ) ) + { + // non-zero joypad2 data + CHECK_ALLOC( b = data.alloc_joypad2( joypad.block ) ); + memcpy( &b->joypads [index] [joypad.offset], p, joypad.count ); + has_second_joypad = true; + } + } + while ( joypad.next() ); + } + + return 0; +} + +blargg_err_t Nes_Film::read_snapshot( Nes_File_Reader& in ) +{ + RETURN_ERR( in.enter_group() ); + + // read snapshot's timestamp + nes_state_t info; + memset( &info, 0, sizeof info ); + for ( ; ; ) + { + RETURN_ERR( in.next_block() ); + if ( in.block_tag() == info.tag ) + { + RETURN_ERR( read_nes_state( in, &info ) ); + break; + } + check( false ); // shouldn't encounter any unknown blocks + } + frame_count_t time = info.frame_count; + + if ( !contains( time ) ) + { + check( false ); + } + else + { + // read snapshot only if it's earlier than any existing snapshot in same segment + Nes_State_* ss = modify_snapshot( time ); + CHECK_ALLOC( ss ); + + // uninitialized snapshot's time is large positive value so always compares greater + if ( time < ss->timestamp() ) + { + // read new snapshot + ss->clear(); + ss->set_nes_state( info ); + do + { + RETURN_ERR( ss->read_blocks( in ) ); + } + while ( in.block_type() != in.group_end ); + } + } + return in.exit_group(); +} + diff --git a/quicknes/nes_emu/Nes_Film.h b/quicknes/nes_emu/Nes_Film.h new file mode 100644 index 0000000000..9a4128bca0 --- /dev/null +++ b/quicknes/nes_emu/Nes_Film.h @@ -0,0 +1,214 @@ + +// Film to record NES movies on using Nes_Recorder + +// Nes_Emu 0.7.0 + +#ifndef NES_FILM_H +#define NES_FILM_H + +#include "blargg_common.h" +#include "Nes_Film_Data.h" + +// See below for custom reader and writer classes that allow user data in movie files +class Nes_Film_Writer; +class Nes_Film_Reader; + +class Nes_Film { +public: + Nes_Film(); + ~Nes_Film(); + + // Clear film to blankness + void clear() { clear( period() ); } + + // Time of first recorded snapshot + frame_count_t begin() const { return begin_; } + + // Time of *end* of last recorded frame + frame_count_t end() const { return end_; } + + // Number of frames in recording + frame_count_t length() const { return end() - begin(); } + + // Trim to subset of recording. OK if new_begin == new_end, which does + // not make film blank, merely of zero length. + void trim( frame_count_t new_begin, frame_count_t new_end ); + + // Write entire recording to file + blargg_err_t write( Auto_File_Writer ) const; + + // Read entire recording from file + blargg_err_t read( Auto_File_Reader ); + +// Additional features + + // Write trimmed recording to file, with snapshots approximately every 'period' frames + blargg_err_t write( Auto_File_Writer, frame_count_t begin, frame_count_t end, + frame_count_t period = 0 ) const; + + // Clear film and set how often snapshots are taken. One snapshot is kept for + // every 'period' frames of recording. A lower period makes seeking faster but + // uses more memory. + void clear( frame_count_t new_period ); + + // Average number of frames between snapshots + frame_count_t period() const { return period_; } + + // True if film has just been cleared + bool blank() const { return end_ < 0; } + + // True if timestamp is within recording. Always false if film is blank. + bool contains( frame_count_t t ) const { return begin() <= t && t <= end(); } + + // True if recording contains frame beginning at timestamp + bool contains_frame( frame_count_t t ) const { return begin() <= t && t < end(); } + + // Constrain timestamp to recorded range, or return unchanged if film is blank + frame_count_t constrain( frame_count_t ) const; + +// Raw access for use by Nes_Recorder + + // True if joypad entries are 0xFF on frames that the joypad isn't read + bool has_joypad_sync() const { return has_joypad_sync_; } + + // Snapshot that might have current timestamp + Nes_State_ const& read_snapshot( frame_count_t ) const; + + // Snapshot that current timestamp maps to. NULL if out of memory. + Nes_State_* modify_snapshot( frame_count_t ); + + // Pointer to nearest snapshot at or before timestamp, or NULL if none + Nes_State_ const* nearest_snapshot( frame_count_t ) const; + + typedef unsigned long joypad_t; + + // Get joypad data for frame beginning at timestamp + joypad_t get_joypad( frame_count_t ) const; + + // Change joypad data for frame beginning at timestamp. Frame must already have + // been recorded normally. + blargg_err_t set_joypad( frame_count_t, joypad_t ); + + // Record new frame beginning at timestamp using joypad data. Returns + // pointer where snapshot should be saved to, or NULL if a snapshot isn't + // needed for this timestamp. Removes anything recorded after frame. + blargg_err_t record_frame( frame_count_t, joypad_t joypad, Nes_State_** out = 0 ); + +private: + // noncopyable + Nes_Film( Nes_Film const& ); + Nes_Film& operator = ( Nes_Film const& ); + + typedef Nes_Film_Data::block_t block_t; + Nes_Film_Data data; + frame_count_t begin_; + frame_count_t end_; + frame_count_t period_; + frame_count_t time_offset; + bool has_joypad_sync_; + bool has_second_joypad; + + int calc_block( frame_count_t time, int* index_out ) const; + int snapshot_index( frame_count_t ) const; + Nes_State_ const& snapshots( int ) const; + int calc_block_count( frame_count_t new_end ) const; + blargg_err_t resize( frame_count_t new_end ); + bool contains_range( frame_count_t first, frame_count_t last ) const; + + blargg_err_t write_blocks( Nes_File_Writer&, frame_count_t first, + frame_count_t last, frame_count_t period ) const; + blargg_err_t begin_read( movie_info_t const& ); + blargg_err_t read_joypad( Nes_File_Reader&, int index ); + blargg_err_t read_snapshot( Nes_File_Reader& ); + + friend class Nes_Film_Reader; + friend class Nes_Film_Writer; + friend class Nes_Film_Joypad_Scanner; +}; + +// Allows user data blocks to be written with film +class Nes_Film_Writer : public Nes_File_Writer { +public: + // Begin writing movie file + blargg_err_t begin( Auto_File_Writer ); + + // End writing movie file. Optionally specify custom period and subset of + // recording to write. + blargg_err_t end( Nes_Film const& ); + blargg_err_t end( Nes_Film const&, frame_count_t first, frame_count_t last, + frame_count_t period ); +}; + +// Allows film information to be checked before loading film, and for handling +// of user data blocks. +class Nes_Film_Reader : public Nes_File_Reader { +public: + Nes_Film_Reader(); + ~Nes_Film_Reader(); + + // Begin reading from movie file. Does not modify film until later (see below). + blargg_err_t begin( Auto_File_Reader, Nes_Film* out ); + + // Go to next custom block in file + blargg_err_t next_block(); + + // Information about film (see nes_state.h for fields). Returns zero + // until information is encountered in file. Once return value becomes + // non-zero, next call to next_block() will read movie into film. + // Until that time, film is not modified or examined at all. + movie_info_t const* info() const { return info_ptr; } + + // to do: allow reading subset of recording from file (for example, last 5 minutes) + +private: + Nes_Film* film; + bool film_initialized; + movie_info_t info_; + movie_info_t* info_ptr; + int joypad_count; + + blargg_err_t customize(); + blargg_err_t next_block_(); +}; + +inline blargg_err_t Nes_Film_Writer::begin( Auto_File_Writer dw ) +{ + return Nes_File_Writer::begin( dw, movie_file_tag ); +} + +inline blargg_err_t Nes_Film::write( Auto_File_Writer out ) const +{ + return write( out, begin(), end(), period() ); +} + +inline blargg_err_t Nes_Film_Writer::end( Nes_Film const& film ) +{ + return end( film, film.begin(), film.end(), film.period() ); +} + +inline int Nes_Film::snapshot_index( frame_count_t time ) const +{ + return (time - time_offset) / period_; +} + +inline Nes_State_ const& Nes_Film::snapshots( int i ) const +{ + return data.read( (unsigned) i / data.block_size ).states [(unsigned) i % data.block_size]; +} + +inline Nes_State_ const& Nes_Film::read_snapshot( frame_count_t time ) const +{ + return snapshots( snapshot_index( time ) ); +} + +inline Nes_State_* Nes_Film::modify_snapshot( frame_count_t time ) +{ + int i = snapshot_index( time ); + block_t* b = data.write( (unsigned) i / data.block_size ); + if ( b ) + return &b->states [(unsigned) i % data.block_size]; + return 0; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Film_Data.cpp b/quicknes/nes_emu/Nes_Film_Data.cpp new file mode 100644 index 0000000000..253356c9f9 --- /dev/null +++ b/quicknes/nes_emu/Nes_Film_Data.cpp @@ -0,0 +1,273 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Film_Data.h" + +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Nes_Film_Data::Nes_Film_Data() +{ + blocks = 0; + active = 0; + block_count = 0; + period_ = 0; + packer = 0; + + BOOST_STATIC_ASSERT( sizeof active->cpu [0] % 4 == 0 ); + BOOST_STATIC_ASSERT( sizeof active->joypad [0] % 4 == 0 ); + BOOST_STATIC_ASSERT( sizeof active->apu [0] % 4 == 0 ); + BOOST_STATIC_ASSERT( sizeof active->ppu [0] % 4 == 0 ); + BOOST_STATIC_ASSERT( sizeof active->mapper [0] % 4 == 0 ); + BOOST_STATIC_ASSERT( sizeof active->states [0] % 4 == 0 ); + //BOOST_STATIC_ASSERT( offsetof (block_t,joypad0) % 4 == 0 ); // XXX +} + +#ifndef NDEBUG + +static void write_file( void const* in, long size, const char* path ) +{ + FILE* out = fopen( path, "wb" ); + if ( out ) + { + fwrite( in, size, 1, out ); + fclose( out ); + } +} + +void Nes_Film_Data::debug_packer() const +{ + comp_block_t* b = blocks [active_index]; + static byte* temp = new byte [active_size() * 2]; + for ( int i = 0; i < active_size() * 2; i++ ) + temp [i] = i; + long vs = packer->unpack( b->data, b->size, temp ); + if ( vs != active_size() - b->offset ) + { + dprintf( "Unpacked size differs\n" ); + write_file( (byte*) active + b->offset, vs, "original" ); + write_file( temp, vs, "error" ); + assert( false ); + } + if ( memcmp( (byte*) active + b->offset, temp, vs ) ) + { + dprintf( "Unpacked content differs\n" ); + write_file( (byte*) active + b->offset, vs, "original" ); + write_file( temp, vs, "error" ); + assert( false ); + } + + if ( 0 ) + { + long total = 0; + for ( int i = 0; i < block_count; i++ ) + if ( blocks [i] ) + total += blocks [i]->size; + //dprintf( "Compression: %ld%%\n", total * 100 / (block_count * (active_size() - period_)) ); + dprintf( "Memory: %ld+%ldK\n", total / 1024, active_size() * 2 / 1024 + 16 ); + //dprintf( "Memory: %ldK\n", (total + active_size() * 2) / 1024 + 16 ); + } +} +#endif + +void Nes_Film_Data::flush_active() const +{ + if ( active_dirty ) + { + assert( (unsigned) active_index < (unsigned) block_count ); + + active_dirty = false; + comp_block_t* b = blocks [active_index]; + assert( b && !b->size ); // should have been reallocated in write() + check( b->offset == joypad_only_ ); + b->size = packer->pack( (byte*) active + b->offset, active_size() - b->offset, b->data ); + assert( b->size <= packer->worst_case( active_size() - b->offset ) ); + + // shrink allocation + void* mem = realloc( b, offsetof (comp_block_t, data) + b->size * sizeof(b->data[0]) ); + if ( mem ) + blocks [active_index] = (comp_block_t*) mem; + else + check( false ); // shrink shouldn't fail, but fine if it does + + #ifndef NDEBUG + debug_packer(); + #endif + } + active_index = -1; +} + +void Nes_Film_Data::init_states() const +{ + memset( active->states, 0, sizeof active->states ); + block_t* b = active; + b->garbage0 = 1; + b->garbage1 = 2; + b->garbage2 = 3; + for ( int j = 0; j < block_size; j++ ) + { + Nes_State_& s = b->states [j]; + s.cpu = &b->cpu [j]; + s.joypad = &b->joypad [j]; + s.apu = &b->apu [j]; + s.ppu = &b->ppu [j]; + s.mapper = &b->mapper [j]; + s.ram = b->ram [j]; + s.sram = b->sram [j]; + s.spr_ram = b->spr_ram [j]; + s.nametable = b->nametable [j]; + s.chr = b->chr [j]; + s.set_timestamp( invalid_frame_count ); + } +} + +void Nes_Film_Data::access( index_t i ) const +{ + assert( (unsigned) i < (unsigned) block_count ); + if ( active_dirty ) + flush_active(); + active_index = i; + comp_block_t* b = blocks [i]; + if ( b ) + { + assert( b->size ); + long size = packer->unpack( b->data, b->size, (byte*) active + b->offset ); + assert( b->offset + size == active_size() ); + if ( b->offset ) + init_states(); + } + else + { + active->joypads [0] = &active->joypad0 [0]; + active->joypads [1] = &active->joypad0 [period_]; + init_states(); + memset( active->joypad0, 0, period_ * 2 ); + } +} + +Nes_Film_Data::block_t* Nes_Film_Data::write( int i ) +{ + require( (unsigned) i < (unsigned) block_count ); + if ( i != active_index ) + access( i ); + if ( !active_dirty ) + { + // preallocate now to avoid losing write when flushed later + long size = packer->worst_case( active_size() - joypad_only_ ); + comp_block_t* new_mem = (comp_block_t*) realloc( blocks [i], size ); + if ( !new_mem ) + return 0; + new_mem->size = 0; + new_mem->offset = joypad_only_; + blocks [i] = new_mem; + active_dirty = true; + } + return active; +} + +void Nes_Film_Data::joypad_only( bool b ) +{ + flush_active(); + joypad_only_ = b * offsetof (block_t,joypad0); +} + +blargg_err_t Nes_Film_Data::resize( int new_count ) +{ + if ( new_count < block_count ) + { + assert( active ); + + if ( active_index >= new_count ) + flush_active(); + + for ( int i = new_count; i < block_count; i++ ) + free( blocks [i] ); + + block_count = new_count; + void* new_blocks = realloc( blocks, new_count * sizeof *blocks ); + if ( new_blocks || !new_count ) + blocks = (comp_block_t**) new_blocks; + else + check( false ); // shrink shouldn't fail, but fine if it does + } + else if ( new_count > block_count ) + { + if ( !packer ) + CHECK_ALLOC( packer = BLARGG_NEW Nes_Film_Packer ); + + if ( !active ) + { + assert( period_ ); + active = (block_t*) calloc( active_size(), 1 ); + CHECK_ALLOC( active ); + //init_active(); // TODO: unnecessary since it's called on first access anyway? + packer->prepare( active, active_size() ); + } + + void* new_blocks = realloc( blocks, new_count * sizeof *blocks ); + CHECK_ALLOC( new_blocks ); + blocks = (comp_block_t**) new_blocks; + + for ( int i = block_count; i < new_count; i++ ) + blocks [i] = 0; + + block_count = new_count; + } + return 0; +} + +void Nes_Film_Data::clear( frame_count_t period ) +{ + active_index = -1; + active_dirty = false; + if ( resize( 0 ) ) + check( false ); // shrink should never fail + joypad_only_ = false; + period_ = period * block_size; + free( active ); + active = 0; +} + +void Nes_Film_Data::trim( int begin, int new_count ) +{ + require( 0 <= begin && begin + new_count <= block_count ); + require( (unsigned) new_count <= (unsigned) block_count ); + if ( (unsigned) new_count < (unsigned) block_count ) + { + if ( begin || active_index >= new_count ) + flush_active(); + + if ( begin ) + { + for ( int i = 0; i < begin; i++ ) + free( blocks [i] ); + memmove( &blocks [0], &blocks [begin], (block_count - begin) * sizeof *blocks ); + block_count -= begin; + } + + if ( resize( new_count ) ) + check( false ); // shrink should never fail + } +} + +Nes_Film_Data::~Nes_Film_Data() +{ + if ( resize( 0 ) ) + check( false ); // shrink should never fail + free( active ); + delete packer; +} + diff --git a/quicknes/nes_emu/Nes_Film_Data.h b/quicknes/nes_emu/Nes_Film_Data.h new file mode 100644 index 0000000000..5e0c25fd95 --- /dev/null +++ b/quicknes/nes_emu/Nes_Film_Data.h @@ -0,0 +1,86 @@ + +// Film data manager that keeps data compressed in memory + +// Nes_Emu 0.7.0 + +#ifndef NES_FILM_DATA_H +#define NES_FILM_DATA_H + +#include "Nes_State.h" +#include "Nes_Film_Packer.h" + +class Nes_Film_Data { +public: + Nes_Film_Data(); + ~Nes_Film_Data(); + + void clear( frame_count_t period ); + frame_count_t period() const { return period_; } + blargg_err_t resize( int new_count ); + void trim( int begin, int end ); + + enum { block_size = 8 }; // 16 helps compression but doubles temp buffer size + typedef int index_t; + struct block_t + { + BOOST::uint8_t* joypads [2]; + + Nes_State_ states [block_size]; + + Nes_Cpu::registers_t cpu [block_size]; + joypad_state_t joypad [block_size]; + apu_state_t apu [block_size]; + ppu_state_t ppu [block_size]; + mapper_state_t mapper [block_size]; + BOOST::uint8_t spr_ram [block_size] [Nes_State_::spr_ram_size]; + BOOST::uint8_t ram [block_size] [Nes_State_::ram_size]; + BOOST::uint8_t nametable [block_size] [Nes_State_::nametable_max]; + BOOST::uint32_t garbage0; + BOOST::uint8_t chr [block_size] [Nes_State_::chr_max]; + BOOST::uint32_t garbage1; + BOOST::uint8_t sram [block_size] [Nes_State_::sram_max]; + BOOST::uint32_t garbage2; + // garbage values prevent matches in compressor from being longer than 256K + BOOST::uint8_t joypad0 [60 * 60L * 60L]; + }; + block_t const& read( index_t ) const; + block_t* write( index_t ); // NULL if out of memory + block_t* alloc_joypad2( index_t i ) { return write( i ); } + void joypad_only( bool ); + +private: + struct comp_block_t + { + long size; + long offset; + BOOST::uint8_t data [1024 * 1024L]; + }; + comp_block_t** blocks; + int block_count; + frame_count_t period_; + block_t* active; + mutable int active_index; + mutable bool active_dirty; + long joypad_only_; + Nes_Film_Packer* packer; + + void debug_packer() const; + void flush_active() const; + void init_active() const; + void init_states() const; + void invalidate_active(); + void access( index_t ) const; + // must be multiple of 4 for packer + long active_size() const { return (offsetof (block_t,joypad0) + 3) & ~3; } +}; + +inline Nes_Film_Data::block_t const& Nes_Film_Data::read( int i ) const +{ + //assert( blocks [i] ); // catch reads of uninitialized blocks + if ( i != active_index ) + access( i ); + return *active; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Film_Packer.cpp b/quicknes/nes_emu/Nes_Film_Packer.cpp new file mode 100644 index 0000000000..ac7e2c0bb6 --- /dev/null +++ b/quicknes/nes_emu/Nes_Film_Packer.cpp @@ -0,0 +1,220 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Film_Packer.h" + +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +// - On my 400 MHz PowerPC G3, pack() = 230MB/sec, unpack() = 320MB/sec. +// - All 32-bit accessess are on 4-byte boundaries of the input/output buffers. +// - This would not make a good general-purpose compressor because the match +// offset is limited to a multiple of 4. + +#ifdef __MWERKS__ + static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants +#else + const unsigned zero = 0; // compile-time constant on other compilers +#endif + +void Nes_Film_Packer::prepare( void const* begin, long size ) +{ + uint32_t const* end = (uint32_t*) ((byte*) begin + size - 4); + uint32_t const** d = dict; + for ( int n = dict_size; n--; ) + *d++ = end; + + uint32_t temp = 0x80000000; + assert( (BOOST::int32_t) temp < 0 ); // be sure high bit is sign +} + +long Nes_Film_Packer::pack( byte const* in_begin, long in_size, byte* out_begin ) +{ +//memcpy( out_begin, in_begin, in_size ); return in_size; + + assert( (in_size & 3) == 0 ); + uint32_t const* const in_end = (uint32_t*) (in_begin + in_size); + uint32_t const* const end = in_end - 2; + uint32_t const* in = (uint32_t*) in_begin; + uint32_t* out = (uint32_t*) out_begin; + + unsigned long first = *in++; + unsigned long offset; + uint32_t const* match; + + unsigned long const factor = 0x100801 + zero; + + // spaghetti program flow gives better efficiency + + goto begin; + + // match loop + do + { + if ( match [-1] != first ) break; + offset <<= 14; + if ( *match != *in ) break; +match: + // count matching words beyond the first two + unsigned long n = (byte*) end - (byte*) in; + first = *++in; + unsigned long m = *++match; + uint32_t const* start = in; + for ( n >>= 2; n; --n ) + { + if ( m != first ) break; + m = *++match; + first = *++in; + } + + // encode match offset and length + unsigned long length = (byte*) in - (byte*) start; + assert( 0 <= length && length <= 0xFFFF << 2 ); + assert( offset >> 16 <= 0x7FFF ); + offset |= length >> 2; + + // check for next match + unsigned long index = (first * factor) >> dict_shift & (dict_size - 1); + match = dict [index]; + *out++ = offset; // interleved write of previous match + offset = (byte*) in - (byte*) match; assert( !(offset & 3) ); + if ( in >= end ) goto match_end; + ++in; + dict [index] = in; + } + while ( offset < 0x20000 ); + +begin: + // start writing next literal + out [1] = first; + uint32_t* literal; + literal = out; + out++; + + // literal loop +literal: + first = *in; + do + { + // check for match + unsigned long index = (first * factor) >> dict_shift & (dict_size - 1); + *++out = first; // interleved write of current literal + match = dict [index]; + dict [index] = in + 1; + if ( in >= end ) goto literal_end; + offset = (byte*) in - (byte*) match; assert( !(offset & 3) ); + ++in; + if ( match [-1] != first ) goto literal; + first = *in; + } + while ( offset >= 0x20000 || *match != first ); + + // set length of completed literal + offset <<= 14; + *literal = (((byte*) out - (byte*) literal) >> 2) | 0x80000000; + goto match; + +match_end: + // start new literal for remaining data after final match + literal = out++; +literal_end: + --out; + + // write remaining data to literal + assert( in < in_end ); + do + { + *++out = *in++; + } + while ( in < in_end ); + *literal = (((byte*) out - (byte*) literal) >> 2) + 0x80000001; + + // mark end with zero word + *++out = 0x80000000; + ++out; + + long out_size = (byte*) out - out_begin; + assert( (out_size & 3) == 0 ); + return out_size; +} + +long Nes_Film_Packer::unpack( byte const* in_begin, long in_size, byte* out_begin ) +{ +//memcpy( out_begin, in_begin, in_size ); return in_size; + + assert( (in_size & 3) == 0 ); + uint32_t const* in = (uint32_t*) in_begin; + uint32_t* out = (uint32_t*) out_begin; + long const literal_offset = 0x7FFFFFFE + zero; + long count = (BOOST::int32_t) *in++; + uint32_t const* m; + + assert( count < 0 ); // first item should be literal + goto literal; + do + { + // match + do + { + assert( m - 1 >= (void*) out_begin ); + assert( m - 1 < out ); + unsigned long data = m [-1]; + *out++ = data; + data = *m; + if ( (count &= 0xFFFF) != 0 ) + { + do + { + *out++ = data; + data = *++m; + } + while ( --count ); + } + count = (BOOST::int32_t) *in++; + *out++ = data; + m = out - (count >> 16); + } + while ( count >= 0 ); + + literal: + unsigned long data = *in++; + *out++ = data; + data = *in++; + if ( (count += literal_offset) != 0 ) + { + do + { + *out++ = data; + data = *in++; + } + while ( --count ); + } + + count = (BOOST::int32_t) data; + m = out - (data >> 16); + } + while ( count >= 0 ); + + assert( count == (BOOST::int32_t) 0x80000000 ); + assert( (byte*) in == in_begin + in_size ); + long out_size = (byte*) out - out_begin; + assert( (out_size & 3) == 0 ); + return out_size; +} + diff --git a/quicknes/nes_emu/Nes_Film_Packer.h b/quicknes/nes_emu/Nes_Film_Packer.h new file mode 100644 index 0000000000..c222b94c78 --- /dev/null +++ b/quicknes/nes_emu/Nes_Film_Packer.h @@ -0,0 +1,32 @@ + +// Fast save state compressor/decompressor for reducing Nes_Film memory usage + +// Nes_Emu 0.7.0 + +#ifndef NES_FILM_PACKER_H +#define NES_FILM_PACKER_H + +#include "blargg_common.h" + +class Nes_Film_Packer { +public: + void prepare( void const* begin, long size ); + + // Worst-case output size for given input size + long worst_case( long in_size ) const { return in_size + 8; } + + typedef unsigned char byte; + long pack( byte const* in, long size, byte* packed_out ); + + long unpack( byte const* packed_in, long packed_size, byte* out ); +private: + enum { dict_bits = 12 }; + enum { dict_size = 1 << dict_bits }; + enum { dict_shift = 32 - dict_bits }; + + typedef BOOST::uint32_t uint32_t; + uint32_t const* dict [dict_size]; +}; + +#endif + diff --git a/quicknes/nes_emu/Nes_Fme07_Apu.cpp b/quicknes/nes_emu/Nes_Fme07_Apu.cpp new file mode 100644 index 0000000000..5ee8eee6d9 --- /dev/null +++ b/quicknes/nes_emu/Nes_Fme07_Apu.cpp @@ -0,0 +1,122 @@ + +// Nes_Emu 0.5.6. http://www.slack.net/~ant/libs/ + +#include "Nes_Fme07_Apu.h" + +#include + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +void Nes_Fme07_Apu::reset() +{ + last_time = 0; + + for ( int i = 0; i < osc_count; i++ ) + oscs [i].last_amp = 0; + + fme07_snapshot_t* state = this; + memset( state, 0, sizeof *state ); +} + +#include BLARGG_ENABLE_OPTIMIZER + +unsigned char Nes_Fme07_Apu::amp_table [16] = +{ + #define ENTRY( n ) (n * amp_range) + 0.5 + ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), + ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), + ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), + ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) + #undef ENTRY +}; + +void Nes_Fme07_Apu::run_until( blip_time_t end_time ) +{ + require( end_time >= last_time ); + + for ( int index = 0; index < osc_count; index++ ) + { + int mode = regs [7] >> index; + int vol_mode = regs [010 + index]; + int volume = amp_table [vol_mode & 0x0f]; + + if ( !oscs [index].output ) + continue; + + // check for unsupported mode + #ifndef NDEBUG + if ( (mode & 011) <= 001 && vol_mode & 0x1f ) + dprintf( "FME07 used unimplemented sound mode: %02X, vol_mode: %02X\n", + mode, vol_mode & 0x1f ); + #endif + + if ( (mode & 001) | (vol_mode & 0x10) ) + volume = 0; // noise and envelope aren't supported + + // period + int const period_factor = 16; + unsigned period = (regs [index * 2 + 1] & 0x0f) * 0x100 * period_factor + + regs [index * 2] * period_factor; + if ( period < 50 ) // around 22 kHz + { + volume = 0; + if ( !period ) // on my AY-3-8910A, period doesn't have extra one added + period = period_factor; + } + + // current amplitude + int amp = volume; + if ( !phases [index] ) + amp = 0; + int delta = amp - oscs [index].last_amp; + if ( delta ) + { + oscs [index].last_amp = amp; + synth.offset( last_time, delta, oscs [index].output ); + } + + blip_time_t time = last_time + delays [index]; + if ( time < end_time ) + { + Blip_Buffer* const osc_output = oscs [index].output; + int delta = amp * 2 - volume; + + if ( volume ) + { + do + { + delta = -delta; + synth.offset_inline( time, delta, osc_output ); + time += period; + } + while ( time < end_time ); + + oscs [index].last_amp = (delta + volume) >> 1; + phases [index] = (delta > 0); + } + else + { + // maintain phase when silent + int count = (end_time - time + period - 1) / period; + phases [index] ^= count & 1; + time += (long) count * period; + } + } + + delays [index] = time - end_time; + } + + last_time = end_time; +} + diff --git a/quicknes/nes_emu/Nes_Fme07_Apu.h b/quicknes/nes_emu/Nes_Fme07_Apu.h new file mode 100644 index 0000000000..79b0037447 --- /dev/null +++ b/quicknes/nes_emu/Nes_Fme07_Apu.h @@ -0,0 +1,133 @@ + +// Sunsoft FME-07 sound emulator + +// Nes_Emu 0.5.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_FME07_APU_H +#define NES_FME07_APU_H + +#include "Blip_Buffer.h" + +struct fme07_snapshot_t +{ + enum { reg_count = 14 }; + BOOST::uint8_t regs [reg_count]; + BOOST::uint8_t phases [3]; // 0 or 1 + BOOST::uint8_t latch; + BOOST::uint16_t delays [3]; // a, b, c +}; +BOOST_STATIC_ASSERT( sizeof (fme07_snapshot_t) == 24 ); + +class Nes_Fme07_Apu : private fme07_snapshot_t { +public: + Nes_Fme07_Apu(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( blip_time_t ); + void save_snapshot( fme07_snapshot_t* ) const; + void load_snapshot( fme07_snapshot_t const& ); + + // Mask and addresses of registers + enum { addr_mask = 0xe000 }; + enum { data_addr = 0xe000 }; + enum { latch_addr = 0xc000 }; + + // (addr & addr_mask) == latch_addr + void write_latch( int ); + + // (addr & addr_mask) == data_addr + void write_data( blip_time_t, int data ); + + // End of public interface +private: + // noncopyable + Nes_Fme07_Apu( const Nes_Fme07_Apu& ); + Nes_Fme07_Apu& operator = ( const Nes_Fme07_Apu& ); + + static unsigned char amp_table [16]; + + struct { + Blip_Buffer* output; + int last_amp; + } oscs [osc_count]; + blip_time_t last_time; + + enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff + Blip_Synth synth; + + void run_until( blip_time_t ); +}; + +inline void Nes_Fme07_Apu::volume( double v ) +{ + synth.volume_unit( 0.38 / amp_range * v ); // to do: fine-tune +} + +inline void Nes_Fme07_Apu::treble_eq( blip_eq_t const& eq ) +{ + synth.treble_eq( eq ); +} + +inline void Nes_Fme07_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +inline void Nes_Fme07_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline Nes_Fme07_Apu::Nes_Fme07_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +inline void Nes_Fme07_Apu::write_latch( int data ) { latch = data; } + +inline void Nes_Fme07_Apu::write_data( blip_time_t time, int data ) +{ + if ( (unsigned) latch >= reg_count ) + { + #ifdef dprintf + dprintf( "FME07 write to %02X (past end of sound registers)\n", (int) latch ); + #endif + return; + } + + run_until( time ); + regs [latch] = data; +} + +inline void Nes_Fme07_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + last_time -= time; + assert( last_time >= 0 ); +} + +inline void Nes_Fme07_Apu::save_snapshot( fme07_snapshot_t* out ) const +{ + *out = *this; +} + +inline void Nes_Fme07_Apu::load_snapshot( fme07_snapshot_t const& in ) +{ + reset(); + fme07_snapshot_t* state = this; + *state = in; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Fme7_Apu.cpp b/quicknes/nes_emu/Nes_Fme7_Apu.cpp new file mode 100644 index 0000000000..2cb8d0cd4b --- /dev/null +++ b/quicknes/nes_emu/Nes_Fme7_Apu.cpp @@ -0,0 +1,120 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Fme7_Apu.h" + +#include + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +void Nes_Fme7_Apu::reset() +{ + last_time = 0; + + for ( int i = 0; i < osc_count; i++ ) + oscs [i].last_amp = 0; + + fme7_apu_state_t* state = this; + memset( state, 0, sizeof *state ); +} + +unsigned char Nes_Fme7_Apu::amp_table [16] = +{ + #define ENTRY( n ) (unsigned char) (n * amp_range + 0.5) + ENTRY(0.0000), ENTRY(0.0078), ENTRY(0.0110), ENTRY(0.0156), + ENTRY(0.0221), ENTRY(0.0312), ENTRY(0.0441), ENTRY(0.0624), + ENTRY(0.0883), ENTRY(0.1249), ENTRY(0.1766), ENTRY(0.2498), + ENTRY(0.3534), ENTRY(0.4998), ENTRY(0.7070), ENTRY(1.0000) + #undef ENTRY +}; + +void Nes_Fme7_Apu::run_until( blip_time_t end_time ) +{ + require( end_time >= last_time ); + + for ( int index = 0; index < osc_count; index++ ) + { + int mode = regs [7] >> index; + int vol_mode = regs [010 + index]; + int volume = amp_table [vol_mode & 0x0f]; + + if ( !oscs [index].output ) + continue; + + // check for unsupported mode + #ifndef NDEBUG + if ( (mode & 011) <= 001 && vol_mode & 0x1f ) + dprintf( "FME7 used unimplemented sound mode: %02X, vol_mode: %02X\n", + mode, vol_mode & 0x1f ); + #endif + + if ( (mode & 001) | (vol_mode & 0x10) ) + volume = 0; // noise and envelope aren't supported + + // period + int const period_factor = 16; + unsigned period = (regs [index * 2 + 1] & 0x0f) * 0x100 * period_factor + + regs [index * 2] * period_factor; + if ( period < 50 ) // around 22 kHz + { + volume = 0; + if ( !period ) // on my AY-3-8910A, period doesn't have extra one added + period = period_factor; + } + + // current amplitude + int amp = volume; + if ( !phases [index] ) + amp = 0; + int delta = amp - oscs [index].last_amp; + if ( delta ) + { + oscs [index].last_amp = amp; + synth.offset( last_time, delta, oscs [index].output ); + } + + blip_time_t time = last_time + delays [index]; + if ( time < end_time ) + { + Blip_Buffer* const osc_output = oscs [index].output; + int delta = amp * 2 - volume; + + if ( volume ) + { + do + { + delta = -delta; + synth.offset_inline( time, delta, osc_output ); + time += period; + } + while ( time < end_time ); + + oscs [index].last_amp = (delta + volume) >> 1; + phases [index] = (delta > 0); + } + else + { + // maintain phase when silent + int count = (end_time - time + period - 1) / period; + phases [index] ^= count & 1; + time += (long) count * period; + } + } + + delays [index] = time - end_time; + } + + last_time = end_time; +} + diff --git a/quicknes/nes_emu/Nes_Fme7_Apu.h b/quicknes/nes_emu/Nes_Fme7_Apu.h new file mode 100644 index 0000000000..c8f5f32563 --- /dev/null +++ b/quicknes/nes_emu/Nes_Fme7_Apu.h @@ -0,0 +1,135 @@ + +// Sunsoft FME-7 sound emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_FME7_APU_H +#define NES_FME7_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct fme7_apu_state_t +{ + enum { reg_count = 14 }; + BOOST::uint8_t regs [reg_count]; + BOOST::uint8_t phases [3]; // 0 or 1 + BOOST::uint8_t latch; + BOOST::uint16_t delays [3]; // a, b, c +}; +BOOST_STATIC_ASSERT( sizeof (fme7_apu_state_t) == 24 ); + +class Nes_Fme7_Apu : private fme7_apu_state_t { +public: + Nes_Fme7_Apu(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( blip_time_t ); + void save_state( fme7_apu_state_t* ) const; + void load_state( fme7_apu_state_t const& ); + + // Mask and addresses of registers + enum { addr_mask = 0xe000 }; + enum { data_addr = 0xe000 }; + enum { latch_addr = 0xc000 }; + + // (addr & addr_mask) == latch_addr + void write_latch( int ); + + // (addr & addr_mask) == data_addr + void write_data( blip_time_t, int data ); + + // End of public interface +private: + // noncopyable + Nes_Fme7_Apu( const Nes_Fme7_Apu& ); + Nes_Fme7_Apu& operator = ( const Nes_Fme7_Apu& ); + + static unsigned char amp_table [16]; + + struct { + Blip_Buffer* output; + int last_amp; + } oscs [osc_count]; + blip_time_t last_time; + + enum { amp_range = 192 }; // can be any value; this gives best error/quality tradeoff + Blip_Synth synth; + + void run_until( blip_time_t ); +}; + +inline void Nes_Fme7_Apu::volume( double v ) +{ + synth.volume( 0.38 / amp_range * v ); // to do: fine-tune +} + +inline void Nes_Fme7_Apu::treble_eq( blip_eq_t const& eq ) +{ + synth.treble_eq( eq ); +} + +inline void Nes_Fme7_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Fme7_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +inline Nes_Fme7_Apu::Nes_Fme7_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +inline void Nes_Fme7_Apu::write_latch( int data ) { latch = data; } + +inline void Nes_Fme7_Apu::write_data( blip_time_t time, int data ) +{ + if ( (unsigned) latch >= reg_count ) + { + #ifdef dprintf + dprintf( "FME7 write to %02X (past end of sound registers)\n", (int) latch ); + #endif + return; + } + + run_until( time ); + regs [latch] = data; +} + +inline void Nes_Fme7_Apu::end_frame( blip_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +inline void Nes_Fme7_Apu::save_state( fme7_apu_state_t* out ) const +{ + *out = *this; +} + +inline void Nes_Fme7_Apu::load_state( fme7_apu_state_t const& in ) +{ + reset(); + fme7_apu_state_t* state = this; + *state = in; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Mapper.cpp b/quicknes/nes_emu/Nes_Mapper.cpp new file mode 100644 index 0000000000..a55f02397c --- /dev/null +++ b/quicknes/nes_emu/Nes_Mapper.cpp @@ -0,0 +1,216 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include +#include "Nes_Core.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Nes_Mapper::Nes_Mapper() +{ + emu_ = NULL; + static char c; + state = &c; // TODO: state must not be null? + state_size = 0; +} + +Nes_Mapper::~Nes_Mapper() +{ +} + +// Sets mirroring, maps first 8K CHR in, first and last 16K of PRG, +// intercepts writes to upper half of memory, and clears registered state. +void Nes_Mapper::default_reset_state() +{ + int mirroring = cart_->mirroring(); + if ( mirroring & 8 ) + mirror_full(); + else if ( mirroring & 1 ) + mirror_vert(); + else + mirror_horiz(); + + set_chr_bank( 0, bank_8k, 0 ); + + set_prg_bank( 0x8000, bank_16k, 0 ); + set_prg_bank( 0xC000, bank_16k, last_bank ); + + intercept_writes( 0x8000, 0x8000 ); + + memset( state, 0, state_size ); +} + +void Nes_Mapper::reset() +{ + default_reset_state(); + reset_state(); + apply_mapping(); +} + +void mapper_state_t::write( const void* p, unsigned long s ) +{ + require( s <= max_mapper_state_size ); + require( !size ); + size = s; + memcpy( data, p, s ); +} + +int mapper_state_t::read( void* p, unsigned long s ) const +{ + if ( (long) s > size ) + s = size; + memcpy( p, data, s ); + return s; +} + +void Nes_Mapper::save_state( mapper_state_t& out ) +{ + out.write( state, state_size ); +} + +void Nes_Mapper::load_state( mapper_state_t const& in ) +{ + default_reset_state(); + read_state( in ); + apply_mapping(); +} + +void Nes_Mapper::read_state( mapper_state_t const& in ) +{ + memset( state, 0, state_size ); + in.read( state, state_size ); + apply_mapping(); +} + +// Timing + +void Nes_Mapper::irq_changed() { emu_->irq_changed(); } + +nes_time_t Nes_Mapper::next_irq( nes_time_t ) { return no_irq; } + +void Nes_Mapper::a12_clocked() { } + +void Nes_Mapper::run_until( nes_time_t ) { } + +void Nes_Mapper::end_frame( nes_time_t ) { } + +bool Nes_Mapper::ppu_enabled() const { return emu().ppu.w2001 & 0x08; } + +// Sound + +int Nes_Mapper::channel_count() const { return 0; } + +void Nes_Mapper::set_channel_buf( int, Blip_Buffer* ) { require( false ); } + +void Nes_Mapper::set_treble( blip_eq_t const& ) { } + +// Memory mapping + +void Nes_Mapper::set_prg_bank( nes_addr_t addr, bank_size_t bs, int bank ) +{ + require( addr >= 0x2000 ); // can't remap low-memory + + int bank_size = 1 << bs; + require( addr % bank_size == 0 ); // must be aligned + + int bank_count = cart_->prg_size() >> bs; + if ( bank < 0 ) + bank += bank_count; + + if ( bank >= bank_count ) + { + check( !(cart_->prg_size() & (cart_->prg_size() - 1)) ); // ensure PRG size is power of 2 + bank %= bank_count; + } + + emu().map_code( addr, bank_size, cart_->prg() + (bank << bs) ); + + if ( unsigned (addr - 0x6000) < 0x2000 ) + emu().enable_prg_6000(); +} + +void Nes_Mapper::set_chr_bank( nes_addr_t addr, bank_size_t bs, int bank ) +{ + emu().ppu.render_until( emu().clock() ); + emu().ppu.set_chr_bank( addr, 1 << bs, bank << bs ); +} + +void Nes_Mapper::mirror_manual( int page0, int page1, int page2, int page3 ) +{ + emu().ppu.render_bg_until( emu().clock() ); + emu().ppu.set_nt_banks( page0, page1, page2, page3 ); +} + +#ifndef NDEBUG +int Nes_Mapper::handle_bus_conflict( nes_addr_t addr, int data ) +{ + if ( emu().Nes_Cpu::get_code( addr ) [0] != data ) + dprintf( "Mapper write had bus conflict\n" ); + return data; +} +#endif + +// Mapper registration + +int const max_mappers = 32; +Nes_Mapper::mapping_t Nes_Mapper::mappers [max_mappers] = +{ + { 0, Nes_Mapper::make_nrom }, + { 1, Nes_Mapper::make_mmc1 }, + { 2, Nes_Mapper::make_unrom }, + { 3, Nes_Mapper::make_cnrom }, + { 4, Nes_Mapper::make_mmc3 }, + { 7, Nes_Mapper::make_aorom } +}; +static int mapper_count = 6; // to do: keep synchronized with pre-supplied mappers above + +Nes_Mapper::creator_func_t Nes_Mapper::get_mapper_creator( int code ) +{ + for ( int i = 0; i < mapper_count; i++ ) + { + if ( mappers [i].code == code ) + return mappers [i].func; + } + return NULL; +} + +void Nes_Mapper::register_mapper( int code, creator_func_t func ) +{ + // Catch attempted registration of a different creation function for same mapper code + require( !get_mapper_creator( code ) || get_mapper_creator( code ) == func ); + require( mapper_count < max_mappers ); // fixed liming on number of registered mappers + + mapping_t& m = mappers [mapper_count++]; + m.code = code; + m.func = func; +} + +Nes_Mapper* Nes_Mapper::create( Nes_Cart const* cart, Nes_Core* emu ) +{ + Nes_Mapper::creator_func_t func = get_mapper_creator( cart->mapper_code() ); + if ( !func ) + return NULL; + + // to do: out of memory will be reported as unsupported mapper + Nes_Mapper* mapper = func(); + if ( mapper ) + { + mapper->cart_ = cart; + mapper->emu_ = emu; + } + return mapper; +} + diff --git a/quicknes/nes_emu/Nes_Mapper.h b/quicknes/nes_emu/Nes_Mapper.h new file mode 100644 index 0000000000..59a258a571 --- /dev/null +++ b/quicknes/nes_emu/Nes_Mapper.h @@ -0,0 +1,234 @@ + +// NES mapper interface + +// Nes_Emu 0.7.0 + +#ifndef NES_MAPPER +#define NES_MAPPER + +#include "Nes_Cart.h" +#include "Nes_Cpu.h" +#include "nes_data.h" +#include "Nes_Core.h" +class Blip_Buffer; +class blip_eq_t; +class Nes_Core; + +class Nes_Mapper { +public: + // Register function that creates mapper for given code. + typedef Nes_Mapper* (*creator_func_t)(); + static void register_mapper( int code, creator_func_t ); + + // Register optional mappers included with Nes_Emu + void register_optional_mappers(); + + // Create mapper appropriate for cartridge. Returns NULL if it uses unsupported mapper. + static Nes_Mapper* create( Nes_Cart const*, Nes_Core* ); + + virtual ~Nes_Mapper(); + + // Reset mapper to power-up state. + virtual void reset(); + + // Save snapshot of mapper state. Default saves registered state. + virtual void save_state( mapper_state_t& ); + + // Resets mapper, loads state, then applies it + virtual void load_state( mapper_state_t const& ); + +// I/O + + // Read from memory + virtual int read( nes_time_t, nes_addr_t ); + + // Write to memory + virtual void write( nes_time_t, nes_addr_t, int data ) = 0; + + // Write to memory below 0x8000 (returns false if mapper didn't handle write) + virtual bool write_intercepted( nes_time_t, nes_addr_t, int data ); + +// Timing + + // Time returned when current mapper state won't ever cause an IRQ + enum { no_irq = LONG_MAX / 2 }; + + // Time next IRQ will occur at + virtual nes_time_t next_irq( nes_time_t present ); + + // Run mapper until given time + virtual void run_until( nes_time_t ); + + // End video frame of given length + virtual void end_frame( nes_time_t length ); + +// Sound + + // Number of sound channels + virtual int channel_count() const; + + // Set sound buffer for channel to output to, or NULL to silence channel. + virtual void set_channel_buf( int index, Blip_Buffer* ); + + // Set treble equalization + virtual void set_treble( blip_eq_t const& ); + +// Misc + + // Called when bit 12 of PPU's VRAM address changes from 0 to 1 due to + // $2006 and $2007 accesses (but not due to PPU scanline rendering). + virtual void a12_clocked(); + +protected: + // Services provided for derived mapper classes + Nes_Mapper(); + + // Register state data to automatically save and load. Be sure the binary + // layout is suitable for use in a file, including any byte-order issues. + // Automatically cleared to zero by default reset(). + void register_state( void*, unsigned ); + + // Enable 8K of RAM at 0x6000-0x7FFF, optionally read-only. + void enable_sram( bool enabled = true, bool read_only = false ); + + // Cause CPU writes within given address range to call mapper's write() function. + // Might map a larger address range, which the mapper can ignore and pass to + // Nes_Mapper::write(). The range 0x8000-0xffff is always intercepted by the mapper. + void intercept_writes( nes_addr_t addr, unsigned size ); + + // Cause CPU reads within given address range to call mapper's read() function. + // Might map a larger address range, which the mapper can ignore and pass to + // Nes_Mapper::read(). CPU opcode/operand reads and low-memory reads always + // go directly to memory and cannot be intercepted. + void intercept_reads( nes_addr_t addr, unsigned size ); + + // Bank sizes for mapping + enum bank_size_t { // 1 << bank_Xk = X * 1024 + bank_1k = 10, + bank_2k = 11, + bank_4k = 12, + bank_8k = 13, + bank_16k = 14, + bank_32k = 15 + }; + + // Index of last PRG/CHR bank. Last_bank selects last bank, last_bank - 1 + // selects next-to-last bank, etc. + enum { last_bank = -1 }; + + // Map 'size' bytes from 'PRG + bank * size' to CPU address space starting at 'addr' + void set_prg_bank( nes_addr_t addr, bank_size_t size, int bank ); + + // Map 'size' bytes from 'CHR + bank * size' to PPU address space starting at 'addr' + void set_chr_bank( nes_addr_t addr, bank_size_t size, int bank ); + + // Set PPU mirroring. All mappings implemented using mirror_manual(). + void mirror_manual( int page0, int page1, int page2, int page3 ); + void mirror_single( int page ); + void mirror_horiz( int page = 0 ); + void mirror_vert( int page = 0 ); + void mirror_full(); + + // True if PPU rendering is enabled. Some mappers watch PPU memory accesses to determine + // when scanlines occur, and can only do this when rendering is enabled. + bool ppu_enabled() const; + + // Cartridge being emulated + Nes_Cart const& cart() const { return *cart_; } + + // Must be called when next_irq()'s return value is earlier than previous, + // current CPU run can be stopped earlier. Best to call whenever time may + // have changed (no performance impact if called even when time didn't change). + void irq_changed(); + + // Handle data written to mapper that doesn't handle bus conflict arising due to + // PRG also reading data. Returns data that mapper should act as if were + // written. Currently always returns 'data' and just checks that data written is + // the same as byte in PRG at same address and writes debug message if it doesn't. + int handle_bus_conflict( nes_addr_t addr, int data ); + + // Reference to emulator that uses this mapper. + Nes_Core& emu() const { return *emu_; } + +protected: + // Services derived classes provide + + // Read state from snapshot. Default reads data into registered state, then calls + // apply_mapping(). + virtual void read_state( mapper_state_t const& ); + + // Apply current mapping state to hardware. Called after reading mapper state + // from a snapshot. + virtual void apply_mapping() = 0; + + // Called by default reset() before apply_mapping() is called. + virtual void reset_state() { } + + // End of general interface +private: + Nes_Core* emu_; + void* state; + unsigned state_size; + Nes_Cart const* cart_; + + void default_reset_state(); + + struct mapping_t { + int code; + Nes_Mapper::creator_func_t func; + }; + static mapping_t mappers []; + static creator_func_t get_mapper_creator( int code ); + + // built-in mappers + static Nes_Mapper* make_nrom(); + static Nes_Mapper* make_unrom(); + static Nes_Mapper* make_aorom(); + static Nes_Mapper* make_cnrom(); + static Nes_Mapper* make_mmc1(); + static Nes_Mapper* make_mmc3(); +}; + +template +struct register_mapper { + /*void*/ register_mapper( int code ) { Nes_Mapper::register_mapper( code, create ); } + static Nes_Mapper* create() { return BLARGG_NEW T; } +}; + +#ifdef NDEBUG +inline int Nes_Mapper::handle_bus_conflict( nes_addr_t addr, int data ) { return data; } +#endif + +inline void Nes_Mapper::mirror_horiz( int p ) { mirror_manual( p, p, p ^ 1, p ^ 1 ); } +inline void Nes_Mapper::mirror_vert( int p ) { mirror_manual( p, p ^ 1, p, p ^ 1 ); } +inline void Nes_Mapper::mirror_single( int p ) { mirror_manual( p, p, p, p ); } +inline void Nes_Mapper::mirror_full() { mirror_manual( 0, 1, 2, 3 ); } + +inline void Nes_Mapper::register_state( void* p, unsigned s ) +{ + assert( s <= max_mapper_state_size ); + state = p; + state_size = s; +} + +inline bool Nes_Mapper::write_intercepted( nes_time_t, nes_addr_t, int ) { return false; } + +inline int Nes_Mapper::read( nes_time_t, nes_addr_t ) { return -1; } // signal to caller + +inline void Nes_Mapper::intercept_reads( nes_addr_t addr, unsigned size ) +{ + emu().add_mapper_intercept( addr, size, true, false ); +} + +inline void Nes_Mapper::intercept_writes( nes_addr_t addr, unsigned size ) +{ + emu().add_mapper_intercept( addr, size, false, true ); +} + +inline void Nes_Mapper::enable_sram( bool enabled, bool read_only ) +{ + emu_->enable_sram( enabled, read_only ); +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Mmc1.cpp b/quicknes/nes_emu/Nes_Mmc1.cpp new file mode 100644 index 0000000000..e00770e288 --- /dev/null +++ b/quicknes/nes_emu/Nes_Mmc1.cpp @@ -0,0 +1,123 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +class Mapper_Mmc1 : public Nes_Mapper, mmc1_state_t { +public: + Mapper_Mmc1() + { + mmc1_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { + regs [0] = 0x0f; + regs [1] = 0x00; + regs [2] = 0x01; + regs [3] = 0x00; + } + + void register_changed( int ); + + virtual void apply_mapping() + { + enable_sram(); // early MMC1 always had SRAM enabled + register_changed( 0 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( !(data & 0x80) ) + { + buf |= (data & 1) << bit; + bit++; + + if ( bit >= 5 ) + { + int reg = addr >> 13 & 3; + regs [reg] = buf & 0x1f; + + bit = 0; + buf = 0; + + register_changed( reg ); + } + } + else + { + bit = 0; + buf = 0; + regs [0] |= 0x0c; + register_changed( 0 ); + } + } +}; + +void Mapper_Mmc1::register_changed( int reg ) +{ + // Mirroring + if ( reg == 0 ) + { + int mode = regs [0] & 3; + if ( mode < 2 ) + mirror_single( mode & 1 ); + else if ( mode == 2 ) + mirror_vert(); + else + mirror_horiz(); + } + + // CHR + if ( reg < 3 && cart().chr_size() > 0 ) + { + if ( regs [0] & 0x10 ) + { + set_chr_bank( 0x0000, bank_4k, regs [1] ); + set_chr_bank( 0x1000, bank_4k, regs [2] ); + } + else + { + set_chr_bank( 0, bank_8k, regs [1] >> 1 ); + } + } + + // PRG + int bank = (regs [1] & 0x10) | (regs [3] & 0x0f); + if ( !(regs [0] & 0x08) ) + { + set_prg_bank( 0x8000, bank_32k, bank >> 1 ); + } + else if ( regs [0] & 0x04 ) + { + set_prg_bank( 0x8000, bank_16k, bank ); + set_prg_bank( 0xC000, bank_16k, bank | 0x0f ); + } + else + { + set_prg_bank( 0x8000, bank_16k, bank & ~0x0f ); + set_prg_bank( 0xC000, bank_16k, bank ); + } +} + +Nes_Mapper* Nes_Mapper::make_mmc1() +{ + return BLARGG_NEW Mapper_Mmc1; +} + diff --git a/quicknes/nes_emu/Nes_Mmc3.cpp b/quicknes/nes_emu/Nes_Mmc3.cpp new file mode 100644 index 0000000000..e23ffa95fd --- /dev/null +++ b/quicknes/nes_emu/Nes_Mmc3.cpp @@ -0,0 +1,252 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +#include +#include "Nes_Core.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// 264 or less breaks Gargoyle's Quest II +// 267 or less breaks Magician +int const irq_fine_tune = 268; +nes_time_t const first_scanline = 20 * Nes_Ppu::scanline_len + irq_fine_tune; +nes_time_t const last_scanline = first_scanline + 240 * Nes_Ppu::scanline_len; + +class Mapper_Mmc3 : public Nes_Mapper, mmc3_state_t { + nes_time_t next_time; + int counter_just_clocked; // used only for debugging +public: + Mapper_Mmc3() + { + mmc3_state_t* state = this; + register_state( state, sizeof *state ); + } + + virtual void reset_state() + { + memcpy( banks, "\0\2\4\5\6\7\0\1", sizeof banks ); + + counter_just_clocked = 0; + next_time = 0; + mirror = 1; + if ( cart().mirroring() & 1 ) + { + mirror = 0; + //dprintf( "cart specified vertical mirroring\n" ); + } + } + + void update_chr_banks(); + void update_prg_banks(); + void write_irq( nes_addr_t addr, int data ); + void write( nes_time_t, nes_addr_t, int ); + + void start_frame() { next_time = first_scanline; } + + virtual void apply_mapping() + { + write( 0, 0xA000, mirror ); + write( 0, 0xA001, sram_mode ); + update_chr_banks(); + update_prg_banks(); + start_frame(); + } + + void clock_counter() + { + if ( counter_just_clocked ) + counter_just_clocked--; + + if ( !irq_ctr-- ) + { + irq_ctr = irq_latch; + //if ( !irq_latch ) + //dprintf( "MMC3 IRQ counter reloaded with 0\n" ); + } + + //dprintf( "%6d MMC3 IRQ clocked\n", time / ppu_overclock ); + if ( irq_ctr == 0 ) + { + //if ( irq_enabled && !irq_flag ) + //dprintf( "%6d MMC3 IRQ triggered: %f\n", time / ppu_overclock, time / scanline_len.0 - 20 ); + irq_flag = irq_enabled; + } + } + + virtual void run_until( nes_time_t ); + + virtual void a12_clocked() + { + clock_counter(); + if ( irq_enabled ) + irq_changed(); + } + + virtual void end_frame( nes_time_t end_time ) + { + run_until( end_time ); + start_frame(); + } + + virtual nes_time_t next_irq( nes_time_t present ) + { + run_until( present ); + + if ( !irq_enabled ) + return no_irq; + + if ( irq_flag ) + return 0; + + if ( !ppu_enabled() ) + return no_irq; + + int remain = irq_ctr - 1; + if ( remain < 0 ) + remain = irq_latch; + + assert( remain >= 0 ); + + long time = remain * 341L + next_time; + if ( time > last_scanline ) + return no_irq; + + return time / ppu_overclock + 1; + } +}; + +void Mapper_Mmc3::run_until( nes_time_t end_time ) +{ + bool bg_enabled = ppu_enabled(); + + end_time *= ppu_overclock; + while ( next_time < end_time && next_time <= last_scanline ) + { + if ( bg_enabled ) + clock_counter(); + next_time += Nes_Ppu::scanline_len; + } +} + +void Mapper_Mmc3::update_chr_banks() +{ + int chr_xor = (mode >> 7 & 1) * 0x1000; + set_chr_bank( 0x0000 ^ chr_xor, bank_2k, banks [0] >> 1 ); + set_chr_bank( 0x0800 ^ chr_xor, bank_2k, banks [1] >> 1 ); + set_chr_bank( 0x1000 ^ chr_xor, bank_1k, banks [2] ); + set_chr_bank( 0x1400 ^ chr_xor, bank_1k, banks [3] ); + set_chr_bank( 0x1800 ^ chr_xor, bank_1k, banks [4] ); + set_chr_bank( 0x1c00 ^ chr_xor, bank_1k, banks [5] ); +} + +void Mapper_Mmc3::update_prg_banks() +{ + set_prg_bank( 0xA000, bank_8k, banks [7] ); + nes_addr_t addr = 0x8000 + 0x4000 * (mode >> 6 & 1); + set_prg_bank( addr, bank_8k, banks [6] ); + set_prg_bank( addr ^ 0x4000, bank_8k, last_bank - 1 ); +} + +void Mapper_Mmc3::write_irq( nes_addr_t addr, int data ) +{ + switch ( addr & 0xE001 ) + { + case 0xC000: + irq_latch = data; + break; + + case 0xC001: + if ( counter_just_clocked == 1 ) + dprintf( "MMC3 IRQ counter pathological behavior triggered\n" ); + counter_just_clocked = 2; + irq_ctr = 0; + break; + + case 0xE000: + irq_flag = false; + irq_enabled = false; + break; + + case 0xE001: + irq_enabled = true; + break; + } + if ( irq_enabled ) + irq_changed(); +} + +void Mapper_Mmc3::write( nes_time_t time, nes_addr_t addr, int data ) +{ + check( !(addr & ~0xe001) ); // writes to mirrored registers are rare + //dprintf( "%6d %02X->%04X\n", time, data, addr ); + + switch ( addr & 0xE001 ) + { + case 0x8000: { + int changed = mode ^ data; + mode = data; + // avoid unnecessary bank updates + if ( changed & 0x80 ) + update_chr_banks(); + if ( changed & 0x40 ) + update_prg_banks(); + break; + } + + case 0x8001: { + int bank = mode & 7; + banks [bank] = data; + if ( bank < 6 ) + update_chr_banks(); + else + update_prg_banks(); + break; + } + + case 0xA000: + mirror = data; + if ( !(cart().mirroring() & 0x08) ) + { + if ( mirror & 1 ) + mirror_horiz(); + else + mirror_vert(); + } + break; + + case 0xA001: + sram_mode = data; + //dprintf( "%02X->%04X\n", data, addr ); + + // Startropics 1 & 2 use MMC6 and always enable low 512 bytes of SRAM + if ( (data & 0x3F) == 0x30 ) + enable_sram( true ); + else + enable_sram( data & 0x80, data & 0x40 ); + break; + + default: + run_until( time ); + write_irq( addr, data ); + break; + } +} + +Nes_Mapper* Nes_Mapper::make_mmc3() +{ + return BLARGG_NEW Mapper_Mmc3; +} + diff --git a/quicknes/nes_emu/Nes_Namco_Apu.cpp b/quicknes/nes_emu/Nes_Namco_Apu.cpp new file mode 100644 index 0000000000..a15f2af9ef --- /dev/null +++ b/quicknes/nes_emu/Nes_Namco_Apu.cpp @@ -0,0 +1,149 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Namco_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Nes_Namco_Apu::Nes_Namco_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Namco_Apu::~Nes_Namco_Apu() +{ +} + +void Nes_Namco_Apu::reset() +{ + last_time = 0; + addr_reg = 0; + + int i; + for ( i = 0; i < reg_count; i++ ) + reg [i] = 0; + + for ( i = 0; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + osc.delay = 0; + osc.last_amp = 0; + osc.wave_pos = 0; + } +} + +void Nes_Namco_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +/* +void Nes_Namco_Apu::reflect_state( Tagged_Data& data ) +{ + reflect_int16( data, 'ADDR', &addr_reg ); + + static const char hex [17] = "0123456789ABCDEF"; + int i; + for ( i = 0; i < reg_count; i++ ) + reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], ® [i] ); + + for ( i = 0; i < osc_count; i++ ) + { + reflect_int32( data, 'DLY0' + i, &oscs [i].delay ); + reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos ); + } +} +*/ + +void Nes_Namco_Apu::end_frame( nes_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +void Nes_Namco_Apu::run_until( nes_time_t nes_end_time ) +{ + int active_oscs = (reg [0x7F] >> 4 & 7) + 1; + for ( int i = osc_count - active_oscs; i < osc_count; i++ ) + { + Namco_Osc& osc = oscs [i]; + Blip_Buffer* output = osc.output; + if ( !output ) + continue; + + blip_resampled_time_t time = + output->resampled_time( last_time ) + osc.delay; + blip_resampled_time_t end_time = output->resampled_time( nes_end_time ); + osc.delay = 0; + if ( time < end_time ) + { + const BOOST::uint8_t* osc_reg = ® [i * 8 + 0x40]; + if ( !(osc_reg [4] & 0xE0) ) + continue; + + int volume = osc_reg [7] & 15; + if ( !volume ) + continue; + + long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0]; + if ( freq < 64 * active_oscs ) + continue; // prevent low frequencies from excessively delaying freq changes + blip_resampled_time_t period = + output->resampled_duration( 983040 ) / freq * active_oscs; + + int wave_size = 32 - (osc_reg [4] >> 2 & 7) * 4; + if ( !wave_size ) + continue; + + int last_amp = osc.last_amp; + int wave_pos = osc.wave_pos; + + do + { + // read wave sample + int addr = wave_pos + osc_reg [6]; + int sample = reg [addr >> 1] >> (addr << 2 & 4); + wave_pos++; + sample = (sample & 15) * volume; + + // output impulse if amplitude changed + int delta = sample - last_amp; + if ( delta ) + { + last_amp = sample; + synth.offset_resampled( time, delta, output ); + } + + // next sample + time += period; + if ( wave_pos >= wave_size ) + wave_pos = 0; + } + while ( time < end_time ); + + osc.wave_pos = wave_pos; + osc.last_amp = last_amp; + } + osc.delay = time - end_time; + } + + last_time = nes_end_time; +} + diff --git a/quicknes/nes_emu/Nes_Namco_Apu.h b/quicknes/nes_emu/Nes_Namco_Apu.h new file mode 100644 index 0000000000..e22433c695 --- /dev/null +++ b/quicknes/nes_emu/Nes_Namco_Apu.h @@ -0,0 +1,104 @@ + +// Namco 106 sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_NAMCO_APU_H +#define NES_NAMCO_APU_H + +#include "Nes_Apu.h" + +struct namco_state_t; + +class Nes_Namco_Apu { +public: + Nes_Namco_Apu(); + ~Nes_Namco_Apu(); + + // See Nes_Apu.h for reference. + void volume( double ); + void treble_eq( const blip_eq_t& ); + void output( Blip_Buffer* ); + enum { osc_count = 8 }; + void osc_output( int index, Blip_Buffer* ); + void reset(); + void end_frame( nes_time_t ); + + // Read/write data register is at 0x4800 + enum { data_reg_addr = 0x4800 }; + void write_data( nes_time_t, int ); + int read_data(); + + // Write-only address register is at 0xF800 + enum { addr_reg_addr = 0xF800 }; + void write_addr( int ); + + // to do: implement save/restore + void save_state( namco_state_t* out ) const; + void load_state( namco_state_t const& ); + +private: + // noncopyable + Nes_Namco_Apu( const Nes_Namco_Apu& ); + Nes_Namco_Apu& operator = ( const Nes_Namco_Apu& ); + + struct Namco_Osc { + long delay; + Blip_Buffer* output; + short last_amp; + short wave_pos; + }; + + Namco_Osc oscs [osc_count]; + + nes_time_t last_time; + int addr_reg; + + enum { reg_count = 0x80 }; + BOOST::uint8_t reg [reg_count]; + Blip_Synth synth; + + BOOST::uint8_t& access(); + void run_until( nes_time_t ); +}; +/* +struct namco_state_t +{ + BOOST::uint8_t regs [0x80]; + BOOST::uint8_t addr; + BOOST::uint8_t unused; + BOOST::uint8_t positions [8]; + BOOST::uint32_t delays [8]; +}; +*/ + +inline BOOST::uint8_t& Nes_Namco_Apu::access() +{ + int addr = addr_reg & 0x7f; + if ( addr_reg & 0x80 ) + addr_reg = (addr + 1) | 0x80; + return reg [addr]; +} + +inline void Nes_Namco_Apu::volume( double v ) { synth.volume( 0.10 / osc_count * v ); } + +inline void Nes_Namco_Apu::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); } + +inline void Nes_Namco_Apu::write_addr( int v ) { addr_reg = v; } + +inline int Nes_Namco_Apu::read_data() { return access(); } + +inline void Nes_Namco_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Namco_Apu::write_data( nes_time_t time, int data ) +{ + run_until( time ); + access() = data; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Nonlinearizer.cpp b/quicknes/nes_emu/Nes_Nonlinearizer.cpp new file mode 100644 index 0000000000..d6bff8d632 --- /dev/null +++ b/quicknes/nes_emu/Nes_Nonlinearizer.cpp @@ -0,0 +1,157 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/ + +#include "Nes_Nonlinearizer.h" + +/* Library Copyright (C) 2003-2005 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nes_Nonlinearizer::Nes_Nonlinearizer() : Multi_Buffer( 1 ) +{ + enable_nonlinearity( true ); +} + +Nes_Nonlinearizer::~Nes_Nonlinearizer() +{ +} + +blargg_err_t Nes_Nonlinearizer::sample_rate( long rate, int msec ) +{ + BLARGG_RETURN_ERR( buf.sample_rate( rate, msec ) ); + BLARGG_RETURN_ERR( tnd.sample_rate( rate, msec ) ); + return Multi_Buffer::sample_rate( buf.sample_rate(), buf.length() ); +} + +void Nes_Nonlinearizer::clock_rate( long rate ) +{ + buf.clock_rate( rate ); + tnd.clock_rate( rate ); +} + +void Nes_Nonlinearizer::bass_freq( int freq ) +{ + buf.bass_freq( freq ); + tnd.bass_freq( freq ); +} + +void Nes_Nonlinearizer::clear() +{ + accum = 0x8000; + buf.clear(); + tnd.clear(); +} + +Nes_Nonlinearizer::channel_t Nes_Nonlinearizer::channel( int i ) +{ + channel_t c; + c.center = (2 <= i && i <= 4) ? &tnd : &buf; + c.left = c.center; + c.right = c.center; + return c; +} + +void Nes_Nonlinearizer::end_frame( blip_time_t length, bool ) +{ + buf.end_frame( length ); + tnd.end_frame( length ); +} + +long Nes_Nonlinearizer::samples_avail() const +{ + return buf.samples_avail(); +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Nes_Nonlinearizer::enable_nonlinearity( bool b ) +{ + require( b ); // to do: implement non-linear output + double gain = 0x7fff * 0.742467605 * 1.2; + for ( int i = 0; i < half * 2; i++ ) + { + int out = i << shift; + if ( i > half ) + { + double n = 202.0 / (half - 1) * (i - half); + double d = 163.67 / (24329.0 / n + 100); + out = int (d * gain) + 0x8000; + } + table [i] = out; + } +} + +void Nes_Nonlinearizer::make_nonlinear( long count ) +{ + const int zero_offset = 0x7f7f; // to do: use private constant from Blip_Buffer.h + + #define ENTRY( s ) (table [((s) >> shift) & entry_mask]) + + BOOST::uint16_t* p = tnd.buffer_; + unsigned prev = ENTRY( accum ); + long accum = this->accum; + + for ( unsigned n = count; n--; ) + { + accum += (long) *p - zero_offset; + if ( (accum >> shift) >= half * 2 ) + { + // to do: extend table to handle overflow better + check( false ); // overflowed + accum = (half * 2 - 1) << shift; + } + unsigned entry = ENTRY( accum ); + *p++ = entry - prev + zero_offset; + prev = entry; + } + + this->accum = accum; +} + +long Nes_Nonlinearizer::read_samples( blip_sample_t* out, long count ) +{ + long avail = buf.samples_avail(); + assert( tnd.samples_avail() == avail ); + if ( count > avail ) + count = avail; + + if ( count ) + { + make_nonlinear( count ); + + Blip_Reader lin; + Blip_Reader nonlin; + + int lin_bass = lin.begin( buf ); + int nonlin_bass = nonlin.begin( tnd ); + + for ( int n = count; n--; ) + { + int s = lin.read() + nonlin.read(); + lin.next( lin_bass ); + nonlin.next( nonlin_bass ); + *out++ = s; + + if ( (BOOST::int16_t) s != s ) + out [-1] = 0x7FFF - (s >> 24); + } + + lin.end( buf ); + nonlin.end( tnd ); + + buf.remove_samples( count ); + tnd.remove_samples( count ); + } + + return count; +} + diff --git a/quicknes/nes_emu/Nes_Nonlinearizer.h b/quicknes/nes_emu/Nes_Nonlinearizer.h new file mode 100644 index 0000000000..1af78e0aaf --- /dev/null +++ b/quicknes/nes_emu/Nes_Nonlinearizer.h @@ -0,0 +1,44 @@ + +// NES non-linear audio output + +// Nes_Emu 0.5.0. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_NONLINEARIZER_H +#define NES_NONLINEARIZER_H + +#include "Multi_Buffer.h" + +class Nes_Nonlinearizer : public Multi_Buffer { +public: + Nes_Nonlinearizer(); + ~Nes_Nonlinearizer(); + + // Enable non-linear output + void enable_nonlinearity( bool = true ); + + // See Multi_Buffer.h + blargg_err_t sample_rate( long rate, int msec = blip_default_length ); + Multi_Buffer::sample_rate; + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + + // End of public interface +private: + enum { shift = 5 }; + enum { half = 0x8000 >> shift }; + enum { entry_mask = half * 2 - 1 }; + Blip_Buffer buf; + Blip_Buffer tnd; + long accum; + BOOST::uint16_t table [half * 2]; + + void make_nonlinear( long ); +}; + +#endif + diff --git a/quicknes/nes_emu/Nes_Oscs.cpp b/quicknes/nes_emu/Nes_Oscs.cpp new file mode 100644 index 0000000000..704274d01a --- /dev/null +++ b/quicknes/nes_emu/Nes_Oscs.cpp @@ -0,0 +1,545 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +// Nes_Osc + +void Nes_Osc::clock_length( int halt_mask ) +{ + if ( length_counter && !(regs [0] & halt_mask) ) + length_counter--; +} + +void Nes_Envelope::clock_envelope() +{ + int period = regs [0] & 15; + if ( reg_written [3] ) { + reg_written [3] = false; + env_delay = period; + envelope = 15; + } + else if ( --env_delay < 0 ) { + env_delay = period; + if ( envelope | (regs [0] & 0x20) ) + envelope = (envelope - 1) & 15; + } +} + +int Nes_Envelope::volume() const +{ + return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope; +} + +// Nes_Square + +void Nes_Square::clock_sweep( int negative_adjust ) +{ + int sweep = regs [1]; + + if ( --sweep_delay < 0 ) + { + reg_written [1] = true; + + int period = this->period(); + int shift = sweep & shift_mask; + if ( shift && (sweep & 0x80) && period >= 8 ) + { + int offset = period >> shift; + + if ( sweep & negate_flag ) + offset = negative_adjust - offset; + + if ( period + offset < 0x800 ) + { + period += offset; + // rewrite period + regs [2] = period & 0xff; + regs [3] = (regs [3] & ~7) | ((period >> 8) & 7); + } + } + } + + if ( reg_written [1] ) { + reg_written [1] = false; + sweep_delay = (sweep >> 4) & 7; + } +} + +// TODO: clean up +inline nes_time_t Nes_Square::maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + long remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + phase = (phase + count) & (phase_range - 1); + time += (long) count * timer_period; + } + return time; +} + +void Nes_Square::run( nes_time_t time, nes_time_t end_time ) +{ + const int period = this->period(); + const int timer_period = (period + 1) * 2; + + if ( !output ) + { + delay = maintain_phase( time + delay, end_time, timer_period ) - end_time; + return; + } + + int offset = period >> (regs [1] & shift_mask); + if ( regs [1] & negate_flag ) + offset = 0; + + const int volume = this->volume(); + if ( volume == 0 || period < 8 || (period + offset) >= 0x800 ) + { + if ( last_amp ) { + synth.offset( time, -last_amp, output ); + last_amp = 0; + } + + time += delay; + time = maintain_phase( time, end_time, timer_period ); + } + else + { + // handle duty select + int duty_select = (regs [0] >> 6) & 3; + int duty = 1 << duty_select; // 1, 2, 4, 2 + int amp = 0; + if ( duty_select == 3 ) { + duty = 2; // negated 25% + amp = volume; + } + if ( phase < duty ) + amp ^= volume; + + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + const Synth& synth = this->synth; + int delta = amp * 2 - volume; + int phase = this->phase; + + do { + phase = (phase + 1) & (phase_range - 1); + if ( phase == 0 || phase == duty ) { + delta = -delta; + synth.offset_inline( time, delta, output ); + } + time += timer_period; + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->phase = phase; + } + } + + delay = time - end_time; +} + +// Nes_Triangle + +void Nes_Triangle::clock_linear_counter() +{ + if ( reg_written [3] ) + linear_counter = regs [0] & 0x7f; + else if ( linear_counter ) + linear_counter--; + + if ( !(regs [0] & 0x80) ) + reg_written [3] = false; +} + +inline int Nes_Triangle::calc_amp() const +{ + int amp = phase_range - phase; + if ( amp < 0 ) + amp = phase - (phase_range + 1); + return amp; +} + +// TODO: clean up +inline nes_time_t Nes_Triangle::maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ) +{ + long remain = end_time - time; + if ( remain > 0 ) + { + int count = (remain + timer_period - 1) / timer_period; + phase = ((unsigned) phase + 1 - count) & (phase_range * 2 - 1); + phase++; + time += (long) count * timer_period; + } + return time; +} + +void Nes_Triangle::run( nes_time_t time, nes_time_t end_time ) +{ + const int timer_period = period() + 1; + if ( !output ) + { + time += delay; + delay = 0; + if ( length_counter && linear_counter && timer_period >= 3 ) + delay = maintain_phase( time, end_time, timer_period ) - end_time; + return; + } + + // to do: track phase when period < 3 + // to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks. + + int delta = update_amp( calc_amp() ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 ) + { + time = end_time; + } + else if ( time < end_time ) + { + Blip_Buffer* const output = this->output; + + int phase = this->phase; + int volume = 1; + if ( phase > phase_range ) { + phase -= phase_range; + volume = -volume; + } + + do { + if ( --phase == 0 ) { + phase = phase_range; + volume = -volume; + } + else { + synth.offset_inline( time, volume, output ); + } + + time += timer_period; + } + while ( time < end_time ); + + if ( volume < 0 ) + phase += phase_range; + this->phase = phase; + last_amp = calc_amp(); + } + delay = time - end_time; +} + +// Nes_Dmc + +void Nes_Dmc::reset() +{ + address = 0; + dac = 0; + buf = 0; + bits_remain = 1; + bits = 0; + buf_full = false; + silence = true; + next_irq = Nes_Apu::no_irq; + irq_flag = false; + irq_enabled = false; + + Nes_Osc::reset(); + period = 0x1ac; +} + +void Nes_Dmc::recalc_irq() +{ + nes_time_t irq = Nes_Apu::no_irq; + if ( irq_enabled && length_counter ) + irq = apu->last_dmc_time + delay + + ((length_counter - 1) * 8 + bits_remain - 1) * nes_time_t (period) + 1; + if ( irq != next_irq ) { + next_irq = irq; + apu->irq_changed(); + } +} + +int Nes_Dmc::count_reads( nes_time_t time, nes_time_t* last_read ) const +{ + if ( last_read ) + *last_read = time; + + if ( length_counter == 0 ) + return 0; // not reading + + long first_read = next_read_time(); + long avail = time - first_read; + if ( avail <= 0 ) + return 0; + + int count = (avail - 1) / (period * 8) + 1; + if ( !(regs [0] & loop_flag) && count > length_counter ) + count = length_counter; + + if ( last_read ) { + *last_read = first_read + (count - 1) * (period * 8) + 1; + assert( *last_read <= time ); + assert( count == count_reads( *last_read, NULL ) ); + assert( count - 1 == count_reads( *last_read - 1, NULL ) ); + } + + return count; +} + +static const short dmc_period_table [2] [16] = { + {0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC + 0x0be, 0x0a0, 0x08e, 0x080, 0x06a, 0x054, 0x048, 0x036}, + + {0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested) + 0x0b1, 0x095, 0x084, 0x077, 0x062, 0x04e, 0x043, 0x032} // to do: verify PAL periods +}; + +inline void Nes_Dmc::reload_sample() +{ + address = 0x4000 + regs [2] * 0x40; + length_counter = regs [3] * 0x10 + 1; +} + +static const unsigned char dac_table [128] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, 9,10,11,12,13,14, + 15,15,16,17,18,19,20,20,21,22,23,24,24,25,26,27, + 27,28,29,30,31,31,32,33,33,34,35,36,36,37,38,38, + 39,40,41,41,42,43,43,44,45,45,46,47,47,48,48,49, + 50,50,51,52,52,53,53,54,55,55,56,56,57,58,58,59, + 59,60,60,61,61,62,63,63,64,64,65,65,66,66,67,67, + 68,68,69,70,70,71,71,72,72,73,73,74,74,75,75,75, + 76,76,77,77,78,78,79,79,80,80,81,81,82,82,82,83, +}; + +void Nes_Dmc::write_register( int addr, int data ) +{ + if ( addr == 0 ) + { + period = dmc_period_table [pal_mode] [data & 15]; + irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled + irq_flag &= irq_enabled; + recalc_irq(); + } + else if ( addr == 1 ) + { + int old_dac = dac; + dac = data & 0x7F; + + // adjust last_amp so that "pop" amplitude will be properly non-linear + // with respect to change in dac + int faked_nonlinear = dac - (dac_table [dac] - dac_table [old_dac]); + if ( !nonlinear ) + last_amp = faked_nonlinear; + } +} + +void Nes_Dmc::start() +{ + reload_sample(); + fill_buffer(); + recalc_irq(); +} + +void Nes_Dmc::fill_buffer() +{ + if ( !buf_full && length_counter ) + { + require( prg_reader ); // prg_reader must be set + buf = prg_reader( prg_reader_data, 0x8000u + address ); + address = (address + 1) & 0x7FFF; + buf_full = true; + if ( --length_counter == 0 ) + { + if ( regs [0] & loop_flag ) { + reload_sample(); + } + else { + apu->osc_enables &= ~0x10; + irq_flag = irq_enabled; + next_irq = Nes_Apu::no_irq; + apu->irq_changed(); + } + } + } +} + +void Nes_Dmc::run( nes_time_t time, nes_time_t end_time ) +{ + int delta = update_amp( dac ); + if ( !output ) + silence = true; + else if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + int bits_remain = this->bits_remain; + if ( silence && !buf_full ) + { + int count = (end_time - time + period - 1) / period; + bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1; + time += count * period; + } + else + { + Blip_Buffer* const output = this->output; + const int period = this->period; + int bits = this->bits; + int dac = this->dac; + + do + { + if ( !silence ) + { + int step = (bits & 1) * 4 - 2; + bits >>= 1; + if ( unsigned (dac + step) <= 0x7F ) { + dac += step; + synth.offset_inline( time, step, output ); + } + } + + time += period; + + if ( --bits_remain == 0 ) + { + bits_remain = 8; + if ( !buf_full ) { + silence = true; + } + else { + silence = false; + bits = buf; + buf_full = false; + if ( !output ) + silence = true; + fill_buffer(); + } + } + } + while ( time < end_time ); + + this->dac = dac; + this->last_amp = dac; + this->bits = bits; + } + this->bits_remain = bits_remain; + } + delay = time - end_time; +} + +// Nes_Noise + +static const short noise_period_table [16] = { + 0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0, + 0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4 +}; + +void Nes_Noise::run( nes_time_t time, nes_time_t end_time ) +{ + int period = noise_period_table [regs [2] & 15]; + #if NES_APU_NOISE_LOW_CPU + if ( period < 8 ) + { + period = 8; + } + #endif + + if ( !output ) + { + // TODO: clean up + time += delay; + delay = time + (end_time - time + period - 1) / period * period - end_time; + return; + } + + const int volume = this->volume(); + int amp = (noise & 1) ? volume : 0; + int delta = update_amp( amp ); + if ( delta ) + synth.offset( time, delta, output ); + + time += delay; + if ( time < end_time ) + { + const int mode_flag = 0x80; + + if ( !volume ) + { + // round to next multiple of period + time += (end_time - time + period - 1) / period * period; + + // approximate noise cycling while muted, by shuffling up noise register + // to do: precise muted noise cycling? + if ( !(regs [2] & mode_flag) ) { + int feedback = (noise << 13) ^ (noise << 14); + noise = (feedback & 0x4000) | (noise >> 1); + } + } + else + { + Blip_Buffer* const output = this->output; + + // using resampled time avoids conversion in synth.offset() + blip_resampled_time_t rperiod = output->resampled_duration( period ); + blip_resampled_time_t rtime = output->resampled_time( time ); + + int noise = this->noise; + int delta = amp * 2 - volume; + const int tap = (regs [2] & mode_flag ? 8 : 13); + + do { + int feedback = (noise << tap) ^ (noise << 14); + time += period; + + if ( (noise + 1) & 2 ) { + // bits 0 and 1 of noise differ + delta = -delta; + synth.offset_resampled( rtime, delta, output ); + } + + rtime += rperiod; + noise = (feedback & 0x4000) | (noise >> 1); + } + while ( time < end_time ); + + last_amp = (delta + volume) >> 1; + this->noise = noise; + } + } + + delay = time - end_time; +} + diff --git a/quicknes/nes_emu/Nes_Oscs.h b/quicknes/nes_emu/Nes_Oscs.h new file mode 100644 index 0000000000..ee14ac7a37 --- /dev/null +++ b/quicknes/nes_emu/Nes_Oscs.h @@ -0,0 +1,150 @@ + +// Private oscillators used by Nes_Apu + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_OSCS_H +#define NES_OSCS_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Nes_Apu; + +struct Nes_Osc +{ + unsigned char regs [4]; + bool reg_written [4]; + Blip_Buffer* output; + int length_counter;// length counter (0 if unused by oscillator) + int delay; // delay until next (potential) transition + int last_amp; // last amplitude oscillator was outputting + + void clock_length( int halt_mask ); + int period() const { + return (regs [3] & 7) * 0x100 + (regs [2] & 0xff); + } + void reset() { + delay = 0; + last_amp = 0; + } + int update_amp( int amp ) { + int delta = amp - last_amp; + last_amp = amp; + return delta; + } +}; + +struct Nes_Envelope : Nes_Osc +{ + int envelope; + int env_delay; + + void clock_envelope(); + int volume() const; + void reset() { + envelope = 0; + env_delay = 0; + Nes_Osc::reset(); + } +}; + +// Nes_Square +struct Nes_Square : Nes_Envelope +{ + enum { negate_flag = 0x08 }; + enum { shift_mask = 0x07 }; + enum { phase_range = 8 }; + int phase; + int sweep_delay; + + typedef Blip_Synth Synth; + Synth const& synth; // shared between squares + + Nes_Square( Synth const* s ) : synth( *s ) { } + + void clock_sweep( int adjust ); + void run( nes_time_t, nes_time_t ); + void reset() { + sweep_delay = 0; + Nes_Envelope::reset(); + } + nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ); +}; + +// Nes_Triangle +struct Nes_Triangle : Nes_Osc +{ + enum { phase_range = 16 }; + int phase; + int linear_counter; + Blip_Synth synth; + + int calc_amp() const; + void run( nes_time_t, nes_time_t ); + void clock_linear_counter(); + void reset() { + linear_counter = 0; + phase = 1; + Nes_Osc::reset(); + } + nes_time_t maintain_phase( nes_time_t time, nes_time_t end_time, + nes_time_t timer_period ); +}; + +// Nes_Noise +struct Nes_Noise : Nes_Envelope +{ + int noise; + Blip_Synth synth; + + void run( nes_time_t, nes_time_t ); + void reset() { + noise = 1 << 14; + Nes_Envelope::reset(); + } +}; + +// Nes_Dmc +struct Nes_Dmc : Nes_Osc +{ + int address; // address of next byte to read + int period; + //int length_counter; // bytes remaining to play (already defined in Nes_Osc) + int buf; + int bits_remain; + int bits; + bool buf_full; + bool silence; + + enum { loop_flag = 0x40 }; + + int dac; + + nes_time_t next_irq; + bool irq_enabled; + bool irq_flag; + bool pal_mode; + bool nonlinear; + + int (*prg_reader)( void*, nes_addr_t ); // needs to be initialized to prg read function + void* prg_reader_data; + + Nes_Apu* apu; + + Blip_Synth synth; + + void start(); + void write_register( int, int ); + void run( nes_time_t, nes_time_t ); + void recalc_irq(); + void fill_buffer(); + void reload_sample(); + void reset(); + int count_reads( nes_time_t, nes_time_t* ) const; + nes_time_t next_read_time() const; +}; + +#endif + diff --git a/quicknes/nes_emu/Nes_Ppu.cpp b/quicknes/nes_emu/Nes_Ppu.cpp new file mode 100644 index 0000000000..22affb2a3c --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu.cpp @@ -0,0 +1,669 @@ + +// Timing and behavior of PPU + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Ppu.h" + +#include +#include "Nes_State.h" +#include "Nes_Mapper.h" +#include "Nes_Core.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// to do: remove unnecessary run_until() calls + +#include "blargg_source.h" + +// Timing + +ppu_time_t const scanline_len = Nes_Ppu::scanline_len; + +// if non-zero, report sprite max at fixed time rather than calculating it +nes_time_t const fixed_sprite_max_time = 0; // 1 * ((21 + 164) * scanline_len + 100) / ppu_overclock; +int const sprite_max_cpu_offset = 2420 + 3; + +ppu_time_t const t_to_v_time = 20 * scanline_len + 302; +ppu_time_t const even_odd_time = 20 * scanline_len + 328; + +ppu_time_t const first_scanline_time = 21 * scanline_len + 60; // this can be varied +ppu_time_t const first_hblank_time = 21 * scanline_len + 252; + +ppu_time_t const earliest_sprite_max = sprite_max_cpu_offset * ppu_overclock; +ppu_time_t const earliest_sprite_hit = 21 * scanline_len + 339; // needs to be 22 * scanline_len when fixed_sprite_max_time is set + +nes_time_t const vbl_end_time = 2272; +ppu_time_t const max_frame_length = 262 * scanline_len; +//ppu_time_t const max_frame_length = 320 * scanline_len; // longer frame for testing movie resync +nes_time_t const earliest_vbl_end_time = max_frame_length / ppu_overclock - 10; + +// Scanline rendering + +void Nes_Ppu::render_bg_until_( nes_time_t cpu_time ) +{ + ppu_time_t time = ppu_time( cpu_time ); + ppu_time_t const frame_duration = scanline_len * 261; + if ( time > frame_duration ) + time = frame_duration; + + // one-time events + if ( frame_phase <= 1 ) + { + if ( frame_phase < 1 ) + { + // vtemp->vaddr + frame_phase = 1; + if ( w2001 & 0x08 ) + vram_addr = vram_temp; + } + + // variable-length scanline + if ( time <= even_odd_time ) + { + next_bg_time = nes_time( even_odd_time ); + return; + } + frame_phase = 2; + if ( !(w2001 & 0x08) || emu.nes.frame_count & 1 ) + { + if ( --frame_length_extra < 0 ) + { + frame_length_extra = 2; + frame_length_++; + } + burst_phase--; + } + burst_phase = (burst_phase + 2) % 3; + } + + // scanlines + if ( scanline_time < time ) + { + int count = (time - scanline_time + scanline_len) / scanline_len; + + // hblank before next scanline + if ( hblank_time < scanline_time ) + { + hblank_time += scanline_len; + run_hblank( 1 ); + } + + scanline_time += count * scanline_len; + + hblank_time += scanline_len * (count - 1); + int saved_vaddr = vram_addr; + + int start = scanline_count; + scanline_count += count; + draw_background( start, count ); + + vram_addr = saved_vaddr; // to do: this is cheap + run_hblank( count - 1 ); + } + + // hblank after current scanline + ppu_time_t next_ppu_time = hblank_time; + if ( hblank_time < time ) + { + hblank_time += scanline_len; + run_hblank( 1 ); + next_ppu_time = scanline_time; // scanline will run next + } + assert( time <= hblank_time ); + + // either hblank or scanline comes next + next_bg_time = nes_time( next_ppu_time ); +} + +void Nes_Ppu::render_until_( nes_time_t time ) +{ + // render bg scanlines then render sprite scanlines up to wherever bg was rendered to + + render_bg_until( time ); + next_sprites_time = nes_time( scanline_time ); + if ( host_pixels ) + { + int start = next_sprites_scanline; + int count = scanline_count - start; + if ( count > 0 ) + { + next_sprites_scanline += count; + draw_sprites( start, count ); + } + } +} + +// Frame events + +inline void Nes_Ppu::end_vblank() +{ + // clear VBL, sprite hit, and max sprites flags first time after 20 scanlines + r2002 &= end_vbl_mask; + end_vbl_mask = ~0; +} + +inline void Nes_Ppu::run_end_frame( nes_time_t time ) +{ + if ( !frame_ended ) + { + // update frame_length + render_bg_until( time ); + + // set VBL when end of frame is reached + nes_time_t len = frame_length(); + if ( time >= len ) + { + r2002 |= 0x80; + frame_ended = true; + if ( w2000 & 0x80 ) + nmi_time_ = len + 2 - (frame_length_extra >> 1); + } + } +} + +// Sprite max + +inline void Nes_Ppu::invalidate_sprite_max_() +{ + next_sprite_max_run = earliest_sprite_max / ppu_overclock; + sprite_max_set_time = 0; +} + +void Nes_Ppu::run_sprite_max_( nes_time_t cpu_time ) +{ + end_vblank(); // might get run outside $2002 handler + + // 577.0 / 0x10000 ~= 1.0 / 113.581, close enough to accurately calculate which scanline it is + int start_scanline = next_sprite_max_scanline; + next_sprite_max_scanline = unsigned ((cpu_time - sprite_max_cpu_offset) * 577) / 0x10000u; + assert( next_sprite_max_scanline >= 0 && next_sprite_max_scanline <= last_sprite_max_scanline ); + + if ( !sprite_max_set_time ) + { + if ( !(w2001 & 0x18) ) + return; + + long t = recalc_sprite_max( start_scanline ); + sprite_max_set_time = indefinite_time; + if ( t > 0 ) + sprite_max_set_time = t / 3 + sprite_max_cpu_offset; + next_sprite_max_run = sprite_max_set_time; + //dprintf( "sprite_max_set_time: %d\n", sprite_max_set_time ); + } + + if ( cpu_time > sprite_max_set_time ) + { + r2002 |= 0x20; + //dprintf( "Sprite max flag set: %d\n", sprite_max_set_time ); + next_sprite_max_run = indefinite_time; + } +} + +inline void Nes_Ppu::run_sprite_max( nes_time_t t ) +{ + if ( !fixed_sprite_max_time && t > next_sprite_max_run ) + run_sprite_max_( t ); +} + +inline void Nes_Ppu::invalidate_sprite_max( nes_time_t t ) +{ + if ( !fixed_sprite_max_time && !(r2002 & 0x20) ) + { + run_sprite_max( t ); + invalidate_sprite_max_(); + } +} + +// Sprite 0 hit + +inline int Nes_Ppu_Impl::first_opaque_sprite_line() const +{ + // advance earliest time if sprite has blank lines at beginning + byte const* p = map_chr( sprite_tile_index( spr_ram ) * 16 ); + int twice = w2000 >> 5 & 1; // loop twice if double height is set + int line = 0; + do + { + for ( int n = 8; n--; p++ ) + { + if ( p [0] | p [8] ) + return line; + line++; + } + + p += 8; + } + while ( !--twice ); + return line; +} + +void Nes_Ppu::update_sprite_hit( nes_time_t cpu_time ) +{ + ppu_time_t earliest = earliest_sprite_hit + spr_ram [0] * scanline_len + spr_ram [3]; + //ppu_time_t latest = earliest + sprite_height() * scanline_len; + + earliest += first_opaque_sprite_line() * scanline_len; + + ppu_time_t time = ppu_time( cpu_time ); + next_sprite_hit_check = indefinite_time; + + if ( false ) + if ( earliest < time ) + { + r2002 |= 0x40; + return; + } + + if ( time < earliest ) + { + next_sprite_hit_check = nes_time( earliest ); + return; + } + + // within possible range; render scanline and compare pixels + int count_needed = 2 + (time - earliest_sprite_hit - spr_ram [3]) / scanline_len; + if ( count_needed > 240 ) + count_needed = 240; + while ( scanline_count < count_needed ) + render_bg_until( max( cpu_time, next_bg_time + 1 ) ); + + if ( sprite_hit_found < 0 ) + return; // sprite won't hit + + if ( !sprite_hit_found ) + { + // check again next scanline + next_sprite_hit_check = nes_time( earliest_sprite_hit + spr_ram [3] + + (scanline_count - 1) * scanline_len ); + } + else + { + // hit found + ppu_time_t hit_time = earliest_sprite_hit + sprite_hit_found - scanline_len; + + if ( time < hit_time ) + { + next_sprite_hit_check = nes_time( hit_time ); + return; + } + + //dprintf( "Sprite hit x: %d, y: %d, scanline_count: %d\n", + // sprite_hit_found % 341, sprite_hit_found / 341, scanline_count ); + + r2002 |= 0x40; + } +} + +// $2002 + +inline void Nes_Ppu::query_until( nes_time_t time ) +{ + end_vblank(); + + // sprite hit + if ( time > next_sprite_hit_check ) + update_sprite_hit( time ); + + // sprite max + if ( !fixed_sprite_max_time ) + run_sprite_max( time ); + else if ( time >= fixed_sprite_max_time ) + r2002 |= (w2001 << 1 & 0x20) | (w2001 << 2 & 0x20); +} + +int Nes_Ppu::read_2002( nes_time_t time ) +{ + nes_time_t next = next_status_event; + next_status_event = vbl_end_time; + int extra_clock = extra_clocks ? (extra_clocks - 1) >> 2 & 1 : 0; + if ( time > next && time > vbl_end_time + extra_clock ) + { + query_until( time ); + + next_status_event = next_sprite_hit_check; + nes_time_t const next_max = fixed_sprite_max_time ? + fixed_sprite_max_time : next_sprite_max_run; + if ( next_status_event > next_max ) + next_status_event = next_max; + + if ( time > earliest_open_bus_decay() ) + { + next_status_event = earliest_open_bus_decay(); + update_open_bus( time ); + } + + if ( time > earliest_vbl_end_time ) + { + if ( next_status_event > earliest_vbl_end_time ) + next_status_event = earliest_vbl_end_time; + run_end_frame( time ); + + // special vbl behavior when read is just before or at clock when it's set + if ( extra_clocks != 1 ) + { + if ( time == frame_length() ) + { + nmi_time_ = indefinite_time; + //dprintf( "Suppressed NMI\n" ); + } + } + else if ( time == frame_length() - 1 ) + { + r2002 &= ~0x80; + frame_ended = true; + nmi_time_ = indefinite_time; + //dprintf( "Suppressed NMI\n" ); + } + } + } + emu.set_ppu_2002_time( next_status_event ); + + int result = r2002; + second_write = false; + r2002 = result & ~0x80; + poke_open_bus( time, result, 0xE0 ); + update_open_bus( time ); + return ( result & 0xE0 ) | ( open_bus & 0x1F ); +} + +void Nes_Ppu::dma_sprites( nes_time_t time, void const* in ) +{ + //dprintf( "%d sprites written\n", time ); + render_until( time ); + + invalidate_sprite_max( time ); + // catch anything trying to dma while rendering is enabled + check( time + 513 <= vbl_end_time || !(w2001 & 0x18) ); + + memcpy( spr_ram + w2003, in, 0x100 - w2003 ); + memcpy( spr_ram, (char*) in + 0x100 - w2003, w2003 ); +} + +// Read + +inline int Nes_Ppu_Impl::read_2007( int addr ) +{ + int result = r2007; + if ( addr < 0x2000 ) + { + r2007 = *map_chr( addr ); + } + else + { + r2007 = get_nametable( addr ) [addr & 0x3ff]; + if ( addr >= 0x3f00 ) + { + return palette [map_palette( addr )] | ( open_bus & 0xC0 ); + } + } + return result; +} + +int Nes_Ppu::read( unsigned addr, nes_time_t time ) +{ + if ( addr & ~0x2007 ) + dprintf( "Read from mirrored PPU register 0x%04X\n", addr ); + + switch ( addr & 7 ) + { + // status + case 2: // handled inline + return read_2002( time ); + + // sprite ram + case 4: { + int result = spr_ram [w2003]; + if ( (w2003 & 3) == 2 ) + result &= 0xe3; + poke_open_bus( time, result, ~0 ); + return result; + } + + // video ram + case 7: { + render_bg_until( time ); + int addr = vram_addr; + int new_addr = addr + addr_inc; + vram_addr = new_addr; + if ( ~addr & new_addr & vaddr_clock_mask ) + { + emu.mapper->a12_clocked(); + addr = vram_addr - addr_inc; // avoid having to save across func call + } + int result = read_2007( addr & 0x3fff ); + poke_open_bus( time, result, ( ( addr & 0x3fff ) >= 0x3f00 ) ? 0x3F : ~0 ); + return result; + } + + default: + dprintf( "Read from unimplemented PPU register 0x%04X\n", addr ); + break; + } + + update_open_bus( time ); + + return open_bus; +} + +// Write + +void Nes_Ppu::write( nes_time_t time, unsigned addr, int data ) +{ + if ( addr & ~0x2007 ) + dprintf( "Wrote to mirrored PPU register 0x%04X\n", addr ); + + switch ( addr & 7 ) + { + case 0:{// control + int changed = w2000 ^ data; + + if ( changed & 0x28 ) + render_until( time ); // obj height or pattern addr changed + else if ( changed & 0x10 ) + render_bg_until( time ); // bg pattern addr changed + else if ( ((data << 10) ^ vram_temp) & 0x0C00 ) + render_bg_until( time ); // nametable changed + + if ( changed & 0x80 ) + { + if ( time > vbl_end_time + ((extra_clocks - 1) >> 2 & 1) ) + end_vblank(); // to do: clean this up + + if ( data & 0x80 & r2002 ) + { + nmi_time_ = time + 2; + emu.event_changed(); + } + if ( time >= earliest_vbl_end_time ) + run_end_frame( time - 1 + (extra_clocks & 1) ); + } + + // nametable select + vram_temp = (vram_temp & ~0x0C00) | ((data & 3) * 0x400); + + if ( changed & 0x20 ) // sprite height changed + invalidate_sprite_max( time ); + w2000 = data; + addr_inc = data & 4 ? 32 : 1; + + break; + } + + case 1:{// sprites, bg enable + int changed = w2001 ^ data; + + if ( changed & 0xE1 ) + { + render_until( time + 1 ); // emphasis/monochrome bits changed + palette_changed = 0x18; + } + + if ( changed & 0x14 ) + render_until( time + 1 ); // sprite enable/clipping changed + else if ( changed & 0x0A ) + render_bg_until( time + 1 ); // bg enable/clipping changed + + if ( changed & 0x08 ) // bg enabled changed + emu.mapper->run_until( time ); + + if ( !(w2001 & 0x18) != !(data & 0x18) ) + invalidate_sprite_max( time ); // all rendering just turned on or off + + w2001 = data; + + if ( changed & 0x08 ) + emu.irq_changed(); + + break; + } + + case 3: // spr addr + w2003 = data; + poke_open_bus( time, w2003, ~0 ); + break; + + case 4: + //dprintf( "%d sprites written\n", time ); + if ( time > first_scanline_time / ppu_overclock ) + { + render_until( time ); + invalidate_sprite_max( time ); + } + spr_ram [w2003] = data; + w2003 = (w2003 + 1) & 0xff; + break; + + case 5: + render_bg_until( time ); + if ( (second_write ^= 1) ) + { + pixel_x = data & 7; + vram_temp = (vram_temp & ~0x1f) | (data >> 3); + } + else + { + vram_temp = (vram_temp & ~0x73e0) | + (data << 12 & 0x7000) | (data << 2 & 0x03e0); + } + break; + + case 6: + render_bg_until( time ); + if ( (second_write ^= 1) ) + { + vram_temp = (vram_temp & 0xff) | (data << 8 & 0x3f00); + } + else + { + int changed = ~vram_addr & vram_temp; + vram_addr = vram_temp = (vram_temp & 0xff00) | data; + if ( changed & vaddr_clock_mask ) + emu.mapper->a12_clocked(); + } + break; + + default: + dprintf( "Wrote to unimplemented PPU register 0x%04X\n", addr ); + break; + } + + poke_open_bus( time, data, ~0 ); +} + +// Frame begin/end + +nes_time_t Nes_Ppu::begin_frame( ppu_time_t timestamp ) +{ + // current time + int cpu_timestamp = timestamp / ppu_overclock; + extra_clocks = timestamp - cpu_timestamp * ppu_overclock; + + // frame end + ppu_time_t const frame_end = max_frame_length - 1 - extra_clocks; + frame_length_ = (frame_end + (ppu_overclock - 1)) / ppu_overclock; + frame_length_extra = frame_length_ * ppu_overclock - frame_end; + assert( (unsigned) frame_length_extra < 3 ); + + // nmi + nmi_time_ = indefinite_time; + if ( w2000 & 0x80 & r2002 ) + nmi_time_ = 2 - (extra_clocks >> 1); + + // bg rendering + frame_phase = 0; + scanline_count = 0; + hblank_time = first_hblank_time; + scanline_time = first_scanline_time; + next_bg_time = nes_time( t_to_v_time ); + + // sprite rendering + next_sprites_scanline = 0; + next_sprites_time = 0; + + // status register + frame_ended = false; + end_vbl_mask = ~0xE0; + next_status_event = 0; + sprite_hit_found = 0; + next_sprite_hit_check = 0; + next_sprite_max_scanline = 0; + invalidate_sprite_max_(); + + decay_low += cpu_timestamp; + decay_high += cpu_timestamp; + + base::begin_frame(); + + //dprintf( "cpu_timestamp: %d\n", cpu_timestamp ); + return cpu_timestamp; +} + +ppu_time_t Nes_Ppu::end_frame( nes_time_t end_time ) +{ + render_bg_until( end_time ); + render_until( end_time ); + query_until( end_time ); + run_end_frame( end_time ); + + update_open_bus( end_time ); + decay_low -= end_time; + decay_high -= end_time; + + // to do: do more PPU RE to get exact behavior + if ( w2001 & 0x08 ) + { + unsigned a = vram_addr + 2; + if ( (vram_addr & 0xff) >= 0xfe ) + a = (vram_addr ^ 0x400) - 0x1e; + vram_addr = a; + } + + if ( w2001 & 0x10 ) + w2003 = 0; + + suspend_rendering(); + + return (end_time - frame_length_) * ppu_overclock + frame_length_extra; +} + +void Nes_Ppu::poke_open_bus( nes_time_t time, int data, int mask ) +{ + open_bus = ( open_bus & ~mask ) | ( data & mask ); + if ( mask & 0x1F ) decay_low = time + scanline_len * 100 / ppu_overclock; + if ( mask & 0xE0 ) decay_high = time + scanline_len * 100 / ppu_overclock; +} + +const nes_time_t Nes_Ppu::earliest_open_bus_decay() const +{ + return ( decay_low < decay_high ) ? decay_low : decay_high; +} diff --git a/quicknes/nes_emu/Nes_Ppu.h b/quicknes/nes_emu/Nes_Ppu.h new file mode 100644 index 0000000000..905c23ce00 --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu.h @@ -0,0 +1,141 @@ + +// NES PPU emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_PPU_H +#define NES_PPU_H + +#include "Nes_Ppu_Rendering.h" +class Nes_Mapper; +class Nes_Core; + +typedef long nes_time_t; +typedef long ppu_time_t; // ppu_time_t = nes_time_t * ppu_overclock + +ppu_time_t const ppu_overclock = 3; // PPU clocks for each CPU clock + +class Nes_Ppu : public Nes_Ppu_Rendering { + typedef Nes_Ppu_Rendering base; +public: + Nes_Ppu( Nes_Core* ); + + // Begin PPU frame and return beginning CPU timestamp + nes_time_t begin_frame( ppu_time_t ); + + nes_time_t nmi_time() { return nmi_time_; } + void acknowledge_nmi() { nmi_time_ = LONG_MAX / 2 + 1; } + + int read_2002( nes_time_t ); + int read( unsigned addr, nes_time_t ); + void write( nes_time_t, unsigned addr, int ); + + void render_bg_until( nes_time_t ); + void render_until( nes_time_t ); + + // CPU time that frame will have ended by + int frame_length() const { return frame_length_; } + + // End frame rendering and return PPU timestamp for next frame + ppu_time_t end_frame( nes_time_t ); + + // Do direct memory copy to sprite RAM + void dma_sprites( nes_time_t, void const* in ); + + int burst_phase; + +private: + + Nes_Core& emu; + + enum { indefinite_time = LONG_MAX / 2 + 1 }; + + void suspend_rendering(); + int read_( unsigned addr, nes_time_t ); // note swapped arguments! + + // NES<->PPU time conversion + int extra_clocks; + ppu_time_t ppu_time( nes_time_t t ) const { return t * ppu_overclock + extra_clocks; } + nes_time_t nes_time( ppu_time_t t ) const { return (t - extra_clocks) / ppu_overclock; } + + // frame + nes_time_t nmi_time_; + int end_vbl_mask; + int frame_length_; + int frame_length_extra; + bool frame_ended; + void end_vblank(); + void run_end_frame( nes_time_t ); + + // bg rendering + nes_time_t next_bg_time; + ppu_time_t scanline_time; + ppu_time_t hblank_time; + int scanline_count; + int frame_phase; + void render_bg_until_( nes_time_t ); + void run_scanlines( int count ); + + // sprite rendering + ppu_time_t next_sprites_time; + int next_sprites_scanline; + void render_until_( nes_time_t ); + + // $2002 status register + nes_time_t next_status_event; + void query_until( nes_time_t ); + + // sprite hit + nes_time_t next_sprite_hit_check; + void update_sprite_hit( nes_time_t ); + + // open bus decay + void update_open_bus( nes_time_t ); + void poke_open_bus( nes_time_t, int, int mask ); + const nes_time_t earliest_open_bus_decay() const; + + // sprite max + nes_time_t next_sprite_max_run; // doesn't need to run until this time + nes_time_t sprite_max_set_time; // if 0, needs to be recalculated + int next_sprite_max_scanline; + void run_sprite_max_( nes_time_t ); + void run_sprite_max( nes_time_t ); + void invalidate_sprite_max_(); + void invalidate_sprite_max( nes_time_t ); + + friend int nes_cpu_read_likely_ppu( class Nes_Core*, unsigned, nes_time_t ); +}; + +inline void Nes_Ppu::suspend_rendering() +{ + next_bg_time = indefinite_time; + next_sprites_time = indefinite_time; + extra_clocks = 0; +} + +inline Nes_Ppu::Nes_Ppu( Nes_Core* e ) : emu( *e ) +{ + burst_phase = 0; + suspend_rendering(); +} + +inline void Nes_Ppu::render_until( nes_time_t t ) +{ + if ( t > next_sprites_time ) + render_until_( t ); +} + +inline void Nes_Ppu::render_bg_until( nes_time_t t ) +{ + if ( t > next_bg_time ) + render_bg_until_( t ); +} + +inline void Nes_Ppu::update_open_bus( nes_time_t time ) +{ + if ( time >= decay_low ) open_bus &= ~0x1F; + if ( time >= decay_high ) open_bus &= ~0xE0; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Ppu_Bg.h b/quicknes/nes_emu/Nes_Ppu_Bg.h new file mode 100644 index 0000000000..3bcfde4a65 --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu_Bg.h @@ -0,0 +1,69 @@ +while ( true ) +{ + while ( count-- ) + { + int attrib = attr_table [addr >> 2 & 0x07]; + attrib >>= (addr >> 4 & 4) | (addr & 2); + unsigned long offset = (attrib & 3) * attrib_factor + this->palette_offset; + + // draw one tile + cache_t const* lines = this->get_bg_tile( nametable [addr] + bg_bank ); + byte* p = pixels; + addr++; + pixels += 8; // next tile + + if ( !clipped ) + { + // optimal case: no clipping + for ( int n = 4; n--; ) + { + unsigned long line = *lines++; + ((uint32_t*) p) [0] = (line >> 4 & mask) + offset; + ((uint32_t*) p) [1] = (line & mask) + offset; + p += row_bytes; + ((uint32_t*) p) [0] = (line >> 6 & mask) + offset; + ((uint32_t*) p) [1] = (line >> 2 & mask) + offset; + p += row_bytes; + } + } + else + { + lines += fine_y >> 1; + + if ( fine_y & 1 ) + { + unsigned long line = *lines++; + ((uint32_t*) p) [0] = (line >> 6 & mask) + offset; + ((uint32_t*) p) [1] = (line >> 2 & mask) + offset; + p += row_bytes; + } + + for ( int n = height >> 1; n--; ) + { + unsigned long line = *lines++; + ((uint32_t*) p) [0] = (line >> 4 & mask) + offset; + ((uint32_t*) p) [1] = (line & mask) + offset; + p += row_bytes; + ((uint32_t*) p) [0] = (line >> 6 & mask) + offset; + ((uint32_t*) p) [1] = (line >> 2 & mask) + offset; + p += row_bytes; + } + + if ( height & 1 ) + { + unsigned long line = *lines; + ((uint32_t*) p) [0] = (line >> 4 & mask) + offset; + ((uint32_t*) p) [1] = (line & mask) + offset; + } + } + } + + count = count2; + count2 = 0; + addr -= 32; + attr_table = attr_table - nametable + nametable2; + nametable = nametable2; + if ( !count ) + break; +} + diff --git a/quicknes/nes_emu/Nes_Ppu_Core.cpp b/quicknes/nes_emu/Nes_Ppu_Core.cpp new file mode 100644 index 0000000000..95be6b51e5 --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu_Core.cpp @@ -0,0 +1,419 @@ + +// NES PPU register read/write and frame timing + +// Nes_Emu 0.5.0. http://www.slack.net/~ant/ + +#include "Nes_Ppu.h" + +#include + +/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// to do: implement junk in unused bits when reading registers + +// to do: put in common or something +template +inline const T& min( const T& x, const T& y ) +{ + if ( x < y ) + return x; + return y; +} + +typedef BOOST::uint8_t byte; + +ppu_time_t const ppu_overclock = 3; // PPU clocks for each CPU clock +ppu_time_t const scanline_duration = 341; +ppu_time_t const t_to_v_time = 20 * scanline_duration + 293; +//ppu_time_t const t_to_v_time = 19 * scanline_duration + 330; // 322 - 339 passes test +ppu_time_t const first_scanline_time = 21 * scanline_duration + 128; +ppu_time_t const first_hblank_time = 21 * scanline_duration + 256; + +void Nes_Ppu::start_frame() +{ + scanline_time = first_scanline_time; + hblank_time = first_hblank_time; + next_time = t_to_v_time / ppu_overclock; + next_scanline = 0; + frame_phase = 0; + query_phase = 0; + w2003 = 0; + memset( sprite_scanlines, 64 - max_sprites, sizeof sprite_scanlines ); +} + +void Nes_Ppu::query_until( nes_time_t time ) +{ + // nothing happens until scanline 20 + if ( time > 2271 ) + { + // clear VBL flag and sprite hit after 20 scanlines + if ( query_phase < 1 ) + { + query_phase = 1; + r2002 &= ~0xc0; + } + + // update sprite hit + if ( query_phase < 2 && update_sprite_hit( time ) ) + query_phase = 2; + + // set VBL flag a few clocks before the end of the frame (cheap hack) + if ( query_phase < 3 && time > 29777 ) + { + query_phase = 3; + r2002 |= 0x80; + } + } +} + +void Nes_Ppu::end_frame( nes_time_t end_time ) +{ + render_until( end_time ); + query_until( end_time ); + // to do: remove (shows number of sprites per line graphically) + if ( false ) + if ( base_pixels ) + { + for ( int i = 0; i < image_height; i++ ) + { + int n = sprite_scanlines [i] - (64 - max_sprites); + memset( base_pixels + 16 + i * row_bytes, (n <= 8 ? 3 : 6), n ); + } + } +// dprintf( "End of frame\n" ); + start_frame(); +} + +inline byte const* Nes_Ppu::map_chr( int addr ) const +{ + return &chr_rom [map_chr_addr( addr )]; +} + +// Read/write + +inline byte* Nes_Ppu::map_palette( int addr ) +{ + if ( (addr & 3) == 0 ) + addr &= 0x0f; // 0x10, 0x14, 0x18, 0x1c map to 0x00, 0x04, 0x08, 0x0c + + return &palette [addr & 0x1f]; +} + +int Nes_Ppu::read( nes_time_t time, unsigned addr ) +{ + // Don't catch rendering up to present since status reads don't affect + // rendering and status is often polled in a tight loop. + + switch ( addr & 7 ) + { + // status + case 2: { + second_write = false; + query_until( time ); + int result = r2002; + r2002 &= ~0x80; + return result; + } + + // sprite ram + case 4: { + int result = spr_ram [w2003]; + if ( (w2003 & 3) == 2 ) + result &= 0xe3; + return result; + } + + // video ram + case 7: { + render_until( time ); // changes to vram_addr affect rendering + int result = r2007; + int a = vram_addr & 0x3fff; + vram_addr = a + ((w2000 & 4) ? 32 : 1); + if ( a < 0x2000 ) + { + r2007 = *map_chr( a ); + } + else + { + r2007 = get_nametable( a ) [a & 0x3ff]; + + // palette doesn't use read buffer, but it's still filled with nametable contents + if ( a >= 0x3f00 ) + result = *map_palette( a ); + } + return result; + } + } + + return 0; +} + +void Nes_Ppu::write( nes_time_t time, unsigned addr, int data ) +{ + if ( addr > 0x2007 ) + printf( "Write to mirrored $200x\n" ); + + int reg = (addr & 7); + if ( reg == 0 ) + { + // render only if changes to register could affect it + int new_temp = (vram_temp & ~0x0c00) | ((data & 3) * 0x400); + if ( (new_temp - vram_temp) | ((w2000 ^ data) & 0x38) ) + render_until( time ); + + vram_temp = new_temp; + w2000 = data; + return; + } + + render_until( time ); + switch ( reg ) + { + //case 0: // control (handled above) + + case 1: // sprites, bg enable + w2001 = data; + break; + + case 3: // spr addr + w2003 = data; + break; + + case 4: + spr_ram [w2003] = data; + w2003 = (w2003 + 1) & 0xff; + break; + + case 5: + if ( second_write ) { + vram_temp = (vram_temp & ~0x73e0) | + ((data & 0xf8) << 2) | ((data & 7) << 12); + } + else { + pixel_x = data & 7; + vram_temp = (vram_temp & ~0x001f) | (data >> 3); + } + second_write ^= 1; + break; + + case 6: { + unsigned old_addr = vram_addr; + if ( second_write ) + { + vram_addr = vram_temp = (vram_temp & ~0x00ff) | data; +// if ( time >= 2271 ) +// dprintf( "%d VRAM fine: %d, tile: %d\n", +// (int) time, int (vram_addr >> 12), int ((vram_addr >> 5) & 31) ); + } + else + { + vram_temp = (vram_temp & ~0xff00) | ((data << 8) & 0x3f00); + } + second_write ^= 1; +// if ( (vram_addr & old_addr) & 0x2000 ) +// dprintf( "%d Toggled A13\n", time ); + break; + } + + case 7: + { + int a = vram_addr & 0x3fff; + vram_addr = a + ((w2000 & 4) ? 32 : 1); + if ( a < 0x2000 ) + { + a = map_chr_addr( a ); + BOOST::uint8_t& b = impl->chr_ram [a]; + if ( (b ^ data) & chr_write_mask ) + { + b = data; + assert( a < sizeof impl->chr_ram ); + tiles_modified [(unsigned) a / bytes_per_tile] = true; + any_tiles_modified = true; + } + } + else if ( a < 0x3f00 ) + { + get_nametable( a ) [a & 0x3ff] = data; + } + else + { + *map_palette( a ) = data & 0x3f; + } + break; + } + } +} + +// Frame rendering + +// returns true when sprite hit checking is done for the frame (hit flag won't change any more) +bool Nes_Ppu::update_sprite_hit( nes_time_t cpu_time ) +{ + // earliest time of hit empirically determined by testing on NES + ppu_time_t const delay = 21 * scanline_duration + 333; + if ( cpu_time < delay / ppu_overclock ) + return false; + + long time = cpu_time * ppu_overclock - delay; + long low_bound = spr_ram [0] * scanline_duration + spr_ram [3]; + if ( time < low_bound ) + return false; + + int tile = spr_ram [1] + ((w2000 << 5) & 0x100); + int height = 1; + if ( w2000 & 0x20 ) + { + height = 2; + tile = (tile & 1) * 0x100 + (tile & 0xfe); + } + byte const* data = map_chr( tile * bytes_per_tile ); + for ( int n = height; n--; ) + { + for ( int n = 8; n--; ) + { + if ( time < low_bound ) + return false; + + if ( data [0] | data [8] ) + { + r2002 |= 0x40; + return true; + } + + data++; + low_bound += scanline_duration; + } + + data += 8; + } + + return true; +} + +void Nes_Ppu::run_hblank( int n ) +{ + hblank_time += scanline_duration * n; + if ( w2001 & 0x08 ) + { +// vram_addr = (vram_addr & ~0x41f) | (vram_temp & 0x41f); + long addr = vram_addr + n * 0x1000; + if ( addr >= 0x8000 ) + { + addr &= 0x7fff; + + int const mask = 0x3e0; + int a = (addr + 0x20) & mask; + if ( a == 30 * 0x20 ) + { + a &= 0x1f; + addr ^= 0x800; + } + addr = (addr & ~mask) | (a & mask); + } + assert( addr < 0x8000 ); + vram_addr = addr; + } +} + +void Nes_Ppu::render_until_( nes_time_t cpu_time ) +{ + ppu_time_t time = cpu_time * ppu_overclock; + ppu_time_t const frame_duration = scanline_duration * 261; + if ( time > frame_duration ) + time = frame_duration; + + if ( frame_phase == 0 ) + { + frame_phase = 1; + if ( w2001 & 0x08 ) + vram_addr = vram_temp; +// else +// dprintf( "PPU off\n" ); + } + + if ( hblank_time < scanline_time && hblank_time < time ) + run_hblank( 1 ); + + int count = 0; + while ( scanline_time < time ) + { + scanline_time += scanline_duration; + count++; + } + + if ( count ) + { + int start = next_scanline; + int end = start + count; + assert( end <= image_height ); + next_scanline = end; + + if ( base_pixels ) + { + if ( start == 0 ) + { + memcpy( host_palette, palette, 32 ); + int bg = palette [0]; + for ( int i = 0; i < 32; i += 4 ) + host_palette [i] = bg; + memcpy( host_palette + 32, host_palette, 32 ); + } + + if ( w2001 & 0x18 && any_tiles_modified ) + { + any_tiles_modified = false; + update_tiles( 0 ); + } + + if ( w2001 & 0x08 ) + { + draw_background( start, end ); + } + else + { + run_hblank( end - start - 1 ); + black_background( start, end ); + } + + // when clipping just sprites, save left strip then restore after drawing sprites + int const obj_mask = 0x04; + int const bg_mask = 0x02; + int clip_mode = ~w2001 & (obj_mask | bg_mask); + + if ( clip_mode == obj_mask ) + save_left( start, end ); + else if ( clip_mode == bg_mask ) + clip_left( start, end ); + + if ( w2001 & 0x10 ) + draw_sprites( start, end ); + + if ( clip_mode == obj_mask ) + restore_left( start, end ); + else if ( clip_mode == (obj_mask | bg_mask) ) + clip_left( start, end ); + } + else + { + run_hblank( end - start - 1 ); + } + } + + if ( hblank_time < time ) + run_hblank( 1 ); + assert( time <= hblank_time ); + + next_time = min( scanline_time, hblank_time ) / ppu_overclock; +} + diff --git a/quicknes/nes_emu/Nes_Ppu_Impl.cpp b/quicknes/nes_emu/Nes_Ppu_Impl.cpp new file mode 100644 index 0000000000..7bb694b1c2 --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu_Impl.cpp @@ -0,0 +1,483 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Ppu_Impl.h" + +#include +#include "blargg_endian.h" +#include "Nes_State.h" +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int const cache_line_size = 128; // tile cache is kept aligned to this boundary + +Nes_Ppu_Impl::Nes_Ppu_Impl() +{ + impl = NULL; + chr_data = NULL; + chr_size = 0; + tile_cache = NULL; + host_palette = NULL; + max_palette_size = 0; + tile_cache_mem = NULL; + ppu_state_t::unused = 0; + + #ifndef NDEBUG + // verify that unaligned accesses work + static unsigned char b [19] = { 0 }; + static unsigned char b2 [19] = { 1,2,3,4,0,5,6,7,8,0,9,0,1,2,0,3,4,5,6 }; + for ( int i = 0; i < 19; i += 5 ) + *(volatile BOOST::uint32_t*) &b [i] = *(volatile BOOST::uint32_t*) &b2 [i]; + assert( !memcmp( b, b2, 19 ) ); + #endif +} + +Nes_Ppu_Impl::~Nes_Ppu_Impl() +{ + close_chr(); + delete impl; +} + +void Nes_Ppu_Impl::all_tiles_modified() +{ + any_tiles_modified = true; + memset( modified_tiles, ~0, sizeof modified_tiles ); +} + +blargg_err_t Nes_Ppu_Impl::open_chr( byte const* new_chr, long chr_data_size ) +{ + close_chr(); + + if ( !impl ) + { + impl = BLARGG_NEW impl_t; + CHECK_ALLOC( impl ); + chr_ram = impl->chr_ram; + } + + chr_data = new_chr; + chr_size = chr_data_size; + chr_is_writable = false; + + if ( chr_data_size == 0 ) + { + // CHR RAM + chr_data = impl->chr_ram; + chr_size = sizeof impl->chr_ram; + chr_is_writable = true; + } + + // allocate aligned memory for cache + assert( chr_size % chr_addr_size == 0 ); + long tile_count = chr_size / bytes_per_tile; + tile_cache_mem = BLARGG_NEW byte [tile_count * sizeof (cached_tile_t) * 2 + cache_line_size]; + CHECK_ALLOC( tile_cache_mem ); + tile_cache = (cached_tile_t*) (tile_cache_mem + cache_line_size - + (uintptr_t) tile_cache_mem % cache_line_size); + flipped_tiles = tile_cache + tile_count; + + // rebuild cache + all_tiles_modified(); + if ( !chr_is_writable ) + { + any_tiles_modified = false; + rebuild_chr( 0, chr_size ); + } + + return 0; +} + +void Nes_Ppu_Impl::close_chr() +{ + delete [] tile_cache_mem; + tile_cache_mem = NULL; +} + +void Nes_Ppu_Impl::set_chr_bank( int addr, int size, long data ) +{ + check( !chr_is_writable || addr == data ); // to do: is CHR RAM ever bank-switched? + //dprintf( "Tried to set CHR RAM bank at %04X to CHR+%04X\n", addr, data ); + + if ( data + size > chr_size ) + data %= chr_size; + + int count = (unsigned) size / chr_page_size; + assert( chr_page_size * count == size ); + assert( addr + size <= chr_addr_size ); + + int page = (unsigned) addr / chr_page_size; + while ( count-- ) + { + chr_pages [page] = data - page * chr_page_size; + page++; + data += chr_page_size; + } +} + +void Nes_Ppu_Impl::save_state( Nes_State_* out ) const +{ + *out->ppu = *this; + out->ppu_valid = true; + + memcpy( out->spr_ram, spr_ram, out->spr_ram_size ); + out->spr_ram_valid = true; + + out->nametable_size = 0x800; + memcpy( out->nametable, impl->nt_ram, 0x800 ); + if ( nt_banks [3] >= &impl->nt_ram [0xC00] ) + { + // save extra nametable data in chr + out->nametable_size = 0x1000; + memcpy( out->chr, &impl->nt_ram [0x800], 0x800 ); + } + + out->chr_size = 0; + if ( chr_is_writable ) + { + out->chr_size = chr_size; + check( out->nametable_size <= 0x800 ); + assert( out->nametable_size <= 0x800 ); + assert( out->chr_size <= out->chr_max ); + memcpy( out->chr, impl->chr_ram, out->chr_size ); + } +} + +void Nes_Ppu_Impl::load_state( Nes_State_ const& in ) +{ + set_nt_banks( 0, 0, 0, 0 ); + set_chr_bank( 0, 0x2000, 0 ); + + if ( in.ppu_valid ) + STATIC_CAST(ppu_state_t&,*this) = *in.ppu; + + if ( in.spr_ram_valid ) + memcpy( spr_ram, in.spr_ram, sizeof spr_ram ); + + assert( in.nametable_size <= (int) sizeof impl->nt_ram ); + if ( in.nametable_size >= 0x800 ) + { + if ( in.nametable_size > 0x800 ) + memcpy( &impl->nt_ram [0x800], in.chr, 0x800 ); + memcpy( impl->nt_ram, in.nametable, 0x800 ); + } + + if ( chr_is_writable && in.chr_size ) + { + assert( in.chr_size <= (int) sizeof impl->chr_ram ); + memcpy( impl->chr_ram, in.chr, in.chr_size ); + all_tiles_modified(); + } +} + +static BOOST::uint8_t const initial_palette [0x20] = +{ + 0x0f,0x01,0x00,0x01,0x00,0x02,0x02,0x0D,0x08,0x10,0x08,0x24,0x00,0x00,0x04,0x2C, + 0x00,0x01,0x34,0x03,0x00,0x04,0x00,0x14,0x00,0x3A,0x00,0x02,0x00,0x20,0x2C,0x08 +}; + +void Nes_Ppu_Impl::reset( bool full_reset ) +{ + w2000 = 0; + w2001 = 0; + r2002 = 0x80; + r2007 = 0; + open_bus = 0; + decay_low = 0; + decay_high = 0; + second_write = false; + vram_temp = 0; + pixel_x = 0; + + if ( full_reset ) + { + vram_addr = 0; + w2003 = 0; + memset( impl->chr_ram, 0xff, sizeof impl->chr_ram ); + memset( impl->nt_ram, 0xff, sizeof impl->nt_ram ); + memcpy( palette, initial_palette, sizeof palette ); + } + + set_nt_banks( 0, 0, 0, 0 ); + set_chr_bank( 0, chr_addr_size, 0 ); + memset( spr_ram, 0xff, sizeof spr_ram ); + all_tiles_modified(); + if ( max_palette_size > 0 ) + memset( host_palette, 0, max_palette_size * sizeof *host_palette ); +} + +void Nes_Ppu_Impl::capture_palette() +{ + if ( palette_size + palette_increment <= max_palette_size ) + { + palette_offset = (palette_begin + palette_size) * 0x01010101; + + short* out = host_palette + palette_size; + palette_size += palette_increment; + + int i; + + int emph = w2001 << 1 & 0x1C0; + int mono = (w2001 & 1 ? 0x30 : 0x3F); + + for ( i = 0; i < 32; i++ ) + out [i] = (palette [i] & mono) | emph; + + int bg = out [0]; + for ( i = 4; i < 32; i += 4 ) + out [i] = bg; + + memcpy( out + 32, out, 32 * sizeof *out ); + } +} + +void Nes_Ppu_Impl::run_hblank( int count ) +{ + require( count >= 0 ); + + long addr = (vram_addr & 0x7be0) + (vram_temp & 0x41f) + (count * 0x1000); + if ( w2001 & 0x08 ) + { + while ( addr >= 0x8000 ) + { + int y = (addr + 0x20) & 0x3e0; + addr = (addr - 0x8000) & ~0x3e0; + if ( y == 30 * 0x20 ) + y = 0x800; + addr ^= y; + } + vram_addr = addr; + } +} + +#ifdef __MWERKS__ + #pragma ppc_unroll_factor_limit 1 // messes up calc_sprite_max_scanlines loop + static int zero = 0; +#else + const int zero = 0; +#endif + +// Tile cache + +inline unsigned long reorder( unsigned long n ) +{ + n |= n << 7; + return ((n << 14) | n); +} + +inline void Nes_Ppu_Impl::update_tile( int index ) +{ + const byte* in = chr_data + (index) * bytes_per_tile; + byte* out = (byte*) tile_cache [index]; + byte* flipped_out = (byte*) flipped_tiles [index]; + + unsigned long bit_mask = 0x11111111 + zero; + + for ( int n = 4; n--; ) + { + // Reorder two lines of two-bit pixels. No bits are wasted, so + // reordered version is also four bytes. + // + // 12345678 to A0E4B1F5C2G6D3H7 + // ABCDEFGH + unsigned long c = + ((reorder( in [0] ) & bit_mask) << 0) | + ((reorder( in [8] ) & bit_mask) << 1) | + ((reorder( in [1] ) & bit_mask) << 2) | + ((reorder( in [9] ) & bit_mask) << 3); + in += 2; + + SET_BE32( out, c ); + out += 4; + + // make horizontally-flipped version + c = ((c >> 28) & 0x000f) | + ((c >> 20) & 0x00f0) | + ((c >> 12) & 0x0f00) | + ((c >> 4) & 0xf000) | + ((c & 0xf000) << 4) | + ((c & 0x0f00) << 12) | + ((c & 0x00f0) << 20) | + ((c & 0x000f) << 28); + SET_BE32( flipped_out, c ); + flipped_out += 4; + } +} + +void Nes_Ppu_Impl::rebuild_chr( unsigned long begin, unsigned long end ) +{ + unsigned end_index = (end + bytes_per_tile - 1) / bytes_per_tile; + for ( unsigned index = begin / bytes_per_tile; index < end_index; index++ ) + update_tile( index ); +} + +void Nes_Ppu_Impl::update_tiles( int first_tile ) +{ + int chunk = 0; + do + { + if ( !(uint32_t&) modified_tiles [chunk] ) + { + chunk += 4; + } + else + { + do + { + int modified = modified_tiles [chunk]; + if ( modified ) + { + modified_tiles [chunk] = 0; + + int index = first_tile + chunk * 8; + do + { + if ( modified & 1 ) + update_tile( index ); + index++; + } + while ( (modified >>= 1) != 0 ); + } + } + while ( ++chunk & 3 ); + } + } + while ( chunk < chr_tile_count / 8 ); +} + +// Sprite max + +template +struct calc_sprite_max_scanlines +{ + static unsigned long func( byte const* sprites, byte* scanlines, int begin ) + { + typedef BOOST::uint32_t uint32_t; + + unsigned long any_hits = 0; + unsigned long const offset = 0x01010101 + zero; + unsigned limit = 239 + height - begin; + for ( int n = 64; n; --n ) + { + int top = *sprites; + sprites += 4; + byte* p = scanlines + top; + if ( (unsigned) (239 - top) < limit ) + { + unsigned long p0 = (uint32_t&) p [0] + offset; + unsigned long p4 = (uint32_t&) p [4] + offset; + (uint32_t&) p [0] = p0; + any_hits |= p0; + (uint32_t&) p [4] = p4; + any_hits |= p4; + if ( height > 8 ) + { + unsigned long p0 = (uint32_t&) p [ 8] + offset; + unsigned long p4 = (uint32_t&) p [12] + offset; + (uint32_t&) p [ 8] = p0; + any_hits |= p0; + (uint32_t&) p [12] = p4; + any_hits |= p4; + } + } + } + + return any_hits; + } +}; + +long Nes_Ppu_Impl::recalc_sprite_max( int scanline ) +{ + int const max_scanline_count = image_height; + + byte sprite_max_scanlines [256 + 16]; + + // recalculate sprites per scanline + memset( sprite_max_scanlines + scanline, 0x78, last_sprite_max_scanline - scanline ); + unsigned long any_hits; + if ( w2000 & 0x20 ) + any_hits = calc_sprite_max_scanlines<16>::func( spr_ram, sprite_max_scanlines, scanline ); + else + any_hits = calc_sprite_max_scanlines<8 >::func( spr_ram, sprite_max_scanlines, scanline ); + + // cause search to terminate past max_scanline_count if none have 8 or more sprites + (uint32_t&) sprite_max_scanlines [max_scanline_count] = 0; + sprite_max_scanlines [max_scanline_count + 3] = 0x80; + + // avoid scan if no possible hits + if ( !(any_hits & 0x80808080) ) + return 0; + + // find soonest scanline with 8 or more sprites + while ( true ) + { + unsigned long const mask = 0x80808080 + zero; + + // check four at a time + byte* pos = &sprite_max_scanlines [scanline]; + unsigned long n = (uint32_t&) *pos; + while ( 1 ) + { + unsigned long x = n & mask; + pos += 4; + n = (uint32_t&) *pos; + if ( x ) + break; + } + + int height = sprite_height(); + int remain = 8; + int i = 0; + + // find which of the four + pos -= 3 + (pos [-4] >> 7 & 1); + pos += 1 - (*pos >> 7 & 1); + pos += 1 - (*pos >> 7 & 1); + assert( *pos & 0x80 ); + + scanline = pos - sprite_max_scanlines; + if ( scanline >= max_scanline_count ) + break; + + // find time that max sprites flag is set (or that it won't be set) + do + { + int relative = scanline - spr_ram [i]; + i += 4; + if ( (unsigned) relative < (unsigned) height && !--remain ) + { + // now use screwey search for 9th sprite + int offset = 0; + while ( i < 0x100 ) + { + int relative = scanline - spr_ram [i + offset]; + //dprintf( "Checking sprite %d [%d]\n", i / 4, offset ); + i += 4; + offset = (offset + 1) & 3; + if ( (unsigned) relative < (unsigned) height ) + { + //dprintf( "sprite max on scanline %d\n", scanline ); + return scanline * scanline_len + (unsigned) i / 2; + } + } + break; + } + } + while ( i < 0x100 ); + scanline++; + } + + return 0; +} + diff --git a/quicknes/nes_emu/Nes_Ppu_Impl.h b/quicknes/nes_emu/Nes_Ppu_Impl.h new file mode 100644 index 0000000000..3eb8bdf6f2 --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu_Impl.h @@ -0,0 +1,198 @@ + +// NES PPU misc functions and setup + +// Nes_Emu 0.7.0 + +#ifndef NES_PPU_IMPL_H +#define NES_PPU_IMPL_H + +#include "nes_data.h" +class Nes_State_; + +class Nes_Ppu_Impl : public ppu_state_t { +public: + typedef BOOST::uint8_t byte; + typedef BOOST::uint32_t uint32_t; + + Nes_Ppu_Impl(); + ~Nes_Ppu_Impl(); + + void reset( bool full_reset ); + + // Setup + blargg_err_t open_chr( const byte*, long size ); + void rebuild_chr( unsigned long begin, unsigned long end ); + void close_chr(); + void save_state( Nes_State_* out ) const; + void load_state( Nes_State_ const& ); + + enum { image_width = 256 }; + enum { image_height = 240 }; + enum { image_left = 8 }; + enum { buffer_width = image_width + 16 }; + enum { buffer_height = image_height }; + + int write_2007( int ); + + // Host palette + enum { palette_increment = 64 }; + short* host_palette; + int palette_begin; + int max_palette_size; + int palette_size; // set after frame is rendered + + // Mapping + enum { vaddr_clock_mask = 0x1000 }; + void set_nt_banks( int bank0, int bank1, int bank2, int bank3 ); + void set_chr_bank( int addr, int size, long data ); + + // Nametable and CHR RAM + enum { nt_ram_size = 0x1000 }; + enum { chr_addr_size = 0x2000 }; + enum { bytes_per_tile = 16 }; + enum { chr_tile_count = chr_addr_size / bytes_per_tile }; + enum { mini_offscreen_height = 16 }; // double-height sprite + struct impl_t + { + byte nt_ram [nt_ram_size]; + byte chr_ram [chr_addr_size]; + union { + BOOST::uint32_t clip_buf [256 * 2]; + byte mini_offscreen [buffer_width * mini_offscreen_height]; + }; + }; + impl_t* impl; + enum { scanline_len = 341 }; + +protected: + byte spr_ram [0x100]; + void begin_frame(); + void run_hblank( int ); + int sprite_height() const { return (w2000 >> 2 & 8) + 8; } + +protected: //friend class Nes_Ppu; private: + + int addr_inc; // pre-calculated $2007 increment (based on w2001 & 0x04) + int read_2007( int addr ); + + enum { last_sprite_max_scanline = 240 }; + long recalc_sprite_max( int scanline ); + int first_opaque_sprite_line() const; + +protected: //friend class Nes_Ppu_Rendering; private: + + unsigned long palette_offset; + int palette_changed; + void capture_palette(); + + bool any_tiles_modified; + bool chr_is_writable; + void update_tiles( int first_tile ); + + typedef uint32_t cache_t; + typedef cache_t cached_tile_t [4]; + cached_tile_t const& get_bg_tile( int index ) const; + cached_tile_t const& get_sprite_tile( byte const* sprite ) const; + byte* get_nametable( int addr ) { return nt_banks [addr >> 10 & 3]; }; + +private: + + static int map_palette( int addr ); + int sprite_tile_index( byte const* sprite ) const; + + // Mapping + enum { chr_page_size = 0x400 }; + long chr_pages [chr_addr_size / chr_page_size]; + long map_chr_addr( unsigned a ) const { return chr_pages [a / chr_page_size] + a; } + byte* nt_banks [4]; + + // CHR data + byte const* chr_data; // points to chr ram when there is no read-only data + byte* chr_ram; // always points to impl->chr_ram; makes write_2007() faster + long chr_size; + byte const* map_chr( int addr ) const { return &chr_data [map_chr_addr( addr )]; } + + // CHR cache + cached_tile_t* tile_cache; + cached_tile_t* flipped_tiles; + byte* tile_cache_mem; + union { + byte modified_tiles [chr_tile_count / 8]; + uint32_t align_; + }; + void all_tiles_modified(); + void update_tile( int index ); +}; + +inline void Nes_Ppu_Impl::set_nt_banks( int bank0, int bank1, int bank2, int bank3 ) +{ + byte* nt_ram = impl->nt_ram; + nt_banks [0] = &nt_ram [bank0 * 0x400]; + nt_banks [1] = &nt_ram [bank1 * 0x400]; + nt_banks [2] = &nt_ram [bank2 * 0x400]; + nt_banks [3] = &nt_ram [bank3 * 0x400]; +} + +inline int Nes_Ppu_Impl::map_palette( int addr ) +{ + if ( (addr & 3) == 0 ) + addr &= 0x0f; // 0x10, 0x14, 0x18, 0x1c map to 0x00, 0x04, 0x08, 0x0c + return addr & 0x1f; +} + +inline int Nes_Ppu_Impl::sprite_tile_index( byte const* sprite ) const +{ + int tile = sprite [1] + (w2000 << 5 & 0x100); + if ( w2000 & 0x20 ) + tile = (tile & 1) * 0x100 + (tile & 0xfe); + return tile; +} + +inline int Nes_Ppu_Impl::write_2007( int data ) +{ + int addr = vram_addr; + byte* chr_ram = this->chr_ram; // pre-read + int changed = addr + addr_inc; + unsigned const divisor = bytes_per_tile * 8; + int mod_index = (unsigned) addr / divisor % (0x4000 / divisor); + vram_addr = changed; + changed ^= addr; + addr &= 0x3fff; + + // use index into modified_tiles [] since it's calculated sooner than addr is masked + if ( (unsigned) mod_index < 0x2000 / divisor ) + { + // Avoid overhead of checking for read-only CHR; if that is the case, + // this modification will be ignored. + int mod = modified_tiles [mod_index]; + chr_ram [addr] = data; + any_tiles_modified = true; + modified_tiles [mod_index] = mod | (1 << ((unsigned) addr / bytes_per_tile % 8)); + } + else if ( addr < 0x3f00 ) + { + get_nametable( addr ) [addr & 0x3ff] = data; + } + else + { + data &= 0x3f; + byte& entry = palette [map_palette( addr )]; + int changed = entry ^ data; + entry = data; + if ( changed ) + palette_changed = 0x18; + } + + return changed; +} + +inline void Nes_Ppu_Impl::begin_frame() +{ + palette_changed = 0x18; + palette_size = 0; + palette_offset = palette_begin * 0x01010101; + addr_inc = w2000 & 4 ? 32 : 1; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Ppu_Rendering.cpp b/quicknes/nes_emu/Nes_Ppu_Rendering.cpp new file mode 100644 index 0000000000..7cc726feb3 --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu_Rendering.cpp @@ -0,0 +1,487 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Ppu_Rendering.h" + +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +#ifdef __MWERKS__ + static unsigned zero = 0; // helps CodeWarrior optimizer when added to constants +#else + const unsigned zero = 0; // compile-time constant on other compilers +#endif + +// Nes_Ppu_Impl + +inline Nes_Ppu_Impl::cached_tile_t const& + Nes_Ppu_Impl::get_sprite_tile( byte const* sprite ) const +{ + cached_tile_t* tiles = tile_cache; + if ( sprite [2] & 0x40 ) + tiles = flipped_tiles; + int index = sprite_tile_index( sprite ); + + // use index directly, since cached tile is same size as native tile + BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile ); + return *(Nes_Ppu_Impl::cached_tile_t*) + ((byte*) tiles + map_chr_addr( index * bytes_per_tile )); +} + +inline Nes_Ppu_Impl::cached_tile_t const& Nes_Ppu_Impl::get_bg_tile( int index ) const +{ + // use index directly, since cached tile is same size as native tile + BOOST_STATIC_ASSERT( sizeof (cached_tile_t) == bytes_per_tile ); + return *(Nes_Ppu_Impl::cached_tile_t*) + ((byte*) tile_cache + map_chr_addr( index * bytes_per_tile )); +} + +// Fill + +void Nes_Ppu_Rendering::fill_background( int count ) +{ + ptrdiff_t const next_line = scanline_row_bytes - image_width; + uint32_t* pixels = (uint32_t*) scanline_pixels; + + unsigned long fill = palette_offset; + if ( (vram_addr & 0x3f00) == 0x3f00 ) + { + // PPU uses current palette entry if addr is within palette ram + int color = vram_addr & 0x1f; + if ( !(color & 3) ) + color &= 0x0f; + fill += color * 0x01010101; + } + + for ( int n = count; n--; ) + { + for ( int n = image_width / 16; n--; ) + { + pixels [0] = fill; + pixels [1] = fill; + pixels [2] = fill; + pixels [3] = fill; + pixels += 4; + } + pixels = (uint32_t*) ((byte*) pixels + next_line); + } +} + +void Nes_Ppu_Rendering::clip_left( int count ) +{ + ptrdiff_t next_line = scanline_row_bytes; + byte* p = scanline_pixels; + unsigned long fill = palette_offset; + + for ( int n = count; n--; ) + { + ((uint32_t*) p) [0] = fill; + ((uint32_t*) p) [1] = fill; + p += next_line; + } +} + +void Nes_Ppu_Rendering::save_left( int count ) +{ + ptrdiff_t next_line = scanline_row_bytes; + byte* in = scanline_pixels; + uint32_t* out = impl->clip_buf; + + for ( int n = count; n--; ) + { + unsigned long in0 = ((uint32_t*) in) [0]; + unsigned long in1 = ((uint32_t*) in) [1]; + in += next_line; + out [0] = in0; + out [1] = in1; + out += 2; + } +} + +void Nes_Ppu_Rendering::restore_left( int count ) +{ + ptrdiff_t next_line = scanline_row_bytes; + byte* out = scanline_pixels; + uint32_t* in = impl->clip_buf; + + for ( int n = count; n--; ) + { + unsigned long in0 = in [0]; + unsigned long in1 = in [1]; + in += 2; + ((uint32_t*) out) [0] = in0; + ((uint32_t*) out) [1] = in1; + out += next_line; + } +} + +// Background + +void Nes_Ppu_Rendering::draw_background_( int remain ) +{ + // Draws 'remain' background scanlines. Does not modify vram_addr. + + int vram_addr = this->vram_addr & 0x7fff; + byte* row_pixels = scanline_pixels - pixel_x; + int left_clip = (w2001 >> 1 & 1) ^ 1; + row_pixels += left_clip * 8; + do + { + // scanlines until next row + int height = 8 - (vram_addr >> 12); + if ( height > remain ) + height = remain; + + // handle hscroll change before next scanline + int hscroll_changed = (vram_addr ^ vram_temp) & 0x41f; + int addr = vram_addr; + if ( hscroll_changed ) + { + vram_addr ^= hscroll_changed; + height = 1; // hscroll will change after first line + } + remain -= height; + + // increment address for next row + vram_addr += height << 12; + assert( vram_addr < 0x10000 ); + if ( vram_addr & 0x8000 ) + { + int y = (vram_addr + 0x20) & 0x3e0; + vram_addr &= 0x7fff & ~0x3e0; + if ( y == 30 * 0x20 ) + y = 0x800; // toggle vertical nametable + vram_addr ^= y; + } + + // nametable change usually occurs in middle of row + byte const* nametable = get_nametable( addr ); + byte const* nametable2 = get_nametable( addr ^ 0x400 ); + int count2 = addr & 31; + int count = 32 - count2 - left_clip; + if ( pixel_x ) + count2++; + + byte const* attr_table = &nametable [0x3c0 | (addr >> 4 & 0x38)]; + int bg_bank = (w2000 << 4) & 0x100; + addr += left_clip; + + // output pixels + ptrdiff_t const row_bytes = scanline_row_bytes; + byte* pixels = row_pixels; + row_pixels += height * row_bytes; + + unsigned long const mask = 0x03030303 + zero; + unsigned long const attrib_factor = 0x04040404 + zero; + + if ( height == 8 ) + { + // unclipped + assert( (addr >> 12) == 0 ); + addr &= 0x03ff; + int const fine_y = 0; + int const clipped = false; + #include "Nes_Ppu_Bg.h" + } + else + { + // clipped + int const fine_y = addr >> 12; + addr &= 0x03ff; + height -= fine_y & 1; + int const clipped = true; + #include "Nes_Ppu_Bg.h" + } + } + while ( remain ); +} + +// Sprites + +void Nes_Ppu_Rendering::draw_sprites_( int begin, int end ) +{ + // Draws sprites on scanlines begin through end - 1. Handles clipping. + + int const sprite_height = this->sprite_height(); + int end_minus_one = end - 1; + int begin_minus_one = begin - 1; + int index = 0; + do + { + byte const* sprite = &spr_ram [index]; + index += 4; + + // find if sprite is visible + int top_minus_one = sprite [0]; + int visible = end_minus_one - top_minus_one; + if ( visible <= 0 ) + continue; // off bottom + + // quickly determine whether sprite is unclipped + int neg_vis = visible - sprite_height; + int neg_skip = top_minus_one - begin_minus_one; + if ( (neg_skip | neg_vis) >= 0 ) // neg_skip >= 0 && neg_vis >= 0 + { + // unclipped + #ifndef NDEBUG + int top = sprite [0] + 1; + assert( (top + sprite_height) > begin && top < end ); + assert( begin <= top && top + sprite_height <= end ); + #endif + + int const skip = 0; + int visible = sprite_height; + + #define CLIPPED 0 + #include "Nes_Ppu_Sprites.h" + } + else + { + // clipped + if ( neg_vis > 0 ) + visible -= neg_vis; + + if ( neg_skip > 0 ) + neg_skip = 0; + visible += neg_skip; + + if ( visible <= 0 ) + continue; // off top + + // visible and clipped + #ifndef NDEBUG + int top = sprite [0] + 1; + assert( (top + sprite_height) > begin && top < end ); + assert( top < begin || top + sprite_height > end ); + #endif + + int skip = -neg_skip; + + //dprintf( "begin: %d, end: %d, top: %d, skip: %d, visible: %d\n", + // begin, end, top_minus_one + 1, skip, visible ); + + #define CLIPPED 1 + #include "Nes_Ppu_Sprites.h" + } + } + while ( index < 0x100 ); +} + +void Nes_Ppu_Rendering::check_sprite_hit( int begin, int end ) +{ + // Checks for sprite 0 hit on scanlines begin through end - 1. + // Updates sprite_hit_found. Background (but not sprites) must have + // already been rendered for the scanlines. + + // clip + int top = spr_ram [0] + 1; + int skip = begin - top; + if ( skip < 0 ) + skip = 0; + + top += skip; + int visible = end - top; + if ( visible <= 0 ) + return; // not visible + + int height = sprite_height(); + if ( visible >= height ) + { + visible = height; + sprite_hit_found = -1; // signal that no more hit checking will take place + } + + // pixels + ptrdiff_t next_row = this->scanline_row_bytes; + byte const* bg = this->scanline_pixels + spr_ram [3] + (top - begin) * next_row; + cache_t const* lines = get_sprite_tile( spr_ram ); + + // left edge clipping + int start_x = 0; + if ( spr_ram [3] < 8 && (w2001 & 0x01e) != 0x1e ) + { + if ( spr_ram [3] == 0 ) + return; // won't hit + start_x = 8 - spr_ram [3]; + } + + // vertical flip + int final = skip + visible; + if ( spr_ram [2] & 0x80 ) + { + skip += height - 1; + final = skip - visible; + } + + // check each line + unsigned long const mask = 0x01010101 + zero; + do + { + // get pixels for line + unsigned long line = lines [skip >> 1]; + unsigned long hit0 = ((uint32_t*) bg) [0]; + unsigned long hit1 = ((uint32_t*) bg) [1]; + bg += next_row; + line >>= skip << 1 & 2; + line |= line >> 1; + + // check for hits + hit0 = ((hit0 >> 1) | hit0) & (line >> 4); + hit1 = ((hit1 >> 1) | hit1) & line; + if ( (hit0 | hit1) & mask ) + { + // write to memory to avoid endian issues + uint32_t quads [3]; + quads [0] = hit0; + quads [1] = hit1; + + // find which pixel hit + int x = start_x; + do + { + if ( ((byte*) quads) [x] & 1 ) + { + x += spr_ram [3]; + if ( x >= 255 ) + break; // ignore right edge + + if ( spr_ram [2] & 0x80 ) + skip = height - 1 - skip; // vertical flip + int y = spr_ram [0] + 1 + skip; + sprite_hit_found = y * scanline_len + x; + + return; + } + } + while ( x++ < 7 ); + } + if ( skip > final ) + skip -= 2; + skip++; + } + while ( skip != final ); +} + +// Draw scanlines + +inline bool Nes_Ppu_Rendering::sprite_hit_possible( int scanline ) const +{ + return !sprite_hit_found && spr_ram [0] <= scanline && (w2001 & 0x18) == 0x18; +} + +void Nes_Ppu_Rendering::draw_scanlines( int start, int count, + byte* pixels, long pitch, int mode ) +{ + assert( start + count <= image_height ); + assert( pixels ); + + scanline_pixels = pixels + image_left; + scanline_row_bytes = pitch; + + int const obj_mask = 2; + int const bg_mask = 1; + int draw_mode = (w2001 >> 3) & 3; + int clip_mode = (~w2001 >> 1) & draw_mode; + + if ( !(draw_mode & bg_mask) ) + { + // no background + clip_mode |= bg_mask; // avoid unnecessary save/restore + if ( mode & bg_mask ) + fill_background( count ); + } + + if ( start == 0 && mode & 1 ) + memset( sprite_scanlines, max_sprites - sprite_limit, 240 ); + + if ( (draw_mode &= mode) ) + { + // sprites and/or background are being rendered + + if ( any_tiles_modified && chr_is_writable ) + { + any_tiles_modified = false; + update_tiles( 0 ); + } + + if ( draw_mode & bg_mask ) + { + //dprintf( "bg %3d-%3d\n", start, start + count - 1 ); + draw_background_( count ); + + if ( clip_mode == bg_mask ) + clip_left( count ); + + if ( sprite_hit_possible( start + count ) ) + check_sprite_hit( start, start + count ); + } + + if ( draw_mode & obj_mask ) + { + // when clipping just sprites, save left strip then restore after drawing them + if ( clip_mode == obj_mask ) + save_left( count ); + + //dprintf( "obj %3d-%3d\n", start, start + count - 1 ); + + draw_sprites_( start, start + count ); + + if ( clip_mode == obj_mask ) + restore_left( count ); + + if ( clip_mode == (obj_mask | bg_mask) ) + clip_left( count ); + } + } + + scanline_pixels = NULL; +} + +void Nes_Ppu_Rendering::draw_background( int start, int count ) +{ + // always capture palette at least once per frame + if ( (start + count >= 240 && !palette_size) || (w2001 & palette_changed) ) + { + palette_changed = false; + capture_palette(); + } + + if ( host_pixels ) + { + draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 1 ); + } + else if ( sprite_hit_possible( start + count ) ) + { + // not rendering, but still handle sprite hit using mini graphics buffer + int y = spr_ram [0] + 1; + int skip = min( count, max( y - start, 0 ) ); + int visible = min( count - skip, sprite_height() ); + + assert( skip + visible <= count ); + assert( visible <= mini_offscreen_height ); + + if ( visible > 0 ) + { + run_hblank( skip ); + draw_scanlines( start + skip, visible, impl->mini_offscreen, buffer_width, 3 ); + } + } +} + diff --git a/quicknes/nes_emu/Nes_Ppu_Rendering.h b/quicknes/nes_emu/Nes_Ppu_Rendering.h new file mode 100644 index 0000000000..68929973f9 --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu_Rendering.h @@ -0,0 +1,63 @@ + +// NES PPU emulator graphics rendering + +// Nes_Emu 0.7.0 + +#ifndef NES_PPU_RENDERING_H +#define NES_PPU_RENDERING_H + +#include "Nes_Ppu_Impl.h" + +class Nes_Ppu_Rendering : public Nes_Ppu_Impl { + typedef Nes_Ppu_Impl base; +public: + Nes_Ppu_Rendering(); + + int sprite_limit; + + byte* host_pixels; + long host_row_bytes; + +protected: + + long sprite_hit_found; // -1: sprite 0 didn't hit, 0: no hit so far, > 0: y * 341 + x + void draw_background( int start, int count ); + void draw_sprites( int start, int count ); + +private: + + void draw_scanlines( int start, int count, byte* pixels, long pitch, int mode ); + void draw_background_( int count ); + + // destination for draw functions; avoids extra parameters + byte* scanline_pixels; + long scanline_row_bytes; + + // fill/copy + void fill_background( int count ); + void clip_left( int count ); + void save_left( int count ); + void restore_left( int count ); + + // sprites + enum { max_sprites = 64 }; + byte sprite_scanlines [image_height]; // number of sprites on each scanline + void draw_sprites_( int start, int count ); + bool sprite_hit_possible( int scanline ) const; + void check_sprite_hit( int begin, int end ); +}; + +inline Nes_Ppu_Rendering::Nes_Ppu_Rendering() +{ + sprite_limit = 8; + host_pixels = NULL; +} + +inline void Nes_Ppu_Rendering::draw_sprites( int start, int count ) +{ + assert( host_pixels ); + draw_scanlines( start, count, host_pixels + host_row_bytes * start, host_row_bytes, 2 ); +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Ppu_Sprites.h b/quicknes/nes_emu/Nes_Ppu_Sprites.h new file mode 100644 index 0000000000..4358ad74c0 --- /dev/null +++ b/quicknes/nes_emu/Nes_Ppu_Sprites.h @@ -0,0 +1,132 @@ + +int sprite_2 = sprite [2]; + +// pixels +ptrdiff_t next_row = this->scanline_row_bytes; +byte* out = this->scanline_pixels + sprite [3] + + (top_minus_one + skip - begin_minus_one) * next_row; +cache_t const* lines = get_sprite_tile( sprite ); + +int dir = 1; +byte* scanlines = this->sprite_scanlines + 1 + top_minus_one + skip; + +if ( sprite_2 & 0x80 ) +{ + // vertical flip + out -= next_row; + out += visible * next_row; + next_row = -next_row; + dir = -1; + scanlines += visible - 1; + #if CLIPPED + int height = this->sprite_height(); + skip = height - skip - visible; + assert( skip + visible <= height ); + #endif +} + +// attributes +unsigned long offset = (sprite_2 & 3) * 0x04040404 + (this->palette_offset + 0x10101010); + +unsigned long const mask = 0x03030303 + zero; +unsigned long const maskgen = 0x80808080 + zero; + +#define DRAW_PAIR( shift ) { \ + int sprite_count = *scanlines; \ + CALC_FOUR( ((uint32_t*) out) [0], (line >> (shift + 4)), out0 ) \ + CALC_FOUR( ((uint32_t*) out) [1], (line >> shift), out1 ) \ + if ( sprite_count < this->max_sprites ) { \ + ((uint32_t*) out) [0] = out0; \ + ((uint32_t*) out) [1] = out1; \ + } \ + if ( CLIPPED ) visible--; \ + out += next_row; \ + *scanlines = sprite_count + 1; \ + scanlines += dir; \ + if ( CLIPPED && !visible ) break; \ +} + +if ( !(sprite_2 & 0x20) ) +{ + // front + unsigned long const maskgen2 = 0x7f7f7f7f + zero; + + #define CALC_FOUR( in, line, out ) \ + unsigned long out; \ + { \ + unsigned long bg = in; \ + unsigned long sp = line & mask; \ + unsigned long bgm = maskgen2 + ((bg >> 4) & mask); \ + unsigned long spm = (maskgen - sp) & maskgen2; \ + unsigned long m = (bgm & spm) >> 2; \ + out = (bg & ~m) | ((sp + offset) & m); \ + } + + #if CLIPPED + lines += skip >> 1; + unsigned long line = *lines++; + if ( skip & 1 ) + goto front_skip; + + while ( true ) + { + DRAW_PAIR( 0 ) + front_skip: + DRAW_PAIR( 2 ) + line = *lines++; + } + #else + for ( int n = visible >> 1; n--; ) + { + unsigned long line = *lines++; + DRAW_PAIR( 0 ) + DRAW_PAIR( 2 ) + } + #endif + + #undef CALC_FOUR +} +else +{ + // behind + unsigned long const omask = 0x20202020 + zero; + unsigned long const bg_or = 0xc3c3c3c3 + zero; + + #define CALC_FOUR( in, line, out ) \ + unsigned long out; \ + { \ + unsigned long bg = in; \ + unsigned long sp = line & mask; \ + unsigned long bgm = maskgen - (bg & mask); \ + unsigned long spm = maskgen - sp; \ + out = (bg & (bgm | bg_or)) | (spm & omask) | \ + (((offset & spm) + sp) & ~(bgm >> 2)); \ + } + + #if CLIPPED + lines += skip >> 1; + unsigned long line = *lines++; + if ( skip & 1 ) + goto back_skip; + + while ( true ) + { + DRAW_PAIR( 0 ) + back_skip: + DRAW_PAIR( 2 ) + line = *lines++; + } + #else + for ( int n = visible >> 1; n--; ) + { + unsigned long line = *lines++; + DRAW_PAIR( 0 ) + DRAW_PAIR( 2 ) + } + #endif + + #undef CALC_FOUR +} + +#undef CLIPPED +#undef DRAW_PAIR diff --git a/quicknes/nes_emu/Nes_Recorder.cpp b/quicknes/nes_emu/Nes_Recorder.cpp new file mode 100644 index 0000000000..861951184e --- /dev/null +++ b/quicknes/nes_emu/Nes_Recorder.cpp @@ -0,0 +1,472 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Recorder.h" + +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int const joypad_sync_value = 0xFF; // joypad data on frames it's never read + +Nes_Recorder::Nes_Recorder() +{ + cache = 0; + film_ = 0; + frames = 0; + resync_enabled = false; + disable_reverse( 1 ); // sets cache_period_ and cache_size + reverse_allowed = true; + buffer_height_ = Nes_Ppu::buffer_height * frames_size + 2; +} + +Nes_Recorder::~Nes_Recorder() +{ + free( frames ); + delete [] cache; +} + +blargg_err_t Nes_Recorder::init_() +{ + RETURN_ERR( base::init_() ); + + cache = new Nes_State [cache_size]; + + int frame_count = reverse_allowed ? frames_size : 1; + CHECK_ALLOC( frames = (saved_frame_t*) calloc( sizeof *frames, frame_count ) ); + for ( int i = 0; i < frame_count; i++ ) + frames [i].top = (long) i * Nes_Ppu::buffer_height + 1; + + return 0; +} + +void Nes_Recorder::clear_cache() +{ + ready_to_resync = false; + reverse_enabled = false; + for ( int i = 0; i < cache_size; i++ ) + cache [i].set_timestamp( invalid_frame_count ); +} + +void Nes_Recorder::set_film( Nes_Film* new_film, frame_count_t time ) +{ + require( new_film ); + film_ = new_film; + clear_cache(); + if ( !film_->blank() ) + { + tell_ = film_->constrain( time ); + check( tell_ == time ); // catch seeks outside film + seek_( tell_ ); + } +} + +void Nes_Recorder::reset( bool full_reset, bool erase_battery_ram ) +{ + base::reset( full_reset, erase_battery_ram ); + tell_ = 0; + film_->clear(); + clear_cache(); +} + +void Nes_Recorder::loading_state( Nes_State const& in ) +{ + reset(); + tell_ = in.timestamp(); +} + +// Frame emulation + +inline int Nes_Recorder::cache_index( frame_count_t t ) const +{ + return (t / cache_period_) % cache_size; +} + +void Nes_Recorder::emulate_frame_( Nes_Film::joypad_t joypad ) +{ + if ( base::timestamp() % cache_period_ == 0 ) + save_state( &cache [cache_index( base::timestamp() )] ); + + if ( base::emulate_frame( joypad & 0xFF, (joypad >> 8) & 0xFF ) ) { } +} + +void Nes_Recorder::replay_frame_( Nes_Film::joypad_t joypad ) +{ + if ( base::timestamp() % film_->period() == 0 ) + { + if ( film_->read_snapshot( base::timestamp() ).timestamp() == invalid_frame_count ) + { + Nes_State_* ss = film_->modify_snapshot( base::timestamp() ); + if ( ss ) + save_state( ss ); + else + check( false ); // out of memory simply causes lack of caching + } + } + + emulate_frame_( joypad ); +} + +int Nes_Recorder::replay_frame() +{ + frame_count_t start_time = base::timestamp(); + int joypad = film_->get_joypad( start_time ); + if ( !film_->has_joypad_sync() ) + { + replay_frame_( joypad ); + } + else if ( (joypad & 0xFF) != joypad_sync_value ) + { + // joypad should be read + replay_frame_( joypad ); + if ( !joypad_read_count() ) + { + // emulator has fallen behind + dprintf( "Fell behind joypad data \n" ); + base::set_timestamp( start_time ); + } + } + else + { + // get joypad for next frame in case emulator gets ahead + if ( film_->contains_frame( start_time + 1 ) ) + { + joypad = film_->get_joypad( start_time + 1 ); + if ( (joypad & 0xFF) == joypad_sync_value ) + joypad = 0; // next frame shouldn't read joypad either, so just give it nothing + } + + // joypad should not be read + replay_frame_( joypad ); + if ( joypad_read_count() ) + { + // emulator is ahead + dprintf( "Ahead of joypad data \n" ); + base::set_timestamp( film_->constrain( base::timestamp() + 1 ) ); + } + } + return base::timestamp() - start_time; +} + +// Film handling + +Nes_State_ const* Nes_Recorder::nearest_snapshot( frame_count_t time ) const +{ + Nes_State_ const* ss = film_->nearest_snapshot( time ); + if ( ss ) + { + // check cache for any snapshots more recent than film_'s + for ( frame_count_t t = time - time % cache_period_; + ss->timestamp() < t; t -= cache_period_ ) + { + Nes_State_ const& cache_ss = cache [cache_index( t )]; + if ( cache_ss.timestamp() == t ) + return &cache_ss; + } + } + return ss; +} + +void Nes_Recorder::seek_( frame_count_t time ) +{ + Nes_State_ const* ss = nearest_snapshot( time ); + if ( !film_->contains( time ) || !ss ) + { + require( false ); // tried to seek outside recording + return; + } + + base::load_state( *ss ); + frame_ = 0; // don't render graphics + frame_count_t max_iter = (time - base::timestamp()) * 2; // don't seek forever + while ( base::timestamp() < time && max_iter-- ) + replay_frame(); +} + +void Nes_Recorder::seek( frame_count_t time ) +{ + check( film_->contains( time ) ); + time = film_->constrain( time ); + if ( time != tell() ) + { + reverse_enabled = false; + seek_( time ); + tell_ = base::timestamp(); + } +} + +inline frame_count_t Nes_Recorder::advancing_frame() +{ + if ( reverse_enabled ) + { + reverse_enabled = false; + if ( !film_->blank() ) + seek_( tell_ ); + } + frame_ = &frames [0]; + tell_ = base::timestamp(); + return tell_++; +} + +blargg_err_t Nes_Recorder::emulate_frame( int joypad, int joypad2 ) +{ + frame_count_t time = advancing_frame(); + require( film_->blank() || film_->contains( time ) ); + + Nes_Film::joypad_t joypads = joypad2 * 0x100 + joypad; + + Nes_State_* ss = 0; + RETURN_ERR( film_->record_frame( time, joypads, &ss ) ); + if ( ss ) + save_state( ss ); + + emulate_frame_( joypads ); + + if ( film_->has_joypad_sync() && !joypad_read_count() ) + RETURN_ERR( film_->set_joypad( time, joypad_sync_value ) ); + + // avoid stale cache snapshot after trimming film + if ( base::timestamp() % cache_period_ == 0 ) + cache [cache_index( base::timestamp() )].set_timestamp( invalid_frame_count ); + + return 0; +} + +void Nes_Recorder::next_frame() +{ + if ( tell() >= film_->end() ) + { + check( false ); // tried to go past end + return; + } + frame_count_t time = advancing_frame(); + assert( base::timestamp() == time || base::timestamp() == time + 1 ); + + // ready_to_resync avoids endless resyncing if joypad isn't getting read when + // it should, thus the timestamp never incrementing. + if ( ready_to_resync ) + { + Nes_State_ const& ss = film_->read_snapshot( time ); + if ( ss.timestamp() == time ) + { + // todo: remove + #if !defined (NDEBUG) && 1 + dprintf( "Resynced \n" ); + + static Nes_State* temp = BLARGG_NEW Nes_State [2]; + static char* temp2 = new char [sizeof *temp]; + if ( temp && temp2 ) + { + save_state( temp ); + memcpy( temp2, temp, sizeof *temp ); + long a = temp->apu.noise.shift_reg; + long b = temp->apu.apu.w40xx [0x11]; + long c = temp->apu.dmc.bits; + base::load_state( ss ); + save_state( temp ); + save_state( &temp [1] ); + + // shift register and dac are not maintained + temp->apu.noise.shift_reg = a; + temp->apu.apu.w40xx [0x11] = b; + temp->apu.dmc.bits = c; + + if ( memcmp( temp2, temp, sizeof *temp ) ) + { + check( !"Film sync corrected error" ); + Std_File_Writer out; + (void) !out.open( "state0" ); + (void) !temp [0].write( out ); + (void) !out.open( "state1" ); + (void) !temp [1].write( out ); + //(void) !out.open( "state2" ); + //(void) !ss.write( out ); + } + } + + if ( 0 ) + #endif + base::load_state( ss ); + } + } + + int count = replay_frame(); + tell_ = base::timestamp(); + + // examination of count prevents endless resync if frame is getting doubled + ready_to_resync = false; + if ( count && resync_enabled && film_->read_snapshot( tell_ ).timestamp() == tell_ ) + { + fade_sound_out = true; + ready_to_resync = true; + } +} + +// Extra features + +void Nes_Recorder::record_keyframe() +{ + if ( !film_->blank() ) + { + Nes_State_* ss = film_->modify_snapshot( base::timestamp() ); + if ( !ss ) + { + check( false ); // out of memory simply causes lack of key frame adjustment + } + // first snapshot can only be replaced if key frame is at beginning of film + else if ( ss->timestamp() > film_->begin() || base::timestamp() == film_->begin() ) + { + if ( ss->timestamp() != base::timestamp() ) + save_state( ss ); + } + } +} + +frame_count_t Nes_Recorder::nearby_keyframe( frame_count_t time ) const +{ + // TODO: reimplement using direct snapshot and cache access + check( film_->contains( time ) ); + + // don't adjust time if seeking to beginning or end + if ( film_->begin() < time && time < film_->end() ) + { + // rounded time must be within about a minute of requested time + int const half_threshold = 45 * frame_rate; + + // find nearest snapshots before and after requested time + frame_count_t after = invalid_frame_count; + frame_count_t before = time + half_threshold; + do + { + after = before; + Nes_State_ const* ss = nearest_snapshot( film_->constrain( before - 1 ) ); + if ( !ss ) + { + require( false ); // tried to seek outside recording + return time; + } + before = ss->timestamp(); + } + while ( time < before ); + + // determine closest and use if within threshold + frame_count_t closest = after; + if ( time - before < after - time ) + closest = before; + int delta = time - closest; + if ( max( delta, -delta ) < half_threshold ) + time = closest; + if ( time < film_->begin() ) + time = film_->begin(); + } + return time; +} + +void Nes_Recorder::skip( int delta ) +{ + if ( delta ) // rounding code can't handle zero + { + // round to nearest cache timestamp (even if not in cache) + frame_count_t current = tell(); + frame_count_t time = current + delta + cache_period_ / 2; + time -= time % cache_period_; + if ( delta < 0 ) + { + if ( time >= current ) + time -= cache_period_; + } + else if ( time <= current ) + { + time += cache_period_; + } + seek( film_->constrain( time ) ); + } +} + +// Reverse handling + +// Get index of frame at given timestamp in reverse frames +inline int Nes_Recorder::reverse_index( frame_count_t time ) const +{ + int index = time % (frames_size * 2); + if ( index >= frames_size ) // opposite direction on odd runs + index = frames_size * 2 - 1 - index; + return index; +} + +// Generate frame at given timestamp into proper position in reverse frames +void Nes_Recorder::reverse_fill( frame_count_t time ) +{ + if ( time >= film_->begin() ) + { + // todo: can cause excessive seeking when joypad data loses sync + if ( base::timestamp() != time ) + seek_( time ); + + saved_frame_t* frame = &frames [reverse_index( time )]; + frame_ = frame; + if ( time % frames_size == frames_size - 1 ) + fade_sound_out = true; + replay_frame(); + frame->sample_count = base::read_samples( frame->samples, frame->max_samples ); + } +} + +void Nes_Recorder::prev_frame() +{ + if ( tell() <= film_->begin() || !reverse_allowed ) + { + check( false ); // tried to go before beginning + return; + } + + int offset = tell_ % frames_size; + frame_count_t aligned = tell_ - offset; + if ( reverse_enabled ) + { + reverse_fill( aligned - 1 - offset ); + } + else + { + reverse_enabled = true; + for ( int i = 0; i < frames_size; i++ ) + { + frame_count_t time = aligned + i; + if ( i >= offset ) + time -= offset + frames_size; // restore some of previous second + reverse_fill( time ); + } + } + + tell_--; + frame_ = &frames [reverse_index( tell_ )]; +} + +long Nes_Recorder::read_samples( short* out, long count ) +{ + require( count >= frame().sample_count ); + if ( !reverse_enabled ) + return base::read_samples( out, count ); + + // copy samples in reverse without reversing left and right channels + // to do: optimize? + count = frame().sample_count; + blip_sample_t const* in = STATIC_CAST(saved_frame_t const&,frame()).samples; + int step = frame().chan_count - 1; + for ( int i = 0; i < count; i++ ) + out [count - 1 - (i ^ step)] = in [i]; + + return count; +} + diff --git a/quicknes/nes_emu/Nes_Recorder.h b/quicknes/nes_emu/Nes_Recorder.h new file mode 100644 index 0000000000..eb6254438f --- /dev/null +++ b/quicknes/nes_emu/Nes_Recorder.h @@ -0,0 +1,142 @@ + +// NES emulator with movie recording and playback in both directions + +// Nes_Emu 0.7.0 + +#ifndef NES_RECORDER_H +#define NES_RECORDER_H + +#include "Nes_Emu.h" +#include "Nes_Film.h" + +class Nes_Recorder : public Nes_Emu { +public: + // Set new film to use for recording and playback. If film isn't empty, seeks to + // its beginning (or to optional specified fime). Film *must* be loaded before + // using the recorder for emulation. + void set_film( Nes_Film*, frame_count_t ); + void set_film( Nes_Film* f ) { set_film( f, f->begin() ); } + + // Currently loaded film + Nes_Film& film() const { return *film_; } + + // Time references are in terms of timestamps at a single moment, rather than + // the frames that occur between two timestamps. All timestamps must be within + // the current recording. The emulator is always at a particular timestamp in the + // recording; a frame exists only in the graphics buffer and sample buffer. Shown + // below are timestamps and the frame that occurs between 1 and 2. + // |---|///|---|--- + // 0 1 2 3 timestamps of snapshots between frames + + // Current timestamp + frame_count_t tell() const; + + // Seek to new timestamp. Time is constrained to recording if it falls outside. + void seek( frame_count_t ); + + // Record new frame at current timestamp and remove anything previously + // recorded after it. + blargg_err_t emulate_frame( int joypad, int joypad2 = 0 ); + + // Increment timestamp and generate frame for that duration. Does nothing + // if already at end of recording. + void next_frame(); + + // Decrement timestamp and generate frame for that duration. Does nothing + // if already at beginning of recording. Performance is close to that of + // next_frame(). + void prev_frame(); + +// Additional features + + // Disable reverse support and optionally use less-frequent cache snapshots, in order + // to drastically reduce the required height of the graphics buffer and memory usage. + // If used, must be called one time *before* any use of emulator. + void disable_reverse( int cache_period_secs = 5 ); + + // Call when current film has been significantly changed (loaded from file). + // Doesn't need to be called if film was merely trimmed. + void film_changed(); + + // Attempt to add keyframe to film at current timestamp, which will make future + // seeking to this point faster after film is later loaded. + void record_keyframe(); + + // Get time of nearby key frame within +/- 45 seconds, otherwise return time unchanged. + // Seeking to times of key frames is much faster than an arbitrary time. Time + // is constrained to film if it falls outside. + frame_count_t nearby_keyframe( frame_count_t ) const; + + // Quickly skip forwards or backwards, staying within recording. May skip slightly + // more or less than delta, but will always skip at least some if delta is non-zero + // (i.e. repeated skip( 1 ) calls will eventually reach the end of the recording). + void skip( int delta ); + + // When enabled, emulator is synchronized at each snapshot in film. This + // corrects any emulation differences compared to when the film was made. Might + // be necessary when playing movies made in an older version of this emulator + // or from other emulators. + void enable_resync( bool b = true ) { resync_enabled = b; } + + // Height of graphics buffer needed when running forward (next_frame() or + // emulate_frame(), but not prev_frame()) + enum { forward_buffer_height = Nes_Ppu::buffer_height }; + +public: + Nes_Recorder(); + virtual ~Nes_Recorder(); + virtual blargg_err_t init_(); + virtual void reset( bool = true, bool = false ); + void load_state( Nes_State const& s ) { Nes_Emu::load_state( s ); } + blargg_err_t load_state( Auto_File_Reader r ) { return Nes_Emu::load_state( r ); } + virtual long read_samples( short* out, long count ); +private: + typedef Nes_Emu base; + + // snapshots + Nes_State* cache; + int cache_size; + int cache_period_; + Nes_Film* film_; + void clear_cache(); + int cache_index( frame_count_t ) const; + Nes_State_ const* nearest_snapshot( frame_count_t ) const; + + // film + frame_count_t tell_; + bool resync_enabled; + bool ready_to_resync; + void emulate_frame_( Nes_Film::joypad_t ); + void replay_frame_( Nes_Film::joypad_t ); + int replay_frame(); + void seek_( frame_count_t ); + frame_count_t advancing_frame(); + void loading_state( Nes_State const& ); + + // reverse handling + enum { frames_size = frame_rate }; + struct saved_frame_t : Nes_Emu::frame_t { + enum { max_samples = 2048 }; + blip_sample_t samples [max_samples]; + }; + saved_frame_t* frames; + bool reverse_enabled; + bool reverse_allowed; + void reverse_fill( frame_count_t ); + int reverse_index( frame_count_t ) const; // index for given frame +}; + +inline frame_count_t Nes_Recorder::tell() const { return film_->constrain( tell_ ); } + +inline void Nes_Recorder::film_changed() { set_film( &film(), film().constrain( tell() ) ); } + +inline void Nes_Recorder::disable_reverse( int new_cache_period ) +{ + reverse_allowed = false; + buffer_height_ = Nes_Ppu::buffer_height; + cache_period_ = new_cache_period * frame_rate; + cache_size = 2 * 60 * frame_rate / cache_period_ + 7; // +7 reduces contention +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Rewinder.cpp b/quicknes/nes_emu/Nes_Rewinder.cpp new file mode 100644 index 0000000000..039a270f41 --- /dev/null +++ b/quicknes/nes_emu/Nes_Rewinder.cpp @@ -0,0 +1,324 @@ + +// Nes_Emu 0.5.6. http://www.slack.net/~ant/ + +#include "Nes_Rewinder.h" + +#include + +/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// to do: fade out at transitions between forward and reverse + +#include BLARGG_SOURCE_BEGIN + +// If true, always keep recent frame images in graphics buffer. Reduces overall +// performance by about 33% on my machine, due to the frame buffer not staying in the cache. +bool const quick_reverse = false; + +Nes_Rewinder::Nes_Rewinder( frame_count_t snapshot_period ) : recorder( snapshot_period ) +{ + pixels = NULL; + frames = NULL; + reverse_enabled = false; +} + +Nes_Rewinder::~Nes_Rewinder() +{ + delete [] frames; +} + +blargg_err_t Nes_Rewinder::init() +{ + if ( !frames ) + { + BLARGG_RETURN_ERR( recorder::init() ); + + frames = BLARGG_NEW frame_t [frames_size]; + BLARGG_CHECK_ALLOC( frames ); + } + + return blargg_success; +} + +blargg_err_t Nes_Rewinder::load_ines_rom( Data_Reader& in, Data_Reader* ips ) +{ + if ( !frames ) + BLARGG_RETURN_ERR( init() ); + + return recorder::load_ines_rom( in, ips ); +} + +long Nes_Rewinder::samples_avail() const +{ + return frames [current_frame].sample_count; +} + +inline void copy_reverse( const blip_sample_t* in, int count, blip_sample_t* out, int step ) +{ + in += count; + while ( count > 0 ) + { + count -= step; + in -= step; + *out = *in; + out += step; + } +} + +long Nes_Rewinder::read_samples( short* out, long out_size ) +{ + int count = samples_avail(); + if ( count ) + { + if ( count > out_size ) + { + count = out_size; + assert( false ); // has no provision for reading partial buffer + } + + if ( !reverse_enabled ) + { + memcpy( out, frames [current_frame].samples, count * sizeof *out ); + } + else + { + int step = samples_per_frame(); + for ( int i = step; i-- > 0; ) + copy_reverse( frames [current_frame].samples + i, count, out + i, step ); + } + + if ( fade_sound_in ) + { + fade_sound_in = false; + fade_samples_( out, count, 1 ); + } + + if ( frames [current_frame].fade_out ) + { + fade_sound_in = true; + fade_samples_( out, count, -1 ); + } + } + return count; +} + +void Nes_Rewinder::seek( frame_count_t time ) +{ + if ( time != tell() ) + { + clear_reverse(); + recorder::seek_( time ); + } +} + +inline void Nes_Rewinder::set_output( int index ) +{ + recorder::set_pixels( pixels + index * frame_height * row_bytes, row_bytes ); +} + +void Nes_Rewinder::frame_rendered( int index, bool using_buffer ) +{ + frame_t& frame = frames [index]; + if ( recorder::frames_emulated() > 0 ) + { + frame.palette_size = recorder::palette_size(); + for ( int i = frame.palette_size; i--; ) + frame.palette [i] = recorder::palette_entry( i ); + frame.sample_count = recorder::read_samples( frame.samples, frame.max_samples ); + } + else if ( pixels && using_buffer ) + { + int old_index = (index + frames_size - 1) % frames_size; + + memcpy( &frame, &frames [old_index], sizeof frame ); + + // to do: handle case where row_bytes is a lot greater than buffer_width + memcpy( pixels + index * frame_height * row_bytes, + pixels + old_index * frame_height * row_bytes, + row_bytes * frame_height ); + } + frame.fade_out = false; +} + +blargg_err_t Nes_Rewinder::next_frame( int joypad, int joypad2 ) +{ + if ( reverse_enabled ) + { + if ( !get_film().empty() ) // if empty then we can't seek + recorder::seek_( reversed_time ); + clear_reverse(); + } + + current_frame = 0; + if ( quick_reverse ) + { + current_frame = recorder::tell() % frames_size; + if ( buffer_scrambled ) + buffer_scrambled--; + } + set_output( current_frame ); + + BLARGG_RETURN_ERR( recorder::next_frame( joypad, joypad2 ) ); + frame_rendered( current_frame, quick_reverse ); + + return blargg_success; +} + +frame_count_t Nes_Rewinder::tell() const +{ + return reverse_enabled ? reversed_time : recorder::tell(); +} + +void Nes_Rewinder::clear_cache() +{ + recorder::clear_cache(); + clear_reverse(); +} + +void Nes_Rewinder::clear_reverse() +{ + buffer_scrambled = frames_size; + reverse_enabled = false; + reverse_unmirrored = 0; + reverse_pivot = 0; + reversed_time = 0; +} + +void Nes_Rewinder::play_frame_( int index ) +{ + if ( negative_seek > 0 ) + { + negative_seek--; + } + else + { + set_output( index ); + recorder::play_frame_(); + frame_rendered( index, true ); + } +} + +void Nes_Rewinder::seek_clamped( frame_count_t time ) +{ + negative_seek = movie_begin() - time; + if ( negative_seek > 0 ) + time = movie_begin(); + recorder::seek_( time ); +} + +void Nes_Rewinder::enter_reverse() +{ + reversed_time = recorder::tell() - 1; + + reverse_pivot = reversed_time % frames_size; + frame_count_t first_frame = reversed_time - reverse_pivot; + if ( buffer_scrambled ) + { + // buffer hasn't been filled with a clean second of frames since last seek + + dprintf( "Refilling reverse buffer, pivot: %d\n", (int) reverse_pivot ); + + // fill beginning + seek_clamped( first_frame ); + for ( int i = 0; i <= reverse_pivot; i++ ) + play_frame_( i ); + frames [0].fade_out = true; + + // fill end + seek_clamped( first_frame - frames_size + reverse_pivot + 1 ); + for ( int i = reverse_pivot + 1; i < frames_size; i++ ) + play_frame_( i ); + } + + if ( reverse_pivot + 1 < frames_size ) + frames [reverse_pivot + 1].fade_out = true; + + reverse_unmirrored = 2; // unmirrored for first two passes, then alternating + reverse_pivot = -reverse_pivot; // don't pivot yet + + seek_clamped( first_frame - frames_size ); + + // Buffer is now filled. Current second is at beginning and previous at end, + // and in this example reversed_time is 24 and reverse_pivot is 4: + // 20 21 22 23 24 25 16 17 18 19 + + // As fragment of current second is played backwards, it will be replaced with + // beginning of previous second: + + // <--------------- + // 20 21 22 23 24 25 16 17 18 19 frame 25 + // 20 21 22 23 24 10 16 17 18 19 frame 24 + // 20 21 22 23 11 10 16 17 18 19 frame 23 + // 20 21 22 12 11 10 16 17 18 19 frame 22 + // 20 21 13 12 11 10 16 17 18 19 frame 21 + // 20 14 13 12 11 10 16 17 18 19 frame 20 + // 15 14 13 12 11 10 16 17 18 19 frame 19 + // Then filling will keep replacing buffer contents in a converging fashion: + // <--------- + // 15 14 13 12 11 10 16 17 18 0 frame 19 + // 15 14 13 12 11 10 16 17 1 0 frame 18 + // 15 14 13 12 11 10 16 2 1 0 frame 17 + // 15 14 13 12 11 10 3 2 1 0 frame 16 + // --------------> + // 4 14 13 12 11 10 3 2 1 0 frame 15 + // 4 5 13 12 11 10 3 2 1 0 frame 14 + // 4 5 6 12 11 10 3 2 1 0 frame 13 + // 4 5 6 7 11 10 3 2 1 0 frame 12 + // 4 5 6 7 8 10 3 2 1 0 frame 11 + // 4 5 6 7 8 9 3 2 1 0 frame 10 + // <--------------- + // etc. +} + +void Nes_Rewinder::prev_frame() +{ + if ( tell() <= movie_begin() ) + { + require( false ); // tried to go before beginning of movie + return; + } + + if ( !reverse_enabled ) + { + reverse_enabled = true; + enter_reverse(); + } + else + { + play_frame_( current_frame ); + + reversed_time--; + if ( reversed_time % frames_size == frames_size - 1 ) + { + if ( reverse_pivot < 0 ) + reverse_pivot = -reverse_pivot; + + if ( --reverse_unmirrored < 0 ) + reverse_unmirrored = 1; + + seek_clamped( reversed_time - frames_size * 2 + 1 ); + } + } + + // determine index of frame in buffer + int raw_index = reversed_time % frames_size; + int index = raw_index; + if ( !reverse_unmirrored ) + index = frames_size - 1 - index; + if ( index <= reverse_pivot ) + index = reverse_pivot - index; + current_frame = index; + + if ( raw_index == 0 ) // previous frame will be from previous restoration + frames [index].fade_out = true; +} + diff --git a/quicknes/nes_emu/Nes_Rewinder.h b/quicknes/nes_emu/Nes_Rewinder.h new file mode 100644 index 0000000000..5617349875 --- /dev/null +++ b/quicknes/nes_emu/Nes_Rewinder.h @@ -0,0 +1,95 @@ + +// NES recorder with smooth rewind (backwards playback) + +// Nes_Emu 0.5.6. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. + +#ifndef NES_REWINDER_H +#define NES_REWINDER_H + +#include "Nes_Recorder.h" + +class Nes_Rewinder : public Nes_Recorder { + typedef Nes_Recorder recorder; + enum { frames_size = frames_per_second }; +public: + explicit Nes_Rewinder( frame_count_t snapshot_period = 0 ); + ~Nes_Rewinder(); + + // Nes_Rewinder adds a single feature to Nes_Recorder: the ability to generate + // consecutive previous frames with similar performance to normal forward + // generation. + + // Emulate frame *ending* at current timestamp. On exit, timestamp = timestamp - 1. + void prev_frame(); + + // Recently-generated frames are stored in a caller-supplied graphics buffer, + // which is many times taller than a single frame image. + enum { frame_height = Nes_Recorder::buffer_height }; + enum { buffer_height = frame_height * frames_size }; + + // Y coordinate of image for current frame in graphics buffer + int get_buffer_y(); + + // Documented in Nes_Emu.h and Nes_Recorder.h + blargg_err_t load_ines_rom( Data_Reader&, Data_Reader* = NULL ); + void set_pixels( void*, long bytes_per_row ); + blargg_err_t next_frame( int joypad, int joypad2 = 0 ); + frame_count_t tell() const; + void seek( frame_count_t ); + long samples_avail() const; + long read_samples( short* out, long max_samples ); + int palette_size() const { return frames [current_frame].palette_size; } + int palette_entry( int i ) const { return frames [current_frame].palette [i]; } + blargg_err_t init(); + + // End of public interface +private: + + BOOST::uint8_t* pixels; + long row_bytes; + + // frame cache + struct frame_t + { + int sample_count; + bool fade_out; + int palette_size; + byte palette [max_palette_size]; + enum { max_samples = 2048 }; + blip_sample_t samples [max_samples]; + }; + frame_t* frames; + int current_frame; + bool fade_sound_in; + void set_output( int index ); + void frame_rendered( int index, bool using_buffer ); + void clear_cache(); // Nes_Recorder override + + // clamped seek + int negative_seek; // if positive, number of frames to ignore before playing + void play_frame_( int index ); + void seek_clamped( frame_count_t ); // allows timestamp to be before beginning + + // reversed frame mapping + frame_count_t reversed_time;// current time is different than emulator time in reverse + int buffer_scrambled; // number of frames remaining before buffer isn't scrambled + bool reverse_enabled; + int reverse_unmirrored; // number of buffers until buffer order is mirrored + int reverse_pivot; // pivot point in buffer + void clear_reverse(); + void enter_reverse(); +}; + +inline int Nes_Rewinder::get_buffer_y() +{ + return current_frame * frame_height + image_top; +} + +inline void Nes_Rewinder::set_pixels( void* p, long rb ) +{ + pixels = (BOOST::uint8_t*) p; + row_bytes = rb; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Rom.cpp b/quicknes/nes_emu/Nes_Rom.cpp new file mode 100644 index 0000000000..d1e74b04e5 --- /dev/null +++ b/quicknes/nes_emu/Nes_Rom.cpp @@ -0,0 +1,216 @@ + +// Nes_Emu 0.5.6. http://www.slack.net/~ant/ + +#include "Nes_Rom.h" + +#include +#include + +/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nes_Rom::Nes_Rom() +{ + prg_ = NULL; + chr_ = NULL; + reset(); +} + +Nes_Rom::~Nes_Rom() +{ + reset(); +} + +void Nes_Rom::reset() +{ + free( prg_ ); + prg_ = NULL; + + free( chr_ ); + chr_ = NULL; + + prg_size_ = 0; + chr_size_ = 0; + mapper = 0; +} + +long Nes_Rom::round_to_bank_size( long n ) +{ + n += bank_size - 1; + return n - n % bank_size; +} + +blargg_err_t Nes_Rom::resize_prg( long size ) +{ + if ( size != prg_size_ ) + { + // extra byte allows CPU to always read operand of instruction, which + // might go past end of ROM + void* p = realloc( prg_, round_to_bank_size( size ) + 1 ); + BLARGG_CHECK_ALLOC( p || !size ); + prg_ = (byte*) p; + prg_size_ = size; + } + return blargg_success; +} + +blargg_err_t Nes_Rom::resize_chr( long size ) +{ + if ( size != chr_size_ ) + { + void* p = realloc( chr_, round_to_bank_size( size ) ); + BLARGG_CHECK_ALLOC( p || !size ); + chr_ = (byte*) p; + chr_size_ = size; + } + return blargg_success; +} + +// iNES reading + +struct ines_header_t { + BOOST::uint8_t signature [4]; + BOOST::uint8_t prg_count; // number of 16K PRG banks + BOOST::uint8_t chr_count; // number of 8K CHR banks + BOOST::uint8_t flags; // MMMM FTBV Mapper low, Four-screen, Trainer, Battery, V mirror + BOOST::uint8_t flags2; // MMMM --XX Mapper high 4 bits + BOOST::uint8_t zero [8]; // if zero [7] is non-zero, treat flags2 as zero +}; +BOOST_STATIC_ASSERT( sizeof (ines_header_t) == 16 ); + +blargg_err_t Nes_Rom::load_ines_rom( Data_Reader& in ) +{ + ines_header_t h; + BLARGG_RETURN_ERR( in.read( &h, sizeof h ) ); + + if ( 0 != memcmp( h.signature, "NES\x1A", 4 ) ) + return "Not a iNES ROM file"; + + if ( h.zero [7] ) // handle header defaced by a fucking idiot's handle + h.flags2 = 0; + + set_mapper( h.flags, h.flags2 ); + + if ( h.flags & 0x04 ) // skip trainer + BLARGG_RETURN_ERR( in.skip( 512 ) ); + + BLARGG_RETURN_ERR( resize_prg( h.prg_count * 16 * 1024L ) ); + BLARGG_RETURN_ERR( resize_chr( h.chr_count * 8 * 1024L ) ); + + BLARGG_RETURN_ERR( in.read( prg(), prg_size() ) ); + BLARGG_RETURN_ERR( in.read( chr(), chr_size() ) ); + + return blargg_success; +} + +// IPS patching + +// IPS patch file format (integers are big-endian): +// 5 "PATCH" +// n blocks +// +// normal block: +// 3 offset +// 2 size +// n data +// +// repeated byte block: +// 3 offset +// 2 0 +// 2 size +// 1 fill value +// +// end block (optional): +// 3 "EOF" +// +// A block can append data to the file by specifying an offset at the end of +// the current file data. + +typedef BOOST::uint8_t byte; +static blargg_err_t apply_ips_patch( Data_Reader& patch, byte** file, long* file_size ) +{ + byte signature [5]; + BLARGG_RETURN_ERR( patch.read( signature, sizeof signature ) ); + if ( memcmp( signature, "PATCH", sizeof signature ) ) + return "Not an IPS patch file"; + + while ( patch.remain() ) + { + // read offset + byte buf [6]; + BLARGG_RETURN_ERR( patch.read( buf, 3 ) ); + long offset = buf [0] * 0x10000 + buf [1] * 0x100 + buf [2]; + if ( offset == 'EOF' ) + break; + + // read size + BLARGG_RETURN_ERR( patch.read( buf, 2 ) ); + long size = buf [0] * 0x100 + buf [1]; + + // size = 0 signals a run of identical bytes + int fill = -1; + if ( size == 0 ) + { + BLARGG_RETURN_ERR( patch.read( buf, 3 ) ); + size = buf [0] * 0x100 + buf [1]; + fill = buf [2]; + } + + // expand file if new data is at exact end of file + if ( offset == *file_size ) + { + *file_size = offset + size; + void* p = realloc( *file, *file_size ); + BLARGG_CHECK_ALLOC( p ); + *file = (byte*) p; + } + + //dprintf( "Patch offset: 0x%04X, size: 0x%04X\n", (int) offset, (int) size ); + + if ( offset < 0 || *file_size < offset + size ) + return "IPS tried to patch past end of file"; + + // read/fill data + if ( fill < 0 ) + BLARGG_RETURN_ERR( patch.read( *file + offset, size ) ); + else + memset( *file + offset, fill, size ); + } + + return blargg_success; +} + +blargg_err_t Nes_Rom::load_patched_ines_rom( Data_Reader& in, Data_Reader& patch ) +{ + // read file into memory + long size = in.remain(); + byte* ines = (byte*) malloc( size ); + BLARGG_CHECK_ALLOC( ines ); + const char* err = in.read( ines, size ); + + // apply patch + if ( !err ) + err = apply_ips_patch( patch, &ines, &size ); + + // load patched file + if ( !err ) + { + Mem_File_Reader patched( ines, size ); + err = load_ines_rom( patched ); + } + + free( ines ); + + return err; +} + diff --git a/quicknes/nes_emu/Nes_Rom.h b/quicknes/nes_emu/Nes_Rom.h new file mode 100644 index 0000000000..814bbfeccf --- /dev/null +++ b/quicknes/nes_emu/Nes_Rom.h @@ -0,0 +1,86 @@ + +// NES ROM data loaded into memory + +// Nes_Emu 0.5.6. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. + +#ifndef NES_ROM_H +#define NES_ROM_H + +#include "blargg_common.h" +#include "abstract_file.h" + +class Nes_Rom { + typedef BOOST::uint8_t byte; +public: + Nes_Rom(); + ~Nes_Rom(); + + // Load iNES ROM file + blargg_err_t load_ines_rom( Data_Reader& ); + + // Load iNES ROM file and apply IPS patch + blargg_err_t load_patched_ines_rom( Data_Reader&, Data_Reader& ips_patch ); + + // to do: support UNIF? + + // True if a ROM is currently loaded + bool loaded() const { return prg_ != NULL; } + + // Free any loaded ROM data + void reset(); + + // True if ROM claims to have battery-backed memory + bool has_battery_ram() const; + + // Size of PRG data + long prg_size() const { return prg_size_; } + + // Size of CHR data + long chr_size() const { return chr_size_; } + + // Change size of PRG (code) data + blargg_err_t resize_prg( long ); + + // Change size of CHR (graphics) data + blargg_err_t resize_chr( long ); + + // Set mapper and information bytes. LSB and MSB are the standard iNES header + // bytes at offsets 6 and 7. + void set_mapper( int mapper_lsb, int mapper_msb ); + + // Initial mirroring setup + int mirroring() const { return mapper & 0x09; } + + // iNES mapper code + int mapper_code() const; + + // Pointer to beginning of PRG data + byte * prg() { return prg_; } + byte const* prg() const { return prg_; } + + // Pointer to beginning of CHR data + byte * chr() { return chr_; } + byte const* chr() const { return chr_; } + + // End of public interface +private: + enum { bank_size = 8 * 1024L }; // bank sizes must be a multiple of this + byte* prg_; + byte* chr_; + long prg_size_; + long chr_size_; + unsigned mapper; + long round_to_bank_size( long n ); +}; + +inline bool Nes_Rom::has_battery_ram() const { return mapper & 0x02; } + +inline void Nes_Rom::set_mapper( int mapper_lsb, int mapper_msb ) +{ + mapper = mapper_msb * 0x100 + mapper_lsb; +} + +inline int Nes_Rom::mapper_code() const { return ((mapper >> 8) & 0xf0) | ((mapper >> 4) & 0x0f); } + +#endif + diff --git a/quicknes/nes_emu/Nes_Snapshot.cpp b/quicknes/nes_emu/Nes_Snapshot.cpp new file mode 100644 index 0000000000..d27c821b8d --- /dev/null +++ b/quicknes/nes_emu/Nes_Snapshot.cpp @@ -0,0 +1,329 @@ + +// Nes_Emu 0.5.6. http://www.slack.net/~ant/ + +#include "Nes_Snapshot.h" + +#include +#include + +#include "blargg_endian.h" +#include "Nes_Emu.h" +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +typedef BOOST::uint8_t byte; + +Nes_Snapshot_Array::Nes_Snapshot_Array() +{ + data = NULL; + size_ = 0; +} + +Nes_Snapshot_Array::~Nes_Snapshot_Array() +{ + free( data ); +} + +blargg_err_t Nes_Snapshot_Array::resize( int new_size ) +{ + void* new_mem = realloc( data, new_size * sizeof (Nes_Snapshot) ); + BLARGG_CHECK_ALLOC( !new_size || new_mem ); + data = (Nes_Snapshot*) new_mem; + + int old_size = size_; + size_ = new_size; + for ( int i = old_size; i < new_size; i++ ) + (*this) [i].clear(); + + return blargg_success; +} + +void Nes_Snapshot::clear() +{ + memset( &nes, 0, sizeof nes ); + nes.frame_count = invalid_frame_count; + + nes_valid = false; + cpu_valid = false; + joypad_valid = false; + apu_valid = false; + ppu_valid = false; + mapper_valid = false; + ram_valid = false; + sram_size = 0; + spr_ram_valid = false; + nametable_size = 0; + chr_size = 0; +} + +// write + +blargg_err_t Nes_Snapshot_Writer::end( Nes_Emu const& emu ) +{ + Nes_Snapshot_Array snapshots; + BLARGG_RETURN_ERR( snapshots.resize( 1 ) ); + emu.save_snapshot( &snapshots [0] ); + return end( snapshots [0] ); +} + +blargg_err_t Nes_Snapshot_Writer::end( Nes_Snapshot const& ss ) +{ + BLARGG_RETURN_ERR( ss.write_blocks( *this ) ); + return Nes_File_Writer::end(); +} + +blargg_err_t Nes_Snapshot::write( Data_Writer& out ) const +{ + Nes_Snapshot_Writer writer; + BLARGG_RETURN_ERR( writer.begin( &out ) ); + return writer.end( *this ); +} + +blargg_err_t Nes_Snapshot::write_blocks( Nes_File_Writer& out ) const +{ + if ( nes_valid ) + { + nes_state_t s = nes; + s.pal = false; + s.timestamp = nes.timestamp * 15; + BLARGG_RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( cpu_valid ) + { + cpu_state_t s; + memset( &s, 0, sizeof s ); + s.pc = cpu.pc; + s.s = cpu.sp; + s.a = cpu.a; + s.x = cpu.x; + s.y = cpu.y; + s.p = cpu.status; + BLARGG_RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( ppu_valid ) + { + ppu_state_t s = ppu; + BLARGG_RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( apu_valid ) + { + apu_snapshot_t s = apu; + BLARGG_RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( joypad_valid ) + { + joypad_state_t s = joypad; + BLARGG_RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( mapper_valid ) + BLARGG_RETURN_ERR( out.write_block( 'MAPR', mapper.data, mapper.size ) ); + + if ( ram_valid ) + BLARGG_RETURN_ERR( out.write_block( 'LRAM', ram, sizeof ram ) ); + + if ( spr_ram_valid ) + BLARGG_RETURN_ERR( out.write_block( 'SPRT', spr_ram, sizeof spr_ram ) ); + + if ( nametable_size ) + BLARGG_RETURN_ERR( out.write_block( 'NTAB', nametable, nametable_size ) ); + + if ( chr_size ) + BLARGG_RETURN_ERR( out.write_block( 'CHRR', chr, chr_size ) ); + + if ( sram_size ) + BLARGG_RETURN_ERR( out.write_block( 'SRAM', sram, sram_size ) ); + + return blargg_success; +} + +// read + +Nes_Snapshot_Reader::Nes_Snapshot_Reader() +{ + snapshot_ = NULL; +} + +Nes_Snapshot_Reader::~Nes_Snapshot_Reader() +{ +} + +blargg_err_t Nes_Snapshot_Reader::begin( Data_Reader* dr, Nes_Snapshot* out ) +{ + snapshot_ = out; + if ( !out ) + { + BLARGG_RETURN_ERR( snapshots.resize( 1 ) ); + snapshot_ = &snapshots [0]; + } + + BLARGG_RETURN_ERR( Nes_File_Reader::begin( dr ) ); + if ( block_tag() != snapshot_file_tag ) + return "Not a snapshot file"; + return blargg_success; +} + +blargg_err_t Nes_Snapshot::read( Data_Reader& in ) +{ + Nes_Snapshot_Reader reader; + BLARGG_RETURN_ERR( reader.begin( &in, this ) ); + while ( !reader.done() ) + BLARGG_RETURN_ERR( reader.next_block() ); + + return blargg_success; +} + +blargg_err_t Nes_Snapshot_Reader::next_block() +{ + if ( depth() != 0 ) + return Nes_File_Reader::next_block(); + return snapshot_->read_blocks( *this ); +} + +void Nes_Snapshot::set_nes_state( nes_state_t const& s ) +{ + nes = s; + nes.timestamp /= 15; + nes_valid = true; +} + +blargg_err_t Nes_Snapshot::read_blocks( Nes_File_Reader& in ) +{ + while ( true ) + { + BLARGG_RETURN_ERR( in.next_block() ); + switch ( in.block_tag() ) + { + case nes.tag: + memset( &nes, 0, sizeof nes ); + BLARGG_RETURN_ERR( read_nes_state( in, &nes ) ); + set_nes_state( nes ); + break; + + case cpu_state_t::tag: { + cpu_state_t s; + memset( &s, 0, sizeof s ); + BLARGG_RETURN_ERR( read_nes_state( in, &s ) ); + cpu.pc = s.pc; + cpu.sp = s.s; + cpu.a = s.a; + cpu.x = s.x; + cpu.y = s.y; + cpu.status = s.p; + cpu_valid = true; + break; + } + + case ppu.tag: + memset( &ppu, 0, sizeof ppu ); + BLARGG_RETURN_ERR( read_nes_state( in, &ppu ) ); + ppu_valid = true; + break; + + case apu.tag: + memset( &apu, 0, sizeof apu ); + BLARGG_RETURN_ERR( read_nes_state( in, &apu ) ); + apu_valid = true; + break; + + case joypad.tag: + memset( &joypad, 0, sizeof joypad ); + BLARGG_RETURN_ERR( read_nes_state( in, &joypad ) ); + joypad_valid = true; + break; + + case 'MAPR': + mapper.size = in.remain(); + BLARGG_RETURN_ERR( in.read_block_data( mapper.data, sizeof mapper.data ) ); + mapper_valid = true; + break; + + case 'SPRT': + spr_ram_valid = true; + BLARGG_RETURN_ERR( in.read_block_data( spr_ram, sizeof spr_ram ) ); + break; + + case 'NTAB': + nametable_size = in.remain(); + BLARGG_RETURN_ERR( in.read_block_data( nametable, sizeof nametable ) ); + break; + + case 'LRAM': + ram_valid = true; + BLARGG_RETURN_ERR( in.read_block_data( ram, sizeof ram ) ); + break; + + case 'CHRR': + chr_size = in.remain(); + BLARGG_RETURN_ERR( in.read_block_data( chr, sizeof chr ) ); + break; + + case 'SRAM': + sram_size = in.remain(); + BLARGG_RETURN_ERR( in.read_block_data( sram, sizeof sram ) ); + break; + + default: + return blargg_success; + } + } +} + +// read_sta_file + +struct sta_regs_t { + byte pc [2]; + byte a; + byte p; + byte x; + byte y; + byte s; +}; +BOOST_STATIC_ASSERT( sizeof (sta_regs_t) == 7 ); + +blargg_err_t Nes_Snapshot::read_sta_file( Data_Reader& in ) +{ + sram_size = 0x2000; + BLARGG_RETURN_ERR( in.read( sram, sram_size ) ); + + ram_valid = true; + BLARGG_RETURN_ERR( in.read( ram, 0x800 ) ); + + sta_regs_t r; + BLARGG_RETURN_ERR( in.read( &r, sizeof r ) ); + this->cpu.pc = r.pc [1] * 0x100 + r.pc [0]; + this->cpu.a = r.a; + this->cpu.status = r.p; + this->cpu.x = r.x; + this->cpu.y = r.y; + this->cpu.sp = r.s; + cpu_valid = true; + + BLARGG_RETURN_ERR( in.read( spr_ram, 0x100 ) ); + spr_ram_valid = true; + + chr_size = 0x2000; + BLARGG_RETURN_ERR( in.read( chr, chr_size ) ); + + nametable_size = 0x1000; + BLARGG_RETURN_ERR( in.read( nametable, nametable_size ) ); + + return blargg_success; +} + diff --git a/quicknes/nes_emu/Nes_Snapshot.h b/quicknes/nes_emu/Nes_Snapshot.h new file mode 100644 index 0000000000..7abb1411f5 --- /dev/null +++ b/quicknes/nes_emu/Nes_Snapshot.h @@ -0,0 +1,165 @@ + +// NES snapshot for saving and restoring emulator state + +// Nes_Emu 0.5.6. Copyright (C) 2004-2005 Shay Green. GNU LGPL license. + +#ifndef NES_SNAPSHOT_H +#define NES_SNAPSHOT_H + +#include "Nes_File.h" +#include "Nes_Cpu.h" +class Nes_Emu; + +typedef long frame_count_t; +frame_count_t const invalid_frame_count = LONG_MAX / 2; // a large positive value + +class Nes_Snapshot; + +class Nes_Snapshot_Array { +public: + Nes_Snapshot_Array(); + ~Nes_Snapshot_Array(); + + // Change size of array + blargg_err_t resize( int new_size ); + + // Current size of array + int size() const; + + Nes_Snapshot& operator [] ( int ); + Nes_Snapshot const& operator [] ( int ) const; + +private: + Nes_Snapshot* data; + int size_; +}; + +class Nes_Snapshot_Writer : public Nes_File_Writer { +public: + // See Nes_File.h + blargg_err_t begin( Data_Writer* ); + + // Write snapshot of current emulator state and finish writing file + blargg_err_t end( Nes_Emu const& ); + + // Write snapshot and finish writing file + blargg_err_t end( Nes_Snapshot const& ); +}; + +class Nes_Snapshot_Reader : public Nes_File_Reader { +public: + Nes_Snapshot_Reader(); + ~Nes_Snapshot_Reader(); + + // Optionally read snapshot into designated location instead of + // internal snapshot. + blargg_err_t begin( Data_Reader*, Nes_Snapshot* out = NULL ); + + // See Nes_File.h + blargg_err_t next_block(); + + // Snapshot valid after all blocks have been read + Nes_Snapshot const& snapshot() const; +private: + Nes_Snapshot_Array snapshots; + Nes_Snapshot* snapshot_; +}; + +class Nes_Snapshot { + Nes_Snapshot(); // use Nes_Snapshot_Array + Nes_Snapshot( Nes_Snapshot const& ); +public: + + // Invalidate all state + void clear(); + + // Change timestamp + void set_timestamp( frame_count_t ); + + // Timestamp snapshot was taken at + frame_count_t timestamp() const; + + // Read Nesticle .sta file. Currently only reads basic fields. + blargg_err_t read_sta_file( Data_Reader& ); + + // Write snapshot to file + blargg_err_t write( Data_Writer& ) const; + + // Read snapshot from file + blargg_err_t read( Data_Reader& ); + + // End of general interface +public: + blargg_err_t write_blocks( Nes_File_Writer& ) const; + void set_nes_state( nes_state_t const& ); + blargg_err_t read_blocks( Nes_File_Reader& ); +private: + + nes_state_t nes; + bool nes_valid; + + Nes_Cpu::registers_t cpu; + bool cpu_valid; + + joypad_state_t joypad; + bool joypad_valid; + + apu_snapshot_t apu; + bool apu_valid; + + ppu_state_t ppu; + bool ppu_valid; + + mapper_state_t mapper; + bool mapper_valid; + + BOOST::uint8_t ram [0x800]; + bool ram_valid; + + BOOST::uint8_t sram [0x2000]; + int sram_size; + + BOOST::uint8_t spr_ram [0x100]; + bool spr_ram_valid; + + BOOST::uint8_t nametable [0x1000]; + int nametable_size; + + BOOST::uint8_t chr [0x2000]; + int chr_size; + + friend class Nes_Emu; + friend class Nes_Ppu_Impl; +}; + +inline Nes_Snapshot const& Nes_Snapshot_Reader::snapshot() const +{ + assert( depth() == 0 && block_type() == group_end ); + return *snapshot_; +} + +inline blargg_err_t Nes_Snapshot_Writer::begin( Data_Writer* dw ) +{ + return Nes_File_Writer::begin( dw, snapshot_file_tag ); +} + +inline void Nes_Snapshot::set_timestamp( frame_count_t t ) { nes.frame_count = t; } + +inline frame_count_t Nes_Snapshot::timestamp() const { return nes.frame_count; } + +inline int Nes_Snapshot_Array::size() const { return size_; } + +inline Nes_Snapshot& Nes_Snapshot_Array::operator [] ( int i ) +{ + assert( (unsigned) i < size_ ); + return data [i]; +} + +inline Nes_Snapshot const& Nes_Snapshot_Array::operator [] ( int i ) const +{ + assert( (unsigned) i < size_ ); + return data [i]; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_State.cpp b/quicknes/nes_emu/Nes_State.cpp new file mode 100644 index 0000000000..359644a08d --- /dev/null +++ b/quicknes/nes_emu/Nes_State.cpp @@ -0,0 +1,293 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_State.h" + +#include +#include + +#include "blargg_endian.h" +#include "Nes_Emu.h" +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +int mem_differs( void const* p, int cmp, unsigned long s ) +{ + unsigned char const* cp = (unsigned char*) p; + while ( s-- ) + { + if ( *cp++ != cmp ) + return 1; + } + return 0; +} + +Nes_State::Nes_State() +{ + Nes_State_::cpu = &this->cpu; + Nes_State_::joypad = &this->joypad; + Nes_State_::apu = &this->apu; + Nes_State_::ppu = &this->ppu; + Nes_State_::mapper = &this->mapper; + Nes_State_::ram = this->ram; + Nes_State_::sram = this->sram; + Nes_State_::spr_ram = this->spr_ram; + Nes_State_::nametable = this->nametable; + Nes_State_::chr = this->chr; +} + +void Nes_State_::clear() +{ + memset( &nes, 0, sizeof nes ); + nes.frame_count = static_cast(invalid_frame_count); + + nes_valid = false; + cpu_valid = false; + joypad_valid = false; + apu_valid = false; + ppu_valid = false; + mapper_valid = false; + ram_valid = false; + sram_size = 0; + spr_ram_valid = false; + nametable_size = 0; + chr_size = 0; +} + +// write + +blargg_err_t Nes_State_Writer::end( Nes_Emu const& emu ) +{ + Nes_State* state = BLARGG_NEW Nes_State; + CHECK_ALLOC( state ); + emu.save_state( state ); + blargg_err_t err = end( *state ); + delete state; + return err; +} + +blargg_err_t Nes_State_Writer::end( Nes_State const& ss ) +{ + RETURN_ERR( ss.write_blocks( *this ) ); + return Nes_File_Writer::end(); +} + +blargg_err_t Nes_State::write( Auto_File_Writer out ) const +{ + Nes_State_Writer writer; + RETURN_ERR( writer.begin( out ) ); + return writer.end( *this ); +} + +blargg_err_t Nes_State_::write_blocks( Nes_File_Writer& out ) const +{ + if ( nes_valid ) + { + nes_state_t s = nes; + s.timestamp *= 5; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( cpu_valid ) + { + cpu_state_t s; + memset( &s, 0, sizeof s ); + s.pc = cpu->pc; + s.s = cpu->sp; + s.a = cpu->a; + s.x = cpu->x; + s.y = cpu->y; + s.p = cpu->status; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( ppu_valid ) + { + ppu_state_t s = *ppu; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( apu_valid ) + { + apu_state_t s = *apu; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( joypad_valid ) + { + joypad_state_t s = *joypad; + RETURN_ERR( write_nes_state( out, s ) ); + } + + if ( mapper_valid ) + RETURN_ERR( out.write_block( FOUR_CHAR('MAPR'), mapper->data, mapper->size ) ); + + if ( ram_valid ) + RETURN_ERR( out.write_block( FOUR_CHAR('LRAM'), ram, ram_size ) ); + + if ( spr_ram_valid ) + RETURN_ERR( out.write_block( FOUR_CHAR('SPRT'), spr_ram, spr_ram_size ) ); + + if ( nametable_size ) + { + check( nametable_size == 0x800 || nametable_size == 0x1000 ); + RETURN_ERR( out.write_block_header( FOUR_CHAR('NTAB'), nametable_size ) ); + RETURN_ERR( out.write( nametable, 0x800 ) ); + if ( nametable_size > 0x800 ) + RETURN_ERR( out.write( chr, 0x800 ) ); + } + + if ( chr_size ) + RETURN_ERR( out.write_block( FOUR_CHAR('CHRR'), chr, chr_size ) ); + +#ifdef __LIBRETRO__ // Maintain constant save state size. + if ( sram_size ) + RETURN_ERR( out.write_block( FOUR_CHAR('SRAM'), sram, sram_size ) ); +#else + // only save sram if it's been modified + if ( sram_size && mem_differs( sram, 0xff, sram_size ) ) + RETURN_ERR( out.write_block( FOUR_CHAR('SRAM'), sram, sram_size ) ); +#endif + + return 0; +} + +// read + +Nes_State_Reader::Nes_State_Reader() { state_ = 0; owned = 0; } + +Nes_State_Reader::~Nes_State_Reader() { delete owned; } + +blargg_err_t Nes_State_Reader::begin( Auto_File_Reader dr, Nes_State* out ) +{ + state_ = out; + if ( !out ) + CHECK_ALLOC( state_ = owned = BLARGG_NEW Nes_State ); + + RETURN_ERR( Nes_File_Reader::begin( dr ) ); + if ( block_tag() != state_file_tag ) + return "Not a state snapshot file"; + return 0; +} + +blargg_err_t Nes_State::read( Auto_File_Reader in ) +{ + Nes_State_Reader reader; + RETURN_ERR( reader.begin( in, this ) ); + while ( !reader.done() ) + RETURN_ERR( reader.next_block() ); + + return 0; +} + +blargg_err_t Nes_State_Reader::next_block() +{ + if ( depth() != 0 ) + return Nes_File_Reader::next_block(); + return state_->read_blocks( *this ); +} + +void Nes_State_::set_nes_state( nes_state_t const& s ) +{ + nes = s; + nes.timestamp /= 5; + nes_valid = true; +} + +blargg_err_t Nes_State_::read_blocks( Nes_File_Reader& in ) +{ + while ( true ) + { + RETURN_ERR( in.next_block() ); + switch ( in.block_tag() ) + { + case nes_state_t::tag: + memset( &nes, 0, sizeof nes ); + RETURN_ERR( read_nes_state( in, &nes ) ); + set_nes_state( nes ); + break; + + case cpu_state_t::tag: { + cpu_state_t s; + memset( &s, 0, sizeof s ); + RETURN_ERR( read_nes_state( in, &s ) ); + cpu->pc = s.pc; + cpu->sp = s.s; + cpu->a = s.a; + cpu->x = s.x; + cpu->y = s.y; + cpu->status = s.p; + cpu_valid = true; + break; + } + + case ppu_state_t::tag: + memset( ppu, 0, sizeof *ppu ); + RETURN_ERR( read_nes_state( in, ppu ) ); + ppu_valid = true; + break; + + case apu_state_t::tag: + memset( apu, 0, sizeof *apu ); + RETURN_ERR( read_nes_state( in, apu ) ); + apu_valid = true; + break; + + case joypad_state_t::tag: + memset( joypad, 0, sizeof *joypad ); + RETURN_ERR( read_nes_state( in, joypad ) ); + joypad_valid = true; + break; + + case FOUR_CHAR('MAPR'): + mapper->size = in.remain(); + RETURN_ERR( in.read_block_data( mapper->data, sizeof mapper->data ) ); + mapper_valid = true; + break; + + case FOUR_CHAR('SPRT'): + spr_ram_valid = true; + RETURN_ERR( in.read_block_data( spr_ram, spr_ram_size ) ); + break; + + case FOUR_CHAR('NTAB'): + nametable_size = in.remain(); + check( nametable_size == 0x800 || nametable_size == 0x1000 ); + RETURN_ERR( in.read( nametable, 0x800 ) ); + if ( nametable_size > 0x800 ) + RETURN_ERR( in.read( chr, 0x800 ) ); + break; + + case FOUR_CHAR('LRAM'): + ram_valid = true; + RETURN_ERR( in.read_block_data( ram, ram_size ) ); + break; + + case FOUR_CHAR('CHRR'): + chr_size = in.remain(); + RETURN_ERR( in.read_block_data( chr, chr_max ) ); + break; + + case FOUR_CHAR('SRAM'): + sram_size = in.remain(); + RETURN_ERR( in.read_block_data( sram, sram_max ) ); + break; + + default: + return 0; + } + } +} + diff --git a/quicknes/nes_emu/Nes_State.h b/quicknes/nes_emu/Nes_State.h new file mode 100644 index 0000000000..97ec8e2107 --- /dev/null +++ b/quicknes/nes_emu/Nes_State.h @@ -0,0 +1,142 @@ + +// NES state snapshot for saving and restoring emulator state + +// Nes_Emu 0.7.0 + +#ifndef NES_STATE_H +#define NES_STATE_H + +#include "Nes_File.h" +#include "Nes_Cpu.h" +class Nes_Emu; +class Nes_State; + +typedef long frame_count_t; + +// Writes state to a file +class Nes_State_Writer : public Nes_File_Writer { +public: + // Begin writing file + blargg_err_t begin( Auto_File_Writer ); + + // Write emulator's current state to file and end + blargg_err_t end( Nes_Emu const& ); + + // Write state to file and end + blargg_err_t end( Nes_State const& ); +}; + +// Reads state from a file +class Nes_State_Reader : public Nes_File_Reader { +public: + + // Begin reading state snapshot from file + blargg_err_t begin( Auto_File_Reader, Nes_State* = 0 ); + + // Go to next unrecognized block in file + blargg_err_t next_block(); + + // State as read from file. Only valid after all blocks have been read. + Nes_State const& state() const; + +public: + Nes_State_Reader(); + ~Nes_State_Reader(); +private: + Nes_State* owned; + Nes_State* state_; +}; + +class Nes_State_ { +public: + + blargg_err_t write_blocks( Nes_File_Writer& ) const; + void set_nes_state( nes_state_t const& ); + blargg_err_t read_blocks( Nes_File_Reader& ); + + enum { ram_size = 0x800 }; + enum { sram_max = 0x2000 }; + enum { spr_ram_size = 0x100 }; + enum { nametable_max = 0x800 }; + enum { chr_max = 0x2000 }; + BOOST::uint8_t *ram, *sram, *spr_ram, *nametable, *chr; + nes_state_t nes; + Nes_Cpu::registers_t* cpu; + joypad_state_t* joypad; + apu_state_t* apu; + ppu_state_t* ppu; + mapper_state_t* mapper; + + bool nes_valid, cpu_valid, joypad_valid, apu_valid, ppu_valid; + bool mapper_valid, ram_valid, spr_ram_valid; + short sram_size, nametable_size, chr_size; + + // Invalidate all state + void clear(); + + // Change timestamp + void set_timestamp( frame_count_t ); + + // Timestamp snapshot was taken at + frame_count_t timestamp() const; +}; + +// Snapshot of emulator state +class Nes_State : private Nes_State_ { +public: + + Nes_State(); + +#if 0 // What is this? + Nes_State_::set_timestamp; + Nes_State_::timestamp; + Nes_State_::clear; +#endif + + // Write snapshot to file + blargg_err_t write( Auto_File_Writer ) const; + + // Read snapshot from file + blargg_err_t read( Auto_File_Reader ); + +private: + Nes_Cpu::registers_t cpu; + joypad_state_t joypad; + apu_state_t apu; + ppu_state_t ppu; + mapper_state_t mapper; + BOOST::uint8_t ram [ram_size]; + BOOST::uint8_t sram [sram_max]; + BOOST::uint8_t spr_ram [spr_ram_size]; + BOOST::uint8_t nametable [nametable_max]; + BOOST::uint8_t chr [chr_max]; + + friend class Nes_Emu; + friend class Nes_State_Writer; + friend class Nes_State_Reader; + friend class Nes_Recorder; +public: + blargg_err_t read_sta_file( Auto_File_Reader ); +}; + +frame_count_t const invalid_frame_count = LONG_MAX / 2 + 1; // a large positive value + +int mem_differs( void const* in, int compare, unsigned long count ); + +inline Nes_State const& Nes_State_Reader::state() const +{ + assert( depth() == 0 && block_type() == group_end ); + return *state_; +} + +inline blargg_err_t Nes_State_Writer::begin( Auto_File_Writer dw ) +{ + return Nes_File_Writer::begin( dw, state_file_tag ); +} + +inline void Nes_State_::set_timestamp( frame_count_t t ) { nes.frame_count = t; } + +inline frame_count_t Nes_State_::timestamp() const { return nes.frame_count; } + +#endif + diff --git a/quicknes/nes_emu/Nes_Vrc6.cpp b/quicknes/nes_emu/Nes_Vrc6.cpp new file mode 100644 index 0000000000..b2e35cd4a2 --- /dev/null +++ b/quicknes/nes_emu/Nes_Vrc6.cpp @@ -0,0 +1,231 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/ + +#include "Nes_Vrc6.h" + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nes_Vrc6::Nes_Vrc6() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Vrc6::~Nes_Vrc6() +{ +} + +void Nes_Vrc6::reset() +{ + last_time = 0; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int j = 0; j < reg_count; j++ ) + osc.regs [j] = 0; + osc.delay = 0; + osc.last_amp = 0; + osc.phase = 1; + osc.amp = 0; + } +} + +void Nes_Vrc6::volume( double v ) +{ + double const factor = 0.0967 * 2; + saw_synth.volume_unit( factor / 31 * v ); + square_synth.volume_unit( factor * 0.5 / 15 * v ); +} + +void Nes_Vrc6::treble_eq( blip_eq_t const& eq ) +{ + saw_synth.treble_eq( eq ); + square_synth.treble_eq( eq ); +} + +void Nes_Vrc6::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +void Nes_Vrc6::run_until( nes_time_t time ) +{ + require( time >= last_time ); + run_square( oscs [0], time ); + run_square( oscs [1], time ); + run_saw( time ); + last_time = time; +} + +void Nes_Vrc6::write_osc( nes_time_t time, int osc_index, int reg, int data ) +{ + require( (unsigned) osc_index < osc_count ); + require( (unsigned) reg < reg_count ); + + run_until( time ); + oscs [osc_index].regs [reg] = data; +} + +void Nes_Vrc6::end_frame( nes_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + last_time -= time; + assert( last_time >= 0 ); +} + +void Nes_Vrc6::save_snapshot( vrc6_snapshot_t* out ) const +{ + out->saw_amp = oscs [2].amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc const& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + out->regs [i] [r] = osc.regs [r]; + + out->delays [i] = osc.delay; + out->phases [i] = osc.phase; + } +} + +void Nes_Vrc6::load_snapshot( vrc6_snapshot_t const& in ) +{ + reset(); + oscs [2].amp = in.saw_amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + osc.regs [r] = in.regs [i] [r]; + + osc.delay = in.delays [i]; + osc.phase = in.phases [i]; + } + if ( !oscs [2].phase ) + oscs [2].phase = 1; +} + +#include BLARGG_ENABLE_OPTIMIZER + +void Nes_Vrc6::run_square( Vrc6_Osc& osc, nes_time_t end_time ) +{ + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int volume = osc.regs [0] & 15; + if ( !(osc.regs [2] & 0x80) ) + volume = 0; + + int gate = osc.regs [0] & 0x80; + int duty = ((osc.regs [0] >> 4) & 7) + 1; + int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; + nes_time_t time = last_time; + if ( delta ) + { + osc.last_amp += delta; + square_synth.offset( time, delta, output ); + } + + time += osc.delay; + osc.delay = 0; + int period = osc.period(); + if ( volume && !gate && period > 4 ) + { + if ( time < end_time ) + { + int phase = osc.phase; + + do + { + phase++; + if ( phase == 16 ) + { + phase = 0; + osc.last_amp = volume; + square_synth.offset( time, volume, output ); + } + if ( phase == duty ) + { + osc.last_amp = 0; + square_synth.offset( time, -volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc.phase = phase; + } + osc.delay = time - end_time; + } +} + +void Nes_Vrc6::run_saw( nes_time_t end_time ) +{ + Vrc6_Osc& osc = oscs [2]; + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int amp = osc.amp; + int amp_step = osc.regs [0] & 0x3F; + nes_time_t time = last_time; + int last_amp = osc.last_amp; + if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) + { + osc.delay = 0; + int delta = (amp >> 3) - last_amp; + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + else + { + time += osc.delay; + if ( time < end_time ) + { + int period = osc.period() * 2; + int phase = osc.phase; + + do + { + if ( --phase == 0 ) + { + phase = 7; + amp = 0; + } + + int delta = (amp >> 3) - last_amp; + if ( delta ) + { + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + + time += period; + amp = (amp + amp_step) & 0xFF; + } + while ( time < end_time ); + + osc.phase = phase; + osc.amp = amp; + } + + osc.delay = time - end_time; + } + + osc.last_amp = last_amp; +} + diff --git a/quicknes/nes_emu/Nes_Vrc6.h b/quicknes/nes_emu/Nes_Vrc6.h new file mode 100644 index 0000000000..908b952210 --- /dev/null +++ b/quicknes/nes_emu/Nes_Vrc6.h @@ -0,0 +1,86 @@ + +// Konami VRC6 sound chip emulator + +// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NES_VRC6_H +#define NES_VRC6_H + +#include "Nes_Apu.h" +#include "Blip_Buffer.h" + +struct vrc6_snapshot_t; + +class Nes_Vrc6 { +public: + Nes_Vrc6(); + ~Nes_Vrc6(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( nes_time_t ); + void save_snapshot( vrc6_snapshot_t* ) const; + void load_snapshot( vrc6_snapshot_t const& ); + + // Oscillator 0 write-only registers are at $9000-$9002 + // Oscillator 1 write-only registers are at $A000-$A002 + // Oscillator 2 write-only registers are at $B000-$B002 + enum { reg_count = 3 }; + enum { base_addr = 0x9000 }; + enum { addr_step = 0x1000 }; + void write_osc( nes_time_t, int osc, int reg, int data ); + +private: + // noncopyable + Nes_Vrc6( const Nes_Vrc6& ); + Nes_Vrc6& operator = ( const Nes_Vrc6& ); + + struct Vrc6_Osc + { + BOOST::uint8_t regs [3]; + Blip_Buffer* output; + int delay; + int last_amp; + int phase; + int amp; // only used by saw + + int period() const + { + return (regs [2] & 0x0f) * 0x100L + regs [1] + 1; + } + }; + + Vrc6_Osc oscs [osc_count]; + nes_time_t last_time; + + Blip_Synth saw_synth; + Blip_Synth square_synth; + + void run_until( nes_time_t ); + void run_square( Vrc6_Osc& osc, nes_time_t ); + void run_saw( nes_time_t ); +}; + +struct vrc6_snapshot_t +{ + BOOST::uint8_t regs [3] [3]; + BOOST::uint8_t saw_amp; + BOOST::uint16_t delays [3]; + BOOST::uint8_t phases [3]; + BOOST::uint8_t unused; +}; +BOOST_STATIC_ASSERT( sizeof (vrc6_snapshot_t) == 20 ); + +inline void Nes_Vrc6::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +#endif + diff --git a/quicknes/nes_emu/Nes_Vrc6_Apu.cpp b/quicknes/nes_emu/Nes_Vrc6_Apu.cpp new file mode 100644 index 0000000000..b100c7e9d3 --- /dev/null +++ b/quicknes/nes_emu/Nes_Vrc6_Apu.cpp @@ -0,0 +1,217 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/ + +#include "Nes_Vrc6_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +Nes_Vrc6_Apu::Nes_Vrc6_Apu() +{ + output( NULL ); + volume( 1.0 ); + reset(); +} + +Nes_Vrc6_Apu::~Nes_Vrc6_Apu() +{ +} + +void Nes_Vrc6_Apu::reset() +{ + last_time = 0; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int j = 0; j < reg_count; j++ ) + osc.regs [j] = 0; + osc.delay = 0; + osc.last_amp = 0; + osc.phase = 1; + osc.amp = 0; + } +} + +void Nes_Vrc6_Apu::output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, buf ); +} + +void Nes_Vrc6_Apu::run_until( nes_time_t time ) +{ + require( time >= last_time ); + run_square( oscs [0], time ); + run_square( oscs [1], time ); + run_saw( time ); + last_time = time; +} + +void Nes_Vrc6_Apu::write_osc( nes_time_t time, int osc_index, int reg, int data ) +{ + require( (unsigned) osc_index < osc_count ); + require( (unsigned) reg < reg_count ); + + run_until( time ); + oscs [osc_index].regs [reg] = data; +} + +void Nes_Vrc6_Apu::end_frame( nes_time_t time ) +{ + if ( time > last_time ) + run_until( time ); + + assert( last_time >= time ); + last_time -= time; +} + +void Nes_Vrc6_Apu::save_state( vrc6_apu_state_t* out ) const +{ + out->saw_amp = oscs [2].amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc const& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + out->regs [i] [r] = osc.regs [r]; + + out->delays [i] = osc.delay; + out->phases [i] = osc.phase; + } +} + +void Nes_Vrc6_Apu::load_state( vrc6_apu_state_t const& in ) +{ + reset(); + oscs [2].amp = in.saw_amp; + for ( int i = 0; i < osc_count; i++ ) + { + Vrc6_Osc& osc = oscs [i]; + for ( int r = 0; r < reg_count; r++ ) + osc.regs [r] = in.regs [i] [r]; + + osc.delay = in.delays [i]; + osc.phase = in.phases [i]; + } + if ( !oscs [2].phase ) + oscs [2].phase = 1; +} + +void Nes_Vrc6_Apu::run_square( Vrc6_Osc& osc, nes_time_t end_time ) +{ + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int volume = osc.regs [0] & 15; + if ( !(osc.regs [2] & 0x80) ) + volume = 0; + + int gate = osc.regs [0] & 0x80; + int duty = ((osc.regs [0] >> 4) & 7) + 1; + int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp; + nes_time_t time = last_time; + if ( delta ) + { + osc.last_amp += delta; + square_synth.offset( time, delta, output ); + } + + time += osc.delay; + osc.delay = 0; + int period = osc.period(); + if ( volume && !gate && period > 4 ) + { + if ( time < end_time ) + { + int phase = osc.phase; + + do + { + phase++; + if ( phase == 16 ) + { + phase = 0; + osc.last_amp = volume; + square_synth.offset( time, volume, output ); + } + if ( phase == duty ) + { + osc.last_amp = 0; + square_synth.offset( time, -volume, output ); + } + time += period; + } + while ( time < end_time ); + + osc.phase = phase; + } + osc.delay = time - end_time; + } +} + +void Nes_Vrc6_Apu::run_saw( nes_time_t end_time ) +{ + Vrc6_Osc& osc = oscs [2]; + Blip_Buffer* output = osc.output; + if ( !output ) + return; + + int amp = osc.amp; + int amp_step = osc.regs [0] & 0x3F; + nes_time_t time = last_time; + int last_amp = osc.last_amp; + if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) ) + { + osc.delay = 0; + int delta = (amp >> 3) - last_amp; + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + else + { + time += osc.delay; + if ( time < end_time ) + { + int period = osc.period() * 2; + int phase = osc.phase; + + do + { + if ( --phase == 0 ) + { + phase = 7; + amp = 0; + } + + int delta = (amp >> 3) - last_amp; + if ( delta ) + { + last_amp = amp >> 3; + saw_synth.offset( time, delta, output ); + } + + time += period; + amp = (amp + amp_step) & 0xFF; + } + while ( time < end_time ); + + osc.phase = phase; + osc.amp = amp; + } + + osc.delay = time - end_time; + } + + osc.last_amp = last_amp; +} + diff --git a/quicknes/nes_emu/Nes_Vrc6_Apu.h b/quicknes/nes_emu/Nes_Vrc6_Apu.h new file mode 100644 index 0000000000..aab2fe5d31 --- /dev/null +++ b/quicknes/nes_emu/Nes_Vrc6_Apu.h @@ -0,0 +1,99 @@ + +// Konami VRC6 sound chip emulator + +// Nes_Snd_Emu 0.1.7 + +#ifndef NES_VRC6_APU_H +#define NES_VRC6_APU_H + +#include "Nes_Apu.h" +#include "Blip_Buffer.h" + +struct vrc6_apu_state_t; + +class Nes_Vrc6_Apu { +public: + Nes_Vrc6_Apu(); + ~Nes_Vrc6_Apu(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void output( Blip_Buffer* ); + enum { osc_count = 3 }; + void osc_output( int index, Blip_Buffer* ); + void end_frame( nes_time_t ); + void save_state( vrc6_apu_state_t* ) const; + void load_state( vrc6_apu_state_t const& ); + + // Oscillator 0 write-only registers are at $9000-$9002 + // Oscillator 1 write-only registers are at $A000-$A002 + // Oscillator 2 write-only registers are at $B000-$B002 + enum { reg_count = 3 }; + enum { base_addr = 0x9000 }; + enum { addr_step = 0x1000 }; + void write_osc( nes_time_t, int osc, int reg, int data ); + +private: + // noncopyable + Nes_Vrc6_Apu( const Nes_Vrc6_Apu& ); + Nes_Vrc6_Apu& operator = ( const Nes_Vrc6_Apu& ); + + struct Vrc6_Osc + { + BOOST::uint8_t regs [3]; + Blip_Buffer* output; + int delay; + int last_amp; + int phase; + int amp; // only used by saw + + int period() const + { + return (regs [2] & 0x0f) * 0x100L + regs [1] + 1; + } + }; + + Vrc6_Osc oscs [osc_count]; + nes_time_t last_time; + + Blip_Synth saw_synth; + Blip_Synth square_synth; + + void run_until( nes_time_t ); + void run_square( Vrc6_Osc& osc, nes_time_t ); + void run_saw( nes_time_t ); +}; + +struct vrc6_apu_state_t +{ + BOOST::uint8_t regs [3] [3]; + BOOST::uint8_t saw_amp; + BOOST::uint16_t delays [3]; + BOOST::uint8_t phases [3]; + BOOST::uint8_t unused; +}; +BOOST_STATIC_ASSERT( sizeof (vrc6_apu_state_t) == 20 ); + +inline void Nes_Vrc6_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; +} + +inline void Nes_Vrc6_Apu::volume( double v ) +{ + double const factor = 0.0967 * 2; + saw_synth.volume( factor / 31 * v ); + square_synth.volume( factor * 0.5 / 15 * v ); +} + +inline void Nes_Vrc6_Apu::treble_eq( blip_eq_t const& eq ) +{ + saw_synth.treble_eq( eq ); + square_synth.treble_eq( eq ); +} + +#endif + diff --git a/quicknes/nes_emu/Nonlinear_Buffer.cpp b/quicknes/nes_emu/Nonlinear_Buffer.cpp new file mode 100644 index 0000000000..d1290f1949 --- /dev/null +++ b/quicknes/nes_emu/Nonlinear_Buffer.cpp @@ -0,0 +1,189 @@ + +// Nes_Emu 0.5.6. http://www.slack.net/~ant/libs/ + +#include "Nonlinear_Buffer.h" + +#include "Nes_Apu.h" + +/* Library Copyright (C) 2003-2005 Shay Green. This library is free software; +you can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Nonlinear_Buffer + +Nonlinear_Buffer::Nonlinear_Buffer() : + Multi_Buffer( 1 ) +{ +} + +Nonlinear_Buffer::~Nonlinear_Buffer() +{ +} + +void Nonlinear_Buffer::enable_nonlinearity( Nes_Apu& apu, bool b ) +{ + if ( b ) + clear(); + nonlinearizer.enable( apu, b ); + for ( int i = 0; i < apu.osc_count; i++ ) + apu.osc_output( i, (i >= 2 ? &tnd : &buf) ); +} + +blargg_err_t Nonlinear_Buffer::sample_rate( long rate, int msec ) +{ + BLARGG_RETURN_ERR( buf.sample_rate( rate, msec ) ); + BLARGG_RETURN_ERR( tnd.sample_rate( rate, msec ) ); + return Multi_Buffer::sample_rate( buf.sample_rate(), buf.length() ); +} + +void Nonlinear_Buffer::clock_rate( long rate ) +{ + buf.clock_rate( rate ); + tnd.clock_rate( rate ); +} + +void Nonlinear_Buffer::bass_freq( int freq ) +{ + buf.bass_freq( freq ); + tnd.bass_freq( freq ); +} + +void Nonlinear_Buffer::clear() +{ + nonlinearizer.clear(); + buf.clear(); + tnd.clear(); +} + +Nonlinear_Buffer::channel_t Nonlinear_Buffer::channel( int i ) +{ + channel_t c; + c.center = &buf; + if ( 2 <= i && i <= 4 ) + c.center = &tnd; // only use for triangle, noise, and dmc + c.left = c.center; + c.right = c.center; + return c; +} + +void Nonlinear_Buffer::end_frame( blip_time_t length, bool ) +{ + buf.end_frame( length ); + tnd.end_frame( length ); +} + +long Nonlinear_Buffer::samples_avail() const +{ + return buf.samples_avail(); +} + +#include BLARGG_ENABLE_OPTIMIZER + +long Nonlinear_Buffer::read_samples( blip_sample_t* out, long count ) +{ + count = nonlinearizer.make_nonlinear( tnd, count ); + if ( count ) + { + Blip_Reader lin; + Blip_Reader nonlin; + + int lin_bass = lin.begin( buf ); + int nonlin_bass = nonlin.begin( tnd ); + + for ( int n = count; n--; ) + { + int s = lin.read() + nonlin.read(); + lin.next( lin_bass ); + nonlin.next( nonlin_bass ); + *out++ = s; + + if ( (BOOST::int16_t) s != s ) + out [-1] = 0x7FFF - (s >> 24); + } + + lin.end( buf ); + nonlin.end( tnd ); + + buf.remove_samples( count ); + tnd.remove_samples( count ); + } + + return count; +} + +// Nes_Nonlinearizer + +Nes_Nonlinearizer::Nes_Nonlinearizer() +{ + nonlinear = false; + + double gain = 0x7fff * 1.3; + // don't use entire range, so any overflow will stay within table + int const range = half * Nes_Apu::nonlinear_tnd_gain(); + for ( int i = 0; i < half * 2; i++ ) + { + int out = i << shift; + if ( i > half ) + { + int j = i - half; + if ( j >= range ) + j = range - 1; + double n = 202.0 / (range - 1) * j; + double d = 163.67 / (24329.0 / n + 100); + out = int (d * gain) + 0x8000; + assert( out < 0x10000 ); + } + table [i] = out; + } + clear(); +} + +void Nes_Nonlinearizer::enable( Nes_Apu& apu, bool b ) +{ + nonlinear = b; + if ( b ) + apu.enable_nonlinear( 1.0 ); + else + apu.volume( 1.0 ); +} + +long Nes_Nonlinearizer::make_nonlinear( Blip_Buffer& buf, long count ) +{ + long avail = buf.samples_avail(); + if ( count > avail ) + count = avail; + + if ( count && nonlinear ) + { + const int zero_offset = Blip_Buffer::sample_offset_; + + #define ENTRY( s ) (table [((s) >> shift) & entry_mask]) + + BOOST::uint16_t* p = buf.buffer_; + unsigned prev = ENTRY( accum ); + long accum = this->accum; + + for ( unsigned n = count; n--; ) + { + accum += (long) *p - zero_offset; + check( (accum >> shift) < half * 2 ); + unsigned entry = ENTRY( accum ); + *p++ = entry - prev + zero_offset; + prev = entry; + } + + this->accum = accum; + } + + return count; +} + diff --git a/quicknes/nes_emu/Nonlinear_Buffer.h b/quicknes/nes_emu/Nonlinear_Buffer.h new file mode 100644 index 0000000000..a3cb7ee8ac --- /dev/null +++ b/quicknes/nes_emu/Nonlinear_Buffer.h @@ -0,0 +1,65 @@ + +// NES non-linear audio output handling. + +// Nes_Emu 0.5.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NONLINEAR_BUFFER_H +#define NONLINEAR_BUFFER_H + +#include "Multi_Buffer.h" +class Nes_Apu; + +// Use to make samples non-linear in Blip_Buffer used for triangle, noise, and DMC only +class Nes_Nonlinearizer { +public: + Nes_Nonlinearizer(); + + // Must be called when buffer is cleared + void clear() { accum = 0x8000; } + + // Enable/disable non-linear output + void enable( Nes_Apu&, bool = true ); + + // Make at most 'count' samples in buffer non-linear and return number + // of samples modified. This many samples must then be read out of the buffer. + long make_nonlinear( Blip_Buffer&, long count ); + +private: + enum { shift = 5 }; + enum { half = 0x8000 >> shift }; + enum { entry_mask = half * 2 - 1 }; + BOOST::uint16_t table [half * 2]; + long accum; + bool nonlinear; +}; + +class Nonlinear_Buffer : public Multi_Buffer { +public: + Nonlinear_Buffer(); + ~Nonlinear_Buffer(); + + // Enable/disable non-linear output + void enable_nonlinearity( Nes_Apu&, bool = true ); + + // Blip_Buffer to output other sound chips to + Blip_Buffer* buffer() { return &buf; } + + // See Multi_Buffer.h + blargg_err_t sample_rate( long rate, int msec = blip_default_length ); + Multi_Buffer::sample_rate; + void clock_rate( long ); + void bass_freq( int ); + void clear(); + channel_t channel( int ); + void end_frame( blip_time_t, bool unused = true ); + long samples_avail() const; + long read_samples( blip_sample_t*, long ); + +private: + Blip_Buffer buf; + Blip_Buffer tnd; + Nes_Nonlinearizer nonlinearizer; +}; + +#endif + diff --git a/quicknes/nes_emu/Nonlinear_Effects_Buffer.cpp b/quicknes/nes_emu/Nonlinear_Effects_Buffer.cpp new file mode 100644 index 0000000000..ab1ec3faa1 --- /dev/null +++ b/quicknes/nes_emu/Nonlinear_Effects_Buffer.cpp @@ -0,0 +1,69 @@ + +// Nes_Emu 0.5.6. http://www.slack.net/~ant/libs/ + +#include "Nonlinear_Effects_Buffer.h" + +/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +Nonlinear_Effects_Buffer::Nonlinear_Effects_Buffer() : Effects_Buffer( true ) +{ + config_t c; + c.effects_enabled = false; + config( c ); +} + +Nonlinear_Effects_Buffer::~Nonlinear_Effects_Buffer() +{ +} + +void Nonlinear_Effects_Buffer::enable_nonlinearity( Nes_Apu& apu, bool b ) +{ + if ( b ) + clear(); + nonlinearizer.enable( apu, b ); +} + +void Nonlinear_Effects_Buffer::config( const config_t& in ) +{ + config_t c = in; + if ( !c.effects_enabled ) + { + // effects must always be enabled to keep separate buffers, so + // set parameters to be equivalent to disabled + c.pan_1 = 0; + c.pan_2 = 0; + c.echo_level = 0; + c.reverb_level = 0; + c.effects_enabled = true; + } + Effects_Buffer::config( c ); +} + +void Nonlinear_Effects_Buffer::clear() +{ + nonlinearizer.clear(); + Effects_Buffer::clear(); +} + +Nonlinear_Effects_Buffer::channel_t Nonlinear_Effects_Buffer::channel( int i ) +{ + return Effects_Buffer::channel( (2 <= i && i <= 4) ? 2 : i & 1 ); +} + +long Nonlinear_Effects_Buffer::read_samples( blip_sample_t* out, long count ) +{ + count = 2 * nonlinearizer.make_nonlinear( *channel( 2 ).center, count / 2 ); + return Effects_Buffer::read_samples( out, count ); +} + diff --git a/quicknes/nes_emu/Nonlinear_Effects_Buffer.h b/quicknes/nes_emu/Nonlinear_Effects_Buffer.h new file mode 100644 index 0000000000..4c79b92cbd --- /dev/null +++ b/quicknes/nes_emu/Nonlinear_Effects_Buffer.h @@ -0,0 +1,33 @@ + +// Effects_Buffer with non-linear sound + +// Nes_Emu 0.5.6. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef NONLINEAR_EFFECTS_BUFFER_H +#define NONLINEAR_EFFECTS_BUFFER_H + +#include "Nonlinear_Buffer.h" +#include "Effects_Buffer.h" + +// Effects_Buffer uses several buffers and outputs stereo sample pairs. +class Nonlinear_Effects_Buffer : public Effects_Buffer { +public: + Nonlinear_Effects_Buffer(); + ~Nonlinear_Effects_Buffer(); + + // Enable/disable non-linear output + void enable_nonlinearity( Nes_Apu&, bool = true ); + + // See Effects_Buffer.h for reference + void config( const config_t& ); + void clear(); + channel_t channel( int ); + long read_samples( blip_sample_t*, long ); + +// End of public interface +private: + Nes_Nonlinearizer nonlinearizer; +}; + +#endif + diff --git a/quicknes/nes_emu/abstract_file.cpp b/quicknes/nes_emu/abstract_file.cpp new file mode 100644 index 0000000000..48a1b3d1e5 --- /dev/null +++ b/quicknes/nes_emu/abstract_file.cpp @@ -0,0 +1,308 @@ + +#include "abstract_file.h" + +#include "blargg_config.h" + +#include +#include +#include + +/* Copyright (C) 2005-2006 Shay Green. Permission is hereby granted, free of +charge, to any person obtaining a copy of this software module and associated +documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and +to permit persons to whom the Software is furnished to do so, subject to the +following conditions: The above copyright notice and this permission notice +shall be included in all copies or substantial portions of the Software. THE +SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ + +// to do: remove? +#ifndef RAISE_ERROR + #define RAISE_ERROR( str ) return str +#endif + +typedef blargg_err_t error_t; + +error_t Data_Writer::write( const void*, long ) { return 0; } + +void Data_Writer::satisfy_lame_linker_() { } + +// Std_File_Writer + +Std_File_Writer::Std_File_Writer() : file_( 0 ) { +} + +Std_File_Writer::~Std_File_Writer() { + close(); +} + +error_t Std_File_Writer::open( const char* path ) +{ + close(); + file_ = fopen( path, "wb" ); + if ( !file_ ) + RAISE_ERROR( "Couldn't open file for writing" ); + + // to do: increase file buffer size + //setvbuf( file_, 0, _IOFBF, 32 * 1024L ); + + return 0; +} + +error_t Std_File_Writer::write( const void* p, long s ) +{ + long result = (long) fwrite( p, 1, s, file_ ); + if ( result != s ) + RAISE_ERROR( "Couldn't write to file" ); + return 0; +} + +void Std_File_Writer::close() +{ + if ( file_ ) { + fclose( file_ ); + file_ = 0; + } +} + +// Mem_Writer + +Mem_Writer::Mem_Writer( void* p, long s, int b ) +{ + data_ = (char*) p; + size_ = 0; + allocated = s; + mode = b ? ignore_excess : fixed; +} + +Mem_Writer::Mem_Writer() +{ + data_ = 0; + size_ = 0; + allocated = 0; + mode = expanding; +} + +Mem_Writer::~Mem_Writer() +{ + if ( mode == expanding ) + free( data_ ); +} + +error_t Mem_Writer::write( const void* p, long s ) +{ + long remain = allocated - size_; + if ( s > remain ) + { + if ( mode == fixed ) + RAISE_ERROR( "Tried to write more data than expected" ); + + if ( mode == ignore_excess ) + { + s = remain; + } + else // expanding + { + long new_allocated = size_ + s; + new_allocated += (new_allocated >> 1) + 2048; + void* p = realloc( data_, new_allocated ); + if ( !p ) + RAISE_ERROR( "Out of memory" ); + data_ = (char*) p; + allocated = new_allocated; + } + } + + assert( size_ + s <= allocated ); + memcpy( data_ + size_, p, s ); + size_ += s; + + return 0; +} + +// Null_Writer + +error_t Null_Writer::write( const void*, long ) +{ + return 0; +} + +// Auto_File_Reader + +#ifndef STD_AUTO_FILE_WRITER + #define STD_AUTO_FILE_WRITER Std_File_Writer +#endif + +#ifdef HAVE_ZLIB_H + #ifndef STD_AUTO_FILE_READER + #define STD_AUTO_FILE_READER Gzip_File_Reader + #endif + + #ifndef STD_AUTO_FILE_COMP_WRITER + #define STD_AUTO_FILE_COMP_WRITER Gzip_File_Writer + #endif + +#else + #ifndef STD_AUTO_FILE_READER + #define STD_AUTO_FILE_READER Std_File_Reader + #endif + + #ifndef STD_AUTO_FILE_COMP_WRITER + #define STD_AUTO_FILE_COMP_WRITER Std_File_Writer + #endif + +#endif + +const char* Auto_File_Reader::open() +{ + #ifdef DISABLE_AUTO_FILE + return 0; + #else + if ( data ) + return 0; + STD_AUTO_FILE_READER* d = new STD_AUTO_FILE_READER; + if ( !d ) + RAISE_ERROR( "Out of memory" ); + data = d; + return d->open( path ); + #endif +} + +Auto_File_Reader::~Auto_File_Reader() +{ + if ( path ) + delete data; +} + +// Auto_File_Writer + +const char* Auto_File_Writer::open() +{ + #ifdef DISABLE_AUTO_FILE + return 0; + #else + if ( data ) + return 0; + STD_AUTO_FILE_WRITER* d = new STD_AUTO_FILE_WRITER; + if ( !d ) + RAISE_ERROR( "Out of memory" ); + data = d; + return d->open( path ); + #endif +} + +const char* Auto_File_Writer::open_comp( int level ) +{ + #ifdef DISABLE_AUTO_FILE + return 0; + #else + if ( data ) + return 0; + STD_AUTO_FILE_COMP_WRITER* d = new STD_AUTO_FILE_COMP_WRITER; + if ( !d ) + RAISE_ERROR( "Out of memory" ); + data = d; + return d->open( path, level ); + #endif +} + +Auto_File_Writer::~Auto_File_Writer() +{ + #ifndef DISABLE_AUTO_FILE + if ( path ) + delete data; + #endif +} + +#ifndef __LIBRETRO__ +#ifdef HAVE_ZLIB_H + +#include "zlib.h" + +static const char* get_gzip_eof( FILE* file, long* eof ) +{ + unsigned char buf [4]; + if ( !fread( buf, 2, 1, file ) ) + RAISE_ERROR( "Couldn't read from file" ); + + if ( buf [0] == 0x1F && buf [1] == 0x8B ) + { + if ( fseek( file, -4, SEEK_END ) ) + RAISE_ERROR( "Couldn't seek in file" ); + + if ( !fread( buf, 4, 1, file ) ) + RAISE_ERROR( "Couldn't read from file" ); + + *eof = buf [3] * 0x1000000L + buf [2] * 0x10000L + buf [1] * 0x100L + buf [0]; + } + else + { + if ( fseek( file, 0, SEEK_END ) ) + RAISE_ERROR( "Couldn't seek in file" ); + + *eof = ftell( file ); + } + + return 0; +} + +const char* get_gzip_eof( const char* path, long* eof ) +{ + FILE* file = fopen( path, "rb" ); + if ( !file ) + return "Couldn't open file"; + const char* error = get_gzip_eof( file, eof ); + fclose( file ); + return error; +} + +// Gzip_File_Writer + +Gzip_File_Writer::Gzip_File_Writer() : file_( 0 ) +{ +} + +Gzip_File_Writer::~Gzip_File_Writer() +{ + close(); +} + +Gzip_File_Writer::error_t Gzip_File_Writer::open( const char* path, int level ) +{ + close(); + + char mode [4] = { 'w', 'b', 0, 0 }; + if ( level >= 0 ) + mode [2] = level + '0'; + file_ = gzopen( path, mode ); + if ( !file_ ) + return "Couldn't open file for writing"; + + return 0; +} + +Gzip_File_Writer::error_t Gzip_File_Writer::write( const void* p, long s ) +{ + long result = (long) gzwrite( (gzFile) file_ , (void*) p, s ); + if ( result != s ) + return "Couldn't write to file"; + return 0; +} + +void Gzip_File_Writer::close() +{ + if ( file_ ) + { + gzclose( (gzFile) file_ ); + file_ = 0; + } +} +#endif + +#endif diff --git a/quicknes/nes_emu/abstract_file.h b/quicknes/nes_emu/abstract_file.h new file mode 100644 index 0000000000..a7606c3d9b --- /dev/null +++ b/quicknes/nes_emu/abstract_file.h @@ -0,0 +1,162 @@ + +// Abstract file access interfaces + +#ifndef ABSTRACT_FILE_H +#define ABSTRACT_FILE_H + +#undef BLARGG_CONFIG_H + +#include + +#include + +// Supports writing +class Data_Writer { +public: + Data_Writer() { } + virtual ~Data_Writer() { } + + typedef blargg_err_t error_t; + + // Write 'n' bytes. NULL on success, otherwise error string. + virtual error_t write( const void*, long n ) = 0; + + void satisfy_lame_linker_(); +private: + // noncopyable + Data_Writer( const Data_Writer& ); + Data_Writer& operator = ( const Data_Writer& ); +}; + +class Std_File_Writer : public Data_Writer { +public: + Std_File_Writer(); + ~Std_File_Writer(); + + error_t open( const char* ); + + FILE* file() const { return file_; } + + // Forward writes to file. Caller must close file later. + //void forward( FILE* ); + + error_t write( const void*, long ); + + void close(); + +protected: + void reset( FILE* f ) { file_ = f; } +private: + FILE* file_; + error_t open( const char* path, int ignored ) { return open( path ); } + friend class Auto_File_Writer; +}; + +// Write data to memory +class Mem_Writer : public Data_Writer { + char* data_; + long size_; + long allocated; + enum { expanding, fixed, ignore_excess } mode; +public: + // Keep all written data in expanding block of memory + Mem_Writer(); + + // Write to fixed-size block of memory. If ignore_excess is false, returns + // error if more than 'size' data is written, otherwise ignores any excess. + Mem_Writer( void*, long size, int ignore_excess = 0 ); + + error_t write( const void*, long ); + + // Pointer to beginning of written data + char* data() { return data_; } + + // Number of bytes written + long size() const { return size_; } + + ~Mem_Writer(); +}; + +// Written data is ignored +class Null_Writer : public Data_Writer { +public: + error_t write( const void*, long ); +}; + +// Auto_File to use in place of Data_Reader&/Data_Writer&, allowing a normal +// file path to be used in addition to a Data_Reader/Data_Writer. + +class Auto_File_Reader { +public: + Auto_File_Reader() : data( 0 ), path( 0 ) { } + Auto_File_Reader( Data_Reader& r ) : data( &r ), path( 0 ) { } +#ifndef DISABLE_AUTO_FILE + Auto_File_Reader( const char* path_ ) : data( 0 ), path( path_ ) { } +#endif + Auto_File_Reader( Auto_File_Reader const& ); + Auto_File_Reader& operator = ( Auto_File_Reader const& ); + ~Auto_File_Reader(); + const char* open(); + + int operator ! () const { return !data; } + Data_Reader* operator -> () const { return data; } + Data_Reader& operator * () const { return *data; } +private: + /* mutable */ Data_Reader* data; + const char* path; +}; + +class Auto_File_Writer { +public: + Auto_File_Writer() : data( 0 ), path( 0 ) { } + Auto_File_Writer( Data_Writer& r ) : data( &r ), path( 0 ) { } +#ifndef DISABLE_AUTO_FILE + Auto_File_Writer( const char* path_ ) : data( 0 ), path( path_ ) { } +#endif + Auto_File_Writer( Auto_File_Writer const& ); + Auto_File_Writer& operator = ( Auto_File_Writer const& ); + ~Auto_File_Writer(); + const char* open(); + const char* open_comp( int level = -1 ); // compress output if possible + + int operator ! () const { return !data; } + Data_Writer* operator -> () const { return data; } + Data_Writer& operator * () const { return *data; } +private: + /* mutable */ Data_Writer* data; + const char* path; +}; + +inline Auto_File_Reader& Auto_File_Reader::operator = ( Auto_File_Reader const& r ) +{ + data = r.data; + path = r.path; + ((Auto_File_Reader*) &r)->data = 0; + return *this; +} +inline Auto_File_Reader::Auto_File_Reader( Auto_File_Reader const& r ) { *this = r; } + +inline Auto_File_Writer& Auto_File_Writer::operator = ( Auto_File_Writer const& r ) +{ + data = r.data; + path = r.path; + ((Auto_File_Writer*) &r)->data = 0; + return *this; +} +inline Auto_File_Writer::Auto_File_Writer( Auto_File_Writer const& r ) { *this = r; } + +#ifndef __LIBRETRO__ +class Gzip_File_Writer : public Data_Writer { + void* file_; +public: + Gzip_File_Writer(); + ~Gzip_File_Writer(); + + error_t open( const char*, int compression = -1 ); + error_t write( const void*, long ); + void close(); +}; +#endif + +#endif + diff --git a/quicknes/nes_emu/apu_snapshot.cpp b/quicknes/nes_emu/apu_snapshot.cpp new file mode 100644 index 0000000000..85ad55cfb5 --- /dev/null +++ b/quicknes/nes_emu/apu_snapshot.cpp @@ -0,0 +1,126 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/ + +#include "apu_snapshot.h" +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#ifdef BLARGG_SOURCE_BEGIN +#include BLARGG_SOURCE_BEGIN +#endif + +template +struct apu_reflection +{ + #define REFLECT( apu, state ) (mode ? void (apu = state) : void (state = apu)) + + static void reflect_env( apu_snapshot_t::env_t& state, Nes_Envelope& osc ) + { + REFLECT( state [0], osc.env_delay ); + REFLECT( state [1], osc.envelope ); + REFLECT( state [2], osc.reg_written [3] ); + } + + static void reflect_square( apu_snapshot_t::square_t& state, Nes_Square& osc ) + { + reflect_env( state.env, osc ); + REFLECT( state.delay, osc.delay ); + REFLECT( state.length, osc.length_counter ); + REFLECT( state.phase, osc.phase ); + REFLECT( state.swp_delay, osc.sweep_delay ); + REFLECT( state.swp_reset, osc.reg_written [1] ); + } + + static void reflect_triangle( apu_snapshot_t::triangle_t& state, Nes_Triangle& osc ) + { + REFLECT( state.delay, osc.delay ); + REFLECT( state.length, osc.length_counter ); + REFLECT( state.linear_counter, osc.linear_counter ); + REFLECT( state.linear_mode, osc.reg_written [3] ); + } + + static void reflect_noise( apu_snapshot_t::noise_t& state, Nes_Noise& osc ) + { + reflect_env( state.env, osc ); + REFLECT( state.delay, osc.delay ); + REFLECT( state.length, osc.length_counter ); + REFLECT( state.shift_reg, osc.noise ); + } + + static void reflect_dmc( apu_snapshot_t::dmc_t& state, Nes_Dmc& osc ) + { + REFLECT( state.delay, osc.delay ); + REFLECT( state.remain, osc.length_counter ); + REFLECT( state.buf, osc.buf ); + REFLECT( state.bits_remain, osc.bits_remain ); + REFLECT( state.bits, osc.bits ); + REFLECT( state.buf_empty, osc.buf_empty ); + REFLECT( state.silence, osc.silence ); + REFLECT( state.irq_flag, osc.irq_flag ); + if ( mode ) + state.addr = osc.address | 0x8000; + else + osc.address = state.addr & 0x7fff; + } +}; + +void Nes_Apu::save_snapshot( apu_snapshot_t* state ) const +{ + for ( int i = 0; i < osc_count * 4; i++ ) + state->w40xx [i] = oscs [i >> 2]->regs [i & 3]; + state->w40xx [0x11] = dmc.dac; + + state->w4015 = osc_enables; + state->w4017 = frame_mode; + state->delay = frame_delay; + state->step = frame; + state->irq_flag = irq_flag; + + typedef apu_reflection<1> refl; + Nes_Apu& apu = *(Nes_Apu*) this; // const_cast + refl::reflect_square ( state->square1, apu.square1 ); + refl::reflect_square ( state->square2, apu.square2 ); + refl::reflect_triangle( state->triangle, apu.triangle ); + refl::reflect_noise ( state->noise, apu.noise ); + refl::reflect_dmc ( state->dmc, apu.dmc ); +} + +void Nes_Apu::load_snapshot( apu_snapshot_t const& state ) +{ + reset(); + + write_register( 0, 0x4017, state.w4017 ); + write_register( 0, 0x4015, state.w4015 ); + + for ( int i = 0; i < osc_count * 4; i++ ) + { + int n = state.w40xx [i]; + oscs [i >> 2]->regs [i & 3] = n; + write_register( 0, 0x4000 + i, n ); + } + + frame_delay = state.delay; + frame = state.step; + irq_flag = state.irq_flag; + + typedef apu_reflection<0> refl; + apu_snapshot_t& st = (apu_snapshot_t&) state; // const_cast + refl::reflect_square ( st.square1, square1 ); + refl::reflect_square ( st.square2, square2 ); + refl::reflect_triangle( st.triangle, triangle ); + refl::reflect_noise ( st.noise, noise ); + refl::reflect_dmc ( st.dmc, dmc ); + dmc.recalc_irq(); + dmc.last_amp = dmc.dac; +} + diff --git a/quicknes/nes_emu/apu_snapshot.h b/quicknes/nes_emu/apu_snapshot.h new file mode 100644 index 0000000000..df8beac325 --- /dev/null +++ b/quicknes/nes_emu/apu_snapshot.h @@ -0,0 +1,75 @@ + +// NES APU snapshot support + +// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license. + +#ifndef APU_SNAPSHOT_H +#define APU_SNAPSHOT_H + +#include "blargg_common.h" + +struct apu_snapshot_t +{ + typedef BOOST::uint8_t byte; + + typedef byte env_t [3]; + /*struct env_t { + byte delay; + byte env;3 + byte written; + };*/ + + byte w40xx [0x14]; // $4000-$4013 + byte w4015; // enables + byte w4017; // mode + BOOST::uint16_t delay; + byte step; + byte irq_flag; + + struct square_t { + BOOST::uint16_t delay; + env_t env; + byte length; + byte phase; + byte swp_delay; + byte swp_reset; + byte unused [1]; + }; + + square_t square1; + square_t square2; + + struct triangle_t { + BOOST::uint16_t delay; + byte length; + byte phase; + byte linear_counter; + byte linear_mode; + } triangle; + + struct noise_t { + BOOST::uint16_t delay; + env_t env; + byte length; + BOOST::uint16_t shift_reg; + } noise; + + struct dmc_t { + BOOST::uint16_t delay; + BOOST::uint16_t remain; + BOOST::uint16_t addr; + byte buf; + byte bits_remain; + byte bits; + byte buf_empty; + byte silence; + byte irq_flag; + } dmc; + + enum { tag = 'APUR' }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (apu_snapshot_t) == 72 ); + +#endif + diff --git a/quicknes/nes_emu/apu_state.cpp b/quicknes/nes_emu/apu_state.cpp new file mode 100644 index 0000000000..704c02a1a6 --- /dev/null +++ b/quicknes/nes_emu/apu_state.cpp @@ -0,0 +1,132 @@ + +// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/ + +#include "apu_state.h" +#include "Nes_Apu.h" + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +template +struct apu_reflection +{ + #define REFLECT( apu, state ) (mode ? void (apu = state) : void (state = apu)) + + static void reflect_env( apu_state_t::env_t* state, Nes_Envelope& osc ) + { + REFLECT( (*state) [0], osc.env_delay ); + REFLECT( (*state) [1], osc.envelope ); + REFLECT( (*state) [2], osc.reg_written [3] ); + } + + static void reflect_square( apu_state_t::square_t& state, Nes_Square& osc ) + { + reflect_env( &state.env, osc ); + REFLECT( state.delay, osc.delay ); + REFLECT( state.length_counter, osc.length_counter ); + REFLECT( state.phase, osc.phase ); + REFLECT( state.swp_delay, osc.sweep_delay ); + REFLECT( state.swp_reset, osc.reg_written [1] ); + } + + static void reflect_triangle( apu_state_t::triangle_t& state, Nes_Triangle& osc ) + { + REFLECT( state.delay, osc.delay ); + REFLECT( state.length_counter, osc.length_counter ); + REFLECT( state.linear_counter, osc.linear_counter ); + REFLECT( state.linear_mode, osc.reg_written [3] ); + } + + static void reflect_noise( apu_state_t::noise_t& state, Nes_Noise& osc ) + { + reflect_env( &state.env, osc ); + REFLECT( state.delay, osc.delay ); + REFLECT( state.length_counter, osc.length_counter ); + REFLECT( state.shift_reg, osc.noise ); + } + + static void reflect_dmc( apu_state_t::dmc_t& state, Nes_Dmc& osc ) + { + REFLECT( state.delay, osc.delay ); + REFLECT( state.remain, osc.length_counter ); + REFLECT( state.buf, osc.buf ); + REFLECT( state.bits_remain, osc.bits_remain ); + REFLECT( state.bits, osc.bits ); + REFLECT( state.buf_full, osc.buf_full ); + REFLECT( state.silence, osc.silence ); + REFLECT( state.irq_flag, osc.irq_flag ); + if ( mode ) + state.addr = osc.address | 0x8000; + else + osc.address = state.addr & 0x7fff; + } +}; + +void Nes_Apu::save_state( apu_state_t* state ) const +{ + for ( int i = 0; i < osc_count * 4; i++ ) + { + int index = i >> 2; + state->apu.w40xx [i] = oscs [index]->regs [i & 3]; + //if ( index < 4 ) + // state->length_counters [index] = oscs [index]->length_counter; + } + state->apu.w40xx [0x11] = dmc.dac; + + state->apu.w4015 = osc_enables; + state->apu.w4017 = frame_mode; + state->apu.frame_delay = frame_delay; + state->apu.frame_step = frame; + state->apu.irq_flag = irq_flag; + + typedef apu_reflection<1> refl; + Nes_Apu& apu = *(Nes_Apu*) this; // const_cast + refl::reflect_square ( state->square1, apu.square1 ); + refl::reflect_square ( state->square2, apu.square2 ); + refl::reflect_triangle( state->triangle, apu.triangle ); + refl::reflect_noise ( state->noise, apu.noise ); + refl::reflect_dmc ( state->dmc, apu.dmc ); +} + +void Nes_Apu::load_state( apu_state_t const& state ) +{ + reset(); + + write_register( 0, 0x4017, state.apu.w4017 ); + write_register( 0, 0x4015, state.apu.w4015 ); + osc_enables = state.apu.w4015; // DMC clears bit 4 + + for ( int i = 0; i < osc_count * 4; i++ ) + { + int n = state.apu.w40xx [i]; + int index = i >> 2; + oscs [index]->regs [i & 3] = n; + write_register( 0, 0x4000 + i, n ); + //if ( index < 4 ) + // oscs [index]->length_counter = state.length_counters [index]; + } + + frame_delay = state.apu.frame_delay; + frame = state.apu.frame_step; + irq_flag = state.apu.irq_flag; + + typedef apu_reflection<0> refl; + apu_state_t& st = (apu_state_t&) state; // const_cast + refl::reflect_square ( st.square1, square1 ); + refl::reflect_square ( st.square2, square2 ); + refl::reflect_triangle( st.triangle, triangle ); + refl::reflect_noise ( st.noise, noise ); + refl::reflect_dmc ( st.dmc, dmc ); + dmc.recalc_irq(); +} + diff --git a/quicknes/nes_emu/apu_state.h b/quicknes/nes_emu/apu_state.h new file mode 100644 index 0000000000..a2ba3ffd76 --- /dev/null +++ b/quicknes/nes_emu/apu_state.h @@ -0,0 +1,79 @@ + +// NES APU state snapshot support + +// Nes_Snd_Emu 0.1.7 + +#ifndef APU_STATE_H +#define APU_STATE_H + +#include "blargg_common.h" + +struct apu_state_t +{ + typedef BOOST::uint8_t byte; + + typedef byte env_t [3]; + /*struct env_t { + byte delay; + byte env; + byte written; + };*/ + + struct apu_t { + byte w40xx [0x14]; // $4000-$4013 + byte w4015; // enables + byte w4017; // mode + BOOST::uint16_t frame_delay; + byte frame_step; + byte irq_flag; + } apu; + + struct square_t { + BOOST::uint16_t delay; + env_t env; + byte length_counter; + byte phase; + byte swp_delay; + byte swp_reset; + byte unused2 [1]; + }; + + square_t square1; + square_t square2; + + struct triangle_t { + BOOST::uint16_t delay; + byte length_counter; + byte phase; + byte linear_counter; + byte linear_mode; + } triangle; + + struct noise_t { + BOOST::uint16_t delay; + env_t env; + byte length_counter; + BOOST::uint16_t shift_reg; + } noise; + + struct dmc_t { + BOOST::uint16_t delay; + BOOST::uint16_t remain; + BOOST::uint16_t addr; + byte buf; + byte bits_remain; + byte bits; + byte buf_full; + byte silence; + byte irq_flag; + } dmc; + + //byte length_counters [4]; + + enum { tag = 0x41505552 }; // 'APUR' + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (apu_state_t) == 72 ); + +#endif + diff --git a/quicknes/nes_emu/blargg_common.h b/quicknes/nes_emu/blargg_common.h new file mode 100644 index 0000000000..ca7f38216e --- /dev/null +++ b/quicknes/nes_emu/blargg_common.h @@ -0,0 +1,164 @@ + +// Sets up common environment for Shay Green's libraries. +// +// To change configuration options, modify blargg_config.h, not this file. + +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include +#include + +// User configuration (allow it to #include "blargg_common.h" normally) +#undef BLARGG_COMMON_H +#include "blargg_config.h" +#define BLARGG_COMMON_H + +/* BLARGG_COMPILER_HAS_BOOL: If 0, provides bool support for old compiler. If 1, + compiler is assumed to support bool. If undefined, availability is determined. + If errors occur here, add the following line to your config.h file: + #define BLARGG_COMPILER_HAS_BOOL 1 +*/ +#ifndef BLARGG_COMPILER_HAS_BOOL + #if defined (__MWERKS__) + #if !__option(bool) + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (_MSC_VER) + #if _MSC_VER < 1100 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif + #elif defined (__GNUC__) + // supports bool + #elif __cplusplus < 199711 + #define BLARGG_COMPILER_HAS_BOOL 0 + #endif +#endif +#if defined (BLARGG_COMPILER_HAS_BOOL) && !BLARGG_COMPILER_HAS_BOOL + typedef int bool; + const bool true = 1; + const bool false = 0; +#endif + +// BLARGG_NEW is used in place of 'new' to create objects. By default, plain new is used. +// To prevent an exception if out of memory, #define BLARGG_NEW new (std::nothrow) +#ifndef BLARGG_NEW + #define BLARGG_NEW new +#endif + +// BOOST::int8_t etc. + +// HAVE_STDINT_H: If defined, use for int8_t etc. +#if defined (HAVE_STDINT_H) + #include + #define BOOST + +// HAVE_INTTYPES_H: If defined, use for int8_t etc. +#elif defined (HAVE_INTTYPES_H) + #include + #define BOOST + +#else + struct BOOST + { + #if UCHAR_MAX == 0xFF && SCHAR_MAX == 0x7F + typedef signed char int8_t; + typedef unsigned char uint8_t; + #else + // No suitable 8-bit type available + typedef struct see_blargg_common_h int8_t; + typedef struct see_blargg_common_h uint8_t; + #endif + + #if USHRT_MAX == 0xFFFF + typedef short int16_t; + typedef unsigned short uint16_t; + #else + // No suitable 16-bit type available + typedef struct see_blargg_common_h int16_t; + typedef struct see_blargg_common_h uint16_t; + #endif + + #if ULONG_MAX == 0xFFFFFFFF + typedef long int32_t; + typedef unsigned long uint32_t; + #elif UINT_MAX == 0xFFFFFFFF + typedef int int32_t; + typedef unsigned int uint32_t; + #else + // No suitable 32-bit type available + typedef struct see_blargg_common_h int32_t; + typedef struct see_blargg_common_h uint32_t; + #endif + }; +#endif + +#ifdef _WIN32 +typedef wchar_t blargg_wchar_t; +#else +#include +typedef uint16_t blargg_wchar_t; +#endif + +// BOOST_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +#ifndef BOOST_STATIC_ASSERT + #ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) fails for use of __LINE__ when /Zl is specified + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] ) + #else + // Some other compilers fail when declaring same function multiple times in class, + // so differentiate them by line + #define BOOST_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) + #endif +#endif + +// In case compiler doesn't support these properly. Used rarely. +#define STATIC_CAST(T,expr) static_cast (expr) +#define CONST_CAST( T,expr) const_cast (expr) + +// blargg_err_t (0 on success, otherwise error string) +#ifndef blargg_err_t + typedef const char* blargg_err_t; +#endif + +// Success; no error +blargg_err_t const blargg_ok = 0; + +/* Pure virtual functions cause a vtable entry to a "called pure virtual" +error handler, requiring linkage to the C++ runtime library. This macro is +used in place of the "= 0", and simply expands to its argument. During +development, it expands to "= 0", allowing detection of missing overrides. */ +#define BLARGG_PURE( def ) def + +/* My code is not written with exceptions in mind, so either uses new (nothrow) +OR overrides operator new in my classes. The former is best since clients +creating objects will get standard exceptions on failure, but that causes it +to require the standard C++ library. So, when the client is using the C +interface, I override operator new to use malloc. */ + +// BLARGG_DISABLE_NOTHROW is put inside classes +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if NULL can be returned + #if __cplusplus >= 199711 || __GNUC__ >= 3 || _MSC_VER >= 1300 + #define BLARGG_THROWS_NOTHING throw () + #else + #define BLARGG_THROWS_NOTHING + #endif + + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS_NOTHING { return malloc( s ); }\ + void operator delete( void* p ) BLARGG_THROWS_NOTHING { free( p ); } + + #define BLARGG_NEW new +#else + // BLARGG_NEW is used in place of new in library code + #include + #define BLARGG_NEW new (std::nothrow) +#endif + +#endif + diff --git a/quicknes/nes_emu/blargg_config.h b/quicknes/nes_emu/blargg_config.h new file mode 100644 index 0000000000..f4d7b83977 --- /dev/null +++ b/quicknes/nes_emu/blargg_config.h @@ -0,0 +1,33 @@ + +// Nes_Emu 0.7.0 user configuration file. Don't replace when updating library. + +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment to transparently decompress files using zlib +#define HAVE_ZLIB_H + +// Uncomment to enable platform-specific (and possibly non-portable) optimizations. +#define BLARGG_NONPORTABLE 1 + +// Uncomment if automatic byte-order determination doesn't work +//#define BLARGG_BIG_ENDIAN 1 + +// Uncomment if you get errors in the bool section of blargg_common.h +//#define BLARGG_COMPILER_HAS_BOOL 1 + +// Uncomment to disable out-of-memory exceptions +//#include +//#define BLARGG_NEW new (std::nothrow) + +#define DISABLE_AUTO_FILE 1 + +#define HAVE_STDINT_H + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif + diff --git a/quicknes/nes_emu/blargg_endian.h b/quicknes/nes_emu/blargg_endian.h new file mode 100644 index 0000000000..821dbe4875 --- /dev/null +++ b/quicknes/nes_emu/blargg_endian.h @@ -0,0 +1,157 @@ + +// CPU Byte Order Utilities + +// Nes_Emu 0.7.0 + +#ifndef BLARGG_ENDIAN +#define BLARGG_ENDIAN + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one must be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__mips__) || defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#else + // Assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() +{ + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif +} + +inline unsigned get_le16( void const* p ) { + return ((unsigned char*) p) [1] * 0x100u + + ((unsigned char*) p) [0]; +} +inline unsigned get_be16( void const* p ) { + return ((unsigned char*) p) [0] * 0x100u + + ((unsigned char*) p) [1]; +} +inline unsigned long get_le32( void const* p ) { + return ((unsigned char*) p) [3] * 0x01000000ul + + ((unsigned char*) p) [2] * 0x00010000ul + + ((unsigned char*) p) [1] * 0x00000100ul + + ((unsigned char*) p) [0]; +} +inline unsigned long get_be32( void const* p ) { + return ((unsigned char*) p) [0] * 0x01000000ul + + ((unsigned char*) p) [1] * 0x00010000ul + + ((unsigned char*) p) [2] * 0x00000100ul + + ((unsigned char*) p) [3]; +} +inline void set_le16( void* p, unsigned n ) { + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} +inline void set_be16( void* p, unsigned n ) { + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} +inline void set_le32( void* p, unsigned long n ) { + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} +inline void set_be32( void* p, unsigned long n ) { + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [3] = (unsigned char) n; +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known + #if BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #endif + + #if BLARGG_CPU_POWERPC && defined (__MWERKS__) + // PowerPC has special byte-reversed instructions + // to do: assumes that PowerPC is running in big-endian mode + // to do: implement for other compilers which don't support these macros + #define GET_LE16( addr ) (__lhbrx( (addr), 0 )) + #define GET_LE32( addr ) (__lwbrx( (addr), 0 )) + #define SET_LE16( addr, data ) (__sthbrx( (data), (addr), 0 )) + #define SET_LE32( addr, data ) (__stwbrx( (data), (addr), 0 )) + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, unsigned long n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, unsigned long n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t* p ) { return GET_LE16( p ); } +inline unsigned long get_le( BOOST::uint32_t* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t* p ) { return GET_BE16( p ); } +inline unsigned long get_be( BOOST::uint32_t* p ) { return GET_BE32( p ); } + +#endif + diff --git a/quicknes/nes_emu/blargg_source.h b/quicknes/nes_emu/blargg_source.h new file mode 100644 index 0000000000..3bd328d9ea --- /dev/null +++ b/quicknes/nes_emu/blargg_source.h @@ -0,0 +1,76 @@ + +// Included at the beginning of library source files, after all other #include lines + +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +// If debugging is enabled, abort program if expr is false. Meant for checking +// internal state and consistency. A failed assertion indicates a bug in the module. +// void assert( bool expr ); +#include + +// If debugging is enabled and expr is false, abort program. Meant for checking +// caller-supplied parameters and operations that are outside the control of the +// module. A failed requirement indicates a bug outside the module. +// void require( bool expr ); +#undef require +#define require( expr ) assert( expr ) + +// Like printf() except output goes to debug log file. Might be defined to do +// nothing (not even evaluate its arguments). +// void dprintf( const char* format, ... ); +inline void blargg_dprintf_( const char*, ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +// If enabled, evaluate expr and if false, make debug log entry with source file +// and line. Meant for finding situations that should be examined further, but that +// don't indicate a problem. In all cases, execution continues normally. +#undef check +#define check( expr ) ((void) 0) + +// If expr yields error string, return it from current function, otherwise continue. +#undef RETURN_ERR +#define RETURN_ERR( expr ) do { \ + blargg_err_t blargg_return_err_ = (expr); \ + if ( blargg_return_err_ ) return blargg_return_err_; \ + } while ( 0 ) + +// If ptr is 0, return out of memory error string. +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) do { if ( (ptr) == 0 ) return "Out of memory"; } while ( 0 ) + +// Avoid any macros which evaluate their arguments multiple times +#undef min +#undef max + +// using const references generates crappy code, and I am currenly only using these +// for built-in types, so they take arguments by value + +template +inline T min( T x, T y ) +{ + if ( x < y ) + return x; + return y; +} + +template +inline T max( T x, T y ) +{ + if ( x < y ) + return y; + return x; +} + +// deprecated +#define BLARGG_CHECK_ALLOC CHECK_ALLOC +#define BLARGG_RETURN_ERR RETURN_ERR + +// BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf and check +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif + diff --git a/quicknes/nes_emu/misc_mappers.cpp b/quicknes/nes_emu/misc_mappers.cpp new file mode 100644 index 0000000000..bc79b768f5 --- /dev/null +++ b/quicknes/nes_emu/misc_mappers.cpp @@ -0,0 +1,207 @@ + +// Optional less-common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Nina-1 (Deadly Towers only) + +class Mapper_Nina1 : public Nes_Mapper { + byte bank; +public: + Mapper_Nina1() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + write( 0, 0, bank ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + bank = data; + set_prg_bank( 0x8000, bank_32k, bank ); + } +}; + +// GNROM + +class Mapper_Gnrom : public Nes_Mapper { + byte bank; +public: + Mapper_Gnrom() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; + write( 0, 0, b ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + int changed = bank ^ data; + bank = data; + + if ( changed & 0x30 ) + set_prg_bank( 0x8000, bank_32k, bank >> 4 & 3 ); + + if ( changed & 0x03 ) + set_chr_bank( 0, bank_8k, bank & 3 ); + } +}; + +// Color Dreams + +class Mapper_Color_Dreams : public Nes_Mapper { + byte bank; +public: + Mapper_Color_Dreams() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; + write( 0, 0, b ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + int changed = bank ^ data; + bank = data; + + if ( changed & 0x0f ) + set_prg_bank( 0x8000, bank_32k, bank & 0x0f ); + + if ( changed & 0xf0 ) + set_chr_bank( 0, bank_8k, bank >> 4 ); + } +}; + +// Jaleco/Konami + +class Mapper_87 : public Nes_Mapper { + byte bank; +public: + Mapper_87() + { + register_state( &bank, 1 ); + } + + void apply_mapping() + { + intercept_writes( 0x6000, 1 ); + write( 0, 0x6000, bank ); + } + + bool write_intercepted( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr != 0x6000 ) + return false; + + bank = data; + set_chr_bank( 0, bank_8k, data >> 1 ); + return true; + } + + void write( nes_time_t, nes_addr_t, int ) { } +}; + +// Camerica + +class Mapper_Camerica : public Nes_Mapper { + byte regs [3]; +public: + Mapper_Camerica() + { + register_state( regs, sizeof regs ); + } + + virtual void apply_mapping() + { + write( 0, 0xc000, regs [0] ); + if ( regs [1] & 0x80 ) + write( 0, 0x9000, regs [1] ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr >= 0xc000 ) + { + regs [0] = data; + set_prg_bank( 0x8000, bank_16k, data ); + } + else if ( (addr & 0xf000) == 0x9000 ) + { + regs [1] = 0x80 | data; + mirror_single( (data >> 4) & 1 ); + } + } +}; + +// Quattro + +class Mapper_Quattro : public Nes_Mapper { + byte regs [2]; +public: + Mapper_Quattro() + { + register_state( regs, sizeof regs ); + } + + virtual void reset_state() + { + regs [0] = 0; + regs [1] = 3; + } + + virtual void apply_mapping() + { + int bank = regs [0] >> 1 & 0x0c; + set_prg_bank( 0x8000, bank_16k, bank + (regs [1] & 3) ); + set_prg_bank( 0xC000, bank_16k, bank + 3 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr < 0xc000 ) + regs [0] = data; + else + regs [1] = data; + Mapper_Quattro::apply_mapping(); + } +}; + +void register_misc_mappers(); +void register_misc_mappers() +{ + register_mapper( 11 ); + register_mapper( 34 ); + register_mapper( 66 ); + register_mapper( 71 ); + register_mapper( 87 ); + register_mapper( 232 ); +} + diff --git a/quicknes/nes_emu/nes_cpu_io.h b/quicknes/nes_emu/nes_cpu_io.h new file mode 100644 index 0000000000..e09e0f45d9 --- /dev/null +++ b/quicknes/nes_emu/nes_cpu_io.h @@ -0,0 +1,144 @@ + +#include "Nes_Core.h" +#include "Nes_Mapper.h" + +#include "blargg_source.h" + +int Nes_Core::cpu_read( nes_addr_t addr, nes_time_t time ) +{ + //LOG_FREQ( "cpu_read", 16, addr >> 12 ); + + { + int result = cpu::low_mem [addr & 0x7FF]; + if ( !(addr & 0xE000) ) + return result; + } + + { + int result = *cpu::get_code( addr ); + if ( addr > 0x7FFF ) + return result; + } + + time += cpu_time_offset; + if ( addr < 0x4000 ) + return ppu.read( addr, time ); + + clock_ = time; + if ( data_reader_mapped [addr >> page_bits] ) + { + int result = mapper->read( time, addr ); + if ( result >= 0 ) + return result; + } + + if ( addr < 0x6000 ) + return read_io( addr ); + + if ( addr < sram_readable ) + return impl->sram [addr & (impl_t::sram_size - 1)]; + + if ( addr < lrom_readable ) + return *cpu::get_code( addr ); + + #ifndef NDEBUG + log_unmapped( addr ); + #endif + + return addr >> 8; // simulate open bus +} + +inline int Nes_Core::cpu_read_ppu( nes_addr_t addr, nes_time_t time ) +{ + //LOG_FREQ( "cpu_read_ppu", 16, addr >> 12 ); + + // Read of status register (0x2002) is heavily optimized since many games + // poll it hundreds of times per frame. + nes_time_t next = ppu_2002_time; + int result = ppu.r2002; + if ( addr == 0x2002 ) + { + ppu.second_write = false; + if ( time >= next ) + result = ppu.read_2002( time + cpu_time_offset ); + } + else + { + result = cpu::low_mem [addr & 0x7FF]; + if ( addr >= 0x2000 ) + result = cpu_read( addr, time ); + } + + return result; +} + +void Nes_Core::cpu_write_2007( int data ) +{ + // ppu.write_2007() is inlined + if ( ppu.write_2007( data ) & Nes_Ppu::vaddr_clock_mask ) + mapper->a12_clocked(); +} + +void Nes_Core::cpu_write( nes_addr_t addr, int data, nes_time_t time ) +{ + //LOG_FREQ( "cpu_write", 16, addr >> 12 ); + + if ( !(addr & 0xE000) ) + { + cpu::low_mem [addr & 0x7FF] = data; + return; + } + + time += cpu_time_offset; + if ( addr < 0x4000 ) + { + if ( (addr & 7) == 7 ) + cpu_write_2007( data ); + else + ppu.write( time, addr, data ); + return; + } + + clock_ = time; + if ( data_writer_mapped [addr >> page_bits] && mapper->write_intercepted( time, addr, data ) ) + return; + + if ( addr < 0x6000 ) + { + write_io( addr, data ); + return; + } + + if ( addr < sram_writable ) + { + impl->sram [addr & (impl_t::sram_size - 1)] = data; + return; + } + + if ( addr > 0x7FFF ) + { + mapper->write( clock_, addr, data ); + return; + } + + #ifndef NDEBUG + log_unmapped( addr, data ); + #endif +} + +#define NES_CPU_READ_PPU( cpu, addr, time ) \ + STATIC_CAST(Nes_Core&,*cpu).cpu_read_ppu( addr, time ) + +#define NES_CPU_READ( cpu, addr, time ) \ + STATIC_CAST(Nes_Core&,*cpu).cpu_read( addr, time ) + +#define NES_CPU_WRITEX( cpu, addr, data, time ){\ + STATIC_CAST(Nes_Core&,*cpu).cpu_write( addr, data, time );\ +} + +#define NES_CPU_WRITE( cpu, addr, data, time ){\ + if ( addr < 0x800 ) cpu->low_mem [addr] = data;\ + else if ( addr == 0x2007 ) STATIC_CAST(Nes_Core&,*cpu).cpu_write_2007( data );\ + else STATIC_CAST(Nes_Core&,*cpu).cpu_write( addr, data, time );\ +} + diff --git a/quicknes/nes_emu/nes_data.cpp b/quicknes/nes_emu/nes_data.cpp new file mode 100644 index 0000000000..50e1c91121 --- /dev/null +++ b/quicknes/nes_emu/nes_data.cpp @@ -0,0 +1,75 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "nes_data.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +#define SWAP_BE( n ) (void) (set_be( &(n), (n) )) +#define SWAP_LE( n ) (void) (set_le( &(n), (n) )) + +void nes_block_t::swap() +{ + SWAP_BE( tag ); + SWAP_LE( size ); +} + +void nes_state_t::swap() +{ + SWAP_LE( timestamp ); + SWAP_LE( frame_count ); +} + +void cpu_state_t::swap() +{ + SWAP_LE( pc ); +} + +void ppu_state_t::swap() +{ + SWAP_LE( vram_addr ); + SWAP_LE( vram_temp ); + SWAP_LE( decay_low ); + SWAP_LE( decay_high ); +} + +void apu_state_t::swap() +{ + SWAP_LE( apu.frame_delay ); + SWAP_LE( square1.delay ); + SWAP_LE( square2.delay ); + SWAP_LE( triangle.delay ); + SWAP_LE( noise.delay ); + SWAP_LE( noise.shift_reg ); + SWAP_LE( dmc.delay ); + SWAP_LE( dmc.remain ); + SWAP_LE( dmc.addr ); +} + +void joypad_state_t::swap() +{ + SWAP_LE( joypad_latches [0] ); + SWAP_LE( joypad_latches [1] ); +} + +void movie_info_t::swap() +{ + SWAP_LE( begin ); + SWAP_LE( length ); + SWAP_LE( period ); + SWAP_LE( extra ); +} + diff --git a/quicknes/nes_emu/nes_data.h b/quicknes/nes_emu/nes_data.h new file mode 100644 index 0000000000..4e33472502 --- /dev/null +++ b/quicknes/nes_emu/nes_data.h @@ -0,0 +1,174 @@ + +// NES data file block formats + +// Nes_Emu 0.7.0 + +#ifndef NES_DATA_H +#define NES_DATA_H + +#include "blargg_common.h" +#include "apu_state.h" + +typedef long nes_tag_t; + +#if 'ABCD' == '\101\102\103\104' +#define FOUR_CHAR( c ) (\ + ((c) / '\1\0\0\0' % 0x100 * 0x01000000L) +\ + ((c) / '\0\1\0\0' % 0x100 * 0x00010000L) +\ + ((c) / '\0\0\1\0' % 0x100 * 0x00000100L) +\ + ((c) / '\0\0\0\1' % 0x100 * 0x00000001L)\ +) +#else +#if 'ABCD' == 0x41424344 +#define FOUR_CHAR( c ) c +#else +#define FOUR_CHAR( c ) (\ + ((c) / 0x01000000 % 0x100 * 0x00000001) +\ + ((c) / 0x00010000 % 0x100 * 0x00000100) +\ + ((c) / 0x00000100 % 0x100 * 0x00010000) +\ + ((c) / 0x00000001 % 0x100 * 0x01000000)\ +) +#endif +#endif + +typedef BOOST::uint8_t byte; + +// Binary format of save state blocks. All multi-byte values are stored in little-endian. + +nes_tag_t const state_file_tag = FOUR_CHAR('NESS'); + +nes_tag_t const movie_file_tag = FOUR_CHAR('NMOV'); + +// Name of cartridge file in 8-bit characters (UTF-8 preferred) with ".nes" etc *removed*, +// no NUL termination. Yes: "Castlevania (U)". No: "Strider (U).nes". +nes_tag_t const cart_name_tag = FOUR_CHAR('romn'); + +// CRC-32 of cartridge's PRG and CHR data combined +nes_tag_t const cart_checksum_tag = FOUR_CHAR('csum'); + +struct nes_block_t +{ + BOOST::uint32_t tag; // ** stored in big-endian + BOOST::uint32_t size; + + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (nes_block_t) == 8 ); + +unsigned long const group_begin_size = 0xffffffff; // group block has this size +nes_tag_t const group_end_tag = FOUR_CHAR('gend'); // group end block has this tag + +struct movie_info_t +{ + BOOST::uint32_t begin; + BOOST::uint32_t length; + BOOST::uint16_t period; + BOOST::uint16_t extra; + byte joypad_count; + byte has_joypad_sync; + byte unused [2]; + + enum { tag = FOUR_CHAR('INFO') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (movie_info_t) == 16 ); + +struct nes_state_t +{ + BOOST::uint16_t timestamp; // CPU clocks * 15 (for NTSC) + byte pal; + byte unused [1]; + BOOST::uint32_t frame_count; // number of frames emulated since power-up + + enum { tag = FOUR_CHAR('TIME') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (nes_state_t) == 8 ); + +struct joypad_state_t +{ + BOOST::uint32_t joypad_latches [2]; // joypad 1 & 2 shift registers + byte w4016; // strobe + byte unused [3]; + + enum { tag = FOUR_CHAR('CTRL') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (joypad_state_t) == 12 ); + +// Increase this (and let me know) if your mapper requires more state. This only +// sets the size of the in-memory buffer; it doesn't affect the file format at all. +unsigned const max_mapper_state_size = 256; +struct mapper_state_t +{ + int size; + union { + double align; + byte data [max_mapper_state_size]; + }; + + void write( const void* p, unsigned long s ); + int read( void* p, unsigned long s ) const; +}; + +struct cpu_state_t +{ + BOOST::uint16_t pc; + byte s; + byte p; + byte a; + byte x; + byte y; + byte unused [1]; + + enum { tag = FOUR_CHAR('CPUR') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (cpu_state_t) == 8 ); + +struct ppu_state_t +{ + byte w2000; // control + byte w2001; // control + byte r2002; // status + byte w2003; // sprite ram addr + byte r2007; // vram read buffer + byte second_write; // next write to $2005/$2006 is second since last $2002 read + BOOST::uint16_t vram_addr; // loopy_v + BOOST::uint16_t vram_temp; // loopy_t + byte pixel_x; // fine-scroll (0-7) + byte unused; + byte palette [0x20]; // entries $10, $14, $18, $1c should be ignored + BOOST::uint16_t decay_low; + BOOST::uint16_t decay_high; + byte open_bus; + byte unused2[3]; + + enum { tag = FOUR_CHAR('PPUR') }; + void swap(); +}; +BOOST_STATIC_ASSERT( sizeof (ppu_state_t) == 20 + 0x20 ); + +struct mmc1_state_t +{ + byte regs [4]; // current registers (5 bits each) + byte bit; // number of bits in buffer (0 to 4) + byte buf; // currently buffered bits (new bits added to bottom) +}; +BOOST_STATIC_ASSERT( sizeof (mmc1_state_t) == 6 ); + +struct mmc3_state_t +{ + byte banks [8]; // last writes to $8001 indexed by (mode & 7) + byte mode; // $8000 + byte mirror; // $a000 + byte sram_mode; // $a001 + byte irq_ctr; // internal counter + byte irq_latch; // $c000 + byte irq_enabled;// last write was to 0) $e000, 1) $e001 + byte irq_flag; +}; +BOOST_STATIC_ASSERT( sizeof (mmc3_state_t) == 15 ); + +#endif + diff --git a/quicknes/nes_emu/nes_mappers.cpp b/quicknes/nes_emu/nes_mappers.cpp new file mode 100644 index 0000000000..264893bc0a --- /dev/null +++ b/quicknes/nes_emu/nes_mappers.cpp @@ -0,0 +1,117 @@ + +// Common simple mappers + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// NROM + +class Mapper_Nrom : public Nes_Mapper { +public: + Mapper_Nrom() { } + + virtual void apply_mapping() { } + + virtual void write( nes_time_t, nes_addr_t, int ) + { + // empty + } +}; + +Nes_Mapper* Nes_Mapper::make_nrom() { return new Mapper_Nrom; } + +// UNROM + +class Mapper_Unrom : public Nes_Mapper { + byte bank; +public: + Mapper_Unrom() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + enable_sram(); // at least one UNROM game needs sram (Bomberman 2) + set_prg_bank( 0x8000, bank_16k, bank ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + bank = handle_bus_conflict( addr, data ); + set_prg_bank( 0x8000, bank_16k, bank ); + } +}; + +Nes_Mapper* Nes_Mapper::make_unrom() { return new Mapper_Unrom; } + +// AOROM + +class Mapper_Aorom : public Nes_Mapper { + byte bank; +public: + Mapper_Aorom() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; // force update + write( 0, 0, b ); + } + + virtual void write( nes_time_t, nes_addr_t, int data ) + { + int changed = bank ^ data; + bank = data; + + if ( changed & 0x10 ) + mirror_single( bank >> 4 & 1 ); + + if ( changed & 0x0f ) + set_prg_bank( 0x8000, bank_32k, bank & 7 ); + } +}; + +Nes_Mapper* Nes_Mapper::make_aorom() { return new Mapper_Aorom; } + +// CNROM + +class Mapper_Cnrom : public Nes_Mapper { + byte bank; +public: + Mapper_Cnrom() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + set_chr_bank( 0, bank_8k, bank & 7 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + bank = handle_bus_conflict( addr, data ); + set_chr_bank( 0, bank_8k, bank & 7 ); + } +}; + +Nes_Mapper* Nes_Mapper::make_cnrom() { return new Mapper_Cnrom; } + diff --git a/quicknes/nes_emu/nes_ntsc.h b/quicknes/nes_emu/nes_ntsc.h new file mode 100644 index 0000000000..2edeed11b9 --- /dev/null +++ b/quicknes/nes_emu/nes_ntsc.h @@ -0,0 +1,199 @@ + +/* NES NTSC video filter */ + +/* nes_ntsc 0.2.0 */ + +#ifndef NES_NTSC_H +#define NES_NTSC_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown +in parenthesis and should remain fairly stable in future versions. */ +typedef struct nes_ntsc_setup_t +{ + /* Basic parameters */ + double hue; /* -1 = -180 degrees +1 = +180 degrees */ + double saturation; /* -1 = grayscale (0.0) +1 = oversaturated colors (2.0) */ + double contrast; /* -1 = dark (0.5) +1 = light (1.5) */ + double brightness; /* -1 = dark (0.5) +1 = light (1.5) */ + double sharpness; /* edge contrast enhancement/blurring */ + + /* Advanced parameters */ + double gamma; /* -1 = dark (1.5) +1 = light (0.5) */ + double resolution; /* image resolution */ + double artifacts; /* artifacts caused by color changes */ + double fringing; /* color artifacts caused by brightness changes */ + double bleed; /* color bleed (color resolution reduction) */ + int merge_fields; /* if 1, merges even and odd fields together to reduce flicker */ + float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */ + + unsigned char* palette_out; /* optional RGB palette out, 3 bytes per color */ +} nes_ntsc_setup_t; + +/* Video format presets */ +extern nes_ntsc_setup_t const nes_ntsc_composite; /* color bleeding + artifacts */ +extern nes_ntsc_setup_t const nes_ntsc_svideo; /* color bleeding only */ +extern nes_ntsc_setup_t const nes_ntsc_rgb; /* crisp image */ +extern nes_ntsc_setup_t const nes_ntsc_monochrome;/* desaturated + artifacts */ + +enum { nes_ntsc_palette_size = 64 }; +enum { nes_ntsc_emph_palette_size = 64 * 8 }; + +/* Initialize and adjust parameters. Can be called multiple times on the same +nes_ntsc_t object. Can pass 0 for either parameter. */ +typedef struct nes_ntsc_t nes_ntsc_t; +void nes_ntsc_init( nes_ntsc_t* ntsc, nes_ntsc_setup_t const* setup ); + +/* Filter one or more rows of pixels. Input pixels are 6-bit palette indicies. +In_row_width is the number of pixels to get to the next input row. Out_pitch +is the number of *bytes* to get to the next output row. Output pixel format +is set by NES_NTSC_OUT_DEPTH (defaults to 16-bit RGB). */ +void nes_ntsc_blit( nes_ntsc_t const* ntsc, unsigned char const* nes_in, + long in_row_width, int burst_phase, int in_width, int in_height, + void* rgb_out, long out_pitch ); + +/* Equivalent functions with color emphasis support. Source pixels are +9-bit values with the upper 3 bits specifying the emphasis bits from +PPU register 0x2001. */ +typedef struct nes_ntsc_emph_t nes_ntsc_emph_t; +void nes_ntsc_init_emph( nes_ntsc_emph_t* ntsc, nes_ntsc_setup_t const* setup ); +void nes_ntsc_blit_emph( nes_ntsc_emph_t const* ntsc, unsigned short const* nes_in, + long in_row_width, int burst_phase, int in_width, int in_height, + void* rgb_out, long out_pitch ); + +/* Number of output pixels written by blitter for given input width. Width might +be rounded down slightly; use NES_NTSC_IN_WIDTH() on result to find rounded +value. Guaranteed not to round 256 down at all. */ +#define NES_NTSC_OUT_WIDTH( in_width ) \ + (((in_width) - 1) / nes_ntsc_in_chunk * nes_ntsc_out_chunk + nes_ntsc_out_chunk) + +/* Number of input pixels that will fit within given output width. Might be +rounded down slightly; use NES_NTSC_OUT_WIDTH() on result to find rounded +value. */ +#define NES_NTSC_IN_WIDTH( out_width ) \ + ((out_width) / nes_ntsc_out_chunk * nes_ntsc_in_chunk - nes_ntsc_in_chunk + 1) + + +/* Interface for user-defined custom blitters. +Can be used with nes_ntsc_t and nes_ntsc_emph_t */ + +enum { nes_ntsc_in_chunk = 3 }; /* number of input pixels read per chunk */ +enum { nes_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */ +enum { nes_ntsc_black = 15 }; /* palette index for black */ +enum { nes_ntsc_burst_count = 3 }; /* burst phase cycles through 0, 1, and 2 */ + +/* Begin outputting row and start three pixels. First pixel will be cut off a bit. +Use nes_ntsc_black for unused pixels. Declares variables, so must be before first +statement in a block (unless you're using C++). */ +#define NES_NTSC_BEGIN_ROW( ntsc, burst, pixel0, pixel1, pixel2 ) \ + char const* const ktable = \ + (char*) (ntsc)->table + burst * (nes_ntsc_burst_size * sizeof (ntsc_rgb_t));\ + NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, NES_NTSC_ENTRY_, ktable ) + +/* Begin input pixel */ +#define NES_NTSC_COLOR_IN( in_index, color_in ) \ + NTSC_COLOR_IN_( in_index, color_in, NES_NTSC_ENTRY_, ktable ) + +/* Generate output pixel. Bits can be 24, 16, 15, 32 (treated as 24), or 0: +24: RRRRRRRR GGGGGGGG BBBBBBBB +16: RRRRRGGG GGGBBBBB +15: RRRRRGG GGGBBBBB + 0: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format; x = junk bits) */ +#define NES_NTSC_RGB_OUT( index, rgb_out, bits ) \ + NTSC_RGB_OUT_14_( index, rgb_out, bits, 0 ) + + +/* private */ +enum { nes_ntsc_entry_size = 128 }; +typedef unsigned long ntsc_rgb_t; +struct nes_ntsc_t { + ntsc_rgb_t table [nes_ntsc_palette_size * nes_ntsc_entry_size]; +}; +struct nes_ntsc_emph_t { + ntsc_rgb_t table [nes_ntsc_emph_palette_size * nes_ntsc_entry_size]; +}; +enum { nes_ntsc_burst_size = nes_ntsc_entry_size / nes_ntsc_burst_count }; + +#define NES_NTSC_ENTRY_( ktable, n ) \ + (ntsc_rgb_t*) (ktable + (n) * (nes_ntsc_entry_size * sizeof (ntsc_rgb_t))) + +/* deprecated */ +#define NES_NTSC_RGB24_OUT( x, out ) NES_NTSC_RGB_OUT( x, out, 24 ) +#define NES_NTSC_RGB16_OUT( x, out ) NES_NTSC_RGB_OUT( x, out, 16 ) +#define NES_NTSC_RGB15_OUT( x, out ) NES_NTSC_RGB_OUT( x, out, 15 ) +#define NES_NTSC_RAW_OUT( x, out ) NES_NTSC_RGB_OUT( x, out, 0 ) + +enum { nes_ntsc_min_in_width = 256 }; +enum { nes_ntsc_min_out_width = NES_NTSC_OUT_WIDTH( nes_ntsc_min_in_width ) }; + +enum { nes_ntsc_640_in_width = 271 }; +enum { nes_ntsc_640_out_width = NES_NTSC_OUT_WIDTH( nes_ntsc_640_in_width ) }; +enum { nes_ntsc_640_overscan_left = 8 }; +enum { nes_ntsc_640_overscan_right = nes_ntsc_640_in_width - 256 - nes_ntsc_640_overscan_left }; + +enum { nes_ntsc_full_in_width = 283 }; +enum { nes_ntsc_full_out_width = NES_NTSC_OUT_WIDTH( nes_ntsc_full_in_width ) }; +enum { nes_ntsc_full_overscan_left = 16 }; +enum { nes_ntsc_full_overscan_right = nes_ntsc_full_in_width - 256 - nes_ntsc_full_overscan_left }; + +/* common 3->7 ntsc macros */ +#define NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, ENTRY, table ) \ + unsigned const ntsc_pixel0_ = (pixel0);\ + ntsc_rgb_t const* kernel0 = ENTRY( table, ntsc_pixel0_ );\ + unsigned const ntsc_pixel1_ = (pixel1);\ + ntsc_rgb_t const* kernel1 = ENTRY( table, ntsc_pixel1_ );\ + unsigned const ntsc_pixel2_ = (pixel2);\ + ntsc_rgb_t const* kernel2 = ENTRY( table, ntsc_pixel2_ );\ + ntsc_rgb_t const* kernelx0;\ + ntsc_rgb_t const* kernelx1 = kernel0;\ + ntsc_rgb_t const* kernelx2 = kernel0 + +#define NTSC_RGB_OUT_14_( x, rgb_out, bits, shift ) {\ + ntsc_rgb_t raw_ =\ + kernel0 [x ] + kernel1 [(x+12)%7+14] + kernel2 [(x+10)%7+28] +\ + kernelx0 [(x+7)%14] + kernelx1 [(x+ 5)%7+21] + kernelx2 [(x+ 3)%7+35];\ + NTSC_CLAMP_( raw_, shift );\ + NTSC_RGB_OUT_( rgb_out, bits, shift );\ +} + +/* common ntsc macros */ +#define ntsc_rgb_builder ((1L << 21) | (1 << 11) | (1 << 1)) +#define ntsc_clamp_mask (ntsc_rgb_builder * 3 / 2) +#define ntsc_clamp_add (ntsc_rgb_builder * 0x101) +#define NTSC_CLAMP_( io, shift ) {\ + ntsc_rgb_t sub = (io) >> (9-(shift)) & ntsc_clamp_mask;\ + ntsc_rgb_t clamp = ntsc_clamp_add - sub;\ + io |= clamp;\ + clamp -= sub;\ + io &= clamp;\ +} + +#define NTSC_COLOR_IN_( index, color, ENTRY, table ) {\ + unsigned color_;\ + kernelx##index = kernel##index;\ + kernel##index = (color_ = (color), ENTRY( table, color_ ));\ +} + +/* x is always zero except in snes_ntsc library */ +#define NTSC_RGB_OUT_( rgb_out, bits, x ) {\ + if ( bits == 16 )\ + rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\ + if ( bits == 24 || bits == 32 )\ + rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\ + if ( bits == 15 )\ + rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\ + if ( bits == 14 )\ + rgb_out = (raw_>>(24-x)& 0x001F)|(raw_>>(9-x)&0x03E0)|(raw_<<(6+x)&0x7C00);\ + if ( bits == 0 )\ + rgb_out = raw_ << x;\ +} + +#ifdef __cplusplus + } +#endif + +#endif + diff --git a/quicknes/nes_emu/nes_ntsc_impl.h b/quicknes/nes_emu/nes_ntsc_impl.h new file mode 100644 index 0000000000..4d54c46b4e --- /dev/null +++ b/quicknes/nes_emu/nes_ntsc_impl.h @@ -0,0 +1,520 @@ + +/* Common implementation of NTSC filters */ + +#include +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#define DISABLE_CORRECTION 0 + +#ifndef gamma_size + #if NTSC_STANDARD_INIT + #define gamma_size 256 + #else + #define gamma_size 1 + #endif +#endif + +#ifndef rgb_bits + #define rgb_bits 8 +#endif + +#ifndef LUMA_CUTOFF + #define LUMA_CUTOFF 0.20 +#endif + +#ifndef artifacts_max + #define artifacts_max (artifacts_mid * 1.5f) +#endif + +#ifndef fringing_max + #define fringing_max (fringing_mid * 2) +#endif + +#ifndef burst_count + #define burst_count 1 +#endif + +#ifndef rescale_in + #define rescale_in 1 + #define rescale_out 1 +#endif + +#ifndef std_decoder_hue + #define std_decoder_hue 0 +#endif + +#define ext_decoder_hue (std_decoder_hue + 15) + +#define NTSC_NAME2_( p, n ) p##_ntsc_##n +#define NTSC_NAME_( p, n ) NTSC_NAME2_( p, n ) +#define NTSC_NAME( name ) NTSC_NAME_( ntsc_prefix, name ) + +#define rgb_unit (1 << rgb_bits) +#define burst_size (NTSC_NAME( entry_size ) / burst_count) +#define ntsc_pixels NTSC_NAME( pixels ) + +#define rgb_offset (rgb_unit * 2 + 0.5f) + +#define NTSC_CLAMP( io ) \ + NTSC_CLAMP_( io, (8 - rgb_bits) ) + +enum { kernel_half = 16 }; +enum { kernel_size = kernel_half * 2 + 1 }; + +typedef struct ntsc_impl_t +{ + float to_float [gamma_size]; + float to_rgb [burst_count * 6]; + float contrast; + float brightness; + float artifacts; + float fringing; + float hue_warping; + float kernel [rescale_out * kernel_size * 2]; +} ntsc_impl_t; + +#undef PI +#define PI 3.14159265358979323846f + +#define ROTATE_IQ( i, q, sin_b, cos_b ) {\ + float t;\ + t = i * cos_b - q * sin_b;\ + q = i * sin_b + q * cos_b;\ + i = t;\ +} + +static void init_ntsc_filters( ntsc_impl_t* impl, NTSC_NAME( setup_t ) const* setup ) +{ +#if rescale_out > 1 + float kernels [kernel_size * 2]; +#else + float* const kernels = impl->kernel; +#endif + + /* generate luma (y) filter using sinc kernel */ + { + /* sinc with rolloff (dsf) */ + float const rolloff = 1 + (float) setup->sharpness * (float) 0.032; + float const maxh = 32; + float const pow_a_n = (float) pow( rolloff, maxh ); + float sum; + int i; + /* quadratic mapping to reduce negative (blurring) range */ + float to_angle = (float) setup->resolution + 1; + to_angle = PI / maxh * (float) LUMA_CUTOFF * (to_angle * to_angle + 1); + + kernels [kernel_size * 3 / 2] = maxh; /* default center value */ + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + int x = i - kernel_half; + float angle = x * to_angle; + /* instability occurs at center point with rolloff very close to 1.0 */ + if ( x || pow_a_n > (float) 1.056 || pow_a_n < (float) 0.981 ) + { + float rolloff_cos_a = rolloff * (float) cos( angle ); + float num = 1 - rolloff_cos_a - + pow_a_n * (float) cos( maxh * angle ) + + pow_a_n * rolloff * (float) cos( (maxh - 1) * angle ); + float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff; + float dsf = num / den; + kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - (float) 0.5; + } + } + + /* apply blackman window and find sum */ + sum = 0; + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + float x = PI * 2 / (kernel_half * 2) * i; + float blackman = 0.42f - 0.5f * (float) cos( x ) + 0.08f * (float) cos( x * 2 ); + sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman); + } + + /* normalize kernel */ + sum = 1.0f / sum; + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + int x = kernel_size * 3 / 2 - kernel_half + i; + kernels [x] *= sum; + assert( kernels [x] == kernels [x] ); /* catch numerical instability */ + } + } + + /* generate chroma (iq) filter using gaussian kernel */ + { + float const cutoff_factor = -0.03125f; + float cutoff = (float) setup->bleed; + int i; + + if ( cutoff < 0 ) + { + /* keep extreme value accessible only near upper end of scale (1.0) */ + cutoff *= cutoff; + cutoff *= cutoff; + cutoff *= cutoff; + cutoff *= -30.0f / 0.65f; + } + cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff; + + for ( i = -kernel_half; i <= kernel_half; i++ ) + kernels [kernel_size / 2 + i] = (float) exp( i * i * cutoff ); + + /* normalize even and odd phases separately */ + for ( i = 0; i < 2; i++ ) + { + float sum = 0; + int x; + for ( x = i; x < kernel_size; x += 2 ) + sum += kernels [x]; + + sum = 1.0f / sum; + for ( x = i; x < kernel_size; x += 2 ) + { + kernels [x] *= sum; + assert( kernels [x] == kernels [x] ); /* catch numerical instability */ + } + } + } + + /* + printf( "luma:\n" ); + for ( i = kernel_size; i < kernel_size * 2; i++ ) + printf( "%f\n", kernels [i] ); + printf( "chroma:\n" ); + for ( i = 0; i < kernel_size; i++ ) + printf( "%f\n", kernels [i] ); + */ + + /* generate linear rescale kernels */ + #if rescale_out > 1 + { + float weight = 1.0f; + float* out = impl->kernel; + do + { + float remain = 0; + int i; + weight -= 1.0f / rescale_in; + for ( i = 0; i < kernel_size * 2; i++ ) + { + float cur = kernels [i]; + float m = cur * weight; + *out++ = m + remain; + remain = cur - m; + } + } + while ( out < &impl->kernel [rescale_out * kernel_size * 2] ); + } + #endif +} + +static float const default_decoder [6] = + { 0.956f, 0.621f, -0.272f, -0.647f, -1.105f, 1.702f }; + +static void init_ntsc_impl( ntsc_impl_t* impl, NTSC_NAME( setup_t ) const* setup ) +{ + impl->brightness = (float) setup->brightness * (0.5f * rgb_unit) + rgb_offset; + impl->contrast = (float) setup->contrast * (0.5f * rgb_unit) + rgb_unit; + + impl->artifacts = (float) setup->artifacts; + if ( impl->artifacts > 0 ) + impl->artifacts *= artifacts_max - artifacts_mid; + impl->artifacts = impl->artifacts * artifacts_mid + artifacts_mid; + + impl->fringing = (float) setup->fringing; + if ( impl->fringing > 0 ) + impl->fringing *= fringing_max - fringing_mid; + impl->fringing = impl->fringing * fringing_mid + fringing_mid; + + init_ntsc_filters( impl, setup ); + + /* generate gamma table */ + if ( gamma_size > 1 ) + { + float const to_float = 1.0f / (gamma_size - (gamma_size > 1)); + float const gamma = 1.1333f - (float) setup->gamma * 0.5f; + /* match common PC's 2.2 gamma to TV's 2.65 gamma */ + int i; + for ( i = 0; i < gamma_size; i++ ) + impl->to_float [i] = + (float) pow( i * to_float, gamma ) * impl->contrast + impl->brightness; + } + + /* setup decoder matricies */ + { + float hue = (float) setup->hue * PI + PI / 180 * ext_decoder_hue; + float sat = (float) setup->saturation + 1; + float const* decoder = setup->decoder_matrix; + if ( !decoder ) + { + decoder = default_decoder; + hue += PI / 180 * (std_decoder_hue - ext_decoder_hue); + } + + { + float s = (float) sin( hue ) * sat; + float c = (float) cos( hue ) * sat; + float* out = impl->to_rgb; + int n; + + n = burst_count; + do + { + float const* in = decoder; + int n = 3; + do + { + float i = *in++; + float q = *in++; + *out++ = i * c - q * s; + *out++ = i * s + q * c; + } + while ( --n ); + if ( burst_count <= 1 ) + break; + ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */ + } + while ( --n ); + } + } +} + +/* kernel generation */ + +#define RGB_TO_YIQ( r, g, b, y, i ) (\ + (y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\ + (i = (r) * 0.596f - (g) * 0.275f - (b) * 0.321f),\ + ((r) * 0.212f - (g) * 0.523f + (b) * 0.311f)\ +) + +#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\ + r = (type) (y + to_rgb [0] * i + to_rgb [1] * q),\ + g = (type) (y + to_rgb [2] * i + to_rgb [3] * q),\ + (type) (y + to_rgb [4] * i + to_rgb [5] * q)\ +) + +#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1) + +enum { rgb_kernel_size = burst_size / alignment_count }; +enum { ntsc_rgb_bias = rgb_unit * 2 * ntsc_rgb_builder }; + +typedef struct pixel_info_t +{ + int offset; + float negate; + float kernel [4]; +} pixel_info_t; + +#if rescale_in > 1 + #define PIXEL_OFFSET_( ntsc, scaled ) \ + (kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \ + (kernel_size * 2 * scaled)) + + #define PIXEL_OFFSET( ntsc, scaled ) \ + PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\ + (((scaled) + rescale_out * 10) % rescale_out) ),\ + (1.0f - (((ntsc) + 100) & 2)) +#else + #define PIXEL_OFFSET( ntsc, scaled ) \ + (kernel_size / 2 + (ntsc) - (scaled)),\ + (1.0f - (((ntsc) + 100) & 2)) +#endif + +extern pixel_info_t const ntsc_pixels [alignment_count]; + +/* Generate pixel at all burst phases and column alignments */ +static void gen_kernel( ntsc_impl_t* impl, float y, float i, float q, ntsc_rgb_t* out ) +{ + /* generate for each scanline burst phase */ + float const* to_rgb = impl->to_rgb; + y -= rgb_offset; + do + { + /* Encode yiq into *two* composite signals (to allow control over artifacting). + Convolve these with kernels which: filter respective components, apply + sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack + into integer. Based on algorithm by NewRisingSun. */ + pixel_info_t const* pixel = ntsc_pixels; + do + { + /* negate is -1 when composite starts at odd multiple of 2 */ + float const yy = y * impl->fringing * pixel->negate; + float const ic0 = (i + yy) * pixel->kernel [0]; + float const qc1 = (q + yy) * pixel->kernel [1]; + float const ic2 = (i - yy) * pixel->kernel [2]; + float const qc3 = (q - yy) * pixel->kernel [3]; + + float const factor = impl->artifacts * pixel->negate; + float const ii = i * factor; + float const yc0 = (y + ii) * pixel->kernel [0]; + float const yc2 = (y - ii) * pixel->kernel [2]; + + float const qq = q * factor; + float const yc1 = (y + qq) * pixel->kernel [1]; + float const yc3 = (y - qq) * pixel->kernel [3]; + + float const* k = &impl->kernel [pixel->offset]; + int n; + for ( n = rgb_kernel_size; n; --n ) + { + float i = k[0]*ic0 + k[2]*ic2; + float q = k[1]*qc1 + k[3]*qc3; + float y = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 + + k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset; + if ( rescale_out <= 1 ) + k--; + else if ( k >= &impl->kernel [kernel_size * 2 * (rescale_out - 1)] ) + k -= kernel_size * 2 * (rescale_out - 1) + 2; + else + k += kernel_size * 2 - 1; + { + int r, g, b = YIQ_TO_RGB( y, i, q, to_rgb, int, r, g ); + *out++ = PACK_RGB( r, g, b ) - ntsc_rgb_bias; + } + } + } + while ( pixel++ < &ntsc_pixels [alignment_count - 1] ); + + if ( burst_count <= 1 ) + break; + + to_rgb += 6; + + ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */ + } + while ( to_rgb < &impl->to_rgb [burst_count * 6] ); +} + +/* only used by NES/SNES filters */ +static void merge_kernel_fields( ntsc_rgb_t* io ) +{ + if ( burst_count == 3 ) + { + int n; + for ( n = burst_size; n; --n ) + { + ntsc_rgb_t p0 = io [burst_size * 0] + ntsc_rgb_bias; + ntsc_rgb_t p1 = io [burst_size * 1] + ntsc_rgb_bias; + ntsc_rgb_t p2 = io [burst_size * 2] + ntsc_rgb_bias; + /* merge colors without losing precision */ + io [burst_size * 0] = + ((p0 + p1 - ((p0 ^ p1) & ntsc_rgb_builder)) >> 1) - ntsc_rgb_bias; + io [burst_size * 1] = + ((p1 + p2 - ((p1 ^ p2) & ntsc_rgb_builder)) >> 1) - ntsc_rgb_bias; + io [burst_size * 2] = + ((p2 + p0 - ((p2 ^ p0) & ntsc_rgb_builder)) >> 1) - ntsc_rgb_bias; + ++io; + } + } +} + +#if DISABLE_CORRECTION + #define CORRECT_ERROR( a ) { out [i] += ntsc_rgb_bias; } + #define DISTRIBUTE_ERROR( a, b, c ) { out [i] += ntsc_rgb_bias; } +#else + #define CORRECT_ERROR( a ) { out [a] += error; } + #define DISTRIBUTE_ERROR( a, b, c ) {\ + ntsc_rgb_t fourth = (error + 2 * ntsc_rgb_builder) >> 2;\ + fourth &= (ntsc_rgb_bias >> 1) - ntsc_rgb_builder;\ + fourth -= ntsc_rgb_bias >> 2;\ + out [a] += fourth;\ + out [b] += fourth;\ + out [c] += fourth;\ + out [i] += error - (fourth * 3);\ + } +#endif + +static void correct_errors( ntsc_rgb_t color, ntsc_rgb_t* out ); + +/* only used by palette-based filters (TI, VIC2) */ +#if NTSC_STANDARD_INIT +void NTSC_NAME( init )( NTSC_NAME( t )* ntsc, NTSC_NAME( setup_t ) const* setup ) +{ + ntsc_impl_t impl; + if ( !setup ) + setup = &NTSC_NAME( composite ); + init_ntsc_impl( &impl, setup ); + + { + int n = NTSC_NAME( palette_size ); + ntsc_rgb_t* kernel_out = (ntsc ? ntsc->table [0] : 0); + unsigned char* palette_out = setup->palette_out; + unsigned char const* palette = setup->palette; + if ( !palette ) + palette = default_palette [0]; + + do + { + float r = impl.to_float [*palette++]; + float g = impl.to_float [*palette++]; + float b = impl.to_float [*palette++]; + + float y, i, q = RGB_TO_YIQ( r, g, b, y, i ); + + { + int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g ); + ntsc_rgb_t rgb = PACK_RGB( r, g, b ); + + if ( palette_out ) + { + ntsc_rgb_t clamped = rgb; + NTSC_CLAMP( clamped ); + *palette_out++ = (unsigned char) (clamped >> 21); + *palette_out++ = (unsigned char) (clamped >> 11); + *palette_out++ = (unsigned char) (clamped >> 1); + } + + if ( kernel_out ) + { + gen_kernel( &impl, y, i, q, kernel_out ); + #if burst_count > 1 + if ( setup->merge_fields ) + merge_fields( kernel_out ); + #endif + correct_errors( rgb, kernel_out ); + kernel_out += burst_size; + } + } + } + while ( --n ); + } +} +#endif + +/* blitter related */ + +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#ifndef restrict + #define restrict +#endif + +#include + +#if UINT_MAX == 0xFFFFFFFF + typedef unsigned int ntsc_uint32_t; +#elif ULONG_MAX == 0xFFFFFFFF + typedef unsigned long ntsc_uint32_t; +#else + #error "Need 32-bit int type" +#endif + +#if USHRT_MAX == 0xFFFF + typedef unsigned short ntsc_uint16_t; +#else + #error "Need 16-bit int type" +#endif + diff --git a/quicknes/nes_emu/nes_util.cpp b/quicknes/nes_emu/nes_util.cpp new file mode 100644 index 0000000000..b6eeb5f548 --- /dev/null +++ b/quicknes/nes_emu/nes_util.cpp @@ -0,0 +1,211 @@ + +// Nes_Emu 0.7.0. http://www.slack.net/~ant/ + +#include "nes_util.h" + +#include "Nes_Cart.h" +#include "Nes_Emu.h" +#include +#include + +/* Copyright (C) 2004-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include "blargg_source.h" + +// Joypad_Filter + +Joypad_Filter::Joypad_Filter() +{ + prev = 0; + mask = ~0x50; + times [0] = 0; + times [1] = 0; + set_a_rate( 0.75 ); + set_b_rate( 0.75 ); +} + +void Joypad_Filter::enable_filtering( bool b ) +{ + bool enabled = (mask + 0x10) >> 5 & 1; + if ( enabled != b ) + mask = b ? ~0x50 : ~0; +} + +int Joypad_Filter::process( int joypad ) +{ + // prevent left+right and up+down (prefer most recent one pressed) + int changed = prev ^ joypad; + int hidden = joypad & ~mask; + prev = joypad; + + int const x_axis = 0xC0; + int const y_axis = 0x30; + + if ( changed & x_axis && hidden & x_axis ) + mask ^= x_axis; + + if ( changed & y_axis && hidden & y_axis ) + mask ^= y_axis; + + // reset turbo if button just pressed, to avoid delaying button press + if ( changed & 0x100 ) times [0] = 0; + if ( changed & 0x200 ) times [1] = 0; + mask |= changed & 0x300 & joypad; + + // mask and combine turbo bits + joypad &= mask; + return (joypad >> 8 & 3) | (joypad & ~0x300); +} + +void Joypad_Filter::clock_turbo() +{ + for ( int i = 0; i < 2; i++ ) + { + int t = times [i] + rates [i]; + mask ^= (t & 0x100) << i; + times [i] = t & 0xFF; + } +} + +// game_genie_patch_t + +blargg_err_t game_genie_patch_t::decode( const char* in ) +{ + int const code_len = 8; + unsigned char result [code_len] = { 0 }; + int in_len = strlen( in ); + if ( in_len != 6 && in_len != 8 ) + return "Game Genie code is wrong length"; + for ( int i = 0; i < code_len; i++ ) + { + char c = 'A'; + if ( i < in_len ) + c = toupper( in [i] ); + + static char const letters [17] = "AEPOZXLUGKISTVYN"; + char const* p = strchr( (char*) letters, c ); + if ( !p ) + return "Game Genie code had invalid character"; + int n = p - letters; + + result [i] |= n >> 1; + result [(i + 1) % code_len] |= (n << 3) & 0x0f; + } + + addr = result [3]<<12 | result [5]<<8 | result [2]<<4 | result [4]; + change_to = result [1]<<4 | result [0]; + compare_with = -1; + if ( addr & 0x8000 ) + compare_with = result [7]<<4 | result [6]; + addr |= 0x8000; + + return 0; +} + +int game_genie_patch_t::apply( Nes_Cart& cart ) const +{ + // determine bank size + long bank_size = 32 * 1024L; // mappers 0, 2, 3, 7, 11, 34, 71, 87 + switch ( cart.mapper_code() ) + { + case 1: // MMC1 + case 71: // Camerica + case 232: // Quattro + bank_size = 16 * 1024L; + break; + + case 4: // MMC3 + case 5: // MMC5 + case 24: // VRC6 + case 26: // VRC6 + case 69: // FME7 + bank_size = 8 * 1024L; + break; + } + + // patch each bank (not very good, since it might patch banks that never occupy + // that address) + int mask = (compare_with >= 0 ? ~0 : 0); + BOOST::uint8_t* p = cart.prg() + addr % bank_size; + int count = 0; + for ( int n = cart.prg_size() / bank_size; n--; p += bank_size ) + { + if ( !((*p ^ compare_with) & mask) ) + { + *p = change_to; + count++; + } + } + return count; +} + +// Cheat_Value_Finder + +Cheat_Value_Finder::Cheat_Value_Finder() +{ + emu = NULL; +} + +void Cheat_Value_Finder::start( Nes_Emu* new_emu ) +{ + emu = new_emu; + pos = 0; + memcpy( original, emu->low_mem(), low_mem_size ); + memset( changed, 0, low_mem_size ); +} + +void Cheat_Value_Finder::rescan() +{ + byte const* low_mem = emu->low_mem(); + for ( int i = 0; i < low_mem_size; i++ ) + changed [i] |= original [i] ^ low_mem [i]; + memcpy( original, emu->low_mem(), low_mem_size ); +} + +void Cheat_Value_Finder::search( int new_original, int new_changed ) +{ + require( new_original != new_changed ); + original_value = new_original; + changed_value = new_changed; + pos = -1; +} + +int Cheat_Value_Finder::next_match( int* addr ) +{ + byte const* low_mem = emu->low_mem(); + while ( ++pos < low_mem_size ) + { + if ( !changed [pos] ) + { + int old = (original [pos] - original_value) & 0xff; + int cur = (low_mem [pos] - changed_value) & 0xff; + + if ( old == cur ) + { + if ( addr ) + *addr = pos; + return (char) old; // sign-extend + } + } + } + + return no_match; +} + +int Cheat_Value_Finder::change_value( int new_value ) +{ + require( (unsigned) pos < low_mem_size ); + int result = emu->low_mem() [pos]; + emu->low_mem() [pos] = new_value; + return result; +} + diff --git a/quicknes/nes_emu/nes_util.h b/quicknes/nes_emu/nes_util.h new file mode 100644 index 0000000000..809a12f60a --- /dev/null +++ b/quicknes/nes_emu/nes_util.h @@ -0,0 +1,90 @@ + +// Experimental utilities for NES emulator + +// Nes_Emu 0.7.0 + +#ifndef NES_UTIL_H +#define NES_UTIL_H + +#include "blargg_common.h" +class Nes_Emu; +class Nes_Cart; + +class Joypad_Filter { +public: + Joypad_Filter(); + + // Control filtering of simultaneous directions. Enabled by default. + void enable_filtering( bool = true ); + + // Prevents simultaneous left+right and up+down to avoid problems in some games. + // Also turns bits 8 and 9 into turbo A and B. + int process( int joypad ); + + // Set A and B turbo rates, where 1.0 is maximum and 0.0 disables them + void set_a_rate( double r ) { rates [0] = (int) (r * 0x100); } + void set_b_rate( double r ) { rates [1] = (int) (r * 0x100); } + + // Call after each emulated frame for which Nes_Emu::frame().joypad_read_count + // is non-zero. + void clock_turbo(); + +private: + int prev; + int mask; + int times [2]; + int rates [2]; +}; + +struct game_genie_patch_t +{ + unsigned addr; // always 0x8000 or greater + int change_to; + int compare_with; // if -1, always change byte + + // Decode Game Genie code + blargg_err_t decode( const char* in ); + + // Apply patch to cartridge data. Might not work for some codes, since this really + // requires emulator support. Returns number of bytes changed, where 0 + // means patch wasn't for that cartridge. + int apply( Nes_Cart& ) const; +}; + +class Cheat_Value_Finder { +public: + Cheat_Value_Finder(); + + // Start scanning emulator's memory for values that are constantly changing. + void start( Nes_Emu* ); + + // Rescan memory and eliminate any changed bytes from later matching. + // Should be called many times after begin_scan() and before begin_matching(). + void rescan(); + + // Start search for any bytes which changed by difference between original and + // changed values. + void search( int original, int changed ); + + // Get next match and return its delta from changed value (closer to 0 + // is more likely to be a match), or no_match if there are no more matches. + // Optionally returns address of matched byte. + enum { no_match = 0x100 }; + int next_match( int* addr = NULL ); + + // Change current match to new value. Returns previous value. + int change_value( int new_value ); + +private: + typedef BOOST::uint8_t byte; + Nes_Emu* emu; + int original_value; + int changed_value; + int pos; + enum { low_mem_size = 0x800 }; + byte original [low_mem_size]; + byte changed [low_mem_size]; +}; + +#endif + diff --git a/quicknes/nes_emu/optional_mappers.cpp b/quicknes/nes_emu/optional_mappers.cpp new file mode 100644 index 0000000000..1171937d2b --- /dev/null +++ b/quicknes/nes_emu/optional_mappers.cpp @@ -0,0 +1,194 @@ + +// Optional less-common simple mappers + +// Nes_Emu 0.5.6. http://www.slack.net/~ant/ + +#include "Nes_Mapper.h" + +/* Copyright (C) 2004-2005 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +#include BLARGG_SOURCE_BEGIN + +// Nina-1 (Deadly Towers only) + +class Mapper_Nina1 : public Nes_Mapper { + byte bank; +public: + Mapper_Nina1() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + write( 0, 0, bank ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + bank = data; + set_prg_bank( 0x8000, bank_32k, bank ); + } +}; + +// GNROM + +class Mapper_Gnrom : public Nes_Mapper { + byte bank; +public: + Mapper_Gnrom() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; + write( 0, 0, b ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + int changed = bank ^ data; + bank = data; + + if ( changed & 0x30 ) + set_prg_bank( 0x8000, bank_32k, bank >> 4 & 3 ); + + if ( changed & 0x03 ) + set_chr_bank( 0, bank_8k, bank & 3 ); + } +}; + +// Color Dreams + +class Mapper_Color_Dreams : public Nes_Mapper { + byte bank; +public: + Mapper_Color_Dreams() + { + register_state( &bank, 1 ); + } + + virtual void apply_mapping() + { + int b = bank; + bank = ~b; + write( 0, 0, b ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + int changed = bank ^ data; + bank = data; + + if ( changed & 0x0f ) + set_prg_bank( 0x8000, bank_32k, bank & 0x0f ); + + if ( changed & 0xf0 ) + set_chr_bank( 0, bank_8k, bank >> 4 ); + } +}; + +// Camerica + +class Mapper_Camerica : public Nes_Mapper { + byte regs [3]; +public: + Mapper_Camerica() + { + register_state( regs, sizeof regs ); + } + + virtual void apply_mapping() + { + write( 0, 0xc000, regs [0] ); + if ( regs [1] & 0x80 ) + write( 0, 0x9000, regs [1] ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr >= 0xc000 ) + { + regs [0] = data; + set_prg_bank( 0x8000, bank_16k, data ); + } + else if ( (addr & 0xf000) == 0x9000 ) + { + regs [1] = 0x80 | data; + mirror_single( (data >> 4) & 1 ); + } + } +}; + +// Quattro + +class Mapper_Quattro : public Nes_Mapper { + byte regs [2]; +public: + Mapper_Quattro() + { + register_state( regs, sizeof regs ); + } + + virtual void reset_state() + { + regs [0] = 0; + regs [1] = 3; + } + + virtual void apply_mapping() + { + int bank = regs [0] >> 1 & 0x0c; + set_prg_bank( 0x8000, bank_16k, bank + (regs [1] & 3) ); + set_prg_bank( 0xC000, bank_16k, bank + 3 ); + } + + virtual void write( nes_time_t, nes_addr_t addr, int data ) + { + if ( addr < 0xc000 ) + regs [0] = data; + else + regs [1] = data; + Mapper_Quattro::apply_mapping(); + } +}; + +void register_misc_mappers(); +void register_misc_mappers() +{ + register_mapper( 11 ); + register_mapper( 34 ); + register_mapper( 66 ); + register_mapper( 71 ); + register_mapper( 232 ); +} + +void Nes_Mapper::register_optional_mappers() +{ + register_misc_mappers(); + + extern void register_vrc6_mapper(); + register_vrc6_mapper(); + + extern void register_mmc5_mapper(); + register_mmc5_mapper(); + + extern void register_fme07_mapper(); + register_fme07_mapper(); + + extern void register_namco106_mapper(); + register_namco106_mapper(); +} +