/// <summary> /// Decodes the file data from the input stream, or nothing if the data is not encoded /// </summary> /// <param name="inputStream">The input data stream to decode</param> /// <param name="outputStream">The output data stream for the decoded data</param> /// <param name="fileEntry">The file entry for the file to decode</param> public void DecodeFile(Stream inputStream, Stream outputStream, object fileEntry) { var entry = (BundleFile_FileEntry)fileEntry; // Decompress the data if compressed if (entry.IsCompressed) { BundleBootHeader.GetEncoder(entry.Pre_BundleVersion, entry.FileSize).DecodeStream(inputStream, outputStream); } }
/// <summary> /// Creates a new archive object /// </summary> /// <returns>The archive object</returns> public object CreateArchive() { // Create the data BundleFile data = new() { BootHeader = new BundleBootHeader(), FilePack = new FilePackMaster(), }; // Configure the data Config.ConfigureIpkData(data.BootHeader); return(data); }
/// <summary> /// Encodes the file data from the input stream, or nothing if the data does not need to be encoded /// </summary> /// <param name="inputStream">The input data stream to encode</param> /// <param name="outputStream">The output data stream for the encoded data</param> /// <param name="fileEntry">The file entry for the file to encode</param> public void EncodeFile(Stream inputStream, Stream outputStream, object fileEntry) { // Get the file entry var entry = (BundleFile_FileEntry)fileEntry; // Set the file size entry.FileSize = (uint)inputStream.Length; // Return the data as is if the file should not be compressed if (!Config.ShouldCompress(entry)) { return; } // Compress the bytes BundleBootHeader.GetEncoder(entry.Pre_BundleVersion, entry.FileSize).EncodeStream(inputStream, outputStream); Logger.Trace("The file {0} has been compressed", entry.Path.FileName); // Set the compressed file size entry.CompressedSize = (uint)outputStream.Length; }
/// <summary> /// Default constructor /// </summary> /// <param name="ipkData">The .ipk file data</param> /// <param name="archiveStream">The archive file stream</param> public IPKFileGenerator(BundleFile ipkData, Stream archiveStream) { // Get the .ipk data IPKData = ipkData; // If the block is compressed, decompress it to a temporary file if (ipkData.BootHeader.IsBlockCompressed) { // Get the temp path and create the file TempFile = new TempFile(true); // Set the stream to the temp file Stream = File.Open(TempFile.TempPath, FileMode.Open, FileAccess.ReadWrite); // Set the archive stream position archiveStream.Position = ipkData.BootHeader.BaseOffset; byte[] buffer = new byte[IPKData.BootHeader.BlockCompressedSize]; archiveStream.Read(buffer, 0, buffer.Length); // Create a memory stream using var memStream = new MemoryStream(buffer); // Decompress the block BundleBootHeader.GetEncoder(IPKData.BootHeader.Version, IPKData.BootHeader.BlockSize).DecodeStream(memStream, Stream); // Set the stream to be disposed DisposeStream = true; } else { // Set the stream to the .ipk archive Stream = archiveStream; // Set the stream not to be disposed DisposeStream = false; } }
private void WriteArchiveContent(BundleFile bundle, Stream stream, IArchiveFileGenerator <BundleFile_FileEntry> fileGenerator, bool compressBlock) { // Make sure we have a generator for each file if (fileGenerator.Count != bundle.FilePack.Files.Length) { throw new Exception("The .ipk file can't be serialized without a file generator for each file"); } TempFile? tempDecompressedBlockFile = null; FileStream?tempDecompressedBlockFileStream = null; try { // Create a temporary file to use if the block should be compressed if (compressBlock) { tempDecompressedBlockFile = new TempFile(true); tempDecompressedBlockFileStream = new FileStream(tempDecompressedBlockFile.TempPath, FileMode.Open); } // Get the stream to write the files to Stream currentStream = compressBlock ? tempDecompressedBlockFileStream ! : stream; // Write the file contents foreach (BundleFile_FileEntry file in bundle.FilePack.Files) { // Get the file stream from the generator using Stream fileStream = fileGenerator.GetFileStream(file); // Make sure the size matches if (fileStream.Length != file.ArchiveSize) { throw new Exception("The archived file size does not match the bytes retrieved from the generator"); } // Handle every file offset foreach (ulong offset in file.Offsets) { // Set the position currentStream.Position = (long)(compressBlock ? offset : (offset + bundle.BootHeader.BaseOffset)); // Write the bytes fileStream.CopyTo(currentStream); fileStream.Position = 0; } } // Handle the data if it should be compressed if (compressBlock) { // Get the length long decompressedSize = tempDecompressedBlockFileStream !.Length; // Create a temporary file for the final compressed data using TempFile tempCompressedBlockFile = new(true); using FileStream tempCompressedBlockFileStream = new(tempCompressedBlockFile.TempPath, FileMode.Open); tempDecompressedBlockFileStream.Position = 0; // Compress the data BundleBootHeader.GetEncoder(bundle.BootHeader.Version, -1).EncodeStream(tempDecompressedBlockFileStream, tempCompressedBlockFileStream); tempCompressedBlockFileStream.Position = 0; // Set the .ipk stream position stream.Position = bundle.BootHeader.BaseOffset; // Write the data to main stream tempCompressedBlockFileStream.CopyTo(stream); // Update the size bundle.BootHeader.BlockCompressedSize = (uint)tempCompressedBlockFileStream.Length; bundle.BootHeader.BlockSize = (uint)decompressedSize; } else { // Reset the size bundle.BootHeader.BlockCompressedSize = 0; bundle.BootHeader.BlockSize = 0; } } finally { tempDecompressedBlockFile?.Dispose(); tempDecompressedBlockFileStream?.Dispose(); } }
/// <summary> /// Configures the .ipk data with the default settings for the current settings /// </summary> /// <param name="data">The .ipk data to configure</param> public void ConfigureIpkData(BundleBootHeader data) { // Set default properties based on settings switch (Settings.Game) { case Game.RaymanOrigins: switch (Settings.Platform) { case Platform.Wii: data.Version = 3; data.Unknown1 = 6; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 1698768603; data.EngineVersion = 0; break; case Platform.Nintendo3DS: data.Version = 4; data.Unknown1 = 5; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 1635089726; data.EngineVersion = 0; break; case Platform.PlayStation3: data.Version = 3; data.Unknown1 = 3; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 1698768603; data.EngineVersion = 0; break; case Platform.PSVita: data.Version = 3; data.Unknown1 = 7; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 559042371; data.EngineVersion = 0; break; case Platform.PC: data.Version = 3; data.Unknown1 = 0; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 877930951; data.EngineVersion = 0; break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Platform), Settings.Platform, null); } break; case Game.RaymanLegends: switch (Settings.Platform) { case Platform.WiiU: data.Version = 5; data.Unknown1 = 6; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 78992; data.Unknown7 = 2697850994; data.EngineVersion = 84435; break; case Platform.NintendoSwitch: data.Version = 7; data.Unknown1 = 10; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 2514498303; data.EngineVersion = 0; break; case Platform.PSVita: data.Version = 5; data.Unknown1 = 6; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 2869177618; data.EngineVersion = 0; break; case Platform.PlayStation4: data.Version = 7; data.Unknown1 = 8; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 80253; data.Unknown7 = 2973796970; data.EngineVersion = 117321; break; case Platform.PC: data.Version = 5; data.Unknown1 = 0; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 1274838019; data.EngineVersion = 0; break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Platform), Settings.Platform, null); } break; case Game.RaymanAdventures: switch (Settings.Platform) { case Platform.Android: data.Version = 8; data.Unknown1 = 12; data.Unknown2 = 11; data.Unknown3 = true; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 127901; data.Unknown7 = 3037303110; data.EngineVersion = 277220; break; case Platform.iOS: data.Version = 8; data.Unknown1 = 12; data.Unknown2 = 19; data.Unknown3 = true; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 127895; data.Unknown7 = 3037303110; data.EngineVersion = 277216; break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Platform), Settings.Platform, null); } break; case Game.RaymanMini: switch (Settings.Platform) { case Platform.Mac: data.Version = 8; data.Unknown1 = 12; data.Unknown2 = 11; data.Unknown3 = true; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 4533; data.Unknown7 = 2293139714; data.EngineVersion = 4533; break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Platform), Settings.Platform, null); } break; case Game.JustDance2017: switch (Settings.Platform) { case Platform.WiiU: data.Version = 5; data.Unknown1 = 8; data.Unknown2 = 0; data.Unknown3 = false; data.Unknown4 = false; data.Unknown5 = false; data.Unknown6 = 0; data.Unknown7 = 3346979248; data.EngineVersion = 241478; break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Platform), Settings.Platform, null); } break; case Game.ValiantHearts: switch (Settings.Platform) { case Platform.Android: data.Version = 7; data.Unknown1 = 10; data.Unknown2 = 0; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown9 = 0; data.Unknown7 = 3713665533; data.EngineVersion = 0; break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Platform), Settings.Platform, null); } break; case Game.ChildOfLight: switch (Settings.Platform) { // NOTE: This is based on the demo case Platform.PC: data.Version = 7; data.Unknown1 = 0; data.Unknown2 = 0; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 3669482532; data.EngineVersion = 30765; break; case Platform.PSVita: data.Version = 7; data.Unknown1 = 6; data.Unknown2 = 0; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 19689438; data.EngineVersion = 0; break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Platform), Settings.Platform, null); } break; case Game.GravityFalls: switch (Settings.Platform) { case Platform.Nintendo3DS: data.Version = 7; data.Unknown1 = 10; data.Unknown2 = 0; data.Unknown3 = false; data.Unknown4 = true; data.Unknown5 = true; data.Unknown6 = 0; data.Unknown7 = 4160251604; data.EngineVersion = 0; break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Platform), Settings.Platform, null); } break; default: throw new ArgumentOutOfRangeException(nameof(Settings.Game), Settings.Game, null); } }