package common import ( "encoding/hex" "encoding/json" "fmt" ) // The base capture remains identified by its original assembly. A separately // proven overlay supplies replacements removed from the current Client. It is // not a claim that every other CW table has been recaptured for that assembly. func (c *CurrencyWarsCatalog) applyRoleCompatibility() error { raw, err := ReadResourceFile("resources/CurrencyWarsRoleCompatibility.json") if err != nil { return 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"` Entries []struct { Previous uint32 `json:"previous_role_id"` Current uint32 `json:"current_role_id"` Variants []CurrencyWarsRoleVariant `json:"variants"` } `json:"entries"` } if err = json.Unmarshal(raw, &overlay); err != nil { return err } if overlay.Format != 1 || overlay.Source != c.Provenance.GameAssemblySHA256 { return fmt.Errorf("CW role compatibility source differs") } for _, hash := range []string{overlay.Target, overlay.Capture} { b, e := hex.DecodeString(hash) if e != nil || len(b) != 32 { return fmt.Errorf("CW role compatibility evidence missing") } } c.roleReplacements = map[uint32]uint32{} for _, entry := range overlay.Entries { if _, duplicate := c.roleReplacements[entry.Previous]; duplicate { return fmt.Errorf("duplicate CW role replacement source") } previous, ok := c.roleByID[entry.Previous] if !ok || entry.Current == 0 || entry.Previous == entry.Current || len(entry.Variants) == 0 { return fmt.Errorf("invalid CW role replacement") } if _, duplicate := c.roleByID[entry.Current]; duplicate { return fmt.Errorf("CW role replacement already exists") } first := entry.Variants[0] if first.Config.AvatarID != previous.Config.AvatarID || first.Config.SeasonID != previous.Config.SeasonID || first.Star != previous.DefaultStar || uint32(len(entry.Variants)) != first.MaxStar { return fmt.Errorf("CW replacement family or star coverage differs") } current := CurrencyWarsCapturedRole{Config: first.Config, Price: first.Price, MaxStar: first.MaxStar, DefaultStar: first.Star} c.roleByID[entry.Current] = current sale, e := c.BaseSalePrice(entry.Current, first.Star) if e != nil { return e } current.DefaultSellPrice = sale c.roleByID[entry.Current] = current stars := map[uint32]bool{} for _, v := range entry.Variants { var star struct{ ID, Star uint32 } if json.Unmarshal(v.StarConfig, &star) != nil || v.Config.ID != entry.Current || star.ID != entry.Current || star.Star != v.Star || v.Star == 0 || v.Star > first.MaxStar || stars[v.Star] || v.Config.AvatarID != previous.Config.AvatarID || v.Config.SeasonID != previous.Config.SeasonID { return fmt.Errorf("CW replacement star identity differs") } stars[v.Star] = true c.variantByKey[[2]uint32{entry.Current, v.Star}] = v } for i := range c.CapturedRoles { if c.CapturedRoles[i].Config.ID == entry.Previous { c.CapturedRoles[i] = current } } kept := make([]CurrencyWarsRoleVariant, 0, len(c.CapturedVariants)+len(entry.Variants)) for _, v := range c.CapturedVariants { if v.Config.ID != entry.Previous { kept = append(kept, v) } } c.CapturedVariants = append(kept, entry.Variants...) c.roleReplacements[entry.Previous] = entry.Current } return nil } func (c *CurrencyWarsCatalog) CurrentRoleID(id uint32) uint32 { if current := c.roleReplacements[id]; current != 0 { return current } return id }