From 2c0b453c688b08e5b94d248b45af698e4daf55f3 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Fri, 27 Mar 2026 17:31:23 +0000 Subject: [PATCH 1/8] Core: add operator== and hash to IPP --- src/common/ipp.cpp | 5 +++++ src/common/ipp.h | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/common/ipp.cpp b/src/common/ipp.cpp index 4a75c843c41..2e761d076b4 100644 --- a/src/common/ipp.cpp +++ b/src/common/ipp.cpp @@ -110,3 +110,8 @@ auto IPP::operator<(const IPP& other) const -> bool { return ip_ < other.ip_ || (ip_ == other.ip_ && port_ < other.port_); } + +auto IPP::operator==(const IPP& other) const -> bool +{ + return ip_ == other.ip_ && port_ == other.port_; +} diff --git a/src/common/ipp.h b/src/common/ipp.h index e80e53efe5a..5ff3198bd1f 100644 --- a/src/common/ipp.h +++ b/src/common/ipp.h @@ -82,6 +82,7 @@ class IPP final // auto operator<(const IPP& other) const -> bool; + auto operator==(const IPP& other) const -> bool; private: // IP is always stored and used in network byte order. @@ -92,3 +93,17 @@ class IPP final }; static_assert(std::is_standard_layout_v, "IPP must be standard-layout"); + +namespace std +{ + +template <> +struct hash +{ + size_t operator()(const IPP& ipp) const noexcept + { + return hash{}(ipp.getRawIPP()); + } +}; + +} // namespace std From 03d2a3e1bef12ed2e0373de0b38153598c5934cf Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Fri, 27 Mar 2026 17:32:50 +0000 Subject: [PATCH 2/8] Core: Move NetworkBuffer definition --- src/common/cbasetypes.h | 4 ++++ src/map/map_constants.h | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/common/cbasetypes.h b/src/common/cbasetypes.h index f6f1d76011d..9f88b584a4b 100644 --- a/src/common/cbasetypes.h +++ b/src/common/cbasetypes.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include #include @@ -90,3 +91,6 @@ struct PtrGreater template using MinHeapPtr = std::priority_queue, PtrGreater>; + +using NetworkBuffer = std::array; +using ByteSpan = std::span; diff --git a/src/map/map_constants.h b/src/map/map_constants.h index 35ddb264ddc..ed446a188e4 100644 --- a/src/map/map_constants.h +++ b/src/map/map_constants.h @@ -71,6 +71,3 @@ static constexpr auto kIPCPumpInterval = 100ms; static constexpr auto kMaxBufferSize = 2500U; static constexpr auto kMaxPacketPerCompression = 32U; static constexpr auto kMaxPacketBacklogSize = kMaxPacketPerCompression * 6U; // If we hit this number, things are going very very badly. - -// TODO: Should this be moved to cbasetypes.h or similar? -using NetworkBuffer = std::array; From c28468d5ae5c77bc3edbe20a859f860998db220a Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Fri, 27 Mar 2026 17:34:23 +0000 Subject: [PATCH 3/8] Core: Remove PacketMod system They were seemingly unused by anyone but me, and everything they did can be done more easily with Ashita/Windower addons, etc. --- scripts/commands/packetmod.lua | 43 ---------------------------------- src/map/lua/lua_baseentity.cpp | 30 ------------------------ src/map/lua/lua_baseentity.h | 3 --- src/map/map_networking.cpp | 41 -------------------------------- 4 files changed, 117 deletions(-) delete mode 100644 scripts/commands/packetmod.lua diff --git a/scripts/commands/packetmod.lua b/scripts/commands/packetmod.lua deleted file mode 100644 index f9a3c769d78..00000000000 --- a/scripts/commands/packetmod.lua +++ /dev/null @@ -1,43 +0,0 @@ ------------------------------------ --- func: packetmod --- desc: Adds a modifier for S->C packets ------------------------------------ ----@type TCommand -local commandObj = {} - -commandObj.cmdprops = -{ - permission = 5, - parameters = 'ssss' -} - -local function error(player, msg) - player:printToPlayer(msg - .. '\n!packetmod (operation) (packet id) (offset) (value)' - .. '\nOperations: add / del / clear / print') -end - -commandObj.onTrigger = function(player, operation, packetId, offset, value) - if - operation == nil or - operation == '' or - tonumber(operation) ~= nil - then - error(player, 'Usage:') - return - end - - if operation == 'add' then - -- TODO: Find the best way to handle number->int conversion - ---@diagnostic disable-next-line param-type-mismatch - player:addPacketMod(tonumber(packetId), tonumber(offset), tonumber(value)) - elseif operation == 'del' then - -- TODO - elseif operation == 'clear' then - player:clearPacketMods() - else - -- TODO: Print list of mods - end -end - -return commandObj diff --git a/src/map/lua/lua_baseentity.cpp b/src/map/lua/lua_baseentity.cpp index 33651c5ad22..0130884b04e 100644 --- a/src/map/lua/lua_baseentity.cpp +++ b/src/map/lua/lua_baseentity.cpp @@ -169,8 +169,6 @@ #include -extern std::unordered_map>>> PacketMods; - //======================================================// CLuaBaseEntity::CLuaBaseEntity(CBaseEntity* PEntity) @@ -19647,31 +19645,6 @@ void CLuaBaseEntity::claimContestReward() } } -void CLuaBaseEntity::addPacketMod(uint16 packetId, uint16 offset, uint8 value) -{ - TracyZoneScoped; - - if (auto* PChar = dynamic_cast(m_PBaseEntity)) - { - ShowInfo(fmt::format("Adding Packet Mod ({}): {}: {}: {}", - PChar->name, - hex16ToString(packetId), - hex16ToString(offset), - hex8ToString(value))); - PacketMods[PChar->id][packetId].emplace_back(std::make_pair(offset, value)); - } -} - -void CLuaBaseEntity::clearPacketMods() -{ - TracyZoneScoped; - - if (auto* PChar = dynamic_cast(m_PBaseEntity)) - { - PacketMods[PChar->id].clear(); - } -} - //==========================================================// void CLuaBaseEntity::Register() @@ -20572,9 +20545,6 @@ void CLuaBaseEntity::Register() SOL_REGISTER("getContestRewardStatus", CLuaBaseEntity::getContestRewardStatus); SOL_REGISTER("getContestRankHistory", CLuaBaseEntity::getContestRankHistory); SOL_REGISTER("claimContestReward", CLuaBaseEntity::claimContestReward); - - SOL_REGISTER("addPacketMod", CLuaBaseEntity::addPacketMod); - SOL_REGISTER("clearPacketMods", CLuaBaseEntity::clearPacketMods); } std::ostream& operator<<(std::ostream& os, const CLuaBaseEntity& entity) diff --git a/src/map/lua/lua_baseentity.h b/src/map/lua/lua_baseentity.h index d8d0c7bdfc8..997dfc73be2 100644 --- a/src/map/lua/lua_baseentity.h +++ b/src/map/lua/lua_baseentity.h @@ -962,9 +962,6 @@ class CLuaBaseEntity auto getContestRankHistory() -> sol::table; void claimContestReward(); - void addPacketMod(uint16 packetId, uint16 offset, uint8 value); - void clearPacketMods(); - bool operator==(const CLuaBaseEntity& other) const { return this->m_PBaseEntity == other.m_PBaseEntity; diff --git a/src/map/map_networking.cpp b/src/map/map_networking.cpp index 7901bfe1be8..32b095ea15f 100644 --- a/src/map/map_networking.cpp +++ b/src/map/map_networking.cpp @@ -48,9 +48,6 @@ extern std::map g_PZoneList; // Global array of pointers for zones -// TODO: Extract into a class and a packetMods() member of MapNetworking -std::unordered_map>>> PacketMods; - namespace { @@ -619,25 +616,6 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* map_s PSmallPacket->setSequence(map_session_data->server_packet_id); auto type = PSmallPacket->getType(); - // Apply packet mods if available - if (!PacketMods[PChar->id].empty()) - { - if (PacketMods[PChar->id].find(type) != PacketMods[PChar->id].end()) - { - for (auto& entry : PacketMods[PChar->id][type]) - { - auto offset = entry.first; - auto value = entry.second; - ShowInfo(fmt::format("Packet Mod ({}): {}: {}: {}", - PChar->name, - hex16ToString(type), - hex16ToString(offset), - hex8ToString(value))); - PSmallPacket->ref(offset) = value; - } - } - } - // Store zoneout packet in case we need to re-send this if (type == 0x00B) { @@ -812,25 +790,6 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS packet->setSequence(map_session_data->server_packet_id); - // Apply packet mods if available - if (!PacketMods[map_session_data->charID].empty()) - { - if (PacketMods[map_session_data->charID].find(type) != PacketMods[map_session_data->charID].end()) - { - for (auto& entry : PacketMods[map_session_data->charID][type]) - { - auto offset = entry.first; - auto value = entry.second; - ShowInfo(fmt::format("Packet Mod (char ID {}): {}: {}: {}", - map_session_data->charID, - hex16ToString(type), - hex16ToString(offset), - hex8ToString(value))); - packet->ref(offset) = value; - } - } - } - std::memcpy(buff + *buffsize, *packet, packet->getSize()); *buffsize += packet->getSize(); packets++; From 6d5a39e3da4dcd04d1c13f94e29c395d00c2e501 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Sat, 28 Mar 2026 00:06:29 +0000 Subject: [PATCH 4/8] Core: Tidy up MapSocket --- src/common/cbasetypes.h | 2 +- src/map/map_networking.cpp | 298 ++++++++++++++++++------------------- src/map/map_networking.h | 15 +- src/map/map_socket.cpp | 41 ++--- src/map/map_socket.h | 19 +-- 5 files changed, 185 insertions(+), 190 deletions(-) diff --git a/src/common/cbasetypes.h b/src/common/cbasetypes.h index 9f88b584a4b..24c4f03f9bb 100644 --- a/src/common/cbasetypes.h +++ b/src/common/cbasetypes.h @@ -92,5 +92,5 @@ struct PtrGreater template using MinHeapPtr = std::priority_queue, PtrGreater>; -using NetworkBuffer = std::array; +using NetworkBuffer = std::array; // TODO: Bring constants in here using ByteSpan = std::span; diff --git a/src/map/map_networking.cpp b/src/map/map_networking.cpp index 32b095ea15f..4980cf7a27a 100644 --- a/src/map/map_networking.cpp +++ b/src/map/map_networking.cpp @@ -81,7 +81,7 @@ MapNetworking::MapNetworking(Scheduler& scheduler, MapStatistics& mapStatistics, try { const auto udpPort = mapIPP_.getPort() == 0 ? settings::get("network.MAP_PORT") : mapIPP_.getPort(); - mapSocket_ = std::make_unique(scheduler_, udpPort, std::bind(&MapNetworking::handle_incoming_packet, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + mapSocket_ = std::make_unique(scheduler_, udpPort, std::bind(&MapNetworking::handle_incoming_packet, this, std::placeholders::_1, std::placeholders::_2)); } catch (const std::exception& e) { @@ -90,109 +90,61 @@ MapNetworking::MapNetworking(Scheduler& scheduler, MapStatistics& mapStatistics, } } -void MapNetworking::tapStatistics() -{ - // Collect statistics - // TODO: Collect these inline - std::size_t activeZoneCount = 0; - std::size_t playerCount = 0; - std::size_t mobCount = 0; - std::size_t dynamicTargIdCount = 0; - std::size_t dynamicTargIdCapacity = 0; - - for (auto& [id, PZone] : g_PZoneList) - { - if (PZone->IsZoneActive()) - { - activeZoneCount += 1; - playerCount += PZone->GetZoneEntities()->GetCharList().size(); - mobCount += PZone->GetZoneEntities()->GetMobList().size(); - dynamicTargIdCount += PZone->GetZoneEntities()->GetUsedDynamicTargIDsCount(); - dynamicTargIdCapacity += 511; - } - } - - // Set statistics - mapStatistics_.set(MapStatistics::Key::TotalPacketsToSendPerTick, TotalPacketsToSendPerTick); - mapStatistics_.set(MapStatistics::Key::TotalPacketsSentPerTick, TotalPacketsSentPerTick); - mapStatistics_.set(MapStatistics::Key::TotalPacketsDelayedPerTick, TotalPacketsDelayedPerTick); - mapStatistics_.set(MapStatistics::Key::ActiveZones, activeZoneCount); - mapStatistics_.set(MapStatistics::Key::ConnectedPlayers, playerCount); - mapStatistics_.set(MapStatistics::Key::ActiveMobs, mobCount); - - const auto percent = dynamicTargIdCapacity > 0 - ? static_cast(dynamicTargIdCount) / static_cast(dynamicTargIdCapacity) * 100.0 - : 0.0; - mapStatistics_.set(MapStatistics::Key::DynamicTargIdUsagePercent, static_cast(percent)); - - // Clear statistics - TotalPacketsToSendPerTick = 0U; - TotalPacketsSentPerTick = 0U; - TotalPacketsDelayedPerTick = 0U; -} - -void MapNetworking::handle_incoming_packet(const std::error_code& ec, std::span buffer, const IPP& ipp) +void MapNetworking::handle_incoming_packet(ByteSpan buffer, IPP ipp) { TracyZoneScoped; - if (!ec && !buffer.empty()) - { - // find player session. May be null if there is a pending session for that char id - MapSession* map_session_data = mapSessions_.getSessionByIPP(ipp); + // find player session. May be null if there is a pending session for that char id + MapSession* PSession = mapSessions_.getSessionByIPP(ipp); - // TODO: Don't copy into PBuff, use buffer directly and smaller scratch buffers if required - std::memcpy(PBuff.data(), buffer.data(), buffer.size()); - size_t size = buffer.size(); + // TODO: Don't copy into PBuff, use buffer directly and smaller scratch buffers if required + std::memcpy(PBuff.data(), buffer.data(), buffer.size()); + size_t size = buffer.size(); - // set map_session_data if it's null and the incoming packet is non-encrypted 0x00A - int32 decryptCount = recv_parse(PBuff.data(), &size, map_session_data, ipp); - if (map_session_data == nullptr) - { - return; - } + // set PSession if it's null and the incoming packet is non-encrypted 0x00A + int32 decryptCount = recv_parse(PBuff.data(), &size, PSession, ipp); + if (PSession == nullptr) + { + return; + } - if (decryptCount != -1) + if (decryptCount != -1) + { + // DecryptCount of 0 means the main key decrypted the packet + if (decryptCount == 0 && PSession->PChar) { - // DecryptCount of 0 means the main key decrypted the packet - if (decryptCount == 0 && map_session_data->PChar) + // If the previous package was lost, then we do not collect a new one, + // and send the previous packet again + if (!parse(PBuff.data(), &size, PSession)) { - // If the previous package was lost, then we do not collect a new one, - // and send the previous packet again - if (!parse(PBuff.data(), &size, map_session_data)) - { - send_parse(PBuff.data(), &size, map_session_data, false); - } - } - else if (decryptCount == 1 && map_session_data->blowfish.status == BLOWFISH_PENDING_ZONE) - { - // TODO: Client will send 0x00D in response to 0x00B, so we are probably always sending an extra 0x00B when we don't need to. - // However, the client will fail to decrypt this if they received it before, effectively being a no-op. - // It could be beneficial to parse 0x00D here anyway. - - // Client failed to receive 0x00B, resend it - GP_SERV_COMMAND_LOGOUT zonePacket(map_session_data->zone_type, map_session_data->zone_ipp); - sendSinglePacketNoPChar(PBuff.data(), &size, map_session_data, true, &zonePacket); - - // Increment sync count with every packet - // TODO: match incoming with a new parse that only cares about sync count - map_session_data->server_packet_id += 1; + send_parse(PBuff.data(), &size, PSession, UsePreviousKey::No); } + } + else if (decryptCount == 1 && PSession->blowfish.status == BLOWFISH_PENDING_ZONE) + { + // TODO: Client will send 0x00D in response to 0x00B, so we are probably always sending an extra 0x00B when we don't need to. + // However, the client will fail to decrypt this if they received it before, effectively being a no-op. + // It could be beneficial to parse 0x00D here anyway. - mapSocket_->send(ipp, { PBuff.data(), size }); + // Client failed to receive 0x00B, resend it + GP_SERV_COMMAND_LOGOUT zonePacket(PSession->zone_type, PSession->zone_ipp); + sendSinglePacketNoPChar(PBuff.data(), &size, PSession, UsePreviousKey::Yes, &zonePacket); - std::swap(PBuff, map_session_data->server_packet_data); - std::swap(size, map_session_data->server_packet_size); + // Increment sync count with every packet + // TODO: match incoming with a new parse that only cares about sync count + PSession->server_packet_id += 1; } - // If client is logging out, just close it. - if (map_session_data->shuttingDown == 1) - { - mapSessions_.destroySession(map_session_data); - } + mapSocket_->send(ipp, { PBuff.data(), size }); + + std::swap(PBuff, PSession->server_packet_data); + std::swap(size, PSession->server_packet_size); } - else if (ec) + + // If client is logging out, just close it. + if (PSession->shuttingDown == 1) { - ShowErrorFmt("Receive error: {}", ec.message()); + mapSessions_.destroySession(PSession); } } @@ -228,7 +180,7 @@ int32 MapNetworking::map_decipher_packet(uint8* buff, size_t buffsize, MapSessio return -1; } -int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* map_session_data, const IPP& ipp) +int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* PSession, IPP ipp) { TracyZoneScoped; @@ -287,19 +239,19 @@ int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* map_s uint32 packetCharID = loginPacket.UniqueNo; - if (map_session_data == nullptr) + if (PSession == nullptr) { auto pendingSession = mapSessions_.getPendingSessionByCharId(packetCharID); if (pendingSession) { mapSessions_.destroyPendingSession(pendingSession); - map_session_data = mapSessions_.createSession(ipp); - if (map_session_data == nullptr) + PSession = mapSessions_.createSession(ipp); + if (PSession == nullptr) { // TODO: err msg? return -1; } - map_session_data->scheduler = &scheduler_; + PSession->scheduler = &scheduler_; } else { @@ -309,12 +261,12 @@ int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* map_s // We can only get here if an 0x00A (not encrypted) packet was here. // If we were pending zones, delete our old char - if (map_session_data->blowfish.status == BLOWFISH_PENDING_ZONE) + if (PSession->blowfish.status == BLOWFISH_PENDING_ZONE) { - map_session_data->PChar.reset(); + PSession->PChar.reset(); } - if (map_session_data->PChar == nullptr) + if (PSession->PChar == nullptr) { uint16 langID = loginPacket.uCliLang; uint32 accountID = 0; @@ -340,24 +292,24 @@ int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* map_s rset = db::preparedStmt("SELECT session_key FROM accounts_sessions WHERE charid = ? LIMIT 1", packetCharID); if (rset && rset->rowsCount() && rset->next()) { - db::extractFromBlob(rset, "session_key", map_session_data->blowfish.key); - map_session_data->initBlowfish(); + db::extractFromBlob(rset, "session_key", PSession->blowfish.key); + PSession->initBlowfish(); } else { ShowError("recv_parse: Cannot load session_key for charid %u", packetCharID); } - map_session_data->PChar = charutils::LoadChar(scheduler_, config_, packetCharID); - map_session_data->charID = packetCharID; - map_session_data->accountID = accountID; + PSession->PChar = charutils::LoadChar(scheduler_, config_, packetCharID); + PSession->charID = packetCharID; + PSession->accountID = accountID; - auto* PChar = map_session_data->PChar.get(); + auto* PChar = PSession->PChar.get(); - PChar->PSession = map_session_data; + PChar->PSession = PSession; // If we're a new char on a new instance and prevzone != zone - if (map_session_data->blowfish.status == BLOWFISH_WAITING && PChar->loc.destination != PChar->loc.prevzone) + if (PSession->blowfish.status == BLOWFISH_WAITING && PChar->loc.destination != PChar->loc.prevzone) { message::send(ipc::KillSession{ .victimId = packetCharID, @@ -365,16 +317,16 @@ int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* map_s } } - map_session_data->client_packet_id = 0; - map_session_data->server_packet_id = 0; - map_session_data->zone_ipp = {}; - map_session_data->zone_type = GP_GAME_LOGOUT_STATE::NONE; + PSession->client_packet_id = 0; + PSession->server_packet_id = 0; + PSession->zone_ipp = {}; + PSession->zone_type = GP_GAME_LOGOUT_STATE::NONE; return 0; } - else if (map_session_data != nullptr) + else if (PSession != nullptr) { - if (map_session_data->blowfish.status == BLOWFISH_PENDING_ZONE) + if (PSession->blowfish.status == BLOWFISH_PENDING_ZONE) { // Copy buff into the backup buffer. Blowfish can't be rewound currently. std::memcpy(PBuffCopy.data(), buff, *buffsize); @@ -382,12 +334,12 @@ int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* map_s int decryptCount = 0; - if (map_decipher_packet(buff, *buffsize, map_session_data, &map_session_data->blowfish) == -1) + if (map_decipher_packet(buff, *buffsize, PSession, &PSession->blowfish) == -1) { // If the client is pending zone, they might not have received 0x00B, and thus not incremented their key // Check old blowfish data - if (map_session_data->blowfish.status == BLOWFISH_PENDING_ZONE && - map_decipher_packet(PBuffCopy.data(), *buffsize, map_session_data, &map_session_data->prev_blowfish) != -1) + if (PSession->blowfish.status == BLOWFISH_PENDING_ZONE && + map_decipher_packet(PBuffCopy.data(), *buffsize, PSession, &PSession->prev_blowfish) != -1) { // Copy decrypted bytes back into buffer std::memcpy(buff, PBuffCopy.data(), *buffsize); @@ -423,7 +375,7 @@ int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* map_s return -1; } -int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* map_session_data) +int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* PSession) { TracyZoneScoped; @@ -431,7 +383,7 @@ int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* map_sessio uint8* PacketData_Begin = &buff[FFXI_HEADER_SIZE]; uint8* PacketData_End = &buff[*buffsize]; - auto* PChar = map_session_data->PChar.get(); + auto* PChar = PSession->PChar.get(); TracyZoneString(PChar->getName()); @@ -442,11 +394,11 @@ int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* map_sessio // TODO: figure out what exactly the client sends when you're not in a CS. there's no C2S packets being sent via the client, // and yet we receive something here. It doesnt look like a valid packet, as it has no size and the type is 0x001 which is not valid. // TODO: Should unencrypted 0x00As not tap the timer? - if (map_session_data->blowfish.status != BLOWFISH_PENDING_ZONE && map_session_data->blowfish.status != BLOWFISH_WAITING) + if (PSession->blowfish.status != BLOWFISH_PENDING_ZONE && PSession->blowfish.status != BLOWFISH_WAITING) { // Update the time we last got a char sync packet // The client can spam some other packets when trying to zone, preventing timely session deletions - map_session_data->last_update = timer::now(); + PSession->last_update = timer::now(); } for (uint8* SmallPD_ptr = PacketData_Begin; SmallPD_ptr + (ref(SmallPD_ptr, 1) & 0xFE) * 2 <= PacketData_End && (ref(SmallPD_ptr, 1) & 0xFE); @@ -461,7 +413,7 @@ int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* map_sessio // if the code of the current package is less than or equal to the last received // or more global then ignore the package - if ((ref(SmallPD_ptr, 2) <= map_session_data->client_packet_id) || (ref(SmallPD_ptr, 2) > SmallPD_Code)) + if ((ref(SmallPD_ptr, 2) <= PSession->client_packet_id) || (ref(SmallPD_ptr, 2) > SmallPD_Code)) { continue; } @@ -499,7 +451,7 @@ int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* map_sessio if (PChar->loc.zone == nullptr && SmallPD_Type != 0x0A) { // Packets aren't unexpected from the old key under BLOWFISH_PENDING_ZONE - if (map_session_data->blowfish.status != BLOWFISH_PENDING_ZONE) + if (PSession->blowfish.status != BLOWFISH_PENDING_ZONE) { ShowWarning("This packet is unexpected from %s - Received %03hX earlier without matching 0x0A", PChar->getName(), SmallPD_Type); } @@ -510,7 +462,7 @@ int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* map_sessio // : instead of creating a new packet here. auto basicPacket = CBasicPacket::createFromBuffer(reinterpret_cast(SmallPD_ptr)); ShowTraceFmt("map::parse: Char: {} ({}): {}", PChar->getName(), PChar->id, hex16ToString(basicPacket->getType())); - PacketParser[SmallPD_Type](map_session_data, PChar, *basicPacket); + PacketParser[SmallPD_Type](PSession, PChar, *basicPacket); } } else @@ -534,13 +486,13 @@ int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* map_sessio // Flush any batched equip changes after processing all incoming packets PChar->flushEquipChanges(); - map_session_data->client_packet_id = SmallPD_Code; + PSession->client_packet_id = SmallPD_Code; // Google Translate: // here we check if the client received the previous package // if not received, then we do not create a new one, but send the previous one - if (ref(buff, 2) != map_session_data->server_packet_id) + if (ref(buff, 2) != PSession->server_packet_id) { // If the client and server have become out of sync, then caching takes place. However, caching // zone packets will result in the client never properly connecting. Ignore those specifically. @@ -549,25 +501,25 @@ int32 MapNetworking::parse(uint8* buff, size_t* buffsize, MapSession* map_sessio return 0; } - ref(map_session_data->server_packet_data.data(), 2) = SmallPD_Code; - ref(map_session_data->server_packet_data.data(), 8) = earth_time::timestamp(); + ref(PSession->server_packet_data.data(), 2) = SmallPD_Code; + ref(PSession->server_packet_data.data(), 8) = earth_time::timestamp(); - PBuff = map_session_data->server_packet_data; - *buffsize = map_session_data->server_packet_size; + PBuff = PSession->server_packet_data; + *buffsize = PSession->server_packet_size; - std::memcpy(map_session_data->server_packet_data.data(), buff, *buffsize); + std::memcpy(PSession->server_packet_data.data(), buff, *buffsize); return -1; } // GT: increase the number of the sent packet only if new data is sent - map_session_data->server_packet_id += 1; + PSession->server_packet_id += 1; return 0; } -int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* map_session_data, bool usePreviousKey) +int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* PSession, UsePreviousKey usePreviousKey) { TracyZoneScoped; @@ -577,14 +529,14 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* map_s // - assign the outgoing packet the number of the last packet sent to the client +1 // - write down the current time of sending the packet - ref(buff, 0) = map_session_data->server_packet_id; - ref(buff, 2) = map_session_data->client_packet_id; + ref(buff, 0) = PSession->server_packet_id; + ref(buff, 2) = PSession->client_packet_id; // save the current time (32 BIT!) ref(buff, 8) = earth_time::timestamp(); // build a large package, consisting of several small packets - auto* PChar = map_session_data->PChar.get(); + auto* PChar = PSession->PChar.get(); TracyZoneString(PChar->name); std::unique_ptr PSmallPacket = nullptr; @@ -613,7 +565,7 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* map_s PSmallPacket = std::move(packetList.front()); packetList.pop_front(); - PSmallPacket->setSequence(map_session_data->server_packet_id); + PSmallPacket->setSequence(PSession->server_packet_id); auto type = PSmallPacket->getType(); // Store zoneout packet in case we need to re-send this @@ -621,13 +573,13 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* map_s { const auto IPPacket = static_cast(PSmallPacket.get()); - map_session_data->zone_ipp = IPPacket->zoneIPP(); - map_session_data->zone_type = IPPacket->zoneType(); + PSession->zone_ipp = IPPacket->zoneIPP(); + PSession->zone_type = IPPacket->zoneType(); incrementKeyAfterEncrypt = true; // Set client port to zero, indicating the client tried to zone out and no longer has a port until the next 0x00A - db::preparedStmt("UPDATE accounts_sessions SET client_port = 0, last_zoneout_time = NOW() WHERE charid = ?", map_session_data->charID); + db::preparedStmt("UPDATE accounts_sessions SET client_port = 0, last_zoneout_time = NOW() WHERE charid = ?", PSession->charID); } std::memcpy(buff + *buffsize, *PSmallPacket, PSmallPacket->getSize()); @@ -693,13 +645,13 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* map_s blowfish_t* pbfkey = nullptr; - if (map_session_data->blowfish.status == BLOWFISH_PENDING_ZONE && usePreviousKey) + if (PSession->blowfish.status == BLOWFISH_PENDING_ZONE && usePreviousKey) { - pbfkey = &map_session_data->prev_blowfish; + pbfkey = &PSession->prev_blowfish; } else { - pbfkey = &map_session_data->blowfish; + pbfkey = &PSession->blowfish; } for (uint32 j = 0; j < CypherSize; j += 2) @@ -740,15 +692,15 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* map_s // Increment the key after 0x00B was sent (otherwise the client would never get it!) if (incrementKeyAfterEncrypt) { - map_session_data->incrementBlowfish(); + PSession->incrementBlowfish(); db::preparedStmt("UPDATE accounts_sessions SET session_key = ? WHERE charid = ? LIMIT 1", - map_session_data->blowfish.key, + PSession->blowfish.key, PChar->id); // see https://github.com/atom0s/XiPackets/blob/main/world/server/0x000B/README.md // GP_GAME_LOGOUT_STATE::GP_GAME_LOGOUT_STATE_LOGOUT = disconnect/logout/shutdown - if (map_session_data->zone_type != GP_GAME_LOGOUT_STATE::LOGOUT) + if (PSession->zone_type != GP_GAME_LOGOUT_STATE::LOGOUT) { message::send(ipc::CharZone{ .charId = PChar->id, @@ -756,14 +708,14 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* map_s }); } - map_session_data->blowfish.status = BLOWFISH_PENDING_ZONE; + PSession->blowfish.status = BLOWFISH_PENDING_ZONE; PChar->PSession->PChar.reset(); // destroy PChar } return 0; } -int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapSession* map_session_data, bool usePreviousKey, CBasicPacket* packet) +int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapSession* PSession, UsePreviousKey usePreviousKey, CBasicPacket* packet) { TracyZoneScoped; @@ -773,8 +725,8 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS // - assign the outgoing packet the number of the last packet sent to the client +1 // - write down the current time of sending the packet - ref(buff, 0) = map_session_data->server_packet_id; - ref(buff, 2) = map_session_data->client_packet_id; + ref(buff, 0) = PSession->server_packet_id; + ref(buff, 2) = PSession->client_packet_id; // save the current time (32 BIT!) ref(buff, 8) = earth_time::timestamp(); @@ -785,10 +737,9 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS TotalPacketsToSendPerTick += static_cast(1); *buffsize = FFXI_HEADER_SIZE; - auto type = packet->getType(); packets = 0; - packet->setSequence(map_session_data->server_packet_id); + packet->setSequence(PSession->server_packet_id); std::memcpy(buff + *buffsize, *packet, packet->getSize()); *buffsize += packet->getSize(); @@ -826,7 +777,7 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS if (PacketSize > kMaxBufferSize) { - ShowCritical("Network: PScratchBuffer is overflowed (%u) by char id %s", PacketSize, map_session_data->charID); + ShowCritical("Network: PScratchBuffer is overflowed (%u) by char id %s", PacketSize, PSession->charID); } // Making total outgoing packet @@ -836,13 +787,13 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS blowfish_t* pbfkey = nullptr; - if (map_session_data->blowfish.status == BLOWFISH_PENDING_ZONE && usePreviousKey) + if (PSession->blowfish.status == BLOWFISH_PENDING_ZONE && usePreviousKey) { - pbfkey = &map_session_data->prev_blowfish; + pbfkey = &PSession->prev_blowfish; } else { - pbfkey = &map_session_data->blowfish; + pbfkey = &PSession->blowfish; } for (uint32 j = 0; j < CypherSize; j += 2) @@ -855,6 +806,47 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS return 0; } +void MapNetworking::tapStatistics() +{ + // Collect statistics + // TODO: Collect these inline + std::size_t activeZoneCount = 0; + std::size_t playerCount = 0; + std::size_t mobCount = 0; + std::size_t dynamicTargIdCount = 0; + std::size_t dynamicTargIdCapacity = 0; + + for (auto& [id, PZone] : g_PZoneList) + { + if (PZone->IsZoneActive()) + { + activeZoneCount += 1; + playerCount += PZone->GetZoneEntities()->GetCharList().size(); + mobCount += PZone->GetZoneEntities()->GetMobList().size(); + dynamicTargIdCount += PZone->GetZoneEntities()->GetUsedDynamicTargIDsCount(); + dynamicTargIdCapacity += 511; + } + } + + // Set statistics + mapStatistics_.set(MapStatistics::Key::TotalPacketsToSendPerTick, TotalPacketsToSendPerTick); + mapStatistics_.set(MapStatistics::Key::TotalPacketsSentPerTick, TotalPacketsSentPerTick); + mapStatistics_.set(MapStatistics::Key::TotalPacketsDelayedPerTick, TotalPacketsDelayedPerTick); + mapStatistics_.set(MapStatistics::Key::ActiveZones, activeZoneCount); + mapStatistics_.set(MapStatistics::Key::ConnectedPlayers, playerCount); + mapStatistics_.set(MapStatistics::Key::ActiveMobs, mobCount); + + const auto percent = dynamicTargIdCapacity > 0 + ? static_cast(dynamicTargIdCount) / static_cast(dynamicTargIdCapacity) * 100.0 + : 0.0; + mapStatistics_.set(MapStatistics::Key::DynamicTargIdUsagePercent, static_cast(percent)); + + // Clear statistics + TotalPacketsToSendPerTick = 0U; + TotalPacketsSentPerTick = 0U; + TotalPacketsDelayedPerTick = 0U; +} + auto MapNetworking::ipp() const -> IPP { return mapIPP_; diff --git a/src/map/map_networking.h b/src/map/map_networking.h index e0d5b333954..1f29fc7eaae 100644 --- a/src/map/map_networking.h +++ b/src/map/map_networking.h @@ -42,25 +42,26 @@ class MapEngine; class MapNetworking { public: + using UsePreviousKey = xi::Flag; + MapNetworking(Scheduler& scheduler, MapStatistics& mapStatistics, MapConfig config); // // Networking // - void tapStatistics(); - // TODO: Pass around std::span instead of uint8* and size_t* // TODO: Stop changing the buffsize size_t as we go along - // TODO: Replace bool with named enum class // TODO: All of these need to become coroutines - void handle_incoming_packet(const std::error_code& ec, std::span buffer, const IPP& ipp); + void handle_incoming_packet(ByteSpan buffer, IPP ipp); int32 map_decipher_packet(uint8*, size_t, MapSession*, blowfish_t*); // Decipher packet - int32 recv_parse(uint8*, size_t*, MapSession*, const IPP& ipp); // main function to parse recv packets + int32 recv_parse(uint8*, size_t*, MapSession*, IPP ipp); // main function to parse recv packets int32 parse(uint8*, size_t*, MapSession*); // main function parsing the packets - int32 send_parse(uint8*, size_t*, MapSession*, bool); // main function is building big packet + int32 send_parse(uint8*, size_t*, MapSession*, UsePreviousKey); // main function is building big packet - int32 sendSinglePacketNoPChar(uint8*, size_t*, MapSession*, bool, CBasicPacket*); // used to resend 0x00B if client didn't receive it (dropped packet) + int32 sendSinglePacketNoPChar(uint8*, size_t*, MapSession*, UsePreviousKey, CBasicPacket*); // used to resend 0x00B if client didn't receive it (dropped packet) + + void tapStatistics(); // // Accessors diff --git a/src/map/map_socket.cpp b/src/map/map_socket.cpp index 38e2b7414a5..6d6f1ad9783 100644 --- a/src/map/map_socket.cpp +++ b/src/map/map_socket.cpp @@ -21,7 +21,7 @@ #include "map_socket.h" -#include "common/logging.h" +#include MapSocket::MapSocket(Scheduler& scheduler, const uint16 port, ReceiveFn onReceiveFn) : scheduler_(scheduler) @@ -38,7 +38,7 @@ MapSocket::MapSocket(Scheduler& scheduler, const uint16 port, ReceiveFn onReceiv socket_.open(listen_endpoint.protocol()); socket_.bind(listen_endpoint); - startReceive(); + receive(); // begin receiving loop } MapSocket::~MapSocket() @@ -51,33 +51,44 @@ MapSocket::~MapSocket() } } -void MapSocket::startReceive() +void MapSocket::receive() { TracyZoneScoped; socket_.async_receive_from( - asio::buffer(buffer_), remote_endpoint_, [this](const std::error_code& ec, std::size_t bytes_recvd) + asio::buffer(buffer_), remoteEndpoint_, [this](const std::error_code& ec, std::size_t bytesRecvd) { // NOTE: ASIO returns the address in host byte order, but we store it in network byte order, // : so we convert it back. - const auto sender_ip = htonl(remote_endpoint_.address().to_v4().to_uint()); - const auto sender_port = remote_endpoint_.port(); - const auto ipp = IPP(sender_ip, sender_port); + const auto senderIP = htonl(remoteEndpoint_.address().to_v4().to_uint()); + const auto senderPort = remoteEndpoint_.port(); + const auto ipp = IPP(senderIP, senderPort); - const auto buffer = std::span(buffer_.data(), bytes_recvd); + const auto sizedBuffer = ByteSpan(buffer_.data(), bytesRecvd); - DebugPacketsFmt("Received {} bytes from {}", buffer.size(), ipp.toString()); + DebugPacketsFmt("Received {} bytes from {}", sizedBuffer.size(), ipp.toString()); - onReceiveFn_(ec, buffer, ipp); + if (ec) + { + ShowErrorFmt("Receive error from {}: {}", ipp.toString(), ec.message()); + } + else if (sizedBuffer.empty()) + { + ShowErrorFmt("Received empty buffer from {}", ipp.toString()); + } + else // Everything is OK + { + onReceiveFn_(sizedBuffer, ipp); + } if (!scheduler_.closeRequested() && socket_.is_open()) { - startReceive(); // Queue up more work + receive(); // Queue up more work } }); } -void MapSocket::send(const IPP& ipp, std::span buffer) +void MapSocket::send(IPP ipp, ByteSpan buffer) { TracyZoneScoped; @@ -102,9 +113,3 @@ void MapSocket::send(const IPP& ipp, std::span buffer) // This will only be called in the middle of a doSocketsFor() call, so we don't // need to enqueue more work when we're done here. } - -void MapSocket::requestExit() -{ - isRunning_ = false; - scheduler_.stop(); -} diff --git a/src/map/map_socket.h b/src/map/map_socket.h index 853df3c1650..fe8783794cb 100644 --- a/src/map/map_socket.h +++ b/src/map/map_socket.h @@ -21,10 +21,10 @@ #pragma once -#include "common/blowfish.h" -#include "common/cbasetypes.h" -#include "common/ipp.h" -#include "common/scheduler.h" +#include +#include +#include +#include #include "map_constants.h" @@ -36,24 +36,21 @@ class MapSocket { public: - using ReceiveFn = std::function, IPP)>; + using ReceiveFn = std::function; MapSocket(Scheduler& scheduler, uint16 port, ReceiveFn onReceiveFn); ~MapSocket(); - void send(const IPP& ipp, std::span buffer); - - void requestExit(); + void send(IPP ipp, ByteSpan buffer); private: - void startReceive(); + void receive(); Scheduler& scheduler_; uint16 port_; asio::ip::udp::socket socket_; NetworkBuffer buffer_; // TODO: Pass in the global buffer, or only use this one - asio::ip::udp::endpoint remote_endpoint_; - bool isRunning_{ true }; + asio::ip::udp::endpoint remoteEndpoint_; ReceiveFn onReceiveFn_; }; From 300d431dee26c0d75720e9a516ce260adcafd962 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Sat, 28 Mar 2026 00:12:03 +0000 Subject: [PATCH 5/8] Core: Move network cpp locals into class members --- src/map/map_networking.cpp | 23 ++++------------------- src/map/map_networking.h | 17 ++++++++++++----- 2 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/map/map_networking.cpp b/src/map/map_networking.cpp index 4980cf7a27a..3d87faa3781 100644 --- a/src/map/map_networking.cpp +++ b/src/map/map_networking.cpp @@ -21,10 +21,10 @@ #include "map_networking.h" -#include "common/arguments.h" -#include "common/md52.h" -#include "common/tracy.h" -#include "common/zlib.h" +#include +#include +#include +#include #include "entities/charentity.h" @@ -48,21 +48,6 @@ extern std::map g_PZoneList; // Global array of pointers for zones -namespace -{ - -NetworkBuffer PBuff; // Global packet clipboard -NetworkBuffer PBuffCopy; // Copy of above, used to decrypt a second time if necessary. -NetworkBuffer PScratchBuffer; // Temporary packet clipboard - -// Runtime statistics -// TODO: Move these to MapStatistics -uint32 TotalPacketsToSendPerTick = 0U; -uint32 TotalPacketsSentPerTick = 0U; -uint32 TotalPacketsDelayedPerTick = 0U; - -} // namespace - MapNetworking::MapNetworking(Scheduler& scheduler, MapStatistics& mapStatistics, MapConfig config) : scheduler_(scheduler) , mapStatistics_(mapStatistics) diff --git a/src/map/map_networking.h b/src/map/map_networking.h index 1f29fc7eaae..104a05eea95 100644 --- a/src/map/map_networking.h +++ b/src/map/map_networking.h @@ -33,11 +33,7 @@ #include "map_socket.h" #include "map_statistics.h" -#include -#include - class CBasicPacket; -class MapEngine; class MapNetworking { @@ -53,7 +49,8 @@ class MapNetworking // TODO: Pass around std::span instead of uint8* and size_t* // TODO: Stop changing the buffsize size_t as we go along // TODO: All of these need to become coroutines - void handle_incoming_packet(ByteSpan buffer, IPP ipp); + void handle_incoming_packet(ByteSpan buffer, IPP ipp); + int32 map_decipher_packet(uint8*, size_t, MapSession*, blowfish_t*); // Decipher packet int32 recv_parse(uint8*, size_t*, MapSession*, IPP ipp); // main function to parse recv packets int32 parse(uint8*, size_t*, MapSession*); // main function parsing the packets @@ -79,4 +76,14 @@ class MapNetworking MapSessionContainer mapSessions_; std::unique_ptr mapSocket_; MapConfig config_; + + // TODO: We can probably dedupe these and move the main buffer into MapSocket + NetworkBuffer PBuff; // Global packet clipboard + NetworkBuffer PBuffCopy; // Copy of above, used to decrypt a second time if necessary. + NetworkBuffer PScratchBuffer; // Temporary packet clipboard + + // TODO: Move these to MapStatistics + uint32 TotalPacketsToSendPerTick{ 0U }; + uint32 TotalPacketsSentPerTick{ 0U }; + uint32 TotalPacketsDelayedPerTick{ 0U }; }; From 2e362739c9e27297af2f3cede052870eea10e62c Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Sat, 28 Mar 2026 12:01:30 +0000 Subject: [PATCH 6/8] Core: Refactor packet building in MapNetworking --- src/map/map_networking.cpp | 146 +++++++++++++------------------------ src/map/map_networking.h | 21 +++++- 2 files changed, 71 insertions(+), 96 deletions(-) diff --git a/src/map/map_networking.cpp b/src/map/map_networking.cpp index 3d87faa3781..517faa97c41 100644 --- a/src/map/map_networking.cpp +++ b/src/map/map_networking.cpp @@ -111,9 +111,32 @@ void MapNetworking::handle_incoming_packet(ByteSpan buffer, IPP ipp) // However, the client will fail to decrypt this if they received it before, effectively being a no-op. // It could be beneficial to parse 0x00D here anyway. - // Client failed to receive 0x00B, resend it + // + // Client failed to receive 0x00B, manually rebuild and resend it + // + GP_SERV_COMMAND_LOGOUT zonePacket(PSession->zone_type, PSession->zone_ipp); - sendSinglePacketNoPChar(PBuff.data(), &size, PSession, UsePreviousKey::Yes, &zonePacket); + + preparePacket(PBuff.data(), PSession); + + TotalPacketsToSendPerTick += 1; + + size = FFXI_HEADER_SIZE; + zonePacket.setSequence(PSession->server_packet_id); + std::memcpy(PBuff.data() + size, &zonePacket, zonePacket.getSize()); + size += zonePacket.getSize(); + + auto maybePacketSize = compressPacket(PBuff.data(), size); + if (!maybePacketSize) + { + ShowError("zlib compression error"); + size = 0; + } + else + { + finalizePacket(PBuff.data(), &size, *maybePacketSize, PSession, UsePreviousKey::Yes); + TotalPacketsSentPerTick += 1; + } // Increment sync count with every packet // TODO: match incoming with a new parse that only cares about sync count @@ -508,17 +531,7 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* PSess { TracyZoneScoped; - // Modify the header of the outgoing packet - // The essence of the transformations: - // - send the client the number of the last packet received from him - // - assign the outgoing packet the number of the last packet sent to the client +1 - // - write down the current time of sending the packet - - ref(buff, 0) = PSession->server_packet_id; - ref(buff, 2) = PSession->client_packet_id; - - // save the current time (32 BIT!) - ref(buff, 8) = earth_time::timestamp(); + preparePacket(buff, PSession); // build a large package, consisting of several small packets auto* PChar = PSession->PChar.get(); @@ -526,7 +539,7 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* PSess std::unique_ptr PSmallPacket = nullptr; - uint32 PacketSize = UINT32_MAX; + size_t PacketSize = static_cast(UINT32_MAX); size_t PacketCount = std::clamp(PChar->getPacketCount(), 0, kMaxPacketPerCompression); uint8 packets = 0; bool incrementKeyAfterEncrypt = false; @@ -578,19 +591,13 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* PSess // Compress the data without regard to the header // The returned size is 8 times the real data - PacketSize = zlib_compress((int8*)(buff + FFXI_HEADER_SIZE), (uint32)(*buffsize - FFXI_HEADER_SIZE), (int8*)PScratchBuffer.data(), kMaxBufferSize); - - // handle compression error - if (PacketSize == static_cast(-1)) + auto maybePacketSize = compressPacket(buff, static_cast(*buffsize)); + if (!maybePacketSize) { ShowError("zlib compression error"); continue; } - - ref(PScratchBuffer.data(), zlib_compressed_size(PacketSize)) = PacketSize; - - PacketSize = (uint32)zlib_compressed_size(PacketSize) + 4; - + PacketSize = *maybePacketSize; } while (PacketCount > 0 && PacketSize > 1300 - FFXI_HEADER_SIZE - 16); // max size for client to accept if (PacketSize == static_cast(-1)) @@ -612,46 +619,7 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* PSess TotalPacketsSentPerTick += packets; TracyZoneString(fmt::format("Sending {} packets", packets)); - // Record data size excluding header - uint8 hash[16]; - md5(PScratchBuffer.data(), hash, PacketSize); - std::memcpy(PScratchBuffer.data() + PacketSize, hash, 16); - PacketSize += 16; - - if (PacketSize > kMaxBufferSize) - { - ShowCritical("Network: PScratchBuffer is overflowed (%u) by %s", PacketSize, PChar->name); - } - - // Making total outgoing packet - std::memcpy(buff + FFXI_HEADER_SIZE, PScratchBuffer.data(), PacketSize); - - uint32 CypherSize = (PacketSize / 4) & -2; - - blowfish_t* pbfkey = nullptr; - - if (PSession->blowfish.status == BLOWFISH_PENDING_ZONE && usePreviousKey) - { - pbfkey = &PSession->prev_blowfish; - } - else - { - pbfkey = &PSession->blowfish; - } - - for (uint32 j = 0; j < CypherSize; j += 2) - { - blowfish_encipher((uint32*)(buff) + j + 7, (uint32*)(buff) + j + 8, pbfkey->P, pbfkey->S[0]); - } - - // Control the size of the sent packet. - // if its size exceeds 1400 bytes (data size + 42 bytes IP header), - // then the client ignores the packet and returns a message about its loss - - // in case of a similar situation, display a warning message and - // decrease the size of BuffMaxSize in 4 byte increments until it is removed (manually) - - *buffsize = PacketSize + FFXI_HEADER_SIZE; + finalizePacket(buff, buffsize, PacketSize, PSession, usePreviousKey); auto remainingPackets = PChar->getPacketCount(); TotalPacketsDelayedPerTick += static_cast(remainingPackets); @@ -700,7 +668,7 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* PSess return 0; } -int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapSession* PSession, UsePreviousKey usePreviousKey, CBasicPacket* packet) +void MapNetworking::preparePacket(uint8* buff, MapSession* PSession) { TracyZoneScoped; @@ -715,54 +683,44 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS // save the current time (32 BIT!) ref(buff, 8) = earth_time::timestamp(); +} - uint32 PacketSize = UINT32_MAX; - uint8 packets = 0; - - TotalPacketsToSendPerTick += static_cast(1); - - *buffsize = FFXI_HEADER_SIZE; - packets = 0; - - packet->setSequence(PSession->server_packet_id); - - std::memcpy(buff + *buffsize, *packet, packet->getSize()); - *buffsize += packet->getSize(); - packets++; +auto MapNetworking::compressPacket(uint8* buff, size_t buffsize) -> Maybe +{ + TracyZoneScoped; // Compress the data without regard to the header // The returned size is 8 times the real data - PacketSize = zlib_compress((int8*)(buff + FFXI_HEADER_SIZE), (uint32)(*buffsize - FFXI_HEADER_SIZE), (int8*)PScratchBuffer.data(), kMaxBufferSize); + int32 result = zlib_compress((int8*)(buff + FFXI_HEADER_SIZE), (uint32)(buffsize - FFXI_HEADER_SIZE), (int8*)PScratchBuffer.data(), kMaxBufferSize); // handle compression error - if (PacketSize == static_cast(-1)) + if (result == -1) { - ShowError("zlib compression error"); - return -1; + return std::nullopt; } - ref(PScratchBuffer.data(), zlib_compressed_size(PacketSize)) = PacketSize; + auto packetSize = static_cast(result); - PacketSize = (uint32)zlib_compressed_size(PacketSize) + 4; + ref(PScratchBuffer.data(), zlib_compressed_size(packetSize)) = static_cast(packetSize); - if (PacketSize == static_cast(-1)) - { - *buffsize = 0; - return -1; - } + packetSize = zlib_compressed_size(packetSize) + 4; - TotalPacketsSentPerTick += packets; - TracyZoneString(fmt::format("Sending {} packets", packets)); + return packetSize; +} + +void MapNetworking::finalizePacket(uint8* buff, size_t* buffsize, size_t PacketSize, MapSession* PSession, UsePreviousKey usePreviousKey) +{ + TracyZoneScoped; // Record data size excluding header uint8 hash[16]; - md5(PScratchBuffer.data(), hash, PacketSize); + md5(PScratchBuffer.data(), hash, static_cast(PacketSize)); std::memcpy(PScratchBuffer.data() + PacketSize, hash, 16); PacketSize += 16; if (PacketSize > kMaxBufferSize) { - ShowCritical("Network: PScratchBuffer is overflowed (%u) by char id %s", PacketSize, PSession->charID); + ShowCritical("Network: PScratchBuffer is overflowed (%u)", PacketSize); } // Making total outgoing packet @@ -772,7 +730,7 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS blowfish_t* pbfkey = nullptr; - if (PSession->blowfish.status == BLOWFISH_PENDING_ZONE && usePreviousKey) + if (PSession->blowfish.status == BLOWFISH_PENDING_ZONE && usePreviousKey == UsePreviousKey::Yes) { pbfkey = &PSession->prev_blowfish; } @@ -787,8 +745,6 @@ int32 MapNetworking::sendSinglePacketNoPChar(uint8* buff, size_t* buffsize, MapS } *buffsize = PacketSize + FFXI_HEADER_SIZE; - - return 0; } void MapNetworking::tapStatistics() diff --git a/src/map/map_networking.h b/src/map/map_networking.h index 104a05eea95..fd4149f576c 100644 --- a/src/map/map_networking.h +++ b/src/map/map_networking.h @@ -49,6 +49,7 @@ class MapNetworking // TODO: Pass around std::span instead of uint8* and size_t* // TODO: Stop changing the buffsize size_t as we go along // TODO: All of these need to become coroutines + // TODO: Properly use size_t or u32/i32 where appropriate, we do a lot of casting void handle_incoming_packet(ByteSpan buffer, IPP ipp); int32 map_decipher_packet(uint8*, size_t, MapSession*, blowfish_t*); // Decipher packet @@ -56,7 +57,23 @@ class MapNetworking int32 parse(uint8*, size_t*, MapSession*); // main function parsing the packets int32 send_parse(uint8*, size_t*, MapSession*, UsePreviousKey); // main function is building big packet - int32 sendSinglePacketNoPChar(uint8*, size_t*, MapSession*, UsePreviousKey, CBasicPacket*); // used to resend 0x00B if client didn't receive it (dropped packet) + // + // Packet Building + // + + // Sets header, sequence, timestamp + void preparePacket(uint8* buff, MapSession* PSession); + + // Add payload between preparePacket and compressPacket + + auto compressPacket(uint8* buff, size_t buffsize) -> Maybe; + + // Sets MD5 hash, blowfish, final buffer size + void finalizePacket(uint8* buff, size_t* buffsize, size_t PacketSize, MapSession* PSession, UsePreviousKey usePreviousKey); + + // + // Utils + // void tapStatistics(); @@ -78,11 +95,13 @@ class MapNetworking MapConfig config_; // TODO: We can probably dedupe these and move the main buffer into MapSocket + // TODO: Update the naming conventions of these NetworkBuffer PBuff; // Global packet clipboard NetworkBuffer PBuffCopy; // Copy of above, used to decrypt a second time if necessary. NetworkBuffer PScratchBuffer; // Temporary packet clipboard // TODO: Move these to MapStatistics + // TODO: Update the naming conventions of these uint32 TotalPacketsToSendPerTick{ 0U }; uint32 TotalPacketsSentPerTick{ 0U }; uint32 TotalPacketsDelayedPerTick{ 0U }; From 8dab201cadf9c80f58322628ac52da881211fe4e Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Sat, 28 Mar 2026 12:27:37 +0000 Subject: [PATCH 7/8] Core: Use IPP as const& --- src/common/ipp.h | 3 +++ src/map/map_networking.cpp | 4 ++-- src/map/map_networking.h | 21 +++++++++++++++------ src/map/map_socket.cpp | 2 +- src/map/map_socket.h | 4 ++-- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/common/ipp.h b/src/common/ipp.h index 5ff3198bd1f..5ed21f1130c 100644 --- a/src/common/ipp.h +++ b/src/common/ipp.h @@ -61,6 +61,9 @@ auto sockaddr2hostport(const sockaddr_in& addr) -> uint16; // // An IP-Port Pair // +// Even though this is a very simple type, cppcheck is unhappy if you pass it around by value, +// so pass it around by reference :( +// class IPP final { public: diff --git a/src/map/map_networking.cpp b/src/map/map_networking.cpp index 517faa97c41..99b85012d47 100644 --- a/src/map/map_networking.cpp +++ b/src/map/map_networking.cpp @@ -75,7 +75,7 @@ MapNetworking::MapNetworking(Scheduler& scheduler, MapStatistics& mapStatistics, } } -void MapNetworking::handle_incoming_packet(ByteSpan buffer, IPP ipp) +void MapNetworking::handle_incoming_packet(ByteSpan buffer, const IPP& ipp) { TracyZoneScoped; @@ -188,7 +188,7 @@ int32 MapNetworking::map_decipher_packet(uint8* buff, size_t buffsize, MapSessio return -1; } -int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* PSession, IPP ipp) +int32 MapNetworking::recv_parse(uint8* buff, size_t* buffsize, MapSession* PSession, const IPP& ipp) { TracyZoneScoped; diff --git a/src/map/map_networking.h b/src/map/map_networking.h index fd4149f576c..8d561636d95 100644 --- a/src/map/map_networking.h +++ b/src/map/map_networking.h @@ -50,12 +50,20 @@ class MapNetworking // TODO: Stop changing the buffsize size_t as we go along // TODO: All of these need to become coroutines // TODO: Properly use size_t or u32/i32 where appropriate, we do a lot of casting - void handle_incoming_packet(ByteSpan buffer, IPP ipp); + // TODO: Do better than returning -1 as an error code + void handle_incoming_packet(ByteSpan buffer, const IPP& ipp); - int32 map_decipher_packet(uint8*, size_t, MapSession*, blowfish_t*); // Decipher packet - int32 recv_parse(uint8*, size_t*, MapSession*, IPP ipp); // main function to parse recv packets - int32 parse(uint8*, size_t*, MapSession*); // main function parsing the packets - int32 send_parse(uint8*, size_t*, MapSession*, UsePreviousKey); // main function is building big packet + // Decipher packet + int32 map_decipher_packet(uint8* buff, size_t buffsize, MapSession* PSession, blowfish_t* pbfkey); + + // main function to parse recv packets + int32 recv_parse(uint8* buff, size_t* buffsize, MapSession* PSession, const IPP& ipp); + + // main function parsing the packets + int32 parse(uint8* buff, size_t* buffsize, MapSession* PSession); + + // main function is building big packet + int32 send_parse(uint8* buff, size_t* buffsize, MapSession* PSession, UsePreviousKey usePreviousKey); // // Packet Building @@ -94,7 +102,8 @@ class MapNetworking std::unique_ptr mapSocket_; MapConfig config_; - // TODO: We can probably dedupe these and move the main buffer into MapSocket + // TODO: We can probably dedupe these and move the main buffer into MapSocket, passing a span + // : to it back into here when we've got our buffer of network data. // TODO: Update the naming conventions of these NetworkBuffer PBuff; // Global packet clipboard NetworkBuffer PBuffCopy; // Copy of above, used to decrypt a second time if necessary. diff --git a/src/map/map_socket.cpp b/src/map/map_socket.cpp index 6d6f1ad9783..d2c3e0d8f1f 100644 --- a/src/map/map_socket.cpp +++ b/src/map/map_socket.cpp @@ -88,7 +88,7 @@ void MapSocket::receive() }); } -void MapSocket::send(IPP ipp, ByteSpan buffer) +void MapSocket::send(const IPP& ipp, ByteSpan buffer) { TracyZoneScoped; diff --git a/src/map/map_socket.h b/src/map/map_socket.h index fe8783794cb..2cfc6c04447 100644 --- a/src/map/map_socket.h +++ b/src/map/map_socket.h @@ -36,12 +36,12 @@ class MapSocket { public: - using ReceiveFn = std::function; + using ReceiveFn = std::function; MapSocket(Scheduler& scheduler, uint16 port, ReceiveFn onReceiveFn); ~MapSocket(); - void send(IPP ipp, ByteSpan buffer); + void send(const IPP& ipp, ByteSpan buffer); private: void receive(); From f7620e3e27ab3a443222bd4a64d041baba3782c3 Mon Sep 17 00:00:00 2001 From: Zach Toogood Date: Sat, 28 Mar 2026 12:49:35 +0000 Subject: [PATCH 8/8] Core: Streamline MapStatistics usage in MapNetworking --- src/map/map_engine.cpp | 1 + src/map/map_engine.h | 6 ++++-- src/map/map_networking.cpp | 40 ++++++++++++++++++++++---------------- src/map/map_networking.h | 8 +------- 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/map/map_engine.cpp b/src/map/map_engine.cpp index 2edfea3d158..be7772b7be2 100644 --- a/src/map/map_engine.cpp +++ b/src/map/map_engine.cpp @@ -241,6 +241,7 @@ auto MapEngine::init() -> Task persistVolatileServerVarsToken_ = scheduler_.intervalOnMainThread(kPersistVolatileServerVarsInterval, serverutils::PersistVolatileServerVars); pumpIPCToken_ = scheduler_.intervalOnMainThread(kIPCPumpInterval, message::handle_incoming); + flushStatisticsToken_ = scheduler_.intervalOnMainThread(kTimeServerTickInterval, std::bind(&MapNetworking::flushStatistics, networking_.get())); } zoneutils::TOTDChange(vanadiel_time::get_totd()); // This tells the zones to spawn stuff based on time of day conditions (such as undead at night) diff --git a/src/map/map_engine.h b/src/map/map_engine.h index 0c11744a61e..85513a51fac 100644 --- a/src/map/map_engine.h +++ b/src/map/map_engine.h @@ -88,13 +88,15 @@ class MapEngine final : public Engine // TODO: gameState() private: - Application& application_; - Scheduler& scheduler_; + Application& application_; + Scheduler& scheduler_; + Maybe mapCleanupToken_; Maybe mapGarbageCollectToken_; Maybe timeServerToken_; Maybe persistVolatileServerVarsToken_; Maybe pumpIPCToken_; + Maybe flushStatisticsToken_; std::unique_ptr mapStatistics_; std::unique_ptr networking_; diff --git a/src/map/map_networking.cpp b/src/map/map_networking.cpp index 99b85012d47..27ad856e7bf 100644 --- a/src/map/map_networking.cpp +++ b/src/map/map_networking.cpp @@ -53,6 +53,9 @@ MapNetworking::MapNetworking(Scheduler& scheduler, MapStatistics& mapStatistics, , mapStatistics_(mapStatistics) , mapIPP_(config.ipp) // TODO: Refactor to not use this, since we have config_ in here , config_(config) +, PBuff{} +, PBuffCopy{} +, PScratchBuffer{} { TracyZoneScoped; @@ -112,18 +115,23 @@ void MapNetworking::handle_incoming_packet(ByteSpan buffer, const IPP& ipp) // It could be beneficial to parse 0x00D here anyway. // - // Client failed to receive 0x00B, manually rebuild and resend it + // Client failed to receive 0x00B, manually rebuild it, and copy it into PBuff for + // re-sending // - GP_SERV_COMMAND_LOGOUT zonePacket(PSession->zone_type, PSession->zone_ipp); + mapStatistics_.increment(MapStatistics::Key::TotalPacketsToSendPerTick); preparePacket(PBuff.data(), PSession); - TotalPacketsToSendPerTick += 1; - + // Build the packet + GP_SERV_COMMAND_LOGOUT zonePacket(PSession->zone_type, PSession->zone_ipp); size = FFXI_HEADER_SIZE; zonePacket.setSequence(PSession->server_packet_id); - std::memcpy(PBuff.data() + size, &zonePacket, zonePacket.getSize()); + + // Copy into PBuff + // TODO: This memcpy is funky, we need to fix the API of BasicPacket and derived + // : packets. + std::memcpy(PBuff.data() + size, &(*zonePacket), zonePacket.getSize()); size += zonePacket.getSize(); auto maybePacketSize = compressPacket(PBuff.data(), size); @@ -135,7 +143,7 @@ void MapNetworking::handle_incoming_packet(ByteSpan buffer, const IPP& ipp) else { finalizePacket(PBuff.data(), &size, *maybePacketSize, PSession, UsePreviousKey::Yes); - TotalPacketsSentPerTick += 1; + mapStatistics_.increment(MapStatistics::Key::TotalPacketsSentPerTick); } // Increment sync count with every packet @@ -544,7 +552,7 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* PSess uint8 packets = 0; bool incrementKeyAfterEncrypt = false; - TotalPacketsToSendPerTick += static_cast(PChar->getPacketCount()); + mapStatistics_.increment(MapStatistics::Key::TotalPacketsToSendPerTick, static_cast(PChar->getPacketCount())); #ifdef LOG_OUTGOING_PACKETS PacketGuard::PrintPacketList(PChar); @@ -616,13 +624,15 @@ int32 MapNetworking::send_parse(uint8* buff, size_t* buffsize, MapSession* PSess } while (PacketSize == static_cast(-1)); PChar->erasePackets(packets); - TotalPacketsSentPerTick += packets; + + mapStatistics_.increment(MapStatistics::Key::TotalPacketsSentPerTick, static_cast(packets)); TracyZoneString(fmt::format("Sending {} packets", packets)); finalizePacket(buff, buffsize, PacketSize, PSession, usePreviousKey); auto remainingPackets = PChar->getPacketCount(); - TotalPacketsDelayedPerTick += static_cast(remainingPackets); + mapStatistics_.increment(MapStatistics::Key::TotalPacketsDelayedPerTick, static_cast(remainingPackets)); + if (settings::get("logging.DEBUG_PACKET_BACKLOG")) { TracyZoneString(fmt::format("{} packets remaining", remainingPackets)); @@ -747,7 +757,7 @@ void MapNetworking::finalizePacket(uint8* buff, size_t* buffsize, size_t PacketS *buffsize = PacketSize + FFXI_HEADER_SIZE; } -void MapNetworking::tapStatistics() +void MapNetworking::flushStatistics() { // Collect statistics // TODO: Collect these inline @@ -770,9 +780,6 @@ void MapNetworking::tapStatistics() } // Set statistics - mapStatistics_.set(MapStatistics::Key::TotalPacketsToSendPerTick, TotalPacketsToSendPerTick); - mapStatistics_.set(MapStatistics::Key::TotalPacketsSentPerTick, TotalPacketsSentPerTick); - mapStatistics_.set(MapStatistics::Key::TotalPacketsDelayedPerTick, TotalPacketsDelayedPerTick); mapStatistics_.set(MapStatistics::Key::ActiveZones, activeZoneCount); mapStatistics_.set(MapStatistics::Key::ConnectedPlayers, playerCount); mapStatistics_.set(MapStatistics::Key::ActiveMobs, mobCount); @@ -780,12 +787,11 @@ void MapNetworking::tapStatistics() const auto percent = dynamicTargIdCapacity > 0 ? static_cast(dynamicTargIdCount) / static_cast(dynamicTargIdCapacity) * 100.0 : 0.0; + mapStatistics_.set(MapStatistics::Key::DynamicTargIdUsagePercent, static_cast(percent)); - // Clear statistics - TotalPacketsToSendPerTick = 0U; - TotalPacketsSentPerTick = 0U; - TotalPacketsDelayedPerTick = 0U; + // This also zeroes out all the stats + mapStatistics_.flush(); } auto MapNetworking::ipp() const -> IPP diff --git a/src/map/map_networking.h b/src/map/map_networking.h index 8d561636d95..3ab7bcd9357 100644 --- a/src/map/map_networking.h +++ b/src/map/map_networking.h @@ -83,7 +83,7 @@ class MapNetworking // Utils // - void tapStatistics(); + void flushStatistics(); // // Accessors @@ -108,10 +108,4 @@ class MapNetworking NetworkBuffer PBuff; // Global packet clipboard NetworkBuffer PBuffCopy; // Copy of above, used to decrypt a second time if necessary. NetworkBuffer PScratchBuffer; // Temporary packet clipboard - - // TODO: Move these to MapStatistics - // TODO: Update the naming conventions of these - uint32 TotalPacketsToSendPerTick{ 0U }; - uint32 TotalPacketsSentPerTick{ 0U }; - uint32 TotalPacketsDelayedPerTick{ 0U }; };