BizHawk/LibretroBridge/vs2015/LibretroBridge.cpp

874 lines
24 KiB
C++

//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,
QUERY_GetMemory,
QUERY_LAST,
CMD_FIRST,
CMD_SetEnvironment,
CMD_LoadNoGame,
CMD_LoadData,
CMD_LoadPath,
CMD_Deinit,
CMD_Reset,
CMD_Run,
CMD_UpdateSerializeSize,
CMD_Serialize,
CMD_Unserialize,
CMD_LAST,
SIG_InputState,
SIG_VideoUpdate,
SIG_Sample,
SIG_SampleBatch,
};
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_initial;
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];
size_t 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, size_t 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, size_t 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;
//stash pitch if needed
//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
BREAK(eMessage::SIG_VideoUpdate);
////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)
{
s16 samples[] = {left,right};
comm.SetBuffer(BufId::Param0,(void*)&samples,4);
BREAK(SIG_Sample);
}
size_t retro_audio_sample_batch(const s16 *data, size_t frames)
{
comm.SetBuffer(BufId::Param0, (void*)data, frames*4);
BREAK(SIG_SampleBatch);
return frames;
}
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;
BREAK(eMessage::SIG_InputState);
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;
retro_game_info* rgiptr = &rgi;
memset(&rgi,0,sizeof(rgi));
if (msg == eMessage::CMD_LoadNoGame)
{
rgiptr = nullptr;
}
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];
}
}
comm.funs.retro_load_game(rgiptr);
//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_initial = 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_UpdateSerializeSize()
{
comm.env.retro_serialize_size = comm.funs.retro_serialize_size();
}
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);
}
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);
}
const Action kHandlers_CMD[] = {
cmd_SetEnvironment,
cmd_LoadNoGame,
cmd_LoadData,
cmd_LoadPath,
cmd_Deinit,
cmd_Reset,
cmd_Run,
cmd_UpdateSerializeSize,
cmd_Serialize,
cmd_Unserialize,
};
const Action kHandlers_QUERY[] = {
query_GetMemory,
};
//------------------------------------------------
//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;
}
}