Exemple #1
0
    private static void ParseM2_Root(string dataPath, M2Data m2Data, M2Texture m2Tex)
    {
        StreamTools s    = new StreamTools();
        string      path = Casc.GetFile(dataPath);

        byte[] M2MainData     = File.ReadAllBytes(path);
        long   streamPosition = 0;

        using (MemoryStream ms = new MemoryStream(M2MainData))
        {
            while (streamPosition < ms.Length)
            {
                ms.Position = streamPosition;
                int chunkID   = s.ReadLong(ms);
                int chunkSize = s.ReadLong(ms);

                streamPosition = ms.Position + chunkSize;

                switch (chunkID)
                {
                case (int)ChunkID.M2ChunkID.MD21:
                    ReadMD21(ms, m2Data, m2Tex);
                    break;

                default:
                    SkipUnknownChunk(ms, chunkID, chunkSize);
                    break;
                }
            }
        };
    }
Exemple #2
0
    // Chunk Data //
    public void ReadMCNKObj(MemoryStream ADTobjstream, string mapname, int MCNKchunkNumber, int MCNKsize)
    {
        if (ADTobjstream.Length == ADTobjstream.Position)
        {
            return;
        }
        StreamTools s              = new StreamTools();
        long        MCNKchnkPos    = ADTobjstream.Position;
        long        streamPosition = ADTobjstream.Position;

        while (streamPosition < MCNKchnkPos + MCNKsize)
        {
            ADTobjstream.Position = streamPosition;
            int chunkID   = s.ReadLong(ADTobjstream);
            int chunkSize = s.ReadLong(ADTobjstream);
            streamPosition = ADTobjstream.Position + chunkSize;
            switch (chunkID)
            {
            case (int)ChunkID.ADT.MCRD:
                ReadMCRD(ADTobjstream, MCNKchunkNumber, chunkSize);     // MCNK.nDoodadRefs into the file's MDDF
                break;

            case (int)ChunkID.ADT.MCRW:
                ReadMCRW(ADTobjstream, MCNKchunkNumber, chunkSize);     // MCNK.nMapObjRefs into the file's MODF
                break;

            default:
                SkipUnknownChunk(ADTobjstream, chunkID, chunkSize);
                break;
            }
        }
    }
Exemple #3
0
    // Terrain Texture Parser //
    private static void ParseADT_Tex(string Path, string MapName, Vector2 coords)
    {
        StreamTools s          = new StreamTools();
        ADTTex      t          = new ADTTex();
        string      ADTtexPath = Path + MapName + "_" + coords.x + "_" + coords.y + "_tex0" + ".adt";
        string      path       = Casc.GetFile(ADTtexPath);

        byte[] ADTtexData = File.ReadAllBytes(path);

        int  MCNKchunkNumber = 0;
        long streamPosition  = 0;

        using (MemoryStream ms = new MemoryStream(ADTtexData))
        {
            while (streamPosition < ms.Length)
            {
                ms.Position = streamPosition;
                int chunkID   = s.ReadLong(ms);
                int chunkSize = s.ReadLong(ms);
                streamPosition = ms.Position + chunkSize;

                switch (chunkID)
                {
                case (int)ChunkID.ADT.MVER:
                    t.ReadMVER(ms);     // ADT file version
                    break;

                case (int)ChunkID.ADT.MAMP:
                    t.ReadMAMP(ms);     // Single value - texture size = 64
                    break;

                case (int)ChunkID.ADT.MTEX:
                    t.ReadMTEX(ms, chunkSize);     // Texture Paths
                    break;

                case (int)ChunkID.ADT.MCNK:
                {
                    t.ReadMCNKtex(ms, MapName, MCNKchunkNumber, chunkSize);         // Texture Data - 256chunks
                    MCNKchunkNumber++;
                }
                break;

                case (int)ChunkID.ADT.MTXF:
                    t.ReadMTXF(ms, chunkSize);     // Texture Paths
                    break;

                case (int)ChunkID.ADT.MTXP:
                    t.ReadMTXP(ms, chunkSize);     // Texture Paths
                    break;

                default:
                    t.SkipUnknownChunk(ms, chunkID, chunkSize);
                    break;
                }
            }
        }
        ADTtexData = null;
    }
Exemple #4
0
    // Terrain Mesh Parser //
    private static void ParseADT_Main(string Path, string MapName, Vector2 coords)  // MS version
    {
        StreamTools s           = new StreamTools();
        ADTRoot     r           = new ADTRoot();
        ChunkID     c           = new ChunkID();
        string      ADTmainPath = Path + MapName + "_" + coords.x + "_" + coords.y + ".adt";
        string      path        = Casc.GetFile(ADTmainPath);

        byte[] ADTmainData = File.ReadAllBytes(path);

        int  MCNKchunkNumber = 0;
        long streamPosition  = 0;

        using (MemoryStream ms = new MemoryStream(ADTmainData))
        {
            while (streamPosition < ms.Length)
            {
                ms.Position = streamPosition;
                int chunkID   = s.ReadLong(ms);
                int chunkSize = s.ReadLong(ms);
                streamPosition = ms.Position + chunkSize;

                switch (chunkID)
                {
                case (int)ChunkID.ADT.MVER:
                    r.ReadMVER(ms);     // ADT file version
                    break;

                case (int)ChunkID.ADT.MHDR:
                    r.ReadMHDR(ms);     // Offsets for specific chunks 0000 if chunks don't exist.
                    break;

                case (int)ChunkID.ADT.MH2O:
                    r.ReadMH2O(ms, chunkSize);     // Water Data
                    break;

                case (int)ChunkID.ADT.MCNK:
                {
                    r.ReadMCNK(ms, MCNKchunkNumber, chunkSize);         // Terrain Data - 256chunks
                    MCNKchunkNumber++;
                }
                break;

                case (int)ChunkID.ADT.MFBO:
                    r.ReadMFBO(ms);     // FlightBounds plane & Death plane
                    break;

                default:
                    r.SkipUnknownChunk(ms, chunkID, chunkSize);
                    break;
                }
            }
        }
        ADTmainData = null;
    }
Exemple #5
0
    // Placement information for WMOs. //
    // Additional to this, the WMOs to render are referenced in each MCRF chunk. (?) //
    public void ReadMODF(MemoryStream ADTobjstream, int MODFsize)
    {
        Flags       f = new Flags();
        StreamTools s = new StreamTools();

        ADTObjData.modelBlockData.WMOInfo = new List <ADTObjData.WMOPlacementInfo>();
        long currentPos = ADTobjstream.Position;

        while (ADTobjstream.Position < currentPos + MODFsize)
        {
            ADTObjData.WMOPlacementInfo data = new ADTObjData.WMOPlacementInfo();

            // references an entry in the MWID chunk, specifying the model to use.
            data.nameID = s.ReadLong(ADTobjstream);

            // this ID should be unique for all ADTs currently loaded. Best, they are unique for the whole map.
            data.uniqueID = s.ReadLong(ADTobjstream);

            // same as in MDDF.
            float Y = ((s.ReadFloat(ADTobjstream) - 17066) * -1) / Settings.worldScale; //-- pos X
            float Z = (s.ReadFloat(ADTobjstream)) / Settings.worldScale;                //-- Height
            float X = ((s.ReadFloat(ADTobjstream) - 17066) * -1) / Settings.worldScale; //-- pos Z
            data.position = new Vector3(X, Z, Y);

            // same as in MDDF.
            float rotX = s.ReadFloat(ADTobjstream);       //-- rot X
            float rotZ = 180 - s.ReadFloat(ADTobjstream); //-- rot Y
            float rotY = s.ReadFloat(ADTobjstream);       //-- rot Z
            data.rotation = Quaternion.Euler(new Vector3(rotX, rotZ, rotY));

            // position plus the transformed wmo bounding box. used for defining if they are rendered as well as collision.
            data.extents = s.ReadBoundingBox(ADTobjstream);

            // values from enum MODFFlags.
            data.flags = f.ReadMODFFlags(ADTobjstream);

            // which WMO doodad set is used.
            data.doodadSet = s.ReadShort(ADTobjstream);

            // which WMO name set is used. Used for renaming goldshire inn to northshire inn while using the same model.
            data.nameSet = s.ReadShort(ADTobjstream);

            // Legion(?)+: has data finally, looks like scaling (same as MDDF). Padding in 0.5.3 alpha.
            int unk = s.ReadShort(ADTobjstream);

            ADTObjData.modelBlockData.WMOInfo.Add(data);
        }
    }
