Release Healer Man 0.1.13

This commit is contained in:
phenom committed 2026-09-01 16:31:26 -04:00
1 parent 65e24f644e
commit 67d7a8ebe7
324 files changed
+917 -48

No files matched your search

+106
View File
@@ -11,8 +11,10 @@ const VALUE_TYPES = {
byteLength: 4,
read: (buffer, offset) => Array.from(buffer.subarray(offset, offset + 4)),
},
uint32: { byteLength: 4, read: (buffer, offset) => buffer.readUInt32LE(offset) },
uint16: { byteLength: 2, read: (buffer, offset) => buffer.readUInt16LE(offset) },
int16: { byteLength: 2, read: (buffer, offset) => buffer.readInt16LE(offset) },
vector3: { byteLength: 12, read: (buffer, offset) => vector3(buffer, offset) },
};
function asBuffer(value) {
@@ -44,6 +46,54 @@ function decodeArray(buffer, descriptorOffset, valueType, label) {
};
}
function decodeNestedArray(buffer, descriptorOffset, valueType, label) {
const descriptor = readArrayHeader(buffer, descriptorOffset, label);
const byteLength = descriptor.count * 8;
if (!Number.isSafeInteger(byteLength)) throw new Error(`${label} is too large to decode.`);
if (descriptor.count) assertRange(buffer, descriptor.offset, byteLength, `${label} sequence descriptors`);
return {
...descriptor,
sequences: Array.from({ length: descriptor.count }, (_, index) => (
decodeArray(buffer, descriptor.offset + index * 8, valueType, `${label} sequence ${index}`)
)),
};
}
function decodeTrack(buffer, offset, valueType, label) {
assertRange(buffer, offset, 20, `${label} track`);
const interpolation = buffer.readUInt16LE(offset);
if (interpolation > 3) throw new Error(`${label} uses unsupported interpolation ${interpolation}.`);
const spline = interpolation === 2 || interpolation === 3;
const trackValueType = spline ? {
byteLength: valueType.byteLength * 3,
read: (source, valueOffset) => ({
value: valueType.read(source, valueOffset),
inTangent: valueType.read(source, valueOffset + valueType.byteLength),
outTangent: valueType.read(source, valueOffset + valueType.byteLength * 2),
}),
} : valueType;
const timestamps = decodeNestedArray(buffer, offset + 4, VALUE_TYPES.uint32, `${label} timestamps`);
const values = decodeNestedArray(buffer, offset + 12, trackValueType, `${label} values`);
if (timestamps.sequences.length !== values.sequences.length) {
throw new Error(`${label} timestamp and value sequence counts do not match.`);
}
const sequences = timestamps.sequences.map((timestampSequence, index) => {
const valueSequence = values.sequences[index];
if (timestampSequence.values.length !== valueSequence.values.length) {
throw new Error(`${label} sequence ${index} timestamp and value counts do not match.`);
}
return {
timestamps: timestampSequence.values,
values: valueSequence.values,
};
});
return {
interpolation,
globalSequence: buffer.readInt16LE(offset + 2),
sequences,
};
}
function decodeString(buffer, descriptorOffset, label) {
const descriptor = readArrayHeader(buffer, descriptorOffset, label);
if (!descriptor.count) return "";
@@ -113,6 +163,54 @@ function decodeMaterials(buffer, name) {
});
}
function decodeAnimations(buffer, name) {
const count = buffer.readUInt32LE(28);
const offset = buffer.readUInt32LE(32);
const recordSize = 64;
const byteLength = count * recordSize;
if (!Number.isSafeInteger(byteLength)) throw new Error(`${name} animations are too large to decode.`);
if (count) assertRange(buffer, offset, byteLength, `${name} animation records`);
return Array.from({ length: count }, (_, index) => {
const animationOffset = offset + index * recordSize;
return {
index,
id: buffer.readUInt16LE(animationOffset),
variationIndex: buffer.readUInt16LE(animationOffset + 2),
durationMs: buffer.readUInt32LE(animationOffset + 4),
moveSpeed: buffer.readFloatLE(animationOffset + 8),
flags: buffer.readUInt32LE(animationOffset + 12),
};
});
}
function decodeColors(buffer, name) {
const count = buffer.readUInt32LE(72);
const offset = buffer.readUInt32LE(76);
const recordSize = 40;
const byteLength = count * recordSize;
if (!Number.isSafeInteger(byteLength)) throw new Error(`${name} colors are too large to decode.`);
if (count) assertRange(buffer, offset, byteLength, `${name} color records`);
return Array.from({ length: count }, (_, index) => {
const colorOffset = offset + index * recordSize;
return {
color: decodeTrack(buffer, colorOffset, VALUE_TYPES.vector3, `${name} color ${index} RGB`),
alpha: decodeTrack(buffer, colorOffset + 20, VALUE_TYPES.int16, `${name} color ${index} alpha`),
};
});
}
function decodeTextureWeights(buffer, name) {
const count = buffer.readUInt32LE(88);
const offset = buffer.readUInt32LE(92);
const recordSize = 20;
const byteLength = count * recordSize;
if (!Number.isSafeInteger(byteLength)) throw new Error(`${name} texture weights are too large to decode.`);
if (count) assertRange(buffer, offset, byteLength, `${name} texture-weight records`);
return Array.from({ length: count }, (_, index) => (
decodeTrack(buffer, offset + index * recordSize, VALUE_TYPES.int16, `${name} texture weight ${index}`)
));
}
function decodeSkinSubmeshes(buffer, count, offset, triangleCount, vertexLookupCount, name) {
const byteLength = count * SKIN_SUBMESH_SIZE;
if (!Number.isSafeInteger(byteLength)) throw new Error(`${name} submeshes are too large to decode.`);
@@ -231,7 +329,11 @@ export function decodeWotlkM2Mesh(m2Source, skinSource, name = "M2 mesh") {
}
const vertices = decodeVertices(m2, name);
const globalSequences = decodeArray(m2, 20, VALUE_TYPES.uint32, `${name} global sequences`).values;
const animations = decodeAnimations(m2, name);
const colors = decodeColors(m2, name);
const textures = decodeTextures(m2, name);
const textureWeights = decodeTextureWeights(m2, name);
const materials = decodeMaterials(m2, name);
const lookups = {
textures: decodeArray(m2, 128, VALUE_TYPES.uint16, `${name} texture lookup`).values,
@@ -274,6 +376,8 @@ export function decodeWotlkM2Mesh(m2Source, skinSource, name = "M2 mesh") {
magic: "MD20",
version: 264,
modelName: decodeString(m2, 8, `${name} model name`),
globalSequences,
animations,
bounds: {
minimum: vector3(m2, 160),
maximum: vector3(m2, 172),
@@ -281,7 +385,9 @@ export function decodeWotlkM2Mesh(m2Source, skinSource, name = "M2 mesh") {
},
vertices,
indices,
colors,
textures,
textureWeights,
materials,
lookups,
skin: {