2017-04-15 19:53:02 +00:00
using System ;
using System.IO ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Runtime.InteropServices ;
using Newtonsoft.Json ;
using BizHawk.Common ;
using BizHawk.Emulation.Common ;
using BizHawk.Common.BufferExtensions ;
namespace BizHawk.Emulation.Cores
{
[CoreAttributes("Libretro", "natt&zeromus")]
public unsafe partial class LibRetroEmulator : IEmulator , ISettable < LibRetroEmulator . Settings , LibRetroEmulator . SyncSettings > ,
ISaveRam , IStatable , IVideoProvider , IInputPollable
{
#region Settings
Settings _Settings = new Settings ( ) ;
SyncSettings _SyncSettings ;
public class SyncSettings
{
public SyncSettings Clone ( )
{
return JsonConvert . DeserializeObject < SyncSettings > ( JsonConvert . SerializeObject ( this ) ) ;
}
public SyncSettings ( )
{
}
}
public class Settings
{
public void Validate ( )
{
}
public Settings ( )
{
SettingsUtil . SetDefaultValues ( this ) ;
}
public Settings Clone ( )
{
return ( Settings ) MemberwiseClone ( ) ;
}
}
public Settings GetSettings ( )
{
return _Settings . Clone ( ) ;
}
public SyncSettings GetSyncSettings ( )
{
return _SyncSettings . Clone ( ) ;
}
public bool PutSettings ( Settings o )
{
_Settings . Validate ( ) ;
_Settings = o ;
//TODO - store settings into core? or we can just keep doing it before frameadvance
return false ;
}
public bool PutSyncSettings ( SyncSettings o )
{
bool reboot = false ;
//we could do it this way roughly if we need to
//if(JsonConvert.SerializeObject(o.FIOConfig) != JsonConvert.SerializeObject(_SyncSettings.FIOConfig)
_SyncSettings = o ;
return reboot ;
}
#endregion
#region callbacks
unsafe void retro_log_printf ( LibRetro . RETRO_LOG_LEVEL level , string fmt , IntPtr a0 , IntPtr a1 , IntPtr a2 , IntPtr a3 , IntPtr a4 , IntPtr a5 , IntPtr a6 , IntPtr a7 , IntPtr a8 , IntPtr a9 , IntPtr a10 , IntPtr a11 , IntPtr a12 , IntPtr a13 , IntPtr a14 , IntPtr a15 )
{
//avert your eyes, these things were not meant to be seen in c#
//I'm not sure this is a great idea. It would suck for silly logging to be unstable. But.. I dont think this is unstable. The sprintf might just print some garbledy stuff.
var args = new IntPtr [ ] { a0 , a1 , a2 , a3 , a4 , a5 , a6 , a7 , a8 , a9 , a10 , a11 , a12 , a13 , a14 , a15 } ;
int idx = 0 ;
Console . Write ( Sprintf . sprintf ( fmt , ( ) = > args [ idx + + ] ) ) ;
}
unsafe bool retro_environment ( LibRetro . RETRO_ENVIRONMENT cmd , IntPtr data )
{
Console . WriteLine ( cmd ) ;
switch ( cmd )
{
2016-05-13 22:44:08 +00:00
case LibRetro . RETRO_ENVIRONMENT . SET_ROTATION :
{
var rotation = ( LibRetro . RETRO_ROTATION ) ( * ( int * ) data . ToPointer ( ) ) ;
if ( rotation = = LibRetro . RETRO_ROTATION . ROTATION_0_CCW ) environmentInfo . Rotation_CCW = 0 ;
if ( rotation = = LibRetro . RETRO_ROTATION . ROTATION_90_CCW ) environmentInfo . Rotation_CCW = 90 ;
if ( rotation = = LibRetro . RETRO_ROTATION . ROTATION_180_CCW ) environmentInfo . Rotation_CCW = 180 ;
if ( rotation = = LibRetro . RETRO_ROTATION . ROTATION_270_CCW ) environmentInfo . Rotation_CCW = 270 ;
2017-04-15 19:53:02 +00:00
return true ;
}
case LibRetro . RETRO_ENVIRONMENT . GET_OVERSCAN :
return false ;
case LibRetro . RETRO_ENVIRONMENT . GET_CAN_DUPE :
//gambatte requires this
* ( bool * ) data . ToPointer ( ) = true ;
return true ;
case LibRetro . RETRO_ENVIRONMENT . SET_MESSAGE :
{
LibRetro . retro_message msg = new LibRetro . retro_message ( ) ;
Marshal . PtrToStructure ( data , msg ) ;
if ( ! string . IsNullOrEmpty ( msg . msg ) )
Console . WriteLine ( "LibRetro Message: {0}" , msg . msg ) ;
return true ;
}
case LibRetro . RETRO_ENVIRONMENT . SHUTDOWN :
return false ;
case LibRetro . RETRO_ENVIRONMENT . SET_PERFORMANCE_LEVEL :
Console . WriteLine ( "Core suggested SET_PERFORMANCE_LEVEL {0}" , * ( uint * ) data . ToPointer ( ) ) ;
return true ;
case LibRetro . RETRO_ENVIRONMENT . GET_SYSTEM_DIRECTORY :
//mednafen NGP neopop fails to launch with no system directory
Directory . CreateDirectory ( SystemDirectory ) ; //just to be safe, it seems likely that cores will crash without a created system directory
Console . WriteLine ( "returning system directory: " + SystemDirectory ) ;
* ( ( IntPtr * ) data . ToPointer ( ) ) = SystemDirectoryAtom ;
return true ;
case LibRetro . RETRO_ENVIRONMENT . SET_PIXEL_FORMAT :
{
LibRetro . RETRO_PIXEL_FORMAT fmt = 0 ;
int [ ] tmp = new int [ 1 ] ;
Marshal . Copy ( data , tmp , 0 , 1 ) ;
fmt = ( LibRetro . RETRO_PIXEL_FORMAT ) tmp [ 0 ] ;
switch ( fmt )
{
case LibRetro . RETRO_PIXEL_FORMAT . RGB565 :
case LibRetro . RETRO_PIXEL_FORMAT . XRGB1555 :
case LibRetro . RETRO_PIXEL_FORMAT . XRGB8888 :
pixelfmt = fmt ;
Console . WriteLine ( "New pixel format set: {0}" , pixelfmt ) ;
return true ;
default :
Console . WriteLine ( "Unrecognized pixel format: {0}" , ( int ) pixelfmt ) ;
return false ;
}
}
case LibRetro . RETRO_ENVIRONMENT . SET_INPUT_DESCRIPTORS :
return false ;
case LibRetro . RETRO_ENVIRONMENT . SET_KEYBOARD_CALLBACK :
return false ;
case LibRetro . RETRO_ENVIRONMENT . SET_DISK_CONTROL_INTERFACE :
return false ;
case LibRetro . RETRO_ENVIRONMENT . SET_HW_RENDER :
{
//mupen64plus needs this, as well as 3dengine
LibRetro . retro_hw_render_callback * info = ( LibRetro . retro_hw_render_callback * ) data . ToPointer ( ) ;
Console . WriteLine ( "SET_HW_RENDER: {0}, version={1}.{2}, dbg/cache={3}/{4}, depth/stencil = {5}/{6}{7}" , info - > context_type , info - > version_minor , info - > version_major , info - > debug_context , info - > cache_context , info - > depth , info - > stencil , info - > bottom_left_origin ? " (bottomleft)" : "" ) ;
return true ;
}
case LibRetro . RETRO_ENVIRONMENT . GET_VARIABLE :
{
void * * variables = ( void * * ) data . ToPointer ( ) ;
IntPtr pKey = new IntPtr ( * variables + + ) ;
string key = Marshal . PtrToStringAnsi ( pKey ) ;
Console . WriteLine ( "Requesting variable: {0}" , key ) ;
//always return default
//TODO: cache settings atoms
if ( ! Description . Variables . ContainsKey ( key ) )
return false ;
//HACK: return pointer for desmume mouse, i want to implement that first
if ( key = = "desmume_pointer_type" )
{
* variables = unmanagedResources . StringToHGlobalAnsi ( "touch" ) . ToPointer ( ) ;
return true ;
}
* variables = unmanagedResources . StringToHGlobalAnsi ( Description . Variables [ key ] . DefaultOption ) . ToPointer ( ) ;
return true ;
2016-05-13 22:44:08 +00:00
}
2017-04-15 19:53:02 +00:00
case LibRetro . RETRO_ENVIRONMENT . SET_VARIABLES :
{
void * * variables = ( void * * ) data . ToPointer ( ) ;
for ( ; ; )
{
IntPtr pKey = new IntPtr ( * variables + + ) ;
IntPtr pValue = new IntPtr ( * variables + + ) ;
if ( pKey = = IntPtr . Zero )
break ;
string key = Marshal . PtrToStringAnsi ( pKey ) ;
string value = Marshal . PtrToStringAnsi ( pValue ) ;
var vd = new VariableDescription ( ) { Name = key } ;
var parts = value . Split ( ';' ) ;
vd . Description = parts [ 0 ] ;
vd . Options = parts [ 1 ] . TrimStart ( ' ' ) . Split ( '|' ) ;
Description . Variables [ vd . Name ] = vd ;
}
}
return false ;
case LibRetro . RETRO_ENVIRONMENT . GET_VARIABLE_UPDATE :
return false ;
case LibRetro . RETRO_ENVIRONMENT . SET_SUPPORT_NO_GAME :
environmentInfo . SupportNoGame = true ;
return false ;
case LibRetro . RETRO_ENVIRONMENT . GET_LIBRETRO_PATH :
return false ;
case LibRetro . RETRO_ENVIRONMENT . SET_AUDIO_CALLBACK :
return false ;
case LibRetro . RETRO_ENVIRONMENT . SET_FRAME_TIME_CALLBACK :
return false ;
case LibRetro . RETRO_ENVIRONMENT . GET_RUMBLE_INTERFACE :
return false ;
case LibRetro . RETRO_ENVIRONMENT . GET_INPUT_DEVICE_CAPABILITIES :
return false ;
case LibRetro . RETRO_ENVIRONMENT . GET_LOG_INTERFACE :
* ( IntPtr * ) data = Marshal . GetFunctionPointerForDelegate ( retro_log_printf_cb ) ;
return true ;
case LibRetro . RETRO_ENVIRONMENT . GET_PERF_INTERFACE :
//some builds of fmsx core crash without this set
Marshal . StructureToPtr ( retro_perf_callback , data , false ) ;
return true ;
case LibRetro . RETRO_ENVIRONMENT . GET_LOCATION_INTERFACE :
return false ;
case LibRetro . RETRO_ENVIRONMENT . GET_CORE_ASSETS_DIRECTORY :
return false ;
case LibRetro . RETRO_ENVIRONMENT . GET_SAVE_DIRECTORY :
//supposedly optional like everything else here, but without it ?? crashes (please write which case)
//this will suffice for now. if we find evidence later it's needed we can stash a string with
//unmanagedResources and CoreFileProvider
//mednafen NGP neopop, desmume, and others, request this, and falls back on the system directory if it isn't provided
//desmume crashes if the directory doesn't exist
Directory . CreateDirectory ( SaveDirectory ) ;
Console . WriteLine ( "returning save directory: " + SaveDirectory ) ;
* ( ( IntPtr * ) data . ToPointer ( ) ) = SaveDirectoryAtom ;
return true ;
case LibRetro . RETRO_ENVIRONMENT . SET_CONTROLLER_INFO :
return true ;
case LibRetro . RETRO_ENVIRONMENT . SET_MEMORY_MAPS :
return false ;
default :
Console . WriteLine ( "Unknkown retro_environment command {0}" , ( int ) cmd ) ;
return false ;
}
}
void retro_input_poll ( )
{
IsLagFrame = false ;
}
private bool GetButton ( uint pnum , string type , string button )
{
string key = string . Format ( "P{0} {1} {2}" , pnum , type , button ) ;
bool b = Controller . IsPressed ( key ) ;
if ( b = = true )
{
return true ; //debugging placeholder
}
else return false ;
}
LibRetro . retro_environment_t retro_environment_cb ;
LibRetro . retro_video_refresh_t retro_video_refresh_cb ;
LibRetro . retro_audio_sample_t retro_audio_sample_cb ;
LibRetro . retro_audio_sample_batch_t retro_audio_sample_batch_cb ;
LibRetro . retro_input_poll_t retro_input_poll_cb ;
LibRetro . retro_input_state_t retro_input_state_cb ;
LibRetro . retro_log_printf_t retro_log_printf_cb ;
LibRetro . retro_perf_callback retro_perf_callback = new LibRetro . retro_perf_callback ( ) ;
#endregion
class RetroEnvironmentInfo
{
2016-05-13 22:44:08 +00:00
public bool SupportNoGame ;
2017-04-15 19:53:02 +00:00
public int Rotation_CCW ;
}
//disposable resources
private LibRetro retro ;
private UnmanagedResourceHeap unmanagedResources = new UnmanagedResourceHeap ( ) ;
/// <summary>
/// Cached information sent to the frontend by environment calls
/// </summary>
RetroEnvironmentInfo environmentInfo = new RetroEnvironmentInfo ( ) ;
public class RetroDescription
{
/// <summary>
/// String containing a friendly display name for the core, but we probably shouldn't use this. I decided it's better to get the user used to using filenames as core 'codenames' instead.
/// </summary>
public string LibraryName ;
/// <summary>
/// String containing a friendly version number for the core library
/// </summary>
public string LibraryVersion ;
/// <summary>
/// List of extensions as "sfc|smc|fig" which this core accepts.
/// </summary>
public string ValidExtensions ;
/// <summary>
/// Whether the core needs roms to be specified as paths (can't take rom data buffersS)
/// </summary>
public bool NeedsRomAsPath ;
/// <summary>
/// Whether the core needs roms stored as archives (e.g. arcade roms). We probably shouldn't employ the dearchiver prompts when opening roms for these cores.
/// </summary>
public bool NeedsArchives ;
/// <summary>
/// Whether the core can be run without a game provided (e.g. stand-alone games, like 2048)
/// </summary>
public bool SupportsNoGame ;
/// <summary>
/// Variables defined by the core
/// </summary>
public Dictionary < string , VariableDescription > Variables = new Dictionary < string , VariableDescription > ( ) ;
}
public class VariableDescription
{
public string Name ;
public string Description ;
public string [ ] Options ;
public string DefaultOption { get { return Options [ 0 ] ; } }
public override string ToString ( )
{
return string . Format ( "{0} ({1}) = ({2})" , Name , Description , string . Join ( "|" , Options ) ) ;
}
}
public readonly RetroDescription Description = new RetroDescription ( ) ;
//path configuration
string SystemDirectory , SaveDirectory ;
IntPtr SystemDirectoryAtom , SaveDirectoryAtom ;
public LibRetroEmulator ( CoreComm nextComm , string modulename )
{
ServiceProvider = new BasicServiceProvider ( this ) ;
_SyncSettings = new SyncSettings ( ) ;
retro_environment_cb = new LibRetro . retro_environment_t ( retro_environment ) ;
retro_video_refresh_cb = new LibRetro . retro_video_refresh_t ( retro_video_refresh ) ;
retro_audio_sample_cb = new LibRetro . retro_audio_sample_t ( retro_audio_sample ) ;
retro_audio_sample_batch_cb = new LibRetro . retro_audio_sample_batch_t ( retro_audio_sample_batch ) ;
retro_input_poll_cb = new LibRetro . retro_input_poll_t ( retro_input_poll ) ;
retro_input_state_cb = new LibRetro . retro_input_state_t ( retro_input_state ) ;
retro_log_printf_cb = new LibRetro . retro_log_printf_t ( retro_log_printf ) ;
//no way (need new mechanism) to check for SSSE3, MMXEXT, SSE4, SSE42
retro_perf_callback . get_cpu_features = new LibRetro . retro_get_cpu_features_t ( ( ) = > ( ulong ) (
( Win32PInvokes . IsProcessorFeaturePresent ( Win32PInvokes . ProcessorFeature . InstructionsXMMIAvailable ) ? LibRetro . RETRO_SIMD . SSE : 0 ) |
( Win32PInvokes . IsProcessorFeaturePresent ( Win32PInvokes . ProcessorFeature . InstructionsXMMI64Available ) ? LibRetro . RETRO_SIMD . SSE2 : 0 ) |
( Win32PInvokes . IsProcessorFeaturePresent ( Win32PInvokes . ProcessorFeature . InstructionsSSE3Available ) ? LibRetro . RETRO_SIMD . SSE3 : 0 ) |
( Win32PInvokes . IsProcessorFeaturePresent ( Win32PInvokes . ProcessorFeature . InstructionsMMXAvailable ) ? LibRetro . RETRO_SIMD . MMX : 0 )
) ) ;
retro_perf_callback . get_perf_counter = new LibRetro . retro_perf_get_counter_t ( ( ) = > System . Diagnostics . Stopwatch . GetTimestamp ( ) ) ;
retro_perf_callback . get_time_usec = new LibRetro . retro_perf_get_time_usec_t ( ( ) = > DateTime . Now . Ticks / 10 ) ;
retro_perf_callback . perf_log = new LibRetro . retro_perf_log_t ( ( ) = > { } ) ;
retro_perf_callback . perf_register = new LibRetro . retro_perf_register_t ( ( ref LibRetro . retro_perf_counter counter ) = > { } ) ;
retro_perf_callback . perf_start = new LibRetro . retro_perf_start_t ( ( ref LibRetro . retro_perf_counter counter ) = > { } ) ;
retro_perf_callback . perf_stop = new LibRetro . retro_perf_stop_t ( ( ref LibRetro . retro_perf_counter counter ) = > { } ) ;
retro = new LibRetro ( modulename ) ;
try
{
CoreComm = nextComm ;
//this series of steps may be mystical.
LibRetro . retro_system_info system_info = new LibRetro . retro_system_info ( ) ;
retro . retro_get_system_info ( ref system_info ) ;
//the dosbox core calls GET_SYSTEM_DIRECTORY and GET_SAVE_DIRECTORY from retro_set_environment.
//so, lets set some temporary values (which we'll replace)
SystemDirectory = Path . GetDirectoryName ( modulename ) ;
SystemDirectoryAtom = unmanagedResources . StringToHGlobalAnsi ( SystemDirectory ) ;
SaveDirectory = Path . GetDirectoryName ( modulename ) ;
SaveDirectoryAtom = unmanagedResources . StringToHGlobalAnsi ( SaveDirectory ) ;
retro . retro_set_environment ( retro_environment_cb ) ;
retro . retro_set_video_refresh ( retro_video_refresh_cb ) ;
retro . retro_set_audio_sample ( retro_audio_sample_cb ) ;
retro . retro_set_audio_sample_batch ( retro_audio_sample_batch_cb ) ;
retro . retro_set_input_poll ( retro_input_poll_cb ) ;
retro . retro_set_input_state ( retro_input_state_cb ) ;
//compile descriptive information
Description . NeedsArchives = system_info . block_extract ;
Description . NeedsRomAsPath = system_info . need_fullpath ;
Description . LibraryName = system_info . library_name ;
Description . LibraryVersion = system_info . library_version ;
Description . ValidExtensions = system_info . valid_extensions ;
Description . SupportsNoGame = environmentInfo . SupportNoGame ;
}
catch
{
retro . Dispose ( ) ;
retro = null ;
throw ;
}
}
public IEmulatorServiceProvider ServiceProvider { get ; private set ; }
public bool LoadData ( byte [ ] data )
{
LibRetro . retro_game_info gi = new LibRetro . retro_game_info ( ) ;
fixed ( byte * p = & data [ 0 ] )
{
gi . data = ( IntPtr ) p ;
gi . meta = "" ;
gi . path = "" ;
gi . size = ( uint ) data . Length ;
return LoadWork ( ref gi ) ;
}
}
public bool LoadPath ( string path )
{
LibRetro . retro_game_info gi = new LibRetro . retro_game_info ( ) ;
gi . path = path ; //is this the right encoding? seems to be ok
return LoadWork ( ref gi ) ;
}
public bool LoadNoGame ( )
{
LibRetro . retro_game_info gi = new LibRetro . retro_game_info ( ) ;
return LoadWork ( ref gi ) ;
}
bool LoadWork ( ref LibRetro . retro_game_info gi )
{
//defer this until loading because during the LibRetroEmulator constructor, we dont have access to the game name and so paths can't be selected
//this cannot be done until set_environment is complete
if ( CoreComm . CoreFileProvider = = null )
{
SaveDirectory = SystemDirectory = "" ;
}
else
{
SystemDirectory = CoreComm . CoreFileProvider . GetRetroSystemPath ( ) ;
SaveDirectory = CoreComm . CoreFileProvider . GetRetroSaveRAMDirectory ( ) ;
SystemDirectoryAtom = unmanagedResources . StringToHGlobalAnsi ( SystemDirectory ) ;
SaveDirectoryAtom = unmanagedResources . StringToHGlobalAnsi ( SaveDirectory ) ;
}
//defer this until loading because it triggers the core to read save and system paths
//if any cores did that from set_environment then i'm assured we can call set_environment again here before retro_init and it should work
//--alcaro says any cores that can't handle that should be considered a bug
//UPDATE: dosbox does that, so lets try it
retro . retro_set_environment ( retro_environment_cb ) ;
retro . retro_init ( ) ;
if ( ! retro . retro_load_game ( ref gi ) )
{
Console . WriteLine ( "retro_load_game() failed" ) ;
return false ;
}
//TODO - libretro cores can return a varying serialize size over time. I tried to get them to write it in the docs...
savebuff = new byte [ retro . retro_serialize_size ( ) ] ;
savebuff2 = new byte [ savebuff . Length + 13 ] ;
LibRetro . retro_system_av_info av = new LibRetro . retro_system_av_info ( ) ;
retro . retro_get_system_av_info ( ref av ) ;
BufferWidth = ( int ) av . geometry . base_width ;
BufferHeight = ( int ) av . geometry . base_height ;
vidbuff = new int [ av . geometry . max_width * av . geometry . max_height ] ;
dar = av . geometry . aspect_ratio ;
// TODO: more precise
CoreComm . VsyncNum = ( int ) ( 10000000 * av . timing . fps ) ;
CoreComm . VsyncDen = 10000000 ;
SetupResampler ( av . timing . fps , av . timing . sample_rate ) ;
( ServiceProvider as BasicServiceProvider ) . Register < ISoundProvider > ( resampler ) ;
ControllerDefinition = CreateControllerDefinition ( _SyncSettings ) ;
return true ;
}
public static ControllerDefinition CreateControllerDefinition ( SyncSettings syncSettings )
{
ControllerDefinition definition = new ControllerDefinition ( ) ;
definition . Name = "LibRetro Controls" ; // <-- for compatibility
foreach ( var item in new [ ] {
"P1 {0} Up" , "P1 {0} Down" , "P1 {0} Left" , "P1 {0} Right" , "P1 {0} Select" , "P1 {0} Start" , "P1 {0} Y" , "P1 {0} B" , "P1 {0} X" , "P1 {0} A" , "P1 {0} L" , "P1 {0} R" ,
"P2 {0} Up" , "P2 {0} Down" , "P2 {0} Left" , "P2 {0} Right" , "P2 {0} Select" , "P2 {0} Start" , "P2 {0} Y" , "P2 {0} B" , "P2 {0} X" , "P2 {0} A" , "P2 {0} L" , "P2 {0} R" ,
} )
definition . BoolButtons . Add ( string . Format ( item , "RetroPad" ) ) ;
definition . BoolButtons . Add ( "Pointer Pressed" ) ; //TODO: this isnt showing up in the binding panel. I dont want to find out why.
definition . FloatControls . Add ( "Pointer X" ) ;
definition . FloatControls . Add ( "Pointer Y" ) ;
definition . FloatRanges . Add ( new ControllerDefinition . FloatRange ( - 32767 , 0 , 32767 ) ) ;
definition . FloatRanges . Add ( new ControllerDefinition . FloatRange ( - 32767 , 0 , 32767 ) ) ;
foreach ( var key in new [ ] {
"Key Backspace" , "Key Tab" , "Key Clear" , "Key Return" , "Key Pause" , "Key Escape" ,
"Key Space" , "Key Exclaim" , "Key QuoteDbl" , "Key Hash" , "Key Dollar" , "Key Ampersand" , "Key Quote" , "Key LeftParen" , "Key RightParen" , "Key Asterisk" , "Key Plus" , "Key Comma" , "Key Minus" , "Key Period" , "Key Slash" ,
"Key 0" , "Key 1" , "Key 2" , "Key 3" , "Key 4" , "Key 5" , "Key 6" , "Key 7" , "Key 8" , "Key 9" ,
"Key Colon" , "Key Semicolon" , "Key Less" , "Key Equals" , "Key Greater" , "Key Question" , "Key At" , "Key LeftBracket" , "Key Backslash" , "Key RightBracket" , "Key Caret" , "Key Underscore" , "Key Backquote" ,
"Key A" , "Key B" , "Key C" , "Key D" , "Key E" , "Key F" , "Key G" , "Key H" , "Key I" , "Key J" , "Key K" , "Key L" , "Key M" , "Key N" , "Key O" , "Key P" , "Key Q" , "Key R" , "Key S" , "Key T" , "Key U" , "Key V" , "Key W" , "Key X" , "Key Y" , "Key Z" ,
"Key Delete" ,
"Key KP0" , "Key KP1" , "Key KP2" , "Key KP3" , "Key KP4" , "Key KP5" , "Key KP6" , "Key KP7" , "Key KP8" , "Key KP9" ,
"Key KP_Period" , "Key KP_Divide" , "Key KP_Multiply" , "Key KP_Minus" , "Key KP_Plus" , "Key KP_Enter" , "Key KP_Equals" ,
"Key Up" , "Key Down" , "Key Right" , "Key Left" , "Key Insert" , "Key Home" , "Key End" , "Key PageUp" , "Key PageDown" ,
"Key F1" , "Key F2" , "Key F3" , "Key F4" , "Key F5" , "Key F6" , "Key F7" , "Key F8" , "Key F9" , "Key F10" , "Key F11" , "Key F12" , "Key F13" , "Key F14" , "Key F15" ,
"Key NumLock" , "Key CapsLock" , "Key ScrollLock" , "Key RShift" , "Key LShift" , "Key RCtrl" , "Key LCtrl" , "Key RAlt" , "Key LAlt" , "Key RMeta" , "Key LMeta" , "Key LSuper" , "Key RSuper" , "Key Mode" , "Key Compose" ,
"Key Help" , "Key Print" , "Key SysReq" , "Key Break" , "Key Menu" , "Key Power" , "Key Euro" , "Key Undo"
} )
{
definition . BoolButtons . Add ( key ) ;
definition . CategoryLabels [ key ] = "RetroKeyboard" ;
}
return definition ;
}
public ControllerDefinition ControllerDefinition { get ; private set ; }
public IController Controller { get ; set ; }
public void FrameAdvance ( bool render , bool rendersound = true )
{
//TODO - consider changing directory and using Libretro subdir of bizhawk as a kind of sandbox, for the duration of the run?
IsLagFrame = true ;
Frame + + ;
nsamprecv = 0 ;
retro . retro_run ( ) ;
//Console.WriteLine("[{0}]", nsamprecv);
}
public int Frame { get ; private set ; }
public string SystemId
{
get { return "Libretro" ; }
}
public bool DeterministicEmulation
{
// who knows
get { return true ; }
}
public string BoardName
{
get { return null ; }
}
#region ISaveRam
//TODO - terrible things will happen if this changes at runtime
byte [ ] saverambuff = new byte [ 0 ] ;
public byte [ ] CloneSaveRam ( )
{
int size = ( int ) retro . retro_get_memory_size ( LibRetro . RETRO_MEMORY . SAVE_RAM ) ;
if ( saverambuff . Length ! = size )
saverambuff = new byte [ size ] ;
IntPtr src = retro . retro_get_memory_data ( LibRetro . RETRO_MEMORY . SAVE_RAM ) ;
if ( src = = IntPtr . Zero )
return null ;
Marshal . Copy ( src , saverambuff , 0 , size ) ;
return ( byte [ ] ) saverambuff . Clone ( ) ;
}
public void StoreSaveRam ( byte [ ] data )
{
int size = ( int ) retro . retro_get_memory_size ( LibRetro . RETRO_MEMORY . SAVE_RAM ) ;
if ( size = = 0 )
return ;
IntPtr dst = retro . retro_get_memory_data ( LibRetro . RETRO_MEMORY . SAVE_RAM ) ;
if ( dst = = IntPtr . Zero )
throw new Exception ( "retro_get_memory_data(RETRO_MEMORY_SAVE_RAM) returned NULL" ) ;
Marshal . Copy ( data , 0 , dst , size ) ;
}
public bool SaveRamModified
{
[FeatureNotImplemented]
get
{
//if we dont have saveram, it isnt modified. otherwise, assume it is
int size = ( int ) retro . retro_get_memory_size ( LibRetro . RETRO_MEMORY . SAVE_RAM ) ;
if ( size = = 0 )
return false ;
return true ;
}
[FeatureNotImplemented]
set { throw new NotImplementedException ( ) ; }
}
#endregion
public void ResetCounters ( )
{
Frame = 0 ;
LagCount = 0 ;
IsLagFrame = false ;
}
#region savestates
private byte [ ] savebuff ;
private byte [ ] savebuff2 ;
public void SaveStateText ( System . IO . TextWriter writer )
{
var temp = SaveStateBinary ( ) ;
temp . SaveAsHex ( writer ) ;
}
public void LoadStateText ( System . IO . TextReader reader )
{
string hex = reader . ReadLine ( ) ;
byte [ ] state = new byte [ hex . Length / 2 ] ;
state . ReadFromHex ( hex ) ;
LoadStateBinary ( new BinaryReader ( new MemoryStream ( state ) ) ) ;
}
public void SaveStateBinary ( System . IO . BinaryWriter writer )
{
//is this the only way we know of to detect unavailable savestates?
if ( savebuff . Length > 0 )
{
fixed ( byte * ptr = & savebuff [ 0 ] )
{
if ( ! retro . retro_serialize ( ( IntPtr ) ptr , ( uint ) savebuff . Length ) )
throw new Exception ( "retro_serialize() failed" ) ;
}
}
writer . Write ( savebuff . Length ) ;
writer . Write ( savebuff ) ;
// other variables
writer . Write ( Frame ) ;
writer . Write ( LagCount ) ;
writer . Write ( IsLagFrame ) ;
}
public void LoadStateBinary ( System . IO . BinaryReader reader )
{
int newlen = reader . ReadInt32 ( ) ;
if ( newlen > savebuff . Length )
throw new Exception ( "Unexpected buffer size" ) ;
reader . Read ( savebuff , 0 , newlen ) ;
if ( savebuff . Length > 0 )
{
fixed ( byte * ptr = & savebuff [ 0 ] )
{
if ( ! retro . retro_unserialize ( ( IntPtr ) ptr , ( uint ) newlen ) )
throw new Exception ( "retro_unserialize() failed" ) ;
}
}
// other variables
Frame = reader . ReadInt32 ( ) ;
LagCount = reader . ReadInt32 ( ) ;
IsLagFrame = reader . ReadBoolean ( ) ;
}
public byte [ ] SaveStateBinary ( )
{
var ms = new System . IO . MemoryStream ( savebuff2 , true ) ;
var bw = new System . IO . BinaryWriter ( ms ) ;
SaveStateBinary ( bw ) ;
bw . Flush ( ) ;
ms . Close ( ) ;
return savebuff2 ;
}
public bool BinarySaveStatesPreferred { get { return true ; } }
#endregion
public CoreComm CoreComm
{
get ;
private set ;
}
#region memory access
void SetupDebuggingStuff ( )
{
}
public MemoryDomainList MemoryDomains { get ; private set ; }
#endregion
public void Dispose ( )
{
if ( resampler ! = null )
{
resampler . Dispose ( ) ;
resampler = null ;
}
if ( retro ! = null )
{
retro . Dispose ( ) ;
retro = null ;
}
unmanagedResources . Dispose ( ) ;
unmanagedResources = null ;
}
#region ISoundProvider
SpeexResampler resampler ;
short [ ] sampbuff = new short [ 0 ] ;
// debug
int nsamprecv = 0 ;
void SetupResampler ( double fps , double sps )
{
Console . WriteLine ( "FPS {0} SPS {1}" , fps , sps ) ;
// todo: more precise?
uint spsnum = ( uint ) sps * 1000 ;
uint spsden = ( uint ) 1000 ;
resampler = new SpeexResampler ( 5 , 44100 * spsden , spsnum , ( uint ) sps , 44100 , null , null ) ;
}
void retro_audio_sample ( short left , short right )
{
resampler . EnqueueSample ( left , right ) ;
nsamprecv + + ;
}
uint retro_audio_sample_batch ( IntPtr data , uint frames )
{
if ( sampbuff . Length < frames * 2 )
sampbuff = new short [ frames * 2 ] ;
Marshal . Copy ( data , sampbuff , 0 , ( int ) ( frames * 2 ) ) ;
resampler . EnqueueSamples ( sampbuff , ( int ) frames ) ;
nsamprecv + = ( int ) frames ;
// what is the return from this used for?
return frames ;
}
#endregion
#region IVideoProvider
float dar ;
int [ ] vidbuff , rawvidbuff ;
LibRetro . RETRO_PIXEL_FORMAT pixelfmt = LibRetro . RETRO_PIXEL_FORMAT . XRGB1555 ;
void Blit555 ( short * src , int * dst , int width , int height , int pitch )
{
for ( int j = 0 ; j < height ; j + + )
{
short * row = src ;
for ( int i = 0 ; i < width ; i + + )
{
short ci = * row ;
int r = ci & 0x001f ;
int g = ci & 0x03e0 ;
int b = ci & 0x7c00 ;
r = ( r < < 3 ) | ( r > > 2 ) ;
g = ( g > > 2 ) | ( g > > 7 ) ;
b = ( b > > 7 ) | ( b > > 12 ) ;
int co = r | g | b | unchecked ( ( int ) 0xff000000 ) ;
* dst = co ;
dst + + ;
row + + ;
}
src + = pitch ;
}
}
void Blit565 ( short * src , int * dst , int width , int height , int pitch )
{
for ( int j = 0 ; j < height ; j + + )
{
short * row = src ;
for ( int i = 0 ; i < width ; i + + )
{
short ci = * row ;
int r = ci & 0x001f ;
int g = ( ci & 0x07e0 ) > > 5 ;
int b = ( ci & 0xf800 ) > > 11 ;
r = ( r < < 3 ) | ( r > > 2 ) ;
g = ( g < < 2 ) | ( g > > 4 ) ;
b = ( b < < 3 ) | ( b > > 2 ) ;
int co = ( b < < 16 ) | ( g < < 8 ) | r ;
* dst = co ;
dst + + ;
row + + ;
}
src + = pitch ;
}
}
void Blit888 ( int * src , int * dst , int width , int height , int pitch )
{
for ( int j = 0 ; j < height ; j + + )
{
int * row = src ;
for ( int i = 0 ; i < width ; i + + )
{
int ci = * row ;
int co = ci | unchecked ( ( int ) 0xff000000 ) ;
* dst = co ;
dst + + ;
row + + ;
}
src + = pitch ;
}
}
void retro_video_refresh ( IntPtr data , uint width , uint height , uint pitch )
{
if ( data = = IntPtr . Zero ) // dup frame
2016-05-13 22:44:08 +00:00
return ;
//if (BufferWidth != width) BufferWidth = (int)width;
//if (BufferHeight != height) BufferHeight = (int)height;
//if (BufferWidth * BufferHeight != rawvidbuff.Length)
2017-04-15 19:53:02 +00:00
// rawvidbuff = new int[BufferWidth * BufferHeight];
//if we have rotation, we might have a geometry mismatch and in any event we need a temp buffer to do the rotation from
2016-05-13 22:44:08 +00:00
//but that's a general problem, isnt it?
if ( rawvidbuff = = null | | rawvidbuff . Length ! = width * height )
{
rawvidbuff = new int [ width * height ] ;
}
int [ ] target = vidbuff ;
if ( environmentInfo . Rotation_CCW ! = 0 )
2017-04-15 19:53:02 +00:00
target = rawvidbuff ;
fixed ( int * dst = & target [ 0 ] )
{
if ( pixelfmt = = LibRetro . RETRO_PIXEL_FORMAT . XRGB8888 )
Blit888 ( ( int * ) data , dst , ( int ) width , ( int ) height , ( int ) pitch / 4 ) ;
else if ( pixelfmt = = LibRetro . RETRO_PIXEL_FORMAT . RGB565 )
Blit565 ( ( short * ) data , dst , ( int ) width , ( int ) height , ( int ) pitch / 2 ) ;
else
Blit555 ( ( short * ) data , dst , ( int ) width , ( int ) height , ( int ) pitch / 2 ) ;
2016-05-13 22:44:08 +00:00
}
int dw = BufferWidth , dh = BufferHeight ;
if ( environmentInfo . Rotation_CCW = = 0 ) { }
else if ( environmentInfo . Rotation_CCW = = 270 )
{
for ( int y = 0 ; y < height ; y + + )
for ( int x = 0 ; x < width ; x + + )
{
int dx = dw - y - 1 ;
int dy = x ;
vidbuff [ dy * dw + dx ] = rawvidbuff [ y * width + x ] ;
}
}
else if ( environmentInfo . Rotation_CCW = = 90 )
{
throw new InvalidOperationException ( "PLEASE REPORT THIS BUG: CANTANKEROUS IMAGEBOUND NINJA" ) ;
}
else if ( environmentInfo . Rotation_CCW = = 180 )
{
throw new InvalidOperationException ( "PLEASE REPORT THIS BUG: STAPH REGULARIZATION PROTOCOL" ) ;
2017-04-15 19:53:02 +00:00
}
}
public int [ ] GetVideoBuffer ( )
{
return vidbuff ;
}
public int VirtualWidth
{
get
{
if ( dar < = 0 )
return BufferWidth ;
else if ( dar > 1.0f )
return ( int ) ( BufferHeight * dar ) ;
else
return BufferWidth ;
}
}
public int VirtualHeight
{
get
{
if ( dar < = 0 )
return BufferHeight ;
if ( dar < 1.0f )
return ( int ) ( BufferWidth / dar ) ;
else
return BufferHeight ;
}
}
public int BufferWidth { get ; private set ; }
public int BufferHeight { get ; private set ; }
public int BackgroundColor { get { return unchecked ( ( int ) 0xff000000 ) ; } }
#endregion
#region IInputPollable
public int LagCount { get ; set ; }
public bool IsLagFrame { get ; set ; }
public IInputCallbackSystem InputCallbacks
{
[FeatureNotImplemented]
get
{ throw new NotImplementedException ( ) ; }
}
#endregion
}
}