//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 #define LIBSNES_IMPORT #include "snes/snes.hpp" #include "libsnes.hpp" #include #include #include #include #include #include #include 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 T ReadPipe() { T ret; ReadPipeBuffer(&ret,sizeof(ret)); return ret; } char* ReadPipeSharedPtr() { return (char*)hMapFilePtr + ReadPipe(); } template<> bool ReadPipe() { return !!ReadPipe(); } 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 void WritePipe(volatile const T& val) { WritePipeBuffer((T*)&val, sizeof(val)); } template 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(); std::string ret; ret.resize(len); if(len!=0) ReadPipeBuffer(&ret[0],len); return ret; } typedef std::vector Blob; void WritePipeBlob(void* buf, int len) { WritePipe(len); WritePipeBuffer(buf,len); } Blob ReadPipeBlob() { int len = ReadPipe(); 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 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())); break; case eMessage_QUERY_get_memory_data: { unsigned int id = ReadPipe(); 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(); unsigned int addr = ReadPipe(); 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(); unsigned int addr = ReadPipe(); uint8_t val = ReadPipe(); 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()) snes_set_trace_callback(snes_trace); else snes_set_trace_callback(NULL); break; case eMessage_QUERY_enable_scanline: if(ReadPipe()) snes_set_scanlineStart(snes_scanlineStart); else snes_set_scanlineStart(NULL); break; case eMessage_QUERY_enable_audio: audio_en = ReadPipe(); break; case eMessage_QUERY_set_layer_enable: { int layer = ReadPipe(); int priority = ReadPipe(); bool enable = ReadPipe(); snes_set_layer_enable(layer,priority,enable); break; } case eMessage_QUERY_set_backdropColor: snes_set_backdropColor(ReadPipe()); break; case eMessage_QUERY_peek_logical_register: WritePipe(snes_peek_logical_register(ReadPipe())); 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(); 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() ? debug_op_exec : hook(); break; case eMessage_QUERY_state_hook_read: SNES::cpu.debugger.op_read = ReadPipe() ? debug_op_read : hook(); break; case eMessage_QUERY_state_hook_write: SNES::cpu.debugger.op_write = ReadPipe() ? debug_op_write : hook(); break; case eMessage_QUERY_state_hook_nmi: SNES::cpu.debugger.op_nmi = ReadPipe() ? debug_op_nmi : hook(); break; case eMessage_QUERY_state_hook_irq: SNES::cpu.debugger.op_irq = ReadPipe() ? debug_op_irq : hook(); 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(); //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(); 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(); 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(); //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(); 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(); int destOfs = ReadPipe(); 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(); int destOfs = ReadPipe(); 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(); }