From 01f3cec62af1d1aaae07943c3d039cd3812b5ed9 Mon Sep 17 00:00:00 2001 From: Anders Date: Sat, 31 May 2025 22:32:56 +0500 Subject: [PATCH 01/28] Added parsing of Skin&Bones --- src/renderware/RwSections.ts | 1 + src/renderware/dff/DffParser.ts | 64 ++++++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/renderware/RwSections.ts b/src/renderware/RwSections.ts index 3ddfa02..8478ec4 100644 --- a/src/renderware/RwSections.ts +++ b/src/renderware/RwSections.ts @@ -13,6 +13,7 @@ export enum RwSections { RwTextureNative = 0x0015, RwTextureDictionary = 0x0016, RwGeometryList = 0x001A, + RwSkin = 0x116, // Toolkit RwMaterialEffectsPLG = 0x0120, diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 525485d..c4d3e3b 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -63,6 +63,7 @@ export interface RwGeometry { boundingSphere?: RwSphere, materialList: RwMaterialList, binMesh: RwBinMesh, + skin?: RwSkin, } export interface RwGeometryList { @@ -81,6 +82,15 @@ export interface RwBinMesh { meshes: RwMesh[], } +export interface RwSkin { + boneCount: number, + usedBoneCount: number, + maxWeightsPerVertex: number, + boneVertexIndices: number[][], + vertexWeights: number[][], + inverseBoneMatrices: number[][], +} + export interface RwMesh { materialIndex: number, indexCount: number, @@ -350,10 +360,14 @@ export class DffParser extends RwFile { } let materialList = this.readMaterialList(); - let sectionSize = this.readSectionHeader().sectionSize; let position = this.getPosition(); let binMesh = this.readBinMesh(); + let skin = undefined; + + if(this.readSectionHeader().sectionType == RwSections.RwSkin) { + skin = this.readSkin(vertexCount); + } this.setPosition(position + sectionSize); @@ -369,6 +383,7 @@ export class DffParser extends RwFile { triangleInformation, materialList, binMesh, + skin, }; } @@ -394,6 +409,53 @@ export class DffParser extends RwFile { }; } + public readSkin(vertexCount : number): RwSkin { + const boneCount = this.readUint8(); + const usedBoneCount = this.readUint8(); + const maxWeightsPerVertex = this.readUint8(); + + this.skip(1); // Padding + this.skip(usedBoneCount); // Skipping special indices + + const boneVertexIndices: number[][] = []; + const vertexWeights: number[][] = []; + const inverseBoneMatrices: number[][] = []; + + for (let i = 0; i < vertexCount; i++) { + const indices: number[] = []; + for(let j = 0; j < maxWeightsPerVertex; j++) { + indices.push(this.readUint8()); + } + boneVertexIndices.push(indices); + } + + for (let i = 0; i < vertexCount; i++) { + const weights: number[] = []; + for(let j = 0; j < maxWeightsPerVertex; j++) { + weights.push(this.readFloat()); + } + vertexWeights.push(weights); + } + + for (let i = 0; i < boneCount; i++) { + const matrix4x4: number[] = []; + for(let j = 0; j < 16; j++) { + const value = this.readFloat(); + matrix4x4.push(value > 0.001 ? value : 0); + } + inverseBoneMatrices.push(matrix4x4); + } + + return { + boneCount, + usedBoneCount, + maxWeightsPerVertex, + boneVertexIndices, + vertexWeights, + inverseBoneMatrices, + } + } + public readMesh(): RwMesh { const indexCount = this.readUint32(); const materialIndex = this.readUint32(); From 4248890d288beb8bd42ae4954897654e21fc88b1 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 1 Jun 2025 06:09:39 +0500 Subject: [PATCH 02/28] Update DffParser.ts --- src/renderware/dff/DffParser.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index c4d3e3b..345e296 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -423,7 +423,7 @@ export class DffParser extends RwFile { for (let i = 0; i < vertexCount; i++) { const indices: number[] = []; - for(let j = 0; j < maxWeightsPerVertex; j++) { + for(let j = 0; j < 4; j++) { indices.push(this.readUint8()); } boneVertexIndices.push(indices); @@ -431,7 +431,7 @@ export class DffParser extends RwFile { for (let i = 0; i < vertexCount; i++) { const weights: number[] = []; - for(let j = 0; j < maxWeightsPerVertex; j++) { + for(let j = 0; j < 4; j++) { weights.push(this.readFloat()); } vertexWeights.push(weights); @@ -440,8 +440,7 @@ export class DffParser extends RwFile { for (let i = 0; i < boneCount; i++) { const matrix4x4: number[] = []; for(let j = 0; j < 16; j++) { - const value = this.readFloat(); - matrix4x4.push(value > 0.001 ? value : 0); + matrix4x4.push(this.readFloat()); } inverseBoneMatrices.push(matrix4x4); } From 70d75014dbb749ed324557055e8fe7b8ae3eb89d Mon Sep 17 00:00:00 2001 From: Anders Date: Mon, 2 Jun 2025 20:10:41 +0500 Subject: [PATCH 03/28] Added parsing of hAnim (bones) section --- src/renderware/RwSections.ts | 1 + src/renderware/dff/DffParser.ts | 51 ++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/renderware/RwSections.ts b/src/renderware/RwSections.ts index 8478ec4..95cf709 100644 --- a/src/renderware/RwSections.ts +++ b/src/renderware/RwSections.ts @@ -14,6 +14,7 @@ export enum RwSections { RwTextureDictionary = 0x0016, RwGeometryList = 0x001A, RwSkin = 0x116, + RwAnim = 0x11E, // Toolkit RwMaterialEffectsPLG = 0x0120, diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 345e296..796a2ab 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -10,6 +10,7 @@ export interface RwDff { frameList: RwFrameList | null, atomics: number[], dummies: string[], + bones: RwBone[], } export interface RwClump { @@ -18,10 +19,17 @@ export interface RwClump { cameraCount?: number, } +export interface RwBone { + boneId: number, + boneCount: number, + flags?: number, + bones?: RwBone[], +} + export interface RwFrame { rotationMatrix: RwMatrix3, coordinatesOffset: RwVector3, - parentFrame: number, + parentFrame: number, } export interface RwFrameList { @@ -147,6 +155,7 @@ export class DffParser extends RwFile { let versionNumber: number | undefined; let atomics: number[] = []; let dummies: string[] = []; + let bones: RwBone[] = []; let geometryList: RwGeometryList | null = null; let frameList: RwFrameList | null = null; @@ -176,6 +185,9 @@ export class DffParser extends RwFile { case RwSections.RwNodeName: dummies.push(this.readString(extensionHeader.sectionSize)); break; + case RwSections.RwAnim: + bones.push(this.readBones()); + break; default: console.debug(`Extension type ${extensionHeader.sectionType} (${extensionHeader.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${extensionHeader.sectionSize} bytes.`); this.skip(extensionHeader.sectionSize); @@ -211,6 +223,7 @@ export class DffParser extends RwFile { frameList: frameList, atomics: atomics, dummies: dummies, + bones: bones, }; } @@ -409,6 +422,7 @@ export class DffParser extends RwFile { }; } + public readSkin(vertexCount : number): RwSkin { const boneCount = this.readUint8(); const usedBoneCount = this.readUint8(); @@ -455,6 +469,41 @@ export class DffParser extends RwFile { } } + public readBones() { + this.skip(4); // Skipping hAnimVersion property (0x100) + const boneId = this.readInt32(); + const boneCount = this.readInt32(); + + if(boneId == 0) { + this.skip(8); // Skipping flags and keyFrameSize properties + } + + if(boneCount == 0) { + return { + boneId, + boneCount, + } + } + + const bones : RwBone[] = []; + + for(let i = 0; i < boneCount; i++) { + const bone = { + boneId: this.readInt32(), + boneCount: this.readInt32(), + flags: this.readInt32(), + }; + bones.push(bone); + } + + return { + boneId, + boneCount, + bones, + } + } + + public readMesh(): RwMesh { const indexCount = this.readUint32(); const materialIndex = this.readUint32(); From 1f94056e87e482c48bcf72a30df0f7ddb3de2729 Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 4 Jun 2025 06:04:20 +0500 Subject: [PATCH 04/28] Added tests + small fix --- src/renderware/dff/DffParser.ts | 10 ++-- tests/assets/wuzimu.dff | Bin 0 -> 83968 bytes tests/dff/wuzimu.dff.test.ts | 93 ++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 tests/assets/wuzimu.dff create mode 100644 tests/dff/wuzimu.dff.test.ts diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 796a2ab..b81bad4 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -29,7 +29,7 @@ export interface RwBone { export interface RwFrame { rotationMatrix: RwMatrix3, coordinatesOffset: RwVector3, - parentFrame: number, + parentFrame: number, } export interface RwFrameList { @@ -205,6 +205,10 @@ export class DffParser extends RwFile { // For some reason, this frame is outside RwExtension. dummies.push(this.readString(header.sectionSize)); break; + case RwSections.RwAnim: + // For III / VC models + bones.push(this.readBones()); + break; default: console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`); this.skip(header.sectionSize); @@ -422,7 +426,6 @@ export class DffParser extends RwFile { }; } - public readSkin(vertexCount : number): RwSkin { const boneCount = this.readUint8(); const usedBoneCount = this.readUint8(); @@ -486,7 +489,7 @@ export class DffParser extends RwFile { } const bones : RwBone[] = []; - + for(let i = 0; i < boneCount; i++) { const bone = { boneId: this.readInt32(), @@ -503,7 +506,6 @@ export class DffParser extends RwFile { } } - public readMesh(): RwMesh { const indexCount = this.readUint32(); const materialIndex = this.readUint32(); diff --git a/tests/assets/wuzimu.dff b/tests/assets/wuzimu.dff new file mode 100644 index 0000000000000000000000000000000000000000..7bebf300fadb3186e554c53c1440c7638af0abee GIT binary patch literal 83968 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 { + const FLOATING_POINT_ERROR = 6; + let rwDff: RwDff; + + beforeAll(async () => { + const consoleDebug = console.debug; + console.debug = jest.fn(); + + const dffParser = new DffParser(await readFile(join(__dirname, '../assets/wuzimu.dff'))); + rwDff = dffParser.parse(); + + console.debug = consoleDebug; + }); + + test('SA version', () => { + expect(rwDff.version).toBe('RenderWare 3.6.0.3 (SA)'); + expect(rwDff.versionNumber).toBe(0x36003); + }); + + test('dummies - length', () => { + expect(rwDff.dummies).toHaveLength(32); + }); + + test('dummies - names', () => { + expect(rwDff.dummies[0]).toBe('Normal'); + expect(rwDff.dummies[1]).toBe(' Pelvis'); + expect(rwDff.dummies[2]).toBe(' R Thigh'); + expect(rwDff.dummies[15]).toBe('Jaw'); + expect(rwDff.dummies[30]).toBe(' R Foot'); + expect(rwDff.dummies[31]).toBe(' R Toe0'); + }) + + test('frames - length', () => { + const frameList = rwDff.frameList; + + expect(frameList).toBeDefined(); + expect(frameList!.frameCount).toBe(33); + expect(frameList!.frames).toHaveLength(frameList!.frameCount); + }); + + test('frames - parent frames', () => { + const frameList = rwDff.frameList; + + expect(frameList!.frames[0].parentFrame).toBe(-1); + expect(frameList!.frames[1].parentFrame).toBe(0); + expect(frameList!.frames[2].parentFrame).toBe(1); + expect(frameList!.frames[3].parentFrame).toBe(2); + expect(frameList!.frames[6].parentFrame).toBe(5); + expect(frameList!.frames[15].parentFrame).toBe(13); + expect(frameList!.frames[31].parentFrame).toBe(30); + expect(frameList!.frames[32].parentFrame).toBe(31); + }); + + test('frames - coordinate offsets', () => { + const frameList = rwDff.frameList; + + expect(frameList!.frames[0].coordinatesOffset).toStrictEqual({ x: 0, y: 0, z: 0 }); + expect(frameList!.frames[1].coordinatesOffset).toStrictEqual({ x: 0, y: 0, z: 0 }); + expect(frameList!.frames[16].coordinatesOffset).toStrictEqual({ + x: expect.closeTo(0.011230167001485825, FLOATING_POINT_ERROR), + y: expect.closeTo(0.0154640581458807, FLOATING_POINT_ERROR), + z: expect.closeTo(-0.004240759648382664, FLOATING_POINT_ERROR) }); + expect(frameList!.frames[32].coordinatesOffset).toStrictEqual({ + x: expect.closeTo(0.10200118273496628, FLOATING_POINT_ERROR), + y: expect.closeTo(0.1541077196598053, FLOATING_POINT_ERROR), + z: expect.closeTo(0.0, FLOATING_POINT_ERROR) }); + }); + + test('frames - rotation matrices', () => { + const frameList = rwDff.frameList; + + expect(frameList!.frames[0].rotationMatrix).toStrictEqual({ + right: { x: 1, y: 0, z: 0 }, + up: { x: 0, y: 1, z: 0}, + at: { x: 0, y: 0, z: 1} }); + expect(frameList!.frames[10].rotationMatrix).toStrictEqual({ + right: { x: expect.closeTo(0.9785407781600952, FLOATING_POINT_ERROR), y: expect.closeTo(0.20605318248271942, FLOATING_POINT_ERROR), z: expect.closeTo(0.0, FLOATING_POINT_ERROR)}, + up: { x: expect.closeTo(-0.20605315268039703, FLOATING_POINT_ERROR), y: expect.closeTo(0.97854083776474, FLOATING_POINT_ERROR), z: expect.closeTo(0.0, FLOATING_POINT_ERROR)}, + at: { x: expect.closeTo(0.0, FLOATING_POINT_ERROR), y: expect.closeTo(0.0, FLOATING_POINT_ERROR), z: expect.closeTo(1.0, FLOATING_POINT_ERROR)} }); + expect(frameList!.frames[32].rotationMatrix).toStrictEqual({ + right: { x: 0, y: 1, z: expect.closeTo(0.0, FLOATING_POINT_ERROR) }, + up: { x: -1, y: 0, z: 0}, + at: { x: 0, y: 0, z: 1} }); + }); + + +}); From 16467b4df475bad38ddb66ffb172464e9572e211 Mon Sep 17 00:00:00 2001 From: Anders Date: Wed, 4 Jun 2025 23:42:18 +0500 Subject: [PATCH 05/28] Adding some tests --- tests/dff/wuzimu.dff.test.ts | 122 ++++++++++++++++++++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/tests/dff/wuzimu.dff.test.ts b/tests/dff/wuzimu.dff.test.ts index c44e080..422db3f 100644 --- a/tests/dff/wuzimu.dff.test.ts +++ b/tests/dff/wuzimu.dff.test.ts @@ -1,9 +1,10 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { DffParser, RwDff } from '../../src/index'; +import exp from 'node:constants'; -// This test for skin and bones only +// This test for skin and bones sections describe('dff parsing - wuzimu', () => { const FLOATING_POINT_ERROR = 6; let rwDff: RwDff; @@ -89,5 +90,124 @@ describe('dff parsing - wuzimu', () => { at: { x: 0, y: 0, z: 1} }); }); + + test('bones - length', () => { + expect(rwDff.bones.length).toStrictEqual(32); + expect(rwDff.bones[0].bones!.length).toStrictEqual(32); + }); + test('bones - ids', () => { + expect(rwDff.bones[0].boneId).toStrictEqual(0); + expect(rwDff.bones[1].boneId).toStrictEqual(1); + expect(rwDff.bones[2].boneId).toStrictEqual(51); + expect(rwDff.bones[3].boneId).toStrictEqual(41); + expect(rwDff.bones[15].boneId).toStrictEqual(8); + expect(rwDff.bones[30].boneId).toStrictEqual(53); + expect(rwDff.bones[31].boneId).toStrictEqual(54); + + expect(rwDff.bones[0].bones![0].boneId).toStrictEqual(0); + expect(rwDff.bones[0].bones![2].boneId).toStrictEqual(2); + expect(rwDff.bones[0].bones![7].boneId).toStrictEqual(6); + expect(rwDff.bones[0].bones![18].boneId).toStrictEqual(24); + expect(rwDff.bones[0].bones![31].boneId).toStrictEqual(54); + }); + + test('bones - bone count', () => { + expect(rwDff.bones[0].boneCount).toStrictEqual(32); + expect(rwDff.bones[3].boneCount).toStrictEqual(0); + expect(rwDff.bones[15].boneCount).toStrictEqual(0); + expect(rwDff.bones[31].boneCount).toStrictEqual(0); +; + }); + + test('skin - bone count', () => { + const skin = rwDff.geometryList?.geometries[0].skin!; + + expect(skin.boneCount).toStrictEqual(32); + expect(skin.usedBoneCount).toStrictEqual(31); + + }); + + test('skin - vertex weights', () => { + const skin = rwDff.geometryList?.geometries[0].skin!; + + expect(skin.maxWeightsPerVertex).toStrictEqual(4); + expect(skin.vertexWeights.length).toStrictEqual(990); + + expect(skin.vertexWeights[0][0]).toBeCloseTo(0.5773563385009766,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[0][1]).toBeCloseTo(0.42264366149902344,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[0][2]).toBeCloseTo(0,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[0][3]).toBeCloseTo(0,FLOATING_POINT_ERROR); + + expect(skin.vertexWeights[654][0]).toBeCloseTo(1,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[654][1]).toBeCloseTo(0,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[654][2]).toBeCloseTo(0,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[654][3]).toBeCloseTo(0,FLOATING_POINT_ERROR); + + expect(skin.vertexWeights[989][0]).toBeCloseTo(1,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[989][1]).toBeCloseTo(0,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[989][2]).toBeCloseTo(0,FLOATING_POINT_ERROR); + expect(skin.vertexWeights[989][3]).toBeCloseTo(0,FLOATING_POINT_ERROR); + }); + + test('skin - bone-vertex map', () => { + const skin = rwDff.geometryList?.geometries[0].skin!; + + expect(skin.boneVertexIndices[0][0]).toStrictEqual(28); + expect(skin.boneVertexIndices[0][1]).toStrictEqual(24); + expect(skin.boneVertexIndices[0][2]).toStrictEqual(0); + expect(skin.boneVertexIndices[0][3]).toStrictEqual(0); + + expect(skin.boneVertexIndices[525][0]).toStrictEqual(5); + expect(skin.boneVertexIndices[525][1]).toStrictEqual(6); + expect(skin.boneVertexIndices[525][2]).toStrictEqual(0); + expect(skin.boneVertexIndices[525][3]).toStrictEqual(0); + + expect(skin.boneVertexIndices[989][0]).toStrictEqual(5); + expect(skin.boneVertexIndices[989][1]).toStrictEqual(0); + expect(skin.boneVertexIndices[989][2]).toStrictEqual(0); + expect(skin.boneVertexIndices[989][3]).toStrictEqual(0); + }); + + test('skin - inverse matrices', () => { + const skin = rwDff.geometryList?.geometries[0].skin!; + + expect(skin.inverseBoneMatrices.length).toStrictEqual(32); + + expect(skin.inverseBoneMatrices[0][0]).toBeCloseTo(1, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][1]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][2]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][3]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][4]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][5]).toBeCloseTo(1, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][6]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][7]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][8]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][9]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][10]).toBeCloseTo(1, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][11]).toBeCloseTo(1.8175872244174958e-20, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][12]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][13]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][14]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0][15]).toBeCloseTo(1.485376372184306e-43, FLOATING_POINT_ERROR); + + expect(skin.inverseBoneMatrices[31][0]).toBeCloseTo(1, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][1]).toBeCloseTo(-9.176544324418501e-8, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][2]).toBeCloseTo(-9.58358531422121e-11, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][3]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][4]).toBeCloseTo( -9.313955162681964e-10, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][5]).toBeCloseTo(-1.2224200318655676e-8, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][6]).toBeCloseTo(-1, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][7]).toBeCloseTo(0, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][8]).toBeCloseTo( 8.127337736141271e-8, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][9]).toBeCloseTo(0.9999998807907104, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][10]).toBeCloseTo(-5.962812021920172e-9, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][11]).toBeCloseTo(1.8175872244174958e-20, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][12]).toBeCloseTo( -0.15182293951511383, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][13]).toBeCloseTo(1.0362001657485962, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][14]).toBeCloseTo(-0.1699587106704712, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[31][15]).toBeCloseTo(1.485376372184306e-43, FLOATING_POINT_ERROR); + + }); + }); From 90d8a5ca9ebc681f9ae384e6896693b9afe25bc9 Mon Sep 17 00:00:00 2001 From: Anders Date: Sat, 7 Jun 2025 08:44:27 +0500 Subject: [PATCH 06/28] Added matrix4x4 structure --- src/renderware/dff/DffParser.ts | 28 +++++++++--- tests/dff/wuzimu.dff.test.ts | 78 +++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 40 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index b81bad4..8e1a264 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -96,7 +96,7 @@ export interface RwSkin { maxWeightsPerVertex: number, boneVertexIndices: number[][], vertexWeights: number[][], - inverseBoneMatrices: number[][], + inverseBoneMatrices: RwMatrix4[], } export interface RwMesh { @@ -111,6 +111,13 @@ export interface RwMatrix3 { at: RwVector3, } +export interface RwMatrix4 { + right: RwVector4, + up: RwVector4, + at: RwVector4, + transform: RwVector4, +} + export interface RwColor { r: number, g: number, @@ -128,6 +135,12 @@ export interface RwVector3 { y: number, z: number, } +export interface RwVector4 { + x: number, + y: number, + z: number, + t: number, +} export interface RwTextureCoordinate { u: number, @@ -436,7 +449,7 @@ export class DffParser extends RwFile { const boneVertexIndices: number[][] = []; const vertexWeights: number[][] = []; - const inverseBoneMatrices: number[][] = []; + const inverseBoneMatrices: RwMatrix4[] = []; for (let i = 0; i < vertexCount; i++) { const indices: number[] = []; @@ -455,10 +468,13 @@ export class DffParser extends RwFile { } for (let i = 0; i < boneCount; i++) { - const matrix4x4: number[] = []; - for(let j = 0; j < 16; j++) { - matrix4x4.push(this.readFloat()); - } + const matrix4x4: RwMatrix4 = { + right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() }, + up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() }, + at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() }, + transform: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat(), t: this.readFloat() }, + }; + inverseBoneMatrices.push(matrix4x4); } diff --git a/tests/dff/wuzimu.dff.test.ts b/tests/dff/wuzimu.dff.test.ts index 422db3f..1f287bd 100644 --- a/tests/dff/wuzimu.dff.test.ts +++ b/tests/dff/wuzimu.dff.test.ts @@ -169,44 +169,54 @@ describe('dff parsing - wuzimu', () => { expect(skin.boneVertexIndices[989][3]).toStrictEqual(0); }); - test('skin - inverse matrices', () => { + test('skin - inverse bone matrices', () => { const skin = rwDff.geometryList?.geometries[0].skin!; expect(skin.inverseBoneMatrices.length).toStrictEqual(32); - expect(skin.inverseBoneMatrices[0][0]).toBeCloseTo(1, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][1]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][2]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][3]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][4]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][5]).toBeCloseTo(1, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][6]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][7]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][8]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][9]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][10]).toBeCloseTo(1, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][11]).toBeCloseTo(1.8175872244174958e-20, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][12]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][13]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][14]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[0][15]).toBeCloseTo(1.485376372184306e-43, FLOATING_POINT_ERROR); - - expect(skin.inverseBoneMatrices[31][0]).toBeCloseTo(1, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][1]).toBeCloseTo(-9.176544324418501e-8, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][2]).toBeCloseTo(-9.58358531422121e-11, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][3]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][4]).toBeCloseTo( -9.313955162681964e-10, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][5]).toBeCloseTo(-1.2224200318655676e-8, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][6]).toBeCloseTo(-1, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][7]).toBeCloseTo(0, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][8]).toBeCloseTo( 8.127337736141271e-8, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][9]).toBeCloseTo(0.9999998807907104, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][10]).toBeCloseTo(-5.962812021920172e-9, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][11]).toBeCloseTo(1.8175872244174958e-20, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][12]).toBeCloseTo( -0.15182293951511383, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][13]).toBeCloseTo(1.0362001657485962, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][14]).toBeCloseTo(-0.1699587106704712, FLOATING_POINT_ERROR); - expect(skin.inverseBoneMatrices[31][15]).toBeCloseTo(1.485376372184306e-43, FLOATING_POINT_ERROR); + expect(skin.inverseBoneMatrices[0]).toStrictEqual( { + right: { + x: expect.closeTo(1, FLOATING_POINT_ERROR), + y: expect.closeTo(0, FLOATING_POINT_ERROR), + z: expect.closeTo(0, FLOATING_POINT_ERROR), + t: expect.closeTo(0, FLOATING_POINT_ERROR) }, + up: { + x: expect.closeTo(0, FLOATING_POINT_ERROR), + y: expect.closeTo(1, FLOATING_POINT_ERROR), + z: expect.closeTo(0, FLOATING_POINT_ERROR), + t: expect.closeTo(0, FLOATING_POINT_ERROR) }, + at: { + x: expect.closeTo(0, FLOATING_POINT_ERROR), + y: expect.closeTo(0, FLOATING_POINT_ERROR), + z: expect.closeTo(1, FLOATING_POINT_ERROR), + t: expect.closeTo(1.8175872244174958e-20, FLOATING_POINT_ERROR) }, + transform: { + x: expect.closeTo(0, FLOATING_POINT_ERROR), + y: expect.closeTo(0, FLOATING_POINT_ERROR), + z: expect.closeTo(0, FLOATING_POINT_ERROR), + t: expect.closeTo(1.485376372184306e-43, FLOATING_POINT_ERROR) } }); + + expect(skin.inverseBoneMatrices[31]).toStrictEqual( { + right: { + x: expect.closeTo(1, FLOATING_POINT_ERROR), + y: expect.closeTo(-9.176544324418501e-8, FLOATING_POINT_ERROR), + z: expect.closeTo(-9.58358531422121e-11, FLOATING_POINT_ERROR), + t: expect.closeTo(0, FLOATING_POINT_ERROR) }, + up: { + x: expect.closeTo(-9.313955162681964e-10, FLOATING_POINT_ERROR), + y: expect.closeTo(-1.2224200318655676e-8, FLOATING_POINT_ERROR), + z: expect.closeTo(-1, FLOATING_POINT_ERROR), + t: expect.closeTo(0, FLOATING_POINT_ERROR) }, + at: { + x: expect.closeTo(8.127337736141271e-8, FLOATING_POINT_ERROR), + y: expect.closeTo(0.9999998807907104, FLOATING_POINT_ERROR), + z: expect.closeTo(-5.962812021920172e-9, FLOATING_POINT_ERROR), + t: expect.closeTo(1.8175872244174958e-20, FLOATING_POINT_ERROR) }, + transform: { + x: expect.closeTo(-0.15182293951511383, FLOATING_POINT_ERROR), + y: expect.closeTo(1.0362001657485962, FLOATING_POINT_ERROR), + z: expect.closeTo(-0.1699587106704712, FLOATING_POINT_ERROR), + t: expect.closeTo(1.485376372184306e-43, FLOATING_POINT_ERROR) } }); }); From 4b98d33ee2f9d4630b5fd82cb8d8c78060a539c6 Mon Sep 17 00:00:00 2001 From: Anders Date: Sat, 7 Jun 2025 13:22:31 +0500 Subject: [PATCH 07/28] Refactor bone structures --- src/renderware/dff/DffParser.ts | 75 ++++++++++++++++----------------- tests/dff/wuzimu.dff.test.ts | 40 +++++++++--------- 2 files changed, 56 insertions(+), 59 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 8e1a264..1f3cea9 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -10,7 +10,7 @@ export interface RwDff { frameList: RwFrameList | null, atomics: number[], dummies: string[], - bones: RwBone[], + animNodes: RwAnimNode[], } export interface RwClump { @@ -19,13 +19,19 @@ export interface RwClump { cameraCount?: number, } +export interface RwAnimNode { + boneId: number, + bonesCount: number, + bones: RwBone[], +} + export interface RwBone { boneId: number, - boneCount: number, - flags?: number, - bones?: RwBone[], + boneIndex: number, + flags: number, } + export interface RwFrame { rotationMatrix: RwMatrix3, coordinatesOffset: RwVector3, @@ -168,7 +174,7 @@ export class DffParser extends RwFile { let versionNumber: number | undefined; let atomics: number[] = []; let dummies: string[] = []; - let bones: RwBone[] = []; + let animNodes: RwAnimNode[] = []; let geometryList: RwGeometryList | null = null; let frameList: RwFrameList | null = null; @@ -199,7 +205,7 @@ export class DffParser extends RwFile { dummies.push(this.readString(extensionHeader.sectionSize)); break; case RwSections.RwAnim: - bones.push(this.readBones()); + animNodes.push(this.readAnimNode()); break; default: console.debug(`Extension type ${extensionHeader.sectionType} (${extensionHeader.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${extensionHeader.sectionSize} bytes.`); @@ -220,7 +226,7 @@ export class DffParser extends RwFile { break; case RwSections.RwAnim: // For III / VC models - bones.push(this.readBones()); + animNodes.push(this.readAnimNode()); break; default: console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`); @@ -240,7 +246,7 @@ export class DffParser extends RwFile { frameList: frameList, atomics: atomics, dummies: dummies, - bones: bones, + animNodes: animNodes, }; } @@ -488,39 +494,32 @@ export class DffParser extends RwFile { } } - public readBones() { - this.skip(4); // Skipping hAnimVersion property (0x100) - const boneId = this.readInt32(); - const boneCount = this.readInt32(); + public readAnimNode() :RwAnimNode { + this.skip(4); // Skipping AnimVersion property (0x100) + const boneId = this.readInt32(); + const boneCount = this.readInt32(); + const bones :RwBone[] = []; - if(boneId == 0) { - this.skip(8); // Skipping flags and keyFrameSize properties - } + if(boneId == 0) { + this.skip(8); // Skipping flags and keyFrameSize properties + } + + if(boneCount > 0) { + for(let i = 0; i < boneCount; i++){ + bones.push({ + boneId: this.readInt32(), + boneIndex: this.readInt32(), + flags: this.readInt32() + }); + } + } - if(boneCount == 0) { return { - boneId, - boneCount, - } - } - - const bones : RwBone[] = []; - - for(let i = 0; i < boneCount; i++) { - const bone = { - boneId: this.readInt32(), - boneCount: this.readInt32(), - flags: this.readInt32(), - }; - bones.push(bone); - } - - return { - boneId, - boneCount, - bones, - } - } + boneId: boneId, + bonesCount: boneCount, + bones: bones + } + } public readMesh(): RwMesh { const indexCount = this.readUint32(); diff --git a/tests/dff/wuzimu.dff.test.ts b/tests/dff/wuzimu.dff.test.ts index 1f287bd..80d6f24 100644 --- a/tests/dff/wuzimu.dff.test.ts +++ b/tests/dff/wuzimu.dff.test.ts @@ -90,33 +90,31 @@ describe('dff parsing - wuzimu', () => { at: { x: 0, y: 0, z: 1} }); }); - test('bones - length', () => { - expect(rwDff.bones.length).toStrictEqual(32); - expect(rwDff.bones[0].bones!.length).toStrictEqual(32); + expect(rwDff.animNodes.length).toStrictEqual(32); + expect(rwDff.animNodes[0].bones.length).toStrictEqual(32); }); - + test('bones - ids', () => { - expect(rwDff.bones[0].boneId).toStrictEqual(0); - expect(rwDff.bones[1].boneId).toStrictEqual(1); - expect(rwDff.bones[2].boneId).toStrictEqual(51); - expect(rwDff.bones[3].boneId).toStrictEqual(41); - expect(rwDff.bones[15].boneId).toStrictEqual(8); - expect(rwDff.bones[30].boneId).toStrictEqual(53); - expect(rwDff.bones[31].boneId).toStrictEqual(54); - - expect(rwDff.bones[0].bones![0].boneId).toStrictEqual(0); - expect(rwDff.bones[0].bones![2].boneId).toStrictEqual(2); - expect(rwDff.bones[0].bones![7].boneId).toStrictEqual(6); - expect(rwDff.bones[0].bones![18].boneId).toStrictEqual(24); - expect(rwDff.bones[0].bones![31].boneId).toStrictEqual(54); + expect(rwDff.animNodes[0].boneId).toStrictEqual(0); + expect(rwDff.animNodes[1].boneId).toStrictEqual(1); + expect(rwDff.animNodes[2].boneId).toStrictEqual(51); + expect(rwDff.animNodes[3].boneId).toStrictEqual(41); + expect(rwDff.animNodes[15].boneId).toStrictEqual(8); + expect(rwDff.animNodes[30].boneId).toStrictEqual(53); + expect(rwDff.animNodes[31].boneId).toStrictEqual(54); + expect(rwDff.animNodes[0].bones[0].boneId).toStrictEqual(0); + expect(rwDff.animNodes[0].bones[2].boneId).toStrictEqual(2); + expect(rwDff.animNodes[0].bones[7].boneId).toStrictEqual(6); + expect(rwDff.animNodes[0].bones[18].boneId).toStrictEqual(24); + expect(rwDff.animNodes[0].bones[31].boneId).toStrictEqual(54); }); test('bones - bone count', () => { - expect(rwDff.bones[0].boneCount).toStrictEqual(32); - expect(rwDff.bones[3].boneCount).toStrictEqual(0); - expect(rwDff.bones[15].boneCount).toStrictEqual(0); - expect(rwDff.bones[31].boneCount).toStrictEqual(0); + expect(rwDff.animNodes[0].bonesCount).toStrictEqual(32); + expect(rwDff.animNodes[3].bonesCount).toStrictEqual(0); + expect(rwDff.animNodes[15].bonesCount).toStrictEqual(0); + expect(rwDff.animNodes[31].bonesCount).toStrictEqual(0); ; }); From 5dacb9ccf56645cd02deb20b482cf86bd9e68452 Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 10 Jun 2025 19:27:49 +0500 Subject: [PATCH 08/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 1f3cea9..7998316 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -504,7 +504,7 @@ export class DffParser extends RwFile { this.skip(8); // Skipping flags and keyFrameSize properties } - if(boneCount > 0) { + if (boneCount > 0) { for(let i = 0; i < boneCount; i++){ bones.push({ boneId: this.readInt32(), From 8bcc51d23097255e5dde4785dfcb86ac78197632 Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 10 Jun 2025 19:28:02 +0500 Subject: [PATCH 09/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 7998316..1def8cb 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -401,7 +401,7 @@ export class DffParser extends RwFile { let binMesh = this.readBinMesh(); let skin = undefined; - if(this.readSectionHeader().sectionType == RwSections.RwSkin) { + if (this.readSectionHeader().sectionType == RwSections.RwSkin) { skin = this.readSkin(vertexCount); } From ba883e18c53142e3f7e431d8ec7be59166ea356e Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 10 Jun 2025 19:28:13 +0500 Subject: [PATCH 10/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 1def8cb..4a080e9 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -459,7 +459,7 @@ export class DffParser extends RwFile { for (let i = 0; i < vertexCount; i++) { const indices: number[] = []; - for(let j = 0; j < 4; j++) { + for (let j = 0; j < 4; j++) { indices.push(this.readUint8()); } boneVertexIndices.push(indices); From bf249f7be1ca07b9102ee53c7fd15451e1eeacc3 Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 10 Jun 2025 19:28:23 +0500 Subject: [PATCH 11/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 4a080e9..8e0efbc 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -467,7 +467,7 @@ export class DffParser extends RwFile { for (let i = 0; i < vertexCount; i++) { const weights: number[] = []; - for(let j = 0; j < 4; j++) { + for (let j = 0; j < 4; j++) { weights.push(this.readFloat()); } vertexWeights.push(weights); From 38164e0be3763527518f9d332cb242bfec389c32 Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 10 Jun 2025 19:28:32 +0500 Subject: [PATCH 12/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 8e0efbc..0aee6fe 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -500,9 +500,9 @@ export class DffParser extends RwFile { const boneCount = this.readInt32(); const bones :RwBone[] = []; - if(boneId == 0) { + if (boneId == 0) { this.skip(8); // Skipping flags and keyFrameSize properties - } + } if (boneCount > 0) { for(let i = 0; i < boneCount; i++){ From 0b5587d6d16c987ffa85e90f3b20449d67d7a3f7 Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 10 Jun 2025 19:28:41 +0500 Subject: [PATCH 13/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 0aee6fe..e4af3bd 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -505,7 +505,7 @@ export class DffParser extends RwFile { } if (boneCount > 0) { - for(let i = 0; i < boneCount; i++){ + for (let i = 0; i < boneCount; i++){ bones.push({ boneId: this.readInt32(), boneIndex: this.readInt32(), From b794834fbb89280d44018bcf6d1a0ce6ed50432c Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 26 Aug 2025 22:32:07 +0500 Subject: [PATCH 14/28] Update gitignore & package.json --- .gitignore | 8 + package-lock.json | 1162 +++++++++++++++++++++++---------------------- package.json | 3 +- 3 files changed, 602 insertions(+), 571 deletions(-) diff --git a/.gitignore b/.gitignore index 38e65a0..962160c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,11 @@ node_modules output coverage + +#-------- +main.ts +*.js +timic/ +tests/ +.vscode/launch.json +package.json diff --git a/package-lock.json b/package-lock.json index 3da2435..3cba752 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -45,9 +46,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "dev": true, "license": "MIT", "engines": { @@ -55,22 +56,22 @@ } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -86,16 +87,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -119,6 +120,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", @@ -192,27 +203,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", + "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -476,28 +487,28 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -516,21 +527,21 @@ "license": "MIT" }, "node_modules/@emnapi/core": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz", - "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.5.tgz", + "integrity": "sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.0.2", + "@emnapi/wasi-threads": "1.0.4", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", - "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", + "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", "dev": true, "license": "MIT", "optional": true, @@ -539,9 +550,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.2.tgz", - "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.4.tgz", + "integrity": "sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==", "dev": true, "license": "MIT", "optional": true, @@ -589,22 +600,23 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@jest/console": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.0.tgz", - "integrity": "sha512-vfpJap6JZQ3I8sUN8dsFqNAKJYO4KIGxkcB+3Fw7Q/BJiWY5HwtMMiuT1oP0avsiDhjE/TCLaDgbGfHwDdBVeg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.5.tgz", + "integrity": "sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.0", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.0.0", - "jest-util": "30.0.0", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0" }, "engines": { @@ -612,39 +624,39 @@ } }, "node_modules/@jest/core": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.0.tgz", - "integrity": "sha512-1zU39zFtWSl5ZuDK3Rd6P8S28MmS4F11x6Z4CURrgJ99iaAJg68hmdJ2SAHEEO6ociaNk43UhUYtHxWKEWoNYw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.0.5.tgz", + "integrity": "sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.0.0", - "@jest/pattern": "30.0.0", - "@jest/reporters": "30.0.0", - "@jest/test-result": "30.0.0", - "@jest/transform": "30.0.0", - "@jest/types": "30.0.0", + "@jest/console": "30.0.5", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.0", - "jest-config": "30.0.0", - "jest-haste-map": "30.0.0", - "jest-message-util": "30.0.0", - "jest-regex-util": "30.0.0", - "jest-resolve": "30.0.0", - "jest-resolve-dependencies": "30.0.0", - "jest-runner": "30.0.0", - "jest-runtime": "30.0.0", - "jest-snapshot": "30.0.0", - "jest-util": "30.0.0", - "jest-validate": "30.0.0", - "jest-watcher": "30.0.0", + "jest-changed-files": "30.0.5", + "jest-config": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.5", + "jest-resolve-dependencies": "30.0.5", + "jest-runner": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", + "jest-watcher": "30.0.5", "micromatch": "^4.0.8", - "pretty-format": "30.0.0", + "pretty-format": "30.0.5", "slash": "^3.0.0" }, "engines": { @@ -660,9 +672,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.0.tgz", - "integrity": "sha512-xMbtoCeKJDto86GW6AiwVv7M4QAuI56R7dVBr1RNGYbOT44M2TIzOiske2RxopBqkumDY+A1H55pGvuribRY9A==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, "license": "MIT", "engines": { @@ -670,70 +682,70 @@ } }, "node_modules/@jest/environment": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.0.tgz", - "integrity": "sha512-09sFbMMgS5JxYnvgmmtwIHhvoyzvR5fUPrVl8nOCrC5KdzmmErTcAxfWyAhJ2bv3rvHNQaKiS+COSG+O7oNbXw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.0.5.tgz", + "integrity": "sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.0.0", - "@jest/types": "30.0.0", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "30.0.0" + "jest-mock": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.0.tgz", - "integrity": "sha512-XZ3j6syhMeKiBknmmc8V3mNIb44kxLTbOQtaXA4IFdHy+vEN0cnXRzbRjdGBtrp4k1PWyMWNU3Fjz3iejrhpQg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.0.0", - "jest-snapshot": "30.0.0" + "expect": "30.0.5", + "jest-snapshot": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.0.tgz", - "integrity": "sha512-UiWfsqNi/+d7xepfOv8KDcbbzcYtkWBe3a3kVDtg6M1kuN6CJ7b4HzIp5e1YHrSaQaVS8sdCoyCMCZClTLNKFQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.0.5.tgz", + "integrity": "sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.0" + "@jest/get-type": "30.0.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.0.tgz", - "integrity": "sha512-yzBmJcrMHAMcAEbV2w1kbxmx8WFpEz8Cth3wjLMSkq+LO8VeGKRhpr5+BUp7PPK+x4njq/b6mVnDR8e/tPL5ng==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.0.5.tgz", + "integrity": "sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.0", + "@jest/types": "30.0.5", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "30.0.0", - "jest-mock": "30.0.0", - "jest-util": "30.0.0" + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/get-type": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.0.tgz", - "integrity": "sha512-VZWMjrBzqfDKngQ7sUctKeLxanAbsBFoZnPxNIG6CmxK7Gv6K44yqd0nzveNIBfuhGZMmk1n5PGbvdSTOu0yTg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.0.1.tgz", + "integrity": "sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==", "dev": true, "license": "MIT", "engines": { @@ -741,47 +753,47 @@ } }, "node_modules/@jest/globals": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.0.tgz", - "integrity": "sha512-OEzYes5A1xwBJVMPqFRa8NCao8Vr42nsUZuf/SpaJWoLE+4kyl6nCQZ1zqfipmCrIXQVALC5qJwKy/7NQQLPhw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.0.5.tgz", + "integrity": "sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.0.0", - "@jest/expect": "30.0.0", - "@jest/types": "30.0.0", - "jest-mock": "30.0.0" + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/types": "30.0.5", + "jest-mock": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/pattern": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.0.tgz", - "integrity": "sha512-k+TpEThzLVXMkbdxf8KHjZ83Wl+G54ytVJoDIGWwS96Ql4xyASRjc6SU1hs5jHVql+hpyK9G8N7WuFhLpGHRpQ==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-regex-util": "30.0.0" + "jest-regex-util": "30.0.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.0.tgz", - "integrity": "sha512-5WHNlLO0Ok+/o6ML5IzgVm1qyERtLHBNhwn67PAq92H4hZ+n5uW/BYj1VVwmTdxIcNrZLxdV9qtpdZkXf16HxA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.5.tgz", + "integrity": "sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.0.0", - "@jest/test-result": "30.0.0", - "@jest/transform": "30.0.0", - "@jest/types": "30.0.0", + "@jest/console": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -794,9 +806,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.0.0", - "jest-util": "30.0.0", - "jest-worker": "30.0.0", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -814,9 +826,9 @@ } }, "node_modules/@jest/schemas": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.0.tgz", - "integrity": "sha512-NID2VRyaEkevCRz6badhfqYwri/RvMbiHY81rk3AkK/LaiB0LSxi1RdVZ7MpZdTjNugtZeGfpL0mLs9Kp3MrQw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", "dependencies": { @@ -827,13 +839,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.0.tgz", - "integrity": "sha512-C/QSFUmvZEYptg2Vin84FggAphwHvj6la39vkw1CNOZQORWZ7O/H0BXmdeeeGnvlXDYY8TlFM5jgFnxLAxpFjA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.0.5.tgz", + "integrity": "sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.0", + "@jest/types": "30.0.5", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -843,9 +855,9 @@ } }, "node_modules/@jest/source-map": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.0.tgz", - "integrity": "sha512-oYBJ4d/NF4ZY3/7iq1VaeoERHRvlwKtrGClgescaXMIa1mmb+vfJd0xMgbW9yrI80IUA7qGbxpBWxlITrHkWoA==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { @@ -858,14 +870,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.0.tgz", - "integrity": "sha512-685zco9HdgBaaWiB9T4xjLtBuN0Q795wgaQPpmuAeZPHwHZSoKFAUnozUtU+ongfi4l5VCz8AclOE5LAQdyjxQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.5.tgz", + "integrity": "sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.0.0", - "@jest/types": "30.0.0", + "@jest/console": "30.0.5", + "@jest/types": "30.0.5", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -874,15 +886,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.0.tgz", - "integrity": "sha512-Hmvv5Yg6UmghXIcVZIydkT0nAK7M/hlXx9WMHR5cLVwdmc14/qUQt3mC72T6GN0olPC6DhmKE6Cd/pHsgDbuqQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.0.5.tgz", + "integrity": "sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.0", + "@jest/test-result": "30.0.5", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.0", + "jest-haste-map": "30.0.5", "slash": "^3.0.0" }, "engines": { @@ -890,23 +902,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.0.tgz", - "integrity": "sha512-8xhpsCGYJsUjqpJOgLyMkeOSSlhqggFZEWAnZquBsvATtueoEs7CkMRxOUmJliF3E5x+mXmZ7gEEsHank029Og==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.5.tgz", + "integrity": "sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.0", + "@jest/types": "30.0.5", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.0", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.0", - "jest-regex-util": "30.0.0", - "jest-util": "30.0.0", + "jest-haste-map": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", @@ -917,14 +929,14 @@ } }, "node_modules/@jest/types": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.0.tgz", - "integrity": "sha512-1Nox8mAL52PKPfEnUQWBvKU/bp8FTT6AiDu76bFDEJj/qsRFSAVSldfCH3XYMqialti2zHXKvD5gN0AaHc0yKA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.0.0", - "@jest/schemas": "30.0.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -936,17 +948,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -954,46 +963,40 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.11.tgz", - "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.9.0" + "@tybys/wasm-util": "^0.10.0" } }, "node_modules/@pkgjs/parseargs": { @@ -1008,9 +1011,9 @@ } }, "node_modules/@pkgr/core": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.7.tgz", - "integrity": "sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, "license": "MIT", "engines": { @@ -1021,9 +1024,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.34.35", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.35.tgz", - "integrity": "sha512-C6ypdODf2VZkgRT6sFM8E1F8vR+HcffniX0Kp8MsU8PIfrlXbNCBz0jzj17GjdmjTx1OtZzdH8+iALL21UjF5A==", + "version": "0.34.38", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.38.tgz", + "integrity": "sha512-HpkxMmc2XmZKhvaKIZZThlHmx1L0I/V1hWK1NubtlFnr6ZqdiOpV72TKudZUNQjZNsyDBay72qFEhEvb+bcwcA==", "dev": true, "license": "MIT" }, @@ -1048,9 +1051,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", "dev": true, "license": "MIT", "optional": true, @@ -1107,13 +1110,15 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -1123,6 +1128,7 @@ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } @@ -1139,9 +1145,9 @@ } }, "node_modules/@types/node": { - "version": "24.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", - "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.1.0.tgz", + "integrity": "sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==", "dev": true, "license": "MIT", "dependencies": { @@ -1180,9 +1186,9 @@ "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.0.tgz", - "integrity": "sha512-h1T2c2Di49ekF2TE8ZCoJkb+jwETKUIPDJ/nO3tJBKlLFPu+fyd93f0rGP/BvArKx2k2HlRM4kqkNarj3dvZlg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", "cpu": [ "arm" ], @@ -1194,9 +1200,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.0.tgz", - "integrity": "sha512-sG1NHtgXtX8owEkJ11yn34vt0Xqzi3k9TJ8zppDmyG8GZV4kVWw44FHwKwHeEFl07uKPeC4ZoyuQaGh5ruJYPA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", "cpu": [ "arm64" ], @@ -1208,9 +1214,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.0.tgz", - "integrity": "sha512-nJ9z47kfFnCxN1z/oYZS7HSNsFh43y2asePzTEZpEvK7kGyuShSl3RRXnm/1QaqFL+iP+BjMwuB+DYUymOkA5A==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", "cpu": [ "arm64" ], @@ -1222,9 +1228,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.0.tgz", - "integrity": "sha512-TK+UA1TTa0qS53rjWn7cVlEKVGz2B6JYe0C++TdQjvWYIyx83ruwh0wd4LRxYBM5HeuAzXcylA9BH2trARXJTw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", "cpu": [ "x64" ], @@ -1236,9 +1242,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.0.tgz", - "integrity": "sha512-6uZwzMRFcD7CcCd0vz3Hp+9qIL2jseE/bx3ZjaLwn8t714nYGwiE84WpaMCYjU+IQET8Vu/+BNAGtYD7BG/0yA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", "cpu": [ "x64" ], @@ -1250,9 +1256,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.0.tgz", - "integrity": "sha512-bPUBksQfrgcfv2+mm+AZinaKq8LCFvt5PThYqRotqSuuZK1TVKkhbVMS/jvSRfYl7jr3AoZLYbDkItxgqMKRkg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", "cpu": [ "arm" ], @@ -1264,9 +1270,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.0.tgz", - "integrity": "sha512-uT6E7UBIrTdCsFQ+y0tQd3g5oudmrS/hds5pbU3h4s2t/1vsGWbbSKhBSCD9mcqaqkBwoqlECpUrRJCmldl8PA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", "cpu": [ "arm" ], @@ -1278,9 +1284,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.0.tgz", - "integrity": "sha512-vdqBh911wc5awE2bX2zx3eflbyv8U9xbE/jVKAm425eRoOVv/VseGZsqi3A3SykckSpF4wSROkbQPvbQFn8EsA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", "cpu": [ "arm64" ], @@ -1292,9 +1298,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.0.tgz", - "integrity": "sha512-/8JFZ/SnuDr1lLEVsxsuVwrsGquTvT51RZGvyDB/dOK3oYK2UqeXzgeyq6Otp8FZXQcEYqJwxb9v+gtdXn03eQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", "cpu": [ "arm64" ], @@ -1306,9 +1312,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.0.tgz", - "integrity": "sha512-FkJjybtrl+rajTw4loI3L6YqSOpeZfDls4SstL/5lsP2bka9TiHUjgMBjygeZEis1oC8LfJTS8FSgpKPaQx2tQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", "cpu": [ "ppc64" ], @@ -1320,9 +1326,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.0.tgz", - "integrity": "sha512-w/NZfHNeDusbqSZ8r/hp8iL4S39h4+vQMc9/vvzuIKMWKppyUGKm3IST0Qv0aOZ1rzIbl9SrDeIqK86ZpUK37w==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", "cpu": [ "riscv64" ], @@ -1334,9 +1340,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.0.tgz", - "integrity": "sha512-bEPBosut8/8KQbUixPry8zg/fOzVOWyvwzOfz0C0Rw6dp+wIBseyiHKjkcSyZKv/98edrbMknBaMNJfA/UEdqw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", "cpu": [ "riscv64" ], @@ -1348,9 +1354,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.0.tgz", - "integrity": "sha512-LDtMT7moE3gK753gG4pc31AAqGUC86j3AplaFusc717EUGF9ZFJ356sdQzzZzkBk1XzMdxFyZ4f/i35NKM/lFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", "cpu": [ "s390x" ], @@ -1362,9 +1368,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.0.tgz", - "integrity": "sha512-WmFd5KINHIXj8o1mPaT8QRjA9HgSXhN1gl9Da4IZihARihEnOylu4co7i/yeaIpcfsI6sYs33cNZKyHYDh0lrA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", "cpu": [ "x64" ], @@ -1376,9 +1382,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.0.tgz", - "integrity": "sha512-CYuXbANW+WgzVRIl8/QvZmDaZxrqvOldOwlbUjIM4pQ46FJ0W5cinJ/Ghwa/Ng1ZPMJMk1VFdsD/XwmCGIXBWg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", "cpu": [ "x64" ], @@ -1390,9 +1396,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.0.tgz", - "integrity": "sha512-6Rp2WH0OoitMYR57Z6VE8Y6corX8C6QEMWLgOV6qXiJIeZ1F9WGXY/yQ8yDC4iTraotyLOeJ2Asea0urWj2fKQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", "cpu": [ "wasm32" ], @@ -1407,9 +1413,9 @@ } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.0.tgz", - "integrity": "sha512-rknkrTRuvujprrbPmGeHi8wYWxmNVlBoNW8+4XF2hXUnASOjmuC9FNF1tGbDiRQWn264q9U/oGtixyO3BT8adQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", "cpu": [ "arm64" ], @@ -1421,9 +1427,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.0.tgz", - "integrity": "sha512-Ceymm+iBl+bgAICtgiHyMLz6hjxmLJKqBim8tDzpX61wpZOx2bPK6Gjuor7I2RiUynVjvvkoRIkrPyMwzBzF3A==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", "cpu": [ "ia32" ], @@ -1435,9 +1441,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.0.tgz", - "integrity": "sha512-k59o9ZyeyS0hAlcaKFezYSH2agQeRFEB7KoQLXl3Nb3rgkqT1NY9Vwy+SqODiLmYnEjxWJVRE/yq2jFVqdIxZw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", "cpu": [ "x64" ], @@ -1482,6 +1488,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1497,6 +1504,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1523,16 +1531,16 @@ "license": "MIT" }, "node_modules/babel-jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.0.tgz", - "integrity": "sha512-JQ0DhdFjODbSawDf0026uZuwaqfKkQzk+9mwWkq2XkKFIaMhFVOxlVmbFCOnnC76jATdxrff3IiUAvOAJec6tw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.0.5.tgz", + "integrity": "sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.0.0", + "@jest/transform": "30.0.5", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.0", + "babel-preset-jest": "30.0.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -1562,9 +1570,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.0.tgz", - "integrity": "sha512-DSRm+US/FCB4xPDD6Rnslb6PAF9Bej1DZ+1u4aTiqJnk7ZX12eHsnDiIOqjGvITCq+u6wLqUhgS+faCNbVY8+g==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", + "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1604,13 +1612,13 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.0.tgz", - "integrity": "sha512-hgEuu/W7gk8QOWUA9+m3Zk+WpGvKc1Egp6rFQEfYxEoM9Fk/q8nuTXNL65OkhwGrTApauEGgakOoWVXj+UfhKw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", + "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.0.0", + "babel-plugin-jest-hoist": "30.0.1", "babel-preset-current-node-syntax": "^1.1.0" }, "engines": { @@ -1651,9 +1659,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -1671,8 +1679,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -1688,6 +1696,7 @@ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, + "license": "MIT", "dependencies": { "fast-json-stable-stringify": "2.x" }, @@ -1700,6 +1709,7 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" } @@ -1732,9 +1742,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "version": "1.0.30001727", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", + "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", "dev": true, "funding": [ { @@ -1757,6 +1767,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1779,9 +1790,9 @@ } }, "node_modules/ci-info": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", - "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", "dev": true, "funding": [ { @@ -1902,6 +1913,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1913,7 +1925,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", @@ -1926,7 +1939,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -1944,12 +1958,13 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -2019,9 +2034,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.167", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz", - "integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==", + "version": "1.5.191", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz", + "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==", "dev": true, "license": "ISC" }, @@ -2113,6 +2128,13 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, "node_modules/exit-x": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", @@ -2124,18 +2146,18 @@ } }, "node_modules/expect": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.0.tgz", - "integrity": "sha512-xCdPp6gwiR9q9lsPCHANarIkFTN/IMZso6Kkq03sOm9IIGtzK/UJqml0dkhHibGh8HKOj8BIDIpZ0BZuU7QK6w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.0.5.tgz", + "integrity": "sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.0.0", - "@jest/get-type": "30.0.0", - "jest-matcher-utils": "30.0.0", - "jest-message-util": "30.0.0", - "jest-mock": "30.0.0", - "jest-util": "30.0.0" + "@jest/expect-utils": "30.0.5", + "@jest/get-type": "30.0.1", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2145,13 +2167,15 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" } @@ -2197,6 +2221,7 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -2222,19 +2247,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2248,6 +2260,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2261,6 +2274,7 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -2319,27 +2333,19 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2472,6 +2478,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -2610,16 +2617,16 @@ } }, "node_modules/jest": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.0.tgz", - "integrity": "sha512-/3G2iFwsUY95vkflmlDn/IdLyLWqpQXcftptooaPH4qkyU52V7qVYf1BjmdSPlp1+0fs6BmNtrGaSFwOfV07ew==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.0.5.tgz", + "integrity": "sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.0.0", - "@jest/types": "30.0.0", + "@jest/core": "30.0.5", + "@jest/types": "30.0.5", "import-local": "^3.2.0", - "jest-cli": "30.0.0" + "jest-cli": "30.0.5" }, "bin": { "jest": "bin/jest.js" @@ -2637,14 +2644,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.0.tgz", - "integrity": "sha512-rzGpvCdPdEV1Ma83c1GbZif0L2KAm3vXSXGRlpx7yCt0vhruwCNouKNRh3SiVcISHP1mb3iJzjb7tAEnNu1laQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", + "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.0.0", + "jest-util": "30.0.5", "p-limit": "^3.1.0" }, "engines": { @@ -2652,29 +2659,29 @@ } }, "node_modules/jest-circus": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.0.tgz", - "integrity": "sha512-nTwah78qcKVyndBS650hAkaEmwWGaVsMMoWdJwMnH77XArRJow2Ir7hc+8p/mATtxVZuM9OTkA/3hQocRIK5Dw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.0.5.tgz", + "integrity": "sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.0.0", - "@jest/expect": "30.0.0", - "@jest/test-result": "30.0.0", - "@jest/types": "30.0.0", + "@jest/environment": "30.0.5", + "@jest/expect": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.0.0", - "jest-matcher-utils": "30.0.0", - "jest-message-util": "30.0.0", - "jest-runtime": "30.0.0", - "jest-snapshot": "30.0.0", - "jest-util": "30.0.0", + "jest-each": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-runtime": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "p-limit": "^3.1.0", - "pretty-format": "30.0.0", + "pretty-format": "30.0.5", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -2684,21 +2691,21 @@ } }, "node_modules/jest-cli": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.0.tgz", - "integrity": "sha512-fWKAgrhlwVVCfeizsmIrPRTBYTzO82WSba3gJniZNR3PKXADgdC0mmCSK+M+t7N8RCXOVfY6kvCkvjUNtzmHYQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.0.5.tgz", + "integrity": "sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.0.0", - "@jest/test-result": "30.0.0", - "@jest/types": "30.0.0", + "@jest/core": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.0.0", - "jest-util": "30.0.0", - "jest-validate": "30.0.0", + "jest-config": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "yargs": "^17.7.2" }, "bin": { @@ -2717,34 +2724,34 @@ } }, "node_modules/jest-config": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.0.tgz", - "integrity": "sha512-p13a/zun+sbOMrBnTEUdq/5N7bZMOGd1yMfqtAJniPNuzURMay4I+vxZLK1XSDbjvIhmeVdG8h8RznqYyjctyg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.0.5.tgz", + "integrity": "sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/get-type": "30.0.0", - "@jest/pattern": "30.0.0", - "@jest/test-sequencer": "30.0.0", - "@jest/types": "30.0.0", - "babel-jest": "30.0.0", + "@jest/get-type": "30.0.1", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.0.5", + "@jest/types": "30.0.5", + "babel-jest": "30.0.5", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.0.0", - "jest-docblock": "30.0.0", - "jest-environment-node": "30.0.0", - "jest-regex-util": "30.0.0", - "jest-resolve": "30.0.0", - "jest-runner": "30.0.0", - "jest-util": "30.0.0", - "jest-validate": "30.0.0", + "jest-circus": "30.0.5", + "jest-docblock": "30.0.1", + "jest-environment-node": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.5", + "jest-runner": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "30.0.0", + "pretty-format": "30.0.5", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -2769,25 +2776,25 @@ } }, "node_modules/jest-diff": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.0.tgz", - "integrity": "sha512-TgT1+KipV8JTLXXeFX0qSvIJR/UXiNNojjxb/awh3vYlBZyChU/NEmyKmq+wijKjWEztyrGJFL790nqMqNjTHA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.0.5.tgz", + "integrity": "sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.0.0", - "@jest/get-type": "30.0.0", + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "pretty-format": "30.0.0" + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.0.tgz", - "integrity": "sha512-By/iQ0nvTzghEecGzUMCp1axLtBh+8wB4Hpoi5o+x1stycjEmPcH1mHugL4D9Q+YKV++vKeX/3ZTW90QC8ICPg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", + "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", "dev": true, "license": "MIT", "dependencies": { @@ -2798,56 +2805,56 @@ } }, "node_modules/jest-each": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.0.tgz", - "integrity": "sha512-qkFEW3cfytEjG2KtrhwtldZfXYnWSanO8xUMXLe4A6yaiHMHJUalk0Yyv4MQH6aeaxgi4sGVrukvF0lPMM7U1w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.0.5.tgz", + "integrity": "sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.0", - "@jest/types": "30.0.0", + "@jest/get-type": "30.0.1", + "@jest/types": "30.0.5", "chalk": "^4.1.2", - "jest-util": "30.0.0", - "pretty-format": "30.0.0" + "jest-util": "30.0.5", + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.0.tgz", - "integrity": "sha512-sF6lxyA25dIURyDk4voYmGU9Uwz2rQKMfjxKnDd19yk+qxKGrimFqS5YsPHWTlAVBo+YhWzXsqZoaMzrTFvqfg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.0.5.tgz", + "integrity": "sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.0.0", - "@jest/fake-timers": "30.0.0", - "@jest/types": "30.0.0", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "30.0.0", - "jest-util": "30.0.0", - "jest-validate": "30.0.0" + "jest-mock": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.0.tgz", - "integrity": "sha512-p4bXAhXTawTsADgQgTpbymdLaTyPW1xWNu1oIGG7/N3LIAbZVkH2JMJqS8/IUcnGR8Kc7WFE+vWbJvsqGCWZXw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.5.tgz", + "integrity": "sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.0", + "@jest/types": "30.0.5", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.0", - "jest-util": "30.0.0", - "jest-worker": "30.0.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", + "jest-worker": "30.0.5", "micromatch": "^4.0.8", "walker": "^1.0.8" }, @@ -2859,49 +2866,49 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.0.tgz", - "integrity": "sha512-E/ly1azdVVbZrS0T6FIpyYHvsdek4FNaThJTtggjV/8IpKxh3p9NLndeUZy2+sjAI3ncS+aM0uLLon/dBg8htA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.0.5.tgz", + "integrity": "sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.0", - "pretty-format": "30.0.0" + "@jest/get-type": "30.0.1", + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.0.tgz", - "integrity": "sha512-m5mrunqopkrqwG1mMdJxe1J4uGmS9AHHKYUmoxeQOxBcLjEvirIrIDwuKmUYrecPHVB/PUBpXs2gPoeA2FSSLQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.0.5.tgz", + "integrity": "sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.0", + "@jest/get-type": "30.0.1", "chalk": "^4.1.2", - "jest-diff": "30.0.0", - "pretty-format": "30.0.0" + "jest-diff": "30.0.5", + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.0.tgz", - "integrity": "sha512-pV3qcrb4utEsa/U7UI2VayNzSDQcmCllBZLSoIucrESRu0geKThFZOjjh0kACDJFJRAQwsK7GVsmS6SpEceD8w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.5.tgz", + "integrity": "sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.0", + "@jest/types": "30.0.5", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", - "pretty-format": "30.0.0", + "pretty-format": "30.0.5", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -2910,15 +2917,15 @@ } }, "node_modules/jest-mock": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.0.tgz", - "integrity": "sha512-W2sRA4ALXILrEetEOh2ooZG6fZ01iwVs0OWMKSSWRcUlaLr4ESHuiKXDNTg+ZVgOq8Ei5445i/Yxrv59VT+XkA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.0", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-util": "30.0.0" + "jest-util": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2943,9 +2950,9 @@ } }, "node_modules/jest-regex-util": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.0.tgz", - "integrity": "sha512-rT84010qRu/5OOU7a9TeidC2Tp3Qgt9Sty4pOZ/VSDuEmRupIjKZAb53gU3jr4ooMlhwScrgC9UixJxWzVu9oQ==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", "engines": { @@ -2953,18 +2960,18 @@ } }, "node_modules/jest-resolve": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.0.tgz", - "integrity": "sha512-zwWl1P15CcAfuQCEuxszjiKdsValhnWcj/aXg/R3aMHs8HVoCWHC4B/+5+1BirMoOud8NnN85GSP2LEZCbj3OA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.0.5.tgz", + "integrity": "sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.0", + "jest-haste-map": "30.0.5", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.0", - "jest-validate": "30.0.0", + "jest-util": "30.0.5", + "jest-validate": "30.0.5", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -2973,46 +2980,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.0.tgz", - "integrity": "sha512-Yhh7odCAUNXhluK1bCpwIlHrN1wycYaTlZwq1GdfNBEESNNI/z1j1a7dUEWHbmB9LGgv0sanxw3JPmWU8NeebQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.0.5.tgz", + "integrity": "sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.0.0", - "jest-snapshot": "30.0.0" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.0.tgz", - "integrity": "sha512-xbhmvWIc8X1IQ8G7xTv0AQJXKjBVyxoVJEJgy7A4RXsSaO+k/1ZSBbHwjnUhvYqMvwQPomWssDkUx6EoidEhlw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.0.5.tgz", + "integrity": "sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.0.0", - "@jest/environment": "30.0.0", - "@jest/test-result": "30.0.0", - "@jest/transform": "30.0.0", - "@jest/types": "30.0.0", + "@jest/console": "30.0.5", + "@jest/environment": "30.0.5", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.0.0", - "jest-environment-node": "30.0.0", - "jest-haste-map": "30.0.0", - "jest-leak-detector": "30.0.0", - "jest-message-util": "30.0.0", - "jest-resolve": "30.0.0", - "jest-runtime": "30.0.0", - "jest-util": "30.0.0", - "jest-watcher": "30.0.0", - "jest-worker": "30.0.0", + "jest-docblock": "30.0.1", + "jest-environment-node": "30.0.5", + "jest-haste-map": "30.0.5", + "jest-leak-detector": "30.0.5", + "jest-message-util": "30.0.5", + "jest-resolve": "30.0.5", + "jest-runtime": "30.0.5", + "jest-util": "30.0.5", + "jest-watcher": "30.0.5", + "jest-worker": "30.0.5", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -3021,32 +3028,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.0.tgz", - "integrity": "sha512-/O07qVgFrFAOGKGigojmdR3jUGz/y3+a/v9S/Yi2MHxsD+v6WcPppglZJw0gNJkRBArRDK8CFAwpM/VuEiiRjA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.0.5.tgz", + "integrity": "sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.0.0", - "@jest/fake-timers": "30.0.0", - "@jest/globals": "30.0.0", - "@jest/source-map": "30.0.0", - "@jest/test-result": "30.0.0", - "@jest/transform": "30.0.0", - "@jest/types": "30.0.0", + "@jest/environment": "30.0.5", + "@jest/fake-timers": "30.0.5", + "@jest/globals": "30.0.5", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.0.0", - "jest-message-util": "30.0.0", - "jest-mock": "30.0.0", - "jest-regex-util": "30.0.0", - "jest-resolve": "30.0.0", - "jest-snapshot": "30.0.0", - "jest-util": "30.0.0", + "jest-haste-map": "30.0.5", + "jest-message-util": "30.0.5", + "jest-mock": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.0.5", + "jest-snapshot": "30.0.5", + "jest-util": "30.0.5", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -3055,9 +3062,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.0.tgz", - "integrity": "sha512-6oCnzjpvfj/UIOMTqKZ6gedWAUgaycMdV8Y8h2dRJPvc2wSjckN03pzeoonw8y33uVngfx7WMo1ygdRGEKOT7w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.0.5.tgz", + "integrity": "sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==", "dev": true, "license": "MIT", "dependencies": { @@ -3066,20 +3073,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.0.0", - "@jest/get-type": "30.0.0", - "@jest/snapshot-utils": "30.0.0", - "@jest/transform": "30.0.0", - "@jest/types": "30.0.0", + "@jest/expect-utils": "30.0.5", + "@jest/get-type": "30.0.1", + "@jest/snapshot-utils": "30.0.5", + "@jest/transform": "30.0.5", + "@jest/types": "30.0.5", "babel-preset-current-node-syntax": "^1.1.0", "chalk": "^4.1.2", - "expect": "30.0.0", + "expect": "30.0.5", "graceful-fs": "^4.2.11", - "jest-diff": "30.0.0", - "jest-matcher-utils": "30.0.0", - "jest-message-util": "30.0.0", - "jest-util": "30.0.0", - "pretty-format": "30.0.0", + "jest-diff": "30.0.5", + "jest-matcher-utils": "30.0.5", + "jest-message-util": "30.0.5", + "jest-util": "30.0.5", + "pretty-format": "30.0.5", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -3101,13 +3108,13 @@ } }, "node_modules/jest-util": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.0.tgz", - "integrity": "sha512-fhNBBM9uSUbd4Lzsf8l/kcAdaHD/4SgoI48en3HXcBEMwKwoleKFMZ6cYEYs21SB779PRuRCyNLmymApAm8tZw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.0.0", + "@jest/types": "30.0.5", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -3119,9 +3126,9 @@ } }, "node_modules/jest-util/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -3132,18 +3139,18 @@ } }, "node_modules/jest-validate": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.0.tgz", - "integrity": "sha512-d6OkzsdlWItHAikUDs1hlLmpOIRhsZoXTCliV2XXalVQ3ZOeb9dy0CQ6AKulJu/XOZqpOEr/FiMH+FeOBVV+nw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.0.5.tgz", + "integrity": "sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.0.0", - "@jest/types": "30.0.0", + "@jest/get-type": "30.0.1", + "@jest/types": "30.0.5", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.0" + "pretty-format": "30.0.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3163,19 +3170,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.0.tgz", - "integrity": "sha512-fbAkojcyS53bOL/B7XYhahORq9cIaPwOgd/p9qW/hybbC8l6CzxfWJJxjlPBAIVN8dRipLR0zdhpGQdam+YBtw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.0.5.tgz", + "integrity": "sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.0.0", - "@jest/types": "30.0.0", + "@jest/test-result": "30.0.5", + "@jest/types": "30.0.5", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.0", + "jest-util": "30.0.5", "string-length": "^4.0.2" }, "engines": { @@ -3183,15 +3190,15 @@ } }, "node_modules/jest-worker": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.0.tgz", - "integrity": "sha512-VZvxfWIybIvwK8N/Bsfe43LfQgd/rD0c4h5nLUx78CAqPxIQcW2qDjsVAC53iUR8yxzFIeCFFvWOh8en8hGzdg==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.5.tgz", + "integrity": "sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.0", + "jest-util": "30.0.5", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -3261,6 +3268,7 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -3290,6 +3298,7 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -3301,7 +3310,8 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lru-cache": { "version": "5.1.1", @@ -3346,13 +3356,15 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" } @@ -3361,7 +3373,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/micromatch": { "version": "4.0.8", @@ -3414,15 +3427,16 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, "node_modules/napi-postinstall": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", - "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.2.tgz", + "integrity": "sha512-tWVJxJHmBWLy69PvO96TZMZDrzmw5KeiZBz3RHmiM2XZ9grBJ2WgMAFVVg25nqp3ZjTFUs2Ftw1JhscL3Teliw==", "dev": true, "license": "MIT", "bin": { @@ -3446,7 +3460,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.19", @@ -3460,6 +3475,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3524,6 +3540,7 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -3536,6 +3553,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -3551,6 +3569,7 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -3586,6 +3605,7 @@ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3646,6 +3666,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -3677,13 +3698,13 @@ } }, "node_modules/pretty-format": { - "version": "30.0.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.0.tgz", - "integrity": "sha512-18NAOUr4ZOQiIR+BgI5NhQE7uREdx4ZyV0dyay5izh4yfQ+1T7BSvggxvRGoXocrRyevqW5OhScUjbi9GB8R8Q==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.0", + "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", "react-is": "^18.3.1" }, @@ -3756,6 +3777,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3765,6 +3787,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -3793,16 +3816,24 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4027,6 +4058,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -4035,13 +4067,13 @@ } }, "node_modules/synckit": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.8.tgz", - "integrity": "sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, "license": "MIT", "dependencies": { - "@pkgr/core": "^0.2.4" + "@pkgr/core": "^0.2.9" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4115,7 +4147,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -4262,38 +4295,38 @@ "license": "MIT" }, "node_modules/unrs-resolver": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.0.tgz", - "integrity": "sha512-wqaRu4UnzBD2ABTC1kLfBjAqIDZ5YUTr/MLGa7By47JV1bJDSW7jq/ZSLigB7enLe7ubNaJhtnBXgrc/50cEhg==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.2.2" + "napi-postinstall": "^0.3.0" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.9.0", - "@unrs/resolver-binding-android-arm64": "1.9.0", - "@unrs/resolver-binding-darwin-arm64": "1.9.0", - "@unrs/resolver-binding-darwin-x64": "1.9.0", - "@unrs/resolver-binding-freebsd-x64": "1.9.0", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.0", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.0", - "@unrs/resolver-binding-linux-arm64-gnu": "1.9.0", - "@unrs/resolver-binding-linux-arm64-musl": "1.9.0", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.0", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.0", - "@unrs/resolver-binding-linux-riscv64-musl": "1.9.0", - "@unrs/resolver-binding-linux-s390x-gnu": "1.9.0", - "@unrs/resolver-binding-linux-x64-gnu": "1.9.0", - "@unrs/resolver-binding-linux-x64-musl": "1.9.0", - "@unrs/resolver-binding-wasm32-wasi": "1.9.0", - "@unrs/resolver-binding-win32-arm64-msvc": "1.9.0", - "@unrs/resolver-binding-win32-ia32-msvc": "1.9.0", - "@unrs/resolver-binding-win32-x64-msvc": "1.9.0" + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "node_modules/update-browserslist-db": { @@ -4347,6 +4380,7 @@ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" } @@ -4483,19 +4517,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -4537,6 +4558,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } diff --git a/package.json b/package.json index 47b7e45..9d79ba3 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "build": "tsc -p tsconfig.json", "dev": "tsc -p tsconfig.json --watch", "prepare": "npm run build", - "test": "jest" + "test": "jest", + "start": "tsc main.ts && node main.js" }, "files": [ "lib", From 6f6b6b8d30a994d91cc62b454b357bffd31b1d94 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 31 Aug 2025 14:18:44 +0500 Subject: [PATCH 15/28] Moved frame data reading Add new readFrameData() method --- src/renderware/dff/DffParser.ts | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index e563699..43c1a20 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -273,26 +273,27 @@ export class DffParser extends RwFile { let frames: RwFrame[] = []; for (let i = 0; i < frameCount; i++) { - // All these could probably be moved to readFrameData() - - const rotationMatrix: RwMatrix3 = { - right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }, - up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }, - at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }, - } + frames.push(this.readFrameData()); + } - const coordinatesOffset: RwVector3 = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }; + return { frameCount, frames }; + } + public readFrameData(): RwFrame { + const rotationMatrix: RwMatrix3 = { + right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }, + up: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }, + at: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }, + } - const parentFrame = this.readInt32(); + const coordinatesOffset: RwVector3 = { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }; - // Skip matrix creation internal flags - // They are read by the game but are not used - this.skip(4); + const parentFrame = this.readInt32(); - frames.push({ rotationMatrix, coordinatesOffset, parentFrame }); - } + // Skip matrix creation internal flags + // They are read by the game but are not used + this.skip(4); - return { frameCount, frames }; + return { rotationMatrix, coordinatesOffset, parentFrame }; } public readGeometryList(): RwGeometryList { From b58e55ee83656fc188fecc9dc60a383282e7a176 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 31 Aug 2025 14:26:21 +0500 Subject: [PATCH 16/28] Multi-Clump Support --- src/renderware/dff/DffParser.ts | 122 ++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 43c1a20..85e3a88 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -6,17 +6,18 @@ import RwVersion from '../utils/RwVersion'; export interface RwDff { version: string, versionNumber: number, - geometryList: RwGeometryList | null, - frameList: RwFrameList | null, - atomics: number[], - dummies: string[], - animNodes: RwAnimNode[], + clumps: RwClump[], } export interface RwClump { atomicCount: number, lightCount?: number, cameraCount?: number, + geometryList: RwGeometryList | null, + frameList: RwFrameList | null, + atomics: RwAtomic[] | null, + dummies: string[], + animNodes: RwAnimNode[], } export interface RwAnimNode { @@ -31,7 +32,6 @@ export interface RwBone { flags: number, } - export interface RwFrame { rotationMatrix: RwMatrix3, coordinatesOffset: RwVector3, @@ -77,7 +77,7 @@ export interface RwGeometry { boundingSphere?: RwSphere, materialList: RwMaterialList, binMesh: RwBinMesh, - skin?: RwSkin, + skin?: RwSkin, } export interface RwGeometryList { @@ -141,6 +141,7 @@ export interface RwVector3 { y: number, z: number, } + export interface RwVector4 { x: number, y: number, @@ -172,11 +173,7 @@ export class DffParser extends RwFile { parse(): RwDff { let version: string | undefined; let versionNumber: number | undefined; - let atomics: number[] = []; - let dummies: string[] = []; - let animNodes: RwAnimNode[] = []; - let geometryList: RwGeometryList | null = null; - let frameList: RwFrameList | null = null; + let clumps: RwClump[] = []; while (this.getPosition() < this.getSize()) { const header = this.readSectionHeader(); @@ -188,13 +185,61 @@ export class DffParser extends RwFile { if (header.sectionSize == 0) { continue; } + versionNumber = RwVersion.unpackVersion(header.versionNumber); + version = RwVersion.versions[versionNumber]; switch (header.sectionType) { case RwSections.RwClump: - // Multiple clumps are used in SA player models, so we should eventually support it - versionNumber = RwVersion.unpackVersion(header.versionNumber); - version = RwVersion.versions[versionNumber]; + clumps.push(this.readClump(header.sectionSize)); + break; + default: + console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`); + this.skip(header.sectionSize); break; + } + } + + if (!version || !versionNumber) { + throw new RwParseStructureNotFoundError('version'); + } + + return { + version: version, + versionNumber: versionNumber, + clumps: clumps, + }; + } + + public readClump(clumpSize: number): RwClump { + const { versionNumber } = this.readSectionHeader(); + let atomics: RwAtomic[] = []; + let dummies: string[] = []; + let geometryList: RwGeometryList | null = null; + let frameList: RwFrameList | null = null; + let animNodes: RwAnimNode[] = []; + + const atomicCount = this.readUint32(); + + let lightCount; + let cameraCount; + if (versionNumber > 0x33000) { + lightCount = this.readUint32(); + cameraCount = this.readUint32(); + } + const startPosition = this.getPosition(); + + while (this.getPosition() - startPosition < clumpSize - 24) { + const header = this.readSectionHeader(); + + if (header.sectionType === 0) { + break; + } + + if (header.sectionSize == 0) { + continue; + } + + switch (header.sectionType) { case RwSections.RwFrameList: frameList = this.readFrameList(); break; @@ -217,8 +262,7 @@ export class DffParser extends RwFile { geometryList = this.readGeometryList(); break; case RwSections.RwAtomic: - const atomic = this.readAtomic(); - atomics[atomic.geometryIndex] = atomic.frameIndex; + atomics.push(this.readAtomic()); break; case RwSections.RwNodeName: // For some reason, this frame is outside RwExtension. @@ -228,6 +272,8 @@ export class DffParser extends RwFile { // For III / VC models animNodes.push(this.readAnimNode()); break; + case RwSections.RwClump: + break; default: console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`); this.skip(header.sectionSize); @@ -235,34 +281,16 @@ export class DffParser extends RwFile { } } - if (!version || !versionNumber) { - throw new RwParseStructureNotFoundError('version'); - } - - return { - version: version, - versionNumber: versionNumber, - geometryList: geometryList, - frameList: frameList, - atomics: atomics, - dummies: dummies, - animNodes: animNodes, - }; - } - - public readClump(): RwClump { - const { versionNumber } = this.readSectionHeader(); - - const atomicCount = this.readUint32(); - - let lightCount; - let cameraCount; - if (versionNumber > 0x33000) { - lightCount = this.readUint32(); - cameraCount = this.readUint32(); - } - - return { atomicCount, lightCount, cameraCount }; + return { + atomicCount, + lightCount, + cameraCount, + geometryList, + frameList, + atomics, + dummies, + animNodes, + }; } public readFrameList(): RwFrameList { @@ -278,6 +306,7 @@ export class DffParser extends RwFile { return { frameCount, frames }; } + public readFrameData(): RwFrame { const rotationMatrix: RwMatrix3 = { right: { x: this.readFloat(), y: this.readFloat(), z: this.readFloat() }, @@ -306,8 +335,7 @@ export class DffParser extends RwFile { for (let i = 0; i < geometricObjectCount; i++) { this.readSectionHeader(); this.readSectionHeader(); - const versionNumber = RwVersion.unpackVersion(header.versionNumber); - const geometryData = this.readGeometry(versionNumber); + const geometryData = this.readGeometry(header.versionNumber); geometries.push(geometryData); } From fdb42538f687be45bb05309d4f542bf15a5667ea Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 2 Sep 2025 19:50:21 +0500 Subject: [PATCH 17/28] Optional BinMesh section Some models do not have BinMesh section at all, so we will not try to read it necessarily if it is missing. --- src/renderware/RwSections.ts | 1 + src/renderware/dff/DffParser.ts | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/renderware/RwSections.ts b/src/renderware/RwSections.ts index 95cf709..f4e2f4c 100644 --- a/src/renderware/RwSections.ts +++ b/src/renderware/RwSections.ts @@ -13,6 +13,7 @@ export enum RwSections { RwTextureNative = 0x0015, RwTextureDictionary = 0x0016, RwGeometryList = 0x001A, + RwBinMesh = 0x50E, RwSkin = 0x116, RwAnim = 0x11E, diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 85e3a88..3fa0ccf 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -76,7 +76,7 @@ export interface RwGeometry { normalInformation: RwVector3[], boundingSphere?: RwSphere, materialList: RwMaterialList, - binMesh: RwBinMesh, + binMesh?: RwBinMesh, skin?: RwSkin, } @@ -327,7 +327,6 @@ export class DffParser extends RwFile { public readGeometryList(): RwGeometryList { const header = this.readSectionHeader(); - const geometricObjectCount = this.readUint32(); let geometries: RwGeometry[] = []; @@ -425,11 +424,21 @@ export class DffParser extends RwFile { } } + const skipCount = 3 * 2 * vertexCount; + if (_shouldModulateMaterialColor) { // DEBUG + for (let i = 0; i < skipCount; i++) { + this.readFloat(); + } + } let materialList = this.readMaterialList(); let sectionSize = this.readSectionHeader().sectionSize; let position = this.getPosition(); - let binMesh = this.readBinMesh(); - let skin = undefined; + let binMesh = undefined; + let skin = undefined; + + if (this.readSectionHeader().sectionType == RwSections.RwBinMesh) { + binMesh = this.readBinMesh(); + } if (this.readSectionHeader().sectionType == RwSections.RwSkin) { skin = this.readSkin(vertexCount); From 5c9e47bfdf13b61b3fc189455eeef6da83e2396f Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 2 Sep 2025 19:52:22 +0500 Subject: [PATCH 18/28] Geometry section reading fix --- src/renderware/dff/DffParser.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 3fa0ccf..83d80d8 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -332,7 +332,6 @@ export class DffParser extends RwFile { let geometries: RwGeometry[] = []; for (let i = 0; i < geometricObjectCount; i++) { - this.readSectionHeader(); this.readSectionHeader(); const geometryData = this.readGeometry(header.versionNumber); geometries.push(geometryData); @@ -342,6 +341,9 @@ export class DffParser extends RwFile { } public readGeometry(versionNumber: number): RwGeometry { + let sectionSize = this.readSectionHeader().sectionSize; + let position = this.getPosition(); + const flags = this.readUint16(); const textureCoordinatesCount = this.readUint8(); const _nativeGeometryFlags = this.readUint8(); @@ -424,15 +426,13 @@ export class DffParser extends RwFile { } } - const skipCount = 3 * 2 * vertexCount; - if (_shouldModulateMaterialColor) { // DEBUG - for (let i = 0; i < skipCount; i++) { - this.readFloat(); - } - } + this.setPosition(position + sectionSize); + let materialList = this.readMaterialList(); - let sectionSize = this.readSectionHeader().sectionSize; - let position = this.getPosition(); + + sectionSize = this.readSectionHeader().sectionSize; + position = this.getPosition(); + let binMesh = undefined; let skin = undefined; From 4fcfaa8112c662b88c127f18c18a6a338297aca0 Mon Sep 17 00:00:00 2001 From: Anders Date: Fri, 5 Sep 2025 23:02:57 +0500 Subject: [PATCH 19/28] Geometry sec. Reading Fix --- src/renderware/dff/DffParser.ts | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 83d80d8..1025b9f 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -434,14 +434,25 @@ export class DffParser extends RwFile { position = this.getPosition(); let binMesh = undefined; - let skin = undefined; + let skin = undefined; - if (this.readSectionHeader().sectionType == RwSections.RwBinMesh) { - binMesh = this.readBinMesh(); - } - - if (this.readSectionHeader().sectionType == RwSections.RwSkin) { - skin = this.readSkin(vertexCount); + let relativePosition = 0; + while (relativePosition < sectionSize) { + const header = this.readSectionHeader(); + relativePosition += header.sectionSize + 12; + + switch(header.sectionType) { + case RwSections.RwBinMesh: + binMesh = this.readBinMesh(); + break; + case RwSections.RwSkin: + skin = this.readSkin(vertexCount); + break; + default: + console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`); + this.skip(header.sectionSize); + break; + } } this.setPosition(position + sectionSize); @@ -463,8 +474,6 @@ export class DffParser extends RwFile { } public readBinMesh(): RwBinMesh { - this.readSectionHeader(); - // Flags (0: triangle list, 1: triangle strip) this.skip(4); From 99e43958e901dac11601f9f139b6cf22a0a42926 Mon Sep 17 00:00:00 2001 From: Anders Date: Fri, 5 Sep 2025 23:22:31 +0500 Subject: [PATCH 20/28] Update tests For new version with multi-clump support --- tests/dff/infernus.dff.test.ts | 46 ++++++++++++------------ tests/dff/wuzimu.dff.test.ts | 66 +++++++++++++++++----------------- 2 files changed, 56 insertions(+), 56 deletions(-) diff --git a/tests/dff/infernus.dff.test.ts b/tests/dff/infernus.dff.test.ts index c191022..8bebe1a 100644 --- a/tests/dff/infernus.dff.test.ts +++ b/tests/dff/infernus.dff.test.ts @@ -24,28 +24,28 @@ describe('dff parsing - infernus', () => { }); test('atomics - length', () => { - expect(rwDff.atomics).toHaveLength(15); + expect(rwDff.clumps[0].atomics).toHaveLength(15); }); test('atomics - index matching', () => { - expect(rwDff.atomics[1]).toBe(14); - expect(rwDff.atomics[4]).toBe(31); - expect(rwDff.atomics[14]).toBe(21); + expect(rwDff.clumps[0].atomics[1]).toBe(14); + expect(rwDff.clumps[0].atomics[4]).toBe(31); + expect(rwDff.clumps[0].atomics[14]).toBe(21); }); test('dummies - length', () => { - expect(rwDff.dummies).toHaveLength(36); + expect(rwDff.clumps[0].dummies).toHaveLength(36); }); test('dummies - names', () => { - expect(rwDff.dummies[0]).toBe('infernus'); - expect(rwDff.dummies[2]).toBe('wheel_lb_dummy'); - expect(rwDff.dummies[3]).toBe('wheel_rf_dummy'); - expect(rwDff.dummies[35]).toBe('wheel'); + expect(rwDff.clumps[0].dummies[0]).toBe('infernus'); + expect(rwDff.clumps[0].dummies[2]).toBe('wheel_lb_dummy'); + expect(rwDff.clumps[0].dummies[3]).toBe('wheel_rf_dummy'); + expect(rwDff.clumps[0].dummies[35]).toBe('wheel'); }) test('frames - length', () => { - const frameList = rwDff.frameList; + const frameList = rwDff.clumps[0].frameList; expect(frameList).toBeDefined(); expect(frameList!.frameCount).toBe(36); @@ -53,7 +53,7 @@ describe('dff parsing - infernus', () => { }); test('frames - parent frames', () => { - const frameList = rwDff.frameList; + const frameList = rwDff.clumps[0].frameList; expect(frameList!.frames[0].parentFrame).toBe(-1); expect(frameList!.frames[1].parentFrame).toBe(0); @@ -64,7 +64,7 @@ describe('dff parsing - infernus', () => { }); test('frames - coordinate offsets', () => { - const frameList = rwDff.frameList; + const frameList = rwDff.clumps[0].frameList; expect(frameList!.frames[0].coordinatesOffset).toStrictEqual({ x: 0, y: 0, z: 0 }); expect(frameList!.frames[1].coordinatesOffset).toStrictEqual({ @@ -76,7 +76,7 @@ describe('dff parsing - infernus', () => { }); test('frames - rotation matrices', () => { - const frameList = rwDff.frameList; + const frameList = rwDff.clumps[0].frameList; expect(frameList!.frames[0].rotationMatrix).toStrictEqual({ right: { x: 1, y: 0, z: 0 }, up: { x: 0, y: 1, z: 0}, at: { x: 0, y: 0, z: 1} }); expect(frameList!.frames[20].rotationMatrix).toStrictEqual({ right: { x: 1, y: 0, z: 0 }, up: { x: 0, y: 1, z: 0}, at: { x: 0, y: 0, z: 1} }); @@ -84,7 +84,7 @@ describe('dff parsing - infernus', () => { }); test('geometries - length', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList).toBeDefined(); expect(geometryList!.geometricObjectCount).toBe(15); @@ -92,7 +92,7 @@ describe('dff parsing - infernus', () => { }); test('geometries - vertices', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].hasVertices).toBeTruthy(); expect(geometryList!.geometries[14].hasVertices).toBeTruthy(); @@ -134,7 +134,7 @@ describe('dff parsing - infernus', () => { }); test('geometries - normals', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].hasNormals).toBeTruthy(); expect(geometryList!.geometries[14].hasNormals).toBeTruthy(); @@ -176,7 +176,7 @@ describe('dff parsing - infernus', () => { }); test('geometries - triangles', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].triangleInformation).toHaveLength(250); expect(geometryList!.geometries[14].triangleInformation).toHaveLength(1710); @@ -201,21 +201,21 @@ describe('dff parsing - infernus', () => { }); test('geometries - vertex colors', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].vertexColorInformation).toHaveLength(0); expect(geometryList!.geometries[14].vertexColorInformation).toHaveLength(0); }); test('geometries - vertex colors', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].vertexColorInformation).toHaveLength(0); expect(geometryList!.geometries[14].vertexColorInformation).toHaveLength(0); }); test('geometries - texture mapping', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].textureCoordinatesCount).toBe(1); expect(geometryList!.geometries[3].textureCoordinatesCount).toBe(1); @@ -261,7 +261,7 @@ describe('dff parsing - infernus', () => { }); test('geometries - materials', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].materialList.materialInstanceCount).toBe(4); expect(geometryList!.geometries[14].materialList.materialInstanceCount).toBe(16); @@ -302,7 +302,7 @@ describe('dff parsing - infernus', () => { }); test('geometries - bin mesh', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].binMesh.meshCount).toBe(4); expect(geometryList!.geometries[14].binMesh.meshCount).toBe(16); @@ -323,7 +323,7 @@ describe('dff parsing - infernus', () => { }); test('geometries - bounding sphere', () => { - const geometryList = rwDff.geometryList; + const geometryList = rwDff.clumps[0].geometryList; expect(geometryList!.geometries[0].boundingSphere).toStrictEqual({ vector: { diff --git a/tests/dff/wuzimu.dff.test.ts b/tests/dff/wuzimu.dff.test.ts index 80d6f24..45b8e4b 100644 --- a/tests/dff/wuzimu.dff.test.ts +++ b/tests/dff/wuzimu.dff.test.ts @@ -25,20 +25,20 @@ describe('dff parsing - wuzimu', () => { }); test('dummies - length', () => { - expect(rwDff.dummies).toHaveLength(32); + expect(rwDff.clumps[0].dummies).toHaveLength(32); }); test('dummies - names', () => { - expect(rwDff.dummies[0]).toBe('Normal'); - expect(rwDff.dummies[1]).toBe(' Pelvis'); - expect(rwDff.dummies[2]).toBe(' R Thigh'); - expect(rwDff.dummies[15]).toBe('Jaw'); - expect(rwDff.dummies[30]).toBe(' R Foot'); - expect(rwDff.dummies[31]).toBe(' R Toe0'); + expect(rwDff.clumps[0].dummies[0]).toBe('Normal'); + expect(rwDff.clumps[0].dummies[1]).toBe(' Pelvis'); + expect(rwDff.clumps[0].dummies[2]).toBe(' R Thigh'); + expect(rwDff.clumps[0].dummies[15]).toBe('Jaw'); + expect(rwDff.clumps[0].dummies[30]).toBe(' R Foot'); + expect(rwDff.clumps[0].dummies[31]).toBe(' R Toe0'); }) test('frames - length', () => { - const frameList = rwDff.frameList; + const frameList = rwDff.clumps[0].frameList; expect(frameList).toBeDefined(); expect(frameList!.frameCount).toBe(33); @@ -46,7 +46,7 @@ describe('dff parsing - wuzimu', () => { }); test('frames - parent frames', () => { - const frameList = rwDff.frameList; + const frameList = rwDff.clumps[0].frameList; expect(frameList!.frames[0].parentFrame).toBe(-1); expect(frameList!.frames[1].parentFrame).toBe(0); @@ -59,7 +59,7 @@ describe('dff parsing - wuzimu', () => { }); test('frames - coordinate offsets', () => { - const frameList = rwDff.frameList; + const frameList = rwDff.clumps[0].frameList; expect(frameList!.frames[0].coordinatesOffset).toStrictEqual({ x: 0, y: 0, z: 0 }); expect(frameList!.frames[1].coordinatesOffset).toStrictEqual({ x: 0, y: 0, z: 0 }); @@ -74,7 +74,7 @@ describe('dff parsing - wuzimu', () => { }); test('frames - rotation matrices', () => { - const frameList = rwDff.frameList; + const frameList = rwDff.clumps[0].frameList; expect(frameList!.frames[0].rotationMatrix).toStrictEqual({ right: { x: 1, y: 0, z: 0 }, @@ -91,35 +91,35 @@ describe('dff parsing - wuzimu', () => { }); test('bones - length', () => { - expect(rwDff.animNodes.length).toStrictEqual(32); - expect(rwDff.animNodes[0].bones.length).toStrictEqual(32); + expect(rwDff.clumps[0].animNodes.length).toStrictEqual(32); + expect(rwDff.clumps[0].animNodes[0].bones.length).toStrictEqual(32); }); test('bones - ids', () => { - expect(rwDff.animNodes[0].boneId).toStrictEqual(0); - expect(rwDff.animNodes[1].boneId).toStrictEqual(1); - expect(rwDff.animNodes[2].boneId).toStrictEqual(51); - expect(rwDff.animNodes[3].boneId).toStrictEqual(41); - expect(rwDff.animNodes[15].boneId).toStrictEqual(8); - expect(rwDff.animNodes[30].boneId).toStrictEqual(53); - expect(rwDff.animNodes[31].boneId).toStrictEqual(54); - expect(rwDff.animNodes[0].bones[0].boneId).toStrictEqual(0); - expect(rwDff.animNodes[0].bones[2].boneId).toStrictEqual(2); - expect(rwDff.animNodes[0].bones[7].boneId).toStrictEqual(6); - expect(rwDff.animNodes[0].bones[18].boneId).toStrictEqual(24); - expect(rwDff.animNodes[0].bones[31].boneId).toStrictEqual(54); + expect(rwDff.clumps[0].animNodes[0].boneId).toStrictEqual(0); + expect(rwDff.clumps[0].animNodes[1].boneId).toStrictEqual(1); + expect(rwDff.clumps[0].animNodes[2].boneId).toStrictEqual(51); + expect(rwDff.clumps[0].animNodes[3].boneId).toStrictEqual(41); + expect(rwDff.clumps[0].animNodes[15].boneId).toStrictEqual(8); + expect(rwDff.clumps[0].animNodes[30].boneId).toStrictEqual(53); + expect(rwDff.clumps[0].animNodes[31].boneId).toStrictEqual(54); + expect(rwDff.clumps[0].animNodes[0].bones[0].boneId).toStrictEqual(0); + expect(rwDff.clumps[0].animNodes[0].bones[2].boneId).toStrictEqual(2); + expect(rwDff.clumps[0].animNodes[0].bones[7].boneId).toStrictEqual(6); + expect(rwDff.clumps[0].animNodes[0].bones[18].boneId).toStrictEqual(24); + expect(rwDff.clumps[0].animNodes[0].bones[31].boneId).toStrictEqual(54); }); test('bones - bone count', () => { - expect(rwDff.animNodes[0].bonesCount).toStrictEqual(32); - expect(rwDff.animNodes[3].bonesCount).toStrictEqual(0); - expect(rwDff.animNodes[15].bonesCount).toStrictEqual(0); - expect(rwDff.animNodes[31].bonesCount).toStrictEqual(0); + expect(rwDff.clumps[0].animNodes[0].bonesCount).toStrictEqual(32); + expect(rwDff.clumps[0].animNodes[3].bonesCount).toStrictEqual(0); + expect(rwDff.clumps[0].animNodes[15].bonesCount).toStrictEqual(0); + expect(rwDff.clumps[0].animNodes[31].bonesCount).toStrictEqual(0); ; }); test('skin - bone count', () => { - const skin = rwDff.geometryList?.geometries[0].skin!; + const skin = rwDff.clumps[0].geometryList.geometries[0].skin!; expect(skin.boneCount).toStrictEqual(32); expect(skin.usedBoneCount).toStrictEqual(31); @@ -127,7 +127,7 @@ describe('dff parsing - wuzimu', () => { }); test('skin - vertex weights', () => { - const skin = rwDff.geometryList?.geometries[0].skin!; + const skin = rwDff.clumps[0].geometryList.geometries[0].skin!; expect(skin.maxWeightsPerVertex).toStrictEqual(4); expect(skin.vertexWeights.length).toStrictEqual(990); @@ -149,7 +149,7 @@ describe('dff parsing - wuzimu', () => { }); test('skin - bone-vertex map', () => { - const skin = rwDff.geometryList?.geometries[0].skin!; + const skin = rwDff.clumps[0].geometryList.geometries[0].skin!; expect(skin.boneVertexIndices[0][0]).toStrictEqual(28); expect(skin.boneVertexIndices[0][1]).toStrictEqual(24); @@ -168,7 +168,7 @@ describe('dff parsing - wuzimu', () => { }); test('skin - inverse bone matrices', () => { - const skin = rwDff.geometryList?.geometries[0].skin!; + const skin = rwDff.clumps[0].geometryList.geometries[0].skin!; expect(skin.inverseBoneMatrices.length).toStrictEqual(32); From 379c8b5632d182925a4a04c2352ee049cf9f9b68 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 14 Sep 2025 21:13:48 +0500 Subject: [PATCH 21/28] Update DffParser.ts Allign reading geometry extensions. --- src/renderware/dff/DffParser.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 1025b9f..b0b7e71 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -440,19 +440,21 @@ export class DffParser extends RwFile { while (relativePosition < sectionSize) { const header = this.readSectionHeader(); relativePosition += header.sectionSize + 12; + const currentPosition = this.getPosition() + header.sectionSize; switch(header.sectionType) { - case RwSections.RwBinMesh: - binMesh = this.readBinMesh(); - break; case RwSections.RwSkin: skin = this.readSkin(vertexCount); break; + case RwSections.RwBinMesh: + binMesh = this.readBinMesh(); + break; default: console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`); this.skip(header.sectionSize); break; - } + } + this.setPosition(currentPosition); } this.setPosition(position + sectionSize); From 8d5425c46982ca95af63ae6e630b9cfe6645db48 Mon Sep 17 00:00:00 2001 From: Anders Date: Sun, 14 Sep 2025 21:25:32 +0500 Subject: [PATCH 22/28] Export isTriangleStrip flag --- src/renderware/dff/DffParser.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index b0b7e71..c2c8b26 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -71,6 +71,7 @@ export interface RwGeometry { textureMappingInformation: RwTextureCoordinate[][], hasVertices: boolean, hasNormals: boolean, + isTriangleStrip: boolean, triangleInformation: RwTriangle[], vertexInformation: RwVector3[], normalInformation: RwVector3[], @@ -362,7 +363,7 @@ export class DffParser extends RwFile { _diffuse = this.readFloat(); } - const _isTriangleStrip = (flags & (1 << 0)) !== 0; + const isTriangleStrip = (flags & (1 << 0)) !== 0; const _vertexTranslation = (flags & (1 << 1)) !== 0; const isTexturedUV1 = (flags & (1 << 2)) !== 0; const isGeometryPrelit = (flags & (1 << 3)) !== 0; @@ -465,6 +466,7 @@ export class DffParser extends RwFile { boundingSphere, hasVertices, hasNormals, + isTriangleStrip, vertexColorInformation, vertexInformation, normalInformation, From 0a9fed893cd3a52ebbd8fcd916598691b8759eb4 Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 16 Sep 2025 22:03:58 +0500 Subject: [PATCH 23/28] Fix dff tests --- package.json | 3 +-- tests/dff/infernus.dff.test.ts | 31 +++++++++++++++++-------------- tests/dff/wuzimu.dff.test.ts | 9 ++++----- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 9d79ba3..47b7e45 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,7 @@ "build": "tsc -p tsconfig.json", "dev": "tsc -p tsconfig.json --watch", "prepare": "npm run build", - "test": "jest", - "start": "tsc main.ts && node main.js" + "test": "jest" }, "files": [ "lib", diff --git a/tests/dff/infernus.dff.test.ts b/tests/dff/infernus.dff.test.ts index 8bebe1a..d6a4797 100644 --- a/tests/dff/infernus.dff.test.ts +++ b/tests/dff/infernus.dff.test.ts @@ -28,9 +28,12 @@ describe('dff parsing - infernus', () => { }); test('atomics - index matching', () => { - expect(rwDff.clumps[0].atomics[1]).toBe(14); - expect(rwDff.clumps[0].atomics[4]).toBe(31); - expect(rwDff.clumps[0].atomics[14]).toBe(21); + if (!rwDff.clumps[0].atomics || rwDff.clumps[0].atomics.length == 0) { + throw new Error("Atomics is missing"); + } + expect(rwDff.clumps[0].atomics[1].frameIndex).toBe(14); + expect(rwDff.clumps[0].atomics[4].frameIndex).toBe(31); + expect(rwDff.clumps[0].atomics[14].frameIndex).toBe(21); }); test('dummies - length', () => { @@ -304,22 +307,22 @@ describe('dff parsing - infernus', () => { test('geometries - bin mesh', () => { const geometryList = rwDff.clumps[0].geometryList; - expect(geometryList!.geometries[0].binMesh.meshCount).toBe(4); - expect(geometryList!.geometries[14].binMesh.meshCount).toBe(16); + expect(geometryList!.geometries[0].binMesh!.meshCount).toBe(4); + expect(geometryList!.geometries[14].binMesh!.meshCount).toBe(16); - expect(geometryList!.geometries[0].binMesh.meshes).toHaveLength(4); - expect(geometryList!.geometries[14].binMesh.meshes).toHaveLength(16); + expect(geometryList!.geometries[0].binMesh!.meshes).toHaveLength(4); + expect(geometryList!.geometries[14].binMesh!.meshes).toHaveLength(16); - expect(geometryList!.geometries[0].binMesh.meshes[0].indexCount).toBe(4); - expect(geometryList!.geometries[0].binMesh.meshes[3].indexCount).toBe(210); + expect(geometryList!.geometries[0].binMesh!.meshes[0].indexCount).toBe(4); + expect(geometryList!.geometries[0].binMesh!.meshes[3].indexCount).toBe(210); - expect(geometryList!.geometries[0].binMesh.meshes[0].indices).toHaveLength(4); - expect(geometryList!.geometries[0].binMesh.meshes[3].indices).toHaveLength(210); + expect(geometryList!.geometries[0].binMesh!.meshes[0].indices).toHaveLength(4); + expect(geometryList!.geometries[0].binMesh!.meshes[3].indices).toHaveLength(210); - expect(geometryList!.geometries[14].binMesh.meshes[15].indices[0]).toBe(1937); - expect(geometryList!.geometries[14].binMesh.meshes[15].indices[100]).toBe(1909); + expect(geometryList!.geometries[14].binMesh!.meshes[15].indices[0]).toBe(1937); + expect(geometryList!.geometries[14].binMesh!.meshes[15].indices[100]).toBe(1909); - expect(geometryList!.geometries[14].binMesh.meshes[14].materialIndex).toBe(11); + expect(geometryList!.geometries[14].binMesh!.meshes[14].materialIndex).toBe(11); }); test('geometries - bounding sphere', () => { diff --git a/tests/dff/wuzimu.dff.test.ts b/tests/dff/wuzimu.dff.test.ts index 45b8e4b..5e5ebb9 100644 --- a/tests/dff/wuzimu.dff.test.ts +++ b/tests/dff/wuzimu.dff.test.ts @@ -1,7 +1,6 @@ import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { DffParser, RwDff } from '../../src/index'; -import exp from 'node:constants'; // This test for skin and bones sections @@ -119,7 +118,7 @@ describe('dff parsing - wuzimu', () => { }); test('skin - bone count', () => { - const skin = rwDff.clumps[0].geometryList.geometries[0].skin!; + const skin = rwDff.clumps[0].geometryList!.geometries[0].skin!; expect(skin.boneCount).toStrictEqual(32); expect(skin.usedBoneCount).toStrictEqual(31); @@ -127,7 +126,7 @@ describe('dff parsing - wuzimu', () => { }); test('skin - vertex weights', () => { - const skin = rwDff.clumps[0].geometryList.geometries[0].skin!; + const skin = rwDff.clumps[0].geometryList!.geometries[0].skin!; expect(skin.maxWeightsPerVertex).toStrictEqual(4); expect(skin.vertexWeights.length).toStrictEqual(990); @@ -149,7 +148,7 @@ describe('dff parsing - wuzimu', () => { }); test('skin - bone-vertex map', () => { - const skin = rwDff.clumps[0].geometryList.geometries[0].skin!; + const skin = rwDff.clumps[0].geometryList!.geometries[0].skin!; expect(skin.boneVertexIndices[0][0]).toStrictEqual(28); expect(skin.boneVertexIndices[0][1]).toStrictEqual(24); @@ -168,7 +167,7 @@ describe('dff parsing - wuzimu', () => { }); test('skin - inverse bone matrices', () => { - const skin = rwDff.clumps[0].geometryList.geometries[0].skin!; + const skin = rwDff.clumps[0].geometryList!.geometries[0].skin!; expect(skin.inverseBoneMatrices.length).toStrictEqual(32); From 2e86efc7da0f278fd2f4f27dc25f4d84e709c08e Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 16 Sep 2025 22:15:15 +0500 Subject: [PATCH 24/28] Fix geometry reading errors *Fix reading empty / unknown sections while reading extension chunks. *Clean up --- src/renderware/dff/DffParser.ts | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index c2c8b26..6516cb7 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -432,7 +432,6 @@ export class DffParser extends RwFile { let materialList = this.readMaterialList(); sectionSize = this.readSectionHeader().sectionSize; - position = this.getPosition(); let binMesh = undefined; let skin = undefined; @@ -441,24 +440,23 @@ export class DffParser extends RwFile { while (relativePosition < sectionSize) { const header = this.readSectionHeader(); relativePosition += header.sectionSize + 12; - const currentPosition = this.getPosition() + header.sectionSize; - + position = this.getPosition(); + switch(header.sectionType) { - case RwSections.RwSkin: + case RwSections.RwBinMesh: + binMesh = this.readBinMesh(); + break; + case RwSections.RwSkin: skin = this.readSkin(vertexCount); break; - case RwSections.RwBinMesh: - binMesh = this.readBinMesh(); - break; default: console.debug(`Section type ${header.sectionType} (${header.sectionType.toString(16)}) not found at offset (${this.getPosition().toString(16)}). Skipping ${header.sectionSize} bytes.`); this.skip(header.sectionSize); break; - } - this.setPosition(currentPosition); - } + } - this.setPosition(position + sectionSize); + this.setPosition(position + header.sectionSize); + } return { textureCoordinatesCount, @@ -497,7 +495,7 @@ export class DffParser extends RwFile { }; } - public readSkin(vertexCount : number): RwSkin { + public readSkin(vertexCount : number): RwSkin { const boneCount = this.readUint8(); const usedBoneCount = this.readUint8(); const maxWeightsPerVertex = this.readUint8(); @@ -505,9 +503,9 @@ export class DffParser extends RwFile { this.skip(1); // Padding this.skip(usedBoneCount); // Skipping special indices - const boneVertexIndices: number[][] = []; - const vertexWeights: number[][] = []; - const inverseBoneMatrices: RwMatrix4[] = []; + const boneVertexIndices: number[][] = []; + const vertexWeights: number[][] = []; + const inverseBoneMatrices: RwMatrix4[] = []; for (let i = 0; i < vertexCount; i++) { const indices: number[] = []; @@ -543,7 +541,7 @@ export class DffParser extends RwFile { boneVertexIndices, vertexWeights, inverseBoneMatrices, - } + } } public readAnimNode() :RwAnimNode { From ae3394612ca19850b14bf001ee28281c3738c063 Mon Sep 17 00:00:00 2001 From: Anders Date: Tue, 16 Sep 2025 22:20:35 +0500 Subject: [PATCH 25/28] Update gitignore --- .gitignore | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.gitignore b/.gitignore index 962160c..38e65a0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,3 @@ node_modules output coverage - -#-------- -main.ts -*.js -timic/ -tests/ -.vscode/launch.json -package.json From fbb5a3343d7db06adf5fabce8c7422e79ab74322 Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 18 Sep 2025 21:14:12 +0500 Subject: [PATCH 26/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 6516cb7..7bd29ea 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -291,7 +291,7 @@ export class DffParser extends RwFile { atomics, dummies, animNodes, - }; + }; } public readFrameList(): RwFrameList { From b774d1c4b59242e988f8e611ac324a8e3afc113f Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 18 Sep 2025 21:14:22 +0500 Subject: [PATCH 27/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index 7bd29ea..bf97133 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -442,7 +442,7 @@ export class DffParser extends RwFile { relativePosition += header.sectionSize + 12; position = this.getPosition(); - switch(header.sectionType) { + switch (header.sectionType) { case RwSections.RwBinMesh: binMesh = this.readBinMesh(); break; From 1cbdda4b9a639eeda4a7d395b9f2922a39755a0d Mon Sep 17 00:00:00 2001 From: Anders Date: Thu, 18 Sep 2025 21:14:39 +0500 Subject: [PATCH 28/28] Update src/renderware/dff/DffParser.ts Co-authored-by: Timotej <20747466+Timic3@users.noreply.github.com> --- src/renderware/dff/DffParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderware/dff/DffParser.ts b/src/renderware/dff/DffParser.ts index bf97133..56d4825 100644 --- a/src/renderware/dff/DffParser.ts +++ b/src/renderware/dff/DffParser.ts @@ -229,7 +229,7 @@ export class DffParser extends RwFile { } const startPosition = this.getPosition(); - while (this.getPosition() - startPosition < clumpSize - 24) { + while (this.getPosition() - startPosition < clumpSize - 24) { const header = this.readSectionHeader(); if (header.sectionType === 0) {