diablo2-web/scripts/lib/tbl-writer.ts

60 lines
2.1 KiB
TypeScript

/**
* Minimal `.tbl` writer, shared by the fixture generator and its checker.
*
* Tooling only: the engine reads TBL files, it never writes them. It lives here so
* the fixture generator and the construction check encode a table the same way,
* instead of two copies drifting apart.
*/
/** One entry: a string, or null for an unused index. */
export type TblEntry = string | null
/**
* Encode a classic `.tbl`: crc, entry count, index table, then
* `u16 characterCount` + UTF-16LE strings.
*
* @param entries - the entries, in index order.
* @returns the encoded bytes.
*/
export function encodeTbl(entries: readonly TblEntry[]): Uint8Array {
const headerBytes = 4
const indexBytes = entries.length * 2
const blocks: { index: number; bytes: Uint8Array }[] = []
let cursor = headerBytes + indexBytes
for (const [index, entry] of entries.entries()) {
if (entry === null) continue
// The count is in characters; an astral character is two UTF-16 units.
let characters = 0
for (const character of entry) characters += character.codePointAt(0)! > 0xffff ? 2 : 1
const body = new Uint8Array(characters * 2)
const view = new DataView(body.buffer)
let units = 0
for (const character of entry) {
const code = character.codePointAt(0)!
if (code > 0xffff) {
const adjusted = code - 0x10000
view.setUint16(units * 2, 0xd800 + (adjusted >> 10), true)
view.setUint16((units + 1) * 2, 0xdc00 + (adjusted & 0x3ff), true)
units += 2
} else {
view.setUint16(units * 2, code, true)
units += 1
}
}
const bytes = new Uint8Array([characters & 0xff, (characters >> 8) & 0xff, ...body])
blocks.push({ index, bytes })
cursor += bytes.byteLength
}
const out = new Uint8Array(cursor)
const view = new DataView(out.buffer)
view.setUint16(0, 0x1234, true)
view.setUint16(2, entries.length, true)
let at = headerBytes + indexBytes
for (const block of blocks) {
view.setUint16(headerBytes + block.index * 2, at, true)
out.set(block.bytes, at)
at += block.bytes.byteLength
}
return out
}