parsing kaitai ksy into struct-fu for jo
by arian_
HTML
<script type="importmap">
{
"imports": {
"js-yaml": "https://cdn.jsdelivr.net/npm/[email protected]/+esm",
"./lib.js": "https://cdn.jsdelivr.net/gh/ariankordi/struct-fu@681ffc3d8592161b1e286392b4e2e63686319474/lib.js"
}
}
</script>
<!-- ./lib.js = struct-fu -->
<!-- Global error display block. -->
<div id="global-error-box" style="display: none; background: #ffeeee; color: #000; border: 2px solid red; padding: 1em; margin: 1em; font-family: monospace;">
<div style="font-weight: bold; font-size: 1.2em;"><span id="global-error-text-error">⚠️ JS Error:</span><span id="global-error-text-promise-rejection">⚠️ JS Promise Rejection:</span></div>
<div id="global-error-message" style="white-space: pre-wrap; margin-top: 0.5em;"></div>
<button id="global-error-hide-button" style="margin-top: 1em;">Close</button>
</div>
<form id="ksy-form">
<button id="ksy-button">parse</button>
<br>
<br>
<textarea name="ksy-text"
style="margin: 0px; height: 400px; width: 400px;">
meta:
id: c_f_li_mii_data_packet
application: |
Mii data format used on 3DS, Wii U, Miitomo
versions < 2.3.0, and on Switch inside amiibo data.
Fields marked as "padding" and "reserved" are always zero.
file-extension:
- cfsd
- ffsd
# Unofficial:
- 3dsmii
#- cfcd
#- ffcd
xref:
struct_names: |
CFLiMiiDataPacket/nnmiiStoreData,
FFLStoreData/AFLStoreData,
nn::mii::Ver3StoreData
endian: le
bit-endian: le
# potential todos!!!!!:
# * what is my strategy for kaitai in general?
# - encoding is a no-go(?)
# * unless i want to leverage debug mode or custom kaitai parser?
# - you MUST keep everything top-level, no nesting/unions
# * create id as the exception? but has .data property
# - will it be an alternative to structs in js or dwarf or something?
# * add `valid` range fields (need to somehow ignore later)?
seq:
- id:...
JavaScript
// @ts-check
/* eslint @stylistic/indent: ['error', 2] -- Define indent rules. */
import * as _Import from './lib.js';
/** @typedef {import('./lib')} _ */
globalThis._ = /** @type {_} */ (/** @type {*} */ (globalThis)._);
let _ = globalThis._;
_ = !_ ? _Import : _;
import * as YAML from 'js-yaml';
/**
* U8 -> Base64
* @param {Array<number>|Uint8Array} bytes - Input data to encode.
* @returns {string} Base64 representation of `buffer`.
*/
const bytesToBase64 = bytes =>
// fromCharCode should be compatible with Uint8Array, but its param type is number[].
btoa(String.fromCharCode.apply(null, /** @type {Array<number>} */ (bytes)));
/* ksy-to-structfu.js
*
* Convert a parsed KSY object (via js-yaml) into a struct-fu definition.
* No `eval`, no file emission – we directly build Field objects.
*
* ./struct-fu/lib.js must already be loaded (CommonJS or ESM).
*/
//#region — public API ---------------------------------------------------------
/**
* Build a struct-fu definition from a KSY YAML string.
* @param {string} ksyYaml Raw YAML text.
* @returns {_.StructInstance<any>} The top-level struct-fu struct.
*/
export function buildStructFromKsy(ksyYaml) {
const ksyRoot = YAML.load(ksyYaml, { json: true });
const typeCache = new Map(); // name → struct instance
return makeType(ksyRoot,
lowerCamelCase(ksyRoot.meta?.id ?? 'root'), typeCache, {
byteEndian : ksyRoot.meta?.endian ?? 'be',
bitEndian : ksyRoot.meta?.['bit-endian'] ?? 'be'
});
}
//#endregion ------------------------------------------------------------------
//#region — helpers -----------------------------------------------------------
/**
* Converts snake_case to lowerCamelCase.
* Mimics Kaitai's doWord().
* @param {string} s
* @returns {string}
*/
function lowerCamelCase(s) {
if (s.startsWith("_")) return "_" + lowerCamelCase(s.substring(1));
const [first, ...rest] =...