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 ;
using System.Diagnostics ;
using System.Globalization ;
using System.IO ;
using System.Collections.Generic ;
using System.Runtime.InteropServices ;
namespace BizHawk.Emulation.Consoles.Nintendo.SNES
{
public unsafe static class LibsnesDll
{
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public static extern string snes_library_id ( ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 18:04:06 +00:00
public static extern int snes_library_revision_major ( ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 18:04:06 +00:00
public static extern int snes_library_revision_minor ( ) ;
2012-09-04 00:20:36 +00:00
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public static extern void snes_init ( ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public static extern void snes_power ( ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-16 17:15:53 +00:00
public static extern void snes_reset ( ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public static extern void snes_run ( ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public static extern void snes_term ( ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-09 21:19:54 +00:00
public static extern void snes_unload_cartridge ( ) ;
2012-09-04 00:20:36 +00:00
2012-09-27 07:22:31 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void snes_set_cartridge_basename ( string basename ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-26 15:59:14 +00:00
[return: MarshalAs(UnmanagedType.U1)]
public static extern bool snes_load_cartridge_normal (
2012-09-04 00:20:36 +00:00
[MarshalAs(UnmanagedType.LPStr)]
2012-09-30 18:21:32 +00:00
string rom_xml ,
2012-09-04 00:20:36 +00:00
[MarshalAs(UnmanagedType.LPArray)]
2012-09-30 18:21:32 +00:00
byte [ ] rom_data ,
2012-09-26 15:59:14 +00:00
uint rom_size ) ;
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.U1)]
public static extern bool snes_load_cartridge_super_game_boy (
[MarshalAs(UnmanagedType.LPStr)]
string rom_xml ,
[MarshalAs(UnmanagedType.LPArray)]
byte [ ] rom_data ,
uint rom_size ,
[MarshalAs(UnmanagedType.LPStr)]
string dmg_xml ,
[MarshalAs(UnmanagedType.LPArray)]
byte [ ] dmg_data ,
uint dmg_size ) ;
2012-09-04 00:20:36 +00:00
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
2012-09-30 18:21:32 +00:00
public delegate void snes_video_refresh_t ( int * data , int width , int height ) ;
2012-09-04 00:20:36 +00:00
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
2012-09-04 01:21:14 +00:00
public delegate void snes_input_poll_t ( ) ;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public delegate ushort snes_input_state_t ( int port , int device , int index , int id ) ;
2012-09-04 01:21:14 +00:00
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
2012-09-23 15:57:01 +00:00
public delegate void snes_input_notify_t ( int index ) ;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
2012-09-04 01:21:14 +00:00
public delegate void snes_audio_sample_t ( ushort left , ushort right ) ;
2012-09-22 05:03:52 +00:00
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void snes_scanlineStart_t ( int line ) ;
2012-09-27 07:22:31 +00:00
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate string snes_path_request_t ( int slot , string hint ) ;
2012-09-04 00:20:36 +00:00
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public static extern void snes_set_video_refresh ( snes_video_refresh_t video_refresh ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public static extern void snes_set_input_poll ( snes_input_poll_t input_poll ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 00:20:36 +00:00
public static extern void snes_set_input_state ( snes_input_state_t input_state ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-23 15:57:01 +00:00
public static extern void snes_set_input_notify ( snes_input_notify_t input_notify ) ;
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 01:21:14 +00:00
public static extern void snes_set_audio_sample ( snes_audio_sample_t audio_sample ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void snes_set_scanlineStart ( snes_scanlineStart_t scanlineStart ) ;
2012-09-27 07:22:31 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void snes_set_path_request ( snes_path_request_t scanlineStart ) ;
2012-09-04 00:20:36 +00:00
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 06:08:46 +00:00
[return: MarshalAs(UnmanagedType.U1)]
public static extern bool snes_check_cartridge (
[MarshalAs(UnmanagedType.LPArray)] byte [ ] rom_data ,
int rom_size ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 07:09:00 +00:00
[return: MarshalAs(UnmanagedType.U1)]
public static extern SNES_REGION snes_get_region ( ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 17:29:20 +00:00
public static extern int snes_get_memory_size ( SNES_MEMORY id ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 07:09:00 +00:00
public static extern IntPtr snes_get_memory_data ( SNES_MEMORY id ) ;
2012-09-04 08:21:01 +00:00
2012-10-03 14:54:32 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern byte bus_read ( uint addr ) ;
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void bus_write ( uint addr , byte val ) ;
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-05 01:30:09 +00:00
public static extern int snes_serialize_size ( ) ;
2012-09-30 18:21:32 +00:00
2012-09-05 01:30:09 +00:00
[return: MarshalAs(UnmanagedType.U1)]
2012-09-30 18:21:32 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool snes_serialize ( IntPtr data , int size ) ;
2012-09-04 08:21:01 +00:00
[return: MarshalAs(UnmanagedType.U1)]
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 08:21:01 +00:00
public static extern bool snes_unserialize ( IntPtr data , int size ) ;
2012-09-04 19:12:16 +00:00
2012-09-27 01:38:27 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int snes_poll_message ( ) ;
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void snes_dequeue_message ( IntPtr strBuffer ) ;
public static bool HasMessage { get { return snes_poll_message ( ) ! = - 1 ; } }
public static string DequeueMessage ( )
{
int len = snes_poll_message ( ) ;
sbyte * temp = stackalloc sbyte [ len + 1 ] ;
temp [ len ] = 0 ;
snes_dequeue_message ( new IntPtr ( temp ) ) ;
return new string ( temp ) ;
}
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-04 19:12:16 +00:00
public static extern void snes_set_layer_enable ( int layer , int priority ,
[MarshalAs(UnmanagedType.U1)]
bool enable
) ;
2012-09-06 08:32:25 +00:00
2012-09-22 05:03:52 +00:00
[DllImport("libsneshawk.dll", CallingConvention = CallingConvention.Cdecl)]
2012-09-06 08:32:25 +00:00
public static extern int snes_peek_logical_register ( SNES_REG reg ) ;
public enum SNES_REG : int
{
//$2105
BG_MODE = 0 ,
BG3_PRIORITY = 1 ,
BG1_TILESIZE = 2 ,
BG2_TILESIZE = 3 ,
BG3_TILESIZE = 4 ,
BG4_TILESIZE = 5 ,
//$2107
BG1_SCADDR = 10 ,
BG1_SCSIZE = 11 ,
//$2108
BG2_SCADDR = 12 ,
BG2_SCSIZE = 13 ,
//$2109
BG3_SCADDR = 14 ,
BG3_SCSIZE = 15 ,
//$210A
BG4_SCADDR = 16 ,
BG4_SCSIZE = 17 ,
//$210B
BG1_TDADDR = 20 ,
BG2_TDADDR = 21 ,
//$210C
BG3_TDADDR = 22 ,
2012-09-24 06:47:34 +00:00
BG4_TDADDR = 23 ,
//$2133 SETINI
2012-09-26 15:59:14 +00:00
SETINI_MODE7_EXTBG = 30 ,
SETINI_HIRES = 31 ,
SETINI_OVERSCAN = 32 ,
SETINI_OBJ_INTERLACE = 33 ,
2012-09-24 07:46:54 +00:00
SETINI_SCREEN_INTERLACE = 34 ,
2012-09-26 15:59:14 +00:00
//$2130 CGWSEL
CGWSEL_COLORMASK = 40 ,
CGWSEL_COLORSUBMASK = 41 ,
CGWSEL_ADDSUBMODE = 42 ,
2012-09-24 07:46:54 +00:00
CGWSEL_DIRECTCOLOR = 43 ,
2012-09-06 08:32:25 +00:00
}
2012-09-30 18:21:32 +00:00
2012-09-04 07:09:00 +00:00
public enum SNES_MEMORY : uint
{
CARTRIDGE_RAM = 0 ,
CARTRIDGE_RTC = 1 ,
BSX_RAM = 2 ,
BSX_PRAM = 3 ,
SUFAMI_TURBO_A_RAM = 4 ,
SUFAMI_TURBO_B_RAM = 5 ,
GAME_BOY_RAM = 6 ,
GAME_BOY_RTC = 7 ,
WRAM = 100 ,
APURAM = 101 ,
VRAM = 102 ,
OAM = 103 ,
CGRAM = 104 ,
}
2012-09-30 18:21:32 +00:00
public enum SNES_REGION : byte
2012-09-04 00:20:36 +00:00
{
2012-09-04 07:09:00 +00:00
NTSC = 0 ,
PAL = 1 ,
}
public enum SNES_DEVICE : uint
{
NONE = 0 ,
JOYPAD = 1 ,
MULTITAP = 2 ,
MOUSE = 3 ,
SUPER_SCOPE = 4 ,
JUSTIFIER = 5 ,
JUSTIFIERS = 6 ,
SERIAL_CABLE = 7
2012-09-04 00:20:36 +00:00
}
public enum SNES_DEVICE_ID : uint
{
JOYPAD_B = 0 ,
JOYPAD_Y = 1 ,
JOYPAD_SELECT = 2 ,
JOYPAD_START = 3 ,
JOYPAD_UP = 4 ,
JOYPAD_DOWN = 5 ,
JOYPAD_LEFT = 6 ,
JOYPAD_RIGHT = 7 ,
JOYPAD_A = 8 ,
JOYPAD_X = 9 ,
JOYPAD_L = 10 ,
JOYPAD_R = 11
}
}
2012-09-22 05:03:52 +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 ( ) ;
}
2012-09-06 08:32:25 +00:00
2012-09-22 05:03:52 +00:00
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 ;
public Action < int > callback ;
}
}
2012-09-30 18:21:32 +00:00
2012-09-04 01:21:14 +00:00
public unsafe class LibsnesCore : IEmulator , IVideoProvider , ISoundProvider
2012-09-04 00:20:36 +00:00
{
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 ;
2012-09-16 17:15:53 +00:00
disposedSaveRam = ReadSaveRam ( ) ;
2012-09-05 23:16:08 +00:00
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_video_refresh ( null ) ;
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_input_poll ( null ) ;
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_input_state ( null ) ;
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_audio_sample ( null ) ;
2012-09-22 05:03:52 +00:00
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_scanlineStart ( null ) ;
2012-09-05 23:16:08 +00:00
2012-09-09 21:19:54 +00:00
LibsnesDll . snes_unload_cartridge ( ) ;
2012-09-04 00:20:36 +00:00
LibsnesDll . snes_term ( ) ;
2012-09-08 01:15:16 +00:00
resampler . Dispose ( ) ;
2012-09-04 00:20:36 +00:00
}
2012-09-16 17:15:53 +00:00
//save the save memory before disposing the core, so we can pull from it in the future after the core is terminated
//that will be necessary to get it saving to disk
byte [ ] disposedSaveRam ;
2012-09-05 23:16:08 +00:00
//we can only have one active snes core at a time, due to libsnes being so static.
//so we'll track the current one here and detach the previous one whenever a new one is booted up.
static LibsnesCore CurrLibsnesCore ;
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 ;
if ( ScanlineHookManager . HookCount = = 0 ) LibsnesDll . snes_set_scanlineStart ( null ) ;
else LibsnesDll . snes_set_scanlineStart ( scanlineStart_cb ) ;
}
void snes_scanlineStart ( int line )
{
ScanlineHookManager . HandleScanline ( line ) ;
}
2012-09-27 07:22:31 +00:00
string snes_path_request_t ( int slot , string hint )
{
//every rom requests this byuu homemade rom
if ( hint = = "msu1.rom" ) return "" ;
//build romfilename
2012-09-27 11:58:04 +00:00
string test = Path . Combine ( CoreInputComm . SNES_FirmwaresPath ? ? "" , hint ) ;
2012-09-27 07:22:31 +00:00
//does it exist?
if ( ! File . Exists ( test ) )
{
System . Windows . Forms . MessageBox . Show ( "libsneshawk is requesting a firmware file which could not be found. make sure it's in your snes firmwares folder. the name is: " + hint ) ;
return "" ;
}
//return the path we built
return test ;
}
public LibsnesCore ( )
{
}
public void Load ( GameInfo game , byte [ ] romData , byte [ ] sgbRomData )
2012-09-04 00:20:36 +00:00
{
2012-09-05 23:16:08 +00:00
//attach this core as the current
2012-09-30 18:21:32 +00:00
if ( CurrLibsnesCore ! = null )
2012-09-05 23:16:08 +00:00
CurrLibsnesCore . Dispose ( ) ;
CurrLibsnesCore = this ;
2012-09-22 05:03:52 +00:00
ScanlineHookManager = new MyScanlineHookManager ( this ) ;
2012-09-04 18:04:06 +00:00
LibsnesDll . snes_init ( ) ;
2012-09-27 07:22:31 +00:00
//LibsnesDll.snes_set_cartridge_basename(@);
2012-09-05 23:16:08 +00:00
vidcb = new LibsnesDll . snes_video_refresh_t ( snes_video_refresh ) ;
2012-09-04 00:20:36 +00:00
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_video_refresh ( vidcb ) ;
2012-09-05 23:16:08 +00:00
pollcb = new LibsnesDll . snes_input_poll_t ( snes_input_poll ) ;
2012-09-04 00:20:36 +00:00
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_input_poll ( pollcb ) ;
2012-09-05 23:16:08 +00:00
inputcb = new LibsnesDll . snes_input_state_t ( snes_input_state ) ;
2012-09-04 00:20:36 +00:00
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_input_state ( inputcb ) ;
2012-09-04 01:21:14 +00:00
2012-09-23 15:57:01 +00:00
notifycb = new LibsnesDll . snes_input_notify_t ( snes_input_notify ) ;
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_input_notify ( notifycb ) ;
2012-09-05 23:16:08 +00:00
soundcb = new LibsnesDll . snes_audio_sample_t ( snes_audio_sample ) ;
2012-09-04 01:21:14 +00:00
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_audio_sample ( soundcb ) ;
2012-09-27 07:22:31 +00:00
pathRequest_cb = new LibsnesDll . snes_path_request_t ( snes_path_request_t ) ;
BizHawk . Emulation . Consoles . Nintendo . SNES . LibsnesDll . snes_set_path_request ( pathRequest_cb ) ;
2012-09-22 05:03:52 +00:00
scanlineStart_cb = new LibsnesDll . snes_scanlineStart_t ( snes_scanlineStart ) ;
2012-09-05 21:21:35 +00:00
// start up audio resampler
2012-09-07 20:12:47 +00:00
InitAudio ( ) ;
2012-09-05 21:21:35 +00:00
2012-09-04 17:29:20 +00:00
//strip header
if ( ( romData . Length & 0x7FFF ) = = 512 )
{
var newData = new byte [ romData . Length - 512 ] ;
Array . Copy ( romData , 512 , newData , 0 , newData . Length ) ;
romData = newData ;
}
2012-09-26 15:59:14 +00:00
if ( game [ "SGB" ] )
{
SystemId = "SGB" ;
if ( ! LibsnesDll . snes_load_cartridge_super_game_boy ( null , sgbRomData , ( uint ) sgbRomData . Length , null , romData , ( uint ) romData . Length ) )
throw new Exception ( "snes_load_cartridge_super_game_boy() failed" ) ;
}
else
{
SystemId = "SNES" ;
if ( ! LibsnesDll . snes_load_cartridge_normal ( null , romData , ( uint ) romData . Length ) )
throw new Exception ( "snes_load_cartridge_normal() failed" ) ;
}
2012-09-30 18:21:32 +00:00
if ( LibsnesDll . snes_get_region ( ) = = LibsnesDll . SNES_REGION . NTSC )
2012-10-01 04:15:21 +00:00
{
//similar to what aviout reports from snes9x and seems logical from bsnes first principles. bsnes uses that numerator (ntsc master clockrate) for sure.
CoreOutputComm . VsyncNum = 21477272 ;
CoreOutputComm . VsyncDen = 4 * 341 * 262 ;
}
2012-09-30 18:21:32 +00:00
else
2012-10-01 04:15:21 +00:00
{
2012-09-30 18:21:32 +00:00
CoreOutputComm . VsyncNum = 50 ;
2012-10-01 04:15:21 +00:00
CoreOutputComm . VsyncDen = 1 ;
}
2012-09-30 18:21:32 +00:00
2012-09-09 21:19:54 +00:00
LibsnesDll . snes_power ( ) ;
2012-09-04 17:29:20 +00:00
SetupMemoryDomains ( romData ) ;
2012-09-30 19:22:54 +00:00
// disallow any future modifications to the DeterministicEmulation parameter, and set initial deterministic savestate
_DeterministicEmulationProtected = true ;
if ( DeterministicEmulation )
CoreSaveStateInternal ( true ) ;
2012-09-04 00:20:36 +00:00
}
2012-09-05 23:16:08 +00:00
//must keep references to these so that they wont get garbage collected
LibsnesDll . snes_video_refresh_t vidcb ;
LibsnesDll . snes_input_poll_t pollcb ;
LibsnesDll . snes_input_state_t inputcb ;
2012-09-23 15:57:01 +00:00
LibsnesDll . snes_input_notify_t notifycb ;
2012-09-05 23:16:08 +00:00
LibsnesDll . snes_audio_sample_t soundcb ;
2012-09-22 05:03:52 +00:00
LibsnesDll . snes_scanlineStart_t scanlineStart_cb ;
2012-09-27 07:22:31 +00:00
LibsnesDll . snes_path_request_t pathRequest_cb ;
2012-09-05 23:16:08 +00:00
2012-09-04 00:20:36 +00:00
ushort snes_input_state ( int port , int device , int index , int id )
{
//Console.WriteLine("{0} {1} {2} {3}", port, device, index, id);
string key = "P" + ( 1 + port ) + " " ;
2012-09-04 07:09:00 +00:00
if ( ( LibsnesDll . SNES_DEVICE ) device = = LibsnesDll . SNES_DEVICE . JOYPAD )
2012-09-04 00:20:36 +00:00
{
2012-09-05 18:52:17 +00:00
switch ( ( LibsnesDll . SNES_DEVICE_ID ) id )
2012-09-04 00:20:36 +00:00
{
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_A : key + = "A" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_B : key + = "B" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_X : key + = "X" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_Y : key + = "Y" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_UP : key + = "Up" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_DOWN : key + = "Down" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_LEFT : key + = "Left" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_RIGHT : key + = "Right" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_L : key + = "L" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_R : key + = "R" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_SELECT : key + = "Select" ; break ;
case LibsnesDll . SNES_DEVICE_ID . JOYPAD_START : key + = "Start" ; break ;
}
return ( ushort ) ( Controller [ key ] ? 1 : 0 ) ;
}
return 0 ;
}
void snes_input_poll ( )
2012-09-23 15:57:01 +00:00
{
// libsnes.cpp calls this on every video refresh regardless of any underlying anything, so...
//IsLagFrame = false;
}
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
{
vidWidth = width ;
vidHeight = height ;
2012-09-30 05:17:08 +00:00
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.
int yskip = 1 ;
if ( width = = 512 )
{
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 ;
}
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
2012-09-30 05:17:08 +00:00
2012-09-04 00:20:36 +00:00
for ( int y = 0 ; y < height ; y + + )
for ( int x = 0 ; x < width ; x + + )
{
2012-09-30 05:17:08 +00:00
int si = y * srcPitch + x + srcStart ;
2012-09-24 08:00:42 +00:00
int di = y * vidWidth * yskip + x ;
2012-09-04 19:25:09 +00:00
int rgb = data [ si ] ;
vidBuffer [ di ] = rgb ;
2012-09-04 00:20:36 +00:00
}
2012-09-24 08:00:42 +00:00
//alternate scanlines
if ( width = = 512 )
for ( int y = 0 ; y < height ; y + + )
for ( int x = 0 ; x < width ; x + + )
{
int si = y * 1024 + x ;
int di = y * vidWidth * yskip + x + 512 ;
int rgb = data [ si ] ;
vidBuffer [ di ] = rgb ;
}
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-09-20 20:25:40 +00:00
// speedup when sound rendering is not needed
2012-09-20 20:36:44 +00:00
if ( ! rendersound )
LibsnesDll . snes_set_audio_sample ( null ) ;
else
LibsnesDll . snes_set_audio_sample ( soundcb ) ;
2012-09-20 20:25:40 +00:00
2012-09-16 17:15:53 +00:00
bool resetSignal = Controller [ "Reset" ] ;
if ( resetSignal ) LibsnesDll . snes_reset ( ) ;
bool powerSignal = Controller [ "Power" ] ;
if ( powerSignal ) LibsnesDll . snes_power ( ) ;
2012-09-04 19:12:16 +00:00
LibsnesDll . snes_set_layer_enable ( 0 , 0 , CoreInputComm . SNES_ShowBG1_0 ) ;
LibsnesDll . snes_set_layer_enable ( 0 , 1 , CoreInputComm . SNES_ShowBG1_1 ) ;
LibsnesDll . snes_set_layer_enable ( 1 , 0 , CoreInputComm . SNES_ShowBG2_0 ) ;
LibsnesDll . snes_set_layer_enable ( 1 , 1 , CoreInputComm . SNES_ShowBG2_1 ) ;
LibsnesDll . snes_set_layer_enable ( 2 , 0 , CoreInputComm . SNES_ShowBG3_0 ) ;
LibsnesDll . snes_set_layer_enable ( 2 , 1 , CoreInputComm . SNES_ShowBG3_1 ) ;
LibsnesDll . snes_set_layer_enable ( 3 , 0 , CoreInputComm . SNES_ShowBG4_0 ) ;
LibsnesDll . snes_set_layer_enable ( 3 , 1 , CoreInputComm . SNES_ShowBG4_1 ) ;
LibsnesDll . snes_set_layer_enable ( 4 , 0 , CoreInputComm . SNES_ShowOBJ_0 ) ;
LibsnesDll . snes_set_layer_enable ( 4 , 1 , CoreInputComm . SNES_ShowOBJ_1 ) ;
LibsnesDll . snes_set_layer_enable ( 4 , 2 , CoreInputComm . SNES_ShowOBJ_2 ) ;
LibsnesDll . snes_set_layer_enable ( 4 , 3 , CoreInputComm . SNES_ShowOBJ_3 ) ;
2012-09-11 01:36:12 +00:00
// if the input poll callback is called, it will set this to false
IsLagFrame = true ;
2012-09-04 00:20:36 +00:00
//apparently this is one frame?
2012-09-07 20:06:57 +00:00
timeFrameCounter + + ;
2012-09-04 00:20:36 +00:00
LibsnesDll . snes_run ( ) ;
2012-09-08 20:03:04 +00:00
2012-09-27 01:38:27 +00:00
while ( LibsnesDll . HasMessage )
Console . WriteLine ( LibsnesDll . DequeueMessage ( ) ) ;
2012-09-11 01:36:12 +00:00
if ( IsLagFrame )
LagCount + + ;
2012-09-30 19:22:54 +00:00
if ( DeterministicEmulation )
{
// save the one internal savestate for this frame now
CoreSaveStateInternal ( true ) ;
}
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 ; } }
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 ; } }
2012-09-04 01:21:14 +00:00
public ISoundProvider SoundProvider { get { return this ; } }
2012-09-04 00:20:36 +00:00
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 = {
2012-09-16 17:15:53 +00:00
"P1 Up" , "P1 Down" , "P1 Left" , "P1 Right" , "P1 Select" , "P1 Start" , "P1 B" , "P1 A" , "P1 X" , "P1 Y" , "P1 L" , "P1 R" , "Reset" , "Power" ,
2012-09-10 18:40:39 +00:00
"P2 Up" , "P2 Down" , "P2 Left" , "P2 Right" , "P2 Select" , "P2 Start" , "P2 B" , "P2 A" , "P2 X" , "P2 Y" , "P2 L" , "P2 R" ,
"P3 Up" , "P3 Down" , "P3 Left" , "P3 Right" , "P3 Select" , "P3 Start" , "P3 B" , "P3 A" , "P3 X" , "P3 Y" , "P3 L" , "P3 R" ,
"P4 Up" , "P4 Down" , "P4 Left" , "P4 Right" , "P4 Select" , "P4 Start" , "P4 B" , "P4 A" , "P4 X" , "P4 Y" , "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
bool _DeterministicEmulation = false ;
bool _DeterministicEmulationProtected = false ;
public bool DeterministicEmulation
{
get { return _DeterministicEmulation ; }
set
{
2012-10-01 14:39:52 +00:00
if ( _DeterministicEmulationProtected & & value ! = _DeterministicEmulation )
2012-09-30 19:22:54 +00:00
throw new Exception ( "snes: DeterministicEmulation must be set before load!" ) ;
_DeterministicEmulation = value ;
}
}
2012-09-04 07:09:00 +00:00
public bool SaveRamModified
{
set { }
get
{
return LibsnesDll . snes_get_memory_size ( LibsnesDll . SNES_MEMORY . CARTRIDGE_RAM ) ! = 0 ;
}
}
2012-09-05 18:52:17 +00:00
2012-09-14 22:28:38 +00:00
public byte [ ] ReadSaveRam ( )
2012-09-16 17:15:53 +00:00
{
if ( disposedSaveRam ! = null ) return disposedSaveRam ;
2012-09-14 22:28:38 +00:00
return snes_get_memory_data_read ( LibsnesDll . SNES_MEMORY . CARTRIDGE_RAM ) ;
}
2012-09-04 07:09:00 +00:00
public static byte [ ] snes_get_memory_data_read ( LibsnesDll . SNES_MEMORY id )
{
var size = ( int ) LibsnesDll . snes_get_memory_size ( id ) ;
2012-09-04 08:21:01 +00:00
if ( size = = 0 ) return new byte [ 0 ] ;
2012-09-04 07:09:00 +00:00
var data = LibsnesDll . snes_get_memory_data ( id ) ;
var ret = new byte [ size ] ;
2012-09-05 18:52:17 +00:00
Marshal . Copy ( data , ret , 0 , size ) ;
2012-09-04 07:09:00 +00:00
return ret ;
}
public void StoreSaveRam ( byte [ ] data )
{
var size = ( int ) LibsnesDll . snes_get_memory_size ( LibsnesDll . SNES_MEMORY . CARTRIDGE_RAM ) ;
2012-09-04 08:21:01 +00:00
if ( size = = 0 ) return ;
2012-09-04 07:09:00 +00:00
var emudata = LibsnesDll . snes_get_memory_data ( LibsnesDll . SNES_MEMORY . CARTRIDGE_RAM ) ;
Marshal . Copy ( data , 0 , emudata , size ) ;
}
2012-09-04 00:20:36 +00:00
2012-09-14 22:28:38 +00:00
public void ClearSaveRam ( )
{
byte [ ] cleardata = new byte [ ( int ) LibsnesDll . snes_get_memory_size ( LibsnesDll . SNES_MEMORY . CARTRIDGE_RAM ) ] ;
StoreSaveRam ( cleardata ) ;
}
2012-09-07 20:06:57 +00:00
public void ResetFrameCounter ( ) { timeFrameCounter = 0 ; }
2012-09-04 08:21:01 +00:00
public void SaveStateText ( TextWriter writer )
{
var temp = SaveStateBinary ( ) ;
temp . SaveAsHex ( writer ) ;
2012-09-23 16:59:44 +00:00
// write extra copy of stuff we don't use
writer . WriteLine ( "Frame {0}" , Frame ) ;
2012-09-04 08:21:01 +00:00
}
public void LoadStateText ( TextReader reader )
{
string hex = reader . ReadLine ( ) ;
byte [ ] state = new byte [ hex . Length / 2 ] ;
state . ReadFromHex ( hex ) ;
LoadStateBinary ( new BinaryReader ( new MemoryStream ( state ) ) ) ;
}
2012-09-05 18:52:17 +00:00
2012-09-04 08:21:01 +00:00
public void SaveStateBinary ( BinaryWriter writer )
{
2012-09-30 19:22:54 +00:00
byte [ ] buf = CoreSaveState ( ) ;
2012-09-04 08:21:01 +00:00
writer . Write ( buf ) ;
2012-09-11 01:50:55 +00:00
// other variables
writer . Write ( IsLagFrame ) ;
writer . Write ( LagCount ) ;
writer . Write ( Frame ) ;
2012-09-04 08:21:01 +00:00
writer . Flush ( ) ;
}
public void LoadStateBinary ( BinaryReader reader )
{
int size = LibsnesDll . snes_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
// other variables
IsLagFrame = reader . ReadBoolean ( ) ;
LagCount = reader . ReadInt32 ( ) ;
Frame = reader . ReadInt32 ( ) ;
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
2012-09-30 19:22:54 +00:00
/// <summary>
/// handle the unmanaged part of loadstating
/// </summary>
void CoreLoadState ( byte [ ] data )
{
int size = LibsnesDll . snes_serialize_size ( ) ;
if ( data . Length ! = size )
throw new Exception ( "Libsnes internal savestate size mismatch!" ) ;
fixed ( byte * pbuf = & data [ 0 ] )
LibsnesDll . snes_unserialize ( new IntPtr ( pbuf ) , size ) ;
}
/// <summary>
/// handle the unmanaged part of savestating
/// </summary>
byte [ ] CoreSaveState ( )
{
if ( ! DeterministicEmulation )
return CoreSaveStateInternal ( false ) ;
else
return savestatebuff ;
}
/// <summary>
/// most recent internal savestate, for deterministic mode
/// </summary>
byte [ ] savestatebuff ;
/// <summary>
/// internal function handling savestate
/// this can cause determinism problems if called improperly!
/// </summary>
2012-10-01 14:39:52 +00:00
byte [ ] CoreSaveStateInternal ( bool cache )
2012-09-30 19:22:54 +00:00
{
int size = LibsnesDll . snes_serialize_size ( ) ;
byte [ ] buf = new byte [ size ] ;
fixed ( byte * pbuf = & buf [ 0 ] )
LibsnesDll . snes_serialize ( new IntPtr ( pbuf ) , size ) ;
2012-10-01 14:39:52 +00:00
if ( cache )
2012-09-30 19:22:54 +00:00
{
savestatebuff = buf ;
return null ;
}
else
2012-10-01 14:39:52 +00:00
{
savestatebuff = null ;
2012-09-30 19:22:54 +00:00
return buf ;
2012-10-01 14:39:52 +00:00
}
2012-09-30 19:22:54 +00:00
}
2012-09-04 00:20:36 +00:00
// Arbitrary extensible core comm mechanism
2012-09-04 19:12:16 +00:00
public CoreInputComm CoreInputComm { get ; set ; }
public CoreOutputComm CoreOutputComm { get { return _CoreOutputComm ; } }
CoreOutputComm _CoreOutputComm = new CoreOutputComm ( ) ;
2012-09-04 00:20:36 +00:00
// ----- Client Debugging API stuff -----
2012-09-04 17:29:20 +00:00
unsafe MemoryDomain MakeMemoryDomain ( string name , LibsnesDll . SNES_MEMORY id , Endian endian )
{
2012-09-21 06:03:27 +00:00
IntPtr block = LibsnesDll . snes_get_memory_data ( id ) ;
2012-09-04 17:29:20 +00:00
int size = LibsnesDll . snes_get_memory_size ( id ) ;
int mask = size - 1 ;
byte * blockptr = ( byte * ) block . ToPointer ( ) ;
MemoryDomain md ;
2012-09-05 18:52:17 +00:00
2012-09-04 17:29:20 +00:00
//have to bitmask these somehow because it's unmanaged memory and we would hate to clobber things or make them nondeterministic
if ( Util . IsPowerOfTwo ( size ) )
{
//can &mask for speed
md = new MemoryDomain ( name , size , endian ,
( addr ) = > blockptr [ addr & mask ] ,
( addr , value ) = > blockptr [ addr & mask ] = value ) ;
}
else
{
//have to use % (only OAM needs this, it seems)
md = new MemoryDomain ( name , size , endian ,
( addr ) = > blockptr [ addr % size ] ,
( addr , value ) = > blockptr [ addr % size ] = value ) ;
}
MemoryDomains . Add ( md ) ;
return md ;
//doesnt cache the addresses. safer. slower. necessary? don't know
//return new MemoryDomain(name, size, endian,
// (addr) => Peek(LibsnesDll.SNES_MEMORY.WRAM, addr & mask),
// (addr, value) => Poke(LibsnesDll.SNES_MEMORY.WRAM, addr & mask, value));
}
void SetupMemoryDomains ( byte [ ] romData )
{
MemoryDomains = new List < MemoryDomain > ( ) ;
2012-09-05 18:52:17 +00:00
2012-09-04 19:12:16 +00:00
var romDomain = new MemoryDomain ( "CARTROM" , romData . Length , Endian . Little ,
2012-09-04 17:29:20 +00:00
( addr ) = > romData [ addr ] ,
( addr , value ) = > romData [ addr ] = value ) ;
MainMemory = MakeMemoryDomain ( "WRAM" , LibsnesDll . SNES_MEMORY . WRAM , Endian . Little ) ;
MemoryDomains . Add ( romDomain ) ;
MakeMemoryDomain ( "CARTRAM" , LibsnesDll . SNES_MEMORY . CARTRIDGE_RAM , Endian . Little ) ;
MakeMemoryDomain ( "VRAM" , LibsnesDll . SNES_MEMORY . VRAM , Endian . Little ) ;
MakeMemoryDomain ( "OAM" , LibsnesDll . SNES_MEMORY . OAM , Endian . Little ) ;
MakeMemoryDomain ( "CGRAM" , LibsnesDll . SNES_MEMORY . CGRAM , Endian . Little ) ;
MakeMemoryDomain ( "APURAM" , LibsnesDll . SNES_MEMORY . APURAM , Endian . Little ) ;
2012-10-03 14:54:32 +00:00
MemoryDomains . Add ( new MemoryDomain ( "BUS" , 0x1000000 , Endian . Little ,
( addr ) = > LibsnesDll . bus_read ( ( uint ) addr ) ,
( addr , val ) = > LibsnesDll . bus_write ( ( uint ) addr , val ) ) ) ;
2012-09-04 17:29:20 +00:00
}
public IList < MemoryDomain > MemoryDomains { get ; private set ; }
public MemoryDomain MainMemory { 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
2012-09-07 20:12:47 +00:00
void InitAudio ( )
{
metaspu = new Sound . MetaspuSoundProvider ( Sound . ESynchMethod . ESynchMethod_V ) ;
resampler = new Sound . Utilities . SpeexResampler ( 6 , 64081 , 88200 , 32041 , 44100 , new Action < short [ ] , int > ( metaspu . buffer . enqueue_samples ) ) ;
2012-09-06 08:32:25 +00:00
2012-09-07 20:12:47 +00:00
}
2012-09-04 01:21:14 +00:00
2012-09-07 20:12:47 +00:00
Sound . Utilities . SpeexResampler resampler ;
2012-09-04 01:21:14 +00:00
2012-09-07 20:12:47 +00:00
Sound . MetaspuSoundProvider metaspu ;
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
2012-09-05 18:52:17 +00:00
//BinaryWriter dbgs = new BinaryWriter(File.Open("dbgwav.raw", FileMode.Create, FileAccess.Write));
2012-09-04 01:21:14 +00:00
2012-09-05 18:52:17 +00:00
public void GetSamples ( short [ ] samples )
2012-09-04 01:21:14 +00:00
{
2012-09-07 20:12:47 +00:00
resampler . Flush ( ) ;
2012-09-05 18:52:17 +00:00
metaspu . GetSamples ( samples ) ;
2012-09-04 01:21:14 +00:00
}
2012-09-05 18:52:17 +00:00
public void DiscardSamples ( )
2012-09-04 01:21:14 +00:00
{
2012-09-05 18:52:17 +00:00
metaspu . DiscardSamples ( ) ;
2012-09-04 01:21:14 +00:00
}
2012-09-05 18:52:17 +00:00
public int MaxVolume { get ; set ; }
#endregion audio stuff
2012-09-04 00:20:36 +00:00
}
2012-09-30 18:21:32 +00:00
}