public override bool Equals(object obj)
        {
            PSO2FileChecksum target = obj as PSO2FileChecksum;

            if (target != null)
            {
                if (Leayal.StringHelper.IsEqual(this.RelativePath, target.RelativePath, true))
                {
                    if (Leayal.StringHelper.IsEqual(this.MD5, target.MD5, true))
                    {
                        if (this.FileSize == target.FileSize)
                        {
                            return(true);
                        }
                    }
                }
            }
            return(false);
        }
        public new IEnumerable <PSO2FileChecksum> ReadToEnd()
        {
            if (result != null)
            {
                return(result);
            }

            result = new List <PSO2FileChecksum>();
            PSO2FileChecksum read = null;

            while (!this.EndOfStream)
            {
                read = this.ReadLine();
                if (read != null)
                {
                    result.Add(read);
                }
            }
            return(result);
        }
        public void ReadChecksumCache(FileStream stream)
        {
            if (this._disposed)
            {
                throw new ObjectDisposedException("ChecksumCache");
            }
            if (this.myCheckSumList != null)
            {
                return;
            }

            if (stream.Length > (ChecksumCacheVersion.Signature.Length + 4))
            {
                try
                {
                    if (stream.Position != ChecksumCacheVersion.Signature.Length)
                    {
                        stream.Seek(ChecksumCacheVersion.Signature.Length, SeekOrigin.Begin);
                    }

                    using (BinaryReader br = new BinaryReader(stream, Encoding.Unicode, true))
                        this._checksumVersion = new Version(br.ReadByte(), br.ReadByte(), br.ReadByte(), br.ReadByte());

                    using (ZlibStream compressStream = new ZlibStream(stream, CompressionMode.Decompress, CompressionLevel.BestCompression, true, Encoding.Unicode))
                        using (BinaryReader br = new BinaryReader(compressStream, Encoding.Unicode, true))
                            using (ChecksumCacheReader ccr = new ChecksumCacheReader(compressStream))
                            {
                                this._PSO2Version = br.ReadString();
                                int count = br.ReadInt32();
                                Dictionary <string, PSO2FileChecksum> dict = new Dictionary <string, PSO2FileChecksum>(count, StringComparer.OrdinalIgnoreCase);
                                PSO2FileChecksum tmpline = null;
                                for (int i = 0; i < count; i++)
                                {
                                    tmpline = ccr.ReadLine();
                                    if (tmpline != null)
                                    {
                                        dict[tmpline.RelativePath] = tmpline;
                                    }
                                    else
                                    {
                                        this._corruptEntryCount++;
                                    }
                                }
                                this.myCheckSumList = new ConcurrentDictionary <string, PSO2FileChecksum>(dict, StringComparer.OrdinalIgnoreCase);
                            }
                }
                catch (Exception)
                {
                    if (this.myCheckSumList == null)
                    {
                        this.myCheckSumList = new ConcurrentDictionary <string, PSO2FileChecksum>(StringComparer.OrdinalIgnoreCase);
                    }
                    else
                    {
                        this.myCheckSumList.Clear();
                    }
                }
            }
            else
            {
                if (this.myCheckSumList == null)
                {
                    this.myCheckSumList = new ConcurrentDictionary <string, PSO2FileChecksum>(StringComparer.OrdinalIgnoreCase);
                }
                else
                {
                    this.myCheckSumList.Clear();
                }
            }
        }
        /// <summary>
        /// Verify and redownload any missing/old files
        /// </summary>
        /// <param name="clientDirectory">The pso2_dir directory</param>
        /// <param name="version">Specify the version</param>
        /// <param name="filelist">Specify the patchlist to check and download</param>
        /// <param name="options">Provide the options for current download sessions</param>
        public async Task VerifyAndDownloadAsync(string clientDirectory, ClientVersionCheckResult version, RemotePatchlist filelist, ClientUpdateOptions options)
        {
            ConcurrentDictionary <PSO2File, Exception> failedfiles = new ConcurrentDictionary <PSO2File, Exception>();

            int currentprogress = 0, downloadedfiles = 0;

            CancellationTokenSource totalCancelSource = new CancellationTokenSource();

            options.ParallelOptions.CancellationToken = totalCancelSource.Token;

            //*
            options.ParallelOptions.CancellationToken.Register(() =>
            {
                if (options.ChecksumCache != null && downloadedfiles > 0)
                {
                    this.StepChanged?.Invoke(UpdateStep.WriteCache, options.ChecksumCache);
                    options.ChecksumCache.WriteChecksumCache(version.LatestVersion);
                }
                options.Dispose();
                this.UpdateCompleted?.Invoke(new PSO2NotifyEventArgs(true, clientDirectory, new ReadOnlyDictionary <PSO2File, Exception>(failedfiles)));
            });
            //*/

            this.cancelBag.Add(totalCancelSource);

            this.StepChanged.Invoke(UpdateStep.BeginFileCheckAndDownload, null);

            if (options.ChecksumCache == null)
            {
                options.Profile = UpdaterProfile.PreferAccuracy;
            }
            else
            {
                options.ChecksumCache.ReadChecksumCache();
            }

            ConcurrentBag <PSO2File> pso2fileBag = new ConcurrentBag <PSO2File>(filelist.Values);
            Func <Task> actionV;

            switch (options.Profile)
            {
            case UpdaterProfile.PreferAccuracy:
                actionV = new Func <Task>(async() =>
                {
                    PSO2File pso2file;
                    while (!totalCancelSource.IsCancellationRequested && pso2fileBag.TryTake(out pso2file))
                    {
                        string fullpath = Path.Combine(clientDirectory, pso2file.WindowFilename);
                        try
                        {
                            if (string.Equals(pso2file.SafeFilename, DefaultValues.CensorFilename, StringComparison.OrdinalIgnoreCase))
                            {
                                if (File.Exists(fullpath))
                                {
                                    string md5fromfile = MD5Wrapper.HashFromFile(fullpath);
                                    if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase))
                                    {
                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);
                                        using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                            try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                        File.Delete(fullpath);
                                        File.Move(fullpath + ".dtmp", fullpath);
                                        Interlocked.Increment(ref downloadedfiles);
                                        if (options.ChecksumCache != null)
                                        {
                                            ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath);
                                            options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                        }
                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                    }
                                    else
                                    {
                                        if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                        {
                                            ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash);
                                            options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                            Interlocked.Increment(ref downloadedfiles);
                                        }
                                    }
                                }
                                else
                                {
                                    fullpath = Path.Combine(clientDirectory, pso2file.WindowFilename + ".backup");
                                    if (File.Exists(fullpath))
                                    {
                                        string md5fromfile = MD5Wrapper.HashFromFile(fullpath);
                                        if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase))
                                        {
                                            this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                            using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                                try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                            File.Delete(fullpath);
                                            File.Move(fullpath + ".dtmp", fullpath);
                                            Interlocked.Increment(ref downloadedfiles);
                                            if (options.ChecksumCache != null)
                                            {
                                                ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath);
                                                options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                            }

                                            this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                        }
                                        else
                                        {
                                            if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                            {
                                                ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash);
                                                options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                                Interlocked.Increment(ref downloadedfiles);
                                            }
                                        }
                                    }
                                }
                            }
                            else
                            {
                                if (File.Exists(fullpath))
                                {
                                    string md5fromfile = MD5Wrapper.HashFromFile(fullpath);
                                    if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase))
                                    {
                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                        using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                            try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                        File.Delete(fullpath);
                                        File.Move(fullpath + ".dtmp", fullpath);
                                        Interlocked.Increment(ref downloadedfiles);
                                        if (options.ChecksumCache != null)
                                        {
                                            ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath);
                                            options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                        }
                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                    }
                                    else
                                    {
                                        if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                        {
                                            ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash);
                                            options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                            Interlocked.Increment(ref downloadedfiles);
                                        }
                                    }
                                }
                                else
                                {
                                    this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                    using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                        try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                    File.Delete(fullpath);
                                    File.Move(fullpath + ".dtmp", fullpath);
                                    Interlocked.Increment(ref downloadedfiles);
                                    if (options.ChecksumCache != null)
                                    {
                                        ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath);
                                        options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                    }
                                    this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                }
                            }
                        }
