589 lines
14 KiB
C++
589 lines
14 KiB
C++
// Copyright (C) 2003 Dolphin Project.
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, version 2.0.
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License 2.0 for more details.
|
|
|
|
// A copy of the GPL 2.0 should have been included with the program.
|
|
// If not, see http://www.gnu.org/licenses/
|
|
|
|
// Official SVN repository and contact information can be found at
|
|
// http://code.google.com/p/dolphin-emu/
|
|
|
|
#include "NetSockets.h"
|
|
#include "NetWindow.h"
|
|
#include "HW/SI_DeviceGCController.h"
|
|
|
|
NetPlay *NetClass_ptr = NULL;
|
|
|
|
void NetPlay::IsGameFound(unsigned char * ptr, std::string m_selected)
|
|
{
|
|
m_critical.Enter();
|
|
|
|
m_selectedGame = m_selected;
|
|
|
|
if (m_games.find(m_selected) != std::string::npos)
|
|
*ptr = 0x1F;
|
|
else
|
|
*ptr = 0x1A;
|
|
|
|
m_critical.Leave();
|
|
}
|
|
|
|
void NetPlay::OnNetEvent(wxCommandEvent& event)
|
|
{
|
|
switch (event.GetId())
|
|
{
|
|
case HOST_FULL:
|
|
{
|
|
AppendText(_(" Server is Full !\n*You have been Disconnected.\n\n"));
|
|
m_isHosting = 2;
|
|
}
|
|
break;
|
|
case HOST_ERROR:
|
|
{
|
|
if (m_isHosting == 0)
|
|
{
|
|
AppendText(_("ERROR : Network Error !\n*You have been Disconnected.\n\n"));
|
|
m_isHosting = 2;
|
|
}
|
|
else
|
|
{
|
|
m_numClients--;
|
|
AppendText( wxString::Format(wxT("ERROR : Network Error !\n")
|
|
wxT("*Player : %s has been dropped from the game.\n\n"),
|
|
(const char *)event.GetString().mb_str()) );
|
|
}
|
|
}
|
|
break;
|
|
case HOST_DISCONNECTED:
|
|
{
|
|
// Event sent from Client's thread, it means that the thread
|
|
// has been killed and so we tell the GUI thread.
|
|
AppendText(_("*Connection to Host lost.\n*You have been Disconnected.\n\n"));
|
|
m_isHosting = 2;
|
|
m_numClients--;
|
|
}
|
|
break;
|
|
case HOST_PLAYERLEFT:
|
|
{
|
|
m_numClients--;
|
|
}
|
|
break;
|
|
case HOST_NEWPLAYER:
|
|
{
|
|
m_numClients++;
|
|
m_NetModel = event.GetInt();
|
|
}
|
|
break;
|
|
case CLIENTS_READY:
|
|
{
|
|
m_clients_ready = true;
|
|
|
|
// Tell clients everyone is ready...
|
|
if (m_ready)
|
|
LoadGame();
|
|
}
|
|
break;
|
|
case CLIENTS_NOTREADY:
|
|
{
|
|
m_clients_ready = false;
|
|
}
|
|
break;
|
|
case GUI_UPDATE:
|
|
UpdateNetWindow(false);
|
|
break;
|
|
case ADD_TEXT:
|
|
AppendText(event.GetString());
|
|
break;
|
|
case ADD_INFO:
|
|
UpdateNetWindow(true, event.GetString());
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ServerSide::IsEveryoneReady()
|
|
{
|
|
int nb_ready = 0;
|
|
|
|
for (int i=0; i < m_numplayers ; i++)
|
|
if (m_client[i].ready)
|
|
nb_ready++;
|
|
|
|
if (nb_ready == m_numplayers)
|
|
Event->SendEvent(CLIENTS_READY);
|
|
else
|
|
Event->SendEvent(CLIENTS_NOTREADY);
|
|
}
|
|
|
|
// Actual Core function which is called on every frame
|
|
int CSIDevice_GCController::GetNetInput(u8 numPAD, SPADStatus PadStatus, u32 *PADStatus)
|
|
{
|
|
if (NetClass_ptr != NULL)
|
|
return NetClass_ptr->GetNetPads(numPAD, PadStatus, PADStatus) ? 1 : 0;
|
|
else
|
|
return 2;
|
|
}
|
|
|
|
void NetPlay::LoadGame()
|
|
{
|
|
// Two implementations, one "p2p" implementation which sends to peer
|
|
// and receive from peer 2 players max. and another which uses server model
|
|
// and always sends to the server which then send it back to all the clients
|
|
// -> P2P model is faster, but is limited to 2 players
|
|
// -> Server model is slower, but supports up to 4 players
|
|
|
|
if (m_isHosting == 1)
|
|
{
|
|
long ping[3] = {0};
|
|
unsigned char value = 0x50;
|
|
|
|
// Get ping
|
|
m_sock_server->Write(0, 0, 0, ping);
|
|
float fping = (ping[0]+ping[1]+ping[2])/(float)m_numClients;
|
|
|
|
// Tell client everyone is ready
|
|
for (int i=0; i < m_numClients ; i++)
|
|
m_sock_server->Write(i, (const char*)&value, 1);
|
|
|
|
// Sleep a bit to start the game at more or less the same time than the peer
|
|
wxMilliSleep(fping/2);
|
|
|
|
m_Logging->AppendText(wxString::Format(wxT("** Everyone is ready... Loading Game ! **\n")
|
|
wxT("** Ping to client(s) is : %f ms\n"), fping));
|
|
}
|
|
else
|
|
m_Logging->AppendText(_("** Everyone is ready... Loading Game ! **\n"));
|
|
|
|
// TODO : Throttle should be on by default, to avoid stuttering
|
|
//soundStream->GetMixer()->SetThrottle(true);
|
|
|
|
int line_p = 0;
|
|
int line_n = 0;
|
|
|
|
m_critical.Enter();
|
|
std::string tmp = m_games.substr(0, m_games.find(m_selectedGame));
|
|
|
|
for (int i=0; i < (int)tmp.size(); i++)
|
|
if (tmp.c_str()[i] == '\n')
|
|
line_n++;
|
|
|
|
// Enable
|
|
NetClass_ptr = this;
|
|
m_timer.Start();
|
|
m_data_received = false;
|
|
m_critical.Leave();
|
|
|
|
// Find corresponding game path
|
|
for (int i=0; i < (int)m_paths.size(); i++)
|
|
{
|
|
if (m_paths.c_str()[i] == '\n')
|
|
line_p++;
|
|
|
|
if (line_n == line_p) {
|
|
// Game path found, get its string
|
|
int str_pos = line_p > 0 ? i+1 : i;
|
|
int str_end = (int)m_paths.find('\n', str_pos);
|
|
// Boot the selected game
|
|
BootManager::BootCore(m_paths.substr(str_pos, str_end - str_pos));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool NetPlay::GetNetPads(u8 padnb, SPADStatus PadStatus, u32 *netValues)
|
|
{
|
|
if (m_numClients < 1)
|
|
{
|
|
m_Logging->AppendText(_("** WARNING : Ping too high (>2000ms) or connection lost ! \n** WARNING : Stopping Netplay... \n"));
|
|
NetClass_ptr = NULL;
|
|
return false;
|
|
}
|
|
|
|
// Store current pad status in netValues[]
|
|
netValues[0] = (u32)((u8)PadStatus.stickY);
|
|
netValues[0] |= (u32)((u8)PadStatus.stickX << 8);
|
|
netValues[0] |= (u32)((u16)PadStatus.button << 16);
|
|
netValues[0] |= 0x00800000;
|
|
netValues[1] = (u8)PadStatus.triggerRight;
|
|
netValues[1] |= (u32)((u8)PadStatus.triggerLeft << 8);
|
|
netValues[1] |= (u32)((u8)PadStatus.substickY << 16);
|
|
netValues[1] |= (u32)((u8)PadStatus.substickX << 24);
|
|
|
|
if (m_NetModel == 0) // Use 2 players Model
|
|
{
|
|
if (padnb == 0)
|
|
{
|
|
// Update the timer and increment total frame number
|
|
m_frame++;
|
|
|
|
if (m_frame == 1)
|
|
{
|
|
// We make sure everyone's pad is enabled
|
|
for (int i = 0; i < m_numClients+1; i++)
|
|
SerialInterface::ChangeDevice(SI_GC_CONTROLLER, i);
|
|
|
|
// Better disable unused ports
|
|
for (int i = m_numClients+1; i < 4; i++)
|
|
SerialInterface::ChangeDevice(SI_DUMMY, i);
|
|
}
|
|
|
|
if (m_timer.GetTimeDifference() > 1000)
|
|
m_timer.Update();
|
|
|
|
#ifdef NET_DEBUG
|
|
char sent[64];
|
|
sprintf(sent, "Sent Values: 0x%08x : 0x%08x \n", netValues[0], netValues[1]);
|
|
m_Logging->AppendText(wxString::FromAscii(sent));
|
|
#endif
|
|
unsigned char player = 0;
|
|
|
|
#ifdef USE_TCP
|
|
unsigned char init_value = 0xA1;
|
|
|
|
if (m_isHosting == 1) {
|
|
// Send pads values
|
|
m_sock_server->Write(0, (const char*)&init_value, 1);
|
|
m_sock_server->Write(0, (const char*)netValues, 8);
|
|
}
|
|
else {
|
|
// Send pads values
|
|
m_sock_client->Write((const char*)&init_value, 1);
|
|
m_sock_client->Write((const char*)netValues, 8);
|
|
player = 1;
|
|
}
|
|
#else // UDP
|
|
u32 padsValues[3];
|
|
|
|
padsValues[0] = m_frame;
|
|
padsValues[1] = netValues[0];
|
|
padsValues[2] = netValues[1];
|
|
|
|
if (m_isHosting == 1) {
|
|
// Send pads values
|
|
m_sock_server->WriteUDP(0, (const char*)padsValues, 12);
|
|
}
|
|
else {
|
|
// Send pads values
|
|
m_sock_client->WriteUDP((const char*)padsValues, 12);
|
|
player = 1;
|
|
}
|
|
#endif
|
|
|
|
if (!m_data_received)
|
|
{
|
|
// Save pad values
|
|
m_pads[player].nHi[m_loopframe] = netValues[0];
|
|
m_pads[player].nLow[m_loopframe] = netValues[1];
|
|
|
|
// Try to read from peer...
|
|
if (m_isHosting == 1)
|
|
m_data_received = m_sock_server->isNewPadData(0, false);
|
|
else
|
|
m_data_received = m_sock_client->isNewPadData(0, false);
|
|
|
|
if (m_data_received)
|
|
{
|
|
// Set our practical frame delay
|
|
m_frameDelay = m_loopframe;
|
|
m_loopframe = 0;
|
|
|
|
// First Data has been received !
|
|
m_Logging->AppendText(_("** Data received from Peer. Starting Sync !"));
|
|
m_Logging->AppendText(wxString::Format(wxT(" Frame Delay : %d **\n"), m_frameDelay));
|
|
}
|
|
else {
|
|
if (m_loopframe > 126)
|
|
{
|
|
m_Logging->AppendText(_("** WARNING : Ping too high (>2000ms) or connection lost ! \n** WARNING : Stopping Netplay... \n"));
|
|
NetClass_ptr = NULL;
|
|
}
|
|
|
|
m_loopframe++;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (m_data_received)
|
|
{
|
|
// We have successfully received the data, now use it...
|
|
// If we received data, we can update our pads on each frame, here's the behaviour :
|
|
// we received our init number, so we should receive our pad values on each frames
|
|
// with a frame delay of 'm_frameDelay' frames from the peer. So here, we just wait
|
|
// for the pad status. note : if the peer can't keep up, sending the values
|
|
// (i.e : framerate is too low) we have to wait for it thus slowing down emulation
|
|
|
|
// Save current pad values, it will be used in 'm_frameDelay' frames :D
|
|
int saveslot = (m_loopframe - 1 < 0 ? m_frameDelay : m_loopframe - 1);
|
|
u32 recvedValues[2];
|
|
|
|
m_pads[player].nHi[saveslot] = netValues[0];
|
|
m_pads[player].nLow[saveslot] = netValues[1];
|
|
|
|
// Read the socket for pad values
|
|
if (m_isHosting == 1)
|
|
m_sock_server->isNewPadData(recvedValues, true);
|
|
else
|
|
m_sock_client->isNewPadData(recvedValues, true);
|
|
|
|
if (player == 0)
|
|
{
|
|
// Store received peer values
|
|
m_pads[1].nHi[m_loopframe] = recvedValues[0];
|
|
m_pads[1].nLow[m_loopframe] = recvedValues[1];
|
|
|
|
// Apply synced pad values
|
|
netValues[0] = m_pads[0].nHi[m_loopframe];
|
|
netValues[1] = m_pads[0].nLow[m_loopframe];
|
|
}
|
|
else
|
|
{
|
|
// Apply received pad values
|
|
netValues[0] = recvedValues[0];
|
|
netValues[1] = recvedValues[1];
|
|
}
|
|
}
|
|
|
|
#ifdef NET_DEBUG
|
|
char usedval[64];
|
|
sprintf(usedval, "Player 1 Values: 0x%08x : 0x%08x \n", netValues[0], netValues[1]);
|
|
m_Logging->AppendText(wxString::FromAscii(usedval));
|
|
#endif
|
|
return true;
|
|
}
|
|
else if (padnb == 1)
|
|
{
|
|
if (m_data_received)
|
|
{
|
|
netValues[0] = m_pads[1].nHi[m_loopframe];
|
|
netValues[1] = m_pads[1].nLow[m_loopframe];
|
|
|
|
// Reset the loop to avoid reading unused values
|
|
if (m_loopframe == m_frameDelay)
|
|
m_loopframe = 0;
|
|
else
|
|
m_loopframe++;
|
|
}
|
|
else
|
|
return false;
|
|
#ifdef NET_DEBUG
|
|
char usedval[64];
|
|
sprintf(usedval, "Player 2 Values: 0x%08x : 0x%08x \n", netValues[0], netValues[1]);
|
|
m_Logging->AppendText(wxString::FromAscii(usedval));
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO : :D
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void NetPlay::ChangeSelectedGame(std::string game)
|
|
{
|
|
wxCriticalSectionLocker lock(m_critical);
|
|
if (m_isHosting == 0)
|
|
{
|
|
m_selectedGame = game;
|
|
return;
|
|
}
|
|
|
|
if (game != m_selectedGame)
|
|
{
|
|
unsigned char value = 0x35;
|
|
int game_size = (int)game.size();
|
|
|
|
// Send command then Game String
|
|
for (int i=0; i < m_numClients ; i++)
|
|
{
|
|
m_sock_server->Write(i, (const char*)&value, 1); // 0x35 -> Change game
|
|
|
|
m_sock_server->Write(i, (const char*)&game_size, 4);
|
|
m_sock_server->Write(i, game.c_str(), game_size + 1);
|
|
}
|
|
|
|
m_selectedGame = game;
|
|
UpdateNetWindow(false);
|
|
m_Logging->AppendText(wxString::Format( wxT(" *Game has been changed to : %s \r\n "), wxString(game.c_str(), wxConvUTF8).c_str()));
|
|
NOTICE_LOG(NETPLAY,"Game has been changed to : %s \n",game.c_str());
|
|
}
|
|
}
|
|
|
|
void NetPlay::OnQuit(wxCloseEvent& WXUNUSED(event))
|
|
{
|
|
// Disable netplay
|
|
NetClass_ptr = NULL;
|
|
|
|
// Destroy the Window
|
|
Destroy();
|
|
|
|
// Then Kill the threads
|
|
if (m_isHosting == 0)
|
|
m_sock_client->Delete();
|
|
else if (m_isHosting == 1) {
|
|
m_sock_server->Delete();
|
|
}
|
|
|
|
}
|
|
|
|
void NetPlay::OnDisconnect(wxCommandEvent& WXUNUSED(event))
|
|
{
|
|
wxCloseEvent close;
|
|
OnQuit(close);
|
|
}
|
|
|
|
bool ClientSide::isNewPadData(u32 *netValues, bool current, bool isVersus)
|
|
{
|
|
#ifdef USE_TCP
|
|
if (current)
|
|
{
|
|
while (1)
|
|
{
|
|
m_CriticalSection.Enter();
|
|
if (m_data_received && isVersus)
|
|
{
|
|
netValues[0] = m_netvalues[0][0];
|
|
netValues[1] = m_netvalues[0][1];
|
|
m_data_received = false;
|
|
|
|
m_CriticalSection.Leave();
|
|
break;
|
|
}
|
|
m_CriticalSection.Leave();
|
|
|
|
if (TestDestroy())
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
wxCriticalSectionLocker lock(m_CriticalSection);
|
|
|
|
return m_data_received;
|
|
#else
|
|
size_t recv_size;
|
|
|
|
if (current)
|
|
{
|
|
m_CriticalSection.Enter();
|
|
|
|
if (isVersus)
|
|
{
|
|
if (m_netvalues[0][1] != 0)
|
|
{
|
|
netValues[0] = m_netvalues[0][1];
|
|
netValues[1] = m_netvalues[0][2];
|
|
}
|
|
else
|
|
{
|
|
while (true)
|
|
{
|
|
u32 frame_saved = m_netvalues[0][0];
|
|
bool pass = RecvT(m_socketUDP, (char*)&m_netvalues[0], 12, recv_size, 5);
|
|
|
|
if (m_netvalues[0][0] < frame_saved+1)
|
|
continue;
|
|
if (m_netvalues[0][0] > frame_saved+1 || !pass)
|
|
PanicAlert("Network ERROR !");
|
|
|
|
netValues[0] = m_netvalues[0][1];
|
|
netValues[1] = m_netvalues[0][2];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_netvalues[0][1] = 0;
|
|
m_CriticalSection.Leave();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return RecvT(m_socketUDP, (char*)&m_netvalues[0], 12, recv_size, 1/1000);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
bool ServerSide::isNewPadData(u32 *netValues, bool current, int client)
|
|
{
|
|
#ifdef USE_TCP
|
|
if (current)
|
|
{
|
|
while (1)
|
|
{
|
|
m_CriticalSection.Enter();
|
|
if (m_data_received)
|
|
{
|
|
netValues[0] = m_netvalues[client][0];
|
|
netValues[1] = m_netvalues[client][1];
|
|
m_data_received = false;
|
|
|
|
m_CriticalSection.Leave();
|
|
break;
|
|
}
|
|
m_CriticalSection.Leave();
|
|
|
|
if (TestDestroy())
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
wxCriticalSectionLocker lock(m_CriticalSection);
|
|
|
|
return m_data_received;
|
|
#else
|
|
size_t recv_size;
|
|
|
|
if (current)
|
|
{
|
|
m_CriticalSection.Enter();
|
|
|
|
if (m_netvalues[0][1] != 0)
|
|
{
|
|
netValues[0] = m_netvalues[client][1];
|
|
netValues[1] = m_netvalues[client][2];
|
|
}
|
|
else
|
|
{
|
|
while (true)
|
|
{
|
|
u32 frame_saved = m_netvalues[client][0];
|
|
bool pass = RecvT(m_socketUDP, (char*)&m_netvalues[client], 12, recv_size, 5);
|
|
|
|
if (m_netvalues[client][0] < frame_saved+1)
|
|
continue;
|
|
if (m_netvalues[client][0] > frame_saved+1 || !pass)
|
|
PanicAlert("Network ERROR !");
|
|
|
|
netValues[0] = m_netvalues[client][1];
|
|
netValues[1] = m_netvalues[client][2];
|
|
break;
|
|
}
|
|
}
|
|
|
|
m_netvalues[client][1] = 0;
|
|
m_CriticalSection.Leave();
|
|
|
|
return true;
|
|
}
|
|
else
|
|
return RecvT(m_socketUDP, (char*)&m_netvalues[client], 12, recv_size, 1/1000);
|
|
|
|
#endif
|
|
}
|
|
|