/// <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);
        }
    }