Example #1
0
        public InstallerVM(MainWindowVM mainWindowVM)
        {
            if (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location.ToLower()) == KnownFolders.Downloads.Path.ToLower())
            {
                MessageBox.Show(
                    "Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " +
                    "conflict with the operations Wabbajack needs to perform. Please move this executable outside of your Downloads folder and then restart the app.",
                    "Cannot run inside Downloads",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
                Environment.Exit(1);
            }

            this.MWVM = mainWindowVM;

            this._ModList = this.WhenAny(x => x.ModListPath)
                            .ObserveOn(RxApp.TaskpoolScheduler)
                            .Select(source =>
            {
                if (source == null)
                {
                    return(default(ModListVM));
                }
                var modList = Installer.LoadFromFile(source);
                if (modList == null)
                {
                    MessageBox.Show("Invalid Modlist, or file not found.", "Invalid Modlist", MessageBoxButton.OK,
                                    MessageBoxImage.Error);
                    Application.Current.Dispatcher.Invoke(() =>
                    {
                        this.MWVM.MainWindow.ExitWhenClosing = false;
                        var window = new ModeSelectionWindow
                        {
                            ShowActivated = true
                        };
                        window.Show();
                        this.MWVM.MainWindow.Close();
                    });
                    return(default(ModListVM));
                }
                return(new ModListVM(modList, source));
            })
                            .ObserveOnGuiThread()
                            .StartWith(default(ModListVM))
                            .ToProperty(this, nameof(this.ModList));
            this._HTMLReport = this.WhenAny(x => x.ModList)
                               .Select(modList => modList?.ReportHTML)
                               .ToProperty(this, nameof(this.HTMLReport));
            this._ProgressPercent = Observable.CombineLatest(
                this.WhenAny(x => x.Installing),
                this.WhenAny(x => x.InstallingMode),
                resultSelector: (installing, mode) => !installing && mode)
                                    .Select(show => show ? 1f : 0f)
                                    // Disable for now, until more reliable
                                    //this.WhenAny(x => x.MWVM.QueueProgress)
                                    //    .Select(i => i / 100f)
                                    .ToProperty(this, nameof(this.ProgressPercent));

            this.Slideshow = new SlideShow(this);

            // Set display items to modlist if configuring or complete,
            // or to the current slideshow data if installing
            this._Image = Observable.CombineLatest(
                this.WhenAny(x => x.ModList)
                .SelectMany(x => x?.ImageObservable ?? Observable.Empty <BitmapImage>())
                .NotNull()
                .StartWith(WabbajackLogo),
                this.WhenAny(x => x.Slideshow.Image)
                .StartWith(default(BitmapImage)),
                this.WhenAny(x => x.Installing),
                resultSelector: (modList, slideshow, installing) => installing ? slideshow : modList)
                          .ToProperty(this, nameof(this.Image));
            this._TitleText = Observable.CombineLatest(
                this.WhenAny(x => x.ModList.Name),
                this.WhenAny(x => x.Slideshow.TargetMod.ModName)
                .StartWith(default(string)),
                this.WhenAny(x => x.Installing),
                resultSelector: (modList, mod, installing) => installing ? mod : modList)
                              .ToProperty(this, nameof(this.TitleText));
            this._AuthorText = Observable.CombineLatest(
                this.WhenAny(x => x.ModList.Author),
                this.WhenAny(x => x.Slideshow.TargetMod.ModAuthor)
                .StartWith(default(string)),
                this.WhenAny(x => x.Installing),
                resultSelector: (modList, mod, installing) => installing ? mod : modList)
                               .ToProperty(this, nameof(this.AuthorText));
            this._Description = Observable.CombineLatest(
                this.WhenAny(x => x.ModList.Description),
                this.WhenAny(x => x.Slideshow.TargetMod.ModDescription)
                .StartWith(default(string)),
                this.WhenAny(x => x.Installing),
                resultSelector: (modList, mod, installing) => installing ? mod : modList)
                                .ToProperty(this, nameof(this.Description));

            this._LocationError = this.WhenAny(x => x.Location)
                                  .Select(x => Utils.IsDirectoryPathValid(x))
                                  .ToProperty(this, nameof(this.LocationError));

            this._DownloadLocationError = this.WhenAny(x => x.DownloadLocation)
                                          .Select(x => Utils.IsDirectoryPathValid(x))
                                          .ToProperty(this, nameof(this.DownloadLocationError));

            // Define commands
            this.ShowReportCommand = ReactiveCommand.Create(ShowReport);
            this.OpenReadmeCommand = ReactiveCommand.Create(
                execute: this.OpenReadmeWindow,
                canExecute: this.WhenAny(x => x.ModList)
                .Select(modList => !string.IsNullOrEmpty(modList?.Readme))
                .ObserveOnGuiThread());
            this.BeginCommand = ReactiveCommand.Create(
                execute: this.ExecuteBegin,
                canExecute: Observable.CombineLatest(
                    this.WhenAny(x => x.Installing),
                    this.WhenAny(x => x.LocationError),
                    this.WhenAny(x => x.DownloadLocationError),
                    resultSelector: (installing, loc, download) =>
            {
                if (installing)
                {
                    return(false);
                }
                return((loc?.Succeeded ?? false) &&
                       (download?.Succeeded ?? false));
            })
                .ObserveOnGuiThread());
            this.VisitWebsiteCommand = ReactiveCommand.Create(
                execute: () => Process.Start(this.ModList.Website),
                canExecute: this.WhenAny(x => x.ModList.Website)
                .Select(x => x?.StartsWith("https://") ?? false)
                .ObserveOnGuiThread());

            // Have Installation location updates modify the downloads location if empty
            this.WhenAny(x => x.Location)
            .Skip(1)     // Don't do it initially
            .Subscribe(installPath =>
            {
                if (string.IsNullOrWhiteSpace(this.DownloadLocation))
                {
                    this.DownloadLocation = Path.Combine(installPath, "downloads");
                }
            })
            .DisposeWith(this.CompositeDisposable);
        }
