// // LocalVersionReader.swift // ylgamehall // // 读本地两个版本号: // - 原生 IPA 版本(来自 ChannelConfig.plist 的 appversion 字段) // - H5 包版本(来自 Library/Caches/{gamedir}/{gamestart}/version.xml 的 /game/version@value) // // 详见 docs/Development-Plan.md ADR-008-F / msext NewRootVC.m:664-682。 // import Foundation public enum LocalVersionReader { /// 原生 IPA 版本。等价 msext `[FuncPublic filename:@"appversion"] integerValue` /// 来源:ChannelConfig.plist 的 `appversion` 字段(沿用渠道注入约定, /// IPA 后处理改 plist 可动态调整)。 /// 解析失败返回 0(msext `[nil intValue] = 0` 兜底语义)。 nonisolated public static var localAppVersion: Int { Int(BundleConfig.shared.appVersion) ?? 0 } /// 大厅 H5 包版本。等价 msext `NewRootVC.m:664-682` 的 GDataXML 路径, /// 新外壳用 Foundation `XMLParser`(无外部依赖)解析。 /// /// 文件路径:`SandboxPaths.lobbyVersionXML` = /// `Library/Caches/{gamedir}/{gamestart}/version.xml` /// 解析 `/game/version` 节点的 `value` 属性,转 Int。 /// /// **缺失 / 损坏返回 0**(必须保留,首装后首次升级的兜底机制: /// `[nil intValue] = 0`,任何远端 gameVersion > 0 触发下载)。 nonisolated public static var localGameVersion: Int { readGameVersion(from: SandboxPaths.lobbyVersionXML) ?? 0 } /// 子游戏 H5 包版本。等价 msext `gameController.m:1249-1254`。 nonisolated public static func subGameVersion(dir: String, start: String) -> Int { let url = SandboxPaths.caches .appendingPathComponent(dir) .appendingPathComponent(start) .appendingPathComponent("version.xml") return readGameVersion(from: url) ?? 0 } // MARK: - 内部 XML 解析 nonisolated private static func readGameVersion(from url: URL) -> Int? { guard let data = try? Data(contentsOf: url) else { return nil } let parser = XMLParser(data: data) let handler = VersionXMLHandler() parser.delegate = handler guard parser.parse(), let v = handler.gameVersion else { return nil } return v } } // MARK: - XMLParser delegate /// 仅提取 ``(在 `/game/version` 路径下)的 value 转 Int。 /// msext 的 xpath 是 `/game/version` 取属性 `value`;新外壳用 SAX 风格 XMLParser /// 也只需要在 `version` 元素出现时取属性即可(嵌套层级宽容化处理)。 nonisolated private final class VersionXMLHandler: NSObject, XMLParserDelegate { var gameVersion: Int? func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String] = [:]) { if elementName == "version", let raw = attributeDict["value"], let v = Int(raw) { gameVersion = v } } }