summaryrefslogtreecommitdiff
path: root/plugins/gme/game-music-emu-0.6pre/gme/Nsf_Impl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/gme/game-music-emu-0.6pre/gme/Nsf_Impl.cpp')
-rw-r--r--plugins/gme/game-music-emu-0.6pre/gme/Nsf_Impl.cpp327
1 files changed, 327 insertions, 0 deletions
diff --git a/plugins/gme/game-music-emu-0.6pre/gme/Nsf_Impl.cpp b/plugins/gme/game-music-emu-0.6pre/gme/Nsf_Impl.cpp
new file mode 100644
index 00000000..53b3a416
--- /dev/null
+++ b/plugins/gme/game-music-emu-0.6pre/gme/Nsf_Impl.cpp
@@ -0,0 +1,327 @@
+// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/
+
+#include "Nsf_Impl.h"
+
+#include "blargg_endian.h"
+
+/* Copyright (C) 2003-2008 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"
+
+// number of frames until play interrupts init
+int const initial_play_delay = 7; // KikiKaikai needed this to work
+int const bank_size = 0x1000;
+int const rom_addr = 0x8000;
+
+int Nsf_Impl::read_code( addr_t addr ) const
+{
+ return *cpu.get_code( addr );
+}
+
+int Nsf_Impl::pcm_read( void* self, int addr )
+{
+ return STATIC_CAST(Nsf_Impl*,self)->read_code( addr );
+}
+
+Nsf_Impl::Nsf_Impl() : rom( bank_size )
+{
+ apu.dmc_reader( pcm_read, this );
+ assert( offsetof (header_t,unused [4]) == header_t::size );
+}
+
+void Nsf_Impl::unload()
+{
+ rom.clear();
+ high_ram.clear();
+ Gme_Loader::unload();
+}
+
+Nsf_Impl::~Nsf_Impl() { unload(); }
+
+bool nsf_header_t::valid_tag() const
+{
+ return 0 == memcmp( tag, "NESM\x1A", 5 );
+}
+
+double nsf_header_t::clock_rate() const
+{
+ return pal_only() ? 1662607.125 : 1789772.727272727;
+}
+
+int nsf_header_t::play_period() const
+{
+ // NTSC
+ int clocks = 29780;
+ int value = 0x411A;
+ byte const* rate_ptr = ntsc_speed;
+
+ // PAL
+ if ( pal_only() )
+ {
+ clocks = 33247;
+ value = 0x4E20;
+ rate_ptr = pal_speed;
+ }
+
+ // Default rate
+ int rate = get_le16( rate_ptr );
+ if ( rate == 0 )
+ rate = value;
+
+ // Custom rate
+ if ( rate != value )
+ clocks = (int) (rate * clock_rate() * (1.0/1000000.0));
+
+ return clocks;
+}
+
+// Gets address, given pointer to it in file header. If zero, returns rom_addr.
+Nsf_Impl::addr_t Nsf_Impl::get_addr( byte const in [] )
+{
+ addr_t addr = get_le16( in );
+ if ( addr == 0 )
+ addr = rom_addr;
+ return addr;
+}
+
+blargg_err_t Nsf_Impl::load_( Data_Reader& in )
+{
+ // pad ROM data with 0
+ RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) );
+
+ if ( !header_.valid_tag() )
+ return blargg_err_file_type;
+
+ RETURN_ERR( high_ram.resize( (fds_enabled() ? fdsram_offset + fdsram_size : fdsram_offset) ) );
+
+ addr_t load_addr = get_addr( header_.load_addr );
+ if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) )
+ set_warning( "Load address is too low" );
+
+ rom.set_addr( load_addr % bank_size );
+
+ if ( header_.vers != 1 )
+ set_warning( "Unknown file version" );
+
+ set_play_period( header_.play_period() );
+
+ return blargg_ok;
+}
+
+void Nsf_Impl::write_bank( int bank, int data )
+{
+ // Find bank in ROM
+ int offset = rom.mask_addr( data * bank_size );
+ if ( offset >= rom.size() )
+ special_event( "invalid bank" );
+ void const* rom_data = rom.at_addr( offset );
+
+ #if !NSF_EMU_APU_ONLY
+ if ( bank < bank_count - fds_banks && fds_enabled() )
+ {
+ // TODO: FDS bank switching is kind of hacky, might need to
+ // treat ROM as RAM so changes won't get lost when switching.
+ byte* out = sram();
+ if ( bank >= fds_banks )
+ {
+ out = fdsram();
+ bank -= fds_banks;
+ }
+ memcpy( &out [bank * bank_size], rom_data, bank_size );
+ return;
+ }
+ #endif
+
+ if ( bank >= fds_banks )
+ cpu.map_code( (bank + 6) * bank_size, bank_size, rom_data );
+}
+
+void Nsf_Impl::map_memory()
+{
+ // Map standard things
+ cpu.reset( unmapped_code() );
+ cpu.map_code( 0, 0x2000, low_ram, low_ram_size ); // mirrored four times
+ cpu.map_code( sram_addr, sram_size, sram() );
+
+ // Determine initial banks
+ byte banks [bank_count];
+ static byte const zero_banks [sizeof header_.banks] = { 0 };
+ if ( memcmp( header_.banks, zero_banks, sizeof zero_banks ) )
+ {
+ banks [0] = header_.banks [6];
+ banks [1] = header_.banks [7];
+ memcpy( banks + fds_banks, header_.banks, sizeof header_.banks );
+ }
+ else
+ {
+ // No initial banks, so assign them based on load_addr
+ int first_bank = (get_addr( header_.load_addr ) - sram_addr) / bank_size;
+ unsigned total_banks = rom.size() / bank_size;
+ for ( int i = bank_count; --i >= 0; )
+ {
+ int bank = i - first_bank;
+ if ( (unsigned) bank >= total_banks )
+ bank = 0;
+ banks [i] = bank;
+ }
+ }
+
+ // Map banks
+ for ( int i = (fds_enabled() ? 0 : fds_banks); i < bank_count; ++i )
+ write_bank( i, banks [i] );
+
+ // Map FDS RAM
+ if ( fds_enabled() )
+ cpu.map_code( rom_addr, fdsram_size, fdsram() );
+}
+
+inline void Nsf_Impl::push_byte( int b )
+{
+ low_ram [0x100 + cpu.r.sp--] = b;
+}
+
+// Jumps to routine, given pointer to address in file header. Pushes idle_addr
+// as return address, NOT old PC.
+void Nsf_Impl::jsr_then_stop( byte const addr [] )
+{
+ cpu.r.pc = get_addr( addr );
+ push_byte( (idle_addr - 1) >> 8 );
+ push_byte( (idle_addr - 1) );
+}
+
+blargg_err_t Nsf_Impl::start_track( int track )
+{
+ int speed_flags = 0;
+ #if NSF_EMU_EXTRA_FLAGS
+ speed_flags = header().speed_flags;
+ #endif
+
+ apu.reset( header().pal_only(), (speed_flags & 0x20) ? 0x3F : 0 );
+ apu.write_register( 0, 0x4015, 0x0F );
+ apu.write_register( 0, 0x4017, (speed_flags & 0x10) ? 0x80 : 0 );
+
+ // Clear memory
+ memset( unmapped_code(), Nes_Cpu::halt_opcode, unmapped_size );
+ memset( low_ram, 0, low_ram_size );
+ memset( sram(), 0, sram_size );
+
+ map_memory();
+
+ // Arrange time of first call to play routine
+ play_extra = 0;
+ next_play = play_period;
+
+ play_delay = initial_play_delay;
+ saved_state.pc = idle_addr;
+
+ // Setup for call to init routine
+ cpu.r.a = track;
+ cpu.r.x = header_.pal_only();
+ cpu.r.sp = 0xFF;
+ jsr_then_stop( header_.init_addr );
+ if ( cpu.r.pc < get_addr( header_.load_addr ) )
+ set_warning( "Init address < load address" );
+
+ return blargg_ok;
+}
+
+void Nsf_Impl::unmapped_write( addr_t addr, int data )
+{
+ dprintf( "Unmapped write $%04X <- %02X\n", (int) addr, data );
+}
+
+int Nsf_Impl::unmapped_read( addr_t addr )
+{
+ dprintf( "Unmapped read $%04X\n", (int) addr );
+ return addr >> 8;
+}
+
+void Nsf_Impl::special_event( const char str [] )
+{
+ dprintf( "%s\n", str );
+}
+
+void Nsf_Impl::run_once( time_t end )
+{
+ // Emulate until next play call if possible
+ if ( run_cpu_until( min( next_play, end ) ) )
+ {
+ // Halt instruction encountered
+
+ if ( cpu.r.pc != idle_addr )
+ {
+ special_event( "illegal instruction" );
+ cpu.count_error();
+ cpu.set_time( cpu.end_time() );
+ return;
+ }
+
+ // Init/play routine returned
+ play_delay = 1; // play can now be called regularly
+
+ if ( saved_state.pc == idle_addr )
+ {
+ // nothing to run
+ time_t t = cpu.end_time();
+ if ( cpu.time() < t )
+ cpu.set_time( t );
+ }
+ else
+ {
+ // continue init routine that was interrupted by play routine
+ cpu.r = saved_state;
+ saved_state.pc = idle_addr;
+ }
+ }
+
+ if ( time() >= next_play )
+ {
+ // Calculate time of next call to play routine
+ play_extra ^= 1; // extra clock every other call
+ next_play += play_period + play_extra;
+
+ // Call routine if ready
+ if ( play_delay && !--play_delay )
+ {
+ // Save state if init routine is still running
+ if ( cpu.r.pc != idle_addr )
+ {
+ check( saved_state.pc == idle_addr );
+ saved_state = cpu.r;
+ special_event( "play called during init" );
+ }
+
+ jsr_then_stop( header_.play_addr );
+ }
+ }
+}
+
+void Nsf_Impl::run_until( time_t end )
+{
+ while ( time() < end )
+ run_once( end );
+}
+
+void Nsf_Impl::end_frame( time_t end )
+{
+ if ( time() < end )
+ run_until( end );
+ cpu.adjust_time( -end );
+
+ // Localize to new time frame
+ next_play -= end;
+ check( next_play >= 0 );
+ if ( next_play < 0 )
+ next_play = 0;
+
+ apu.end_frame( end );
+}