Ejemplo n.º 1
0
        public void Read(string filepath)
        {
            try
            {
                FileName     = filepath;
                Chunkoffsets = new List <uint>();
                using (var br = new BinaryReader(new FileStream(filepath, FileMode.Open)))
                {
                    Files = new List <TextureCacheItem>();
                    br.BaseStream.Seek(-32, SeekOrigin.End);
                    Crc                = br.ReadUInt64();
                    UsedPages          = br.ReadUInt32();
                    EntryCount         = br.ReadUInt32();
                    StringTableSize    = br.ReadUInt32();
                    MipOffsetTableSize = br.ReadUInt32();
                    IDString           = br.ReadUInt32();
                    Version            = br.ReadUInt32();
                    //JMP to the top of the info table:
                    //32 is the the size of the stuff we read so far.
                    //Every entry has 52 bytes of info
                    //The stringtable
                    //Every offset is 4 bytes
                    //The sum of this is how much we need to jump from the back
                    var jmp = -(32 + (EntryCount * 52) + StringTableSize + (MipOffsetTableSize * 4));
                    br.BaseStream.Seek(jmp, SeekOrigin.End);
                    for (var i = 0; i < MipOffsetTableSize; i++)
                    {
                        Chunkoffsets.Add(br.ReadUInt32());
                    }
                    Names = new List <string>();
                    for (var i = 0; i < EntryCount; i++)
                    {
                        Names.Add(br.ReadCR2WString());
                    }
                    for (var i = 0; i < EntryCount; i++)
                    {
                        var ti = new TextureCacheItem(this)
                        {
                            Name       = Names[i],
                            ParentFile = FileName,
                            Hash       = br.ReadUInt32(),
                            /*-------------TextureCacheEntryBase---------------*/
                            PathStringIndex  = br.ReadInt32(),
                            PageOFfset       = br.ReadInt32(),
                            CompressedSize   = br.ReadInt32(),
                            UncompressedSize = br.ReadInt32(),
                            BaseAlignment    = br.ReadUInt32(),
                            BaseWidth        = br.ReadUInt16(),
                            BaseHeight       = br.ReadUInt16(),
                            Mipcount         = br.ReadUInt16(),
                            SliceCount       = br.ReadUInt16(),
                            MipOffsetIndex   = br.ReadInt32(),
                            NumMipOffsets    = br.ReadInt32(),
                            TimeStamp        = br.ReadInt64(),
                            /*-------------TextureCacheEntryBase---------------*/
                            Type1  = br.ReadByte(),
                            Type2  = br.ReadByte(),
                            IsCube = br.ReadByte(),
                            Unk1   = br.ReadByte()
                        };
                        Files.Add(ti);
                    }
                    //BUG: "C:\\Users\\bence.hambalko\\Documents\\The Witcher 3\\bin\\x64\\..\\..\\Mods\\modW3EE\\content\\texture.cache" dies here! Investigate!!!!!!!!!!!!!
                    foreach (var t in Files)
                    {
                        br.BaseStream.Seek(t.PageOFfset * 4096, SeekOrigin.Begin);
                        t.ZSize    = br.ReadUInt32(); //Compressed size
                        t.Size     = br.ReadInt32();  //Uncompressed size
                        t.SliceIdx = br.ReadByte();   //maybe the 48bit part of OFFSET

                        var lastpos = br.BaseStream.Position + t.ZSize;


                        for (int i = 0; i < t.NumMipOffsets; i++)
                        {
                            br.BaseStream.Seek(lastpos, SeekOrigin.Begin);

                            var mzsize = br.ReadUInt32();
                            t.MipMapInfo.Add(new Tuple <uint, uint>(
                                                 (uint)lastpos + 9, // mipmap offset
                                                 mzsize));          //zsize

                            lastpos += 9 + mzsize;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Debug.Assert(e != null);
            }
        }
Ejemplo n.º 2
0
 public void Read(string filepath)
 {
     FileName     = filepath;
     Chunkoffsets = new List <uint>();
     using (var br = new BinaryReader(new FileStream(filepath, FileMode.Open)))
     {
         Files = new List <TextureCacheItem>();
         br.BaseStream.Seek(-20, SeekOrigin.End);
         TextureCount     = br.ReadUInt32();
         NamesBlockSize   = br.ReadUInt32();
         ChunkOffsetCount = br.ReadUInt32();
         Magic            = br.ReadUInt32();
         Version          = br.ReadUInt32();
         var jmp = -(20 + 12 + (TextureCount * 52) + NamesBlockSize + (ChunkOffsetCount * 4));
         br.BaseStream.Seek(jmp, SeekOrigin.End);
         for (var i = 0; i < ChunkOffsetCount; i++)
         {
             Chunkoffsets.Add(br.ReadUInt32());
         }
         Names = new List <string>();
         for (var i = 0; i < TextureCount; i++)
         {
             Names.Add(br.ReadCR2WString());
         }
         for (var i = 0; i < TextureCount; i++)
         {
             var ti = new TextureCacheItem(this)
             {
                 Name           = Names[i],
                 ParentFile     = FileName,
                 Id             = br.ReadUInt32(),   //number (unique???)
                 Filenameoffset = br.ReadUInt32(),   //filename, start offset in block2
                 Offset         = br.ReadUInt32(),   //* 4096 = start offset, first chunk
                 PackedSize     = br.ReadUInt32(),   //packed size (all chunks)
                 UnpackedSize   = br.ReadUInt32(),   //unpacked size
                 Bpp            = br.ReadUInt32(),   //bpp? always 16
                 Width          = br.ReadUInt16(),   //width
                 Height         = br.ReadUInt16(),   //height
                 Mips           = br.ReadUInt16(),   //mips
                 Typeinfo       = br.ReadUInt16(),   //1/6/N, single, cubemaps, arrays
                 B1Offset       = br.ReadUInt32(),   //offset in block1, second packed chunk
                 Rpc            = br.ReadUInt32(),   //the number of remaining packed chunks
                 Unk1           = br.ReadUInt32(),   //???
                 Unk2           = br.ReadUInt32(),   //???
                 Dxt            = br.ReadByte(),     //0-RGBA?, 7-DXT1, 8-DXT5, 10-???, 13-DXT3, 14-ATI1, 15-???, 253-???
                 Type           = br.ReadByte(),     //3-cubemaps, 4-texture
                 Unk3           = br.ReadUInt16()    //0/1 ???
             };
             Files.Add(ti);
         }
         for (var i = 0; i < 3; i++)
         {
             br.ReadBytes(4);
         }
         foreach (var t in Files)
         {
             br.BaseStream.Seek(t.Offset * 4096, SeekOrigin.Begin);
             t.ZSize = br.ReadUInt32(); //Compressed size
             t.Size  = br.ReadInt32();  //Uncompressed size
             t.Part  = br.ReadByte();   //maybe the 48bit part of OFFSET
         }
     }
 }
        private void Read(string filepath)
        {
            try
            {
                ArchiveAbsolutePath = filepath;
                MipOffsets          = new List <uint>();
                using (var br = new BinaryReader(new FileStream(filepath, FileMode.Open)))
                {
                    Files = new List <TextureCacheItem>();
                    Names = new List <string>();

                    #region Footer

                    br.BaseStream.Seek(-32, SeekOrigin.End);

                    Crc                = br.ReadUInt64();
                    UsedPages          = br.ReadUInt32();
                    EntryCount         = br.ReadUInt32();
                    StringTableSize    = br.ReadUInt32();
                    MipTableEntryCount = br.ReadUInt32();
                    var magic = br.ReadUInt32();
                    if (magic != MagicInt)
                    {
                        throw new Exception("Invalid file.");
                    }
                    Version = br.ReadUInt32();

                    #endregion Footer

                    // JMP to the top of the info table:
                    // 32 is the the size of the stuff we read so far.
                    // + Every entry has 52 bytes of info
                    // + The stringtablesize (found in the footer)
                    // + MipTableEntryCount * offset is 4 bytes
                    // The sum of this is how much we need to jump from the back
                    var stringtableoffset = -(32 + (EntryCount * 52) + StringTableSize + (MipTableEntryCount * 4));
                    br.BaseStream.Seek(stringtableoffset, SeekOrigin.End);

                    #region MipMapTable

                    for (var i = 0; i < MipTableEntryCount; i++)
                    {
                        MipOffsets.Add(br.ReadUInt32());
                    }

                    #endregion MipMapTable

                    #region StringTable

                    for (var i = 0; i < EntryCount; i++)
                    {
                        Names.Add(br.ReadCR2WString());
                    }

                    #endregion StringTable

                    #region EntryTable

                    // jump to entry table
                    var entrytableoffset = -(32 + (EntryCount * 52));
                    br.BaseStream.Seek(entrytableoffset, SeekOrigin.End);

                    for (var i = 0; i < EntryCount; i++)
                    {
                        var ti = new TextureCacheItem(this)
                        {
                            Name       = Names[i],
                            ParentFile = ArchiveAbsolutePath,

                            Hash = br.ReadUInt32(),
                            /*-------------TextureCacheEntryBase---------------*/
                            StringTableOffset = br.ReadInt32(),
                            PageOffset        = br.ReadUInt32(),
                            CompressedSize    = br.ReadUInt32(),
                            UncompressedSize  = br.ReadUInt32(),

                            BaseAlignment = br.ReadUInt32(),
                            BaseWidth     = br.ReadUInt16(),
                            BaseHeight    = br.ReadUInt16(),
                            Mipcount      = br.ReadUInt16(),
                            SliceCount    = br.ReadUInt16(),

                            MipOffsetIndex = br.ReadInt32(),
                            NumMipOffsets  = br.ReadInt32(),
                            TimeStamp      = br.ReadInt64(),
                            /*-------------TextureCacheEntryBase---------------*/
                            Type1  = br.ReadByte(),
                            Type2  = br.ReadByte(),
                            IsCube = br.ReadByte(),
                            Unk1   = br.ReadByte()
                        };
                        ti.Format = CommonImageTools.GetEFormatFromREDEngineByte(ti.Type1);
                        Files.Add(ti);
                    }

                    #endregion EntryTable

                    //BUG: "C:\\Users\\bence.hambalko\\Documents\\The Witcher 3\\bin\\x64\\..\\..\\Mods\\modW3EE\\content\\texture.cache" dies here! Investigate!!!!!!!!!!!!!
                    foreach (var t in Files)
                    {
                        br.BaseStream.Seek(t.PageOffset * 4096, SeekOrigin.Begin);
                        t.ZSize  = br.ReadUInt32(); //Compressed size
                        t.Size   = br.ReadUInt32(); //Uncompressed size
                        t.MipIdx = br.ReadByte();   //maybe the 48bit part of OFFSET

                        var lastpos = br.BaseStream.Position + t.ZSize;

                        for (int i = 0; i < t.NumMipOffsets; i++)
                        {
                            br.BaseStream.Seek(lastpos, SeekOrigin.Begin);

                            var mzsize = br.ReadUInt32();
                            var msize  = br.ReadUInt32();
                            var midx   = br.ReadByte();

                            t.MipMapInfo.Add(new MipmapInfo(lastpos + 9, mzsize, msize, midx));

                            lastpos += 9 + mzsize;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Debug.Assert(e != null);
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="inputfolder"></param>
        public void LoadFiles(string inputfolder, ILoggerService logger = null)
        {
            var di = new DirectoryInfo(inputfolder);

            if (!di.Exists)
            {
                return;
            }
            var inputfiles = di.GetFiles("*.dds", SearchOption.AllDirectories)
                             .Select(_ => _.FullName).ToList();

            logger?.LogString($"[TextureCache] Begin caching.", Logtype.Important);
            logger?.LogString($"[TextureCache] Found {inputfiles.Count} files.", Logtype.Important);

            // clear data
            Files.Clear();
            Names.Clear();
            MipOffsets.Clear();

            foreach (var filename in inputfiles)
            {
                var ext = Path.GetExtension(filename);

                switch (ext)
                {
                //case ".xbm":
                //    {
                //        // read cr2wfile
                //        var cr2w = new CR2WFile()
                //        {
                //            FileName = filename,
                //        };
                //        using (var cfs = new FileStream(filename, FileMode.Open, FileAccess.Read))
                //        using (var reader = new BinaryReader(cfs))
                //        {
                //            var errorcode = cr2w.Read(reader);
                //            if (errorcode != EFileReadErrorCodes.NoError)
                //                continue;
                //        }

                //        // check if CBitmapTexture
                //        if (!(cr2w.chunks.FirstOrDefault()?.data is CBitmapTexture xbm))
                //        {
                //            continue;
                //        }

                //        break;
                //    }
                case ".DDS":
                case ".dds":
                {
                    var ddsheader = DDSUtils.ReadHeader(filename);

                    var redpath      = Path.ChangeExtension(filename, ddsheader.Iscubemap ? ".w2cube" : ".xbm");
                    var relativepath = redpath.Substring(di.FullName.Length + 1);

                    #region Create Table Item

                    var(type1, type2) =
                        CommonImageTools.GetREDEngineByteFromEFormat(ddsheader.Format);

                    if (ddsheader.Width % 2 != 0 || ddsheader.Height % 2 != 0)
                    {
                        continue;
                    }

                    var maxSide = Math.Max(ddsheader.Width, ddsheader.Height);
                    //var minSide = Math.Min(ddsheader.Width, ddsheader.Height);
                    var realmipscount = Math.Max(1, Math.Log10(maxSide / 32) / Math.Log10(2));
                    if (ddsheader.Mipscount == 1)     //TODO: fix this
                    {
                        realmipscount = 0;
                    }
                    if (ddsheader.Mipscount == 0)     //TODO: fix this
                    {
                        realmipscount = 0;
                    }

                    var ti = new TextureCacheItem(this)
                    {
                        Name     = relativepath,
                        FullName = filename,

                        Hash = relativepath.HashStringKey(),

                        /*------------- TextureCache Data ---------------*/
                        // NOTE: these need to be populated after writing the files
                        ParentFile        = "",    //done
                        StringTableOffset = -1,    //done
                        PageOffset        = 0,     //done
                        CompressedSize    = 0,     //done
                        UncompressedSize  = 0,     //done
                        MipOffsetIndex    = 0,     //done

                        /*------------- Image data ---------------*/
                        NumMipOffsets = (int)realmipscount,
                        BaseAlignment = ddsheader.Bpp,
                        BaseWidth     = (ushort)ddsheader.Width,
                        BaseHeight    = (ushort)ddsheader.Height,
                        Mipcount      = (ushort)Math.Max(1, ddsheader.Mipscount),
                        SliceCount    = (ushort)ddsheader.Slicecount,     //TODO

                        TimeStamp = 0 /*(long)CDateTime.Now.ToUInt64()*/, //NOTE: Not even CDPR could be bothered to use their own Timestamps

                        Type1  = type1,
                        Type2  = type2,
                        IsCube = ddsheader.Iscubemap ? (byte)1 : (byte)0,
                        Unk1   = 0x00,   //TODO: figure this out

                        Format = CommonImageTools.GetEFormatFromREDEngineByte(type1)
                    };

                    #endregion Create Table Item

                    Files.Add(ti);
                    Names.Add(ti.Name);

                    logger?.LogString($"Cached {ti.Name}", Logtype.Normal);

                    break;
                }
                }
            }

            logger?.LogString($"[TextureCache] Caching successful.", Logtype.Success);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Generate a bundle from a list of binary Files. (uncompressed)
        /// </summary>
        /// <param name="Files"></param>
        private void Read(FileInfo[] _files)
        {
            //stringtable
            var stringTable = new Dictionary <string, int>();
            int stoffset    = 0;

            foreach (var f in _files)
            {
                stringTable.Add(GetRelativePath(f.FullName, f.Directory.FullName), stoffset);
                stoffset += f.Name.Length + 1;
            }

            long offset = 0;

            foreach (FileInfo f in _files)
            {
                long size;
                uint zSize;

                //get the raw bytes, rawbyte length and compressed bytes length
                using (var file = MemoryMappedFile.CreateFromFile(f.FullName, FileMode.Open))
                    using (var vs = file.CreateViewStream(0, f.Length))
                    {
                        var buffer = new byte[f.Length];
                        vs.Read(buffer, 0, buffer.Length);

                        size = buffer.Length;
                        byte[] compressed = GetCompressed(buffer);
                        zSize = (uint)compressed.Length;
                    }

                //padding
                long nextOffset = GetOffset(offset + (int)zSize);

                TextureCacheItem newItem = new TextureCacheItem(this)
                {
                    DepotPath = GetRelativePath(f.FullName, f.Directory.FullName),
                    Hash      = 0, //FIXME

                    /*-------------TextureCacheEntryBase---------------*/
                    PathStringIndex = stringTable[f.Name],
                    PageOffset      = offset / ALIGNMENT_TARGET,
                    ZSize           = zSize,
                    Size            = size,
                    BaseAlignment   = 0, //FIXME
                    BaseWidth       = 0, //FIXME
                    BaseHeight      = 0, //FIXME
                    TotalMipsCount  = 0, //FIXME
                    SliceCount      = 0, //FIXME
                    MipOffsetIndex  = 0, //FIXME
                    MipsCount       = 0, //FIXME
                    TimeStamp       = 0, //FIXME
                    /*-------------TextureCacheEntryBase---------------*/
                    Type   = 0,          //FIXME
                    IsCube = 0,          //FIXME
                };
                Items.Add(newItem);

                offset = nextOffset;
            }

            //create footer
            Footer = new TextureCacheFooter
            {
                Crc             = 0, //NOTE this stays 0, crc will get created when writing
                UsedPages       = (UInt32)(offset / ALIGNMENT_TARGET),
                EntryCount      = (UInt32)Items.Count,
                StringTableSize = (UInt32)stoffset,
                MipEntryCount   = 0, //FIXME
                IDString        = IDString,
            };
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Reads a list of TextureCacheItems. (compressed)
        /// </summary>
        /// <param name="_files"></param>
        private void Read(params TextureCacheItem[] _files)
        {
            //stringtable
            var stringTable  = new Dictionary <int, int>();
            int stoffset     = 0;
            var relMipsTable = new Dictionary <int, uint[]>();

            for (int i = 0; i < _files.Length; i++)
            {
                TextureCacheItem f = _files[i];
                stringTable.Add(i, stoffset);
                stoffset += f.DepotPath.Length + 1;

                //mipmaps
                relMipsTable.Add(i, f.GetMipsOffsettable());
            }

            //entrytable
            long offset = 0;

            for (int i = 0; i < _files.Length; i++)
            {
                TextureCacheItem item       = (TextureCacheItem)_files[i];
                long             nextOffset = GetOffset(offset + (int)item.ZSize);

                //calculate mipoffset
                var mo = relMipsTable.Where(_ => _.Key < i).SelectMany(_ => _.Value).Count();
                var l  = relMipsTable[i];
                for (int j = 0; j < relMipsTable[i].Count(); j++)
                {
                    l[j] += item.CachedZSizeNoMips + 9;
                }
                MipsOffsets.AddRange(l);

                if (offset / ALIGNMENT_TARGET < 0)
                {
                }

                TextureCacheItem newItem = new TextureCacheItem(item.Bundle)
                {
                    BundlePath = item.Bundle.FileName,

                    CachedMipsCount   = item.CachedMipsCount,
                    CachedZSizeNoMips = item.CachedZSizeNoMips,
                    CachedSizeNoMips  = item.CachedSizeNoMips,

                    DepotPath = item.DepotPath,
                    Hash      = item.Hash,
                    /*-------------TextureCacheEntryBase---------------*/
                    PathStringIndex = stringTable[i],
                    PageOffset      = offset / ALIGNMENT_TARGET,
                    ZSize           = item.ZSize,
                    Size            = item.Size,
                    BaseAlignment   = item.BaseAlignment,
                    BaseWidth       = item.BaseWidth,
                    BaseHeight      = item.BaseHeight,
                    TotalMipsCount  = item.TotalMipsCount,
                    SliceCount      = item.SliceCount,
                    MipOffsetIndex  = mo,
                    MipsCount       = item.MipsCount,
                    TimeStamp       = item.TimeStamp,
                    /*-------------TextureCacheEntryBase---------------*/
                    Type   = item.Type,
                    IsCube = item.IsCube
                };
                Items.Add(newItem);

                offset = nextOffset;
            }

            //create footer
            Footer = new TextureCacheFooter
            {
                Crc             = 0,
                UsedPages       = (UInt32)(offset / ALIGNMENT_TARGET),
                EntryCount      = (UInt32)Items.Count,
                StringTableSize = (UInt32)stoffset,
                MipEntryCount   = (uint)MipsOffsets.Count,
                IDString        = IDString,
            };
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Reads a texture.cache file.
        /// </summary>
        /// <param name="filepath"></param>
        private void Read(string filepath)
        {
            FileName = filepath;
            if (!File.Exists(filepath))
            {
                return;
            }


            using (var br = new BinaryReader(new FileStream(filepath, FileMode.Open)))
            {
                Items = new List <TextureCacheItem>();

                #region Footer
                br.BaseStream.Seek(-32, SeekOrigin.End);
                Footer = new TextureCacheFooter();
                Footer.Read(br);

                //errorhandling
                if (!IDString.SequenceEqual(Footer.IDString))
                {
                    throw new InvalidCacheException("Cache header mismatch.");
                }
                //errorhandling

                #endregion

                #region InfoTable
                //JMP to the top of the info table:
                //32 is the the size of the stuff we read so far.
                //Every entry has 52 bytes of info
                //The stringtable
                //Every offset is 4 bytes
                //The sum of this is how much we need to jump from the back
                var jmp = -(32 + (Footer.EntryCount * 52) + Footer.StringTableSize + (Footer.MipEntryCount * 4));
                br.BaseStream.Seek(jmp, SeekOrigin.End);
                var jmpoffset = br.BaseStream.Position;

                //Mips
                for (var i = 0; i < Footer.MipEntryCount; i++)
                {
                    MipsOffsets.Add(br.ReadUInt32());
                }

                //Names
                //BUG: "modW3EE\\content\\texture.cache" dies here! Investigate!!!!!!!!!!!!!

                /*
                 * for some reason, some entries are doubled in the (middle of the) stringtable of the texture.cache.
                 * leading to the string table being longer than it should be (more entries than entrycount)
                 * this in turn let's the for loop (which runs over entrycount) to stop in the middle of the string-table
                 * As a twist, there are actually duplicate file NAMES (but different compressed files) inside the bob texture.cache
                 * which breaks any way of properly solving the w3ee problem
                 * FIX
                 * - check for entrys and names count
                 * - if they are different (in the case of w3ee) try to resolve the error by making the names distinct
                 * - this works for w3ee but is not guaranteed to work in all cases.
                 * - skip loading if that didnt resolve the names/entry count
                 */
                var Names            = new List <string>();
                var entrytableoffset = jmpoffset + (Footer.MipEntryCount * 4) + Footer.StringTableSize;
                while (br.BaseStream.Position < entrytableoffset)
                {
                    string entryname = br.ReadCR2WString();
                    Names.Add(entryname);
                }

                //errorhandling
                if (Footer.EntryCount != Names.Count)
                {
                    //try resolving the error
                    var resolvedNames = Names.Distinct().ToList();
                    if (Footer.EntryCount == resolvedNames.Count)
                    {
                        Names = resolvedNames;
                    }
                    else
                    {
                        throw new NotImplementedException();
                    }
                }

                //errorhandling

                //Entries
                br.BaseStream.Seek(entrytableoffset, SeekOrigin.Begin);
                for (var i = 0; i < Footer.EntryCount; i++)
                {
                    var ti = new TextureCacheItem(this)
                    {
                        DepotPath  = Names[i],
                        ParentFile = FileName,
                        Hash       = br.ReadInt32(),
                        /*-------------TextureCacheEntryBase---------------*/
                        PathStringIndex = br.ReadInt32(),
                        PageOffset      = br.ReadInt32(), //NOTE: texturecache pointers are stored as pagenumber, while bundleitems store absolute offset -_-
                        ZSize           = (uint)br.ReadInt32(),
                        Size            = br.ReadInt32(),
                        BaseAlignment   = br.ReadUInt32(),

                        BaseWidth      = br.ReadUInt16(),
                        BaseHeight     = br.ReadUInt16(),
                        TotalMipsCount = br.ReadUInt16(),
                        SliceCount     = br.ReadUInt16(),

                        MipOffsetIndex = br.ReadInt32(),
                        MipsCount      = br.ReadInt32(),
                        TimeStamp      = br.ReadInt64(),
                        /*-------------TextureCacheEntryBase---------------*/
                        Type   = br.ReadInt16(),
                        IsCube = br.ReadInt16()
                    };
                    Items.Add(ti);
                }
                #endregion

                #region Data
                //errorhandling
                var footeroffset = br.BaseStream.Length - 32;
                if (br.BaseStream.Position != footeroffset)
                {
                    throw new NotImplementedException();
                }
                //errorhandling

                for (int i = 0; i < Items.Count; i++)
                {
                    TextureCacheItem t = Items[i];
                    br.BaseStream.Seek(t.PageOffset * 4096, SeekOrigin.Begin);
                    t.CachedZSizeNoMips = br.ReadUInt32(); //Compressed size
                    t.CachedSizeNoMips  = br.ReadInt32();  //Uncompressed size
                    t.CachedMipsCount   = br.ReadByte();   //mips count
                }
                #endregion
            }
        }