feat(pcap): complete replay system with filtering, metadata, and live replay

Wire ExcludeOpcodes config into RecordingConn so configured opcodes
(e.g. ping, nop, position) are filtered at record time. Add padded
metadata with in-place PatchMetadata to populate CharID/UserID after
login. Implement --mode replay using protbot's encrypted connection
with timing-aware packet sending, auto-ping response, concurrent
S→C collection, and byte-level payload diff reporting.
This commit is contained in:
Houmgaor
2026-02-23 19:34:30 +01:00
parent 7ef5efc549
commit f712e3c04d
14 changed files with 679 additions and 42 deletions

View File

@@ -2,11 +2,22 @@ package main
import (
"fmt"
"strings"
"erupe-ce/network"
"erupe-ce/network/pcap"
)
// maxPayloadDiffs is the maximum number of byte-level diffs to report per packet.
const maxPayloadDiffs = 16
// ByteDiff describes a single byte difference between expected and actual payloads.
type ByteDiff struct {
Offset int
Expected byte
Actual byte
}
// PacketDiff describes a difference between an expected and actual packet.
type PacketDiff struct {
Index int
@@ -14,10 +25,15 @@ type PacketDiff struct {
Actual *pcap.PacketRecord // nil if no response received
OpcodeMismatch bool
SizeDelta int
PayloadDiffs []ByteDiff // byte-level diffs (when opcodes match and sizes match)
}
func (d PacketDiff) String() string {
if d.Actual == nil {
if d.Expected.Opcode == 0 {
return fmt.Sprintf("#%d: unexpected extra response 0x%04X (%s)",
d.Index, d.Expected.Opcode, network.PacketID(d.Expected.Opcode))
}
return fmt.Sprintf("#%d: expected 0x%04X (%s), got no response",
d.Index, d.Expected.Opcode, network.PacketID(d.Expected.Opcode))
}
@@ -27,8 +43,21 @@ func (d PacketDiff) String() string {
d.Expected.Opcode, network.PacketID(d.Expected.Opcode),
d.Actual.Opcode, network.PacketID(d.Actual.Opcode))
}
return fmt.Sprintf("#%d: 0x%04X (%s) size delta %+d bytes",
d.Index, d.Expected.Opcode, network.PacketID(d.Expected.Opcode), d.SizeDelta)
if d.SizeDelta != 0 {
return fmt.Sprintf("#%d: 0x%04X (%s) size delta %+d bytes",
d.Index, d.Expected.Opcode, network.PacketID(d.Expected.Opcode), d.SizeDelta)
}
if len(d.PayloadDiffs) > 0 {
var sb strings.Builder
fmt.Fprintf(&sb, "#%d: 0x%04X (%s) %d byte diff(s):",
d.Index, d.Expected.Opcode, network.PacketID(d.Expected.Opcode), len(d.PayloadDiffs))
for _, bd := range d.PayloadDiffs {
fmt.Fprintf(&sb, " [0x%04X: %02X→%02X]", bd.Offset, bd.Expected, bd.Actual)
}
return sb.String()
}
return fmt.Sprintf("#%d: 0x%04X (%s) unknown diff",
d.Index, d.Expected.Opcode, network.PacketID(d.Expected.Opcode))
}
// ComparePackets compares expected server responses against actual responses.
@@ -62,6 +91,17 @@ func ComparePackets(expected, actual []pcap.PacketRecord) []PacketDiff {
Actual: &act,
SizeDelta: len(act.Payload) - len(exp.Payload),
})
} else {
// Same opcode and size — check for byte-level diffs.
byteDiffs := comparePayloads(exp.Payload, act.Payload)
if len(byteDiffs) > 0 {
diffs = append(diffs, PacketDiff{
Index: i,
Expected: exp,
Actual: &act,
PayloadDiffs: byteDiffs,
})
}
}
}
@@ -77,3 +117,19 @@ func ComparePackets(expected, actual []pcap.PacketRecord) []PacketDiff {
return diffs
}
// comparePayloads returns byte-level diffs between two equal-length payloads.
// Returns at most maxPayloadDiffs entries.
func comparePayloads(expected, actual []byte) []ByteDiff {
var diffs []ByteDiff
for i := 0; i < len(expected) && len(diffs) < maxPayloadDiffs; i++ {
if expected[i] != actual[i] {
diffs = append(diffs, ByteDiff{
Offset: i,
Expected: expected[i],
Actual: actual[i],
})
}
}
return diffs
}