2017-04-19 14:41:52 +00:00
// TODO - add serializer (?)
2012-09-30 05:17:08 +00:00
2017-04-19 14:41:52 +00:00
// http://wiki.superfamicom.org/snes/show/Backgrounds
2012-09-06 08:32:25 +00:00
2017-04-19 14:41:52 +00:00
// TODO
// 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
2017-04-19 14:41:52 +00:00
// wrap dll code around some kind of library-accessing interface so that it doesnt malfunction if the dll is unavailablecd
2012-09-04 07:09:00 +00:00
using System ;
2012-09-04 00:20:36 +00:00
using System.Linq ;
2013-04-24 22:09:11 +00:00
using System.Xml ;
2012-09-04 00:20:36 +00:00
using System.IO ;
using System.Collections.Generic ;
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-12-12 01:58:12 +00:00
[ServiceNotApplicable(typeof(IDriveLight))]
2017-04-19 13:31:48 +00:00
public unsafe partial class LibsnesCore : IEmulator , IVideoProvider , ISaveRam , IStatable , IInputPollable , IRegionable , ICodeDataLogger ,
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-12-04 03:38:30 +00:00
ServiceProvider = new BasicServiceProvider ( this ) ;
2016-08-15 22:39:26 +00:00
Tracer = new TraceBuffer
{
Header = "65816: PC, mnemonic, operands, registers (A, X, Y, S, D, DB, flags (NVMXDIZC), V, H)"
} ;
2014-12-23 01:58:12 +00:00
( ServiceProvider as BasicServiceProvider ) . Register < ITraceable > ( Tracer ) ;
2014-12-05 01:56:45 +00:00
2015-01-26 00:20:01 +00:00
( ServiceProvider as BasicServiceProvider ) . Register < IDisassemblable > ( new BizHawk . Emulation . Cores . Components . W65816 . W65816_DisassemblerService ( ) ) ;
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 ( ) ;
}
2017-04-19 13:31:48 +00:00
_settings = ( SnesSettings ) Settings ? ? new SnesSettings ( ) ;
_syncSettings = ( SnesSyncSettings ) SyncSettings ? ? new SnesSyncSettings ( ) ;
2014-11-18 23:44:42 +00:00
2015-11-02 17:26:49 +00:00
api = new LibsnesApi ( GetDllPath ( ) ) ;
2014-11-18 23:44:42 +00:00
api . ReadHook = ReadHook ;
api . ExecHook = ExecHook ;
api . WriteHook = WriteHook ;
ScanlineHookManager = new MyScanlineHookManager ( this ) ;
2017-04-19 13:31:48 +00:00
_controllerDeck = new LibsnesControllerDeck (
_syncSettings . LeftPort ,
_syncSettings . RightPort ) ;
2017-04-15 22:27:04 +00:00
_controllerDeck . NativeInit ( api ) ;
2017-04-15 23:34:12 +00:00
2014-11-18 23:44:42 +00:00
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 ( ) ;
2016-12-11 17:14:42 +00:00
( ServiceProvider as BasicServiceProvider ) . Register < ISoundProvider > ( resampler ) ;
2014-11-18 23:44:42 +00:00
//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_data = romData ,
} ;
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" ) ;
}
2017-03-06 09:21:10 +00:00
if ( api . Region = = LibsnesApi . SNES_REGION . NTSC )
2014-11-18 23:44:42 +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.
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 ;
}
api . CMD_power ( ) ;
SetupMemoryDomains ( romData , sgbRomData ) ;
2017-04-19 15:36:08 +00:00
// DeterministicEmulation = deterministicEmulation; // Note we don't respect the value coming in and force it instead
2014-11-18 23:44:42 +00:00
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 ( ) ;
2017-04-19 14:41:52 +00:00
_savestatebuff = ms . ToArray ( ) ;
2014-11-18 23:44:42 +00:00
}
}
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" ;
}
2017-04-19 13:31:48 +00:00
return _syncSettings . Profile ;
2014-11-19 00:32:51 +00:00
}
}
2012-10-05 04:47:45 +00:00
public bool IsSGB { get ; private set ; }
2017-04-15 22:27:04 +00:00
private LibsnesControllerDeck _controllerDeck ;
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 ;
2014-12-23 01:58:12 +00:00
public ITraceable Tracer { get ; private set ; }
2017-01-10 01:23:05 +00:00
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 ( ) ;
}
}
2017-04-19 14:41:52 +00:00
2017-04-19 15:36:08 +00:00
bool _disposed = false ;
2012-09-22 05:03:52 +00:00
public MyScanlineHookManager ScanlineHookManager ;
void OnScanlineHooksChanged ( )
{
2017-04-19 15:36:08 +00:00
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 )
{
2016-02-21 22:34:14 +00:00
// TODO: get them out of the core split up and remove this hackery
2016-07-27 23:54:48 +00:00
string splitStr = "A:" ;
2016-02-21 22:34:14 +00:00
2016-07-27 23:54:48 +00:00
var split = msg . Split ( new [ ] { splitStr } , 2 , StringSplitOptions . None ) ;
2016-02-21 22:34:14 +00:00
Tracer . Put ( new TraceInfo
{
2016-08-15 22:39:26 +00:00
Disassembly = split [ 0 ] . PadRight ( 34 ) ,
2016-02-21 22:34:14 +00:00
RegisterInfo = splitStr + split [ 1 ]
} ) ;
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
2015-11-02 17:26:49 +00:00
string GetDllPath ( )
2012-09-27 07:22:31 +00:00
{
2015-11-02 17:26:49 +00:00
var exename = "libsneshawk-32-" + CurrentProfile . ToLower ( ) + ".dll" ;
2013-12-27 17:59:19 +00:00
2015-11-02 17:26:49 +00:00
string dllPath = Path . Combine ( CoreComm . CoreFileProvider . DllPath ( ) , exename ) ;
2013-12-27 17:59:19 +00:00
2015-11-02 17:26:49 +00:00
if ( ! File . Exists ( dllPath ) )
throw new InvalidOperationException ( "Couldn't locate the DLL 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
2015-11-02 17:26:49 +00:00
return dllPath ;
2013-12-27 17:59:19 +00:00
}
2013-11-12 02:34:56 +00:00
void ReadHook ( uint addr )
{
2014-12-07 18:53:56 +00:00
MemoryCallbacks . CallReads ( addr ) ;
2013-11-12 02:34:56 +00:00
//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-12 02:34:56 +00:00
}
void ExecHook ( uint addr )
{
2014-12-07 18:53:56 +00:00
MemoryCallbacks . CallExecutes ( addr ) ;
2013-11-12 02:34:56 +00:00
//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-12 02:34:56 +00:00
}
void WriteHook ( uint addr , byte val )
{
2014-12-07 18:53:56 +00:00
MemoryCallbacks . CallWrites ( addr ) ;
2013-11-12 02:34:56 +00:00
//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();
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 byte [ ] dmg_data ;
}
LoadParams CurrLoadParams ;
bool LoadCurrent ( )
{
2016-08-08 11:40:06 +00:00
bool result = false ;
2014-05-23 00:13:04 +00:00
if ( CurrLoadParams . type = = LoadParamType . Normal )
2016-08-08 11:40:06 +00:00
result = api . CMD_load_cartridge_normal ( CurrLoadParams . xml_data , CurrLoadParams . rom_data ) ;
2017-03-06 09:21:10 +00:00
else result = api . CMD_load_cartridge_super_game_boy ( CurrLoadParams . rom_xml , CurrLoadParams . rom_data , CurrLoadParams . rom_size , CurrLoadParams . dmg_data ) ;
2016-08-08 11:40:06 +00:00
2017-03-06 09:21:10 +00:00
mapper = api . Mapper ;
2016-08-08 11:40:06 +00:00
return result ;
2014-05-23 00:13:04 +00:00
}
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>
2017-04-16 22:08:57 +00:00
short snes_input_state ( int port , int device , int index , int id )
2012-09-04 00:20:36 +00:00
{
2017-04-15 22:27:04 +00:00
return _controllerDeck . CoreInputState ( Controller , port , device , index , id ) ;
2012-09-04 00:20:36 +00:00
}
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
{
2016-03-01 02:22:30 +00:00
// gets called with the following numbers:
// 4xxx : lag frame related
// 0: signifies latch bit going to 0. should be reported as oninputpoll
// 1: signifies latch bit going to 1. should be reported as oninputpoll
if ( index > = 0x4000 )
IsLagFrame = false ;
2012-09-04 00:20:36 +00:00
}
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
{
2017-04-19 13:31:48 +00:00
bool doubleSize = _settings . AlwaysDoubleSize ;
2013-04-22 22:34:18 +00:00
bool lineDouble = doubleSize , dotDouble = doubleSize ;
2017-04-19 14:41:52 +00:00
_videoWidth = width ;
_videoHeight = 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.
if ( width = = 512 )
2013-04-22 22:34:18 +00:00
{
2017-04-19 14:41:52 +00:00
_videoHeight * = 2 ;
2013-04-22 22:34:18 +00:00
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
{
2017-04-19 14:41:52 +00:00
_videoHeight * = 2 ;
2012-09-24 08:00:42 +00:00
yskip = 2 ;
}
2012-09-30 05:17:08 +00:00
int srcPitch = 1024 ;
int srcStart = 0 ;
bool interlaced = ( height = = 478 | | height = = 448 ) ;
if ( interlaced )
{
2015-09-24 01:28:38 +00:00
//from bsnes in interlaced mode we have each field side by side
//so we will come in with a dimension of 512x448, say
//but the fields are side by side, so it's actually 1024x224.
//copy the first scanline from row 0, then the 2nd scanline from row 0 (offset 512)
//EXAMPLE: yu yu hakushu legal screens
2015-09-24 01:48:07 +00:00
//EXAMPLE: World Class Service Super Nintendo Tester (double resolution vertically but not horizontally, in character test the stars should shrink)
2015-09-24 01:28:38 +00:00
lineDouble = false ;
srcPitch = 512 ;
yskip = 1 ;
2017-04-19 14:41:52 +00:00
_videoHeight = height ;
2012-09-30 05:17:08 +00:00
}
2013-04-22 22:34:18 +00:00
if ( dotDouble )
{
2017-04-19 14:41:52 +00:00
_videoWidth * = 2 ;
2013-04-22 22:34:18 +00:00
xskip = 2 ;
}
2017-04-19 14:41:52 +00:00
int size = _videoWidth * _videoHeight ;
if ( _videoBuffer . Length ! = size )
_videoBuffer = 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 ;
2017-04-19 14:41:52 +00:00
int bonus = i * _videoWidth + xbonus ;
2013-04-22 22:34:18 +00:00
for ( int y = 0 ; y < height ; y + + )
for ( int x = 0 ; x < width ; x + + )
{
int si = y * srcPitch + x + srcStart ;
2017-04-19 14:41:52 +00:00
int di = y * _videoWidth * yskip + x * xskip + bonus ;
2013-04-22 22:34:18 +00:00
int rgb = data [ si ] ;
2017-04-19 14:41:52 +00:00
_videoBuffer [ di ] = rgb ;
2013-04-22 22:34:18 +00:00
}
2012-09-04 00:20:36 +00:00
}
2013-04-22 22:34:18 +00:00
}
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
{
2014-12-05 01:56:45 +00:00
var mcs = MemoryCallbacks ;
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
}
2017-04-19 15:36:08 +00:00
private int _timeFrameCounter ;
2012-09-30 19:22:54 +00:00
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-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>
2016-12-12 18:30:32 +00:00
public ControllerDefinition Definition
2012-10-08 14:37:42 +00:00
{
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 )
{
2016-12-12 18:30:32 +00:00
this . def = source . Definition ;
2012-10-08 14:37:42 +00:00
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 ] ;
}
}
#endregion
2012-09-30 19:22:54 +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 ;
}
2016-08-08 11:40:06 +00:00
private LibsnesApi . SNES_MAPPER ? mapper = null ;
// works for ROM, garbage for anything else
byte FakeBusRead ( int addr )
{
addr & = 0xffffff ;
int bank = addr > > 16 ;
int low = addr & 0xffff ;
if ( ! mapper . HasValue )
{
return 0 ;
}
switch ( mapper )
{
case LibsnesApi . SNES_MAPPER . LOROM :
if ( low > = 0x8000 )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
case LibsnesApi . SNES_MAPPER . EXLOROM :
if ( ( bank > = 0x40 & & bank < = 0x7f ) | | low > = 0x8000 )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
case LibsnesApi . SNES_MAPPER . HIROM :
case LibsnesApi . SNES_MAPPER . EXHIROM :
if ( ( bank > = 0x40 & & bank < = 0x7f ) | | bank > = 0xc0 | | low > = 0x8000 )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
case LibsnesApi . SNES_MAPPER . SUPERFXROM :
if ( ( bank > = 0x40 & & bank < = 0x5f ) | | ( bank > = 0xc0 & & bank < = 0xdf ) | |
( low > = 0x8000 & & ( ( bank > = 0x00 & & bank < = 0x3f ) | | ( bank > = 0x80 & & bank < = 0xbf ) ) ) )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
case LibsnesApi . SNES_MAPPER . SA1ROM :
if ( bank > = 0xc0 | | ( low > = 0x8000 & & ( ( bank > = 0x00 & & bank < = 0x3f ) | | ( bank > = 0x80 & & bank < = 0xbf ) ) ) )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
case LibsnesApi . SNES_MAPPER . BSCLOROM :
if ( low > = 0x8000 & & ( ( bank > = 0x00 & & bank < = 0x3f ) | | ( bank > = 0x80 & & bank < = 0xbf ) ) )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
case LibsnesApi . SNES_MAPPER . BSCHIROM :
if ( ( bank > = 0x40 & & bank < = 0x5f ) | | ( bank > = 0xc0 & & bank < = 0xdf ) | |
( low > = 0x8000 & & ( ( bank > = 0x00 & & bank < = 0x1f ) | | ( bank > = 0x80 & & bank < = 0x9f ) ) ) )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
case LibsnesApi . SNES_MAPPER . BSXROM :
if ( ( bank > = 0x40 & & bank < = 0x7f ) | | bank > = 0xc0 | |
( low > = 0x8000 & & ( ( bank > = 0x00 & & bank < = 0x3f ) | | ( bank > = 0x80 & & bank < = 0xbf ) ) ) | |
( low > = 0x6000 & & low < = 0x7fff & & ( bank > = 0x20 & & bank < = 0x3f ) ) )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
case LibsnesApi . SNES_MAPPER . STROM :
if ( low > = 0x8000 & & ( ( bank > = 0x00 & & bank < = 0x5f ) | | ( bank > = 0x80 & & bank < = 0xdf ) ) )
{
return api . QUERY_peek ( LibsnesApi . SNES_MEMORY . SYSBUS , ( uint ) addr ) ;
}
break ;
default :
throw new InvalidOperationException ( string . Format ( "Unknown mapper: {0}" , mapper ) ) ;
}
return 0 ;
}
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
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 ( )
{
2017-04-19 13:31:48 +00:00
SetPalette ( ( SnesColors . ColorType ) Enum . Parse ( typeof ( SnesColors . ColorType ) , _settings . Palette , false ) ) ;
2013-12-27 17:59:19 +00:00
}
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
}