package companion import ( "context" "errors" "fmt" "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 only a frozen grant and typed items. It cannot // receive SQL, a raw database row, a shell command, an RCON command, or secrets. type AuthorizedRewardPort interface { DeliverReward(context.Context, RewardGrant) (DeliveryReceipt, error) } type RewardGrant struct { GrantID string PlayerID string Items []RewardItem } type RewardItem struct { CatalogCode string Quantity int } type DeliveryReceipt struct{ Outcome 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 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{"outcome": "unknown"}, nil } if receipt.Outcome != "delivered" { return map[string]any{"outcome": "failed"}, nil } return map[string]any{"outcome": "delivered"}, 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) raw, itemsOK := payload["items"].([]any) if !grantOK || !playerOK || !itemsOK || len(raw) == 0 || len(raw) > 8 { return RewardGrant{}, fmt.Errorf("reward payload is invalid") } items := make([]RewardItem, 0, len(raw)) for _, value := range raw { item, ok := value.(map[string]any) if !ok { return RewardGrant{}, fmt.Errorf("reward payload is invalid") } code, codeOK := item["catalogCode"].(string) quantity, quantityOK := item["quantity"].(float64) if !codeOK || !quantityOK || quantity < 1 || quantity > 99 { return RewardGrant{}, fmt.Errorf("reward payload is invalid") } items = append(items, RewardItem{CatalogCode: code, Quantity: int(quantity)}) } return RewardGrant{GrantID: grantID, PlayerID: playerID, Items: items}, nil }