From 8b0b04923014e0acfcb6cb3d9d7cb726c994d30c Mon Sep 17 00:00:00 2001 From: skidau Date: Fri, 20 Mar 2015 02:11:12 +0000 Subject: [PATCH] Preliminary update to the GameCube to GBA link cable emulation. Fixes Zelda Wind Waker's Tingle Tuner connection, Pac-Man Vs, Final Fantasy: Crystal Chronicles multiplayer, and most other Gamecube to GBA link cable games. * Added a second socket at port 49420 (0xc10c) which sends clock information * Handled disconnections from the GBA and GC * Made the transfers asynchronous * Blocks the socket before the connection times out Requires Dolphin 4.0-5899 or later. git-svn-id: https://svn.code.sf.net/p/vbam/code/trunk@1235 a31d4220-a93d-0410-bf67-fe4944624d44 --- src/gba/GBA.cpp | 17 +++---- src/gba/GBALink.cpp | 95 ++++++++++++++++++++++++++++++++++----- src/gba/GBALink.h | 1 + src/gba/GBASockClient.cpp | 65 +++++++++++++++++++++++++-- src/gba/GBASockClient.h | 11 ++++- src/gba/Globals.h | 2 + src/win32/Direct3D.cpp | 2 +- src/win32/DirectSound.cpp | 2 +- src/win32/MainWnd.cpp | 2 +- src/win32/OpenAL.cpp | 2 +- src/win32/OpenGL.cpp | 2 +- src/win32/VBA.cpp | 2 +- src/win32/XAudio2.cpp | 2 +- 13 files changed, 174 insertions(+), 31 deletions(-) diff --git a/src/gba/GBA.cpp b/src/gba/GBA.cpp index c53ac87a..92120977 100644 --- a/src/gba/GBA.cpp +++ b/src/gba/GBA.cpp @@ -3007,7 +3007,7 @@ void CPUUpdateRegister(u32 address, u16 value) UPDATE_REG(COMM_JOY_RECV_L, value); break; case COMM_JOY_RECV_H: - UPDATE_REG(COMM_JOY_RECV_H, value); + UPDATE_REG(COMM_JOY_RECV_H, value); break; case COMM_JOY_TRANS_L: @@ -3016,10 +3016,11 @@ void CPUUpdateRegister(u32 address, u16 value) break; case COMM_JOY_TRANS_H: UPDATE_REG(COMM_JOY_TRANS_H, value); + UPDATE_REG(COMM_JOYSTAT, READ16LE(&ioMem[COMM_JOYSTAT]) | JOYSTAT_SEND); break; case COMM_JOYSTAT: - UPDATE_REG(COMM_JOYSTAT, (READ16LE(&ioMem[COMM_JOYSTAT]) & 0xf) | (value & 0xf0)); + UPDATE_REG(COMM_JOYSTAT, (READ16LE(&ioMem[COMM_JOYSTAT]) & 0x0a) | (value & ~0x0a)); break; #endif @@ -3590,8 +3591,8 @@ void CPULoop(int ticks) #ifndef NO_LINK // shuffle2: what's the purpose? - if(gba_link_enabled) - cpuNextEvent = 1; + //if(gba_link_enabled) + //cpuNextEvent = 1; #endif cpuBreakLoop = false; @@ -4040,12 +4041,12 @@ void CPULoop(int ticks) ticks -= clockTicks; - if (gba_joybus_enabled) - JoyBusUpdate(clockTicks); - #ifndef NO_LINK if (gba_link_enabled) LinkUpdate(clockTicks); + + if (gba_joybus_enabled) + JoyBusUpdate(clockTicks); #endif cpuNextEvent = CPUUpdateTicks(); @@ -4063,7 +4064,7 @@ void CPULoop(int ticks) #ifndef NO_LINK // shuffle2: what's the purpose? - if(gba_link_enabled) + if(gba_link_enabled || gba_joybus_active) cpuNextEvent = 1; #endif diff --git a/src/gba/GBALink.cpp b/src/gba/GBALink.cpp index 3c78d8d9..62c6df32 100644 --- a/src/gba/GBALink.cpp +++ b/src/gba/GBALink.cpp @@ -30,6 +30,7 @@ const char *MakeInstanceFilename(const char *Input) // Joybus bool gba_joybus_enabled = false; +bool gba_joybus_active = false; // If disabled, gba core won't call any (non-joybus) link functions bool gba_link_enabled = false; @@ -388,39 +389,95 @@ void JoyBusShutdown() dol = NULL; } +const u64 TICKS_PER_FRAME = 16777216 / 60; +const u64 TICKS_PER_SECOND = 16777216; +const u64 BITS_PER_SECOND = 115200; +const u64 BYTES_PER_SECOND = BITS_PER_SECOND / 8; + +static u32 lastjoybusupdate = 0; +static u32 nextjoybusupdate = 0; +static u32 lastcommand = 0; +static bool booted = false; + void JoyBusUpdate(int ticks) { - linktime += ticks; - static int lastjoybusupdate = 0; + lastjoybusupdate += ticks; + lastcommand += ticks; - // Kinda ugly hack to update joybus stuff intermittently - if (linktime > lastjoybusupdate + 0x3000) + bool joybus_activated = ((READ16LE(&ioMem[COMM_RCNT])) >> 14) == 3; + gba_joybus_active = dol && gba_joybus_enabled && joybus_activated; + + if ((lastjoybusupdate > nextjoybusupdate)) { - lastjoybusupdate = linktime; + if (!joybus_activated) + { + if (dol && booted) + { + JoyBusShutdown(); + } - char data[5] = {0x10, 0, 0, 0, 0}; // init with invalid cmd - std::vector resp; + lastjoybusupdate = 0; + nextjoybusupdate = 0; + lastcommand = 0; + return; + } if (!dol) + { + booted = false; JoyBusConnect(); + } + + dol->ReceiveClock(false); + + if (dol->IsDisconnected()) + { + JoyBusShutdown(); + nextjoybusupdate = TICKS_PER_SECOND * 2; // try to connect after 2 seconds + lastjoybusupdate = 0; + lastcommand = 0; + return; + } + + dol->ClockSync(lastjoybusupdate); + + char data[5] = { 0x10, 0, 0, 0, 0 }; // init with invalid cmd + std::vector resp; + u8 cmd = 0x10; + + if (lastcommand > (TICKS_PER_FRAME * 4)) + { + cmd = dol->ReceiveCmd(data, true); + } + else + { + cmd = dol->ReceiveCmd(data, false); + } - u8 cmd = dol->ReceiveCmd(data); switch (cmd) { case JOY_CMD_RESET: UPDATE_REG(COMM_JOYCNT, READ16LE(&ioMem[COMM_JOYCNT]) | JOYCNT_RESET); + resp.push_back(0x00); // GBA device ID + resp.push_back(0x04); + nextjoybusupdate = TICKS_PER_SECOND / BYTES_PER_SECOND; + break; case JOY_CMD_STATUS: resp.push_back(0x00); // GBA device ID resp.push_back(0x04); + + nextjoybusupdate = TICKS_PER_SECOND / BYTES_PER_SECOND; break; - + case JOY_CMD_READ: resp.push_back((u8)(READ16LE(&ioMem[COMM_JOY_TRANS_L]) & 0xff)); resp.push_back((u8)(READ16LE(&ioMem[COMM_JOY_TRANS_L]) >> 8)); resp.push_back((u8)(READ16LE(&ioMem[COMM_JOY_TRANS_H]) & 0xff)); resp.push_back((u8)(READ16LE(&ioMem[COMM_JOY_TRANS_H]) >> 8)); - UPDATE_REG(COMM_JOYSTAT, READ16LE(&ioMem[COMM_JOYSTAT]) & ~JOYSTAT_SEND); + UPDATE_REG(COMM_JOYCNT, READ16LE(&ioMem[COMM_JOYCNT]) | JOYCNT_SEND_COMPLETE); + nextjoybusupdate = TICKS_PER_SECOND / BYTES_PER_SECOND; + booted = true; break; case JOY_CMD_WRITE: @@ -428,22 +485,35 @@ void JoyBusUpdate(int ticks) UPDATE_REG(COMM_JOY_RECV_H, (u16)((u16)data[4] << 8) | (u8)data[3]); UPDATE_REG(COMM_JOYSTAT, READ16LE(&ioMem[COMM_JOYSTAT]) | JOYSTAT_RECV); UPDATE_REG(COMM_JOYCNT, READ16LE(&ioMem[COMM_JOYCNT]) | JOYCNT_RECV_COMPLETE); + nextjoybusupdate = TICKS_PER_SECOND / BYTES_PER_SECOND; + booted = true; break; default: + nextjoybusupdate = TICKS_PER_SECOND / 40000; + lastjoybusupdate = 0; return; // ignore } + lastjoybusupdate = 0; resp.push_back((u8)READ16LE(&ioMem[COMM_JOYSTAT])); + + if (cmd == JOY_CMD_READ) + { + UPDATE_REG(COMM_JOYSTAT, READ16LE(&ioMem[COMM_JOYSTAT]) & ~JOYSTAT_SEND); + } + dol->Send(resp); // Generate SIO interrupt if we can - if ( ((cmd == JOY_CMD_RESET) || (cmd == JOY_CMD_READ) || (cmd == JOY_CMD_WRITE)) + if (((cmd == JOY_CMD_RESET) || (cmd == JOY_CMD_READ) || (cmd == JOY_CMD_WRITE)) && (READ16LE(&ioMem[COMM_JOYCNT]) & JOYCNT_INT_ENABLE) ) { IF |= 0x80; UPDATE_REG(0x202, IF); } + + lastcommand = 0; } } @@ -451,6 +521,9 @@ static void ReInitLink(); void LinkUpdate(int ticks) { + if (((READ16LE(&ioMem[COMM_RCNT])) >> 14) == 3) + return; + // this actually gets called every single instruction, so keep default // path as short as possible diff --git a/src/gba/GBALink.h b/src/gba/GBALink.h index c2d7024f..e9291605 100644 --- a/src/gba/GBALink.h +++ b/src/gba/GBALink.h @@ -145,6 +145,7 @@ typedef struct { } LANLINKDATA; extern bool gba_joybus_enabled; +extern bool gba_joybus_active; extern bool gba_link_enabled; extern sf::IPAddress joybusHostAddr; diff --git a/src/gba/GBASockClient.cpp b/src/gba/GBASockClient.cpp index 05843f56..83e97757 100644 --- a/src/gba/GBASockClient.cpp +++ b/src/gba/GBASockClient.cpp @@ -12,14 +12,23 @@ GBASockClient::GBASockClient(sf::IPAddress _server_addr) server_addr = _server_addr; client.Connect(0xd6ba, server_addr); - //client.SetBlocking(false); + client.SetBlocking(false); + + clock_client.Connect(0xc10c, server_addr); + clock_client.SetBlocking(false); + + clock_sync = 0; + is_disconnected = false; } GBASockClient::~GBASockClient() { client.Close(); + clock_client.Close(); } +u32 clock_sync_ticks = 0; + void GBASockClient::Send(std::vector data) { char* plain_data = new char[data.size()]; @@ -31,12 +40,60 @@ void GBASockClient::Send(std::vector data) } // Returns cmd for convenience -char GBASockClient::ReceiveCmd(char* data_in) +char GBASockClient::ReceiveCmd(char* data_in, bool block) { - std::size_t num_received; - client.Receive(data_in, 5, num_received); + if (IsDisconnected()) + return data_in[0]; + + std::size_t num_received = 0; + if (block || clock_sync == 0) + { + sf::SelectorTCP Selector; + Selector.Add(client); + Selector.Wait(6); + } + if (client.Receive(data_in, 5, num_received) == sf::Socket::Disconnected) + Disconnect(); return data_in[0]; } +void GBASockClient::ReceiveClock(bool block) +{ + if (IsDisconnected()) + return; + + char sync_ticks[4] = { 0, 0, 0, 0 }; + std::size_t num_received = 0; + if (clock_client.Receive(sync_ticks, 4, num_received) == sf::Socket::Disconnected) + Disconnect(); + + if (num_received == 4) + { + clock_sync_ticks = 0; + for (int i = 0; i < 4; i++) + clock_sync_ticks |= (u8)(sync_ticks[i]) << ((3 - i) * 8); + clock_sync += clock_sync_ticks; + } +} + +void GBASockClient::ClockSync(u32 ticks) +{ + if (clock_sync > (s32)ticks) + clock_sync -= (s32)ticks; + else + clock_sync = 0; +} + +void GBASockClient::Disconnect() +{ + is_disconnected = true; + client.Close(); + clock_client.Close(); +} + +bool GBASockClient::IsDisconnected() +{ + return !client.IsValid() || !clock_client.IsValid() || is_disconnected; +} #endif // NO_LINK diff --git a/src/gba/GBASockClient.h b/src/gba/GBASockClient.h index 937959d1..fb4bdd24 100644 --- a/src/gba/GBASockClient.h +++ b/src/gba/GBASockClient.h @@ -10,9 +10,18 @@ public: ~GBASockClient(); void Send(std::vector data); - char ReceiveCmd(char* data_in); + char ReceiveCmd(char* data_in, bool block); + void ReceiveClock(bool block); + + void ClockSync(u32 ticks); + void Disconnect(); + bool IsDisconnected(); private: sf::IPAddress server_addr; sf::SocketTCP client; + sf::SocketTCP clock_client; + + s32 clock_sync; + bool is_disconnected; }; diff --git a/src/gba/Globals.h b/src/gba/Globals.h index 7981738c..8ca7f592 100644 --- a/src/gba/Globals.h +++ b/src/gba/Globals.h @@ -33,6 +33,8 @@ extern bool skipBios; extern int frameSkip; extern bool speedup; extern bool synchronize; +extern bool gba_joybus_enabled; +extern bool gba_joybus_active; extern bool cpuDisableSfx; extern bool cpuIsMultiBoot; extern bool parseDebug; diff --git a/src/win32/Direct3D.cpp b/src/win32/Direct3D.cpp index 6eac9507..cdc314c2 100644 --- a/src/win32/Direct3D.cpp +++ b/src/win32/Direct3D.cpp @@ -180,7 +180,7 @@ void Direct3DDisplay::prepareDisplayMode() dpp.hDeviceWindow = theApp.m_pMainWnd->GetSafeHwnd(); dpp.FullScreen_RefreshRateInHz = ( dpp.Windowed == TRUE ) ? 0 : theApp.fsFrequency; dpp.Flags = 0; - dpp.PresentationInterval = theApp.vsync ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE; + dpp.PresentationInterval = (theApp.vsync && !gba_joybus_active) ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE; // D3DPRESENT_INTERVAL_ONE means VSync ON diff --git a/src/win32/DirectSound.cpp b/src/win32/DirectSound.cpp index a263f906..de9b0bf0 100644 --- a/src/win32/DirectSound.cpp +++ b/src/win32/DirectSound.cpp @@ -240,7 +240,7 @@ void DirectSound::write(u16 * finalWave, int length) LPVOID lpvPtr2; DWORD dwBytes2 = 0; - if( !speedup && synchronize && !theApp.throttle ) { + if( !speedup && synchronize && !theApp.throttle && !gba_joybus_active) { hr = dsbSecondary->GetStatus(&status); if( status & DSBSTATUS_PLAYING ) { if( !soundPaused ) { diff --git a/src/win32/MainWnd.cpp b/src/win32/MainWnd.cpp index 7b7f8744..c84dce54 100644 --- a/src/win32/MainWnd.cpp +++ b/src/win32/MainWnd.cpp @@ -1218,7 +1218,7 @@ void MainWnd::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) } } else { theApp.wasPaused = true; - if(theApp.pauseWhenInactive) { + if(theApp.pauseWhenInactive && !gba_joybus_active) { if(emulating) { soundPause(); } diff --git a/src/win32/OpenAL.cpp b/src/win32/OpenAL.cpp index 08107c22..cf354ab0 100644 --- a/src/win32/OpenAL.cpp +++ b/src/win32/OpenAL.cpp @@ -281,7 +281,7 @@ void OpenAL::write(u16 * finalWave, int length) } } - if( !speedup && synchronize && !theApp.throttle ) { + if( !speedup && synchronize && !theApp.throttle && !gba_joybus_active) { // wait until at least one buffer has finished while( nBuffersProcessed == 0 ) { winlog( " waiting...\n" ); diff --git a/src/win32/OpenGL.cpp b/src/win32/OpenGL.cpp index ba6cc546..e83fb1c6 100644 --- a/src/win32/OpenGL.cpp +++ b/src/win32/OpenGL.cpp @@ -336,7 +336,7 @@ bool OpenGLDisplay::initialize() initializeMatrices( theApp.surfaceSizeX, theApp.surfaceSizeY ); - setVSync( theApp.vsync ); + setVSync( theApp.vsync && !gba_joybus_active ); #ifdef MMX if(!theApp.disableMMX) diff --git a/src/win32/VBA.cpp b/src/win32/VBA.cpp index 912fb2b8..0a0c245e 100644 --- a/src/win32/VBA.cpp +++ b/src/win32/VBA.cpp @@ -2706,7 +2706,7 @@ void Sm60FPS_Sleep() if( theApp.autoFrameSkip ) { u32 dwTimePass = Sm60FPS::dwTimeElapse + (GetTickCount() - Sm60FPS::dwTime0); u32 dwTimeShould = (u32)(Sm60FPS::nFrameCnt * Sm60FPS::K_fDT); - if( dwTimeShould > dwTimePass ) { + if (dwTimeShould > dwTimePass && !gba_joybus_active) { Sleep(dwTimeShould - dwTimePass); } } diff --git a/src/win32/XAudio2.cpp b/src/win32/XAudio2.cpp index 0541d7fd..cd9dea3d 100644 --- a/src/win32/XAudio2.cpp +++ b/src/win32/XAudio2.cpp @@ -451,7 +451,7 @@ void XAudio2_Output::write(u16 * finalWave, int length) break; } else { // the maximum number of buffers is currently queued - if( synchronize && !speedup && !theApp.throttle ) { + if( synchronize && !speedup && !theApp.throttle && !gba_joybus_active ) { // wait for one buffer to finish playing if (WaitForSingleObject( notify.hBufferEndEvent, 10000 ) == WAIT_TIMEOUT) { device_changed = true;