/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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, either version 3 of the License, or * (at your option) any later version. * * 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 for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/config-manager.h" #include "scumm/he/intern_he.h" // For random map generation #include "scumm/he/moonbase/moonbase.h" #include "scumm/he/moonbase/map_main.h" #include "scumm/he/net/net_main.h" #include "scumm/he/net/net_defines.h" namespace Scumm { Net::Net(ScummEngine_v90he *vm) : _latencyTime(1), _fakeLatency(false), _vm(vm) { //some defaults for fields _gameName = _vm->_game.gameid; _gameVersion = ""; if (_vm->_game.variant) _gameVersion = _vm->_game.variant; // 1.0/1.1/Demo _tmpbuffer = (byte *)malloc(MAX_PACKET_SIZE); _enet = nullptr; _sessionHost = nullptr; _broadcastSocket = nullptr; _sessionServerAddress = Address {"multiplayer.scummvm.org", 9120}; _forcedAddress = false; _sessionServerPeer = -1; _sessionServerHost = nullptr; _gotSessions = false; _isRelayingGame = false; _numUsers = 0; _numBots = 0; _maxPlayers = 2; if (_gameName == "moonbase") _maxPlayers = 4; _userIdCounter = 0; _myUserId = -1; _fromUserId = -1; _sessionId = -1; _isHost = false; _isShuttingDown = false; _sessionName = Common::String(); _sessions = Common::Array(); _mapGenerator = 0; _mapSeed = 0; _mapSize = 0; _mapTileset = 0; _mapEnergy = 0; _mapTerrain = 0; _mapWater = 0; _encodedMap = Common::String(); _hostPort = 0; _hostDataQueue = Common::Queue(); _peerIndexQueue = Common::Queue(); } Net::~Net() { free(_tmpbuffer); closeProvider(); } Net::Address Net::getAddressFromString(Common::String addressStr) { Address address; int portPos = addressStr.findFirstOf(":"); if (portPos > -1) { address.port = atoi(addressStr.substr(portPos + 1).c_str()); address.host = addressStr.substr(0, portPos); } else { // Assume that the string has no port defined. address.host = addressStr; address.port = 0; } return address; } Common::String Net::getStringFromAddress(Address address) { return Common::String::format("%s:%d", address.host.c_str(), address.port); } void Net::setSessionServer(Common::String sessionServer) { debugC(DEBUG_NETWORK, "Net::setSessionServer(\"%s\")", sessionServer.c_str()); _forcedAddress = true; ConfMan.setBool("enable_session_server", true); ConfMan.setBool("enable_lan_broadcast", false); _sessionServerAddress = getAddressFromString(sessionServer); // Set port to default if not defined. if (!_sessionServerAddress.port) _sessionServerAddress.port = 9120; } int Net::hostGame(char *sessionName, char *userName) { if (createSession(sessionName)) { if (addUser(userName, userName)) { _myUserId = _userIdCounter; _userIdToPeerIndex[_myUserId] = -1; return 1; } else { _vm->displayMessage("Error Adding User \"%s\" to Session \"%s\"", userName, sessionName); endSession(); closeProvider(); } } else { _vm->displayMessage("Error creating session \"%s\"", userName ); closeProvider(); } return 0; } int Net::joinGame(Common::String IP, char *userName) { debugC(DEBUG_NETWORK, "Net::joinGame(\"%s\", \"%s\")", IP.c_str(), userName); // PN_JoinTCPIPGame Address address = getAddressFromString(IP); bool getGeneratedMap = false; bool isLocal = false; // TODO: 20-bit block address (172.16.0.0 – 172.31.255.255) if (address.host == "127.0.0.1" || address.host == "localhost" || address.host == "255.255.255.255" || address.host.matchString("10.*.*.*") || address.host.matchString("192.168.*.*")) { isLocal = true; } if (isLocal) { if (!address.port) { // Local connection with no port specified. Send a session request to get port: startQuerySessions(false); if (!_broadcastSocket) { return 0; } _sessions.clear(); _broadcastSocket->send(address.host.c_str(), 9130, "{\"cmd\": \"get_session\"}"); uint tickCount = 0; while (!_sessions.size()) { serviceBroadcast(); // Wait for one second for response before giving up tickCount += 5; g_system->delayMillis(5); if (tickCount >= 1000) break; } if (!_sessions.size()) return 0; if (address.host == "255.255.255.255") address.host = _sessions[0].host; address.port = _sessions[0].port; getGeneratedMap = _sessions[0].getGeneratedMap; stopQuerySessions(); } // We got our address and port, attempt connection: if (connectToSession(address.host, address.port, getGeneratedMap)) { // Connected, add our user. return addUser(userName, userName); } else { warning("NETWORK: Failed to connect to %s:%d", address.host.c_str(), address.port); } } else { warning("STUB: joinGame: Public IP connection %s", address.host.c_str()); } return 0; } bool Net::connectToSession(Common::String address, int port, bool queryGeneratedMap) { if (_hostPort) _sessionHost = _enet->connectToHost("0.0.0.0", _hostPort, address, port); else _sessionHost = _enet->connectToHost(address, port); if (!_sessionHost) return false; if (_gameName == "moonbase" && queryGeneratedMap) { Common::String queryMap = Common::String("{\"cmd\":\"query_map\"}"); _sessionHost->send(queryMap.c_str(), 0, 0, true); uint tickCount = 0; while (_vm->_moonbase->_map->getGenerator() > 0) { remoteReceiveData(); // Wait for five seconds for map data before giving up tickCount += 5; g_system->delayMillis(5); if (tickCount >= 5000) break; } } return true; } int Net::addUser(char *shortName, char *longName) { debugC(DEBUG_NETWORK, "Net::addUser(\"%s\", \"%s\")", shortName, longName); // PN_AddUser // TODO: What's the difference between shortName and longName? if (_isHost) { if (getTotalPlayers() > 4) { // We are full. return 0; } _userIdToName[++_userIdCounter] = longName; _numUsers++; if (_sessionId && _sessionServerPeer > -1) { // Update player count to session server { Common::String updatePlayers = Common::String::format( "{\"cmd\":\"update_players\",\"game\":\"%s\",\"version\":\"%s\",\"players\":%d}", _gameName.c_str(), _gameVersion.c_str(), getTotalPlayers()); _sessionHost->send(updatePlayers.c_str(), _sessionServerPeer); } return 1; } // Client: if (_myUserId != -1) return 1; Common::String addUser = Common::String::format( "{\"cmd\":\"add_user\",\"name\":\"%s\"}", longName); _sessionHost->send(addUser.c_str(), 0, 0, true); uint tickCount = 0; while (_myUserId == -1) { remoteReceiveData(); // Wait for five seconds for our user id before giving up tickCount += 5; g_system->delayMillis(5); if (tickCount >= 5000) break; } return (_myUserId > -1) ? 1 : 0; } int Net::removeUser() { debugC(DEBUG_NETWORK, "Net::removeUser()"); // PN_RemoveUser if (_myUserId != -1) destroyPlayer(_myUserId); return 1; } int Net::whoSentThis() { debugC(DEBUG_NETWORK, "Net::whoSentThis(): return %d", _fromUserId); // PN_WhoSentThis return _fromUserId; } int Net::whoAmI() { debugC(DEBUG_NETWORK, "Net::whoAmI(): return %d", _myUserId); // PN_WhoAmI return _myUserId; } int Net::createSession(char *name) { debugC(DEBUG_NETWORK, "Net::createSession(\"%s\")", name); // PN_CreateSession if (!_enet) { return 0; }; _sessionId = -1; _sessionName = name; // Normally we would do only one peer or three peers but we are reserving one // for our connection to the session server. _sessionHost = _enet->createHost("0.0.0.0", 0, _maxPlayers + 1); if (!_sessionHost) { return 0; } _isHost = true; Common::String mapData = "{}"; if (_gameName == "moonbase") { Map *map = _vm->_moonbase->_map; if (map->generateNewMap()) { // Store the configured map variables _mapGenerator = map->getGenerator(); _mapSeed = map->getSeed(); _mapSize = map->getSize(); _mapTileset = map->getTileset(); _mapEnergy = map->getEnergy(); _mapTerrain = map->getTerrain(); _mapWater = map->getWater(); _encodedMap = map->getEncodedMap(); mapData = Common::String::format( "{\"generator\":%d,\"seed\":%d,\"size\":%d,\"tileset\":%d,\"energy\":%d,\"terrain\":%d,\"water\":%d,\"data\":\"%s\"}", _mapGenerator, _mapSeed, _mapSize, _mapTileset, _mapEnergy, _mapTerrain, _mapWater, _encodedMap.c_str()); } } bool enableSessionServer = true; bool enableLanBroadcast = true; if (ConfMan.hasKey("enable_session_server")) enableSessionServer = ConfMan.getBool("enable_session_server"); if (ConfMan.hasKey("enable_lan_broadcast")) enableLanBroadcast = ConfMan.getBool("enable_lan_broadcast"); if (enableSessionServer) { if (!_forcedAddress && ConfMan.hasKey("session_server")) { _sessionServerAddress = getAddressFromString(ConfMan.get("session_server")); // Set port to default if not defined. if (!_sessionServerAddress.port) _sessionServerAddress.port = 9120; } if (_sessionHost->connectPeer(_sessionServerAddress.host, _sessionServerAddress.port)) { // FIXME: Get the IP address of the session server when a domain address is used. // _sessionServerPeer = _sessionHost->getPeerIndexFromHost(_sessionServerAddress.host, _sessionServerAddress.port); _sessionServerPeer = 0; // Create session to the session server. Common::String req = Common::String::format( "{\"cmd\":\"host_session\",\"game\":\"%s\",\"version\":\"%s\",\"name\":\"%s\",\"maxplayers\":%d,\"network_version\":\"%s\",\"map_data\":%s}", _gameName.c_str(), _gameVersion.c_str(), name, _maxPlayers, NETWORK_VERSION, mapData.c_str()); debugC(DEBUG_NETWORK, "NETWORK: Sending to session server: %s", req.c_str()); _sessionHost->send(req.c_str(), _sessionServerPeer); } else { warning("Failed to connect to session server! This game will not be listed on the Internet"); } } if (enableLanBroadcast) { _broadcastSocket = _enet->createSocket("0.0.0.0", 9130); if (!_broadcastSocket) { warning("NETWORK: Unable to create broadcast socket, your game will not be broadcast over LAN"); } } return 1; } int Net::getTotalPlayers() { return _numUsers + _numBots; } int Net::joinSessionById(int sessionId) { debugC(DEBUG_NETWORK, "Net::joinSessionById(%d)", sessionId); if (_sessions.empty()) { warning("Net::joinSession(): no sessions"); return 0; } for (auto &i : _sessions) { if (i.id == sessionId) { return doJoinSession(i); } } warning("Net::joinSessionById(): session %d not found", sessionId); return 0; } int Net::ifSessionExist(int sessionId) { debugC(DEBUG_NETWORK, "Net::ifSessionExist(%d)", sessionId); if (_sessions.empty()) { debugC(DEBUG_NETWORK, "Net::ifSessionExist(): no sessions"); return 0; } for (auto &i : _sessions) { if (i.id == sessionId) { return 1; } } debugC(DEBUG_NETWORK, "Net::ifSessionExist(): session %d not found.", sessionId); return 0; } int Net::doJoinSession(Session session) { if (!session.local && _sessionServerHost) { Common::String joinSession = Common::String::format( "{\"cmd\":\"join_session\",\"game\":\"%s\",\"version\":\"%s\",\"id\":%d}", _gameName.c_str(), _gameVersion.c_str(), session.id); _sessionServerHost->send(joinSession.c_str(), 0); // Give the host time to hole punch us. g_system->delayMillis(500); } // Disconnect the session server to free up and use the same port we've connected previously. if (_sessionServerHost) { _sessionServerHost->disconnectPeer(0); delete _sessionServerHost; _sessionServerHost = nullptr; } if (_gameName == "moonbase" && session.mapGenerator > 0) { generateMoonbaseMap(session); } bool success = connectToSession(session.host, session.port, session.getGeneratedMap); if (!success) { if (!session.local) { // Start up a relay session with the host. // This will re-connect us to the session server. startQuerySessions(); if (_sessionServerHost) { Common::String startRelay = Common::String::format( "{\"cmd\":\"start_relay\",\"game\":\"%s\",\"version\":\"%s\",\"session\":%d}", _gameName.c_str(), _gameVersion.c_str(), session.id); _sessionServerHost->send(startRelay.c_str(), 0); uint tickCount = 0; while (_myUserId == -1) { serviceSessionServer(); // Wait for five seconds for our user id before giving up tickCount += 5; g_system->delayMillis(5); if (tickCount >= 5000) break; } if (_myUserId > -1) // If we have gotten our user id, that means that we are now relaying. return true; } } _vm->displayMessage("Unable to join game session with address \"%s:%d\"", session.host.c_str(), session.port); return false; } return true; } void Net::generateMoonbaseMap(Session session) { _vm->_moonbase->_map->generateMapWithInfo(session.encodedMap, session.mapGenerator, session.mapSeed, session.mapSize, session.mapTileset, session.mapEnergy, session.mapTerrain, session.mapWater); } int Net::joinSession(int sessionIndex) { debugC(DEBUG_NETWORK, "Net::joinSession(%d)", sessionIndex); // PN_JoinSession if (_sessions.empty()) { warning("Net::joinSession(): no sessions"); return 0; } if (sessionIndex >= (int)_sessions.size()) { warning("Net::joinSession(): session number too big: %d >= %d", sessionIndex, _sessions.size()); return 0; } Session session = _sessions[sessionIndex]; return doJoinSession(session); } int Net::endSession() { debugC(DEBUG_NETWORK, "Net::endSession()"); // PN_EndSession if (_isHost && _hostDataQueue.size()) { _isShuttingDown = true; // Send out any remaining data from the queue before shutting down. while (_hostDataQueue.size()) { if (_hostDataQueue.size() != _peerIndexQueue.size()) warning("NETWORK: Sizes of data and peer index queues does not match! Expect some wonky stuff"); Common::JSONValue *json = _hostDataQueue.pop(); int peerIndex = _peerIndexQueue.pop(); handleGameDataHost(json, peerIndex); _isShuttingDown = false; } } if (_sessionHost && _sessionServerPeer > -1) { _sessionHost->disconnectPeer(_sessionServerPeer); _sessionServerPeer = -1; } if (_sessionHost) { delete _sessionHost; _sessionHost = nullptr; } if (_sessionServerHost) { _sessionServerHost->disconnectPeer(0); delete _sessionServerHost; _sessionServerHost = nullptr; } if (_broadcastSocket) { delete _broadcastSocket; _broadcastSocket = nullptr; } _hostPort = 0; _numUsers = 0; _numBots = 0; _userIdCounter = 0; _userIdToName.clear(); _userIdToPeerIndex.clear(); _sessionId = -1; _sessionName.clear(); _myUserId = -1; _fromUserId = -1; _isHost = false; _hostDataQueue.clear(); _peerIndexQueue.clear(); _isRelayingGame = false; _mapGenerator = 0; _mapSeed = 0; _mapSize = 0; _mapTileset = 0; _mapEnergy = 0; _mapTerrain = 0; _mapWater = 0; _encodedMap = ""; return 1; } void Net::disableSessionJoining() { debugC(DEBUG_NETWORK, "Net::disableSessionJoining()"); // PN_DisableSessionPlayerJoin if (_sessionHost && _sessionServerPeer > -1 && !_isRelayingGame) { _sessionHost->disconnectPeer(_sessionServerPeer); _sessionServerPeer = -1; } if (_broadcastSocket) { delete _broadcastSocket; _broadcastSocket = nullptr; } } void Net::enableSessionJoining() { warning("STUB: Net::enableSessionJoining()"); // PN_EnableSessionPlayerJoin } void Net::setBotsCount(int botsCount) { debugC(DEBUG_NETWORK, "Net::setBotsCount(%d)", botsCount); // PN_SetAIPlayerCountKludge _numBots = botsCount; } int32 Net::setProviderByName(int32 parameter1, int32 parameter2) { char name[MAX_PROVIDER_NAME]; char ipaddress[MAX_IP_SIZE]; ipaddress[0] = '\0'; _vm->getStringFromArray(parameter1, name, sizeof(name)); if (parameter2) _vm->getStringFromArray(parameter2, ipaddress, sizeof(ipaddress)); debugC(DEBUG_NETWORK, "Net::setProviderByName(\"%s\", \"%s\")", name, ipaddress); // PN_SetProviderByName // Emulate that we found a TCP/IP provider // Initialize provider: return initProvider(); } void Net::setFakeLatency(int time) { _latencyTime = time; debugC(DEBUG_NETWORK, "NETWORK: Setting Fake Latency to %d ms", _latencyTime); _fakeLatency = true; } bool Net::destroyPlayer(int32 userId) { // bool PNETWIN_destroyplayer(DPID idPlayer) debugC(DEBUG_NETWORK, "Net::destroyPlayer(%d)", userId); if (_isHost) { if (userId == 1) return true; if (_userIdToName.contains(userId)) { _userIdToName.erase(userId); _numUsers--; if (_userIdToAddress.contains(userId)) { Common::String address = _userIdToAddress[userId]; _addressToUserId.erase(address); _userIdToAddress.erase(userId); } if (_userIdToPeerIndex.contains(userId) && _userIdToPeerIndex[userId] != _sessionServerPeer) { _sessionHost->disconnectPeer(_userIdToPeerIndex[userId]); _userIdToPeerIndex.erase(userId); } return true; } warning("NETWORK: destoryPlayer(%d): User does not exist!", userId); return false; } Common::String removerUser = "{\"cmd\":\"remove_user\"}"; _sessionHost->send(removerUser.c_str(), 0, 0, true); _sessionHost->disconnectPeer(0); return true; } int32 Net::startQuerySessions(bool connectToSessionServer) { debugC(DEBUG_NETWORK, "Net::startQuerySessions()"); if (!_enet) { warning("NETWORKING: ENet not initialized yet"); return 0; } bool enableSessionServer = true; bool enableLanBroadcast = true; if (ConfMan.hasKey("enable_session_server")) enableSessionServer = ConfMan.getBool("enable_session_server"); if (ConfMan.hasKey("enable_lan_broadcast")) enableLanBroadcast = ConfMan.getBool("enable_lan_broadcast"); if (connectToSessionServer && enableSessionServer) { if (!_sessionServerHost) { if (!_forcedAddress && ConfMan.hasKey("session_server")) { _sessionServerAddress = getAddressFromString(ConfMan.get("session_server")); // Set port to default if not defined. if (!_sessionServerAddress.port) _sessionServerAddress.port = 9120; } _sessionServerHost = _enet->connectToHost(_sessionServerAddress.host, _sessionServerAddress.port); if (!_sessionServerHost) warning("Failed to connect to session server! You'll won't be able to join internet sessions"); } } if (enableLanBroadcast && !_broadcastSocket) { _broadcastSocket = _enet->createSocket("0.0.0.0", 0); } return 1; } int32 Net::updateQuerySessions() { debugC(DEBUG_NETWORK, "Net::updateQuerySessions(): begin"); // UpdateQuerySessions if (_sessionServerHost) { // Get internet-based sessions from the session server. Common::String getSessions = Common::String::format( "{\"cmd\":\"get_sessions\",\"game\":\"%s\",\"version\":\"%s\",\"network_version\":\"%s\"}", _gameName.c_str(), _gameVersion.c_str(), NETWORK_VERSION); _sessionServerHost->send(getSessions.c_str(), 0); _gotSessions = false; uint32 tickCount = g_system->getMillis() + 1000; while (g_system->getMillis() < tickCount) { serviceSessionServer(); if (_gotSessions) break; } } if (_broadcastSocket) { // Send a session query to the broadcast address. _broadcastSocket->send("255.255.255.255", 9130, "{\"cmd\": \"get_session\"}"); uint32 tickCount = g_system->getMillis() + 500; while (g_system->getMillis() < tickCount) { serviceBroadcast(); } } for (Common::Array::iterator i = _sessions.begin(); i != _sessions.end();) { if (g_system->getMillis() - i->timestamp > 5000) { // It has been 5 seconds since we have last seen this session, remove it. i = _sessions.erase(i); } else { i++; } } debugC(DEBUG_NETWORK, "Net::updateQuerySessions(): got %d", _sessions.size()); return _sessions.size(); } void Net::stopQuerySessions() { debugC(DEBUG_NETWORK, "Net::stopQuerySessions()"); // StopQuerySessions if (_sessionServerHost && !_isRelayingGame) { _sessionServerHost->disconnectPeer(0); delete _sessionServerHost; _sessionServerHost = nullptr; } if (_broadcastSocket) { delete _broadcastSocket; _broadcastSocket = nullptr; } _sessions.clear(); // No op } int Net::querySessions() { debugC(DEBUG_NETWORK, "Net::querySessions()"); // PN_QuerySessions // Deprecated OP used in Football and Baseball to query sessions, // emulate this by using the functions used in Moonbase Commander: startQuerySessions(); return updateQuerySessions(); } int Net::queryProviders() { debugC(DEBUG_NETWORK, "Net::queryProviders()"); // PN_QueryProviders // Emulate that we have 1 provider, TCP/IP return 1; } int Net::setProvider(int providerIndex) { warning("STUB: Net::setProvider(%d)", providerIndex); // PN_SetProvider return 0; } int Net::closeProvider() { debugC(DEBUG_NETWORK, "Net::closeProvider()"); // PN_CloseProvider if (_enet) { // Destroy all ENet instances and deinitialize. if (_sessionHost) { endSession(); } delete _enet; _enet = nullptr; } return 1; } bool Net::initAll() { warning("STUB: Net::initAll()"); // PN_DoInitAll return false; } bool Net::initProvider() { debugC(DEBUG_NETWORK, "Net::initProvider"); // PN_DoInitProvider // Create a new ENet instance and initialize the library. if (_enet) return true; _enet = new Networking::ENet::ENet(); if (!_enet->initialize()) { _vm->displayMessage("Unable to initialize ENet library."); Net::closeProvider(); return false; } return true; } bool Net::initSession() { warning("STUB: Net::initSession()"); // PN_DoInitSession return false; } bool Net::initUser() { warning("STUB: Net::initUser()"); // PN_DoInitUser return false; } void Net::remoteStartScript(int typeOfSend, int sendTypeParam, int priority, int argsCount, int32 *args) { Common::String res = "\"params\": ["; if (argsCount > 2) for (int i = 0; i < argsCount - 1; i++) res += Common::String::format("%d,", args[i]); if (argsCount > 1) res += Common::String::format("%d]", args[argsCount - 1]); else res += "]"; debugC(DEBUG_NETWORK, "Net::remoteStartScript(%d, %d, %d, %d, ...)", typeOfSend, sendTypeParam, priority, argsCount); // PN_RemoteStartScriptCommand remoteSendData(typeOfSend, sendTypeParam, PACKETTYPE_REMOTESTARTSCRIPT, res, priority); } int Net::remoteSendData(int typeOfSend, int sendTypeParam, int type, Common::String data, int priority, int defaultRes, bool wait, int callid) { if (!_enet || !_sessionHost || _myUserId == -1) return defaultRes; if (typeOfSend == PN_SENDTYPE_INDIVIDUAL && sendTypeParam == 0) // In DirectPlay, sending a message to 0 means all players // sooo, send all. typeOfSend = PN_SENDTYPE_ALL; bool reliable = false; if (priority == PN_PRIORITY_HIGH || typeOfSend == PN_SENDTYPE_ALL_RELIABLE || typeOfSend == PN_SENDTYPE_ALL_RELIABLE_TIMED) reliable = true; // Since I am lazy, instead of constructing the JSON object manually // I'd rather parse it Common::String res = Common::String::format( "{\"cmd\":\"game\",\"from\":%d,\"to\":%d,\"toparam\":%d," "\"type\":%d, \"reliable\":%s, \"data\":{%s}}", _myUserId, typeOfSend, sendTypeParam, type, reliable == true ? "true" : "false", data.c_str()); debugC(DEBUG_NETWORK, "NETWORK: Sending data: %s", res.c_str()); Common::JSONValue *str = Common::JSON::parse(res); if (_isHost) { _hostDataQueue.push(str); _peerIndexQueue.push(sendTypeParam - 1); } else { _sessionHost->send(res.c_str(), 0, 0, reliable); } return defaultRes; } void Net::remoteSendArray(int typeOfSend, int sendTypeParam, int priority, int arrayIndex) { debugC(DEBUG_NETWORK, "Net::remoteSendArray(%d, %d, %d, %d)", typeOfSend, sendTypeParam, priority, arrayIndex & ~MAGIC_ARRAY_NUMBER); // PN_RemoteSendArrayCommand ScummEngine_v90he::ArrayHeader *ah = (ScummEngine_v90he::ArrayHeader *)_vm->getResourceAddress(rtString, arrayIndex & ~MAGIC_ARRAY_NUMBER); Common::String jsonData = Common::String::format( "\"type\":%d,\"dim1start\":%d,\"dim1end\":%d,\"dim2start\":%d,\"dim2end\":%d,\"data\":[", ah->type, ah->acrossMin, ah->acrossMax, ah->downMin, ah->downMax); int32 size = (FROM_LE_32(ah->acrossMax) - FROM_LE_32(ah->acrossMin) + 1) * (FROM_LE_32(ah->downMax) - FROM_LE_32(ah->downMin) + 1); for (int i = 0; i < size; i++) { int32 data; switch (FROM_LE_32(ah->type)) { case ScummEngine_v90he::kByteArray: case ScummEngine_v90he::kStringArray: data = ah->data[i]; break; case ScummEngine_v90he::kIntArray: data = (int16)READ_LE_UINT16(ah->data + i * 2); break; case ScummEngine_v90he::kDwordArray: data = (int32)READ_LE_UINT32(ah->data + i * 4); break; default: error("Net::remoteSendArray(): Unknown array type %d for array %d", FROM_LE_32(ah->type), arrayIndex); } jsonData += Common::String::format("%d", data); if (i < size - 1) jsonData += ","; else jsonData += "]"; } remoteSendData(typeOfSend, sendTypeParam, PACKETTYPE_REMOTESENDSCUMMARRAY, jsonData, priority); } int Net::remoteStartScriptFunction(int typeOfSend, int sendTypeParam, int priority, int defaultReturnValue, int argsCount, int32 *args) { warning("STUB: Net::remoteStartScriptFunction(%d, %d, %d, %d, %d, ...)", typeOfSend, sendTypeParam, priority, defaultReturnValue, argsCount); return 0; int callid = _vm->_rnd.getRandomNumber(1000000); Common::String res = Common::String::format("\"callid\":%d, \"params\": [", callid); if (argsCount > 2) for (int i = 0; i < argsCount - 1; i++) res += Common::String::format("%d, ", args[i]); if (argsCount > 1) res += Common::String::format("%d]", args[argsCount - 1]); else res += "]"; debugC(DEBUG_NETWORK, "Net::remoteStartScriptFunction(%d, %d, %d, %d, %d, ...)", typeOfSend, sendTypeParam, priority, defaultReturnValue, argsCount); // PN_RemoteStartScriptFunction return remoteSendData(typeOfSend, sendTypeParam, PACKETTYPE_REMOTESTARTSCRIPTRETURN, res, defaultReturnValue, true, callid); } bool Net::getHostName(char *hostname, int length) { warning("STUB: Net::getHostName(\"%s\", %d)", hostname, length); // PN_GetHostName return false; } bool Net::getIPfromName(char *ip, int ipLength, char *nameBuffer) { warning("STUB: Net::getIPfromName(\"%s\", %d, \"%s\")", ip, ipLength, nameBuffer); // PN_GetIPfromName return false; } void Net::getSessionName(int sessionNumber, char *buffer, int length) { debugC(DEBUG_NETWORK, "Net::getSessionName(%d, ..., %d)", sessionNumber, length); // PN_GetSessionName if (_sessions.empty()) { *buffer = '\0'; warning("Net::getSessionName(): no sessions"); return; } if (sessionNumber >= (int)_sessions.size()) { *buffer = '\0'; warning("Net::getSessionName(): session number too big: %d >= %d", sessionNumber, (int)_sessions.size()); return; } Common::strlcpy(buffer, _sessions[sessionNumber].name.c_str(), length); } int Net::getSessionPlayerCount(int sessionNumber) { debugC(DEBUG_NETWORK, "Net::getSessionPlayerCount(%d)", sessionNumber); // case GET_SESSION_PLAYER_COUNT_KLUDGE: if (_sessions.empty()) { warning("Net::getSessionPlayerCount(): no sessions"); return 0; } if (sessionNumber >= (int)_sessions.size()) { warning("Net::getSessionPlayerCount(): session number too big: %d >= %d", sessionNumber, (int)_sessions.size()); return 0; } if (_sessions[sessionNumber].players < 1) { warning("Net::getSessionPlayerCount(): no players in session"); return 0; } return _sessions[sessionNumber].players; } void Net::getProviderName(int providerIndex, char *buffer, int length) { warning("STUB: Net::getProviderName(%d, \"%s\", %d)", providerIndex, buffer, length); // PN_GetProviderName } void Net::serviceSessionServer() { if (!_sessionServerHost) return; uint8 type = _sessionServerHost->service(); switch (type) { case ENET_EVENT_TYPE_NONE: break; case ENET_EVENT_TYPE_DISCONNECT: warning("NETWORK: Lost connection to session server"); delete _sessionServerHost; _sessionServerHost = nullptr; break; case ENET_EVENT_TYPE_RECEIVE: handleSessionServerData(_sessionServerHost->getPacketData()); break; } } void Net::handleSessionServerData(Common::String data) { debugC(DEBUG_NETWORK, "NETWORK: Received data from session server. Data: %s", data.c_str()); Common::JSONValue *json = Common::JSON::parse(data); if (!json) { warning("NETWORK: Received non-JSON string from session server, \"%s\", ignoring", data.c_str()); return; } if (!json->isObject()){ warning("NETWORK: Received non-JSON object from session server: \"%s\"", data.c_str()); return; } Common::JSONObject root = json->asObject(); if (root.contains("cmd") && root["cmd"]->isString()) { Common::String command = root["cmd"]->asString(); if (_isHost && command == "host_session_resp") { if (root.contains("id")) { _sessionId = root["id"]->asIntegerNumber(); debugC(DEBUG_NETWORK, "NETWORK: Our session id from session server: %d", _sessionId); } } else if (!_isHost && command == "get_sessions_resp") { if (root.contains("address") && root.contains("sessions")) { _hostPort = getAddressFromString(root["address"]->asString()).port; Common::JSONArray sessions = root["sessions"]->asArray(); for (uint i = 0; i != sessions.size(); i++) { Common::JSONObject sessionData = sessions[i]->asObject(); Address sessionAddress = getAddressFromString(sessionData["address"]->asString()); // Check if we already know about this session: bool makeNewSession = true; for (auto &j : _sessions) { if (j.id == sessionData["id"]->asIntegerNumber()) { // Yes we do, Update the timestamp and player count. makeNewSession = false; if (!j.local) { // Only update if it's not a local session j.timestamp = g_system->getMillis(); j.players = sessionData["players"]->asIntegerNumber(); } break; } } if (!makeNewSession) continue; Session session; session.id = sessionData["id"]->asIntegerNumber(); session.name = sessionData["name"]->asString(); session.players = sessionData["players"]->asIntegerNumber(); session.host = sessionAddress.host; session.port = sessionAddress.port; session.timestamp = g_system->getMillis(); if (_gameName == "moonbase" && sessionData.contains("map_data")) { Common::JSONObject mapData = sessionData["map_data"]->asObject(); if (mapData.contains("generator") && mapData.contains("seed") && mapData.contains("size") && mapData.contains("tileset") && mapData.contains("energy") && mapData.contains("terrain") && mapData.contains("water") && mapData.contains("data")) { session.mapGenerator = mapData["generator"]->asIntegerNumber(); session.mapSeed = mapData["seed"]->asIntegerNumber(); session.mapSize = mapData["size"]->asIntegerNumber(); session.mapTileset = mapData["tileset"]->asIntegerNumber(); session.mapEnergy = mapData["energy"]->asIntegerNumber(); session.mapTerrain = mapData["terrain"]->asIntegerNumber(); session.mapWater = mapData["water"]->asIntegerNumber(); session.encodedMap = mapData["data"]->asString(); } } _sessions.push_back(session); } _gotSessions = true; } } else if (_isHost && command == "joining_session") { // Someone is gonna attempt to join our session. Get their address and hole-punch: if (_sessionHost && root.contains("address")) { Address address = getAddressFromString(root["address"]->asString()); // By sending an UDP packet, the router will open a hole for the // destinated address, allowing someone with the same address to // communicate with us. This does not work with every router though... // // More information: https://en.wikipedia.org/wiki/UDP_hole_punching debugC(DEBUG_NETWORK, "NETWORK: Hole punching %s:%d", address.host.c_str(), address.port); _sessionHost->sendRawData(address.host, address.port, ""); } } else if (_isHost && command == "add_user_for_relay") { // For cases that peer-to-peer communication is not possible, the session server // will act as a relay to the host. if (root.contains("address")) { // To be sent back for context. Common::String address = root["address"]->asString(); if (addUser(const_cast(address.c_str()), const_cast(address.c_str()))) { _userIdToAddress[_userIdCounter] = getStringFromAddress(_sessionServerAddress); _addressToUserId[getStringFromAddress(_sessionServerAddress)] = _userIdCounter; _userIdToPeerIndex[_userIdCounter] = _sessionServerPeer; _isRelayingGame = true; Common::String resp = Common::String::format( "{\"cmd\":\"add_user_resp\",\"game\":\"%s\",\"version\":\"%s\",\"address\":\"%s\",\"id\":%d}", _gameName.c_str(), _gameVersion.c_str(), address.c_str(), _userIdCounter); _sessionHost->send(resp.c_str(), _sessionServerPeer); } } } else if (!_isHost && command == "add_user_resp") { if (root.contains("id") && _myUserId == -1) { _myUserId = root["id"]->asIntegerNumber(); // We are now relaying data to the session server, // set the sessionServerHost as the sessionHost. _isRelayingGame = true; _sessionHost = _sessionServerHost; _sessionServerHost = nullptr; } } else if (_isHost && command == "remove_user") { // Relay user wants their removal (if they haven't been removed already). if (root.contains("id")) { int userId = root["id"]->asIntegerNumber(); if (_userIdToName.contains(userId)) { if (_userIdToPeerIndex[userId] == _sessionServerPeer) { debugC(DEBUG_NETWORK, "Removing relay user %d", userId); destroyPlayer(userId); } else { warning("NETWORK: Attempt to remove non-relay user: %d", userId); } } } } else if (command == "game") { // Received relayed data. if (_isHost) handleGameDataHost(json, _sessionServerPeer); else handleGameData(json, _sessionServerPeer); } } } bool Net::serviceBroadcast() { if (!_broadcastSocket) return false; if (!_broadcastSocket->receive()) return false; handleBroadcastData(_broadcastSocket->getData(), _broadcastSocket->getHost(), _broadcastSocket->getPort()); return true; } void Net::handleBroadcastData(Common::String data, Common::String host, int port) { debugC(DEBUG_NETWORK, "NETWORK: Received data from broadcast socket. Source: %s:%d Data: %s", host.c_str(), port, data.c_str()); Common::JSONValue *json = Common::JSON::parse(data); if (!json) { // Just about anything could come from the broadcast address, so do not warn. debugC(DEBUG_NETWORK, "NETWORK: Not a JSON string, ignoring."); return; } if (!json->isObject()){ warning("NETWORK: Received non JSON object from broadcast socket: \"%s\"", data.c_str()); return; } Common::JSONObject root = json->asObject(); if (root.contains("cmd") && root["cmd"]->isString()) { Common::String command = root["cmd"]->asString(); if (command == "get_session") { // Session query. if (_sessionHost) { Common::String resp = Common::String::format( "{\"cmd\":\"session_resp\",\"game\":\"%s\",\"version\":\"%s\",\"id\":%d,\"name\":\"%s\",\"players\":%d,\"generated_map\":%s}", _gameName.c_str(), _gameVersion.c_str(), _sessionId, _sessionName.c_str(), getTotalPlayers(), _mapGenerator > 0 ? "true" : "false"); // Send this through the session host instead of the broadcast socket // because that will send the correct port to connect to. // They'll still receive it though, that's the power of connection-less sockets. _sessionHost->sendRawData(host, port, resp.c_str()); } } else if (command == "session_resp") { if (!_sessionHost && root.contains("game") && root.contains("version") && root.contains("id") && root.contains("name") && root.contains("players")) { Common::String game = root["game"]->asString(); Common::String version = root["version"]->asString(); int sessionId = root["id"]->asIntegerNumber(); Common::String name = root["name"]->asString(); int players = root["players"]->asIntegerNumber(); if (game != _gameName || version != _gameVersion) // Game/Version mismatch. return; if (players < 1 || players > _maxPlayers) // This session is either full or not finished initalizing (adding the host player itself) return; // Check if the session of the game ID (from the internet session server) exists. // if so, update it as a local session and swap the internet-based address to local. for (auto &i : _sessions) { if (i.id == sessionId && !i.local) { i.local = true; i.host = host; i.port = port; i.timestamp = g_system->getMillis(); i.players = players; return; } } // Check if we already know about this session: for (auto &i : _sessions) { if (i.host == host && i.port == port) { // Yes we do, Update the timestamp and player count. i.timestamp = g_system->getMillis(); i.players = players; return; } } // If we're here, assume that we had no clue about this session, store it. Session session; session.local = true; session.host = host; session.port = port; session.name = name; session.players = players; session.timestamp = g_system->getMillis(); if (_gameName == "moonbase" && root.contains("generated_map")) { session.getGeneratedMap = root["generated_map"]->asBool(); } _sessions.push_back(session); } } } } void Net::remoteReceiveData() { uint8 messageType = _sessionHost->service(); switch (messageType) { case ENET_EVENT_TYPE_NONE: break; case ENET_EVENT_TYPE_CONNECT: { debugC(DEBUG_NETWORK, "NETWORK: New connection from %s:%d", _sessionHost->getHost().c_str(), _sessionHost->getPort()); break; } case ENET_EVENT_TYPE_DISCONNECT: { Common::String address = Common::String::format("%s:%d", _sessionHost->getHost().c_str(), _sessionHost->getPort()); int userId = -1; if (_addressToUserId.contains(address)) userId = _addressToUserId[address]; if (userId > -1) { debugC(DEBUG_NETWORK, "NETWORK: User %s (%d) has disconnected.", _userIdToName[userId].c_str(), userId); if (_isHost) destroyPlayer(userId); } else debugC(DEBUG_NETWORK, "NETWORK: Connection from %s has disconnected.", address.c_str()); if (!_isHost) { // Since we've lost connect to our host, it's safe // to shut everything down. closeProvider(); } if (_gameName == "moonbase") { // TODO: Host migration if (!_isHost && _vm->_currentRoom == 2) { _vm->displayMessage("You have been disconnected from the game host.\nNormally, host migration would take place, but ScummVM doesn't do that yet, so this game session will now end."); _vm->VAR(253) = 26; // gGameMode = GAME-OVER _vm->runScript(2104, 1, 0, 0); // leave-game } } break; } case ENET_EVENT_TYPE_RECEIVE: { Common::String host = _sessionHost->getHost(); int port = _sessionHost->getPort(); debugC(DEBUG_NETWORK, "NETWORK: Got data from %s:%d", host.c_str(), port); int peerIndex = _sessionHost->getPeerIndexFromHost(host, port); if (peerIndex == -1) { warning("NETWORK: Unable to get peer index for host %s:%d", host.c_str(), port); _sessionHost->destroyPacket(); break; } Common::String data = _sessionHost->getPacketData(); debugC(DEBUG_NETWORK, "%s", data.c_str()); if (peerIndex == _sessionServerPeer) { handleSessionServerData(data); break; } Common::JSONValue *json = Common::JSON::parse(data); if (!json) { // Just about anything could come from the broadcast address, so do not warn. warning("NETWORK: Received non-JSON string. Got: \"%s\"", data.c_str()); _sessionHost->destroyPacket(); break; } if (!json->isObject()){ warning("NETWORK: Received non JSON object from broadcast socket: \"%s\"", data.c_str()); _sessionHost->destroyPacket(); break; } Common::JSONObject root = json->asObject(); if (root.contains("cmd") && root["cmd"]->isString()) { Common::String command = root["cmd"]->asString(); if (_isHost && command == "add_user") { if (root.contains("name")) { Common::String name = root["name"]->asString(); if (getTotalPlayers() > 4) { // We are full. break; } _userIdToName[++_userIdCounter] = name; _numUsers++; if (_sessionId && _sessionServerPeer > -1) { // Update player count to session server Common::String updatePlayers = Common::String::format( "{\"cmd\":\"update_players\",\"game\":\"%s\",\"version\":\"%s\",\"players\":%d}", _gameName.c_str(), _gameVersion.c_str(), getTotalPlayers()); _sessionHost->send(updatePlayers.c_str(), _sessionServerPeer); } Common::String address = Common::String::format("%s:%d", host.c_str(), port); _userIdToAddress[_userIdCounter] = address; _addressToUserId[address] = _userIdCounter; _userIdToPeerIndex[_userIdCounter] = peerIndex; Common::String resp = Common::String::format( "{\"cmd\":\"add_user_resp\",\"id\":%d}", _userIdCounter); _sessionHost->send(resp.c_str(), peerIndex); } } else if (!_isHost && command == "add_user_resp") { if (root.contains("id") && _myUserId == -1) { _myUserId = root["id"]->asIntegerNumber(); } } else if (_isHost && command == "remove_user") { Common::String address = Common::String::format("%s:%d", host.c_str(), port); int userId = -1; userId = _addressToUserId[address]; if (userId == -1) { warning("Got remove_user but we don't know the user for address: %s", address.c_str()); break; } destroyPlayer(userId); } else if (command == "game") { if (_isHost) handleGameDataHost(json, peerIndex); else handleGameData(json, peerIndex); } else if (_isHost && command == "query_map" && _gameName == "moonbase" && _mapGenerator > 0) { // LAN connection wants generated map data Common::String resp = Common::String::format( "{\"cmd\":\"map_data\",\"generator\":%d,\"seed\":%d,\"size\":%d,\"tileset\":%d,\"energy\":%d,\"terrain\":%d,\"water\":%d,\"data\":\"%s\"}", _mapGenerator, _mapSeed, _mapSize, _mapTileset, _mapEnergy, _mapTerrain, _mapWater, _encodedMap.c_str()); _sessionHost->send(resp.c_str(), peerIndex); } else if (!_isHost && command == "map_data" && _gameName == "moonbase") { if (root.contains("generator") && root.contains("seed") && root.contains("size") && root.contains("tileset") && root.contains("energy") && root.contains("terrain") && root.contains("water") && root.contains("data")) { uint8 mapGenerator = root["generator"]->asIntegerNumber(); int mapSeed = root["seed"]->asIntegerNumber(); int mapSize = root["size"]->asIntegerNumber(); int mapTileset = root["tileset"]->asIntegerNumber(); int mapEnergy = root["energy"]->asIntegerNumber(); int mapTerrain = root["terrain"]->asIntegerNumber(); int mapWater = root["water"]->asIntegerNumber(); Common::String encodedMap = root["data"]->asString(); _vm->_moonbase->_map->generateMapWithInfo(encodedMap, mapGenerator, mapSeed, mapSize, mapTileset, mapEnergy, mapTerrain, mapWater); } } } if (_sessionHost) _sessionHost->destroyPacket(); } break; default: warning("NETWORK: Received unknown event type %d", messageType); } } void Net::doNetworkOnceAFrame(int msecs) { if (!_enet || !_sessionHost) return; remoteReceiveData(); if (_sessionServerHost) serviceSessionServer(); if (_broadcastSocket) serviceBroadcast(); if (_isHost && _hostDataQueue.size()) { if (_hostDataQueue.size() != _peerIndexQueue.size()) warning("NETWORK: Sizes of data and peer index queues does not match! Expect some wonky stuff"); Common::JSONValue *json = _hostDataQueue.pop(); int peerIndex = _peerIndexQueue.pop(); handleGameDataHost(json, peerIndex); } } void Net::handleGameData(Common::JSONValue *json, int peerIndex) { if (!_enet || !_sessionHost) return; _fromUserId = json->child("from")->asIntegerNumber(); uint type = json->child("type")->asIntegerNumber(); uint32 *params; switch (type) { case PACKETTYPE_REMOTESTARTSCRIPT: { Common::JSONArray paramsArray = json->child("data")->child("params")->asArray(); if (_gameName == "moonbase") { // Detect if the host has disconnected. if (paramsArray[0]->asIntegerNumber() == 145 && _fromUserId == 1) { if (!_isHost && _vm->_currentRoom == 2) { // TODO: Host migration _vm->displayMessage("You have been disconnected from the game host.\nNormally, host migration would take place, but ScummVM doesn't do that yet, so this game session will now end."); _vm->VAR(253) = 26; // GAME-OVER _vm->runScript(2104, 1, 0, 0); // leave-game return; } } } int datalen = paramsArray.size(); params = (uint32 *)_tmpbuffer; for (int i = 0; i < datalen; i++) { *params = paramsArray[i]->asIntegerNumber(); params++; } if (!_vm->VAR(_vm->VAR_REMOTE_START_SCRIPT)) { warning("NETWORK: VAR_REMOTE_START_SCRIPT not defined!"); return; } _vm->runScript(_vm->VAR(_vm->VAR_REMOTE_START_SCRIPT), 1, 0, (int *)_tmpbuffer); _vm->pop(); } break; case PACKETTYPE_REMOTESTARTSCRIPTRETURN: { int datalen = json->child("data")->child("params")->asArray().size(); params = (uint32 *)_tmpbuffer; for (int i = 0; i < datalen; i++) { *params = json->child("data")->child("params")->asArray()[i]->asIntegerNumber(); params++; } _vm->runScript(_vm->VAR(_vm->VAR_REMOTE_START_SCRIPT), 1, 0, (int *)_tmpbuffer); int result = _vm->pop(); Common::String res = Common::String::format("\"result\": %d, \"callid\": %d", result, (int)json->child("data")->child("callid")->asIntegerNumber()); remoteSendData(PN_SENDTYPE_INDIVIDUAL, _fromUserId, PACKETTYPE_REMOTESTARTSCRIPTRESULT, res, PN_PRIORITY_HIGH); } break; case PACKETTYPE_REMOTESTARTSCRIPTRESULT: // // Ignore it. // break; case PACKETTYPE_REMOTESENDSCUMMARRAY: { int newArray = 0; // Assume that the packet data contains a "SCUMM PACKAGE" // and unpack it into an scumm array :-) int dim1start = json->child("data")->child("dim1start")->asIntegerNumber(); int dim1end = json->child("data")->child("dim1end")->asIntegerNumber(); int dim2start = json->child("data")->child("dim2start")->asIntegerNumber(); int dim2end = json->child("data")->child("dim2end")->asIntegerNumber(); int atype = json->child("data")->child("type")->asIntegerNumber(); byte *data = _vm->defineArray(0, atype, dim2start, dim2end, dim1start, dim1end, true, &newArray); int32 size = (dim1end - dim1start + 1) * (dim2end - dim2start + 1); int32 value; for (int i = 0; i < size; i++) { value = json->child("data")->child("data")->asArray()[i]->asIntegerNumber(); switch (atype) { case ScummEngine_v90he::kByteArray: case ScummEngine_v90he::kStringArray: data[i] = value; break; case ScummEngine_v90he::kIntArray: WRITE_LE_UINT16(data + i * 2, value); break; case ScummEngine_v90he::kDwordArray: WRITE_LE_UINT32(data + i * 4, value); break; default: error("Net::remoteReceiveData(): Unknown array type %d", atype); } } memset(_tmpbuffer, 0, 25 * 4); WRITE_UINT32(_tmpbuffer, newArray); // Quick start the script (1st param is the new array) _vm->runScript(_vm->VAR(_vm->VAR_NETWORK_RECEIVE_ARRAY_SCRIPT), 1, 0, (int *)_tmpbuffer); } break; default: warning("NETWORK: Received unknown network command %d", type); } } void Net::handleGameDataHost(Common::JSONValue *json, int peerIndex) { int from = json->child("from")->asIntegerNumber(); int to = json->child("to")->asIntegerNumber(); int toparam = json->child("toparam")->asIntegerNumber(); bool reliable = json->child("reliable")->asBool(); switch (to) { case PN_SENDTYPE_INDIVIDUAL: { if (toparam == _myUserId) { // It's for us, handle it. handleGameData(json, peerIndex); return; } // It's for someone else, transfer it. if (!_userIdToName.contains(toparam)) { warning("NETWORK: Got individual message for %d, but we don't know this person! Ignoring...", toparam); return; } debugC(DEBUG_NETWORK, "NETWORK: Transferring message to %s (%d), peerIndex: %d", _userIdToName[toparam].c_str(), toparam, _userIdToPeerIndex[toparam]); Common::String str = Common::JSON::stringify(json); _sessionHost->send(str.c_str(), _userIdToPeerIndex[toparam], 0, reliable); } break; case PN_SENDTYPE_GROUP: warning("STUB: PN_SENDTYPE_GROUP"); break; case PN_SENDTYPE_HOST: { // It's for us, handle it. handleGameData(json, peerIndex); } break; case PN_SENDTYPE_ALL: case PN_SENDTYPE_ALL_RELIABLE: case PN_SENDTYPE_ALL_RELIABLE_TIMED: { // It's for all of us, including the host. // Don't handle data if we're shutting down, or the game will crash. if (!_isShuttingDown && from != _myUserId) handleGameData(json, peerIndex); Common::String str = Common::JSON::stringify(json); bool sentToSessionServer = false; for (int i = 0; i < _numUsers; i++) { if (i != _userIdToPeerIndex[from]) { if (i == _sessionServerPeer) { // If we are relaying game data, make sure that the data only get sent // to the session server only once. if (_isRelayingGame && !sentToSessionServer) { _sessionHost->send(str.c_str(), _sessionServerPeer, 0, reliable); sentToSessionServer = true; } } else _sessionHost->send(str.c_str(), i, 0, reliable); } } } break; default: warning("NETWORK: Unknown data type: %d", to); } } } // End of namespace Scumm