// @ts-check import * as THREE from 'three'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; // import SPECTOR from 'https://cdn.jsdelivr.net/npm/spectorjs@0.9.30/+esm'; // const spector = new SPECTOR.Spector(); import { KaitaiStream } from 'kaitai-struct'; import * as RFLResourceImport from './RFLResource.js'; // Import as UMD or ESM. let RFLResource = /** @type {*} */ (globalThis).RFLResource; RFLResource = (!RFLResource) ? RFLResourceImport : RFLResource; /** * {@link https://github.com/SMGCommunity/Petari/blob/98fe1905624e3b499869eee6b74b12fcaf94f38a/libs/RVLFaceLib/include/RFLi_Types.h#L23C1-L43C13} * @enum {number} */ const RFLiArcID = { Beard: 0, Eye: 1, Eyebrow: 2, Faceline: 3, FaceTex: 4, ForeHead: 5, Glass: 6, GlassTex: 7, Hair: 8, Mask: 9, Mole: 10, Mouth: 11, Mustache: 12, Nose: 13, Nline: 14, NlineTex: 15, Cap: 16, CapTex: 17, Max: 18 }; /** * Gets an offset of an individual file/element/part within the archive. * @param {RFLResource} res * @param {RFLiArcID} arcID * @param {number} index * @returns {number} Absolute offset of file within the archive. */ function getOffsetArchiveFile(res, arcID, index) { /** Initial position. */ const _pos = res._io.pos; /** Offsets begin at this location. */ const initialOffset = 8; /** Offset of the beginning of the archive. */ const archiveOffset = res.offsets[arcID]; console.assert(archiveOffset); res._io.seek(/* initialOffset + */archiveOffset); // Seek and read archive. /** Construct archive to get its offsets. */ const archive = new RFLResource.RFLiArchive(res._io, res, res._root); console.assert(archive.num > 0); /** Location after the archive's file offsets. Value = num files * sizeof(u32). */ const filesOffset = archive.num * 4; /** Individual file's offset. */ const fileOffset = archive.offset[index]; console.assert(typeof fileOffset === 'number'); /** Total offset of file. */ const finalOffset = initialOffset + archiveOffset + filesOffset + fileOffset; res._io.seek(_pos); // Seek to initial position before returning. return finalOffset; } /** * @param {RFLResource} res * @param {RFLiArcID} arcID * @param {number} index * @returns {typeof RFLResource.RFLiShape} * @todo Makes use of types VIA KAITAI: RFLResource, RFLiShape (custom) */ function getResourceShape(res, arcID, index) { /** Initial position. */ const _pos = res._io.pos; const finalOffset = getOffsetArchiveFile(res, arcID, index); /** Seek to the offset of the file. */ res._io.seek(finalOffset); /** Read the shape at the offset. */ const shape = new RFLResource.RFLiShape(res._io, this, res._root); res._io.seek(_pos); // Seek to initial position before returning. return shape; } // ==> LOADING <== /** @type {Object} */ const RFLiArcIDShapesAndNames = { [RFLiArcID.Faceline]: 'Faceline', [RFLiArcID.ForeHead]: 'ForeHead', [RFLiArcID.Glass]: 'Glass', [RFLiArcID.Hair]: 'Hair', [RFLiArcID.Mask]: 'Mask', [RFLiArcID.Nose]: 'Nose', [RFLiArcID.Nline]: 'Nline', [RFLiArcID.Cap]: 'Cap' }; /** List of RFLiArcIDs that contain shapes. */ const shapeArchiveIds = Object.keys(RFLiArcIDShapesAndNames) .map(key => Number(key)); /** * Helper: returns how many files are in archive `arcID`. * @param {RFLResource} resource * @param {number} arcID * @returns {number} */ function getFileCountInArchive(resource, arcID) { const archiveOffset = resource.offsets[arcID]; // Seek to archive header const savedPosition = resource._io.pos; resource._io.seek(archiveOffset); const archiveHeader = new RFLResource.RFLiArchive(resource._io, resource, resource._root); resource._io.seek(savedPosition); return archiveHeader.num; } /** * Helper: returns the byte size of file `fileIndex` within archive `arcID`. * @param {RFLResource} resource * @param {number} arcID * @param {number} fileIndex * @returns {number} */ function getFileSizeInArchive(resource, arcID, fileIndex) { const archiveOffset = resource.offsets[arcID]; // We need the offset of this file and the offset of the next file const savedPosition = resource._io.pos; resource._io.seek(archiveOffset); const archiveHeader = new RFLResource.RFLiArchive(resource._io, resource, resource._root); const offsetsArray = archiveHeader.offset; // length = num+1 let startOffset = offsetsArray[fileIndex]; let endOffset; if (fileIndex < offsetsArray.length - 1) { endOffset = offsetsArray[fileIndex + 1]; } else { // Last file: size is archiveHeader.maxsize or subtract from fileSize endOffset = offsetsArray[fileIndex] + archiveHeader.maxsize; } resource._io.seek(savedPosition); return endOffset - startOffset; } /** * Populate the