#include "GameMessageDecoder.h" #include #include #include #include #include "GameMessages.h" #include "StartSkill.h" #include "SyncSkill.h" #include "EchoStartSkill.h" #include "EchoSyncSkill.h" #include "RequestServerProjectileImpact.h" #include "DoClientProjectileImpact.h" #include "MessageCapture.h" namespace { using json = nlohmann::json; using Decoder = std::function(RakNet::BitStream&)>; // Object IDs as strings: they don't fit in a JavaScript number std::string Id(LWOOBJID id) { return std::to_string(id); } json Point(const NiPoint3& point) { return json::array({ point.x, point.y, point.z }); } json Rotation(const NiQuaternion& rotation) { return json::array({ rotation.x, rotation.y, rotation.z, rotation.w }); } // A skill's behavior data, which is its own bit stream json Bytes(const std::string& bytes) { return MessageCapture::ToHex(bytes); } // Reads T with its Deserialize, then lists its fields with `fields` template Decoder Make(std::function fields) { return [fields](RakNet::BitStream& stream) -> std::optional { T message; if (!message.Deserialize(stream)) return std::nullopt; json out = json::object(); fields(message, out); return out; }; } // (to server, message ID) -> decoder const std::map, Decoder>& Decoders() { using namespace GameMessages; using enum MessageType::Game; static const std::map, Decoder> decoders{ // Sent by the client { { true, REQUEST_USE }, Make([](const RequestUse& m, json& j) { j = { {"bIsMultiInteractUse", m.bIsMultiInteractUse}, {"multiInteractID", m.multiInteractID}, {"multiInteractType", m.multiInteractType}, {"object", Id(m.object)}, {"secondary", m.secondary} }; }) }, { { true, REQUEST_SERVER_OBJECT_INFO }, Make([](const RequestServerObjectInfo& m, json& j) { j = { {"bVerbose", m.bVerbose}, {"clientId", Id(m.clientId)}, {"targetForReport", Id(m.targetForReport)} }; }) }, { { true, SHOOTING_GALLERY_FIRE }, Make([](const ShootingGalleryFire& m, json& j) { j = { {"target", Point(m.target)}, {"rotation", Rotation(m.rotation)} }; }) }, { { true, PICKUP_ITEM }, Make([](const PickupItem& m, json& j) { j = { {"lootID", Id(m.lootID)}, {"lootOwnerID", Id(m.lootOwnerID)} }; }) }, { { true, START_SKILL }, Make([](const StartSkill& m, json& j) { j = { {"bUsedMouse", m.bUsedMouse}, {"consumableItemID", Id(m.consumableItemID)}, {"fCasterLatency", m.fCasterLatency}, {"iCastType", m.iCastType}, {"lastClickedPosit", Point(m.lastClickedPosit)}, {"optionalOriginatorID", Id(m.optionalOriginatorID)}, {"optionalTargetID", Id(m.optionalTargetID)}, {"originatorRot", Rotation(m.originatorRot)}, {"sBitStream", Bytes(m.sBitStream)}, {"skillID", m.skillID}, {"uiSkillHandle", m.uiSkillHandle} }; }) }, { { true, SYNC_SKILL }, Make([](const SyncSkill& m, json& j) { j = { {"bDone", m.bDone}, {"sBitStream", Bytes(m.sBitStream)}, {"uiBehaviorHandle", m.uiBehaviorHandle}, {"uiSkillHandle", m.uiSkillHandle} }; }) }, { { true, REQUEST_SERVER_PROJECTILE_IMPACT }, Make([](const RequestServerProjectileImpact& m, json& j) { j = { {"i64LocalID", Id(m.i64LocalID)}, {"i64TargetID", Id(m.i64TargetID)}, {"sBitStream", Bytes(m.sBitStream)} }; }) }, // Sent to the client { { false, ECHO_START_SKILL }, Make([](const EchoStartSkill& m, json& j) { j = { {"bUsedMouse", m.bUsedMouse}, {"fCasterLatency", m.fCasterLatency}, {"iCastType", m.iCastType}, {"lastClickedPosit", Point(m.lastClickedPosit)}, {"optionalOriginatorID", Id(m.optionalOriginatorID)}, {"optionalTargetID", Id(m.optionalTargetID)}, {"originatorRot", Rotation(m.originatorRot)}, {"sBitStream", Bytes(m.sBitStream)}, {"skillID", m.skillID}, {"uiSkillHandle", m.uiSkillHandle} }; }) }, { { false, ECHO_SYNC_SKILL }, Make([](const EchoSyncSkill& m, json& j) { j = { {"bDone", m.bDone}, {"sBitStream", Bytes(m.sBitStream)}, {"uiBehaviorHandle", m.uiBehaviorHandle}, {"uiSkillHandle", m.uiSkillHandle} }; }) }, { { false, DO_CLIENT_PROJECTILE_IMPACT }, Make([](const DoClientProjectileImpact& m, json& j) { j = { {"i64OrgID", Id(m.i64OrgID)}, {"i64OwnerID", Id(m.i64OwnerID)}, {"i64TargetID", Id(m.i64TargetID)}, {"sBitStream", Bytes(m.sBitStream)} }; }) }, }; return decoders; } } namespace GameMessageDecoder { bool CanDecode(MessageType::Game messageId, bool toServer) { return Decoders().contains({ toServer, messageId }); } std::optional Decode(MessageType::Game messageId, bool toServer, RakNet::BitStream& payload) { const auto it = Decoders().find({ toServer, messageId }); if (it == Decoders().end()) return std::nullopt; return it->second(payload); } }