#if !DEBUG
                        catch (Exception ex)
                        {
                            failedfiles.TryAdd(pso2file, ex);
                        }
#endif
                        finally
                        {
                            try { File.Delete(fullpath + ".dtmp"); } catch { }
                            Interlocked.Increment(ref currentprogress);
                            this.ProgressChanged?.Invoke(currentprogress, filelist.Count);
                        }
                    }
                });
                break;

            case UpdaterProfile.PreferSpeed:
                actionV = new Func <Task>(async() =>
                {
                    PSO2File pso2file;
                    while (!totalCancelSource.IsCancellationRequested && pso2fileBag.TryTake(out pso2file))
                    {
                        string fullpath = Path.Combine(clientDirectory, pso2file.WindowFilename);
                        try
                        {
                            if (!string.Equals(pso2file.SafeFilename, DefaultValues.CensorFilename, StringComparison.OrdinalIgnoreCase))
                            {
                                if (options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                {
                                    if (!string.Equals(options.ChecksumCache.ChecksumList[pso2file.Filename].MD5, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase))
                                    {
                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                        using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                            try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                        File.Delete(fullpath);
                                        File.Move(fullpath + ".dtmp", fullpath);
                                        Interlocked.Increment(ref downloadedfiles);
                                        ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath);
                                        options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));

                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                    }
                                }
                                else
                                {
                                    this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                    using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                        try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                    File.Delete(fullpath);
                                    File.Move(fullpath + ".dtmp", fullpath);
                                    Interlocked.Increment(ref downloadedfiles);
                                    ChecksumCache.PSO2FileChecksum newchecksum = ChecksumCache.PSO2FileChecksum.FromFile(clientDirectory, fullpath);
                                    options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));

                                    this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                }
                            }
                        }
