init commit
This commit is contained in:
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
Reference in New Issue
Block a user