/* * The Mana Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2012 The Mana Developers * * This file is part of The Mana Client. * * 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 2 of the License, or * 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 "net/tmwa/network.h" #include "log.h" #include "net/tmwa/messagein.h" #include "net/tmwa/protocol.h" #include "utils/gettext.h" #include "utils/stringutils.h" #include #include /** Warning: buffers and other variables are shared, so there can be only one connection active at a time */ namespace TmwAthena { struct PacketInfo { uint16_t id; uint16_t length; const char *name; }; // indicator for a variable-length packet const uint16_t VAR = 1; static const PacketInfo packet_infos[] = { // login server messages { SMSG_UPDATE_HOST, VAR, "SMSG_UPDATE_HOST" }, { CMSG_LOGIN_REGISTER, 55, "CMSG_LOGIN_REGISTER" }, { SMSG_LOGIN_DATA, VAR, "SMSG_LOGIN_DATA" }, { SMSG_LOGIN_ERROR, 23, "SMSG_LOGIN_ERROR" }, // char server messages { CMSG_CHAR_PASSWORD_CHANGE, 50, "CMSG_CHAR_PASSWORD_CHANGE" }, { SMSG_CHAR_PASSWORD_RESPONSE, 3, "SMSG_CHAR_PASSWORD_RESPONSE" }, { CMSG_CHAR_SERVER_CONNECT, 17, "CMSG_CHAR_SERVER_CONNECT" }, { CMSG_CHAR_SELECT, 3, "CMSG_CHAR_SELECT" }, { CMSG_CHAR_CREATE, 37, "CMSG_CHAR_CREATE" }, { CMSG_CHAR_DELETE, 46, "CMSG_CHAR_DELETE" }, { SMSG_CHAR_LOGIN, VAR, "SMSG_CHAR_LOGIN" }, { SMSG_CHAR_LOGIN_ERROR, 3, "SMSG_CHAR_LOGIN_ERROR" }, { SMSG_CHAR_CREATE_SUCCEEDED, 108, "SMSG_CHAR_CREATE_SUCCEEDED" }, { SMSG_CHAR_CREATE_FAILED, 3, "SMSG_CHAR_CREATE_FAILED" }, { SMSG_CHAR_DELETE_SUCCEEDED, 2, "SMSG_CHAR_DELETE_SUCCEEDED" }, { SMSG_CHAR_DELETE_FAILED, 3, "SMSG_CHAR_DELETE_FAILED" }, { SMSG_CHAR_MAP_INFO, 28, "SMSG_CHAR_MAP_INFO" }, // map server messages { CMSG_MAP_SERVER_CONNECT, 19, "CMSG_MAP_SERVER_CONNECT" }, { SMSG_MAP_LOGIN_SUCCESS, 11, "SMSG_MAP_LOGIN_SUCCESS" }, { SMSG_BEING_VISIBLE, 54, "SMSG_BEING_VISIBLE" }, { SMSG_BEING_MOVE, 60, "SMSG_BEING_MOVE" }, { SMSG_BEING_SPAWN, 41, "SMSG_BEING_SPAWN" }, { CMSG_MAP_LOADED, 2, "CMSG_MAP_LOADED" }, { CMSG_MAP_PING, 6, "CMSG_MAP_PING" }, { SMSG_SERVER_PING, 6, "SMSG_SERVER_PING" }, { SMSG_BEING_REMOVE, 7, "SMSG_BEING_REMOVE" }, { CMSG_PLAYER_CHANGE_DEST, 5, "CMSG_PLAYER_CHANGE_DEST" }, { SMSG_WALK_RESPONSE, 12, "SMSG_WALK_RESPONSE" }, { SMSG_PLAYER_STOP, 10, "SMSG_PLAYER_STOP" }, { CMSG_PLAYER_CHANGE_ACT, 7, "CMSG_PLAYER_CHANGE_ACT" }, { SMSG_BEING_ACTION, 29, "SMSG_BEING_ACTION" }, { CMSG_CHAT_MESSAGE, VAR, "CMSG_CHAT_MESSAGE" }, { SMSG_BEING_CHAT, VAR, "SMSG_BEING_CHAT" }, { SMSG_PLAYER_CHAT, VAR, "SMSG_PLAYER_CHAT" }, { CMSG_NPC_TALK, 7, "CMSG_NPC_TALK" }, { SMSG_PLAYER_WARP, 22, "SMSG_PLAYER_WARP" }, { SMSG_CHANGE_MAP_SERVER, 28, "SMSG_CHANGE_MAP_SERVER" }, { CMSG_NAME_REQUEST, 6, "CMSG_NAME_REQUEST" }, { SMSG_BEING_NAME_RESPONSE, 30, "SMSG_BEING_NAME_RESPONSE" }, { CMSG_CHAT_WHISPER, VAR, "CMSG_CHAT_WHISPER" }, { SMSG_WHISPER, VAR, "SMSG_WHISPER" }, { SMSG_WHISPER_RESPONSE, 3, "SMSG_WHISPER_RESPONSE" }, { SMSG_GM_CHAT, VAR, "SMSG_GM_CHAT" }, { CMSG_PLAYER_CHANGE_DIR, 5, "CMSG_PLAYER_CHANGE_DIR" }, { SMSG_BEING_CHANGE_DIRECTION, 9, "SMSG_BEING_CHANGE_DIRECTION" }, { SMSG_ITEM_VISIBLE, 17, "SMSG_ITEM_VISIBLE" }, { SMSG_ITEM_DROPPED, 17, "SMSG_ITEM_DROPPED" }, { CMSG_ITEM_PICKUP, 6, "CMSG_ITEM_PICKUP" }, { SMSG_PLAYER_INVENTORY_ADD, 23, "SMSG_PLAYER_INVENTORY_ADD" }, { SMSG_ITEM_REMOVE, 6, "SMSG_ITEM_REMOVE" }, { CMSG_PLAYER_INVENTORY_DROP, 6, "CMSG_PLAYER_INVENTORY_DROP" }, { SMSG_PLAYER_EQUIPMENT, VAR, "SMSG_PLAYER_EQUIPMENT" }, { SMSG_PLAYER_STORAGE_EQUIP, VAR, "SMSG_PLAYER_STORAGE_EQUIP" }, { CMSG_PLAYER_INVENTORY_USE, 8, "CMSG_PLAYER_INVENTORY_USE" }, { SMSG_ITEM_USE_RESPONSE, 7, "SMSG_ITEM_USE_RESPONSE" }, { CMSG_PLAYER_EQUIP, 6, "CMSG_PLAYER_EQUIP" }, { SMSG_PLAYER_EQUIP, 7, "SMSG_PLAYER_EQUIP" }, { CMSG_PLAYER_UNEQUIP, 4, "CMSG_PLAYER_UNEQUIP" }, { SMSG_PLAYER_UNEQUIP, 7, "SMSG_PLAYER_UNEQUIP" }, { SMSG_PLAYER_INVENTORY_REMOVE, 6, "SMSG_PLAYER_INVENTORY_REMOVE" }, { SMSG_PLAYER_STAT_UPDATE_1, 8, "SMSG_PLAYER_STAT_UPDATE_1" }, { SMSG_PLAYER_STAT_UPDATE_2, 8, "SMSG_PLAYER_STAT_UPDATE_2" }, { CMSG_PLAYER_REBOOT, 3, "CMSG_PLAYER_REBOOT" }, { SMSG_CHAR_SWITCH_RESPONSE, 3, "SMSG_CHAR_SWITCH_RESPONSE" }, { SMSG_NPC_MESSAGE, VAR, "SMSG_NPC_MESSAGE" }, { SMSG_NPC_NEXT, 6, "SMSG_NPC_NEXT" }, { SMSG_NPC_CLOSE, 6, "SMSG_NPC_CLOSE" }, { SMSG_NPC_CHOICE, VAR, "SMSG_NPC_CHOICE" }, { CMSG_NPC_LIST_CHOICE, 7, "CMSG_NPC_LIST_CHOICE" }, { CMSG_NPC_NEXT_REQUEST, 6, "CMSG_NPC_NEXT_REQUEST" }, { CMSG_STAT_UPDATE_REQUEST, 5, "CMSG_STAT_UPDATE_REQUEST" }, { SMSG_PLAYER_STAT_UPDATE_4, 6, "SMSG_PLAYER_STAT_UPDATE_4" }, { SMSG_PLAYER_STAT_UPDATE_5, 44, "SMSG_PLAYER_STAT_UPDATE_5" }, { SMSG_PLAYER_STAT_UPDATE_6, 5, "SMSG_PLAYER_STAT_UPDATE_6" }, { CMSG_PLAYER_EMOTE, 3, "CMSG_PLAYER_EMOTE" }, { SMSG_BEING_EMOTION, 7, "SMSG_BEING_EMOTION" }, { SMSG_NPC_BUY_SELL_CHOICE, 6, "SMSG_NPC_BUY_SELL_CHOICE" }, { CMSG_NPC_BUY_SELL_REQUEST, 7, "CMSG_NPC_BUY_SELL_REQUEST" }, { SMSG_NPC_BUY, VAR, "SMSG_NPC_BUY" }, { SMSG_NPC_SELL, VAR, "SMSG_NPC_SELL" }, { CMSG_NPC_BUY_REQUEST, VAR, "CMSG_NPC_BUY_REQUEST" }, { CMSG_NPC_SELL_REQUEST, VAR, "CMSG_NPC_SELL_REQUEST" }, { SMSG_NPC_BUY_RESPONSE, 3, "SMSG_NPC_BUY_RESPONSE" }, { SMSG_NPC_SELL_RESPONSE, 3, "SMSG_NPC_SELL_RESPONSE" }, { SMSG_ADMIN_KICK_ACK, 6, "SMSG_ADMIN_KICK_ACK" }, { CMSG_TRADE_REQUEST, 6, "CMSG_TRADE_REQUEST" }, { SMSG_TRADE_REQUEST, 26, "SMSG_TRADE_REQUEST" }, { CMSG_TRADE_RESPONSE, 3, "CMSG_TRADE_RESPONSE" }, { SMSG_TRADE_RESPONSE, 3, "SMSG_TRADE_RESPONSE" }, { CMSG_TRADE_ITEM_ADD_REQUEST, 8, "CMSG_TRADE_ITEM_ADD_REQUEST" }, { SMSG_TRADE_ITEM_ADD, 19, "SMSG_TRADE_ITEM_ADD" }, { CMSG_TRADE_ADD_COMPLETE, 2, "CMSG_TRADE_ADD_COMPLETE" }, { SMSG_TRADE_OK, 3, "SMSG_TRADE_OK" }, { CMSG_TRADE_CANCEL_REQUEST, 2, "CMSG_TRADE_CANCEL_REQUEST" }, { SMSG_TRADE_CANCEL, 2, "SMSG_TRADE_CANCEL" }, { CMSG_TRADE_OK, 2, "CMSG_TRADE_OK" }, { SMSG_TRADE_COMPLETE, 3, "SMSG_TRADE_COMPLETE" }, { SMSG_PLAYER_STORAGE_STATUS, 6, "SMSG_PLAYER_STORAGE_STATUS" }, { CMSG_MOVE_TO_STORAGE, 8, "CMSG_MOVE_TO_STORAGE" }, { SMSG_PLAYER_STORAGE_ADD, 21, "SMSG_PLAYER_STORAGE_ADD" }, { CMSG_MOVE_FROM_STORAGE, 8, "CMSG_MOVE_FROM_STORAGE" }, { SMSG_PLAYER_STORAGE_REMOVE, 8, "SMSG_PLAYER_STORAGE_REMOVE" }, { CMSG_CLOSE_STORAGE, 2, "CMSG_CLOSE_STORAGE" }, { SMSG_PLAYER_STORAGE_CLOSE, 2, "SMSG_PLAYER_STORAGE_CLOSE" }, { CMSG_PARTY_CREATE, 26, "CMSG_PARTY_CREATE" }, { SMSG_PARTY_CREATE, 3, "SMSG_PARTY_CREATE" }, { SMSG_PARTY_INFO, VAR, "SMSG_PARTY_INFO" }, { CMSG_PARTY_INVITE, 6, "CMSG_PARTY_INVITE" }, { SMSG_PARTY_INVITE_RESPONSE, 27, "SMSG_PARTY_INVITE_RESPONSE" }, { SMSG_PARTY_INVITED, 30, "SMSG_PARTY_INVITED" }, { CMSG_PARTY_INVITED, 10, "CMSG_PARTY_INVITED" }, { CMSG_PARTY_LEAVE, 2, "CMSG_PARTY_LEAVE" }, { SMSG_PARTY_SETTINGS, 6, "SMSG_PARTY_SETTINGS" }, { CMSG_PARTY_SETTINGS, 6, "CMSG_PARTY_SETTINGS" }, { CMSG_PARTY_KICK, 30, "CMSG_PARTY_KICK" }, { SMSG_PARTY_LEAVE, 31, "SMSG_PARTY_LEAVE" }, { SMSG_PARTY_UPDATE_HP, 10, "SMSG_PARTY_UPDATE_HP" }, { SMSG_PARTY_UPDATE_COORDS, 10, "SMSG_PARTY_UPDATE_COORDS" }, { CMSG_PARTY_MESSAGE, VAR, "CMSG_PARTY_MESSAGE" }, { SMSG_PARTY_MESSAGE, VAR, "SMSG_PARTY_MESSAGE" }, { SMSG_PLAYER_SKILL_UP, 11, "SMSG_PLAYER_SKILL_UP" }, { SMSG_PLAYER_SKILLS, VAR, "SMSG_PLAYER_SKILLS" }, { SMSG_SKILL_FAILED, 10, "SMSG_SKILL_FAILED" }, { CMSG_SKILL_LEVELUP_REQUEST, 4, "CMSG_SKILL_LEVELUP_REQUEST" }, { CMSG_PLAYER_STOP_ATTACK, 2, "CMSG_PLAYER_STOP_ATTACK" }, { SMSG_PLAYER_STATUS_CHANGE, 13, "SMSG_PLAYER_STATUS_CHANGE" }, { SMSG_PLAYER_MOVE_TO_ATTACK, 16, "SMSG_PLAYER_MOVE_TO_ATTACK" }, { SMSG_PLAYER_ATTACK_RANGE, 4, "SMSG_PLAYER_ATTACK_RANGE" }, { SMSG_PLAYER_ARROW_MESSAGE, 4, "SMSG_PLAYER_ARROW_MESSAGE" }, { SMSG_PLAYER_ARROW_EQUIP, 4, "SMSG_PLAYER_ARROW_EQUIP" }, { SMSG_PLAYER_STAT_UPDATE_3, 14, "SMSG_PLAYER_STAT_UPDATE_3" }, { SMSG_NPC_INT_INPUT, 6, "SMSG_NPC_INT_INPUT" }, { CMSG_NPC_INT_RESPONSE, 10, "CMSG_NPC_INT_RESPONSE" }, { CMSG_NPC_CLOSE, 6, "CMSG_NPC_CLOSE" }, { SMSG_BEING_RESURRECT, 8, "SMSG_BEING_RESURRECT" }, { CMSG_CLIENT_QUIT, 4, "CMSG_CLIENT_QUIT" }, { SMSG_MAP_QUIT_RESPONSE, 4, "SMSG_MAP_QUIT_RESPONSE" }, { SMSG_PLAYER_GUILD_PARTY_INFO, 102, "SMSG_PLAYER_GUILD_PARTY_INFO" }, { SMSG_BEING_STATUS_CHANGE, 9, "SMSG_BEING_STATUS_CHANGE" }, { SMSG_PVP_MAP_MODE, 4, "SMSG_PVP_MAP_MODE" }, { SMSG_PVP_SET, 14, "SMSG_PVP_SET" }, { SMSG_BEING_SELFEFFECT, 10, "SMSG_BEING_SELFEFFECT" }, { SMSG_TRADE_ITEM_ADD_RESPONSE, 7, "SMSG_TRADE_ITEM_ADD_RESPONSE" }, { SMSG_PLAYER_INVENTORY_USE, 13, "SMSG_PLAYER_INVENTORY_USE" }, { SMSG_NPC_STR_INPUT, 6, "SMSG_NPC_STR_INPUT" }, { CMSG_NPC_STR_RESPONSE, VAR, "CMSG_NPC_STR_RESPONSE" }, { SMSG_BEING_CHANGE_LOOKS2, 11, "SMSG_BEING_CHANGE_LOOKS2" }, { SMSG_PLAYER_UPDATE_1, 54, "SMSG_PLAYER_UPDATE_1" }, { SMSG_PLAYER_UPDATE_2, 53, "SMSG_PLAYER_UPDATE_2" }, { SMSG_PLAYER_MOVE, 60, "SMSG_PLAYER_MOVE" }, { SMSG_SKILL_DAMAGE, 33, "SMSG_SKILL_DAMAGE" }, { SMSG_PLAYER_INVENTORY, VAR, "SMSG_PLAYER_INVENTORY" }, { SMSG_PLAYER_STORAGE_ITEMS, VAR, "SMSG_PLAYER_STORAGE_ITEMS" }, { SMSG_BEING_IP_RESPONSE, 10, "SMSG_BEING_IP_RESPONSE" }, { CMSG_ONLINE_LIST, 2, "CMSG_ONLINE_LIST" }, { SMSG_ONLINE_LIST, VAR, "SMSG_ONLINE_LIST" }, { SMSG_NPC_COMMAND, 16, "SMSG_NPC_COMMAND" }, { SMSG_QUEST_SET_VAR, 8, "SMSG_QUEST_SET_VAR" }, { SMSG_QUEST_PLAYER_VARS, VAR, "SMSG_QUEST_PLAYER_VARS" }, { SMSG_BEING_MOVE3, VAR, "SMSG_BEING_MOVE3" }, { SMSG_MAP_MASK, 10, "SMSG_MAP_MASK" }, { SMSG_MAP_MUSIC, VAR, "SMSG_MAP_MUSIC" }, { SMSG_NPC_CHANGETITLE, VAR, "SMSG_NPC_CHANGETITLE" }, { SMSG_SCRIPT_MESSAGE, VAR, "SMSG_SCRIPT_MESSAGE" }, { SMSG_PLAYER_CLIENT_COMMAND, VAR, "SMSG_PLAYER_CLIENT_COMMAND" }, { SMSG_MAP_SET_TILES_TYPE, 34, "SMSG_MAP_SET_TILES_TYPE" }, { SMSG_PLAYER_HP, 10, "SMSG_PLAYER_HP" }, { SMSG_PLAYER_HP_FULL, 14, "SMSG_PLAYER_HP_FULL" }, // any server messages { SMSG_CONNECTION_PROBLEM, 3, "SMSG_CONNECTION_PROBLEM" }, { CMSG_SERVER_VERSION_REQUEST, 2, "CMSG_SERVER_VERSION_REQUEST" }, { SMSG_SERVER_VERSION_RESPONSE, 10, "SMSG_SERVER_VERSION_RESPONSE" }, { CMSG_CLIENT_DISCONNECT, 2, "CMSG_CLIENT_DISCONNECT" }, }; const unsigned int BUFFER_SIZE = 65536; int networkThread(void *data) { auto *network = static_cast(data); if (!network->realConnect()) return -1; network->receive(); return 0; } Network *Network::mInstance = nullptr; Network::Network(): mInBuffer(new char[BUFFER_SIZE]), mOutBuffer(new char[BUFFER_SIZE]) { SDLNet_Init(); mInstance = this; for (const auto &packetInfo : packet_infos) { assert(packetInfo.length != 0); mPacketInfo[packetInfo.id] = &packetInfo; } } Network::~Network() { clearHandlers(); if (mState != IDLE && mState != NET_ERROR) disconnect(); mInstance = nullptr; delete[] mInBuffer; delete[] mOutBuffer; SDLNet_Quit(); } bool Network::connect(const ServerInfo &server) { if (mState != IDLE && mState != NET_ERROR) { logger->log("Tried to connect an already connected socket!"); assert(false); return false; } if (server.hostname.empty()) { setError(_("Empty address given to Network::connect()!")); return false; } logger->log("Network::Connecting to %s:%i", server.hostname.c_str(), server.port); mServer.hostname = server.hostname; mServer.port = server.port; // Reset to sane values mOutSize = 0; mInSize = 0; mToSkip = 0; mState = CONNECTING; mWorkerThread = SDL_CreateThread(networkThread, "Network", this); if (!mWorkerThread) { setError("Unable to create network worker thread"); return false; } return true; } void Network::disconnect() { mState = IDLE; if (mWorkerThread) { SDL_WaitThread(mWorkerThread, nullptr); mWorkerThread = nullptr; } if (mSocket) { SDLNet_TCP_Close(mSocket); mSocket = nullptr; } } void Network::registerHandler(MessageHandler *handler) { for (const Uint16 *i = handler->handledMessages; *i; ++i) { mMessageHandlers[*i] = handler; } handler->setNetwork(this); } void Network::unregisterHandler(MessageHandler *handler) { for (const Uint16 *i = handler->handledMessages; *i; ++i) { mMessageHandlers.erase(*i); } handler->setNetwork(nullptr); } void Network::clearHandlers() { for (auto& [_, messageHandler] : mMessageHandlers) { messageHandler->setNetwork(nullptr); } mMessageHandlers.clear(); } void Network::dispatchMessages() { MutexLocker lock(&mMutex); while (true) { // Not even a message ID has been received if (mInSize < 2) break; const uint16_t msgId = readWord(0); auto packetInfoIt = mPacketInfo.find(msgId); if (packetInfoIt == mPacketInfo.end()) { auto error = strprintf("Unknown packet 0x%x received.", msgId); logger->error(error); break; } auto packetInfo = packetInfoIt->second; // Determine the length of the packet uint16_t len = packetInfo->length; if (len == VAR) { // We have not received the length yet if (mInSize < 4) break; len = readWord(2); if (len < 4) { auto error = strprintf("Variable length packet 0x%x has invalid length %d.", msgId, len); logger->error(error); break; } } // The message has not been fully received yet if (mInSize < len) break; MessageIn message(mInBuffer, len); // Dispatch the message to the appropriate handler auto iter = mMessageHandlers.find(msgId); if (iter != mMessageHandlers.end()) { #ifdef DEBUG logger->log("Handling %s (0x%x) of length %d", packetInfo->name, msgId, len); #endif iter->second->handleMessage(message); } else { logger->log("Unhandled %s (0x%x) of length %d", packetInfo->name, msgId, len); } skip(len); } } void Network::flush() { if (!mOutSize || mState != CONNECTED) return; int ret; MutexLocker lock(&mMutex); ret = SDLNet_TCP_Send(mSocket, mOutBuffer, mOutSize); if (ret < (int)mOutSize) { setError("Error in SDLNet_TCP_Send(): " + std::string(SDLNet_GetError())); } mOutSize = 0; } void Network::skip(int len) { MutexLocker lock(&mMutex); mToSkip += len; if (!mInSize) return; if (mInSize >= mToSkip) { mInSize -= mToSkip; memmove(mInBuffer, mInBuffer + mToSkip, mInSize); mToSkip = 0; } else { mToSkip -= mInSize; mInSize = 0; } } bool Network::realConnect() { IPaddress ipAddress; if (SDLNet_ResolveHost(&ipAddress, mServer.hostname.c_str(), mServer.port) == -1) { std::string errorMessage = _("Unable to resolve host \"") + mServer.hostname + "\""; setError(errorMessage); logger->log("SDLNet_ResolveHost: %s", errorMessage.c_str()); return false; } mState = CONNECTING; mSocket = SDLNet_TCP_Open(&ipAddress); if (!mSocket) { logger->log("Error in SDLNet_TCP_Open(): %s", SDLNet_GetError()); setError(SDLNet_GetError()); return false; } logger->log("Network::Started session with %s:%i", ipToString(ipAddress.host), ipAddress.port); mState = CONNECTED; return true; } void Network::receive() { SDLNet_SocketSet set; if (!(set = SDLNet_AllocSocketSet(1))) { setError("Error in SDLNet_AllocSocketSet(): " + std::string(SDLNet_GetError())); return; } if (SDLNet_TCP_AddSocket(set, mSocket) == -1) { setError("Error in SDLNet_AddSocket(): " + std::string(SDLNet_GetError())); } while (mState == CONNECTED) { // TODO Try to get this to block all the time while still being able // to escape the loop int numReady = SDLNet_CheckSockets(set, ((Uint32)500)); int ret; switch (numReady) { case -1: logger->log("Error: SDLNet_CheckSockets"); // FALLTHROUGH case 0: break; case 1: { // Receive data from the socket MutexLocker lock(&mMutex); ret = SDLNet_TCP_Recv(mSocket, mInBuffer + mInSize, BUFFER_SIZE - mInSize); if (!ret) { // We got disconnected mState = IDLE; logger->log("Disconnected."); } else if (ret < 0) { setError(_("Connection to server terminated. ") + std::string(SDLNet_GetError())); } else { mInSize += ret; if (mToSkip) { if (mInSize >= mToSkip) { mInSize -= mToSkip; memmove(mInBuffer, mInBuffer + mToSkip, mInSize); mToSkip = 0; } else { mToSkip -= mInSize; mInSize = 0; } } } break; } default: // more than one socket is ready.. // this should not happen since we only listen once socket. std::stringstream errorStream; errorStream << "Error in SDLNet_TCP_Recv(), " << numReady << " sockets are ready: " << SDLNet_GetError(); setError(errorStream.str()); break; } } if (SDLNet_TCP_DelSocket(set, mSocket) == -1) { logger->log("Error in SDLNet_DelSocket(): %s", SDLNet_GetError()); } SDLNet_FreeSocketSet(set); } void Network::setError(const std::string &error) { logger->log("Network error: %s", error.c_str()); mError = error; mState = NET_ERROR; } uint16_t Network::readWord(int pos) { uint16_t value; memcpy(&value, mInBuffer + pos, sizeof(uint16_t)); return SDL_SwapLE16(value); } } // namespace TmwAthena