#if !DEBUG
                        catch (Exception ex)
                        {
                            failedfiles.TryAdd(pso2file, ex);
                        }
#endif
                        finally
                        {
                            try { File.Delete(fullpath + ".dtmp"); } catch { }
                            Interlocked.Increment(ref currentprogress);
                            this.ProgressChanged?.Invoke(currentprogress, filelist.Count);
                        }
                    }
                });
                break;

            default:
                actionV = new Func <Task>(async() =>
                {
                    PSO2File pso2file;
                    while (!totalCancelSource.IsCancellationRequested && pso2fileBag.TryTake(out pso2file))
                    {
                        string fullpath = Path.Combine(clientDirectory, pso2file.WindowFilename);
                        try
                        {
                            if (string.Equals(pso2file.SafeFilename, DefaultValues.CensorFilename, StringComparison.OrdinalIgnoreCase))
                            {
                                if (File.Exists(fullpath))
                                {
                                    string md5fromfile = null;
                                    if (options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                    {
                                        var checksumfile = options.ChecksumCache.ChecksumList[pso2file.Filename];
                                        using (FileStream fs = File.OpenRead(fullpath))
                                            if (fs.Length == checksumfile.FileSize)
                                            {
                                                md5fromfile = checksumfile.MD5;
                                            }
                                    }
                                    if (string.IsNullOrEmpty(md5fromfile))
                                    {
                                        md5fromfile = MD5Wrapper.HashFromFile(fullpath);
                                    }

                                    if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase))
                                    {
                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                        using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                        {
                                            try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                            ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, fs.Length, pso2file.MD5Hash);
                                            options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                        }
                                        File.Delete(fullpath);
                                        File.Move(fullpath + ".dtmp", fullpath);
                                        Interlocked.Increment(ref downloadedfiles);

                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                    }
                                    else
                                    {
                                        if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                        {
                                            ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash);
                                            options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                            Interlocked.Increment(ref downloadedfiles);
                                        }
                                    }
                                }
                                else
                                {
                                    fullpath = Path.Combine(clientDirectory, pso2file.Filename + ".backup");
                                    if (File.Exists(fullpath))
                                    {
                                        string md5fromfile = null;
                                        if (options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                        {
                                            var checksumfile = options.ChecksumCache.ChecksumList[pso2file.Filename];
                                            using (FileStream fs = File.OpenRead(fullpath))
                                                if (fs.Length == checksumfile.FileSize)
                                                {
                                                    md5fromfile = checksumfile.MD5;
                                                }
                                        }
                                        if (string.IsNullOrEmpty(md5fromfile))
                                        {
                                            md5fromfile = MD5Wrapper.HashFromFile(fullpath);
                                        }

                                        if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase))
                                        {
                                            this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                            using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                            {
                                                try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                                ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, fs.Length, pso2file.MD5Hash);
                                                options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                            }
                                            File.Delete(fullpath);
                                            File.Move(fullpath + ".dtmp", fullpath);
                                            Interlocked.Increment(ref downloadedfiles);

                                            this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                        }
                                        else
                                        {
                                            if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                            {
                                                ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash);
                                                options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                                Interlocked.Increment(ref downloadedfiles);
                                            }
                                        }
                                    }
                                }
                            }
                            else
                            {
                                if (File.Exists(fullpath))
                                {
                                    string md5fromfile = null;
                                    if (options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                    {
                                        var checksumfile = options.ChecksumCache.ChecksumList[pso2file.Filename];
                                        using (FileStream fs = File.OpenRead(fullpath))
                                            if (fs.Length == checksumfile.FileSize)
                                            {
                                                md5fromfile = checksumfile.MD5;
                                            }
                                    }
                                    if (string.IsNullOrEmpty(md5fromfile))
                                    {
                                        md5fromfile = MD5Wrapper.HashFromFile(fullpath);
                                    }

                                    if (!string.Equals(md5fromfile, pso2file.MD5Hash, StringComparison.OrdinalIgnoreCase))
                                    {
                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                        using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                        {
                                            try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                            ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, fs.Length, pso2file.MD5Hash);
                                            options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                        }
                                        File.Delete(fullpath);
                                        File.Move(fullpath + ".dtmp", fullpath);
                                        Interlocked.Increment(ref downloadedfiles);

                                        this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                    }
                                    else
                                    {
                                        if (!options.ChecksumCache.ChecksumList.ContainsKey(pso2file.Filename))
                                        {
                                            ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, pso2file.Length, pso2file.MD5Hash);
                                            options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                            Interlocked.Increment(ref downloadedfiles);
                                        }
                                    }
                                }
                                else
                                {
                                    this.StepChanged?.Invoke(UpdateStep.DownloadingFileStart, pso2file);

                                    using (FileStream fs = File.Create(fullpath + ".dtmp"))
                                    {
                                        try { await this.DownloadFileAsync(pso2file, fs, totalCancelSource); } catch (TaskCanceledException) { }
                                        ChecksumCache.PSO2FileChecksum newchecksum = new ChecksumCache.PSO2FileChecksum(pso2file.Filename, fs.Length, pso2file.MD5Hash);
                                        options.ChecksumCache.ChecksumList.AddOrUpdate(pso2file.Filename, newchecksum, new Func <string, ChecksumCache.PSO2FileChecksum, ChecksumCache.PSO2FileChecksum>((key, oldval) => { return(newchecksum); }));
                                    }
                                    File.Delete(fullpath);
                                    File.Move(fullpath + ".dtmp", fullpath);
                                    Interlocked.Increment(ref downloadedfiles);

                                    this.StepChanged?.Invoke(UpdateStep.DownloadingFileEnd, pso2file);
                                }
                            }
                        }
