예제 #1
0
 private void OnDownloadProgress(long done, long total)
 {
     ProgressValue         = done;
     ProgressMaximum       = total;
     ProgressIndeterminate = false;
     DownloadStatus        = $@"{FileSize.FormatSize(ProgressValue)}/{FileSize.FormatSize(ProgressMaximum)}";
 }
예제 #2
0
        private void Refresh()
        {
            TrackedMemoryObjects.Where(x => !x.IsAlive()).ToList().ForEach(x => x.RemainingLifetimeAfterGC--);
            TrackedMemoryObjects.RemoveAll(x => !x.IsAlive() && x.RemainingLifetimeAfterGC < 0);
            InstancedTrackedMemoryObjects.ReplaceAll(TrackedMemoryObjects);
            LastRefreshText = "Last refreshed: " + DateTime.Now;

            var sizeUsed = System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64;

            CurrentMemoryUsageText = "Current memory usage: " + FileSize.FormatSize(sizeUsed);

            TotalLargeInUseStr   = FileSize.FormatSize(MemoryManager.LargePoolTotalSize);
            LargeInUseStr        = FileSize.FormatSize(MemoryManager.LargePoolInUseSize);
            LargeFreeStr         = FileSize.FormatSize(MemoryManager.LargePoolFreeSize);
            TotalSmallInUseStr   = FileSize.FormatSize(MemoryManager.SmallPoolTotalSize);
            SmallInUseStr        = FileSize.FormatSize(MemoryManager.SmallPoolInUseSize);
            SmallFreeStr         = FileSize.FormatSize(MemoryManager.SmallPoolFreeSize);
            MaxBufferSize        = FileSize.FormatSize(MemoryManager.MaximumBufferSize);
            MemoryBlockSize      = FileSize.FormatSize(MemoryManager.BlockSize);
            SmallBlocksAvailable = MemoryManager.SmallBlocksAvailable;
            LargeBlocksAvailable = MemoryManager.LargeBlocksAvailable;

            MaxMemoryObserved     = Math.Max(MaxMemoryObserved, sizeUsed);
            MaxMemoryObservedText = $"Max memory used: {FileSize.FormatSize(MaxMemoryObserved)}";
            //foreach (var item in InstancedTrackedMemoryObjects)
            //{
            //    item.RefreshStatus();
            //}
        }
예제 #3
0
 public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
 {
     if (value is long l)
     {
         return(FileSize.FormatSize(l));
     }
     return($"Invalid value {value}");
 }
예제 #4
0
 public override string ToString()
 {
     if (!Selectable)
     {
         return(filepath);
     }
     return(Path.GetFileName(filepath) + @" - " + FileSize.FormatSize(new FileInfo(filepath).Length));
 }
예제 #5
0
 public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
 {
     if (value is DLCPackage.FileEntryStruct fes)
     {
         var compressed = fes.BlockSizeTableIndex != 0xFFFFFFFF;
         var uncompSize = FileSize.FormatSize(fes.RealUncompressedSize);
         return($"{(compressed ? $"Compressed" : "Uncompressed")}, uncompressed size: {uncompSize}");
     }
     return($"Invalid value {value}");
 }
예제 #6
0
 public void GetDirectoryContents(ListView l)
 {
     foreach (PAZ.PAZFileEntry entry in this.Entries)
     {
         ListViewItem l1 = new ListViewItem(entry.Name);
         l1.SubItems.Add(FileSize.FormatSize(entry.Data.Length));
         ByteFileInfo file = new ByteFileInfo(entry);
         string       type = FileHandler.GetType(file);
         if (file.IsLZ77Compressed)
         {
             l1.ForeColor = Color.Blue;
         }
         file.Dispose();
         FileHandler.SetListViewItemInfo(l1, type, l);
         l.Items.Add(l1);
     }
 }
        private void Refresh()
        {
            TrackedMemoryObjects.Where(x => !x.IsAlive()).ToList().ForEach(x => x.RemainingLifetimeAfterGC--);
            TrackedMemoryObjects.RemoveAll(x => !x.IsAlive() && x.RemainingLifetimeAfterGC < 0);
            InstancedTrackedMemoryObjects.ReplaceAll(TrackedMemoryObjects);
            LastRefreshText        = "Last refreshed: " + DateTime.Now;
            CurrentMemoryUsageText = "Current process allocation: " + FileSize.FormatSize(System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64);

            LargeInUseStr   = FileSize.FormatSize(MixinHandler.MixinMemoryStreamManager.LargePoolInUseSize);
            LargeFreeStr    = FileSize.FormatSize(MixinHandler.MixinMemoryStreamManager.LargePoolFreeSize);
            SmallInUseStr   = FileSize.FormatSize(MixinHandler.MixinMemoryStreamManager.SmallPoolInUseSize);
            SmallFreeStr    = FileSize.FormatSize(MixinHandler.MixinMemoryStreamManager.SmallPoolFreeSize);
            MaxBufferSize   = FileSize.FormatSize(MixinHandler.MixinMemoryStreamManager.MaximumBufferSize);
            MemoryBlockSize = FileSize.FormatSize(MixinHandler.MixinMemoryStreamManager.BlockSize);
            //foreach (var item in InstancedTrackedMemoryObjects)
            //{
            //    item.RefreshStatus();
            //}
        }
예제 #8
0
 public void GetDirectoryContents(ListView l)
 {
     foreach (NARC.DirectoryEntry subdir in this.Subdirs)
     {
         l.Items.Add(new ListViewItem(subdir.Name, 0)
         {
             Group = l.Groups[0]
         });
     }
     foreach (NARC.FileEntry file1 in this.Files)
     {
         ListViewItem l1 = new ListViewItem(file1.Name);
         l1.SubItems.Add(FileSize.FormatSize(file1.Content.Length));
         ByteFileInfo file2 = new ByteFileInfo(file1);
         string       type  = FileHandler.GetType(file2);
         if (file2.IsLZ77Compressed)
         {
             l1.ForeColor = Color.Blue;
         }
         file2.Dispose();
         FileHandler.SetListViewItemInfo(l1, type, l);
         l.Items.Add(l1);
     }
 }
예제 #9
0
        public static async void BeginFlow(MainWindow window)
        {
            // PRE LIBRARY LOAD
            RegistryHandler.RegistrySettingsPath       = @"HKEY_CURRENT_USER\Software\MassEffect2Randomizer";
            RegistryHandler.CurrentUserRegistrySubpath = @"Software\MassEffect2Randomizer";
            LegendaryExplorerCoreLib.SetSynchronizationContext(TaskScheduler.FromCurrentSynchronizationContext());

            try
            {
                // This is in a try catch because this is a critical no-crash zone that is before launch
                window.Title = $"Mass Effect 2 Randomizer {App.AppVersion}";
            }
            catch { }

            if (Utilities.GetExecutablePath().StartsWith(Path.GetTempPath(), StringComparison.InvariantCultureIgnoreCase))
            {
                // Running from temp! This is not allowed
                await window.ShowMessageAsync("Cannot run from temp directory", $"Mass Effect 2 Randomizer cannot be run from the system's Temp directory. If this executable was run from within an archive, it needs to be extracted first.");

                Environment.Exit(1);
            }

            var pd = await window.ShowProgressAsync("Starting up", $"Mass Effect 2 Randomizer is starting up. Please wait.");

            pd.SetIndeterminate();
            NamedBackgroundWorker bw = new NamedBackgroundWorker("StartupThread");

            bw.DoWork += (a, b) =>
            {
                ALOTInstallerCoreLib.Startup(SetWrapperLogger, RunOnUIThread, startTelemetry, stopTelemetry, $"Mass Effect 2 Randomizer {App.AppVersion} starting up", false);
                // Logger is now available

                // Setup telemetry handlers
                CoreAnalytics.TrackEvent = TelemetryController.TrackEvent;
                CoreCrashes.TrackError   = TelemetryController.TrackError;
                CoreCrashes.TrackError2  = TelemetryController.TrackError2;
                CoreCrashes.TrackError3  = TelemetryController.TrackError3;

                // Setup the InteropPackage for the update check
                #region Update interop
                CancellationTokenSource ct = new CancellationTokenSource();

                AppUpdateInteropPackage interopPackage = new AppUpdateInteropPackage()
                {
                    GithubOwner              = "Mgamerz",
                    GithubReponame           = "MassEffect2Randomizer",
                    UpdateAssetPrefix        = "ME2Randomizer",
                    UpdateFilenameInArchive  = "ME2Randomizer.exe",
                    ShowUpdatePromptCallback = (title, text, updateButtonText, declineButtonText) =>
                    {
                        bool   response = false;
                        object syncObj  = new object();
                        Application.Current.Dispatcher.Invoke(async() =>
                        {
                            if (Application.Current.MainWindow is MainWindow mw)
                            {
                                var result = await mw.ShowMessageAsync(title, text, MessageDialogStyle.AffirmativeAndNegative, new MetroDialogSettings()
                                {
                                    AffirmativeButtonText = updateButtonText,
                                    NegativeButtonText    = declineButtonText,
                                    DefaultButtonFocus    = MessageDialogResult.Affirmative
                                },
                                                                       75);
                                response = result == MessageDialogResult.Affirmative;
                                lock (syncObj)
                                {
                                    Monitor.Pulse(syncObj);
                                }
                            }
                        });
                        lock (syncObj)
                        {
                            Monitor.Wait(syncObj);
                        }
                        return(response);
                    },
                    ShowUpdateProgressDialogCallback = (title, initialmessage, canCancel) =>
                    {
                        // We don't use this as we are already in a progress dialog
                        pd.SetCancelable(canCancel);
                        pd.SetMessage(initialmessage);
                        pd.SetTitle(title);
                    },
                    SetUpdateDialogTextCallback = s =>
                    {
                        pd.SetMessage(s);
                    },
                    ProgressCallback = (done, total) =>
                    {
                        pd.SetProgress(done * 1d / total);
                        pd.SetMessage($"Downloading update {FileSize.FormatSize(done)} / {FileSize.FormatSize(total)}");
                    },
                    ProgressIndeterminateCallback = () =>
                    {
                        pd.SetIndeterminate();
                    },
                    ShowMessageCallback = (title, message) =>
                    {
                        object syncObj = new object();
                        Application.Current.Dispatcher.Invoke(async() =>
                        {
                            if (Application.Current.MainWindow is MainWindow mw)
                            {
                                await mw.ShowMessageAsync(title, message);
                                lock (syncObj)
                                {
                                    Monitor.Pulse(syncObj);
                                }
                            }
                        });
                        lock (syncObj)
                        {
                            Monitor.Wait(syncObj);
                        }
                    },
                    NotifyBetaAvailable = () =>
                    {
                        App.BetaAvailable = true;
                    },
                    DownloadCompleted = () =>
                    {
                        pd.SetCancelable(false);
                    },
                    cancellationTokenSource    = ct,
                    ApplicationName            = "Mass Effect 2 Randomizer",
                    RequestHeader              = "ME2Randomizer",
                    ForcedUpgradeMaxReleaseAge = 3
                };

                #endregion



                pd.SetMessage("Checking for application updates");
                pd.Canceled += (sender, args) =>
                {
                    ct.Cancel();
                };
                AppUpdater.PerformGithubAppUpdateCheck(interopPackage);

                // If user aborts download
                pd.SetCancelable(false);
                pd.SetIndeterminate();
                pd.SetTitle("Starting up");

                void setStatus(string message)
                {
                    pd.SetIndeterminate();
                    pd.SetMessage(message);
                }

                GameTarget target = null;
                try
                {
                    pd.SetMessage("Loading Mass Effect 2 Randomizer framework");
                    ToolTipService.ShowOnDisabledProperty.OverrideMetadata(typeof(Control), new FrameworkPropertyMetadata(true));
                    ToolTipService.ShowDurationProperty.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(int.MaxValue));

                    ALOTInstallerCoreLib.PostCriticalStartup(x => pd.SetMessage(x), RunOnUIThread, false);

#if __LE2__
                    LE2Directory.ReloadDefaultGamePath(true);
                    if (LE2Directory.DefaultGamePath != null)
                    {
                        GameTarget gt = new GameTarget(MEGame.LE2, LE2Directory.DefaultGamePath, true);
                        if (gt.ValidateTarget() == null)
                        {
                            Locations.SetTarget(gt, false);
                        }
                    }
#endif
                    MEPackageHandler.GlobalSharedCacheEnabled = false; // ME2R does not use the global shared cache.

                    handleM3Passthrough();
                    target = Locations.GetTarget(MERFileSystem.Game);
                    if (target == null)
                    {
                        var gamePath = MEDirectories.GetDefaultGamePath(MERFileSystem.Game);
                        if (Directory.Exists(gamePath))
                        {
                            target = new GameTarget(MERFileSystem.Game, gamePath, true);
                            var validationFailedReason = target.ValidateTarget();
                            if (validationFailedReason == null)
                            {
                                // CHECK NOT TEXTURE MODIFIED
                                if (target.TextureModded)
                                {
                                    MERLog.Error($@"Game target is texture modded: {target.TargetPath}. This game target is not targetable by ME2R");
                                    object o = new object();
                                    Application.Current.Dispatcher.Invoke(async() =>
                                    {
                                        if (Application.Current.MainWindow is MainWindow mw)
                                        {
                                            await mw.ShowMessageAsync("Mass Effect 2 target is texture modded", $"The game located at {target.TargetPath} has had textures modified. Mass Effect 2 Randomizer cannot randomize texture modified games, as it adds package files. If you want to texture mod your game, it must be done after randomization.", ContentWidthPercent: 75);
                                            lock (o)
                                            {
                                                Monitor.Pulse(o);
                                            }
                                        }
                                    });
                                    lock (o)
                                    {
                                        Monitor.Wait(o);
                                    }
                                }

                                // We still set target so we can restore game if necessary
                                Locations.SetTarget(target, false);
                            }
                        }
                    }


                    pd.SetMessage("Performing startup checks");
                    MERStartupCheck.PerformStartupCheck((title, message) =>
                    {
                        object o = new object();
                        Application.Current.Dispatcher.Invoke(async() =>
                        {
                            if (Application.Current.MainWindow is MainWindow mw)
                            {
                                await mw.ShowMessageAsync(title, message, ContentWidthPercent: 75);
                                lock (o)
                                {
                                    Monitor.Pulse(o);
                                }
                            }
                        });
                        lock (o)
                        {
                            Monitor.Wait(o);
                        }
                    }, x => pd.SetMessage(x));

                    // force initial refresh
                    MERPeriodicRefresh(null, null);
                }
                catch (Exception e)
                {
                    MERLog.Exception(e, @"There was an error starting up the framework!");
                }

                pd.SetMessage("Preparing interface");
                Thread.Sleep(250); // This will allow this message to show up for moment so user can see it.

                Application.Current.Dispatcher.Invoke(async() =>
                {
                    if (Application.Current.MainWindow is MainWindow mw)
                    {
                        mw.SetupTargetDescriptionText();


                        var backupStatus                = BackupService.GetBackupStatus(MERFileSystem.Game);
                        mw.BackupRestoreText            = backupStatus?.BackupActionText;
                        mw.BackupRestore_Button.ToolTip = backupStatus != null && backupStatus.BackedUp ? "Click to restore game/uninstall randomizer mod" : "Click to backup game";

                        mw.FinalizeInterfaceLoad();

                        /*
                         * if (!hasWorkingMEM)
                         * {
                         *  await mw.ShowMessageAsync("Required components are not available",
                         *      "Some components for installation are not available, likely due to network issues (blocking, no internet, etc). To install these components, folow the 'How to install the Installer Support Package' directions on any of the ALOT pages on NexusMods. The installer will not work without these files installed.",
                         *      ContentWidthPercent: 75);
                         * }*/

                        PeriodicRefresh.OnPeriodicRefresh += MERPeriodicRefresh;
                    }
                });
            };
            bw.RunWorkerCompleted += async(a, b) =>
            {
                // Post critical startup
                Random random  = new Random();
                var    preseed = random.Next();
                window.ImageCredits.ReplaceAll(ImageCredit.LoadImageCredits("imagecredits.txt", false));
                window.ContributorCredits.ReplaceAll(window.GetContributorCredits());
                window.LibraryCredits.ReplaceAll(LibraryCredit.LoadLibraryCredits("librarycredits.txt"));
#if DEBUG
                window.SeedTextBox.Text = 529572808.ToString();
#else
                window.SeedTextBox.Text = preseed.ToString();
#endif
                window.TextBlock_AssemblyVersion.Text = $"Version {App.AppVersion}";
                window.SelectedRandomizeMode          = MainWindow.RandomizationMode.ERandomizationMode_SelectAny;


                var hasFirstRun = RegistryHandler.GetRegistrySettingBool(MainWindow.SETTING_FIRSTRUN);
                if (hasFirstRun == null || !hasFirstRun.Value)
                {
                    window.FirstRunFlyoutOpen = true;
                }
                await pd.CloseAsync();
            };
            bw.RunWorkerAsync();
        }
        private void ImportSelectedFolder()
        {
            //Check destination path
            var destinationName = Utilities.SanitizePath(ModNameText);

            if (string.IsNullOrWhiteSpace(destinationName))
            {
                //cannot use this name
                Log.Error(@"Invalid mod name: " + ModNameText);
                M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_dialog_invalidModNameWillResolveToNothing), M3L.GetString(M3L.string_invalidModName), MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }

            //Check free space.
            var sourceDir = Path.Combine(M3Directories.GetDLCPath(SelectedTarget), SelectedDLCFolder.DLCFolderName);
            var library   = Utilities.GetModDirectoryForGame(SelectedTarget.Game);

            if (Utilities.DriveFreeBytes(library, out var freeBytes))
            {
                //Check enough space
                var sourceSize = Utilities.GetSizeOfDirectory(sourceDir);
                if (sourceSize > (long)freeBytes)
                {
                    //Not enough space
                    Log.Error($@"Not enough disk space to import mod. Required space: {FileSize.FormatSize(sourceSize)}, available space: {FileSize.FormatSize(freeBytes)}");
                    M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_insufficientDiskSpaceToImport, Path.GetPathRoot(library), FileSize.FormatSize(sourceSize), FileSize.FormatSize(freeBytes)), M3L.GetString(M3L.string_insufficientFreeDiskSpace), MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }

            //Check directory doesn't exist already
            var outDir = Path.Combine(library, destinationName);

            if (Directory.Exists(outDir))
            {
                var okToDelete = M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_dialog_importingWillDeleteExistingMod, outDir), M3L.GetString(M3L.string_sameNamedModInLibrary), MessageBoxButton.YesNo, MessageBoxImage.Warning);
                if (okToDelete == MessageBoxResult.No)
                {
                    return; //cancel
                }

                try
                {
                    Utilities.DeleteFilesAndFoldersRecursively(outDir);
                }
                catch (Exception e)
                {
                    M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_couldNotDeleteExistingModDirectory, e.Message), M3L.GetString(M3L.string_errorDeletingModFolder), MessageBoxButton.OK, MessageBoxImage.Error);
                    return; //abort
                }
            }

            NamedBackgroundWorker nbw = new NamedBackgroundWorker(@"GameDLCModImporter");

            nbw.DoWork             += ImportDLCFolder_BackgroundThread;
            nbw.RunWorkerCompleted += (a, b) =>
            {
                if (b.Error != null)
                {
                    Log.Error($@"Exception occurred in {nbw.Name} thread: {b.Error.Message}");
                }
                else
                {
                    if (b.Error == null && b.Result != null)
                    {
                        Analytics.TrackEvent(@"Imported a mod from game installation", new Dictionary <string, string>()
                        {
                            { @"Game", SelectedTarget.Game.ToString() },
                            { @"Folder", SelectedDLCFolder.DLCFolderName }
                        });
                    }

                    OperationInProgress = false;
                    if (b.Error == null && b.Result != null)
                    {
                        OnClosing(new DataEventArgs(b.Result)); //avoid accessing b.Result if error occurred
                    }
                }
            };
            nbw.RunWorkerAsync();
        }
        private UploadModResult UploadMod(Action <double> progressCallback = null, Action <TaskbarProgressBarState> setTaskbarProgressState = null)
        {
            #region online fetch

            //Fetch current production manifest for mod (it may not exist)
            setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Indeterminate);
            using var wc = new System.Net.WebClient();
            try
            {
                CurrentActionText = M3L.GetString(M3L.string_checkingIfUpdaterServiceIsConfiguredForMod);
                string validationUrl = $@"{UpdaterServiceCodeValidationEndpoint}?updatecode={mod.ModClassicUpdateCode}&updatexmlname={mod.UpdaterServiceServerFolderShortname}.xml";
                string isBeingServed = wc.DownloadStringAwareOfEncoding(validationUrl);
                if (string.IsNullOrWhiteSpace(isBeingServed) || isBeingServed != @"true") //we don't parse for bool because it might have a different text that is not specifically true or false. It might
                                                                                          // have an error for example
                {
                    //Not being served
                    Log.Error(@"This mod is not configured for serving on the Updater Service. Please contact Mgamerz.");
                    CurrentActionText = M3L.GetString(M3L.string_serverNotConfiguredForModContactMgamerz);
                    HideChangelogArea();
                    return(UploadModResult.NOT_BEING_SERVED);
                }
            }
            catch (Exception ex)
            {
                Log.Error(@"Error validating mod is configured on updater service: " + ex.Message);
                CurrentActionText = M3L.GetString(M3L.string_interp_errorCheckingUpdaterServiceConfiguration, ex.Message);
                HideChangelogArea();
                return(UploadModResult.ERROR_VALIDATING_MOD_IS_CONFIGURED);
            }

            #endregion

            #region get current production version to see if we should prompt user

            var latestVersionOnServer = OnlineContent.GetLatestVersionOfModOnUpdaterService(mod.ModClassicUpdateCode);
            if (latestVersionOnServer != null)
            {
                if (latestVersionOnServer >= mod.ParsedModVersion)
                {
                    bool cancel = false;
                    setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Paused);
                    Application.Current.Dispatcher.Invoke(delegate
                    {
                        // server is newer or same as version we are pushing
                        var response = M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_dialog_serverVersionSameOrNewerThanLocal, mod.ParsedModVersion, latestVersionOnServer), M3L.GetString(M3L.string_serverVersionSameOrNewerThanLocal), MessageBoxButton.YesNo, MessageBoxImage.Warning);
                        if (response == MessageBoxResult.No)
                        {
                            CurrentActionText = M3L.GetString(M3L.string_uploadAbortedModOnServerIsSameOrNewerThanLocalOneBeingUploaded);
                            HideChangelogArea();
                            cancel = true;
                            return;
                        }
                    });
                    setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Indeterminate);
                    if (cancel)
                    {
                        return(UploadModResult.ABORTED_BY_USER_SAME_VERSION_UPLOADED);
                    }
                }
            }

            #endregion

            #region mod variables

            //get refs
            var files = mod.GetAllRelativeReferences(true);
            files = files.OrderByDescending(x => new FileInfo(Path.Combine(mod.ModPath, x)).Length).ToList();
            long totalModSizeUncompressed = files.Sum(x => new FileInfo(Path.Combine(mod.ModPath, x)).Length);

            #endregion

            #region Check package files are not natively compressed
            {
                // In brackets to scope
                Log.Information(@"UpdaterServiceUpload: Checking for compressed packages");
                double numDone            = 0;
                int    totalFiles         = files.Count;
                var    compressedPackages = new List <string>();
                foreach (var f in files)
                {
                    CurrentActionText = M3L.GetString(M3L.string_interp_checkingPackagesBeforeUploadX, (numDone * 100 / totalFiles));
                    numDone++;
                    if (f.RepresentsPackageFilePath())
                    {
                        //var p = MEPackageHandler.OpenMEPackage(Path.Combine(mod.ModPath, f));
                        //p.Save(compress: true);
                        var quickP = MEPackageHandler.QuickOpenMEPackage(Path.Combine(mod.ModPath, f));
                        if (quickP.IsCompressed)
                        {
                            if (quickP.NumCompressedChunksAtLoad > 0)
                            {
                                Log.Error($@"Found compressed package: {quickP.FilePath}");
                            }
                            else
                            {
                                Log.Error($@"Found package with IsCompressed flag but no compressed chunks: {quickP.FilePath}");
                            }

                            compressedPackages.Add(f);
                        }
                    }
                }

                if (compressedPackages.Any())
                {
                    CurrentActionText = M3L.GetString(M3L.string_uploadAborted_foundCompressedPackage);
                    // Abort
                    Application.Current.Dispatcher.InvokeAsync(() =>
                    {
                        M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_dialog_uploadAborted_foundCompressedPackage, string.Join('\n', compressedPackages.OrderBy(x => x))),
                                       M3L.GetString(M3L.string_cannotUploadMod), MessageBoxButton.OK, MessageBoxImage.Error);
                    });
                    CancelOperations = true;
                    return(UploadModResult.CANT_UPLOAD_NATIVE_COMPRESSED_PACKAGES);
                }
            }

            #endregion

            #region compress and stage mod

            void updateCurrentTextCallback(string newText)
            {
                CurrentActionText = newText;
            }

            bool?canceledCheckCallback() => CancelOperations;

            CurrentActionText = M3L.GetString(M3L.string_compressingModForUpdaterService);
            progressCallback?.Invoke(0);
            setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Normal);
            var lzmaStagingPath = OnlineContent.StageModForUploadToUpdaterService(mod, files, totalModSizeUncompressed, canceledCheckCallback, updateCurrentTextCallback, progressCallback);

            #endregion

            if (CancelOperations)
            {
                AbortUpload();
                return(UploadModResult.ABORTED_BY_USER);
            }

            setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Indeterminate);

            #region hash mod and build server manifest

            CurrentActionText = M3L.GetString(M3L.string_buildingServerManifest);

            long amountHashed = 0;
            ConcurrentDictionary <string, SourceFile> manifestFiles = new ConcurrentDictionary <string, SourceFile>();
            Parallel.ForEach(files, new ParallelOptions()
            {
                MaxDegreeOfParallelism = Math.Max(1, Environment.ProcessorCount - 1)
            }, x =>
            {
                if (CancelOperations)
                {
                    return;
                }
                SourceFile sf       = new SourceFile();
                var sFile           = Path.Combine(mod.ModPath, x);
                var lFile           = Path.Combine(lzmaStagingPath, x + @".lzma");
                sf.hash             = Utilities.CalculateMD5(sFile);
                sf.lzmahash         = Utilities.CalculateMD5(lFile);
                var fileInfo        = new FileInfo(sFile);
                sf.size             = fileInfo.Length;
                sf.timestamp        = fileInfo.LastWriteTimeUtc.Ticks;
                sf.relativefilepath = x;
                sf.lzmasize         = new FileInfo(lFile).Length;
                manifestFiles.TryAdd(x, sf);
                var done          = Interlocked.Add(ref amountHashed, sf.size);
                CurrentActionText = M3L.GetString(M3L.string_buildingServerManifest) + $@" {Math.Round(done * 100.0 / totalModSizeUncompressed)}%";
            });
            if (CancelOperations)
            {
                AbortUpload();
                return(UploadModResult.ABORTED_BY_USER);
            }

            //Build document
            XmlDocument xmlDoc   = new XmlDocument();
            XmlNode     rootNode = xmlDoc.CreateElement(@"mod");
            xmlDoc.AppendChild(rootNode);

            foreach (var mf in manifestFiles)
            {
                if (CancelOperations)
                {
                    AbortUpload();
                    return(UploadModResult.ABORTED_BY_USER);
                }

                XmlNode sourceNode = xmlDoc.CreateElement(@"sourcefile");

                var size = xmlDoc.CreateAttribute(@"size");
                size.InnerText = mf.Value.size.ToString();

                var hash = xmlDoc.CreateAttribute(@"hash");
                hash.InnerText = mf.Value.hash;

                var lzmasize = xmlDoc.CreateAttribute(@"lzmasize");
                lzmasize.InnerText = mf.Value.lzmasize.ToString();

                var lzmahash = xmlDoc.CreateAttribute(@"lzmahash");
                lzmahash.InnerText = mf.Value.lzmahash;

                var timestamp = xmlDoc.CreateAttribute(@"timestamp");
                timestamp.InnerText = mf.Value.timestamp.ToString();

                sourceNode.InnerText = mf.Key;
                sourceNode.Attributes.Append(size);
                sourceNode.Attributes.Append(hash);
                sourceNode.Attributes.Append(lzmasize);
                sourceNode.Attributes.Append(lzmahash);
                sourceNode.Attributes.Append(timestamp);

                rootNode.AppendChild(sourceNode);
            }

            if (CancelOperations)
            {
                AbortUpload();
                return(UploadModResult.ABORTED_BY_USER);
            }

            foreach (var bf in mod.UpdaterServiceBlacklistedFiles)
            {
                if (CancelOperations)
                {
                    AbortUpload();
                    return(UploadModResult.ABORTED_BY_USER);
                }

                var bfn = xmlDoc.CreateElement(@"blacklistedfile");
                bfn.InnerText = bf;
                rootNode.AppendChild(bfn);
            }

            if (CancelOperations)
            {
                AbortUpload();
                return(UploadModResult.ABORTED_BY_USER);
            }

            var updatecode = xmlDoc.CreateAttribute(@"updatecode");
            updatecode.InnerText = mod.ModClassicUpdateCode.ToString();
            rootNode.Attributes.Append(updatecode);

            var version = xmlDoc.CreateAttribute(@"version");
            version.InnerText = mod.ParsedModVersion.ToString();
            rootNode.Attributes.Append(version);

            var serverfolder = xmlDoc.CreateAttribute(@"folder");
            serverfolder.InnerText = mod.UpdaterServiceServerFolder;
            rootNode.Attributes.Append(serverfolder);


            setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Indeterminate);
            #endregion

            //wait to ensure changelog is set.

            while (ChangelogNotYetSet)
            {
                setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Paused);
                if (CancelOperations)
                {
                    AbortUpload();
                    return(UploadModResult.ABORTED_BY_USER);
                }

                CurrentActionText = M3L.GetString(M3L.string_waitingForChangelogToBeSet);
                Thread.Sleep(250); //wait for changelog to be set.
            }

            setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Indeterminate);

            #region Finish building manifest

            var changelog = xmlDoc.CreateAttribute(@"changelog");
            changelog.InnerText = ChangelogText;
            rootNode.Attributes.Append(changelog);

            using var stringWriter = new StringWriterWithEncoding(Encoding.UTF8);
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent         = true;
            settings.IndentChars    = @" ";
            settings.Encoding       = Encoding.UTF8;
            using var xmlTextWriter = XmlWriter.Create(stringWriter, settings);
            xmlDoc.WriteTo(xmlTextWriter);
            xmlTextWriter.Flush();


            #endregion

            var finalManifestText = stringWriter.GetStringBuilder().ToString();

            #region Connect to ME3Tweaks

            CurrentActionText = M3L.GetString(M3L.string_connectingToME3TweaksUpdaterService);
            Log.Information(@"Connecting to ME3Tweaks as " + Username);
            string host     = @"ftp.me3tweaks.com";
            string username = Username;
            string password = Settings.DecryptUpdaterServicePassword();

            using SftpClient sftp = new SftpClient(host, username, password);
            sftp.Connect();

            Log.Information(@"Connected to ME3Tweaks over SSH (SFTP)");

            CurrentActionText = M3L.GetString(M3L.string_connectedToME3TweaksUpdaterService);
            var serverFolderName = mod.UpdaterServiceServerFolderShortname;

            //sftp.ChangeDirectory(LZMAStoragePath);

            //Log.Information(@"Listing files/folders for " + LZMAStoragePath);
            //var lzmaStorageDirectoryItems = sftp.ListDirectory(LZMAStoragePath);
            var  serverModPath  = LZMAStoragePath + @"/" + serverFolderName;
            bool justMadeFolder = false;
            if (!sftp.Exists(serverModPath))
            {
                CurrentActionText = M3L.GetString(M3L.string_creatingServerFolderForMod);
                Log.Information(@"Creating server folder for mod: " + serverModPath);
                sftp.CreateDirectory(serverModPath);
                justMadeFolder = true;
            }

            var dirContents = sftp.ListDirectory(serverModPath).ToList();
            Dictionary <string, string> serverHashes = new Dictionary <string, string>();

            //Open SSH connection as we will need to hash files out afterwards.
            Log.Information(@"Connecting to ME3Tweaks Updater Service over SSH (SSH Shell)");
            using SshClient sshClient = new SshClient(host, username, password);
            sshClient.Connect();
            Log.Information(@"Connected to ME3Tweaks Updater Service over SSH (SSH Shell)");

            if (!justMadeFolder && dirContents.Any(x => x.Name != @"." && x.Name != @".."))
            {
                CurrentActionText = M3L.GetString(M3L.string_hashingFilesOnServerForDelta);
                Log.Information(@"Hashing existing files on server to compare for delta");
                serverHashes = getServerHashes(sshClient, serverFolderName, serverModPath);
            }

            //Calculate what needs to be updated or removed from server
            List <string> filesToUploadToServer  = new List <string>();
            List <string> filesToDeleteOffServer = new List <string>();

            //Files to upload
            foreach (var sourceFile in manifestFiles)
            {
                //find matching server file
                if (serverHashes.TryGetValue(sourceFile.Key.Replace('\\', '/') + @".lzma", out var matchingHash))
                {
                    //exists on server, compare hash
                    if (matchingHash != sourceFile.Value.lzmahash)
                    {
                        //server hash is different! Upload new file.
                        Log.Information(@"Server version of file is different from local: " + sourceFile.Key);
                        filesToUploadToServer.Add(sourceFile.Key);
                    }
                    else
                    {
                        Log.Information(@"Server version of file is same as local: " + sourceFile.Key);
                    }
                }
                else
                {
                    Log.Information(@"Server does not have file: " + sourceFile.Key);
                    filesToUploadToServer.Add(sourceFile.Key);
                }
            }

            //Files to remove from server
            foreach (var serverfile in serverHashes.Keys)
            {
                if (!manifestFiles.Any(x => (x.Key + @".lzma") == serverfile.Replace('/', '\\')))
                {
                    Log.Information(@"File exists on server but not locally: " + serverfile);
                    filesToDeleteOffServer.Add(serverfile);
                }
            }

            #endregion


            long amountUploaded = 0, amountToUpload = 1;
            //Confirm changes
            if (filesToDeleteOffServer.Any() || filesToUploadToServer.Any())
            {
                var text = M3L.GetString(M3L.string_interp_updaterServiceDeltaConfirmationHeader, mod.ModName);
                if (filesToUploadToServer.Any())
                {
                    text += M3L.GetString(M3L.string_nnFilesToUploadToServern) + @" " + string.Join('\n' + @" - ", filesToUploadToServer);                              //weird stuff to deal with localizer
                }
                if (filesToDeleteOffServer.Any())
                {
                    text += M3L.GetString(M3L.string_nnFilesToDeleteOffServern) + @" " + string.Join('\n' + @" - ", filesToDeleteOffServer);                               //weird stuff to deal with localizer
                }
                text += M3L.GetString(M3L.string_interp_updaterServiceDeltaConfirmationFooter);
                bool performUpload = false;
                Log.Information(@"Prompting user to accept server delta");
                setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Paused);
                Application.Current.Dispatcher.Invoke(() => { performUpload = M3L.ShowDialog(mainwindow, text, M3L.GetString(M3L.string_confirmChanges), MessageBoxButton.OKCancel, MessageBoxImage.Exclamation) == MessageBoxResult.OK; });
                setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Indeterminate);

                if (performUpload)
                {
                    Log.Information(@"User has accepted the delta, applying delta to server");

                    #region upload files

                    //Create directories
                    SortedSet <string> directoriesToCreate = new SortedSet <string>();
                    foreach (var f in filesToUploadToServer)
                    {
                        string foldername = f;
                        var    lastIndex  = foldername.LastIndexOf(@"\");

                        while (lastIndex > 0)
                        {
                            foldername = foldername.Substring(0, lastIndex);
                            directoriesToCreate.Add(foldername.Replace('\\', '/'));
                            lastIndex = foldername.LastIndexOf(@"\");
                        }
                    }

                    #endregion

                    //UploadDirectory(sftp, lzmaStagingPath, serverModPath, (ucb) => Debug.WriteLine("UCB: " + ucb));
                    var dirsToCreateOnServerSorted = directoriesToCreate.ToList();
                    dirsToCreateOnServerSorted.Sort((a, b) => a.Length.CompareTo(b.Length)); //short to longest so we create top levels first!
                    int numFoldersToCreate = dirsToCreateOnServerSorted.Count();
                    int numDone            = 0;
                    if (dirsToCreateOnServerSorted.Count > 0)
                    {
                        CurrentActionText = M3L.GetString(M3L.string_creatingModDirectoriesOnServer);
                        foreach (var f in dirsToCreateOnServerSorted)
                        {
                            var serverFolderStr = serverModPath + @"/" + f;
                            if (!sftp.Exists(serverFolderStr))
                            {
                                Log.Information(@"Creating directory on server: " + serverFolderStr);
                                sftp.CreateDirectory(serverFolderStr);
                            }
                            else
                            {
                                Log.Information(@"Server folder already exists, skipping: " + serverFolderStr);
                            }

                            numDone++;
                            CurrentActionText = M3L.GetString(M3L.string_creatingModDirectoriesOnServer) + @" " + Math.Round(numDone * 100.0 / numFoldersToCreate) + @"%";
                        }
                    }

                    //Upload files
                    progressCallback?.Invoke(0);
                    setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Normal);

                    amountToUpload = filesToUploadToServer.Sum(x => new FileInfo(Path.Combine(lzmaStagingPath, x + @".lzma")).Length);
                    foreach (var file in filesToUploadToServer)
                    {
                        if (CancelOperations)
                        {
                            AbortUpload();
                            return(UploadModResult.ABORTED_BY_USER);
                        }

                        var fullPath       = Path.Combine(lzmaStagingPath, file + @".lzma");
                        var serverFilePath = serverModPath + @"/" + file.Replace(@"\", @"/") + @".lzma";
                        Log.Information(@"Uploading file " + fullPath + @" to " + serverFilePath);
                        long amountUploadedBeforeChunk = amountUploaded;
                        using Stream fileStream = new FileStream(fullPath, FileMode.Open);
                        try
                        {
                            sftp.UploadFile(fileStream, serverFilePath, true, (x) =>
                            {
                                if (CancelOperations)
                                {
                                    CurrentActionText = M3L.GetString(M3L.string_abortingUpload);
                                    return;
                                }

                                amountUploaded    = amountUploadedBeforeChunk + (long)x;
                                var uploadedHR    = FileSize.FormatSize(amountUploaded);
                                var totalUploadHR = FileSize.FormatSize(amountToUpload);
                                if (amountToUpload > 0)
                                {
                                    progressCallback?.Invoke(amountUploaded * 1.0 / amountToUpload);
                                }

                                CurrentActionText = M3L.GetString(M3L.string_interp_uploadingFilesToServerXY, uploadedHR, totalUploadHR);
                            });
                        }
                        catch (Exception e)
                        {
                            Log.Error($@"Error uploading file {fullPath} to server: {e.Message}");
                            CurrentActionText = M3L.GetString(M3L.string_interp_uploadFailedX, e.Message);
                            // Abort
                            Application.Current.Dispatcher.InvokeAsync(() =>
                            {
                                M3L.ShowDialog(mainwindow, M3L.GetString(M3L.string_interp_dialog_updaterServiceUploadFailed, fullPath, e.Message),
                                               M3L.GetString(M3L.string_uploadFailed), MessageBoxButton.OK, MessageBoxImage.Error);
                            });
                            return(UploadModResult.ERROR_UPLOADING_FILE);
                        }
                    }
                    setTaskbarProgressState?.Invoke(TaskbarProgressBarState.Indeterminate);

                    if (CancelOperations)
                    {
                        AbortUpload();
                        return(UploadModResult.ABORTED_BY_USER);
                    }

                    //delete extra files
                    int numdone = 0;
                    foreach (var file in filesToDeleteOffServer)
                    {
                        CurrentActionText = M3L.GetString(M3L.string_interp_deletingObsoleteFiles, numdone, filesToDeleteOffServer.Count);
                        var fullPath = $@"{LZMAStoragePath}/{serverFolderName}/{file}";
                        Log.Information(@"Deleting unused file off server: " + fullPath);
                        sftp.DeleteFile(fullPath);
                        numdone++;
                    }

                    //Upload manifest
                    using var manifestStream = finalManifestText.ToStream();
                    var serverManifestPath = $@"{ManifestStoragePath}/{serverFolderName}.xml";
                    Log.Information(@"Uploading manifest to server: " + serverManifestPath);
                    sftp.UploadFile(manifestStream, serverManifestPath, true, (x) =>
                    {
                        var uploadedAmountHR    = FileSize.FormatSize(amountUploaded);
                        var uploadAmountTotalHR = FileSize.FormatSize(amountToUpload);
                        CurrentActionText       = M3L.GetString(M3L.string_uploadingUpdateManifestToServer) + $@"{uploadedAmountHR}/{uploadAmountTotalHR}";
                    });
                }
                else
                {
                    Log.Warning(@"User has declined uploading the delta. We will not change anything on the server.");
                    CancelOperations = true;
                    AbortUpload();
                    return(UploadModResult.ABORTED_BY_USER);
                }

                CurrentActionText = M3L.GetString(M3L.string_validatingModOnServer);
                Log.Information(@"Verifying hashes on server for new files");
                var newServerhashes = getServerHashes(sshClient, serverFolderName, serverModPath);
                var badHashes       = verifyHashes(manifestFiles, newServerhashes);
                if (badHashes.Any())
                {
                    CurrentActionText = M3L.GetString(M3L.string_someHashesOnServerAreIncorrectContactMgamerz);
                    return(UploadModResult.BAD_SERVER_HASHES_AFTER_VALIDATION);
                }
                else
                {
                    CurrentActionText = M3L.GetString(M3L.string_modUploadedToUpdaterService);
                    return(UploadModResult.UPLOAD_OK);
                }
            }
            else
            {
                //Upload manifest
                Log.Information(@"Hashes on server match local already");
                // This ensures version on server is same as expected.
                using var manifestStream = finalManifestText.ToStream();
                var serverManifestPath = $@"{ManifestStoragePath}/{serverFolderName}.xml";
                Log.Information(@"Uploading manifest to server: " + serverManifestPath);
                sftp.UploadFile(manifestStream, serverManifestPath, true, (x) =>
                {
                    var uploadedAmountHR    = FileSize.FormatSize(amountUploaded);
                    var uploadAmountTotalHR = FileSize.FormatSize(amountToUpload);
                    CurrentActionText       = M3L.GetString(M3L.string_uploadingUpdateManifestToServer) + $@"{uploadedAmountHR}/{uploadAmountTotalHR}";
                });
                CurrentActionText = M3L.GetString(M3L.string_manifestUpdatedOnServer);
                return(UploadModResult.UPLOAD_OK);
            }

            //return UploadModResult.ABORTED_BY_USER;
        }
            private bool validateBackupPath(string backupPath, GameTarget targetToBackup)
            {
                //Check empty
                if (!targetToBackup.IsCustomOption && Directory.Exists(backupPath))
                {
                    if (Directory.GetFiles(backupPath).Length > 0 ||
                        Directory.GetDirectories(backupPath).Length > 0)
                    {
                        //Directory not empty
                        Log.Error(@"Selected backup directory is not empty.");
                        M3L.ShowDialog(window,
                                       M3L.GetString(M3L.string_directoryIsNotEmptyMustBeEmpty),
                                       M3L.GetString(M3L.string_directoryNotEmpty), MessageBoxButton.OK,
                                       MessageBoxImage.Error);
                        return(false);
                    }
                }

                //Check is Documents folder
                var docsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"BioWare", Utilities.GetGameName(Game));

                if (backupPath.Equals(docsPath, StringComparison.InvariantCultureIgnoreCase) || backupPath.IsSubPathOf(docsPath))
                {
                    Log.Error(@"User chose path in or around the documents path for the game - not allowed as game can load files from here.");
                    M3L.ShowDialog(window, M3L.GetString(M3L.string_interp_dialog_linkFailedSubdirectoryOfGameDocumentsFolder, Utilities.GetGameName(Game)),
                                   M3L.GetString(M3L.string_locationNotAllowedForBackup), MessageBoxButton.OK,
                                   MessageBoxImage.Error);
                    return(false);
                }

                //Check space
                if (!targetToBackup.IsCustomOption)
                {
                    Utilities.GetDiskFreeSpaceEx(backupPath, out var freeBytes, out var totalBytes,
                                                 out var totalFreeBytes);
                    var requiredSpace = (ulong)(Utilities.GetSizeOfDirectory(targetToBackup.TargetPath) * 1.1);  //10% buffer
                    Log.Information(
                        $@"Backup space check. Backup size: {FileSize.FormatSize(requiredSpace)}, free space: {FileSize.FormatSize(freeBytes)}");
                    if (freeBytes < requiredSpace)
                    {
                        //Not enough space.
                        Log.Error(
                            $@"Not enough disk space to create backup at {backupPath}. Required space: {FileSize.FormatSize(requiredSpace)} Free space: {FileSize.FormatSize(freeBytes)}");
                        M3L.ShowDialog(window,
                                       M3L.GetString(M3L.string_dialogInsufficientDiskSpace,
                                                     Path.GetPathRoot(backupPath), FileSize.FormatSize(freeBytes).ToString(),
                                                     FileSize.FormatSize(requiredSpace).ToString()),
                                       M3L.GetString(M3L.string_insufficientDiskSpace), MessageBoxButton.OK,
                                       MessageBoxImage.Error);
                        return(false);
                    }

                    //Check writable
                    var writable = Utilities.IsDirectoryWritable(backupPath);
                    if (!writable)
                    {
                        //Not enough space.
                        Log.Error(
                            $@"Backup destination selected is not writable.");
                        M3L.ShowDialog(window,
                                       M3L.GetString(M3L.string_dialog_userAccountDoesntHaveWritePermissionsBackup),
                                       M3L.GetString(M3L.string_cannotCreateBackup), MessageBoxButton.OK,
                                       MessageBoxImage.Error);
                        return(false);
                    }
                }

                //Check it is not subdirectory of the game (we might want to check its not subdir of a target)
                foreach (var target in AvailableBackupSources)
                {
                    if (backupPath.IsSubPathOf(target.TargetPath))
                    {
                        //Not enough space.
                        Log.Error(
                            $@"A backup cannot be created in a subdirectory of a game. {backupPath} is a subdir of {targetToBackup.TargetPath}");
                        M3L.ShowDialog(window,
                                       M3L.GetString(M3L.string_dialogBackupCannotBeSubdirectoryOfGame,
                                                     backupPath, target.TargetPath),
                                       M3L.GetString(M3L.string_cannotCreateBackup), MessageBoxButton.OK,
                                       MessageBoxImage.Error);
                        return(false);
                    }
                }

                return(true);
            }
예제 #13
0
        public void Extract(string outPath)
        {
            if (!File.Exists(filePath))
            {
                throw new Exception("filename missing");
            }

            UnpackCanceled       = false;
            IndeterminateState   = true;
            CurrentOverallStatus = $"Extracting {GetPrettyDLCNameFromPath(filePath)}";
            CurrentStatus        = $"Loading {GetPrettyDLCNameFromPath(filePath)} into memory ({FileSize.FormatSize(new FileInfo(filePath).Length)})";
            byte[] buffer = File.ReadAllBytes(filePath);
            CurrentFilesProcessed = 0;
            IndeterminateState    = false;

            using (MemoryStream stream = new MemoryStream(buffer))
            {
                for (int i = 0; i < TotalFilesInDLC; i++, CurrentFilesProcessed++)
                {
                    if (UnpackCanceled)
                    {
                        break;
                    }

                    if (filenamesIndex == i)
                    {
                        continue;
                    }
                    if (filesList[i].filenamePath == null)
                    {
                        throw new Exception("filename missing");
                    }

                    CurrentStatus   = "File " + (i + 1) + " of " + filesList.Count + " - " + Path.GetFileName(filesList[i].filenamePath);
                    CurrentProgress = (int)(100.0 * CurrentFilesProcessed) / (int)TotalFilesInDLC;

                    int    pos      = filesList[i].filenamePath.IndexOf("\\BIOGame\\DLC\\", StringComparison.OrdinalIgnoreCase);
                    string filename = filesList[i].filenamePath.Substring(pos + ("\\BIOGame\\DLC\\").Length);
                    string dir      = Path.GetDirectoryName(outPath);
                    Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(dir, filename)));
                    using (FileStream outputFile = new FileStream(Path.Combine(dir, filename), FileMode.Create, FileAccess.Write))
                    {
                        ExtractEntry(filesList[i], stream, outputFile);
                    }
                }
            }

            if (UnpackCanceled)
            {
                CurrentStatus      = "Canceling, cleaning up...";
                IndeterminateState = true;
                string dir = Path.GetDirectoryName(outPath);
                for (int i = 0; i < TotalFilesInDLC; i++)
                {
                    if (filenamesIndex == i)
                    {
                        continue;
                    }
                    int    pos      = filesList[i].filenamePath.IndexOf("\\BIOGame\\DLC\\", StringComparison.OrdinalIgnoreCase);
                    string filename = filesList[i].filenamePath.Substring(pos + ("\\BIOGame\\DLC\\").Length);
                    if (File.Exists(Path.Combine(dir, filename)))
                    {
                        File.Delete(Path.Combine(dir, filename));
                    }
                }
                IndeterminateState = false;
                return;
            }

            File.Delete(filePath);
            using (FileStream outputFile = new FileStream(filePath, FileMode.Create, FileAccess.Write))
            {
                outputFile.WriteUInt32(SfarTag);
                outputFile.WriteUInt32(SfarVersion);
                outputFile.WriteUInt32(HeaderSize);
                outputFile.WriteUInt32(HeaderSize);
                outputFile.WriteUInt32(0);
                outputFile.WriteUInt32(HeaderSize);
                outputFile.WriteUInt32((uint)MaxBlockSize);
                outputFile.WriteUInt32(LZMATag);
            }
        }