tibia-rme/source/live_peer.cpp
Eduardo Dantas 6d501a5ad2
feat: support reading assets from client 11 or higher (#70)
feat: migrate Remeres to 12.x assets system and modernized item handling

• Migrated old Remeres .dat/.spr system to new 12.x client /assets/ appearance & sprite format
• Removed OTB dependency and outdated client version structures
• Integrated Protobuf-based sprite reader (credit: @nekiro)
• Major refactor on graphics.cpp structure and brush icon handling
• Updated brushes and tags system
• Fixed top order drawing bugs and missing brushes icons
• Fixed outfit color paint and correct offset handling for non-32x32 sprites
• Re-enabled map saving and loading
• Improved creature sprite rendering and brush previews
• Added new item properties window with toggle for legacy/new layout

Co-authored-by: Marcos <66353315+marcosvf132@users.noreply.github.com>
Co-authored-by: Pedro Henrique Alves Cruz <phac@cin.ufpe.br>
2025-05-10 16:38:45 -03:00

310 lines
8.4 KiB
C++

//////////////////////////////////////////////////////////////////////
// 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 <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////
#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, 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 std::error_code &error) {
if (error == asio::error::eof || error == asio::error::connection_reset) {
logMessage(wxString() + getHostName() + ": disconnected.");
close();
return true;
} else if (error == 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;
asio::async_read(socket, asio::buffer(readMessage.buffer, 4), [this](const std::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<uint32_t>());
}
});
}
void LivePeer::receive(uint32_t packetSize) {
readMessage.buffer.resize(readMessage.position + packetSize);
asio::async_read(socket, asio::buffer(&readMessage.buffer[readMessage.position], packetSize), [this](const std::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);
asio::async_write(socket, asio::buffer(message.buffer, message.size + 4), [this](const std::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<uint8_t>();
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<uint8_t>();
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<uint32_t>();
if (rmeVersion != __RME_VERSION_ID__) {
NetworkMessage outMessage;
outMessage.write<uint8_t>(PACKET_KICK);
outMessage.write<std::string>("Wrong editor version.");
send(outMessage);
close();
return;
}
uint32_t netVersion = message.read<uint32_t>();
if (netVersion != __LIVE_NET_VERSION__) {
NetworkMessage outMessage;
outMessage.write<uint8_t>(PACKET_KICK);
outMessage.write<std::string>("Wrong protocol version.");
send(outMessage);
close();
return;
}
uint32_t clientVersion = message.read<uint32_t>();
std::string nickname = message.read<std::string>();
std::string password = message.read<std::string>();
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;
outMessage.write<uint8_t>(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<uint8_t>(PACKET_KICK);
outMessage.write<std::string>("Server is full.");
send(outMessage);
close();
return;
}
server->updateClientList();
// Let's reply
NetworkMessage outMessage;
outMessage.write<uint8_t>(PACKET_HELLO_FROM_SERVER);
const Map &map = server->getEditor()->getMap();
outMessage.write<std::string>(map.getName());
outMessage.write<uint16_t>(map.getWidth());
outMessage.write<uint16_t>(map.getHeight());
send(outMessage);
}
void LivePeer::parseNodeRequest(NetworkMessage &message) {
Map &map = server->getEditor()->getMap();
for (uint32_t nodes = message.read<uint32_t>(); nodes != 0; --nodes) {
uint32_t ind = message.read<uint32_t>();
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<std::string>();
mapReader.assign(reinterpret_cast<const uint8_t*>(data.c_str() - 1), data.size());
BinaryNode* rootNode = mapReader.getRootNode();
BinaryNode* tileNode = rootNode->getChild();
NetworkedAction* action = static_cast<NetworkedAction*>(editor.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.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<std::string>();
server->broadcastChat(name, wxstr(chatMessage));
}