Asset export¶
nnnotes export assets writes every object of the selected bundles in a common format: textures and sprites as PNG,
meshes as binary glTF, text assets and fonts as their stored bytes, prefabs, materials, animations and every other
class as JSON. It also writes the semantic views of the master data (cards, characters, stamps, …) with the files of
the objects they name. The work is split into stages (stages.md); each stage runs one task per subject
(a bundle, a view, …), and a task's result is stored under a key that covers exactly what its output depends on
(contracts.md). A second run over the same data finds every result in the store and only
rewrites what changed.
nnnotes export assets -o out/assets
nnnotes export assets -o out/cards --select group:membercard --views cards
nnnotes store plan --select key:Image/Jacket/
nnnotes store plan --check && echo "up to date"
Commands¶
nnnotes export assets -o OUT [--layout original[,cas]] [--select group:G|key:PREFIX|bundle:GLOB ...] [--views all|none|a,b]
[--store DIR] [--workers N] [--memory GiB] [--fetch-workers N] [--stored-catalog L|SHA]
[--png-level N] [--flac-level N] [--only-class C,...] [--link auto|clone|hard|copy] [--strict]
[--dry-run] [--explain]
nnnotes store plan [the selection and parameters of export] [-o OUT] [--json] [--since RUN] [--check] [--census]
[--emit-tasks DIR] [--why TASK]
nnnotes store run TASK.json|DIR [...] [--store DIR] [--fetch] [--force]
nnnotes catalog versions [--json] | import FILE [--label L] [--apk-catalog FILE] [--resource-version V] | fetch [--label L]
| diff OLD NEW [--json]
nnnotes store verify [--quick] [--workers N]
export assets¶
| Flag | Meaning |
|---|---|
-o OUT |
output directory |
--select SEL |
what to export (repeatable, the union; default everything): group:G the bundles of an Addressables group (the stable bundle name up to _assets_, else up to its first _), key:PREFIX the bundles and raw files the catalog keys under PREFIX load, bundle:GLOB the bundles and raw files whose stable name matches |
--views |
the semantic views to build (views.md): all, none or names, comma-separated; default all when master data is set, else none |
--layout |
original, cas or both (below) |
--store DIR |
the store ([paths] store; default <cache>/store) |
--workers N |
worker processes (default: the CPUs the process may run on, its affinity mask as taskset or a container's cpuset sets it; 0: every task in this process) |
--memory GiB |
memory budget (default: 80 % of the physical memory where it can be read, else none) |
--fetch-workers N |
parallel downloads of bundles not in the cache (default 8) |
--stored-catalog |
a catalog version of the store (catalog versions) instead of the current catalog |
--png-level N |
zlib level 0-9 of the PNG files (default 6); part of the task keys |
--flac-level N |
FLAC compression level 0-12 of the CRI audio and the movies' audio (default 8; every level decodes to the same samples; 12 writes files 0.2-1.3 % smaller in about five times the encoding time); part of the task keys |
--only-class C,... |
export only objects of these Unity classes; part of the task keys |
--link |
how the layout files are made from the store (below): auto (default; [export] link), clone, hard or copy |
--strict |
exit 1 when a view has gaps: a required role whose address names no catalog key or no fitting sub-object (empty values and roles that do not apply are not gaps); in voices, a voice whose cue sheet has no catalog key or whose decoded sheet lacks its cue, and a story episode without a catalog key |
--dry-run |
print the plan and stop |
--explain |
print every setting of the run first: store, context, selection, pipeline, stages not installed, parameters, workers, memory budget, recycling, layouts |
A run resolves the context (region, language, catalog version) and indexes the catalog, identifies the selected bundles (the sha256 of each decrypted bundle, remembered by path, size and modification time; bundles not in the cache are fetched), takes the census of each bundle, links the scripts and addresses, exports the bundles, runs the stages that need those results (atlas sprites, views), writes the layouts and the reports. The selected raw files (CRI audio and movies) not in the cache are fetched while the bundle stages run. The census also covers the bundles the selected ones depend on, so script classes and atlas textures in other bundles resolve.
Tasks run in spawned worker processes that stay alive between tasks (no more of them start than a stage has tasks to
run). The longest tasks (by estimated CPU time) start first; a task starts only while the estimated peak memory of
the running tasks and its own fits the budget, and a task estimated above the budget runs alone. A worker is
replaced after 500 tasks or when its resident memory after a task is above 3 GiB, or above 40 % of its part of the
memory budget (the budget divided by the workers) when that is less, but not below 256 MiB: a worker keeps much of
its largest task's memory, which the budget does not count (--explain prints the threshold). The estimates come
from each stage's cost model, scaled per stage by the costs measured in earlier runs of the store
(<store>/costs/calibration.json, updated after every export; a task's measured peak counts from its worker's
memory at its start). None of this changes an output: a run with one worker and a run with many write the same store
and the same files.
A failed object is reported with its task's result and stays failed until something in its key changes. A task that fails as a whole (an exception, an input that cannot be read, a worker that dies) leaves no result and runs again next time.
store plan¶
What export assets with the same flags would do, without running or fetching anything: every task with its status
(hit: its result is stored, run, unknown: it reads the result of a task that would run first, or of a bundle
not fetched yet), the reasons it runs (compared with the latest run of the same selection, or --since RUN), the
estimated CPU time and peak memory, and with -o the files each layout would write, remove and keep. The text
gives per stage its counts and its first 20 tasks that are not hits; --json has every task.
| Flag | Meaning |
|---|---|
--json |
the plan document (nnnotes.plan/1) |
--check |
exit 1 when anything would run or a layout file would change, else 0 |
--census |
first fetch the bundles without a census and take it, so the plan knows every bundle's classes |
--emit-tasks DIR |
write the tasks that can run now as task descriptions DIR/<key>.json |
--why TASK |
one task: its status, reasons, and its key parts beside those of the previous run |
Reasons: new-input, input-changed, stage-version, atom-changed, params-changed, context-changed,
previous-failed, result-missing, forced; hits are unchanged or cached (the result is in the store from
another run); unknown tasks are pending on a task, or on fetch:<file> for a bundle whose content is not known
yet. See contracts.md.
store run¶
Runs task descriptions (nnnotes.task/1, as plan --emit-tasks writes them) into a store: each is checked (its key
is recomputed, its stage must be installed at its version), its inputs are read by content id through their
locators (the store, the cache, a file, or with --fetch the catalog), and its result is stored exactly as export assets
would store it. Several tasks run one after another in one process; stages that do not read bundles never load
UnityPy. --force runs tasks whose result is stored again (the new result must be identical).
catalog versions, import, fetch, diff¶
The catalog versions of a store: import a catalog file (with the APK's catalog from [paths] apk or
--apk-catalog), fetch the current catalog of the configured region and language, list them, and diff two of
them (labels, version ids or sha256 prefixes): keys added, removed and changed, bundles and raw files by stable name,
the keys that reach a changed file, and, when a run over each version has stored its address table, the objects by
stable address (an object keeps its stable address while its object id changes with its bundle's content). A version
is labelled by --label, else by its resource version, else by the first 12 hex digits of its sha256. fetch
records the resource version it downloads (none for main); import takes --resource-version, else the JP
catalog's source version, else the version in a file name catalog_<version>_<language>.bin as fetch and the
cache name them (none for main). export assets imports the current catalog when it is not imported yet.
store verify¶
Checks that every stored content has the sha256 its name says (--quick: sizes only) and that every result names
its own key, has the key of its parts and names stored contents of the right size. Exit 1 when a problem is found. --workers hashes in
parallel (default: the CPUs the process may use; 0: one at a time).
Layouts¶
A layout places the artifacts of a run as files; it never changes their bytes. With one layout the files are
written into OUT, with several into OUT/<layout>. Each layout directory has a manifest.json
(nnnotes.layout/1) listing the files it owns; a run writes files atomically, removes the files it owned that are
no longer wanted, never touches other files, and continues an interrupted write on the next run.
original uses the game's names:
- an object with a container path is written at that path, in the catalog's spelling
(
Assets/AddressableResources/Image/Jacket/jacket_001.png), its extension appended unless the path has it (…/prefab_x.prefab.json); - other objects under the same container path:
stem[<name>].<ext>, the Addressables sub-object form (…/member_full[square].png); - objects without a container path:
_bundles/<stable bundle name>/<Class>/<name>~<pathId>.<ext>; - secondary artifacts beside the primary one:
<path>.<role>.<ext>(….meta.json,….blob.m_Data.moc3); - a container path present in two bundles:
stem@<stable bundle name>.extfor each; - names Windows refuses, reserved device names and trailing dots are escaped as
%XX; paths that coincide ignoring case get~<8 hex>and are listed in the layout report; - the outputs of the CRI stages at the catalog names of their content:
Cri/Sound/<sheet>/<stream>.flacwithcues.jsonandstreams.json,Cri/Video/<name>/…; a content several names refer to is placed under each name and listed in the layout report; - the views:
views/<view>.json, each object entry with thepathof its file in this layout (views/voices.json: each stream of a voice).
How the files are made (--link, or [export] link). Every method writes the same files and the same layout
manifest; the run manifest and the printed summary name the method and why it was chosen.
--link |
the files | extra space | an edit of a file |
|---|---|---|---|
clone |
clones (reflinks) of the store objects: Linux FICLONE (Btrfs, XFS, bcachefs, …), macOS APFS clonefile |
none until a file is written (a quota that counts each file's blocks, such as an XFS project quota, counts them in full) | changes that file only |
hard |
hard links to the store objects (same file system) | none | fails, except for the superuser: the store objects are read-only (on Windows a delete fails too) |
copy |
copies | the whole layout again | changes that file only |
auto (default) |
clone where the file system makes clones, else hard on the same file system, else copy |
auto tries each method once, on a temporary file, before the run starts. An explicit clone or hard that does
not work there is a usage error (exit 2) before anything runs; a file that fails with the chosen method is reported
(summary, layout report, exit 1), left out of the manifest and placed by the next run: no file falls back to
another method. The store writes its objects read-only; a layout clears the attribute of a file it owns before it
replaces or removes it where the system needs that (Windows), and the store object stays read-only.
cas is assets/<sha256>.<ext> per distinct content, plus the manifest. Each layout follows from the other: cas
from the original manifest alone, original from cas and the results. The views are the exception: original
has them with the paths of their objects, cas the view documents they are made from.
Reports¶
OUT/_reports/:
| File | Content |
|---|---|
run.json |
the run manifest (nnnotes.run/1): context, selection, parameters, stage versions, every task with its key and status, the layout manifests, a summary; also stored as <store>/runs/<id>.json |
coverage.json |
per class the census total and the count of every item status (exported, contained, generic, unsupported, failed, and missing: objects no task covered; an object two tasks cover, such as a sprite whose image sprite.crop makes, counts once); per reason code its count and five examples |
failures.json |
failed tasks and failed objects, with their codes and messages |
sources.json |
files of the selection that could not be read: source.absent (a file the catalog names that is not there, or an APK file without [paths] apk), source.error (the download failed) |
layout-<name>.json |
the paths renamed for collisions, the container paths present in several bundles, the contents placed under several names |
The summary printed at the end names the run, the task and item counts, the layouts' write / remove / keep counts and the reports directory.
Exit codes¶
| Command | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
export assets |
complete (unsupported objects allowed) | a task, an object or a download failed (reports written); with --strict also view gaps |
usage or settings | aborted: a setting or tool a task needs is missing, or interrupted; results written so far stay valid | |
store plan |
with --check: something would run or change |
usage or settings | |||
store run |
every task ran or hit | a task failed or its result is partial | usage or settings | a task is incompatible (edited, unknown stage or version); nothing of the batch runs | |
store verify |
no problem | problems found | usage or settings |
Running tasks elsewhere¶
Task descriptions carry everything a task needs; any machine with the same nnnotes version and access to the store
can run them. A task can only be described once the content of its inputs is known, so the bundles are identified
first (plan --census fetches and hashes them and takes their census). Then plan the tasks that can run now, run
them in batches on as many workers as you like, and plan again until nothing is left; export assets finally writes the
layouts and reports (every task is a hit by then).
store=/data/nnnotes-store
nnnotes store plan --store "$store" --census > /dev/null
for round in 1 2 3 4 5 6 7 8; do
rm -rf tasks
nnnotes store plan --store "$store" --emit-tasks tasks > /dev/null
ls tasks/*.json > /dev/null 2>&1 || break # nothing left to run
ls tasks/*.json | xargs -n 50 -P 16 nnnotes store run --store "$store" --fetch
done
nnnotes export assets --store "$store" -o out/assets
Each round runs the tasks whose inputs are known: the catalog index and the censuses, then the script and address
tables, then the bundle exports, then the stages that read their results. A task whose result is already stored is
a hit, so a batch can be run twice; a task that keeps failing is planned again in every round (the loop above stops
after eight) and reported by export assets.