whiteoutlib 0.1.3

Read and write Blizzard game assets from Rust: models (MDX, M2, M3), textures (BLP, DDS, PNG, JPEG, BMP, TGA, TIFF, GIF) and archives (CASC, MPQ).
Documentation

#include "../../../common/binary_reader.h"
#include "../binary_parse_visitor.h"
#include "../chunk_parser.h"
#include "../wow_file_system.h"

namespace whiteout {
namespace m2 {

using common::BinaryReader;

BinaryParseVisitor::BinaryParseVisitor(common::BinaryReader& reader, WoWFileSystem* wfs,
                                       i32 maxSize_)
    : reader(reader), wfs(wfs), maxSize(maxSize_) {}

void BinaryParseVisitor::start() {
    baseOffset = reader.getPosition();
}

void BinaryParseVisitor::visit(GlobalFlags& flags) {
    flags.value = static_cast<GlobalFlag>(reader.read<u32>());
}

void BinaryParseVisitor::visit(GlobalSequence& seq) {
    seq.timestamp = reader.read<u32>();
}

void BinaryParseVisitor::visit(Sequence& seq) {
    seq.id = reader.read<u16>();
    seq.variationIndex = reader.read<u16>();
    seq.duration = reader.read<u32>();
    seq.movespeed = reader.read<f32>();
    seq.flags = reader.read<SequenceFlag>();
    if (wfs->getAnimBuffer(seq.id, seq.variationIndex).empty()) {
        animBuffers.emplace_back();
    } else {
        auto animData = wfs->getAnimBuffer(seq.id, seq.variationIndex);
        if (hasFlag(globalFlags, GlobalFlag::UpgradedFormat)) {
            ChunkParser chunkParser;
            common::span_streambuf sbuf(animData);
            std::istream in(&sbuf);
            BinaryReader animReader(in);
            AnimFile animFile;
            animFile.animId = seq.id;
            animFile.variant = seq.variationIndex;
            chunkParser.parseChunkedAnim(animReader, animFile, wfs, true);
            animProfiles.emplace_back(std::move(animFile.profile));
            auto& profile = animProfiles.back();
            if (profile.afm2_chunk) {
                animBuffers.emplace_back(profile.afm2_chunk->animationData);
            }
        } else {
            animBuffers.emplace_back(animData);
        }
    }
    seq.frequency = reader.read<i16>();
    seq.padding = reader.read<u16>();
    seq.replayMin = reader.read<u32>();
    seq.replayMax = reader.read<u32>();
    seq.blendTimeIn = reader.read<u16>();
    seq.blendTimeOut = reader.read<u16>();

    seq.bounding.minimum = reader.read<Vector3f>();
    seq.bounding.maximum = reader.read<Vector3f>();
    seq.bounding.sphereRadius = reader.read<f32>();

    seq.variationNext = reader.read<i16>();
    seq.aliasNext = reader.read<u16>();
}

void BinaryParseVisitor::visit(Vertex& vertex) {
    vertex.position = reader.read<Vector3f>();
    vertex.boneWeights = reader.readArray<u8, 4>();
    vertex.boneIndices = reader.readArray<u8, 4>();
    vertex.normal = reader.read<Vector3f>();
    vertex.texCoords = reader.readArray<Vector2f, 2>();
}

void BinaryParseVisitor::visit(Bone& bone) {
    bone.keyBoneId = reader.read<i32>();
    bone.flags = reader.read<u32>();
    bone.parentBoneId = reader.read<i16>();
    bone.submeshId = reader.read<u16>();
    bone.boneNameCRC = reader.read<u32>();

    visit(bone.translation);
    visit(bone.rotation);
    visit(bone.scale);

    bone.pivot = reader.read<Vector3f>();
}

void BinaryParseVisitor::visit(Texture& texture) {
    texture.type = reader.read<u32>();
    texture.flags = reader.read<u32>();
    visit(texture.filename);
}

void BinaryParseVisitor::visit(Material& material) {
    material.flags = reader.read<u16>();
    material.blendingMode = reader.read<u16>();
}

void BinaryParseVisitor::visit(TextureWeight& weight) {
    visit(weight.weight);
}

void BinaryParseVisitor::visit(TextureTransform& transform) {
    visit(transform.translation);
    visit(transform.rotation);
    visit(transform.scaling);
}

void BinaryParseVisitor::visit(ColorAnimation& color) {
    visit(color.color);
    visit(color.alpha);
}

void BinaryParseVisitor::visit(Light& light) {
    light.type = reader.read<u16>();
    light.boneId = reader.read<i16>();
    light.position = reader.read<Vector3f>();

    visit(light.ambientColor);
    visit(light.ambientIntensity);
    visit(light.diffuseColor);
    visit(light.diffuseIntensity);
    visit(light.attenuationStart);
    visit(light.attenuationEnd);
    visit(light.visibility);
}

void BinaryParseVisitor::visit(CameraSpline& spline) {
    spline.value = reader.read<Vector3f>();
    spline.inTangent = reader.read<Vector3f>();
    spline.outTangent = reader.read<Vector3f>();
}

void BinaryParseVisitor::visit(Camera& camera) {
    camera.type = reader.read<u32>();
    if (version < M2_VERSION_CATA) {
        camera.fieldOfView = reader.read<f32>();
    }
    camera.farClip = reader.read<f32>();
    camera.nearClip = reader.read<f32>();

    visit(camera.positions);

    camera.positionBase = reader.read<Vector3f>();

    visit(camera.targetPositions);

    camera.targetPositionBase = reader.read<Vector3f>();

    visit(camera.roll);
    if (version >= M2_VERSION_CATA) {
        visit(camera.fieldOfViewTrack);
    }
}

void BinaryParseVisitor::visit(Attachment& attachment) {
    attachment.id = reader.read<u32>();
    attachment.boneId = reader.read<u16>();
    attachment.unknown = reader.read<u16>();
    attachment.position = reader.read<Vector3f>();
    visit(attachment.animate);
}

void BinaryParseVisitor::visit(RibbonEmitter& emitter) {
    emitter.ribbonId = reader.read<u32>();
    emitter.boneId = reader.read<u32>();
    emitter.position = reader.read<Vector3f>();

    visit(emitter.textureIndices);
    visit(emitter.materialIndices);

    visit(emitter.colorTrack);
    visit(emitter.alphaTrack);
    visit(emitter.heightAbove);
    visit(emitter.heightBelow);

    emitter.edgesPerSecond = reader.read<f32>();
    emitter.edgeLifetime = reader.read<f32>();
    emitter.gravity = reader.read<f32>();
    emitter.textureRows = reader.read<u16>();
    emitter.textureCols = reader.read<u16>();

    visit(emitter.texSlot);
    visit(emitter.visibility);

    emitter.priorityPlane = reader.read<i16>();
    emitter.ribbonColorIndex = reader.read<i8>();
    emitter.textureTransformIndex = reader.read<i8>();
}

void BinaryParseVisitor::visit(ParticleEmitter& emitter) {
    emitter.particleId = reader.read<u32>();
    emitter.flags = reader.read<ParticleFlag>();
    emitter.position = reader.read<Vector3f>();
    emitter.boneId = reader.read<u16>();
    emitter.textureId = reader.read<u16>();

    visit(emitter.particleModelFilename);
    visit(emitter.childEmittersModelFilename);

    emitter.blendingType = static_cast<ParticleBlending>(reader.read<u8>());
    emitter.emitterType = static_cast<ParticleEmitterType>(reader.read<u8>());
    emitter.particleColorIndex = reader.read<u16>();

    emitter.multiTexScale[0] = reader.read<fixed8_5>();
    emitter.multiTexScale[1] = reader.read<fixed8_5>();
    emitter.textureTilerotation = reader.read<i16>();
    emitter.rows = reader.read<u16>();
    emitter.columns = reader.read<u16>();

    visit(emitter.emissionSpeed);
    visit(emitter.speedVariation);
    visit(emitter.verticalRange);
    visit(emitter.horizontalRange);
    visit(emitter.gravity);
    visit(emitter.lifespan);

    emitter.lifespanVariation = reader.read<f32>();

    visit(emitter.emissionRate);

    emitter.emissionRateVariation = reader.read<f32>();

    visit(emitter.emissionAreaWidth);
    visit(emitter.emissionAreaLength);
    visit(emitter.zSource);

    visit(emitter.colorTrack);
    visit(emitter.alphaTrack);
    visit(emitter.scaleTrack);
    emitter.scaleVary = reader.read<Vector2f>();
    visit(emitter.headUVScroll);
    visit(emitter.tailUVScroll);

    emitter.tailLength = reader.read<f32>();
    emitter.twinkleSpeed = reader.read<f32>();
    emitter.twinklePercent = reader.read<f32>();
    emitter.twinkleScale.x = reader.read<f32>();
    emitter.twinkleScale.y = reader.read<f32>();
    emitter.inheritVelocityScale = reader.read<f32>();
    emitter.drag = reader.read<f32>();
    emitter.baseSpin = reader.read<f32>();
    emitter.baseSpinVariation = reader.read<f32>();
    emitter.spinSpeed = reader.read<f32>();
    emitter.spinSpeedVariation = reader.read<f32>();

    emitter.tumble.minimum = reader.read<Vector3f>();
    emitter.tumble.maximum = reader.read<Vector3f>();
    emitter.windVector = reader.read<Vector3f>();
    emitter.windTime = reader.read<f32>();

    emitter.followSpeed1 = reader.read<f32>();
    emitter.followScale1 = reader.read<f32>();
    emitter.followSpeed2 = reader.read<f32>();
    emitter.followScale2 = reader.read<f32>();

    visit(emitter.splinePoints);
    visit(emitter.enabledIn);

    const bool extendedParticle =
        (version > 271) || hasFlag(globalFlags, GlobalFlag::NewParticleRecord);
    if (extendedParticle) {
        emitter.multiTexScrollMid[0][0] = reader.read<fixed16_9>();
        emitter.multiTexScrollMid[0][1] = reader.read<fixed16_9>();
        emitter.multiTexScrollMid[1][0] = reader.read<fixed16_9>();
        emitter.multiTexScrollMid[1][1] = reader.read<fixed16_9>();
        emitter.multiTexScrollRange[0][0] = reader.read<fixed16_9>();
        emitter.multiTexScrollRange[0][1] = reader.read<fixed16_9>();
        emitter.multiTexScrollRange[1][0] = reader.read<fixed16_9>();
        emitter.multiTexScrollRange[1][1] = reader.read<fixed16_9>();
    }
}

void BinaryParseVisitor::visit(Event& event) {
    event.identifier = reader.read<u32>();
    event.data = reader.read<u32>();
    event.boneId = reader.read<u32>();
    event.position = reader.read<Vector3f>();
    visit(event.enabled);
}

void BinaryParseVisitor::visit(MD20Header& header) {
    header.magic = reader.read<u32>();
    header.version = reader.read<u32>();
    version = header.version;

    visit(header.model);
}

void BinaryParseVisitor::visit(Model& header) {
    visit(header.modelName);
    visit(header.globalFlags);
    globalFlags = header.globalFlags.value;

    visit(header.globalLoops);
    visit(header.sequences);
    visit(header.sequenceIdxHashById);

    visit(header.bones);
    visit(header.keyBoneIds);

    visit(header.vertices);

    if (version >= M2_VERSION_WOTLK) {
        header.numSkinProfiles = reader.read<u32>();
        header.skinProfiles.reserve(header.numSkinProfiles);
        header.lodProfiles.reserve(header.numSkinProfiles);
    } else {
        visit(header.skinProfiles);
    }

    visit(header.colors);
    visit(header.textures);
    visit(header.textureWeights);
    visit(header.textureTransforms);
    visit(header.textureIndicesById);
    visit(header.materials);
    visit(header.boneCombos);
    visit(header.textureCombos);
    visit(header.textureCoordCombos);
    visit(header.textureWeightCombos);
    visit(header.textureTransformCombos);
    header.bounding.minimum = reader.read<Vector3f>();
    header.bounding.maximum = reader.read<Vector3f>();
    header.bounding.sphereRadius = reader.read<f32>();

    header.collision.minimum = reader.read<Vector3f>();
    header.collision.maximum = reader.read<Vector3f>();
    header.collision.sphereRadius = reader.read<f32>();

    visit(header.collisionTriangleIndices);
    visit(header.collisionVertices);
    visit(header.collisionFaceNormals);

    visit(header.attachments);
    visit(header.attachmentIndicesById);
    visit(header.events);
    visit(header.lights);
    visit(header.cameras);
    visit(header.cameraIndicesById);
    visit(header.ribbonEmitters);
    visit(header.particleEmitters);

    if (hasFlag(header.globalFlags.value, GlobalFlag::UseTextureCombinerCombos)) {
        visit(header.textureCombinerCombos);
    }
}

void BinaryParseVisitor::visit(std::string& str) {
    const auto count = reader.read<u32>();
    const auto offset = reader.read<u32>();
    if (count == 0) {
        str.clear();
        return;
    }
    const auto currentPos = reader.getPosition();
    reader.setPosition(offset + baseOffset);
    str = reader.readString(count);
    reader.setPosition(currentPos);
}

void BinaryParseVisitor::visit(AnimationTrackBase& track) {
    track.interpolationType = static_cast<InterpolationType>(reader.read<u16>());
    track.globalSequenceId = reader.read<u16>();
    visit(track.timestamps);
}

} // namespace m2
} // namespace whiteout