pre cw with probe and a bunch script to read game behavior
This commit is contained in:
1 parent
5120219ae2
commit
da8a32f60d
310 files changed
+219693
-872
No files matched your search
@@ -0,0 +1,147 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const currencyWarsSaveFormat = 1 // Version of this repository's save envelope.
|
||||
|
||||
var ErrCurrencyWarsSaveConflict = errors.New("CW save changed in another session")
|
||||
|
||||
// WireData is a serialized GridFightGetDataScRsp, retaining unknown protocol
|
||||
// fields. UID and Client identity prevent restoring a different account/schema.
|
||||
type CurrencyWarsSnapshot struct {
|
||||
FormatVersion uint32 `json:"format_version"`
|
||||
UID uint32 `json:"uid"`
|
||||
ClientVersion string `json:"client_version"`
|
||||
AssemblySHA256 string `json:"assembly_sha256"`
|
||||
Revision uint64 `json:"revision"`
|
||||
WireData []byte `json:"wire_data"`
|
||||
}
|
||||
|
||||
func (s *CurrencyWarsSnapshot) validate() error {
|
||||
if s == nil || s.FormatVersion != currencyWarsSaveFormat || s.UID == 0 || s.ClientVersion == "" || len(s.WireData) == 0 {
|
||||
return fmt.Errorf("invalid CW snapshot envelope")
|
||||
}
|
||||
if digest, err := hex.DecodeString(s.AssemblySHA256); err != nil || len(digest) != sha256.Size {
|
||||
return fmt.Errorf("invalid CW snapshot source hash")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadCurrencyWarsSnapshot never creates or rewrites persistent. A revision
|
||||
// mismatch is surfaced rather than silently replacing progress with defaults.
|
||||
func ReadCurrencyWarsSnapshot(uid uint32, version, assemblySHA string) (*CurrencyWarsSnapshot, error) {
|
||||
root, err := readCurrencyWarsPersistentRoot()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var accounts map[string]json.RawMessage
|
||||
if raw, ok := root["currency_wars"]; ok {
|
||||
if err := json.Unmarshal(raw, &accounts); err != nil || accounts == nil {
|
||||
return nil, fmt.Errorf("invalid CW account store")
|
||||
}
|
||||
}
|
||||
raw, found := accounts[strconv.FormatUint(uint64(uid), 10)]
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
var snapshot CurrencyWarsSnapshot
|
||||
if err := json.Unmarshal(raw, &snapshot); err != nil {
|
||||
return nil, fmt.Errorf("decode CW snapshot: %w", err)
|
||||
}
|
||||
if err := snapshot.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if snapshot.UID != uid || snapshot.ClientVersion != version || snapshot.AssemblySHA256 != assemblySHA || snapshot.Revision == 0 {
|
||||
return nil, fmt.Errorf("CW snapshot account/Client revision does not match")
|
||||
}
|
||||
return &snapshot, nil
|
||||
}
|
||||
|
||||
func readCurrencyWarsPersistentRoot() (map[string]json.RawMessage, error) {
|
||||
raw, err := os.ReadFile("persistent")
|
||||
if os.IsNotExist(err) {
|
||||
return make(map[string]json.RawMessage), nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var root map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &root); err != nil || root == nil {
|
||||
return nil, fmt.Errorf("invalid persistent object; CW save refused")
|
||||
}
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// CommitCurrencyWarsSnapshot updates only this UID's CW value, sharing the
|
||||
// existing cross-process lock and atomic writer. expectedRevision prevents a
|
||||
// stale game session from overwriting a later run. The caller must validate
|
||||
// the protocol payload before committing; reads do not perform migration.
|
||||
func CommitCurrencyWarsSnapshot(expectedRevision uint64, next CurrencyWarsSnapshot) (uint64, error) {
|
||||
if err := next.validate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
persistentWriteMu.Lock()
|
||||
defer persistentWriteMu.Unlock()
|
||||
unlock, err := lockPersistentFile()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer unlock()
|
||||
root, err := readCurrencyWarsPersistentRoot()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
accounts := make(map[string]json.RawMessage)
|
||||
if raw, exists := root["currency_wars"]; exists {
|
||||
if err := json.Unmarshal(raw, &accounts); err != nil || accounts == nil {
|
||||
return 0, fmt.Errorf("invalid CW account store; save refused")
|
||||
}
|
||||
}
|
||||
key := strconv.FormatUint(uint64(next.UID), 10)
|
||||
var revision uint64
|
||||
if raw, exists := accounts[key]; exists {
|
||||
var previous CurrencyWarsSnapshot
|
||||
if err := json.Unmarshal(raw, &previous); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := previous.validate(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if previous.UID != next.UID || previous.ClientVersion != next.ClientVersion || previous.AssemblySHA256 != next.AssemblySHA256 || previous.Revision == 0 {
|
||||
return 0, fmt.Errorf("CW snapshot source differs; migration is required")
|
||||
}
|
||||
revision = previous.Revision
|
||||
}
|
||||
if revision != expectedRevision {
|
||||
return 0, ErrCurrencyWarsSaveConflict
|
||||
}
|
||||
if revision == ^uint64(0) {
|
||||
return 0, fmt.Errorf("CW snapshot revision exhausted")
|
||||
}
|
||||
next.Revision = revision + 1
|
||||
accounts[key], err = json.Marshal(next)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
root["currency_wars"], err = json.Marshal(accounts)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := writeJSONAtomic("persistent", root); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return next.Revision, nil
|
||||
}
|
||||
|
||||
func NewCurrencyWarsSnapshot(uid uint32, version, assemblySHA string, wire []byte) CurrencyWarsSnapshot {
|
||||
return CurrencyWarsSnapshot{FormatVersion: currencyWarsSaveFormat, UID: uid,
|
||||
ClientVersion: version, AssemblySHA256: assemblySHA, WireData: append([]byte(nil), wire...)}
|
||||
}
|
||||
Reference in new issue
Block a user