package catalogs import ( "encoding/json" "os" "path/filepath" "sync" ) var _ = json.RawMessage{} var _ = sync.Mutex{} var _ = os.ReadFile var _ = filepath.Join // // Reads the carriage composition of every metro train out of the two rail config tables the // client already ships: RailLineConfig.json (lineId -> TrainTemplateId) and // RailTrainConfig.json (templateId -> CarriageIds). // Why the server needs this at all — see GameRouter's OnAskMetroGadgetIds: the client // builds each LightRail prefab itself (LightRail_4_405/carriage0|1|2/Seats/Gadgets_888_LightRailChair*) // and then asks the server which gadget instance ids belong to that train. Verified 2026-09-23 by // frida dump of a live train: // carriage0 = 4 seats, carriage1 = 6 seats (the injected ids only stuck on carriage1 back when we // answered one flat count for every carriage) — the client matches InnerGadgetIds index-for-index // against the carriage's own seat slots, so the answer must be one entry per carriage, each with // exactly that carriage's seat count. // The mapping metroId -> line comes from the ids the client asks with: 103/104/105 are line 1 trains, // 205/206/207 are line 2, 304 line 3, 400/405 line 4 — i.e. lineId = metroId / 100. // Composition per line (build 4229938): // line 1/2 (Bayshore, template 1 LightRailVehicle01) -> [10, 2, 2, 2, 3] // line 3 (Central, template 2 LightRailVehicle01) -> [11, 7, 7, 7, 8] // line 4 (East, template 3 LightRailVehicle03) -> [4, 5, 9] <- the one we dumped // line 5-8 (test) template 4 LightRailVehicle01 -> [10] // line 9-14 (1/2/3号线 上下行, template 5 CX01) -> [12, 13, 13, 13, 14] // line 15 (画舫 TourBoat01, template 6) -> [15] // Seat counts per carriage template are NOT in any config (they live in the prefab), so they are a // knob on MetroGadgetSettings — seed it from the in-game dump. // var ( MetroCarriageCatalog4229938_cacheLock sync.RWMutex MetroCarriageCatalog4229938_cached = false MetroCarriageCatalog4229938_entries []any ) // LoadMetroCarriageCatalog4229938 loads catalog records from client config directory func LoadMetroCarriageCatalog4229938(configDir string) ([]any, error) { MetroCarriageCatalog4229938_cacheLock.RLock() if MetroCarriageCatalog4229938_cached { defer MetroCarriageCatalog4229938_cacheLock.RUnlock() return MetroCarriageCatalog4229938_entries, nil } MetroCarriageCatalog4229938_cacheLock.RUnlock() MetroCarriageCatalog4229938_cacheLock.Lock() defer MetroCarriageCatalog4229938_cacheLock.Unlock() if MetroCarriageCatalog4229938_cached { return MetroCarriageCatalog4229938_entries, nil } filePath := filepath.Join(configDir, "MetroCarriageCatalog.json.json") data, err := os.ReadFile(filePath) if err != nil { return nil, err } var raw struct { Records []json.RawMessage `json:"records"` } if err := json.Unmarshal(data, &raw); err != nil { return nil, err } var result []any for _, item := range raw.Records { var m map[string]any if err := json.Unmarshal(item, &m); err == nil { result = append(result, m) } } MetroCarriageCatalog4229938_entries = result MetroCarriageCatalog4229938_cached = true return result, nil } // // Carriage template ids of one train, in carriage order (index 0 = the client's carriage0). // False when the metroId does not resolve to a known line/template. // func MetroCarriageCatalog4229938_TryGetCarriageTemplates(metroId int32, carriageTemplates []int32) bool { // Catalog helper MetroCarriageCatalog4229938_TryGetCarriageTemplates return false }