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
This commit is contained in:
parent
1ac06a6e66
commit
8b0b049230
|
@ -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
|
||||
|
||||
|
|
|
@ -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<char> 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<char> 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
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<char> data)
|
||||
{
|
||||
char* plain_data = new char[data.size()];
|
||||
|
@ -31,12 +40,60 @@ void GBASockClient::Send(std::vector<char> 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
|
||||
|
|
|
@ -10,9 +10,18 @@ public:
|
|||
~GBASockClient();
|
||||
|
||||
void Send(std::vector<char> 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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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 ) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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" );
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue