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
24 changes: 24 additions & 0 deletions docs/products.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ changes, or what it reaches changes.
"CAB-…:40": {"status": "exported", "class": "Texture2D", "name": "texture_00",
"files": {"image": {"sha256": "…", "size": 812345, "ext": "png"},
"json": {"sha256": "…", "size": 640, "ext": "json"}}}},
"fenced": {},
"missing": []}
```

Expand All @@ -74,6 +75,11 @@ game loads it by. Each object has the status of its item (the most severe when t
class and name where known, its files by role (the artifacts of its items), the object whose file holds it (`in`, a contained
object), or the reason code of a generic, unsupported or failed object.

`fenced` names the objects that a reached object references across the font fence: the font bundles (the keys under
`EmbFont/` and `Font/`) are not followed from objects outside them, so each such reference is listed with the class and
name of the object it names, while the reference itself carries only its file and path id. A reference to an object of
the table is not listed.

## Product layout

A product layout places products for a player (`export assets --layout product`, `export live`,
Expand Down Expand Up @@ -365,6 +371,7 @@ A story episode plays with products that several episodes share, made by `nnnote
| `story.asset` | a catalog key a story loads: a stage (`Adv/Stage/<name>`), frame, effect, post effect, still, timeline, chat window, chat icon or stamp, transition | `storyassets/<name>.json`, `<name>` the key with `/` as `~` | the key | `asset.json` and the object table |
| `story.video` | a video key `Cri/Video/<asset>` (the video of a Movie / Clip command) | `videos/<name>.json`, `<name>` the asset with `/` as `~` | the key | `video.json` and `movie.webm` |
| `story.chat` | `default` | `storychat/default.json` | the shared chat shards `Adv/Chat/AdvChat-Text`, `Adv/Chat/AdvChat-Sound` and `Adv/Chat/AdvChat-SoundCueSheet` | `chat.json` |
| `story.ui` | a client language (`ja`, `en`, `zh-Hant`, `zh-Hans`, `ko`) | `storyui/<language>.json` | the story UI's catalog keys (the widget, talk windows, chat widget, dialogs, UIManager, letterbox, animations and settings), the built-in `GraphicsSettings` and `LocalizeManager`, and the resources `DOTweenSettings` and `TMP Settings` | `ui.json` and the object table |
| `story` | a `MasterAdv` id | `stories/<id>.json` | the episode key `Adv/Episode/<asset>/<asset>` (`<asset>`: the row's `_advEpisodeAsset`), its shard keys (the key with `-Text`, `-Sound`, `-SoundCueSheet`, `-Video`) and `EmbCommon/Adv/Settings/AdvPlayerSettings` | `episode.json` and `story.json` |

`story.sound`, `story.crilips`, `story.video`, `story.chat` and `story` keep no objects: their files are all they
Expand Down Expand Up @@ -416,6 +423,23 @@ chat message texts), `sounds` (their sound rows) and `cueSheets` (the cue sheet
rows of its shard as stored; a shard key without a TextAsset gives `[]`. The `product` layout makes it when the
selection holds the three shard keys.

`story.ui` is the ADV story UI in one client language: the ADV front canvas, the talk windows, the chat phone, the
dialogs and the settings, with the text records of their texts in that language. Its input `master` holds the
`MasterText` rows, each text in the language's column (`{"texts": {MasterText id: text}}`, the empty text when the row
has none). `ui.json` has `format` 1 and `language` (`code`, `mode` (the LanguageMode), `field` (the text column),
`lineSpacing`, `fontNames`, `additionalFontNames`, `materialTypes` and `fontSwap`, the Japanese font asset names mapped
to the language's), `widget`, `talkWindows` (each window's name to its object), `chatWidget`, `dialogs`, `uiManager`,
`letterbox`, `clips`, `controllers`, `settings` (`masterIdSettings`, `dotween`, `tmpSettings`, `localizeManager`),
`graphics`, `texts` (each TMP text by its component id: `localized`, the font asset, material and line spacing its
LocalizeText gives it in the language, or null, and `style`, its text record), `textStyle` (the language's field, the
units and the line metrics of the font roles the texts use), `masterIdTexts` (each `AdvMasterIdSettings` text with its
MasterText id and text), `chatStatusTexts` (incoming-call and lock-screen status keys to their texts), `bindings`
and `shaders`. Text styles use settings read from the client's font assets. The input `ui.objects` is the closure
of the UI roots; font assets and chat windows are additional assembly inputs for metrics and status text keys.
The retained object table is the UI closure, so the font assets' files remain in the depot. The input
`font-bundles` names the bundles holding the font assets, and `apk:AndroidManifest.xml` supplies the client version.
A client requests the product for each language it uses.

`story` is one episode. Its input `master` is the episode's master record: its `MasterAdv` row, the `MasterText` row
of its title (none when `_titleTextId` is empty) and every `MasterAdvChat` row. `episode.json` is the episode as
`export episode` writes it, each resource given by its kind and address: the command list with the lines of every
Expand Down
1 change: 1 addition & 0 deletions docs/stages.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ codec and channel of a USM) and `catalog.index` (version 2: gzip input and Japan
| `story.asset` | a catalog key a story loads | `objects`: the object table of what the key reaches | `asset.json` and the object table |
| `story.video` | a video key `Cri/Video/<asset>` | `objects`: the object table of the key; `usm:<key>` and `movies`: the key's USM and its `cri.movie` files with their facts ([products](products.md#stories)); the `cri.movie` task takes `format` `webm` | `video.json` and `movie.webm` |
| `story.chat` | `default` | `objects`: the object table of the shared chat shards | `chat.json` |
| `story.ui` | a client language (`ja`, `en`, `zh-Hant`, `zh-Hans`, `ko`) | `objects`: UI, font and chat-window objects; `ui.objects`: the retained UI closure; `master`: localized `MasterText` rows; `font-bundles`; `apk:AndroidManifest.xml` ([products](products.md#stories)) | `ui.json` (text styles and chat status texts) and the UI object table |
| `story` | a `MasterAdv` id | `objects`: the object table of the episode's command list, its shards and the ADV player settings ([products](products.md#stories)); `master`: the episode's master record | `episode.json` and `story.json` |
| `link.addresses` | global | catalog index, censuses (the built-in files' too) | key -> objects (a scene key: the objects of the scene's file outside a hierarchy), Resources.Load path -> objects, built-in selector (class, MonoBehaviour script class) -> objects, serialized file -> bundle or built-in file, bundle -> keys |
| `view.<name>` | a catalog (`main`) | `master:<Table>` per master table it reads, `rules` (the view's rule and the resolvers it uses), `addresses` (the part of the address table it read: the keys looked up and the prefixes listed); no parameters, atoms or context | `view.<name>:<subject>#view` (facts: rows, entries, gaps, unreferenced) |
Expand Down
28 changes: 23 additions & 5 deletions scripts/gen_typetrees.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
nnnotes).

nnnotes reads a few MonoBehaviours of the APK's boot data (`assets/bin/Data/data.unity3d`): the URP pipeline
assets, renderer data, renderer features and post-process data, DOTween and TextMeshPro settings, and the sound
and app-config defaults (the `apk/data.unity3d` tasks of `unity.export`, and PlayerData). IL2CPP player builds
strip script typetrees from that data, so nnnotes ships the typetrees of exactly these classes (CLASSES below) in
`src/nnnotes/unity/typetrees/<Unity version>.json`, one file per Unity version (read by nnnotes.unity.scripttypes).
assets, renderer data, renderer features and post-process data, DOTween and TextMeshPro settings, the sound and
app-config defaults and the localization manager (the `apk/data.unity3d` tasks of `unity.export`, and PlayerData).
IL2CPP player builds strip script typetrees from that data, so nnnotes ships the typetrees of exactly these classes
(CLASSES below) in `src/nnnotes/unity/typetrees/<Unity version>.json`, one file per Unity version (read by
nnnotes.unity.scripttypes).
Each entry holds the typetree nodes as [level, type, name, meta flag] rows and the type hash the serialized file
records for the class; the file also records the game version it was generated from.

Expand All @@ -14,7 +15,9 @@
structs carry their native names (`Vector3f`, `ColorRGBA`, `AABB`, ...), and arrays of them, of strings and of
PPtrs are `vector`; the align flag (0x4000, align the stream to 4 bytes after the node) sits where Unity puts it.
unity_rows turns the rows of TypeTreeGeneratorAPI's default (AssetsTools) backend into that layout. The other meta
flag bits are editor-only and left 0.
flag bits are editor-only and left 0. A field after which the serialized bytes are aligned, although the generated
tree does not say so, is listed in ALIGNED_FIELDS; generate() sets its align flag and the read check of its class
verifies it.

Supporting a new game version
-----------------------------
Expand Down Expand Up @@ -56,6 +59,7 @@
("Adv", "AdvSystem.Rendering", "AdvFieldRendererFeature"),
("App.Runtime", "App.Config", "AppConfigDefaultData"),
("DOTween.dll", "DG.Tweening.Core", "DOTweenSettings"),
("Fwk", "Fwk.Localization", "LocalizeManager"),
("Fwk", "Fwk.Rendering", "ScreenCaptureRendererFeature"),
("Fwk", "Fwk.Sound", "SoundVolumeSettings"),
("Fwk", "Fwk.UI.Rendering", "UIRendererFeature"),
Expand All @@ -76,6 +80,8 @@
ALIGN = 0x4000 # TransferMetaFlags.kAlignBytesFlag
# the MonoBehaviour header: field -> meta flag
HEADER = {"m_GameObject": 0, "m_Enabled": ALIGN, "m_Script": 0, "m_Name": 0}
# class -> its root fields after which the serialized bytes are aligned (the flag the generated tree leaves out)
ALIGNED_FIELDS = {"LocalizeManager": ("_dontDestroyOnload",)}
# built-in structs the generator names after their C# type -> the native type name Unity serializes
NATIVE_TYPES = {
"Bounds": "AABB",
Expand Down Expand Up @@ -196,6 +202,17 @@ def unity_rows(rows: list) -> list:
return _rows(root)


def _aligned(rows: list, names: tuple) -> list:
"""The rows with the align flag set on the root fields `names` (ALIGNED_FIELDS); ValueError for a missing one."""
roots = {r[2] for r in rows if r[0] == 1}
if not set(names) <= roots:
raise ValueError(f"no root field {sorted(set(names) - roots)}")
for r in rows:
if r[0] == 1 and r[2] in names:
r[3] |= ALIGN
return rows


def objects_by_class(player: PlayerData) -> dict[tuple[str, str, str], list]:
out: dict[tuple[str, str, str], list] = {}
for o in player._by_type.get("MonoBehaviour", []):
Expand All @@ -222,6 +239,7 @@ def generate(player: PlayerData, gen: TypeTreeGenerator, game_version: str) -> d
for n in gen.get_nodes(asm if asm.endswith(".dll") else f"{asm}.dll", name)
]
)
rows = _aligned(rows, ALIGNED_FIELDS.get(cls, ()))
except ValueError as e:
raise SystemExit(f"{name} ({asm}): {e}") from e
node = typetree_node(rows)
Expand Down
1 change: 1 addition & 0 deletions src/nnnotes/cli/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ def matches(self, stage: str) -> bool:
StageSource("story.asset", "nnnotes.pipeline.story:AssetStage", output=True, product=True),
StageSource("story.video", "nnnotes.pipeline.story:VideoStage", output=True, product=True),
StageSource("story.chat", "nnnotes.pipeline.story:ChatStage", output=True, product=True),
StageSource("story.ui", "nnnotes.pipeline.story:UiStage", output=True, product=True),
StageSource("story", "nnnotes.pipeline.story:StoryStage", output=True, product=True),
StageSource("view.", "nnnotes.pipeline.views:stages", output=True),
StageSource("view.voices", "nnnotes.pipeline.voices:VoiceStage", output=True),
Expand Down
22 changes: 21 additions & 1 deletion src/nnnotes/content/prefabs.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"key_object",
"mat4",
"mesh_roles",
"mono_json",
"prefab_ids",
"ref_id",
"root_game_object",
Expand All @@ -68,8 +69,12 @@ def ids(self, cls: str | None = None) -> list[str]: ...

def key(self, key: str) -> list[str]: ...

def keys(self) -> list[str]: ...

def info(self, oid: str) -> dict: ...

def fenced(self, oid: str) -> dict | None: ...

def bytes(self, oid: str, role: str) -> bytes: ...

def json(self, oid: str, role: str = "json") -> Any: ...
Expand All @@ -94,13 +99,28 @@ def has_json(objects: Objects, oid: str) -> bool:


def script_class(objects: Objects, oid: str) -> str | None:
"""The script class of a MonoBehaviour outside a hierarchy (its `json`'s `$script`), None without one."""
"""The script class of a MonoBehaviour or a contained component, None without one."""
if oid in objects and objects.info(oid).get("in"):
file, pid = oid.rsplit(":", 1)
hit = component_ref(objects, "", {"$ref": {"file": file, "pathId": int(pid)}})
return component_class(hit[2]) if hit is not None else None
if not has_json(objects, oid):
return None
entry = objects.json(oid).get("$script")
return entry.rsplit("|", 1)[-1] if isinstance(entry, str) else None


def mono_json(objects: Objects, oid: str) -> dict:
"""The serialized fields of a MonoBehaviour, including a component stored in a prefab's document."""
if objects.info(oid).get("in"):
file, pid = oid.rsplit(":", 1)
hit = component_ref(objects, "", {"$ref": {"file": file, "pathId": int(pid)}})
if hit is None or hit[2]["type"] != "MonoBehaviour":
raise ValueError(f"{oid}: not a MonoBehaviour component")
return {"$script": hit[2].get("script"), **hit[2]["fields"]}
return objects.json(oid)


def root_game_object(objects: Objects, what: str) -> str:
"""The one GameObject among the table's roots (ValueError naming `what` otherwise)."""
gos = [o for o in objects.roots if o in objects and objects.info(o).get("class") == "GameObject"]
Expand Down
Loading
Loading