/// <summary> /// Adds or replace a given file with name filename with data from sourceStream /// </summary> /// <param name="filename">The filename used inside the pak</param> /// <param name="sourceStream">Source Stream of file to be added</param> /// <param name="CreateTime">Time to use as original file creation time</param> /// <param name="ModifyTime">Time to use as last modified time</param> /// <param name="autoSpareSpace">Enable adding 25% of the sourceStream size as padding when not replacing a file</param> /// <param name="pfi">AAPakFileInfo of the newly added or modified file</param> /// <returns></returns> public bool AddFileFromStream(string filename, Stream sourceStream, DateTime CreateTime, DateTime ModifyTime, bool autoSpareSpace, out AAPakFileInfo pfi) { pfi = nullAAPakFileInfo; if (readOnly) { return(false); } bool addAsNew = true; // Try to find the existing file if (GetFileByName(filename, ref pfi)) { var reservedSizeMax = pfi.size + pfi.paddingSize; addAsNew = (sourceStream.Length > reservedSizeMax); // Bugfix: If we have inssuficient space, make sure to delete the old file first as well if (addAsNew) { DeleteFile(pfi); } } if (addAsNew) { return(AddAsNewFile(filename, sourceStream, CreateTime, ModifyTime, autoSpareSpace, out pfi)); } else { return(ReplaceFile(ref pfi, sourceStream, ModifyTime)); } }
/// <summary> /// Exports a given file as a Stream /// </summary> /// <param name="file">AAPakFileInfo of the file to be exported</param> /// <returns>Returns a SubStream of file within the pak</returns> public Stream ExportFileAsStream(AAPakFileInfo file) { return(new SubStream(_gpFileStream, file.offset, file.size)); }
/// <summary> /// Writes current files info back into FAT (encrypted) /// </summary> /// <returns>Returns true on success</returns> public bool WriteToFAT() { // Read all File Table Data into Memory FAT.SetLength(0); int bufSize = 0x150; // Marshal.SizeOf(typeof(AAPakFileInfo)); MemoryStream ms = new MemoryStream(bufSize); // Could probably do without the intermediate memorystream, but it's easier to process BinaryWriter writer = new BinaryWriter(ms); // Init File Counts var totalFileCount = _owner.files.Count + _owner.extraFiles.Count; var filesToGo = _owner.files.Count; var extrasToGo = _owner.extraFiles.Count; int fileIndex = 0; int extrasIndex = 0; for (int i = 0; i < totalFileCount; i++) { ms.Position = 0; AAPakFileInfo pfi = null; if (_owner.PakType == PakFileType.PakTypeA) { // TypeA has files first, extra files after that if (filesToGo > 0) { filesToGo--; pfi = _owner.files[fileIndex]; fileIndex++; } else if (extrasToGo > 0) { extrasToGo--; pfi = _owner.extraFiles[extrasIndex]; extrasIndex++; } else { // If we get here, your PC cannot math and something went wrong pfi = null; break; } } else if (_owner.PakType == PakFileType.PakTypeB) { // TypeA has files first, extra files after that if (extrasToGo > 0) { extrasToGo--; pfi = _owner.extraFiles[extrasIndex]; extrasIndex++; } else if (filesToGo > 0) { filesToGo--; pfi = _owner.files[fileIndex]; fileIndex++; } else { // If we get here, your PC cannot math and something went wrong pfi = null; break; } } else { // Unsupported Type somehow } if (_owner.PakType == PakFileType.PakTypeA) { // Manually write the string for filename for (int c = 0; c < 0x108; c++) { byte ch = 0; if (c < pfi.name.Length) { ch = (byte)pfi.name[c]; } writer.Write(ch); } writer.Write(pfi.offset); writer.Write(pfi.size); writer.Write(pfi.sizeDuplicate); writer.Write(pfi.paddingSize); writer.Write(pfi.md5); writer.Write(pfi.dummy1); writer.Write(pfi.createTime); writer.Write(pfi.modifyTime); writer.Write(pfi.dummy2); } else if (_owner.PakType == PakFileType.PakTypeB) { writer.Write(pfi.paddingSize); writer.Write(pfi.md5); writer.Write(pfi.dummy1); writer.Write(pfi.size); // Manually write the string for filename for (int c = 0; c < 0x108; c++) { byte ch = 0; if (c < pfi.name.Length) { ch = (byte)pfi.name[c]; } writer.Write(ch); } writer.Write(pfi.sizeDuplicate); writer.Write(pfi.offset); writer.Write(pfi.modifyTime); writer.Write(pfi.createTime); writer.Write(pfi.dummy2); } else { // Uhm, what now ? } // encrypt and write our new file into the FAT memory stream byte[] decryptedFileData = new byte[bufSize]; ms.Position = 0; ms.Read(decryptedFileData, 0, bufSize); byte[] rawFileData = EncryptAES(decryptedFileData, key, true); // encrypt header data FAT.Write(rawFileData, 0, bufSize); } ms.Dispose(); // Calculate padding to header var dif = (FAT.Length % 0x200); if (dif > 0) { var pad = (0x200 - dif); FAT.SetLength(FAT.Length + pad); FAT.Position = FAT.Length; } // Update header info fileCount = (uint)_owner.files.Count; extraFileCount = (uint)_owner.extraFiles.Count; // Stretch size for header FAT.SetLength(FAT.Length + headerSize); // Encrypt the Header data EncryptHeaderData(); // Write encrypted header FAT.Write(rawData, 0, 0x20); return(true); }
/// <summary> /// Read and decrypt the File Details Table that was loaded into the FAT MemoryStream /// </summary> public void ReadFileTable() { // Check aa.bms QuickBMS file for reference FAT.Position = 0; int bufSize = 0x150; // Marshal.SizeOf(typeof(AAPakFileInfo)); MemoryStream ms = new MemoryStream(bufSize); // Could probably do without the intermediate memorystream, but it's easier to process BinaryReader reader = new BinaryReader(ms); // Read the Files _owner.files.Clear(); _owner.extraFiles.Clear(); var totalFileCount = fileCount + extraFileCount; var filesToGo = fileCount; var extraToGo = extraFileCount; for (uint i = 0; i < totalFileCount; i++) { // Read and decrypt a fileinfo block byte[] rawFileData = new byte[bufSize]; // decrypted header data FAT.Read(rawFileData, 0, bufSize); byte[] decryptedFileData = EncryptAES(rawFileData, key, false); // Read decrypted data into a AAPakFileInfo ms.SetLength(0); ms.Write(decryptedFileData, 0, bufSize); ms.Position = 0; AAPakFileInfo pfi = new AAPakFileInfo(); if (_owner.PakType == PakFileType.PakTypeA) { // Manually read the string for filename pfi.name = ""; for (int c = 0; c < 0x108; c++) { byte ch = reader.ReadByte(); if (ch != 0) { pfi.name += (char)ch; } else { break; } } ms.Position = 0x108; pfi.offset = reader.ReadInt64(); pfi.size = reader.ReadInt64(); pfi.sizeDuplicate = reader.ReadInt64(); pfi.paddingSize = reader.ReadInt32(); pfi.md5 = reader.ReadBytes(16); pfi.dummy1 = reader.ReadUInt32(); // observed 0x00000000 pfi.createTime = reader.ReadInt64(); pfi.modifyTime = reader.ReadInt64(); pfi.dummy2 = reader.ReadUInt64(); // unused ? } else if (_owner.PakType == PakFileType.PakTypeB) { pfi.paddingSize = reader.ReadInt32(); pfi.md5 = reader.ReadBytes(16); pfi.dummy1 = reader.ReadUInt32(); // 0x80000000 pfi.size = reader.ReadInt64(); // Manually read the string for filename pfi.name = ""; for (int c = 0; c < 0x108; c++) { byte ch = reader.ReadByte(); if (ch != 0) { pfi.name += (char)ch; } else { break; } } ms.Position = 0x128; pfi.sizeDuplicate = reader.ReadInt64(); pfi.offset = reader.ReadInt64(); pfi.modifyTime = reader.ReadInt64(); pfi.createTime = reader.ReadInt64(); pfi.dummy2 = reader.ReadUInt64(); // unused ? } if (_owner.PakType == PakFileType.PakTypeA) { // TypeA has files first and extra files last if (filesToGo > 0) { filesToGo--; _owner.files.Add(pfi); } else if (extraToGo > 0) { // "Extra" Files. It looks like these are old deleted files renamed to "__unused__" // There might be more to these, but can't be sure at this moment, looks like they are 512 byte blocks on my paks extraToGo--; _owner.extraFiles.Add(pfi); } } else if (_owner.PakType == PakFileType.PakTypeB) { // TypeB has extra files first and normal files last if (extraToGo > 0) { extraToGo--; _owner.extraFiles.Add(pfi); } else if (filesToGo > 0) { filesToGo--; _owner.files.Add(pfi); } } else { // Call the police, illegal Types are invading our safespace } /* * // Debug stuff * if (pfi.name == "bin32/archeage.exe") * { * ByteArrayToHexFile(decryptedFileData, "file-"+ i.ToString() + ".hex"); * File.WriteAllBytes("file-" + i.ToString() + ".bin",decryptedFileData); * } */ // Update our "end of file data" location if needed if ((pfi.offset + pfi.size + pfi.paddingSize) > AddFileOffset) { AddFileOffset = pfi.offset + pfi.size + pfi.paddingSize; } } ms.Dispose(); }
/// <summary> /// Adds a new file into the pak /// </summary> /// <param name="filename">Filename of the file inside the pakfile</param> /// <param name="sourceStream">Source Stream containing the file data</param> /// <param name="CreateTime">Time to use as initial file creation timestamp</param> /// <param name="ModifyTime">Time to use as last modified timestamp</param> /// <param name="autoSpareSpace">When set, tries to pre-allocate extra free space at the end of the file, this will be 25% of the filesize if used. If a "deleted file" is used, this parameter is ignored</param> /// <param name="pfi">Returns the fileinfo of the newly created file</param> /// <returns>Returns true on success</returns> public bool AddAsNewFile(string filename, Stream sourceStream, DateTime CreateTime, DateTime ModifyTime, bool autoSpareSpace, out AAPakFileInfo pfi) { // When we have a new file, or previous space wasn't enough, we will add it where the file table starts, and move the file table if (readOnly) { pfi = nullAAPakFileInfo; return(false); } bool addedAtTheEnd = true; AAPakFileInfo newFile = new AAPakFileInfo(); newFile.name = filename; newFile.offset = _header.FirstFileInfoOffset; newFile.size = sourceStream.Length; newFile.sizeDuplicate = newFile.size; newFile.createTime = CreateTime.ToFileTime(); newFile.modifyTime = ModifyTime.ToFileTime(); newFile.paddingSize = 0; newFile.md5 = new byte[16]; if (PakType == PakFileType.PakTypeB) { newFile.dummy1 = 0x80000000; } // check if we have "unused" space in extraFiles that we can use for (int i = 0; i < extraFiles.Count; i++) { if (newFile.size <= extraFiles[i].size) { // Copy the spare file's properties and remove it from extraFiles newFile.offset = extraFiles[i].offset; newFile.paddingSize = (int)(extraFiles[i].size - newFile.size); // This should already be aligned addedAtTheEnd = false; extraFiles.Remove(extraFiles[i]); break; } } if (addedAtTheEnd) { // Only need to calculate padding if we are adding at the end var dif = (newFile.size % 0x200); if (dif > 0) { newFile.paddingSize = (int)(0x200 - dif); } if (autoSpareSpace) { // If autoSpareSpace is used to add files, we will reserve some extra space as padding // Add 25% by default var spareSpace = (newFile.size / 4); spareSpace -= (spareSpace % 0x200); // Align the spare space newFile.paddingSize += (int)spareSpace; } } // Add to files list files.Add(newFile); isDirty = true; // Add File Data _gpFileStream.Position = newFile.offset; sourceStream.Position = 0; sourceStream.CopyTo(_gpFileStream); if (addedAtTheEnd) { _header.FirstFileInfoOffset = newFile.offset + newFile.size + newFile.paddingSize; } UpdateMD5(newFile); // TODO: optimize this to calculate WHILE we are copying the stream // Set output pfi = newFile; return(true); }