Captured a real mission egg delivered from the SheepShaver console to the DOSBox pod on TCP 1501, no -egg bypass: 8x ReceiveEggFileMessage chunks, 7514 bytes, reassembled byte-perfect (declared notationFileLength=7514). The egg is the operator's own mission (BattleTech / cavern / night / freeforall, pilot "cyd", vehicle madcat). This closes the networking workstream end to end. - net-tools/decode_egg.py: reassemble an egg from a captured console->pod TCP payload hex dump (skips interleaved StateQuery polls). - net-tools/captured_cavern_mission.egg: the delivered egg. - NET-NOTES.md: milestone writeup + the pod's mission-LOAD crash. The pod faults loading the mission (page fault, wild ptr 0xFF008B5B, exception 000E at game 00FF:219D, NetNub Munga exit 14) -- a game-side bug in the mission-load path, separate from networking. Leading suspect: RIO was disabled for this run and the player vehicle/control setup derefs null. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
40 lines
1.5 KiB
Python
40 lines
1.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Reassemble a mission egg from a captured console->pod TCP stream.
|
|
|
|
Input: a hex dump of the concatenated console->pod TCP payloads, e.g.
|
|
tshark -r cap.pcapng -Y 'ip.src==<console> && tcp.len>0' \
|
|
-T fields -e tcp.payload | tr -d '\n' > c2p.hex
|
|
|
|
The console delivers the mission as a series of ReceiveEggFileMessage
|
|
packets (decoded from CODE/RP/MUNGA/NETWORK.HPP, wire is little-endian):
|
|
NetworkPacketHeader (16B) + Receiver::Message hdr (12B: messageLength@+16,
|
|
messageID@+20, flags@+24) + [egg body when messageLength==1024:
|
|
sequenceNumber, notationFileLength, thisMessageLength, notationData[1000]].
|
|
StateQuery packets (messageLength=16) are interleaved and skipped.
|
|
|
|
Usage: python decode_egg.py c2p.hex out.egg
|
|
"""
|
|
import sys, struct
|
|
|
|
data = bytes.fromhex(open(sys.argv[1]).read())
|
|
out = sys.argv[2] if len(sys.argv) > 2 else "reassembled.egg"
|
|
|
|
i, chunks, total = 0, {}, None
|
|
while i + 28 <= len(data):
|
|
mlen, mid = struct.unpack("<ii", data[i+16:i+24])
|
|
if mlen < 12 or mlen > 4000:
|
|
break
|
|
pkt = data[i:i+16+mlen]
|
|
if len(pkt) < 16 + mlen:
|
|
break
|
|
if mlen == 1024: # ReceiveEggFileMessage
|
|
seq, flen, tlen = struct.unpack("<iii", pkt[28:40])
|
|
chunks[seq] = pkt[40:40+tlen]
|
|
total = flen
|
|
i += 16 + mlen
|
|
|
|
egg = b"".join(chunks[k] for k in sorted(chunks))
|
|
open(out, "wb").write(egg)
|
|
print(f"chunks={len(chunks)} reassembled={len(egg)}B declared={total} "
|
|
f"complete={total is not None and len(egg) >= total} -> {out}")
|