1300 lines
34 KiB
C++
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();
|
|
} |