Exemple #6
0
    /////////////////////////////
    ///// MCNKtex Subchunks /////
    /////////////////////////////

    public void ReadMCLY(MemoryStream ADTtexstream, ADTTexData.TextureChunkData chunkData, int MCLYsize)
    {
        /*
         *  Texture layer definitions for this map chunk. 16 bytes per layer, up to 4 layers (thus, layer count = size / 16).
         *  Every texture layer other than the first will have an alpha map to specify blending amounts. The first layer is rendered with full opacity. To know which alphamap is used, there is an offset into the MCAL chunk. That one is relative to MCAL.
         *  You can animate these by setting the flags. Only simple linear animations are possible. You can specify the direction in 45° steps and the speed.
         *  The textureId is just the array index of the filename array in the MTEX chunk.
         *  For getting the right feeling when walking, you should set the effectId which links to GroundEffectTextureRec::m_ID. It defines the little detail doodads as well as the footstep sounds and if footprints are visible. You can set the id to -1 (int16!) to have no detail doodads and footsteps at all. Also, you need to define the currently on-top layer in the MCNK structure for the correct detail doodads to show up!
         *  Introduced in Wrath of the Lich King, terrain can now reflect a skybox. This is used for icecubes made out of ADTs to reflect something. You need to have the MTXF chunk in, if you want that. Look at an skybox Blizzard made to see how you should do it.
         */
        if (MCLYsize == 0)
        {
            return;
        }

        StreamTools s = new StreamTools();
        long        MCLYStartPosition = ADTtexstream.Position;
        int         numberOfLayers    = MCLYsize / 16;

        chunkData.NumberOfTextureLayers = numberOfLayers;
        chunkData.textureIds            = new int[numberOfLayers];
        chunkData.LayerOffsetsInMCAL    = new int[numberOfLayers];
        for (int l = 0; l < numberOfLayers; l++)
        {
            chunkData.textureIds[l] = s.ReadLong(ADTtexstream); // texture ID
            // <flags>
            byte[] arrayOfBytes = new byte[4];
            ADTtexstream.Read(arrayOfBytes, 0, 4);
            BitArray flags = new BitArray(arrayOfBytes);
            int      animation_rotation = (flags[0] ? 1 : 0) + (flags[1] ? 1 : 0) + (flags[2] ? 1 : 0); // each tick is 45°
            int      animation_speed    = (flags[3] ? 1 : 0) + (flags[4] ? 1 : 0) + (flags[5] ? 1 : 0); // 0 to 3
            bool     animation_enabled  = flags[6];
            bool     overbright         = flags[7];                                                     // This will make the texture way brighter. Used for lava to make it "glow".
            bool     use_alpha_map      = flags[8];                                                     // set for every layer after the first
            chunkData.alpha_map_compressed[l] = flags[9];                                               // see MCAL chunk description - MCLY_AlphaType_Flag
            bool use_cube_map_reflection = flags[10];                                                   // This makes the layer behave like its a reflection of the skybox. See below
            bool unknown_0x800           = flags[11];                                                   // WoD?+ if either of 0x800 or 0x1000 is set, texture effects' texture_scale is applied
            bool unknown_0x1000          = flags[12];                                                   // WoD?+ see 0x800
            // flags 13-32 unused
            // </flags>
            int layerOffset = s.ReadLong(ADTtexstream);
            chunkData.LayerOffsetsInMCAL[l] = layerOffset;
            int effectId = s.ReadLong(ADTtexstream); //foreign_keyⁱ <uint32_t, &GroundEffectTextureRec::m_ID>; // 0xFFFFFFFF for none, in alpha: uint16_t + padding
        }
    }
Exemple #7
0
    // List of offsets of WMO filenames in the MWMO chunk. //
    public void ReadMWID(MemoryStream ADTobjstream, int MWIDsize)
    {
        StreamTools s          = new StreamTools();
        long        currentPos = ADTobjstream.Position;

        while (ADTobjstream.Position < currentPos + MWIDsize)
        {
            ADTObjData.modelBlockData.WMOOffsets.Add(s.ReadLong(ADTobjstream));
        }
    }
Exemple #8
0
    public void ReadMH2O(MemoryStream ADTstream, int MH2Osize)
    {
        StreamTools s = new StreamTools();
        long        chunkStartPosition = ADTstream.Position;

        // header - SMLiquidChunk
        for (int a = 0; a < 256; a++)
        {
            int offset_instances  = s.ReadLong(ADTstream);      // points to SMLiquidInstance[layer_count]
            int layer_count       = s.ReadLong(ADTstream);      // 0 if the chunk has no liquids. If > 1, the offsets will point to arrays.
            int offset_attributes = s.ReadLong(ADTstream);      // points to mh2o_chunk_attributes, can be ommitted for all-0
            if (offset_instances >= 0)
            {
                // instances @24bytes
                ADTstream.Seek(chunkStartPosition + offset_instances, SeekOrigin.Begin);
                int liquid_type          = s.ReadShort(ADTstream); //DBC - foreign_keyⁱ<uint16_t, &LiquidTypeRec::m_ID> liquid_type;
                int liquid_object_or_lvf = s.ReadShort(ADTstream); //DBC -  foreign_keyⁱ<uint16_t, &LiquidObjectRec::m_ID> liquid_object_or_lvf;
                                                                   // if > 41, an id into DB/LiquidObject. If below, LiquidVertexFormat, used in ADT/v18#instance_vertex_data Note hardcoded LO ids below.
                                                                   // if >= 42, look up via DB/LiquidType and DB/LiquidMaterial, otherwise use liquid_object_or_lvf as LVF
                                                                   // also see below for offset_vertex_data: if that's 0 and lt ≠ 2 → lvf = 2
                float min_height_level = s.ReadFloat(ADTstream);   // used as height if no heightmap given and culling ᵘ
                float max_height_level = s.ReadFloat(ADTstream);   // ≥ WoD ignores value and assumes to both be 0.0 for LVF = 2! ᵘ
                int   x_offset         = ADTstream.ReadByte();     // The X offset of the liquid square (0-7)
                int   y_offset         = ADTstream.ReadByte();     // The Y offset of the liquid square (0-7)
                int   width            = ADTstream.ReadByte();     // The width of the liquid square (1-8)
                int   height           = ADTstream.ReadByte();     // The height of the liquid square (1-8)
                                                                   // The above four members are only used if liquid_object_or_lvf <= 41. Otherwise they are assumed 0, 0, 8, 8. (18179)
                int offset_exists_bitmap = s.ReadLong(ADTstream);  // not all tiles in the instances need to be filled. always 8*8 bits.
                                                                   // offset can be 0 for all-exist. also see (and extend) Talk:ADT/v18#SMLiquidInstance
                int offset_vertex_data = s.ReadLong(ADTstream);    // actual data format defined by LiquidMaterialRec::m_LVF via LiquidTypeRec::m_materialID
                                                                   // if offset = 0 and liquidType ≠ 2, then let LVF = 2, i.e. some ocean shit
            }
            //attributes
            if (offset_attributes >= 0)
            {
                ADTstream.Seek(chunkStartPosition + offset_attributes, SeekOrigin.Begin);
                ulong fishable = s.ReadUint64(ADTstream);               // seems to be usable as visibility information.
                ulong deep     = s.ReadUint64(ADTstream);
            }
        }
        ADTstream.Seek(chunkStartPosition + MH2Osize, SeekOrigin.Begin); // set stream location to right after MH2O
    }
