/// <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, }; }
/// <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 } }