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:
skidau 2015-03-20 02:11:12 +00:00
parent 1ac06a6e66
commit 8b0b049230
13 changed files with 174 additions and 31 deletions

View File

@ -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

View File

@ -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,30 +389,84 @@ 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();
}
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 (!dol)
JoyBusConnect();
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:
@ -419,8 +474,10 @@ void JoyBusUpdate(int ticks)
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,13 +485,24 @@ 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
@ -444,6 +512,8 @@ void JoyBusUpdate(int ticks)
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

View File

@ -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;

View File

@ -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

View File

@ -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;
};

View File

@ -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;

View File

@ -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

View File

@ -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 ) {

View File

@ -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();
}

View File

@ -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" );

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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;