Exemple #9
0
    // Placement information for doodads (M2 models). //
    // Additional to this, the models to render are referenced in each MCRF chunk. //
    public void ReadMDDF(MemoryStream ADTobjstream, int MDDFsize)
    {
        Flags       f = new Flags();
        StreamTools s = new StreamTools();

        ADTObjData.modelBlockData.M2Info = new List <ADTObjData.M2PlacementInfo>();
        long currentPos = ADTobjstream.Position;

        while (ADTobjstream.Position < currentPos + MDDFsize)
        {
            ADTObjData.M2PlacementInfo data = new ADTObjData.M2PlacementInfo();

            // References an entry in the MMID chunk, specifying the model to use.
            data.nameID = s.ReadLong(ADTobjstream);

            // This ID should be unique for all ADTs currently loaded.
            // Best, they are unique for the whole map. Blizzard has these unique for the whole game.
            data.uniqueID = s.ReadLong(ADTobjstream);

            // This is relative to a corner of the map. Subtract 17066 from the non vertical values and you should start to see
            // something that makes sense. You'll then likely have to negate one of the non vertical values in whatever coordinate
            // system you're using to finally move it into place.
            float Y = ((s.ReadFloat(ADTobjstream) - 17066) * -1) / Settings.worldScale; //-- pos X
            float Z = (s.ReadFloat(ADTobjstream)) / Settings.worldScale;                //-- Height
            float X = ((s.ReadFloat(ADTobjstream) - 17066) * -1) / Settings.worldScale; //-- pos Z
            data.position = new Vector3(X, Z, Y);

            // degrees. This is not the same coordinate system orientation like the ADT itself! (see history.)
            float rotX = s.ReadFloat(ADTobjstream);       //-- rot X
            float rotZ = 180 - s.ReadFloat(ADTobjstream); //-- rot Y
            float rotY = s.ReadFloat(ADTobjstream);       //-- rot Z
            data.rotation = Quaternion.Euler(new Vector3(rotX, rotZ, rotY));

            // 1024 is the default size equaling 1.0f.
            data.scale = s.ReadShort(ADTobjstream) / 1024.0f;

            // values from struct MDDFFlags.
            data.flags = f.ReadMDDFFlags(ADTobjstream);

            ADTObjData.modelBlockData.M2Info.Add(data);
        }
    }
Exemple #10
0
    // MCNK.nMapObjRefs into the file's MODF //
    public void ReadMCRW(MemoryStream ADTobjstream, int MCNKchunkNumber, int MCRWsize)
    {
        StreamTools s           = new StreamTools();
        List <int>  MODFentries = new List <int>();
        long        currentPos  = ADTobjstream.Position;

        while (ADTobjstream.Position < currentPos + MCRWsize)
        {
            MODFentries.Add(s.ReadLong(ADTobjstream));
        }
    }
Exemple #11
0
    public void ReadMCNKtex(MemoryStream ADTtexstream, string mapname, int MCNKchunkNumber, int MCNKsize)
    {
        if (ADTtexstream.Length == ADTtexstream.Position)
        {
            return;
        }

        StreamTools s = new StreamTools();

        ADTTexData.TextureChunkData chunkData = new ADTTexData.TextureChunkData();

        long MCNKchnkPos    = ADTtexstream.Position;
        long streamPosition = ADTtexstream.Position;

        while (streamPosition < MCNKchnkPos + MCNKsize)
        {
            ADTtexstream.Position = streamPosition;
            int chunkID   = s.ReadLong(ADTtexstream);
            int chunkSize = s.ReadLong(ADTtexstream);
            streamPosition = ADTtexstream.Position + chunkSize;
            switch (chunkID)
            {
            case (int)ChunkID.ADT.MCLY:
                ReadMCLY(ADTtexstream, chunkData, chunkSize);     // texture layers
                break;

            case (int)ChunkID.ADT.MCSH:
                ReadMCSH(ADTtexstream, chunkData);     // static shadow maps
                break;

            case (int)ChunkID.ADT.MCAL:
                ReadMCAL(ADTtexstream, mapname, chunkData);     // alpha layers
                break;

            default:
                SkipUnknownChunk(ADTtexstream, chunkID, chunkSize);
                break;
            }
        }
        ADTTexData.textureBlockData.textureChunksData.Add(chunkData);
    }
Exemple #12
0
    public void ReadMTXP(MemoryStream ADTtexstream, int MTXPsize)  // 16 bytes per MTEX texture
    {
        StreamTools s = new StreamTools();
        Flags       f = new Flags();

        ADTTexData.textureBlockData.MTXP = true;
        for (int i = 0; i < MTXPsize / 16; i++)
        {
            ADTTexData.textureBlockData.textureFlags.Add(ADTTexData.textureBlockData.terrainTexturePaths[i], f.ReadTerrainTextureFlag(ADTtexstream));
            // default 0.0 -- the _h texture values are scaled to [0, value) to determine actual "height".
            // this determines if textures overlap or not (e.g. roots on top of roads).
            ADTTexData.textureBlockData.heightScales.Add(ADTTexData.textureBlockData.terrainTexturePaths[i], s.ReadFloat(ADTtexstream));
            // default 1.0 -- note that _h based chunks are still influenced by MCAL (blendTex below)
            ADTTexData.textureBlockData.heightOffsets.Add(ADTTexData.textureBlockData.terrainTexturePaths[i], s.ReadFloat(ADTtexstream));
            // no default, no non-zero values in 20490
            int padding = s.ReadLong(ADTtexstream);
        }
    }
