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) } }