Files
Erupe/cmd/replay/compare.go
Houmgaor f712e3c04d 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.
2026-02-23 19:34:30 +01:00

136 lines
3.8 KiB
Go

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
Expected pcap.PacketRecord
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))
}
if d.OpcodeMismatch {
return fmt.Sprintf("#%d: opcode mismatch: expected 0x%04X (%s), got 0x%04X (%s)",
d.Index,
d.Expected.Opcode, network.PacketID(d.Expected.Opcode),
d.Actual.Opcode, network.PacketID(d.Actual.Opcode))
}
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.
// Only compares S→C packets (server responses).
func ComparePackets(expected, actual []pcap.PacketRecord) []PacketDiff {
expectedS2C := pcap.FilterByDirection(expected, pcap.DirServerToClient)
actualS2C := pcap.FilterByDirection(actual, pcap.DirServerToClient)
var diffs []PacketDiff
for i, exp := range expectedS2C {
if i >= len(actualS2C) {
diffs = append(diffs, PacketDiff{
Index: i,
Expected: exp,
Actual: nil,
})
continue
}
act := actualS2C[i]
if exp.Opcode != act.Opcode {
diffs = append(diffs, PacketDiff{
Index: i,
Expected: exp,
Actual: &act,
OpcodeMismatch: true,
})
} else if len(exp.Payload) != len(act.Payload) {
diffs = append(diffs, PacketDiff{
Index: i,
Expected: exp,
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,
})
}
}
}
// Extra actual packets beyond expected.
for i := len(expectedS2C); i < len(actualS2C); i++ {
act := actualS2C[i]
diffs = append(diffs, PacketDiff{
Index: i,
Expected: pcap.PacketRecord{},
Actual: &act,
})
}
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
}