Files
GaCy-GO/internal/common/stella_jump.go
T
2026-10-28 18:54:43 +07:00

180 lines
5.1 KiB
Go

package common
import (
"encoding/hex"
"encoding/json"
"fmt"
"strconv"
)
type StellaJumpCase struct {
ID uint32 `json:"GameBoyCaseID"`
Challenges []uint32 `json:"GameBoyChallengeIDList"`
CheatQuestID uint32
FDGroupID, FDHardInstanceID, FDHardEntityID, FDCheatInstanceID, FDCheatEntityID uint32
}
func LoadStellaJumpCases() (map[uint32]StellaJumpCase, error) {
raw, err := ReadResourceFile("resources/StellaJumpConfig.json")
if err != nil {
return nil, err
}
var resource struct {
Format uint32 `json:"format_version"`
Assembly string `json:"client_assembly_sha256"`
Evidence string `json:"evidence_id"`
Cases []StellaJumpCase `json:"cases"`
}
if err = json.Unmarshal(raw, &resource); err != nil {
return nil, err
}
digest, err := hex.DecodeString(resource.Assembly)
if err != nil || len(digest) != 32 || resource.Format != 1 || resource.Evidence == "" || len(resource.Cases) == 0 {
return nil, fmt.Errorf("Stella Jump catalog provenance invalid")
}
cases := make(map[uint32]StellaJumpCase, len(resource.Cases))
for _, row := range resource.Cases {
if _, exists := cases[row.ID]; row.ID == 0 || exists || row.FDGroupID == 0 || row.FDHardInstanceID == 0 || row.FDCheatInstanceID == 0 {
return nil, fmt.Errorf("Stella Jump case invalid")
}
seen := map[uint32]bool{}
for _, id := range row.Challenges {
if id == 0 || seen[id] {
return nil, fmt.Errorf("Stella Jump challenge invalid")
}
seen[id] = true
}
cases[row.ID] = row
}
return cases, nil
}
type StellaJumpRecord struct {
BestTime uint64 `json:"best_time"`
FinishedChallenges []uint32 `json:"finished_challenges"`
CheatInput bool `json:"cheat_input"`
CheatMode bool `json:"cheat_mode"`
}
func SaveStellaJumpCheatSetting(id uint32, input bool, enabled bool) error {
cases, err := LoadStellaJumpCases()
if err != nil {
return err
}
if _, ok := cases[id]; !ok {
return fmt.Errorf("unknown Stella Jump case %d", id)
}
persistentWriteMu.Lock()
defer persistentWriteMu.Unlock()
unlock, err := lockPersistentFile()
if err != nil {
return err
}
defer unlock()
root, err := readCurrencyWarsPersistentRoot()
if err != nil {
return err
}
records := map[uint32]StellaJumpRecord{}
if raw, exists := root["stella_jump"]; exists {
if err = json.Unmarshal(raw, &records); err != nil {
return err
}
if records == nil {
records = map[uint32]StellaJumpRecord{}
}
}
record := records[id]
if input {
record.CheatInput = enabled
} else {
record.CheatMode = enabled
}
records[id] = record
root["stella_jump"], err = json.Marshal(records)
if err != nil {
return err
}
return writeJSONAtomic("persistent", root)
}
func ReadStellaJumpRecord(id uint32) (StellaJumpRecord, error) {
root, err := readCurrencyWarsPersistentRoot()
if err != nil {
return StellaJumpRecord{}, err
}
var records map[uint32]StellaJumpRecord
if raw, exists := root["stella_jump"]; exists {
if err = json.Unmarshal(raw, &records); err != nil {
return StellaJumpRecord{}, err
}
}
return records[id], nil
}
// Merge under the shared process/file locks. An old game session cannot replace
// another case's score, CW snapshot, or a switch changed through the SDK.
func SaveStellaJumpResult(id uint32, elapsed uint64, challenges []uint32) (StellaJumpRecord, error) {
cases, err := LoadStellaJumpCases()
if err != nil {
return StellaJumpRecord{}, err
}
row, ok := cases[id]
if !ok {
return StellaJumpRecord{}, fmt.Errorf("unknown Stella Jump case %d", id)
}
allowed := map[uint32]bool{}
for _, v := range row.Challenges {
allowed[v] = true
}
for _, v := range challenges {
if !allowed[v] {
return StellaJumpRecord{}, fmt.Errorf("challenge %d not in case %d", v, id)
}
}
persistentWriteMu.Lock()
defer persistentWriteMu.Unlock()
unlock, err := lockPersistentFile()
if err != nil {
return StellaJumpRecord{}, err
}
defer unlock()
root, err := readCurrencyWarsPersistentRoot()
if err != nil {
return StellaJumpRecord{}, err
}
records := map[uint32]StellaJumpRecord{}
if raw, exists := root["stella_jump"]; exists {
if err = json.Unmarshal(raw, &records); err != nil {
return StellaJumpRecord{}, err
}
if records == nil {
records = map[uint32]StellaJumpRecord{}
}
}
record := records[id]
if elapsed != 0 && (record.BestTime == 0 || elapsed < record.BestTime) {
record.BestTime = elapsed
}
seen := map[uint32]bool{}
for _, v := range record.FinishedChallenges {
seen[v] = true
}
for _, v := range challenges {
if !seen[v] {
record.FinishedChallenges = append(record.FinishedChallenges, v)
seen[v] = true
}
}
records[id] = record
encoded, err := json.Marshal(records)
if err != nil {
return StellaJumpRecord{}, err
}
root["stella_jump"] = encoded
if err = writeJSONAtomic("persistent", root); err != nil {
return StellaJumpRecord{}, fmt.Errorf("save Stella Jump case %s: %w", strconv.FormatUint(uint64(id), 10), err)
}
return record, nil
}