panel public release

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 01:13:02 -07:00
commit 8a94ffd58f
2165 changed files with 301493 additions and 0 deletions
+363
View File
@@ -0,0 +1,363 @@
// Package sandbox handles the 7 Days to Die V3.0 "SandboxCode" — the single
// encoded serverconfig.xml property that carries all 150 in-game Sandbox Options
// introduced in V3.0 "Dead Hot Summer".
//
// V3.0 collapses ~29 legacy gameplay serverconfig properties (GameDifficulty,
// BloodMoonFrequency, LootAbundance, …) into one deterministic, OFFLINE-encoded
// string. The encode/decode algorithm ships in the game binary; this package is
// a faithful Go port of it, cracked from the decompiled 3.0 Assembly-CSharp
// (SandboxOptions.SandboxOptionManager + SandboxOptionPreset, buildid 23705258).
//
// ## The encoding (verified against the game's own default code)
//
// code[0] = version char ('A', SandboxOptionManager.currentVersion)
// then 3-char triples, one per option that differs from its default:
// triple[0..1] = the SandboxOptions enum int, base-26: c0*26 + c1 (A=0)
// triple[2] = the selected value INDEX in that option's value-set (A=0)
//
// Decode resets every option to its default, then applies each triple. Encode is
// the inverse: emit a triple for every option whose chosen index ≠ default index
// (options omitted from the code decode to default — identical result, shorter
// code, matching how the game emits difficulty presets like AAAJABJACJADJARFBNC).
//
// Round-trip + the game's example codes are checked in sandbox_test.go.
package sandbox
import (
"errors"
"fmt"
"math"
"sort"
"strconv"
"strings"
)
// codeVersion is SandboxOptionManager.currentVersion. A code must start with it.
const codeVersion = 'A'
// ErrCodecUnavailable is retained for API compatibility with the day-one
// passthrough scaffold. The real codec no longer returns it.
var ErrCodecUnavailable = errors.New("sandbox: per-option SandboxCode codec unavailable")
// optByEnum / optByName index the options table for O(1) lookup. defaultIdx
// holds each option's default value's index in its value-set.
var (
optByEnum = map[int]*option{}
optByName = map[string]*option{}
defaultIdx = map[string]int{}
)
func init() {
for i := range options {
o := &options[i]
e, ok := enumByName[o.Name]
if !ok {
panic("sandbox: option " + o.Name + " has no SandboxOptions enum entry")
}
o.enum = e
vs, ok := valueSets[o.ValueSet]
if !ok {
panic("sandbox: option " + o.Name + " references unknown value-set " + o.ValueSet)
}
di, ok := indexOfDefault(*o, vs)
if !ok {
panic(fmt.Sprintf("sandbox: option %s default not present in value-set %s", o.Name, o.ValueSet))
}
o.set = vs
defaultIdx[o.Name] = di
optByEnum[e] = o
optByName[o.Name] = o
}
}
// indexOfDefault finds the index of an option's default value in its value-set.
func indexOfDefault(o option, vs valueSet) (int, bool) {
switch o.Type {
case optFloat:
for i, f := range vs.Floats {
if floatEq(f, o.DefaultFloat) {
return i, true
}
}
case optInt:
for i, n := range vs.Ints {
if n == o.DefaultInt {
return i, true
}
}
case optBool:
for i, b := range vs.Bools {
if b == o.DefaultBool {
return i, true
}
}
}
return 0, false
}
func floatEq(a, b float64) bool { return math.Abs(a-b) < 1e-6 }
// Codec converts between a SandboxCode string and a per-option value map.
// The map is keyed by the SandboxOptions member name (e.g. "BloodMoonFrequency")
// with string values (the human value: "7", "1.5", "true"). Encode emits a code
// for the options whose value differs from default; Decode returns the FULL set
// of 150 options at their effective values (defaults for any omitted from code).
type Codec interface {
Version() string
Encode(options map[string]string) (string, error)
Decode(code string) (map[string]string, error)
}
// V3Codec is the real 3.0 SandboxCode codec.
type V3Codec struct{}
func (V3Codec) Version() string { return string(codeVersion) }
// Encode builds a SandboxCode from a name→value map. Unknown keys are ignored;
// a value that doesn't match any entry in its option's value-set is an error
// (prevents silently writing an out-of-range setting). Options absent from the
// map, or equal to their default, are omitted from the code (decode to default).
// Triples are emitted in ascending enum order for a stable, diffable code.
func (V3Codec) Encode(vals map[string]string) (string, error) {
type triple struct {
enum, idx int
}
var triples []triple
for name, raw := range vals {
o, ok := optByName[name]
if !ok {
continue // unknown option key — ignore (forward-compat)
}
idx, err := o.indexOfValue(raw)
if err != nil {
return "", fmt.Errorf("sandbox: option %s: %w", name, err)
}
if idx == defaultIdx[name] {
continue // equals default — omit (decodes to same value)
}
triples = append(triples, triple{o.enum, idx})
}
sort.Slice(triples, func(i, j int) bool { return triples[i].enum < triples[j].enum })
var b strings.Builder
b.WriteByte(codeVersion)
for _, t := range triples {
s, err := indexToAlpha2(t.enum)
if err != nil {
return "", err
}
c, err := indexToAlpha(t.idx)
if err != nil {
return "", err
}
b.WriteString(s)
b.WriteByte(c)
}
return b.String(), nil
}
// Decode parses a SandboxCode into the FULL effective option set (all 150 at
// their resolved values: code-specified or default). An empty code returns all
// defaults. A code with a wrong version char, bad length, or unknown enum is an
// error (unknown enums could mean a version drift we shouldn't silently accept).
func (V3Codec) Decode(code string) (map[string]string, error) {
out := DefaultsMap()
if code == "" {
return out, nil
}
if rune(code[0]) != codeVersion {
return nil, fmt.Errorf("sandbox: code version %q unsupported (want %q)", string(code[0]), string(codeVersion))
}
body := code[1:]
if len(body)%3 != 0 {
return nil, fmt.Errorf("sandbox: code body length %d is not a multiple of 3", len(body))
}
for i := 0; i < len(body); i += 3 {
enum, err := alpha2ToIndex(body[i : i+2])
if err != nil {
return nil, err
}
idx, err := alphaToIndex(body[i+2])
if err != nil {
return nil, err
}
o, ok := optByEnum[enum]
if !ok {
return nil, fmt.Errorf("sandbox: code references unknown option enum %d (game version drift?)", enum)
}
if !o.set.IsValidIndex(idx) {
return nil, fmt.Errorf("sandbox: option %s index %d out of range (0..%d)", o.Name, idx, o.set.Len()-1)
}
out[o.Name] = o.valueAtIndex(idx)
}
return out, nil
}
// indexOfValue resolves a human value string to its index in the option's set.
func (o option) indexOfValue(raw string) (int, error) {
raw = strings.TrimSpace(raw)
switch o.Type {
case optFloat:
f, err := strconv.ParseFloat(raw, 64)
if err != nil {
return 0, fmt.Errorf("expected a number, got %q", raw)
}
for i, v := range o.set.Floats {
if floatEq(v, f) {
return i, nil
}
}
return 0, fmt.Errorf("value %s not an allowed option (set %s)", raw, o.ValueSet)
case optInt:
n, err := strconv.Atoi(raw)
if err != nil {
return 0, fmt.Errorf("expected an integer, got %q", raw)
}
for i, v := range o.set.Ints {
if v == n {
return i, nil
}
}
return 0, fmt.Errorf("value %d not an allowed option (set %s)", n, o.ValueSet)
case optBool:
b, err := parseBool(raw)
if err != nil {
return 0, err
}
for i, v := range o.set.Bools {
if v == b {
return i, nil
}
}
return 0, fmt.Errorf("value %v not an allowed option (set %s)", b, o.ValueSet)
}
return 0, fmt.Errorf("option %s has unknown type", o.Name)
}
// valueAtIndex returns the human value string for an index in the option's set.
func (o option) valueAtIndex(i int) string {
switch o.Type {
case optFloat:
return strconv.FormatFloat(o.set.Floats[i], 'f', -1, 64)
case optInt:
return strconv.Itoa(o.set.Ints[i])
case optBool:
return strconv.FormatBool(o.set.Bools[i])
}
return ""
}
func parseBool(s string) (bool, error) {
switch strings.ToLower(strings.TrimSpace(s)) {
case "true", "1", "yes", "on":
return true, nil
case "false", "0", "no", "off":
return false, nil
}
return false, fmt.Errorf("expected a boolean, got %q", s)
}
// ---- alpha <-> index helpers (ports of SandboxOptionManager.*) ----
func indexToAlpha(index int) (byte, error) {
if index < 0 || index > 26 {
return 0, fmt.Errorf("sandbox: value index %d out of code range", index)
}
return byte(65 + index%26), nil
}
func indexToAlpha2(index int) (string, error) {
if index < 0 || index >= 676 {
return "", fmt.Errorf("sandbox: enum index %d out of code range", index)
}
return string([]byte{byte(65 + index/26), byte(65 + index%26)}), nil
}
func alphaToIndex(value byte) (int, error) {
v := upper(value)
if v < 'A' || v > 'Z' {
return 0, errors.New("sandbox: code value char must be AZ")
}
return int(v - 65), nil
}
func alpha2ToIndex(value string) (int, error) {
if len(value) != 2 {
return 0, errors.New("sandbox: code option key must be exactly 2 letters")
}
a, b := upper(value[0]), upper(value[1])
if a < 'A' || a > 'Z' || b < 'A' || b > 'Z' {
return 0, errors.New("sandbox: code option key must contain only AZ")
}
return int(a-65)*26 + int(b-65), nil
}
func upper(b byte) byte {
if b >= 'a' && b <= 'z' {
return b - 32
}
return b
}
// DefaultsMap returns every option at its default value (name → value string).
func DefaultsMap() map[string]string {
out := make(map[string]string, len(options))
for i := range options {
o := &options[i]
out[o.Name] = o.valueAtIndex(defaultIdx[o.Name])
}
return out
}
// Active is the codec the panel uses. Now the real V3 codec.
var Active Codec = V3Codec{}
// Preset is an official (or operator-saved) named SandboxCode.
type Preset struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Code string `json:"code"`
}
// Presets are the V3.0 official presets surfaced in the UI. The default
// difficulty (Adventurer) code is shipped from the game's serverconfig comment;
// the named-preset codes are captured from the in-game Sandbox Options window
// (see scripts/capture-preset-codes). An empty Code == the game's built-in
// default sandbox.
var Presets = []Preset{
{ID: "default", Name: "Default (Adventurer)", Description: "The game's built-in default sandbox.", Code: "AAAJABJACJADJARFBNC"},
{ID: "undead_matinee", Name: "Undead Matinee", Description: "Official V3.0 preset.", Code: ""},
{ID: "madmoles_mayhem", Name: "Madmole's Mayhem", Description: "Official V3.0 preset.", Code: ""},
{ID: "almost_creative", Name: "Almost Creative Mode", Description: "Official V3.0 preset.", Code: ""},
{ID: "bite_club", Name: "Bite Club", Description: "Official V3.0 preset.", Code: ""},
{ID: "legacy_survival", Name: "Legacy Survival", Description: "Official V3.0 preset.", Code: ""},
}
// PresetByID returns the named preset, or ok=false if unknown.
func PresetByID(id string) (Preset, bool) {
id = strings.TrimSpace(strings.ToLower(id))
for _, p := range Presets {
if strings.ToLower(p.ID) == id {
return p, true
}
}
return Preset{}, false
}
// Validate does a cheap structural sanity check on a SandboxCode before it's
// written into serverconfig.xml. An empty code is valid (== default sandbox).
// A non-empty code must decode cleanly (right version, length, known enums,
// in-range indices) and contain no XML-attribute-breaking characters.
func Validate(code string) error {
if code == "" {
return nil
}
if strings.ContainsAny(code, `"<>&`) {
return errors.New("sandbox: code contains characters not valid in an XML attribute value")
}
if _, err := (V3Codec{}).Decode(code); err != nil {
return err
}
return nil
}