Example #2
0
        private Archive ResolveArchive(string sha, IDictionary <string, IndexedArchive> archives)
        {
            if (archives.TryGetValue(sha, out var found))
            {
                if (found.IniData == null)
                {
                    Error("No download metadata found for {0}, please use MO2 to query info or add a .meta file and try again.", found.Name);
                }
                var general = found.IniData.General;
                if (general == null)
                {
                    Error("No General section in mod metadata found for {0}, please use MO2 to query info or add the info and try again.", found.Name);
                }

                Archive result;

                if (general.directURL != null && general.directURL.StartsWith("https://drive.google.com"))
                {
                    var regex = new Regex("((?<=id=)[a-zA-Z0-9_-]*)|(?<=\\/file\\/d\\/)[a-zA-Z0-9_-]*");
                    var match = regex.Match(general.directURL);
                    result = new GoogleDriveMod()
                    {
                        Id = match.ToString()
                    };
                }
                else if (general.directURL != null && general.directURL.StartsWith(Consts.MegaPrefix))
                {
                    result = new MEGAArchive()
                    {
                        URL = general.directURL
                    };
                }
                else if (general.directURL != null && general.directURL.StartsWith("https://www.dropbox.com/"))
                {
                    var uri   = new UriBuilder((string)general.directURL);
                    var query = HttpUtility.ParseQueryString(uri.Query);

                    if (query.GetValues("dl").Count() > 0)
                    {
                        query.Remove("dl");
                    }

                    query.Set("dl", "1");

                    uri.Query = query.ToString();

                    result = new DirectURLArchive()
                    {
                        URL = uri.ToString()
                    };
                }
                else if (general.directURL != null && general.directURL.StartsWith("https://www.moddb.com/downloads/start"))
                {
                    result = new MODDBArchive()
                    {
                        URL = general.directURL
                    };
                }
                else if (general.directURL != null && general.directURL.StartsWith("http://www.mediafire.com/file/"))
                {
                    Error("Mediafire links are not currently supported");
                    return(null);

                    /*result = new MediaFireArchive()
                     * {
                     *  URL = general.directURL
                     * };*/
                }
                else if (general.directURL != null)
                {
                    var tmp = new DirectURLArchive()
                    {
                        URL = general.directURL
                    };
                    if (general.directURLHeaders != null)
                    {
                        tmp.Headers = new List <string>();
                        tmp.Headers.AddRange(general.directURLHeaders.Split('|'));
                    }
                    result = tmp;
                }
                else if (general.manualURL != null)
                {
                    result = new ManualURLArchive()
                    {
                        URL = general.manualURL.ToString()
                    };
                }
                else if (general.modID != null && general.fileID != null && general.gameName != null)
                {
                    var nm = new NexusMod()
                    {
                        GameName = general.gameName,
                        FileID   = general.fileID,
                        ModID    = general.modID,
                        Version  = general.version ?? "0.0.0.0"
                    };
                    var info = NexusAPI.GetModInfo(nm, NexusKey);
                    nm.Author          = info.author;
                    nm.UploadedBy      = info.uploaded_by;
                    nm.UploaderProfile = info.uploaded_users_profile_url;
                    result             = nm;
                }
                else
                {
                    Error("No way to handle archive {0} but it's required by the modpack", found.Name);
                    return(null);
                }

                result.Name = found.Name;
                result.Hash = found.File.Hash;
                result.Meta = found.Meta;

                Info($"Checking link for {found.Name}");

                var installer = new Installer(null, "", s => Utils.Log(s));
                installer.NexusAPIKey = NexusKey;
                if (!installer.DownloadArchive(result, false))
                {
                    Error($"Unable to resolve link for {found.Name}. If this is hosted on the nexus the file may have been removed.");
                }

                return(result);
            }
            Error("No match found for Archive sha: {0} this shouldn't happen", sha);
            return(null);
        }
