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);
        }