Exemple #13
0
    public static void ParseSkin(MemoryStream ms, M2Data m2Data)
    {
        StreamTools s = new StreamTools();

        string  magic        = s.ReadFourCC(ms);    // 'SKIN'
        M2Array indices      = s.ReadM2Array(ms);
        M2Array triangles    = s.ReadM2Array(ms);
        M2Array bones        = s.ReadM2Array(ms);
        M2Array submeshes    = s.ReadM2Array(ms);
        M2Array batches      = s.ReadM2Array(ms); // nTexture_units
        int     boneCountMax = s.ReadLong(ms);    // WoW takes this and divides it by the number of bones in each submesh, then stores the biggest one.
                                                  // Maximum number of bones per drawcall for each view. Related to (old) GPU numbers of registers.
                                                  // Values seen : 256, 64, 53, 21
        M2Array shadow_batches = s.ReadM2Array(ms);

        /// Read Batches ///
        ms.Seek(batches.offset, SeekOrigin.Begin);
        for (var batch = 0; batch < batches.size; batch++)
        {
            M2BatchIndices m2BatchIndices = new M2BatchIndices();

            m2BatchIndices.M2Batch_flags            = s.ReadShort(ms);      // probably two uint8_t? -- Usually 16 for static textures, and 0 for animated textures. &0x1: materials invert something; &0x2: transform &0x4: projected texture; &0x10: something batch compatible; &0x20: projected texture?; &0x40: transparency something
            m2BatchIndices.M2Batch_shader_id        = s.ReadShort(ms);      // See below.
            m2BatchIndices.M2Batch_submesh_index    = s.ReadShort(ms);      // A duplicate entry of a submesh from the list above.
            m2BatchIndices.M2Batch_submesh_index2   = s.ReadShort(ms);      // See below.
            m2BatchIndices.M2Batch_color_index      = s.ReadShort(ms);      // A Color out of the Colors-Block or -1 if none.
            m2BatchIndices.M2Batch_render_flags     = s.ReadShort(ms);      // The renderflags used on this texture-unit.
            m2BatchIndices.M2Batch_layer            = s.ReadShort(ms);      //
            m2BatchIndices.M2Batch_op_count         = s.ReadShort(ms);      // 1 to 4. See below. Also seems to be the number of textures to load, starting at the texture lookup in the next field (0x10).
            m2BatchIndices.M2Batch_texture          = s.ReadShort(ms);      // Index into Texture lookup table
            m2BatchIndices.M2Batch_tex_unit_number2 = s.ReadShort(ms);      // Index into the texture unit lookup table.
            m2BatchIndices.M2Batch_transparency     = s.ReadShort(ms);      // Index into transparency lookup table.
            m2BatchIndices.M2Batch_texture_anim     = s.ReadShort(ms);      // Index into uvanimation lookup table.

            m2Data.m2BatchIndices.Add(m2BatchIndices);
        }

        // Read SubMesh Data //

        int[] Indices   = new int[indices.size];
        int[] Triangles = new int[triangles.size];

        int[] skinSectionId         = new int[submeshes.size];                  // Mesh part ID, see below.
        int[] submesh_StartVertex   = new int[submeshes.size];                  // Starting vertex number.
        int[] submesh_NbrVerts      = new int[submeshes.size];                  // Number of vertices.
        int[] submesh_StartTriangle = new int[submeshes.size];                  // Starting triangle index (that's 3* the number of triangles drawn so far).
        int[] submesh_NbrTris       = new int[submeshes.size];                  // Number of triangle indices.

        int[] submesh_boneCount      = new int[submeshes.size];                 // Number of elements in the bone lookup table. Max seems to be 256 in Wrath. Shall be ≠ 0.
        int[] submesh_boneComboIndex = new int[submeshes.size];                 // Starting index in the bone lookup table.
        int[] submesh_boneInfluences = new int[submeshes.size];                 // <= 4
                                                                                // from <=BC documentation: Highest number of bones needed at one time in this Submesh --Tinyn (wowdev.org)
                                                                                // In 2.x this is the amount of of bones up the parent-chain affecting the submesh --NaK
                                                                                // Highest number of bones referenced by a vertex of this submesh. 3.3.5a and suspectedly all other client revisions. -- Skarn
        int[]     submesh_centerBoneIndex    = new int[submeshes.size];
        Vector3[] submesh_centerPosition     = new Vector3[submeshes.size];     // Average position of all the vertices in the sub mesh.
        Vector3[] submesh_sortCenterPosition = new Vector3[submeshes.size];     // The center of the box when an axis aligned box is built around the vertices in the submesh.
        float[]   submesh_sortRadius         = new float[submeshes.size];       // Distance of the vertex farthest from CenterBoundingBox.

        /// Indices ///
        ms.Seek(indices.offset, SeekOrigin.Begin);
        for (var ind = 0; ind < indices.size; ind++)
        {
            Indices[ind] = s.ReadShort(ms);
        }

        /// triangles ///
        ms.Seek(triangles.offset, SeekOrigin.Begin);
        for (var tri = 0; tri < triangles.size; tri++)
        {
            Triangles[tri] = s.ReadShort(ms);
        }

        /// submeshes ///
        ms.Seek(submeshes.offset, SeekOrigin.Begin);
        for (var sub = 0; sub < submeshes.size; sub++)
        {
            skinSectionId[sub] = s.ReadUint16(ms);
            int Level = s.ReadUint16(ms);                            // (level << 16) is added (|ed) to startTriangle and alike to avoid having to increase those fields to uint32s.
            submesh_StartVertex[sub]   = s.ReadUint16(ms) + (Level << 16);
            submesh_NbrVerts[sub]      = s.ReadUint16(ms);
            submesh_StartTriangle[sub] = s.ReadUint16(ms) + (Level << 16);
            submesh_NbrTris[sub]       = s.ReadUint16(ms);

            submesh_boneCount[sub]       = s.ReadUint16(ms);
            submesh_boneComboIndex[sub]  = s.ReadUint16(ms);
            submesh_boneInfluences[sub]  = s.ReadUint16(ms);
            submesh_centerBoneIndex[sub] = s.ReadUint16(ms);
            Vector3 raw_centerPosition = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale);
            submesh_centerPosition[sub] = new Vector3(-raw_centerPosition.x, raw_centerPosition.z, -raw_centerPosition.y);
            Vector3 raw_sortCenterPosition = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale);
            submesh_sortCenterPosition[sub] = new Vector3(-raw_sortCenterPosition.x, raw_sortCenterPosition.z, -raw_sortCenterPosition.y);
            submesh_sortRadius[sub]         = s.ReadFloat(ms);
        }

        /// Assemble Submeshes ///
        m2Data.submeshData = new List <SubmeshData>();
        for (int sm = 0; sm < submeshes.size; sm++)
        {
            Vector3[] vertList  = new Vector3[submesh_NbrVerts[sm]];
            Vector3[] normsList = new Vector3[submesh_NbrVerts[sm]];
            Vector2[] uvsList   = new Vector2[submesh_NbrVerts[sm]];
            Vector2[] uvs2List  = new Vector2[submesh_NbrVerts[sm]];

            BoneWeights[] boneWeights = new BoneWeights[submesh_NbrVerts[sm]];

            for (int vn = 0; vn < submesh_NbrVerts[sm]; vn++)
            {
                vertList[vn]  = m2Data.meshData.pos[vn + submesh_StartVertex[sm]];
                normsList[vn] = m2Data.meshData.normal[vn + submesh_StartVertex[sm]];
                uvsList[vn]   = m2Data.meshData.tex_coords[vn + submesh_StartVertex[sm]];
                uvs2List[vn]  = m2Data.meshData.tex_coords2[vn + submesh_StartVertex[sm]];

                BoneWeights boneWeightVert = new BoneWeights();
                int[]       boneIndex      = new int[4];
                float[]     boneWeight     = new float[4];

                for (int bn = 0; bn < 4; bn++)
                {
                    boneIndex[bn]  = m2Data.meshData.bone_indices[vn + submesh_boneComboIndex[sm]][bn];
                    boneWeight[bn] = m2Data.meshData.bone_weights[vn + submesh_boneComboIndex[sm]][bn];
                }
                boneWeightVert.boneIndex  = boneIndex;
                boneWeightVert.boneWeight = boneWeight;
                boneWeights[vn]           = boneWeightVert;
            }

            int[] triList = new int[submesh_NbrTris[sm]];
            for (var t = 0; t < submesh_NbrTris[sm]; t++)
            {
                //triList[t] = Triangles[t + submesh_StartTriangle[sm]] - submesh_StartVertex[sm];  // using Separate Meshes, reset first triangle to index 0;
                triList[t] = Triangles[t + submesh_StartTriangle[sm]];                              // using Unity Submeshes, don't reset first triangle to index 0;
            }


            SubmeshData submeshData = new SubmeshData();

            submeshData.ID        = skinSectionId[sm];
            submeshData.vertList  = vertList;
            submeshData.normsList = normsList;
            submeshData.uvsList   = uvsList;
            submeshData.uvs2List  = uvs2List;
            Array.Reverse(triList);
            submeshData.triList                = triList;
            submeshData.submesh_StartVertex    = submesh_StartVertex[sm];
            submeshData.boneWeights            = boneWeights;
            submeshData.submesh_boneCount      = submesh_boneCount[sm];
            submeshData.submesh_boneInfluences = submesh_boneInfluences[sm];
            m2Data.submeshData.Add(submeshData);
        }

        /// Assemble Bone Data ///
        ///
    }