Example #3
0
        public MainWindow()
        {
            var    args = Environment.GetCommandLineArgs();
            bool   DebugMode = false;
            string MO2Folder = null, InstallFolder = null, MO2Profile = null;

            if (args.Length > 1)
            {
                DebugMode     = true;
                MO2Folder     = args[1];
                MO2Profile    = args[2];
                InstallFolder = args[3];
            }

            InitializeComponent();

            var context = new AppState(Dispatcher, "Building");

            context.LogMsg($"Wabbajack Build - {ThisAssembly.Git.Sha}");
            SetupHandlers(context);
            this.DataContext = context;
            WorkQueue.Init((id, msg, progress) => context.SetProgress(id, msg, progress),
                           (max, current) => context.SetQueueSize(max, current));

            Utils.SetLoggerFn(s => context.LogMsg(s));
            Utils.SetStatusFn((msg, progress) => WorkQueue.Report(msg, progress));



            if (DebugMode)
            {
                new Thread(() =>
                {
                    var compiler = new Compiler(MO2Folder, msg => context.LogMsg(msg));

                    compiler.MO2Profile = MO2Profile;
                    context.ModListName = compiler.MO2Profile;

                    context.Mode = "Building";
                    compiler.Compile();

                    var modlist = compiler.ModList.ToJSON();
                    compiler    = null;

                    context.ConfigureForInstall(modlist);
                }).Start();
            }
            else
            {
                new Thread(() =>
                {
                    var modlist = Installer.CheckForModPack();
                    if (modlist == null)
                    {
                        Utils.Log("No Modlist found, running in Compiler mode.");
                    }
                    else
                    {
                        context.ConfigureForInstall(modlist);
                    }
                }).Start();
            }
        }
