public static void CreateBarFile(IReadOnlyCollection <FileInfo> fileInfos, string inputPath,
                                         string outputFileName, string rootDir, bool ignoreLastWriteTime = true, bool xmlToXmb = true)
        {
            if (inputPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
            {
                inputPath = inputPath.Substring(0, inputPath.Length - 1);
            }

            var folder = Path.GetDirectoryName(outputFileName);

            if (folder != null && !Directory.Exists(folder))
            {
                Directory.CreateDirectory(folder);
            }

            var newFilesInfos = new List <FileInfo>();

            foreach (var file in fileInfos.ToArray())
            {
                if (file.FullName.EndsWith(".age4scn", StringComparison.OrdinalIgnoreCase))
                {
                    if (L33TZipUtils.IsL33TZip(file.FullName))
                    {
                        var data = L33TZipUtils.DecompressL33TZip(file.FullName);
                        File.Delete(file.FullName);
                        File.WriteAllBytes(file.FullName, data);
                        newFilesInfos.Add(new FileInfo(file.FullName));
                    }
                    else
                    {
                        newFilesInfos.Add(file);
                    }
                }
                else if (xmlToXmb && file.FullName.EndsWith(".quest", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".region", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".tactics", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".character", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".dataset", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".empire", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".spawneritem", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".groupingset", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".set", StringComparison.OrdinalIgnoreCase) ||
                         file.FullName.EndsWith(".xml", StringComparison.OrdinalIgnoreCase))
                {
                    if (XmbFile.IsXmlFile(file.FullName))
                    {
                        XmbFileUtils.XmlFileToXmbFile(file.FullName, file.FullName + ".xmb");
                        File.Delete(file.FullName);
                        newFilesInfos.Add(new FileInfo(file.FullName + ".xmb"));
                    }
                    else
                    {
                        newFilesInfos.Add(file);
                    }
                }
                else
                {
                    newFilesInfos.Add(file);
                }
            }

            using var fileStream = File.Open(outputFileName, FileMode.Create, FileAccess.Write, FileShare.None);
            using var writer     = new BinaryWriter(fileStream);
            //Write Bar Header
            var header = new BarFileHeader(Path.GetFileName(outputFileName), newFilesInfos);

            writer.Write(header.ToByteArray());

            //Write Files
            var barEntrys = new List <BarEntry>();

            foreach (var file in newFilesInfos)
            {
                var filePath = file.FullName;
                barEntrys.Add(new BarEntry(inputPath, file, (int)writer.BaseStream.Position,
                                           ignoreLastWriteTime));
                using var fileStream2 = File.Open(filePath, FileMode.Open, FileAccess.Read,
                                                  FileShare.Read);
                using var binReader = new BinaryReader(fileStream2);
                var buffer = new byte[4096];
                int read;
                while ((read = binReader.Read(buffer, 0, buffer.Length)) > 0)
                {
                    writer.Write(buffer, 0, read);
                }
            }

            //Write Bar Entrys
            var end = new BarFileBody(rootDir, barEntrys);

            writer.Write(end.ToByteArray());
        }
        public static async Task <bool> ScanAndRepairFile(GameFileInfo fileInfo, string gameFilePath,
                                                          IProgress <ScanSubProgress> progress = null, int concurrentDownload = 0, CancellationToken ct = default)
        {
            var filePath = Path.Combine(gameFilePath, fileInfo.FileName);

            //#1 Check File
            ct.ThrowIfCancellationRequested();
            progress?.Report(new ScanSubProgress(ScanSubProgressStep.Check, 0));

            Progress <double> subProgressCheck = null;

            if (progress != null)
            {
                subProgressCheck = new Progress <double>();
                subProgressCheck.ProgressChanged += (o, d) =>
                {
                    progress.Report(new ScanSubProgress(
                                        ScanSubProgressStep.Check, d));
                };
            }

            if (await RunFileCheck(filePath, fileInfo.Size, fileInfo.Crc32, ct, subProgressCheck))
            {
                progress?.Report(new ScanSubProgress(ScanSubProgressStep.End, 100));
                return(true);
            }

            //#2 Download File
            ct.ThrowIfCancellationRequested();
            progress?.Report(new ScanSubProgress(ScanSubProgressStep.Download, 0));

            var tempFileName = Path.Combine(GameScannerTempPath, $"{fileInfo.FileName.GetHashCode():X4}.tmp");

            if (File.Exists(tempFileName))
            {
                File.Delete(tempFileName);
            }

            var fileDownloader = concurrentDownload == 1
                ? (IFileDownloader) new SimpleFileDownloader(fileInfo.HttpLink, tempFileName)
                : new ChunkFileDownloader(fileInfo.HttpLink, tempFileName, GameScannerTempPath,
                                          concurrentDownload);

            if (progress != null)
            {
                fileDownloader.ProgressChanged += (sender, eventArg) =>
                {
                    switch (fileDownloader.State)
                    {
                    case FileDownloaderState.Invalid:
                    case FileDownloaderState.Download:
                        progress.Report(new ScanSubProgress(
                                            ScanSubProgressStep.Download, fileDownloader.DownloadProgress * 0.99,
                                            new ScanDownloadProgress(fileDownloader.DownloadSize, fileDownloader.BytesDownloaded,
                                                                     fileDownloader.DownloadSpeed)));
                        break;

                    case FileDownloaderState.Finalize:
                        progress.Report(new ScanSubProgress(
                                            ScanSubProgressStep.Download, 99,
                                            new ScanDownloadProgress(fileDownloader.DownloadSize, fileDownloader.BytesDownloaded,
                                                                     0)));
                        break;

                    case FileDownloaderState.Complete:
                        progress.Report(new ScanSubProgress(
                                            ScanSubProgressStep.Download, 100,
                                            new ScanDownloadProgress(fileDownloader.DownloadSize, fileDownloader.BytesDownloaded,
                                                                     0)));
                        break;

                    case FileDownloaderState.Error:
                    case FileDownloaderState.Abort:
                        break;

                    default:
                        throw new ArgumentOutOfRangeException(nameof(fileDownloader.State),
                                                              fileDownloader.State, null);
                    }
                }
            }
            ;

            await fileDownloader.DownloadAsync(ct);


            //#3 Check Downloaded File
            ct.ThrowIfCancellationRequested();

            Progress <double> subProgressCheckDown = null;

            if (progress != null)
            {
                progress.Report(new ScanSubProgress(ScanSubProgressStep.CheckDownload, 0));

                subProgressCheckDown = new Progress <double>();
                subProgressCheckDown.ProgressChanged += (o, d) =>
                {
                    progress.Report(new ScanSubProgress(
                                        ScanSubProgressStep.CheckDownload, d));
                };
            }

            try
            {
                await EnsureValidGameFile(tempFileName, fileInfo.BinSize, fileInfo.BinCrc32, ct, subProgressCheckDown);
            }
            catch
            {
                if (File.Exists(tempFileName))
                {
                    File.Delete(tempFileName);
                }

                throw;
            }

            //#4 Extract downloaded file
            ct.ThrowIfCancellationRequested();
            if (L33TZipUtils.IsL33TZip(tempFileName))
            {
                var tempFileName2 = $"{tempFileName.Replace(".tmp", string.Empty)}.ext.tmp";
                //
                Progress <double> extractProgress = null;
                if (progress != null)
                {
                    progress.Report(new ScanSubProgress(
                                        ScanSubProgressStep.ExtractDownload, 0));

                    extractProgress = new Progress <double>();
                    extractProgress.ProgressChanged += (o, d) =>
                    {
                        progress.Report(new ScanSubProgress(
                                            ScanSubProgressStep.ExtractDownload, d));
                    };
                }

                await L33TZipUtils.DecompressL33TZipAsync(tempFileName, tempFileName2, extractProgress, ct);

                //#4.1 Check Extracted File
                ct.ThrowIfCancellationRequested();
                Progress <double> subProgressCheckExt = null;
                if (progress != null)
                {
                    progress.Report(new ScanSubProgress(
                                        ScanSubProgressStep.CheckExtractDownload, 0));

                    subProgressCheckExt = new Progress <double>();
                    subProgressCheckExt.ProgressChanged += (o, d) =>
                    {
                        progress.Report(new ScanSubProgress(
                                            ScanSubProgressStep.CheckExtractDownload, d));
                    };
                }

                await EnsureValidGameFile(tempFileName2, fileInfo.Size, fileInfo.Crc32, ct, subProgressCheckExt);

                File.Delete(tempFileName);

                tempFileName = tempFileName2;
            }

            //#5 Move new file to game folder
            ct.ThrowIfCancellationRequested();

            progress?.Report(new ScanSubProgress(
                                 ScanSubProgressStep.Finalize, 0));

            if (File.Exists(filePath))
            {
                File.Delete(filePath);
            }
            else
            {
                var pathName = Path.GetDirectoryName(filePath);
                if (!string.IsNullOrEmpty(pathName) && !Directory.Exists(pathName))
                {
                    Directory.CreateDirectory(pathName);
                }
            }

            File.Move(tempFileName, filePath);

            //#6 End
            progress?.Report(new ScanSubProgress(
                                 ScanSubProgressStep.End, 100));

            return(true);
        }
        public static void ExtractBarFile(string inputFile, string file, string outputPath, bool convertFile = true)
        {
            if (string.IsNullOrWhiteSpace(file))
            {
                throw new ArgumentNullException(nameof(file), "Value cannot be null or empty.");
            }

            if (!File.Exists(inputFile))
            {
                throw new FileNotFoundException($"File '{inputFile}' not found!", inputFile);
            }

            if (!outputPath.EndsWith(Path.DirectorySeparatorChar.ToString()))
            {
                outputPath = outputPath + Path.DirectorySeparatorChar;
            }

            BarFileBody barFilesInfo;

            using (var fileStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                using var binReader = new BinaryReader(fileStream);
                //Read Header
                binReader.BaseStream.Seek(0, SeekOrigin.Begin); //Seek to header
                var barFileHeader = new BarFileHeader(binReader);

                //Read Files Info
                binReader.BaseStream.Seek(barFileHeader.FilesTableOffset, SeekOrigin.Begin); //Seek to file table

                barFilesInfo = new BarFileBody(binReader);
            }

            var barFileInfo = barFilesInfo.Entries.First(
                key => string.Equals(key.FileName, file, StringComparison.OrdinalIgnoreCase));

            using (var fileStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                using var binReader = new BinaryReader(fileStream);
                binReader.BaseStream.Seek(barFileInfo.Offset, SeekOrigin.Begin); //Seek to file

                var path = Path.Combine(outputPath, barFilesInfo.RootPath,
                                        Path.GetDirectoryName(barFileInfo.FileName) ?? string.Empty);

                if (!Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }

                var filePath = Path.Combine(outputPath, barFilesInfo.RootPath, barFileInfo.FileName);

                //Extract to tmp file
                var tempFileName = Path.GetTempFileName();
                using (var fileStreamFinal =
                           File.Open(tempFileName, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    using var final = new BinaryWriter(fileStreamFinal);
                    var buffer = new byte[4096];
                    int read;
                    var totalread = 0L;
                    while ((read = binReader.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        if (read > barFileInfo.FileSize)
                        {
                            totalread = barFileInfo.FileSize;
                            final.Write(buffer, 0, barFileInfo.FileSize);
                        }
                        else if (totalread + read <= barFileInfo.FileSize)
                        {
                            totalread += read;
                            final.Write(buffer, 0, read);
                        }
                        else if (totalread + read > barFileInfo.FileSize)
                        {
                            var leftToRead = barFileInfo.FileSize - totalread;
                            totalread = barFileInfo.FileSize;
                            final.Write(buffer, 0, Convert.ToInt32(leftToRead));
                        }

                        if (totalread >= barFileInfo.FileSize)
                        {
                            break;
                        }
                    }
                }

                //Convert file
                if (convertFile)
                {
                    //if (L33TZipUtils.IsL33TZip(tempFileName) &&
                    //    !barFileInfo.FileName.EndsWith(".age4scn", StringComparison.OrdinalIgnoreCase))
                    //{
                    //    var rnd = new Random(Guid.NewGuid().GetHashCode());
                    //    var tempFileName2 =
                    //        Path.Combine(Path.GetTempPath(),
                    //            $"{Path.GetFileName(barFileInfo.FileName)}-{rnd.Next()}.tmp");
                    //    L33TZipUtils.DecompressL33TZip(tempFileName, tempFileName2);

                    //    if (File.Exists(tempFileName))
                    //        File.Delete(tempFileName);

                    //    tempFileName = tempFileName2;
                    //}

                    if (barFileInfo.FileName.EndsWith(".xmb", StringComparison.OrdinalIgnoreCase))
                    {
                        try
                        {
                            var rnd           = new Random(Guid.NewGuid().GetHashCode());
                            var tempFileName2 =
                                Path.Combine(Path.GetTempPath(),
                                             $"{Path.GetFileName(barFileInfo.FileName)}-{rnd.Next()}.tmp");
                            XmbFileUtils.XmbFileToXmlFile(tempFileName, tempFileName2);

                            if (File.Exists(tempFileName))
                            {
                                File.Delete(tempFileName);
                            }

                            tempFileName = tempFileName2;

                            filePath = filePath.Substring(0, filePath.Length - 4);
                        }
                        catch (Exception)
                        {
                            //
                        }
                    }
                    else if (barFileInfo.FileName.EndsWith(".age4scn",
                                                           StringComparison.OrdinalIgnoreCase) &&
                             !L33TZipUtils.IsL33TZip(tempFileName))
                    {
                        var rnd           = new Random(Guid.NewGuid().GetHashCode());
                        var tempFileName2 =
                            Path.Combine(Path.GetTempPath(),
                                         $"{Path.GetFileName(barFileInfo.FileName)}-{rnd.Next()}.tmp");
                        L33TZipUtils.CompressFileAsL33TZipAsync(tempFileName, tempFileName2).GetAwaiter().GetResult();

                        if (File.Exists(tempFileName))
                        {
                            File.Delete(tempFileName);
                        }

                        tempFileName = tempFileName2;
                    }
                }

                //Move new file
                if (File.Exists(filePath))
                {
                    File.Delete(filePath);
                }

                //
                File.Move(tempFileName, filePath);

                //
                File.SetCreationTimeUtc(filePath,
                                        new DateTime(barFileInfo.LastWriteTime.Year, barFileInfo.LastWriteTime.Month,
                                                     barFileInfo.LastWriteTime.Day, barFileInfo.LastWriteTime.Hour,
                                                     barFileInfo.LastWriteTime.Minute, barFileInfo.LastWriteTime.Second));

                File.SetLastWriteTimeUtc(filePath,
                                         new DateTime(barFileInfo.LastWriteTime.Year, barFileInfo.LastWriteTime.Month,
                                                      barFileInfo.LastWriteTime.Day, barFileInfo.LastWriteTime.Hour,
                                                      barFileInfo.LastWriteTime.Minute, barFileInfo.LastWriteTime.Second));
            }
        }