BizHawk/libsnes/bsnes/target-libsnes/libsnes_pwrap.cpp

1300 lines
34 KiB
C++

//TODO - find out somehow when the parent process is gone (check a pid or some slot on that for existence and match vs logged value?)
// we cant just check the state of a socket or named pipe or whatever in the case of IPCRingBuffer
//TODO - clean up signaling namespaces.. hooks vs callbacks vs etc. (unify them, at least)
//maybe turn into signal vs break concept (signals would be synchronous [expecting the frontend to always field them immediately and allow execution to resume immediately]) vs breaks (which the frontend receives control after)
//also a COMMAND concept (comes from frontend.. run, init, etc.)
//
//TODO - consolidate scanline breakpoint into this, with something like a set_break(type,address,size) which could be used in the future for read/write/execute but for now, for scanline (address would be the scanline)
//
//TODO - factor out modular components (ringbuffer and the like)
//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.
//(and there are other assorted special messages...)
#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 <map>
#include <string>
#include <vector>
typedef uint8 u8;
typedef int32 s32;
typedef uint32 u32;
typedef uint16 u16;
enum eMessage : int32
{
eMessage_NotSet,
eMessage_SetBuffer,
eMessage_BeginBufferIO,
eMessage_EndBufferIO,
eMessage_ResumeAfterBRK, //resumes execution of the core, after a BRK. no change to current CMD
eMessage_QUERY_library_id,
eMessage_QUERY_library_revision_major,
eMessage_QUERY_library_revision_minor,
eMessage_QUERY_get_region,
eMessage_QUERY_get_memory_size,
eMessage_QUERY_get_memory_data, //note: this function isnt used and hasnt been tested in a while
eMessage_QUERY_peek,
eMessage_QUERY_poke,
eMessage_QUERY_serialize_size,
eMessage_QUERY_poll_message,
eMessage_QUERY_dequeue_message,
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_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_super_game_boy,
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_scanlineStart,
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,
};
enum eEmulationExitReason
{
eEmulationExitReason_NotSet,
eEmulationExitReason_BRK,
eEmulationExitReason_SIG,
eEmulationExitReason_CMD_Complete,
};
enum eEmulationCallback
{
eEmulationCallback_snes_video_refresh,
eEmulationCallback_snes_audio_flush,
eEmulationCallback_snes_input_poll,
eEmulationCallback_snes_input_state,
eEmulationCallback_snes_input_notify,
eEmulationCallback_snes_path_request,
eEmulationCallback_snes_allocSharedMemory,
eEmulationCallback_snes_freeSharedMemory,
eEmulationCallback_snes_trace
};
struct EmulationControl
{
volatile eMessage command;
volatile eEmulationExitReason exitReason;
//the result code for a CMD
s32 cmd_result;
union
{
struct
{
volatile eMessage hookExitType;
uint32 hookAddr;
uint8 hookValue;
};
struct
{
volatile eEmulationCallback exitCallbackType;
union
{
struct
{
const uint32_t *data;
unsigned width;
unsigned height;
} cb_video_refresh_params;
struct
{
unsigned port, device, index, id;
int16_t result;
} cb_input_state_params;
struct
{
int index;
} cb_input_notify_params;
struct
{
int slot;
const char* hint;
//yuck
char result[MAX_PATH];
} cb_path_request_params;
struct
{
const char* memtype;
size_t amt;
void* result;
} cb_allocSharedMemory_params;
struct
{
void* ptr;
} cb_freeSharedMemory_params;
struct
{
const char* msg;
} cb_trace_params;
};
};
};
};
static EmulationControl s_EmulationControl;
class IPCRingBuffer
{
private:
HANDLE mmf;
u8* mmvaPtr;
volatile u8* begin;
volatile s32* head, *tail;
int bufsize;
//this code depends on conventional interpretations of volatile and sequence points which are unlikely to be violated on any system an emulator would be running on
void Setup(int size)
{
bool init = size != -1;
Owner = init;
mmvaPtr = (u8*)MapViewOfFile(mmf, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
//setup management area
head = (s32*)mmvaPtr;
tail = (s32*)mmvaPtr + 1;
s32* bufsizeptr = (s32*)mmvaPtr + 2;
begin = mmvaPtr + 12;
if (init)
*bufsizeptr = bufsize = size - 12;
else bufsize = *bufsizeptr;
}
void WaitForWriteCapacity(int amt)
{
for (; ; )
{
//dont return when available == amt because then we would consume the buffer and be unable to distinguish between full and empty
if (Available() > amt)
return;
//this is a greedy spinlock.
}
}
int Size()
{
int h = *head;
int t = *tail;
int size = h - t;
if (size < 0) size += bufsize;
else if (size >= bufsize)
{
//shouldnt be possible for size to be anything but bufsize here
size = 0;
}
return size;
}
int Available()
{
return bufsize - Size();
}
public:
bool Owner;
//void Allocate(int size) //not supported
void Open(const std::string& id)
{
HANDLE h = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, id.c_str());
if(h == INVALID_HANDLE_VALUE)
return;
mmf = h;
Setup(-1);
}
~IPCRingBuffer()
{
if (mmf == NULL) return;
CloseHandle(mmf);
mmf = NULL;
}
int WaitForSomethingToRead()
{
for (; ; )
{
int available = Size();
if (available > 0)
return available;
//this is a greedy spinlock.
//NOTE: it's annoying right now because libsnes processes die and eat a whole core.
//we need to gracefully exit somehow
}
}
void Write(const void* ptr, int amt)
{
u8* bptr = (u8*)ptr;
int ofs = 0;
while (amt > 0)
{
int todo = amt;
//make sure we don't write a big chunk beyond the end of the buffer
int remain = bufsize - *head;
if (todo > remain) todo = remain;
//dont request the entire buffer. we would never get that much available, because we never completely fill up the buffer
if (todo > bufsize - 1) todo = bufsize - 1;
//a super efficient approach would chunk this several times maybe instead of waiting for the buffer to be emptied before writing again. but who cares
WaitForWriteCapacity(todo);
//messages are likely to be small. we should probably just loop to copy in here. but for now..
memcpy((u8*)begin + *head, bptr + ofs, todo);
amt -= todo;
ofs += todo;
*head += todo;
if (*head >= bufsize) *head -= bufsize;
}
}
void Read(void* ptr, int amt)
{
u8* bptr = (u8*)ptr;
int ofs = 0;
while (amt > 0)
{
int available = WaitForSomethingToRead();
int todo = amt;
if (todo > available) todo = available;
//make sure we don't read a big chunk beyond the end of the buffer
int remain = bufsize - *tail;
if (todo > remain) todo = remain;
//messages are likely to be small. we should probably just loop to copy in here. but for now..
memcpy(bptr + ofs, (u8*)begin + *tail, todo);
amt -= todo;
ofs += todo;
*tail += todo;
if (*tail >= bufsize) *tail -= bufsize;
}
}
}; //class IPCRingBuffer
class Watchdog
{
public:
void Start(const char* _eventName)
{
HANDLE thread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)&ThreadProc, this, 0, NULL);
SetThreadPriority(thread,THREAD_PRIORITY_LOWEST);
eventName = _eventName;
}
private:
std::string eventName;
static DWORD ThreadProc(LPVOID lpParam)
{
Watchdog* w = (Watchdog*)lpParam;
for(;;)
{
//only check once per second
Sleep(1000);
//try opening the handle. if its gone, the process is gone
HANDLE hEvent = OpenEvent(SYNCHRONIZE | EVENT_ALL_ACCESS, FALSE, w->eventName.c_str());
//printf("event handle: %08X (%d)\n",hEvent,hEvent?0:GetLastError()); //debugging
//handle was gone, terminate process
if(hEvent == 0)
{
TerminateProcess(INVALID_HANDLE_VALUE,0);
}
CloseHandle(hEvent);
}
}
}; //class Watchdog
static bool bufio = false;
static IPCRingBuffer *rbuf = NULL, *wbuf = NULL;
Watchdog s_Watchdog;
HANDLE hPipe, hMapFile, hEvent;
void* hMapFilePtr;
static bool running = false;
cothread_t co_control, co_emu, co_emu_suspended;
#define SETCONTROL \
{ \
co_emu_suspended = co_active(); \
co_switch(co_control); \
}
#define SETEMU \
{ \
cothread_t temp = co_emu_suspended; \
co_emu_suspended = NULL; \
co_switch(temp); \
}
void ReadPipeBuffer(void* buf, int len)
{
if(bufio)
{
rbuf->Read(buf,len);
return;
}
DWORD bytesRead;
BOOL result = ReadFile(hPipe, buf, len, &bytesRead, NULL);
if(!result || bytesRead != len)
exit(1);
}
template<typename T> T ReadPipe()
{
T ret;
ReadPipeBuffer(&ret,sizeof(ret));
return ret;
}
char* ReadPipeSharedPtr()
{
return (char*)hMapFilePtr + ReadPipe<int>();
}
template<> bool ReadPipe<bool>()
{
return !!ReadPipe<char>();
}
void WritePipeBuffer(const void* buf, int len)
{
if(co_active() != co_control)
{
printf("WARNING: WRITING FROM NON-CONTROL THREAD\n");
}
//static FILE* outf = NULL;
//if(!outf) outf = fopen("c:\\trace.bin","wb"); fwrite(buf,1,len,outf); fflush(outf);
if(bufio)
{
wbuf->Write(buf,len);
return;
}
DWORD bytesWritten;
BOOL result = WriteFile(hPipe, buf, len, &bytesWritten, NULL);
if(!result || bytesWritten != len)
exit(1);
}
//remove volatile qualifier...... crazy?
template<typename T> void WritePipe(volatile const T& val)
{
WritePipeBuffer((T*)&val, sizeof(val));
}
template<typename T> void WritePipe(const T& val)
{
WritePipeBuffer(&val, sizeof(val));
}
void WritePipeString(const char* str)
{
int len = strlen(str);
WritePipe(len);
WritePipeBuffer(str,len);
}
std::string ReadPipeString()
{
int len = ReadPipe<int>();
std::string ret;
ret.resize(len);
if(len!=0)
ReadPipeBuffer(&ret[0],len);
return ret;
}
typedef std::vector<char> Blob;
void WritePipeBlob(void* buf, int len)
{
WritePipe(len);
WritePipeBuffer(buf,len);
}
Blob ReadPipeBlob()
{
int len = ReadPipe<int>();
Blob ret;
ret.resize(len);
if(len!=0)
ReadPipeBuffer(&ret[0],len);
return ret;
}
void snes_video_refresh(const uint32_t *data, unsigned width, unsigned height)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_video_refresh;
s_EmulationControl.cb_video_refresh_params.data = data;
s_EmulationControl.cb_video_refresh_params.width = width;
s_EmulationControl.cb_video_refresh_params.height = height;
SETCONTROL;
}
bool audio_en = false;
static const int AUDIOBUFFER_SIZE = 44100*2;
uint16_t audiobuffer[AUDIOBUFFER_SIZE];
int audiobuffer_idx = 0;
void SIG_FlushAudio()
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_audio_flush;
SETCONTROL;
}
//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)
SIG_FlushAudio();
audiobuffer[audiobuffer_idx++] = left;
audiobuffer[audiobuffer_idx++] = right;
}
void snes_input_poll(void)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_poll;
SETCONTROL;
}
int16_t snes_input_state(unsigned port, unsigned device, unsigned index, unsigned id)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_state;
s_EmulationControl.cb_input_state_params.port = port;
s_EmulationControl.cb_input_state_params.device = device;
s_EmulationControl.cb_input_state_params.index = index;
s_EmulationControl.cb_input_state_params.id = id;
SETCONTROL;
return s_EmulationControl.cb_input_state_params.result;
}
void snes_input_notify(int index)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_input_notify;
s_EmulationControl.cb_input_notify_params.index = index;
SETCONTROL;
}
void snes_trace(const char *msg)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_trace;
s_EmulationControl.cb_trace_params.msg = msg;
SETCONTROL;
}
const char* snes_path_request(int slot, const char* hint)
{
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_path_request;
s_EmulationControl.cb_path_request_params.slot = slot;
s_EmulationControl.cb_path_request_params.hint = hint;
SETCONTROL;
return (const char*)s_EmulationControl.cb_path_request_params.result;
}
void RunControlMessageLoop();
void snes_scanlineStart(int line)
{
//TODO
//WritePipe(eMessage_snes_cb_scanlineStart);
//WritePipe(line);
////we've got to wait for the frontend to finish processing.
////in theory we could let emulation proceed after snagging the vram and registers, and do decoding and stuff on another thread...
////but its too hard for now.
//RunMessageLoop();
}
class SharedMemoryBlock
{
public:
std::string memtype;
HANDLE handle;
};
static std::map<void*,SharedMemoryBlock*> memHandleTable;
void* implementation_snes_allocSharedMemory()
{
const char* memtype = s_EmulationControl.cb_allocSharedMemory_params.memtype;
size_t amt = s_EmulationControl.cb_allocSharedMemory_params.amt;
if(!running)
{
s_EmulationControl.cb_allocSharedMemory_params.result = NULL;
return NULL;
}
//printf("WritePipe(eMessage_SIG_allocSharedMemory)\n");
WritePipe(eMessage_SIG_allocSharedMemory);
WritePipeString(memtype);
WritePipe(amt);
std::string blockname = ReadPipeString();
auto mapfile = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, blockname.c_str());
if(mapfile == INVALID_HANDLE_VALUE)
return NULL;
auto ptr = MapViewOfFile(mapfile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
auto smb = new SharedMemoryBlock();
smb->memtype = memtype;
smb->handle = mapfile;
memHandleTable[ptr] = smb;
s_EmulationControl.cb_allocSharedMemory_params.result = ptr;
}
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;
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_allocSharedMemory;
s_EmulationControl.cb_allocSharedMemory_params.memtype = memtype;
s_EmulationControl.cb_allocSharedMemory_params.amt = amt;
SETCONTROL;
return s_EmulationControl.cb_allocSharedMemory_params.result;
}
void implementation_snes_freeSharedMemory()
{
void* ptr = s_EmulationControl.cb_freeSharedMemory_params.ptr;
if(!ptr) return;
auto smb = memHandleTable.find(ptr)->second;
UnmapViewOfFile(ptr);
CloseHandle(smb->handle);
//printf("WritePipe(eMessage_SIG_freeSharedMemory);\n");
WritePipe(eMessage_SIG_freeSharedMemory);
WritePipeString(smb->memtype.c_str());
}
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;
s_EmulationControl.exitReason = eEmulationExitReason_SIG;
s_EmulationControl.exitCallbackType = eEmulationCallback_snes_freeSharedMemory;
s_EmulationControl.cb_freeSharedMemory_params.ptr = ptr;
SETCONTROL;
}
void InitBsnes()
{
//setup all hooks to forward messages to the frontend
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 debug_op_exec(uint24 addr)
{
s_EmulationControl.exitReason = eEmulationExitReason_BRK;
s_EmulationControl.hookExitType = eMessage_BRK_hook_exec;
s_EmulationControl.hookAddr = (uint32)addr;
SETCONTROL;
//WritePipe(eMessage_snes_cb_hook_exec);
//WritePipe((uint32)addr);
}
static void debug_op_read(uint24 addr)
{
s_EmulationControl.exitReason = eEmulationExitReason_BRK;
s_EmulationControl.hookExitType = eMessage_BRK_hook_read;
s_EmulationControl.hookAddr = (uint32)addr;
SETCONTROL;
//WritePipe(eMessage_snes_cb_hook_read);
//WritePipe((uint32)addr);
}
static void debug_op_write(uint24 addr, uint8 value)
{
s_EmulationControl.exitReason = eEmulationExitReason_BRK;
s_EmulationControl.hookExitType = eMessage_BRK_hook_write;
s_EmulationControl.hookAddr = (uint32)addr;
s_EmulationControl.hookValue = value;
SETCONTROL;
//WritePipe(eMessage_snes_cb_hook_write);
//WritePipe((uint32)addr);
//WritePipe(value);
}
static void debug_op_nmi()
{
WritePipe(eMessage_BRK_hook_nmi);
}
static void debug_op_irq()
{
WritePipe(eMessage_BRK_hook_irq);
}
void HandleMessage_QUERY(eMessage msg)
{
}
bool Handle_QUERY(eMessage msg)
{
switch(msg)
{
default:
return false;
case eMessage_QUERY_library_id:
WritePipeString(snes_library_id());
break;
case eMessage_QUERY_library_revision_major:
WritePipe(snes_library_revision_major());
break;
case eMessage_QUERY_library_revision_minor:
WritePipe(snes_library_revision_minor());
break;
case eMessage_QUERY_get_region:
WritePipe((char)snes_get_region());
break;
case eMessage_QUERY_get_memory_size:
WritePipe((u32)snes_get_memory_size(ReadPipe<u32>()));
break;
case eMessage_QUERY_get_memory_data:
{
unsigned int id = ReadPipe<u32>();
char* dstbuf = ReadPipeSharedPtr();
uint8_t* srcbuf = snes_get_memory_data(id);
memcpy(dstbuf,srcbuf,snes_get_memory_size(id));
WritePipe(eMessage_BRK_Complete);
break;
}
case eMessage_QUERY_peek:
{
int id = ReadPipe<s32>();
unsigned int addr = ReadPipe<u32>();
uint8_t ret;
if(id == SNES_MEMORY_SYSBUS)
ret = bus_read(addr);
else ret = snes_get_memory_data(id)[addr];
WritePipe(ret);
}
break;
case eMessage_QUERY_poke:
{
int id = ReadPipe<s32>();
unsigned int addr = ReadPipe<u32>();
uint8_t val = ReadPipe<uint8_t>();
if(id == SNES_MEMORY_SYSBUS)
bus_write(addr,val);
else snes_get_memory_data(id)[addr] = val;
break;
}
break;
case eMessage_QUERY_serialize_size:
WritePipe((u32)snes_serialize_size());
break;
case eMessage_QUERY_poll_message:
//TBD
WritePipe(-1);
break;
case eMessage_QUERY_dequeue_message:
//TBD
break;
case eMessage_QUERY_set_color_lut:
{
auto blob = ReadPipeBlob();
snes_set_color_lut((uint32_t*)&blob[0]);
break;
}
break;
case eMessage_QUERY_enable_trace:
if(!!ReadPipe<char>())
snes_set_trace_callback(snes_trace);
else snes_set_trace_callback(NULL);
break;
case eMessage_QUERY_enable_scanline:
if(ReadPipe<bool>())
snes_set_scanlineStart(snes_scanlineStart);
else snes_set_scanlineStart(NULL);
break;
case eMessage_QUERY_enable_audio:
audio_en = ReadPipe<bool>();
break;
case eMessage_QUERY_set_layer_enable:
{
int layer = ReadPipe<s32>();
int priority = ReadPipe<s32>();
bool enable = ReadPipe<bool>();
snes_set_layer_enable(layer,priority,enable);
break;
}
case eMessage_QUERY_set_backdropColor:
snes_set_backdropColor(ReadPipe<s32>());
break;
case eMessage_QUERY_peek_logical_register:
WritePipe(snes_peek_logical_register(ReadPipe<s32>()));
break;
case eMessage_QUERY_peek_cpu_regs:
{
//watch it! the size of this struct is important!
struct {
u32 pc;
u16 a,x,y,z,s,d,vector; //7x
u8 p, nothing;
u32 aa,rd;
u8 sp, dp, db, mdr;
} __attribute__((__packed__)) cpuregs;
cpuregs.pc = (u32)SNES::cpu.regs.pc;
cpuregs.a = SNES::cpu.regs.a;
cpuregs.x = SNES::cpu.regs.x;
cpuregs.y = SNES::cpu.regs.y;
cpuregs.z = SNES::cpu.regs.z;
cpuregs.s = SNES::cpu.regs.s;
cpuregs.d = SNES::cpu.regs.d;
cpuregs.aa = (u32)SNES::cpu.aa;
cpuregs.rd = (u32)SNES::cpu.rd;
cpuregs.sp = SNES::cpu.sp;
cpuregs.dp = SNES::cpu.dp;
cpuregs.db = SNES::cpu.regs.db;
cpuregs.mdr = SNES::cpu.regs.mdr;
cpuregs.vector = SNES::cpu.regs.vector;
cpuregs.p = SNES::cpu.regs.p;
cpuregs.nothing = 0;
WritePipeBuffer(&cpuregs,32); //watch it! the size of this struct is important!
}
break;
case eMessage_QUERY_GetMemoryIdName:
{
uint32 id = ReadPipe<uint32>();
const char* ret = snes_get_memory_id_name(id);
if(!ret) ret = "";
WritePipeString(ret);
break;
}
case eMessage_QUERY_state_hook_exec:
SNES::cpu.debugger.op_exec = ReadPipe<bool>() ? debug_op_exec : hook<void (uint24)>();
break;
case eMessage_QUERY_state_hook_read:
SNES::cpu.debugger.op_read = ReadPipe<bool>() ? debug_op_read : hook<void (uint24)>();
break;
case eMessage_QUERY_state_hook_write:
SNES::cpu.debugger.op_write = ReadPipe<bool>() ? debug_op_write : hook<void (uint24, uint8)>();
break;
case eMessage_QUERY_state_hook_nmi:
SNES::cpu.debugger.op_nmi = ReadPipe<bool>() ? debug_op_nmi : hook<void ()>();
break;
case eMessage_QUERY_state_hook_irq:
SNES::cpu.debugger.op_irq = ReadPipe<bool>() ? debug_op_irq : hook<void ()>();
break;
}
return true;
}
bool Handle_CMD(eMessage msg)
{
if(msg == eMessage_ResumeAfterBRK)
{
//careful! dont switch back to co_emu, we were in another cothread probably when the BRK happened.
//i'm not sure its completely safe to be returning to co_emu below in the normal CMD handler, either...
co_switch(co_emu_suspended);
return true;
}
if(msg<=eMessage_CMD_FIRST || msg>=eMessage_CMD_LAST) return false;
s_EmulationControl.command = msg;
s_EmulationControl.exitReason = eEmulationExitReason_NotSet;
co_switch(co_emu);
return true;
}
void Handle_SIG_audio_flush()
{
WritePipe(eMessage_SIG_audio_flush);
int nsamples = audiobuffer_idx;
WritePipe(nsamples);
char* buf = ReadPipeSharedPtr();
memcpy(buf,audiobuffer,nsamples*2);
//extra just in case we had to unexpectedly flush audio and then carry on with some other process... yeah, its rickety.
WritePipe(0); //dummy synchronization
//wait for frontend to consume data
ReadPipe<int>(); //dummy synchronization
WritePipe(0); //dummy synchronization
audiobuffer_idx = 0;
}
void RunControlMessageLoop()
{
for(;;)
{
TOP:
switch(s_EmulationControl.exitReason)
{
case eEmulationExitReason_NotSet:
goto HANDLEMESSAGES;
case eEmulationExitReason_CMD_Complete:
//printf("eEmulationExitReason_CMD_Complete (command:%d)\n",s_EmulationControl.command);
//MessageBox(0,"ZING","ZING",MB_OK);
//printf("WRITING COMPLETE\n");
WritePipe(eMessage_BRK_Complete);
//special post-completion messages (return values)
switch(s_EmulationControl.command)
{
case eMessage_CMD_load_cartridge_normal:
case eMessage_CMD_load_cartridge_super_game_boy:
case eMessage_CMD_serialize:
case eMessage_CMD_unserialize:
WritePipe(s_EmulationControl.cmd_result);
break;
}
s_EmulationControl.exitReason = eEmulationExitReason_NotSet;
s_EmulationControl.command = eMessage_NotSet;
goto TOP;
case eEmulationExitReason_SIG:
s_EmulationControl.exitReason = eEmulationExitReason_NotSet;
switch(s_EmulationControl.exitCallbackType)
{
case eEmulationCallback_snes_video_refresh:
{
WritePipe(eMessage_SIG_video_refresh);
WritePipe(s_EmulationControl.cb_video_refresh_params.width);
WritePipe(s_EmulationControl.cb_video_refresh_params.height);
int destOfs = ReadPipe<int>();
char* buf = (char*)hMapFilePtr + destOfs;
int bufsize = 512 * 480 * 4;
memcpy(buf,s_EmulationControl.cb_video_refresh_params.data,bufsize);
WritePipe((char)0); //dummy synchronization
break;
}
case eEmulationCallback_snes_audio_flush:
Handle_SIG_audio_flush();
break;
case eEmulationCallback_snes_input_poll:
WritePipe(eMessage_SIG_input_poll);
break;
case eEmulationCallback_snes_input_state:
WritePipe(eMessage_SIG_input_state);
WritePipe(s_EmulationControl.cb_input_state_params.port);
WritePipe(s_EmulationControl.cb_input_state_params.device);
WritePipe(s_EmulationControl.cb_input_state_params.index);
WritePipe(s_EmulationControl.cb_input_state_params.id);
s_EmulationControl.cb_input_state_params.result = ReadPipe<int16_t>();
break;
case eEmulationCallback_snes_input_notify:
WritePipe(eMessage_SIG_input_notify);
WritePipe(s_EmulationControl.cb_input_notify_params.index);
break;
case eEmulationCallback_snes_path_request:
{
WritePipe(eMessage_SIG_path_request);
WritePipe(s_EmulationControl.cb_path_request_params.slot);
WritePipeString(s_EmulationControl.cb_path_request_params.hint);
std::string temp = ReadPipeString();
//yucky! use strncpy and ARRAY_SIZE or something!
strcpy(s_EmulationControl.cb_path_request_params.result,temp.c_str());
}
break;
case eEmulationCallback_snes_allocSharedMemory:
implementation_snes_allocSharedMemory();
break;
case eEmulationCallback_snes_freeSharedMemory:
implementation_snes_freeSharedMemory();
break;
case eEmulationCallback_snes_trace:
WritePipe(eMessage_SIG_trace_callback);
WritePipeString(s_EmulationControl.cb_trace_params.msg);
break;
}
//when callbacks finish, we automatically resume emulation. be careful to go back to top!!!!!!!!
SETEMU;
goto TOP;
case eEmulationExitReason_BRK:
s_EmulationControl.exitReason = eEmulationExitReason_NotSet;
switch(s_EmulationControl.hookExitType)
{
case eMessage_BRK_hook_exec:
WritePipe(eMessage_BRK_hook_exec);
WritePipe((uint32)s_EmulationControl.hookAddr);
break;
case eMessage_BRK_hook_read:
WritePipe(eMessage_BRK_hook_read);
WritePipe((uint32)s_EmulationControl.hookAddr);
break;
case eMessage_BRK_hook_write:
WritePipe(eMessage_BRK_hook_write);
WritePipe((uint32)s_EmulationControl.hookAddr);
WritePipe((uint8)s_EmulationControl.hookValue);
break;
}
goto TOP;
}
HANDLEMESSAGES:
//printf("Reading message from pipe...\n");
eMessage msg = ReadPipe<eMessage>();
//printf("...slam: %08X\n",msg);
if(Handle_QUERY(msg))
goto TOP;
if(Handle_CMD(msg))
goto TOP;
switch(msg)
{
case eMessage_BRK_Complete:
return;
case eMessage_SetBuffer:
{
printf("eMessage_SetBuffer\n");
int which = ReadPipe<s32>();
std::string name = ReadPipeString();
IPCRingBuffer* ipcrb = new IPCRingBuffer();
ipcrb->Open(name);
if(which==0) rbuf = ipcrb;
else wbuf = ipcrb;
break;
}
case eMessage_BeginBufferIO:
bufio = true;
break;
case eMessage_EndBufferIO:
bufio = false;
break;
} //switch(msg)
}
}
void OpenConsole()
{
AllocConsole();
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
freopen("CONIN$", "r", stdin);
}
void EMUTHREAD_handleCommand_LoadCartridgeNormal()
{
Blob xml = ReadPipeBlob();
xml.push_back(0); //make sure the xml is null terminated
const char* xmlptr = NULL;
if(xml.size() != 1) xmlptr = &xml[0];
Blob rom_data = ReadPipeBlob();
const unsigned char* rom_ptr = NULL;
if(rom_data.size() != 0) rom_ptr = (unsigned char*)&rom_data[0];
bool ret = snes_load_cartridge_normal(xmlptr,rom_ptr,rom_data.size());
s_EmulationControl.cmd_result = ret?1:0;
}
void EMUTHREAD_handleCommand_LoadCartridgeSuperGameBoy()
{
std::string rom_xml = ReadPipeString();
const char* rom_xmlptr = NULL;
if(rom_xml != "") rom_xmlptr = rom_xml.c_str();
Blob rom_data = ReadPipeBlob();
uint32 rom_length = rom_data.size();
std::string dmg_xml = ReadPipeString();
const char* dmg_xmlptr = NULL;
if(dmg_xml != "") dmg_xmlptr = dmg_xml.c_str();
Blob dmg_data = ReadPipeBlob();
uint32 dmg_length = dmg_data.size();
bool ret = snes_load_cartridge_super_game_boy(rom_xmlptr,(uint8*)&rom_data[0],rom_length, dmg_xmlptr,(uint8*)&dmg_data[0], dmg_length);
s_EmulationControl.cmd_result = ret?1:0;
}
void EMUTHREAD_handle_CMD_serialize()
{
int size = ReadPipe<s32>();
int destOfs = ReadPipe<s32>();
char* buf = (char*)hMapFilePtr + destOfs;
bool ret = snes_serialize((uint8_t*)buf,size);
s_EmulationControl.cmd_result = ret?1:0;
}
void EMUTHREAD_handle_CMD_unserialize()
{
int size = ReadPipe<s32>();
int destOfs = ReadPipe<s32>();
char* buf = (char*)hMapFilePtr + destOfs;
bool ret = snes_unserialize((uint8_t*)buf ,size);
s_EmulationControl.cmd_result = ret?1:0;
}
void emuthread()
{
for(;;)
{
switch(s_EmulationControl.command)
{
case eMessage_CMD_init:
snes_init();
break;
case eMessage_CMD_power:
snes_power();
break;
case eMessage_CMD_reset:
snes_reset();
break;
case eMessage_CMD_term:
snes_term();
break;
case eMessage_CMD_unload_cartridge:
snes_unload_cartridge();
break;
case eMessage_CMD_load_cartridge_normal:
EMUTHREAD_handleCommand_LoadCartridgeNormal();
break;
case eMessage_CMD_load_cartridge_super_game_boy:
EMUTHREAD_handleCommand_LoadCartridgeSuperGameBoy();
break;
case eMessage_CMD_serialize:
EMUTHREAD_handle_CMD_serialize();
break;
case eMessage_CMD_unserialize:
EMUTHREAD_handle_CMD_unserialize();
break;
case eMessage_CMD_run:
SIG_FlushAudio();
//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;
}
SIG_FlushAudio();
break;
}
s_EmulationControl.exitReason = eEmulationExitReason_CMD_Complete;
SETCONTROL;
}
}
int xmain(int argc, char** argv)
{
if(argc != 2)
{
printf("This program is run from the libsneshawk emulator core. It is useless to you directly.");
exit(1);
}
if(!strcmp(argv[1],"Bongizong"))
{
fprintf(stderr,"Honga Wongkong");
exit(0x16817);
}
char pipename[256];
char eventname[256];
sprintf(pipename, "\\\\.\\Pipe\\%s",argv[1]);
sprintf(eventname, "%s-event",argv[1]);
if(!strncmp(argv[1],"console",7))
{
OpenConsole();
}
printf("pipe: %s\n",pipename);
printf("event: %s\n",eventname);
s_Watchdog.Start(eventname);
hPipe = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if(hPipe == INVALID_HANDLE_VALUE)
return 1;
hMapFile = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE, FALSE, argv[1]);
if(hMapFile == INVALID_HANDLE_VALUE)
return 1;
hMapFilePtr = MapViewOfFile(hMapFile, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
//make a coroutine thread to run the emulation in. we'll switch back to this thread when communicating with the frontend
co_control = co_active();
co_emu = co_create(65536*sizeof(void*),emuthread);
running = true;
printf("running\n");
RunControlMessageLoop();
return 0;
}
int CALLBACK WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
int argc = __argc;
char** argv = __argv;
if(argc != 2)
{
if(IDOK == MessageBox(0,"This program is run from the libsneshawk emulator core. It is useless to you directly. But if you're really, that curious, click OK.","Whatfor my daddy-o",MB_OKCANCEL))
{
ShellExecute(0,"open","http://www.youtube.com/watch?v=boanuwUMNNQ#t=98s",NULL,NULL,SW_SHOWNORMAL);
}
exit(1);
}
xmain(argc,argv);
}
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
InitBsnes();
}