public StaticMesh(Stream s)
        {
            s.Align(4);

            s.Align(8);

            Header = s.ReadStruct <StaticMeshHeader>();

            // Read texture names
            int textureNameSize = s.ReadInt32();

            s.Align(16);

            s.ReadByte();

            byte[] textureNameData = new byte[textureNameSize];
            s.Read(textureNameData, 0, textureNameSize);
            MemoryStream textureNameStream = new MemoryStream(textureNameData);

            while (textureNameStream.Position < textureNameData.Length)
            {
                textureNameStream.Align(2);

                int    position    = (int)textureNameStream.Position;
                string textureName = textureNameStream.ReadAsciiNullTerminatedString();
                byte   paddingByte = textureNameStream.ReadUInt8();

                TextureNames.Add(textureName, position);

                if (paddingByte != 0)
                {
                    throw new Exception();
                }

                textureNameStream.Align(2);
            }

            if (Header.Version < 0x2A)
            {
                Header.NumSubmeshVIDs = (ushort)Header.NumLogicalSubmeshes;
            }

            if (Header.NumNavpoints > 0)
            {
                s.Align(16);

                for (int i = 0; i < Header.NumNavpoints; i++)
                {
                    StaticMeshNavpoint navpoint = s.ReadStruct <StaticMeshNavpoint>();
                    Navpoints.Add(navpoint);
                }
            }

            if (Header.NumCSpheres > 0)
            {
                s.Align(16);

                for (int i = 0; i < Header.NumCSpheres; i++)
                {
                    CMeshCSphere cmeshCSphere = s.ReadStruct <CMeshCSphere>();
                    CSpheres.Add(cmeshCSphere);
                }
            }

            if (Header.NumCCylinders > 0)
            {
                s.Align(16);

                for (int i = 0; i < Header.NumCCylinders; i++)
                {
                    CMeshCCylinder cylinder = s.ReadStruct <CMeshCCylinder>();
                    CCylinders.Add(cylinder);
                }
            }

            if (Header.NumRigBones > 0)
            {
                s.Align(16);

                for (int i = 0; i < Header.NumRigBones; i++)
                {
                    UInt32 rigBone = s.ReadUInt32();
                    RigBones.Add(rigBone);
                }
            }

            s.Align(8);

            long originalOffset = s.Position;

            MeshVersion = s.ReadUInt16();

            s.Align(4);

            MeshGpuDataCrc  = s.ReadUInt32();
            MeshCpuDataSize = s.ReadUInt32();

            MiscData = new byte[(originalOffset + MeshCpuDataSize) - s.Position];
            s.Read(MiscData, 0, MiscData.Length);

            if (Header.NumMaterialMaps > 0)
            {
                s.Align(8);

                // Skip pointer tables?
                s.Seek(8 * Header.NumMaterialMaps, SeekOrigin.Current);

                MaterialMapCrcs = new uint[Header.NumMaterialMaps];
                for (int i = 0; i < Header.NumMaterialMaps; i++)
                {
                    MaterialMapCrcs[i] = s.ReadUInt32();
                }
            }

            if (Header.NumMaterials > 0)
            {
                s.Align(8);

                // Skip more pointers
                s.Seek(8 * Header.NumMaterials, SeekOrigin.Current);

                for (int i = 0; i < Header.NumMaterials; i++)
                {
                    StaticMeshMaterial material = new StaticMeshMaterial();
                    Materials.Add(material);

                    long materialStart = s.Position;

                    material.DataSize = s.ReadUInt32();

                    s.Align(8);

                    material.Data = s.ReadStruct <RenderLibMaterialData>();

                    s.Align(4);

                    for (int j = 0; j < material.Data.NumTextures; j++)
                    {
                        RenderLibMaterialTextureDesc textureDesc = s.ReadStruct <RenderLibMaterialTextureDesc>();
                        material.TextureDescriptions.Add(textureDesc);
                    }

                    s.Align(4);

                    for (int j = 0; j < material.Data.NumConstants; j++)
                    {
                        uint constantNameChecksum = s.ReadUInt32();
                        material.NameChecksums.Add(constantNameChecksum);
                    }

                    s.Align(16);

                    for (int j = 0; j < material.Data.NumConstants; j++)
                    {
                        RenderLibMaterialConstants constant = s.ReadStruct <RenderLibMaterialConstants>();
                        material.Constants.Add(constant);
                    }

                    uint materialSize = (uint)(s.Position - materialStart);
                    if (materialSize != material.DataSize)
                    {
                        throw new Exception(String.Format("materialSize != material.DataSize - {0} != {1}", materialSize, material.DataSize));
                    }

                    if (material.Data.NumTextures > 0)
                    {
                        for (int j = 0; j < material.Data.NumTextures; j++)
                        {
                            RenderLibMaterialTextureDesc textureDesc = material.TextureDescriptions[j];
                            textureNameStream.Seek(textureDesc.TextureHandle, SeekOrigin.Begin);
                            string textureName = textureNameStream.ReadAsciiNullTerminatedString();
                            material.TextureNames.Add(textureName);
                        }
                    }
                }
            }

            MaterialMapData = new byte[s.Length - s.Position];
            s.Read(MaterialMapData, 0, MaterialMapData.Length);
        }
        public void Save(Stream s)
        {
            s.Align(4);

            s.Align(8);

            Header.NumNavpoints = (short)Navpoints.Count;
            Header.NumRigBones  = (short)RigBones.Count;
            // Header.NumMaterials
            // Header.NumMaterialMaps
            // Header.NumLODsPerSubmesh
            // Header.NumSubmeshVIDs
            Header.NumCSpheres   = (ushort)CSpheres.Count;
            Header.NumCCylinders = (ushort)CCylinders.Count;
            // NumLogicalSubmeshes

            s.WriteStruct(Header);

            using (MemoryStream textureNameStream = new MemoryStream())
            {
                string[] textureNames = new string[TextureNames.Count];
                TextureNames.Keys.CopyTo(textureNames, 0);
                foreach (var textureName in textureNames)
                {
                    textureNameStream.Align(2);
                    TextureNames[textureName] = (int)textureNameStream.Position;
                    textureNameStream.WriteAsciiNullTerminatedString(textureName);
                    textureNameStream.WriteUInt8(0); // padding byte?
                    textureNameStream.Align(2);
                }

                s.WriteInt32((int)textureNameStream.Length);
                s.Align(16);
                s.WriteUInt8(0);
                textureNameStream.Seek(0, SeekOrigin.Begin);
                textureNameStream.CopyTo(s);
            }

            if (Header.NumNavpoints > 0)
            {
                s.Align(16);

                foreach (StaticMeshNavpoint navpoint in Navpoints)
                {
                    s.WriteStruct(navpoint);
                }
            }

            if (Header.NumCSpheres > 0)
            {
                s.Align(16);

                foreach (CMeshCSphere csphere in CSpheres)
                {
                    s.WriteStruct(csphere);
                }
            }

            if (Header.NumCCylinders > 0)
            {
                s.Align(16);

                foreach (CMeshCCylinder ccylinder in CCylinders)
                {
                    s.WriteStruct(ccylinder);
                }
            }

            if (Header.NumRigBones > 0)
            {
                s.Align(16);

                foreach (uint rigBone in RigBones)
                {
                    s.WriteUInt32(rigBone);
                }
            }

            s.Align(8);

            s.WriteUInt16(MeshVersion);

            s.Align(4);
            s.WriteUInt32(MeshGpuDataCrc);
            s.WriteUInt32(MeshCpuDataSize);

            s.Write(MiscData, 0, MiscData.Length);

            if (Header.NumMaterialMaps > 0)
            {
                s.Align(8);

                // Skip pointer tables?
                s.Seek(8 * Header.NumMaterialMaps, SeekOrigin.Current);

                for (int i = 0; i < Header.NumMaterialMaps; i++)
                {
                    s.WriteUInt32(MaterialMapCrcs[i]);
                }
            }

            if (Header.NumMaterials > 0)
            {
                s.Align(8);

                // Skip more pointers
                s.Seek(8 * Header.NumMaterials, SeekOrigin.Current);

                for (int i = 0; i < Header.NumMaterials; i++)
                {
                    StaticMeshMaterial material = Materials[i];
                    s.WriteUInt32(material.DataSize);

                    s.Align(8);
                    s.WriteStruct(material.Data);

                    for (int j = 0; j < material.Data.NumTextures; j++)
                    {
                        string textureName = material.TextureNames[j];
                        RenderLibMaterialTextureDesc textureDesc = material.TextureDescriptions[j];
                        int textureNameOffset = TextureNames[textureName];
                        textureDesc.TextureHandle = textureNameOffset;

                        s.WriteStruct(textureDesc);
                    }

                    for (int j = 0; j < material.Data.NumConstants; j++)
                    {
                        uint constantNameChecksum = material.NameChecksums[j];
                        s.WriteUInt32(constantNameChecksum);
                    }

                    s.Align(16);

                    for (int j = 0; j < material.Data.NumConstants; j++)
                    {
                        RenderLibMaterialConstants constant = material.Constants[j];
                        s.WriteStruct(constant);
                    }
                }
            }

            s.Write(MaterialMapData, 0, MaterialMapData.Length);
        }