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