216 lines
5.0 KiB
Go
216 lines
5.0 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"bufio"
|
||
|
|
"bytes"
|
||
|
|
"encoding/hex"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"io/ioutil"
|
||
|
|
"os"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
type LogEntry struct {
|
||
|
|
Timestamp string `json:"timestamp"`
|
||
|
|
Protocol string `json:"protocol"`
|
||
|
|
Type string `json:"type"`
|
||
|
|
Summary string `json:"summary"`
|
||
|
|
Data interface{} `json:"data"`
|
||
|
|
}
|
||
|
|
|
||
|
|
func main() {
|
||
|
|
// Hardcode for reliability
|
||
|
|
logFile := "session_20260107_174406.jsonl"
|
||
|
|
|
||
|
|
if logFile == "" {
|
||
|
|
panic("No session log found")
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Printf("Reading %s...\n", logFile)
|
||
|
|
f, err := os.Open(logFile)
|
||
|
|
if err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
defer f.Close()
|
||
|
|
|
||
|
|
scanner := bufio.NewScanner(f)
|
||
|
|
// Larger buffer for huge lines
|
||
|
|
buf := make([]byte, 1024*1024)
|
||
|
|
scanner.Buffer(buf, 1024*1024)
|
||
|
|
|
||
|
|
for scanner.Scan() {
|
||
|
|
var entry LogEntry
|
||
|
|
if err := json.Unmarshal(scanner.Bytes(), &entry); err != nil {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
// Looking for the huge packet
|
||
|
|
// Summary: "Packet 1 | Req 256 | Len 24182" (or similar length)
|
||
|
|
if entry.Protocol == "TCP" && strings.Contains(entry.Summary, "Len") {
|
||
|
|
// Check if Data has hex
|
||
|
|
dataMap, ok := entry.Data.(map[string]interface{})
|
||
|
|
if !ok {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
hexData, ok := dataMap["hex"].(string)
|
||
|
|
if !ok || len(hexData) < 20000 { // Look for huge payload
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
|
||
|
|
fmt.Println("Found candidate packet:", entry.Summary)
|
||
|
|
packetBytes, err := hex.DecodeString(hexData)
|
||
|
|
if err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse wrapper
|
||
|
|
// We assume the payload of the wrapper is the Message
|
||
|
|
// Wrapper: [Tag1:PacketID] [Tag3:MessageBytes]
|
||
|
|
// We want MessageBytes.
|
||
|
|
// Simple parser: skip Tag1/Val1. Find Tag3 (0x1a).
|
||
|
|
// 08 02 (Packet ID 2) -> 2 bytes.
|
||
|
|
// 1a (Tag 3) -> 1 byte.
|
||
|
|
// Length (Varint).
|
||
|
|
|
||
|
|
// Heuristic search for 1a and length
|
||
|
|
// Or simple protobuf reader
|
||
|
|
r := bytes.NewReader(packetBytes)
|
||
|
|
// Skip Field 1 (08 xx)
|
||
|
|
b, _ := r.ReadByte()
|
||
|
|
if b == 0x08 {
|
||
|
|
// Read varint
|
||
|
|
for {
|
||
|
|
b, _ := r.ReadByte()
|
||
|
|
if b < 0x80 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Might be diff packet ID? Log said Packet 1. 08 01.
|
||
|
|
fmt.Println("Warning: Expected 0x08 tag, got", b)
|
||
|
|
// Reset and try to find 0x1a anyway
|
||
|
|
r.Seek(0, 0)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Read Tag
|
||
|
|
for {
|
||
|
|
b, err := r.ReadByte()
|
||
|
|
if err != nil {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
if b == 0x1a { // Field 3
|
||
|
|
// This is likely the payload
|
||
|
|
fmt.Println("Found Field 3 tag (Message Payload)")
|
||
|
|
|
||
|
|
// Decode length
|
||
|
|
lenVal := uint64(0)
|
||
|
|
shift := uint(0)
|
||
|
|
for {
|
||
|
|
b, _ := r.ReadByte()
|
||
|
|
lenVal |= uint64(b&0x7F) << shift
|
||
|
|
if b < 0x80 {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
shift += 7
|
||
|
|
}
|
||
|
|
fmt.Printf("Payload Length: %d\n", lenVal)
|
||
|
|
|
||
|
|
payload := make([]byte, lenVal)
|
||
|
|
n, _ := r.Read(payload)
|
||
|
|
if uint64(n) != lenVal {
|
||
|
|
fmt.Println("Warning: Short read")
|
||
|
|
}
|
||
|
|
|
||
|
|
// Now we have the Message (RequestNumber + GameStatusReportRequest).
|
||
|
|
// RequestNumber is Field 1 (08 80 04 -> 512).
|
||
|
|
// GameStatusReportRequest is Field 512 (Tag 82 20 -> 1002).
|
||
|
|
// We need the value of Field 512.
|
||
|
|
|
||
|
|
r2 := bytes.NewReader(payload)
|
||
|
|
|
||
|
|
for {
|
||
|
|
tag, err := readVarint(r2)
|
||
|
|
if err != nil {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
fieldNum := tag >> 3
|
||
|
|
wireType := tag & 7
|
||
|
|
|
||
|
|
if fieldNum == 512 {
|
||
|
|
fmt.Println("Found Field 512 (GameStatusReportRequest)")
|
||
|
|
// Read length
|
||
|
|
msgLen, _ := readVarint(r2)
|
||
|
|
msgBytes := make([]byte, msgLen)
|
||
|
|
r2.Read(msgBytes)
|
||
|
|
|
||
|
|
// msgBytes is GameStatusReportRequest.
|
||
|
|
// It contains status_report (Field 1).
|
||
|
|
// We want to save *msgBytes* so we can inject it into a new Message struct?
|
||
|
|
// Or extract StatusReport payload?
|
||
|
|
|
||
|
|
// The user wants to set GameStatusReportRequest.StatusReport.
|
||
|
|
// So check inside msgBytes for Field 1.
|
||
|
|
r3 := bytes.NewReader(msgBytes)
|
||
|
|
tag3, _ := readVarint(r3)
|
||
|
|
if (tag3 >> 3) == 1 {
|
||
|
|
fmt.Println("Found Field 1 (StatusReport)")
|
||
|
|
srLen, _ := readVarint(r3)
|
||
|
|
srBytes := make([]byte, srLen)
|
||
|
|
r3.Read(srBytes)
|
||
|
|
|
||
|
|
// Wait, is StatusReport just one field?
|
||
|
|
// The log showed StatusReport had GameID, Status, Players...
|
||
|
|
// Ah, GameStatusReportRequest message only has ONE field: `status_report`.
|
||
|
|
// So `msgBytes` is the wire bytes for `GameStatusReportRequest`.
|
||
|
|
// If we assume `main.go` has `GameStatusReportRequest` struct, we can unmarshal `msgBytes` into it.
|
||
|
|
// YES.
|
||
|
|
|
||
|
|
err = ioutil.WriteFile("mock_gamestatus.bin", msgBytes, 0644)
|
||
|
|
if err != nil {
|
||
|
|
panic(err)
|
||
|
|
}
|
||
|
|
fmt.Println("Saved mock_gamestatus.bin")
|
||
|
|
return
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Skip
|
||
|
|
skip(r2, wireType)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func readVarint(r *bytes.Reader) (uint64, error) {
|
||
|
|
x := uint64(0)
|
||
|
|
s := uint(0)
|
||
|
|
for {
|
||
|
|
b, err := r.ReadByte()
|
||
|
|
if err != nil {
|
||
|
|
return 0, err
|
||
|
|
}
|
||
|
|
x |= uint64(b&0x7F) << s
|
||
|
|
if b < 0x80 {
|
||
|
|
return x, nil
|
||
|
|
}
|
||
|
|
s += 7
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func skip(r *bytes.Reader, wireType uint64) {
|
||
|
|
switch wireType {
|
||
|
|
case 0:
|
||
|
|
readVarint(r)
|
||
|
|
case 1:
|
||
|
|
r.Seek(8, 1)
|
||
|
|
case 2:
|
||
|
|
l, _ := readVarint(r)
|
||
|
|
r.Seek(int64(l), 1)
|
||
|
|
case 5:
|
||
|
|
r.Seek(4, 1)
|
||
|
|
}
|
||
|
|
}
|