638 lines
17 KiB
C++
638 lines
17 KiB
C++
//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.
|
|
|
|
#include <Windows.h>
|
|
|
|
#define LIBSNES_IMPORT
|
|
#include "snes/snes.hpp"
|
|
#include "libsnes.hpp"
|
|
|
|
#include <libco/libco.h>
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
extern SNES::Interface *iface;
|
|
|
|
typedef uint8 u8;
|
|
typedef uint16 u16;
|
|
typedef uint64 u64;
|
|
typedef uint32 u32;
|
|
|
|
typedef int32 s32;
|
|
|
|
typedef void(*Action)();
|
|
|
|
enum eMessage : int32
|
|
{
|
|
eMessage_NotSet,
|
|
|
|
eMessage_Resume,
|
|
|
|
eMessage_QUERY_FIRST,
|
|
eMessage_QUERY_get_memory_size,
|
|
eMessage_QUERY_peek,
|
|
eMessage_QUERY_poke,
|
|
eMessage_QUERY_serialize_size,
|
|
eMessage_QUERY_set_color_lut,
|
|
eMessage_QUERY_GetMemoryIdName,
|
|
eMessage_QUERY_state_hook_exec,
|
|
eMessage_QUERY_state_hook_read,
|
|
eMessage_QUERY_state_hook_write,
|
|
eMessage_QUERY_state_hook_nmi,
|
|
eMessage_QUERY_state_hook_irq,
|
|
eMessage_QUERY_enable_trace,
|
|
eMessage_QUERY_enable_scanline,
|
|
eMessage_QUERY_enable_audio,
|
|
eMessage_QUERY_set_layer_enable,
|
|
eMessage_QUERY_set_backdropColor,
|
|
eMessage_QUERY_peek_logical_register,
|
|
eMessage_QUERY_peek_cpu_regs,
|
|
eMessage_QUERY_set_cdl,
|
|
eMessage_QUERY_LAST,
|
|
|
|
eMessage_CMD_FIRST,
|
|
eMessage_CMD_init,
|
|
eMessage_CMD_power,
|
|
eMessage_CMD_reset,
|
|
eMessage_CMD_run,
|
|
eMessage_CMD_serialize,
|
|
eMessage_CMD_unserialize,
|
|
eMessage_CMD_load_cartridge_normal,
|
|
eMessage_CMD_load_cartridge_sgb,
|
|
eMessage_CMD_term,
|
|
eMessage_CMD_unload_cartridge,
|
|
eMessage_CMD_LAST,
|
|
|
|
eMessage_SIG_video_refresh,
|
|
eMessage_SIG_input_poll,
|
|
eMessage_SIG_input_state,
|
|
eMessage_SIG_input_notify,
|
|
eMessage_SIG_audio_flush,
|
|
eMessage_SIG_path_request,
|
|
eMessage_SIG_trace_callback,
|
|
eMessage_SIG_allocSharedMemory,
|
|
eMessage_SIG_freeSharedMemory,
|
|
|
|
eMessage_BRK_Complete,
|
|
eMessage_BRK_hook_exec,
|
|
eMessage_BRK_hook_read,
|
|
eMessage_BRK_hook_write,
|
|
eMessage_BRK_hook_nmi,
|
|
eMessage_BRK_hook_irq,
|
|
eMessage_BRK_scanlineStart,
|
|
};
|
|
|
|
enum eStatus : int32
|
|
{
|
|
eStatus_Idle,
|
|
eStatus_CMD,
|
|
eStatus_BRK
|
|
};
|
|
|
|
|
|
//watch it! the size of this struct is important!
|
|
#ifdef _MSC_VER
|
|
#pragma pack(push,1)
|
|
#endif
|
|
struct CPURegsComm {
|
|
u32 pc;
|
|
u16 a, x, y, z, s, d, vector; //7x
|
|
u8 p, nothing;
|
|
u32 aa, rd;
|
|
u8 sp, dp, db, mdr;
|
|
}
|
|
#ifndef _MSC_VER
|
|
__attribute__((__packed__))
|
|
#endif
|
|
;
|
|
#ifdef _MSC_VER
|
|
#pragma pack(pop)
|
|
#endif
|
|
|
|
struct LayerEnablesComm
|
|
{
|
|
u8 BG1_Prio0, BG1_Prio1;
|
|
u8 BG2_Prio0, BG2_Prio1;
|
|
u8 BG3_Prio0, BG3_Prio1;
|
|
u8 BG4_Prio0, BG4_Prio1;
|
|
u8 Obj_Prio0, Obj_Prio1, Obj_Prio2, Obj_Prio3;
|
|
};
|
|
|
|
//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
|
|
char* str;
|
|
void* ptr;
|
|
uint32 id, addr, value, size;
|
|
int32 port, device, index, slot;
|
|
int32 width, height;
|
|
int32 scanline;
|
|
SNES::Input::Device inports[2];
|
|
|
|
//always used in pairs
|
|
void* buf[3];
|
|
int32 buf_size[3];
|
|
|
|
int64 cdl_ptr[4];
|
|
int32 cdl_size[4];
|
|
|
|
CPURegsComm cpuregs;
|
|
LayerEnablesComm layerEnables;
|
|
|
|
//static configuration-type information which can be grabbed off the core at any time without even needing a QUERY command
|
|
uint32 region;
|
|
uint32 mapper;
|
|
|
|
//===========================================================
|
|
|
|
//private stuff
|
|
void* privbuf[2]; //TODO remember to tidy this..
|
|
|
|
void CopyBuffer(int id, void* ptr, int32 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, int32 size)
|
|
{
|
|
if (privbuf[id]) free(privbuf[id]);
|
|
privbuf[id] = nullptr;
|
|
buf[id] = ptr;
|
|
buf_size[id] = size;
|
|
}
|
|
|
|
|
|
} comm;
|
|
|
|
//coroutines
|
|
cothread_t co_control, co_emu, co_emu_suspended;
|
|
|
|
//internal state
|
|
bool audio_en = false;
|
|
static const int AUDIOBUFFER_SIZE = 44100 * 2;
|
|
uint16_t audiobuffer[AUDIOBUFFER_SIZE];
|
|
int audiobuffer_idx = 0;
|
|
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;
|
|
}
|
|
|
|
void snes_video_refresh(const uint32_t *data, unsigned width, unsigned height)
|
|
{
|
|
comm.width = width;
|
|
comm.height = height;
|
|
comm.ptr = (void*)data;
|
|
|
|
BREAK(eMessage_SIG_video_refresh);
|
|
}
|
|
|
|
void do_SIG_audio_flush()
|
|
{
|
|
comm.ptr = audiobuffer;
|
|
comm.size = audiobuffer_idx;
|
|
BREAK(eMessage_SIG_audio_flush);
|
|
audiobuffer_idx = 0;
|
|
}
|
|
|
|
//this is the raw callback from the emulator internals when a new audio sample is available
|
|
void snes_audio_sample(uint16_t left, uint16_t right)
|
|
{
|
|
if(!audio_en) return;
|
|
|
|
//if theres no room in the audio buffer, we need to send a flush signal
|
|
if (audiobuffer_idx == AUDIOBUFFER_SIZE)
|
|
{
|
|
do_SIG_audio_flush();
|
|
}
|
|
|
|
audiobuffer[audiobuffer_idx++] = left;
|
|
audiobuffer[audiobuffer_idx++] = right;
|
|
}
|
|
|
|
void snes_input_poll(void)
|
|
{
|
|
BREAK(eMessage_SIG_input_poll);
|
|
}
|
|
|
|
int16_t snes_input_state(unsigned port, unsigned device, unsigned index, unsigned id)
|
|
{
|
|
comm.port = port;
|
|
comm.device = device;
|
|
comm.index = index;
|
|
comm.id = id;
|
|
BREAK(eMessage_SIG_input_state);
|
|
return comm.value;
|
|
}
|
|
void snes_input_notify(int index)
|
|
{
|
|
comm.index = index;
|
|
BREAK(eMessage_SIG_input_notify);
|
|
}
|
|
|
|
void snes_trace(const char *msg)
|
|
{
|
|
comm.str = (char*) msg;
|
|
BREAK(eMessage_SIG_trace_callback);
|
|
}
|
|
|
|
const char* snes_path_request(int slot, const char* hint)
|
|
{
|
|
comm.slot = slot;
|
|
comm.str= (char *)hint;
|
|
BREAK(eMessage_SIG_path_request);
|
|
return (const char*)comm.buf[0];
|
|
}
|
|
|
|
void snes_scanlineStart(int line)
|
|
{
|
|
comm.scanline = line;
|
|
BREAK(eMessage_BRK_scanlineStart);
|
|
}
|
|
|
|
void* snes_allocSharedMemory(const char* memtype, size_t amt)
|
|
{
|
|
//its important that this happen before the message marshaling because allocation/free attempts can happen before the marshaling is setup (or at shutdown time, in case of errors?)
|
|
//if(!running) return NULL;
|
|
|
|
comm.str = (char*)memtype;
|
|
comm.size = amt;
|
|
|
|
BREAK(eMessage_SIG_allocSharedMemory);
|
|
|
|
return comm.ptr;
|
|
}
|
|
|
|
void snes_freeSharedMemory(void* ptr)
|
|
{
|
|
//its important that this happen before the message marshaling because allocation/free attempts can happen before the marshaling is setup (or at shutdown time, in case of errors?)
|
|
//if(!running) return;
|
|
|
|
if (!ptr) return;
|
|
|
|
comm.ptr = ptr;
|
|
|
|
BREAK(eMessage_SIG_freeSharedMemory);
|
|
}
|
|
|
|
static void debug_op_exec(uint24 addr)
|
|
{
|
|
comm.addr = addr;
|
|
BREAK(eMessage_BRK_hook_exec);
|
|
}
|
|
|
|
static void debug_op_read(uint24 addr)
|
|
{
|
|
comm.addr = addr;
|
|
BREAK(eMessage_BRK_hook_read);
|
|
}
|
|
|
|
static void debug_op_write(uint24 addr, uint8 value)
|
|
{
|
|
comm.addr = addr;
|
|
comm.value = value;
|
|
BREAK(eMessage_BRK_hook_write);
|
|
}
|
|
|
|
static void debug_op_nmi()
|
|
{
|
|
BREAK(eMessage_BRK_hook_nmi);
|
|
}
|
|
|
|
static void debug_op_irq()
|
|
{
|
|
BREAK(eMessage_BRK_hook_irq);
|
|
}
|
|
|
|
void pwrap_init()
|
|
{
|
|
//bsnes's interface initialization calls into this after initializing itself, so we can get a chance to mod it for pwrap functionalities
|
|
snes_set_video_refresh(snes_video_refresh);
|
|
snes_set_audio_sample(snes_audio_sample);
|
|
snes_set_input_poll(snes_input_poll);
|
|
snes_set_input_state(snes_input_state);
|
|
snes_set_input_notify(snes_input_notify);
|
|
snes_set_path_request(snes_path_request);
|
|
snes_set_allocSharedMemory(snes_allocSharedMemory);
|
|
snes_set_freeSharedMemory(snes_freeSharedMemory);
|
|
}
|
|
|
|
static void Analyze()
|
|
{
|
|
//gather some "static" type information, so we dont have to poll annoyingly for it later
|
|
comm.mapper = snes_get_mapper();
|
|
comm.region = snes_get_region();
|
|
}
|
|
|
|
void CMD_LoadCartridgeNormal()
|
|
{
|
|
const char* xml = (const char*)comm.buf[0];
|
|
if(!xml[0]) xml = nullptr;
|
|
bool ret = snes_load_cartridge_normal(xml, (const uint8_t*)comm.buf[1], comm.buf_size[1]);
|
|
comm.value = ret?1:0;
|
|
|
|
if(ret)
|
|
Analyze();
|
|
}
|
|
|
|
void CMD_LoadCartridgeSGB()
|
|
{
|
|
bool ret = snes_load_cartridge_super_game_boy((const char*)comm.buf[0], (const u8*)comm.buf[1], comm.buf_size[1], nullptr, (const u8*)comm.buf[2], comm.buf_size[2]);
|
|
comm.value = ret ? 1 : 0;
|
|
|
|
if(ret)
|
|
Analyze();
|
|
}
|
|
|
|
void CMD_init()
|
|
{
|
|
snes_init();
|
|
|
|
SNES::input.connect(SNES::Controller::Port1, comm.inports[0]);
|
|
SNES::input.connect(SNES::Controller::Port2, comm.inports[1]);
|
|
}
|
|
|
|
void CMD_Serialize()
|
|
{
|
|
int size = comm.buf_size[0];
|
|
char* buf = (char*)comm.buf[0];
|
|
bool ret = snes_serialize((uint8_t*)buf,size);
|
|
comm.value = ret ? 1 : 0;
|
|
}
|
|
|
|
void CMD_Unserialize()
|
|
{
|
|
int size = comm.buf_size[0];
|
|
char* buf = (char*)comm.buf[0];
|
|
bool ret = snes_unserialize((uint8_t*)buf ,size);
|
|
comm.value = ret ? 1 : 0;
|
|
}
|
|
|
|
static void CMD_Run()
|
|
{
|
|
do_SIG_audio_flush();
|
|
|
|
//we could avoid this if we saved the current thread before jumping back to co_control, instead of always jumping back to co_emu
|
|
//in effect, we're scrambling the scheduler
|
|
//EDIT - well, we changed that, but.. we still want this probably, for debugging and stuff
|
|
for (;;)
|
|
{
|
|
SNES::scheduler.sync = SNES::Scheduler::SynchronizeMode::None;
|
|
SNES::scheduler.clearExitReason();
|
|
SNES::scheduler.enter();
|
|
if (SNES::scheduler.exit_reason() == SNES::Scheduler::ExitReason::FrameEvent)
|
|
{
|
|
SNES::video.update();
|
|
break;
|
|
}
|
|
//not used yet
|
|
if (SNES::scheduler.exit_reason() == SNES::Scheduler::ExitReason::DebuggerEvent)
|
|
break;
|
|
}
|
|
|
|
do_SIG_audio_flush();
|
|
}
|
|
|
|
void QUERY_get_memory_size() {
|
|
comm.value = snes_get_memory_size(comm.value);
|
|
}
|
|
void QUERY_peek() {
|
|
if (comm.id == SNES_MEMORY_SYSBUS)
|
|
comm.value = bus_read(comm.addr);
|
|
else comm.value = snes_get_memory_data(comm.id)[comm.addr];
|
|
}
|
|
void QUERY_poke() {
|
|
if (comm.id == SNES_MEMORY_SYSBUS)
|
|
bus_write(comm.addr, comm.value);
|
|
else snes_get_memory_data(comm.id)[comm.addr] = comm.value;
|
|
}
|
|
void QUERY_set_color_lut() {
|
|
snes_set_color_lut((uint32_t*)comm.ptr);
|
|
}
|
|
void QUERY_GetMemoryIdName() {
|
|
comm.str = (char* )snes_get_memory_id_name(comm.id);
|
|
}
|
|
void QUERY_state_hook_exec() {
|
|
SNES::cpu.debugger.op_exec = comm.value ? debug_op_exec : hook<void(uint24)>();
|
|
}
|
|
void QUERY_state_hook_read() {
|
|
SNES::cpu.debugger.op_read = comm.value ? debug_op_read : hook<void(uint24)>();
|
|
}
|
|
void QUERY_state_hook_write() {
|
|
SNES::cpu.debugger.op_write = comm.value ? debug_op_write : hook<void(uint24, uint8)>();
|
|
}
|
|
void QUERY_state_hook_nmi() {
|
|
SNES::cpu.debugger.op_nmi = comm.value ? debug_op_nmi : hook<void()>();
|
|
}
|
|
void QUERY_state_hook_irq() {
|
|
SNES::cpu.debugger.op_irq = comm.value ? debug_op_irq : hook<void()>();
|
|
}
|
|
void QUERY_state_enable_trace() {
|
|
if (comm.value)
|
|
snes_set_trace_callback(snes_trace);
|
|
else snes_set_trace_callback(nullptr);
|
|
}
|
|
void QUERY_state_enable_scanline() {
|
|
if (comm.value)
|
|
snes_set_scanlineStart(snes_scanlineStart);
|
|
else snes_set_scanlineStart(nullptr);
|
|
}
|
|
void QUERY_state_enable_audio() {
|
|
audio_en = !!comm.value;
|
|
}
|
|
void QUERY_state_set_layer_enable() {
|
|
snes_set_layer_enable(0, 0, !!comm.layerEnables.BG1_Prio0);
|
|
snes_set_layer_enable(0, 1, !!comm.layerEnables.BG1_Prio1);
|
|
snes_set_layer_enable(1, 0, !!comm.layerEnables.BG2_Prio0);
|
|
snes_set_layer_enable(1, 1, !!comm.layerEnables.BG2_Prio1);
|
|
snes_set_layer_enable(2, 0, !!comm.layerEnables.BG3_Prio0);
|
|
snes_set_layer_enable(2, 1, !!comm.layerEnables.BG3_Prio1);
|
|
snes_set_layer_enable(3, 0, !!comm.layerEnables.BG4_Prio0);
|
|
snes_set_layer_enable(3, 1, !!comm.layerEnables.BG4_Prio1);
|
|
snes_set_layer_enable(4, 0, !!comm.layerEnables.Obj_Prio0);
|
|
snes_set_layer_enable(4, 1, !!comm.layerEnables.Obj_Prio1);
|
|
snes_set_layer_enable(4, 2, !!comm.layerEnables.Obj_Prio2);
|
|
snes_set_layer_enable(4, 3, !!comm.layerEnables.Obj_Prio3);
|
|
}
|
|
void QUERY_set_backdropColor() {
|
|
snes_set_backdropColor((s32)comm.value);
|
|
}
|
|
void QUERY_peek_logical_register() {
|
|
comm.value = snes_peek_logical_register(comm.id);
|
|
}
|
|
void QUERY_peek_cpu_regs() {
|
|
comm.cpuregs.pc = (u32)SNES::cpu.regs.pc;
|
|
comm.cpuregs.a = SNES::cpu.regs.a;
|
|
comm.cpuregs.x = SNES::cpu.regs.x;
|
|
comm.cpuregs.y = SNES::cpu.regs.y;
|
|
comm.cpuregs.z = SNES::cpu.regs.z;
|
|
comm.cpuregs.s = SNES::cpu.regs.s;
|
|
comm.cpuregs.d = SNES::cpu.regs.d;
|
|
comm.cpuregs.aa = (u32)SNES::cpu.aa;
|
|
comm.cpuregs.rd = (u32)SNES::cpu.rd;
|
|
comm.cpuregs.sp = SNES::cpu.sp;
|
|
comm.cpuregs.dp = SNES::cpu.dp;
|
|
comm.cpuregs.db = SNES::cpu.regs.db;
|
|
comm.cpuregs.mdr = SNES::cpu.regs.mdr;
|
|
comm.cpuregs.vector = SNES::cpu.regs.vector;
|
|
comm.cpuregs.p = SNES::cpu.regs.p;
|
|
comm.cpuregs.nothing = 0;
|
|
}
|
|
void QUERY_peek_set_cdl() {
|
|
for (int i = 0; i<eCDLog_AddrType_NUM; i++)
|
|
{
|
|
cdlInfo.blocks[i] = (uint8*)comm.cdl_ptr[i];
|
|
cdlInfo.blockSizes[i] = comm.cdl_size[i];
|
|
}
|
|
}
|
|
void QUERY_serialize_size() {
|
|
comm.size = snes_serialize_size();
|
|
}
|
|
|
|
const Action kHandlers_CMD[] = {
|
|
CMD_init,
|
|
snes_power,
|
|
snes_reset,
|
|
CMD_Run,
|
|
CMD_Serialize,
|
|
CMD_Unserialize,
|
|
CMD_LoadCartridgeNormal,
|
|
CMD_LoadCartridgeSGB,
|
|
snes_term,
|
|
snes_unload_cartridge,
|
|
};
|
|
|
|
const Action kHandlers_QUERY[] = {
|
|
QUERY_get_memory_size, //eMessage_QUERY_get_memory_size TODO - grab during bootup (for all possible memdomains)
|
|
QUERY_peek,
|
|
QUERY_poke,
|
|
QUERY_serialize_size, //eMessage_QUERY_serialize_size TODO - grab during bootup/reset (for all possible memdomains)
|
|
QUERY_set_color_lut,
|
|
QUERY_GetMemoryIdName, //snes_get_memory_id_name TODO - grab during bootup (for all possible memdomains)
|
|
QUERY_state_hook_exec, //eMessage_QUERY_state_hook_exec
|
|
QUERY_state_hook_read, //eMessage_QUERY_state_hook_read
|
|
QUERY_state_hook_write, //eMessage_QUERY_state_hook_write
|
|
QUERY_state_hook_nmi, //eMessage_QUERY_state_hook_nmi
|
|
QUERY_state_hook_irq, //eMessage_QUERY_state_hook_irq
|
|
QUERY_state_enable_trace, //eMessage_QUERY_enable_trace TODO - consolidate enable flags
|
|
QUERY_state_enable_scanline, //eMessage_QUERY_enable_scanline TODO - consolidate enable flags
|
|
QUERY_state_enable_audio, //eMessage_QUERY_enable_audio TODO - consolidate enable flags
|
|
QUERY_state_set_layer_enable, //eMessage_QUERY_set_layer_enable
|
|
QUERY_set_backdropColor, //eMessage_QUERY_set_backdropColor
|
|
QUERY_peek_logical_register, //eMessage_QUERY_peek_logical_register
|
|
QUERY_peek_cpu_regs, //eMessage_QUERY_peek_cpu_regs
|
|
QUERY_peek_set_cdl, //eMessage_QUERY_set_cdl
|
|
};
|
|
|
|
//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);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------
|
|
//DLL INTERFACE
|
|
|
|
BOOL WINAPI DllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
extern "C" dllexport void* __cdecl DllInit()
|
|
{
|
|
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(65536 * sizeof(void*), new_emuthread);
|
|
|
|
return &comm;
|
|
}
|
|
|
|
extern "C" 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" dllexport void __cdecl CopyBuffer(int id, void* ptr, int32 size)
|
|
{
|
|
comm.CopyBuffer(id, ptr, size);
|
|
}
|
|
|
|
//receives the given buffer and STASHES IT. use this (carefully) for sending params for CMDs
|
|
extern "C" dllexport void __cdecl SetBuffer(int id, void* ptr, int32 size)
|
|
{
|
|
comm.SetBuffer(id, ptr, size);
|
|
} |