diff --git a/src/pipeline/m2_loader.cpp b/src/pipeline/m2_loader.cpp index 37d4a08f..733cbe19 100644 --- a/src/pipeline/m2_loader.cpp +++ b/src/pipeline/m2_loader.cpp @@ -252,12 +252,16 @@ T readValue(const std::vector& data, uint32_t offset) { template std::vector readArray(const std::vector& data, uint32_t offset, uint32_t count) { std::vector result; - if (count == 0 || offset + count * sizeof(T) > data.size()) { - return result; - } + if (count == 0) return result; + // Overflow-safe bounds check: avoid uint32 wrap on count * sizeof(T) + size_t totalBytes = static_cast(count) * sizeof(T); + if (totalBytes / sizeof(T) != count) return result; // multiplication overflowed + if (static_cast(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); - std::memcpy(result.data(), &data[offset], count * sizeof(T)); + std::memcpy(result.data(), &data[offset], totalBytes); return result; } @@ -652,17 +656,61 @@ M2Model M2Loader::load(const std::vector& m2Data) { model.attachmentLookup = readArray(m2Data, header.ofsAttachmentLookup, header.nAttachmentLookup); } - // Particle emitter parsing disabled. - // The assumed EMITTER_STRUCT_SIZE (476 bytes) is incorrect for WotLK 3.3.5a M2 files. - // When iterating multiple emitters, each one after the first reads from a misaligned - // offset, producing garbage M2TrackDisk headers with huge nTimestamps/nKeys counts. - // parseAnimTrack then calls readArray which allocates vectors sized by those garbage - // counts — this caused RAM usage to explode from ~1 GB to 130+ GB, consuming all - // system memory and swap. - // TODO: determine the correct emitter struct size for build 12340 and add overflow - // guards to readArray (count * sizeof(T) can wrap uint32_t, bypassing bounds checks). - (void)header.nParticleEmitters; - (void)header.ofsParticleEmitters; + // Parse particle emitters (WotLK M2ParticleOld: 0x1F0 = 496 bytes per emitter) + static constexpr uint32_t EMITTER_STRUCT_SIZE = 0x1F0; + if (header.nParticleEmitters > 0 && header.ofsParticleEmitters > 0 && + header.nParticleEmitters < 256 && + static_cast(header.ofsParticleEmitters) + + static_cast(header.nParticleEmitters) * EMITTER_STRUCT_SIZE <= m2Data.size()) { + + // Build sequence flags for parseAnimTrack + std::vector emSeqFlags; + emSeqFlags.reserve(model.sequences.size()); + 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(m2Data, base + 0x00); + em.flags = readValue(m2Data, base + 0x04); + em.position.x = readValue(m2Data, base + 0x08); + em.position.y = readValue(m2Data, base + 0x0C); + em.position.z = readValue(m2Data, base + 0x10); + em.bone = readValue(m2Data, base + 0x14); + em.texture = readValue(m2Data, base + 0x16); + em.blendingType = readValue(m2Data, base + 0x28); + em.emitterType = readValue(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(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; if (m2LoadLogBudget-- > 0) { diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index 76446b4e..f3cdc29d 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -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 gpuModel.textureTransforms = model.textureTransforms; @@ -1261,11 +1269,11 @@ void M2Renderer::update(float deltaTime) { computeBoneMatrices(model, instance); - // M2 particle emitter update — disabled for now (too expensive with many instances) - // if (!model.particleEmitters.empty()) { - // emitParticles(instance, model, deltaTime); - // updateParticles(instance, deltaTime); - // } + // M2 particle emitter update + if (!model.particleEmitters.empty()) { + emitParticles(instance, model, deltaTime); + updateParticles(instance, deltaTime); + } } }