/* 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