Adding smb3 for mapper 37 (4 nametable bank) testing
This commit is contained in:
parent
ea4c425e47
commit
17bb8c99d8
|
@ -1,648 +0,0 @@
|
|||
|
||||
// Nes_Emu 0.7.0. http://www.slack.net/~ant/
|
||||
|
||||
#include "Nes_Core.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#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"
|
||||
|
||||
/*
|
||||
New mapping distribution by Sergio Martin (eien86)
|
||||
https://github.com/SergioMartin86/jaffarPlus
|
||||
*/
|
||||
#include "mappers/mapper000.hpp"
|
||||
#include "mappers/mapper001.hpp"
|
||||
#include "mappers/mapper002.hpp"
|
||||
#include "mappers/mapper003.hpp"
|
||||
#include "mappers/mapper004.hpp"
|
||||
#include "mappers/mapper005.hpp"
|
||||
#include "mappers/mapper007.hpp"
|
||||
#include "mappers/mapper009.hpp"
|
||||
#include "mappers/mapper010.hpp"
|
||||
#include "mappers/mapper011.hpp"
|
||||
#include "mappers/mapper015.hpp"
|
||||
#include "mappers/mapper019.hpp"
|
||||
#include "mappers/mapper021.hpp"
|
||||
#include "mappers/mapper022.hpp"
|
||||
#include "mappers/mapper023.hpp"
|
||||
#include "mappers/mapper024.hpp"
|
||||
#include "mappers/mapper025.hpp"
|
||||
#include "mappers/mapper026.hpp"
|
||||
#include "mappers/mapper030.hpp"
|
||||
#include "mappers/mapper032.hpp"
|
||||
#include "mappers/mapper033.hpp"
|
||||
#include "mappers/mapper034.hpp"
|
||||
#include "mappers/mapper060.hpp"
|
||||
#include "mappers/mapper066.hpp"
|
||||
#include "mappers/mapper069.hpp"
|
||||
#include "mappers/mapper070.hpp"
|
||||
#include "mappers/mapper071.hpp"
|
||||
#include "mappers/mapper073.hpp"
|
||||
#include "mappers/mapper075.hpp"
|
||||
#include "mappers/mapper078.hpp"
|
||||
#include "mappers/mapper079.hpp"
|
||||
#include "mappers/mapper085.hpp"
|
||||
#include "mappers/mapper086.hpp"
|
||||
#include "mappers/mapper087.hpp"
|
||||
#include "mappers/mapper088.hpp"
|
||||
#include "mappers/mapper089.hpp"
|
||||
#include "mappers/mapper093.hpp"
|
||||
#include "mappers/mapper094.hpp"
|
||||
#include "mappers/mapper097.hpp"
|
||||
#include "mappers/mapper113.hpp"
|
||||
#include "mappers/mapper140.hpp"
|
||||
#include "mappers/mapper152.hpp"
|
||||
#include "mappers/mapper154.hpp"
|
||||
#include "mappers/mapper156.hpp"
|
||||
#include "mappers/mapper180.hpp"
|
||||
#include "mappers/mapper184.hpp"
|
||||
#include "mappers/mapper190.hpp"
|
||||
#include "mappers/mapper193.hpp"
|
||||
#include "mappers/mapper206.hpp"
|
||||
#include "mappers/mapper207.hpp"
|
||||
#include "mappers/mapper232.hpp"
|
||||
#include "mappers/mapper240.hpp"
|
||||
#include "mappers/mapper241.hpp"
|
||||
#include "mappers/mapper244.hpp"
|
||||
#include "mappers/mapper246.hpp"
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
const char * Nes_Core::init()
|
||||
{
|
||||
if ( !impl )
|
||||
{
|
||||
CHECK_ALLOC( impl = new impl_t );
|
||||
impl->apu.dmc_reader( read_dmc, this );
|
||||
impl->apu.irq_notifier( apu_irq_changed, this );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Nes_Core::close()
|
||||
{
|
||||
cart = NULL;
|
||||
delete mapper;
|
||||
mapper = NULL;
|
||||
|
||||
ppu.close_chr();
|
||||
|
||||
disable_rendering();
|
||||
}
|
||||
|
||||
const char * Nes_Core::open( Nes_Cart const* new_cart )
|
||||
{
|
||||
close();
|
||||
RETURN_ERR( init() );
|
||||
|
||||
// Getting cartdrige mapper code
|
||||
auto mapperCode = new_cart->mapper_code();
|
||||
|
||||
// Now checking if the detected mapper code is supported
|
||||
if (mapperCode == 0) mapper = new Mapper000();
|
||||
if (mapperCode == 1) mapper = new Mapper001();
|
||||
if (mapperCode == 2) mapper = new Mapper002();
|
||||
if (mapperCode == 3) mapper = new Mapper003();
|
||||
if (mapperCode == 4) mapper = new Mapper004();
|
||||
if (mapperCode == 5) mapper = new Mapper005();
|
||||
if (mapperCode == 7) mapper = new Mapper007();
|
||||
if (mapperCode == 9) mapper = new Mapper009();
|
||||
if (mapperCode == 10) mapper = new Mapper010();
|
||||
if (mapperCode == 11) mapper = new Mapper011();
|
||||
if (mapperCode == 15) mapper = new Mapper015();
|
||||
if (mapperCode == 19) mapper = new Mapper019();
|
||||
if (mapperCode == 21) mapper = new Mapper021();
|
||||
if (mapperCode == 22) mapper = new Mapper022();
|
||||
if (mapperCode == 23) mapper = new Mapper023();
|
||||
if (mapperCode == 24) mapper = new Mapper024();
|
||||
if (mapperCode == 25) mapper = new Mapper025();
|
||||
if (mapperCode == 26) mapper = new Mapper026();
|
||||
if (mapperCode == 30) mapper = new Mapper030();
|
||||
if (mapperCode == 32) mapper = new Mapper032();
|
||||
if (mapperCode == 33) mapper = new Mapper033();
|
||||
if (mapperCode == 34) mapper = new Mapper034();
|
||||
if (mapperCode == 60) mapper = new Mapper060();
|
||||
if (mapperCode == 66) mapper = new Mapper066();
|
||||
if (mapperCode == 69) mapper = new Mapper069();
|
||||
if (mapperCode == 70) mapper = new Mapper070();
|
||||
if (mapperCode == 71) mapper = new Mapper071();
|
||||
if (mapperCode == 73) mapper = new Mapper073();
|
||||
if (mapperCode == 75) mapper = new Mapper075();
|
||||
if (mapperCode == 78) mapper = new Mapper078();
|
||||
if (mapperCode == 79) mapper = new Mapper079();
|
||||
if (mapperCode == 85) mapper = new Mapper085();
|
||||
if (mapperCode == 86) mapper = new Mapper086();
|
||||
if (mapperCode == 87) mapper = new Mapper087();
|
||||
if (mapperCode == 88) mapper = new Mapper088();
|
||||
if (mapperCode == 89) mapper = new Mapper089();
|
||||
if (mapperCode == 93) mapper = new Mapper093();
|
||||
if (mapperCode == 94) mapper = new Mapper094();
|
||||
if (mapperCode == 97) mapper = new Mapper097();
|
||||
if (mapperCode == 113) mapper = new Mapper113();
|
||||
if (mapperCode == 140) mapper = new Mapper140();
|
||||
if (mapperCode == 152) mapper = new Mapper152();
|
||||
if (mapperCode == 154) mapper = new Mapper154();
|
||||
if (mapperCode == 156) mapper = new Mapper156();
|
||||
if (mapperCode == 180) mapper = new Mapper180();
|
||||
if (mapperCode == 184) mapper = new Mapper184();
|
||||
if (mapperCode == 190) mapper = new Mapper190();
|
||||
if (mapperCode == 193) mapper = new Mapper193();
|
||||
if (mapperCode == 206) mapper = new Mapper206();
|
||||
if (mapperCode == 207) mapper = new Mapper207();
|
||||
if (mapperCode == 232) mapper = new Mapper232();
|
||||
if (mapperCode == 240) mapper = new Mapper240();
|
||||
if (mapperCode == 241) mapper = new Mapper241();
|
||||
if (mapperCode == 244) mapper = new Mapper244();
|
||||
if (mapperCode == 246) mapper = new Mapper246();
|
||||
|
||||
// If no mapper was found, return null (error) now
|
||||
if (mapper == NULL)
|
||||
{
|
||||
fprintf(stderr, "Could not find mapper for code: %u\n", mapperCode);
|
||||
return unsupported_mapper;
|
||||
}
|
||||
|
||||
// Assigning backwards pointers to cartdrige and emulator now
|
||||
mapper->cart_ = new_cart;
|
||||
mapper->emu_ = this;
|
||||
|
||||
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<Nes_State_*>(out) );
|
||||
}
|
||||
|
||||
void Nes_Core::load_state( Nes_State_ const& in )
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
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.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;
|
||||
}
|
||||
}
|
||||
|
||||
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() );
|
||||
|
||||
return addr >> 8; // simulate open bus
|
||||
}
|
||||
|
||||
// CPU
|
||||
|
||||
const int irq_inhibit_mask = 0x04;
|
||||
|
||||
nes_addr_t Nes_Core::read_vector( nes_addr_t addr )
|
||||
{
|
||||
uint8_t const* p = cpu::get_code( addr );
|
||||
return p [1] * 0x100 + p [0];
|
||||
}
|
||||
|
||||
void Nes_Core::reset( bool full_reset, bool erase_battery_ram )
|
||||
{
|
||||
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 = true;
|
||||
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) )
|
||||
{
|
||||
/* vectored NMI at end of frame */
|
||||
vector_interrupt( 0xFFFA );
|
||||
present += 7;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
if ( extra_instructions > 2 )
|
||||
{
|
||||
return present;
|
||||
}
|
||||
|
||||
if ( last_result != cpu::result_cli && last_result != cpu::result_sei &&
|
||||
(ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002)) )
|
||||
return present;
|
||||
|
||||
/* Executing extra instructions for frame */
|
||||
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 )
|
||||
{
|
||||
/* IRQ vectored */
|
||||
mapper->run_until( present );
|
||||
vector_interrupt( 0xFFFE );
|
||||
}
|
||||
else
|
||||
{
|
||||
// CLI delays IRQ
|
||||
cpu_set_irq_time( present + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
// 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(int joypad1, int joypad2)
|
||||
{
|
||||
current_joypad [0] = (joypad1 |= ~0xFF);
|
||||
current_joypad [1] = (joypad2 |= ~0xFF);
|
||||
|
||||
cpu_time_offset = ppu.begin_frame( nes.timestamp ) - 1;
|
||||
ppu_2002_time = 0;
|
||||
clock_ = cpu_time_offset;
|
||||
|
||||
// TODO: clean this fucking mess up
|
||||
auto t0 = emulate_frame_();
|
||||
impl->apu.run_until_( t0 );
|
||||
clock_ = cpu_time_offset;
|
||||
auto t1 = cpu_time();
|
||||
impl->apu.run_until_( t1 );
|
||||
|
||||
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 );
|
||||
|
||||
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 )
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -2,6 +2,17 @@
|
|||
|
||||
// Internal NES emulator
|
||||
|
||||
/* 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 */
|
||||
|
||||
// Nes_Emu 0.7.0
|
||||
|
||||
|
||||
|
@ -10,27 +21,387 @@
|
|||
#include "Nes_Cpu.h"
|
||||
#include "Nes_Ppu.h"
|
||||
#include "Nes_Mapper.h"
|
||||
#include "Nes_State.h"
|
||||
#include <cstdio>
|
||||
|
||||
/*
|
||||
New mapping distribution by Sergio Martin (eien86)
|
||||
https://github.com/SergioMartin86/jaffarPlus
|
||||
*/
|
||||
#include "mappers/mapper000.hpp"
|
||||
#include "mappers/mapper001.hpp"
|
||||
#include "mappers/mapper002.hpp"
|
||||
#include "mappers/mapper003.hpp"
|
||||
#include "mappers/mapper004.hpp"
|
||||
#include "mappers/mapper005.hpp"
|
||||
#include "mappers/mapper007.hpp"
|
||||
#include "mappers/mapper009.hpp"
|
||||
#include "mappers/mapper010.hpp"
|
||||
#include "mappers/mapper011.hpp"
|
||||
#include "mappers/mapper015.hpp"
|
||||
#include "mappers/mapper019.hpp"
|
||||
#include "mappers/mapper021.hpp"
|
||||
#include "mappers/mapper022.hpp"
|
||||
#include "mappers/mapper023.hpp"
|
||||
#include "mappers/mapper024.hpp"
|
||||
#include "mappers/mapper025.hpp"
|
||||
#include "mappers/mapper026.hpp"
|
||||
#include "mappers/mapper030.hpp"
|
||||
#include "mappers/mapper032.hpp"
|
||||
#include "mappers/mapper033.hpp"
|
||||
#include "mappers/mapper034.hpp"
|
||||
#include "mappers/mapper060.hpp"
|
||||
#include "mappers/mapper066.hpp"
|
||||
#include "mappers/mapper069.hpp"
|
||||
#include "mappers/mapper070.hpp"
|
||||
#include "mappers/mapper071.hpp"
|
||||
#include "mappers/mapper073.hpp"
|
||||
#include "mappers/mapper075.hpp"
|
||||
#include "mappers/mapper078.hpp"
|
||||
#include "mappers/mapper079.hpp"
|
||||
#include "mappers/mapper085.hpp"
|
||||
#include "mappers/mapper086.hpp"
|
||||
#include "mappers/mapper087.hpp"
|
||||
#include "mappers/mapper088.hpp"
|
||||
#include "mappers/mapper089.hpp"
|
||||
#include "mappers/mapper093.hpp"
|
||||
#include "mappers/mapper094.hpp"
|
||||
#include "mappers/mapper097.hpp"
|
||||
#include "mappers/mapper113.hpp"
|
||||
#include "mappers/mapper140.hpp"
|
||||
#include "mappers/mapper152.hpp"
|
||||
#include "mappers/mapper154.hpp"
|
||||
#include "mappers/mapper156.hpp"
|
||||
#include "mappers/mapper180.hpp"
|
||||
#include "mappers/mapper184.hpp"
|
||||
#include "mappers/mapper190.hpp"
|
||||
#include "mappers/mapper193.hpp"
|
||||
#include "mappers/mapper206.hpp"
|
||||
#include "mappers/mapper207.hpp"
|
||||
#include "mappers/mapper232.hpp"
|
||||
#include "mappers/mapper240.hpp"
|
||||
#include "mappers/mapper241.hpp"
|
||||
#include "mappers/mapper244.hpp"
|
||||
#include "mappers/mapper246.hpp"
|
||||
|
||||
class Nes_Cart;
|
||||
class Nes_State;
|
||||
|
||||
#undef NES_EMU_CPU_HOOK
|
||||
#ifndef NES_EMU_CPU_HOOK
|
||||
#define NES_EMU_CPU_HOOK( cpu, end_time ) cpu::run( end_time )
|
||||
#endif
|
||||
|
||||
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;
|
||||
const int irq_inhibit_mask = 0x04;
|
||||
|
||||
class Nes_Core : private Nes_Cpu {
|
||||
typedef Nes_Cpu cpu;
|
||||
public:
|
||||
Nes_Core();
|
||||
~Nes_Core();
|
||||
|
||||
const char * init();
|
||||
const char * open( Nes_Cart const* );
|
||||
void reset( bool full_reset = true, bool erase_battery_ram = false );
|
||||
blip_time_t emulate_frame(int joypad1, int joypad2);
|
||||
void close();
|
||||
Nes_Core() : ppu( this )
|
||||
{
|
||||
cart = NULL;
|
||||
impl = NULL;
|
||||
mapper = NULL;
|
||||
memset( &nes, 0, sizeof nes );
|
||||
memset( &joypad, 0, sizeof joypad );
|
||||
}
|
||||
|
||||
|
||||
void save_state( Nes_State* ) const;
|
||||
void save_state( Nes_State_* ) const;
|
||||
void load_state( Nes_State_ const& );
|
||||
~Nes_Core()
|
||||
{
|
||||
close();
|
||||
delete impl;
|
||||
}
|
||||
|
||||
void irq_changed();
|
||||
void event_changed();
|
||||
const char * init()
|
||||
{
|
||||
if ( !impl )
|
||||
{
|
||||
CHECK_ALLOC( impl = new impl_t );
|
||||
impl->apu.dmc_reader( read_dmc, this );
|
||||
impl->apu.irq_notifier( apu_irq_changed, this );
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char * open( Nes_Cart const* new_cart )
|
||||
{
|
||||
close();
|
||||
RETURN_ERR( init() );
|
||||
|
||||
// Getting cartdrige mapper code
|
||||
auto mapperCode = new_cart->mapper_code();
|
||||
|
||||
// Now checking if the detected mapper code is supported
|
||||
if (mapperCode == 0) mapper = new Mapper000();
|
||||
if (mapperCode == 1) mapper = new Mapper001();
|
||||
if (mapperCode == 2) mapper = new Mapper002();
|
||||
if (mapperCode == 3) mapper = new Mapper003();
|
||||
if (mapperCode == 4) mapper = new Mapper004();
|
||||
if (mapperCode == 5) mapper = new Mapper005();
|
||||
if (mapperCode == 7) mapper = new Mapper007();
|
||||
if (mapperCode == 9) mapper = new Mapper009();
|
||||
if (mapperCode == 10) mapper = new Mapper010();
|
||||
if (mapperCode == 11) mapper = new Mapper011();
|
||||
if (mapperCode == 15) mapper = new Mapper015();
|
||||
if (mapperCode == 19) mapper = new Mapper019();
|
||||
if (mapperCode == 21) mapper = new Mapper021();
|
||||
if (mapperCode == 22) mapper = new Mapper022();
|
||||
if (mapperCode == 23) mapper = new Mapper023();
|
||||
if (mapperCode == 24) mapper = new Mapper024();
|
||||
if (mapperCode == 25) mapper = new Mapper025();
|
||||
if (mapperCode == 26) mapper = new Mapper026();
|
||||
if (mapperCode == 30) mapper = new Mapper030();
|
||||
if (mapperCode == 32) mapper = new Mapper032();
|
||||
if (mapperCode == 33) mapper = new Mapper033();
|
||||
if (mapperCode == 34) mapper = new Mapper034();
|
||||
if (mapperCode == 60) mapper = new Mapper060();
|
||||
if (mapperCode == 66) mapper = new Mapper066();
|
||||
if (mapperCode == 69) mapper = new Mapper069();
|
||||
if (mapperCode == 70) mapper = new Mapper070();
|
||||
if (mapperCode == 71) mapper = new Mapper071();
|
||||
if (mapperCode == 73) mapper = new Mapper073();
|
||||
if (mapperCode == 75) mapper = new Mapper075();
|
||||
if (mapperCode == 78) mapper = new Mapper078();
|
||||
if (mapperCode == 79) mapper = new Mapper079();
|
||||
if (mapperCode == 85) mapper = new Mapper085();
|
||||
if (mapperCode == 86) mapper = new Mapper086();
|
||||
if (mapperCode == 87) mapper = new Mapper087();
|
||||
if (mapperCode == 88) mapper = new Mapper088();
|
||||
if (mapperCode == 89) mapper = new Mapper089();
|
||||
if (mapperCode == 93) mapper = new Mapper093();
|
||||
if (mapperCode == 94) mapper = new Mapper094();
|
||||
if (mapperCode == 97) mapper = new Mapper097();
|
||||
if (mapperCode == 113) mapper = new Mapper113();
|
||||
if (mapperCode == 140) mapper = new Mapper140();
|
||||
if (mapperCode == 152) mapper = new Mapper152();
|
||||
if (mapperCode == 154) mapper = new Mapper154();
|
||||
if (mapperCode == 156) mapper = new Mapper156();
|
||||
if (mapperCode == 180) mapper = new Mapper180();
|
||||
if (mapperCode == 184) mapper = new Mapper184();
|
||||
if (mapperCode == 190) mapper = new Mapper190();
|
||||
if (mapperCode == 193) mapper = new Mapper193();
|
||||
if (mapperCode == 206) mapper = new Mapper206();
|
||||
if (mapperCode == 207) mapper = new Mapper207();
|
||||
if (mapperCode == 232) mapper = new Mapper232();
|
||||
if (mapperCode == 240) mapper = new Mapper240();
|
||||
if (mapperCode == 241) mapper = new Mapper241();
|
||||
if (mapperCode == 244) mapper = new Mapper244();
|
||||
if (mapperCode == 246) mapper = new Mapper246();
|
||||
|
||||
// If no mapper was found, return null (error) now
|
||||
if (mapper == NULL)
|
||||
{
|
||||
fprintf(stderr, "Could not find mapper for code: %u\n", mapperCode);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Assigning backwards pointers to cartdrige and emulator now
|
||||
mapper->cart_ = new_cart;
|
||||
mapper->emu_ = this;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
size_t getLightweightStateSize()
|
||||
{
|
||||
size_t size = 0;
|
||||
return size;
|
||||
}
|
||||
|
||||
void reset( bool full_reset, bool erase_battery_ram )
|
||||
{
|
||||
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 = true;
|
||||
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;
|
||||
}
|
||||
|
||||
nes_time_t emulate_frame(int joypad1, int joypad2)
|
||||
{
|
||||
current_joypad [0] = (joypad1 |= ~0xFF);
|
||||
current_joypad [1] = (joypad2 |= ~0xFF);
|
||||
|
||||
cpu_time_offset = ppu.begin_frame( nes.timestamp ) - 1;
|
||||
ppu_2002_time = 0;
|
||||
clock_ = cpu_time_offset;
|
||||
|
||||
// TODO: clean this fucking mess up
|
||||
auto t0 = emulate_frame_();
|
||||
impl->apu.run_until_( t0 );
|
||||
clock_ = cpu_time_offset;
|
||||
auto t1 = cpu_time();
|
||||
impl->apu.run_until_( t1 );
|
||||
|
||||
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 );
|
||||
|
||||
disable_rendering();
|
||||
nes.frame_count++;
|
||||
|
||||
return ppu_frame_length;
|
||||
}
|
||||
|
||||
void close()
|
||||
{
|
||||
cart = NULL;
|
||||
delete mapper;
|
||||
mapper = NULL;
|
||||
|
||||
ppu.close_chr();
|
||||
|
||||
disable_rendering();
|
||||
}
|
||||
|
||||
void save_state( Nes_State* out ) const
|
||||
{
|
||||
save_state( reinterpret_cast<Nes_State_*>(out) );
|
||||
}
|
||||
|
||||
void 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 load_state( Nes_State_ const& in )
|
||||
{
|
||||
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 irq_changed()
|
||||
{
|
||||
cpu_set_irq_time( earliest_irq( cpu_time() ) );
|
||||
}
|
||||
|
||||
void event_changed()
|
||||
{
|
||||
cpu_set_end_time( earliest_event( cpu_time() ) );
|
||||
}
|
||||
|
||||
public: private: friend class Nes_Emu;
|
||||
|
||||
|
@ -63,16 +434,116 @@ private:
|
|||
// 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 );
|
||||
|
||||
inline nes_time_t earliest_irq( nes_time_t present )
|
||||
{
|
||||
return min( impl->apu.earliest_irq( present ), mapper->next_irq( present ) );
|
||||
}
|
||||
|
||||
inline nes_time_t 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 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;
|
||||
}
|
||||
|
||||
// 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 );
|
||||
|
||||
int 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() );
|
||||
|
||||
return addr >> 8; // simulate open bus
|
||||
}
|
||||
|
||||
void 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.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;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int 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;
|
||||
}
|
||||
|
||||
static inline void apu_irq_changed( void* emu )
|
||||
{
|
||||
((Nes_Core*) emu)->irq_changed();
|
||||
}
|
||||
|
||||
|
||||
// CPU
|
||||
unsigned sram_readable;
|
||||
|
@ -80,23 +551,165 @@ private:
|
|||
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 );
|
||||
|
||||
nes_time_t 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) )
|
||||
{
|
||||
/* vectored NMI at end of frame */
|
||||
vector_interrupt( 0xFFFA );
|
||||
present += 7;
|
||||
}
|
||||
return present;
|
||||
}
|
||||
|
||||
if ( extra_instructions > 2 )
|
||||
{
|
||||
return present;
|
||||
}
|
||||
|
||||
if ( last_result != cpu::result_cli && last_result != cpu::result_sei &&
|
||||
(ppu.nmi_time() >= 0x10000 || (ppu.w2000 & 0x80 & ppu.r2002)) )
|
||||
return present;
|
||||
|
||||
/* Executing extra instructions for frame */
|
||||
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 )
|
||||
{
|
||||
/* IRQ vectored */
|
||||
mapper->run_until( present );
|
||||
vector_interrupt( 0xFFFE );
|
||||
}
|
||||
else
|
||||
{
|
||||
// CLI delays IRQ
|
||||
cpu_set_irq_time( present + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
// 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_addr_t read_vector( nes_addr_t addr )
|
||||
{
|
||||
uint8_t const* p = cpu::get_code( addr );
|
||||
return p [1] * 0x100 + p [0];
|
||||
}
|
||||
|
||||
void 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 );
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
inline void cpu_adjust_time( int n )
|
||||
{
|
||||
ppu_2002_time -= n;
|
||||
cpu_time_offset += n;
|
||||
cpu::reduce_limit( n );
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
void enable_prg_6000()
|
||||
{
|
||||
sram_writable = 0;
|
||||
sram_readable = 0;
|
||||
lrom_readable = 0x8000;
|
||||
}
|
||||
|
||||
void enable_sram( bool b, bool read_only = false)
|
||||
{
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
nes_time_t clock() const { return clock_; }
|
||||
void add_mapper_intercept( nes_addr_t start, unsigned size, bool read, bool write );
|
||||
|
||||
void add_mapper_intercept( nes_addr_t addr, unsigned size, bool read, bool write )
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public: private: friend class Nes_Cpu;
|
||||
int cpu_read_ppu( nes_addr_t, nes_time_t );
|
||||
|
|
|
@ -31,7 +31,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
|
|||
#define st_d 0x08
|
||||
#define st_i 0x04
|
||||
#define st_z 0x02
|
||||
#define st_c 0x01
|
||||
#define st_c 0x01
|
||||
|
||||
// Macros
|
||||
|
||||
|
@ -154,13 +154,6 @@ void Nes_Cpu::reset( void const* unmapped_page )
|
|||
isCorrectExecution = true;
|
||||
}
|
||||
|
||||
void Nes_Cpu::map_code( nes_addr_t start, unsigned size, const void* data )
|
||||
{
|
||||
unsigned first_page = start / page_size;
|
||||
const uint8_t* newPtr = (uint8_t*) data - start;
|
||||
for ( unsigned i = size / page_size; i--; ) code_map [first_page + i] = newPtr;
|
||||
}
|
||||
|
||||
// Note: 'addr' is evaulated more than once in the following macros, so it
|
||||
// must not contain side-effects.
|
||||
|
||||
|
@ -196,12 +189,12 @@ void Nes_Cpu::map_code( nes_addr_t start, unsigned size, const void* data )
|
|||
nz |= ~in & st_z; \
|
||||
} while ( 0 )
|
||||
|
||||
int Nes_Cpu::read( nes_addr_t addr )
|
||||
inline int Nes_Cpu::read( nes_addr_t addr )
|
||||
{
|
||||
return READ( addr );
|
||||
}
|
||||
|
||||
void Nes_Cpu::write( nes_addr_t addr, int value )
|
||||
inline void Nes_Cpu::write( nes_addr_t addr, int value )
|
||||
{
|
||||
WRITE( addr, value );
|
||||
}
|
||||
|
|
|
@ -22,8 +22,14 @@ public:
|
|||
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 );
|
||||
|
||||
inline void map_code( nes_addr_t start, unsigned size, const void* data )
|
||||
{
|
||||
unsigned first_page = start / page_size;
|
||||
const uint8_t* newPtr = (uint8_t*) data - start;
|
||||
for ( unsigned i = size / page_size; i--; ) code_map [first_page + i] = newPtr;
|
||||
}
|
||||
|
||||
// Access memory as the emulated CPU does.
|
||||
int read( nes_addr_t );
|
||||
void write( nes_addr_t, int data );
|
||||
|
|
|
@ -42,6 +42,8 @@ public:
|
|||
|
||||
const uint8_t* getHostPixels () const { return emu.ppu.host_pixels; }
|
||||
|
||||
size_t getLightweightStateSize() { return emu.getLightweightStateSize(); }
|
||||
|
||||
// Basic emulation
|
||||
|
||||
// Emulate one video frame using joypad1 and joypad2 as input. Afterwards, image
|
||||
|
|
|
@ -23,7 +23,6 @@ quickerNESSrc = [
|
|||
'nes_ntsc.cpp',
|
||||
'Nes_Vrc7.cpp',
|
||||
'Nes_Mapper.cpp',
|
||||
'Nes_Core.cpp',
|
||||
'Nes_Cpu.cpp',
|
||||
'quickerNESInstance.hpp'
|
||||
]
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
|
||||
#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 );
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#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 );\
|
||||
}
|
|
@ -26,6 +26,9 @@ class QuickerNESInstance : public EmuInstance
|
|||
{
|
||||
// Loading rom data
|
||||
auto result = _nes->load_ines((uint8_t*)romData.data());
|
||||
|
||||
// printf("Lightweight size: %lu vs Full Size: %lu\n", _nes->getLightweightStateSize(), getStateSizeImpl());
|
||||
// exit(0);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,4 +18,5 @@ subdir('galaga')
|
|||
subdir('saintSeiyaOugonDensetsu')
|
||||
subdir('saintSeiyaKanketsuHen')
|
||||
subdir('superMarioBros')
|
||||
subdir('superMarioBros3')
|
||||
subdir('saiyuukiWorld')
|
|
@ -2,8 +2,6 @@ game = 'superMarioBros'
|
|||
|
||||
goal = 'warps'
|
||||
test(goal + '-FullCycle', bash, workdir : meson.current_source_dir(), args : [ testCommands, goal + '.test', '--fullCycle'], suite: [ game, goal ] )
|
||||
test(goal, bash, workdir : meson.current_source_dir(), args : [ testCommands, goal + '.test'], suite: [ game, goal ] )
|
||||
|
||||
goal = 'warpless'
|
||||
test(goal + '-FullCycle', bash, workdir : meson.current_source_dir(), args : [ testCommands, goal + '.test', '--fullCycle'], suite: [ game, goal ] )
|
||||
test(goal, bash, workdir : meson.current_source_dir(), args : [ testCommands, goal + '.test'], suite: [ game, goal ] )
|
||||
test(goal + '-FullCycle', bash, workdir : meson.current_source_dir(), args : [ testCommands, goal + '.test', '--fullCycle'], suite: [ game, goal ] )
|
|
@ -0,0 +1,4 @@
|
|||
game = 'superMarioBros3'
|
||||
|
||||
goal = 'warps'
|
||||
test(goal + '-FullCycle', bash, workdir : meson.current_source_dir(), args : [ testCommands, goal + '.test', '--fullCycle'], suite: [ game, goal ] )
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"Rom File": "Super Mario Bros. 3 (U) (PRG0) [!].nes",
|
||||
"Expected ROM SHA1": "A03E7E526E79DF222E048AE22214BCA2BC49C449",
|
||||
"Initial State File": "",
|
||||
"Sequence File": "warps.sol"
|
||||
}
|
Loading…
Reference in New Issue