Exemple #14
0
    public static void Read(string fileName, byte[] fileData)
    {
        using (MemoryStream ms = new MemoryStream(fileData))
        {
            // Header //
            StreamTools         s      = new StreamTools();
            DB2.wdc1_db2_header header = ReadHeader(ms);

            // Field Meta Data //
            fields = new DB2.field_structure[header.total_field_count];
            for (int f = 0; f < header.total_field_count; f++)
            {
                DB2.field_structure field = new DB2.field_structure();
                field.size   = s.ReadShort(ms);
                field.offset = s.ReadShort(ms);
                fields[f]    = field;
            }

            if (!header.flags.HasFlag(DB2.DB2Flags.Sparse))
            {
                /// Normal records ///

                // Read Records Data //
                recordsData = new byte[header.record_count * header.record_size];
                ms.Read(recordsData, 0, header.record_count * header.record_size);
                Array.Resize(ref recordsData, recordsData.Length + 8);

                // Read String Data //
                //Debug.Log("header.string_table_size " + header.string_table_size);
                m_stringsTable = new Dictionary <long, string>();
                for (int i = 0; i < header.string_table_size;)
                {
                    long oldPos = ms.Position;

                    m_stringsTable[i] = s.ReadCString(ms, Encoding.UTF8);
                    //Debug.Log(m_stringsTable[i]);

                    i += (int)(ms.Position - oldPos);
                }
            }
            else
            {
                /// Offset map records /// -- these records have null-terminated strings inlined, and
                // since they are variable-length, they are pointed to by an array of 6-byte
                // offset+size pairs.

                ms.Read(recordsData, 0, header.offset_map_offset - headerSize - Marshal.SizeOf <DB2.field_structure>() * header.total_field_count);

                if (ms.Position != header.offset_map_offset)
                {
                    Debug.LogError("Error: r.BaseStream.Position != sparseTableOffset");
                }

                Dictionary <uint, int>      offSetKeyMap      = new Dictionary <uint, int>();
                List <DB2.offset_map_entry> tempSparseEntries = new List <DB2.offset_map_entry>();

                for (int i = 0; i < (header.max_id - header.min_id + 1); i++)
                {
                    DB2.offset_map_entry sparse = s.Read <DB2.offset_map_entry>(ms);

                    if (sparse.offset == 0 || sparse.size == 0)
                    {
                        continue;
                    }

                    // special case, may contain duplicates in the offset map that we don't want
                    if (header.copy_table_size == 0)
                    {
                        if (offSetKeyMap.ContainsKey(sparse.offset))
                        {
                            continue;
                        }
                    }

                    tempSparseEntries.Add(sparse);
                    offSetKeyMap.Add(sparse.offset, 0);
                }
                sparseEntries = tempSparseEntries.ToArray();
            }

            // Index Data //
            m_indexData = new int[header.id_list_size / 4];
            for (int iD = 0; iD < header.id_list_size / 4; iD++)
            {
                m_indexData[iD] = s.ReadLong(ms);
            }

            // Duplicate Rows Data //
            Dictionary <int, int> copyData = new Dictionary <int, int>();
            for (int i = 0; i < header.copy_table_size / 8; i++)
            {
                copyData[s.ReadLong(ms)] = s.ReadLong(ms);
            }

            for (int t = 0; t < header.record_count; t++)
            {
                if (copyData.ContainsKey(t))
                {
                    Debug.Log("copy " + t + " to " + copyData[t]);
                }
            }

            // Column Meta Data //
            m_columnMeta = new DB2.ColumnMetaData[header.total_field_count];
            //Debug.Log("header.total_field_count " + header.total_field_count);
            for (int cmd = 0; cmd < header.total_field_count; cmd++)
            {
                DB2.ColumnMetaData columnData = new DB2.ColumnMetaData();
                columnData.RecordOffset       = s.ReadUint16(ms);
                columnData.Size               = s.ReadUint16(ms);
                columnData.AdditionalDataSize = s.ReadUint32(ms);
                columnData.CompressionType    = (DB2.CompressionType)s.ReadLong(ms);
                switch (columnData.CompressionType)
                {
                case DB2.CompressionType.Immediate:
                {
                    DB2.ColumnCompressionData_Immediate Immediate = new DB2.ColumnCompressionData_Immediate();
                    Immediate.BitOffset  = s.ReadUint32(ms);
                    Immediate.BitWidth   = s.ReadUint32(ms);
                    Immediate.Flags      = s.ReadUint32(ms);
                    columnData.Immediate = Immediate;
                    break;
                }

                case DB2.CompressionType.Pallet:
                {
                    DB2.ColumnCompressionData_Pallet Pallet = new DB2.ColumnCompressionData_Pallet();
                    Pallet.BitOffset   = s.ReadUint32(ms);
                    Pallet.BitWidth    = s.ReadUint32(ms);
                    Pallet.Cardinality = s.ReadUint32(ms);
                    columnData.Pallet  = Pallet;
                    break;
                }

                case DB2.CompressionType.Common:
                {
                    DB2.ColumnCompressionData_Common Common = new DB2.ColumnCompressionData_Common();
                    Common.DefaultValue = s.ReadValue32(ms, 32);
                    Common.B            = s.ReadUint32(ms);
                    Common.C            = s.ReadUint32(ms);
                    columnData.Common   = Common;
                    break;
                }

                default:
                {
                    s.ReadUint32(ms);
                    s.ReadUint32(ms);
                    s.ReadUint32(ms);
                    break;
                }
                }
                m_columnMeta[cmd] = columnData;
            }

            // Pallet Data //
            m_palletData = new DB2.Value32[m_columnMeta.Length][];
            for (int i = 0; i < m_columnMeta.Length; i++)
            {
                if (m_columnMeta[i].CompressionType == DB2.CompressionType.Pallet || m_columnMeta[i].CompressionType == DB2.CompressionType.PalletArray)
                {
                    m_palletData[i] = s.ReadArray <DB2.Value32>(ms, (int)m_columnMeta[i].AdditionalDataSize / 4);
                }
            }

            // Common Data //
            m_commonData = new Dictionary <int, DB2.Value32> [m_columnMeta.Length];
            for (int i = 0; i < m_columnMeta.Length; i++)
            {
                //Debug.Log(m_columnMeta[i].CompressionType);
                if (m_columnMeta[i].CompressionType == DB2.CompressionType.Common)
                {
                    Dictionary <int, DB2.Value32> commonValues = new Dictionary <int, DB2.Value32>();
                    m_commonData[i] = commonValues;

                    for (int j = 0; j < m_columnMeta[i].AdditionalDataSize / 8; j++)
                    {
                        commonValues[s.ReadLong(ms)] = s.Read <DB2.Value32>(ms);
                    }
                }
            }

            // Reference Data //
            DB2.ReferenceData refData = null;

            if (header.relationship_data_size > 0)
            {
                refData = new DB2.ReferenceData
                {
                    NumRecords = s.ReadLong(ms),
                    MinId      = s.ReadLong(ms),
                    MaxId      = s.ReadLong(ms)
                };

                refData.Entries = s.ReadArray <DB2.ReferenceEntry>(ms, refData.NumRecords);
            }

            //DB2.BitReader bitReader = new DB2.BitReader(recordsData);

            //int position = 0;
            using (MemoryStream rs = new MemoryStream(recordsData))
            {
                for (int i = 0; i < header.record_count; ++i)
                {
                    int[] values = new int[m_columnMeta.Length];
                    values[0] = s.ReadUint32(rs);
                    values[1] = s.ReadUint16(rs);
                    values[2] = s.ReadUint16(rs);
                    values[3] = s.ReadUint32(rs);

                    //Debug.Log(i + " " + values[0] + " " + values[1] + " " + values[2] + " " + values[3]);
                }
            }


            for (int i = 0; i < header.record_count; ++i)
            {
                int[] values = new int[m_columnMeta.Length];
                //values[0] = s.ReadUint32(ms);
                //values[1] = s.ReadUint16(ms);
                //values[2] = s.ReadUint16(ms);
                //values[3] = ms.ReadByte();

                //values[0] = new recordsData[position]

                //Debug.Log(recordsData[])

                //Debug.Log(i + " " + values[0] + " " + values[1] + " " + values[2] + " " + values[3]);

                for (int j = 0; j < m_columnMeta.Length; j++)
                {
                    /*
                     * if (m_columnMeta[j].CompressionType == DB2.CompressionType.Common)
                     *  values[j] = m_commonData[j][i].GetValue<int>();
                     * if (m_columnMeta[j].CompressionType == DB2.CompressionType.Pallet)
                     *  values[j] = m_palletData[j][i].GetValue<int>();
                     */
                }

                /*
                 * bitReader.Position = 0;
                 * bitReader.Offset = i * header.record_size;
                 *
                 * DB2.IDB2Row rec = new WDC1Row(this, bitReader, indexDataSize != 0 ? m_indexData[i] : -1, refData?.Entries[i]);
                 * rec.RecordIndex = i;
                 *
                 * if (indexDataSize != 0)
                 *  _Records.Add(m_indexData[i], rec);
                 * else
                 *  _Records.Add(rec.Id, rec);
                 */
            }
        }
    }
