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.
* Changed the SI buffer processing so that transfers do not have to be completed instantly * Added a second socket at port 49420 (0xc10c) which sends clock information to the GBA slaves * Handled disconnections from the GBA and GC * Made the transfers asynchronous * Blocks the socket before the connection times out Requires VBA-M SVN 1235 or later.
This commit is contained in:
parent
337f89959b
commit
c3344eaa88
|
@ -23,8 +23,9 @@ namespace SerialInterface
|
|||
{
|
||||
|
||||
static int changeDevice;
|
||||
static int et_transfer_pending;
|
||||
|
||||
void RunSIBuffer();
|
||||
void RunSIBuffer(u64 userdata, int cyclesLate);
|
||||
void UpdateInterrupts();
|
||||
|
||||
// SI Interrupt Types
|
||||
|
@ -274,6 +275,7 @@ void Init()
|
|||
memset(g_SIBuffer, 0, 128);
|
||||
|
||||
changeDevice = CoreTiming::RegisterEvent("ChangeSIDevice", ChangeDeviceCallback);
|
||||
et_transfer_pending = CoreTiming::RegisterEvent("SITransferPending", RunSIBuffer);
|
||||
}
|
||||
|
||||
void Shutdown()
|
||||
|
@ -346,8 +348,18 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|||
if (tmpComCSR.TCINT) g_ComCSR.TCINT = 0;
|
||||
|
||||
// be careful: run si-buffer after updating the INT flags
|
||||
if (tmpComCSR.TSTART) RunSIBuffer();
|
||||
UpdateInterrupts();
|
||||
if (tmpComCSR.TSTART)
|
||||
{
|
||||
g_ComCSR.TSTART = 1;
|
||||
RunSIBuffer(0, 0);
|
||||
}
|
||||
else if (g_ComCSR.TSTART)
|
||||
{
|
||||
CoreTiming::RemoveEvent(et_transfer_pending);
|
||||
}
|
||||
|
||||
if (!g_ComCSR.TSTART)
|
||||
UpdateInterrupts();
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -519,29 +531,40 @@ SIDevices GetDeviceType(int channel)
|
|||
return g_Channel[channel].m_pDevice->GetDeviceType();
|
||||
}
|
||||
|
||||
void RunSIBuffer()
|
||||
void RunSIBuffer(u64 userdata, int cyclesLate)
|
||||
{
|
||||
// Math inLength
|
||||
int inLength = g_ComCSR.INLNGTH;
|
||||
if (inLength == 0)
|
||||
inLength = 128;
|
||||
else
|
||||
inLength++;
|
||||
if (g_ComCSR.TSTART)
|
||||
{
|
||||
// Math inLength
|
||||
int inLength = g_ComCSR.INLNGTH;
|
||||
if (inLength == 0)
|
||||
inLength = 128;
|
||||
else
|
||||
inLength++;
|
||||
|
||||
// Math outLength
|
||||
int outLength = g_ComCSR.OUTLNGTH;
|
||||
if (outLength == 0)
|
||||
outLength = 128;
|
||||
else
|
||||
outLength++;
|
||||
// Math outLength
|
||||
int outLength = g_ComCSR.OUTLNGTH;
|
||||
if (outLength == 0)
|
||||
outLength = 128;
|
||||
else
|
||||
outLength++;
|
||||
|
||||
int numOutput = g_Channel[g_ComCSR.CHANNEL].m_pDevice->RunBuffer(g_SIBuffer, inLength);
|
||||
int numOutput = 0;
|
||||
|
||||
DEBUG_LOG(SERIALINTERFACE, "RunSIBuffer (intLen: %i outLen: %i) (processed: %i)", inLength, outLength, numOutput);
|
||||
numOutput = g_Channel[g_ComCSR.CHANNEL].m_pDevice->RunBuffer(g_SIBuffer, inLength);
|
||||
|
||||
// Transfer completed
|
||||
GenerateSIInterrupt(INT_TCINT);
|
||||
g_ComCSR.TSTART = 0;
|
||||
DEBUG_LOG(SERIALINTERFACE, "RunSIBuffer chan: %d inLen: %i outLen: %i processed: %i", g_ComCSR.CHANNEL, inLength, outLength, numOutput);
|
||||
|
||||
if (numOutput != 0)
|
||||
{
|
||||
g_ComCSR.TSTART = 0;
|
||||
GenerateSIInterrupt(INT_TCINT);
|
||||
}
|
||||
else
|
||||
{
|
||||
CoreTiming::ScheduleEvent(g_Channel[g_ComCSR.CHANNEL].m_pDevice->TransferInterval() - cyclesLate, et_transfer_pending);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int GetTicksToNextSIPoll()
|
||||
|
|
|
@ -39,6 +39,10 @@ int ISIDevice::RunBuffer(u8* _pBuffer, int _iLength)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int ISIDevice::TransferInterval()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Stub class for saying nothing is attached, and not having to deal with null pointers :)
|
||||
class CSIDevice_Null : public ISIDevice
|
||||
|
|
|
@ -73,6 +73,7 @@ public:
|
|||
|
||||
// Run the SI Buffer
|
||||
virtual int RunBuffer(u8* _pBuffer, int _iLength);
|
||||
virtual int TransferInterval();
|
||||
|
||||
// Return true on new data
|
||||
virtual bool GetData(u32& _Hi, u32& _Low) = 0;
|
||||
|
|
|
@ -5,36 +5,103 @@
|
|||
#include <queue>
|
||||
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/Flag.h"
|
||||
#include "Common/StdMakeUnique.h"
|
||||
#include "Common/Thread.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
#include "Core/HW/SI_Device.h"
|
||||
#include "Core/HW/SI_DeviceGBA.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
#include "Core/HW/VideoInterface.h"
|
||||
|
||||
#include "SFML/Network.hpp"
|
||||
|
||||
static std::thread connectionThread;
|
||||
static std::queue<std::unique_ptr<sf::TcpSocket>> waiting_socks;
|
||||
static std::queue<std::unique_ptr<sf::TcpSocket>> waiting_clocks;
|
||||
static std::mutex cs_gba;
|
||||
namespace { volatile bool server_running; }
|
||||
static std::mutex cs_gba_clk;
|
||||
static u8 num_connected;
|
||||
|
||||
namespace { Common::Flag server_running; }
|
||||
|
||||
enum EJoybusCmds
|
||||
{
|
||||
CMD_RESET = 0xff,
|
||||
CMD_STATUS = 0x00,
|
||||
CMD_READ = 0x14,
|
||||
CMD_WRITE = 0x15
|
||||
};
|
||||
|
||||
const u64 BITS_PER_SECOND = 115200;
|
||||
const u64 BYTES_PER_SECOND = BITS_PER_SECOND / 8;
|
||||
|
||||
u8 GetNumConnected()
|
||||
{
|
||||
int num_ports_connected = num_connected;
|
||||
if (num_ports_connected == 0)
|
||||
num_ports_connected = 1;
|
||||
|
||||
return num_ports_connected;
|
||||
}
|
||||
|
||||
// --- GameBoy Advance "Link Cable" ---
|
||||
|
||||
int GetTransferTime(u8 cmd)
|
||||
{
|
||||
u64 bytes_transferred = 0;
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case CMD_RESET:
|
||||
case CMD_STATUS:
|
||||
{
|
||||
bytes_transferred = 4;
|
||||
break;
|
||||
}
|
||||
case CMD_READ:
|
||||
{
|
||||
bytes_transferred = 6;
|
||||
break;
|
||||
}
|
||||
case CMD_WRITE:
|
||||
{
|
||||
bytes_transferred = 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
bytes_transferred = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (int)(bytes_transferred * SystemTimers::GetTicksPerSecond() / (GetNumConnected() * BYTES_PER_SECOND));
|
||||
}
|
||||
|
||||
static void GBAConnectionWaiter()
|
||||
{
|
||||
server_running = true;
|
||||
server_running.Set();
|
||||
|
||||
Common::SetCurrentThreadName("GBA Connection Waiter");
|
||||
|
||||
sf::TcpListener server;
|
||||
sf::TcpListener clock_server;
|
||||
|
||||
// "dolphin gba"
|
||||
if (server.listen(0xd6ba) != sf::Socket::Done)
|
||||
return;
|
||||
|
||||
// "clock"
|
||||
if (clock_server.listen(0xc10c) != sf::Socket::Done)
|
||||
return;
|
||||
|
||||
server.setBlocking(false);
|
||||
clock_server.setBlocking(false);
|
||||
|
||||
auto new_client = std::make_unique<sf::TcpSocket>();
|
||||
while (server_running)
|
||||
while (server_running.IsSet())
|
||||
{
|
||||
if (server.accept(*new_client) == sf::Socket::Done)
|
||||
{
|
||||
|
@ -43,13 +110,21 @@ static void GBAConnectionWaiter()
|
|||
|
||||
new_client = std::make_unique<sf::TcpSocket>();
|
||||
}
|
||||
SLEEP(1);
|
||||
if (clock_server.accept(*new_client) == sf::Socket::Done)
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(cs_gba_clk);
|
||||
waiting_clocks.push(std::move(new_client));
|
||||
|
||||
new_client = std::make_unique<sf::TcpSocket>();
|
||||
}
|
||||
|
||||
Common::SleepCurrentThread(1);
|
||||
}
|
||||
}
|
||||
|
||||
void GBAConnectionWaiter_Shutdown()
|
||||
{
|
||||
server_running = false;
|
||||
server_running.Clear();
|
||||
if (connectionThread.joinable())
|
||||
connectionThread.join();
|
||||
}
|
||||
|
@ -70,69 +145,229 @@ static bool GetAvailableSock(std::unique_ptr<sf::TcpSocket>& sock_to_fill)
|
|||
return sock_filled;
|
||||
}
|
||||
|
||||
GBASockServer::GBASockServer()
|
||||
static bool GetNextClock(std::unique_ptr<sf::TcpSocket>& sock_to_fill)
|
||||
{
|
||||
bool sock_filled = false;
|
||||
|
||||
std::lock_guard<std::mutex> lk(cs_gba_clk);
|
||||
|
||||
if (!waiting_clocks.empty())
|
||||
{
|
||||
sock_to_fill = std::move(waiting_clocks.front());
|
||||
waiting_clocks.pop();
|
||||
sock_filled = true;
|
||||
}
|
||||
|
||||
return sock_filled;
|
||||
}
|
||||
|
||||
GBASockServer::GBASockServer(int _iDeviceNumber)
|
||||
{
|
||||
if (!connectionThread.joinable())
|
||||
connectionThread = std::thread(GBAConnectionWaiter);
|
||||
|
||||
cmd = 0;
|
||||
num_connected = 0;
|
||||
last_time_slice = 0;
|
||||
booted = false;
|
||||
device_number = _iDeviceNumber;
|
||||
}
|
||||
|
||||
GBASockServer::~GBASockServer()
|
||||
{
|
||||
Disconnect();
|
||||
}
|
||||
|
||||
// Blocking, since GBA must always send lower byte of REG_JOYSTAT
|
||||
void GBASockServer::Transfer(char* si_buffer)
|
||||
void GBASockServer::Disconnect()
|
||||
{
|
||||
if (!client || client->getLocalPort() == 0)
|
||||
if (client)
|
||||
{
|
||||
num_connected--;
|
||||
client->disconnect();
|
||||
client = nullptr;
|
||||
}
|
||||
if (clock_sync)
|
||||
{
|
||||
clock_sync->disconnect();
|
||||
clock_sync = nullptr;
|
||||
}
|
||||
last_time_slice = 0;
|
||||
booted = false;
|
||||
}
|
||||
|
||||
void GBASockServer::ClockSync()
|
||||
{
|
||||
if (!clock_sync)
|
||||
if (!GetNextClock(clock_sync))
|
||||
return;
|
||||
|
||||
u32 time_slice = 0;
|
||||
|
||||
if (last_time_slice == 0)
|
||||
{
|
||||
num_connected++;
|
||||
last_time_slice = CoreTiming::GetTicks();
|
||||
time_slice = (u32)(SystemTimers::GetTicksPerSecond() / 60);
|
||||
}
|
||||
else
|
||||
{
|
||||
time_slice = (u32)(CoreTiming::GetTicks() - last_time_slice);
|
||||
}
|
||||
|
||||
time_slice = (u32)((u64)time_slice * 16777216 / SystemTimers::GetTicksPerSecond());
|
||||
last_time_slice = CoreTiming::GetTicks();
|
||||
char bytes[4] = { 0, 0, 0, 0 };
|
||||
bytes[0] = (time_slice >> 24) & 0xff;
|
||||
bytes[1] = (time_slice >> 16) & 0xff;
|
||||
bytes[2] = (time_slice >> 8) & 0xff;
|
||||
bytes[3] = time_slice & 0xff;
|
||||
|
||||
sf::Socket::Status status = clock_sync->send(bytes, 4);
|
||||
if (status == sf::Socket::Disconnected)
|
||||
{
|
||||
clock_sync->disconnect();
|
||||
clock_sync = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GBASockServer::Send(u8* si_buffer)
|
||||
{
|
||||
if (!client)
|
||||
if (!GetAvailableSock(client))
|
||||
return;
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
current_data[i] = si_buffer[i ^ 3];
|
||||
send_data[i] = si_buffer[i ^ 3];
|
||||
|
||||
u8 cmd = *current_data;
|
||||
|
||||
if (cmd == CMD_WRITE)
|
||||
client->send(current_data, sizeof(current_data));
|
||||
else
|
||||
client->send(current_data, 1);
|
||||
|
||||
DEBUG_LOG(SERIALINTERFACE, "> command %02x %02x%02x%02x%02x",
|
||||
(u8)current_data[0], (u8)current_data[1], (u8)current_data[2],
|
||||
(u8)current_data[3], (u8)current_data[4]);
|
||||
|
||||
memset(current_data, 0, sizeof(current_data));
|
||||
size_t num_received = 0;
|
||||
if (client->receive(current_data, sizeof(current_data), num_received) == sf::Socket::Disconnected)
|
||||
client->disconnect();
|
||||
|
||||
DEBUG_LOG(SERIALINTERFACE, "< %02x%02x%02x%02x%02x",
|
||||
(u8)current_data[0], (u8)current_data[1], (u8)current_data[2],
|
||||
(u8)current_data[3], (u8)current_data[4]);
|
||||
cmd = (u8)send_data[0];
|
||||
|
||||
#ifdef _DEBUG
|
||||
size_t num_expecting = 3;
|
||||
if (cmd == CMD_READ)
|
||||
num_expecting = 5;
|
||||
else if (cmd == CMD_WRITE)
|
||||
num_expecting = 1;
|
||||
if (num_received != num_expecting)
|
||||
ERROR_LOG(SERIALINTERFACE, "%x:%x:%x", (u8)cmd,
|
||||
(unsigned int)num_received, (unsigned int)num_expecting);
|
||||
NOTICE_LOG(SERIALINTERFACE, "%01d cmd %02x [> %02x%02x%02x%02x]",
|
||||
device_number,
|
||||
(u8)send_data[0], (u8)send_data[1], (u8)send_data[2],
|
||||
(u8)send_data[3], (u8)send_data[4]);
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
si_buffer[i ^ 3] = current_data[i];
|
||||
client->setBlocking(false);
|
||||
sf::Socket::Status status;
|
||||
if (cmd == CMD_WRITE)
|
||||
status = client->send(send_data, sizeof(send_data));
|
||||
else
|
||||
status = client->send(send_data, 1);
|
||||
|
||||
if (cmd != CMD_STATUS)
|
||||
booted = true;
|
||||
|
||||
if (status == sf::Socket::Disconnected)
|
||||
Disconnect();
|
||||
|
||||
time_cmd_sent = CoreTiming::GetTicks();
|
||||
}
|
||||
|
||||
int GBASockServer::Receive(u8* si_buffer)
|
||||
{
|
||||
if (!client)
|
||||
if (!GetAvailableSock(client))
|
||||
return 5;
|
||||
|
||||
size_t num_received = 0;
|
||||
u64 transferTime = GetTransferTime((u8)send_data[0]);
|
||||
bool block = (CoreTiming::GetTicks() - time_cmd_sent) > transferTime;
|
||||
if (cmd == CMD_STATUS && !booted)
|
||||
block = false;
|
||||
|
||||
if (block)
|
||||
{
|
||||
sf::SocketSelector Selector;
|
||||
Selector.add(*client);
|
||||
Selector.wait(sf::milliseconds(1000));
|
||||
}
|
||||
|
||||
sf::Socket::Status recv_stat = client->receive(recv_data, sizeof(recv_data), num_received);
|
||||
if (recv_stat == sf::Socket::Disconnected)
|
||||
{
|
||||
Disconnect();
|
||||
return 5;
|
||||
}
|
||||
|
||||
if (recv_stat == sf::Socket::NotReady)
|
||||
num_received = 0;
|
||||
|
||||
if (num_received > sizeof(recv_data))
|
||||
num_received = sizeof(recv_data);
|
||||
|
||||
if (num_received > 0)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
if ((u8)send_data[0] == 0x00 || (u8)send_data[0] == 0xff)
|
||||
{
|
||||
WARN_LOG(SERIALINTERFACE, "%01d [< %02x%02x%02x%02x%02x] (%d)",
|
||||
device_number,
|
||||
(u8)recv_data[0], (u8)recv_data[1], (u8)recv_data[2],
|
||||
(u8)recv_data[3], (u8)recv_data[4],
|
||||
num_received);
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG(SERIALINTERFACE, "%01d [< %02x%02x%02x%02x%02x] (%d)",
|
||||
device_number,
|
||||
(u8)recv_data[0], (u8)recv_data[1], (u8)recv_data[2],
|
||||
(u8)recv_data[3], (u8)recv_data[4],
|
||||
num_received);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
si_buffer[i ^ 3] = recv_data[i];
|
||||
}
|
||||
|
||||
return (int)num_received;
|
||||
}
|
||||
|
||||
CSIDevice_GBA::CSIDevice_GBA(SIDevices _device, int _iDeviceNumber)
|
||||
: ISIDevice(_device, _iDeviceNumber)
|
||||
, GBASockServer()
|
||||
, GBASockServer(_iDeviceNumber)
|
||||
{
|
||||
waiting_for_response = false;
|
||||
}
|
||||
|
||||
CSIDevice_GBA::~CSIDevice_GBA()
|
||||
{
|
||||
GBASockServer::Disconnect();
|
||||
}
|
||||
|
||||
int CSIDevice_GBA::RunBuffer(u8* _pBuffer, int _iLength)
|
||||
{
|
||||
Transfer((char*)_pBuffer);
|
||||
return _iLength;
|
||||
if (!waiting_for_response)
|
||||
{
|
||||
for (int i = 0; i < 5; i++)
|
||||
send_data[i] = _pBuffer[i ^ 3];
|
||||
|
||||
num_data_received = 0;
|
||||
ClockSync();
|
||||
Send(_pBuffer);
|
||||
timestamp_sent = CoreTiming::GetTicks();
|
||||
waiting_for_response = true;
|
||||
}
|
||||
|
||||
if (waiting_for_response && num_data_received == 0)
|
||||
{
|
||||
num_data_received = Receive(_pBuffer);
|
||||
}
|
||||
|
||||
if ((GetTransferTime(send_data[0])) > (int)(CoreTiming::GetTicks() - timestamp_sent))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (num_data_received != 0)
|
||||
waiting_for_response = false;
|
||||
return num_data_received;
|
||||
}
|
||||
}
|
||||
|
||||
int CSIDevice_GBA::TransferInterval()
|
||||
{
|
||||
return GetTransferTime(send_data[0]);
|
||||
}
|
||||
|
|
|
@ -12,38 +12,51 @@
|
|||
|
||||
// GameBoy Advance "Link Cable"
|
||||
|
||||
u8 GetNumConnected();
|
||||
int GetTransferTime(u8 cmd);
|
||||
void GBAConnectionWaiter_Shutdown();
|
||||
|
||||
class GBASockServer
|
||||
{
|
||||
public:
|
||||
GBASockServer();
|
||||
GBASockServer(int _iDeviceNumber);
|
||||
~GBASockServer();
|
||||
|
||||
void Transfer(char* si_buffer);
|
||||
void Disconnect();
|
||||
|
||||
void ClockSync();
|
||||
|
||||
void Send(u8* si_buffer);
|
||||
int Receive(u8* si_buffer);
|
||||
|
||||
private:
|
||||
enum EJoybusCmds
|
||||
{
|
||||
CMD_RESET = 0xff,
|
||||
CMD_STATUS = 0x00,
|
||||
CMD_READ = 0x14,
|
||||
CMD_WRITE = 0x15
|
||||
};
|
||||
|
||||
std::unique_ptr<sf::TcpSocket> client;
|
||||
char current_data[5];
|
||||
std::unique_ptr<sf::TcpSocket> clock_sync;
|
||||
char send_data[5];
|
||||
char recv_data[5];
|
||||
|
||||
u64 time_cmd_sent;
|
||||
u64 last_time_slice;
|
||||
u8 device_number;
|
||||
u8 cmd;
|
||||
bool booted;
|
||||
};
|
||||
|
||||
class CSIDevice_GBA : public ISIDevice, private GBASockServer
|
||||
{
|
||||
public:
|
||||
CSIDevice_GBA(SIDevices device, int _iDeviceNumber);
|
||||
~CSIDevice_GBA() {}
|
||||
~CSIDevice_GBA();
|
||||
|
||||
// Run the SI Buffer
|
||||
virtual int RunBuffer(u8* _pBuffer, int _iLength) override;
|
||||
virtual int TransferInterval() override;
|
||||
|
||||
virtual bool GetData(u32& _Hi, u32& _Low) override { return true; }
|
||||
virtual bool GetData(u32& _Hi, u32& _Low) override { return false; }
|
||||
virtual void SendCommand(u32 _Cmd, u8 _Poll) override {}
|
||||
|
||||
private:
|
||||
u8 send_data[5];
|
||||
int num_data_received;
|
||||
u64 timestamp_sent;
|
||||
bool waiting_for_response;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue