/** * One entry in the GR2 section table (44 bytes on disk — see * `docs/gr2-format.md` § Section record). `compression_name` and * `semantic_name` are computed accessors ; the rest map 1-to-1 to the * on-disk u32s. */ export type GR2Section = { /** * — 0-based position in the section table. */ index: number; /** * — compression algorithm tag (0=none, 1=Oodle0, 2=Oodle1, 3=BitKnit, 4=BitKnit2). */ compression: number; /** * — offset of this section's compressed bytes, relative to the file start. */ data_offset: number; /** * — length of this section's compressed bytes on disk. */ data_size: number; /** * — target length of the section once decompressed. */ expanded_size: number; /** * — required alignment for this section's data buffer (4 / 8 / …). */ internal_alignment: number; /** * — Oodle0 block-stop 1 : decoded-byte offset where the 16-bit length context ends. */ first_16bit: number; /** * — Oodle0 block-stop 2 : decoded-byte offset where the 8-bit length context ends. */ first_8bit: number; /** * — pointer-fixup table offset (S5+). */ pointer_fixup_offset: number; /** * — pointer-fixup entry count, 12 bytes each. */ pointer_fixup_count: number; /** * — mixed-marshalling table offset (S5+). */ mixed_marshalling_offset: number; /** * — mixed-marshalling entry count, 12 bytes each. */ mixed_marshalling_count: number; /** * — computed : human name for {@link GR2Section.compression}. */ compression_name: string; /** * — computed : Granny semantic name for {@link GR2Section.index}. */ semantic_name: string; }; /** * Top-level GR2 file header (~72 bytes for version ≥ 7, ~60 bytes otherwise). */ export type GR2Header = { /** * — Granny file format version — ≥ 7 across our iRO ver12 corpus. */ version: number; /** * — total file size as declared by the writer. */ total_size: number; /** * — CRC32 of the file's contents. */ crc: number; /** * — offset of the section array relative to the end of the magic. */ section_array_offset: number; /** * — number of entries in the section array. */ section_count: number; /** * — `[section_index, offset_within_section]`. */ root_type: readonly [ number, number ]; /** * — `[section_index, offset_within_section]`. */ root_object: readonly [ number, number ]; /** * — type-tag identifying the .gr2 schema generation. */ type_tag: number; /** * — 4 user / auxiliary tag values. */ extra_tags: readonly number[]; /** * — string-database CRC (version ≥ 7). */ string_db_crc: number; /** * — 3 reserved u32 (version ≥ 7). */ reserved: readonly number[]; /** * — pointer width baked into the file's serialized references. */ pointer_size: 32 | 64; /** * — true if u32s are stored byte-reversed. */ byte_reversed: boolean; }; /** * Parsed GR2 file, ready for section decompression. */ export type GR2File = { header: GR2Header; sections: readonly GR2Section[]; /** * — raw input bytes — kept for sliced reads via {@link GR2File.sectionBytes}. */ data: Uint8Array; /** * — slice of `data` * carrying `section`'s on-disk compressed bytes. */ sectionBytes: (section: GR2Section) => Uint8Array; }; /** * Encode a `[section, offset]` ref as a single u32 fake pointer. * Round-trips with {@link decodeFakePointer}. * * @param {number} section * @param {number} offset * @returns {number} */ export function makeFakePointer(section: number, offset: number): number; /** * Decode a fake pointer back into `[section, offset]`. Returns `null` * for pointers outside the fake-pointer range or pointing at a section * index that doesn't exist. * * @param {number} pointer * @param {number} sectionCount * @returns {SectionRef | null} */ export function decodeFakePointer(pointer: number, sectionCount: number): SectionRef | null; /** * Decompress every section, apply the pointer-fixup table, and return a * `LoadedGR2` ready for the type-tree walker. See module header for the * fake-pointer encoding rationale. * * @throws Error when the file declares `byte_reversed` AND has non-empty * mixed-marshalling tables — we'd need to actually flip endianness for * in-section scalars, which the iRO corpus never requires. (LE assets * carry empty mixed-marshalling tables ; the throw catches accidental * silent corruption on a future BE asset.) * * @param {import('./GrannyFile.js').GR2File} file * @returns {LoadedGR2} */ export function loadGR2(file: GR2File): LoadedGR2; /** * Walk a `DataTypeDefinition` chain starting at `ref` (a * `[section, offset]` pair, typically `file.header.root_type` or a * member's `referenceType`). Returns an array of member descriptors * terminating at the MT_END sentinel. * * Each member is read from a 32-byte record : * u32 member_type * u32 name_ptr (fake pointer → string) * u32 type_ptr (fake pointer → sub-type tree, or 0) * u32 array_width (default 1 if 0) * u32 extra[3] * u32 _unused * * Note : the on-disk pointer slots are pointer-sized (4 or 8 bytes), but * for the all-32-bit iRO corpus they fit exactly in 32 bits. * * @param {LoadedGR2} loaded * @param {SectionRef} ref — `[section, offset]` of the type-definition chain. * @param {ParseTypeTreeOptions} [options] * @returns {readonly TypeMember[]} member descriptors in source order. */ export function parseTypeTree(loaded: LoadedGR2, ref: SectionRef, options?: ParseTypeTreeOptions): readonly TypeMember[]; /** * Recursive total of `members`' storage sizes. INLINE members recurse * into their sub-type. The `seen` set guards against cyclic schemas * (Granny's schema is acyclic in practice but the guard is cheap). * * Cycle protection is per-recursion-stack : each sibling member starts * with a *fresh copy* of the caller's `seen` set. Without the copy, * structs with multiple inline siblings of the same sub-type (e.g. * TransformTrack's three identical CurveData inline members) collapse * to the size of one — a stride bug invisible to S5/S6 but fatal to * S7 animation walks, where `objectStorageSize` drives array stride. * * A shared sub-type in a DAG of inline members is sized once and cached in * `memo` (keyed by the sub-type ref), collapsing the otherwise-`B^D` re-walk * a crafted file could force. The memo-hit path **adds** the cached size (it * does not skip like the cycle guard), so sibling members of the same type * still sum correctly — the stride correctness above is preserved. For any * acyclic schema (all real assets) a type's size is context-independent, so * the memoized value equals the full recursion result → byte-exact. * * @param {LoadedGR2} loaded * @param {readonly TypeMember[]} members * @param {number} pointerSize * @param {Set} seen — cycle guard ; pass a fresh `Set` at the call site. * @param {Record} [memo] - per-call size cache keyed by sub-type ref ; defaults to a fresh `{}`. * @returns {number} total storage size in bytes. */ export function objectStorageSize(loaded: LoadedGR2, members: readonly TypeMember[], pointerSize: number, seen: Set, memo?: Record): number; /** * Materialize one instance against its `typeTree`, walking * `loaded.sectionsFixed[ref[0]]` from `ref[1]`. Returns a plain JS * object keyed by member name : * * { * Meshes: { type: 'array_of_references', count, target, * element_refs: [{section, offset}, ...] }, * Skeletons: { ... }, * Animations: { ... }, * ... * } * * Scalars are read from `sectionsOriginal` (unmodified bytes) ; pointers * are read from `sectionsFixed` (rewritten with fake pointers by * `loadGR2`). * * @param {LoadedGR2} loaded * @param {readonly TypeMember[]} typeTree * @param {SectionRef} ref — `[section, offset]` of the instance. * @param {ParseObjectOptions} [options] * @param {number} [depth] - internal: current INLINE recursion depth (caps at {@link MAX_INLINE_DEPTH}). * @param {Set | null} [seen] - internal: INLINE type-refs on the recursion stack (cycle guard); allocated lazily. * @returns {ParsedObject} field map keyed by member name. */ export function parseObject(loaded: LoadedGR2, typeTree: readonly TypeMember[], ref: SectionRef, options?: ParseObjectOptions, depth?: number, seen?: Set | null): ParsedObject; /** * Walk an inline-struct array : `count` objects of `typeRef`-shaped struct, * packed back-to-back starting at `arrayRef`. Returns each as * `{ref, fields}` where `ref` is the per-object `{section, offset}` (needed * by callers to read raw bytes — e.g. SkeletonBone's 68-byte Transform — * located at field-offsets inside the object's section) and `fields` is the * `parseObject` materialization. * * Port of blendergranny `types.read_reference_array_objects`. Null-safe : * returns `[]` when either ref is null or count is non-positive. * * @param {LoadedGR2} loaded * @param {RefDict | null} arrayRef * @param {number} count * @param {RefDict | null} typeRef * @param {ReadReferenceArrayObjectsOptions} [options] * @returns {readonly ReferenceArrayObject[]} */ export function readReferenceArrayObjects(loaded: LoadedGR2, arrayRef: RefDict | null, count: number, typeRef: RefDict | null, options?: ReadReferenceArrayObjectsOptions): readonly ReferenceArrayObject[]; /** * Typed error for malformed / hostile type-tree input (recursion breach, * cycle). Catch-friendly ; mirrors `DecompressionError` so callers can * distinguish a parser rejection from a bare `RangeError`. */ export class GrannyParseError extends Error { constructor(message: any); } /** * `[section_index, offset_within_section]` pair used everywhere as a ref. * @typedef {readonly [number, number]} SectionRef */ /** * `{section, offset}` dict shape returned for caller convenience. * @typedef {object} RefDict * @property {number} section * @property {number} offset */ /** * One MEMBER_TYPE constant value (0–22). See {@link MEMBER_TYPE_NAMES}. * @typedef {number} MemberTypeConstant */ /** * Member descriptor read from one 32-byte record of a DataTypeDefinition chain. * * @typedef {object} TypeMember * @property {MemberTypeConstant} memberType — raw `MT_*` enum value (0–22). * @property {string} memberTypeName — human name for {@link TypeMember.memberType}. * @property {string} name — member name (ASCII, decoded from the string-pointer slot). * @property {SectionRef | null} referenceType — decoded sub-type ref, or `null` for scalar / terminal members. * @property {number} arrayWidth — array width (default 1 for non-array members). * @property {readonly [number, number, number]} extra — three extra u32 slots in the on-disk record. * @property {number} offset — byte offset of this record within its containing section. */ /** * One pointer-fixup entry : rebases a writer-side pointer to a `[section, offset]` ref. * * @typedef {object} PointerFixup * @property {number} source_section * @property {number} source_offset * @property {SectionRef} target */ /** * One mixed-marshalling entry : describes an endian-flip the writer expects. * * @typedef {object} MixedMarshallingFixup * @property {number} source_section * @property {number} count * @property {number} offset * @property {SectionRef} type_ref */ /** * Bundle of decompressed sections + applied pointer fixups ready for walking. * * @typedef {object} LoadedGR2 * @property {import('./GrannyFile.js').GR2File} file — the originating GR2 file (header + section table). * @property {readonly Uint8Array[]} sectionsOriginal — one `Uint8Array` per section, decompressed, untouched (scalar reads). * @property {readonly Uint8Array[]} sectionsFixed — one `Uint8Array` per section, with pointer-fixup slots overwritten by fake pointers. * @property {readonly PointerFixup[]} pointerFixups — all pointer fixups parsed from the file (flattened across sections). * @property {readonly MixedMarshallingFixup[]} mixedFixups — all mixed-marshalling fixups (typically empty for LE corpus). * @property {4 | 8} pointerSize — pointer width in bytes (4 for 32-bit files, 8 for 64-bit). */ /** * Common shape of a materialized field returned by {@link parseObject}. * * @typedef {object} ParsedField * @property {string} type — `memberTypeName` from the source {@link TypeMember}. * @property {number} offset — byte offset of the field inside its parent struct. * @property {RefDict} [reference_type] - sub-type ref, if the source member had one. * @property {number | string} [value] - decoded scalar value (`int*` / `uint*` / `real32` / `string`). * @property {RefDict | null} [target] - resolved target ref (`reference` / `*_to_array` / `*_of_references`). * @property {number} [count] - element count (`*_to_array` / `*_of_references` / `*_variant_array`). * @property {boolean} [truncated] - true when {@link ParsedField.count} exceeded `maxArrayRefs`. * @property {readonly RefDict[]} [element_refs] - decoded per-element refs (only `array_of_references`). * @property {RefDict | null} [variant_type] - resolved variant-type ref (`variant_reference` / `reference_to_variant_array`). * @property {ParsedObject} [inline] - materialized sub-object (only `inline` members). */ /** * Plain JS object materialized from one `[ref, typeTree]` pair, keyed by member name. * @typedef {{ readonly [memberName: string]: ParsedField }} ParsedObject */ /** * Options for {@link parseTypeTree}. * @typedef {object} ParseTypeTreeOptions * @property {number} [maxMembers] - cap on the number of member records walked (default 512). */ /** * Options for {@link parseObject}. * @typedef {object} ParseObjectOptions * @property {number} [maxArrayRefs] - cap on the number of `element_refs` returned per array (default 256). */ /** * Options for {@link readReferenceArrayObjects}. * @typedef {object} ReadReferenceArrayObjectsOptions * @property {number} [maxCount] - cap on the number of array elements walked (default 64). * @property {number} [maxArrayRefs] - cap forwarded to {@link parseObject} per element (default `maxCount`). */ /** * One element of an inline-struct array as returned by {@link readReferenceArrayObjects}. * * @typedef {object} ReferenceArrayObject * @property {RefDict} ref — per-object `{section, offset}` within the array's section. * @property {ParsedObject} fields — materialized field map for this element. */ /** Marker terminating a type-definition chain (no payload). */ export const MT_END: 0; /** Inline sub-struct (no pointer, walks sub-type tree in place). */ export const MT_INLINE: 1; /** Pointer to a sub-object (single instance). */ export const MT_REFERENCE: 2; /** `(count:u32, pointer)` pair → array of `count` structs of `reference_type`. */ export const MT_REFERENCE_TO_ARRAY: 3; /** `(count:u32, pointer)` pair → array of `count` pointers to structs of `reference_type`. */ export const MT_ARRAY_OF_REFERENCES: 4; /** `(type_ptr, object_ptr)` pair → reference with runtime-resolved type. */ export const MT_VARIANT_REFERENCE: 5; /** Placeholder for a type the file uses but the SDK doesn't expose. */ export const MT_UNSUPPORTED: 6; /** `(type_ptr, count:u32, object_ptr)` triple → variant-typed array. */ export const MT_REFERENCE_TO_VARIANT_ARRAY: 7; /** Pointer to a NUL-terminated (or length-prefixed) string. */ export const MT_STRING: 8; /** Inline 4×4 + translation transform (68 bytes). */ export const MT_TRANSFORM: 9; /** 32-bit IEEE float. */ export const MT_REAL32: 10; /** Signed 8-bit integer. */ export const MT_INT8: 11; /** Unsigned 8-bit integer. */ export const MT_UINT8: 12; /** Signed 8-bit, intended as a normalized binormal component. */ export const MT_BINORMAL_INT8: 13; /** Unsigned 8-bit, intended as a normalized normal component. */ export const MT_NORMAL_UINT8: 14; /** Signed 16-bit integer. */ export const MT_INT16: 15; /** Unsigned 16-bit integer. */ export const MT_UINT16: 16; /** Signed 16-bit, intended as a normalized binormal component. */ export const MT_BINORMAL_INT16: 17; /** Unsigned 16-bit, intended as a normalized normal component. */ export const MT_NORMAL_UINT16: 18; /** Signed 32-bit integer. */ export const MT_INT32: 19; /** Unsigned 32-bit integer. */ export const MT_UINT32: 20; /** 16-bit half-float (rare). */ export const MT_REAL16: 21; /** Null reference marker (occupies one pointer slot, never resolves). */ export const MT_EMPTY_REFERENCE: 22; /** MEMBER_TYPE constant → human name. * @type {Readonly>} */ export const MEMBER_TYPE_NAMES: Readonly>; /** Base value distinguishing fake pointers from raw u32 noise. */ export const FAKE_POINTER_BASE: 268435456; /** Per-section stride inside the fake-pointer encoding. */ export const FAKE_SECTION_STRIDE: 1048576; export const MAX_INLINE_DEPTH: 64; export namespace __test__ { export { makeU32Reader }; export { readPointer }; export { writePointer }; export { readGrannyString }; export { readStringPointer }; export { memberStorageSize }; export { objectStorageSize }; export { readArrayReferences }; export { looksText }; export { SCALAR_SIZES }; } /** * `[section_index, offset_within_section]` pair used everywhere as a ref. */ export type SectionRef = readonly [ number, number ]; /** * `{section, offset}` dict shape returned for caller convenience. */ export type RefDict = { section: number; offset: number; }; /** * One MEMBER_TYPE constant value (0–22). See {@link MEMBER_TYPE_NAMES}. */ export type MemberTypeConstant = number; /** * Member descriptor read from one 32-byte record of a DataTypeDefinition chain. */ export type TypeMember = { /** * — raw `MT_*` enum value (0–22). */ memberType: MemberTypeConstant; /** * — human name for {@link TypeMember.memberType}. */ memberTypeName: string; /** * — member name (ASCII, decoded from the string-pointer slot). */ name: string; /** * — decoded sub-type ref, or `null` for scalar / terminal members. */ referenceType: SectionRef | null; /** * — array width (default 1 for non-array members). */ arrayWidth: number; /** * — three extra u32 slots in the on-disk record. */ extra: readonly [ number, number, number ]; /** * — byte offset of this record within its containing section. */ offset: number; }; /** * One pointer-fixup entry : rebases a writer-side pointer to a `[section, offset]` ref. */ export type PointerFixup = { source_section: number; source_offset: number; target: SectionRef; }; /** * One mixed-marshalling entry : describes an endian-flip the writer expects. */ export type MixedMarshallingFixup = { source_section: number; count: number; offset: number; type_ref: SectionRef; }; /** * Bundle of decompressed sections + applied pointer fixups ready for walking. */ export type LoadedGR2 = { /** * — the originating GR2 file (header + section table). */ file: GR2File; /** * — one `Uint8Array` per section, decompressed, untouched (scalar reads). */ sectionsOriginal: readonly Uint8Array[]; /** * — one `Uint8Array` per section, with pointer-fixup slots overwritten by fake pointers. */ sectionsFixed: readonly Uint8Array[]; /** * — all pointer fixups parsed from the file (flattened across sections). */ pointerFixups: readonly PointerFixup[]; /** * — all mixed-marshalling fixups (typically empty for LE corpus). */ mixedFixups: readonly MixedMarshallingFixup[]; /** * — pointer width in bytes (4 for 32-bit files, 8 for 64-bit). */ pointerSize: 4 | 8; }; /** * Common shape of a materialized field returned by {@link parseObject}. */ export type ParsedField = { /** * — `memberTypeName` from the source {@link TypeMember}. */ type: string; /** * — byte offset of the field inside its parent struct. */ offset: number; /** * - sub-type ref, if the source member had one. */ reference_type?: RefDict; /** * - decoded scalar value (`int*` / `uint*` / `real32` / `string`). */ value?: number | string; /** * - resolved target ref (`reference` / `*_to_array` / `*_of_references`). */ target?: RefDict | null; /** * - element count (`*_to_array` / `*_of_references` / `*_variant_array`). */ count?: number; /** * - true when {@link ParsedField.count} exceeded `maxArrayRefs`. */ truncated?: boolean; /** * - decoded per-element refs (only `array_of_references`). */ element_refs?: readonly RefDict[]; /** * - resolved variant-type ref (`variant_reference` / `reference_to_variant_array`). */ variant_type?: RefDict | null; /** * - materialized sub-object (only `inline` members). */ inline?: ParsedObject; }; /** * Plain JS object materialized from one `[ref, typeTree]` pair, keyed by member name. */ export type ParsedObject = { readonly [memberName: string]: ParsedField; }; /** * Options for {@link parseTypeTree}. */ export type ParseTypeTreeOptions = { /** * - cap on the number of member records walked (default 512). */ maxMembers?: number; }; /** * Options for {@link parseObject}. */ export type ParseObjectOptions = { /** * - cap on the number of `element_refs` returned per array (default 256). */ maxArrayRefs?: number; }; /** * Options for {@link readReferenceArrayObjects}. */ export type ReadReferenceArrayObjectsOptions = { /** * - cap on the number of array elements walked (default 64). */ maxCount?: number; /** * - cap forwarded to {@link parseObject} per element (default `maxCount`). */ maxArrayRefs?: number; }; /** * One element of an inline-struct array as returned by {@link readReferenceArrayObjects}. */ export type ReferenceArrayObject = { /** * — per-object `{section, offset}` within the array's section. */ ref: RefDict; /** * — materialized field map for this element. */ fields: ParsedObject; }; declare function makeU32Reader(byteReversed: any): (bytes: any, offset: any) => number; declare function readPointer(bytes: any, offset: any, pointerSize: any, byteReversed: any): number; declare function writePointer(bytes: any, offset: any, value: any, pointerSize: any, byteReversed: any): void; declare function readGrannyString(loaded: any, ref: any, maxLength?: number): string; declare function readStringPointer(loaded: any, pointer: any): string; declare function memberStorageSize(member: any, pointerSize: any): any; declare function readArrayReferences(loaded: any, arrayRef: any, count: any, maxCount: any): any[]; declare function looksText(bytes: any): boolean; declare const SCALAR_SIZES: { 10: number; 11: number; 12: number; 13: number; 14: number; 15: number; 16: number; 17: number; 18: number; 19: number; 20: number; 21: number; }; export {};