package common import ( "encoding/hex" "encoding/json" "fmt" "sort" "strconv" ) // LoadCurrencyWarsGuideMissions returns only the native CW panel's declared // FinishMainMission prerequisites for the server's existing freeplay policy. // It does not finish all imported missions, change a save or force Client UI // state. The current condition capture is an explicit overlay of the older CW // catalog, not a claim that every CW table was recaptured for that assembly. func LoadCurrencyWarsGuideMissions() ([]uint32, error) { catalog, err := LoadCurrencyWarsCatalog() if err != nil { return nil, err } raw, err := ReadResourceFile("resources/CurrencyWarsActivityConditions.json") if err != nil { return nil, err } var overlay struct { Format uint32 `json:"format_version"` Source string `json:"source_assembly_sha256"` Target string `json:"target_assembly_sha256"` Capture string `json:"capture_sha256"` Policy string `json:"guide_policy"` Types map[string]uint32 `json:"condition_types"` Rows []struct { PanelID uint32 GuideConditions []struct { Type uint32 Param string } } `json:"rows"` } if err := json.Unmarshal(raw, &overlay); err != nil { return nil, fmt.Errorf("decode CW activity conditions: %w", err) } if overlay.Format != 1 || overlay.Source != catalog.Provenance.GameAssemblySHA256 || overlay.Policy != "complete_declared_guide_finish_main_missions" { return nil, fmt.Errorf("CW activity condition source/policy differs") } for _, digest := range []string{overlay.Target, overlay.Capture} { value, e := hex.DecodeString(digest) if e != nil || len(value) != 32 { return nil, fmt.Errorf("CW activity condition evidence missing") } } // The independently captured current activity catalog provides the target // assembly identity; a configured version label is not an identity check. current, err := ReadResourceFile("resources/PreviewGameplayCatalog.json") if err != nil { return nil, err } var identity struct { Assembly string `json:"client_assembly_sha256"` } if err := json.Unmarshal(current, &identity); err != nil || identity.Assembly != overlay.Target { return nil, fmt.Errorf("CW activity condition target differs from current activity capture") } finishType := overlay.Types["FinishMainMission"] if finishType == 0 || len(overlay.Rows) == 0 { return nil, fmt.Errorf("CW guide condition enum/rows missing") } mainData, err := ReadResourceFile("resources/MainMission.json") if err != nil { return nil, err } var main struct { Rows []struct { ID uint32 `json:"MainMissionID"` } `json:"main_mission_config"` } if err := json.Unmarshal(mainData, &main); err != nil { return nil, err } known := make(map[uint32]bool, len(main.Rows)) for _, mission := range main.Rows { known[mission.ID] = true } panels := map[uint32]bool{} for _, schedule := range catalog.ActivitySchedules() { panels[schedule.ActivityID] = true } seenPanels, seenMissions := map[uint32]bool{}, map[uint32]bool{} var result []uint32 for _, row := range overlay.Rows { if row.PanelID == 0 || !panels[row.PanelID] || seenPanels[row.PanelID] { return nil, fmt.Errorf("CW guide condition panel is not an active unique CW panel") } seenPanels[row.PanelID] = true for _, condition := range row.GuideConditions { // An unimplemented condition cannot silently be treated as finished. if condition.Type != finishType { return nil, fmt.Errorf("CW guide condition kind %d is not implemented", condition.Type) } id, err := strconv.ParseUint(condition.Param, 10, 32) if err != nil || id == 0 || !known[uint32(id)] { return nil, fmt.Errorf("CW guide mission is not present in the mission resource") } if !seenMissions[uint32(id)] { seenMissions[uint32(id)] = true result = append(result, uint32(id)) } } } if len(result) == 0 { return nil, fmt.Errorf("CW guide condition mission list is empty") } sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) return result, nil }