/// <summary> /// The load. /// </summary> /// <param name="bundleId"> /// The bundle id. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> public bool Load(string bundleId) { string headerFile = bundleId + "_h.bundle"; if (!File.Exists(headerFile)) { Console.WriteLine("Bundle header file does not exist."); return false; } try { var fs = new FileStream(headerFile, FileMode.Open); var br = new BinaryReader(fs); this.Header.Add(br.ReadUInt32()); this.Header.Add(br.ReadUInt32()); uint itemCount = br.ReadUInt32(); this.Header.Add(itemCount); this.Header.Add(br.ReadUInt32()); this.Header.Add(br.ReadUInt32()); this.HasLengthField = this.Header[4] == 24; if (this.HasLengthField) { this.Header.Add(br.ReadUInt32()); this.Header.Add(br.ReadUInt32()); } for (int i = 0; i < itemCount; ++i) { var be = new BundleEntry { Id = br.ReadUInt32(), Address = br.ReadUInt32() }; if (this.HasLengthField) { be.Length = br.ReadInt32(); } this.entries.Add(be); if (this.HasLengthField || i <= 0) { continue; } BundleEntry pbe = this.entries[i - 1]; pbe.Length = (int)be.Address - (int)pbe.Address; } if (itemCount > 0 && !this.HasLengthField) { this.entries[this.entries.Count - 1].Length = -1; } this.Footer = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position)); br.Close(); fs.Close(); } catch (Exception) { return false; } return true; }
/// <summary> /// The load. /// </summary> /// <param name="bundleId"> /// The bundle id. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> public bool Load(string bundleId) { string headerFile = bundleId + "_h.bundle"; if (!File.Exists(headerFile)) { Console.WriteLine("Bundle header file does not exist."); return false; } try { var fs = new FileStream(headerFile, FileMode.Open); var br = new BinaryReader(fs); this.Header.Add(br.ReadUInt32()); // offset to footer this.Header.Add(br.ReadUInt32()); // tag uint itemCount = br.ReadUInt32(); //count this.Header.Add(itemCount); this.Header.Add(br.ReadUInt32()); //count this.Header.Add(br.ReadUInt32()); //offset uint offset; if (this.Header[1] == this.Header[2]) { offset = this.Header[3]; } else { if (this.Header[1] == 0) offset = this.Header[4] + 4; else offset = this.Header[4]; } this.HasLengthField = offset == 24; if (this.HasLengthField) { this.Header.Add(br.ReadUInt32()); } br.BaseStream.Position = offset; this.Header.Add(br.ReadUInt32()); for (int i = 0; i < itemCount; ++i) { var be = new BundleEntry { Id = br.ReadUInt32(), Address = br.ReadUInt32() }; if (this.HasLengthField) { be.Length = br.ReadInt32(); } this.entries.Add(be); if (this.HasLengthField || i <= 0) { continue; } BundleEntry pbe = this.entries[i - 1]; pbe.Length = (int)be.Address - (int)pbe.Address; } if (itemCount > 0 && !this.HasLengthField) { this.entries[this.entries.Count - 1].Length = -1; } //Footer breakdown /* * uint32 - tag * uint32 - section size * uint32 - count * uint32 - unknown * uint32 - unknown * uint32 - tag? * foreach (count): * uint64 - hash (extension) * uint64 - hash (path) * uint32 - end? * uint32 (0) - end */ this.Footer = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position)); br.Close(); fs.Close(); } catch (Exception) { return false; } return true; }
/// <summary> /// The write zip entry. /// </summary> /// <param name="output"> /// The output. /// </param> /// <param name="rewriteItem"> /// Replacement parameters in form of BundleRewriteItem /// </param> /// <returns> /// How many bytes were written to the stream. /// </returns> private int WriteZipEntry(BundleEntry bundleEntry, Stream input, Stream output, BundleRewriteItem rewriteItem) { if (rewriteItem.SourceFile != null) { if(this.Zip == null) this.Zip = new ZipFile(rewriteItem.SourceFile); else if (!this.Zip.Name.Equals(rewriteItem.SourceFile)) { this.Zip.Dispose(); this.Zip = new ZipFile(rewriteItem.SourceFile); } } int extractedLength = 0; ZipEntry zip_entry = this.Zip[rewriteItem.ReplacementFile]; if (zip_entry != null) { if (zip_entry.UsesEncryption) { zip_entry.Password = "******"; //zip_entry.Encryption = EncryptionAlgorithm.WinZipAes256; } var br = new BinaryReader(input); var bw = new BinaryWriter(output); var data = new MemoryStream(); zip_entry.Extract(data); data.Seek(0, SeekOrigin.Begin); if (zip_entry.FileName.EndsWith(".script")) { StreamReader scriptStream = new StreamReader(data); List<BundleRewriteScriptAction> scriptActions = ParseScriptActions(ref scriptStream, zip_entry.FileName); data.Close(); scriptStream.Close(); //apply script functions here if (input != output) input.Seek(bundleEntry.Address, SeekOrigin.Begin); else input.Seek(0, SeekOrigin.Begin); long entryLength = bundleEntry.Length == -1 ? input.Length - input.Position : bundleEntry.Length; List<byte> entryBytes = br.ReadBytes((int)entryLength).ToList(); output.Seek(0, SeekOrigin.Begin); if (!ApplyScriptActions(ref entryBytes, ref scriptActions)) { bw.Write(br.ReadBytes((int)entryLength)); return (int)(entryLength); } bw.Write(entryBytes.ToArray()); return (int)entryBytes.ToArray().Length; } output.Seek(0, SeekOrigin.Begin); zip_entry.Extract(output); extractedLength = (int)zip_entry.UncompressedSize; data.Close(); } return extractedLength; }
/// <summary> /// The restore entry. /// </summary> /// <param name="output"> /// The output. /// </param> /// <param name="rewriteItem"> /// Replacement parameters in form of BundleRewriteItem /// </param> /// <returns> /// How many bytes were written to the stream. /// </returns> private int RestoreEntry(BundleEntry restoreBundleEntry, uint currentAddress, Stream input, Stream output) { var br = new BinaryReader(input); var bw = new BinaryWriter(output); input.Seek(restoreBundleEntry.Address, SeekOrigin.Begin); long entryLength = restoreBundleEntry.Length == -1 ? input.Length - input.Position : restoreBundleEntry.Length; output.Seek(currentAddress, SeekOrigin.Begin); bw.Write(br.ReadBytes((int)entryLength)); return (int)entryLength; }
/// <summary> /// The patch bundle restore. /// </summary> /// <param name="bundleId"> /// The bundle id. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> private bool PatchBundleRestore(BundleMod mod, string bundleId, bool method) { if (method) { if (!File.Exists(Path.Combine(this._assetFolder, bundleId + ".bundle")) || !File.Exists(Path.Combine(this._assetFolder, bundleId + "_h.bundle")) || !File.Exists(Path.Combine(this._assetFolder, "asset_backups", bundleId + ".bundle")) || !File.Exists(Path.Combine(this._assetFolder, "asset_backups", bundleId + "_h.bundle"))) return false; File.Delete(Path.Combine(this._assetFolder, bundleId + ".bundle")); File.Delete(Path.Combine(this._assetFolder, bundleId + "_h.bundle")); File.Copy(Path.Combine(this._assetFolder, "asset_backups", bundleId + ".bundle"), Path.Combine(this._assetFolder, bundleId + ".bundle"), true); File.Copy(Path.Combine(this._assetFolder, "asset_backups", bundleId + "_h.bundle"), Path.Combine(this._assetFolder, bundleId + "_h.bundle"), true); this.SetBundleProgress("Done", 100); return true; } var inHeader = new BundleHeader(); var inRestoreHeader = new BundleHeader(); Dictionary<uint, BundleEntry> inRestoreDictionary = new Dictionary<uint, BundleEntry>(); if (!inHeader.Load(Path.Combine(this._assetFolder, bundleId))) { return false; } if (!inRestoreHeader.Load(Path.Combine(this._assetFolder, "asset_backups", bundleId))) return false; foreach (BundleEntry entry in inRestoreHeader.Entries) { if (!inRestoreDictionary.ContainsKey(entry.Id)) inRestoreDictionary.Add(entry.Id, entry); } var outHeader = new BundleHeader(); outHeader.Footer = inHeader.Footer; outHeader.Header = inHeader.Header; outHeader.HasLengthField = inHeader.HasLengthField; var inRestoreBundle = new FileStream(Path.Combine(this._assetFolder, "asset_backups", bundleId + ".bundle"), FileMode.Open, FileAccess.Read); var inBundle = new FileStream(Path.Combine(this._assetFolder, bundleId + ".bundle"), FileMode.Open, FileAccess.Read); var outBundle = new FileStream( Path.Combine(this._assetFolder, bundleId + ".bundle.new"), FileMode.OpenOrCreate, FileAccess.ReadWrite); int currentAddress = 0; int currentEntry = 1; int entryCount = inHeader.Entries.Count; foreach (BundleEntry entry in inHeader.Entries) { if (currentEntry % 100 == 0) { this.SetBundleProgress( string.Format("Writing entry {0}/{1}", currentEntry, entryCount), (int)((currentEntry / (float)entryCount) * 100.0f)); } var newEntry = new BundleEntry(); newEntry.Id = entry.Id; newEntry.Length = entry.Length; newEntry.Address = (uint)currentAddress; bool replaced = false; foreach (int length in from rewriteItem in mod.ItemQueue where rewriteItem.Ids.Contains(entry.Id) select this.RestoreEntry(inRestoreDictionary[entry.Id], (uint)currentAddress, inRestoreBundle, outBundle)) { currentAddress += length; newEntry.Length = length; replaced = true; break; } outHeader.Entries.Add(newEntry); if (!replaced) { inBundle.Seek(entry.Address, SeekOrigin.Begin); long entryLength = entry.Length == -1 ? inBundle.Length - inBundle.Position : entry.Length; var br = new BinaryReader(inBundle); var bw = new BinaryWriter(outBundle); bw.Write(br.ReadBytes((int)entryLength)); currentAddress += (int)entryLength; } currentEntry += 1; } inBundle.Close(); inRestoreBundle.Close(); outBundle.Close(); var outHeaderStream = new FileStream( Path.Combine(this._assetFolder, bundleId + "_h.bundle"), FileMode.OpenOrCreate, FileAccess.Write); var outHeaderBr = new BinaryWriter(outHeaderStream); outHeader.WriteHeader(outHeaderBr); foreach (BundleEntry entry in outHeader.Entries) { entry.WriteEntry(outHeaderBr, outHeader.HasLengthField); } outHeader.WriteFooter(outHeaderBr); outHeaderBr.Close(); outHeaderStream.Close(); File.Delete(Path.Combine(this._assetFolder, bundleId + ".bundle")); File.Move(Path.Combine(this._assetFolder, bundleId + ".bundle.new"), Path.Combine(this._assetFolder, bundleId + ".bundle")); this.SetBundleProgress("Done", 100); return true; }
/// <summary> /// The patch bundle. /// </summary> /// <param name="bundleId"> /// The bundle id. /// </param> /// <returns> /// The <see cref="bool"/>. /// </returns> private bool PatchBundle(string bundleId) { System.Diagnostics.Stopwatch st_total = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch st_entry = new System.Diagnostics.Stopwatch(); System.Diagnostics.Stopwatch st_writing = new System.Diagnostics.Stopwatch(); bool isAll = bundleId.Contains("all_"); var inHeader = new BundleHeader(); if (!inHeader.Load(Path.Combine(this._assetFolder, bundleId))) { return false; } var outHeader = new BundleHeader(); outHeader.Footer = inHeader.Footer; outHeader.Header = inHeader.Header; outHeader.HasLengthField = inHeader.HasLengthField; var inBundle = new FileStream(Path.Combine(this._assetFolder, bundleId + ".bundle"), FileMode.Open, FileAccess.Read); var outBundle = new FileStream( Path.Combine(this._assetFolder, bundleId + ".bundle.new"), FileMode.Create, FileAccess.ReadWrite); //BufferedStream bsin = new BufferedStream(inBundle); //BufferedStream bsout = new BufferedStream(outBundle); var br = new BinaryReader(inBundle); var bw = new BinaryWriter(outBundle); long inFileLength = inBundle.Length; int currentAddress = 0; int currentEntry = 1; int entryCount = inHeader.Entries.Count; //stream buffers //int bufferSize = 4096; //1024^2 byte[] buffer = new byte[this.bufferSize]; if (isAll) inHeader.SortEntriesAddress(); foreach (BundleEntry entry in inHeader.Entries) { st_total.Restart(); st_entry.Restart(); if (currentEntry % 100 == 0) { this.SetBundleProgress( string.Format("Writing entry {0}/{1}", currentEntry, entryCount), (int)((currentEntry / (float)entryCount) * 100.0f)); } var newEntry = new BundleEntry(); newEntry.Id = entry.Id; newEntry.Length = entry.Length; newEntry.Address = (uint)currentAddress; bool replaced = false; bool firstpatched = false; bool restore = false; if (this._rewriteItems.ContainsKey(entry.Id)) { if (this._rewriteItems[entry.Id].toRestore) restore = true; MemoryStream newData = new MemoryStream(); if (restore) { if (this._backupType == 0) { var inRestoreBundle = new FileStream(Path.Combine(this._assetFolder, "asset_backups", bundleId + ".bundle"), FileMode.Open, FileAccess.Read); var inRestoreHeader = new BundleHeader(); if (!inRestoreHeader.Load(Path.Combine(this._assetFolder, "asset_backups", bundleId))) return false; foreach (BundleEntry restoreEntry in inRestoreHeader.Entries) { if (restoreEntry.Id == entry.Id) { newEntry.Length = this.RestoreEntry(restoreEntry, 0, inRestoreBundle, newData); replaced = true; break; } } } else { NameEntry ne = StaticStorage.Index.Id2Name(entry.Id); if (File.Exists(Path.Combine(this._assetFolder, "asset_backups", ne.ToString()))) { using (FileStream importFS = new FileStream(Path.Combine(this._assetFolder, "asset_backups", ne.ToString()), FileMode.Open, FileAccess.Read)) { using (BinaryReader importBR = new BinaryReader(importFS)) { newEntry.Length = (int)importFS.Length; newData.Seek(0, SeekOrigin.Begin); newData.Write(importBR.ReadBytes((int)importFS.Length), 0, (int)importFS.Length); replaced = true; } } } else { return false; } } } if (this._rewriteItems[entry.Id].priorityBundleRewriteItem != new BundleRewriteItem()) { newEntry.Length = this.WriteZipEntry(entry, inBundle, newData, this._rewriteItems[entry.Id].priorityBundleRewriteItem); firstpatched = true; replaced = true; } foreach (var rewriteItem in this._rewriteItems[entry.Id].BundleRewriteItem_queue) { if (rewriteItem.toRemove) continue; if (!firstpatched) { newEntry.Length = this.WriteZipEntry(entry, inBundle, newData, rewriteItem); firstpatched = true; } else { newEntry.Length = this.WriteZipEntry(newEntry, newData, newData, rewriteItem); } replaced = true; } if (newData.Length > 0L) { //buffer = new byte[megabyte]; newData.Position = 0L; int bytesRead = newData.Read(buffer, 0, this.bufferSize); while (bytesRead > 0) { outBundle.Write(buffer, 0, bytesRead); bytesRead = newData.Read(buffer, 0, this.bufferSize); } //outBundle.Seek(currentAddress, SeekOrigin.Begin); //newData.CopyTo(outBundle); } } st_entry.Stop(); st_writing.Restart(); if (replaced) currentAddress += newEntry.Length; outHeader.Entries.Add(newEntry); if (!replaced) { /* inBundle.Seek(entry.Address, SeekOrigin.Begin); long entryLength = entry.Length == -1 ? inBundle.Length - inBundle.Position : entry.Length; var br = new BinaryReader(inBundle); var bw = new BinaryWriter(outBundle); bw.Write(br.ReadBytes((int)entryLength)); currentAddress += (int)entryLength; */ //inBundle.Seek(entry.Address, SeekOrigin.Begin); inBundle.Seek((long)entry.Address, SeekOrigin.Begin); long entryLength = (entry.Length == -1 ? inFileLength - (long)entry.Address : (long)entry.Length); int remaining = (int)entryLength; int totalread = 0; //buffer = new byte[megabyte]; //bw.Write(br.ReadBytes((int)entryLength)); int bytesRead = inBundle.Read(buffer, 0, (((remaining - totalread) / this.bufferSize) > 0 ? this.bufferSize : remaining - totalread)); totalread += bytesRead; while (bytesRead > 0 && totalread <= remaining) { bw.Write(buffer, 0, bytesRead); bytesRead = inBundle.Read(buffer, 0, (((remaining - totalread) / this.bufferSize) > 0 ? this.bufferSize : remaining - totalread)); totalread += bytesRead; } //bw.Flush(); currentAddress += (int)entryLength; } currentEntry += 1; CurrentEntryCount++; SpeedEntryCount++; st_writing.Stop(); st_total.Stop(); if (st_total.ElapsedMilliseconds > 200) Console.WriteLine(bundleId + " - T: " + st_total.ElapsedMilliseconds + " ms. [entry: " + st_entry.ElapsedMilliseconds + " ms. writing: " + st_writing.ElapsedMilliseconds + " ms.] with " + newEntry.Length + " for replaced " + replaced); } bw.Flush(); inBundle.Close(); outBundle.Close(); if (isAll) outHeader.SortEntriesId(); var outHeaderStream = new FileStream( Path.Combine(this._assetFolder, bundleId + "_h.bundle"), FileMode.OpenOrCreate, FileAccess.Write); var outHeaderBr = new BinaryWriter(outHeaderStream); outHeader.WriteHeader(outHeaderBr); foreach (BundleEntry entry in outHeader.Entries) { entry.WriteEntry(outHeaderBr, outHeader.HasLengthField); } outHeader.WriteFooter(outHeaderBr); outHeaderBr.Close(); outHeaderStream.Close(); File.Delete(Path.Combine(this._assetFolder, bundleId + ".bundle")); File.Move(Path.Combine(this._assetFolder, bundleId + ".bundle.new"), Path.Combine(this._assetFolder, bundleId + ".bundle")); this.SetBundleProgress("Done", 100); return true; }