feat: implement protocol signature computation from Overte's PacketHeaders.h
This commit is contained in:
188
tools/compute_protocol_v2.py
Normal file
188
tools/compute_protocol_v2.py
Normal file
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Parse Overte PacketHeaders.cpp and compute protocol signature exactly as Overte does.
|
||||
This reads the actual versionForPacketType() implementation and evaluates it.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import struct
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
def parse_packet_type_enum(header_path):
|
||||
"""Parse the PacketType enum to get the count"""
|
||||
with open(header_path) as f:
|
||||
content = f.read()
|
||||
|
||||
# Find PacketType enum
|
||||
match = re.search(r'enum class Value : uint8_t \{(.+?)NUM_PACKET_TYPE', content, re.DOTALL)
|
||||
if not match:
|
||||
raise ValueError("Could not find PacketType enum")
|
||||
|
||||
# Count entries (count commas in non-comment lines)
|
||||
enum_body = match.group(1)
|
||||
lines = [line for line in enum_body.split('\n')
|
||||
if ',' in line and not line.strip().startswith('//')]
|
||||
|
||||
return len(lines)
|
||||
|
||||
def parse_entity_version_enum(header_path):
|
||||
"""Parse EntityVersion enum to get LAST_PACKET_TYPE value"""
|
||||
with open(header_path) as f:
|
||||
content = f.read()
|
||||
|
||||
match = re.search(r'enum class EntityVersion[^{]+\{(.+?)NUM_PACKET_TYPE', content, re.DOTALL)
|
||||
if not match:
|
||||
raise ValueError("Could not find EntityVersion enum")
|
||||
|
||||
enum_body = match.group(1)
|
||||
lines = [line for line in enum_body.split('\n')
|
||||
if ',' in line and not line.strip().startswith('//')]
|
||||
|
||||
# LAST_PACKET_TYPE = NUM - 1
|
||||
return len(lines) - 1
|
||||
|
||||
def get_enum_value_from_header(header_path, enum_name, value_name):
|
||||
"""Get an enum value by searching for it"""
|
||||
with open(header_path) as f:
|
||||
content = f.read()
|
||||
|
||||
# Find the enum
|
||||
pattern = rf'enum class {enum_name}[^{{]+\{{(.+?)\}};'
|
||||
match = re.search(pattern, content, re.DOTALL)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
enum_body = match.group(1)
|
||||
lines = enum_body.split('\n')
|
||||
|
||||
count = 0
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if not stripped or stripped.startswith('//') or stripped.startswith('*'):
|
||||
continue
|
||||
|
||||
# Check for explicit assignment
|
||||
if '=' in stripped and value_name in stripped:
|
||||
val_part = stripped.split('=')[1].strip().rstrip(',')
|
||||
try:
|
||||
return int(val_part)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Check if this is our value
|
||||
if stripped.startswith(value_name):
|
||||
return count
|
||||
|
||||
if ',' in stripped:
|
||||
count += 1
|
||||
|
||||
return None
|
||||
|
||||
def main():
|
||||
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: {header} not found")
|
||||
return 1
|
||||
|
||||
print(f"Parsing {header}")
|
||||
|
||||
num_packet_types = parse_packet_type_enum(header)
|
||||
entity_version_last = parse_entity_version_enum(header)
|
||||
|
||||
print(f"PacketType::NUM_PACKET_TYPE = {num_packet_types}")
|
||||
print(f"EntityVersion::LAST_PACKET_TYPE = {entity_version_last}")
|
||||
|
||||
# Get enum values we need
|
||||
socket_types = get_enum_value_from_header(header, "DomainListVersion", "SocketTypes")
|
||||
remove_attachments = get_enum_value_from_header(header, "AvatarMixerPacketVersion", "RemoveAttachments")
|
||||
conical_frustums = get_enum_value_from_header(header, "EntityQueryPacketVersion", "ConicalFrustums")
|
||||
|
||||
print(f"DomainListVersion::SocketTypes = {socket_types}")
|
||||
print(f"AvatarMixerPacketVersion::RemoveAttachments = {remove_attachments}")
|
||||
print(f"EntityQueryPacketVersion::ConicalFrustums = {conical_frustums}")
|
||||
|
||||
# Now build the version array based on versionForPacketType logic
|
||||
versions = [22] * num_packet_types # default
|
||||
|
||||
# Apply all the overrides from the switch statement
|
||||
versions[1] = 17 # DomainConnectRequestPending
|
||||
versions[2] = socket_types if socket_types else 25 # DomainList
|
||||
|
||||
# Entity packets
|
||||
for pt in [23, 88, 25, 21, 68]: # EntityAdd, EntityClone, EntityEdit, EntityData, EntityPhysics
|
||||
if pt < num_packet_types:
|
||||
versions[pt] = entity_version_last
|
||||
|
||||
versions[22] = conical_frustums if conical_frustums else 22 # EntityQuery
|
||||
|
||||
# Avatar packets
|
||||
remove_att_val = remove_attachments if remove_attachments else 25
|
||||
for pt in [29, 6, 11, 5]: # AvatarIdentity, AvatarData, BulkAvatarData, KillAvatar
|
||||
if pt < num_packet_types:
|
||||
versions[pt] = remove_att_val
|
||||
|
||||
versions[57] = 18 # MessagesData::TextOrBinaryData
|
||||
|
||||
# ICE packets
|
||||
for pt in [18, 63, 19, 40]:
|
||||
if pt < num_packet_types:
|
||||
versions[pt] = 17
|
||||
if 38 < num_packet_types: versions[38] = 18 # ICEServerHeartbeat
|
||||
if 39 < num_packet_types: versions[39] = 18 # ICEPing
|
||||
|
||||
# Asset packets
|
||||
for pt in [61, 62, 53, 49, 51]:
|
||||
if pt < num_packet_types:
|
||||
versions[pt] = 24
|
||||
|
||||
if 30 < num_packet_types: versions[30] = 18 # NodeIgnoreRequest
|
||||
if 16 < num_packet_types: versions[16] = 18 # DomainConnectionDenied
|
||||
|
||||
# SocketTypes packets
|
||||
for pt in [31, 13, 17]:
|
||||
if pt < num_packet_types:
|
||||
versions[pt] = socket_types if socket_types else 25
|
||||
|
||||
if 92 < num_packet_types: versions[92] = 19 # EntityScriptCallMethod
|
||||
|
||||
# Audio packets
|
||||
for pt in [8, 12, 7, 9, 10, 18, 103]:
|
||||
if pt < num_packet_types:
|
||||
versions[pt] = 24
|
||||
|
||||
if 48 < num_packet_types: versions[48] = 18
|
||||
if 3 < num_packet_types: versions[3] = 18
|
||||
if 72 < num_packet_types: versions[72] = 22
|
||||
if 89 < num_packet_types: versions[89] = 68 # ParticleSpin - need actual value
|
||||
if 102 < num_packet_types: versions[102] = 26
|
||||
if 90 < num_packet_types: versions[90] = 26
|
||||
|
||||
# Compute MD5 exactly as Overte does with QDataStream
|
||||
data = struct.pack('B', num_packet_types)
|
||||
for v in versions:
|
||||
data += struct.pack('B', v & 0xFF)
|
||||
|
||||
md5 = hashlib.md5(data).digest()
|
||||
|
||||
print(f"\nProtocol Signature:")
|
||||
print(f" Hex: {md5.hex()}")
|
||||
|
||||
import base64
|
||||
b64 = base64.b64encode(md5).decode()
|
||||
print(f" Base64: {b64}")
|
||||
|
||||
print(f"\nC++ code:")
|
||||
print("std::vector<uint8_t> signature = {")
|
||||
print(f" {', '.join(f'0x{b:02x}' for b in md5)}")
|
||||
print("};")
|
||||
|
||||
# Debug: show first 20 versions
|
||||
print(f"\nFirst 20 packet versions:")
|
||||
for i in range(min(20, num_packet_types)):
|
||||
print(f" [{i}] = {versions[i]}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user