Exemple #15
0
    // Terrain Models Parser //
    public static void ParseADT_Obj(string Path, string MapName, Vector2 coords)
    {
        StreamTools s          = new StreamTools();
        ADTObj      o          = new ADTObj();
        string      ADTobjPath = Path + MapName + "_" + coords.x + "_" + coords.y + "_obj0" + ".adt";
        string      path       = Casc.GetFile(ADTobjPath);

        byte[] ADTobjData = File.ReadAllBytes(path);

        int  MCNKchunkNumber = 0;
        long streamPosition  = 0;

        using (MemoryStream ms = new MemoryStream(ADTobjData))
        {
            while (streamPosition < ms.Length)
            {
                ms.Position = streamPosition;
                int chunkID   = s.ReadLong(ms);
                int chunkSize = s.ReadLong(ms);
                streamPosition = ms.Position + chunkSize;

                switch (chunkID)
                {
                case (int)ChunkID.ADT.MVER:
                    o.ReadMVER(ms);     // ADT file version
                    break;

                case (int)ChunkID.ADT.MMDX:
                    o.ReadMMDX(ms, chunkSize);     // List of filenames for M2 models
                    break;

                case (int)ChunkID.ADT.MMID:
                    o.ReadMMID(ms, chunkSize);     // List of offsets of model filenames in the MMDX chunk.
                    break;

                case (int)ChunkID.ADT.MWMO:
                    o.ReadMWMO(ms, chunkSize);     // List of filenames for WMOs (world map objects) that appear in this map tile.
                    break;

                case (int)ChunkID.ADT.MWID:
                    o.ReadMWID(ms, chunkSize);     // List of offsets of WMO filenames in the MWMO chunk.
                    break;

                case (int)ChunkID.ADT.MDDF:
                    o.ReadMDDF(ms, chunkSize);     // Placement information for doodads (M2 models).
                    break;

                case (int)ChunkID.ADT.MODF:
                    o.ReadMODF(ms, chunkSize);     // Placement information for WMOs.
                    break;

                case (int)ChunkID.ADT.MCNK:
                {
                    o.ReadMCNKObj(ms, MapName, MCNKchunkNumber, chunkSize);         // 256chunks
                    MCNKchunkNumber++;
                }
                break;

                default:
                    SkipUnknownChunk(ms, chunkID, chunkSize);
                    break;
                }
            }
        }
        ADTobjData = null;
    }
Exemple #16
0
    public void ReadMCNK(MemoryStream ADTstream, int MCNKchunkNumber, int MCNKsize)
    {
        StreamTools s = new StreamTools();
        Flags       f = new Flags();

        ADTRootData.MeshChunkData chunkData = new ADTRootData.MeshChunkData();
        long MCNKchnkPos = ADTstream.Position;

        // <Header> - 128 bytes
        chunkData.flags = f.ReadMCNKflags(ADTstream);

        chunkData.IndexX  = s.ReadLong(ADTstream);
        chunkData.IndexY  = s.ReadLong(ADTstream);
        chunkData.nLayers = s.ReadLong(ADTstream);  // maximum 4
        int nDoodadRefs = s.ReadLong(ADTstream);

        chunkData.holes_high_res = s.ReadUint64(ADTstream);  // only used with flags.high_res_holes
        int ofsLayer    = s.ReadLong(ADTstream);
        int ofsRefs     = s.ReadLong(ADTstream);
        int ofsAlpha    = s.ReadLong(ADTstream);
        int sizeAlpha   = s.ReadLong(ADTstream);
        int ofsShadow   = s.ReadLong(ADTstream); // only with flags.has_mcsh
        int sizeShadow  = s.ReadLong(ADTstream);
        int areaid      = s.ReadLong(ADTstream); // in alpha: both zone id and sub zone id, as uint16s.
        int nMapObjRefs = s.ReadLong(ADTstream);

        chunkData.holes_low_res = s.ReadShort(ADTstream);
        int unknown_but_used = s.ReadShort(ADTstream);       // in alpha: padding

        byte[] ReallyLowQualityTextureingMap = new byte[16]; // uint2_t[8][8] "predTex", It is used to determine which detail doodads to show. Values are an array of two bit
        for (int b = 0; b < 16; b++)
        {
            ReallyLowQualityTextureingMap[b] = (byte)ADTstream.ReadByte();
        }
        // unsigned integers, naming the layer.
        ulong noEffectDoodad = s.ReadUint64(ADTstream);                 // WoD: may be an explicit MCDD chunk
        int   ofsSndEmitters = s.ReadLong(ADTstream);
        int   nSndEmitters   = s.ReadLong(ADTstream);                   // will be set to 0 in the client if ofsSndEmitters doesn't point to MCSE!
        int   ofsLiquid      = s.ReadLong(ADTstream);
        int   sizeLiquid     = s.ReadLong(ADTstream);                   // 8 when not used; only read if >8.

        // in alpha, remainder is padding but unused.
        chunkData.MeshPosition = new Vector3(s.ReadFloat(ADTstream), s.ReadFloat(ADTstream), s.ReadFloat(ADTstream));
        int ofsMCCV = s.ReadLong(ADTstream);                             // only with flags.has_mccv, had uint32_t textureId; in ObscuR's structure.
        int ofsMCLV = s.ReadLong(ADTstream);                             // introduced in Cataclysm
        int unused  = s.ReadLong(ADTstream);                             // currently unused

        // </header>

        if (!chunkData.flags.has_mccv)
        {
            FillMCCV(chunkData); // fill vertex shading with 127...
        }
        long streamPosition = ADTstream.Position;

        while (streamPosition < MCNKchnkPos + MCNKsize)
        {
            ADTstream.Position = streamPosition;
            int chunkID   = s.ReadLong(ADTstream);
            int chunkSize = s.ReadLong(ADTstream);
            streamPosition = ADTstream.Position + chunkSize;
            switch (chunkID)
            {
            case (int)ChunkID.ADT.MCVT:
                ReadMCVT(ADTstream, chunkData);     // vertex heights
                break;

            case (int)ChunkID.ADT.MCLV:
                ReadMCLV(ADTstream, chunkData);     // chunk lighting
                break;

            case (int)ChunkID.ADT.MCCV:
                ReadMCCV(ADTstream, chunkData);     // vertex shading
                break;

            case (int)ChunkID.ADT.MCNR:
                ReadMCNR(ADTstream, chunkData);     // normals
                break;

            case (int)ChunkID.ADT.MCSE:
                ReadMCSE(ADTstream, chunkData, chunkSize);     // sound emitters
                break;

            case (int)ChunkID.ADT.MCBB:
                ReadMCBB(ADTstream, chunkData, chunkSize);
                break;

            case (int)ChunkID.ADT.MCDD:
                ReadMCDD(ADTstream, chunkData, chunkSize);
                break;

            default:
                SkipUnknownChunk(ADTstream, chunkID, chunkSize);
                break;
            }
        }
        ADTRootData.meshBlockData.meshChunksData.Add(chunkData);
    }
Exemple #17
0
 public void ReadMAMP(MemoryStream ADTtexstream)
 {
     StreamTools s            = new StreamTools();
     int         texture_size = s.ReadLong(ADTtexstream); // either defined here or in MHDR.mamp_value.
 }
