diff --git a/src/NLPacketCodec.cpp b/src/NLPacketCodec.cpp index 21ee503..8f3ead8 100644 --- a/src/NLPacketCodec.cpp +++ b/src/NLPacketCodec.cpp @@ -4,6 +4,10 @@ #include #include #include +#include +#include +#include +#include namespace Overte { @@ -149,20 +153,230 @@ PacketType NLPacket::getType(const uint8_t* data, size_t size) { return static_cast(data[sizeof(uint32_t)]); } +// --- Helpers to parse Overte header enums to ensure exact version numbers --- +static std::string readFileToString(const std::string& path) { + std::ifstream in(path); + if (!in.is_open()) return {}; + std::ostringstream ss; ss << in.rdbuf(); + return ss.str(); +} + +static std::unordered_map parseEnumValues(const std::string& content, const std::string& enumName) { + std::unordered_map values; + std::string startToken = "enum class " + enumName; + auto startPos = content.find(startToken); + if (startPos == std::string::npos) return values; + auto bracePos = content.find('{', startPos); + if (bracePos == std::string::npos) return values; + auto endPos = content.find('};', bracePos); + if (endPos == std::string::npos) return values; + std::string body = content.substr(bracePos + 1, endPos - bracePos - 1); + + int current = -1; + std::istringstream lines(body); + std::string line; + while (std::getline(lines, line)) { + // strip comments + auto cpos = line.find("//"); + if (cpos != std::string::npos) line = line.substr(0, cpos); + // trim + auto notspace = [](int ch){ return !std::isspace(ch); }; + line.erase(line.begin(), std::find_if(line.begin(), line.end(), notspace)); + line.erase(std::find_if(line.rbegin(), line.rend(), notspace).base(), line.end()); + if (line.empty()) continue; + // split by comma; a line may have trailing comma + auto comma = line.find(','); + std::string token = (comma == std::string::npos) ? line : line.substr(0, comma); + // handle assignments + auto eq = token.find('='); + std::string name = token; + if (eq != std::string::npos) { + name = token.substr(0, eq); + std::string val = token.substr(eq + 1); + // trim name + name.erase(name.begin(), std::find_if(name.begin(), name.end(), notspace)); + name.erase(std::find_if(name.rbegin(), name.rend(), notspace).base(), name.end()); + // trim val + val.erase(val.begin(), std::find_if(val.begin(), val.end(), notspace)); + val.erase(std::find_if(val.rbegin(), val.rend(), notspace).base(), val.end()); + // numeric (no hex used in these enums) + try { current = std::stoi(val); } catch (...) { continue; } + } else { + // trim name + name.erase(name.begin(), std::find_if(name.begin(), name.end(), notspace)); + name.erase(std::find_if(name.rbegin(), name.rend(), notspace).base(), name.end()); + current = current + 1; + } + if (!name.empty()) values[name] = current; + } + return values; +} + +static int parsePacketTypeCount(const std::string& content) { + // Count identifiers in PacketTypeEnum::Value until NUM_PACKET_TYPE + auto pos = content.find("enum class Value : uint8_t"); + if (pos == std::string::npos) return 106; // fallback + auto brace = content.find('{', pos); + if (brace == std::string::npos) return 106; + auto end = content.find("NUM_PACKET_TYPE", brace); + if (end == std::string::npos) return 106; + std::string body = content.substr(brace + 1, end - brace - 1); + int count = 0; + std::istringstream lines(body); + std::string line; + while (std::getline(lines, line)) { + auto cpos = line.find("//"); if (cpos != std::string::npos) line = line.substr(0, cpos); + auto notspace = [](int ch){ return !std::isspace(ch); }; + line.erase(line.begin(), std::find_if(line.begin(), line.end(), notspace)); + line.erase(std::find_if(line.rbegin(), line.rend(), notspace).base(), line.end()); + if (line.empty()) continue; + if (line.find('=') != std::string::npos) { + // handle explicit value lines (rare in this enum) + count++; + } else if (line.find(',') != std::string::npos) { + count++; + } + } + return count; // this should equal NUM_PACKET_TYPE +} + +static void ensureVersionTable(uint8_t& vAvatarRemoveAttachments, + uint8_t& vAvatarTraitsAck, + uint8_t& vEntityLastPacket, + uint8_t& vAssetBakingTextureMeta, + uint8_t& vEntityScriptClientCallable, + uint8_t& vEntityQueryCbor, + uint8_t& vDomainServerAddedNodeSocketTypes, + uint8_t& vDomainListSocketTypes, + uint8_t& vDomainListRequestSocketTypes, + uint8_t& vDomainConnectionDeniedExtraInfo, + uint8_t& vPingIncludeConnID, + uint8_t& vIcePingSendPeerID, + uint8_t& vAudioStopInjectors, + int& numPacketTypes) +{ + static bool inited = false; + static uint8_t s_vAvatarRemoveAttachments, s_vAvatarTraitsAck, s_vEntityLastPacket, + s_vAssetBakingTextureMeta, s_vEntityScriptClientCallable, s_vEntityQueryCbor, + s_vDomainServerAddedNodeSocketTypes, s_vDomainListSocketTypes, + s_vDomainListRequestSocketTypes, s_vDomainConnectionDeniedExtraInfo, + s_vPingIncludeConnID, s_vIcePingSendPeerID, s_vAudioStopInjectors; + static int s_numPacketTypes; + if (!inited) { + std::string path = "third_party/overte-src/libraries/networking/src/udt/PacketHeaders.h"; + auto content = readFileToString(path); + if (!content.empty()) { + auto avatar = parseEnumValues(content, "AvatarMixerPacketVersion"); + auto entity = parseEnumValues(content, "EntityVersion"); + auto asset = parseEnumValues(content, "AssetServerPacketVersion"); + auto entScript = parseEnumValues(content, "EntityScriptCallMethodVersion"); + auto entQuery = parseEnumValues(content, "EntityQueryPacketVersion"); + auto domAdded = parseEnumValues(content, "DomainServerAddedNodeVersion"); + auto domList = parseEnumValues(content, "DomainListVersion"); + auto domListReq = parseEnumValues(content, "DomainListRequestVersion"); + auto domDenied = parseEnumValues(content, "DomainConnectionDeniedVersion"); + auto ping = parseEnumValues(content, "PingVersion"); + auto icePing = parseEnumValues(content, "IcePingVersion"); + auto audio = parseEnumValues(content, "AudioVersion"); + + s_vAvatarRemoveAttachments = static_cast(avatar["RemoveAttachments"]); + s_vAvatarTraitsAck = static_cast(avatar["AvatarTraitsAck"]); + // Entity LAST_PACKET_TYPE is number of entries - 1 before NUM_PACKET_TYPE + // If parsing map failed to give LAST_PACKET_TYPE, derive from count of entries before NUM_PACKET_TYPE label. + int entityCount = 0; + { + // Count entries until NUM_PACKET_TYPE in the EntityVersion enum body + auto ep = content.find("enum class EntityVersion"); + if (ep != std::string::npos) { + auto eb = content.find('{', ep); + auto ee = content.find("NUM_PACKET_TYPE", eb); + if (eb != std::string::npos && ee != std::string::npos) { + std::string body = content.substr(eb + 1, ee - eb - 1); + std::istringstream ls(body); + std::string l; + while (std::getline(ls, l)) { + auto cpos = l.find("//"); if (cpos != std::string::npos) l = l.substr(0, cpos); + auto notspace = [](int ch){ return !std::isspace(ch); }; + l.erase(l.begin(), std::find_if(l.begin(), l.end(), notspace)); + l.erase(std::find_if(l.rbegin(), l.rend(), notspace).base(), l.end()); + if (l.empty()) continue; + if (l.find(',') != std::string::npos) entityCount++; + } + } + } + } + s_vEntityLastPacket = entityCount > 0 ? static_cast(entityCount - 1) : 23; + s_vAssetBakingTextureMeta = static_cast(asset["BakingTextureMeta"]); + s_vEntityScriptClientCallable = static_cast(entScript["ClientCallable"]); + s_vEntityQueryCbor = static_cast(entQuery["CborData"]); + s_vDomainServerAddedNodeSocketTypes = static_cast(domAdded["SocketTypes"]); + s_vDomainListSocketTypes = static_cast(domList["SocketTypes"]); + s_vDomainListRequestSocketTypes = static_cast(domListReq["SocketTypes"]); + s_vDomainConnectionDeniedExtraInfo = static_cast(domDenied["IncludesExtraInfo"]); + s_vPingIncludeConnID = static_cast(ping["IncludeConnectionID"]); + s_vIcePingSendPeerID = static_cast(icePing["SendICEPeerID"]); + s_vAudioStopInjectors = static_cast(audio["StopInjectors"]); + s_numPacketTypes = parsePacketTypeCount(content); + inited = true; + } else { + // Fallback values (best-known) + s_vAvatarRemoveAttachments = 38; // conservative guess + s_vAvatarTraitsAck = 43; // guess + s_vEntityLastPacket = 99; // guess + s_vAssetBakingTextureMeta = 22; + s_vEntityScriptClientCallable = 19; + s_vEntityQueryCbor = 24; + s_vDomainServerAddedNodeSocketTypes = 19; + s_vDomainListSocketTypes = 25; + s_vDomainListRequestSocketTypes = 23; + s_vDomainConnectionDeniedExtraInfo = 19; + s_vPingIncludeConnID = 18; + s_vIcePingSendPeerID = 18; + s_vAudioStopInjectors = 24; + s_numPacketTypes = 106; + inited = true; + } + } + vAvatarRemoveAttachments = s_vAvatarRemoveAttachments; + vAvatarTraitsAck = s_vAvatarTraitsAck; + vEntityLastPacket = s_vEntityLastPacket; + vAssetBakingTextureMeta = s_vAssetBakingTextureMeta; + vEntityScriptClientCallable = s_vEntityScriptClientCallable; + vEntityQueryCbor = s_vEntityQueryCbor; + vDomainServerAddedNodeSocketTypes = s_vDomainServerAddedNodeSocketTypes; + vDomainListSocketTypes = s_vDomainListSocketTypes; + vDomainListRequestSocketTypes = s_vDomainListRequestSocketTypes; + vDomainConnectionDeniedExtraInfo = s_vDomainConnectionDeniedExtraInfo; + vPingIncludeConnID = s_vPingIncludeConnID; + vIcePingSendPeerID = s_vIcePingSendPeerID; + vAudioStopInjectors = s_vAudioStopInjectors; + numPacketTypes = s_numPacketTypes; +} + uint8_t NLPacket::versionForPacketType(PacketType type) { + uint8_t vAvatarRemoveAttachments, vAvatarTraitsAck, vEntityLastPacket, + vAssetBakingTextureMeta, vEntityScriptClientCallable, vEntityQueryCbor, + vDomainServerAddedNodeSocketTypes, vDomainListSocketTypes, vDomainListRequestSocketTypes, + vDomainConnectionDeniedExtraInfo, vPingIncludeConnID, vIcePingSendPeerID, vAudioStopInjectors; + int numPacketTypes = 106; + ensureVersionTable(vAvatarRemoveAttachments, vAvatarTraitsAck, vEntityLastPacket, + vAssetBakingTextureMeta, vEntityScriptClientCallable, vEntityQueryCbor, + vDomainServerAddedNodeSocketTypes, vDomainListSocketTypes, vDomainListRequestSocketTypes, + vDomainConnectionDeniedExtraInfo, vPingIncludeConnID, vIcePingSendPeerID, vAudioStopInjectors, + numPacketTypes); // Based on Overte's PacketHeaders.cpp versionForPacketType() // Returns the protocol version for each packet type switch (type) { case PacketType::DomainConnectRequest: return PacketVersions::DomainConnectRequest_SocketTypes; case PacketType::DomainListRequest: - return PacketVersions::DomainListRequest_SocketTypes; + return vDomainListRequestSocketTypes; case PacketType::DomainList: - return PacketVersions::DomainList_SocketTypes; + return vDomainListSocketTypes; case PacketType::Ping: - return PacketVersions::Ping_IncludeConnectionID; + return vPingIncludeConnID; case PacketType::DomainConnectionDenied: - return 19; // IncludesExtraInfo + return vDomainConnectionDeniedExtraInfo; case PacketType::DomainConnectRequestPending: return 17; case PacketType::PingReply: @@ -170,10 +384,43 @@ uint8_t NLPacket::versionForPacketType(PacketType type) { case PacketType::ICEServerPeerInformation: case PacketType::ICEServerQuery: return 17; + case PacketType::ICEPing: + return vIcePingSendPeerID; case PacketType::NodeIgnoreRequest: return 18; case PacketType::DomainServerAddedNode: - return 25; // SocketTypes + return vDomainServerAddedNodeSocketTypes; + case PacketType::EntityAdd: + case PacketType::EntityClone: + case PacketType::EntityEdit: + case PacketType::EntityData: + case PacketType::EntityPhysics: + return vEntityLastPacket; + case PacketType::EntityQuery: + return vEntityQueryCbor; + case PacketType::AvatarIdentity: + case PacketType::AvatarData: + case PacketType::BulkAvatarData: + case PacketType::KillAvatar: + return vAvatarRemoveAttachments; + case PacketType::MessagesData: + return 18; // TextOrBinaryData + case PacketType::AssetMappingOperation: + case PacketType::AssetMappingOperationReply: + case PacketType::AssetGetInfo: + case PacketType::AssetGet: + case PacketType::AssetUpload: + return vAssetBakingTextureMeta; + case PacketType::EntityScriptCallMethod: + return vEntityScriptClientCallable; + case PacketType::MixedAudio: + case PacketType::SilentAudioFrame: + case PacketType::InjectAudio: + case PacketType::MicrophoneAudioNoEcho: + case PacketType::MicrophoneAudioWithEcho: + case PacketType::AudioStreamStats: + case PacketType::StopInjector: + return vAudioStopInjectors; // For other packet types, return a default version // In real Overte, each has a specific version default: @@ -188,7 +435,12 @@ std::vector NLPacket::computeProtocolVersionSignature() { std::vector buffer; // Write number of packet types (256 max, but we'll use actual count) - uint8_t numPacketTypes = 106; // NUM_PACKET_TYPE from Overte + int dummyA=0,dummyB=0,dummyC=0,dummyD=0,dummyE=0,dummyF=0,dummyG=0,dummyH=0,dummyI=0,dummyJ=0,dummyK=0,dummyL=0,dummyM=0; + int numPacketTypesInt = 106; + ensureVersionTable(*(uint8_t*)&dummyA, *(uint8_t*)&dummyB, *(uint8_t*)&dummyC, *(uint8_t*)&dummyD, *(uint8_t*)&dummyE, + *(uint8_t*)&dummyF, *(uint8_t*)&dummyG, *(uint8_t*)&dummyH, *(uint8_t*)&dummyI, *(uint8_t*)&dummyJ, + *(uint8_t*)&dummyK, *(uint8_t*)&dummyL, *(uint8_t*)&dummyM, numPacketTypesInt); + uint8_t numPacketTypes = static_cast(numPacketTypesInt); buffer.push_back(numPacketTypes); // Write version for each packet type diff --git a/src/NLPacketCodec.hpp b/src/NLPacketCodec.hpp index 119b0d1..711533f 100644 --- a/src/NLPacketCodec.hpp +++ b/src/NLPacketCodec.hpp @@ -13,45 +13,113 @@ namespace Overte { // Packet types from Overte protocol enum class PacketType : uint8_t { - Unknown = 0, - DomainConnectRequestPending = 1, - DomainList = 2, - Ping = 3, - PingReply = 4, - KillAvatar = 5, - AvatarData = 6, - InjectAudio = 7, - MixedAudio = 8, - MicrophoneAudioNoEcho = 9, - MicrophoneAudioWithEcho = 10, - BulkAvatarData = 11, - SilentAudioFrame = 12, - DomainListRequest = 13, - RequestAssignment = 14, - CreateAssignment = 15, - DomainConnectionDenied = 16, - MuteEnvironment = 17, - AudioStreamStats = 18, - DomainServerPathQuery = 19, - DomainServerPathResponse = 20, - DomainServerAddedNode = 21, - ICEServerPeerInformation = 22, - ICEServerQuery = 23, - OctreeStats = 24, - SetAvatarTraits = 25, - InjectorGainSet = 26, - AssignmentClientStatus = 27, - NoisyMute = 28, - AvatarIdentity = 29, - NodeIgnoreRequest = 30, - DomainConnectRequest = 31, - DomainServerRequireDTLS = 32, - // ... many more packet types - EntityAdd = 0x41, - EntityEdit = 0x42, - EntityErase = 0x43, - EntityQuery = 0x44, - EntityData = 0x45, + Unknown, + DomainConnectRequestPending, + DomainList, + Ping, + PingReply, + KillAvatar, + AvatarData, + InjectAudio, + MixedAudio, + MicrophoneAudioNoEcho, + MicrophoneAudioWithEcho, + BulkAvatarData, + SilentAudioFrame, + DomainListRequest, + RequestAssignment, + CreateAssignment, + DomainConnectionDenied, + MuteEnvironment, + AudioStreamStats, + DomainServerPathQuery, + DomainServerPathResponse, + DomainServerAddedNode, + ICEServerPeerInformation, + ICEServerQuery, + OctreeStats, + SetAvatarTraits, + InjectorGainSet, + AssignmentClientStatus, + NoisyMute, + AvatarIdentity, + NodeIgnoreRequest, + DomainConnectRequest, + DomainServerRequireDTLS, + NodeJsonStats, + OctreeDataNack, + StopNode, + AudioEnvironment, + EntityEditNack, + ICEServerHeartbeat, + ICEPing, + ICEPingReply, + EntityData, + EntityQuery, + EntityAdd, + EntityErase, + EntityEdit, + DomainServerConnectionToken, + DomainSettingsRequest, + DomainSettings, + AssetGet, + AssetGetReply, + AssetUpload, + AssetUploadReply, + AssetGetInfo, + AssetGetInfoReply, + DomainDisconnectRequest, + DomainServerRemovedNode, + MessagesData, + MessagesSubscribe, + MessagesUnsubscribe, + ICEServerHeartbeatDenied, + AssetMappingOperation, + AssetMappingOperationReply, + ICEServerHeartbeatACK, + NegotiateAudioFormat, + SelectedAudioFormat, + MoreEntityShapes, + NodeKickRequest, + NodeMuteRequest, + RadiusIgnoreRequest, + UsernameFromIDRequest, + UsernameFromIDReply, + AvatarQuery, + RequestsDomainListData, + PerAvatarGainSet, + EntityScriptGetStatus, + EntityScriptGetStatusReply, + ReloadEntityServerScript, + EntityPhysics, + EntityServerScriptLog, + AdjustAvatarSorting, + OctreeFileReplacement, + CollisionEventChanges, + ReplicatedMicrophoneAudioNoEcho, + ReplicatedMicrophoneAudioWithEcho, + ReplicatedInjectAudio, + ReplicatedSilentAudioFrame, + ReplicatedAvatarIdentity, + ReplicatedKillAvatar, + ReplicatedBulkAvatarData, + DomainContentReplacementFromUrl, + DropOnNextProtocolChange_1, + EntityScriptCallMethod, + DropOnNextProtocolChange_2, + DropOnNextProtocolChange_3, + OctreeDataFileRequest, + OctreeDataFileReply, + OctreeDataPersist, + EntityClone, + EntityQueryInitialResultsComplete, + BulkAvatarTraits, + AudioSoloRequest, + BulkAvatarTraitsAck, + StopInjector, + AvatarZonePresence, + WebRTCSignaling, + NUM_PACKET_TYPE }; using PacketVersion = uint8_t;