mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Fix M2 particle emitter crash: correct struct size, FBlock format, and add safety caps
The particle emitter parser had three bugs causing OOM crashes during loading: - Struct size was 496 bytes instead of correct WotLK 476 (0x1DC), misaligning multi-emitter models - FBlocks were read as 20-byte M2TrackDisk instead of 16-byte FBlockDisk (no interp/seq prefix) - parseAnimTrack had no cap on sequence count, allowing garbage data to allocate billions of entries
This commit is contained in:
parent
104a9d0898
commit
b9dfce3c66
1 changed files with 22 additions and 9 deletions
|
|
@ -129,6 +129,15 @@ struct M2TrackDisk {
|
||||||
uint32_t ofsKeys;
|
uint32_t ofsKeys;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// FBlock header (on-disk, 16 bytes) — particle lifetime curves
|
||||||
|
// Like M2TrackDisk but WITHOUT interpolationType/globalSequence prefix
|
||||||
|
struct FBlockDisk {
|
||||||
|
uint32_t nTimestamps;
|
||||||
|
uint32_t ofsTimestamps;
|
||||||
|
uint32_t nKeys;
|
||||||
|
uint32_t ofsKeys;
|
||||||
|
};
|
||||||
|
|
||||||
// Full M2 bone structure (on-disk, 88 bytes)
|
// Full M2 bone structure (on-disk, 88 bytes)
|
||||||
struct M2BoneDisk {
|
struct M2BoneDisk {
|
||||||
int32_t keyBoneId; // 4
|
int32_t keyBoneId; // 4
|
||||||
|
|
@ -296,6 +305,8 @@ void parseAnimTrack(const std::vector<uint8_t>& data,
|
||||||
if (disk.nTimestamps == 0 || disk.nKeys == 0) return;
|
if (disk.nTimestamps == 0 || disk.nKeys == 0) return;
|
||||||
|
|
||||||
uint32_t numSubArrays = disk.nTimestamps;
|
uint32_t numSubArrays = disk.nTimestamps;
|
||||||
|
// Sanity cap: no model has >4096 animation sequences; garbage counts cause OOM
|
||||||
|
if (numSubArrays > 4096) return;
|
||||||
track.sequences.resize(numSubArrays);
|
track.sequences.resize(numSubArrays);
|
||||||
|
|
||||||
for (uint32_t i = 0; i < numSubArrays; i++) {
|
for (uint32_t i = 0; i < numSubArrays; i++) {
|
||||||
|
|
@ -370,15 +381,17 @@ void parseAnimTrack(const std::vector<uint8_t>& data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse an FBlock (particle lifetime curve) from a 20-byte on-disk header.
|
// Parse an FBlock (particle lifetime curve) from a 16-byte on-disk header.
|
||||||
// FBlocks use the same layout as M2TrackDisk but timestamps/values are flat arrays.
|
// FBlocks are like M2Track but WITHOUT the interpolationType/globalSequence prefix.
|
||||||
void parseFBlock(const std::vector<uint8_t>& data, uint32_t offset,
|
void parseFBlock(const std::vector<uint8_t>& data, uint32_t offset,
|
||||||
M2FBlock& fb, int valueType) {
|
M2FBlock& fb, int valueType) {
|
||||||
// valueType: 0 = color (3 bytes RGB), 1 = alpha (uint16), 2 = scale (float pair)
|
// valueType: 0 = color (3 bytes RGB), 1 = alpha (uint16), 2 = scale (float pair)
|
||||||
if (offset + 20 > data.size()) return;
|
if (offset + sizeof(FBlockDisk) > data.size()) return;
|
||||||
|
|
||||||
M2TrackDisk disk = readValue<M2TrackDisk>(data, offset);
|
FBlockDisk disk = readValue<FBlockDisk>(data, offset);
|
||||||
if (disk.nTimestamps == 0 || disk.nKeys == 0) return;
|
if (disk.nTimestamps == 0 || disk.nKeys == 0) return;
|
||||||
|
// Sanity cap: particle FBlocks typically have 3 keyframes
|
||||||
|
if (disk.nTimestamps > 1024 || disk.nKeys > 1024) return;
|
||||||
|
|
||||||
// FBlock timestamps are uint16 (not sub-arrays), stored directly
|
// FBlock timestamps are uint16 (not sub-arrays), stored directly
|
||||||
if (disk.ofsTimestamps + disk.nTimestamps * sizeof(uint16_t) > data.size()) return;
|
if (disk.ofsTimestamps + disk.nTimestamps * sizeof(uint16_t) > data.size()) return;
|
||||||
|
|
@ -656,8 +669,8 @@ M2Model M2Loader::load(const std::vector<uint8_t>& m2Data) {
|
||||||
model.attachmentLookup = readArray<uint16_t>(m2Data, header.ofsAttachmentLookup, header.nAttachmentLookup);
|
model.attachmentLookup = readArray<uint16_t>(m2Data, header.ofsAttachmentLookup, header.nAttachmentLookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse particle emitters (WotLK M2ParticleOld: 0x1F0 = 496 bytes per emitter)
|
// Parse particle emitters (WotLK M2ParticleOld: 0x1DC = 476 bytes per emitter)
|
||||||
static constexpr uint32_t EMITTER_STRUCT_SIZE = 0x1F0;
|
static constexpr uint32_t EMITTER_STRUCT_SIZE = 0x1DC;
|
||||||
if (header.nParticleEmitters > 0 && header.ofsParticleEmitters > 0 &&
|
if (header.nParticleEmitters > 0 && header.ofsParticleEmitters > 0 &&
|
||||||
header.nParticleEmitters < 256 &&
|
header.nParticleEmitters < 256 &&
|
||||||
static_cast<size_t>(header.ofsParticleEmitters) +
|
static_cast<size_t>(header.ofsParticleEmitters) +
|
||||||
|
|
@ -702,10 +715,10 @@ M2Model M2Loader::load(const std::vector<uint8_t>& m2Data) {
|
||||||
parseTrack(0xDC, em.emissionAreaWidth);
|
parseTrack(0xDC, em.emissionAreaWidth);
|
||||||
parseTrack(0xF0, em.deceleration);
|
parseTrack(0xF0, em.deceleration);
|
||||||
|
|
||||||
// Parse FBlocks (color, alpha, scale)
|
// Parse FBlocks (color, alpha, scale) — FBlocks are 16 bytes each
|
||||||
parseFBlock(m2Data, base + 0x104, em.particleColor, 0);
|
parseFBlock(m2Data, base + 0x104, em.particleColor, 0);
|
||||||
parseFBlock(m2Data, base + 0x118, em.particleAlpha, 1);
|
parseFBlock(m2Data, base + 0x114, em.particleAlpha, 1);
|
||||||
parseFBlock(m2Data, base + 0x12C, em.particleScale, 2);
|
parseFBlock(m2Data, base + 0x124, em.particleScale, 2);
|
||||||
|
|
||||||
model.particleEmitters.push_back(std::move(em));
|
model.particleEmitters.push_back(std::move(em));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue