/* Starshatter: The Open Source Project Copyright (c) 2021-2024, Starshatter: The Open Source Project Contributors Copyright (c) 2011-2012, Starshatter OpenSource Distribution Contributors Copyright (c) 1997-2006, Destroyer Studios LLC. AUTHOR: John DiCamillo OVERVIEW ======== Stream-oriented network client class */ #include "NetLobbyClient.h" #include "NetClientConfig.h" #include "NetAuth.h" #include "NetChat.h" #include "Campaign.h" #include "Player.h" #include "Starshatter.h" #include "ModInfo.h" #include "NetHost.h" #include "NetPeer.h" #include "NetLink.h" #include "NetMsg.h" #include "NetLayer.h" #include "FormatUtil.h" #include "VersionInfo.h" #include "Game.h" // +-------------------------------------------------------------------+ NetLobbyClient::NetLobbyClient() : NetLobby(false), server_id(0), host(false), exit_code(0), temporary(false) { NetHost me; Text server_name; WORD port = 11101; ping_req_time = 0; chat_req_time = 0; user_req_time = 0; camp_req_time = 0; unit_req_time = 0; mods_req_time = 0; NetClientConfig* ncc = NetClientConfig::GetInstance(); if (ncc) { NetServerInfo* info = ncc->GetSelectedServer(); if (info) { server_name = info->hostname; addr = info->addr; port = info->port; gamepass = info->password; } } if (server_name.length() && port > 0) { Print(" '%s' is a client of '%s'\n", me.Name(), server_name.data()); link = new NetLink; server_id = link->AddPeer(NetAddr(server_name, port)); } else if (port == 0) { Print(" '%s' invalid lobby port number %d\n", me.Name(), port); } else { Print(" '%s' is a client without a server\n", me.Name()); } } NetLobbyClient::NetLobbyClient(const NetAddr& server_addr) : NetLobby(true), server_id(0), addr(server_addr), host(false), exit_code(0), temporary(true) { ping_req_time = 0; chat_req_time = 0; user_req_time = 0; camp_req_time = 0; unit_req_time = 0; mods_req_time = 0; if (addr.IPAddr() != 0) { link = new NetLink; server_id = link->AddPeer(addr); } } NetLobbyClient::~NetLobbyClient() { missions.destroy(); } // +--------------------------------------------------------------------+ void NetLobbyClient::SendData(int type, Text msg) { if (link && server_id && type > 0 && type < 255) { if (msg.length()) link->SendMessage(server_id, (BYTE) type, msg.data(), msg.length(), NetMsg::RELIABLE); else link->SendMessage(server_id, (BYTE) type, 0, 0, NetMsg::RELIABLE); } } // +--------------------------------------------------------------------+ void NetLobbyClient::ExecFrame() { NetLobby::ExecFrame(); if (!temporary) { // check health of server: NetPeer* s = link->FindPeer(server_id); if (s && (NetLayer::GetUTC() - s->LastReceiveTime() > NET_DISCONNECT_TIME)) { exit_code = 2; } // send keep alive ping: if ((NetLayer::GetUTC() - ping_req_time > NET_DISCONNECT_TIME/3)) { SendData(NET_LOBBY_SERVER_INFO, Text()); ping_req_time = NetLayer::GetUTC(); } } } // +--------------------------------------------------------------------+ bool NetLobbyClient::Login(bool host_req) { Player* player = Player::GetCurrentPlayer(); if (!player) return false; host = host_req; Text login = "name \""; login += SafeQuotes(player->Name()); login += "\" pass \""; login += SafeQuotes(player->Password()); login += "\" version \""; login += versionInfo; login += "\" "; char buffer[256]; sprintf_s(buffer, "host %s rank %d time %d miss %d kill %d loss %d ", host ? "true" : "false", player->Rank(), player->FlightTime(), player->Missions(), player->Kills(), player->Losses()); login += buffer; login += "sig \""; login += SafeQuotes(player->Signature()); login += "\" squad \""; login += SafeQuotes(player->Squadron()); login += "\" "; if (gamepass.length() > 0) { login += "gamepass \""; login += SafeQuotes(gamepass); login += "\" "; } SendData(NET_LOBBY_LOGIN, login); ExecFrame(); return true; } bool NetLobbyClient::Logout() { if (host) GameStop(); SendData(NET_LOBBY_LOGOUT, Text()); Sleep(250); ExecFrame(); return true; } // +--------------------------------------------------------------------+ bool NetLobbyClient::Ping() { Text no_data; SendData(NET_LOBBY_PING, no_data); Sleep(100); SendData(NET_LOBBY_PING, no_data); Sleep(100); SendData(NET_LOBBY_PING, no_data); Sleep(100); SendData(NET_LOBBY_SERVER_INFO, no_data); Sleep(700); ExecFrame(); return (server_info.status > NetServerInfo::OFFLINE); } void NetLobbyClient::GameStart() { SendData(NET_LOBBY_GAME_START, Text()); Sleep(100); SetStatus(NetServerInfo::ACTIVE); Starshatter::GetInstance()->SetGameMode(Game::PREP_MODE); // discard unit map selection data so that // it will be refreshed when we return to // the lobby after the mission: ClearUnitMap(); } void NetLobbyClient::GameStop() { SendData(NET_LOBBY_GAME_STOP, Text()); ExecFrame(); Sleep(100); SetStatus(NetServerInfo::LOBBY); } // +--------------------------------------------------------------------+ const Text& NetLobbyClient::GetMachineInfo() { if (server_info.status > NetServerInfo::OFFLINE) return server_info.machine_info; return NetLobby::GetMachineInfo(); } const Text& NetLobbyClient::GetVersion() { if (server_info.status > NetServerInfo::OFFLINE) return server_info.version; return NetLobby::GetVersion(); } int NetLobbyClient::GetStatus() const { if (server_info.status > NetServerInfo::OFFLINE) return server_info.status; return NetLobby::GetStatus(); } int NetLobbyClient::NumUsers() { if (server_info.status > NetServerInfo::OFFLINE) return server_info.nplayers; return NetLobby::NumUsers(); } bool NetLobbyClient::HasHost() { if (server_info.status > NetServerInfo::OFFLINE) return server_info.hosted ? true : false; return NetLobby::HasHost(); } WORD NetLobbyClient::GetGamePort() { if (server_info.status > NetServerInfo::OFFLINE) return server_info.gameport; return NetLobby::GetGamePort(); } // +-------------------------------------------------------------------+ void NetLobbyClient::AddChat(NetUser* user, const char* msg, bool route) { if (!msg || !*msg) return; char buffer[280]; sprintf_s(buffer, "msg \"%s\"", SafeQuotes(msg)); SendData(NET_LOBBY_CHAT, buffer); ExecFrame(); } List& NetLobbyClient::GetChat() { if (chat_log.size() < 1 && (NetLayer::GetUTC() - chat_req_time > 3)) { SendData(NET_LOBBY_CHAT, Text()); chat_req_time = NetLayer::GetUTC(); } return chat_log; } List& NetLobbyClient::GetUsers() { if (users.size() < 1 && (NetLayer::GetUTC() - user_req_time > 2)) { SendData(NET_LOBBY_USER_LIST, Text()); user_req_time = NetLayer::GetUTC(); } return users; } List& NetLobbyClient::GetServerMods() { if (server_mods.size() < 1 && (NetLayer::GetUTC() - mods_req_time > 2)) { SendData(NET_LOBBY_SERVER_MODS, Text()); mods_req_time = NetLayer::GetUTC(); } return server_mods; } List& NetLobbyClient::GetUnitMap() { bool request = selected_mission && unit_map.size() < 1 && (NetLayer::GetUTC() - unit_req_time > 2); if (selected_mission && GetStatus() == NetServerInfo::ACTIVE && (NetLayer::GetUTC() - unit_req_time > 5)) request = true; if (request) { SendData(NET_LOBBY_UNIT_LIST, Text()); unit_req_time = NetLayer::GetUTC(); } return unit_map; } // +--------------------------------------------------------------------+ List& NetLobbyClient::GetCampaigns() { if (campaigns.size() < 1 && (NetLayer::GetUTC() - camp_req_time > 3)) { SendData(NET_LOBBY_MISSION_LIST, Text()); camp_req_time = NetLayer::GetUTC(); } return campaigns; } // +--------------------------------------------------------------------+ void NetLobbyClient::BanUser(NetUser* user) { char buffer[512]; sprintf_s(buffer, "user \"%s\"", SafeQuotes(user->Name())); SendData(NET_LOBBY_BAN_USER, buffer); } // +--------------------------------------------------------------------+ void NetLobbyClient::SelectMission(DWORD id) { char buffer[32]; sprintf_s(buffer, "m_id 0x%08x", id); SendData(NET_LOBBY_MISSION_SELECT, buffer); } void NetLobbyClient::MapUnit(int n, const char* user, bool lock) { if (user && strlen(user) > 250) return; char buffer[512]; sprintf_s(buffer, "id %d user \"%s\" lock %s", n, SafeQuotes(user), lock ? "true" : "false"); SendData(NET_LOBBY_MAP_UNIT, buffer); } Mission* NetLobbyClient::GetSelectedMission() { mission = 0; // ask server for mission: SendData(NET_LOBBY_MISSION_DATA, Text()); // wait for answer: int i = 150; while (i-- > 0 && !mission) { Sleep(100); ExecFrame(); } return mission; } // +--------------------------------------------------------------------+ void NetLobbyClient::DoAuthUser(NetPeer* peer, Text msg) { List params; ParseMsg(msg, params); int level = NetAuth::NET_AUTH_STANDARD; Text salt; for (int i = 0; i < params.size(); i++) { NetLobbyParam* p = params[i]; int num = 0; sscanf_s(p->value, "%d", &num); if (p->name == "level") { level = num; } else if (p->name == "salt") { salt = p->value; } } Text response = NetAuth::CreateAuthResponse(level, salt); if (response.length() > 0) SendData(NET_LOBBY_USER_AUTH, response); } void NetLobbyClient::DoServerInfo(NetPeer* peer, Text msg) { List params; ParseMsg(msg, params); for (int i = 0; i < params.size(); i++) { NetLobbyParam* p = params[i]; int num = 0; sscanf_s(p->value, "%d", &num); if (p->name == "info") { server_info.machine_info = p->value; } else if (p->name == "version") { server_info.version = p->value; } else if (p->name == "mode") { server_info.status = num; } else if (p->name == "users") { server_info.nplayers = num; } else if (p->name == "host") { server_info.hosted = (p->value == "true"); } else if (p->name == "port") { server_info.gameport = (WORD) num; } } params.destroy(); } void NetLobbyClient::DoChat(NetPeer* peer, Text msg) { List params; ParseMsg(msg, params); int id = 0; Text user_name; Text chat_msg; for (int i = 0; i < params.size(); i++) { NetLobbyParam* p = params[i]; int num = 0; sscanf_s(p->value, "%d", &num); if (p->name == "id") id = num; else if (p->name == "user") user_name = p->value; else if (p->name == "msg") chat_msg = p->value; } params.destroy(); // receive chat from server: if (id && chat_msg.length()) { NetChatEntry* entry = new NetChatEntry(id, user_name, chat_msg); if (!chat_log.contains(entry)) chat_log.insertSort(entry); else delete entry; // received duplicate } } void NetLobbyClient::DoServerMods(NetPeer* peer, Text msg) { mods_req_time = NetLayer::GetUTC() + 3600; List params; ParseMsg(msg, params); server_mods.destroy(); Text name; Text version; Text url; for (int i = 0; i < params.size(); i++) { NetLobbyParam* p = params[i]; if (p->name == "mod") { name = p->value; } else if (p->name == "url") { url = p->value; } else if (p->name == "ver") { version = p->value; ModInfo* info = new ModInfo(name, version, url); server_mods.append(info); } } params.destroy(); } void NetLobbyClient::DoUserList(NetPeer* peer, Text msg) { List params; ParseMsg(msg, params); users.destroy(); Text user_name; Text host_flag; Text signature; Text squadron; int rank = 0; int flight_time = 0; int mission_count = 0; int kills = 0; int losses = 0; for (int i = 0; i < params.size(); i++) { NetLobbyParam* p = params[i]; int num = 0; sscanf_s(p->value, "%d", &num); if (p->name == "name") user_name = p->value; else if (p->name == "sig") signature = p->value; else if (p->name == "squad") squadron = p->value; else if (p->name == "rank") rank = num; else if (p->name == "time") flight_time = num; else if (p->name == "miss") mission_count = num; else if (p->name == "kill") kills = num; else if (p->name == "loss") losses = num; else if (p->name == "host") { host_flag = p->value; NetUser* u = new NetUser(user_name); u->SetHost((host_flag == "true") ? true : false); u->SetSignature(signature); u->SetSquadron(squadron); u->SetRank(rank); u->SetFlightTime(flight_time); u->SetMissions(mission_count); u->SetKills(kills); u->SetLosses(losses); AddUser(u); } } params.destroy(); } void NetLobbyClient::DoMissionList(NetPeer* peer, Text msg) { List params; ParseMsg(msg, params); if (params.size() > 2) { campaigns.destroy(); NetCampaignInfo* c = 0; MissionInfo* m = 0; for (int i = 0; i < params.size(); i++) { NetLobbyParam* p = params[i]; if (p->name == "c_id") { c = new NetCampaignInfo; sscanf_s(p->value, "0x%x", &c->id); campaigns.append(c); m = 0; } else if (c && p->name == "c_name") { c->name = p->value; } else if (p->name == "m_id") { int id = 0; sscanf_s(p->value, "0x%x", &id); int m_id = id & NET_MISSION_MASK; int c_id = id >> NET_CAMPAIGN_SHIFT; for (int i = 0; i < campaigns.size(); i++) { NetCampaignInfo* c = campaigns[i]; if (c->id == c_id) { m = new MissionInfo; m->id = m_id; c->missions.append(m); missions.append(m); // for later garbage collection break; } } } else if (m && p->name == "m_name") { m->name = p->value; } else if (m && p->name == "m_desc") { m->description = p->value; } } } params.destroy(); } void NetLobbyClient::DoMissionSelect(NetPeer* peer, Text msg) { List params; ParseMsg(msg, params); for (int i = 0; i < params.size(); i++) { NetLobbyParam* p = params[i]; int num = 0; sscanf_s(p->value, "0x%x", &num); if (p->name == "m_id") { if (selected_mission != (DWORD) num) { selected_mission = num; ClearUnitMap(); } } } params.destroy(); } void NetLobbyClient::DoMissionData(NetPeer* peer, Text msg) { Campaign* c = Campaign::SelectCampaign("Multiplayer Missions"); if (c) { c->LoadNetMission(99999, msg.data()); mission = c->GetMission(99999); } if (msg.length()) { FILE* f; ::fopen_s(&f, "multi_mission_recv.def", "wb"); if (f) { ::fwrite(msg.data(), msg.length(), 1, f); ::fclose(f); } } } void NetLobbyClient::DoUnitList(NetPeer* peer, Text msg) { List params; ParseMsg(msg, params); if (params.size() > 2) { ClearUnitMap(); Text elem_name; Text design; Text user_name; int iff; int index; int lives = 1; int hull = 100; int role = 0; int lock = 0; for (int i = 0; i < params.size(); i++) { NetLobbyParam* p = params[i]; if (p->name == "name") { elem_name = p->value; } else if (p->name == "design") { design = p->value; } else if (p->name == "user") { user_name = p->value; } else if (p->name == "index") { sscanf_s(p->value, "%d", &index); } else if (p->name == "iff") { sscanf_s(p->value, "%d", &iff); } else if (p->name == "lives") { sscanf_s(p->value, "%d", &lives); } else if (p->name == "hull") { sscanf_s(p->value, "%d", &hull); } else if (p->name == "role") { sscanf_s(p->value, "%d", &role); } else if (p->name == "lock") { sscanf_s(p->value, "%d", &lock); NetUnitEntry* entry = new NetUnitEntry(elem_name, design, iff, index); entry->SetUserName(user_name); entry->SetLives(lives); entry->SetIntegrity(hull); entry->SetMissionRole(role); entry->SetLock(lock ? true : false); unit_map.append(entry); } } } params.destroy(); } void NetLobbyClient::DoMapUnit(NetPeer* peer, Text msg) { } void NetLobbyClient::DoGameStart(NetPeer* peer, Text msg) { } void NetLobbyClient::DoExit(NetPeer* peer, Text msg) { exit_code = 1; }