//////////////////////////////////////////////////////////////////////
// This file is part of Remere's Map Editor
//////////////////////////////////////////////////////////////////////
// Remere's Map Editor 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.
//
// Remere's Map Editor 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 "main.h"
#include "live_peer.h"
#include "live_server.h"
#include "live_tab.h"
#include "live_action.h"
#include "editor.h"
LivePeer::LivePeer(LiveServer* server, boost::asio::ip::tcp::socket socket) :
LiveSocket(),
readMessage(), server(server), socket(std::move(socket)), color(), id(0), clientId(0), connected(false) {
ASSERT(server != nullptr);
}
LivePeer::~LivePeer() {
if (socket.is_open()) {
socket.close();
}
}
void LivePeer::close() {
server->removeClient(id);
}
bool LivePeer::handleError(const boost::system::error_code& error) {
if (error == boost::asio::error::eof || error == boost::asio::error::connection_reset) {
logMessage(wxString() + getHostName() + ": disconnected.");
close();
return true;
} else if (error == boost::asio::error::connection_aborted) {
logMessage(name + " have left the server.");
return true;
}
return false;
}
std::string LivePeer::getHostName() const {
return socket.remote_endpoint().address().to_string();
}
void LivePeer::receiveHeader() {
readMessage.position = 0;
boost::asio::async_read(socket, boost::asio::buffer(readMessage.buffer, 4), [this](const boost::system::error_code& error, size_t bytesReceived) -> void {
if (error) {
if (!handleError(error)) {
logMessage(wxString() + getHostName() + ": " + error.message());
}
} else if (bytesReceived < 4) {
logMessage(wxString() + getHostName() + ": Could not receive header[size: " + std::to_string(bytesReceived) + "], disconnecting client.");
} else {
receive(readMessage.read());
}
});
}
void LivePeer::receive(uint32_t packetSize) {
readMessage.buffer.resize(readMessage.position + packetSize);
boost::asio::async_read(socket, boost::asio::buffer(&readMessage.buffer[readMessage.position], packetSize), [this](const boost::system::error_code& error, size_t bytesReceived) -> void {
if (error) {
if (!handleError(error)) {
logMessage(wxString() + getHostName() + ": " + error.message());
}
} else if (bytesReceived < readMessage.buffer.size() - 4) {
logMessage(wxString() + getHostName() + ": Could not receive packet[size: " + std::to_string(bytesReceived) + "], disconnecting client.");
} else {
wxTheApp->CallAfter([this]() {
if (connected) {
parseEditorPacket(std::move(readMessage));
} else {
parseLoginPacket(std::move(readMessage));
}
receiveHeader();
});
}
});
}
void LivePeer::send(NetworkMessage& message) {
memcpy(&message.buffer[0], &message.size, 4);
boost::asio::async_write(socket, boost::asio::buffer(message.buffer, message.size + 4), [this](const boost::system::error_code& error, size_t bytesTransferred) -> void {
if (error) {
logMessage(wxString() + getHostName() + ": " + error.message());
}
});
}
void LivePeer::parseLoginPacket(NetworkMessage message) {
uint8_t packetType;
while (message.position < message.buffer.size()) {
packetType = message.read();
switch (packetType) {
case PACKET_HELLO_FROM_CLIENT:
parseHello(message);
break;
case PACKET_READY_CLIENT:
parseReady(message);
break;
default: {
log->Message("Invalid login packet receieved, connection severed.");
close();
break;
}
}
}
}
void LivePeer::parseEditorPacket(NetworkMessage message) {
uint8_t packetType;
while (message.position < message.buffer.size()) {
packetType = message.read();
switch (packetType) {
case PACKET_REQUEST_NODES:
parseNodeRequest(message);
break;
case PACKET_CHANGE_LIST:
parseReceiveChanges(message);
break;
case PACKET_ADD_HOUSE:
parseAddHouse(message);
break;
case PACKET_EDIT_HOUSE:
parseEditHouse(message);
break;
case PACKET_REMOVE_HOUSE:
parseRemoveHouse(message);
break;
case PACKET_CLIENT_UPDATE_CURSOR:
parseCursorUpdate(message);
break;
case PACKET_CLIENT_TALK:
parseChatMessage(message);
break;
default: {
log->Message("Invalid editor packet receieved, connection severed.");
close();
break;
}
}
}
}
void LivePeer::parseHello(NetworkMessage& message) {
if (connected) {
close();
return;
}
uint32_t rmeVersion = message.read();
if (rmeVersion != __RME_VERSION_ID__) {
NetworkMessage outMessage;
outMessage.write(PACKET_KICK);
outMessage.write("Wrong editor version.");
send(outMessage);
close();
return;
}
uint32_t netVersion = message.read();
if (netVersion != __LIVE_NET_VERSION__) {
NetworkMessage outMessage;
outMessage.write(PACKET_KICK);
outMessage.write("Wrong protocol version.");
send(outMessage);
close();
return;
}
uint32_t clientVersion = message.read();
std::string nickname = message.read();
std::string password = message.read();
if (server->getPassword() != wxString(password.c_str(), wxConvUTF8)) {
log->Message("Client tried to connect, but used the wrong password, connection refused.");
close();
return;
}
name = wxString(nickname.c_str(), wxConvUTF8);
log->Message(name + " (" + getHostName() + ") connected.");
NetworkMessage outMessage;
if (static_cast(clientVersion) != g_gui.GetCurrentVersionID()) {
outMessage.write(PACKET_CHANGE_CLIENT_VERSION);
outMessage.write(g_gui.GetCurrentVersionID());
} else {
outMessage.write(PACKET_ACCEPTED_CLIENT);
}
send(outMessage);
}
void LivePeer::parseReady(NetworkMessage& message) {
if (connected) {
close();
return;
}
connected = true;
// Find free client id
clientId = server->getFreeClientId();
if (clientId == 0) {
NetworkMessage outMessage;
outMessage.write(PACKET_KICK);
outMessage.write("Server is full.");
send(outMessage);
close();
return;
}
server->updateClientList();
// Let's reply
NetworkMessage outMessage;
outMessage.write(PACKET_HELLO_FROM_SERVER);
Map& map = server->getEditor()->map;
outMessage.write(map.getName());
outMessage.write(map.getWidth());
outMessage.write(map.getHeight());
send(outMessage);
}
void LivePeer::parseNodeRequest(NetworkMessage& message) {
Map& map = server->getEditor()->map;
for (uint32_t nodes = message.read(); nodes != 0; --nodes) {
uint32_t ind = message.read();
int32_t ndx = ind >> 18;
int32_t ndy = (ind >> 4) & 0x3FFF;
bool underground = ind & 1;
QTreeNode* node = map.createLeaf(ndx * 4, ndy * 4);
if (node) {
sendNode(clientId, node, ndx, ndy, underground ? 0xFF00 : 0x00FF);
}
}
}
void LivePeer::parseReceiveChanges(NetworkMessage& message) {
Editor& editor = *server->getEditor();
// -1 on address since we skip the first START_NODE when sending
const std::string& data = message.read();
mapReader.assign(reinterpret_cast(data.c_str() - 1), data.size());
BinaryNode* rootNode = mapReader.getRootNode();
BinaryNode* tileNode = rootNode->getChild();
NetworkedAction* action = static_cast(editor.actionQueue->createAction(ACTION_REMOTE));
action->owner = clientId;
if (tileNode) {
do {
Tile* tile = readTile(tileNode, editor, nullptr);
if (tile) {
action->addChange(newd Change(tile));
}
} while (tileNode->advance());
}
mapReader.close();
editor.actionQueue->addAction(action);
g_gui.RefreshView();
g_gui.UpdateMinimap();
}
void LivePeer::parseAddHouse(NetworkMessage& message) {
}
void LivePeer::parseEditHouse(NetworkMessage& message) {
}
void LivePeer::parseRemoveHouse(NetworkMessage& message) {
}
void LivePeer::parseCursorUpdate(NetworkMessage& message) {
LiveCursor cursor = readCursor(message);
cursor.id = clientId;
if (cursor.color != color) {
setUsedColor(cursor.color);
server->updateClientList();
}
server->broadcastCursor(cursor);
g_gui.RefreshView();
}
void LivePeer::parseChatMessage(NetworkMessage& message) {
const std::string& chatMessage = message.read();
server->broadcastChat(name, wxstr(chatMessage));
}