init commit
This commit is contained in:
215
cmd/extract/main.go
Normal file
215
cmd/extract/main.go
Normal file
@@ -0,0 +1,215 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user