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)
|
||||||
|
}
|
||||||
|
}
|
||||||
441
cmd/proxy/main.go
Normal file
441
cmd/proxy/main.go
Normal file
@@ -0,0 +1,441 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config
|
||||||
|
var (
|
||||||
|
TargetHTTPHost = "https://api.asmodee.net"
|
||||||
|
TargetTCPHost = "got.games.asmodee.net"
|
||||||
|
TargetTCPPort = "2445"
|
||||||
|
ProxyHTTPPort = ":8080"
|
||||||
|
ProxyTCPPort = ":3000"
|
||||||
|
LogFileName = ""
|
||||||
|
LogFile *os.File
|
||||||
|
LogMutex sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// Log Entry Structure
|
||||||
|
type LogEntry struct {
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
Protocol string `json:"protocol"` // HTTP or TCP
|
||||||
|
Type string `json:"type"` // REQUEST or RESPONSE
|
||||||
|
Summary string `json:"summary"` // e.g. "GET /v1/foo" or "Packet 123"
|
||||||
|
Data interface{} `json:"data"` // Structured data or raw info
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to JSONL
|
||||||
|
func logJSON(entry LogEntry) {
|
||||||
|
entry.Timestamp = time.Now().Format(time.RFC3339Nano)
|
||||||
|
LogMutex.Lock()
|
||||||
|
defer LogMutex.Unlock()
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(entry)
|
||||||
|
if err == nil {
|
||||||
|
if LogFile != nil {
|
||||||
|
LogFile.Write(bytes)
|
||||||
|
LogFile.WriteString("\n")
|
||||||
|
} else {
|
||||||
|
fmt.Println(string(bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.StringVar(&TargetHTTPHost, "target-http", "https://api.asmodee.net", "Target HTTP Host")
|
||||||
|
flag.StringVar(&TargetTCPHost, "target-tcp-host", "got.games.asmodee.net", "Target TCP Host")
|
||||||
|
flag.StringVar(&TargetTCPPort, "target-tcp-port", "2445", "Target TCP Port")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
LogFileName = fmt.Sprintf("session_%s.jsonl", time.Now().Format("20060102_150405"))
|
||||||
|
f, err := os.Create(LogFileName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
LogFile = f
|
||||||
|
defer LogFile.Close()
|
||||||
|
|
||||||
|
fmt.Printf("Starting Proxy...\nLog: %s\nHTTP Target: %s\nTCP Target Default: %s:%s\n", LogFileName, TargetHTTPHost, TargetTCPHost, TargetTCPPort)
|
||||||
|
|
||||||
|
// Start HTTP Proxy
|
||||||
|
go startHTTPProxy()
|
||||||
|
|
||||||
|
// Start TCP Proxy
|
||||||
|
startTCPProxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HTTP Proxy ---
|
||||||
|
|
||||||
|
func startHTTPProxy() {
|
||||||
|
http.HandleFunc("/", handleHTTPRequest)
|
||||||
|
fmt.Println("[HTTP] Listening on " + ProxyHTTPPort)
|
||||||
|
log.Fatal(http.ListenAndServe(ProxyHTTPPort, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleHTTPRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Log Request
|
||||||
|
bodyBytes, _ := ioutil.ReadAll(r.Body)
|
||||||
|
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Restore for forwarding
|
||||||
|
|
||||||
|
logJSON(LogEntry{
|
||||||
|
Protocol: "HTTP",
|
||||||
|
Type: "REQUEST",
|
||||||
|
Summary: fmt.Sprintf("%s %s", r.Method, r.URL.Path),
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"headers": r.Header,
|
||||||
|
"body": string(bodyBytes),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Forward Request
|
||||||
|
targetURL := TargetHTTPHost + r.URL.Path
|
||||||
|
if r.URL.RawQuery != "" {
|
||||||
|
targetURL += "?" + r.URL.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
proxyReq, err := http.NewRequest(r.Method, targetURL, bytes.NewBuffer(bodyBytes))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadGateway)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Copy headers
|
||||||
|
for name, values := range r.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
proxyReq.Header.Add(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove Hop-by-hop headers
|
||||||
|
proxyReq.Header.Del("Host") // Let http client set it
|
||||||
|
// Might need to set Host header manually if target expects it
|
||||||
|
// proxyReq.Host = ...
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(proxyReq)
|
||||||
|
if err != nil {
|
||||||
|
// Log Error
|
||||||
|
logJSON(LogEntry{
|
||||||
|
Protocol: "HTTP",
|
||||||
|
Type: "ERROR",
|
||||||
|
Summary: "Forwarding Failed",
|
||||||
|
Data: err.Error(),
|
||||||
|
})
|
||||||
|
http.Error(w, err.Error(), http.StatusBadGateway)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, _ := ioutil.ReadAll(resp.Body)
|
||||||
|
|
||||||
|
// Log Response
|
||||||
|
logJSON(LogEntry{
|
||||||
|
Protocol: "HTTP",
|
||||||
|
Type: "RESPONSE",
|
||||||
|
Summary: fmt.Sprintf("%s %d", r.URL.Path, resp.StatusCode),
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"status": resp.StatusCode,
|
||||||
|
"headers": resp.Header,
|
||||||
|
"body": string(respBody),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Sniff for TCP Info
|
||||||
|
// Look for "server": { "host": ... } or "HostName": ...
|
||||||
|
sniffTCPInfo(respBody)
|
||||||
|
|
||||||
|
// Write Response
|
||||||
|
for name, values := range resp.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
w.Header().Add(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
w.Write(respBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sniffTCPInfo(body []byte) {
|
||||||
|
// Search for "host":"..." and "port":... pattern
|
||||||
|
s := string(body)
|
||||||
|
if strings.Contains(s, "\"host\"") && strings.Contains(s, "\"port\"") {
|
||||||
|
idxHost := strings.Index(s, "\"host\"")
|
||||||
|
if idxHost != -1 {
|
||||||
|
// Find value
|
||||||
|
startQuote := strings.Index(s[idxHost:], ":") + idxHost + 1
|
||||||
|
startQuote = strings.Index(s[startQuote:], "\"") + startQuote
|
||||||
|
endQuote := strings.Index(s[startQuote+1:], "\"") + startQuote + 1
|
||||||
|
|
||||||
|
if startQuote != -1 && endQuote != -1 {
|
||||||
|
extractedHost := s[startQuote+1 : endQuote]
|
||||||
|
if extractedHost != "" {
|
||||||
|
fmt.Printf("[PROXY] Sniffed TCP Host: %s\n", extractedHost)
|
||||||
|
TargetTCPHost = extractedHost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idxPort := strings.Index(s, "\"port\"")
|
||||||
|
if idxPort != -1 {
|
||||||
|
// Find value (number)
|
||||||
|
colon := strings.Index(s[idxPort:], ":") + idxPort
|
||||||
|
// Find first digit after colon
|
||||||
|
startNum := -1
|
||||||
|
endNum := -1
|
||||||
|
for i := colon + 1; i < len(s); i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
if startNum == -1 {
|
||||||
|
startNum = i
|
||||||
|
}
|
||||||
|
} else if startNum != -1 {
|
||||||
|
// End of number
|
||||||
|
endNum = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if startNum != -1 {
|
||||||
|
if endNum == -1 {
|
||||||
|
endNum = len(s)
|
||||||
|
}
|
||||||
|
extractedPort := s[startNum:endNum]
|
||||||
|
fmt.Printf("[PROXY] Sniffed TCP Port: %s\n", extractedPort)
|
||||||
|
TargetTCPPort = extractedPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func findHostPortRecursive(obj interface{}) {
|
||||||
|
switch v := obj.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
if h, ok := v["host"].(string); ok {
|
||||||
|
TargetTCPHost = h
|
||||||
|
fmt.Printf("[PROXY] Sniffed TCP Host: %s\n", h)
|
||||||
|
}
|
||||||
|
if p, ok := v["port"].(float64); ok { // JSON numbers are float64 in generic interface
|
||||||
|
TargetTCPPort = fmt.Sprintf("%d", int(p))
|
||||||
|
fmt.Printf("[PROXY] Sniffed TCP Port: %s\n", TargetTCPPort)
|
||||||
|
}
|
||||||
|
for _, val := range v {
|
||||||
|
findHostPortRecursive(val)
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
for _, val := range v {
|
||||||
|
findHostPortRecursive(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TCP Proxy ---
|
||||||
|
|
||||||
|
func startTCPProxy() {
|
||||||
|
cert, err := generateSelfSignedCert()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := tls.Listen("tcp", ProxyTCPPort, cert)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("TCP Listen failed: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("[TCP] Listening on " + ProxyTCPPort)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("TCP Accept error:", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go handleTCPConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTCPConn(clientConn net.Conn) {
|
||||||
|
defer clientConn.Close()
|
||||||
|
|
||||||
|
// Connect to Real Server
|
||||||
|
targetAddr := fmt.Sprintf("%s:%s", TargetTCPHost, TargetTCPPort)
|
||||||
|
fmt.Printf("[TCP] New Connection. Forwarding to %s\n", targetAddr)
|
||||||
|
|
||||||
|
// Use TLS to connect to Real Server
|
||||||
|
serverConn, err := tls.Dial("tcp", targetAddr, &tls.Config{
|
||||||
|
InsecureSkipVerify: true, // We assume official server cert is fine but we skip verify to avoid setup
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to dial target %s: %v\n", targetAddr, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer serverConn.Close()
|
||||||
|
|
||||||
|
// Pipe
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
|
||||||
|
// Client -> Server
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
interceptAndLogTCP(clientConn, serverConn, "CLIENT->SERVER")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Server -> Client
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
interceptAndLogTCP(serverConn, clientConn, "SERVER->CLIENT")
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func interceptAndLogTCP(src, dst net.Conn, direction string) {
|
||||||
|
// We need to parse frames to log them effectively?
|
||||||
|
// The protocol is: 4 bytes length + Payload.
|
||||||
|
// Payload: Varint PacketID, Varint RequestID, Payload.
|
||||||
|
|
||||||
|
// We will try to read in a buffer loop, parse length prefix, log, then forward.
|
||||||
|
// But simply copying stream `io.Copy` is safer for latency.
|
||||||
|
// However, we want to LOG. So we MUST read packet by packet.
|
||||||
|
|
||||||
|
header := make([]byte, 4)
|
||||||
|
for {
|
||||||
|
// Read Head
|
||||||
|
_, err := io.ReadFull(src, header)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
length := binary.BigEndian.Uint32(header)
|
||||||
|
|
||||||
|
// Read Body
|
||||||
|
body := make([]byte, length)
|
||||||
|
_, err = io.ReadFull(src, body)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to Dst
|
||||||
|
// Write header + body
|
||||||
|
dst.Write(header)
|
||||||
|
dst.Write(body)
|
||||||
|
|
||||||
|
// Log
|
||||||
|
parseAndLogPacket(direction, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAndLogPacket(direction string, data []byte) {
|
||||||
|
// Basic Decode of Packet Wrapper
|
||||||
|
// Packet ID (Field 1, Varint)
|
||||||
|
// Payload (Field 3, Bytes) -> Message
|
||||||
|
|
||||||
|
r := bytes.NewReader(data)
|
||||||
|
|
||||||
|
packetID := int64(-1)
|
||||||
|
var payloadBytes []byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
tag, err := binary.ReadUvarint(r)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fieldNum := tag >> 3
|
||||||
|
wireType := tag & 7
|
||||||
|
|
||||||
|
if fieldNum == 1 && wireType == 0 {
|
||||||
|
packetID, _ = binary.ReadVarint(r) // ReadVarint for int64
|
||||||
|
} else if fieldNum == 3 && wireType == 2 {
|
||||||
|
l, _ := binary.ReadUvarint(r)
|
||||||
|
payloadBytes = make([]byte, l)
|
||||||
|
r.Read(payloadBytes)
|
||||||
|
} else {
|
||||||
|
// Skip
|
||||||
|
skip(r, wireType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found payload, parse Message
|
||||||
|
requestID := int64(-1)
|
||||||
|
if payloadBytes != nil {
|
||||||
|
pr := bytes.NewReader(payloadBytes)
|
||||||
|
for {
|
||||||
|
tag, err := binary.ReadUvarint(pr)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fieldNum := tag >> 3
|
||||||
|
wireType := tag & 7
|
||||||
|
|
||||||
|
if fieldNum == 1 && wireType == 0 { // Message.request_number
|
||||||
|
requestID, _ = binary.ReadVarint(pr)
|
||||||
|
} else {
|
||||||
|
skip(pr, wireType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logJSON(LogEntry{
|
||||||
|
Protocol: "TCP",
|
||||||
|
Type: direction,
|
||||||
|
Summary: fmt.Sprintf("Packet %d | Req %d | Len %d", packetID, requestID, len(data)),
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"packet_id": packetID,
|
||||||
|
"request_id": requestID,
|
||||||
|
"hex": hex.EncodeToString(data),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func skip(r *bytes.Reader, wireType uint64) {
|
||||||
|
switch wireType {
|
||||||
|
case 0:
|
||||||
|
binary.ReadUvarint(r)
|
||||||
|
case 1:
|
||||||
|
r.Seek(8, io.SeekCurrent)
|
||||||
|
case 2:
|
||||||
|
l, _ := binary.ReadUvarint(r)
|
||||||
|
r.Seek(int64(l), io.SeekCurrent)
|
||||||
|
case 5:
|
||||||
|
r.Seek(4, io.SeekCurrent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Cert Gen (Copied) ---
|
||||||
|
func generateSelfSignedCert() (*tls.Config, error) {
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{Organization: []string{"Proxy"}},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24),
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||||
|
DNSNames: []string{"localhost"},
|
||||||
|
}
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &tls.Config{Certificates: []tls.Certificate{{Certificate: [][]byte{derBytes}, PrivateKey: priv}}}, nil
|
||||||
|
}
|
||||||
24
cmd/server/main.go
Normal file
24
cmd/server/main.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"customServer/internal/network"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// The state manager initializes itself via init() in internal/state
|
||||||
|
|
||||||
|
// 1. HTTP Server for REST API (Port 8080)
|
||||||
|
go func() {
|
||||||
|
if err := network.StartHTTPServer(":8080"); err != nil {
|
||||||
|
log.Fatalf("HTTP server failed: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 2. TCP Server for Scalable Server (Port 3000) with SSL
|
||||||
|
fmt.Println("[Main] Starting TCP Server...")
|
||||||
|
if err := network.StartTCPServer(":3000"); err != nil {
|
||||||
|
log.Fatalf("TCP server failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
1022
cmd/server/main.go.bak
Normal file
1022
cmd/server/main.go.bak
Normal file
File diff suppressed because it is too large
Load Diff
1
cmd/server/mock_gamedata.b64
Normal file
1
cmd/server/mock_gamedata.b64
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CICggOjD6dXeNxAKGAMiVnsibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjE5MiwidHJzIjpbeyJlbHQiOls2NTNdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6MX19IlZ7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjo2NTMsInRycyI6W3siZWx0IjpbNTUxXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjF9fSJWeyJuYW1lIjoic3d0IiwidmFsdWUiOnsic2xuIjp7ImVpZCI6MjAyLCJ0cnMiOlt7ImVsdCI6WzYzOV0sIm5hbWUiOiJlbHRyIn1dfSwiY3RyIjoxfX0iVnsibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjYzOSwidHJzIjpbeyJlbHQiOls1NTFdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6MX19IlZ7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjoyMDIsInRycyI6W3siZWx0IjpbNjM5XSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjF9fSJWeyJuYW1lIjoic3d0IiwidmFsdWUiOnsic2xuIjp7ImVpZCI6MTkyLCJ0cnMiOlt7ImVsdCI6WzY2MV0sIm5hbWUiOiJlbHRyIn1dfSwiY3RyIjoxfX0iVnsibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjE5MCwidHJzIjpbeyJlbHQiOls2NjNdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6MX19IlZ7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjo2NjMsInRycyI6W3siZWx0IjpbNTUxXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjF9fSJWeyJuYW1lIjoic3d0IiwidmFsdWUiOnsic2xuIjp7ImVpZCI6MTkwLCJ0cnMiOlt7ImVsdCI6WzY0NV0sIm5hbWUiOiJlbHRyIn1dfSwiY3RyIjoxfX0iIHsibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjE5MiwidHJzIjpbeyJlbHQiOltdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6NTd9fSJXeyJuYW1lIjoic3d0IiwidmFsdWUiOnsic2xuIjp7ImVpZCI6MTc1LCJ0cnMiOlt7ImVsdCI6WzE2N10sIm5hbWUiOiJlbHRyIn1dfSwiY3RyIjo2MX19IlR7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjoxNjksInRycyI6W3siZWx0IjpbXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjYzfX0iV3sibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjI1MCwidHJzIjpbeyJlbHQiOlsxNTZdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6NzJ9fSIgeyJuYW1lIjoibXMiLCJ2YWx1ZSI6eyJjdHIiOjc1fX0iL3sibmFtZSI6ImdjYyIsInZhbHVlIjp7InNlbGVjdGlvbiI6MCwiY3RyIjo4MX19Ild7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjoyNTAsInRycyI6W3siZWx0IjpbMTUyXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjg4fX0iL3sibmFtZSI6ImdjYyIsInZhbHVlIjp7InNlbGVjdGlvbiI6MywiY3RyIjo5N319Ilh7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjo1NDMsInRycyI6W3siZWx0IjpbMTkyXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjExMX19IjB7Im5hbWUiOiJnY2MiLCJ2YWx1ZSI6eyJzZWxlY3Rpb24iOjQsImN0ciI6MTE4fX0iMHsibmFtZSI6ImdjYyIsInZhbHVlIjp7InNlbGVjdGlvbiI6MCwiY3RyIjoxMjJ9fToWOgIIAToECAIQBToECAMQAToECAQQBA==
|
||||||
72
internal/handlers/auth.go
Normal file
72
internal/handlers/auth.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"customServer/internal/protocol"
|
||||||
|
"customServer/internal/state"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleAsyncAuthRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling AsyncAuthRequest")
|
||||||
|
|
||||||
|
// Session Message
|
||||||
|
sessionData := make([]byte, 0)
|
||||||
|
// field 1 (Id), Varint (WireType 0)
|
||||||
|
sessionData = append(sessionData, 0x08)
|
||||||
|
sessionData = append(sessionData, protocol.EncodeVarint(uint64(time.Now().UnixNano()))...)
|
||||||
|
|
||||||
|
// Player Message
|
||||||
|
player := state.GetMockPlayerBytes()
|
||||||
|
|
||||||
|
// AsyncConnectedRequest
|
||||||
|
asyncConnected := make([]byte, 0)
|
||||||
|
// field 1 (Session), message
|
||||||
|
asyncConnected = append(asyncConnected, 0x0a)
|
||||||
|
asyncConnected = append(asyncConnected, protocol.EncodeVarint(uint64(len(sessionData)))...)
|
||||||
|
asyncConnected = append(asyncConnected, sessionData...)
|
||||||
|
|
||||||
|
// field 2 (Player), message
|
||||||
|
asyncConnected = append(asyncConnected, 0x12)
|
||||||
|
asyncConnected = append(asyncConnected, protocol.EncodeVarint(uint64(len(player)))...)
|
||||||
|
asyncConnected = append(asyncConnected, player...)
|
||||||
|
|
||||||
|
return asyncConnected, 406
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlePingRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling PingRequest")
|
||||||
|
// The client sends a PingRequest with a timestamp (field 1).
|
||||||
|
// We should respond with a PingRequest (777) containing the same timestamp.
|
||||||
|
|
||||||
|
// Extract timestamp if possible, otherwise just send back a default one
|
||||||
|
// For simplicity, we just echo back what we got if it's small, or send a new one.
|
||||||
|
return requestData, 777
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleAsyncDisconnectRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling AsyncDisconnectRequest")
|
||||||
|
return []byte{}, 401
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleAskServerStatisticsRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling AskServerStatisticsRequest")
|
||||||
|
// Return ServerStatisticsRequest (409)
|
||||||
|
// Fields: 1:HostedGames, 2:Players, 3:ConnectedPlayers (all int32)
|
||||||
|
stats := make([]byte, 0)
|
||||||
|
|
||||||
|
// Field 1: HostedGames (Varint)
|
||||||
|
stats = append(stats, 0x08)
|
||||||
|
stats = append(stats, protocol.EncodeVarint(1)...)
|
||||||
|
|
||||||
|
// Field 2: Players (Varint)
|
||||||
|
stats = append(stats, 0x10)
|
||||||
|
stats = append(stats, protocol.EncodeVarint(1)...)
|
||||||
|
|
||||||
|
// Field 3: ConnectedPlayers (Varint)
|
||||||
|
stats = append(stats, 0x18)
|
||||||
|
stats = append(stats, protocol.EncodeVarint(1)...)
|
||||||
|
|
||||||
|
return stats, 409
|
||||||
|
}
|
||||||
66
internal/handlers/dispatcher.go
Normal file
66
internal/handlers/dispatcher.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"customServer/internal/protocol"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Dispatcher Function
|
||||||
|
// Note: We might pass dependencies here if we want to avoid globals,
|
||||||
|
// but for this refactor we'll stick to dispatching to handler functions.
|
||||||
|
|
||||||
|
func Dispatch(conn net.Conn, packetID int64, requestNumber int32, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Printf("[Dispatcher] Dispatching Request %d (PacketID: %d)\n", requestNumber, packetID)
|
||||||
|
|
||||||
|
switch requestNumber {
|
||||||
|
// Auth
|
||||||
|
case 400:
|
||||||
|
return HandleAsyncAuthRequest(conn, requestData)
|
||||||
|
case 401:
|
||||||
|
return HandleAsyncDisconnectRequest(conn, requestData)
|
||||||
|
case 408:
|
||||||
|
return HandleAskServerStatisticsRequest(conn, requestData)
|
||||||
|
|
||||||
|
// System / Buddies
|
||||||
|
case 515:
|
||||||
|
return HandleAsyncBuddyListRequest(conn, requestData)
|
||||||
|
case 560:
|
||||||
|
return HandleAsyncIgnoreListRequest(conn, requestData)
|
||||||
|
|
||||||
|
// Lobby
|
||||||
|
case 600:
|
||||||
|
return HandleEnterLobbyRequest(conn, requestData)
|
||||||
|
case 604:
|
||||||
|
return HandleLobbyPlayerListRequest(conn, requestData)
|
||||||
|
case 607:
|
||||||
|
return HandleLobbyCreateGameRequest(conn, requestData)
|
||||||
|
case 609:
|
||||||
|
return HandleLobbyGameListRequest(conn, requestData)
|
||||||
|
case 610:
|
||||||
|
return HandleLobbyJoinGameRequest(conn, requestData)
|
||||||
|
case 622:
|
||||||
|
return HandleObservableGameListRequest(conn, requestData)
|
||||||
|
|
||||||
|
// Game
|
||||||
|
case 511:
|
||||||
|
return HandleWhatsNewPussycatRequest(conn, requestData)
|
||||||
|
case 608:
|
||||||
|
// Note: 608 is LobbyGameCreatedRequest (OUT), 607 is IN.
|
||||||
|
// If client sends 608, it's weird.
|
||||||
|
return nil, 0
|
||||||
|
|
||||||
|
// System / Ping
|
||||||
|
case 777:
|
||||||
|
return HandlePingRequest(conn, requestData)
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Printf("[Dispatcher] Unknown Request %d\n", requestNumber)
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to encode varint for handlers that need it locally
|
||||||
|
func encodeVarint(v uint64) []byte {
|
||||||
|
return protocol.EncodeVarint(v)
|
||||||
|
}
|
||||||
79
internal/handlers/game.go
Normal file
79
internal/handlers/game.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"customServer/internal/protocol"
|
||||||
|
"customServer/internal/state"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleWhatsNewPussycatRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling WhatsNewPussycatRequest")
|
||||||
|
|
||||||
|
mgr := state.GlobalManager
|
||||||
|
if mgr.HasActiveGame() {
|
||||||
|
activeGameID := mgr.GetActiveGameID()
|
||||||
|
fmt.Printf("[TCP] StatusReport: Reporting Active Game %d\n", activeGameID)
|
||||||
|
|
||||||
|
// StatusReport Message (Inner)
|
||||||
|
innerReport := make([]byte, 0)
|
||||||
|
|
||||||
|
// Field 1: GameId (int64)
|
||||||
|
innerReport = append(innerReport, 0x08)
|
||||||
|
innerReport = append(innerReport, protocol.EncodeVarint(uint64(activeGameID))...)
|
||||||
|
|
||||||
|
// Field 2: Status (Enum) = 1 (IN_PROGRESS)
|
||||||
|
innerReport = append(innerReport, 0x10)
|
||||||
|
innerReport = append(innerReport, protocol.EncodeVarint(uint64(protocol.GameStatus_IN_PROGRESS))...)
|
||||||
|
|
||||||
|
// Field 3: Data (Bytes) - Dynamic IronGameState
|
||||||
|
dataBytes := state.GetMockIronGameStateBytes()
|
||||||
|
innerReport = append(innerReport, 0x1a)
|
||||||
|
innerReport = append(innerReport, protocol.EncodeVarint(uint64(len(dataBytes)))...)
|
||||||
|
innerReport = append(innerReport, dataBytes...)
|
||||||
|
|
||||||
|
// Field 4: TurnId (int32) = 4
|
||||||
|
innerReport = append(innerReport, 0x20)
|
||||||
|
innerReport = append(innerReport, protocol.EncodeVarint(4)...)
|
||||||
|
|
||||||
|
// Field 5: NextPlayerIds (Repeated Int32)
|
||||||
|
nextPlayers := []uint64{uint64(state.MockUserID)}
|
||||||
|
npBytes := make([]byte, 0)
|
||||||
|
for _, pid := range nextPlayers {
|
||||||
|
npBytes = append(npBytes, protocol.EncodeVarint(pid)...)
|
||||||
|
}
|
||||||
|
innerReport = append(innerReport, 0x2a)
|
||||||
|
innerReport = append(innerReport, protocol.EncodeVarint(uint64(len(npBytes)))...)
|
||||||
|
innerReport = append(innerReport, npBytes...)
|
||||||
|
|
||||||
|
// Field 6: Players (Repeated Player)
|
||||||
|
mockPlayer := state.GetMockPlayerBytes()
|
||||||
|
innerReport = append(innerReport, 0x32)
|
||||||
|
innerReport = append(innerReport, protocol.EncodeVarint(uint64(len(mockPlayer)))...)
|
||||||
|
innerReport = append(innerReport, mockPlayer...)
|
||||||
|
|
||||||
|
// Field 14: Configuration (GameConfiguration)
|
||||||
|
fallbackConfig := getFallbackConfigBytes()
|
||||||
|
innerReport = append(innerReport, 0x72)
|
||||||
|
innerReport = append(innerReport, protocol.EncodeVarint(uint64(len(fallbackConfig)))...)
|
||||||
|
innerReport = append(innerReport, fallbackConfig...)
|
||||||
|
|
||||||
|
// Field 16: ActivePlayer (Int32)
|
||||||
|
innerReport = append(innerReport, 0x80, 0x01)
|
||||||
|
innerReport = append(innerReport, protocol.EncodeVarint(uint64(state.MockUserID))...)
|
||||||
|
|
||||||
|
// Wrap in GameStatusReportRequest (Field 1: repeated StatusReport)
|
||||||
|
responsePayload := make([]byte, 0)
|
||||||
|
responsePayload = append(responsePayload, 0x0a)
|
||||||
|
responsePayload = append(responsePayload, protocol.EncodeVarint(uint64(len(innerReport)))...)
|
||||||
|
responsePayload = append(responsePayload, innerReport...)
|
||||||
|
|
||||||
|
return responsePayload, 512
|
||||||
|
} else {
|
||||||
|
fmt.Println("[TCP] StatusReport: Reporting Idle/Lobby State (Dynamic)")
|
||||||
|
|
||||||
|
// Idle Status (Empty reports list)
|
||||||
|
responsePayload := make([]byte, 0)
|
||||||
|
return responsePayload, 512
|
||||||
|
}
|
||||||
|
}
|
||||||
208
internal/handlers/lobby.go
Normal file
208
internal/handlers/lobby.go
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"customServer/internal/protocol"
|
||||||
|
"customServer/internal/state"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleEnterLobbyRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling EnterLobbyRequest")
|
||||||
|
|
||||||
|
// Pre-push the game list so it's there when the client transitions
|
||||||
|
gameList := getMockGameListBytes()
|
||||||
|
conn.Write(protocol.WrapPacket(609, gameList, 999))
|
||||||
|
|
||||||
|
return []byte{}, 601
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleLobbyPlayerListRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling LobbyPlayerListRequest")
|
||||||
|
mockSmallPlayer := state.GetMockSmallPlayerBytes()
|
||||||
|
|
||||||
|
// PlayerList Message
|
||||||
|
playerList := make([]byte, 0)
|
||||||
|
playerList = append(playerList, 0x0a)
|
||||||
|
playerList = append(playerList, protocol.EncodeVarint(uint64(len(mockSmallPlayer)))...)
|
||||||
|
playerList = append(playerList, mockSmallPlayer...)
|
||||||
|
|
||||||
|
// Deflate
|
||||||
|
compressed := protocol.ZlibCompress(playerList)
|
||||||
|
|
||||||
|
responsePayload := make([]byte, 0)
|
||||||
|
responsePayload = append(responsePayload, 0x0a) // Field 1: playerList (ByteString)
|
||||||
|
responsePayload = append(responsePayload, protocol.EncodeVarint(uint64(len(compressed)))...)
|
||||||
|
responsePayload = append(responsePayload, compressed...)
|
||||||
|
|
||||||
|
return responsePayload, 604
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleLobbyGameListRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling LobbyGameListRequest")
|
||||||
|
return getMockGameListBytes(), 609
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleObservableGameListRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling ObservableGameListRequest")
|
||||||
|
return getMockGameListBytes(), 622
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleLobbyCreateGameRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling LobbyCreateGameRequest")
|
||||||
|
|
||||||
|
// 1. Scan the request data for Field 607 (LobbyCreateGameRequest)
|
||||||
|
// Actually, the dispatcher already gives us the requestData which IS the payload of the request number?
|
||||||
|
// In the original main.go, the Switch handled requestNumber.
|
||||||
|
// For 607, it manually scanned payloadBytes again.
|
||||||
|
|
||||||
|
var configBytes []byte
|
||||||
|
msgReader := bytes.NewReader(requestData)
|
||||||
|
for {
|
||||||
|
tag, err := protocol.ReadVarint(msgReader)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fieldNum := tag >> 3
|
||||||
|
wireType := tag & 0x7
|
||||||
|
|
||||||
|
if fieldNum == 607 && wireType == 2 {
|
||||||
|
length, _ := protocol.ReadVarint(msgReader)
|
||||||
|
createGameReqBytes := make([]byte, length)
|
||||||
|
msgReader.Read(createGameReqBytes)
|
||||||
|
|
||||||
|
// Extract Configuration (Field 1) from LobbyCreateGameRequest
|
||||||
|
reqReader := bytes.NewReader(createGameReqBytes)
|
||||||
|
for {
|
||||||
|
rtag, rerr := protocol.ReadVarint(reqReader)
|
||||||
|
if rerr != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
rfieldNum := rtag >> 3
|
||||||
|
rwireType := rtag & 0x7
|
||||||
|
if rfieldNum == 1 && rwireType == 2 {
|
||||||
|
rlength, _ := protocol.ReadVarint(reqReader)
|
||||||
|
configBytes = make([]byte, rlength)
|
||||||
|
reqReader.Read(configBytes)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
protocol.SkipField(reqReader, rwireType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
protocol.SkipField(msgReader, wireType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update State
|
||||||
|
newGameID := int64(4016461897007108096)
|
||||||
|
state.GlobalManager.CreateGame(newGameID)
|
||||||
|
|
||||||
|
// Construct Response (LobbyGameCreatedRequest 608)
|
||||||
|
gameDetails := getMockGameDetailsBytes(newGameID, configBytes, [][]byte{state.GetMockPlayerBytes()})
|
||||||
|
|
||||||
|
// Push updated game list
|
||||||
|
gameList := getMockGameListBytes()
|
||||||
|
conn.Write(protocol.WrapPacket(609, gameList, 1000))
|
||||||
|
|
||||||
|
responsePayload := make([]byte, 0)
|
||||||
|
responsePayload = append(responsePayload, 0x0a)
|
||||||
|
responsePayload = append(responsePayload, protocol.EncodeVarint(uint64(len(gameDetails)))...)
|
||||||
|
responsePayload = append(responsePayload, gameDetails...)
|
||||||
|
|
||||||
|
return responsePayload, 608
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleLobbyJoinGameRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling LobbyJoinGameRequest")
|
||||||
|
state.GlobalManager.CreateGame(4016461897007108096)
|
||||||
|
|
||||||
|
// Construct LobbyNewPlayerRequest (611)
|
||||||
|
gameDetails := getMockGameDetailsBytes(4016461897007108096, nil, [][]byte{state.GetMockPlayerBytes()})
|
||||||
|
|
||||||
|
// LobbyNewPlayerRequest Message
|
||||||
|
newPlayerReq := make([]byte, 0)
|
||||||
|
// Field 1: GameDetails
|
||||||
|
newPlayerReq = append(newPlayerReq, 0x0a)
|
||||||
|
newPlayerReq = append(newPlayerReq, protocol.EncodeVarint(uint64(len(gameDetails)))...)
|
||||||
|
newPlayerReq = append(newPlayerReq, gameDetails...)
|
||||||
|
|
||||||
|
// Field 2: JoiningPlayer
|
||||||
|
newPlayerReq = append(newPlayerReq, 0x10)
|
||||||
|
newPlayerReq = append(newPlayerReq, protocol.EncodeVarint(uint64(state.MockUserID))...)
|
||||||
|
|
||||||
|
return newPlayerReq, 611
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
func getMockGameDetailsBytes(gameID int64, configBytes []byte, players [][]byte) []byte {
|
||||||
|
gameDetails := make([]byte, 0)
|
||||||
|
gameDetails = append(gameDetails, 0x08)
|
||||||
|
gameDetails = append(gameDetails, protocol.EncodeVarint(uint64(gameID))...)
|
||||||
|
|
||||||
|
for _, p := range players {
|
||||||
|
gameDetails = append(gameDetails, 0x12)
|
||||||
|
gameDetails = append(gameDetails, protocol.EncodeVarint(uint64(len(p)))...)
|
||||||
|
gameDetails = append(gameDetails, p...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(configBytes) > 0 {
|
||||||
|
// Field 3: Configuration (From request)
|
||||||
|
gameDetails = append(gameDetails, 0x1a)
|
||||||
|
gameDetails = append(gameDetails, protocol.EncodeVarint(uint64(len(configBytes)))...)
|
||||||
|
gameDetails = append(gameDetails, configBytes...)
|
||||||
|
} else {
|
||||||
|
// Field 3: Configuration (Fallback)
|
||||||
|
fallbackConfig := getFallbackConfigBytes()
|
||||||
|
gameDetails = append(gameDetails, 0x1a)
|
||||||
|
gameDetails = append(gameDetails, protocol.EncodeVarint(uint64(len(fallbackConfig)))...)
|
||||||
|
gameDetails = append(gameDetails, fallbackConfig...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return gameDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMockGameListBytes() []byte {
|
||||||
|
// For the lobby list, show a game with NO players so anyone can join
|
||||||
|
gameData := getMockGameDetailsBytes(4016461897007108096, nil, [][]byte{})
|
||||||
|
|
||||||
|
// GameList Message
|
||||||
|
gameList := make([]byte, 0)
|
||||||
|
gameList = append(gameList, 0x0a)
|
||||||
|
gameList = append(gameList, protocol.EncodeVarint(uint64(len(gameData)))...)
|
||||||
|
gameList = append(gameList, gameData...)
|
||||||
|
|
||||||
|
// Deflate
|
||||||
|
compressed := protocol.ZlibCompress(gameList)
|
||||||
|
|
||||||
|
responsePayload := make([]byte, 0)
|
||||||
|
responsePayload = append(responsePayload, 0x0a) // Field 1: gameList (ByteString)
|
||||||
|
responsePayload = append(responsePayload, protocol.EncodeVarint(uint64(len(compressed)))...)
|
||||||
|
responsePayload = append(responsePayload, compressed...)
|
||||||
|
return responsePayload
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFallbackConfigBytes() []byte {
|
||||||
|
fallbackConfig := make([]byte, 0)
|
||||||
|
name := "Fallback Game"
|
||||||
|
fallbackConfig = append(fallbackConfig, 0x0a)
|
||||||
|
fallbackConfig = append(fallbackConfig, protocol.EncodeVarint(uint64(len(name)))...)
|
||||||
|
fallbackConfig = append(fallbackConfig, name...)
|
||||||
|
fallbackConfig = append(fallbackConfig, 0x10, 0x00, 0x18, 0x01, 0x20, 0x00)
|
||||||
|
fallbackConfig = append(fallbackConfig, 0x28)
|
||||||
|
fallbackConfig = append(fallbackConfig, protocol.EncodeVarint(1)...)
|
||||||
|
fallbackConfig = append(fallbackConfig, 0x30)
|
||||||
|
fallbackConfig = append(fallbackConfig, protocol.EncodeVarint(6)...)
|
||||||
|
fallbackConfig = append(fallbackConfig, 0x48, 0x00, 0x50, 0x2d)
|
||||||
|
|
||||||
|
// Field 11: Data (IronGameConfiguration)
|
||||||
|
ironData := state.GetMockIronGameConfigurationBytes()
|
||||||
|
fallbackConfig = append(fallbackConfig, 0x5a) // (11 << 3) | 2 = 88 | 2 = 90 = 0x5a
|
||||||
|
fallbackConfig = append(fallbackConfig, protocol.EncodeVarint(uint64(len(ironData)))...)
|
||||||
|
fallbackConfig = append(fallbackConfig, ironData...)
|
||||||
|
|
||||||
|
return fallbackConfig
|
||||||
|
}
|
||||||
20
internal/handlers/system.go
Normal file
20
internal/handlers/system.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
func HandleAsyncBuddyListRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling AsyncBuddyListRequest")
|
||||||
|
// Return AsyncBuddyListContentRequest (516)
|
||||||
|
// Field 2: Buddies (AsyncBuddyList)
|
||||||
|
return []byte{0x12, 0x00}, 516
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleAsyncIgnoreListRequest(conn net.Conn, requestData []byte) ([]byte, int) {
|
||||||
|
fmt.Println("[TCP] Handling AsyncIgnoreListRequest")
|
||||||
|
// Return AsyncIgnoreListContentRequest (516)
|
||||||
|
// Field 2: Ignores (AsyncBuddyList)
|
||||||
|
return []byte{0x12, 0x00}, 561
|
||||||
|
}
|
||||||
161
internal/network/http_server.go
Normal file
161
internal/network/http_server.go
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"customServer/internal/state"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartHTTPServer(addr string) error {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/main/v2/oauth/token", handleToken)
|
||||||
|
mux.HandleFunc("/main/v1/user/me", handleUserMe)
|
||||||
|
mux.HandleFunc("/main/v1/user/me/link", handleLink)
|
||||||
|
mux.HandleFunc("/main/v1/users", handleUserSearch)
|
||||||
|
mux.HandleFunc("/main/v3/showcase/games/steam/es", handleShowcase)
|
||||||
|
mux.HandleFunc("/main/v1/user/me/buddies", handleBuddies)
|
||||||
|
mux.HandleFunc("/main/v1/user/me/lastopponents/GOTDBG", handleLastOpponents)
|
||||||
|
|
||||||
|
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("[HTTP] [UNKNOWN] Request to %s (%s) from %s - Full URI: %s\n", r.URL.Path, r.Method, r.RemoteAddr, r.RequestURI)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
w.Write([]byte(`{"error":true,"status":404,"message":"Not found"}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
fmt.Printf("[HTTP] REST API: http://localhost%s\n", addr)
|
||||||
|
return http.ListenAndServe(addr, mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"access_token": "mock_access_token_12345",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600,
|
||||||
|
"refresh_token": "mock_refresh_token_67890",
|
||||||
|
"scope": "public private boardgames onlinegames partners features",
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, _ := json.Marshal(response)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUserMe(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"error": false,
|
||||||
|
"status": 200,
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"user": map[string]interface{}{
|
||||||
|
"user_id": state.MockUserID,
|
||||||
|
"login_name": state.MockUserName,
|
||||||
|
"email": "player@customserver.local",
|
||||||
|
"name": state.MockUserName,
|
||||||
|
"email_valid": true,
|
||||||
|
"validated": true,
|
||||||
|
"country": "US",
|
||||||
|
"language": "en",
|
||||||
|
"time_zone": "UTC",
|
||||||
|
"posted_msg_count": 42,
|
||||||
|
"features": []string{"online_play", "all_expansions", "community", "profile", "userpages"},
|
||||||
|
"partners": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"partner_id": 12, // Steam
|
||||||
|
"partner_user_id": "76561198084728812",
|
||||||
|
"created_at": "2026-01-01T00:00:00Z",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"boardgames": []interface{}{},
|
||||||
|
"onlinegames": []interface{}{},
|
||||||
|
"avatar": "https://uploads.asmodee.net/builtin/avatar-neutral.jpg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, _ := json.Marshal(response)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleLink(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("[HTTP] Request to %s (%s) from %s\n", r.URL.Path, r.Method, r.RemoteAddr)
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"error": false,
|
||||||
|
"status": 200,
|
||||||
|
"data": map[string]interface{}{},
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, _ := json.Marshal(response)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleUserSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||||
|
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"error": false,
|
||||||
|
"status": 200,
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"total": 1,
|
||||||
|
"_links": map[string]interface{}{},
|
||||||
|
"users": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"user_id": state.MockUserID,
|
||||||
|
"login_name": state.MockUserName,
|
||||||
|
"avatar": "https://uploads.asmodee.net/builtin/avatar-neutral.jpg",
|
||||||
|
"features": []string{"online_play", "all_expansions"},
|
||||||
|
"boardgames": []interface{}{},
|
||||||
|
"onlinegames": []interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, _ := json.Marshal(response)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleShowcase(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("[HTTP] Request to %s (%s) from %s\n", r.URL.Path, r.Method, r.RemoteAddr)
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"error": false,
|
||||||
|
"status": 200,
|
||||||
|
"data": []interface{}{},
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, _ := json.Marshal(response)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleBuddies(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"error": false,
|
||||||
|
"status": 200,
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"total": 0,
|
||||||
|
"buddies": []interface{}{},
|
||||||
|
"_links": map[string]interface{}{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, _ := json.Marshal(response)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleLastOpponents(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Printf("[HTTP] Request to %s from %s\n", r.URL.Path, r.RemoteAddr)
|
||||||
|
response := map[string]interface{}{
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"opponents": []interface{}{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
data, _ := json.Marshal(response)
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
180
internal/network/tcp_server.go
Normal file
180
internal/network/tcp_server.go
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package network
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"customServer/internal/handlers"
|
||||||
|
"customServer/internal/protocol"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StartTCPServer(addr string) error {
|
||||||
|
tlsConfig, err := generateSelfSignedCert()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate self-signed cert: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := tls.Listen("tcp", addr, tlsConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("TCP TLS listener failed: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("[TCP] Scalable Server (TLS): localhost%s\n", addr)
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Accept error: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go handleTCPConnection(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleTCPConnection(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
fmt.Printf("[TCP] New TLS connection from %s\n", conn.RemoteAddr())
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Read length (4 bytes, Big Endian)
|
||||||
|
length, err := protocol.ReadPacketLength(conn)
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
fmt.Printf("[TCP] Read length error: %v\n", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read packet
|
||||||
|
data := make([]byte, length)
|
||||||
|
_, err = io.ReadFull(conn, data)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("[TCP] Read data error: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[TCP] Received packet of %d bytes\n", length)
|
||||||
|
|
||||||
|
packetID := int64(0)
|
||||||
|
requestNumber := int32(0)
|
||||||
|
var payloadBytes []byte
|
||||||
|
|
||||||
|
reader := bytes.NewReader(data)
|
||||||
|
for {
|
||||||
|
tag, err := protocol.ReadVarint(reader)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fieldNum := tag >> 3
|
||||||
|
wireType := tag & 0x7
|
||||||
|
|
||||||
|
if fieldNum == 1 && wireType == 0 { // Packet.id
|
||||||
|
packetID, _ = protocol.ReadVarintInt64(reader)
|
||||||
|
} else if fieldNum == 3 && wireType == 2 { // Packet.payload (Message)
|
||||||
|
payloadLen, _ := protocol.ReadVarint(reader)
|
||||||
|
payloadBytes = make([]byte, payloadLen)
|
||||||
|
reader.Read(payloadBytes)
|
||||||
|
|
||||||
|
payloadReader := bytes.NewReader(payloadBytes)
|
||||||
|
for {
|
||||||
|
pTag, err := protocol.ReadVarint(payloadReader)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pFieldNum := pTag >> 3
|
||||||
|
pWireType := pTag & 0x7
|
||||||
|
if pFieldNum == 1 && pWireType == 0 { // Message.request_number
|
||||||
|
reqNum64, _ := protocol.ReadVarintInt64(payloadReader)
|
||||||
|
requestNumber = int32(reqNum64)
|
||||||
|
} else {
|
||||||
|
protocol.SkipField(payloadReader, pWireType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
protocol.SkipField(reader, wireType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("[TCP] Got Request ID: %d, Number: %d\n", packetID, requestNumber)
|
||||||
|
|
||||||
|
responsePayload, responseFieldNum := handlers.Dispatch(conn, packetID, requestNumber, payloadBytes)
|
||||||
|
if responsePayload != nil {
|
||||||
|
sendTCPResponse(conn, packetID, responseFieldNum, responsePayload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendTCPResponse(conn net.Conn, packetID int64, fieldNum int, payload []byte) {
|
||||||
|
// Construct Message wrapper
|
||||||
|
message := make([]byte, 0)
|
||||||
|
// Field 1: request_number
|
||||||
|
message = append(message, 0x08)
|
||||||
|
message = append(message, protocol.EncodeVarint(uint64(fieldNum))...)
|
||||||
|
|
||||||
|
// Field [fieldNum]: payload (WireType 2)
|
||||||
|
message = append(message, protocol.EncodeVarint(uint64(fieldNum<<3|2))...)
|
||||||
|
message = append(message, protocol.EncodeVarint(uint64(len(payload)))...)
|
||||||
|
message = append(message, payload...)
|
||||||
|
|
||||||
|
// Construct Packet wrapper
|
||||||
|
packet := make([]byte, 0)
|
||||||
|
// Field 1: Id
|
||||||
|
packet = append(packet, 0x08)
|
||||||
|
packet = append(packet, protocol.EncodeVarint(uint64(packetID))...)
|
||||||
|
// Field 3: Payload
|
||||||
|
packet = append(packet, 0x1a)
|
||||||
|
packet = append(packet, protocol.EncodeVarint(uint64(len(message)))...)
|
||||||
|
packet = append(packet, message...)
|
||||||
|
|
||||||
|
// Send length + packet
|
||||||
|
lengthBuf := make([]byte, 4)
|
||||||
|
length := uint32(len(packet))
|
||||||
|
lengthBuf[0] = byte(length >> 24)
|
||||||
|
lengthBuf[1] = byte(length >> 16)
|
||||||
|
lengthBuf[2] = byte(length >> 8)
|
||||||
|
lengthBuf[3] = byte(length)
|
||||||
|
|
||||||
|
conn.Write(lengthBuf)
|
||||||
|
conn.Write(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateSelfSignedCert() (*tls.Config, error) {
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"Custom Server Mod"},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24 * 365),
|
||||||
|
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||||
|
DNSNames: []string{"localhost"},
|
||||||
|
}
|
||||||
|
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert := tls.Certificate{
|
||||||
|
Certificate: [][]byte{derBytes},
|
||||||
|
PrivateKey: priv,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tls.Config{Certificates: []tls.Certificate{cert}}, nil
|
||||||
|
}
|
||||||
95
internal/protocol/codec.go
Normal file
95
internal/protocol/codec.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ZlibCompress(data []byte) []byte {
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := zlib.NewWriter(&b)
|
||||||
|
w.Write(data)
|
||||||
|
w.Close()
|
||||||
|
return b.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Codec Functions (migrated from main.go)
|
||||||
|
|
||||||
|
func ReadVarint(r io.ByteReader) (uint64, error) {
|
||||||
|
return binary.ReadUvarint(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadVarintInt64(r io.ByteReader) (int64, error) {
|
||||||
|
v, err := binary.ReadUvarint(r)
|
||||||
|
return int64(v), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeVarint(v uint64) []byte {
|
||||||
|
buf := make([]byte, binary.MaxVarintLen64)
|
||||||
|
n := binary.PutUvarint(buf, v)
|
||||||
|
return buf[:n]
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeDouble(v float64) []byte {
|
||||||
|
buf := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(buf, math.Float64bits(v))
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func SkipField(reader *bytes.Reader, wireType uint64) {
|
||||||
|
switch wireType {
|
||||||
|
case 0: // Varint
|
||||||
|
ReadVarint(reader)
|
||||||
|
case 1: // Fixed64
|
||||||
|
reader.Seek(8, io.SeekCurrent)
|
||||||
|
case 2: // Length-delimited
|
||||||
|
length, _ := ReadVarint(reader)
|
||||||
|
reader.Seek(int64(length), io.SeekCurrent)
|
||||||
|
case 5: // Fixed32
|
||||||
|
reader.Seek(4, io.SeekCurrent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to read a packet length (4 bytes, Big Endian) as per official protocol
|
||||||
|
func ReadPacketLength(conn io.Reader) (uint32, error) {
|
||||||
|
var length uint32
|
||||||
|
err := binary.Read(conn, binary.BigEndian, &length)
|
||||||
|
return length, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapPacket encapsulates a payload in the Message and Packet structure
|
||||||
|
// 1. Message wrapper (Field 1: requestNumber, Field [requestNumber]: payload field)
|
||||||
|
// 2. Packet wrapper (Field 1: packetID, Field 3: Message bytes)
|
||||||
|
// 3. 4-byte Big Endian length prefix
|
||||||
|
func WrapPacket(requestNumber int, payload []byte, packetID uint32) []byte {
|
||||||
|
// 1. Wrap in Message
|
||||||
|
msg := make([]byte, 0)
|
||||||
|
// Field 1: request_number (Varint)
|
||||||
|
msg = append(msg, 0x08)
|
||||||
|
msg = append(msg, EncodeVarint(uint64(requestNumber))...)
|
||||||
|
|
||||||
|
// Field [requestNumber]: The actual response payload
|
||||||
|
msg = append(msg, EncodeVarint(uint64(uint32(requestNumber)<<3|2))...)
|
||||||
|
msg = append(msg, EncodeVarint(uint64(len(payload)))...)
|
||||||
|
msg = append(msg, payload...)
|
||||||
|
|
||||||
|
// 2. Wrap in Packet
|
||||||
|
packet := make([]byte, 0)
|
||||||
|
// Field 1: Id (Varint)
|
||||||
|
packet = append(packet, 0x08)
|
||||||
|
packet = append(packet, EncodeVarint(uint64(packetID))...)
|
||||||
|
// Field 3: Payload (Message)
|
||||||
|
packet = append(packet, 0x1a)
|
||||||
|
packet = append(packet, EncodeVarint(uint64(len(msg)))...)
|
||||||
|
packet = append(packet, msg...)
|
||||||
|
|
||||||
|
// 3. Add 4-byte length prefix
|
||||||
|
final := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(final, uint32(len(packet)))
|
||||||
|
final = append(final, packet...)
|
||||||
|
|
||||||
|
return final
|
||||||
|
}
|
||||||
120
internal/protocol/types.go
Normal file
120
internal/protocol/types.go
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package protocol
|
||||||
|
|
||||||
|
// Enum Definitions
|
||||||
|
type GameStatus int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
GameStatus_RESERVED GameStatus = 0
|
||||||
|
GameStatus_IN_PROGRESS GameStatus = 1
|
||||||
|
GameStatus_OVER GameStatus = 2
|
||||||
|
GameStatus_NOT_STARTED GameStatus = 3
|
||||||
|
GameStatus_ABORTED GameStatus = 4
|
||||||
|
GameStatus_OUTCOME GameStatus = 5
|
||||||
|
GameStatus_PLAYER_TIMEOUT GameStatus = 6
|
||||||
|
GameStatus_ABORTING GameStatus = 7
|
||||||
|
GameStatus_WAITING_INVITATION GameStatus = 8
|
||||||
|
GameStatus_FIRST_USER_DATA_ROUND GameStatus = 9
|
||||||
|
GameStatus_SIMULTANEOUS GameStatus = 10
|
||||||
|
GameStatus_INTERRUPTIBLE GameStatus = 11
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorCode int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorCode_NO_ERROR ErrorCode = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message Definitions
|
||||||
|
|
||||||
|
type GameConfiguration struct {
|
||||||
|
Name string
|
||||||
|
Private bool
|
||||||
|
Lurkable bool
|
||||||
|
Rated bool
|
||||||
|
MinPlayers int32
|
||||||
|
MaxPlayers int32
|
||||||
|
MinKarma int32
|
||||||
|
FirstPlayer int32
|
||||||
|
GameMode int32
|
||||||
|
Timeout int32
|
||||||
|
Data []byte
|
||||||
|
ReqFirstUserRound bool
|
||||||
|
ObservableBy int32
|
||||||
|
MinRankScore int32
|
||||||
|
MainVariant string
|
||||||
|
RulesVer int32
|
||||||
|
IdleTime int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
Id int32
|
||||||
|
Name string
|
||||||
|
Karma int32
|
||||||
|
RankScore float64
|
||||||
|
Rank int32
|
||||||
|
NbGames int32
|
||||||
|
Language int32
|
||||||
|
Avatar string // Simplified for mock
|
||||||
|
Tz string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SmallPlayer struct {
|
||||||
|
WWWId int64
|
||||||
|
Name string
|
||||||
|
Karma int32
|
||||||
|
RankScore float64
|
||||||
|
Rank int32
|
||||||
|
NbGames int32
|
||||||
|
Language int32
|
||||||
|
Avatar string
|
||||||
|
}
|
||||||
|
|
||||||
|
type GameDetails struct {
|
||||||
|
GameId int64
|
||||||
|
Players []*Player
|
||||||
|
Configuration *GameConfiguration
|
||||||
|
Data []byte
|
||||||
|
UserData []*PlayerPregameData
|
||||||
|
}
|
||||||
|
|
||||||
|
type PlayerPregameData struct {
|
||||||
|
PlayerId int32
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type StatusReport struct {
|
||||||
|
GameId int64
|
||||||
|
Status GameStatus
|
||||||
|
Data []byte
|
||||||
|
TurnId int32
|
||||||
|
NextPlayerIds []int32
|
||||||
|
Players []*Player
|
||||||
|
Configuration *GameConfiguration
|
||||||
|
ActivePlayer int32
|
||||||
|
// Add other fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session struct {
|
||||||
|
Id int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request/Response Wrappers (Simplification: We handle fields manually in codec mostly,
|
||||||
|
// but these structs represent the data content)
|
||||||
|
|
||||||
|
type AsyncConnectedRequest struct {
|
||||||
|
Session *Session
|
||||||
|
Player *Player
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerStatisticsRequest struct {
|
||||||
|
HostedGames int32
|
||||||
|
Players int32
|
||||||
|
ConnectedPlayers int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type LobbyGameCreatedRequest struct {
|
||||||
|
Game *GameDetails
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Encode helpers can be added here or in codec.
|
||||||
|
// For now, these structs serve as the 'definitions' requested by the user.
|
||||||
158
internal/state/helpers.go
Normal file
158
internal/state/helpers.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"customServer/internal/protocol"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const (
|
||||||
|
MockUserID = 8381763
|
||||||
|
MockUserName = "CustomPlayer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helpers to generate mock bytes using Protocol Codecs
|
||||||
|
// We use manual encoding to ensure exact byte match with original monolithic server for safety
|
||||||
|
|
||||||
|
func GetMockPlayerBytes() []byte {
|
||||||
|
player := make([]byte, 0)
|
||||||
|
|
||||||
|
// field 1 (Name), string
|
||||||
|
player = append(player, 0x0a)
|
||||||
|
player = append(player, protocol.EncodeVarint(uint64(len(MockUserName)))...)
|
||||||
|
player = append(player, MockUserName...)
|
||||||
|
|
||||||
|
// field 2 (Id), varint
|
||||||
|
player = append(player, 0x10)
|
||||||
|
player = append(player, protocol.EncodeVarint(uint64(MockUserID))...)
|
||||||
|
|
||||||
|
// field 5 (Karma), varint
|
||||||
|
player = append(player, 0x28)
|
||||||
|
player = append(player, protocol.EncodeVarint(52)...)
|
||||||
|
|
||||||
|
// field 7 (RankScore), fixed64 (WireType 1) - double
|
||||||
|
player = append(player, 0x39) // (7 << 3) | 1 = 57 = 0x39
|
||||||
|
player = append(player, protocol.EncodeDouble(1500.0)...)
|
||||||
|
|
||||||
|
// field 9 (WWWId), varint
|
||||||
|
player = append(player, 0x48)
|
||||||
|
player = append(player, protocol.EncodeVarint(uint64(MockUserID))...)
|
||||||
|
|
||||||
|
// field 11 (nbGames), varint
|
||||||
|
player = append(player, 0x58)
|
||||||
|
player = append(player, protocol.EncodeVarint(2)...)
|
||||||
|
|
||||||
|
// field 12 (Banned), varint
|
||||||
|
player = append(player, 0x60)
|
||||||
|
player = append(player, protocol.EncodeVarint(0)...) // False
|
||||||
|
|
||||||
|
// field 14 (Avatar), message
|
||||||
|
avatarMsg := make([]byte, 0)
|
||||||
|
avatarUrl := "https://uploads.asmodee.net/builtin/avatar-neutral.jpg"
|
||||||
|
// Avatar.Image (Field 3), String
|
||||||
|
avatarMsg = append(avatarMsg, 0x1a)
|
||||||
|
avatarMsg = append(avatarMsg, protocol.EncodeVarint(uint64(len(avatarUrl)))...)
|
||||||
|
avatarMsg = append(avatarMsg, avatarUrl...)
|
||||||
|
|
||||||
|
player = append(player, 0x72) // Field 14, WireType 2
|
||||||
|
player = append(player, protocol.EncodeVarint(uint64(len(avatarMsg)))...)
|
||||||
|
player = append(player, avatarMsg...)
|
||||||
|
|
||||||
|
// field 15 (Language), varint
|
||||||
|
player = append(player, 0x78)
|
||||||
|
player = append(player, protocol.EncodeVarint(1)...)
|
||||||
|
|
||||||
|
// field 16 (Tz), string
|
||||||
|
player = append(player, 0x82, 0x01) // Field 16 (128 + 2), WireType 2
|
||||||
|
player = append(player, protocol.EncodeVarint(uint64(len("UTC")))...)
|
||||||
|
player = append(player, "UTC"...)
|
||||||
|
|
||||||
|
return player
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockSmallPlayerBytes() []byte {
|
||||||
|
player := make([]byte, 0)
|
||||||
|
|
||||||
|
// field 1 (WWWId), int32
|
||||||
|
player = append(player, 0x08)
|
||||||
|
player = append(player, protocol.EncodeVarint(uint64(MockUserID))...)
|
||||||
|
|
||||||
|
// field 2 (Name), string
|
||||||
|
player = append(player, 0x12)
|
||||||
|
player = append(player, protocol.EncodeVarint(uint64(len(MockUserName)))...)
|
||||||
|
player = append(player, MockUserName...)
|
||||||
|
|
||||||
|
// field 3 (Karma), int32
|
||||||
|
player = append(player, 0x18)
|
||||||
|
player = append(player, protocol.EncodeVarint(52)...)
|
||||||
|
|
||||||
|
// field 4 (RankScore), double
|
||||||
|
player = append(player, 0x21) // (4 << 3) | 1 = 33 = 0x21
|
||||||
|
player = append(player, protocol.EncodeDouble(1500.0)...)
|
||||||
|
|
||||||
|
return player
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockIronHumanPlayerBytes() []byte {
|
||||||
|
// IronHumanPlayer
|
||||||
|
// Field 1: PlayerId (int32)
|
||||||
|
// Field 2: House (Enum) -> Stark (0)
|
||||||
|
data := make([]byte, 0)
|
||||||
|
// PlayerId
|
||||||
|
data = append(data, 0x08)
|
||||||
|
data = append(data, protocol.EncodeVarint(uint64(MockUserID))...)
|
||||||
|
// House (0 is default, we skip it)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockIronGameConfigurationBytes() []byte {
|
||||||
|
// IronGameConfiguration
|
||||||
|
// Field 1: TotalPlayers (int32) -> 6
|
||||||
|
// Field 7: HumanPlayers (repeated IronHumanPlayer)
|
||||||
|
// Field 8: CreatedBy (int64)
|
||||||
|
data := make([]byte, 0)
|
||||||
|
|
||||||
|
// TotalPlayers = 6
|
||||||
|
data = append(data, 0x08)
|
||||||
|
data = append(data, protocol.EncodeVarint(6)...)
|
||||||
|
|
||||||
|
// HumanPlayers
|
||||||
|
hpBytes := GetMockIronHumanPlayerBytes()
|
||||||
|
data = append(data, 0x3a) // Field 7, WireType 2
|
||||||
|
data = append(data, protocol.EncodeVarint(uint64(len(hpBytes)))...)
|
||||||
|
data = append(data, hpBytes...)
|
||||||
|
|
||||||
|
// CreatedBy
|
||||||
|
data = append(data, 0x40) // Field 8, WireType 0
|
||||||
|
data = append(data, protocol.EncodeVarint(uint64(MockUserID))...)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMockIronGameStateBytes() []byte {
|
||||||
|
// IronGameState
|
||||||
|
// Field 1: GameId (int64)
|
||||||
|
// Field 2: Version (int32)
|
||||||
|
// Field 7: Configuration (IronGameConfiguration)
|
||||||
|
// Field 9: AllianceStateVersion (int32)
|
||||||
|
data := make([]byte, 0)
|
||||||
|
|
||||||
|
// GameId
|
||||||
|
data = append(data, 0x08)
|
||||||
|
data = append(data, protocol.EncodeVarint(4016461897007108096)...)
|
||||||
|
|
||||||
|
// Version
|
||||||
|
data = append(data, 0x10)
|
||||||
|
data = append(data, protocol.EncodeVarint(1)...)
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
configBytes := GetMockIronGameConfigurationBytes()
|
||||||
|
data = append(data, 0x3a) // Field 7, WireType 2
|
||||||
|
data = append(data, protocol.EncodeVarint(uint64(len(configBytes)))...)
|
||||||
|
data = append(data, configBytes...)
|
||||||
|
|
||||||
|
// AllianceStateVersion
|
||||||
|
data = append(data, 0x48)
|
||||||
|
data = append(data, protocol.EncodeVarint(1)...)
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
64
internal/state/manager.go
Normal file
64
internal/state/manager.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Global Game State Manager
|
||||||
|
|
||||||
|
type GameManager struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
hasActiveGame bool
|
||||||
|
activeGameID int64
|
||||||
|
activeGameData []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var GlobalManager *GameManager
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
GlobalManager = &GameManager{
|
||||||
|
hasActiveGame: false,
|
||||||
|
activeGameID: 0,
|
||||||
|
}
|
||||||
|
// Pre-decode the embedded game data
|
||||||
|
data, err := base64.StdEncoding.DecodeString(MockGameDataBase64)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error decoding mock game data: %v\n", err)
|
||||||
|
} else {
|
||||||
|
GlobalManager.activeGameData = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GameManager) HasActiveGame() bool {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
return m.hasActiveGame
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GameManager) GetActiveGameID() int64 {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
return m.activeGameID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GameManager) GetActiveGameData() []byte {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
return m.activeGameData
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *GameManager) CreateGame(id int64) {
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
m.hasActiveGame = true
|
||||||
|
m.activeGameID = id
|
||||||
|
m.activeGameData = GetMockIronGameStateBytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embeds
|
||||||
|
|
||||||
|
//go:embed mock_gamedata.b64
|
||||||
|
var MockGameDataBase64 string
|
||||||
1
internal/state/mock_gamedata.b64
Normal file
1
internal/state/mock_gamedata.b64
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CICggOjD6dXeNxAKGAMiVnsibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjE5MiwidHJzIjpbeyJlbHQiOls2NTNdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6MX19IlZ7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjo2NTMsInRycyI6W3siZWx0IjpbNTUxXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjF9fSJWeyJuYW1lIjoic3d0IiwidmFsdWUiOnsic2xuIjp7ImVpZCI6MjAyLCJ0cnMiOlt7ImVsdCI6WzYzOV0sIm5hbWUiOiJlbHRyIn1dfSwiY3RyIjoxfX0iVnsibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjYzOSwidHJzIjpbeyJlbHQiOls1NTFdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6MX19IlZ7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjoyMDIsInRycyI6W3siZWx0IjpbNjM5XSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjF9fSJWeyJuYW1lIjoic3d0IiwidmFsdWUiOnsic2xuIjp7ImVpZCI6MTkyLCJ0cnMiOlt7ImVsdCI6WzY2MV0sIm5hbWUiOiJlbHRyIn1dfSwiY3RyIjoxfX0iVnsibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjE5MCwidHJzIjpbeyJlbHQiOls2NjNdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6MX19IlZ7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjo2NjMsInRycyI6W3siZWx0IjpbNTUxXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjF9fSJWeyJuYW1lIjoic3d0IiwidmFsdWUiOnsic2xuIjp7ImVpZCI6MTkwLCJ0cnMiOlt7ImVsdCI6WzY0NV0sIm5hbWUiOiJlbHRyIn1dfSwiY3RyIjoxfX0iIHsibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjE5MiwidHJzIjpbeyJlbHQiOltdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6NTd9fSJXeyJuYW1lIjoic3d0IiwidmFsdWUiOnsic2xuIjp7ImVpZCI6MTc1LCJ0cnMiOlt7ImVsdCI6WzE2N10sIm5hbWUiOiJlbHRyIn1dfSwiY3RyIjo2MX19IlR7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjoxNjksInRycyI6W3siZWx0IjpbXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjYzfX0iV3sibmFtZSI6InN3dCIsInZhbHVlIjp7InNsbiI6eyJlaWQiOjI1MCwidHJzIjpbeyJlbHQiOlsxNTZdLCJuYW1lIjoiZWx0ciJ9XX0sImN0ciI6NzJ9fSIgeyJuYW1lIjoibXMiLCJ2YWx1ZSI6eyJjdHIiOjc1fX0iL3sibmFtZSI6ImdjYyIsInZhbHVlIjp7InNlbGVjdGlvbiI6MCwiY3RyIjo4MX19Ild7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjoyNTAsInRycyI6W3siZWx0IjpbMTUyXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjg4fX0iL3sibmFtZSI6ImdjYyIsInZhbHVlIjp7InNlbGVjdGlvbiI6MywiY3RyIjo5N319Ilh7Im5hbWUiOiJzd3QiLCJ2YWx1ZSI6eyJzbG4iOnsiZWlkIjo1NDMsInRycyI6W3siZWx0IjpbMTkyXSwibmFtZSI6ImVsdHIifV19LCJjdHIiOjExMX19IjB7Im5hbWUiOiJnY2MiLCJ2YWx1ZSI6eyJzZWxlY3Rpb24iOjQsImN0ciI6MTE4fX0iMHsibmFtZSI6ImdjYyIsInZhbHVlIjp7InNlbGVjdGlvbiI6MCwiY3RyIjoxMjJ9fToWOgIIAToECAIQBToECAMQAToECAQQBA==
|
||||||
0
internal/state/mock_gamestatus.bin
Normal file
0
internal/state/mock_gamestatus.bin
Normal file
265
log.2.oficial.txt
Normal file
265
log.2.oficial.txt
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
[Message: BepInEx] BepInEx 5.4.23.4 - AGameOfThronesTheBoardGame (05/01/2026 18:34:08)
|
||||||
|
[Info : BepInEx] Running under Unity v2020.3.31.7034039
|
||||||
|
[Info : BepInEx] CLR runtime version: 4.0.30319.42000
|
||||||
|
[Info : BepInEx] Supports SRE: True
|
||||||
|
[Info : BepInEx] System platform: Bits64, Windows
|
||||||
|
[Message: BepInEx] Preloader started
|
||||||
|
[Info : BepInEx] Loaded 1 patcher method from [BepInEx.Preloader 5.4.23.4]
|
||||||
|
[Info : BepInEx] 1 patcher plugin loaded
|
||||||
|
[Info : BepInEx] Patching [UnityEngine.CoreModule] with [BepInEx.Chainloader]
|
||||||
|
[Message: BepInEx] Preloader finished
|
||||||
|
[Message: BepInEx] Chainloader ready
|
||||||
|
[Message: BepInEx] Chainloader started
|
||||||
|
[Info : BepInEx] 5 plugins to load
|
||||||
|
[Info : BepInEx] Loading [Custom Server Mod 1.0.0]
|
||||||
|
[Info :Custom Server Mod] Custom Server Mod Loading...
|
||||||
|
[Info :Custom Server Mod] Custom Server Mod Patched Successfully!
|
||||||
|
[Info : BepInEx] Loading [Idle Status UI 1.0.0]
|
||||||
|
[Info :Idle Status UI] Idle Status UI Mod Loading...
|
||||||
|
[Info :Idle Status UI] Idle Status UI Mod Patched Successfully on PlayerList
|
||||||
|
[Info : BepInEx] Loading [Strategic Board 1.0.0]
|
||||||
|
[Info :Strategic Board] Strategic Board Mod Loaded
|
||||||
|
[Info : BepInEx] Loading [Strategic Map Plus 28.0.0]
|
||||||
|
[Info :Strategic Map Plus] Strategic Map Plus v28: Posición 0,0,0.
|
||||||
|
[Info : BepInEx] Loading [UnityExplorer 4.9.0]
|
||||||
|
[Message:UnityExplorer] UnityExplorer 4.9.0 initializing...
|
||||||
|
[Message:UnityExplorer] [UniverseLib] UniverseLib 1.5.1 initializing...
|
||||||
|
[Message:UnityExplorer] [UniverseLib] Finished UniverseLib initial setup.
|
||||||
|
[Message:UnityExplorer] Initialized UnityCrashPrevention for: Canvas.get_renderingDisplaySize
|
||||||
|
[Message: BepInEx] Chainloader startup complete
|
||||||
|
[Message:UnityExplorer] [UniverseLib] Initialized Legacy Input support
|
||||||
|
[Message:UnityExplorer] [UniverseLib] Loaded modern bundle for Unity 2020.3.31f1
|
||||||
|
[Message:UnityExplorer] [UniverseLib] UniverseLib 1.5.1 initialized.
|
||||||
|
[Message:UnityExplorer] Creating UI...
|
||||||
|
[Message:UnityExplorer] UnityExplorer 4.9.0 (Mono) initialized.
|
||||||
|
Setting breakpad minidump AppID = 1075190
|
||||||
|
SteamInternal_SetMinidumpSteamID: Caching Steam ID: 76561198084728812 [API loaded no]
|
||||||
|
[Warning: Unity Log] [2026-01-07T15:27:57.9157095Z][Warning][CoreApplication] Interface Skin not provided -> Fall back to default
|
||||||
|
[Info : Unity Log] initCoreSystems
|
||||||
|
[Info : Unity Log] initAsmodeeSDK
|
||||||
|
[Info : Unity Log] Setting FirstParty Steam: 76561198084728812
|
||||||
|
[Info : Unity Log] Logging boot event. True
|
||||||
|
[Warning: Unity Log] [2026-01-07T15:27:58.1752870Z][Warning][LocalizationManager] Couldn't find a saved language in PlayerPrefs
|
||||||
|
[Warning: Unity Log] [2026-01-07T15:27:58.1917131Z][Warning][CommunityHubLauncher] Skin not provided -> Fall back to default
|
||||||
|
[Info : Unity Log] [DirusProductProvider] RetrieveIAPProducts url: https://ak57vzqnbk.execute-api.us-west-2.amazonaws.com/prod/commerce/v1/getcatalog?client=iron
|
||||||
|
[Info : Unity Log] Done sending App boot event
|
||||||
|
[Info : Unity Log] initPlatformScenes
|
||||||
|
[Info : Unity Log] initFacebookSDK
|
||||||
|
[Info : Unity Log] initFirebaseAnalyticsSDK
|
||||||
|
[Info : Unity Log] initGame
|
||||||
|
[Info : Unity Log] ChangeScene(Landing)
|
||||||
|
[Error : Unity Log] [2026-01-07T15:27:58.9694763Z][Error][OAuthGate.receiver] Private access token failure {"client_id":"gotdbg","grant_type":"refresh_token","status":400,"url":"https://api.asmodee.net/main/v2/oauth/token","error_description":"HTTP/1.1 400 Bad Request","error":"error"}
|
||||||
|
[Warning: Unity Log] [2026-01-07T15:27:58.9704895Z][Warning][OAuthGate] Silently failed to find a private access token
|
||||||
|
[Warning: Unity Log] [2026-01-07T15:27:58.9714828Z][Warning][InternetConnectionWithScalableServerConnectionOptionalStatus] Failure
|
||||||
|
[Info : Unity Log] [DirusProductProvider] got catalog response: [{"archetypeID":"2229999397666787289","attributes":[{"name":100072,"value":{"id":"affc.name"},"originalValue":{"id":"name"}},{"name":100073,"value":{"id":"affc.description"},"originalValue":{"id":"affc.description"}},{"name":100074,"value":"affc","originalValue":"affc"},{"name":100075,"value":{"USD":499},"originalValue":{"USD":499}},{"name":100067,"value":1416391,"originalValue":1416391},{"name":100069,"value":"com.asmodeedigital.agameofthrones.afeastforcrows","originalValue":"com.asmodeedigital.agameofthrones.afeastforcrows"},{"name":100068,"value":"com.asmodeedigital.agameofthrones.afeastforcrows","originalValue":"com.asmodeedigital.agameofthrones.afeastforcrows"},{"name":400127,"value":"gotdbg-affc","originalValue":"gotdbg-affc"},{"name":100078,"value":1226888921,"originalValue":1226888921},{"name":100079,"value":"a0c6ab458c9546db894a8b390d36cc40","originalValue":"a0c6ab458c9546db894a8b390d36cc40"},{"name":100080,"value":"61efbef7a81a4cc6a4424ff850dfdc3b","originalValue":"61efbef7a81a4cc6a4424ff850dfdc3b"}]},{"archetypeID":"5493239248328644216","attributes":[{"name":100072,"value":{"id":"adwd.name"},"originalValue":{"id":"adwd.name"}},{"name":100073,"value":{"id":"adwd.description"},"originalValue":{"id":"adwd.description"}},{"name":100074,"value":"adwd","originalValue":"adwd"},{"name":100075,"value":{"USD":499},"originalValue":{"USD":499}},{"name":100067,"value":1416390,"originalValue":1416390},{"name":100069,"value":"com.asmodeedigital.agameofthrones.adancewithdragons","originalValue":"com.asmodeedigital.agameofthrones.adancewithdragons"},{"name":100068,"value":"com.asmodeedigital.agameofthrones.adancewithdragons","originalValue":"com.asmodeedigital.agameofthrones.adancewithdragons"},{"name":400127,"value":"gotdbg-adwd","originalValue":"gotdbg-adwd"},{"name":100078,"value":1379712203,"originalValue":1379712203},{"name":100079,"value":"7860c6d3f5794e17a694a64d35e191a3","originalValue":"7860c6d3f5794e17a694a64d35e191a3"},{"name":100080,"value":"7dc01c52c98d4a44b07943c5a8b27f7d","originalValue":"7dc01c52c98d4a44b07943c5a8b27f7d"}]}]
|
||||||
|
[Info : Unity Log] [DirusProductProvider] Adding productComponents: [dwd.core.archetypes.ArchetypeComponent: 2229999397666787289], [dwd.core.archetypes.ArchetypeComponent: 5493239248328644216]
|
||||||
|
[Info : Unity Log] [DirusProductProvider] initialized
|
||||||
|
[Info : Unity Log] [IAPStoreModel] Initialize
|
||||||
|
[Info : Unity Log] [IAPStoreModel] PopulateCatalog
|
||||||
|
[Info : Unity Log] [GetIAPCatalogCommand] model.ProductProvider: IAP (dwd.iap.store.DirusProductProvider) initialized: True
|
||||||
|
[Info : Unity Log] [DirusProductProvider] GetProductsForInitialization(steam) Lookup: 2
|
||||||
|
[Info : Unity Log] [IAPStoreModel(debugLoggin: True] Adding product to catalog Label:affc.name
|
||||||
|
Description: affc.description
|
||||||
|
ProductID: 1416391
|
||||||
|
ArchetypeID: 2229999397666787289
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: $4,99
|
||||||
|
Purchaseable: False
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode:
|
||||||
|
CurrencyCode:
|
||||||
|
CurrencySymbol:
|
||||||
|
Image: affc
|
||||||
|
Tags:
|
||||||
|
|
||||||
|
[Info : Unity Log] Adding product to catalog: Label:affc.name
|
||||||
|
Description: affc.description
|
||||||
|
ProductID: 1416391
|
||||||
|
ArchetypeID: 2229999397666787289
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: $4,99
|
||||||
|
Purchaseable: False
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode:
|
||||||
|
CurrencyCode:
|
||||||
|
CurrencySymbol:
|
||||||
|
Image: affc
|
||||||
|
Tags:
|
||||||
|
|
||||||
|
[Info : Unity Log] [IAPStoreModel(debugLoggin: True] Adding product to catalog Label:adwd.name
|
||||||
|
Description: adwd.description
|
||||||
|
ProductID: 1416390
|
||||||
|
ArchetypeID: 5493239248328644216
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: $4,99
|
||||||
|
Purchaseable: False
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode:
|
||||||
|
CurrencyCode:
|
||||||
|
CurrencySymbol:
|
||||||
|
Image: adwd
|
||||||
|
Tags:
|
||||||
|
|
||||||
|
[Info : Unity Log] Adding product to catalog: Label:adwd.name
|
||||||
|
Description: adwd.description
|
||||||
|
ProductID: 1416390
|
||||||
|
ArchetypeID: 5493239248328644216
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: $4,99
|
||||||
|
Purchaseable: False
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode:
|
||||||
|
CurrencyCode:
|
||||||
|
CurrencySymbol:
|
||||||
|
Image: adwd
|
||||||
|
Tags:
|
||||||
|
|
||||||
|
[Info : Unity Log] Get SteamProduct Details: https://store.steampowered.com/api/appdetails?appids=1416391&filters=price_overview&&cc=ES
|
||||||
|
[Info : Unity Log] Got steam product details: {"1416391":{"success":true,"data":{"price_overview":{"currency":"EUR","initial":499,"final":499,"discount_percent":0,"initial_formatted":"","final_formatted":"4,99€"}}}}
|
||||||
|
[Info : Unity Log] Get SteamProduct Details: https://store.steampowered.com/api/appdetails?appids=1416390&filters=price_overview&&cc=ES
|
||||||
|
[Info : Unity Log] Got steam product details: {"1416390":{"success":true,"data":{"price_overview":{"currency":"EUR","initial":499,"final":499,"discount_percent":0,"initial_formatted":"","final_formatted":"4,99€"}}}}
|
||||||
|
[Info : Unity Log] Catalog populated:
|
||||||
|
Label:affc.name
|
||||||
|
Description: affc.description
|
||||||
|
ProductID: 1416391
|
||||||
|
ArchetypeID: 2229999397666787289
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: 4,99€
|
||||||
|
Purchaseable: True
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode: ES
|
||||||
|
CurrencyCode: EUR
|
||||||
|
CurrencySymbol: €
|
||||||
|
Image: affc
|
||||||
|
Tags:
|
||||||
|
|
||||||
|
Label:adwd.name
|
||||||
|
Description: adwd.description
|
||||||
|
ProductID: 1416390
|
||||||
|
ArchetypeID: 5493239248328644216
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: 4,99€
|
||||||
|
Purchaseable: True
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode: ES
|
||||||
|
CurrencyCode: EUR
|
||||||
|
CurrencySymbol: €
|
||||||
|
Image: adwd
|
||||||
|
Tags:
|
||||||
|
|
||||||
|
[Info : Unity Log] [IAPStoreModel] checkPendingClaims
|
||||||
|
[Info : Unity Log] getting products from Asmodee token
|
||||||
|
[Info : Unity Log] no asmodee features found
|
||||||
|
[Info : Unity Log] have token products: False
|
||||||
|
[Info : Unity Log] [GetProductOwnershipCommand] ownedArchIDs: storeModel.Catalog: [1416391, Label:affc.name
|
||||||
|
Description: affc.description
|
||||||
|
ProductID: 1416391
|
||||||
|
ArchetypeID: 2229999397666787289
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: 4,99€
|
||||||
|
Purchaseable: True
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode: ES
|
||||||
|
CurrencyCode: EUR
|
||||||
|
CurrencySymbol: €
|
||||||
|
Image: affc
|
||||||
|
Tags:
|
||||||
|
], [1416390, Label:adwd.name
|
||||||
|
Description: adwd.description
|
||||||
|
ProductID: 1416390
|
||||||
|
ArchetypeID: 5493239248328644216
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: 4,99€
|
||||||
|
Purchaseable: True
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode: ES
|
||||||
|
CurrencyCode: EUR
|
||||||
|
CurrencySymbol: €
|
||||||
|
Image: adwd
|
||||||
|
Tags:
|
||||||
|
]
|
||||||
|
[Info : Unity Log] online? True
|
||||||
|
[Info : Unity Log] initMetdata
|
||||||
|
[Info : Unity Log] check ownershipSavePath: C:/Users/PC/AppData/LocalLow/Dire Wolf Digital/A Game of Thrones_ The Board Game/ownership.json
|
||||||
|
[Info : Unity Log] check local ownership discrepancies
|
||||||
|
[Info : Unity Log] checking local ownership of 1416391, False
|
||||||
|
[Info : Unity Log] checking local ownership of 1416390, False
|
||||||
|
[Info : Unity Log] owned products: 0,
|
||||||
|
[Info : Unity Log] [IAPStoreModel] Initialization complete
|
||||||
|
[Info : Unity Log] listenForAsmodeeLogin loggedIn, refreshOwnedProducts
|
||||||
|
[Info : Unity Log] getting products from Asmodee token
|
||||||
|
[Info : Unity Log] found 5 features: community, fistofdragonstones, profile, queensnecklace, userpages
|
||||||
|
[Info : Unity Log] have token products: False
|
||||||
|
[Info : Unity Log] [GetProductOwnershipCommand] ownedArchIDs: storeModel.Catalog: [1416391, Label:affc.name
|
||||||
|
Description: affc.description
|
||||||
|
ProductID: 1416391
|
||||||
|
ArchetypeID: 2229999397666787289
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: 4,99€
|
||||||
|
Purchaseable: True
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode: ES
|
||||||
|
CurrencyCode: EUR
|
||||||
|
CurrencySymbol: €
|
||||||
|
Image: affc
|
||||||
|
Tags:
|
||||||
|
], [1416390, Label:adwd.name
|
||||||
|
Description: adwd.description
|
||||||
|
ProductID: 1416390
|
||||||
|
ArchetypeID: 5493239248328644216
|
||||||
|
Type: NonConsumable
|
||||||
|
Status: Definition
|
||||||
|
Price: 4,99€
|
||||||
|
Purchaseable: True
|
||||||
|
CultureCode: en
|
||||||
|
CountryCode: ES
|
||||||
|
CurrencyCode: EUR
|
||||||
|
CurrencySymbol: €
|
||||||
|
Image: adwd
|
||||||
|
Tags:
|
||||||
|
]
|
||||||
|
[Info : Unity Log] online? True
|
||||||
|
[Info : Unity Log] initMetdata
|
||||||
|
[Info :Custom Server Mod] [OUT] Packet 1 (Type: AsyncAuthRequest): { "requestNumber": 400, "asyncAuthRequest": { "name": "josledeta", "partnerToken": { "sessionToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhc25ldCI6eyJwYXIiOiJzdGVhbSIsInB1aWQiOiI3NjU2MTE5ODA4NDcyODgxMiJ9LCJpZCI6Ijk4ZWFkZmQ0NjEyODQwOWU1NDViMjNiNjczOWVhNTA5OTE2ODNkNzYiLCJqdGkiOiI5OGVhZGZkNDYxMjg0MDllNTQ1YjIzYjY3MzllYTUwOTkxNjgzZDc2IiwiaXNzIjoiaHR0cHM6XC9cL2FwaS5wbGF5cmVhbC5saXZlIiwiYXVkIjoiZ290ZGJnIiwic3ViIjoiODM4MTc2MyIsImV4cCI6MTc2Nzg4NjA2MywiaWF0IjoxNzY3Nzk5NjYzLCJ0b2tlbl90eXBlIjoiYmVhcmVyIiwic2NvcGUiOiJwcml2YXRlIHB1YmxpYyJ9.aKWVXWVoh8bjea4w7jRqXVHtSl0tCKu-HaePPTXMDNw" }, "userAgent": { "fullUserAgent": "AGameofThrones_TheBoardGame/1.1.0 Windows/Win32NT-10.0.19045.0 Unity/2020.3.31f1" }, "gameType": "GOTDBG", "device": { "type": "STEAM" } } }
|
||||||
|
[Info : Unity Log] check ownershipSavePath: C:/Users/PC/AppData/LocalLow/Dire Wolf Digital/A Game of Thrones_ The Board Game/ownership.json
|
||||||
|
[Info : Unity Log] check local ownership discrepancies
|
||||||
|
[Info : Unity Log] checking local ownership of 1416391, False
|
||||||
|
[Info : Unity Log] checking local ownership of 1416390, False
|
||||||
|
[Info : Unity Log] owned products: 0,
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 1 (Type: AsyncConnectedRequest): { "requestNumber": 406, "asyncConnectedRequest": { "session": { "id": "4016461897007108096" }, "player": { "name": "josledeta", "id": 127, "karma": 52, "rankScore": 1515.0804810769762, "wWWId": 8381763, "nbGames": 2, "avatar": { "image": "avatar-neutral.jpg" }, "language": 1, "tz": "America\\/New_York", "rights": { } } } }
|
||||||
|
[Info : Unity Log] ChangeScene(Lobby)
|
||||||
|
[Info :Custom Server Mod] [OUT] Packet 2 (Type: WhatsNewPussycatRequest): { "requestNumber": 511, "whatsNewPussycatRequest": { } }
|
||||||
|
[Info : Unity Log] Players in lobby:
|
||||||
|
[Info :Custom Server Mod] [OUT] Packet 3 (Type: EnterLobbyRequest): { "requestNumber": 600, "enterLobbyRequest": { } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 3 (Type: LobbyEnteredRequest): { "requestNumber": 601, "lobbyEnteredRequest": { } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: LobbyPlayerListRequest): { "requestNumber": 604, "lobbyPlayerListRequest": { "playerList": "eJzjMuXYsPgpkxBnQGJpcUlqap6hhJHivukps7wnTncw4LJg9BKREkosSyxJLNLNSy0tKUrM0csqSOcy5zj2/R+zELdTTmJytkJYZkFqkYS1osNcnjmxb2c4GPDj1BjMcb91DbMQS7ZBWZWEvKLSiZZtxVmzHQwWMwG1mAmZZJSUFBRb6euXFuTkJ6YU6yUW5+anpKbq5aWW6CeVZuaUZObpFxXpQk02MAYbasqx+OUfZiHO0ryU1CIzIJAQVtz055x312WgJ9hwusWEY94koFvYC/KLSvJzEiW4FSdIMlxc+3qmg8FNJpy6jDlO3/wPtCwlPzOvJL8I6HEjRQYQKJjugFOTKcfhUyBNWfnFOakpqSWJEiaKtssWZgatAboQp10A0auMEg==" } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: LobbyGameListRequest): { "requestNumber": 609, "lobbyGameListRequest": { "gameList": "eJzjmsbM0bCgoePhjb6H5kLBXNxOOYnJ2QphmQWpRQKMGtaWDnN55sS+neHgcez7P+YI/iIRKaHEssSSxCLdvNTSkqLEHL2sgvQKxiZGQcfc1KLM5MQYfb/U8vjI/KLsSYwMCxgZhQK42Avyi0rycxIFmDS4LSdIMlxc+3qmg8e8SWuYI24ykWFiPhdLtkFZlQCzhryl0omWbcVZsx087rcCjVvMVGQmZJJRUlJQbKWvX1qQk5+YUqyXWJybn5KaqpeXWqKfVJqZU5KZp19UpAu11cCYkIVSflzCSOGirlCskJ6Ym6rAqMFmwObA6MEYMEEmSsmKnQMURgIcVkyY2AEklcHYwcg4gTFKiQi1ADJKgF0=" } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: ClientChatRequest): { "requestNumber": 525, "clientChatRequest": { "text": "\nThe chat function has been enabled.\n\n\nType /help for a list of commands\n\nType /report nickname to signal any inappropriate behavior\n", "sender": { "name": "Administrator", "id": 0, "wWWId": 0, "avatar": { "location": "https://cdn.asmodee.net/img/asmo-digital-logo.png" } } } }
|
||||||
|
[Info : Unity Log] Players in lobby:
|
||||||
|
[Info :Custom Server Mod] [OUT] Packet 4 (Type: AskServerStatisticsRequest): { "requestNumber": 408, "askServerStatisticsRequest": { "subscribe": true } }
|
||||||
|
[Info : Unity Log] Players in lobby: Pausteen1, Black Viper, k0vz, under6666, portola, dointorer, josledeta
|
||||||
|
[Info : Unity Log] GameID: 4017838439931056128, name: Black Viper' s game, players: Black Viper, portola, k0vz
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: ServerStatisticsRequest): { "requestNumber": 409, "serverStatisticsRequest": { "hostedGames": 3899, "players": 3050, "connectedPlayers": 56 } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: ServerStatisticsRequest): { "requestNumber": 409, "serverStatisticsRequest": { "hostedGames": 3899, "players": 3050, "connectedPlayers": 56 } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: ServerStatisticsRequest): { "requestNumber": 409, "serverStatisticsRequest": { "hostedGames": 3899, "players": 3050, "connectedPlayers": 57 } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: ServerStatisticsRequest): { "requestNumber": 409, "serverStatisticsRequest": { "hostedGames": 3899, "players": 3050, "connectedPlayers": 58 } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: LobbyPlayerListRequest): { "requestNumber": 604, "lobbyPlayerListRequest": { "playerList": "eJzjMuXYsPgpkxBnQGJpcUlqap6hhJHivukps7wnTncw4LJg9BKREkosSyxJLNLNSy0tKUrM0csqSOcy5zj2/R+zELdTTmJytkJYZkFqkYS1osNcnjmxb2c4GPDj1BjMcb91DbMQS7ZBWZWEvKLSiZZtxVmzHQwWMwG1mAmZZJSUFBRb6euXFuTkJ6YU6yUW5+anpKbq5aWW6CeVZuaUZObpFxXpQk02MAYbasqx+OUfZiHO0ryU1CIzIJAQVtz055x312WgJ9hwusWEY94koFvYC/KLSvJzEiW4FSdIMlxc+3qmg8FNJpy6jDlO3/wPtCwlPzOvJL8I6HEjRQYQKJjugFOTKcfhUyBNWfnFOakpqSWJEiaKtssWZgatAboQp10A0auMEg==" } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: LobbyGameListRequest): { "requestNumber": 609, "lobbyGameListRequest": { "gameList": "eJzjmsbM0bCgoePhjb6H5kLBXNxOOYnJ2QphmQWpRQKMGtaWDnN55sS+neHgcez7P+YI/iIRKaHEssSSxCLdvNTSkqLEHL2sgvQKxiZGQcfc1KLM5MQYfb/U8vjI/KLsSYwMCxgZhQK42Avyi0rycxIFmDS4LSdIMlxc+3qmg8e8SWuYI24ykWFiPhdLtkFZlQCzhryl0omWbcVZsx087rcCjVvMVGQmZJJRUlJQbKWvX1qQk5+YUqyXWJybn5KaqpeXWqKfVJqZU5KZp19UpAu11cCYkIVSflzCSOGirlCskJ6Ym6rAqMFmwObA6MEYMEEmSsmKnQMURgIcVkyY2AEklcHYwcg4gTFKiQi1ADJKgF0=" } }
|
||||||
|
[Info : Unity Log] Players in lobby: Pausteen1, Black Viper, k0vz, under6666, portola, dointorer, josledeta
|
||||||
|
[Info : Unity Log] GameID: 4017838439931056128, name: Black Viper' s game, players: Black Viper, portola, k0vz
|
||||||
|
[Info :Custom Server Mod] [OUT] Packet 5 (Type: PingRequest): { "requestNumber": 777, "pingRequest": { "timestamp": "1767799695" } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 5 (Type: PingRequest): { "requestNumber": 777, "pingRequest": { "timestamp": "1767799695" } }
|
||||||
|
[Info : Unity Log] ChangeScene(Landing)
|
||||||
|
[Info :Custom Server Mod] [OUT] Packet 6 (Type: AskServerStatisticsRequest): { "requestNumber": 408, "askServerStatisticsRequest": { } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 0 (Type: ServerStatisticsRequest): { "requestNumber": 409, "serverStatisticsRequest": { "hostedGames": 3899, "players": 3050, "connectedPlayers": 58 } }
|
||||||
|
[Info :Custom Server Mod] [OUT] Packet 7 (Type: ExitLobbyRequest): { "requestNumber": 602, "exitLobbyRequest": { } }
|
||||||
|
[Info :Custom Server Mod] [IN] Packet 7 (Type: LobbyExitedRequest): { "requestNumber": 603, "lobbyExitedRequest": { } }
|
||||||
308
log.oficial.txt
Normal file
308
log.oficial.txt
Normal file
File diff suppressed because one or more lines are too long
BIN
server.exe
Normal file
BIN
server.exe
Normal file
Binary file not shown.
98
session_20260107_174406.jsonl
Normal file
98
session_20260107_174406.jsonl
Normal file
File diff suppressed because one or more lines are too long
259
session_20260107_175522.jsonl
Normal file
259
session_20260107_175522.jsonl
Normal file
File diff suppressed because one or more lines are too long
73
session_20260107_180244.jsonl
Normal file
73
session_20260107_180244.jsonl
Normal file
File diff suppressed because one or more lines are too long
54
session_20260107_181927.jsonl
Normal file
54
session_20260107_181927.jsonl
Normal file
File diff suppressed because one or more lines are too long
14
session_20260107_185529.jsonl
Normal file
14
session_20260107_185529.jsonl
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{"timestamp":"2026-01-07T18:55:42.2207946+01:00","protocol":"HTTP","type":"REQUEST","summary":"POST /main/v2/oauth/token","data":{"body":"User-Agent=AGameofThrones_TheBoardGame%2f1.1.0%20Windows%2fWin32NT-10.0.19045.0%20Unity%2f2020.3.31f1\u0026Cache-Control=no-cache\u0026Content-Type=application%2fx-www-form-urlencoded\u0026client_secret=7tSf9FAPMHq6P2rR98plIb9AyGXppyAXng4V1LwEDC0%3d\u0026refresh_token=mock_refresh_token_67890\u0026grant_type=refresh_token\u0026client_id=gotdbg","headers":{"Accept":["*/*"],"Accept-Encoding":["deflate, gzip"],"Content-Length":["315"],"Content-Type":["application/x-www-form-urlencoded"],"User-Agent":["UnityPlayer/2020.3.31f1 (UnityWebRequest/1.0, libcurl/7.80.0-DEV)"],"X-Unity-Version":["2020.3.31f1"]}}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:42.6449839+01:00","protocol":"HTTP","type":"RESPONSE","summary":"/main/v2/oauth/token 400","data":{"body":"{\"status\":400,\"error\":\"invalid_grant\",\"error_description\":\"Invalid refresh token\",\"error_code\":\"API027\"}","headers":{"Access-Control-Allow-Headers":["X-Requested-With, Content-Type, Accept, Origin, Authorization"],"Access-Control-Allow-Methods":["GET,HEAD,PUT,POST,DELETE"],"Access-Control-Allow-Origin":["*"],"Cache-Control":["no-store"],"Content-Type":["application/json"],"Date":["Wed, 07 Jan 2026 17:55:22 GMT"],"Server":["nginx"],"X-Ratelimit-Limit":["2000"],"X-Ratelimit-Remaining":["1999"],"X-Ratelimit-Reset":["1767810322"],"X-Request-Id":["1Qy4Ezzsc7fFKHko"]},"status":400}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:51.2160652+01:00","protocol":"HTTP","type":"REQUEST","summary":"POST /main/v2/oauth/token","data":{"body":"User-Agent=AGameofThrones_TheBoardGame%2f1.1.0%20Windows%2fWin32NT-10.0.19045.0%20Unity%2f2020.3.31f1\u0026Cache-Control=no-cache\u0026Content-Type=application%2fx-www-form-urlencoded\u0026client_secret=7tSf9FAPMHq6P2rR98plIb9AyGXppyAXng4V1LwEDC0%3d\u0026session_ticket=1400000006CDA16BAAB4B87AEC276B070100100128995E6918000000010000000200000002ABF7610AC1D3C4BF6A2F027A000000B20000003200000004000000EC276B0701001001F66710005B6C7954A400A8C000000000FDF55B697DA577690100FE80050000000000C667618194375E72E84D5CE90C866869F7376DD095686967FB53CA4FE0E6B101CDD65B27BA52D45E2448EEC20CA58A5B9F368530EF1AC4C6A75F6AF01DEC010CF9344A19BC4C58D959FCF14AF229BB27F3A715208F6301ED276041B79D202AB9D6D265AE170E266197D8B0D13EC45EBB043B7A0CCB3B60A27FB0373E1FF8F0E1\u0026app_id=1075190\u0026grant_type=steam_partner\u0026client_id=gotdbg","headers":{"Accept":["*/*"],"Accept-Encoding":["deflate, gzip"],"Content-Length":["775"],"Content-Type":["application/x-www-form-urlencoded"],"User-Agent":["UnityPlayer/2020.3.31f1 (UnityWebRequest/1.0, libcurl/7.80.0-DEV)"],"X-Unity-Version":["2020.3.31f1"]}}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:51.7680506+01:00","protocol":"HTTP","type":"RESPONSE","summary":"/main/v2/oauth/token 200","data":{"body":"{\"access_token\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhc25ldCI6eyJwYXIiOiJzdGVhbSIsInB1aWQiOiI3NjU2MTE5ODA4NDcyODgxMiJ9LCJpZCI6IjQ2ZjZiNWI4MjNiNzRhNDY1MjkyYjY5MWU1OTk1YzcwMmNjOTlkYjQiLCJqdGkiOiI0NmY2YjViODIzYjc0YTQ2NTI5MmI2OTFlNTk5NWM3MDJjYzk5ZGI0IiwiaXNzIjoiaHR0cHM6XC9cL2FwaS5wbGF5cmVhbC5saXZlIiwiYXVkIjoiZ290ZGJnIiwic3ViIjoiODM4MTc2MyIsImV4cCI6MTc2Nzg5NDkzMSwiaWF0IjoxNzY3ODA4NTMxLCJ0b2tlbl90eXBlIjoiYmVhcmVyIiwic2NvcGUiOiJwcml2YXRlIHB1YmxpYyJ9.CcC-VUgBQav-Xai96bwcGZzR5cj4IFMecP-yeEJouQc\",\"expires_in\":86400,\"token_type\":\"bearer\",\"scope\":\"private public\",\"refresh_token\":\"db2252dbb66f5f98e6bee1bc34f634c83f931b31\"}","headers":{"Access-Control-Allow-Headers":["X-Requested-With, Content-Type, Accept, Origin, Authorization"],"Access-Control-Allow-Methods":["GET,HEAD,PUT,POST,DELETE"],"Access-Control-Allow-Origin":["*"],"Cache-Control":["no-store"],"Content-Type":["application/json"],"Date":["Wed, 07 Jan 2026 17:55:31 GMT"],"Pragma":["no-cache"],"Server":["nginx"],"X-Ratelimit-Limit":["2000"],"X-Ratelimit-Remaining":["1998"],"X-Ratelimit-Reset":["1767810322"],"X-Request-Id":["-aAIM7-Ce-jNu4GE"]},"status":200}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:51.7862575+01:00","protocol":"HTTP","type":"REQUEST","summary":"GET /main/v1/user/me","data":{"body":"","headers":{"Accept":["*/*"],"Accept-Encoding":["deflate, gzip"],"Authorization":["bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhc25ldCI6eyJwYXIiOiJzdGVhbSIsInB1aWQiOiI3NjU2MTE5ODA4NDcyODgxMiJ9LCJpZCI6IjQ2ZjZiNWI4MjNiNzRhNDY1MjkyYjY5MWU1OTk1YzcwMmNjOTlkYjQiLCJqdGkiOiI0NmY2YjViODIzYjc0YTQ2NTI5MmI2OTFlNTk5NWM3MDJjYzk5ZGI0IiwiaXNzIjoiaHR0cHM6XC9cL2FwaS5wbGF5cmVhbC5saXZlIiwiYXVkIjoiZ290ZGJnIiwic3ViIjoiODM4MTc2MyIsImV4cCI6MTc2Nzg5NDkzMSwiaWF0IjoxNzY3ODA4NTMxLCJ0b2tlbl90eXBlIjoiYmVhcmVyIiwic2NvcGUiOiJwcml2YXRlIHB1YmxpYyJ9.CcC-VUgBQav-Xai96bwcGZzR5cj4IFMecP-yeEJouQc"],"User-Agent":["AGameofThrones_TheBoardGame/1.1.0 Windows/Win32NT-10.0.19045.0 Unity/2020.3.31f1"],"X-Unity-Version":["2020.3.31f1"]}}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:51.9672444+01:00","protocol":"HTTP","type":"RESPONSE","summary":"/main/v1/user/me 200","data":{"body":"{\"data\":{\"user\":{\"login_name\":\"josledeta\",\"country\":\"US\",\"email_valid\":true,\"language\":\"en\",\"time_zone\":\"America\\/New_York\",\"posted_msg_count\":0,\"last_post_id\":0,\"validated\":true,\"features\":[\"community\",\"fistofdragonstones\",\"profile\",\"queensnecklace\",\"userpages\"],\"boardgames\":[],\"onlinegames\":[],\"user_id\":8381763,\"avatar\":\"https:\\/\\/uploads.asmodee.net\\/builtin\\/avatar-neutral.jpg\",\"zipcode\":\"\",\"birthday\":0,\"email\":\"josele.96@gmail.com\",\"gender\":\"UNSPECIFIED\",\"coppa\":false,\"name\":\"\",\"avatarApproved\":true,\"partners\":[{\"id\":6947520,\"partner_id\":12,\"partner_user_id\":\"76561198084728812\",\"created_at\":\"2026-01-05T17:35:21+00:00\",\"partner_user_displayname\":\"JosLeDeta\"}],\"join_date_ts\":1767634515,\"join_date\":\"2026-01-05T17:35:15+00:00\",\"join_date_cet\":\"2026-01-05T18:35:15+01:00\",\"last_visit_ts\":1767808531,\"last_visit\":\"2026-01-07T17:55:31+00:00\",\"last_visit_cet\":\"2026-01-07T18:55:31+01:00\"}},\"error\":false,\"status\":200}","headers":{"Access-Control-Allow-Headers":["X-Requested-With, Content-Type, Accept, Origin, Authorization"],"Access-Control-Allow-Methods":["GET,HEAD,PUT,POST,DELETE"],"Access-Control-Allow-Origin":["*"],"Content-Type":["application/json"],"Date":["Wed, 07 Jan 2026 17:55:31 GMT"],"Server":["nginx"],"X-Client-Id":["gotdbg"],"X-Ratelimit-Limit":["2000"],"X-Ratelimit-Remaining":["1999"],"X-Ratelimit-Reset":["1767810331"],"X-Request-Id":["wPNNYhwyb1El5bhQ"]},"status":200}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:51.9985712+01:00","protocol":"HTTP","type":"REQUEST","summary":"GET /main/v1/users","data":{"body":"","headers":{"Accept":["*/*"],"Accept-Encoding":["deflate, gzip"],"Authorization":["bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhc25ldCI6eyJwYXIiOiJzdGVhbSIsInB1aWQiOiI3NjU2MTE5ODA4NDcyODgxMiJ9LCJpZCI6IjQ2ZjZiNWI4MjNiNzRhNDY1MjkyYjY5MWU1OTk1YzcwMmNjOTlkYjQiLCJqdGkiOiI0NmY2YjViODIzYjc0YTQ2NTI5MmI2OTFlNTk5NWM3MDJjYzk5ZGI0IiwiaXNzIjoiaHR0cHM6XC9cL2FwaS5wbGF5cmVhbC5saXZlIiwiYXVkIjoiZ290ZGJnIiwic3ViIjoiODM4MTc2MyIsImV4cCI6MTc2Nzg5NDkzMSwiaWF0IjoxNzY3ODA4NTMxLCJ0b2tlbl90eXBlIjoiYmVhcmVyIiwic2NvcGUiOiJwcml2YXRlIHB1YmxpYyJ9.CcC-VUgBQav-Xai96bwcGZzR5cj4IFMecP-yeEJouQc"],"User-Agent":["AGameofThrones_TheBoardGame/1.1.0 Windows/Win32NT-10.0.19045.0 Unity/2020.3.31f1"],"X-Unity-Version":["2020.3.31f1"]}}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:52.1698572+01:00","protocol":"HTTP","type":"RESPONSE","summary":"/main/v1/users 200","data":{"body":"{\"data\":{\"total\":1,\"_links\":{\"first\":\"https:\\/\\/api.asmodee.net\\/main\\/v1\\/users?ids=8381763\u0026extras=avatar\u0026limit=100\u0026offset=0\",\"last\":\"https:\\/\\/api.asmodee.net\\/main\\/v1\\/users?ids=8381763\u0026extras=avatar\u0026limit=100\u0026offset=0\"},\"users\":[{\"user_id\":8381763,\"login_name\":\"josledeta\",\"avatar\":\"https:\\/\\/uploads.asmodee.net\\/builtin\\/avatar-neutral.jpg\"}]},\"error\":false,\"status\":200}","headers":{"Access-Control-Allow-Headers":["X-Requested-With, Content-Type, Accept, Origin, Authorization"],"Access-Control-Allow-Methods":["GET,HEAD,PUT,POST,DELETE"],"Access-Control-Allow-Origin":["*"],"Content-Type":["application/json"],"Date":["Wed, 07 Jan 2026 17:55:32 GMT"],"Server":["nginx"],"X-Cache":["MISS"],"X-Client-Id":["gotdbg"],"X-Ratelimit-Limit":["2000"],"X-Ratelimit-Remaining":["1998"],"X-Ratelimit-Reset":["1767810331"],"X-Request-Id":["Ip8ItuWbFNhPTLRY"]},"status":200}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:55.5015658+01:00","protocol":"TCP","type":"CLIENT-\u003eSERVER","summary":"Packet -1 | Req 200 | Len 602","data":{"hex":"08011ad5040890038219ce040a096a6f736c65646574611ae0031add0365794a30655841694f694a4b563151694c434a68624763694f694a49557a49314e694a392e65794a686332356c6443493665794a77595849694f694a7a6447566862534973496e4231615751694f6949334e6a55324d5445354f4441344e4463794f4467784d694a394c434a705a434936496a51325a6a5a694e5749344d6a4e694e7a52684e4459314d6a6b79596a59354d5755314f546b31597a63774d6d4e6a4f546c6b596a51694c434a7164476b694f6949304e6d5932596a56694f44497a596a6330595451324e5449354d6d49324f54466c4e546b354e574d334d444a6a597a6b355a4749304969776961584e7a496a6f696148523063484d36584339634c3246776153357762474635636d56686243357361585a6c496977695958566b496a6f695a3239305a474a6e4969776963335669496a6f694f444d344d5463324d794973496d5634634349364d5463324e7a67354e446b7a4d53776961574630496a6f784e7a59334f4441344e544d784c434a306232746c626c39306558426c496a6f69596d5668636d56794969776963324e76634755694f694a77636d6c325958526c49484231596d787059794a392e4363432d565567425161762d5861693936627763475a7a5235636a3449464d6563502d7965454a6f755163225212504147616d656f665468726f6e65735f546865426f61726447616d652f312e312e302057696e646f77732f57696e33324e542d31302e302e31393034352e3020556e6974792f323032302e332e333166313206474f544442473a021005","packet_id":-1,"request_id":200}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:55.7681117+01:00","protocol":"TCP","type":"SERVER-\u003eCLIENT","summary":"Packet -1 | Req 203 | Len 105","data":{"hex":"08011a65089603b2195f0a0a0880a080888bdad5de3712510a096a6f736c6564657461107f2834393da6a16952ac974048c3caff03580272141a126176617461722d6e65757472616c2e6a70677801820111416d65726963615c2f4e65775f596f726b920100a00101","packet_id":-1,"request_id":203}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:55.7820519+01:00","protocol":"HTTP","type":"REQUEST","summary":"GET /main/v1/user/me/buddies","data":{"body":"","headers":{"Accept":["*/*"],"Accept-Encoding":["deflate, gzip"],"Authorization":["bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhc25ldCI6eyJwYXIiOiJzdGVhbSIsInB1aWQiOiI3NjU2MTE5ODA4NDcyODgxMiJ9LCJpZCI6IjQ2ZjZiNWI4MjNiNzRhNDY1MjkyYjY5MWU1OTk1YzcwMmNjOTlkYjQiLCJqdGkiOiI0NmY2YjViODIzYjc0YTQ2NTI5MmI2OTFlNTk5NWM3MDJjYzk5ZGI0IiwiaXNzIjoiaHR0cHM6XC9cL2FwaS5wbGF5cmVhbC5saXZlIiwiYXVkIjoiZ290ZGJnIiwic3ViIjoiODM4MTc2MyIsImV4cCI6MTc2Nzg5NDkzMSwiaWF0IjoxNzY3ODA4NTMxLCJ0b2tlbl90eXBlIjoiYmVhcmVyIiwic2NvcGUiOiJwcml2YXRlIHB1YmxpYyJ9.CcC-VUgBQav-Xai96bwcGZzR5cj4IFMecP-yeEJouQc"],"User-Agent":["AGameofThrones_TheBoardGame/1.1.0 Windows/Win32NT-10.0.19045.0 Unity/2020.3.31f1"],"X-Unity-Version":["2020.3.31f1"]}}}
|
||||||
|
{"timestamp":"2026-01-07T18:55:55.9335852+01:00","protocol":"HTTP","type":"RESPONSE","summary":"/main/v1/user/me/buddies 200","data":{"body":"{\"data\":{\"total\":0,\"_links\":{\"first\":\"https:\\/\\/api.asmodee.net\\/main\\/v1\\/user\\/me\\/buddies?offset=0\u0026limit=20\",\"last\":\"https:\\/\\/api.asmodee.net\\/main\\/v1\\/user\\/me\\/buddies?offset=0\u0026limit=20\"},\"buddies\":[]},\"error\":false,\"status\":200}","headers":{"Access-Control-Allow-Headers":["X-Requested-With, Content-Type, Accept, Origin, Authorization"],"Access-Control-Allow-Methods":["GET,HEAD,PUT,POST,DELETE"],"Access-Control-Allow-Origin":["*"],"Content-Type":["application/json"],"Date":["Wed, 07 Jan 2026 17:55:35 GMT"],"Server":["nginx"],"X-Client-Id":["gotdbg"],"X-Ratelimit-Limit":["2000"],"X-Ratelimit-Remaining":["1997"],"X-Ratelimit-Reset":["1767810331"],"X-Request-Id":["NbEb20YvfFazkXwl"]},"status":200}}
|
||||||
|
{"timestamp":"2026-01-07T18:56:05.7802083+01:00","protocol":"TCP","type":"CLIENT-\u003eSERVER","summary":"Packet 1 | Req -389 | Len 16","data":{"hex":"08021a0c088906ca300608b5bcfaca06","packet_id":1,"request_id":-389}}
|
||||||
|
{"timestamp":"2026-01-07T18:56:05.8800322+01:00","protocol":"TCP","type":"SERVER-\u003eCLIENT","summary":"Packet 1 | Req -389 | Len 16","data":{"hex":"08021a0c088906ca300608b5bcfaca06","packet_id":1,"request_id":-389}}
|
||||||
Reference in New Issue
Block a user