本仓库是战网客户端的元数据仓库,承担两件事:数据仓库(作者提交源树,CI 构建后发布静态文件)+ 解析库(Ra3.BattleNet.Metadata,客户端经 URL/本地入口读取发布物)。
- 写数据(新增/修改 Mod、应用、新闻):见 AGENTS.md「数据贡献者指南」 —— 5 步快速开始、三条规则、模板、提交前自检都在那里
静态文件树,不是 RPC:
{BaseUrl}/metadata.xml—— 展平后的业务数据(无 Include){BaseUrl}/{相对路径}—— 资源(图片/Markdown/叶子 Manifest),路径来自登记节点的Source- 根属性:
SchemaVersion(契约版本)、ContentRevision(内容修订,用于缓存判断)
- 配置
BaseUrl(生产 = CDN;调试 = 本地缓存目录)。 MetadataBuilder.Load(url|path)→ 列出实体、接 LINQ 组合(见下表)。SchemaVersion不兼容 → 提示升级客户端(MetadataSchema.IsCompatible);ContentRevision变化 → 整树刷新。- Package.Manifest / Icon / Changelog / Post.Content 等一律是限定 ID(
路径前缀:localId),在展平树中找同 ID 的登记节点——别自己走树,用库的导航方法。 - 资源 =
BaseUrl+ 登记节点Source(MetadataResourceUri.Resolve);图片扩展名以Source为准(发布后可能是.webp)。 - 叶子 Manifest(含 File 表)用
Source再拉一次并解析——File 表不在metadata.xml内;挂载顺序交给ManifestMountPlanner。
开发调试 = 同一套解析:MetadataBuilder.Build(本地源仓, 缓存目录) → Load(cache/metadata.xml)。
// 生产:URL;开发:本地展平文件
var doc = MetadataBuilder.Load("https://metadata.ra3battle.net/metadata.xml");
// 列出实体:延迟序列,直接接 LINQ
foreach (var mod in doc.Mods().Where(m => m.Version is not null).OrderBy(m => m.Id))
{
Console.WriteLine($"{mod.Id}@{mod.Version}");
}
var app = doc.Catalog().Application("RA3BattleNet"); // 按 ID 查,大小写不敏感
// 导航:条目 → 叶子清单地址 → 叶子清单
var corona = doc.Catalog().Mod("Corona")!;
var leafUrl = MetadataResourceUri.Resolve("https://metadata.ra3battle.net/metadata.xml", corona.ManifestSource());
var manifest = MetadataBuilder.Load(leafUrl.AbsoluteUri).Find("Manifest")!.ToManifestEntry();
// 导航:应用更新日志的相对 Source(找不到该语言返回 null)
var changelog = app!.ChangelogSource("zh-CN");
// 挂载计划:写了 Skudef 按声明顺序与条件,没写按旧 File@Mount 角色
var plan = ManifestMountPlanner.Build(
manifest.Skudef,
manifest.Files.Select(f => new ManifestMountFile(f.FileName, f.Mount, f.Language, f.Package)).ToList(),
language: "en",
packages: ["hd-shadow"],
localConfigExists: _ => false);| API | 作用 |
|---|---|
root.Mods() / Applications() / Markdowns() / Images() |
列出全部实体/资源;延迟求值,可接 Where/OrderBy/Select/First |
root.Catalog() |
目录入口:Mods/Applications/Markdowns/Images 属性 + Mod(id) / Application(id)(大小写不敏感) |
root.GetAllElements(name) |
按节点名遍历展平树(延迟) |
root.ManifestRegistration(id) |
按限定 ID 找 Manifest 登记节点;不存在返回 null |
ModEntry.Package(version) / ManifestSource(version) |
取版本包(缺省取当前版本)/ 取叶子清单相对 Source;缺版本、缺 Manifest、缺 Source 都抛,消息带原因 |
ApplicationEntry.Package(version) / ChangelogSource(language) |
取版本包 / 取更新日志相对 Source;没配该语言返回 null |
node.ToManifestEntry() |
叶子清单节点 → ManifestEntry(Files / Dependencies / Skudef) |
MetadataSchema.Current / IsCompatible(version) |
契约版本常量与兼容判断 |
MetadataResourceUri.Resolve(baseUrl, source) |
相对 Source → 绝对地址 |
ManifestMountPlanner.Build(skudef, files, language, packages, localConfigExists) |
Skudef 声明或 File@Mount 角色 → 有序挂载计划 ManifestMountCommand(Big / Config) |
核心构建(作者/CI/Desktop 调试):
MetadataBuilder.Build(sourceDir, outputDir, schemaVersion: "1.0", contentRevision: gitSha);主库无 SkiaSharp;WebP 仅发布期 Ra3.BattleNet.Metadata.Imaging。
核心构建硬失败(非 0、清理半残输出):源树 XSD、发布物 XSD、循环 Include、缺资源、断 ID、残留 ${...}、新格式清单校验。
叶子 Manifest 的 File 表可声明下载来源与哈希算法(新格式);未声明新格式的旧清单照旧可用。
| 成员 | 位置 | 说明 |
|---|---|---|
Manifest@HashAlgorithm |
属性(可选) | CRC32C / MD5 / SHA256,缺省 CRC32C |
File@Hash |
属性(必填) | 解压后安装文件(FileName)的哈希,算法由 Manifest@HashAlgorithm 决定 |
File@Size |
属性(可选) | 下载物的字节数,正整数 |
File@DownloadName |
属性(可选) | 下载物的服务端文件名;缺省与 FileName 相同 |
File@Compression |
属性(可选) | 下载物的压缩格式,目前只有 zstd;校验通过后解压成 FileName |
Sources/Source |
子元素(可选) | @Type(HTTP / BT)+ @Url;HTTP 必须 http/https 绝对地址,BT 必须以 .torrent 结尾 |
Dependencies/Dll |
子元素(可选) | @Name、@Hash 必填,@Version、@KindOf 可选 |
Skudef |
子元素(可选) | 客户端 skudef 的声明,必须写在 File 之前;@GameVersion 缺省 1.12 |
Skudef/AddBig |
子元素(Skudef 内) |
@File(必填,引用同清单的 FileName)配可选条件 @Language 或 @Package(只能写一个);不写条件就是总是挂 |
Skudef/AddConfig |
子元素(Skudef 内) |
@LocalFile(必填,模组目录下的纯文件名)、@Optional(缺省 false:客户端找不到该文件就报错) |
Skudef 是「写什么、按什么顺序」的唯一来源:客户端按子元素的文档顺序生成 add-big / add-config 行,
@Language 与客户端语言设置一致、@Package 在客户端开关里才挂。写了 Skudef 的清单,File 上不许再写 Mount / Language / Package,
且每个 File 必须被恰好一条 AddBig 引用。
File@Hash 描述的是解压后的安装文件(FileName)的 CRC32C;File@Size 描述的是下载物(DownloadName 对应的字节):
服务端只放压缩包(如 corona_3.258.zst),安装名是解压结果(corona_3.258.lyi),两者用
DownloadName + Compression 表达。客户端下载后先解压,再按 @Hash 校验解压结果。
不压缩直发的文件(如启动器资源 Disabler.big)不写 DownloadName 与 Compression,此时 @Size 与 @Hash 描述的就是 FileName 本身。
声明了新格式(带 HashAlgorithm、写了 Skudef,或任一 File 含 Sources)的清单,构建期额外硬校验:哈希长度与算法匹配、禁止占位哈希、每个 File 至少一个 Source、Size 为正整数、FileName + RelativePath 唯一、Dll@Name 唯一、RelativePath 必须是相对路径、DownloadName 必须是不含路径分隔符的文件名、Compression 只能是 zstd 且必须配 DownloadName(且与 FileName 不同名)。旧清单不触发。
写了 Skudef 的清单再多查一层:GameVersion 形如 1.12、至少一条指令、FileName 全表唯一、每条 AddBig@File 指向存在的 FileName 且只指一次、所有 File 都被引用到、Language / Package 只能写一个且只含字母数字下划线连字符、AddConfig@LocalFile 必须是纯文件名、File 上不能再写 Mount / Language / Package。没写 Skudef 的旧清单继续用 File@Mount(base / language / optional)那套角色标记,规则不变。
解析:doc.ManifestRegistration(id) 取登记节点,叶子清单节点 ToManifestEntry() → ManifestEntry(Files / Dependencies / Skudef)。
| 成员 | 位置 | 说明 |
|---|---|---|
SchemaVersion / ContentRevision |
根属性 | 契约版本 / 构建注入修订 |
Application |
子元素 | @ID、Version、Packages、Posts |
Mod |
子元素 | @ID、CurrentVersion、Icon(Image ID)、DisplayName(@Language + 文本,可多条)、Style、Packages、Posts |
Package |
子元素 | @Version、ReleaseDate、Changelogs(@Language+Markdown ID)、Manifest(ID) |
Post |
子元素 | @DateTime、Titles/Contents(@Language + Markdown ID) |
Image / Markdown / Manifest |
登记节点 | @ID(限定 ID)、@Source(相对路径);Image 可 @Url 外链;Manifest 为 stub |
Style(Logo/Controls/Background)与继承(Base/InheritFrom)细节:见 Metadata/MetadataSchema.xsd 与 AGENTS.md。
源树中登记 ID 一律短名,展平时自动限定为 {路径前缀}:{localId};实体 ID 全局唯一,冲突即构建失败。
npm run build # 核心构建(展平 + 校验)→ ./Output
npm run build:release # 核心 + WebP(发布)
npm run deploy # 构建 + Cloudflare Pages 部署- 核心构建(纯 managed):XSD → 展平 → 变量 → 复制被引用资源 → 语义校验;
Output/只含metadata.xml+ 被引用资源 +_redirects - 叶子清单:
Manifest登记节点的 Source 文件与入口一样走展平(去掉Includes、限定 ID、替换变量)后发布,客户端MetadataBuilder.Load可直接解析;图片与 Markdown 原样复制 - Imaging:只转图并回传 MD5,不进主 NuGet;
--webp时展平后按图调用 - Schema:
Metadata/MetadataSchema.xsd(源树)、Metadata/MetadataPublishSchema.xsd(发布物) - 变量:
${TIMESTAMP}/${ENV:NAME}/${MD5:}
- MSTest only(禁止 xunit);
dotnet test Metadata.sln - 覆盖:展平、XSD 硬失败、ID/资源、防冲突、发布面、消费端 Catalog 解析、查询/导航 API、挂载计划(Skudef 与旧 Mount 角色)
Metadata/ 源数据 + XSD
Ra3.BattleNet.Metadata/ 纯 managed 库 + 核心 CLI
Ra3.BattleNet.Metadata.Imaging/ 发布 WebP
Ra3.BattleNet.Metadata.Tests/ MSTest
AGENTS.md Agent/贡献者/数据作者指引