refactor(common): consolidate header struct to kmx-plus-file.ts

Consolidate the header structure in KMX+ to make it easier to add a
version value to the header for v19+ KMX+ files; also DRYs out the
header definitions a little.
This commit is contained in:
Marc Durdin 2025-10-17 08:46:53 +02:00
parent 39951f15f6
commit b4bb07ecc4
15 changed files with 89 additions and 65 deletions

View file

@ -61,6 +61,8 @@ export class KMXPlusFileFormat extends KMXFile {
public readonly COMP_PLUS_VARS: any;
public readonly COMP_PLUS_VARS_ITEM: any;
private readonly COMP_PLUS_SectionHeader: any;
constructor() {
super();
// Binary-correct structures matching kmx_plus.h
@ -73,6 +75,14 @@ export class KMXPlusFileFormat extends KMXFile {
const CHAR32 = r.uint32le;
const STR_OR_CHAR32_OR_USET = r.uint32le;
const IDENT = r.uint32le;
// Section header - version dependent
this.COMP_PLUS_SectionHeader = new r.Struct({
ident: IDENT,
size: r.uint32le,
});
// 'sect'
this.COMP_PLUS_SECT_ITEM = new r.Struct({
@ -81,8 +91,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_SECT = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
total: r.uint32le,
count: r.uint32le,
items: new r.Array(this.COMP_PLUS_SECT_ITEM, 'count')
@ -98,8 +107,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_DISP = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
count: r.uint32le,
baseCharacter: CHAR32,
items: new r.Array(this.COMP_PLUS_DISP_ITEM, 'count'),
@ -118,8 +126,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_ELEM = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
count: r.uint32le,
strings: new r.Array(this.COMP_PLUS_ELEM_STRING, 'count')
// + variable subtable: Element data (see KMXPlusBuilder.emitElements())
@ -155,8 +162,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_LAYR = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
listCount: r.uint32le,
layerCount: r.uint32le,
rowCount: r.uint32le,
@ -199,8 +205,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_KEYS = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
keyCount: r.uint32le,
flicksCount: r.uint32le,
flickCount: r.uint32le,
@ -223,8 +228,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_LIST = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
listCount: r.uint32le,
indexCount: r.uint32le,
lists: new r.Array(this.COMP_PLUS_LIST_LIST, 'listCount'),
@ -236,8 +240,7 @@ export class KMXPlusFileFormat extends KMXFile {
this.COMP_PLUS_LOCA_ITEM = r.uint32le; //str
this.COMP_PLUS_LOCA = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
count: r.uint32le,
items: new r.Array(this.COMP_PLUS_LOCA_ITEM, 'count')
});
@ -245,8 +248,7 @@ export class KMXPlusFileFormat extends KMXFile {
// 'meta'
this.COMP_PLUS_META = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
author: STR_REF, //str
conform: STR_REF, //str
layout: STR_REF, //str
@ -270,8 +272,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_STRS = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
count: r.uint32le,
items: new r.Array(this.COMP_PLUS_STRS_ITEM, 'count')
// + variable subtable: String data (see KMXPlusBuilder.emitStrings())
@ -298,8 +299,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_TRAN = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
groupCount: r.uint32le,
transformCount: r.uint32le,
reorderCount: r.uint32le,
@ -322,8 +322,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_USET = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
usetCount: r.uint32le,
rangeCount: r.uint32le,
usets: new r.Array(this.COMP_PLUS_USET_USET, 'usetCount'),
@ -340,8 +339,7 @@ export class KMXPlusFileFormat extends KMXFile {
});
this.COMP_PLUS_VARS = new r.Struct({
ident: IDENT,
size: r.uint32le,
header: this.COMP_PLUS_SectionHeader,
markers: LIST_REF,
varCount: r.uint32le,
varEntries: new r.Array(this.COMP_PLUS_VARS_ITEM, 'varCount'),

View file

@ -30,8 +30,10 @@ export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILD
}
const disp: BUILDER_DISP = {
ident: constants.hex_section_id(constants.section.disp),
size: constants.length_disp + constants.length_disp_item * kmxplus.disp.disps.length,
header: {
ident: constants.hex_section_id(constants.section.disp),
size: constants.length_disp + constants.length_disp_item * kmxplus.disp.disps.length,
},
_offset: 0,
count: kmxplus.disp.disps.length,
baseCharacter: build_strs_index(sect_strs, kmxplus.disp.baseCharacter),

View file

@ -48,8 +48,10 @@ function binaryElemCompare(a: BUILDER_ELEM_STRING, b: BUILDER_ELEM_STRING): numb
export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset: BUILDER_USET): BUILDER_ELEM {
const result: BUILDER_ELEM = {
ident: constants.hex_section_id(constants.section.elem),
size: 0, // finalized below
header: {
ident: constants.hex_section_id(constants.section.elem),
size: 0, // finalized below
},
_offset: 0,
count: source_elem.strings.length,
strings: [], // finalized below
@ -98,7 +100,7 @@ export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS, sect_uset
}
}
result.size = offset;
result.header.size = offset;
return result;
}

View file

@ -76,8 +76,10 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
}
const keys: BUILDER_KEYS = {
ident: constants.hex_section_id(constants.section.keys),
size: 0,
header: {
ident: constants.hex_section_id(constants.section.keys),
size: 0,
},
keyCount: kmxplus.keys.keys.length,
flicksCount: kmxplus.keys.flicks.length,
flickCount: 0,
@ -169,7 +171,7 @@ export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
(constants.length_keys_flick_element * keys.flickCount) +
(constants.length_keys_flick_list * keys.flicksCount) +
(constants.length_keys_kmap * keys.kmapCount);
keys.size = offset;
keys.header.size = offset;
return keys;
}

View file

@ -72,8 +72,10 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
}
const layr: BUILDER_LAYR = {
ident: constants.hex_section_id(constants.section.layr),
size: constants.length_layr,
header: {
ident: constants.hex_section_id(constants.section.layr),
size: constants.length_layr,
},
_offset: 0,
listCount: kmxplus.layr.lists.length,
layerCount: 0, // calculated below
@ -155,6 +157,6 @@ export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_l
(constants.length_layr_entry * layr.layerCount) +
(constants.length_layr_row * layr.rowCount) +
(constants.length_layr_key * layr.keyCount);
layr.size = offset;
layr.header.size = offset;
return layr;
}

View file

@ -44,8 +44,10 @@ export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_
}
const result: BUILDER_LIST = {
ident: constants.hex_section_id(constants.section.list),
size: 0,
header: {
ident: constants.hex_section_id(constants.section.list),
size: 0,
},
_offset: 0,
listCount: source_list.lists.length,
indexCount: 0,
@ -77,7 +79,7 @@ export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_
const offset = constants.length_list +
(constants.length_list_item * result.listCount) +
(constants.length_list_index * result.indexCount);
result.size = offset;
result.header.size = offset;
return result;
}

View file

@ -20,8 +20,10 @@ export interface BUILDER_LOCA extends BUILDER_SECTION {
export function build_loca(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_LOCA {
const loca: BUILDER_LOCA = {
ident: constants.hex_section_id(constants.section.loca),
size: constants.length_loca + constants.length_loca_item * kmxplus.loca.locales.length,
header: {
ident: constants.hex_section_id(constants.section.loca),
size: constants.length_loca + constants.length_loca_item * kmxplus.loca.locales.length,
},
_offset: 0,
count: kmxplus.loca.locales.length,
items: []

View file

@ -25,8 +25,10 @@ export interface BUILDER_META extends BUILDER_SECTION {
export function build_meta(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_META {
return {
ident: constants.hex_section_id(constants.section.meta),
size: constants.length_meta,
header: {
ident: constants.hex_section_id(constants.section.meta),
size: constants.length_meta,
},
_offset: 0,
author: build_strs_index(sect_strs, kmxplus.meta.author),
conform: build_strs_index(sect_strs, kmxplus.meta.conform),

View file

@ -21,8 +21,10 @@ export interface BUILDER_SECT extends BUILDER_SECTION {
export function build_sect(): BUILDER_SECT {
return {
ident: constants.hex_section_id(constants.section.sect),
size: 0, // finalized later
header: {
ident: constants.hex_section_id(constants.section.sect),
size: 0, // finalized later
},
_offset: 0,
total: 0, // finalized later
count: 0, // finalized later

View file

@ -30,8 +30,10 @@ export interface BUILDER_STRS extends BUILDER_SECTION {
export function build_strs(source_strs: Strs): BUILDER_STRS {
const result: BUILDER_STRS = {
ident: constants.hex_section_id(constants.section.strs),
size: 0, // finalized later
header: {
ident: constants.hex_section_id(constants.section.strs),
size: 0, // finalized later
},
_offset: 0,
count: source_strs.strings.length,
items: [], // filled below
@ -46,7 +48,7 @@ export function build_strs(source_strs: Strs): BUILDER_STRS {
item.offset = offset;
offset += item.length * 2 + 2; /* UTF-16 code units + sizeof null terminator */
}
result.size = offset;
result.header.size = offset;
return result;
}

View file

@ -48,8 +48,10 @@ export function build_tran(source_tran: Tran | Bksp, sect_strs: BUILDER_STRS, se
}
const tran: BUILDER_TRAN = {
ident: constants.hex_section_id(source_tran.id),
size: 0, // need to compute total transforms + reorders
header: {
ident: constants.hex_section_id(source_tran.id),
size: 0, // need to compute total transforms + reorders
},
_offset: 0,
groupCount: source_tran.groups.length,
transformCount: 0,
@ -94,7 +96,7 @@ export function build_tran(source_tran: Tran | Bksp, sect_strs: BUILDER_STRS, se
tran.transformCount = tran.transforms.length;
tran.reorderCount = tran.reorders.length;
tran.size = constants.length_tran +
tran.header.size = constants.length_tran +
(constants.length_tran_group * source_tran.groups.length) +
(constants.length_tran_transform * tran.transforms.length) +
(constants.length_tran_reorder * tran.reorders.length);

View file

@ -57,10 +57,12 @@ export function build_uset(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS ) : BUI
});
const uset: BUILDER_USET = {
ident: constants.hex_section_id(constants.section.uset),
size: constants.length_uset +
(constants.length_uset_uset * usets.length) +
(constants.length_uset_range * ranges.length),
header: {
ident: constants.hex_section_id(constants.section.uset),
size: constants.length_uset +
(constants.length_uset_uset * usets.length) +
(constants.length_uset_range * ranges.length),
},
usetCount: usets.length,
rangeCount: ranges.length,
usets,

View file

@ -46,9 +46,11 @@ export function build_vars(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_e
});
const vars: BUILDER_VARS = {
ident: constants.hex_section_id(constants.section.vars),
size: constants.length_vars +
(constants.length_vars_item * kmxplus.vars.totalCount()),
header: {
ident: constants.hex_section_id(constants.section.vars),
size: constants.length_vars +
(constants.length_vars_item * kmxplus.vars.totalCount()),
},
_offset: 0,
markers: build_list_index(sect_list, kmxplus.vars.markers),
varCount: kmxplus.vars.totalCount(),

View file

@ -4,8 +4,10 @@ export type BUILDER_IDENT = number;
export type BUILDER_U32CHAR = number;
export interface BUILDER_SECTION {
ident: BUILDER_IDENT;
size: number;
header: {
ident: BUILDER_IDENT;
size: number;
};
_offset: number; // used only for building the output
}
;

View file

@ -115,9 +115,9 @@ export default class KMXPlusBuilder {
}
});
this.sect.sect.size = constants.length_sect + constants.length_sect_item * this.sect.sect.count;
this.sect.sect.header.size = constants.length_sect + constants.length_sect_item * this.sect.sect.count;
let offset = this.sect.sect.size;
let offset = this.sect.sect.header.size;
// Note: in order! Everyone's here except 'sect' which is at offset 0
offset = this.finalize_sect_item(this.sect.bksp, offset);
offset = this.finalize_sect_item(this.sect.disp, offset);
@ -141,16 +141,16 @@ export default class KMXPlusBuilder {
return offset;
}
sect._offset = offset;
this.sect.sect.items.push({sect: sect.ident, offset: offset});
return offset + sect.size;
this.sect.sect.items.push({sect: sect.header.ident, offset: offset});
return offset + sect.header.size;
}
private emitSection(file: Uint8Array, comp: any, sect: BUILDER_SECTION) {
if(sect) {
const buf = comp.toBuffer(sect);
if (buf.length > sect.size) {
if (buf.length > sect.header.size) {
// buf.length may be < sect.size if there is a variable part (i.e. elem)
throw new RangeError(`Internal Error: Section ${constants.str_section_id(sect.ident)} claimed size ${sect.size} but produced buffer of size ${buf.length}.`);
throw new RangeError(`Internal Error: Section ${constants.str_section_id(sect.header.ident)} claimed size ${sect.header.size} but produced buffer of size ${buf.length}.`);
}
file.set(buf, sect._offset);
}