Exemple #18
0
    public void ReadMHDR(MemoryStream ADTstream)
    {
        StreamTools s     = new StreamTools();
        int         flags = s.ReadLong(ADTstream);
        int         mcin  = s.ReadLong(ADTstream); // Cata+: obviously gone. probably all offsets gone, except mh2o(which remains in root file).
        int         mtex  = s.ReadLong(ADTstream);
        int         mmdx  = s.ReadLong(ADTstream);
        int         mmid  = s.ReadLong(ADTstream);
        int         mwmo  = s.ReadLong(ADTstream);
        int         mwid  = s.ReadLong(ADTstream);
        int         mddf  = s.ReadLong(ADTstream);
        int         modf  = s.ReadLong(ADTstream);

        MFBOoffset = s.ReadLong(ADTstream); // this is only set if flags & mhdr_MFBO.
        MH2Ooffset = s.ReadLong(ADTstream);
        int mtxf       = s.ReadLong(ADTstream);
        int mamp_value = ADTstream.ReadByte(); // Cata+, explicit MAMP chunk overrides data

        int[] padding = new int[3];
        padding[0] = ADTstream.ReadByte();
        padding[1] = ADTstream.ReadByte();
        padding[2] = ADTstream.ReadByte();
        int[] unused = new int[3];
        unused[0] = s.ReadLong(ADTstream);
        unused[1] = s.ReadLong(ADTstream);
        unused[2] = s.ReadLong(ADTstream);
    }
