Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,19 @@ jobs:
env:
GOLDENS_STRICT: "1"
run: python -m pytest tests/pipeline/test_goldens.py tests/pipeline/test_objexport.py

docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: pip
cache-dependency-path: pyproject.toml
- name: Install
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[docs]"
- name: Build the documentation site
run: python -m mkdocs build --strict
51 changes: 51 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Docs

# Publishes the documentation site (mkdocs.yml, docs/) to GitHub Pages.
on:
push:
branches: [main]
paths:
- "docs/**"
- "mkdocs.yml"
- ".github/workflows/docs.yml"
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.13"
cache: pip
cache-dependency-path: pyproject.toml
- name: Install
run: |
python -m pip install --upgrade pip
python -m pip install -e ".[docs]"
- name: Build
run: python -m mkdocs build --strict
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: site

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v5
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ nnnotes.toml
.env
out/
cache/
/site/
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Changelog

All notable changes to this project are listed here. Versions follow [Semantic Versioning](https://semver.org/).
All notable changes to this project are listed here. Versions follow [Semantic Versioning](https://semver.org/)
over the command line (commands, options, exit statuses and `--json` reports), the configuration keys, the JSON
schemas in `nnnotes.schemas` and the documented Python API. Before 1.0 a minor version can change any of them.

## [0.2.1](https://github.com/MetaSekaiLab/nnnotes/compare/v0.2.0...v0.2.1) - 2026-10-13

Expand Down
205 changes: 0 additions & 205 deletions README.en.md

This file was deleted.

219 changes: 78 additions & 141 deletions README.md

Large diffs are not rendered by default.

90 changes: 90 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# nnnotes?!

[English](https://github.com/MetaSekaiLab/nnnotes/blob/main/README.md) | [简体中文](https://github.com/MetaSekaiLab/nnnotes/blob/main/README.zh-CN.md)

nnnotes 是 BanG Dream! Our Notes 游戏文件的离线数据工具包:读取 Addressables catalog,下载并解密资源包,下载并解码
masterdata,再把剧情、Live2D 模型、据点、着色器、CRI 音频和谱面导出成结构化 JSON 与通用格式文件。
`nnnotes site build` 写出 [ournotes-player](https://github.com/empty-sekai/ournotes-player) 播放的静态站点,
`nnnotes music-data` 写出包含全部歌曲与谱面的单个 JSON 文件。命名灵感来自 sssekai。

本项目为非官方爱好者项目,与游戏的开发和运营方无关。仓库不包含任何游戏资源、密钥或服务器地址:游戏文件、密钥和服务器地址
都由使用者在自己的配置中提供,导出结果只保存在使用者指定的本地目录。

**文档(英文):<https://metasekailab.github.io/nnnotes/>**

## 安装

```sh
pip install nnnotes # Python 3.11+
pip install 'nnnotes[fonts]' # 游戏字体(--fonts game)与 site build 的剧情文字
pip install 'nnnotes[deck]' # music-data 的组卡模型谱面统计
```

此外还需要游戏的 `base.apk`;读取 masterdata 的命令需要解码后的 masterdata;音频与视频需要外部工具
[vgmstream](https://vgmstream.org/) 和 [FFmpeg](https://ffmpeg.org/)(`site build` 另需 Node.js 20+ 与构建好的
ournotes-player)。

## 快速上手

```sh
nnnotes config init --user # 逐项询问设置(密钥不回显),写入用户配置文件
nnnotes config check # 每项设置的来源与是否有效,不显示设置值
nnnotes master download --latest -o work/master-bin
nnnotes master decode work/master-bin -o work/master
nnnotes --master work/master export story 10462 -o out/story_10462
nnnotes site build -o out/site --all --player path/to/ournotes-player
```

每个命令输出简短摘要,加 `--json` 时输出 JSON 报告。脚本中可以不经提问写好配置文件:
`nnnotes config init --user --no-input --set 段.键=值 ...`。

## 命令

| 命令 | 作用 |
| --- | --- |
| `config init\|set\|unset\|check\|path` | 写入、修改和检查配置文件 |
| `servers` | 游戏 API 的服务器列表:各区服及其 CDN 与 API 根地址 |
| `catalog keys\|browse` | 区服 catalog 的资源键;在本地网页中浏览 catalog 与资源包 |
| `catalog versions\|import\|fetch\|diff` | store 中保存的 catalog 版本,以及两个版本之间的变化 |
| `bundle pull` | 下载并解密资源键的依赖闭包到缓存 |
| `master version\|download\|decode` | 区服当前的 masterdata 版本、下载,以及解码为 JSON |
| `export assets` | 经内容寻址的 store,把所选资源包中的每个对象导出为通用格式 |
| `export episode\|story\|live\|live2d\|spot\|room\|shader\|audio\|ui` | 单个对象:ADV 剧情集、完整剧情、谱面、Live2D 模型、据点、房间、着色器、cue sheet、UI 库 |
| `apk settings\|hca-key` | `base.apk` 中的画面设置与 CRI HCA 密钥 |
| `store plan\|run\|verify` | `export assets` 将运行什么及原因、单独运行任务、检查 store |
| `voices list\|search\|summary\|get` | masterdata 中的角色语音与剧情语音 |
| `site build` | ournotes-player 静态站点:谱面、Live2D 模型与剧情,支持多个区服与语言 |
| `music-data` | 全部歌曲与谱面的单个 JSON 文件,附组卡模型的谱面统计 |

[命令参考](https://metasekailab.github.io/nnnotes/cli/)列出了所有参数。

## 文档

- [快速上手](https://metasekailab.github.io/nnnotes/quickstart/):从空配置到导出一集剧情。
- 操作指南:[配置](https://metasekailab.github.io/nnnotes/configuration/)、
[导出剧情与谱面](https://metasekailab.github.io/nnnotes/export/)、
[构建 ournotes-player 站点](https://metasekailab.github.io/nnnotes/site/)、
[导出全部资源](https://metasekailab.github.io/nnnotes/assets/)、
[日服](https://metasekailab.github.io/nnnotes/jp/)、
[music-data](https://metasekailab.github.io/nnnotes/music-data/)。
- 参考:[命令](https://metasekailab.github.io/nnnotes/cli/)、
[命令输出](https://metasekailab.github.io/nnnotes/commands/)、
[JSON schema](https://metasekailab.github.io/nnnotes/schemas/)、
[契约](https://metasekailab.github.io/nnnotes/contracts/)、[stage](https://metasekailab.github.io/nnnotes/stages/)、
[视图](https://metasekailab.github.io/nnnotes/views/)。
- 原理:[架构](https://metasekailab.github.io/nnnotes/architecture/)、
[确定性](https://metasekailab.github.io/nnnotes/determinism/)、
[验证](https://metasekailab.github.io/nnnotes/validation/)。

在同一套环境下导出是确定的:同样的输入,在 nnnotes、Python 依赖库(UnityPy、Pillow、numpy 等)和外部工具版本都相同时,
导出文件逐字节相同;JSON 一律 UTF-8、LF 换行,非有限数写作 `1e999`。版本不同时,相同内容可能写成不同的字节(不同版本的
Pillow 写出的 PNG 像素相同,字节可能不同)。

## 版本记录与版本号

每个版本的改动见 [CHANGELOG.md](https://github.com/MetaSekaiLab/nnnotes/blob/main/CHANGELOG.md)。版本号遵循
[语义化版本](https://semver.org/lang/zh-CN/),约束的接口是命令行、配置键、JSON schema 和文档中的 Python API。

## License

MIT,见 [LICENSE](https://github.com/MetaSekaiLab/nnnotes/blob/main/LICENSE)。
4 changes: 3 additions & 1 deletion cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
header = """
# Changelog

All notable changes to this project are listed here. Versions follow [Semantic Versioning](https://semver.org/).\n
All notable changes to this project are listed here. Versions follow [Semantic Versioning](https://semver.org/)
over the command line (commands, options, exit statuses and `--json` reports), the configuration keys, the JSON
schemas in `nnnotes.schemas` and the documented Python API. Before 1.0 a minor version can change any of them.\n
"""
body = """
{%- macro commit_link(id) -%}
Expand Down
56 changes: 56 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Architecture

nnnotes reads the game's files in layers: settings and sources at the bottom, file formats and Unity objects above
them, the game's content above those, then the pipeline, the products and the command line. Each layer is one
subpackage of `nnnotes` and imports only the layers below it.

## Layers

| Layer | Subpackage | What it holds |
|---|---|---|
| Command line | `nnnotes.cli` | one module per command group; parses options, reads the configuration, prints summaries and `--json` reports |
| Products | `nnnotes.site`, `nnnotes.musicdata` | the ournotes-player site (charts, models, stories, indexes); the music data file and its deck statistics |
| Pipeline | `nnnotes.pipeline` | stages, task descriptions and results, the content-addressed store, plans, layouts, the catalog versions, the views of the master data |
| Content | `nnnotes.content` | the game's subjects as exports: ADV episodes and stories, lives and scores, spots, rooms, the UI library |
| Unity objects | `nnnotes.unity` | bundle reading, the typetree exporter, the player data, Live2D prefabs, text styles and TextMesh Pro fonts |
| File formats | `nnnotes.formats` | formats read from bytes: CRI ACB/AWB and HCA, USM movies, CRI Lips data, shaders, motions |
| Sources | `nnnotes.source` | the configuration, the APK, Addressables catalogs and bundles, the game API, master data, languages, `Session` |
| Core | `nnnotes.core` | the one JSON writer and the process caches |

The order is a contract: the `layers` contract of [import-linter](https://import-linter.readthedocs.io/) in
`pyproject.toml` fails the build when a module imports a layer above its own, and `nnnotes.site` and
`nnnotes.musicdata` do not import each other. No library module imports `nnnotes.cli`: the commands parse options
and print, and the work they run is in the library modules below them.

## Sources and the Session

Every setting comes from the configuration ([sources and precedence](configuration.md#sources-and-precedence)).
`nnnotes.source.session.Session` turns one configuration into the data sources a command needs: the catalog of a
region, its decoded master data and the master data version it serves, the APK, the bundle and master data keys and
the store root. It reads a setting when it is first needed and raises a `ConfigError` naming the setting when it is missing or wrong;
the command line turns that error into exit status 2.

The catalog (`nnnotes.source.catalog.Catalog`) resolves an addressable key to its bundle and the bundle's
dependency closure, takes a bundle from the APK when the APK has it and from the CDN otherwise, and keeps fetched and
decrypted bundles in the cache directory.

## Two ways to export

- **One subject.** `export episode`, `export story`, `export live`, `export live2d`, `export spot`, `export room`,
`export shader`, `export audio`, `export ui`, `site build` and `music-data` read what one subject needs through the
Session and write their output directory directly ([Export stories and lives](export.md)).
- **Every asset.** `export assets` splits the work into stages ([stages](stages.md)). A task is one stage applied to
one subject (a bundle, a content id, a view); its key covers exactly what its output depends on: the stage and
converter versions, the input contents and the options ([contracts](contracts.md)). Results and their files are
stored once by content hash in the store, and the output directory is laid out from the store. A second run finds
every unchanged result in the store and only lays out what changed; `store plan` says what would run and why
([asset export](assets.md)).

Both ways use the same readers below the pipeline layer, so a texture, shader, mesh or audio stream is converted by
one implementation whichever command asks for it.

## Outputs

Every JSON file is written by one writer (`nnnotes.core.jsonio`), which fixes the encoding, the line endings and the
number format; [determinism](determinism.md) explains what makes the outputs reproducible. Command reports and the
documents of the asset export have JSON Schemas shipped in `nnnotes.schemas` ([JSON schemas](schemas.md)).
Loading
Loading