//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 #include #include #include #include #include #include #include #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;ikey)) { 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;ilog = 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 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 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(width, height, pitch, x, y, dstbuf, dst) = co; dst++; row++; } srcbuf += pitch/2; } } template 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(width, height, pitch, x, y, dstbuf, dst) = co; dst++; row++; } srcbuf += pitch/2; } } template 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(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