mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-17 17:43:52 +00:00
Enable M2 particle emitters with correct WotLK struct parsing and overflow guards
This commit is contained in:
parent
d54aba3950
commit
51be0beea6
2 changed files with 77 additions and 21 deletions
|
|
@ -252,12 +252,16 @@ T readValue(const std::vector<uint8_t>& data, uint32_t offset) {
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::vector<T> readArray(const std::vector<uint8_t>& data, uint32_t offset, uint32_t count) {
|
std::vector<T> readArray(const std::vector<uint8_t>& data, uint32_t offset, uint32_t count) {
|
||||||
std::vector<T> result;
|
std::vector<T> result;
|
||||||
if (count == 0 || offset + count * sizeof(T) > data.size()) {
|
if (count == 0) return result;
|
||||||
return result;
|
// Overflow-safe bounds check: avoid uint32 wrap on count * sizeof(T)
|
||||||
}
|
size_t totalBytes = static_cast<size_t>(count) * sizeof(T);
|
||||||
|
if (totalBytes / sizeof(T) != count) return result; // multiplication overflowed
|
||||||
|
if (static_cast<size_t>(offset) + totalBytes > data.size()) return result;
|
||||||
|
// Sanity cap: refuse allocations > 64MB to prevent garbage counts from OOMing
|
||||||
|
if (totalBytes > 64 * 1024 * 1024) return result;
|
||||||
|
|
||||||
result.resize(count);
|
result.resize(count);
|
||||||
std::memcpy(result.data(), &data[offset], count * sizeof(T));
|
std::memcpy(result.data(), &data[offset], totalBytes);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -652,17 +656,61 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Particle emitter parsing disabled.
|
// Parse particle emitters (WotLK M2ParticleOld: 0x1F0 = 496 bytes per emitter)
|
||||||
// The assumed EMITTER_STRUCT_SIZE (476 bytes) is incorrect for WotLK 3.3.5a M2 files.
|
static constexpr uint32_t EMITTER_STRUCT_SIZE = 0x1F0;
|
||||||
// When iterating multiple emitters, each one after the first reads from a misaligned
|
if (header.nParticleEmitters > 0 && header.ofsParticleEmitters > 0 &&
|
||||||
// offset, producing garbage M2TrackDisk headers with huge nTimestamps/nKeys counts.
|
header.nParticleEmitters < 256 &&
|
||||||
// parseAnimTrack then calls readArray which allocates vectors sized by those garbage
|
static_cast<size_t>(header.ofsParticleEmitters) +
|
||||||
// counts — this caused RAM usage to explode from ~1 GB to 130+ GB, consuming all
|
static_cast<size_t>(header.nParticleEmitters) * EMITTER_STRUCT_SIZE <= m2Data.size()) {
|
||||||
// system memory and swap.
|
|
||||||
// TODO: determine the correct emitter struct size for build 12340 and add overflow
|
// Build sequence flags for parseAnimTrack
|
||||||
// guards to readArray (count * sizeof(T) can wrap uint32_t, bypassing bounds checks).
|
std::vector<uint32_t> emSeqFlags;
|
||||||
(void)header.nParticleEmitters;
|
emSeqFlags.reserve(model.sequences.size());
|
||||||
(void)header.ofsParticleEmitters;
|
for (const auto& seq : model.sequences) {
|
||||||
|
emSeqFlags.push_back(seq.flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t ei = 0; ei < header.nParticleEmitters; ei++) {
|
||||||
|
uint32_t base = header.ofsParticleEmitters + ei * EMITTER_STRUCT_SIZE;
|
||||||
|
|
||||||
|
M2ParticleEmitter em;
|
||||||
|
em.particleId = readValue<int32_t>(m2Data, base + 0x00);
|
||||||
|
em.flags = readValue<uint32_t>(m2Data, base + 0x04);
|
||||||
|
em.position.x = readValue<float>(m2Data, base + 0x08);
|
||||||
|
em.position.y = readValue<float>(m2Data, base + 0x0C);
|
||||||
|
em.position.z = readValue<float>(m2Data, base + 0x10);
|
||||||
|
em.bone = readValue<uint16_t>(m2Data, base + 0x14);
|
||||||
|
em.texture = readValue<uint16_t>(m2Data, base + 0x16);
|
||||||
|
em.blendingType = readValue<uint8_t>(m2Data, base + 0x28);
|
||||||
|
em.emitterType = readValue<uint8_t>(m2Data, base + 0x29);
|
||||||
|
|
||||||
|
// Parse animated tracks (M2TrackDisk at known offsets)
|
||||||
|
auto parseTrack = [&](uint32_t off, M2AnimationTrack& track) {
|
||||||
|
if (base + off + sizeof(M2TrackDisk) <= m2Data.size()) {
|
||||||
|
M2TrackDisk disk = readValue<M2TrackDisk>(m2Data, base + off);
|
||||||
|
parseAnimTrack(m2Data, disk, track, TrackType::FLOAT, emSeqFlags);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
parseTrack(0x34, em.emissionSpeed);
|
||||||
|
parseTrack(0x48, em.speedVariation);
|
||||||
|
parseTrack(0x5C, em.verticalRange);
|
||||||
|
parseTrack(0x70, em.horizontalRange);
|
||||||
|
parseTrack(0x84, em.gravity);
|
||||||
|
parseTrack(0x98, em.lifespan);
|
||||||
|
parseTrack(0xB0, em.emissionRate);
|
||||||
|
parseTrack(0xC8, em.emissionAreaLength);
|
||||||
|
parseTrack(0xDC, em.emissionAreaWidth);
|
||||||
|
parseTrack(0xF0, em.deceleration);
|
||||||
|
|
||||||
|
// Parse FBlocks (color, alpha, scale)
|
||||||
|
parseFBlock(m2Data, base + 0x104, em.particleColor, 0);
|
||||||
|
parseFBlock(m2Data, base + 0x118, em.particleAlpha, 1);
|
||||||
|
parseFBlock(m2Data, base + 0x12C, em.particleScale, 2);
|
||||||
|
|
||||||
|
model.particleEmitters.push_back(std::move(em));
|
||||||
|
}
|
||||||
|
core::Logger::getInstance().debug(" Particle emitters: ", model.particleEmitters.size());
|
||||||
|
}
|
||||||
|
|
||||||
static int m2LoadLogBudget = 200;
|
static int m2LoadLogBudget = 200;
|
||||||
if (m2LoadLogBudget-- > 0) {
|
if (m2LoadLogBudget-- > 0) {
|
||||||
|
|
|
||||||
|
|
@ -824,7 +824,15 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Particle emitter data copy disabled (parsing disabled for now)
|
// Copy particle emitter data and resolve textures
|
||||||
|
gpuModel.particleEmitters = model.particleEmitters;
|
||||||
|
gpuModel.particleTextures.resize(model.particleEmitters.size(), whiteTexture);
|
||||||
|
for (size_t ei = 0; ei < model.particleEmitters.size(); ei++) {
|
||||||
|
uint16_t texIdx = model.particleEmitters[ei].texture;
|
||||||
|
if (texIdx < allTextures.size() && allTextures[texIdx] != 0) {
|
||||||
|
gpuModel.particleTextures[ei] = allTextures[texIdx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copy texture transform data for UV animation
|
// Copy texture transform data for UV animation
|
||||||
gpuModel.textureTransforms = model.textureTransforms;
|
gpuModel.textureTransforms = model.textureTransforms;
|
||||||
|
|
@ -1261,11 +1269,11 @@ void M2Renderer::update(float deltaTime) {
|
||||||
|
|
||||||
computeBoneMatrices(model, instance);
|
computeBoneMatrices(model, instance);
|
||||||
|
|
||||||
// M2 particle emitter update — disabled for now (too expensive with many instances)
|
// M2 particle emitter update
|
||||||
// if (!model.particleEmitters.empty()) {
|
if (!model.particleEmitters.empty()) {
|
||||||
// emitParticles(instance, model, deltaTime);
|
emitParticles(instance, model, deltaTime);
|
||||||
// updateParticles(instance, deltaTime);
|
updateParticles(instance, deltaTime);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue