diff --git a/tools/compute_overte_protocol.py b/tools/compute_overte_protocol.py new file mode 100644 index 0000000..73df5b3 --- /dev/null +++ b/tools/compute_overte_protocol.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +""" +Compute Overte protocol signature from local source code. +This reads the actual PacketHeaders.h to get accurate enum counts. +""" + +import hashlib +import struct +import re +import sys +from pathlib import Path + +def count_enum_members(enum_text): + """Count comma-separated members in an enum""" + # Remove comments and count commas + lines = enum_text.split('\n') + count = 0 + for line in lines: + # Remove line comments + line = re.sub(r'//.*$', '', line) + if ',' in line and not line.strip().startswith('//'): + count += 1 + return count + +def parse_header(header_path): + """Parse PacketHeaders.h to get enum counts""" + with open(header_path) as f: + content = f.read() + + # Extract PacketType enum + packet_type_match = re.search( + r'enum class Value : uint8_t \{(.+?)NUM_PACKET_TYPE', + content, re.DOTALL + ) + if not packet_type_match: + print("ERROR: Could not find PacketType enum") + return None + + packet_type_count = count_enum_members(packet_type_match.group(1)) + + # Extract EntityVersion enum + entity_version_match = re.search( + r'enum class EntityVersion[^{]+\{(.+?)NUM_PACKET_TYPE', + content, re.DOTALL + ) + entity_version_count = count_enum_members(entity_version_match.group(1)) if entity_version_match else 0 + + print(f"PacketType count: {packet_type_count}") + print(f"EntityVersion count: {entity_version_count}") + print(f"EntityVersion::LAST_PACKET_TYPE = {entity_version_count - 1}") + + return { + 'num_packet_types': packet_type_count, + 'entity_version_last': entity_version_count - 1, # LAST = NUM - 1 + } + +def compute_signature(counts): + """Compute protocol signature based on versionForPacketType logic""" + num_types = counts['num_packet_types'] + entity_last = counts['entity_version_last'] + + # Default version (changed to 22 in 2025.05.1, was 23 in master) + versions = [22] * num_types + + # Apply specific overrides from versionForPacketType() + # Based on the switch statement in PacketHeaders.cpp + + # Packet type indices (from enum class Value) + versions[1] = 17 # DomainConnectRequestPending + versions[2] = 25 # DomainList::SocketTypes + + # Entity packets use EntityVersion::LAST_PACKET_TYPE + for pt in [23, 88, 25, 21, 68]: # EntityAdd, EntityClone, EntityEdit, EntityData, EntityPhysics + versions[pt] = entity_last + + versions[22] = 22 # EntityQuery::ConicalFrustums (same as default in this version) + + # Avatar packets - RemoveAttachments (assume 25) + for pt in [29, 6, 11, 5]: # AvatarIdentity, AvatarData, BulkAvatarData, KillAvatar + versions[pt] = 25 + + versions[57] = 18 # MessagesData::TextOrBinaryData + + # ICE packets + ice_17 = [18, 63, 19, 40] # Indices for ICEServerPeerInfo, HeartbeatACK, Query, PingReply + for pt in ice_17: + versions[pt] = 17 + versions[38] = 18 # ICEServerHeartbeat + versions[39] = 18 # ICEPing::SendICEPeerID + + # Asset packets - BakingTextureMeta (assume 24) + for pt in [61, 62, 53, 49, 51]: # AssetMappingOp, AssetMappingOpReply, GetInfo, Get, Upload + versions[pt] = 24 + + versions[30] = 18 # NodeIgnoreRequest + versions[16] = 18 # DomainConnectionDenied::IncludesExtraInfo + + # Socket type packets (25) + for pt in [31, 13, 17]: # DomainConnectRequest, DomainListRequest, DomainServerAddedNode + versions[pt] = 25 + + versions[92] = 19 # EntityScriptCallMethod::ClientCallable + + # Audio packets - StopInjectors (24) + for pt in [8, 12, 7, 9, 10, 18, 103]: # MixedAudio, SilentAudioFrame, InjectAudio, MicNoEcho, MicWithEcho, AudioStreamStats, StopInjector + if pt < num_types: + versions[pt] = 24 + + versions[48] = 18 # DomainSettings + versions[3] = 18 # Ping::IncludeConnectionID + versions[72] = 22 # AvatarQuery::ConicalFrustums + + # These use EntityVersion values + versions[89] = 68 # EntityQueryInitialResultsComplete::ParticleSpin (guess - need exact value) + versions[102] = 26 # BulkAvatarTraitsAck::AvatarTraitsAck + versions[90] = 26 # BulkAvatarTraits + + # Compute MD5 + data = struct.pack('B', num_types) + for v in versions: + data += struct.pack('B', v & 0xFF) + + md5 = hashlib.md5(data).digest() + return md5, versions + +def main(): + # Find PacketHeaders.h + overte_src = Path("/home/mayatheshy/stardust/starworld/third_party/overte-src") + header = overte_src / "libraries/networking/src/udt/PacketHeaders.h" + + if not header.exists(): + print(f"ERROR: Could not find {header}") + sys.exit(1) + + print(f"Parsing {header}") + counts = parse_header(header) + + if not counts: + sys.exit(1) + + signature, versions = compute_signature(counts) + + print(f"\nProtocol Signature:") + print(f" Hex: {signature.hex()}") + + import base64 + b64 = base64.b64encode(signature).decode() + print(f" Base64: {b64}") + + print(f"\nC++ code for NLPacketCodec.cpp:") + print("std::vector NLPacket::computeProtocolVersionSignature() {") + print(" std::vector signature = {") + print(f" {', '.join(f'0x{b:02x}' for b in signature)}") + print(" };") + print(" return signature;") + print("}") + + # Save to file for easy copy-paste + output_file = Path("/home/mayatheshy/stardust/starworld/tools/protocol_signature.txt") + with open(output_file, 'w') as f: + f.write(f"Hex: {signature.hex()}\n") + f.write(f"Base64: {b64}\n") + f.write(f"C++ array: {{{', '.join(f'0x{b:02x}' for b in signature)}}}\n") + print(f"\nSaved to: {output_file}") + +if __name__ == '__main__': + main()