/// <summary> /// Translates the modified BNK data (as modified by <seealso cref="Marshaller"/> for most use cases) and populates it into the specified stream. /// </summary> /// <param name="target">The stream to populate the new BNK file contents into</param> /// <returns></returns> public void TranslateToBNKFile(Stream target) { // BinaryWriter is little endian. THANK YOU. Makes my life easy here. Let's make a new stream for ease of access. BinaryWriter fileData = new BinaryWriter(target, Encoding.ASCII); // Now go through all of the sections. foreach (Section sect in SectionsInternal) { Console.WriteLine("Wrote " + sect.Identity); fileData.Write(sect.Identity.ToCharArray()); // DO NOT WRITE THE STRING. This puts a `0x04` before the string and makes everything go haywire. if (sect.Identity == "DIDX") { // We'll need to actually construct a new DIDX object. Of course, this can be emulated. // Length is how many bytes are there after after the length param is specified (aka how many to read after we've read length) // DIDX's wem file identities each take up 12 bytes a pop, so the length is... WEMFileIdentity[] identities = Marshaller.WEMFileIdentities.ToArray(); fileData.Write(identities.Length * 12); for (int idx = 0; idx < identities.Length; idx++) { fileData.Write(identities[idx].WemID); fileData.Write(identities[idx].Offset); fileData.Write(identities[idx].Size); } } else if (sect.Identity == "DATA") { // Same as DIDX -- Custom length! // Data is a bit odd. Thankfully the Marshaller has code to handle that and grabbing RawAllWEMFiles does all the work for us. byte[] allData = Marshaller.RawAllWEMFiles; fileData.Write(allData.Length); fileData.Write(allData); } else { SectionXXXX sectAmbiguous = (SectionXXXX)sect; fileData.Write(sectAmbiguous.Length); fileData.Write(sectAmbiguous.Data); } } }
/// <summary> /// Deconstruct a BNK file into its component parts. /// </summary> /// <param name="inputFile">The file to open.</param> public BNKFile(Stream inputFile) { // Documentation on the format was taken from http://wiki.xentax.com/index.php/Wwise_SoundBank_(*.bnk) // Please note: THIS IS HORRIBLY OUTDATED. // Literally only the DIDX and DATA sections are the same from the older versions of Wwise. Console.ForegroundColor = ConsoleColor.Green; SectionDIDX dataIndex = null; SectionDATA data = null; try { // Translate the incoming stream into a byte array. byte[] entireFile = null; int currentIndex = 0; using (MemoryStream buffer = new MemoryStream()) { inputFile.CopyTo(buffer); entireFile = buffer.ToArray(); inputFile.Dispose(); // Don't need this anymore. } RawFile = entireFile; // Catch case because ints are limited. if (entireFile.LongLength > int.MaxValue) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("This file is larger than " + int.MaxValue + " bytes! Shit is going to hit the fan after that many bytes are read due to indexes being constrained to integer values."); Console.ForegroundColor = ConsoleColor.Green; } Console.WriteLine("Reading file..."); Console.ForegroundColor = ConsoleColor.DarkGreen; while (currentIndex < entireFile.Length) { // Get the section identity (BKHD, DIDX, etc.) string identity = Section.ConvertFourBytesToString(entireFile, currentIndex); if (identity == SectionDIDX.SECTION_IDENTITY) { // DIDX Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(">> Populating Section " + identity + "..."); Console.ForegroundColor = ConsoleColor.DarkGreen; // Use DIDX's construction method. Section sect = SectionDIDX.MakeSectionFromByteArray(entireFile.Skip(currentIndex).ToArray()); // Bump the index forward and register the garbage. currentIndex += (int)sect.Length + 8; SectionsInternal.Add(sect); dataIndex = (SectionDIDX)sect; Console.WriteLine("Populated " + sect.Length + " bytes."); } else if (identity == SectionDATA.SECTION_IDENTITY) { // DATA Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(">> Populating Section " + identity + "..."); Console.ForegroundColor = ConsoleColor.DarkGreen; // Same thing. Populate via DATA's construction method. Section sect = SectionDATA.MakeSectionFromByteArray(entireFile.Skip(currentIndex).ToArray()); // Bump the index and write the garbage. currentIndex += (int)sect.Length + 8; SectionsInternal.Add(sect); data = (SectionDATA)sect; Console.WriteLine("Populated " + sect.Length + " bytes."); } else { // This represents an ambiguous type. It basically grabs the identity then stores the rest of the data in a raw byte array. This ensures 100% accuracy when cloning. Console.ForegroundColor = ConsoleColor.DarkYellow; Console.WriteLine(">> Skimming Section " + identity + "... (This section is not necessary for BNK patching)"); Console.ForegroundColor = ConsoleColor.DarkGreen; Section sect = SectionXXXX.MakeSectionFromByteArray(entireFile.Skip(currentIndex).ToArray()); currentIndex += (int)sect.Length + 8; Console.WriteLine("Populated " + sect.Length + " bytes."); SectionsInternal.Add(sect); } } } // Report exceptions. catch (InvalidCastException ex) { Console.ForegroundColor = ConsoleColor.Red; Console.Write("[System.InvalidCastException thrown!]: "); Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine(ex.Message); Console.ForegroundColor = ConsoleColor.DarkRed; Console.WriteLine(ex.StackTrace); Console.ForegroundColor = ConsoleColor.White; } catch (IndexOutOfRangeException) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("IndexOutOfRangeException was thrown when trying to write this category. stoopid lol"); Console.ForegroundColor = ConsoleColor.Green; } finally { if (dataIndex != null && data != null) { Console.WriteLine("Populating marshaller..."); //data.PopulateWEMFileArray(dataIndex); Marshaller = new WEMMarshaller(dataIndex.WEMFileIdentities, data.RawAllWEMFiles); Console.WriteLine("WEM Marshaller has been set up."); } Console.WriteLine("Done populating BNK file."); } }