2017-04-18 03:48:16 +00:00
//derived from libsnes
//types of messages:
//cmd: frontend->core: "command to core" a command from the frontend which causes emulation to proceed. when sending a command, the frontend should wait for an eMessage::BRK_Complete before proceeding, although a debugger might proceed after any BRK
//query: frontend->core: "query to core" a query from the frontend which can (and should) be satisfied immediately by the core but which does not result in emulation processes (notably, nothing resembling a CMD and nothing which can trigger a BRK)
//sig: core->frontend: "core signal" a synchronous operation called from the emulation process which the frontend should handle immediately without issuing any calls into the core
//brk: core->frontend: "core break" the emulation process has suspended. the frontend is free to do whatever it wishes.
# define _CRT_NONSTDC_NO_DEPRECATE
# include <Windows.h>
# include <stdint.h>
# include <stdlib.h>
# include <string.h>
# include <stdarg.h>
# include <stdio.h>
# include <stdio.h>
# include <string>
# define bool unsigned char
# include "libretro.h"
# undef bool
extern " C " uint64_t cpu_features_get ( ) ;
# include "libco/libco.h"
//can't use retroarch's dynamic.h, it's too full of weird stuff. don't need it anyway
typedef uint8_t u8 ;
typedef uint16_t u16 ;
typedef uint64_t u64 ;
typedef uint32_t u32 ;
typedef u8 u8bool ;
typedef int16_t s16 ;
typedef int32_t s32 ;
typedef int64_t s64 ;
typedef void ( * Action ) ( ) ;
struct retro_core_t
{
void ( * retro_init ) ( void ) ;
void ( * retro_deinit ) ( void ) ;
unsigned ( * retro_api_version ) ( void ) ;
void ( * retro_get_system_info ) ( struct retro_system_info * ) ;
void ( * retro_get_system_av_info ) ( struct retro_system_av_info * ) ;
void ( * retro_set_environment ) ( retro_environment_t ) ;
void ( * retro_set_video_refresh ) ( retro_video_refresh_t ) ;
void ( * retro_set_audio_sample ) ( retro_audio_sample_t ) ;
void ( * retro_set_audio_sample_batch ) ( retro_audio_sample_batch_t ) ;
void ( * retro_set_input_poll ) ( retro_input_poll_t ) ;
void ( * retro_set_input_state ) ( retro_input_state_t ) ;
void ( * retro_set_controller_port_device ) ( unsigned , unsigned ) ;
void ( * retro_reset ) ( void ) ;
void ( * retro_run ) ( void ) ;
size_t ( * retro_serialize_size ) ( void ) ;
u8bool ( * retro_serialize ) ( void * , size_t ) ;
u8bool ( * retro_unserialize ) ( const void * , size_t ) ;
void ( * retro_cheat_reset ) ( void ) ;
void ( * retro_cheat_set ) ( unsigned , u8bool , const char * ) ;
u8bool ( * retro_load_game ) ( const struct retro_game_info * ) ;
u8bool ( * retro_load_game_special ) ( unsigned ,
const struct retro_game_info * , size_t ) ;
void ( * retro_unload_game ) ( void ) ;
unsigned ( * retro_get_region ) ( void ) ;
void * ( * retro_get_memory_data ) ( unsigned ) ;
size_t ( * retro_get_memory_size ) ( unsigned ) ;
} ;
enum eMessage : s32
{
NotSet ,
Resume ,
QUERY_FIRST ,
2017-04-18 08:17:11 +00:00
QUERY_GetMemory ,
2017-04-18 03:48:16 +00:00
QUERY_LAST ,
CMD_FIRST ,
CMD_SetEnvironment ,
CMD_LoadNoGame ,
CMD_LoadData ,
CMD_LoadPath ,
CMD_Deinit ,
CMD_Reset ,
CMD_Run ,
CMD_Serialize ,
CMD_Unserialize ,
CMD_LAST ,
2017-05-01 09:16:31 +00:00
SIG_InputState ,
2017-04-18 03:48:16 +00:00
SIG_VideoUpdate ,
2017-05-01 09:33:09 +00:00
SIG_Sample ,
SIG_SampleBatch ,
2017-04-18 03:48:16 +00:00
} ;
enum eStatus : s32
{
eStatus_Idle ,
eStatus_CMD ,
eStatus_BRK
} ;
enum BufId : s32 {
Param0 = 0 ,
Param1 = 1 ,
SystemDirectory = 2 ,
SaveDirectory = 3 ,
CoreDirectory = 4 ,
CoreAssetsDirectory = 5 ,
BufId_Num //excess sized by 1.. no big deal
} ;
//TODO: do any of these need to be volatile?
struct CommStruct
{
//the cmd being executed
eMessage cmd ;
//the status of the core
eStatus status ;
//the SIG or BRK that the core is halted in
eMessage reason ;
//flexible in/out parameters
//these are all "overloaded" a little so it isn't clear what's used for what in for any particular message..
//but I think it will beat having to have some kind of extremely verbose custom layouts for every message
u32 id , addr , value , size ;
u32 port , device , index , slot ; //for input state
//variables meant for stateful communication (not parameters)
//may be in, out, or inout. it's pretty sloppy.
struct {
//set by the core
retro_system_info retro_system_info ;
retro_system_av_info retro_system_av_info ;
size_t retro_serialize_size ;
u32 retro_region ;
u32 retro_api_version ;
retro_pixel_format pixel_format ; //default is 0 -- RETRO_PIXEL_FORMAT_0RGB1555
s32 rotation_ccw ;
bool support_no_game ;
retro_get_proc_address_t core_get_proc_address ;
retro_game_geometry retro_game_geometry ;
u8bool retro_game_geometry_dirty ; //c# can clear this when it's acknowledged (but I think we might handle it from here? not sure)
//defined by the core. values arent put here, this is just the variables defined by the core
//todo: shutdown tidy
s32 variable_count ;
const char * * variable_keys ;
const char * * variable_comments ;
//c# sets these with thunked callbacks
retro_perf_callback retro_perf_callback ;
//various stashed stuff solely for c# convenience
u64 processor_features ;
s32 fb_width , fb_height ; //core sets these; c# picks up, and..
s32 * fb_bufptr ; //..sets this for the core to spill its data nito
} env ;
//always used in pairs
void * buf [ BufId_Num ] ;
s32 buf_size [ BufId_Num ] ;
//===========================================================
//private stuff
std : : string * variables ;
bool variables_dirty ;
void * privbuf [ BufId_Num ] ; //TODO remember to tidy this.. (needs to be done in snes too)
void SetString ( int id , const char * str )
{
size_t len = strlen ( str ) ;
CopyBuffer ( id , ( void * ) str , len + 1 ) ;
}
void CopyBuffer ( int id , void * ptr , s32 size )
{
if ( privbuf [ id ] ) free ( privbuf [ id ] ) ;
buf [ id ] = privbuf [ id ] = malloc ( size ) ;
memcpy ( buf [ id ] , ptr , size ) ;
buf_size [ id ] = size ;
}
void SetBuffer ( int id , void * ptr , s32 size )
{
buf [ id ] = ptr ;
buf_size [ id ] = size ;
}
struct {
} strings ;
HMODULE dllModule ;
retro_core_t funs ;
void LoadSymbols ( )
{
//retroarch would throw an error here if the FP ws null. maybe better than throwing an error later, but are all the functions required?
# define SYMBOL(x) { \
FARPROC func = GetProcAddress ( dllModule , # x ) ; \
memcpy ( & funs . x , & func , sizeof ( func ) ) ; \
}
SYMBOL ( retro_init ) ;
SYMBOL ( retro_deinit ) ;
SYMBOL ( retro_api_version ) ;
SYMBOL ( retro_get_system_info ) ;
SYMBOL ( retro_get_system_av_info ) ;
SYMBOL ( retro_set_environment ) ;
SYMBOL ( retro_set_video_refresh ) ;
SYMBOL ( retro_set_audio_sample ) ;
SYMBOL ( retro_set_audio_sample_batch ) ;
SYMBOL ( retro_set_input_poll ) ;
SYMBOL ( retro_set_input_state ) ;
SYMBOL ( retro_set_controller_port_device ) ;
SYMBOL ( retro_reset ) ;
SYMBOL ( retro_run ) ;
SYMBOL ( retro_serialize_size ) ;
SYMBOL ( retro_serialize ) ;
SYMBOL ( retro_unserialize ) ;
SYMBOL ( retro_cheat_reset ) ;
SYMBOL ( retro_cheat_set ) ;
SYMBOL ( retro_load_game ) ;
SYMBOL ( retro_load_game_special ) ;
SYMBOL ( retro_unload_game ) ;
SYMBOL ( retro_get_region ) ;
SYMBOL ( retro_get_memory_data ) ;
SYMBOL ( retro_get_memory_size ) ;
}
retro_core_t fn ;
} comm ;
//coroutines
cothread_t co_control , co_emu , co_emu_suspended ;
//internal state
Action CMD_cb ;
void BREAK ( eMessage msg ) {
comm . status = eStatus_BRK ;
comm . reason = msg ;
co_emu_suspended = co_active ( ) ;
co_switch ( co_control ) ;
comm . status = eStatus_CMD ;
}
//all this does is run commands on the emulation thread infinitely forever
//(I should probably make a mechanism for bailing...)
void new_emuthread ( )
{
for ( ; ; )
{
//process the current CMD
CMD_cb ( ) ;
//when that returned, we're definitely done with the CMD--so we're now IDLE
comm . status = eStatus_Idle ;
co_switch ( co_control ) ;
}
}
void retro_log_printf ( enum retro_log_level level , const char * fmt , . . . )
{
va_list args ;
va_start ( args , fmt ) ;
vprintf ( fmt , args ) ;
va_end ( args ) ;
}
u8bool retro_environment ( unsigned cmd , void * data )
{
switch ( cmd )
{
case RETRO_ENVIRONMENT_SET_ROTATION :
comm . env . rotation_ccw = ( int ) * ( const unsigned * ) data * 90 ;
return true ;
case RETRO_ENVIRONMENT_GET_OVERSCAN :
return false ; //could return true to crop overscan
case RETRO_ENVIRONMENT_GET_CAN_DUPE :
return true ;
case RETRO_ENVIRONMENT_SET_MESSAGE :
{
//TODO: try to respect design principle by forwarding to frontend with the timer
auto & msg = * ( retro_message * ) data ;
printf ( " %s \n " , msg . msg ) ;
return true ;
}
case RETRO_ENVIRONMENT_SHUTDOWN :
//TODO low priority
return false ;
case RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL :
//unneeded
return false ;
case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY :
* ( const char * * ) data = ( const char * ) comm . buf [ SystemDirectory ] ;
return true ;
case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT :
comm . env . pixel_format = * ( const enum retro_pixel_format * ) data ;
return true ;
case RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS :
//TODO medium priority
return false ;
case RETRO_ENVIRONMENT_SET_KEYBOARD_CALLBACK :
//TODO high priority (to support keyboard consoles, probably high value for us. but that may take a lot of infrastructure work)
return false ;
case RETRO_ENVIRONMENT_SET_DISK_CONTROL_INTERFACE :
//TODO high priority (to support disc systems)
return false ;
case RETRO_ENVIRONMENT_SET_HW_RENDER :
//TODO high priority (to support 3d renderers
return false ;
case RETRO_ENVIRONMENT_GET_VARIABLE :
{
//according to retroarch's `core_option_manager_get` this is what we should do
comm . variables_dirty = false ;
auto req = ( retro_variable * ) data ;
req - > value = nullptr ;
for ( int i = 0 ; i < comm . env . variable_count ; i + + )
{
if ( ! strcmp ( comm . env . variable_keys [ i ] , req - > key ) )
{
req - > value = comm . variables [ i ] . c_str ( ) ;
return true ;
}
}
return true ;
}
case RETRO_ENVIRONMENT_SET_VARIABLES :
{
auto var = ( retro_variable * ) data ;
int nVars = 0 ;
while ( var - > key )
nVars + + , var + + ;
comm . variables = new std : : string [ nVars ] ;
comm . env . variable_count = nVars ;
comm . env . variable_keys = new const char * [ nVars ] ;
comm . env . variable_comments = new const char * [ nVars ] ;
var = ( retro_variable * ) data ;
for ( int i = 0 ; i < nVars ; i + + )
{
comm . env . variable_keys [ i ] = var [ i ] . key ;
comm . env . variable_comments [ i ] = var [ i ] . value ;
//analyze to find default and save it
std : : string comment = var [ i ] . value ;
auto ofs = comment . find_first_of ( ' ; ' ) + 2 ;
auto pipe = comment . find ( ' | ' , ofs ) ;
if ( pipe = = std : : string : : npos )
comm . variables [ i ] = comment . substr ( ofs ) ;
else
comm . variables [ i ] = comment . substr ( ofs , pipe - ofs ) ;
}
return true ;
}
case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE :
* ( u8bool * ) data = comm . variables_dirty ;
break ;
case RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME :
comm . env . support_no_game = ! ! * ( u8bool * ) data ;
break ;
case RETRO_ENVIRONMENT_GET_LIBRETRO_PATH :
* ( const char * * ) data = ( const char * ) comm . buf [ CoreDirectory ] ;
return true ;
case RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK :
//dont know what to do with this yet
return false ;
case RETRO_ENVIRONMENT_SET_FRAME_TIME_CALLBACK :
//dont know what to do with this yet
return false ;
case RETRO_ENVIRONMENT_GET_RUMBLE_INTERFACE :
//TODO low priority
return false ;
case RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES :
//TODO medium priority - other input methods
* ( u64 * ) data = ( 1 < < RETRO_DEVICE_JOYPAD ) ;
return true ;
case RETRO_ENVIRONMENT_GET_LOG_INTERFACE :
( ( retro_log_callback * ) data ) - > log = retro_log_printf ;
return true ;
case RETRO_ENVIRONMENT_GET_PERF_INTERFACE :
* ( ( retro_perf_callback * ) data ) = comm . env . retro_perf_callback ;
return true ;
case RETRO_ENVIRONMENT_GET_LOCATION_INTERFACE :
//TODO low priority
return false ;
case RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY :
* ( const char * * ) data = ( const char * ) comm . buf [ CoreAssetsDirectory ] ;
return true ;
case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY :
* ( const char * * ) data = ( const char * ) comm . buf [ SaveDirectory ] ;
return true ;
case RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO :
printf ( " NEED RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO \n " ) ;
return false ;
case RETRO_ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK :
comm . env . core_get_proc_address = ( ( retro_get_proc_address_interface * ) data ) - > get_proc_address ;
return true ;
case RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO :
//needs retro_load_game_special to be useful; not supported yet
return false ;
case RETRO_ENVIRONMENT_SET_CONTROLLER_INFO :
//TODO medium priority probably
return false ;
case RETRO_ENVIRONMENT_SET_GEOMETRY :
comm . env . retro_game_geometry = * ( ( const retro_game_geometry * ) data ) ;
comm . env . retro_game_geometry_dirty = true ;
return true ;
case RETRO_ENVIRONMENT_GET_USERNAME :
//we definitely want to return false here so the core will do something deterministic
return false ;
case RETRO_ENVIRONMENT_GET_LANGUAGE :
* ( ( unsigned * ) data ) = RETRO_LANGUAGE_ENGLISH ;
return true ;
}
return false ;
}
template < int ROT > static inline int * address ( int width , int height , int pitch , int x , int y , int * dstbuf , int * optimize0dst )
{
switch ( ROT )
{
case 0 :
return optimize0dst ;
case 90 :
//TODO:
return optimize0dst ;
case 180 :
//TODO:
return optimize0dst ;
case 270 :
{
int dx = width - y - 1 ;
int dy = x ;
return dstbuf + dy * width + dx ;
}
default :
//impossible
return 0 ;
}
}
template < int ROT > void Blit555 ( short * srcbuf , s32 * dstbuf , int width , int height , int pitch )
{
s32 * dst = dstbuf ;
for ( int y = 0 ; y < height ; y + + )
{
short * row = srcbuf ;
for ( int x = 0 ; x < width ; x + + )
{
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 | 0xff000000 ;
* address < ROT > ( width , height , pitch , x , y , dstbuf , dst ) = co ;
dst + + ;
row + + ;
}
srcbuf + = pitch / 2 ;
}
}
template < int ROT > void Blit565 ( short * srcbuf , s32 * dstbuf , int width , int height , int pitch )
{
s32 * dst = dstbuf ;
for ( int y = 0 ; y < height ; y + + )
{
short * row = srcbuf ;
for ( int x = 0 ; x < width ; x + + )
{
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 ;
* address < ROT > ( width , height , pitch , x , y , dstbuf , dst ) = co ;
dst + + ;
row + + ;
}
srcbuf + = pitch / 2 ;
}
}
template < int ROT > void Blit888 ( int * srcbuf , s32 * dstbuf , int width , int height , int pitch )
{
s32 * dst = dstbuf ;
for ( int y = 0 ; y < height ; y + + )
{
int * row = srcbuf ;
for ( int x = 0 ; x < width ; x + + )
{
int ci = * row ;
int co = ci | 0xff000000 ;
* address < ROT > ( width , height , pitch , x , y , dstbuf , dst ) = co ;
dst + + ;
row + + ;
}
srcbuf + = pitch / 4 ;
}
}
void retro_video_refresh ( const void * data , unsigned width , unsigned height , size_t pitch )
{
//handle a "dup frame" -- same as previous frame. so there isn't anything to be done here
if ( ! data )
return ;
comm . env . fb_width = ( s32 ) width ;
comm . env . fb_height = ( s32 ) height ;
2017-05-01 09:16:31 +00:00
//stash pitch if needed
2017-04-18 03:48:16 +00:00
//notify c# of these new settings and let it allocate a buffer suitable for receiving the output (so we can work directly into c#'s int[])
//c# can read the settings right out of the comm env
2017-05-01 09:16:31 +00:00
BREAK ( eMessage : : SIG_VideoUpdate ) ;
2017-04-18 03:48:16 +00:00
////if (BufferWidth != width) BufferWidth = (int)width;
////if (BufferHeight != height) BufferHeight = (int)height;
////if (BufferWidth * BufferHeight != rawvidbuff.Length)
//// 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
////but that's a general problem, isnt it?
//if (comm.env.fb.raw == nullptr || comm.env.fb.raw_length != width * height)
//{
// if(comm.env.fb.raw)
// delete[] comm.env.fb.raw;
// comm.env.fb.raw = new u32[width * height];
// comm.env.fb.width = width;
// comm.env.fb.height = height;
//}
int w = ( int ) width ;
int h = ( int ) height ;
int p = ( int ) pitch ;
switch ( comm . env . pixel_format )
{
case RETRO_PIXEL_FORMAT_0RGB1555 :
switch ( comm . env . rotation_ccw )
{
case 0 : Blit555 < 0 > ( ( short * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 90 : Blit555 < 90 > ( ( short * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 180 : Blit555 < 180 > ( ( short * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 270 : Blit555 < 270 > ( ( short * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
}
break ;
case RETRO_PIXEL_FORMAT_XRGB8888 :
switch ( comm . env . rotation_ccw )
{
case 0 : Blit888 < 0 > ( ( int * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 90 : Blit888 < 90 > ( ( int * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 180 : Blit888 < 180 > ( ( int * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 270 : Blit888 < 270 > ( ( int * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
}
break ;
case RETRO_PIXEL_FORMAT_RGB565 :
switch ( comm . env . rotation_ccw )
{
case 0 : Blit565 < 0 > ( ( short * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 90 : Blit565 < 90 > ( ( short * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 180 : Blit565 < 180 > ( ( short * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
case 270 : Blit565 < 270 > ( ( short * ) data , comm . env . fb_bufptr , w , h , p ) ; break ;
}
break ;
}
}
void retro_audio_sample ( s16 left , s16 right )
{
2017-05-01 09:33:09 +00:00
s16 samples [ ] = { left , right } ;
comm . SetBuffer ( BufId : : Param0 , ( void * ) & samples , 4 ) ;
BREAK ( SIG_Sample ) ;
2017-04-18 03:48:16 +00:00
}
size_t retro_audio_sample_batch ( const s16 * data , size_t frames )
{
2017-05-01 09:33:09 +00:00
comm . SetBuffer ( BufId : : Param0 , ( void * ) data , frames * 4 ) ;
BREAK ( SIG_SampleBatch ) ;
return frames ;
2017-04-18 03:48:16 +00:00
}
void retro_input_poll ( )
{
}
s16 retro_input_state ( unsigned port , unsigned device , unsigned index , unsigned id )
{
//we have to bail to c# for this, it's too complex.
comm . port = port ;
comm . device = device ;
comm . index = index ;
comm . id = id ;
2017-05-01 09:16:31 +00:00
BREAK ( eMessage : : SIG_InputState ) ;
2017-04-18 03:48:16 +00:00
return ( s16 ) comm . value ;
}
//loads the game, too
//REQUIREMENTS:
//set SystemDirectory, SaveDirectory, CoreDirectory, CoreAssetsDirectory are set
//retro_perf_callback is set
static void LoadHandler ( eMessage msg )
{
//retro_set_environment() is guaranteed to be called before retro_init().
comm . funs . retro_init ( ) ;
retro_game_info rgi ;
2017-04-26 20:19:31 +00:00
retro_game_info * rgiptr = & rgi ;
2017-04-18 03:48:16 +00:00
memset ( & rgi , 0 , sizeof ( rgi ) ) ;
2017-04-26 20:19:31 +00:00
if ( msg = = eMessage : : CMD_LoadNoGame )
{
rgiptr = nullptr ;
}
2017-04-18 03:48:16 +00:00
else
{
rgi . path = ( const char * ) comm . buf [ BufId : : Param0 ] ;
if ( msg = = eMessage : : CMD_LoadData )
{
rgi . data = comm . buf [ BufId : : Param1 ] ;
rgi . size = comm . buf_size [ BufId : : Param1 ] ;
}
}
2017-04-26 20:19:31 +00:00
comm . funs . retro_load_game ( rgiptr ) ;
2017-04-18 03:48:16 +00:00
//Can be called only after retro_load_game() has successfully completed.
comm . funs . retro_get_system_av_info ( & comm . env . retro_system_av_info ) ;
//guaranteed to have been called before the first call to retro_run() is made.
//(I've put this after the retro_system_av_info runs, in case that's important
comm . funs . retro_set_video_refresh ( retro_video_refresh ) ;
comm . funs . retro_set_audio_sample ( retro_audio_sample ) ;
comm . funs . retro_set_audio_sample_batch ( retro_audio_sample_batch ) ;
comm . funs . retro_set_input_poll ( retro_input_poll ) ;
comm . funs . retro_set_input_state ( retro_input_state ) ;
//Between calls to retro_load_game() and retro_unload_game(), the returned size is never allowed to be larger than a previous returned
//value, to ensure that the frontend can allocate a save state buffer once.
comm . env . retro_serialize_size = comm . funs . retro_serialize_size ( ) ;
//not sure when this can be called, but it's surely safe here
comm . env . retro_region = comm . funs . retro_get_region ( ) ;
}
void cmd_LoadNoGame ( ) { LoadHandler ( eMessage : : CMD_LoadNoGame ) ; }
void cmd_LoadData ( ) { LoadHandler ( eMessage : : CMD_LoadData ) ; }
void cmd_LoadPath ( ) { LoadHandler ( eMessage : : CMD_LoadPath ) ; }
void cmd_Deinit ( )
{
//not sure if we need this
comm . funs . retro_unload_game ( ) ;
comm . funs . retro_deinit ( ) ;
//TODO: tidy
}
void cmd_Reset ( )
{
comm . funs . retro_reset ( ) ;
}
void cmd_Run ( )
{
comm . funs . retro_run ( ) ;
}
void cmd_Serialize ( )
{
comm . value = ! ! comm . funs . retro_serialize ( comm . buf [ BufId : : Param0 ] , comm . buf_size [ BufId : : Param0 ] ) ;
}
void cmd_Unserialize ( )
{
comm . value = ! ! comm . funs . retro_unserialize ( comm . buf [ BufId : : Param0 ] , comm . buf_size [ BufId : : Param0 ] ) ;
}
//TODO
//void(*retro_set_controller_port_device)(unsigned, unsigned);
//void *(*retro_get_memory_data)(unsigned);
//size_t(*retro_get_memory_size)(unsigned);
//TODO low priority
//void(*retro_cheat_reset)(void);
//void(*retro_cheat_set)(unsigned, bool, const char*);
//bool(*retro_load_game_special)(unsigned,
//TODO maybe not sensible though
//void(*retro_unload_game)(void);
void cmd_SetEnvironment ( )
{
//stuff that can't be done until our environment is setup (the core will immediately query the environment)
comm . funs . retro_set_environment ( retro_environment ) ;
}
2017-04-18 08:17:11 +00:00
void query_GetMemory ( )
{
comm . buf_size [ BufId : : Param0 ] = comm . funs . retro_get_memory_size ( comm . value ) ;
comm . buf [ BufId : : Param0 ] = comm . funs . retro_get_memory_data ( comm . value ) ;
}
2017-04-18 03:48:16 +00:00
const Action kHandlers_CMD [ ] = {
cmd_SetEnvironment ,
cmd_LoadNoGame ,
cmd_LoadData ,
cmd_LoadPath ,
cmd_Deinit ,
cmd_Reset ,
cmd_Run ,
cmd_Serialize ,
cmd_Unserialize ,
} ;
const Action kHandlers_QUERY [ ] = {
2017-04-18 08:17:11 +00:00
query_GetMemory ,
2017-04-18 03:48:16 +00:00
} ;
//------------------------------------------------
//DLL INTERFACE
BOOL WINAPI DllMain ( _In_ HINSTANCE hinstDLL , _In_ DWORD fdwReason , _In_ LPVOID lpvReserved )
{
return TRUE ;
}
extern " C " __declspec ( dllexport ) void * __cdecl DllInit ( HMODULE dllModule )
{
memset ( & comm , 0 , sizeof ( comm ) ) ;
//make a coroutine thread to run the emulation in. we'll switch back to this cothread when communicating with the frontend
co_control = co_active ( ) ;
co_emu = co_create ( 128 * 1024 * sizeof ( void * ) , new_emuthread ) ;
//grab all the function pointers we need.
comm . dllModule = dllModule ;
comm . LoadSymbols ( ) ;
//libretro startup steps
//"Can be called at any time, even before retro_init()."
comm . funs . retro_get_system_info ( & comm . env . retro_system_info ) ;
comm . env . retro_api_version = ( u32 ) comm . funs . retro_api_version ( ) ;
//now after this we return to the c# side to let some more setup happen
return & comm ;
}
extern " C " __declspec ( dllexport ) void __cdecl Message ( eMessage msg )
{
if ( msg = = eMessage : : Resume )
{
cothread_t temp = co_emu_suspended ;
co_emu_suspended = NULL ;
co_switch ( temp ) ;
}
if ( msg > = eMessage : : CMD_FIRST & & msg < = eMessage : : CMD_LAST )
{
//CMD is only valid if status is idle
if ( comm . status ! = eStatus_Idle )
{
printf ( " ERROR: cmd during non-idle \n " ) ;
return ;
}
comm . status = eStatus_CMD ;
comm . cmd = msg ;
CMD_cb = kHandlers_CMD [ msg - eMessage : : CMD_FIRST - 1 ] ;
co_switch ( co_emu ) ;
//we could be in ANY STATE when we return from here
}
//QUERY can run any time
//but... some of them might not be safe for re-entrancy.
//later, we should have metadata for messages that indicates that
if ( msg > = eMessage : : QUERY_FIRST & & msg < = eMessage : : QUERY_LAST )
{
Action cb = kHandlers_QUERY [ msg - eMessage : : QUERY_FIRST - 1 ] ;
if ( cb ) cb ( ) ;
}
}
//receives the given buffer and COPIES it. use this for returning values from SIGs
extern " C " __declspec ( dllexport ) void __cdecl CopyBuffer ( int id , void * ptr , s32 size )
{
comm . CopyBuffer ( id , ptr , size ) ;
}
//receives the given buffer and STASHES IT. use this (carefully) for sending params for CMDs
extern " C " __declspec ( dllexport ) void __cdecl SetBuffer ( int id , void * ptr , s32 size )
{
comm . SetBuffer ( id , ptr , size ) ;
}
extern " C " __declspec ( dllexport ) void __cdecl SetVariable ( const char * key , const char * val )
{
for ( int i = 0 ; i < comm . env . variable_count ; i + + )
if ( ! strcmp ( key , comm . env . variable_keys [ i ] ) )
{
comm . variables [ i ] = val ;
comm . variables_dirty = true ;
}
}