package companion import ( "context" "errors" "fmt" "math" "sort" "strings" "unicode/utf8" ) var errAdapterUnsupported = errors.New("runtime capability is unavailable") // AuthorizedConfigPort is supplied through the platform-authorized Run channel. // It exposes logical, allowlisted configuration values only; it never exposes a // path, DSN, credential, arbitrary command, or database handle. type AuthorizedConfigPort interface { ReadConfig(context.Context) (map[string]string, error) ApplyConfigPatch(context.Context, string, []ConfigFieldPatch) (map[string]string, error) } type ConfigFieldPatch struct { Key string Value string } // AuthorizedGameDataPort is a typed, Run-owned read/patch boundary. Implementations // must probe their local schema, use field allowlists and safe windows, and return // bounded snapshots rather than rows or connection details. type AuthorizedGameDataPort interface { ReadPlayerState(context.Context, string, []string) (PlayerStateSnapshot, error) ApplyPlayerState(context.Context, PlayerStatePatch) (PlayerStateSnapshot, error) } type PlayerStateSnapshot struct { PlayerID string StateVersion string SafeWindow bool Fields map[string]float64 } type PlayerStatePatch struct { PlayerID string ExpectedStateVersion string Fields []StateFieldPatch } type StateFieldPatch struct { Key string Before float64 After float64 } // AuthorizedRewardPort accepts a frozen grant with typed items and operations. type AuthorizedRewardPort interface { DeliverReward(context.Context, RewardGrant) (DeliveryReceipt, error) } type RewardGrant struct { GrantID string PlayerID string Items []RewardItem Operations []string } type RewardItem struct { CatalogCode string Quantity int } type DeliveryReceipt struct { Outcome string DeliveryID string } type AuthorizedEventPort interface { StartEvent(context.Context, EventStartRequest) (EventStartReceipt, error) } type EventStartRequest struct { EventID string EventType string Class int Title string Placard string Percent int NPC int Item int Zombie int Animal int Produces []EventProduceRequest DurationSeconds int MaxParticipants int Announce bool } type EventProduceRequest struct { TradeGoodsID string Percent int Value int Radius float64 X float64 Y float64 Z float64 } type EventStartReceipt struct { Accepted bool Status string EventID string Message string } const fixedNotificationType = 4 type UE4SSNotificationPort interface { SendPlayerNotification(context.Context, ue4SSPlayerNotification) (UE4SSNotificationReceipt, error) } type UE4SSNotificationReceipt struct{ Accepted bool } type ue4SSPlayerNotification struct { ServerID string RecipientSteamID string Message string chatType int protectedAuditCommand string } type UE4SSVehicleSpawnPort interface { SpawnVehicle(context.Context, ue4SSVehicleSpawn) (UE4SSVehicleSpawnReceipt, error) } type UE4SSVehicleSpawnOutcome string const ( UE4SSVehicleSpawnAccepted UE4SSVehicleSpawnOutcome = "accepted" UE4SSVehicleSpawnRejected UE4SSVehicleSpawnOutcome = "rejected" UE4SSVehicleSpawnUnknown UE4SSVehicleSpawnOutcome = "unknown" ) type UE4SSVehicleSpawnReceipt struct{ Outcome UE4SSVehicleSpawnOutcome } type ue4SSVehicleSpawn struct { ServerID string VehicleCode string protectedAuditCommand string } // RuntimeAdapter is bound to one server. Availability is discovered from its // configured typed ports and declared capabilities. A failed probe affects // only its operation. type RuntimeAdapter struct { BoundServerID string Config AuthorizedConfigPort GameData AuthorizedGameDataPort Rewards AuthorizedRewardPort Events AuthorizedEventPort Notification UE4SSNotificationPort VehicleSpawn UE4SSVehicleSpawnPort DiagnosticsState map[string]string } func (adapter RuntimeAdapter) ServerBinding() string { return adapter.BoundServerID } func (adapter RuntimeAdapter) ReadConfiguration(ctx context.Context) (map[string]any, error) { if adapter.Config == nil { return nil, errAdapterUnsupported } fields, err := adapter.Config.ReadConfig(ctx) if err != nil { return nil, err } return map[string]any{"fields": redactConfigValues(fields)}, nil } func (adapter RuntimeAdapter) PatchConfiguration(ctx context.Context, payload map[string]any) (map[string]any, error) { if adapter.Config == nil { return nil, errAdapterUnsupported } revision, _ := payload["revision"].(string) raw, _ := payload["fields"].([]any) fields := make([]ConfigFieldPatch, 0, len(raw)) for _, value := range raw { item, ok := value.(map[string]any) if !ok { return nil, fmt.Errorf("configuration patch payload is invalid") } key, keyOK := item["key"].(string) fieldValue, valueOK := item["value"].(string) if !keyOK || !valueOK || !supportedConfigKey(key) { return nil, fmt.Errorf("configuration patch field is unsupported") } fields = append(fields, ConfigFieldPatch{Key: key, Value: fieldValue}) } applied, err := adapter.Config.ApplyConfigPatch(ctx, revision, fields) if err != nil { return nil, err } return map[string]any{"appliedFields": redactConfigValues(applied)}, nil } func (adapter RuntimeAdapter) Diagnostics(context.Context) (map[string]any, error) { state := map[string]any{"adapter": "runtime-capability"} for key, value := range adapter.DiagnosticsState { if safeDiagnosticField(key, value) { state[key] = value } } return state, nil } func (adapter RuntimeAdapter) PatchGameState(ctx context.Context, payload map[string]any) (map[string]any, error) { if adapter.GameData == nil { return nil, errAdapterUnsupported } playerID, _ := payload["playerId"].(string) expected, _ := payload["expectedStateVersion"].(string) raw, _ := payload["changes"].([]any) fields, err := statePatchFields(raw) if err != nil { return nil, err } before, err := adapter.GameData.ReadPlayerState(ctx, playerID, statePatchKeys(fields)) if err != nil { return nil, err } if before.PlayerID != playerID || before.StateVersion != expected || !before.SafeWindow || !stateMatches(before.Fields, fields) { return map[string]any{"outcome": "failed"}, nil } after, err := adapter.GameData.ApplyPlayerState(ctx, PlayerStatePatch{PlayerID: playerID, ExpectedStateVersion: expected, Fields: fields}) if err != nil { return map[string]any{"outcome": "unknown"}, nil } confirmed, err := adapter.GameData.ReadPlayerState(ctx, playerID, statePatchKeys(fields)) if err != nil || after.PlayerID != playerID || !stateApplied(confirmed.Fields, fields) { return map[string]any{"outcome": "unknown"}, nil } return map[string]any{"outcome": "succeeded", "changedFields": len(fields)}, nil } func (adapter RuntimeAdapter) DeliverReward(ctx context.Context, payload map[string]any) (map[string]any, error) { if adapter.Rewards == nil { return nil, errAdapterUnsupported } grant, err := rewardGrant(payload) if err != nil { return nil, err } receipt, err := adapter.Rewards.DeliverReward(ctx, grant) if err != nil || receipt.Outcome == "unknown" { return map[string]any{"accepted": false, "status": "rejected", "message": "reward delivery result is unknown"}, nil } if receipt.Outcome != "delivered" { return map[string]any{"accepted": false, "status": "rejected"}, nil } result := map[string]any{"accepted": true, "status": "delivered"} if receipt.DeliveryID != "" { result["deliveryId"] = receipt.DeliveryID } return result, nil } func (adapter RuntimeAdapter) StartEvent(ctx context.Context, payload map[string]any) (map[string]any, error) { if adapter.Events == nil { return nil, errAdapterUnsupported } request, err := eventStartRequest(payload) if err != nil { return nil, err } receipt, err := adapter.Events.StartEvent(ctx, request) if err != nil { return nil, err } status := receipt.Status if status == "" { status = "rejected" } if status != "started" && status != "queued" && status != "rejected" { return nil, fmt.Errorf("event start receipt is invalid") } eventID := receipt.EventID if eventID == "" { eventID = request.EventID } result := map[string]any{"accepted": receipt.Accepted, "status": status, "eventId": eventID} if receipt.Message != "" { result["message"] = receipt.Message } return result, nil } func (adapter RuntimeAdapter) NotifyPlayer(ctx context.Context, payload map[string]any) (map[string]any, error) { if adapter.BoundServerID == "" || adapter.Notification == nil { return nil, errAdapterUnsupported } playerID, playerOK := payload["playerId"].(string) message, messageOK := payload["message"].(string) notification, err := newUE4SSPlayerNotification(adapter.BoundServerID, playerID, message) if !playerOK || !messageOK || err != nil { return nil, fmt.Errorf("notification payload is invalid") } receipt, err := adapter.Notification.SendPlayerNotification(ctx, notification) if err != nil { return nil, fmt.Errorf("notification transport failed") } return map[string]any{"accepted": receipt.Accepted}, nil } func (adapter RuntimeAdapter) SpawnVehicle(ctx context.Context, payload map[string]any) (map[string]any, error) { if adapter.BoundServerID == "" || adapter.VehicleSpawn == nil { return nil, errAdapterUnsupported } vehicleCode, ok := payload["vehicleCode"].(string) spawn, err := newUE4SSVehicleSpawn(adapter.BoundServerID, vehicleCode) if !ok || err != nil { return nil, fmt.Errorf("vehicle spawn payload is invalid") } receipt, err := adapter.VehicleSpawn.SpawnVehicle(ctx, spawn) if err != nil || receipt.Outcome == UE4SSVehicleSpawnUnknown { return map[string]any{"outcome": "unknown"}, nil } if receipt.Outcome == UE4SSVehicleSpawnRejected { return map[string]any{"outcome": "failed"}, nil } if receipt.Outcome != UE4SSVehicleSpawnAccepted { return map[string]any{"outcome": "unknown"}, nil } return map[string]any{"outcome": "succeeded"}, nil } func newUE4SSPlayerNotification(serverID, playerID, message string) (ue4SSPlayerNotification, error) { if strings.TrimSpace(serverID) == "" || !steamID64(playerID) || !validNotificationMessage(message) { return ue4SSPlayerNotification{}, fmt.Errorf("invalid typed notification") } return ue4SSPlayerNotification{ServerID: serverID, RecipientSteamID: playerID, Message: message, chatType: fixedNotificationType, protectedAuditCommand: "SendChat 4 \"" + escapeUE4SSChatMessage(message) + "\" " + playerID}, nil } func newUE4SSVehicleSpawn(serverID, vehicleCode string) (ue4SSVehicleSpawn, error) { if strings.TrimSpace(serverID) == "" || !supportedVehicleSpawnCode(vehicleCode) { return ue4SSVehicleSpawn{}, fmt.Errorf("invalid typed vehicle spawn") } return ue4SSVehicleSpawn{ServerID: serverID, VehicleCode: vehicleCode, protectedAuditCommand: "#spawnvehicle " + vehicleCode}, nil } func steamID64(value string) bool { if len(value) != 17 { return false } for _, character := range value { if character < '0' || character > '9' { return false } } return true } func validNotificationMessage(value string) bool { if value == "" || len(value) > 200 || !utf8.ValidString(value) { return false } for _, character := range value { if character < 0x20 || character == 0x7f { return false } } return true } func escapeUE4SSChatMessage(value string) string { return strings.NewReplacer("\\", "\\\\", "\"", "\\\"").Replace(value) } func supportedVehicleSpawnCode(value string) bool { return map[string]bool{"BPC_Laika_C": true, "BPC_WolfsWagen_C": true}[value] } func supportedConfigKey(key string) bool { return map[string]bool{"ServerName": true, "GamePort": true, "QueryPort": true, "MaxPlayers": true, "WelcomeMessage": true}[key] } func redactConfigValues(values map[string]string) map[string]string { result := map[string]string{} keys := make([]string, 0, len(values)) for key := range values { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { if supportedConfigKey(key) && safeDiagnosticField(key, values[key]) { result[key] = values[key] } } return result } func safeDiagnosticField(key, value string) bool { lowered := strings.ToLower(key + "=" + value) return !strings.Contains(lowered, "path") && !strings.Contains(lowered, "credential") && !strings.Contains(lowered, "password") && !strings.Contains(lowered, "bearer ") && !strings.Contains(lowered, "rcon") && !strings.Contains(lowered, "sql") && !strings.Contains(lowered, "://") } func statePatchFields(raw []any) ([]StateFieldPatch, error) { if len(raw) == 0 || len(raw) > 8 { return nil, fmt.Errorf("state patch payload is invalid") } fields := make([]StateFieldPatch, 0, len(raw)) for _, value := range raw { item, ok := value.(map[string]any) if !ok { return nil, fmt.Errorf("state patch payload is invalid") } key, _ := item["fieldKey"].(string) before, beforeOK := item["before"].(float64) after, afterOK := item["after"].(float64) if key == "" || !beforeOK || !afterOK { return nil, fmt.Errorf("state patch payload is invalid") } fields = append(fields, StateFieldPatch{Key: key, Before: before, After: after}) } return fields, nil } func statePatchKeys(fields []StateFieldPatch) []string { keys := make([]string, 0, len(fields)) for _, field := range fields { keys = append(keys, field.Key) } return keys } func stateMatches(values map[string]float64, fields []StateFieldPatch) bool { for _, field := range fields { if values[field.Key] != field.Before { return false } } return true } func stateApplied(values map[string]float64, fields []StateFieldPatch) bool { for _, field := range fields { if values[field.Key] != field.After { return false } } return true } func rewardGrant(payload map[string]any) (RewardGrant, error) { grantID, grantOK := payload["grantId"].(string) playerID, playerOK := payload["playerId"].(string) rawItems, itemsOK := payload["items"].([]any) rawOperations, operationsOK := payload["operations"].([]any) if !grantOK || !playerOK || !itemsOK || !operationsOK || (len(rawItems) == 0 && len(rawOperations) == 0) { return RewardGrant{}, fmt.Errorf("reward payload is invalid") } items := make([]RewardItem, 0, len(rawItems)) for _, value := range rawItems { item, ok := value.(map[string]any) if !ok || len(item) != 2 { return RewardGrant{}, fmt.Errorf("reward payload is invalid") } code, codeOK := item["catalogCode"].(string) quantity, quantityOK := integerPayloadValue(item["quantity"]) if !codeOK || !supportedCatalogCode(code) || !quantityOK || quantity < 1 { return RewardGrant{}, fmt.Errorf("reward payload is invalid") } items = append(items, RewardItem{CatalogCode: code, Quantity: quantity}) } operations := make([]string, 0, len(rawOperations)) for _, value := range rawOperations { operation, ok := value.(string) if !ok || operation == "" || !utf8.ValidString(operation) { return RewardGrant{}, fmt.Errorf("reward payload is invalid") } operations = append(operations, operation) } return RewardGrant{GrantID: grantID, PlayerID: playerID, Items: items, Operations: operations}, nil } func eventStartRequest(payload map[string]any) (EventStartRequest, error) { eventID, eventIDOK := payload["eventId"].(string) eventType, eventTypeOK := payload["eventType"].(string) eventClass, classOK := integerPayloadValue(payload["class"]) title, titleOK := payload["title"].(string) duration, durationOK := integerPayloadValue(payload["durationSeconds"]) percent, percentOK := integerPayloadValue(payload["percent"]) placard, placardOK := payload["placard"].(string) produces, producesOK := eventProduceRequests(payload["produces"]) counts := make([]int, 4) for index, key := range []string{"npc", "item", "zombie", "animal"} { if value, exists := payload[key]; exists { count, ok := integerPayloadValue(value) if !ok || count < 0 { return EventStartRequest{}, fmt.Errorf("event start payload is invalid") } counts[index] = count } } participants := 0 if value, exists := payload["maxParticipants"]; exists { var ok bool participants, ok = integerPayloadValue(value) if !ok || participants < 1 { return EventStartRequest{}, fmt.Errorf("event start payload is invalid") } } announce := false if value, exists := payload["announce"]; exists { var ok bool announce, ok = value.(bool) if !ok { return EventStartRequest{}, fmt.Errorf("event start payload is invalid") } } if !eventIDOK || !eventTypeOK || !classOK || !titleOK || !durationOK || !percentOK || !placardOK || !producesOK || !supportedEventType(eventType) || eventClass < 1 || eventClass > 2 || (eventClass == 1) != (eventType == "range") || strings.TrimSpace(eventID) == "" || strings.TrimSpace(title) == "" || len(placard) > 500 || percent < 0 || percent > 100 || duration < 1 { return EventStartRequest{}, fmt.Errorf("event start payload is invalid") } return EventStartRequest{EventID: eventID, EventType: eventType, Class: eventClass, Title: title, Placard: placard, Percent: percent, NPC: counts[0], Item: counts[1], Zombie: counts[2], Animal: counts[3], Produces: produces, DurationSeconds: duration, MaxParticipants: participants, Announce: announce}, nil } func eventProduceRequests(value any) ([]EventProduceRequest, bool) { raw, ok := value.([]any) if !ok { return nil, false } result := make([]EventProduceRequest, 0, len(raw)) for _, candidate := range raw { produce, ok := candidate.(map[string]any) if !ok || len(produce) != 7 { return nil, false } tradeGoodsID, idOK := produce["tradeGoodsId"].(string) percent, percentOK := integerPayloadValue(produce["percent"]) quantity, quantityOK := integerPayloadValue(produce["value"]) radius, radiusOK := numberPayloadValue(produce["r"]) x, xOK := numberPayloadValue(produce["x"]) y, yOK := numberPayloadValue(produce["y"]) z, zOK := numberPayloadValue(produce["z"]) if !idOK || strings.TrimSpace(tradeGoodsID) == "" || len(tradeGoodsID) > 128 || !percentOK || percent < 0 || percent > 100 || !quantityOK || quantity < 1 || !radiusOK || radius < 0 || !xOK || !yOK || !zOK { return nil, false } result = append(result, EventProduceRequest{TradeGoodsID: tradeGoodsID, Percent: percent, Value: quantity, Radius: radius, X: x, Y: y, Z: z}) } return result, true } func integerPayloadValue(value any) (int, bool) { switch number := value.(type) { case float64: if number != float64(int(number)) { return 0, false } return int(number), true case int: return number, true case int32: return int(number), true case int64: return int(number), true default: return 0, false } } func numberPayloadValue(value any) (float64, bool) { var result float64 switch number := value.(type) { case float64: result = number case float32: result = float64(number) case int: result = float64(number) case int32: result = float64(number) case int64: result = float64(number) default: return 0, false } return result, !math.IsNaN(result) && !math.IsInf(result, 0) } func supportedCatalogCode(value string) bool { if value == "" || len(value) > 128 { return false } for _, character := range value { if (character < 'a' || character > 'z') && (character < 'A' || character > 'Z') && (character < '0' || character > '9') && character != '_' && character != '-' && character != '.' { return false } } return true } func supportedEventType(value string) bool { return value == "range" || value == "fixed" }