UNPKG

miijs

Version:

Read, Edit, Write, and make Special Miis from a Wiimote binary file or 3DS QR Code to a binary file or QR code

1,567 lines (1,441 loc) 183 kB
// @ts-check 'use strict'; /*! * Bindings for FFL, a Mii renderer, in JavaScript. * https://github.com/ariankordi/FFL.js * @author Arian Kordi <https://github.com/ariankordi> */ // ------------------ ESM imports, uncomment if you use ESM ------------------ /* // Also see the bottom of the script for corresponding exports. import * as THREE from 'three'; // import * as THREE from 'https://cdn.jsdelivr.net/npm/three@0.167.0/+esm'; import * as _Import from './struct-fu.js'; */ // Hack to get library globals recognized throughout the file (remove for ESM). /** // * @ typedef {import('./struct-fu')} _ // * @ typedef {import('three')} THREE */ /* eslint-disable no-self-assign -- Get TypeScript to identify global imports. */ const _=require("./struct-fu.js"); const THREE=require("three"); globalThis._ = /** @type {_} */ (/** @type {*} */ (globalThis)._); globalThis.THREE = /** @type {THREE} */ (/** @type {*} */ (globalThis).THREE); // NOTeslint-disable-next-line @stylistic/max-statements-per-line -- Hack to use either UMD or browser ESM import. // let _ = globalThis._; _ = (!_) ? _Import : _; // Uncomment for ESM /* eslint-enable no-self-assign -- Get TypeScript to identify global imports. */ /* globals _ THREE -- Global dependencies. */ // // --------------------------------------------------------------------- // // Emscripten Types // // --------------------------------------------------------------------- // TODO PATH: src/ModuleType.js /** * Emscripten "Module" type. * https://github.com/DefinitelyTyped/DefinitelyTyped/blob/c03bddd4d3c7774d00fa256a9e165d68c7534ccc/types/emscripten/index.d.ts#L26 * @typedef {Object} Module * @property {function(): void} onRuntimeInitialized * @property {function(object): void} destroy * @property {boolean|null} calledRun * // USE_TYPED_ARRAYS == 2 * @property {Int8Array} HEAP8 * @property {Uint8Array} HEAPU8 * @property {Uint16Array} HEAPU16 * @property {Uint32Array} HEAPU32 * @property {Float32Array} HEAPF32 * Runtime methods: * @property {function(number): number} _malloc * @property {function(number): void} _free * @property {function((...args: *[]) => *, string=): number} addFunction * @property {function(number): void} removeFunction * * ------------------------------- FFL Bindings ------------------------------- * @property {function(number, number, number, number): *} _FFLInitCharModelCPUStepWithCallback * @property {function(number, number, number): *} _FFLInitCharModelCPUStep * @property {function(number): *} _FFLDeleteCharModel * @property {function(number): *} _FFLGetDrawParamOpaFaceline * @property {function(number): *} _FFLGetDrawParamOpaBeard * @property {function(number): *} _FFLGetDrawParamOpaNose * @property {function(number): *} _FFLGetDrawParamOpaForehead * @property {function(number): *} _FFLGetDrawParamOpaHair * @property {function(number): *} _FFLGetDrawParamOpaCap * @property {function(number): *} _FFLGetDrawParamXluMask * @property {function(number): *} _FFLGetDrawParamXluNoseLine * @property {function(number): *} _FFLGetDrawParamXluGlass * @property {function(number, number): *} _FFLSetExpression * @property {function(number): *} _FFLGetExpression * @property {function(number, number): *} _FFLSetViewModelType * @property {function(number, number): *} _FFLGetBoundingBox * @property {function(number, number): *} _FFLIsAvailableExpression * @property {function(number, number): *} _FFLSetCoordinate * @property {function(number): *} _FFLSetScale * @property {function(number, number, number, number): *} _FFLiGetRandomCharInfo * @property {function(number, number): *} _FFLpGetStoreDataFromCharInfo * @property {function(number, number): *} _FFLpGetCharInfoFromStoreData * @property {function(number, number): *} _FFLpGetCharInfoFromMiiDataOfficialRFL * @property {function(number, number, number, number, boolean): *} _FFLGetAdditionalInfo * @property {function(number, number): *} _FFLInitRes * @property {function(): *} _FFLInitResGPUStep * @property {function(): *} _FFLExit * @property {function(): *} _FFLIsAvailable * @property {function(number, number): *} _FFLGetFavoriteColor * @property {function(number): *} _FFLSetLinearGammaMode * @property {function(number, number): *} _FFLGetFacelineColor * @property {function(boolean): *} _FFLSetTextureFlipY * @property {function(boolean): *} _FFLSetNormalIsSnorm8_8_8_8 * @property {function(boolean): *} _FFLSetFrontCullForFlipX * @property {function(number): *} _FFLSetTextureCallback * @property {function(number): *} _FFLiDeleteTextureTempObject * @property {function(number, number, number): *} _FFLiDeleteTempObjectMaskTextures * @property {function(number, number, number): *} _FFLiDeleteTempObjectFacelineTexture * @property {function(number): *} _FFLiiGetEyeRotateOffset * @property {function(number): *} _FFLiiGetEyebrowRotateOffset * @property {function(number): *} _FFLiInvalidateTempObjectFacelineTexture * @property {function(number): *} _FFLiInvalidatePartsTextures * @property {function(number): *} _FFLiInvalidateRawMask * @property {function(number, boolean): *} _FFLiVerifyCharInfoWithReason * @property {function(): void} _exit */ // // --------------------------------------------------------------------- // // Enum Definitions // // --------------------------------------------------------------------- // TODO PATH: src/Enums.js /** * Uses FFL decomp enum rather than real FFL enum. * Reference: https://github.com/aboood40091/ffl/blob/master/include/nn/ffl/FFLResult.h * @enum {number} */ const FFLResult = { OK: 0, ERROR: 1, HDB_EMPTY: 2, FILE_INVALID: 3, MANAGER_NOT_CONSTRUCT: 4, FILE_LOAD_ERROR: 5, // : 6, FILE_SAVE_ERROR: 7, // : 8, RES_FS_ERROR: 9, ODB_EMPTY: 10, // : 11, OUT_OF_MEMORY: 12, // : 13, // : 14, // : 15, // : 16, UNKNOWN_17: 17, FS_ERROR: 18, FS_NOT_FOUND: 19, MAX: 20 }; /** @enum {number} */ const FFLiShapeType = { OPA_BEARD: 0, OPA_FACELINE: 1, OPA_HAIR_NORMAL: 2, OPA_FOREHEAD_NORMAL: 3, XLU_MASK: 4, XLU_NOSELINE: 5, OPA_NOSE: 6, OPA_HAT_NORMAL: 7, XLU_GLASS: 8, OPA_HAIR_CAP: 9, OPA_FOREHEAD_CAP: 10, OPA_HAT_CAP: 11, MAX: 12 }; /** @enum {number} */ const FFLAttributeBufferType = { POSITION: 0, TEXCOORD: 1, NORMAL: 2, TANGENT: 3, COLOR: 4, MAX: 5 }; /** @enum {number} */ const FFLCullMode = { NONE: 0, BACK: 1, FRONT: 2, MAX: 3 }; /** @enum {number} */ const FFLModulateMode = { /** No Texture, Has Color (R) */ CONSTANT: 0, /** Has Texture, No Color */ TEXTURE_DIRECT: 1, /** Has Texture, Has Color (R + G + B) */ RGB_LAYERED: 2, /** Has Texture, Has Color (R) */ ALPHA: 3, /** Has Texture, Has Color (R) */ LUMINANCE_ALPHA: 4, /** Has Texture, Has Color (R) */ ALPHA_OPA: 5 }; /** @enum {number} */ const FFLModulateType = { SHAPE_FACELINE: 0, SHAPE_BEARD: 1, SHAPE_NOSE: 2, SHAPE_FOREHEAD: 3, SHAPE_HAIR: 4, SHAPE_CAP: 5, SHAPE_MASK: 6, SHAPE_NOSELINE: 7, SHAPE_GLASS: 8, MUSTACHE: 9, MOUTH: 10, EYEBROW: 11, EYE: 12, MOLE: 13, FACE_MAKE: 14, FACE_LINE: 15, FACE_BEARD: 16, FILL: 17, SHAPE_MAX: 9 }; /** @enum {number} */ const FFLResourceType = { MIDDLE: 0, HIGH: 1, MAX: 2 }; /** * Reference: https://github.com/ariankordi/ffl/blob/nsmbu-win-port-linux64/include/nn/ffl/FFLExpression.h * @enum {number} */ const FFLExpression = { NORMAL: 0, SMILE: 1, ANGER: 2, /** Primary name for expression 3. */ SORROW: 3, PUZZLED: 3, /** Primary name for expression 4. */ SURPRISE: 4, SURPRISED: 4, BLINK: 5, OPEN_MOUTH: 6, /** Primary name for expression 7. */ SMILE_OPEN_MOUTH: 7, HAPPY: 7, ANGER_OPEN_MOUTH: 8, SORROW_OPEN_MOUTH: 9, SURPRISE_OPEN_MOUTH: 10, BLINK_OPEN_MOUTH: 11, WINK_LEFT: 12, WINK_RIGHT: 13, WINK_LEFT_OPEN_MOUTH: 14, WINK_RIGHT_OPEN_MOUTH: 15, /** Primary name for expression 16. */ LIKE_WINK_LEFT: 16, LIKE: 16, LIKE_WINK_RIGHT: 17, FRUSTRATED: 18, // Additional expressions from AFL. // Enum names are completely made up. BORED: 19, BORED_OPEN_MOUTH: 20, SIGH_MOUTH_STRAIGHT: 21, SIGH: 22, DISGUSTED_MOUTH_STRAIGHT: 23, DISGUSTED: 24, LOVE: 25, LOVE_OPEN_MOUTH: 26, DETERMINED_MOUTH_STRAIGHT: 27, DETERMINED: 28, CRY_MOUTH_STRAIGHT: 29, CRY: 30, BIG_SMILE_MOUTH_STRAIGHT: 31, BIG_SMILE: 32, CHEEKY: 33, CHEEKY_DUPLICATE: 34, JOJO_EYES_FUNNY_MOUTH: 35, JOJO_EYES_FUNNY_MOUTH_OPEN: 36, SMUG: 37, SMUG_OPEN_MOUTH: 38, RESOLVE: 39, RESOLVE_OPEN_MOUTH: 40, UNBELIEVABLE: 41, UNBELIEVABLE_DUPLICATE: 42, CUNNING: 43, CUNNING_DUPLICATE: 44, RASPBERRY: 45, RASPBERRY_DUPLICATE: 46, INNOCENT: 47, INNOCENT_DUPLICATE: 48, CAT: 49, CAT_DUPLICATE: 50, DOG: 51, DOG_DUPLICATE: 52, TASTY: 53, TASTY_DUPLICATE: 54, MONEY_MOUTH_STRAIGHT: 55, MONEY: 56, SPIRAL_MOUTH_STRAIGHT: 57, CONFUSED: 58, CHEERFUL_MOUTH_STRAIGHT: 59, CHEERFUL: 60, BLANK_61: 61, BLANK_62: 62, GRUMBLE_MOUTH_STRAIGHT: 63, GRUMBLE: 64, MOVED_MOUTH_STRAIGHT: 65, MOVED: 66, SINGING_MOUTH_SMALL: 67, SINGING: 68, STUNNED: 69, MAX: 70 }; /** * Model flags modify how the head model is created. These are * used in the `modelFlag` property of {@link FFLCharModelDesc}. * @enum {number} */ const FFLModelFlag = { /** Default model setting. */ NORMAL: 1 << 0, /** Uses a variant of hair designed for hats. */ HAT: 1 << 1, /** Discards hair from the model, used for helmets and similar headwear. */ FACE_ONLY: 1 << 2, /** Limits Z depth on the nose, useful for helmets and similar headwear. */ FLATTEN_NOSE: 1 << 3, /** Enables the model's expression flag to use expressions beyond 32. */ NEW_EXPRESSIONS: 1 << 4, /** * This flag only generates new textures when initializing a CharModel * but does not initialize shapes. * **Note:** This means you cannot use DrawOpa/Xlu when this is set. */ NEW_MASK_ONLY: 1 << 5 }; // // --------------------------------------------------------------------- // // Struct Definitions (struct-fu) // // --------------------------------------------------------------------- // TODO PATH: src/Structs.js // Mostly leading up to FFLDrawParam. /** Mirror for {@link _.uint32le} to indicate a pointer. */ const _uintptr = _.uint32le; /** * @typedef {Object} FFLAttributeBuffer * @property {number} size * @property {number} stride * @property {number} ptr */ /** @type {import('./struct-fu').StructInstance<FFLAttributeBuffer>} */ const FFLAttributeBuffer = _.struct([ _.uint32le('size'), _.uint32le('stride'), _uintptr('ptr') ]); /** * @typedef {Object} FFLAttributeBufferParam * @property {Array<FFLAttributeBuffer>} attributeBuffers */ /** @type {import('./struct-fu').StructInstance<FFLAttributeBufferParam>} */ const FFLAttributeBufferParam = _.struct([ _.struct('attributeBuffers', [FFLAttributeBuffer], 5) ]); /** * @typedef {Object} FFLPrimitiveParam * @property {number} primitiveType * @property {number} indexCount * @property {number} pAdjustMatrix * @property {number} pIndexBuffer */ /** @type {import('./struct-fu').StructInstance<FFLPrimitiveParam>} */ const FFLPrimitiveParam = _.struct([ _.uint32le('primitiveType'), _.uint32le('indexCount'), _uintptr('pAdjustMatrix'), // TODO: Not uintptr in 64-bit. _uintptr('pIndexBuffer') ]); /** * @typedef {Object} FFLColor * @property {number} r * @property {number} g * @property {number} b * @property {number} a */ /** @type {import('./struct-fu').StructInstance<FFLColor>} */ const FFLColor = _.struct([ _.float32le('r'), _.float32le('g'), _.float32le('b'), _.float32le('a') ]); /** * @typedef {Object} FFLVec3 * @property {number} x * @property {number} y * @property {number} z */ /** @type {import('./struct-fu').StructInstance<FFLVec3>} */ const FFLVec3 = _.struct([ _.float32le('x'), _.float32le('y'), _.float32le('z') ]); /** * @typedef {Object} FFLModulateParam * @property {FFLModulateMode} mode * @property {FFLModulateType} type * @property {number} pColorR - Pointer to FFLColor * @property {number} pColorG - Pointer to FFLColor * @property {number} pColorB - Pointer to FFLColor * @property {number} pTexture2D */ /** @type {import('./struct-fu').StructInstance<FFLModulateParam>} */ const FFLModulateParam = _.struct([ _.uint32le('mode'), // enum FFLModulateMode _.uint32le('type'), // enum FFLModulateType _uintptr('pColorR'), _uintptr('pColorG'), _uintptr('pColorB'), _uintptr('pTexture2D') ]); /** * @typedef {Object} FFLDrawParam * @property {FFLAttributeBufferParam} attributeBufferParam * @property {FFLModulateParam} modulateParam * @property {FFLCullMode} cullMode * @property {FFLPrimitiveParam} primitiveParam */ /** @type {import('./struct-fu').StructInstance<FFLDrawParam>} */ const FFLDrawParam = _.struct([ _.struct('attributeBufferParam', [FFLAttributeBufferParam]), _.struct('modulateParam', [FFLModulateParam]), _.uint32le('cullMode'), _.struct('primitiveParam', [FFLPrimitiveParam]) ]); // ---------------------- Begin FFLiCharInfo Definition ---------------------- // TODO PATH: src/StructFFLiCharModel.js /** * @typedef {Object} FFLCreateID * @property {Array<number>} data */ /** @type {import('./struct-fu').StructInstance<FFLCreateID>} */ const FFLCreateID = _.struct([ _.uint8('data', 10) ]); /** * @typedef {Object} FFLiCharInfo_faceline * @property {number} type * @property {number} color * @property {number} texture * @property {number} make */ /** * @typedef {Object} FFLiCharInfo_hair * @property {number} type * @property {number} color * @property {number} flip */ /** * @typedef {Object} FFLiCharInfo_eye * @property {number} type * @property {number} color * @property {number} scale * @property {number} aspect * @property {number} rotate * @property {number} x * @property {number} y */ /** * @typedef {Object} FFLiCharInfo_eyebrow * @property {number} type * @property {number} color * @property {number} scale * @property {number} aspect * @property {number} rotate * @property {number} x * @property {number} y */ /** * @typedef {Object} FFLiCharInfo_nose * @property {number} type * @property {number} scale * @property {number} y */ /** * @typedef {Object} FFLiCharInfo_mouth * @property {number} type * @property {number} color * @property {number} scale * @property {number} aspect * @property {number} y */ /** * @typedef {Object} FFLiCharInfo_beard * @property {number} mustache * @property {number} type * @property {number} color * @property {number} scale * @property {number} y */ /** * @typedef {Object} FFLiCharInfo_glass * @property {number} type * @property {number} color * @property {number} scale * @property {number} y */ /** * @typedef {Object} FFLiCharInfo_mole * @property {number} type * @property {number} scale * @property {number} x * @property {number} y */ /** * @typedef {Object} FFLiCharInfo_body * @property {number} height * @property {number} build */ /** * @typedef {Object} FFLiCharInfo_personal * @property {string} name * @property {string} creator * @property {number} gender * @property {number} birthMonth * @property {number} birthDay * @property {number} favoriteColor * @property {number} favorite * @property {number} copyable * @property {number} ngWord * @property {number} localonly * @property {number} regionMove * @property {number} fontRegion * @property {number} roomIndex * @property {number} positionInRoom * @property {number} birthPlatform */ /** * @typedef {Object} FFLiCharInfo * @property {number} miiVersion * @property {FFLiCharInfo_faceline} faceline * @property {FFLiCharInfo_hair} hair * @property {FFLiCharInfo_eye} eye * @property {FFLiCharInfo_eyebrow} eyebrow * @property {FFLiCharInfo_nose} nose * @property {FFLiCharInfo_mouth} mouth * @property {FFLiCharInfo_beard} beard * @property {FFLiCharInfo_glass} glass * @property {FFLiCharInfo_mole} mole * @property {FFLiCharInfo_body} body * @property {FFLiCharInfo_personal} personal * @property {FFLCreateID} createID * @property {number} padding_0 * @property {number} authorType * @property {Array<number>} authorID */ /** @type {import('./struct-fu').StructInstance<FFLiCharInfo>} */ const FFLiCharInfo = _.struct([ _.int32le('miiVersion'), _.struct('faceline', [_.int32le('type'), _.int32le('color'), _.int32le('texture'), _.int32le('make')]), _.struct('hair', [_.int32le('type'), _.int32le('color'), _.int32le('flip')]), _.struct('eye', [_.int32le('type'), _.int32le('color'), _.int32le('scale'), _.int32le('aspect'), _.int32le('rotate'), _.int32le('x'), _.int32le('y')]), _.struct('eyebrow', [_.int32le('type'), _.int32le('color'), _.int32le('scale'), _.int32le('aspect'), _.int32le('rotate'), _.int32le('x'), _.int32le('y')]), _.struct('nose', [_.int32le('type'), _.int32le('scale'), _.int32le('y')]), _.struct('mouth', [_.int32le('type'), _.int32le('color'), _.int32le('scale'), _.int32le('aspect'), _.int32le('y')]), _.struct('beard', [_.int32le('mustache'), _.int32le('type'), _.int32le('color'), _.int32le('scale'), _.int32le('y')]), _.struct('glass', [_.int32le('type'), _.int32le('color'), _.int32le('scale'), _.int32le('y')]), _.struct('mole', [_.int32le('type'), _.int32le('scale'), _.int32le('x'), _.int32le('y')]), _.struct('body', [_.int32le('height'), _.int32le('build')]), _.struct('personal', [ _.char16le('name', 22), _.char16le('creator', 22), _.int32le('gender'), _.int32le('birthMonth'), _.int32le('birthDay'), _.int32le('favoriteColor'), _.uint8('favorite'), _.uint8('copyable'), _.uint8('ngWord'), _.uint8('localonly'), _.int32le('regionMove'), _.int32le('fontRegion'), _.int32le('roomIndex'), _.int32le('positionInRoom'), _.int32le('birthPlatform') ]), _.struct('createID', [FFLCreateID]), _.uint16le('padding_0'), _.int32le('authorType'), _.uint8('authorID', 8) // stub ]); /** * Size of FFLStoreData, a structure not included currently. * @public */ /** sizeof(FFLStoreData) */ const FFLStoreData_size = 96; // ---------------------- Common Color Mask Definitions ---------------------- /** @package */ const commonColorEnableMask = (1 << 31); /** * Applies (unofficial) mask: FFLI_NN_MII_COMMON_COLOR_ENABLE_MASK * to a common color index to indicate to FFL which color table it should use. * @param {number} color - The color index to flag. * @returns {number} The flagged color index to use in FFLiCharinfo. */ const commonColorMask = color => color | commonColorEnableMask; /** * Removes (unofficial) mask: FFLI_NN_MII_COMMON_COLOR_ENABLE_MASK * to a common color index to reveal the original common color index. * @param {number} color - The flagged color index. * @returns {number} The original color index before flagging. */ const commonColorUnmask = color => (color & ~commonColorEnableMask) === 0 // Only unmask color if the mask is enabled. ? color : color & ~commonColorEnableMask; // --------------------- Begin FFLiCharModel Definitions --------------------- /** * @typedef {Object} FFLAdditionalInfo * @property {string} name * @property {string} creator * @property {FFLCreateID} createID * @property {FFLColor} skinColor * @property {number} flags * @property {number} facelineType * @property {number} hairType */ /** @type {import('./struct-fu').StructInstance<FFLAdditionalInfo>} */ const FFLAdditionalInfo = _.struct([ _.char16le('name', 22), _.char16le('creator', 22), _.struct('createID', [FFLCreateID]), _.byte('_padding0', 2), // alignment _.struct('skinColor', [FFLColor]), _.uint32le('flags'), // _.ubitLE('hairFlip', 1), // _.ubitLE('fontRegion', 2), // _.ubitLE('ngWord', 1), // _.ubitLE('build', 7), // _.ubitLE('height', 7), // _.ubitLE('favoriteColor', 4), // _.ubitLE('birthDay', 5), // _.ubitLE('birthMonth', 4), // _.ubitLE('gender', 1), _.uint8('facelineType'), _.uint8('hairType'), _.byte('_padding1', 2) // alignment ]); const FFLiRenderTexture = _.struct([ // STUB: four pointers in one field _uintptr('pTexture2DRenderBufferColorTargetDepthTarget', 4) ]); /** * @typedef {Object} FFLiFacelineTextureTempObject * @property {number} pTextureFaceLine * @property {FFLDrawParam} drawParamFaceLine * @property {number} pTextureFaceMake * @property {FFLDrawParam} drawParamFaceMake * @property {number} pTextureFaceBeard * @property {FFLDrawParam} drawParamFaceBeard * @property {Array<number>} pRenderTextureCompressorParam */ /** @type {import('./struct-fu').StructInstance<FFLiFacelineTextureTempObject>} */ const FFLiFacelineTextureTempObject = _.struct([ _uintptr('pTextureFaceLine'), _.struct('drawParamFaceLine', [FFLDrawParam]), _uintptr('pTextureFaceMake'), _.struct('drawParamFaceMake', [FFLDrawParam]), _uintptr('pTextureFaceBeard'), _.struct('drawParamFaceBeard', [FFLDrawParam]), _uintptr('pRenderTextureCompressorParam', 2) // stub ]); /** * @typedef {Object} FFLiRawMaskDrawParam * @property {Array<FFLDrawParam>} drawParamRawMaskPartsEye - 2 * @property {Array<FFLDrawParam>} drawParamRawMaskPartsEyebrow - 2 * @property {FFLDrawParam} drawParamRawMaskPartsMouth * @property {Array<FFLDrawParam>} drawParamRawMaskPartsMustache - 2 * @property {FFLDrawParam} drawParamRawMaskPartsMole * @property {FFLDrawParam} drawParamRawMaskPartsFill */ /** @type {import('./struct-fu').StructInstance<FFLiRawMaskDrawParam>} */ const FFLiRawMaskDrawParam = _.struct([ _.struct('drawParamRawMaskPartsEye', [FFLDrawParam], 2), _.struct('drawParamRawMaskPartsEyebrow', [FFLDrawParam], 2), _.struct('drawParamRawMaskPartsMouth', [FFLDrawParam]), _.struct('drawParamRawMaskPartsMustache', [FFLDrawParam], 2), _.struct('drawParamRawMaskPartsMole', [FFLDrawParam]), _.struct('drawParamRawMaskPartsFill', [FFLDrawParam]) ]); /** * @typedef {Object} FFLiMaskTexturesTempObject * @property {Array<number>} partsTextures * @property {Array<number>} pRawMaskDrawParam * @property {Uint8Array} _remaining */ /** @type {import('./struct-fu').StructInstance<FFLiMaskTexturesTempObject>} */ const FFLiMaskTexturesTempObject = _.struct([ _.uint8('partsTextures', 0x154), _uintptr('pRawMaskDrawParam', FFLExpression.MAX), _.byte('_remaining', 0x388 - 620) // stub ]); /** * @typedef {Object} FFLiTextureTempObject * @property {FFLiMaskTexturesTempObject} maskTextures * @property {FFLiFacelineTextureTempObject} facelineTexture */ /** @type {import('./struct-fu').StructInstance<FFLiTextureTempObject>} */ const FFLiTextureTempObject = _.struct([ _.struct('maskTextures', [FFLiMaskTexturesTempObject]), _.struct('facelineTexture', [FFLiFacelineTextureTempObject]) ]); /** * @typedef {Object} FFLiMaskTextures * @property {Array<number>} pRenderTextures */ /** @type {import('./struct-fu').StructInstance<FFLiMaskTextures>} */ const FFLiMaskTextures = _.struct([ _uintptr('pRenderTextures', FFLExpression.MAX) ]); /** @package */ const FFL_RESOLUTION_MASK = 0x3fffffff; /** * @typedef {Object} FFLCharModelDesc * @property {number} resolution - Texture resolution for faceline/mask. It's recommended to only use powers of two. * @property {Uint32Array} allExpressionFlag - Expression flag, created by {@link makeExpressionFlag} * @property {FFLModelFlag} modelFlag * @property {FFLResourceType} resourceType */ /** @type {import('./struct-fu').StructInstance<FFLCharModelDesc>} */ const FFLCharModelDesc = _.struct([ _.uint32le('resolution'), _.uint32le('allExpressionFlag', 3), _.uint32le('modelFlag'), _.uint32le('resourceType') ]); /** * Static default for FFLCharModelDesc. * @type {FFLCharModelDesc} * @readonly * @public */ const FFLCharModelDescDefault = { /** Typical default. */ resolution: 512, /** Normal expression. */ allExpressionFlag: new Uint32Array([1, 0, 0]), modelFlag: FFLModelFlag.NORMAL, /** Default resource type. */ resourceType: FFLResourceType.HIGH }; /** @typedef {FFLCharModelDesc|Array<FFLExpression>|FFLExpression|Uint32Array|null} CharModelDescOrExpressionFlag */ /** * @typedef {Object<string, FFLVec3>} FFLBoundingBox * @property {FFLVec3} min * @property {FFLVec3} max */ /** @type {import('./struct-fu').StructInstance<FFLBoundingBox>} */ const FFLBoundingBox = _.struct([ _.struct('min', [FFLVec3]), _.struct('max', [FFLVec3]) ]); /** * @typedef {Object<string, FFLVec3>} FFLPartsTransform * @property {FFLVec3} hatTranslate * @property {FFLVec3} headFrontRotate * @property {FFLVec3} headFrontTranslate * @property {FFLVec3} headSideRotate * @property {FFLVec3} headSideTranslate * @property {FFLVec3} headTopRotate * @property {FFLVec3} headTopTranslate */ /** @type {import('./struct-fu').StructInstance<FFLPartsTransform>} */ const FFLPartsTransform = _.struct([ _.struct('hatTranslate', [FFLVec3]), _.struct('headFrontRotate', [FFLVec3]), _.struct('headFrontTranslate', [FFLVec3]), _.struct('headSideRotate', [FFLVec3]), _.struct('headSideTranslate', [FFLVec3]), _.struct('headTopRotate', [FFLVec3]), _.struct('headTopTranslate', [FFLVec3]) ]); /** * PartsTransform with THREE.Vector3 type. * @typedef {Object<string, import('three').Vector3>} PartsTransform * @property {import('three').Vector3} hatTranslate * @property {import('three').Vector3} headFrontRotate * @property {import('three').Vector3} headFrontTranslate * @property {import('three').Vector3} headSideRotate * @property {import('three').Vector3} headSideTranslate * @property {import('three').Vector3} headTopRotate * @property {import('three').Vector3} headTopTranslate */ /** * Internal representation within FFL for the created CharModel. * @typedef {Object} FFLiCharModel * @property {FFLiCharInfo} charInfo * @property {FFLCharModelDesc} charModelDesc * @property {FFLExpression} expression * @property {number} pTextureTempObject * @property {Array<FFLDrawParam>} drawParam * @property {Array<number>} pShapeData * @property {Array<Object>} facelineRenderTexture * @property {Array<number>} pCapGlassNoselineTextures * @property {FFLiMaskTextures} maskTextures * @property {Array<FFLVec3>} beardHairFaceCenterPos * @property {FFLPartsTransform} partsTransform * @property {number} modelType - FFLModelType * @property {Array<FFLBoundingBox>} boundingBox */ /** @type {import('./struct-fu').StructInstance<FFLiCharModel>} */ const FFLiCharModel = _.struct([ _.struct('charInfo', [FFLiCharInfo]), _.struct('charModelDesc', [FFLCharModelDesc]), _.uint32le('expression'), // enum FFLExpression _uintptr('pTextureTempObject'), // stub _.struct('drawParam', [FFLDrawParam], FFLiShapeType.MAX), _uintptr('pShapeData', FFLiShapeType.MAX), _.struct('facelineRenderTexture', [FFLiRenderTexture]), _uintptr('pCapGlassNoselineTextures', 3), _.struct('maskTextures', [FFLiMaskTextures]), _.struct('beardHairFaceCenterPos', [FFLVec3], 3), _.struct('partsTransform', [FFLPartsTransform]), _.uint32le('modelType'), // enum FFLModelType // FFLBoundingBox[FFL_MODEL_TYPE_MAX = 3] _.struct('boundingBox', [FFLBoundingBox], 3) ]); /** @enum {number} */ const FFLDataSource = { OFFICIAL: 0, DEFAULT: 1, MIDDLE_DB: 2, STORE_DATA_OFFICIAL: 3, STORE_DATA: 4, BUFFER: 5, DIRECT_POINTER: 6 }; /** * @typedef {Object} FFLCharModelSource * @property {FFLDataSource} dataSource * @property {number} pBuffer * @property {number} index - Only for default, official, MiddleDB; unneeded for raw data */ /** @type {import('./struct-fu').StructInstance<FFLCharModelSource>} */ const FFLCharModelSource = _.struct([ _.uint32le('dataSource'), _uintptr('pBuffer'), _.uint16le('index') ]); // The enums below are only for FFLiGetRandomCharInfo. // Hence, why each one has a value called ALL. /** @enum {number} */ const FFLGender = { MALE: 0, FEMALE: 1, ALL: 2 }; /** @enum {number} */ const FFLAge = { CHILD: 0, ADULT: 1, ELDER: 2, ALL: 3 }; /** @enum {number} */ const FFLRace = { BLACK: 0, WHITE: 1, ASIAN: 2, ALL: 3 }; /** * @typedef {Object} FFLResourceDesc * @property {Array<number>} pData * @property {Array<number>} size */ /** @type {import('./struct-fu').StructInstance<FFLResourceDesc>} */ const FFLResourceDesc = _.struct([ _uintptr('pData', FFLResourceType.MAX), _.uint32le('size', FFLResourceType.MAX) ]); // // --------------------------------------------------------------------- // // Texture Management // // --------------------------------------------------------------------- // ------------------------- Texture Related Structs ------------------------- /** @enum {number} */ const FFLTextureFormat = { R8_UNORM: 0, R8_G8_UNORM: 1, R8_G8_B8_A8_UNORM: 2, MAX: 3 }; /** * @typedef {Object} FFLTextureInfo * @property {number} width * @property {number} height * @property {number} mipCount * @property {FFLTextureFormat} format * @property {number} isGX2Tiled * @property {number} imageSize * @property {number} imagePtr * @property {number} mipSize * @property {number} mipPtr * @property {Array<number>} mipLevelOffset */ /** @type {import('./struct-fu').StructInstance<FFLTextureInfo>} */ const FFLTextureInfo = _.struct([ _.uint16le('width'), _.uint16le('height'), _.uint8('mipCount'), _.uint8('format'), _.uint8('isGX2Tiled'), _.byte('_padding', 1), _.uint32le('imageSize'), _uintptr('imagePtr'), _.uint32le('mipSize'), _uintptr('mipPtr'), _.uint32le('mipLevelOffset', 13) ]); const FFLTextureCallback = _.struct([ _uintptr('pObj'), _.uint8('useOriginalTileMode'), _.byte('_padding', 3), // alignment _uintptr('pCreateFunc'), _uintptr('pDeleteFunc') ]); // TODO PATH: src/TextureManager.js // ------------------------ Class: TextureManager ----------------------------- /** * Manages THREE.Texture objects created via FFL. * Must be instantiated after FFL is fully initialized. */ class TextureManager { /** * Constructs the TextureManager. This MUST be created after initializing FFL. * @param {Module} module - The Emscripten module. * @param {boolean} [setToFFLGlobal] - Whether or not to call FFLSetTextureCallback on the constructed callback. * @param {WebGLRenderingContext|WebGL2RenderingContext} [gl] - WebGL context for capability detection. */ constructor(module, setToFFLGlobal = false, gl = null) { /** * @type {Module} * @private */ this._module = module; /** * @type {Map<number, import('three').Texture>} * @private */ this._textures = new Map(); // Internal map of texture id -> THREE.Texture. /** @package */ this._textureCallbackPtr = 0; /** * Controls whether or not the TextureManager * will log creations and deletions of textures * in order to better track memory allocations. * @public */ this.logging = false; /** * WebGL context for capability detection * @type {WebGLRenderingContext|WebGL2RenderingContext|null} * @private */ this._gl = gl; /** * Whether we're running on WebGL 1 * @type {boolean} * @private */ this._isWebGL1 = this._detectWebGL1(); /** * Maximum texture size supported by the GPU * @type {number} * @private */ this._maxTextureSize = this._getMaxTextureSize(); // Create and set texture callback instance. this._setTextureCallback(); if (setToFFLGlobal) { // Set texture callback globally within FFL if chosen. module._FFLSetTextureCallback(this._textureCallbackPtr); } } /** * Detects if we're running on WebGL 1 * @returns {boolean} * @private */ _detectWebGL1() { if (this._gl) { return !(this._gl instanceof WebGL2RenderingContext); } // Fallback: assume WebGL1 for safety unless explicitly WebGL2 // This is more conservative and helps avoid WebGL1 compatibility issues return true; } /** * Gets the maximum texture size supported by the GPU * @returns {number} * @private */ _getMaxTextureSize() { if (this._gl) { return this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE); } // Conservative default for WebGL 1 return this._isWebGL1 ? 2048 : 4096; } /** * Creates and allocates an {@link FFLTextureCallback} instance from callback function pointers. * @param {Module} module - The Emscripten module. * @param {number} createCallback - Function pointer for the create callback. * @param {number} deleteCallback - Function pointer for the delete callback. * @returns {number} Pointer to the {@link FFLTextureCallback}. * Note that you MUST free this after using it (done in {@link TextureManager.disposeCallback}). * @private */ static _allocateTextureCallback(module, createCallback, deleteCallback) { const ptr = module._malloc(FFLTextureCallback.size); const textureCallback = { pObj: 0, useOriginalTileMode: false, _padding: [0, 0, 0], pCreateFunc: createCallback, pDeleteFunc: deleteCallback }; const packed = FFLTextureCallback.pack(textureCallback); module.HEAPU8.set(packed, ptr); return ptr; } /** * Creates the create/delete functions in Emscripten and allocates and sets * the {@link FFLTextureCallback} object as {@link TextureManager._textureCallbackPtr}. * @param {boolean} addDeleteCallback - Whether or not to bind the delete function to the texture callback. */ _setTextureCallback(addDeleteCallback = false) { const mod = this._module; // Bind the callbacks to this instance. /** @private */ this._createCallback = mod.addFunction(this._textureCreateFunc.bind(this), 'vppp'); if (addDeleteCallback) { /** @private */ this._deleteCallback = mod.addFunction(this._textureDeleteFunc.bind(this), 'vpp'); } /** @private */ this._textureCallbackPtr = TextureManager._allocateTextureCallback(mod, this._createCallback, this._deleteCallback ? this._deleteCallback : 0); } /** * @param {number} format - Enum value for FFLTextureFormat. * @returns {import('three').PixelFormat} Three.js texture format constant. * @throws {Error} Unexpected FFLTextureFormat value * @private */ _getTextureFormat(format) { // Map FFLTextureFormat to Three.js texture formats. // For WebGL 1 compatibility, always use Luminance formats for single/dual channel textures const r8 = this._isWebGL1 ? THREE.LuminanceFormat : THREE.RedFormat; const r8g8 = this._isWebGL1 ? THREE.LuminanceAlphaFormat : THREE.RGFormat; const textureFormatToThreeFormat = { [FFLTextureFormat.R8_UNORM]: r8, [FFLTextureFormat.R8_G8_UNORM]: r8g8, [FFLTextureFormat.R8_G8_B8_A8_UNORM]: THREE.RGBAFormat }; // Determine the data format from the table. const dataFormat = textureFormatToThreeFormat[format]; if (dataFormat === undefined) { throw new Error(`_textureCreateFunc: Unexpected FFLTextureFormat value: ${format}`); } return dataFormat; } /** * Checks if a dimension is a power of two * @param {number} value * @returns {boolean} * @private */ _isPowerOfTwo(value) { return value > 0 && (value & (value - 1)) === 0; } /** * Finds the next power of two greater than or equal to the input * @param {number} value * @returns {number} * @private */ _nextPowerOfTwo(value) { return Math.pow(2, Math.ceil(Math.log2(value))); } /** * Resizes image data to power-of-two dimensions for WebGL 1 compatibility * @param {Uint8Array} imageData * @param {number} width * @param {number} height * @param {number} channels * @returns {{data: Uint8Array, width: number, height: number}} * @private */ _resizeToPoT(imageData, width, height, channels) { const newWidth = Math.min(this._nextPowerOfTwo(width), this._maxTextureSize); const newHeight = Math.min(this._nextPowerOfTwo(height), this._maxTextureSize); if (newWidth === width && newHeight === height) { return { data: imageData, width, height }; } if (this.logging) { //console.debug(`Resizing texture from ${width}x${height} to ${newWidth}x${newHeight} for WebGL1 compatibility`); } // Improved bilinear interpolation resize const newData = new Uint8Array(newWidth * newHeight * channels); const xRatio = (width - 1) / newWidth; const yRatio = (height - 1) / newHeight; for (let y = 0; y < newHeight; y++) { for (let x = 0; x < newWidth; x++) { const srcX = x * xRatio; const srcY = y * yRatio; const x1 = Math.floor(srcX); const y1 = Math.floor(srcY); const x2 = Math.min(x1 + 1, width - 1); const y2 = Math.min(y1 + 1, height - 1); const xWeight = srcX - x1; const yWeight = srcY - y1; const dstIndex = (y * newWidth + x) * channels; for (let c = 0; c < channels; c++) { const p1 = imageData[(y1 * width + x1) * channels + c]; const p2 = imageData[(y1 * width + x2) * channels + c]; const p3 = imageData[(y2 * width + x1) * channels + c]; const p4 = imageData[(y2 * width + x2) * channels + c]; const top = p1 * (1 - xWeight) + p2 * xWeight; const bottom = p3 * (1 - xWeight) + p4 * xWeight; const result = top * (1 - yWeight) + bottom * yWeight; newData[dstIndex + c] = Math.round(result); } } } return { data: newData, width: newWidth, height: newHeight }; } /** * @param {number} _ - Originally pObj, unused here. * @param {number} textureInfoPtr - Pointer to {@link FFLTextureInfo}. * @param {number} texturePtrPtr - Pointer to the texture handle (pTexture2D). * @private */ _textureCreateFunc(_, textureInfoPtr, texturePtrPtr) { const u8 = this._module.HEAPU8.subarray(textureInfoPtr, textureInfoPtr + FFLTextureInfo.size); const textureInfo = FFLTextureInfo.unpack(u8); if (this.logging) { //console.debug(`_textureCreateFunc: width=${textureInfo.width}, height=${textureInfo.height}, format=${textureInfo.format}, imageSize=${textureInfo.imageSize}, mipCount=${textureInfo.mipCount}`); } /** Resolve THREE.PixelFormat. */ const format = this._getTextureFormat(textureInfo.format); // Determine number of channels based on format const channels = this._getChannelCount(textureInfo.format); // Copy image data from HEAPU8 via slice. This is base level/mip level 0. let imageData = this._module.HEAPU8.slice(textureInfo.imagePtr, textureInfo.imagePtr + textureInfo.imageSize); let { width, height } = textureInfo; const originalWidth = width; const originalHeight = height; // Check if original dimensions are power of two const isOriginalPoT = this._isPowerOfTwo(width) && this._isPowerOfTwo(height); // Only resize if we're in WebGL 1 AND the texture is NPOT AND it's not too large // Be more conservative about when to resize const shouldResize = this._isWebGL1 && !isOriginalPoT && (width <= 256 && height <= 256); if (shouldResize) { const resized = this._resizeToPoT(imageData, width, height, channels); imageData = resized.data; width = resized.width; height = resized.height; if (this.logging) { //console.debug(`Resized texture from ${originalWidth}x${originalHeight} to ${width}x${height}`); } } // Final power-of-two check const finalIsPoT = this._isPowerOfTwo(width) && this._isPowerOfTwo(height); // Mipmaps: only if we have mipmap data and appropriate conditions const canUseMipmaps = textureInfo.mipCount > 1 && Number(THREE.REVISION) >= 138 && !shouldResize; const useMipmaps = canUseMipmaps && (finalIsPoT || !this._isWebGL1); // Create new THREE.Texture with the specified format. const texture = new THREE.DataTexture(useMipmaps ? null : imageData, width, height, format, THREE.UnsignedByteType); // Configure texture settings BEFORE setting needsUpdate this._configureTextureForWebGL1(texture, width, height, useMipmaps, finalIsPoT); if (useMipmaps) { // Add base texture. texture.mipmaps = [{ data: imageData, width: width, height: height }]; // Enable filtering option for mipmap and add levels. texture.minFilter = THREE.LinearMipmapLinearFilter; texture.generateMipmaps = false; this._addMipmaps(texture, textureInfo, channels); } texture.needsUpdate = true; this.set(texture.id, texture); this._module.HEAPU32[texturePtrPtr / 4] = texture.id; } /** * Gets the number of channels for a given texture format * @param {number} format - FFLTextureFormat enum value * @returns {number} * @private */ _getChannelCount(format) { const channelMap = { [FFLTextureFormat.R8_UNORM]: 1, [FFLTextureFormat.R8_G8_UNORM]: 2, [FFLTextureFormat.R8_G8_B8_A8_UNORM]: 4 }; return channelMap[format] || 4; } /** * Configures texture settings for WebGL 1 compatibility * @param {import('three').Texture} texture * @param {number} width * @param {number} height * @param {boolean} useMipmaps * @param {boolean} isPowerOfTwo * @private */ _configureTextureForWebGL1(texture, width, height, useMipmaps, isPowerOfTwo = null) { // Calculate if power of two if not provided if (isPowerOfTwo === null) { isPowerOfTwo = this._isPowerOfTwo(width) && this._isPowerOfTwo(height); } // Start with safe defaults for NPOT textures if (!isPowerOfTwo || this._isWebGL1) { texture.wrapS = THREE.ClampToEdgeWrapping; texture.wrapT = THREE.ClampToEdgeWrapping; texture.generateMipmaps = false; } else { // Power-of-two textures can use repeat wrapping texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; texture.generateMipmaps = false; // Still disable auto-generation } // Set filtering texture.magFilter = THREE.LinearFilter; if (useMipmaps && isPowerOfTwo) { // Only use mipmap filtering if we actually have mipmaps and texture is PoT texture.minFilter = THREE.LinearMipmapLinearFilter; } else { // Safe linear filtering for all other cases texture.minFilter = THREE.LinearFilter; } if (this.logging) { //console.debug(`Texture config: ${width}x${height}, PoT=${isPowerOfTwo}, mipmaps=${useMipmaps}, WebGL1=${this._isWebGL1}`); } } /** * Ensures NPOT textures are WebGL1-safe. * @param {import('three').Texture} texture * @private * @deprecated Use _configureTextureForWebGL1 instead */ _fixNPOT(texture) { const width = texture.image?.width; const height = texture.image?.height; const isPowerOfTwo = (x) => (x & (x - 1)) === 0; if (!isPowerOfTwo(width) || !isPowerOfTwo(height)) { texture.wrapS = THREE.ClampToEdgeWrapping; texture.wrapT = THREE.ClampToEdgeWrapping; texture.generateMipmaps = false; texture.minFilter = THREE.LinearFilter; texture.magFilter = THREE.LinearFilter; if (this.logging) { //console.debug(`NPOT fix applied: ${width}x${height}`); } } } /** * @param {import('three').Texture} texture - Texture to upload mipmaps into. * @param {FFLTextureInfo} textureInfo - FFLTextureInfo object representing this texture. * @param {number} channels - Number of channels in the texture * @throws {Error} Throws if mipPtr is null. * @private */ _addMipmaps(texture, textureInfo, channels) { // Make sure mipPtr is not null. if (textureInfo.mipPtr === 0) { throw new Error('_addMipmaps: mipPtr is null, so the caller incorrectly assumed this texture has mipmaps'); } // Iterate through mip levels starting from 1 (base level is mip level 0). for (let mipLevel = 1; mipLevel < textureInfo.mipCount; mipLevel++) { // Calculate the offset for the current mip level. const mipOffset = textureInfo.mipLevelOffset[mipLevel - 1]; // Calculate dimensions of the current mip level. let mipWidth = Math.max(1, textureInfo.width >> mipLevel); let mipHeight = Math.max(1, textureInfo.height >> mipLevel); // Get the offset of the next mipmap and calculate end offset. const nextMipOffset = textureInfo.mipLevelOffset[mipLevel] || textureInfo.mipSize; const end = textureInfo.mipPtr + nextMipOffset; // Copy the data from the heap. const start = textureInfo.mipPtr + mipOffset; let mipData = this._module.HEAPU8.slice(start, end); // For WebGL 1, ensure mipmap dimensions are power-of-two if needed if (this._isWebGL1 && (!this._isPowerOfTwo(mipWidth) || !this._isPowerOfTwo(mipHeight))) { const resized = this._resizeToPoT(mipData, mipWidth, mipHeight, channels); mipData = resized.data; mipWidth = resized.width; mipHeight = resized.height; } if (this.logging) { //console.debug(` - Mip ${mipLevel}: ${mipWidth}x${mipHeight}, offset=${mipOffset}, range=${start}-${end}`); } // Push this mip level data into the texture's mipmaps array. // @ts-ignore - data = "CompressedTextureMipmap & CubeTexture & HTMLCanvasElement" texture.mipmaps.push({ data: mipData, // Should still accept Uint8Array. width: mipWidth, height: mipHeight }); } } /** * @param {number} _ - Originally pObj, unused here. * @param {number} texturePtrPtr - Pointer to the texture handle (pTexture2D). * @private */ _textureDeleteFunc(_, texturePtrPtr) { const texId = this._module.HEAPU32[texturePtrPtr / 4]; // this.delete(texId); // NOTE: This is effectively no longer used as when // we delete a CharModel instance it deletes // cap/noseline/glass textures before we are // finished with the model itself. It is now only logging const tex = this._textures.get(texId); if (tex && this.logging) { //console.debug('Delete texture ', tex.id); } } /** * @param {number} id - ID assigned to the texture. * @returns {import('three').Texture|null|undefined} Returns the texture if it is found. * @public */ get(id) { const texture = this._textures.get(id); if (!texture && this.logging) { console.error('Unknown texture', id); } return texture; } /** * @param {number} id - ID assigned to the texture. * @param {import('three').Texture} texture - Texture to add. * @public */ set(id, texture) { // Set texture with an override for dispose. const disposeReal = texture.dispose.bind(texture); texture.dispose = () => { // Remove this texture from the map after disposing. disposeReal(); this.delete(id); // this = TextureManager }; this._textures.set(id, texture); // Log is spaced to match delete/deleting/dispose messages. if (this.logging) { //console.debug('Adding texture ', texture.id); } } /** * @param {number} id - ID assigned to the texture. * @public */ delete(id) { // Get texture from array instead of with get() // because it's okay if it was already deleted. const texture = this._textures.get(id); if (texture) { // This is assuming the texture has already been disposed. /** @type {Object<string, *>} */ (texture).source = null; /** @type {Object<string, *>} */ (texture).mipmaps = null; if (this.logging) { //console.debug('Deleted texture ', id); } this._textures.delete(id); } } /** * Disposes/frees the {@link FFLTextureCallback} along with * removing the created Emscripten functions. * @public */ disposeCallback() { // if (!this._module) { // return; // } if (this._textureCallbackPtr) { this._module._free(this._textureCallbackPtr); this._textureCallbackPtr = 0; } if (this._deleteCallback) { this._module.removeFunction(this._deleteCallback); this._deleteCallback = 0; } // should always exist?: if (this._createCallback) { this._module.removeFunction(this._createCallback); this._createCallback = 0; } // this._module = null; } /** * Disposes of all textures and frees the {@link FFLTextureCallback}. * @public */ dispose() { // Dispose of all stored textures. this._textures.forEach((tex) => { tex.dispose(); }); // Clear texture map. this._textures.clear(); // Free texture callback. this.disposeCallback(); } } // // --------------------------------------------------------------------- // // Classes for FFL Exceptions // // --------------------------------------------------------------------- // TODO PATH: src/Exceptions.js /** * Base exception type for all exceptions based on FFLResult. * https://github.com/ariankordi/FFLSharp/blob/master/FFLSharp.FFLManager/FFLExceptions.cs * https://github.com/aboood40091/ffl/blob/master/include/nn/ffl/FFLResult.h */ class FFLResultException extends Error { /** * @param {number|FFLResult} result - The returned {@link FFLResult}. * @param {string} [funcName] - The name of the function that was called. * @param {string} [message] - An optional message for the exception. */ constructor(result, funcName, message) { if (!message) { if (funcName) { message = `${funcName} failed with FFLResult: ${result}`; } else { message = `From FFLResult: ${result}`; } } super(message); /** The stored {@link FFLResult} code. */ this.resul