#if !DEBUG
                        catch (Exception ex)
                        {
                            failedfiles.TryAdd(pso2file, ex);
                        }
#endif
                        finally
                        {
                            try { File.Delete(fullpath + ".dtmp"); } catch { }
                            Interlocked.Increment(ref currentprogress);
                            this.ProgressChanged?.Invoke(currentprogress, filelist.Count);
                        }
                    }
                });
                break;
            }

            try
            {
                Task[] tasks = new Task[options.ParallelOptions.MaxDegreeOfParallelism];
                for (int i = 0; i < tasks.Length; i++)
                {
                    tasks[i] = Task.Factory.StartNew(actionV, TaskCreationOptions.LongRunning).Unwrap();
                }
                await Task.WhenAll(tasks);

                if (options.ChecksumCache != null && downloadedfiles > 0)
                {
                    this.StepChanged?.Invoke(UpdateStep.WriteCache, options.ChecksumCache);
                    options.ChecksumCache.WriteChecksumCache(version.LatestVersion);
                }
                options.Dispose();
                if (!totalCancelSource.IsCancellationRequested)
                {
                    if (failedfiles.Count < 4)
                    {
                        Settings.VersionString = version.LatestVersion;
                    }
                    this.UpdateCompleted?.Invoke(new PSO2NotifyEventArgs(version.LatestVersion, clientDirectory, new ReadOnlyDictionary <PSO2File, Exception>(failedfiles)));
                }
                else
                {
                    this.UpdateCompleted?.Invoke(new PSO2NotifyEventArgs(true, clientDirectory, new ReadOnlyDictionary <PSO2File, Exception>(failedfiles)));
                }
            }
            catch (OperationCanceledException) { }
            catch (Exception)
            {
                if (options.ChecksumCache != null && downloadedfiles > 0)
                {
                    this.StepChanged?.Invoke(UpdateStep.WriteCache, options.ChecksumCache);
                    options.ChecksumCache.WriteChecksumCache(version.LatestVersion);
                }
                options.Dispose();
                throw;
            }
        }