Exemple #19
0
    public static void ReadMD21(MemoryStream ms, M2Data m2Data, M2Texture m2Tex)
    {
        long md20position = ms.Position;

        StreamTools s                               = new StreamTools();
        int         MD20                            = s.ReadLong(ms);    // "MD20". Legion uses a chunked file format starting with MD21.
        int         version                         = s.ReadLong(ms);
        M2Array     name                            = s.ReadM2Array(ms); // should be globally unique, used to reload by name in internal clients
        var         flags                           = s.ReadLong(ms);
        M2Array     global_loops                    = s.ReadM2Array(ms); // Timestamps used in global looping animations.
        M2Array     sequences                       = s.ReadM2Array(ms); // Information about the animations in the model.
        M2Array     sequences_lookups               = s.ReadM2Array(ms); // Mapping of sequence IDs to the entries in the Animation sequences block.
        M2Array     bones                           = s.ReadM2Array(ms); // MAX_BONES = 0x100 => Creature\SlimeGiant\GiantSlime.M2 has 312 bones(Wrath)
        M2Array     key_bone_lookup                 = s.ReadM2Array(ms); // Lookup table for key skeletal bones.
        M2Array     vertices                        = s.ReadM2Array(ms);
        int         num_skin_profiles               = s.ReadLong(ms);
        M2Array     colors                          = s.ReadM2Array(ms); // Color and alpha animations definitions.
        M2Array     textures                        = s.ReadM2Array(ms);
        M2Array     texture_weights                 = s.ReadM2Array(ms); // Transparency of textures.
        M2Array     texture_transforms              = s.ReadM2Array(ms);
        M2Array     replaceable_texture_lookup      = s.ReadM2Array(ms);
        M2Array     materials                       = s.ReadM2Array(ms); // Blending modes / render flags.
        M2Array     bone_lookup_table               = s.ReadM2Array(ms);
        M2Array     texture_lookup_table            = s.ReadM2Array(ms);
        M2Array     tex_unit_lookup_table           = s.ReadM2Array(ms); // ≥ Cata: unused
        M2Array     transparency_lookup_table       = s.ReadM2Array(ms);
        M2Array     texture_transforms_lookup_table = s.ReadM2Array(ms);

        m2Data.bounding_box = s.ReadBoundingBox(ms);                    // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height
        float       bounding_sphere_radius  = s.ReadFloat(ms);          // detail doodad draw dist = clamp (bounding_sphere_radius * detailDoodadDensityFade * detailDoodadDist, …)
        BoundingBox collision_box           = s.ReadBoundingBox(ms);
        float       collision_sphere_radius = s.ReadFloat(ms);

        M2Array collision_triangles     = s.ReadM2Array(ms);
        M2Array collision_vertices      = s.ReadM2Array(ms);
        M2Array collision_normals       = s.ReadM2Array(ms);
        M2Array attachments             = s.ReadM2Array(ms);            // position of equipped weapons or effects
        M2Array attachment_lookup_table = s.ReadM2Array(ms);
        M2Array events              = s.ReadM2Array(ms);                // Used for playing sounds when dying and a lot else.
        M2Array lights              = s.ReadM2Array(ms);                // Lights are mainly used in loginscreens but in wands and some doodads too.
        M2Array cameras             = s.ReadM2Array(ms);                // The cameras are present in most models for having a model in the character tab.
        M2Array camera_lookup_table = s.ReadM2Array(ms);
        M2Array ribbon_emitters     = s.ReadM2Array(ms);                // Things swirling around. See the CoT-entrance for light-trails.
        M2Array particle_emitters   = s.ReadM2Array(ms);

        // Name //
        ms.Position = name.offset + md20position;
        for (int n = 0; n < name.size; n++)
        {
            m2Data.name += Convert.ToChar(ms.ReadByte());
        }


        // Bones //
        ms.Position = bones.offset + md20position;
        M2TrackBase[] translationM2track = new M2TrackBase[bones.size];
        M2TrackBase[] rotationM22track   = new M2TrackBase[bones.size];
        M2TrackBase[] scaleM22track      = new M2TrackBase[bones.size];
        for (int cb = 0; cb < bones.size; cb++)
        {
            M2CompBone m2CompBone = new M2CompBone();

            m2CompBone.key_bone_id      = s.ReadLong(ms);               // Back-reference to the key bone lookup table. -1 if this is no key bone.
            m2CompBone.flags            = s.ReadUint32(ms);
            m2CompBone.parent_bone      = s.ReadShort(ms);
            m2CompBone.submesh_id       = s.ReadUint16(ms);
            m2CompBone.uDistToFurthDesc = s.ReadUint16(ms);
            m2CompBone.uZRatioOfChain   = s.ReadUint16(ms);

            translationM2track[cb] = s.ReadM2Track(ms);
            rotationM22track[cb]   = s.ReadM2Track(ms);
            scaleM22track[cb]      = s.ReadM2Track(ms);

            Vector3 pivotRaw = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale);
            m2CompBone.pivot = new Vector3(-pivotRaw.x, pivotRaw.z, -pivotRaw.y);

            m2Data.m2CompBone.Add(m2CompBone);
        }


        // Animations //
        int numberOfAnimations = 0;

        for (int ab = 0; ab < bones.size; ab++)
        {
            List <Animation_Vector3>    bone_position_animations = new List <Animation_Vector3>();
            List <Animation_Quaternion> bone_rotation_animations = new List <Animation_Quaternion>();
            List <Animation_Vector3>    bone_scale_animations    = new List <Animation_Vector3>();

            // Position //
            int numberOfPositionAnimations = translationM2track[ab].Timestamps.size;
            if (numberOfAnimations < numberOfPositionAnimations)
            {
                numberOfAnimations = numberOfPositionAnimations;
            }
            for (int at = 0; at < numberOfPositionAnimations; at++)
            {
                Animation         bone_animation = new Animation();
                Animation_Vector3 positions      = new Animation_Vector3();

                // Timestamps //
                List <int> timeStamps = new List <int>();
                ms.Position = translationM2track[ab].Timestamps.offset + md20position;
                M2Array m2AnimationOffset = s.ReadM2Array(ms);
                ms.Position = m2AnimationOffset.offset;
                for (int t = 0; t < m2AnimationOffset.size; t++)
                {
                    timeStamps.Add(s.ReadLong(ms));
                }
                positions.timeStamps = timeStamps;

                // Values //
                List <Vector3> values = new List <Vector3>();
                ms.Position = translationM2track[ab].Values.offset + md20position;
                M2Array m2AnimationValues = s.ReadM2Array(ms);
                ms.Position = m2AnimationValues.offset;
                for (int t = 0; t < m2AnimationValues.size; t++)
                {
                    Vector3 rawPosition = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale);
                    values.Add(new Vector3(-rawPosition.x, rawPosition.z, -rawPosition.y));
                }
                positions.values = values;
                bone_position_animations.Add(positions);
            }


            // Rotation //
            int numberOfRotationAnimations = rotationM22track[ab].Timestamps.size;
            if (numberOfAnimations < numberOfRotationAnimations)
            {
                numberOfAnimations = numberOfRotationAnimations;
            }
            for (int ar = 0; ar < numberOfRotationAnimations; ar++)
            {
                Animation_Quaternion rotations = new Animation_Quaternion();

                // Timestamps //
                List <int> timeStamps = new List <int>();
                ms.Position = rotationM22track[ab].Timestamps.offset + md20position;
                M2Array m2AnimationOffset = s.ReadM2Array(ms);
                ms.Position = m2AnimationOffset.offset;
                for (int t = 0; t < m2AnimationOffset.size; t++)
                {
                    timeStamps.Add(s.ReadLong(ms));
                }
                rotations.timeStamps = timeStamps;

                // Values //
                List <Quaternion> values = new List <Quaternion>();
                ms.Position = rotationM22track[ab].Values.offset + md20position;
                M2Array m2AnimationValues = s.ReadM2Array(ms);
                ms.Position = m2AnimationValues.offset;
                for (int t = 0; t < m2AnimationValues.size; t++)
                {
                    Quaternion rawRotation = s.ReadQuaternion16(ms);
                    values.Add(new Quaternion(rawRotation.x, rawRotation.y, rawRotation.z, rawRotation.w));
                }
                rotations.values = values;
                bone_rotation_animations.Add(rotations);
            }

            // Scale //
            int numberOfScaleAnimations = scaleM22track[ab].Timestamps.size;
            if (numberOfAnimations < numberOfScaleAnimations)
            {
                numberOfAnimations = numberOfScaleAnimations;
            }
            for (int aS = 0; aS < numberOfScaleAnimations; aS++)
            {
                Animation_Vector3 scales = new Animation_Vector3();

                // Timestamps //
                List <int> timeStamps = new List <int>();
                ms.Position = scaleM22track[ab].Timestamps.offset + md20position;
                M2Array m2AnimationOffset = s.ReadM2Array(ms);
                ms.Position = m2AnimationOffset.offset;
                for (int t = 0; t < m2AnimationOffset.size; t++)
                {
                    timeStamps.Add(s.ReadLong(ms));
                }
                scales.timeStamps = timeStamps;

                // Values //
                List <Vector3> values = new List <Vector3>();
                ms.Position = scaleM22track[ab].Values.offset + md20position;
                M2Array m2AnimationValues = s.ReadM2Array(ms);
                ms.Position = m2AnimationValues.offset;
                for (int t = 0; t < m2AnimationValues.size; t++)
                {
                    Vector3 rawScale = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale);
                    values.Add(new Vector3(-rawScale.x, rawScale.z, -rawScale.y));
                }
                scales.values = values;
                bone_scale_animations.Add(scales);
            }
            //Debug.Log(numberOfPositionAnimations + " " + numberOfRotationAnimations + " " + numberOfScaleAnimations);
            m2Data.position_animations.Add(bone_position_animations);
            m2Data.rotation_animations.Add(bone_rotation_animations);
            m2Data.scale_animations.Add(bone_scale_animations);
        }
        m2Data.numberOfAnimations = numberOfAnimations;

        // Bone Lookup Table //
        ms.Position = bone_lookup_table.offset + md20position;
        for (int blt = 0; blt < key_bone_lookup.size; blt++)
        {
            m2Data.bone_lookup_table.Add(s.ReadUint16(ms));
        }

        // Key-Bone Lookup //
        ms.Position = key_bone_lookup.offset + md20position;
        for (int kbl = 0; kbl < key_bone_lookup.size; kbl++)
        {
            m2Data.key_bone_lookup.Add(s.ReadShort(ms));
        }

        // Vertices //
        ms.Position     = vertices.offset + md20position;
        m2Data.meshData = new MeshData();
        for (int v = 0; v < vertices.size; v++)
        {
            Vector3 rawPosition = new Vector3(s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale, s.ReadFloat(ms) / Settings.worldScale);
            m2Data.meshData.pos.Add(new Vector3(-rawPosition.x, rawPosition.z, -rawPosition.y));
            m2Data.meshData.bone_weights.Add(new float[] { ms.ReadByte() / 255.0f, ms.ReadByte() / 255.0f, ms.ReadByte() / 255.0f, ms.ReadByte() / 255.0f });
            m2Data.meshData.bone_indices.Add(new int[] { ms.ReadByte(), ms.ReadByte(), ms.ReadByte(), ms.ReadByte() });
            //Debug.Log(m2Data.meshData.bone_indices[v][0] + " " + m2Data.meshData.bone_indices[v][1] + " " + m2Data.meshData.bone_indices[v][2] + " " + m2Data.meshData.bone_indices[v][3]);
            Vector3 rawnormal = new Vector3(s.ReadFloat(ms) * Settings.worldScale, s.ReadFloat(ms) * Settings.worldScale, s.ReadFloat(ms) * Settings.worldScale);
            m2Data.meshData.normal.Add(new Vector3(-rawnormal.x, rawnormal.z, -rawnormal.y));
            m2Data.meshData.tex_coords.Add(new Vector2(s.ReadFloat(ms), s.ReadFloat(ms)));
            m2Data.meshData.tex_coords2.Add(new Vector2(s.ReadFloat(ms), s.ReadFloat(ms)));
        }

        // Textures //
        ms.Position = textures.offset + md20position;
        for (int t = 0; t < textures.size; t++)
        {
            M2Texture m2Texture = new M2Texture();

            m2Texture.type  = s.ReadLong(ms);
            m2Texture.flags = s.ReadLong(ms);

            M2Array filename = s.ReadM2Array(ms);

            // seek to filename and read //
            long savePosition = ms.Position;
            ms.Position = filename.offset + md20position;
            string fileNameString = "";
            for (int n = 0; n < filename.size; n++)
            {
                fileNameString += Convert.ToChar(ms.ReadByte());
            }
            ms.Position = savePosition;

            string fileNameStringFix = fileNameString.TrimEnd(fileNameString[fileNameString.Length - 1]);
            m2Texture.filename = fileNameStringFix;

            Texture2Ddata texture2Ddata = new Texture2Ddata();

            if (fileNameStringFix.Length > 1)
            {
                if (!LoadedBLPs.Contains(fileNameStringFix))
                {
                    string  extractedPath = Casc.GetFile(fileNameStringFix);
                    Stream  stream        = File.Open(extractedPath, FileMode.Open);
                    BLP     blp           = new BLP();
                    byte[]  data          = blp.GetUncompressed(stream, true);
                    BLPinfo info          = blp.Info();
                    texture2Ddata.hasMipmaps    = info.hasMipmaps;
                    texture2Ddata.width         = info.width;
                    texture2Ddata.height        = info.height;
                    texture2Ddata.textureFormat = info.textureFormat;
                    texture2Ddata.TextureData   = data;
                    m2Texture.texture2Ddata     = texture2Ddata;
                    stream.Close();
                    stream.Dispose();
                    stream = null;
                    LoadedBLPs.Add(fileNameString);
                }
            }
            m2Data.m2Tex.Add(m2Texture);
        }

        // texture_lookup_table //
        ms.Position = texture_lookup_table.offset + md20position;
        for (int tl = 0; tl < texture_lookup_table.size; tl++)
        {
            m2Data.textureLookupTable.Add(s.ReadUint16(ms));
        }
    }