/* Load the contents of an existing TexturePAK */ public override PAKReturnType Load() { if (!File.Exists(FilePathPAK)) { return(PAKReturnType.FAIL_TRIED_TO_LOAD_VIRTUAL_ARCHIVE); } try { /* First, parse the BIN and pull ALL info from it */ BinaryReader ArchiveFileBin = new BinaryReader(File.OpenRead(FilePathBIN)); //Read the header info from the BIN VersionNumber_BIN = ArchiveFileBin.ReadInt32(); if (VersionNumber_BIN != 45) { return(PAKReturnType.FAIL_ARCHIVE_IS_NOT_EXCPETED_TYPE); } //BIN version number is 45 for textures NumberOfEntriesBIN = ArchiveFileBin.ReadInt32(); HeaderListBeginBIN = ArchiveFileBin.ReadInt32(); //Read all file names from BIN string ThisFileName = ""; for (int i = 0; i < NumberOfEntriesBIN; i++) { ThisFileName = ""; for (byte b; (b = ArchiveFileBin.ReadByte()) != 0x00;) { ThisFileName += (char)b; } if (Path.GetExtension(ThisFileName).ToUpper() != ".DDS") { ThisFileName += ".dds"; } //Create texture entry and add filename TEX4 TextureEntry = new TEX4(); TextureEntry.FileName = ThisFileName; TextureEntries.Add(TextureEntry); } //Read the texture headers from the BIN ArchiveFileBin.BaseStream.Position = HeaderListBeginBIN + 12; for (int i = 0; i < NumberOfEntriesBIN; i++) { TextureEntries[i].HeaderPos = (int)ArchiveFileBin.BaseStream.Position; for (int x = 0; x < 4; x++) { TextureEntries[i].Magic += ArchiveFileBin.ReadChar(); } TextureEntries[i].Format = (TextureFormat)ArchiveFileBin.ReadInt32(); TextureEntries[i].Length_V2 = ArchiveFileBin.ReadInt32(); TextureEntries[i].Length_V1 = ArchiveFileBin.ReadInt32(); TextureEntries[i].Texture_V1.Width = ArchiveFileBin.ReadInt16(); TextureEntries[i].Texture_V1.Height = ArchiveFileBin.ReadInt16(); TextureEntries[i].Unk_V1 = ArchiveFileBin.ReadInt16(); TextureEntries[i].Texture_V2.Width = ArchiveFileBin.ReadInt16(); TextureEntries[i].Texture_V2.Height = ArchiveFileBin.ReadInt16(); TextureEntries[i].Unk_V2 = ArchiveFileBin.ReadInt16(); TextureEntries[i].UnknownHeaderBytes = ArchiveFileBin.ReadBytes(20); } /* Second, parse the PAK and pull ONLY header info from it - we'll pull textures when requested (to save memory) */ ArchiveFileBin.Close(); BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK)); //Read the header info from the PAK BigEndianUtils BigEndian = new BigEndianUtils(); ArchiveFile.BaseStream.Position += 4; //Skip nulls VersionNumber_PAK = BigEndian.ReadInt32(ArchiveFile); if (BigEndian.ReadInt32(ArchiveFile) != VersionNumber_BIN) { throw new Exception("Archive version mismatch!"); } NumberOfEntriesPAK = BigEndian.ReadInt32(ArchiveFile); if (BigEndian.ReadInt32(ArchiveFile) != NumberOfEntriesPAK) { throw new Exception("PAK entry count mismatch!"); } ArchiveFile.BaseStream.Position += 12; //Skip unknowns (1,1,1) //Read the texture headers from the PAK int OffsetTracker = (NumberOfEntriesPAK * 48) + 32; for (int i = 0; i < NumberOfEntriesPAK; i++) { //Header indexes are out of order, so optimise replacements by saving position int HeaderPosition = (int)ArchiveFile.BaseStream.Position; //Pull the entry info byte[] UnknownHeaderLead = ArchiveFile.ReadBytes(8); int PartLength = BigEndian.ReadInt32(ArchiveFile); if (PartLength != BigEndian.ReadInt32(ArchiveFile)) { continue; } byte[] UnknownHeaderTrail_1 = ArchiveFile.ReadBytes(18); //Find the entry TEX4 TextureEntry = TextureEntries[BigEndian.ReadInt16(ArchiveFile)]; TEX4_Part TexturePart = (!TextureEntry.Texture_V1.Saved) ? TextureEntry.Texture_V1 : TextureEntry.Texture_V2; //Write out the info TexturePart.HeaderPos = HeaderPosition; TexturePart.StartPos = OffsetTracker; TexturePart.UnknownHeaderLead = UnknownHeaderLead; TexturePart.Length = PartLength; TexturePart.Saved = true; TexturePart.UnknownHeaderTrail_1 = UnknownHeaderTrail_1; TexturePart.UnknownHeaderTrail_2 = ArchiveFile.ReadBytes(12); //Keep file offset updated OffsetTracker += TexturePart.Length; } HeaderListEndPAK = (int)ArchiveFile.BaseStream.Position; //Close PAK ArchiveFile.Close(); return(PAKReturnType.SUCCESS); } catch (IOException) { return(PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE); } catch (Exception) { return(PAKReturnType.FAIL_UNKNOWN); } }
/* Replace an existing file in the TexturePAK archive */ public override PAKReturnType ReplaceFile(string PathToNewFile, string FileName) { try { //Get the texture entry & parse new DDS int EntryIndex = GetFileIndex(FileName); if (EntryIndex == -1) { return(PAKReturnType.FAIL_GENERAL_LOGIC_ERROR); //CHANGED FOR OPENCAGE } TEX4 TextureEntry = TextureEntries[EntryIndex]; DDSReader NewTexture = new DDSReader(PathToNewFile); //Currently we only apply the new texture to the "biggest", some have lower mips that we don't edit (TODO) TEX4_Part BiggestPart = TextureEntry.Texture_V2; if (BiggestPart.HeaderPos == -1 || !BiggestPart.Saved) { BiggestPart = TextureEntry.Texture_V1; } if (BiggestPart.HeaderPos == -1 || !BiggestPart.Saved) { return(PAKReturnType.FAIL_REQUEST_IS_UNSUPPORTED); //Shouldn't reach this. } //CATHODE seems to ignore texture header information regarding size, so as default, resize any imported textures to the original size. //An option is provided in the toolkit to write size information to the header (done above) however, so don't resize if that's the case. //More work needs to be done to figure out why CATHODE doesn't honour the header's size value. int OriginalLength = BiggestPart.Length; Array.Resize(ref NewTexture.DataBlock, OriginalLength); //Update our internal knowledge of the textures BiggestPart.Length = (int)NewTexture.DataBlock.Length; BiggestPart.Width = (Int16)NewTexture.Width; BiggestPart.Height = (Int16)NewTexture.Height; TextureEntry.Format = NewTexture.Format; //TODO: Update smallest here too if it exists! //Will need to be written into the PAK at "Pull PAK sections before/after V2" too - headers are handled already. //Load the BIN and write out updated BIN texture header BinaryWriter ArchiveFileBinWriter = new BinaryWriter(File.OpenWrite(FilePathBIN)); ExtraBinaryUtils BinaryUtils = new ExtraBinaryUtils(); ArchiveFileBinWriter.BaseStream.Position = TextureEntry.HeaderPos; BinaryUtils.WriteString(TextureEntry.Magic, ArchiveFileBinWriter); ArchiveFileBinWriter.Write(BitConverter.GetBytes((int)TextureEntry.Format)); ArchiveFileBinWriter.Write((TextureEntry.Texture_V2.Length == -1) ? 0 : TextureEntry.Texture_V2.Length); ArchiveFileBinWriter.Write(TextureEntry.Texture_V1.Length); ArchiveFileBinWriter.Write(TextureEntry.Texture_V1.Width); ArchiveFileBinWriter.Write(TextureEntry.Texture_V1.Height); ArchiveFileBinWriter.Write(TextureEntry.Unk_V1); ArchiveFileBinWriter.Write(TextureEntry.Texture_V2.Width); ArchiveFileBinWriter.Write(TextureEntry.Texture_V2.Height); ArchiveFileBinWriter.Write(TextureEntry.Unk_V2); ArchiveFileBinWriter.Write(TextureEntry.UnknownHeaderBytes); ArchiveFileBinWriter.Close(); //Update headers for V1+2 in PAK if they exist BinaryWriter ArchiveFileWriter = new BinaryWriter(File.OpenWrite(FilePathPAK)); BigEndianUtils BigEndian = new BigEndianUtils(); if (TextureEntry.Texture_V1.HeaderPos != -1) { ArchiveFileWriter.BaseStream.Position = TextureEntry.Texture_V1.HeaderPos; ArchiveFileWriter.Write(TextureEntry.Texture_V1.UnknownHeaderLead); ArchiveFileWriter.Write(BigEndian.FlipEndian(BitConverter.GetBytes(TextureEntry.Texture_V1.Length))); ArchiveFileWriter.Write(BigEndian.FlipEndian(BitConverter.GetBytes(TextureEntry.Texture_V1.Length))); ArchiveFileWriter.Write(TextureEntry.Texture_V1.UnknownHeaderTrail_1); ArchiveFileWriter.Write(BigEndian.FlipEndian(BitConverter.GetBytes((Int16)EntryIndex))); ArchiveFileWriter.Write(TextureEntry.Texture_V1.UnknownHeaderTrail_2); } if (TextureEntry.Texture_V2.HeaderPos != -1) { ArchiveFileWriter.BaseStream.Position = TextureEntry.Texture_V2.HeaderPos; ArchiveFileWriter.Write(TextureEntry.Texture_V2.UnknownHeaderLead); ArchiveFileWriter.Write(BigEndian.FlipEndian(BitConverter.GetBytes(TextureEntry.Texture_V2.Length))); ArchiveFileWriter.Write(BigEndian.FlipEndian(BitConverter.GetBytes(TextureEntry.Texture_V2.Length))); ArchiveFileWriter.Write(TextureEntry.Texture_V2.UnknownHeaderTrail_1); ArchiveFileWriter.Write(BigEndian.FlipEndian(BitConverter.GetBytes((Int16)EntryIndex))); ArchiveFileWriter.Write(TextureEntry.Texture_V2.UnknownHeaderTrail_2); } ArchiveFileWriter.Close(); //Pull PAK sections before/after V2 BinaryReader ArchiveFile = new BinaryReader(File.OpenRead(FilePathPAK)); byte[] PAK_Pt1 = ArchiveFile.ReadBytes(BiggestPart.StartPos); ArchiveFile.BaseStream.Position += OriginalLength; byte[] PAK_Pt2 = ArchiveFile.ReadBytes((int)ArchiveFile.BaseStream.Length - (int)ArchiveFile.BaseStream.Position); ArchiveFile.Close(); //Write the PAK back out with new content ArchiveFileWriter = new BinaryWriter(File.OpenWrite(FilePathPAK)); ArchiveFileWriter.BaseStream.SetLength(0); ArchiveFileWriter.Write(PAK_Pt1); ArchiveFileWriter.Write(NewTexture.DataBlock); ArchiveFileWriter.Write(PAK_Pt2); ArchiveFileWriter.Close(); return(PAKReturnType.SUCCESS); } catch (IOException) { return(PAKReturnType.FAIL_COULD_NOT_ACCESS_FILE); } catch (Exception) { return(PAKReturnType.FAIL_UNKNOWN); } }