Example #4
0
        public InstallerVM(MainWindowVM mainWindowVM)
        {
            if (Path.GetDirectoryName(Assembly.GetEntryAssembly().Location.ToLower()) == KnownFolders.Downloads.Path.ToLower())
            {
                MessageBox.Show(
                    "Wabbajack is running inside your Downloads folder. This folder is often highly monitored by antivirus software and these can often " +
                    "conflict with the operations Wabbajack needs to perform. Please move this executable outside of your Downloads folder and then restart the app.",
                    "Cannot run inside Downloads",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);
                Environment.Exit(1);
            }

            MWVM = mainWindowVM;

            ModListLocation = new FilePickerVM()
            {
                ExistCheckOption = FilePickerVM.CheckOptions.On,
                PathType         = FilePickerVM.PathTypeOptions.File,
                PromptTitle      = "Select a modlist to install"
            };

            // Swap to proper sub VM based on selected type
            _installer = this.WhenAny(x => x.TargetManager)
                         // Delay so the initial VM swap comes in immediately, subVM comes right after
                         .DelayInitial(TimeSpan.FromMilliseconds(50), RxApp.MainThreadScheduler)
                         .Select <ModManager?, ISubInstallerVM>(type =>
            {
                switch (type)
                {
                case ModManager.MO2:
                    return(new MO2InstallerVM(this));

                case ModManager.Vortex:
                    return(new VortexInstallerVM(this));

                default:
                    return(null);
                }
            })
                         // Unload old VM
                         .Pairwise()
                         .Do(pair =>
            {
                pair.Previous?.Unload();
            })
                         .Select(p => p.Current)
                         .ToProperty(this, nameof(Installer));

            // Load settings
            MWVM.Settings.SaveSignal
            .Subscribe(_ =>
            {
                MWVM.Settings.Installer.LastInstalledListLocation = ModListLocation.TargetPath;
            })
            .DisposeWith(CompositeDisposable);

            _modList = this.WhenAny(x => x.ModListLocation.TargetPath)
                       .ObserveOn(RxApp.TaskpoolScheduler)
                       .Select(modListPath =>
            {
                if (modListPath == null)
                {
                    return(default(ModListVM));
                }
                if (!File.Exists(modListPath))
                {
                    return(default(ModListVM));
                }
                return(new ModListVM(modListPath));
            })
                       .ObserveOnGuiThread()
                       .StartWith(default(ModListVM))
                       .ToProperty(this, nameof(ModList));
            _htmlReport = this.WhenAny(x => x.ModList)
                          .Select(modList => modList?.ReportHTML)
                          .ToProperty(this, nameof(HTMLReport));
            _installing = this.WhenAny(x => x.Installer.ActiveInstallation)
                          .Select(i => i != null)
                          .ObserveOnGuiThread()
                          .ToProperty(this, nameof(Installing));
            _TargetManager = this.WhenAny(x => x.ModList)
                             .Select(modList => modList?.ModManager)
                             .ToProperty(this, nameof(TargetManager));

            // Add additional error check on modlist
            ModListLocation.AdditionalError = this.WhenAny(x => x.ModList)
                                              .Select <ModListVM, IErrorResponse>(modList =>
            {
                if (modList == null)
                {
                    return(ErrorResponse.Fail("Modlist path resulted in a null object."));
                }
                if (modList.Error != null)
                {
                    return(ErrorResponse.Fail("Modlist is corrupt", modList.Error));
                }
                return(ErrorResponse.Success);
            });

            BackCommand = ReactiveCommand.Create(
                execute: () =>
            {
                StartedInstallation     = false;
                Completed               = null;
                mainWindowVM.ActivePane = mainWindowVM.ModeSelectionVM;
            },
                canExecute: this.WhenAny(x => x.Installing)
                .Select(x => !x));

            _percentCompleted = this.WhenAny(x => x.Installer.ActiveInstallation)
                                .StartWith(default(AInstaller))
                                .CombineLatest(
                this.WhenAny(x => x.Completed),
                (installer, completed) =>
            {
                if (installer == null)
                {
                    return(Observable.Return <float>(completed != null ? 1f : 0f));
                }
                return(installer.PercentCompleted.StartWith(0f));
            })
                                .Switch()
                                .Debounce(TimeSpan.FromMilliseconds(25))
                                .ToProperty(this, nameof(PercentCompleted));

            Slideshow = new SlideShow(this);

            // Set display items to modlist if configuring or complete,
            // or to the current slideshow data if installing
            _image = Observable.CombineLatest(
                this.WhenAny(x => x.ModList.Error),
                this.WhenAny(x => x.ModList)
                .Select(x => x?.ImageObservable ?? Observable.Empty <BitmapImage>())
                .Switch()
                .StartWith(WabbajackLogo),
                this.WhenAny(x => x.Slideshow.Image)
                .StartWith(default(BitmapImage)),
                this.WhenAny(x => x.Installing),
                resultSelector: (err, modList, slideshow, installing) =>
            {
                if (err != null)
                {
                    return(WabbajackErrLogo);
                }
                var ret = installing ? slideshow : modList;
                return(ret ?? WabbajackLogo);
            })
                     .Select <BitmapImage, ImageSource>(x => x)
                     .ToProperty(this, nameof(Image));
            _titleText = Observable.CombineLatest(
                this.WhenAny(x => x.ModList.Name),
                this.WhenAny(x => x.Slideshow.TargetMod.ModName)
                .StartWith(default(string)),
                this.WhenAny(x => x.Installing),
                resultSelector: (modList, mod, installing) => installing ? mod : modList)
                         .ToProperty(this, nameof(TitleText));
            _authorText = Observable.CombineLatest(
                this.WhenAny(x => x.ModList.Author),
                this.WhenAny(x => x.Slideshow.TargetMod.ModAuthor)
                .StartWith(default(string)),
                this.WhenAny(x => x.Installing),
                resultSelector: (modList, mod, installing) => installing ? mod : modList)
                          .ToProperty(this, nameof(AuthorText));
            _description = Observable.CombineLatest(
                this.WhenAny(x => x.ModList.Description),
                this.WhenAny(x => x.Slideshow.TargetMod.ModDescription)
                .StartWith(default(string)),
                this.WhenAny(x => x.Installing),
                resultSelector: (modList, mod, installing) => installing ? mod : modList)
                           .ToProperty(this, nameof(Description));
            _modListName = Observable.CombineLatest(
                this.WhenAny(x => x.ModList.Error)
                .Select(x => x != null),
                this.WhenAny(x => x.ModList)
                .Select(x => x?.Name),
                resultSelector: (err, name) =>
            {
                if (err)
                {
                    return("Corrupted Modlist");
                }
                return(name);
            })
                           .ToProperty(this, nameof(ModListName));

            // Define commands
            ShowReportCommand = ReactiveCommand.Create(ShowReport);
            OpenReadmeCommand = ReactiveCommand.Create(
                execute: () => this.ModList?.OpenReadmeWindow(),
                canExecute: this.WhenAny(x => x.ModList)
                .Select(modList => !string.IsNullOrEmpty(modList?.Readme))
                .ObserveOnGuiThread());
            VisitWebsiteCommand = ReactiveCommand.Create(
                execute: () => Process.Start(ModList.Website),
                canExecute: this.WhenAny(x => x.ModList.Website)
                .Select(x => x?.StartsWith("https://") ?? false)
                .ObserveOnGuiThread());

            _progressTitle = Observable.CombineLatest(
                this.WhenAny(x => x.Installing),
                this.WhenAny(x => x.StartedInstallation),
                resultSelector: (installing, started) =>
            {
                if (!installing)
                {
                    return("Configuring");
                }
                return(started ? "Installing" : "Installed");
            })
                             .ToProperty(this, nameof(ProgressTitle));

            Dictionary <int, CPUDisplayVM> cpuDisplays = new Dictionary <int, CPUDisplayVM>();

            // Compile progress updates and populate ObservableCollection
            this.WhenAny(x => x.Installer.ActiveInstallation)
            .SelectMany(c => c?.QueueStatus ?? Observable.Empty <CPUStatus>())
            .ObserveOn(RxApp.TaskpoolScheduler)
            // Attach start times to incoming CPU items
            .Scan(
                new CPUDisplayVM(),
                (_, cpu) =>
            {
                var ret = cpuDisplays.TryCreate(cpu.ID);
                ret.AbsorbStatus(cpu);
                return(ret);
            })
            .ToObservableChangeSet(x => x.Status.ID)
            .Batch(TimeSpan.FromMilliseconds(250), RxApp.TaskpoolScheduler)
            .EnsureUniqueChanges()
            .Filter(i => i.Status.IsWorking && i.Status.ID != WorkQueue.UnassignedCpuId)
            .ObserveOn(RxApp.MainThreadScheduler)
            .Sort(SortExpressionComparer <CPUDisplayVM> .Ascending(s => s.StartTime))
            .Bind(StatusList)
            .Subscribe()
            .DisposeWith(CompositeDisposable);

            BeginCommand = ReactiveCommand.CreateFromTask(
                canExecute: this.WhenAny(x => x.Installer.CanInstall)
                .Switch(),
                execute: async() =>
            {
                try
                {
                    await this.Installer.Install();
                    Completed = ErrorResponse.Success;
                    try
                    {
                        this.ModList?.OpenReadmeWindow();
                    }
                    catch (Exception ex)
                    {
                        Utils.Error(ex);
                    }
                }
                catch (Exception ex)
                {
                    while (ex.InnerException != null)
                    {
                        ex = ex.InnerException;
                    }
                    Utils.Log(ex.StackTrace);
                    Utils.Log(ex.ToString());
                    Utils.Log($"{ex.Message} - Can't continue");
                    Completed = ErrorResponse.Fail(ex);
                }
            });

            // When sub installer begins an install, mark state variable
            BeginCommand.StartingExecution()
            .Subscribe(_ =>
            {
                StartedInstallation = true;
            })
            .DisposeWith(CompositeDisposable);

            // Listen for user interventions, and compile a dynamic list of all unhandled ones
            var activeInterventions = this.WhenAny(x => x.Installer.ActiveInstallation)
                                      .SelectMany(c => c?.LogMessages ?? Observable.Empty <IStatusMessage>())
                                      .WhereCastable <IStatusMessage, IUserIntervention>()
                                      .ToObservableChangeSet()
                                      .AutoRefresh(i => i.Handled)
                                      .Filter(i => !i.Handled)
                                      .AsObservableList();

            // Find the top intervention /w no CPU ID to be marked as "global"
            _ActiveGlobalUserIntervention = activeInterventions.Connect()
                                            .Filter(x => x.CpuID == WorkQueue.UnassignedCpuId)
                                            .QueryWhenChanged(query => query.FirstOrDefault())
                                            .ObserveOnGuiThread()
                                            .ToProperty(this, nameof(ActiveGlobalUserIntervention));

            CloseWhenCompleteCommand = ReactiveCommand.Create(
                canExecute: this.WhenAny(x => x.Completed)
                .Select(x => x != null),
                execute: () =>
            {
                MWVM.ShutdownApplication();
            });

            GoToInstallCommand = ReactiveCommand.Create(
                canExecute: Observable.CombineLatest(
                    this.WhenAny(x => x.Completed)
                    .Select(x => x != null),
                    this.WhenAny(x => x.Installer.SupportsAfterInstallNavigation),
                    resultSelector: (complete, supports) => complete && supports),
                execute: () =>
            {
                Installer.AfterInstallNavigation();
            });
        }