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