2012-09-30 05:17:08 +00:00
//TODO - add serializer, add interlace field variable to serializer
//http://wiki.superfamicom.org/snes/show/Backgrounds
2012-09-06 08:32:25 +00:00
//TODO
2012-09-04 19:12:16 +00:00
//libsnes needs to be modified to support multiple instances - THIS IS NECESSARY - or else loading one game and then another breaks things
2012-09-22 05:03:52 +00:00
// edit - this is a lot of work
2012-09-04 07:09:00 +00:00
//wrap dll code around some kind of library-accessing interface so that it doesnt malfunction if the dll is unavailable
using System ;
2012-09-04 00:20:36 +00:00
using System.Linq ;
2013-04-24 22:09:11 +00:00
using System.Xml ;
using System.Xml.Linq ;
2012-09-04 00:20:36 +00:00
using System.IO ;
using System.Collections.Generic ;
using System.Runtime.InteropServices ;
2013-10-27 22:07:40 +00:00
using BizHawk.Common ;
2014-07-03 18:54:53 +00:00
using BizHawk.Common.BufferExtensions ;
2013-11-04 01:06:36 +00:00
using BizHawk.Emulation.Common ;
2013-10-27 22:07:40 +00:00
2013-11-13 23:36:21 +00:00
namespace BizHawk.Emulation.Cores.Nintendo.SNES
2012-09-04 00:20:36 +00:00
{
2014-04-25 01:19:57 +00:00
[ CoreAttributes (
"BSNES" ,
"byuu" ,
isPorted : true ,
2014-06-01 01:57:22 +00:00
isReleased : true ,
portedVersion : "v87" ,
portedUrl : "http://byuu.org/"
2014-04-25 01:19:57 +00:00
) ]
2014-11-30 20:29:30 +00:00
public unsafe class LibsnesCore : IEmulator , IVideoProvider , IMemoryDomains , ISaveRam , IStatable , IInputPollable ,
2014-10-19 01:22:47 +00:00
IDebuggable , ISettable < LibsnesCore . SnesSettings , LibsnesCore . SnesSyncSettings >
2012-09-04 00:20:36 +00:00
{
2014-11-18 23:44:42 +00:00
public LibsnesCore ( GameInfo game , byte [ ] romData , bool deterministicEmulation , byte [ ] xmlData , CoreComm comm , object Settings , object SyncSettings )
{
2014-11-19 00:32:51 +00:00
_game = game ;
2014-11-18 23:44:42 +00:00
CoreComm = comm ;
byte [ ] sgbRomData = null ;
if ( game [ "SGB" ] )
{
if ( ( romData [ 0x143 ] & 0xc0 ) = = 0xc0 )
throw new CGBNotSupportedException ( ) ;
sgbRomData = CoreComm . CoreFileProvider . GetFirmware ( "SNES" , "Rom_SGB" , true , "SGB Rom is required for SGB emulation." ) ;
game . FirmwareHash = sgbRomData . HashSHA1 ( ) ;
}
this . Settings = ( SnesSettings ) Settings ? ? new SnesSettings ( ) ;
this . SyncSettings = ( SnesSyncSettings ) SyncSettings ? ? new SnesSyncSettings ( ) ;
api = new LibsnesApi ( GetExePath ( ) ) ;
api . CMD_init ( ) ;
api . ReadHook = ReadHook ;
api . ExecHook = ExecHook ;
api . WriteHook = WriteHook ;
ScanlineHookManager = new MyScanlineHookManager ( this ) ;
api . CMD_init ( ) ;
api . QUERY_set_video_refresh ( snes_video_refresh ) ;
api . QUERY_set_input_poll ( snes_input_poll ) ;
api . QUERY_set_input_state ( snes_input_state ) ;
api . QUERY_set_input_notify ( snes_input_notify ) ;
api . QUERY_set_path_request ( snes_path_request ) ;
scanlineStart_cb = new LibsnesApi . snes_scanlineStart_t ( snes_scanlineStart ) ;
tracecb = new LibsnesApi . snes_trace_t ( snes_trace ) ;
soundcb = new LibsnesApi . snes_audio_sample_t ( snes_audio_sample ) ;
api . QUERY_set_audio_sample ( soundcb ) ;
RefreshPalette ( ) ;
// start up audio resampler
InitAudio ( ) ;
//strip header
if ( romData ! = null )
if ( ( romData . Length & 0x7FFF ) = = 512 )
{
var newData = new byte [ romData . Length - 512 ] ;
Array . Copy ( romData , 512 , newData , 0 , newData . Length ) ;
romData = newData ;
}
if ( game [ "SGB" ] )
{
IsSGB = true ;
SystemId = "SNES" ;
BoardName = "SGB" ;
CurrLoadParams = new LoadParams ( )
{
type = LoadParamType . SuperGameBoy ,
rom_xml = null ,
rom_data = sgbRomData ,
rom_size = ( uint ) sgbRomData . Length ,
dmg_xml = null ,
dmg_data = romData ,
dmg_size = ( uint ) romData . Length
} ;
if ( ! LoadCurrent ( ) )
throw new Exception ( "snes_load_cartridge_normal() failed" ) ;
}
else
{
//we may need to get some information out of the cart, even during the following bootup/load process
if ( xmlData ! = null )
{
romxml = new System . Xml . XmlDocument ( ) ;
romxml . Load ( new MemoryStream ( xmlData ) ) ;
//bsnes wont inspect the xml to load the necessary sfc file.
//so, we have to do that here and pass it in as the romData :/
if ( romxml [ "cartridge" ] ! = null & & romxml [ "cartridge" ] [ "rom" ] ! = null )
romData = File . ReadAllBytes ( CoreComm . CoreFileProvider . PathSubfile ( romxml [ "cartridge" ] [ "rom" ] . Attributes [ "name" ] . Value ) ) ;
else
throw new Exception ( "Could not find rom file specification in xml file. Please check the integrity of your xml file" ) ;
}
SystemId = "SNES" ;
CurrLoadParams = new LoadParams ( )
{
type = LoadParamType . Normal ,
xml_data = xmlData ,
rom_data = romData
} ;
if ( ! LoadCurrent ( ) )
throw new Exception ( "snes_load_cartridge_normal() failed" ) ;
}
if ( api . QUERY_get_region ( ) = = LibsnesApi . SNES_REGION . NTSC )
{
//similar to what aviout reports from snes9x and seems logical from bsnes first principles. bsnes uses that numerator (ntsc master clockrate) for sure.
CoreComm . VsyncNum = 21477272 ;
CoreComm . VsyncDen = 4 * 341 * 262 ;
}
else
{
//http://forums.nesdev.com/viewtopic.php?t=5367&start=19
CoreComm . VsyncNum = 21281370 ;
CoreComm . VsyncDen = 4 * 341 * 312 ;
}
CoreComm . CpuTraceAvailable = true ;
api . CMD_power ( ) ;
SetupMemoryDomains ( romData , sgbRomData ) ;
DeterministicEmulation = deterministicEmulation ;
if ( DeterministicEmulation ) // save frame-0 savestate now
{
MemoryStream ms = new MemoryStream ( ) ;
BinaryWriter bw = new BinaryWriter ( ms ) ;
bw . Write ( CoreSaveState ( ) ) ;
bw . Write ( true ) ; // framezero, so no controller follows and don't frameadvance on load
// hack: write fake dummy controller info
bw . Write ( new byte [ 536 ] ) ;
bw . Close ( ) ;
savestatebuff = ms . ToArray ( ) ;
}
}
2014-11-19 00:32:51 +00:00
private GameInfo _game ;
public string CurrentProfile
{
get
{
// TODO: This logic will only work until Accuracy is ready, would we really want to override the user's choice of Accuracy with Compatibility?
if ( _game . OptionValue ( "profile" ) = = "Compatibility" )
{
return "Compatibility" ;
}
return SyncSettings . Profile ;
}
}
2012-10-05 04:47:45 +00:00
public bool IsSGB { get ; private set ; }
2012-10-08 14:37:42 +00:00
/// <summary>disable all external callbacks. the front end should not even know the core is frame advancing</summary>
bool nocallbacks = false ;
2012-09-05 23:16:08 +00:00
bool disposed = false ;
2012-09-04 00:20:36 +00:00
public void Dispose ( )
{
2012-09-05 23:16:08 +00:00
if ( disposed ) return ;
disposed = true ;
2013-11-22 09:33:56 +00:00
api . CMD_unload_cartridge ( ) ;
api . CMD_term ( ) ;
2012-09-08 01:15:16 +00:00
resampler . Dispose ( ) ;
2012-12-25 20:36:04 +00:00
api . Dispose ( ) ;
2012-09-04 00:20:36 +00:00
}
2012-09-05 23:16:08 +00:00
2014-11-23 16:22:02 +00:00
public IDictionary < string , int > GetCpuFlagsAndRegisters ( )
2013-11-11 03:20:33 +00:00
{
2013-11-22 09:33:56 +00:00
LibsnesApi . CpuRegs regs ;
api . QUERY_peek_cpu_regs ( out regs ) ;
bool fn = ( regs . p & 0x80 ) ! = 0 ;
bool fv = ( regs . p & 0x40 ) ! = 0 ;
bool fm = ( regs . p & 0x20 ) ! = 0 ;
bool fx = ( regs . p & 0x10 ) ! = 0 ;
bool fd = ( regs . p & 0x08 ) ! = 0 ;
bool fi = ( regs . p & 0x04 ) ! = 0 ;
bool fz = ( regs . p & 0x02 ) ! = 0 ;
bool fc = ( regs . p & 0x01 ) ! = 0 ;
2014-04-19 22:23:13 +00:00
return new Dictionary < string , int >
2013-11-12 15:47:31 +00:00
{
2014-04-19 22:23:13 +00:00
{ "PC" , ( int ) regs . pc } ,
{ "A" , ( int ) regs . a } ,
{ "X" , ( int ) regs . x } ,
{ "Y" , ( int ) regs . y } ,
{ "Z" , ( int ) regs . z } ,
{ "S" , ( int ) regs . s } ,
{ "D" , ( int ) regs . d } ,
{ "Vector" , ( int ) regs . vector } ,
{ "P" , ( int ) regs . p } ,
{ "AA" , ( int ) regs . aa } ,
{ "RD" , ( int ) regs . rd } ,
{ "SP" , ( int ) regs . sp } ,
{ "DP" , ( int ) regs . dp } ,
{ "DB" , ( int ) regs . db } ,
{ "MDR" , ( int ) regs . mdr } ,
{ "Flag N" , fn ? 1 : 0 } ,
{ "Flag V" , fv ? 1 : 0 } ,
{ "Flag M" , fm ? 1 : 0 } ,
{ "Flag X" , fx ? 1 : 0 } ,
{ "Flag D" , fd ? 1 : 0 } ,
{ "Flag I" , fi ? 1 : 0 } ,
{ "Flag Z" , fz ? 1 : 0 } ,
{ "Flag C" , fc ? 1 : 0 } ,
2013-11-22 09:33:56 +00:00
} ;
2013-11-11 03:20:33 +00:00
}
2014-12-04 00:43:12 +00:00
private readonly InputCallbackSystem _inputCallbacks = new InputCallbackSystem ( ) ;
// TODO: optimize managed to unmanaged using the ActiveChanged event
public IInputCallbackSystem InputCallbacks { [ FeatureNotImplemented ] get { return _inputCallbacks ; } }
2014-11-24 01:17:05 +00:00
[FeatureNotImplemented]
2014-05-31 17:03:21 +00:00
public void SetCpuRegister ( string register , int value )
{
throw new NotImplementedException ( ) ;
}
2012-09-22 05:03:52 +00:00
public class MyScanlineHookManager : ScanlineHookManager
{
public MyScanlineHookManager ( LibsnesCore core )
{
this . core = core ;
}
LibsnesCore core ;
public override void OnHooksChanged ( )
{
core . OnScanlineHooksChanged ( ) ;
}
}
public MyScanlineHookManager ScanlineHookManager ;
void OnScanlineHooksChanged ( )
{
if ( disposed ) return ;
2013-11-22 09:33:56 +00:00
if ( ScanlineHookManager . HookCount = = 0 ) api . QUERY_set_scanlineStart ( null ) ;
else api . QUERY_set_scanlineStart ( scanlineStart_cb ) ;
2012-09-22 05:03:52 +00:00
}
void snes_scanlineStart ( int line )
{
ScanlineHookManager . HandleScanline ( line ) ;
}
2012-10-05 04:47:45 +00:00
string snes_path_request ( int slot , string hint )
2012-09-27 07:22:31 +00:00
{
2013-04-24 22:09:11 +00:00
//every rom requests msu1.rom... why? who knows.
//also handle msu-1 pcm files here
bool is_msu1_rom = hint = = "msu1.rom" ;
bool is_msu1_pcm = Path . GetExtension ( hint ) . ToLower ( ) = = ".pcm" ;
if ( is_msu1_rom | | is_msu1_pcm )
{
//well, check if we have an msu-1 xml
if ( romxml ! = null & & romxml [ "cartridge" ] ! = null & & romxml [ "cartridge" ] [ "msu1" ] ! = null )
{
var msu1 = romxml [ "cartridge" ] [ "msu1" ] ;
if ( is_msu1_rom & & msu1 [ "rom" ] . Attributes [ "name" ] ! = null )
2013-08-10 01:17:06 +00:00
return CoreComm . CoreFileProvider . PathSubfile ( msu1 [ "rom" ] . Attributes [ "name" ] . Value ) ;
2013-04-24 22:09:11 +00:00
if ( is_msu1_pcm )
{
//return @"D:\roms\snes\SuperRoadBlaster\SuperRoadBlaster-1.pcm";
//return "";
int wantsTrackNumber = int . Parse ( hint . Replace ( "track-" , "" ) . Replace ( ".pcm" , "" ) ) ;
wantsTrackNumber + + ;
string wantsTrackString = wantsTrackNumber . ToString ( ) ;
foreach ( var child in msu1 . ChildNodes . Cast < XmlNode > ( ) )
{
if ( child . Name = = "track" & & child . Attributes [ "number" ] . Value = = wantsTrackString )
2013-08-10 01:17:06 +00:00
return CoreComm . CoreFileProvider . PathSubfile ( child . Attributes [ "name" ] . Value ) ;
2013-04-24 22:09:11 +00:00
}
}
}
2013-04-24 17:17:25 +00:00
2013-04-24 22:09:11 +00:00
//not found.. what to do? (every rom will get here when msu1.rom is requested)
return "" ;
}
2012-09-27 07:22:31 +00:00
2013-12-09 21:41:18 +00:00
// not MSU-1. ok.
string firmwareID ;
2012-09-27 07:22:31 +00:00
2013-12-09 21:41:18 +00:00
switch ( hint )
2012-09-27 07:22:31 +00:00
{
2013-12-09 21:41:18 +00:00
case "cx4.rom" : firmwareID = "CX4" ; break ;
case "dsp1.rom" : firmwareID = "DSP1" ; break ;
case "dsp1b.rom" : firmwareID = "DSP1b" ; break ;
case "dsp2.rom" : firmwareID = "DSP2" ; break ;
case "dsp3.rom" : firmwareID = "DSP3" ; break ;
case "dsp4.rom" : firmwareID = "DSP4" ; break ;
case "st010.rom" : firmwareID = "ST010" ; break ;
case "st011.rom" : firmwareID = "ST011" ; break ;
case "st018.rom" : firmwareID = "ST018" ; break ;
default :
2013-12-10 17:58:12 +00:00
CoreComm . ShowMessage ( string . Format ( "Unrecognized SNES firmware request \"{0}\"." , hint ) ) ;
2013-12-09 21:41:18 +00:00
return "" ;
2012-09-27 07:22:31 +00:00
}
2013-12-09 21:41:18 +00:00
//build romfilename
2013-12-10 17:58:12 +00:00
string test = CoreComm . CoreFileProvider . GetFirmwarePath ( "SNES" , firmwareID , false , "Game may function incorrectly without the requested firmware." ) ;
2013-12-09 21:41:18 +00:00
2014-05-23 00:13:04 +00:00
//we need to return something to bsnes
test = test ? ? "" ;
2012-10-28 23:42:04 +00:00
Console . WriteLine ( "Served libsnes request for firmware \"{0}\" with \"{1}\"" , hint , test ) ;
2012-09-27 07:22:31 +00:00
//return the path we built
return test ;
}
2012-12-03 01:48:18 +00:00
void snes_trace ( string msg )
{
2012-12-10 00:43:43 +00:00
CoreComm . Tracer . Put ( msg ) ;
2012-12-03 01:48:18 +00:00
}
2012-11-25 20:06:31 +00:00
public SnesColors . ColorType CurrPalette { get ; private set ; }
public void SetPalette ( SnesColors . ColorType pal )
{
CurrPalette = pal ;
int [ ] tmp = SnesColors . GetLUT ( pal ) ;
fixed ( int * p = & tmp [ 0 ] )
2013-11-22 09:33:56 +00:00
api . QUERY_set_color_lut ( ( IntPtr ) p ) ;
2012-11-25 20:06:31 +00:00
}
2012-12-25 20:36:04 +00:00
public LibsnesApi api ;
2013-04-24 22:09:11 +00:00
System . Xml . XmlDocument romxml ;
2012-12-25 20:36:04 +00:00
2013-12-27 17:59:19 +00:00
string GetExePath ( )
2012-09-27 07:22:31 +00:00
{
2013-12-27 17:59:19 +00:00
const string bits = "32" ;
// disabled til it works
// if (Win32.Is64BitOperatingSystem)
// bits = "64";
2014-11-19 00:32:51 +00:00
var exename = "libsneshawk-" + bits + "-" + CurrentProfile . ToLower ( ) + ".exe" ;
2013-12-27 17:59:19 +00:00
string exePath = Path . Combine ( CoreComm . CoreFileProvider . DllPath ( ) , exename ) ;
if ( ! File . Exists ( exePath ) )
2014-11-19 00:32:51 +00:00
throw new InvalidOperationException ( "Couldn't locate the executable for SNES emulation for profile: " + CurrentProfile + ". Please make sure you're using a fresh dearchive of a BizHawk distribution." ) ;
2013-12-27 17:59:19 +00:00
return exePath ;
}
2013-11-12 02:34:56 +00:00
void ReadHook ( uint addr )
{
CoreComm . MemoryCallbackSystem . CallRead ( addr ) ;
//we RefreshMemoryCallbacks() after the trigger in case the trigger turns itself off at that point
2013-11-12 20:35:59 +00:00
//EDIT: for now, theres some IPC re-entrancy problem
//RefreshMemoryCallbacks();
2013-11-22 09:33:56 +00:00
api . SPECIAL_Resume ( ) ;
2013-11-12 02:34:56 +00:00
}
void ExecHook ( uint addr )
{
CoreComm . MemoryCallbackSystem . CallExecute ( addr ) ;
//we RefreshMemoryCallbacks() after the trigger in case the trigger turns itself off at that point
2013-11-12 20:35:59 +00:00
//EDIT: for now, theres some IPC re-entrancy problem
//RefreshMemoryCallbacks();
2013-11-22 09:33:56 +00:00
api . SPECIAL_Resume ( ) ;
2013-11-12 02:34:56 +00:00
}
void WriteHook ( uint addr , byte val )
{
CoreComm . MemoryCallbackSystem . CallWrite ( addr ) ;
//we RefreshMemoryCallbacks() after the trigger in case the trigger turns itself off at that point
2013-11-12 20:35:59 +00:00
//EDIT: for now, theres some IPC re-entrancy problem
//RefreshMemoryCallbacks();
2013-11-22 09:33:56 +00:00
api . SPECIAL_Resume ( ) ;
2012-09-27 07:22:31 +00:00
}
2012-12-25 20:36:04 +00:00
LibsnesApi . snes_scanlineStart_t scanlineStart_cb ;
LibsnesApi . snes_trace_t tracecb ;
LibsnesApi . snes_audio_sample_t soundcb ;
2014-05-23 00:13:04 +00:00
enum LoadParamType
{
Normal , SuperGameBoy
}
struct LoadParams
{
public LoadParamType type ;
public byte [ ] xml_data ;
public string rom_xml ;
public byte [ ] rom_data ;
public uint rom_size ;
public string dmg_xml ;
public byte [ ] dmg_data ;
public uint dmg_size ;
}
LoadParams CurrLoadParams ;
bool LoadCurrent ( )
{
if ( CurrLoadParams . type = = LoadParamType . Normal )
return api . CMD_load_cartridge_normal ( CurrLoadParams . xml_data , CurrLoadParams . rom_data ) ;
else return api . CMD_load_cartridge_super_game_boy ( CurrLoadParams . rom_xml , CurrLoadParams . rom_data , CurrLoadParams . rom_size , CurrLoadParams . dmg_xml , CurrLoadParams . dmg_data , CurrLoadParams . dmg_size ) ;
}
2012-12-29 02:43:00 +00:00
/// <summary>
///
/// </summary>
/// <param name="port">0 or 1, corresponding to L and R physical ports on the snes</param>
/// <param name="device">LibsnesApi.SNES_DEVICE enum index specifying type of device</param>
/// <param name="index">meaningless for most controllers. for multitap, 0-3 for which multitap controller</param>
/// <param name="id">button ID enum; in the case of a regular controller, this corresponds to shift register position</param>
/// <returns>for regular controllers, one bit D0 of button status. for other controls, varying ranges depending on id</returns>
2012-09-04 00:20:36 +00:00
ushort snes_input_state ( int port , int device , int index , int id )
{
2012-12-29 02:43:00 +00:00
// as this is implemented right now, only P1 and P2 normal controllers work
2014-12-04 00:43:12 +00:00
InputCallbacks . Call ( ) ;
2012-09-04 00:20:36 +00:00
//Console.WriteLine("{0} {1} {2} {3}", port, device, index, id);
string key = "P" + ( 1 + port ) + " " ;
2012-12-25 20:36:04 +00:00
if ( ( LibsnesApi . SNES_DEVICE ) device = = LibsnesApi . SNES_DEVICE . JOYPAD )
2012-09-04 00:20:36 +00:00
{
2012-12-25 20:36:04 +00:00
switch ( ( LibsnesApi . SNES_DEVICE_ID ) id )
2012-09-04 00:20:36 +00:00
{
2012-12-25 20:36:04 +00:00
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_A : key + = "A" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_B : key + = "B" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_X : key + = "X" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_Y : key + = "Y" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_UP : key + = "Up" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_DOWN : key + = "Down" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_LEFT : key + = "Left" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_RIGHT : key + = "Right" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_L : key + = "L" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_R : key + = "R" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_SELECT : key + = "Select" ; break ;
case LibsnesApi . SNES_DEVICE_ID . JOYPAD_START : key + = "Start" ; break ;
2012-10-08 18:55:25 +00:00
default : return 0 ;
2012-09-04 00:20:36 +00:00
}
return ( ushort ) ( Controller [ key ] ? 1 : 0 ) ;
}
return 0 ;
}
void snes_input_poll ( )
2012-09-23 15:57:01 +00:00
{
2012-10-06 13:34:04 +00:00
// this doesn't actually correspond to anything in the underlying bsnes;
// it gets called once per frame with video_refresh() and has nothing to do with anything
2012-09-23 15:57:01 +00:00
}
void snes_input_notify ( int index )
2012-09-04 00:20:36 +00:00
{
2012-09-11 01:36:12 +00:00
IsLagFrame = false ;
2012-09-04 00:20:36 +00:00
}
2012-09-30 05:17:08 +00:00
int field = 0 ;
2012-09-04 19:25:09 +00:00
void snes_video_refresh ( int * data , int width , int height )
2012-09-04 00:20:36 +00:00
{
2013-12-27 17:59:19 +00:00
bool doubleSize = Settings . AlwaysDoubleSize ;
2013-04-22 22:34:18 +00:00
bool lineDouble = doubleSize , dotDouble = doubleSize ;
2012-09-04 00:20:36 +00:00
vidWidth = width ;
vidHeight = height ;
2012-09-30 05:17:08 +00:00
2013-04-22 22:34:18 +00:00
int yskip = 1 , xskip = 1 ;
2012-09-24 08:00:42 +00:00
//if we are in high-res mode, we get double width. so, lets double the height here to keep it square.
//TODO - does interlacing have something to do with the correct way to handle this? need an example that turns it on.
if ( width = = 512 )
2013-04-22 22:34:18 +00:00
{
vidHeight * = 2 ;
yskip = 2 ;
lineDouble = true ;
//we dont dot double here because the user wanted double res and the game provided double res
dotDouble = false ;
}
else if ( lineDouble )
2012-09-24 08:00:42 +00:00
{
vidHeight * = 2 ;
yskip = 2 ;
}
2012-09-30 05:17:08 +00:00
int srcPitch = 1024 ;
int srcStart = 0 ;
//for interlaced mode, we're gonna alternate fields. you know, like we're supposed to
bool interlaced = ( height = = 478 | | height = = 448 ) ;
if ( interlaced )
{
srcPitch = 1024 ;
if ( field = = 1 )
srcStart = 512 ; //start on second field
//really only half as high as the video output
vidHeight / = 2 ;
height / = 2 ;
//alternate fields
field ^ = 1 ;
}
2013-04-22 22:34:18 +00:00
if ( dotDouble )
{
vidWidth * = 2 ;
xskip = 2 ;
}
2012-09-04 00:20:36 +00:00
int size = vidWidth * vidHeight ;
if ( vidBuffer . Length ! = size )
vidBuffer = new int [ size ] ;
2012-09-24 08:00:42 +00:00
2013-04-22 22:34:18 +00:00
for ( int j = 0 ; j < 2 ; j + + )
{
if ( j = = 1 & & ! dotDouble ) break ;
int xbonus = j ;
for ( int i = 0 ; i < 2 ; i + + )
2012-09-04 00:20:36 +00:00
{
2013-04-22 22:34:18 +00:00
//potentially do this twice, if we need to line double
if ( i = = 1 & & ! lineDouble ) break ;
int bonus = i * vidWidth + xbonus ;
for ( int y = 0 ; y < height ; y + + )
for ( int x = 0 ; x < width ; x + + )
{
int si = y * srcPitch + x + srcStart ;
int di = y * vidWidth * yskip + x * xskip + bonus ;
int rgb = data [ si ] ;
vidBuffer [ di ] = rgb ;
}
2012-09-04 00:20:36 +00:00
}
2013-04-22 22:34:18 +00:00
}
2012-09-04 00:20:36 +00:00
}
2012-09-20 19:52:47 +00:00
public void FrameAdvance ( bool render , bool rendersound )
2012-09-04 00:20:36 +00:00
{
2012-12-25 20:36:04 +00:00
api . MessageCounter = 0 ;
2013-12-27 17:59:19 +00:00
if ( Settings . UseRingBuffer )
2013-01-18 05:06:26 +00:00
api . BeginBufferIO ( ) ;
2012-12-27 07:59:19 +00:00
2013-06-29 23:32:41 +00:00
/ * if the input poll callback is called , it will set this to false
* this has to be done before we save the per - frame state in deterministic
* mode , because in there , the core actually advances , and might advance
* through the point in time where IsLagFrame gets set to false . makes sense ?
* /
IsLagFrame = true ;
2012-10-08 14:37:42 +00:00
// for deterministic emulation, save the state we're going to use before frame advance
// don't do this during nocallbacks though, since it's already been done
if ( ! nocallbacks & & DeterministicEmulation )
{
MemoryStream ms = new MemoryStream ( ) ;
BinaryWriter bw = new BinaryWriter ( ms ) ;
bw . Write ( CoreSaveState ( ) ) ;
bw . Write ( false ) ; // not framezero
SnesSaveController ssc = new SnesSaveController ( ) ;
ssc . CopyFrom ( Controller ) ;
ssc . Serialize ( bw ) ;
bw . Close ( ) ;
savestatebuff = ms . ToArray ( ) ;
}
2012-12-10 00:43:43 +00:00
if ( ! nocallbacks & & CoreComm . Tracer . Enabled )
2013-11-22 09:33:56 +00:00
api . QUERY_set_trace_callback ( tracecb ) ;
2012-12-03 01:48:18 +00:00
else
2013-11-22 09:33:56 +00:00
api . QUERY_set_trace_callback ( null ) ;
2012-12-03 01:48:18 +00:00
2012-09-20 20:25:40 +00:00
// speedup when sound rendering is not needed
2012-09-20 20:36:44 +00:00
if ( ! rendersound )
2013-11-22 09:33:56 +00:00
api . QUERY_set_audio_sample ( null ) ;
2012-09-20 20:36:44 +00:00
else
2013-11-22 09:33:56 +00:00
api . QUERY_set_audio_sample ( soundcb ) ;
2012-09-20 20:25:40 +00:00
2012-09-16 17:15:53 +00:00
bool resetSignal = Controller [ "Reset" ] ;
2013-11-22 09:33:56 +00:00
if ( resetSignal ) api . CMD_reset ( ) ;
2012-09-16 17:15:53 +00:00
bool powerSignal = Controller [ "Power" ] ;
2013-11-22 09:33:56 +00:00
if ( powerSignal ) api . CMD_power ( ) ;
2012-12-25 20:36:04 +00:00
//too many messages
2013-12-27 17:59:19 +00:00
api . QUERY_set_layer_enable ( 0 , 0 , Settings . ShowBG1_0 ) ;
api . QUERY_set_layer_enable ( 0 , 1 , Settings . ShowBG1_1 ) ;
api . QUERY_set_layer_enable ( 1 , 0 , Settings . ShowBG2_0 ) ;
api . QUERY_set_layer_enable ( 1 , 1 , Settings . ShowBG2_1 ) ;
api . QUERY_set_layer_enable ( 2 , 0 , Settings . ShowBG3_0 ) ;
api . QUERY_set_layer_enable ( 2 , 1 , Settings . ShowBG3_1 ) ;
api . QUERY_set_layer_enable ( 3 , 0 , Settings . ShowBG4_0 ) ;
api . QUERY_set_layer_enable ( 3 , 1 , Settings . ShowBG4_1 ) ;
api . QUERY_set_layer_enable ( 4 , 0 , Settings . ShowOBJ_0 ) ;
api . QUERY_set_layer_enable ( 4 , 1 , Settings . ShowOBJ_1 ) ;
api . QUERY_set_layer_enable ( 4 , 2 , Settings . ShowOBJ_2 ) ;
api . QUERY_set_layer_enable ( 4 , 3 , Settings . ShowOBJ_3 ) ;
2012-09-04 19:12:16 +00:00
2013-12-29 02:20:13 +00:00
RefreshMemoryCallbacks ( false ) ;
2013-11-12 02:34:56 +00:00
2012-09-04 00:20:36 +00:00
//apparently this is one frame?
2012-09-07 20:06:57 +00:00
timeFrameCounter + + ;
2013-11-22 09:33:56 +00:00
api . CMD_run ( ) ;
2012-09-08 20:03:04 +00:00
2013-11-22 09:33:56 +00:00
while ( api . QUERY_HasMessage )
Console . WriteLine ( api . QUERY_DequeueMessage ( ) ) ;
2012-09-27 01:38:27 +00:00
2012-09-11 01:36:12 +00:00
if ( IsLagFrame )
LagCount + + ;
2012-09-30 19:22:54 +00:00
2012-12-25 20:36:04 +00:00
//diagnostics for IPC traffic
//Console.WriteLine(api.MessageCounter);
2012-12-27 07:59:19 +00:00
api . EndBufferIO ( ) ;
2012-09-04 00:20:36 +00:00
}
2013-12-29 02:20:13 +00:00
void RefreshMemoryCallbacks ( bool suppress )
2013-11-12 02:34:56 +00:00
{
var mcs = CoreComm . MemoryCallbackSystem ;
2013-12-29 02:20:13 +00:00
api . QUERY_set_state_hook_exec ( ! suppress & & mcs . HasExecutes ) ;
api . QUERY_set_state_hook_read ( ! suppress & & mcs . HasReads ) ;
api . QUERY_set_state_hook_write ( ! suppress & & mcs . HasWrites ) ;
2013-11-12 02:34:56 +00:00
}
2012-10-06 16:28:42 +00:00
public DisplayType DisplayType
{
get
{
2013-11-22 09:33:56 +00:00
if ( api . QUERY_get_region ( ) = = LibsnesApi . SNES_REGION . NTSC )
2013-11-04 01:39:19 +00:00
return DisplayType . NTSC ;
2012-10-06 16:28:42 +00:00
else
2013-11-04 01:39:19 +00:00
return DisplayType . PAL ;
2012-10-06 16:28:42 +00:00
}
}
2012-09-04 00:20:36 +00:00
//video provider
int IVideoProvider . BackgroundColor { get { return 0 ; } }
int [ ] IVideoProvider . GetVideoBuffer ( ) { return vidBuffer ; }
int IVideoProvider . VirtualWidth { get { return vidWidth ; } }
2014-04-30 23:48:37 +00:00
public int VirtualHeight { get { return vidHeight ; } }
2012-09-05 18:52:17 +00:00
int IVideoProvider . BufferWidth { get { return vidWidth ; } }
2012-09-04 00:20:36 +00:00
int IVideoProvider . BufferHeight { get { return vidHeight ; } }
2012-09-30 14:08:50 +00:00
int [ ] vidBuffer = new int [ 256 * 224 ] ;
int vidWidth = 256 , vidHeight = 224 ;
2012-09-04 00:20:36 +00:00
public IVideoProvider VideoProvider { get { return this ; } }
public ControllerDefinition ControllerDefinition { get { return SNESController ; } }
IController controller ;
public IController Controller
{
get { return controller ; }
set { controller = value ; }
}
public static readonly ControllerDefinition SNESController =
new ControllerDefinition
{
Name = "SNES Controller" ,
BoolButtons = {
2013-07-29 02:11:00 +00:00
"Reset" , "Power" ,
2014-06-29 12:55:01 +00:00
"P1 Up" , "P1 Down" , "P1 Left" , "P1 Right" , "P1 Select" , "P1 Start" , "P1 Y" , "P1 B" , "P1 X" , "P1 A" , "P1 L" , "P1 R" ,
"P2 Up" , "P2 Down" , "P2 Left" , "P2 Right" , "P2 Select" , "P2 Start" , "P2 Y" , "P2 B" , "P2 X" , "P2 A" , "P2 L" , "P2 R" ,
2014-06-28 22:59:04 +00:00
// adelikat: disabling these since they aren't hooked up
2014-06-29 12:55:01 +00:00
// "P3 Up", "P3 Down", "P3 Left", "P3 Right", "P3 Select", "P3 Start", "P3 Y", "P3 B", "P3 X", "P3 A", "P3 L", "P3 R",
// "P4 Up", "P4 Down", "P4 Left", "P4 Right", "P4 Select", "P4 Start", "P4 Y", "P4 B", "P4 X", "P4 A", "P4 L", "P4 R",
2012-09-04 00:20:36 +00:00
}
} ;
2012-09-05 18:52:17 +00:00
2012-09-04 00:20:36 +00:00
int timeFrameCounter ;
2012-09-07 20:31:05 +00:00
public int Frame { get { return timeFrameCounter ; } set { timeFrameCounter = value ; } }
2012-09-04 00:20:36 +00:00
public int LagCount { get ; set ; }
public bool IsLagFrame { get ; private set ; }
2012-09-26 15:59:14 +00:00
public string SystemId { get ; private set ; }
2012-09-30 19:22:54 +00:00
2014-06-12 20:46:42 +00:00
public string BoardName { get ; private set ; }
2013-08-24 16:54:22 +00:00
2014-07-21 19:07:21 +00:00
// adelikat: Nasty hack to force new business logic. Compatibility (and Accuracy when fully supported) will ALWAYS be in deterministic mode,
// a consequence is a permanent performance hit to the compatibility core
// Perormance will NEVER be in deterministic mode (and the client side logic will prohibit movie recording on it)
2012-09-30 19:22:54 +00:00
public bool DeterministicEmulation
{
2014-11-19 00:32:51 +00:00
get { return CurrentProfile = = "Compatibility" | | CurrentProfile = = "Accuracy" ; }
2014-07-21 19:07:21 +00:00
private set { /* Do nothing */ }
2012-09-30 19:22:54 +00:00
}
2012-09-04 07:09:00 +00:00
public bool SaveRamModified
{
get
{
2013-11-22 09:33:56 +00:00
return api . QUERY_get_memory_size ( LibsnesApi . SNES_MEMORY . CARTRIDGE_RAM ) ! = 0 ;
2012-09-04 07:09:00 +00:00
}
}
2012-09-05 18:52:17 +00:00
2014-08-13 17:52:13 +00:00
public byte [ ] CloneSaveRam ( )
2012-09-16 17:15:53 +00:00
{
2013-11-22 09:33:56 +00:00
byte * buf = api . QUERY_get_memory_data ( LibsnesApi . SNES_MEMORY . CARTRIDGE_RAM ) ;
var size = api . QUERY_get_memory_size ( LibsnesApi . SNES_MEMORY . CARTRIDGE_RAM ) ;
2012-09-04 07:09:00 +00:00
var ret = new byte [ size ] ;
2012-12-25 20:36:04 +00:00
Marshal . Copy ( ( IntPtr ) buf , ret , 0 , size ) ;
2012-09-04 07:09:00 +00:00
return ret ;
}
2012-12-25 20:36:04 +00:00
//public byte[] snes_get_memory_data_read(LibsnesApi.SNES_MEMORY id)
//{
// var size = (int)api.snes_get_memory_size(id);
// if (size == 0) return new byte[0];
// var ret = api.snes_get_memory_data(id);
// return ret;
//}
2012-09-04 07:09:00 +00:00
public void StoreSaveRam ( byte [ ] data )
{
2013-11-22 09:33:56 +00:00
var size = api . QUERY_get_memory_size ( LibsnesApi . SNES_MEMORY . CARTRIDGE_RAM ) ;
2012-09-04 08:21:01 +00:00
if ( size = = 0 ) return ;
2012-12-25 20:36:04 +00:00
if ( size ! = data . Length ) throw new InvalidOperationException ( "Somehow, we got a mismatch between saveram size and what bsnes says the saveram size is" ) ;
2013-11-22 09:33:56 +00:00
byte * buf = api . QUERY_get_memory_data ( LibsnesApi . SNES_MEMORY . CARTRIDGE_RAM ) ;
2012-12-25 20:36:04 +00:00
Marshal . Copy ( data , 0 , ( IntPtr ) buf , size ) ;
2012-09-04 07:09:00 +00:00
}
2012-09-04 00:20:36 +00:00
2013-11-03 16:29:51 +00:00
public void ResetCounters ( )
2012-11-25 15:41:40 +00:00
{
timeFrameCounter = 0 ;
LagCount = 0 ;
IsLagFrame = false ;
}
2012-10-08 14:37:42 +00:00
#region savestates
/// <summary>
/// can freeze a copy of a controller input set and serialize\deserialize it
/// </summary>
2012-12-29 01:25:06 +00:00
public class SnesSaveController : IController
2012-10-08 14:37:42 +00:00
{
// this is all rather general, so perhaps should be moved out of LibsnesCore
ControllerDefinition def ;
public SnesSaveController ( )
{
this . def = null ;
}
public SnesSaveController ( ControllerDefinition def )
{
this . def = def ;
}
WorkingDictionary < string , float > buttons = new WorkingDictionary < string , float > ( ) ;
/// <summary>
/// invalid until CopyFrom has been called
/// </summary>
public ControllerDefinition Type
{
get { return def ; }
}
public void Serialize ( BinaryWriter b )
{
b . Write ( buttons . Keys . Count ) ;
foreach ( var k in buttons . Keys )
{
b . Write ( k ) ;
b . Write ( buttons [ k ] ) ;
}
}
/// <summary>
/// no checking to see if the deserialized controls match any definition
/// </summary>
/// <param name="b"></param>
public void DeSerialize ( BinaryReader b )
{
buttons . Clear ( ) ;
int numbuttons = b . ReadInt32 ( ) ;
for ( int i = 0 ; i < numbuttons ; i + + )
{
string k = b . ReadString ( ) ;
float v = b . ReadSingle ( ) ;
buttons . Add ( k , v ) ;
}
}
/// <summary>
/// this controller's definition changes to that of source
/// </summary>
/// <param name="source"></param>
public void CopyFrom ( IController source )
{
this . def = source . Type ;
buttons . Clear ( ) ;
foreach ( var k in def . BoolButtons )
buttons . Add ( k , source . IsPressed ( k ) ? 1.0f : 0 ) ;
foreach ( var k in def . FloatControls )
{
if ( buttons . Keys . Contains ( k ) )
throw new Exception ( "name collision between bool and float lists!" ) ;
buttons . Add ( k , source . GetFloat ( k ) ) ;
}
}
2012-12-29 01:25:06 +00:00
public void Clear ( )
{
buttons . Clear ( ) ;
}
public void Set ( string button )
{
buttons [ button ] = 1.0f ;
}
2012-10-08 14:37:42 +00:00
public bool this [ string button ]
{
get { return buttons [ button ] ! = 0 ; }
}
public bool IsPressed ( string button )
{
return buttons [ button ] ! = 0 ;
}
public float GetFloat ( string name )
{
return buttons [ name ] ;
}
}
2012-09-04 08:21:01 +00:00
public void SaveStateText ( TextWriter writer )
{
var temp = SaveStateBinary ( ) ;
2013-08-05 12:57:08 +00:00
temp . SaveAsHexFast ( writer ) ;
2013-03-17 18:11:30 +00:00
writer . WriteLine ( "Frame {0}" , Frame ) ; // we don't parse this, it's only for the client to use
2014-11-19 00:32:51 +00:00
writer . WriteLine ( "Profile {0}" , CurrentProfile ) ;
2012-09-04 08:21:01 +00:00
}
public void LoadStateText ( TextReader reader )
{
string hex = reader . ReadLine ( ) ;
byte [ ] state = new byte [ hex . Length / 2 ] ;
2013-08-05 12:57:08 +00:00
state . ReadFromHexFast ( hex ) ;
2012-09-04 08:21:01 +00:00
LoadStateBinary ( new BinaryReader ( new MemoryStream ( state ) ) ) ;
2013-03-17 18:11:30 +00:00
reader . ReadLine ( ) ; // Frame #
2012-12-25 20:36:04 +00:00
var profile = reader . ReadLine ( ) . Split ( ' ' ) [ 1 ] ;
ValidateLoadstateProfile ( profile ) ;
2012-09-04 08:21:01 +00:00
}
2012-09-05 18:52:17 +00:00
2012-09-04 08:21:01 +00:00
public void SaveStateBinary ( BinaryWriter writer )
{
2012-10-08 14:37:42 +00:00
if ( ! DeterministicEmulation )
writer . Write ( CoreSaveState ( ) ) ;
else
writer . Write ( savestatebuff ) ;
2012-09-11 01:50:55 +00:00
// other variables
writer . Write ( IsLagFrame ) ;
writer . Write ( LagCount ) ;
writer . Write ( Frame ) ;
2014-11-19 00:32:51 +00:00
writer . Write ( CurrentProfile ) ;
2012-09-11 01:50:55 +00:00
2012-09-04 08:21:01 +00:00
writer . Flush ( ) ;
}
public void LoadStateBinary ( BinaryReader reader )
{
2013-11-22 09:33:56 +00:00
int size = api . QUERY_serialize_size ( ) ;
2012-09-11 01:50:55 +00:00
byte [ ] buf = reader . ReadBytes ( size ) ;
2012-09-30 19:22:54 +00:00
CoreLoadState ( buf ) ;
2012-09-11 01:50:55 +00:00
2012-10-08 14:37:42 +00:00
if ( DeterministicEmulation ) // deserialize controller and fast-foward now
{
// reconstruct savestatebuff at the same time to avoid a costly core serialize
MemoryStream ms = new MemoryStream ( ) ;
BinaryWriter bw = new BinaryWriter ( ms ) ;
bw . Write ( buf ) ;
bool framezero = reader . ReadBoolean ( ) ;
bw . Write ( framezero ) ;
if ( ! framezero )
{
SnesSaveController ssc = new SnesSaveController ( ControllerDefinition ) ;
ssc . DeSerialize ( reader ) ;
IController tmp = this . Controller ;
this . Controller = ssc ;
nocallbacks = true ;
FrameAdvance ( false , false ) ;
nocallbacks = false ;
this . Controller = tmp ;
ssc . Serialize ( bw ) ;
}
2012-10-08 18:18:43 +00:00
else // hack: dummy controller info
{
bw . Write ( reader . ReadBytes ( 536 ) ) ;
}
2012-10-08 14:37:42 +00:00
bw . Close ( ) ;
savestatebuff = ms . ToArray ( ) ;
}
2012-09-11 01:50:55 +00:00
// other variables
IsLagFrame = reader . ReadBoolean ( ) ;
LagCount = reader . ReadInt32 ( ) ;
Frame = reader . ReadInt32 ( ) ;
2012-12-25 20:36:04 +00:00
var profile = reader . ReadString ( ) ;
ValidateLoadstateProfile ( profile ) ;
}
void ValidateLoadstateProfile ( string profile )
{
2014-11-19 00:32:51 +00:00
if ( profile ! = CurrentProfile )
2012-12-25 20:36:04 +00:00
{
2014-11-19 00:32:51 +00:00
throw new InvalidOperationException ( string . Format ( "You've attempted to load a savestate made using a different SNES profile ({0}) than your current configuration ({1}). We COULD automatically switch for you, but we havent done that yet. This error is to make sure you know that this isnt going to work right now." , profile , CurrentProfile ) ) ;
2012-12-25 20:36:04 +00:00
}
2012-09-04 08:21:01 +00:00
}
2012-12-25 20:36:04 +00:00
2012-09-04 08:21:01 +00:00
public byte [ ] SaveStateBinary ( )
{
MemoryStream ms = new MemoryStream ( ) ;
BinaryWriter bw = new BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
return ms . ToArray ( ) ;
}
2012-09-04 00:20:36 +00:00
2013-05-06 20:51:28 +00:00
public bool BinarySaveStatesPreferred { get { return true ; } }
2012-09-30 19:22:54 +00:00
/// <summary>
/// handle the unmanaged part of loadstating
/// </summary>
void CoreLoadState ( byte [ ] data )
{
2013-11-22 09:33:56 +00:00
int size = api . QUERY_serialize_size ( ) ;
2012-09-30 19:22:54 +00:00
if ( data . Length ! = size )
2012-12-01 06:21:53 +00:00
throw new Exception ( "Libsnes internal savestate size mismatch!" ) ;
2013-11-22 09:33:56 +00:00
api . CMD_init ( ) ;
2014-09-02 05:01:51 +00:00
//zero 01-sep-2014 - this approach isn't being used anymore, it's too slow!
//LoadCurrent(); //need to make sure chip roms are reloaded
2012-09-30 19:22:54 +00:00
fixed ( byte * pbuf = & data [ 0 ] )
2013-11-22 09:33:56 +00:00
api . CMD_unserialize ( new IntPtr ( pbuf ) , size ) ;
2012-09-30 19:22:54 +00:00
}
2013-12-29 02:20:13 +00:00
2012-09-30 19:22:54 +00:00
/// <summary>
/// handle the unmanaged part of savestating
/// </summary>
byte [ ] CoreSaveState ( )
{
2013-11-22 09:33:56 +00:00
int size = api . QUERY_serialize_size ( ) ;
2012-10-08 14:37:42 +00:00
byte [ ] buf = new byte [ size ] ;
fixed ( byte * pbuf = & buf [ 0 ] )
2013-11-22 09:33:56 +00:00
api . CMD_serialize ( new IntPtr ( pbuf ) , size ) ;
2012-10-08 14:37:42 +00:00
return buf ;
2012-09-30 19:22:54 +00:00
}
/// <summary>
2012-10-08 14:37:42 +00:00
/// most recent internal savestate, for deterministic mode ONLY
2012-09-30 19:22:54 +00:00
/// </summary>
byte [ ] savestatebuff ;
2012-10-08 14:37:42 +00:00
#endregion
2012-09-30 19:22:54 +00:00
2012-12-10 00:43:43 +00:00
public CoreComm CoreComm { get ; private set ; }
2012-09-04 00:20:36 +00:00
2014-09-08 15:43:34 +00:00
// works for WRAM, garbage for anything else
static int? FakeBusMap ( int addr )
{
addr & = 0xffffff ;
int bank = addr > > 16 ;
if ( bank = = 0x7e | | bank = = 0x7f )
return addr & 0x1ffff ;
bank & = 0x7f ;
int low = addr & 0xffff ;
if ( bank < 0x40 & & low < 0x2000 )
return low ;
return null ;
}
unsafe void MakeFakeBus ( )
{
int size = api . QUERY_get_memory_size ( LibsnesApi . SNES_MEMORY . WRAM ) ;
if ( size ! = 0x20000 )
throw new InvalidOperationException ( ) ;
byte * blockptr = api . QUERY_get_memory_data ( LibsnesApi . SNES_MEMORY . WRAM ) ;
var md = new MemoryDomain ( "BUS" , 0x1000000 , MemoryDomain . Endian . Little ,
( addr ) = >
{
var a = FakeBusMap ( addr ) ;
if ( a . HasValue )
return blockptr [ a . Value ] ;
else
return 0 ;
} ,
( addr , val ) = >
{
var a = FakeBusMap ( addr ) ;
if ( a . HasValue )
blockptr [ a . Value ] = val ;
} ) ;
_memoryDomains . Add ( md ) ;
}
2012-09-04 00:20:36 +00:00
// ----- Client Debugging API stuff -----
2013-11-04 02:11:40 +00:00
unsafe MemoryDomain MakeMemoryDomain ( string name , LibsnesApi . SNES_MEMORY id , MemoryDomain . Endian endian )
2012-09-04 17:29:20 +00:00
{
2013-11-22 09:33:56 +00:00
int size = api . QUERY_get_memory_size ( id ) ;
2012-09-04 17:29:20 +00:00
int mask = size - 1 ;
2014-02-26 09:33:38 +00:00
bool pow2 = Util . IsPowerOfTwo ( size ) ;
2012-12-26 18:25:45 +00:00
2013-01-14 07:22:36 +00:00
//if this type of memory isnt available, dont make the memory domain (most commonly save ram)
if ( size = = 0 )
return null ;
2013-11-22 09:33:56 +00:00
byte * blockptr = api . QUERY_get_memory_data ( id ) ;
2012-12-26 18:25:45 +00:00
2012-09-04 17:29:20 +00:00
MemoryDomain md ;
2012-09-05 18:52:17 +00:00
2012-12-26 18:25:45 +00:00
if ( id = = LibsnesApi . SNES_MEMORY . OAM )
2012-09-04 17:29:20 +00:00
{
2012-12-26 18:25:45 +00:00
//OAM is actually two differently sized banks of memory which arent truly considered adjacent.
//maybe a better way to visualize it is with an empty bus and adjacent banks
//so, we just throw away everything above its size of 544 bytes
if ( size ! = 544 ) throw new InvalidOperationException ( "oam size isnt 544 bytes.. wtf?" ) ;
2012-09-04 17:29:20 +00:00
md = new MemoryDomain ( name , size , endian ,
2012-12-26 18:25:45 +00:00
( addr ) = > ( addr < 544 ) ? blockptr [ addr ] : ( byte ) 0x00 ,
( addr , value ) = > { if ( addr < 544 ) blockptr [ addr ] = value ; }
) ;
2012-09-04 17:29:20 +00:00
}
2014-02-26 09:33:38 +00:00
else if ( pow2 )
2012-09-04 17:29:20 +00:00
md = new MemoryDomain ( name , size , endian ,
2012-12-26 18:25:45 +00:00
( addr ) = > blockptr [ addr & mask ] ,
( addr , value ) = > blockptr [ addr & mask ] = value ) ;
2014-02-26 09:33:38 +00:00
else
md = new MemoryDomain ( name , size , endian ,
( addr ) = > blockptr [ addr % size ] ,
( addr , value ) = > blockptr [ addr % size ] = value ) ;
2012-09-04 17:29:20 +00:00
2013-11-06 02:15:29 +00:00
_memoryDomains . Add ( md ) ;
2012-09-04 17:29:20 +00:00
return md ;
}
2013-08-26 07:17:47 +00:00
void SetupMemoryDomains ( byte [ ] romData , byte [ ] sgbRomData )
2012-09-04 17:29:20 +00:00
{
2013-05-14 21:33:36 +00:00
// remember, MainMemory must always be the same as MemoryDomains[0], else GIANT DRAGONS
2013-08-26 07:17:47 +00:00
//<zeromus> - this is stupid.
2012-09-05 18:52:17 +00:00
2013-08-26 07:17:47 +00:00
//lets just do this entirely differently for SGB
if ( IsSGB )
2013-04-24 22:09:11 +00:00
{
2013-08-26 07:17:47 +00:00
//NOTE: CGB has 32K of wram, and DMG has 8KB of wram. Not sure how to control this right now.. bsnes might not have any ready way of doign that? I couldnt spot it.
//You wouldnt expect a DMG game to access excess wram, but what if it tried to? maybe an oversight in bsnes?
2013-11-04 02:11:40 +00:00
MakeMemoryDomain ( "SGB WRAM" , LibsnesApi . SNES_MEMORY . SGB_WRAM , MemoryDomain . Endian . Little ) ;
2013-08-26 07:17:47 +00:00
2013-11-04 02:11:40 +00:00
var romDomain = new MemoryDomain ( "SGB CARTROM" , romData . Length , MemoryDomain . Endian . Little ,
2013-04-24 22:09:11 +00:00
( addr ) = > romData [ addr ] ,
( addr , value ) = > romData [ addr ] = value ) ;
2013-11-06 02:15:29 +00:00
_memoryDomains . Add ( romDomain ) ;
2013-08-26 07:17:47 +00:00
//the last 1 byte of this is special.. its an interrupt enable register, instead of ram. weird. maybe its actually ram and just getting specially used?
2013-11-04 02:11:40 +00:00
MakeMemoryDomain ( "SGB HRAM" , LibsnesApi . SNES_MEMORY . SGB_HRAM , MemoryDomain . Endian . Little ) ;
2012-12-27 18:47:15 +00:00
2013-11-04 02:11:40 +00:00
MakeMemoryDomain ( "SGB CARTRAM" , LibsnesApi . SNES_MEMORY . SGB_CARTRAM , MemoryDomain . Endian . Little ) ;
2012-10-03 14:54:32 +00:00
2013-11-04 02:11:40 +00:00
MainMemory = MakeMemoryDomain ( "WRAM" , LibsnesApi . SNES_MEMORY . WRAM , MemoryDomain . Endian . Little ) ;
2013-08-26 07:17:47 +00:00
2013-11-04 02:11:40 +00:00
var sgbromDomain = new MemoryDomain ( "SGB.SFC ROM" , sgbRomData . Length , MemoryDomain . Endian . Little ,
2013-08-26 07:17:47 +00:00
( addr ) = > sgbRomData [ addr ] ,
( addr , value ) = > sgbRomData [ addr ] = value ) ;
2013-11-06 02:15:29 +00:00
_memoryDomains . Add ( sgbromDomain ) ;
2013-08-26 07:17:47 +00:00
}
else
2012-12-27 18:47:15 +00:00
{
2013-11-04 02:11:40 +00:00
MainMemory = MakeMemoryDomain ( "WRAM" , LibsnesApi . SNES_MEMORY . WRAM , MemoryDomain . Endian . Little ) ;
2013-08-26 07:17:47 +00:00
2014-01-29 21:59:06 +00:00
MakeMemoryDomain ( "CARTROM" , LibsnesApi . SNES_MEMORY . CARTRIDGE_ROM , MemoryDomain . Endian . Little ) ;
2013-11-04 02:11:40 +00:00
MakeMemoryDomain ( "CARTRAM" , LibsnesApi . SNES_MEMORY . CARTRIDGE_RAM , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "VRAM" , LibsnesApi . SNES_MEMORY . VRAM , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "OAM" , LibsnesApi . SNES_MEMORY . OAM , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "CGRAM" , LibsnesApi . SNES_MEMORY . CGRAM , MemoryDomain . Endian . Little ) ;
MakeMemoryDomain ( "APURAM" , LibsnesApi . SNES_MEMORY . APURAM , MemoryDomain . Endian . Little ) ;
2012-12-27 18:47:15 +00:00
if ( ! DeterministicEmulation )
2014-09-08 15:43:34 +00:00
{
2013-11-06 02:15:29 +00:00
_memoryDomains . Add ( new MemoryDomain ( "BUS" , 0x1000000 , MemoryDomain . Endian . Little ,
2013-11-22 09:33:56 +00:00
( addr ) = > api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ,
( addr , val ) = > api . QUERY_poke ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr , val ) ) ) ;
2014-09-08 15:43:34 +00:00
}
else
{
// limited function bus
MakeFakeBus ( ) ;
}
2012-12-27 18:47:15 +00:00
}
2013-11-06 02:15:29 +00:00
MemoryDomains = new MemoryDomainList ( _memoryDomains ) ;
2012-09-04 17:29:20 +00:00
}
2013-08-26 07:17:47 +00:00
2013-11-06 02:15:29 +00:00
private MemoryDomain MainMemory ;
private List < MemoryDomain > _memoryDomains = new List < MemoryDomain > ( ) ;
public MemoryDomainList MemoryDomains { get ; private set ; }
2012-09-04 01:21:14 +00:00
2012-09-05 18:52:17 +00:00
#region audio stuff
2012-09-04 01:21:14 +00:00
2013-11-14 19:33:13 +00:00
SpeexResampler resampler ;
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
2012-09-07 20:12:47 +00:00
void InitAudio ( )
{
2013-11-14 19:33:13 +00:00
resampler = new SpeexResampler ( 6 , 64081 , 88200 , 32041 , 44100 ) ;
2012-09-07 20:12:47 +00:00
}
2012-09-04 01:21:14 +00:00
2012-09-05 18:52:17 +00:00
void snes_audio_sample ( ushort left , ushort right )
{
2012-09-30 18:21:32 +00:00
resampler . EnqueueSample ( ( short ) left , ( short ) right ) ;
2012-09-05 18:52:17 +00:00
}
2012-09-04 01:21:14 +00:00
sound api changes. added a new ISyncSoundProvider, which works similarly to ISoundProvider except the source (not the sink) determines the number of samples to process. Added facilities to metaspu, dcfilter, speexresampler to work with ISyncSoundProvider. Add ISyncSoundProvider to IEmulator. All IEmulators must provide sync sound, but they need not provide async sound. When async is needed and an IEmulator doesn't provide it, the frontend will wrap it in a vecna metaspu. SNES, GB changed to provide sync sound only. All other emulator cores mostly unchanged; they just provide stub fakesync alongside async, for now. For the moment, the only use of the sync sound is for realtime audio throttling, where it works and sounds quite nice. In the future, sync sound will be supported for AV dumping as well.
2012-10-11 00:44:59 +00:00
public ISoundProvider SoundProvider { get { return null ; } }
public ISyncSoundProvider SyncSoundProvider { get { return resampler ; } }
public bool StartAsyncSound ( ) { return false ; }
public void EndAsyncSound ( ) { }
2012-09-05 18:52:17 +00:00
#endregion audio stuff
2013-12-22 00:44:39 +00:00
2013-12-27 17:59:19 +00:00
void RefreshPalette ( )
{
SetPalette ( ( SnesColors . ColorType ) Enum . Parse ( typeof ( SnesColors . ColorType ) , Settings . Palette , false ) ) ;
}
SnesSettings Settings ;
SnesSyncSettings SyncSettings ;
2014-10-19 01:22:47 +00:00
public SnesSettings GetSettings ( ) { return Settings . Clone ( ) ; }
public SnesSyncSettings GetSyncSettings ( ) { return SyncSettings . Clone ( ) ; }
public bool PutSettings ( SnesSettings o )
2013-12-27 17:59:19 +00:00
{
2014-10-19 01:22:47 +00:00
bool refreshneeded = o . Palette ! = Settings . Palette ;
Settings = o ;
2013-12-27 17:59:19 +00:00
if ( refreshneeded )
RefreshPalette ( ) ;
return false ;
}
2014-10-19 01:22:47 +00:00
public bool PutSyncSettings ( SnesSyncSettings o )
2013-12-27 17:59:19 +00:00
{
2014-10-19 01:22:47 +00:00
bool ret = o . Profile ! = SyncSettings . Profile ;
SyncSettings = o ;
2013-12-27 17:59:19 +00:00
return ret ;
}
public class SnesSettings
{
public bool ShowBG1_0 = true ;
public bool ShowBG2_0 = true ;
public bool ShowBG3_0 = true ;
public bool ShowBG4_0 = true ;
public bool ShowBG1_1 = true ;
public bool ShowBG2_1 = true ;
public bool ShowBG3_1 = true ;
public bool ShowBG4_1 = true ;
public bool ShowOBJ_0 = true ;
public bool ShowOBJ_1 = true ;
public bool ShowOBJ_2 = true ;
public bool ShowOBJ_3 = true ;
public bool UseRingBuffer = true ;
public bool AlwaysDoubleSize = false ;
public string Palette = "BizHawk" ;
public SnesSettings Clone ( )
{
return ( SnesSettings ) MemberwiseClone ( ) ;
}
}
public class SnesSyncSettings
{
2014-05-18 13:44:58 +00:00
public string Profile = "Performance" ; // "Accuracy", and "Compatibility" are the other choicec, todo: make this an enum
2013-12-27 17:59:19 +00:00
public SnesSyncSettings Clone ( )
{
return ( SnesSyncSettings ) MemberwiseClone ( ) ;
}
}
2012-09-04 00:20:36 +00:00
}
2014-11-18 23:44:42 +00:00
public class ScanlineHookManager
{
public void Register ( object tag , Action < int > callback )
{
var rr = new RegistrationRecord ( ) ;
rr . tag = tag ;
rr . callback = callback ;
Unregister ( tag ) ;
records . Add ( rr ) ;
OnHooksChanged ( ) ;
}
public int HookCount { get { return records . Count ; } }
public virtual void OnHooksChanged ( ) { }
public void Unregister ( object tag )
{
records . RemoveAll ( ( r ) = > r . tag = = tag ) ;
}
public void HandleScanline ( int scanline )
{
foreach ( var rr in records ) rr . callback ( scanline ) ;
}
List < RegistrationRecord > records = new List < RegistrationRecord > ( ) ;
class RegistrationRecord
{
public object tag ;
public int scanline = 0 ;
public Action < int > callback ;
}
}
2012-09-30 18:21:32 +00:00
}