104 lines
3.3 KiB
Go
104 lines
3.3 KiB
Go
package sdkserver
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
|
||
|
|
"gac-hkrpg-go/internal/common"
|
||
|
|
)
|
||
|
|
|
||
|
|
// POST patches fields; PUT explicitly replaces a complete player document.
|
||
|
|
// Keep raw JSON values so unrelated IDs and numeric values are not rounded.
|
||
|
|
func preparePlayerDataUpdate(existing, payload []byte, replace bool) ([]byte, error) {
|
||
|
|
var patch map[string]json.RawMessage
|
||
|
|
if err := json.Unmarshal(payload, &patch); err != nil || patch == nil {
|
||
|
|
return nil, fmt.Errorf("player data must be a JSON object")
|
||
|
|
}
|
||
|
|
result := make(map[string]json.RawMessage)
|
||
|
|
if replace {
|
||
|
|
for _, key := range []string{"avatars", "lightcones", "relics", "battle_config"} {
|
||
|
|
if _, present := patch[key]; !present {
|
||
|
|
return nil, fmt.Errorf("full replacement is missing %s; use POST for a partial update", key)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// FreeSR exports do not necessarily include the inventory editor's
|
||
|
|
// material overrides. Omission preserves them; an explicit materials
|
||
|
|
// object (including zero counts or {}) still replaces that field.
|
||
|
|
if _, present := patch["materials"]; !present && len(existing) != 0 {
|
||
|
|
var current map[string]json.RawMessage
|
||
|
|
if err := json.Unmarshal(existing, ¤t); err != nil || current == nil {
|
||
|
|
return nil, fmt.Errorf("existing player data is invalid; restore it before applying updates")
|
||
|
|
}
|
||
|
|
if value, present := current["materials"]; present {
|
||
|
|
result["materials"] = value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else if err := json.Unmarshal(existing, &result); err != nil || result == nil {
|
||
|
|
return nil, fmt.Errorf("existing player data is invalid; restore it before applying updates")
|
||
|
|
}
|
||
|
|
for key, value := range patch {
|
||
|
|
if key == "avatars" && !replace {
|
||
|
|
var incoming map[string]json.RawMessage
|
||
|
|
if err := json.Unmarshal(value, &incoming); err != nil || len(incoming) == 0 {
|
||
|
|
return nil, fmt.Errorf("avatars update must be a non-empty object")
|
||
|
|
}
|
||
|
|
current := make(map[string]json.RawMessage)
|
||
|
|
if raw, found := result[key]; found {
|
||
|
|
if err := json.Unmarshal(raw, ¤t); err != nil || current == nil {
|
||
|
|
return nil, fmt.Errorf("existing avatars data is invalid")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for id, avatar := range incoming {
|
||
|
|
current[id] = avatar
|
||
|
|
}
|
||
|
|
merged, err := json.Marshal(current)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
result[key] = merged
|
||
|
|
} else {
|
||
|
|
result[key] = value
|
||
|
|
}
|
||
|
|
}
|
||
|
|
out, err := json.MarshalIndent(result, "", " ")
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
var data common.FreesrData
|
||
|
|
if err := json.Unmarshal(out, &data); err != nil {
|
||
|
|
return nil, fmt.Errorf("invalid player data: %w", err)
|
||
|
|
}
|
||
|
|
if err := common.ValidateAvatarRoster(data.Avatars); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if saved, err := common.ReadPersistent(); err == nil {
|
||
|
|
if err := common.ValidateAdventureLineup(data.Avatars, saved.Lineups); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
} else if !os.IsNotExist(err) {
|
||
|
|
return nil, fmt.Errorf("cannot validate active lineup: %w", err)
|
||
|
|
}
|
||
|
|
return out, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func writePlayerDataAtomic(data []byte) error {
|
||
|
|
file, err := os.CreateTemp(".", ".gac-freesr-*.tmp")
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
name := file.Name()
|
||
|
|
defer os.Remove(name)
|
||
|
|
if _, err := file.Write(data); err != nil {
|
||
|
|
file.Close()
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := file.Sync(); err != nil {
|
||
|
|
file.Close()
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if err := file.Close(); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return os.Rename(name, "freesr-data.json")
|
||
|
|
}
|