// Copyright (C) 2003-2009 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 "NetWindow.h" /////////////////////// // Main Frame window BEGIN_EVENT_TABLE(NetPlay, wxDialog) EVT_BUTTON(ID_BUTTON_JOIN, NetPlay::OnJoin) EVT_BUTTON(ID_BUTTON_HOST, NetPlay::OnHost) EVT_HOST_COMMAND(wxID_ANY, NetPlay::OnNetEvent) EVT_CHECKBOX(ID_READY, NetPlay::OnGUIEvent) EVT_CHECKBOX(ID_RECORD, NetPlay::OnGUIEvent) EVT_BUTTON(ID_CHANGEGAME, NetPlay::OnGUIEvent) EVT_BUTTON(ID_BUTTON_GETIP, NetPlay::OnGUIEvent) EVT_BUTTON(ID_BUTTON_GETPING, NetPlay::OnGUIEvent) EVT_BUTTON(ID_BUTTON_CHAT, NetPlay::OnGUIEvent) EVT_TEXT_ENTER(ID_CHAT, NetPlay::OnGUIEvent) EVT_BUTTON(ID_BUTTON_QUIT, NetPlay::OnDisconnect) EVT_CLOSE(NetPlay::OnQuit) END_EVENT_TABLE() NetPlay::NetPlay(wxWindow* parent, std::string GamePaths, std::string GameNames) : wxDialog(parent, wxID_ANY, _T("Net Play"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE & ~ wxMAXIMIZE_BOX ) { m_selectedGame = 'a'; m_hostaddr = 'a'; m_games = GameNames; m_paths = GamePaths; m_isHosting = 2; m_ready = m_clients_ready = false; m_loopframe = m_frame = 0; DrawGUI(); } void NetPlay::OnJoin(wxCommandEvent& WXUNUSED(event)) { wxString addr = m_ConAddr->GetValue(); wxString host = addr.substr(0, addr.find(':')); wxString port_str = addr.substr(addr.find(':')+1); int port; TryParseInt(port_str.mb_str(), &port); m_nick = std::string(m_SetNick->GetValue().mb_str()); if (m_nick.size() > 255) m_nick = m_nick.substr(0 , 255); // Create the client socket sf::SocketTCP sock_client; if (sock_client.Connect(port, (const char *)host.mb_str(), 1.5) == sf::Socket::Done) { m_sock_client = new ClientSide(this, sock_client, std::string(addr.mb_str()), m_nick); m_sock_client->Create(); m_sock_client->Run(); // Create the GUI m_isHosting = false; DrawNetWindow(); } else { PanicAlert("Can't connect to the specified IP Address ! \nMake sure Hosting port is forwarded !"); } } void NetPlay::OnHost(wxCommandEvent& WXUNUSED(event)) { TryParseInt(m_SetPort->GetValue().mb_str(), (int*)&m_port); if (m_GameList->GetSelection() == wxNOT_FOUND) { PanicAlert("No Game Selected ! Please select a Game..."); return; } if (!m_SetPort->GetValue().size() || m_port < 100 || m_port > 65535) { PanicAlert("Bad Port entered (%d) ! Please enter a working socket port...", m_port); return; } m_nick = std::string(m_SetNick->GetValue().mb_str()); if (m_nick.size() > 255) m_nick = m_nick.substr(0 , 255); m_NetModel = m_NetMode->GetSelection(); m_selectedGame = std::string(m_GameList_str[m_GameList->GetSelection()].mb_str()); m_numClients = 0; // Start the listening socket if (m_listensocket.Listen(m_port)) { m_sock_server = new ServerSide(this, m_listensocket, m_NetModel, m_nick); m_sock_server->Create(); m_sock_server->Run(); // Create the GUI m_isHosting = true; DrawNetWindow(); m_Logging->AppendText(wxString::Format(wxT("WARNING : Hosting requires port to be forwarded in firewall!\n" "*Creation Successful on port %d : Waiting for peers...\n"), m_port)); } else { PanicAlert("Could not listen at specified port !\nMake sure hosting port is not in use !"); return; } } void NetPlay::DrawGUI() { int str_end = -1; int str_start = -1; wxArrayString netmodes_str; for(int i = 0; i < (int)m_games.size(); i++) { str_start = str_end + 1; str_end = (int)m_games.find('\n', str_start); std::string buffer = m_games.substr(str_start, str_end - str_start); if (str_end == (int)std::string::npos || buffer.size() < 1) break; // we reached the end of the string m_GameList_str.Add(wxString::FromAscii(buffer.c_str())); } netmodes_str.Add(wxT("P2P Versus (2 players, faster)")); // TODO : // netmodes_str.Add(wxT("Server Mode (4 players, slower)")); // Tabs m_Notebook = new wxNotebook(this, ID_NOTEBOOK, wxDefaultPosition, wxDefaultSize); m_Tab_Connect = new wxPanel(m_Notebook, ID_TAB_CONN, wxDefaultPosition, wxDefaultSize); m_Notebook->AddPage(m_Tab_Connect, wxT("Connect")); m_Tab_Host = new wxPanel(m_Notebook, ID_TAB_HOST, wxDefaultPosition, wxDefaultSize); m_Notebook->AddPage(m_Tab_Host, wxT("Host")); // Nickname setting m_SetNick_text = new wxStaticText(this, ID_SETNICK_TXT, wxT(" Nickname : "), wxDefaultPosition, wxDefaultSize); m_SetNick = new wxTextCtrl(this, ID_SETNICK, wxT("Mingebag"), wxDefaultPosition, wxDefaultSize, 0); m_NetMode = new wxChoice(this, ID_NETMODE, wxDefaultPosition, wxDefaultSize, netmodes_str, 0, wxDefaultValidator); m_NetMode->SetSelection(0); // CONNECTION TAB m_ConAddr_text = new wxStaticText(m_Tab_Connect, ID_CONNADDR_TXT, wxT(" IP Address :"), wxDefaultPosition, wxDefaultSize); m_ConAddr = new wxTextCtrl(m_Tab_Connect, ID_CONNADDR, wxT("127.0.0.1:12345"), wxDefaultPosition, wxSize(250,20), 0); m_UseRandomPort = new wxCheckBox(m_Tab_Connect, ID_USE_RANDOMPORT, wxT("Use random client port for connection")); m_UseRandomPort->SetValue(true); m_UseRandomPort->Enable(false); m_JoinGame = new wxButton(m_Tab_Connect, ID_BUTTON_JOIN, wxT("Connect"), wxDefaultPosition, wxDefaultSize); // Sizers CONNECT wxBoxSizer* sConnectTop = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sConnectSizer = new wxBoxSizer(wxVERTICAL); sConnectTop->Add(m_ConAddr_text, 0, wxALL|wxALIGN_CENTER, 5); sConnectTop->Add(m_ConAddr, 3, wxALL|wxEXPAND, 5); sConnectTop->Add(m_JoinGame, 0, wxALL|wxALIGN_RIGHT, 5); sConnectSizer->Add(sConnectTop, 0, wxALL|wxEXPAND, 5); sConnectSizer->Add(m_UseRandomPort, 0, wxALL|wxALIGN_CENTER, 5); m_Tab_Connect->SetSizer(sConnectSizer); // HOSTING TAB m_SetPort_text = new wxStaticText(m_Tab_Host, ID_SETPORT_TXT, wxT(" Use Port : "), wxDefaultPosition, wxDefaultSize); m_SetPort = new wxTextCtrl(m_Tab_Host, ID_SETPORT, wxT("12345"), wxDefaultPosition, wxDefaultSize, 0); m_GameList_text = new wxStaticText(m_Tab_Host, ID_GAMELIST_TXT, wxT("Warning: Use a forwarded port ! Select Game and press Host :"), wxDefaultPosition, wxDefaultSize); m_GameList = new wxListBox(m_Tab_Host, ID_GAMELIST, wxDefaultPosition, wxDefaultSize, m_GameList_str, wxLB_SINGLE | wxLB_NEEDED_SB); m_HostGame = new wxButton(m_Tab_Host, ID_BUTTON_HOST, wxT("Host"), wxDefaultPosition, wxDefaultSize); // Sizers HOST wxBoxSizer *sHostBox = new wxBoxSizer(wxVERTICAL); wxBoxSizer *sHostTop = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer *sHostMid = new wxBoxSizer(wxHORIZONTAL); sHostTop->Add(m_SetPort_text, 0, wxALL|wxALIGN_CENTER, 5); sHostTop->Add(m_SetPort, 1, wxALL|wxEXPAND, 5); sHostMid->Add(m_GameList, 1, wxALL|wxEXPAND, 5); sHostBox->Add(m_GameList_text, 0, wxALL|wxALIGN_CENTER, 5); sHostBox->Add(sHostTop, 0, wxALL|wxEXPAND, 1); sHostBox->Add(sHostMid, 1, wxALL|wxEXPAND, 1); sHostBox->Add(m_HostGame, 0, wxALL|wxALIGN_RIGHT, 10); m_Tab_Host->SetSizer(sHostBox); sHostBox->Layout(); // main sizers wxBoxSizer* sMain = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sMain_top = new wxBoxSizer(wxHORIZONTAL); sMain_top->Add(m_SetNick_text, 0, wxALL|wxALIGN_CENTER, 3); sMain_top->Add(m_SetNick, 0, wxALL, 3); sMain_top->AddStretchSpacer(1); sMain_top->Add(m_NetMode, 0, wxALL|wxALIGN_RIGHT, 3); sMain->Add(sMain_top, 0, wxALL|wxEXPAND, 5); sMain->Add(m_Notebook, 1, wxALL|wxEXPAND, 5); SetSizerAndFit(sMain); Center(); Layout(); Show(); } void NetPlay::DrawNetWindow() { // Remove everything from the precedent GUI :D DestroyChildren(); SetTitle(_("Connection Window")); wxBoxSizer* sMain = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sTop = new wxBoxSizer(wxVERTICAL); wxBoxSizer* sBottom = new wxBoxSizer(wxHORIZONTAL); m_Game_str = new wxButton(this, wxID_ANY, wxT(" Game : "), wxDefaultPosition, wxSize(400, 25), wxBU_LEFT); m_Game_str->Disable(); m_Logging = new wxTextCtrl(this, ID_LOGGING_TXT, wxEmptyString, wxDefaultPosition, wxSize(400, 250), wxTE_RICH2 | wxTE_MULTILINE | wxTE_READONLY); // Too bad this doesn't work... //m_Logging->SetDefaultStyle(wxTextAttr(*wxRED)); m_Chat = new wxTextCtrl(this, ID_CHAT, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); m_Chat_ok = new wxButton(this, ID_BUTTON_CHAT, wxT("Send"));; m_Ready = new wxCheckBox(this, ID_READY, wxT("Click here when ready"), wxDefaultPosition, wxDefaultSize, 0); m_RecordGame = new wxCheckBox(this, ID_RECORD, wxT("Record Game Input"), wxDefaultPosition, wxDefaultSize, 0); // TODO: Fix the recording ? m_RecordGame->Disable(); m_ConInfo_text = new wxStaticText(this, ID_CONNINFO_TXT, wxT(" Fps : 0 | Ping : 00 ms")); m_GetPing = new wxButton(this, ID_BUTTON_GETPING, wxT("Ping"), wxDefaultPosition, wxDefaultSize); m_Disconnect = new wxButton(this, ID_BUTTON_QUIT, wxT("Disconnect"), wxDefaultPosition, wxDefaultSize); wxBoxSizer* sChat = new wxBoxSizer(wxHORIZONTAL); sTop->Add(m_Game_str, 0, wxALL|wxEXPAND, 1); sTop->Add(m_Logging, 1, wxALL|wxEXPAND, 5); sChat->Add(m_Chat, 1, wxALL|wxEXPAND, 2); sChat->Add(m_Chat_ok, 0, wxALL, 2); sTop->Add(sChat, 0, wxALL|wxEXPAND, 2); wxBoxSizer* sBottom0 = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sBottom1 = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sBottomM = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sBottom2 = new wxBoxSizer(wxVERTICAL); sBottom0->Add(m_Ready, 0, wxALL, 5); sBottom0->Add(m_RecordGame, 0, wxALL, 5); sBottomM->Add(m_ConInfo_text, 0, wxALL, 5); sBottom1->Add(m_Disconnect, 0, wxALL|wxALIGN_LEFT, 5); sBottom1->AddStretchSpacer(1); sBottom1->Add(m_GetPing, 0, wxALL|wxALIGN_RIGHT, 5); sBottom2->Add(sBottom0, 0, wxALL, 0); sBottom2->Add(sBottomM, 0, wxALL | wxALIGN_LEFT, 0); sBottom2->Add(sBottom1, 0, wxALL | wxEXPAND, 5); sBottom->Add(sBottom2, 2, wxALL | wxEXPAND | wxALIGN_CENTER, 0); if (m_isHosting) { m_wtfismyip = new wxButton(this, ID_BUTTON_GETIP, wxT("What is my IP")); m_ChangeGame = new wxButton(this, ID_CHANGEGAME, wxT("Change Game")); wxStaticBoxSizer* sBottom3 = new wxStaticBoxSizer(wxVERTICAL, this, _("Host")); sBottom3->Add(m_ChangeGame, 0, wxALL | wxEXPAND, 5); sBottom3->Add(m_wtfismyip, 0, wxALL | wxEXPAND, 5); sBottom->Add(sBottom3, 1, wxALL | wxEXPAND, 0); UpdateNetWindow(false); } sMain->Add(sTop, 1, wxALL | wxEXPAND, 5); sMain->Add(sBottom, 0, wxALL | wxEXPAND | wxALIGN_CENTER, 2); SetSizerAndFit(sMain); Center(); Layout(); Show(); } void NetPlay::UpdateNetWindow(bool update_infos, wxString infos/*int fps, float ping, int frame_delay*/) { std::vector str_arr; if (update_infos) { // String of the type : FPSxPINGxFRAME_DELAY SplitString(std::string(infos.mb_str()), "x", str_arr); m_ConInfo_text->SetLabel (wxString::Format(wxT(" Fps : %s | Ping : %s | Frame Delay : %s"), str_arr[0].c_str(), str_arr[1].c_str(), str_arr[2].c_str()) ); } else { m_critical.Enter(); m_Game_str->SetLabel(wxString::Format(wxT(" Game : %s"), m_selectedGame.c_str())); m_critical.Leave(); } } void NetPlay::OnGUIEvent(wxCommandEvent& event) { unsigned char value;; switch (event.GetId()) { case ID_READY: { std::string buffer; value = 0x40; if (!m_ready) buffer = ">> "+m_nick+" is now ready !\n"; else buffer = ">> "+m_nick+" is now Unready !\n"; m_ready = !m_ready; if (m_isHosting == 1) { if (m_numClients > 0) { int buffer_size = (int)buffer.size(); for (int i=0; i < m_numClients ; i++) { m_sock_server->Write(i, (const char*)&value, 1); m_sock_server->Write(i, (const char*)&buffer_size, 4); m_sock_server->Write(i, buffer.c_str(), buffer_size + 1); } } m_Logging->AppendText(wxString::FromAscii(buffer.c_str())); if (m_ready && m_clients_ready) LoadGame(); } else { if (m_numClients > 0) m_sock_client->Write((const char*)&value, 1); // 0x40 -> Ready } break; } case ID_BUTTON_GETIP: { if (m_numClients == 0) // Get IP Address from the Internet { // simple IP address caching if (m_hostaddr.at(0) != 'a') { m_Logging->AppendText(wxString::FromAscii(m_hostaddr.c_str())); return; } char buffer[8]; sprintf(buffer, "%d", m_port); m_hostaddr = "> Your IP is : " + sf::IPAddress::GetPublicAddress().ToString() + ':' + std::string(buffer) + '\n'; m_Logging->AppendText(wxString::FromAscii(m_hostaddr.c_str())); } else // Ask client to send server IP { value = 0x20; m_sock_server->Write(0, (const char*)&value, 1); } break; } case ID_BUTTON_GETPING: { if (m_numClients == 0) return; // TODO : This is not designed for > 2 players long ping[3] = {0}; float fping; if (m_isHosting == 1) { m_sock_server->Write(0, 0, 0, ping); fping = (ping[0]+ping[1]+ping[2])/(float)m_numClients; } else { m_sock_client->Write(0, 0, ping); fping = ping[0]; } UpdateNetWindow( true, wxString::Format(wxT("000x%fx%d"), fping, (int)ceil(fping/(1000.0/60.0))) ); break; } case ID_BUTTON_CHAT: case ID_CHAT: { value = 0x30; wxString chat_str = wxString::Format(wxT("> %s : %s\n"), m_nick.c_str(), m_Chat->GetValue().c_str()); int chat_size = (int)chat_str.size(); // If there's no distant connection, we write but we don't send if (m_numClients == 0) { m_Logging->AppendText(chat_str); return; } // Max size that we handle is 1024, there's no need for more if ((chat_str.size()+1) * sizeof(char) > 1024) { m_Logging->AppendText(wxT("ERROR : Packet too large !\n")); return; } // Send to all if (m_isHosting == 1) { for (int i=0; i < m_numClients ; i++) { // Send Chat command m_sock_server->Write(i, (const char*)&value, 1); // 0x30 -> Chat // Send Chat string m_sock_server->Write(i, (const char*)&chat_size, 4); m_sock_server->Write(i, chat_str.mb_str(), chat_size + 1); } } else { m_sock_client->Write((const char*)&value, 1); m_sock_client->Write((const char*)&chat_size, 4); m_sock_client->Write(chat_str.mb_str(), chat_size + 1); } m_Chat->Clear(); // We should maybe wait for the server to send it... // but we write it anyway :p m_Logging->AppendText(chat_str); break; } case ID_RECORD: // TODO : // Record raw pad data break; case ID_CHANGEGAME: { GameListPopup PopUp(this, m_GameList_str); PopUp.ShowModal(); break; } } } ///////////////////////// // GameList popup window BEGIN_EVENT_TABLE(GameListPopup, wxDialog) EVT_BUTTON(wxID_OK, GameListPopup::OnButtons) EVT_BUTTON(wxID_CANCEL, GameListPopup::OnButtons) END_EVENT_TABLE() GameListPopup::GameListPopup(NetPlay *parent, wxArrayString GameNames) : wxDialog(parent, wxID_ANY, _T("Choose a Game :"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) { m_netParent = parent; m_GameList_str = GameNames; m_GameList = new wxListBox(this, ID_GAMELIST, wxDefaultPosition, wxSize(300, 250), GameNames, wxLB_SINGLE | wxLB_SORT | wxLB_NEEDED_SB); m_Cancel = new wxButton(this, wxID_CANCEL, wxT("Cancel"), wxDefaultPosition, wxDefaultSize); m_Accept = new wxButton(this, wxID_OK, wxT("Apply"), wxDefaultPosition, wxDefaultSize); wxBoxSizer* sButtons = new wxBoxSizer(wxHORIZONTAL); wxBoxSizer* sMain = new wxBoxSizer(wxVERTICAL); sButtons->Add(m_Cancel, 0, wxALL, 0); sButtons->AddStretchSpacer(1); sButtons->Add(m_Accept, 0, wxALL | wxALIGN_RIGHT, 0); sMain->Add(m_GameList, 0, wxALL | wxEXPAND, 2); sMain->Add(sButtons, 0, wxALL | wxEXPAND, 5); SetSizerAndFit(sMain); Center(); Layout(); Show(); } void GameListPopup::OnButtons(wxCommandEvent& event) { switch (event.GetId()) { case wxID_OK: m_netParent->ChangeSelectedGame(std::string(m_GameList_str[m_GameList->GetSelection()].mb_str())); Destroy(); break; case wxID_CANCEL: Destroy(); break; } }