private void ReadTextureFile(PenumbraFileResource resource, MemoryStream ms)
    {
        var blocks = Reader.ReadStructures <LodBlock>(( int )resource.FileInfo !.BlockCount);

        // if there is a mipmap header, the comp_offset
        // will not be 0
        var mipMapSize = blocks[0].CompressedOffset;

        if (mipMapSize != 0)
        {
            var originalPos = BaseStream.Position;

            BaseStream.Position = resource.FileInfo.Offset + resource.FileInfo.HeaderSize;
            ms.Write(Reader.ReadBytes(( int )mipMapSize));

            BaseStream.Position = originalPos;
        }

        // i is for texture blocks, j is 'data blocks'...
        for (byte i = 0; i < blocks.Count; i++)
        {
            // start from comp_offset
            var runningBlockTotal = blocks[i].CompressedOffset + resource.FileInfo.Offset + resource.FileInfo.HeaderSize;
            ReadFileBlock(runningBlockTotal, ms, true);

            for (var j = 1; j < blocks[i].BlockCount; j++)
            {
                runningBlockTotal += ( uint )Reader.ReadInt16();
                ReadFileBlock(runningBlockTotal, ms, true);
            }

            // unknown
            Reader.ReadInt16();
        }
    }
    private void ReadStandardFile(PenumbraFileResource resource, MemoryStream ms)
    {
        var blocks = Reader.ReadStructures <DatStdFileBlockInfos>(( int )resource.FileInfo !.BlockCount);

        foreach (var block in blocks)
        {
            ReadFileBlock(resource.FileInfo.Offset + resource.FileInfo.HeaderSize + block.Offset, ms);
        }

        // reset position ready for reading
        ms.Position = 0;
    }
    private unsafe void ReadModelFile(PenumbraFileResource resource, MemoryStream ms)
    {
        var mdlBlock   = resource.FileInfo !.ModelBlock;
        var baseOffset = resource.FileInfo.Offset + resource.FileInfo.HeaderSize;

        // 1/1/3/3/3 stack/runtime/vertex/egeo/index
        // TODO: consider testing if this is more reliable than the Explorer method
        // of adding mdlBlock.IndexBufferDataBlockIndex[2] + mdlBlock.IndexBufferDataBlockNum[2]
        // i don't want to move this to that method right now, because i know sometimes the index is 0
        // but it seems to work fine in explorer...
        int totalBlocks = mdlBlock.StackBlockNum;

        totalBlocks += mdlBlock.RuntimeBlockNum;
        for (var i = 0; i < 3; i++)
        {
            totalBlocks += mdlBlock.VertexBufferBlockNum[i];
        }

        for (var i = 0; i < 3; i++)
        {
            totalBlocks += mdlBlock.EdgeGeometryVertexBufferBlockNum[i];
        }

        for (var i = 0; i < 3; i++)
        {
            totalBlocks += mdlBlock.IndexBufferBlockNum[i];
        }

        var compressedBlockSizes = Reader.ReadStructures <ushort>(totalBlocks);
        var currentBlock         = 0;
        var vertexDataOffsets    = new int[3];
        var indexDataOffsets     = new int[3];
        var vertexBufferSizes    = new int[3];
        var indexBufferSizes     = new int[3];

        ms.Seek(0x44, SeekOrigin.Begin);

        Reader.Seek(baseOffset + mdlBlock.StackOffset);
        var stackStart = ms.Position;

        for (var i = 0; i < mdlBlock.StackBlockNum; i++)
        {
            var lastPos = Reader.BaseStream.Position;
            ReadFileBlock(ms);
            Reader.Seek(lastPos + compressedBlockSizes[currentBlock]);
            currentBlock++;
        }

        var stackEnd  = ms.Position;
        var stackSize = ( int )(stackEnd - stackStart);

        Reader.Seek(baseOffset + mdlBlock.RuntimeOffset);
        var runtimeStart = ms.Position;

        for (var i = 0; i < mdlBlock.RuntimeBlockNum; i++)
        {
            var lastPos = Reader.BaseStream.Position;
            ReadFileBlock(ms);
            Reader.Seek(lastPos + compressedBlockSizes[currentBlock]);
            currentBlock++;
        }

        var runtimeEnd  = ms.Position;
        var runtimeSize = ( int )(runtimeEnd - runtimeStart);

        for (var i = 0; i < 3; i++)
        {
            if (mdlBlock.VertexBufferBlockNum[i] != 0)
            {
                var currentVertexOffset = ( int )ms.Position;
                if (i == 0 || currentVertexOffset != vertexDataOffsets[i - 1])
                {
                    vertexDataOffsets[i] = currentVertexOffset;
                }
                else
                {
                    vertexDataOffsets[i] = 0;
                }

                Reader.Seek(baseOffset + mdlBlock.VertexBufferOffset[i]);

                for (var j = 0; j < mdlBlock.VertexBufferBlockNum[i]; j++)
                {
                    var lastPos = Reader.BaseStream.Position;
                    vertexBufferSizes[i] += ( int )ReadFileBlock(ms);
                    Reader.Seek(lastPos + compressedBlockSizes[currentBlock]);
                    currentBlock++;
                }
            }

            if (mdlBlock.EdgeGeometryVertexBufferBlockNum[i] != 0)
            {
                for (var j = 0; j < mdlBlock.EdgeGeometryVertexBufferBlockNum[i]; j++)
                {
                    var lastPos = Reader.BaseStream.Position;
                    ReadFileBlock(ms);
                    Reader.Seek(lastPos + compressedBlockSizes[currentBlock]);
                    currentBlock++;
                }
            }

            if (mdlBlock.IndexBufferBlockNum[i] != 0)
            {
                var currentIndexOffset = ( int )ms.Position;
                if (i == 0 || currentIndexOffset != indexDataOffsets[i - 1])
                {
                    indexDataOffsets[i] = currentIndexOffset;
                }
                else
                {
                    indexDataOffsets[i] = 0;
                }

                // i guess this is only needed in the vertex area, for i = 0
                // Reader.Seek( baseOffset + mdlBlock.IndexBufferOffset[ i ] );

                for (var j = 0; j < mdlBlock.IndexBufferBlockNum[i]; j++)
                {
                    var lastPos = Reader.BaseStream.Position;
                    indexBufferSizes[i] += ( int )ReadFileBlock(ms);
                    Reader.Seek(lastPos + compressedBlockSizes[currentBlock]);
                    currentBlock++;
                }
            }
        }

        ms.Seek(0, SeekOrigin.Begin);
        ms.Write(BitConverter.GetBytes(mdlBlock.Version));
        ms.Write(BitConverter.GetBytes(stackSize));
        ms.Write(BitConverter.GetBytes(runtimeSize));
        ms.Write(BitConverter.GetBytes(mdlBlock.VertexDeclarationNum));
        ms.Write(BitConverter.GetBytes(mdlBlock.MaterialNum));
        for (var i = 0; i < 3; i++)
        {
            ms.Write(BitConverter.GetBytes(vertexDataOffsets[i]));
        }

        for (var i = 0; i < 3; i++)
        {
            ms.Write(BitConverter.GetBytes(indexDataOffsets[i]));
        }

        for (var i = 0; i < 3; i++)
        {
            ms.Write(BitConverter.GetBytes(vertexBufferSizes[i]));
        }

        for (var i = 0; i < 3; i++)
        {
            ms.Write(BitConverter.GetBytes(indexBufferSizes[i]));
        }

        ms.Write(new[] { mdlBlock.NumLods });
        ms.Write(BitConverter.GetBytes(mdlBlock.IndexBufferStreamingEnabled));
        ms.Write(BitConverter.GetBytes(mdlBlock.EdgeGeometryEnabled));
        ms.Write(new byte[] { 0 });
    }