merge master

This commit is contained in:
Timothy Baldridge 2019-10-11 15:23:20 -06:00
commit 29943aab87
49 changed files with 1333 additions and 777 deletions

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "OMOD-Framework"]
path = OMOD-Framework
url = https://github.com/erri120/OMOD-Framework.git

View File

@ -42,9 +42,8 @@ The installer may have selected other options as well but these are the most imp
### Starting development
1) **Fork and clone the project:** go to the Github repo page, click the fork button, copy the url from the forked repo, navigate to your project folder, open Git Bash or normal command prompt and type `git clone url name` and replace url with the copied url and name with the folder name
2) **Initialize the submodules** using `git submodule init` and `git submodule update`
3) **Open Wabbajack.sln** in Visual Studio 2019
4) **Download NuGet Packages** by selecting the solution and *Right Click*->*Restore NuGet Packages*
2) **Open Wabbajack.sln** in Visual Studio 2019
3) **Download NuGet Packages** by selecting the solution and *Right Click*->*Restore NuGet Packages*
It may take a while for Visual Studio to download all packages and update all References so be patience. Once all packages are downloaded go and try building Wabbajack. If the build is successful than good job, if not head over to the *#wabbajack-development* channel on the discord and talk about your build error.

View File

@ -174,7 +174,7 @@ Look at the [`RECIPES.md`] file, we keep a knowledgebase of how to deal with giv
**How do I contribute to Wabbajack?**
Look at the [`CONTRIBUTION.md`](https://github.com/halgari/wabbajack/blob/master/CONTRIBUTING.md) file for detailed guidelines.
Look at the [`CONTRIBUTING.md`](https://github.com/halgari/wabbajack/blob/master/CONTRIBUTING.md) file for detailed guidelines.
**Why does each modlist install another copy of Mod Organizer 2?**

View File

@ -61,7 +61,7 @@ directURL=http://enbdev.com/enbseries_skyrimse_v0390.zip
directURLHeaders=Referer:http://enbdev.com/download_mod_tesskyrimse.html
```
**NOTE:** The author of ENBSeries has a habbit of updating the program without changing the version number. If you get
**NOTE:** The author of ENBSeries has a habit of updating the program without changing the version number. If you get
install errors stating that the hash of the downloaded file wasn't what was expected, update your ENBSeries to the latest
version and recompile the modlist.

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>Notify property changed property</Title>
<Shortcut>guiprop</Shortcut>
<Description>Code snippet for a NotifyingPropertyChanged variable</Description>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>Property type</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>Property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
</Declarations>
<Code Language="csharp"><![CDATA[private $type$ _$property$;
public $type$ $property$ { get => _$property$; set => this.RaiseAndSetIfChanged(ref _$property$, value); }$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>

View File

@ -53,8 +53,11 @@
<Reference Include="AlphaFS, Version=2.2.0.0, Culture=neutral, PublicKeyToken=4d31a58f7d7ad5c9, processorArchitecture=MSIL">
<HintPath>..\packages\AlphaFS.2.2.6\lib\net452\AlphaFS.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=1.1.0.145, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.1.1.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
<Reference Include="erri120.OMODFramework, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\erri120.OMODFramework.1.0.0\lib\net472\erri120.OMODFramework.dll</HintPath>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=1.2.0.246, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.1.2.0\lib\net45\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="INIFileParser, Version=2.5.2.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL">
<HintPath>..\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll</HintPath>
@ -71,6 +74,9 @@
<Reference Include="protobuf-net, Version=2.4.0.0, Culture=neutral, PublicKeyToken=257b51d87d2e4d67, processorArchitecture=MSIL">
<HintPath>..\packages\protobuf-net.2.4.0\lib\net40\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="SevenZip, Version=19.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\SevenZip.19.0.0\lib\net20\SevenZip.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AlphaFS" version="2.2.6" targetFramework="net472" />
<package id="erri120.OMODFramework" version="1.0.0" targetFramework="net472" />
<package id="ini-parser" version="2.5.2" targetFramework="net472" />
<package id="murmurhash" version="1.0.3" targetFramework="net472" />
<package id="Newtonsoft.Json" version="12.0.2" targetFramework="net472" />
<package id="Newtonsoft.Json.Bson" version="1.0.2" targetFramework="net472" />
<package id="protobuf-net" version="2.4.0" targetFramework="net472" />
<package id="SharpZipLib" version="1.1.0" targetFramework="net472" />
<package id="SevenZip" version="19.0.0" targetFramework="net472" />
<package id="SharpZipLib" version="1.2.0" targetFramework="net472" />
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net472" />
</packages>

View File

@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wabbajack"
StartupUri="ModeSelectionWindow.xaml"
StartupUri="UI\ModeSelectionWindow.xaml"
ShutdownMode="OnExplicitShutdown">
<Application.Resources>
<ResourceDictionary>

View File

@ -1,5 +1,4 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using Wabbajack.Common;
using Wabbajack.Updater;
@ -20,8 +19,8 @@ namespace Wabbajack
var args = Environment.GetCommandLineArgs();
if (args.Length > 1)
{
Utils.SetLoggerFn(f => {});
WorkQueue.Init((a, b, c) => {}, (a, b) => {});
Utils.SetLoggerFn(f => { });
WorkQueue.Init((a, b, c) => { }, (a, b) => { });
var updater = new CheckForUpdates(args[1]);
if (updater.FindOutdatedMods())
{

View File

@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
@ -14,59 +15,44 @@ using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Wabbajack.Common;
using Wabbajack.NexusApi;
using Wabbajack.UI;
namespace Wabbajack
{
internal class AppState : INotifyPropertyChanged, IDataErrorInfo
public enum TaskMode { INSTALLING, BUILDING }
internal class AppState : ViewModel, IDataErrorInfo
{
private ICommand _begin;
public const bool GcCollect = true;
private ICommand _changeDownloadPath;
private SlideShow _slideShow;
private ICommand _changePath;
private string _downloadLocation;
private string _htmlReport;
private bool _ignoreMissingFiles;
private string _location;
public bool installing = false;
private string _mo2Folder;
private string _mode;
private ModList _modList;
private string _modListName;
private int _queueProgress;
private ICommand _showReportCommand;
private ICommand _visitNexusSiteCommand;
private readonly DateTime _startTime;
public volatile bool Dirty;
private readonly Dispatcher dispatcher;
public readonly Dispatcher dispatcher;
public AppState(Dispatcher d, string mode)
public AppState(Dispatcher d, TaskMode mode)
{
_wabbajackLogo = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner.png");
_splashScreenImage = _wabbajackLogo;
_noneImage = UIUtils.BitmapImageFromResource("Wabbajack.UI.none.jpg");
_nextIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.next.png");
var image = new BitmapImage();
image.BeginInit();
image.StreamSource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Wabbajack.banner.png");
image.EndInit();
_wabbajackLogo = image;
_splashScreenImage = image;
SetupSlideshow();
_slideShow = new SlideShow(this, true);
if (Assembly.GetEntryAssembly().Location.ToLower().Contains("\\downloads\\"))
{
MessageBox.Show(
"This app seems to be running inside a folder called `Downloads`, such folders are often highly monitored by antivirus software and they 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`",
"This app seems to be running inside a folder called 'Downloads', such folders are often highly monitored by antivirus software and they 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);
@ -77,384 +63,131 @@ namespace Wabbajack
Mode = mode;
Dirty = false;
dispatcher = d;
Log = new ObservableCollection<string>();
Status = new ObservableCollection<CPUStatus>();
InternalStatus = new List<CPUStatus>();
var th = new Thread(() => UpdateLoop());
th.Priority = ThreadPriority.BelowNormal;
th.IsBackground = true;
th.Start();
slideshowThread = new Thread(UpdateLoop)
{
Priority = ThreadPriority.BelowNormal,
IsBackground = true
};
slideshowThread.Start();
}
private void SetupSlideshow()
{
var files = NexusApiClient.CachedSlideShow;
if (files.Any())
{
SlideShowElements = files.ToList();
}
}
public DateTime lastSlideShowUpdate = new DateTime();
public Random _random = new Random();
public List<SlideShowItem> SlideShowElements = new List<SlideShowItem>();
private DateTime _lastSlideShowUpdate = new DateTime();
public ObservableCollection<string> Log { get; } = new ObservableCollection<string>();
public ObservableCollection<CPUStatus> Status { get; } = new ObservableCollection<CPUStatus>();
public ObservableCollection<string> Log { get; }
public ObservableCollection<CPUStatus> Status { get; }
private TaskMode _Mode;
public TaskMode Mode { get => _Mode; set => this.RaiseAndSetIfChanged(ref _Mode, value); }
public string Mode
{
get => _mode;
set
{
_mode = value;
OnPropertyChanged("Mode");
}
}
private string _ModListName;
public string ModListName { get => _ModListName; set => this.RaiseAndSetIfChanged(ref _ModListName, value); }
public string ModListName
{
get => _modListName;
set
{
_modListName = value;
OnPropertyChanged("ModListName");
}
}
private string _Location;
public string Location { get => _Location; set => this.RaiseAndSetIfChanged(ref _Location, value); }
public string Location
{
get => _location;
set
{
_location = value;
OnPropertyChanged("Location");
}
}
private string _LocationLabel;
public string LocationLabel { get => _LocationLabel; set => this.RaiseAndSetIfChanged(ref _LocationLabel, value); }
public string DownloadLocation
{
get => _downloadLocation;
set
{
_downloadLocation = value;
OnPropertyChanged("DownloadLocation");
}
}
private string _DownloadLocation;
public string DownloadLocation { get => _DownloadLocation; set => this.RaiseAndSetIfChanged(ref _DownloadLocation, value); }
public Visibility ShowReportButton => _htmlReport == null ? Visibility.Collapsed : Visibility.Visible;
private string _htmlReport;
public string HTMLReport
{
get => _htmlReport;
set
{
_htmlReport = value;
OnPropertyChanged("HTMLReport");
OnPropertyChanged("ShowReportButton");
RaisePropertyChanged();
RaisePropertyChanged(nameof(ShowReportButton));
}
}
public int QueueProgress
{
get => _queueProgress;
set
{
if (value != _queueProgress)
{
_queueProgress = value;
OnPropertyChanged("QueueProgress");
}
}
}
private int _queueProgress;
public int QueueProgress { get => _queueProgress; set => this.RaiseAndSetIfChanged(ref _queueProgress, value); }
private List<CPUStatus> InternalStatus { get; }
private List<CPUStatus> InternalStatus { get; } = new List<CPUStatus>();
public string LogFile { get; }
private ICommand _changePath;
public ICommand ChangePath
{
get
{
if (_changePath == null) _changePath = new LambdaCommand(() => true, () => ExecuteChangePath());
if (_changePath == null) _changePath = new LambdaCommand(() => true, ExecuteChangePath);
return _changePath;
}
}
private ICommand _changeDownloadPath;
public ICommand ChangeDownloadPath
{
get
{
if (_changeDownloadPath == null)
_changeDownloadPath = new LambdaCommand(() => true, () => ExecuteChangeDownloadPath());
_changeDownloadPath = new LambdaCommand(() => true, ExecuteChangeDownloadPath);
return _changeDownloadPath;
}
}
private ICommand _begin;
public ICommand Begin
{
get
{
if (_begin == null) _begin = new LambdaCommand(() => true, () => ExecuteBegin());
if (_begin == null) _begin = new LambdaCommand(() => true, ExecuteBegin);
return _begin;
}
}
private ICommand _showReportCommand;
public ICommand ShowReportCommand
{
get
{
if (_showReportCommand == null) _showReportCommand = new LambdaCommand(() => true, () => ShowReport());
return _showReportCommand;
return _showReportCommand ?? (_showReportCommand = new LambdaCommand(() => true, ShowReport));
}
}
private ICommand _visitNexusSiteCommand;
public ICommand VisitNexusSiteCommand
{
get
{
if (_visitNexusSiteCommand == null) _visitNexusSiteCommand = new LambdaCommand(() => true, () => VisitNexusSite());
return _visitNexusSiteCommand;
return _visitNexusSiteCommand ??
(_visitNexusSiteCommand = new LambdaCommand(() => true, VisitNexusSite));
}
}
public string _nexusSiteURL = null;
private void VisitNexusSite()
{
if (_nexusSiteURL != null && _nexusSiteURL.StartsWith("https://"))
{
Process.Start(_nexusSiteURL);
}
}
private bool _uiReady = false;
public bool UIReady
{
get => _uiReady;
set
{
_uiReady = value;
OnPropertyChanged("UIReady");
}
}
private BitmapImage _wabbajackLogo = null;
private BitmapImage _splashScreenImage = null;
public BitmapImage SplashScreenImage
{
get => _splashScreenImage;
set
{
_splashScreenImage = value;
OnPropertyChanged("SplashScreenImage");
}
}
public string _splashScreenModName = "Wabbajack";
public string SplashScreenModName
{
get => _splashScreenModName;
set
{
_splashScreenModName = value;
OnPropertyChanged("SplashScreenModName");
}
}
public string _splashScreenAuthorName = "Halgari & the Wabbajack Team";
public string SplashScreenAuthorName
{
get => _splashScreenAuthorName;
set
{
_splashScreenAuthorName = value;
OnPropertyChanged("SplashScreenAuthorName");
}
}
public string _splashScreenSummary = "";
private string _modListPath;
public string SplashScreenSummary
{
get => _splashScreenSummary;
set
{
_splashScreenSummary = value;
OnPropertyChanged("SplashScreenSummary");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string name)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
public string Error
{
get { return "Error"; }
}
public string this[string columnName]
public ICommand OpenModListPropertiesCommand
{
get
{
return Validate(columnName);
}
}
private string Validate(string columnName)
{
string validationMessage = null;
switch (columnName)
{
case "Location":
if (Location == null)
{
validationMessage = null;
}
else if (Location != null && Directory.Exists(Location) && File.Exists(Path.Combine(Location, "modlist.txt")))
{
Location = Path.Combine(Location, "modlist.txt");
validationMessage = null;
ConfigureForBuild();
}
else
{
validationMessage = "Invalid Mod Organizer profile directory";
}
break;
}
return validationMessage;
}
private void UpdateLoop()
{
while (Running)
{
if (Dirty)
lock (InternalStatus)
{
var data = InternalStatus.ToArray();
dispatcher.Invoke(() =>
{
for (var idx = 0; idx < data.Length; idx += 1)
if (idx >= Status.Count)
Status.Add(data[idx]);
else if (Status[idx] != data[idx])
Status[idx] = data[idx];
});
Dirty = false;
}
if (SlideShowElements.Any())
{
if (DateTime.Now - _lastSlideShowUpdate > TimeSpan.FromSeconds(10))
{
var idx = _random.Next(0, SlideShowElements.Count);
try
{
var element = SlideShowElements[idx];
var data = new MemoryStream();
using (var stream = new HttpClient().GetStreamSync(element.ImageURL))
stream.CopyTo(data);
data.Seek(0, SeekOrigin.Begin);
dispatcher.Invoke(() =>
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = data;
bitmap.EndInit();
SplashScreenImage = bitmap;
SplashScreenModName = element.ModName;
SplashScreenAuthorName = element.AuthorName;
SplashScreenSummary = element.ModSummary;
_nexusSiteURL = element.ModURL;
_lastSlideShowUpdate = DateTime.Now;
});
}
catch (Exception ex)
{
}
return new LambdaCommand(() => true, OpenModListProperties);
}
}
Thread.Sleep(1000);
}
}
public bool Running { get; set; } = true;
internal void ConfigureForInstall(string source, ModList modlist)
public ICommand SlideShowNextItem
{
_modList = modlist;
_modListPath = source;
Mode = "Installing";
ModListName = _modList.Name;
HTMLReport = _modList.ReportHTML;
Location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
SlideShowElements = modlist.Archives.OfType<NexusMod>().Select(m => new SlideShowItem
get
{
ModName = NexusApiUtils.FixupSummary(m.ModName),
AuthorName = NexusApiUtils.FixupSummary(m.Author),
ModSummary = NexusApiUtils.FixupSummary(m.Summary),
ImageURL = m.SlideShowPic,
ModURL = m.NexusURL,
}).ToList();
return new LambdaCommand(() => true, _slideShow.UpdateSlideShowItem);
}
public void LogMsg(string msg)
{
dispatcher.Invoke(() => Log.Add(msg));
}
public void SetProgress(int id, string msg, int progress)
{
lock (InternalStatus)
{
Dirty = true;
while (id >= InternalStatus.Count) InternalStatus.Add(new CPUStatus());
InternalStatus[id] = new CPUStatus {ID = id, Msg = msg, Progress = progress};
}
}
public void SetQueueSize(int max, int current)
{
if (max == 0)
max = 1;
var total = current * 100 / max;
QueueProgress = total;
}
private void ExecuteChangePath()
{
if (Mode == "Installing")
if (Mode == TaskMode.INSTALLING)
{
var folder = UIUtils.ShowFolderSelectionDialog("Select Installation directory");
if (folder != null)
{
if (folder == null) return;
Location = folder;
if (_downloadLocation == null)
if (DownloadLocation == null)
DownloadLocation = Path.Combine(Location, "downloads");
}
}
else
{
var folder = UIUtils.ShowFolderSelectionDialog("Select Your MO2 profile directory");
@ -468,6 +201,258 @@ namespace Wabbajack
if (folder != null) DownloadLocation = folder;
}
private void ShowReport()
{
var file = Path.GetTempFileName() + ".html";
File.WriteAllText(file, HTMLReport);
Process.Start(file);
}
public string _nexusSiteURL = null;
private void VisitNexusSite()
{
if (_nexusSiteURL != null && _nexusSiteURL.StartsWith("https://"))
{
Process.Start(_nexusSiteURL);
}
}
private ModlistPropertiesWindow modlistPropertiesWindow;
public string newImagePath;
public string readmePath;
public bool ChangedProperties;
private void OpenModListProperties()
{
if (UIReady)
{
if (modlistPropertiesWindow == null)
{
modlistPropertiesWindow = new ModlistPropertiesWindow(this);
newImagePath = null;
ChangedProperties = false;
}
if(!modlistPropertiesWindow.IsClosed)
modlistPropertiesWindow.Show();
else
{
modlistPropertiesWindow = null;
OpenModListProperties();
}
}
}
public bool HasReadme { get; set; }
public ICommand OpenReadme
{
get
{
return new LambdaCommand(()=> true,OpenReadmeWindow);
}
}
private void OpenReadmeWindow()
{
if (!UIReady || string.IsNullOrEmpty(_modList.Readme)) return;
var text = "";
using (var fs = new FileStream(_modListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(_modList.Readme);
using (var e = entry.Open())
e.CopyTo(ms);
ms.Seek(0, SeekOrigin.Begin);
using (var sr = new StreamReader(ms))
{
string line;
while ((line = sr.ReadLine()) != null)
text += line+Environment.NewLine;
//text = sr.ReadToEnd();
}
}
var viewer = new TextViewer(text, _ModListName);
viewer.Show();
}
private bool _uiReady = false;
public bool UIReady
{
get => _uiReady;
set => this.RaiseAndSetIfChanged(ref _uiReady, value);
}
private readonly BitmapImage _wabbajackLogo = null;
public readonly BitmapImage _noneImage = null;
private BitmapImage _splashScreenImage = null;
public BitmapImage SplashScreenImage
{
get => _splashScreenImage;
set
{
_splashScreenImage = value;
RaisePropertyChanged();
}
}
private string _SplashScreenModName = "Wabbajack";
public string SplashScreenModName { get => _SplashScreenModName; set => this.RaiseAndSetIfChanged(ref _SplashScreenModName, value); }
private BitmapImage _nextIcon = null;
public BitmapImage NextIcon { get => _nextIcon; set => this.RaiseAndSetIfChanged(ref _nextIcon, value); }
private string _SplashScreenAuthorName = "Halgari & the Wabbajack Team";
public string SplashScreenAuthorName { get => _SplashScreenAuthorName; set => this.RaiseAndSetIfChanged(ref _SplashScreenAuthorName, value); }
private string _modListPath;
private string _SplashScreenSummary;
public string SplashScreenSummary { get => _SplashScreenSummary; set => this.RaiseAndSetIfChanged(ref _SplashScreenSummary, value); }
private bool _splashShowNSFW = false;
public bool SplashShowNSFW { get => _splashShowNSFW; set => this.RaiseAndSetIfChanged(ref _splashShowNSFW, value); }
private readonly Thread slideshowThread = null;
private bool _enableSlideShow = true;
public bool EnableSlideShow
{
get => _enableSlideShow;
set
{
RaiseAndSetIfChanged(ref _enableSlideShow, value);
if (!slideshowThread.IsAlive) return;
if (!_enableSlideShow)
{
ApplyModlistProperties();
}
else
{
_slideShow.UpdateSlideShowItem();
}
}
}
public string Error => "Error";
public string this[string columnName] => Validate(columnName);
private string Validate(string columnName)
{
string validationMessage = null;
switch (columnName)
{
case "Location":
if (Location == null)
{
validationMessage = null;
}
else switch (Mode)
{
case TaskMode.BUILDING when Location != null && Directory.Exists(Location) && File.Exists(Path.Combine(Location, "modlist.txt")):
Location = Path.Combine(Location, "modlist.txt");
validationMessage = null;
ConfigureForBuild();
break;
case TaskMode.INSTALLING when Location != null && Directory.Exists(Location) && !Directory.EnumerateFileSystemEntries(Location).Any():
validationMessage = null;
break;
case TaskMode.INSTALLING when Location != null && Directory.Exists(Location) && Directory.EnumerateFileSystemEntries(Location).Any():
validationMessage = "You have selected a non-empty directory. Installing the modlist here might result in a broken install!";
break;
default:
validationMessage = "Invalid Mod Organizer profile directory";
break;
}
break;
}
return validationMessage;
}
private void UpdateLoop()
{
while (Running)
{
if (Dirty)
lock (InternalStatus)
{
CPUStatus[] data = InternalStatus.ToArray();
dispatcher.Invoke(() =>
{
for (var idx = 0; idx < data.Length; idx += 1)
if (idx >= Status.Count)
Status.Add(data[idx]);
else if (Status[idx] != data[idx])
Status[idx] = data[idx];
});
Dirty = false;
}
if (_slideShow.SlidesQueue.Any())
{
if (DateTime.Now - lastSlideShowUpdate > TimeSpan.FromSeconds(10))
{
_slideShow.UpdateSlideShowItem();
}
}
Thread.Sleep(1000);
}
}
public bool Running { get; set; } = true;
private void ApplyModlistProperties()
{
SplashScreenModName = _modList.Name;
SplashScreenAuthorName = _modList.Author;
_nexusSiteURL = _modList.Website;
SplashScreenSummary = _modList.Description;
if (!string.IsNullOrEmpty(_modList.Image) && _modList.Image.Length == 36)
{
SplashScreenImage = _wabbajackLogo;
using (var fs = new FileStream(_modListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(_modList.Image);
using (var e = entry.Open())
e.CopyTo(ms);
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
SplashScreenImage = image;
}
}
else
{
SplashScreenImage = _wabbajackLogo;
}
}
public void LogMsg(string msg)
{
dispatcher.Invoke(() => Log.Add(msg));
}
public void SetProgress(int id, string msg, int progress)
{
lock (InternalStatus)
{
Dirty = true;
while (id >= InternalStatus.Count) InternalStatus.Add(new CPUStatus());
InternalStatus[id] = new CPUStatus { ID = id, Msg = msg, Progress = progress };
}
}
public void SetQueueSize(int max, int current)
{
if (max == 0)
max = 1;
var total = current * 100 / max;
QueueProgress = total;
}
private void ConfigureForBuild()
{
var profile_folder = Path.GetDirectoryName(Location);
@ -477,7 +462,7 @@ namespace Wabbajack
var profile_name = Path.GetFileName(profile_folder);
ModListName = profile_name;
Mode = "Building";
Mode = TaskMode.BUILDING;
var tmp_compiler = new Compiler(mo2folder);
DownloadLocation = tmp_compiler.MO2DownloadsFolder;
@ -485,22 +470,37 @@ namespace Wabbajack
_mo2Folder = mo2folder;
}
private void ShowReport()
internal void ConfigureForInstall(string source, ModList modlist)
{
var file = Path.GetTempFileName() + ".html";
File.WriteAllText(file, HTMLReport);
Process.Start(file);
}
_modList = modlist;
_modListPath = source;
HasReadme = !string.IsNullOrEmpty(_modList.Readme);
Mode = TaskMode.INSTALLING;
ModListName = _modList.Name;
HTMLReport = _modList.ReportHTML;
Location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
ApplyModlistProperties();
_slideShow.SlideShowElements = modlist.Archives.OfType<NexusMod>().Select(m =>
new Slide(NexusApiUtils.FixupSummary(m.ModName),m.ModID,
NexusApiUtils.FixupSummary(m.Summary), NexusApiUtils.FixupSummary(m.Author),
m.Adult,m.NexusURL,m.SlideShowPic)).ToList();
_slideShow.PreloadSlideShow();
}
private void ExecuteBegin()
{
UIReady = false;
if (Mode == "Installing")
if (Mode == TaskMode.INSTALLING)
{
var installer = new Installer(_modListPath, _modList, Location);
installer.DownloadFolder = DownloadLocation;
installing = true;
var installer = new Installer(_modListPath, _modList, Location)
{
DownloadFolder = DownloadLocation
};
var th = new Thread(() =>
{
UIReady = false;
@ -518,15 +518,28 @@ namespace Wabbajack
finally
{
UIReady = true;
Running = false;
installing = false;
slideshowThread.Abort();
}
});
th.Priority = ThreadPriority.BelowNormal;
})
{
Priority = ThreadPriority.BelowNormal
};
th.Start();
}
else if (_mo2Folder != null)
{
var compiler = new Compiler(_mo2Folder);
compiler.MO2Profile = ModListName;
var compiler = new Compiler(_mo2Folder)
{
MO2Profile = ModListName,
ModListName = ChangedProperties ? SplashScreenModName : null,
ModListAuthor = ChangedProperties ? SplashScreenAuthorName : null,
ModListDescription = ChangedProperties ? SplashScreenSummary : null,
ModListImage = ChangedProperties ? newImagePath : null,
ModListWebsite = ChangedProperties ? _nexusSiteURL : null,
ModListReadme = ChangedProperties ? readmePath : null
};
var th = new Thread(() =>
{
UIReady = false;
@ -547,8 +560,10 @@ namespace Wabbajack
{
UIReady = true;
}
});
th.Priority = ThreadPriority.BelowNormal;
})
{
Priority = ThreadPriority.BelowNormal
};
th.Start();
}
else
@ -559,8 +574,6 @@ namespace Wabbajack
}
}
public class CPUStatus
{
public int Progress { get; internal set; }

View File

@ -21,7 +21,7 @@ namespace Wabbajack
public static bool GetScrollOnNewItem(DependencyObject obj)
{
return (bool) obj.GetValue(ScrollOnNewItemProperty);
return (bool)obj.GetValue(ScrollOnNewItemProperty);
}
public static void SetScrollOnNewItem(DependencyObject obj, bool value)
@ -35,7 +35,7 @@ namespace Wabbajack
{
var listBox = d as ListBox;
if (listBox == null) return;
bool oldValue = (bool) e.OldValue, newValue = (bool) e.NewValue;
bool oldValue = (bool)e.OldValue, newValue = (bool)e.NewValue;
if (newValue == oldValue) return;
if (newValue)
{
@ -57,7 +57,7 @@ namespace Wabbajack
private static void ListBox_ItemsSourceChanged(object sender, EventArgs e)
{
var listBox = (ListBox) sender;
var listBox = (ListBox)sender;
if (Associations.ContainsKey(listBox))
Associations[listBox].Dispose();
Associations[listBox] = new Capture(listBox);
@ -65,7 +65,7 @@ namespace Wabbajack
private static void ListBox_Unloaded(object sender, RoutedEventArgs e)
{
var listBox = (ListBox) sender;
var listBox = (ListBox)sender;
if (Associations.ContainsKey(listBox))
Associations[listBox].Dispose();
listBox.Unloaded -= ListBox_Unloaded;
@ -73,7 +73,7 @@ namespace Wabbajack
private static void ListBox_Loaded(object sender, RoutedEventArgs e)
{
var listBox = (ListBox) sender;
var listBox = (ListBox)sender;
var incc = listBox.Items as INotifyCollectionChanged;
if (incc == null) return;
listBox.Loaded -= ListBox_Loaded;

View File

@ -1,20 +1,16 @@
using System;
using CommonMark;
using Compression.BSA;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using CommonMark;
using Compression.BSA;
using K4os.Compression.LZ4;
using K4os.Compression.LZ4.Streams;
using Newtonsoft.Json;
using System.IO.Compression;
using VFS;
using Wabbajack.Common;
using Wabbajack.NexusApi;
@ -37,12 +33,13 @@ namespace Wabbajack
public string MO2Profile;
public string ModListName, ModListAuthor, ModListDescription, ModListWebsite, ModListImage, ModListReadme;
public Compiler(string mo2_folder)
{
MO2Folder = mo2_folder;
MO2Ini = Path.Combine(MO2Folder, "ModOrganizer.ini").LoadIniFile();
GamePath = ((string) MO2Ini.General.gamePath).Replace("\\\\", "\\");
GamePath = ((string)MO2Ini.General.gamePath).Replace("\\\\", "\\");
}
public dynamic MO2Ini { get; }
@ -143,12 +140,12 @@ namespace Wabbajack
var mo2_files = Directory.EnumerateFiles(MO2Folder, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Lookup(p)) {Path = p.RelativeTo(MO2Folder)});
.Select(p => new RawSourceFile(VFS.Lookup(p)) { Path = p.RelativeTo(MO2Folder) });
var game_files = Directory.EnumerateFiles(GamePath, "*", SearchOption.AllDirectories)
.Where(p => p.FileExists())
.Select(p => new RawSourceFile(VFS.Lookup(p))
{Path = Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath))});
{ Path = Path.Combine(Consts.GameFolderFilesDir, p.RelativeTo(GamePath)) });
var loot_path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"LOOT");
@ -280,7 +277,12 @@ namespace Wabbajack
GameType = GameRegistry.Games.Values.First(f => f.MO2Name == MO2Ini.General.gameName).Game,
Archives = SelectedArchives,
Directives = InstallDirectives,
Name = MO2Profile
Name = ModListName ?? MO2Profile,
Author = ModListAuthor ?? "",
Description = ModListDescription ?? "",
Readme = ModListReadme ?? "",
Image = ModListImage ?? "",
Website = ModListWebsite ?? ""
};
ValidateModlist.RunValidation(ModList);
@ -358,8 +360,8 @@ namespace Wabbajack
}
}
ModList.ReportHTML = "<style>"+css+"</style>"
+CommonMarkConverter.Convert(File.ReadAllText($"{ModList.Name}.md"));
ModList.ReportHTML = "<style>" + css + "</style>"
+ CommonMarkConverter.Convert(File.ReadAllText($"{ModList.Name}.md"));
}
/// <summary>
@ -432,11 +434,7 @@ namespace Wabbajack
using (var a = new BSAReader(Path.Combine(MO2Folder, bsa.To)))
{
var file = a.Files.First(e => e.Path == Path.Combine(to.Split('\\').Skip(2).ToArray()));
using (var ms = new MemoryStream())
{
file.CopyDataTo(ms);
return ms.ToArray();
}
return file.GetData();
}
}
@ -489,7 +487,7 @@ namespace Wabbajack
}
else if (general.directURL != null && general.directURL.StartsWith("https://www.dropbox.com/"))
{
var uri = new UriBuilder((string) general.directURL);
var uri = new UriBuilder((string)general.directURL);
var query = HttpUtility.ParseQueryString(uri.Query);
if (query.GetValues("dl").Count() > 0)
@ -552,6 +550,7 @@ namespace Wabbajack
nm.SlideShowPic = info.picture_url;
nm.NexusURL = NexusApiUtils.GetModURL(info.game_name, info.mod_id);
nm.Summary = info.summary;
nm.Adult = info.contains_adult_content;
result = nm;
}
@ -616,6 +615,7 @@ namespace Wabbajack
Info("Generating compilation stack");
return new List<Func<RawSourceFile, Directive>>
{
IncludePropertyFiles(),
IgnoreStartsWith("logs\\"),
IncludeRegex("^downloads\\\\.*\\.meta"),
IgnoreStartsWith("downloads\\"),
@ -672,6 +672,34 @@ namespace Wabbajack
};
}
private Func<RawSourceFile, Directive> IncludePropertyFiles()
{
return source =>
{
var files = new HashSet<string>
{
ModListImage, ModListReadme
};
if (!files.Any(f => source.AbsolutePath.Equals(f))) return null;
if (!File.Exists(source.AbsolutePath)) return null;
var isBanner = source.AbsolutePath == ModListImage;
//var isReadme = source.AbsolutePath == ModListReadme;
var result = source.EvolveTo<PropertyFile>();
result.SourceDataID = IncludeFile(File.ReadAllBytes(source.AbsolutePath));
if (isBanner)
{
result.Type = PropertyType.Banner;
ModListImage = result.SourceDataID;
}
else
{
result.Type = PropertyType.Readme;
ModListReadme = result.SourceDataID;
}
return result;
};
}
private Func<RawSourceFile, Directive> IgnoreWabbajackInstallCruft()
{
var cruft_files = new HashSet<string>
@ -928,16 +956,23 @@ namespace Wabbajack
ExtraFiles.Add(match);
}
;
CreateBSA directive;
using (var bsa = new BSAReader(source.AbsolutePath))
{
directive = new CreateBSA
{
To = source.Path,
State = bsa.State,
FileStates = bsa.Files.Select(f => f.State).ToList()
TempID = id,
Type = (uint)bsa.HeaderType,
FileFlags = (uint)bsa.FileFlags,
ArchiveFlags = (uint)bsa.ArchiveFlags
};
}
;
return directive;
};
}

View File

@ -1,7 +1,6 @@
using System;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using Compression.BSA;
using Newtonsoft.Json;
using VFS;
using Wabbajack.Common;
@ -55,6 +54,31 @@ namespace Wabbajack
/// </summary>
public string Name;
/// <summary>
/// Author of the ModList
/// </summary>
public string Author;
/// <summary>
/// Description of the ModList
/// </summary>
public string Description;
/// <summary>
/// Hash of the banner-image
/// </summary>
public string Image;
/// <summary>
/// Website of the ModList
/// </summary>
public string Website;
/// <summary>
/// Hash of the readme
/// </summary>
public string Readme;
/// <summary>
/// Content Report in HTML form
/// </summary>
@ -92,6 +116,17 @@ namespace Wabbajack
public string SourceDataID;
}
public enum PropertyType { Banner, Readme }
/// <summary>
/// File meant to be extracted before the installation
/// </summary>
[Serializable]
public class PropertyFile : InlineFile
{
public PropertyType Type;
}
[Serializable]
public class CleanedESM : InlineFile
{
@ -132,9 +167,15 @@ namespace Wabbajack
[Serializable]
public class CreateBSA : Directive
{
public string IsCompressed;
public bool ShareData;
public string TempID;
public ArchiveStateObject State { get; set; }
public List<FileStateObject> FileStates { get; set; }
public uint Type;
public uint Version;
public uint FileFlags { get; set; }
public bool Compress { get; set; }
public uint ArchiveFlags { get; set; }
}
[Serializable]
@ -197,6 +238,7 @@ namespace Wabbajack
public string ModName;
public string NexusURL;
public string Summary;
public bool Adult;
}
[Serializable]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,18 +1,14 @@
using System;
using CG.Web.MegaApiClient;
using Compression.BSA;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;
using CG.Web.MegaApiClient;
using Compression.BSA;
using K4os.Compression.LZ4.Streams;
using System.IO.Compression;
using VFS;
using Wabbajack.Common;
using Wabbajack.NexusApi;
@ -76,7 +72,7 @@ namespace Wabbajack
private byte[] LoadBytesFromPath(string path)
{
using (var fs = new FileStream(ModListArchive, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs,ZipArchiveMode.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using (var ms = new MemoryStream())
{
var entry = ar.GetEntry(path);
@ -218,7 +214,7 @@ namespace Wabbajack
{
HashedArchives.Do(a => VFS.AddKnown(new VirtualFile
{
Paths = new[] {a.Value},
Paths = new[] { a.Value },
Hash = a.Key
}));
VFS.RefreshIndexes();
@ -231,7 +227,7 @@ namespace Wabbajack
var updated_path = new string[f.ArchiveHashPath.Length];
f.ArchiveHashPath.CopyTo(updated_path, 0);
updated_path[0] = VFS.HashIndex[updated_path[0]].Where(e => e.IsConcrete).First().FullPath;
VFS.AddKnown(new VirtualFile {Paths = updated_path});
VFS.AddKnown(new VirtualFile { Paths = updated_path });
});
VFS.BackfillMissing();
@ -251,15 +247,19 @@ namespace Wabbajack
.ToList();
if (source_files.Count > 0)
using (var a = bsa.State.MakeBuilder())
using (var a = new BSABuilder())
{
var indexed = bsa.FileStates.ToDictionary(d => d.Path);
//a.Create(Path.Combine(Outputfolder, bsa.To), (bsa_archive_type_t)bsa.Type, entries);
a.HeaderType = (VersionType)bsa.Type;
a.FileFlags = (FileFlags)bsa.FileFlags;
a.ArchiveFlags = (ArchiveFlags)bsa.ArchiveFlags;
source_files.PMap(f =>
{
Status($"Adding {f} to BSA");
using (var fs = File.OpenRead(Path.Combine(source_dir, f)))
{
a.AddFile(indexed[f.ToLower()], fs);
a.AddFile(f, fs);
}
});
@ -288,9 +288,9 @@ namespace Wabbajack
var out_path = Path.Combine(Outputfolder, directive.To);
if (File.Exists(out_path)) File.Delete(out_path);
if (directive is RemappedInlineFile)
WriteRemappedFile((RemappedInlineFile) directive);
WriteRemappedFile((RemappedInlineFile)directive);
else if (directive is CleanedESM)
GenerateCleanedESM((CleanedESM) directive);
GenerateCleanedESM((CleanedESM)directive);
else
File.WriteAllBytes(out_path, LoadBytesFromPath(directive.SourceDataID));
});
@ -358,7 +358,7 @@ namespace Wabbajack
.GroupBy(e => e.ArchiveHashPath[0])
.ToDictionary(k => k.Key);
var archives = ModList.Archives
.Select(a => new {Archive = a, AbsolutePath = HashedArchives.GetOrDefault(a.Hash)})
.Select(a => new { Archive = a, AbsolutePath = HashedArchives.GetOrDefault(a.Hash) })
.Where(a => a.AbsolutePath != null)
.ToList();

View File

@ -1,119 +0,0 @@
<Window x:Class="Wabbajack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wabbajack"
mc:Ignorable="d"
Title="Wabbajack" Height="960" Width="1448"
Style="{StaticResource {x:Type Window}}" Icon="square_transparent_icon.ico" WindowStyle="ToolWindow"
ResizeMode="NoResize"
Closing="Window_Closing">
<Grid Margin="16">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="460"/>
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="700"></ColumnDefinition>
<ColumnDefinition Width="700"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0, 16, 0, 16">
<TextBlock Text="{Binding Mode}" FontSize="16" FontWeight="Bold" />
<TextBlock Text=" : " FontSize="16" />
<TextBlock Text="{Binding ModListName}" FontSize="16" />
</StackPanel>
<!-- Slideshow -->
<Image Grid.Row="1" Grid.Column="0" Source="{Binding SplashScreenImage}" Stretch="Uniform" Margin="5"></Image>
<Grid Grid.Row="1" Grid.Column="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="{Binding SplashScreenModName}" Grid.Row="0" FontSize="30" FontWeight="Bold"></TextBlock>
<TextBlock Text="{Binding SplashScreenAuthorName}" Grid.Row="1" FontSize="15" FontWeight="Bold"></TextBlock>
<TextBlock Text="{Binding SplashScreenSummary}" TextWrapping="Wrap" Grid.Row="2" FontSize="15" FontWeight="Bold"></TextBlock>
<Button Height="30" Grid.Row="3" Grid.Column="1" Command="{Binding VisitNexusSiteCommand}">
<TextBlock Text="View Nexus Site" FontSize="15" FontWeight="Bold"></TextBlock>
</Button>
</Grid>
<!-- End Slideshow-->
<TextBlock Text="Log:" Grid.Row="3" FontSize="14" Margin="0, 16, 0, 8" />
<ListBox local:AutoScrollBehavior.ScrollOnNewItem="True" Grid.Row="4" ItemsSource="{Binding Log}" />
<Grid HorizontalAlignment="Stretch" Grid.Row="5" Grid.Column="0" Grid.RowSpan="2" Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition MinHeight="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="MO2 Profile:" Grid.Column="0" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Location, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" IsEnabled="{Binding UIReady}"/>
<Button Grid.Row="0" Content="Select" MinWidth="80" Grid.Column="2" Command="{Binding ChangePath}" IsEnabled="{Binding UIReady}"/>
<Label Grid.Row="2" Content="Download Location:" Grid.Column="0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DownloadLocation}" IsEnabled="{Binding UIReady}"/>
<Button Grid.Row="2" Content="Select" MinWidth="80" Grid.Column="2" Command="{Binding ChangeDownloadPath}" IsEnabled="{Binding UIReady}"/>
</Grid>
<!-- Work Queue Start -->
<ProgressBar Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Value="{Binding QueueProgress}" Minimum="0" Maximum="100" Background="#444444" />
<TextBlock Text="Work Queue:" Grid.Row="3" Grid.Column="1" FontSize="14" Margin="0, 16, 0, 8" />
<ListBox Grid.Row="4" Grid.Column="1" ItemsSource="{Binding Status}" Width="Auto" HorizontalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress, Mode=OneTime}" Width="100"
Grid.Column="0">
<ProgressBar.Style>
<Style TargetType="ProgressBar">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Progress}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ProgressBar.Style>
</ProgressBar>
<TextBlock Text=" CPU " Grid.Column="1" />
<TextBlock Text="{Binding ID}" Grid.Column="2" />
<TextBlock Text=" - " Grid.Column="3" />
<TextBlock Text="{Binding Msg}" Grid.Column="4" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Work Queue End-->
<Button Content="View Modlist Contents" Grid.Row="5" Grid.Column="1" Height="30" Visibility="{Binding ShowReportButton}"
Command="{Binding ShowReportCommand}" Margin="10" />
<Button Content="Begin" Grid.Row="6" Height="30" Grid.Column="1" Command="{Binding Begin}" IsEnabled="{Binding UIReady}" Margin="10"/>
</Grid>
</Window>

View File

@ -1,100 +0,0 @@
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using Alphaleonis.Win32.Filesystem;
using Wabbajack.Common;
using Application = System.Windows.Application;
using MessageBox = System.Windows.MessageBox;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private AppState _state;
public enum RunMode
{
Compile,
Install
}
public MainWindow(RunMode mode, string source)
{
_mode = mode;
_source = source;
var args = Environment.GetCommandLineArgs();
var DebugMode = false;
string MO2Folder = null, InstallFolder = null, MO2Profile = null;
InitializeComponent();
}
public MainWindow Start()
{
_context = new AppState(Dispatcher, "Building");
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));
UIUtils.Dispatcher = Dispatcher;
_context._nexusSiteURL = "https://github.com/halgari/wabbajack";
var thread = new Thread(() => { SetupWindow(_mode, _source, _context); });
thread.Start();
return this;
}
private void SetupWindow(RunMode mode, string source, AppState context)
{
if (mode == RunMode.Compile)
{
Utils.Log("Compiler ready to execute");
context.Location = Path.GetDirectoryName(source);
}
else if (mode == RunMode.Install)
{
context.UIReady = false;
var modlist = Installer.LoadFromFile(source);
if (modlist == null)
{
MessageBox.Show("Invalid Modlist, or file not found.", "Invalid Modlist", MessageBoxButton.OK,
MessageBoxImage.Error);
Dispatcher.Invoke(() =>
{
context.Running = false;
ExitWhenClosing = false;
var window = new ModeSelectionWindow();
window.ShowActivated = true;
window.Show();
Close();
});
}
else
{
context.ConfigureForInstall(source, modlist);
}
}
context.UIReady = true;
}
internal bool ExitWhenClosing = true;
private RunMode _mode;
private string _source;
private AppState _context;
private void Window_Closing(object sender, CancelEventArgs e)
{
if (ExitWhenClosing)
Application.Current.Shutdown();
}
}
}

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wabbajack.NexusApi
{
@ -47,15 +43,7 @@ namespace Wabbajack.NexusApi
public string uploaded_by;
public string uploaded_users_profile_url;
public string picture_url;
}
public class SlideShowItem
{
public string ImageURL;
public string ModName;
public string ModSummary;
public string AuthorName;
public string ModURL;
public bool contains_adult_content;
}
public class EndorsementResponse

View File

@ -11,12 +11,12 @@ using System.Reflection;
using System.Security.Authentication;
using System.Threading.Tasks;
using Wabbajack.Common;
using static Wabbajack.NexusApi.NexusApiUtils;
using WebSocketSharp;
using static Wabbajack.NexusApi.NexusApiUtils;
namespace Wabbajack.NexusApi
{
public class NexusApiClient : INotifyPropertyChanged
public class NexusApiClient : ViewModel
{
private static readonly string API_KEY_CACHE_FILE = "nexus.key_cache";
@ -136,9 +136,8 @@ namespace Wabbajack.NexusApi
_dailyRemaining = Math.Min(dailyRemaining, hourlyRemaining);
_hourlyRemaining = Math.Min(dailyRemaining, hourlyRemaining);
}
OnPropertyChanged(nameof(DailyRemaining));
OnPropertyChanged(nameof(HourlyRemaining));
RaisePropertyChanged(nameof(DailyRemaining));
RaisePropertyChanged(nameof(HourlyRemaining));
}
#endregion
@ -250,7 +249,7 @@ namespace Wabbajack.NexusApi
Utils.Status($"Endorsing ${mod.GameName} - ${mod.ModID}");
var url = $"https://api.nexusmods.com/v1/games/{ConvertGameName(mod.GameName)}/mods/{mod.ModID}/endorse.json";
var content = new FormUrlEncodedContent(new Dictionary<string, string> {{"version", mod.Version}});
var content = new FormUrlEncodedContent(new Dictionary<string, string> { { "version", mod.Version } });
using (var stream = _httpClient.PostStreamSync(url, content))
{
@ -259,11 +258,11 @@ namespace Wabbajack.NexusApi
}
public static IEnumerable<SlideShowItem> CachedSlideShow
public static IEnumerable<UI.Slide> CachedSlideShow
{
get
{
if (!Directory.Exists(Consts.NexusCacheDirectory)) return new SlideShowItem[]{};
if (!Directory.Exists(Consts.NexusCacheDirectory)) return new UI.Slide[] { };
return Directory.EnumerateFiles(Consts.NexusCacheDirectory)
.Where(f => f.EndsWith(".json"))
@ -281,14 +280,7 @@ namespace Wabbajack.NexusApi
})
.Where(m => m != null)
.Where(m => m._internal_version == CACHED_VERSION_NUMBER && m.picture_url != null)
.Select(m => new SlideShowItem
{
ImageURL = m.picture_url,
ModName = FixupSummary(m.name),
AuthorName = FixupSummary(m.author),
ModURL = GetModURL(m.game_name, m.mod_id),
ModSummary = FixupSummary(m.summary)
});
.Select(m => new UI.Slide(m.name,m.mod_id,m.summary,m.author,m.contains_adult_content,GetModURL(m.game_name,m.mod_id),m.picture_url));
}
}
@ -297,18 +289,6 @@ namespace Wabbajack.NexusApi
{
public string URI { get; set; }
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
#endregion
}
}

View File

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Common;
namespace Wabbajack.NexusApi
{

View File

@ -46,7 +46,10 @@ namespace Wabbajack
public void Build(Compiler c, ModList lst)
{
Text($"### {lst.Name} - Installation Summary");
Text($"### {lst.Name} by {lst.Author} - Installation Summary");
Text(lst.Description);
Text($"#### Website:");
NoWrapText($"[{lst.Website}]({lst.Website})");
var readme_file = Path.Combine(c.MO2ProfileDir, "readme.md");
if (File.Exists(readme_file))
@ -101,6 +104,9 @@ namespace Wabbajack
foreach (var directive in files.OrderBy(f => f.GetType().Name).ThenByDescending(f => f.To))
switch (directive)
{
case PropertyFile i:
NoWrapText($"* `{i.SourceDataID}` as a `{Enum.GetName(typeof(PropertyType),i.Type)}`");
break;
case FromArchive f:
//NoWrapText($"* `{f.To}` from `{f.FullPath}`");
break;

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
Wabbajack/UI/Icons/next.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -0,0 +1,173 @@
<Window x:Class="Wabbajack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wabbajack"
mc:Ignorable="d"
Title="Wabbajack"
MinWidth="1280" MinHeight="960"
Width="1280" Height="960"
Style="{StaticResource {x:Type Window}}" Icon="Icons/wabbajack.ico" WindowStyle="ToolWindow"
ResizeMode="CanResize"
Closing="Window_Closing">
<Viewbox Stretch="Uniform">
<!--<Grid Width="1280" Height="960">-->
<Grid Margin="4,0,4,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="435"/>
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
<RowDefinition Height="320"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="640"/>
<ColumnDefinition Width="640"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0, 8, 0, 8">
<TextBlock Text="{Binding Mode}" FontSize="16" FontWeight="Bold" />
<TextBlock Text=" : " FontSize="16" />
<TextBlock Text="{Binding ModListName}" FontSize="16" />
</StackPanel>
<!-- Properties -->
<Grid Grid.Row="1" Grid.Column="0" Margin="0,0,2,4" Name="PropertyCompilerGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Margin="0,0,0,4" Source="{Binding SplashScreenImage}" Stretch="Fill"/>
<Button Grid.Row="1" Height="30" Command="{Binding OpenModListPropertiesCommand}" IsEnabled="{Binding UIReady}">
<TextBlock Text="Modlist Properties" FontSize="15" FontWeight="Bold"/>
</Button>
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Margin="0,0,2,4" Name="PropertyInstallerGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Source="{Binding SplashScreenImage}" Stretch="Fill"/>
<Button Grid.Row="1" Height="30" Command="{Binding OpenReadme}" IsEnabled="{Binding HasReadme}">
<TextBlock Text="Open README" FontSize="15" FontWeight="Bold"/>
</Button>
</Grid>
<!-- End Properties -->
<!-- Slideshow -->
<Grid Grid.Row="1" Grid.Column="1" Margin="2,0,0,4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding SplashScreenModName}" Grid.Row="0" FontSize="30" FontWeight="Bold"/>
<TextBlock Text="{Binding SplashScreenAuthorName}" Grid.Row="1" FontSize="15" FontWeight="Bold"/>
<TextBlock Text="{Binding SplashScreenSummary}" TextWrapping="Wrap" Grid.Row="2" FontSize="15" FontWeight="Bold"/>
<Grid Grid.Row="3" VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="48"/>
</Grid.ColumnDefinitions>
<CheckBox Margin="0,10,0,0" Grid.Column="0" IsChecked="{Binding EnableSlideShow}" Name="EnableSlideShow">Enable the Slideshow</CheckBox>
<CheckBox Margin="4,10,0,0" Grid.Column="1" IsChecked="{Binding SplashShowNSFW}" Name="ShowNSFWContent">Show NSFW Mods in the Slideshow</CheckBox>
<Button HorizontalAlignment="Right" Height="30" Grid.Column="2" Command="{Binding SlideShowNextItem}" ToolTip="Spamming this button will result in problems">
<DockPanel>
<Image Source="{Binding NextIcon}" Stretch="Fill"/>
</DockPanel>
</Button>
</Grid>
<Button Height="30" Grid.Row="4" Command="{Binding VisitNexusSiteCommand}">
<TextBlock Text="View Nexus Site" FontSize="15" FontWeight="Bold"/>
</Button>
</Grid>
<!-- End Slideshow-->
<ProgressBar Grid.Row="2" Margin="1,0,1,0" Grid.Column="0" Grid.ColumnSpan="2" Value="{Binding QueueProgress}" Minimum="0" Maximum="100" Background="#444444" />
<!-- Log -->
<TextBlock Text="Log:" Grid.Row="3" FontSize="14" Margin="0, 16, 0, 8" />
<ListBox local:AutoScrollBehavior.ScrollOnNewItem="True" Margin="0,0,2,0" Grid.Row="4" ItemsSource="{Binding Log}" />
<!-- End Log -->
<!-- Location -->
<Grid HorizontalAlignment="Stretch" Margin="-4,10,2,10" Grid.Row="5" Grid.Column="0" Grid.RowSpan="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition MinHeight="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Label Grid.Row="0" Content="{Binding LocationLabel}" Grid.Column="0" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Location, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True, NotifyOnValidationError=True}" IsEnabled="{Binding UIReady}"/>
<Button Grid.Row="0" Content="Select" MinWidth="80" Grid.Column="2" Command="{Binding ChangePath}" IsEnabled="{Binding UIReady}"/>
<Label Grid.Row="2" Content="Download Location:" Grid.Column="0" />
<TextBox Grid.Row="2" Grid.Column="1" Text="{Binding DownloadLocation}" IsEnabled="{Binding UIReady}"/>
<Button Grid.Row="2" Content="Select" MinWidth="80" Grid.Column="2" Command="{Binding ChangeDownloadPath}" IsEnabled="{Binding UIReady}"/>
</Grid>
<!-- End Location -->
<!-- Work Queue Start -->
<TextBlock Text="Work Queue:" Grid.Row="3" Grid.Column="1" FontSize="14" Margin="2, 16, 0, 8" />
<ListBox Margin="2,0,0,0" Grid.Row="4" Grid.Column="1" ItemsSource="{Binding Status}" Width="Auto" HorizontalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ProgressBar Minimum="0" Maximum="100" Value="{Binding Progress, Mode=OneTime}" Width="100"
Grid.Column="0">
<ProgressBar.Style>
<Style TargetType="ProgressBar">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Progress}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ProgressBar.Style>
</ProgressBar>
<TextBlock Text=" CPU " Grid.Column="1" />
<TextBlock Text="{Binding ID}" Grid.Column="2" />
<TextBlock Text=" - " Grid.Column="3" />
<TextBlock Text="{Binding Msg}" Grid.Column="4" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<!-- Work Queue End-->
<Grid Margin="2,10,0,10" Grid.Row="5" Grid.RowSpan="2" Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Button Margin="0,0,0,4" Grid.Row="0" Visibility="{Binding ShowReportButton}" Command="{Binding ShowReportCommand}">
<TextBlock FontSize="13" FontWeight="Bold">View ModList Contents</TextBlock>
</Button>
<Button Margin="0,4,0,0" Grid.Row="1" Command="{Binding Begin}" IsEnabled="{Binding UIReady}">
<TextBlock FontSize="13" FontWeight="Bold">Begin</TextBlock>
</Button>
</Grid>
</Grid>
</Viewbox>
</Window>

View File

@ -0,0 +1,118 @@
using Alphaleonis.Win32.Filesystem;
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows;
using Wabbajack.Common;
using Application = System.Windows.Application;
using MessageBox = System.Windows.MessageBox;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private AppState _state;
public enum RunMode
{
Compile,
Install
}
public MainWindow(RunMode mode, string source)
{
var args = Environment.GetCommandLineArgs();
var DebugMode = false;
string MO2Folder = null, InstallFolder = null, MO2Profile = null;
InitializeComponent();
var context = new AppState(Dispatcher, TaskMode.BUILDING);
context.LogMsg($"Wabbajack Build - {ThisAssembly.Git.Sha}");
SetupHandlers(context);
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));
UIUtils.Dispatcher = Dispatcher;
_state._nexusSiteURL = "https://github.com/wabbajack-tools/wabbajack";
if (mode == RunMode.Compile)
{
PropertyCompilerGrid.Visibility = Visibility.Visible;
PropertyInstallerGrid.Visibility = Visibility.Hidden;
}
else
{
PropertyCompilerGrid.Visibility = Visibility.Hidden;
PropertyInstallerGrid.Visibility = Visibility.Visible;
}
new Thread(() =>
{
if (mode == RunMode.Compile)
{
Utils.Log("Compiler ready to execute");
context.Location = Path.GetDirectoryName(source);
context.LocationLabel = "MO2 Profile:";
}
else if (mode == RunMode.Install)
{
context.UIReady = false;
context.LocationLabel = "Installation Location:";
var modlist = Installer.LoadFromFile(source);
if (modlist == null)
{
MessageBox.Show("Invalid Modlist, or file not found.", "Invalid Modlist", MessageBoxButton.OK,
MessageBoxImage.Error);
Dispatcher.Invoke(() =>
{
context.Running = false;
ExitWhenClosing = false;
var window = new ModeSelectionWindow
{
ShowActivated = true
};
window.Show();
Close();
});
}
else
{
context.ConfigureForInstall(source, modlist);
}
}
context.UIReady = true;
}).Start();
}
private void SetupHandlers(AppState state)
{
_state = state;
AppDomain.CurrentDomain.UnhandledException += AppHandler;
}
private void AppHandler(object sender, UnhandledExceptionEventArgs e)
{
_state.LogMsg("Uncaught error:");
_state.LogMsg(((Exception)e.ExceptionObject).ExceptionToString());
}
internal bool ExitWhenClosing = true;
private void Window_Closing(object sender, CancelEventArgs e)
{
if (ExitWhenClosing)
Application.Current.Shutdown();
}
}
}

View File

@ -3,27 +3,27 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Style="{StaticResource {x:Type Window}}" Icon="square_transparent_icon.ico" WindowStyle="ToolWindow"
Style="{StaticResource {x:Type Window}}" Icon="Icons/wabbajack.ico" WindowStyle="ToolWindow"
xmlns:local="clr-namespace:Wabbajack"
mc:Ignorable="d"
Title="Wabbajack" Height="500" Width="800" ResizeMode="NoResize"
Closing="Close_Window">
<Grid Margin="20" ShowGridLines="False">
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="30"></ColumnDefinition>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="30"/>
<ColumnDefinition Width="30"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition></RowDefinition>
<RowDefinition Height="70"></RowDefinition>
<RowDefinition Height="70"></RowDefinition>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="70"/>
<RowDefinition Height="70"/>
</Grid.RowDefinitions>
<Image MouseLeftButtonDown="GitHub_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Name="GitHub"></Image>
<Image MouseLeftButtonDown="Patreon_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="1" Name="Patreon"></Image>
<Image MouseLeftButtonDown="Discord_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="2" Name="Discord"></Image>
<Image Grid.Row="1" Grid.ColumnSpan="3" Name="Banner"></Image>
<Image MouseLeftButtonDown="GitHub_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Name="GitHub"/>
<Image MouseLeftButtonDown="Patreon_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="1" Name="Patreon"/>
<Image MouseLeftButtonDown="Discord_MouseLeftButtonDown" Margin="5,0,0,0" Grid.Row="0" Grid.Column="2" Name="Discord"/>
<Image Grid.Row="1" Grid.ColumnSpan="3" Name="Banner" Stretch="Uniform" Margin="2,0,2,0"/>
<Button Name="InstallModlist" Grid.ColumnSpan="3" Grid.Row="2" Margin="2" Click="InstallModlist_Click">
<TextBlock FontSize="40">Install a ModList</TextBlock>
</Button>

View File

@ -1,19 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Wabbajack.Common;
using static Wabbajack.MainWindow;
@ -27,13 +15,13 @@ namespace Wabbajack
public ModeSelectionWindow()
{
InitializeComponent();
var bannerImage = UIUtils.BitmapImageFromResource("Wabbajack.banner_small.png");
var bannerImage = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner_small.png");
Banner.Source = bannerImage;
var patreonIcon = UIUtils.BitmapImageFromResource("Wabbajack.Icons.patreon.png");
var patreonIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.patreon_light.png");
Patreon.Source = patreonIcon;
var githubIcon = UIUtils.BitmapImageFromResource("Wabbajack.Icons.github.png");
var githubIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.github_light.png");
GitHub.Source = githubIcon;
var discordIcon = UIUtils.BitmapImageFromResource("Wabbajack.Icons.discord.png");
var discordIcon = UIUtils.BitmapImageFromResource("Wabbajack.UI.Icons.discord.png");
Discord.Source = discordIcon;
}

View File

@ -0,0 +1,61 @@
<Window x:Class="Wabbajack.ModlistPropertiesWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wabbajack"
mc:Ignorable="d"
Title="Wabbajack (Modlist Properties)" Height="600" Width="900"
Style="{StaticResource {x:Type Window}}" Icon="Icons/wabbajack.ico" WindowStyle="ToolWindow"
ResizeMode="NoResize"
Closing="Window_Closing">
<Grid Margin="8">
<Grid.RowDefinitions>
<RowDefinition Height="300"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Grid.Row="0" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Image Grid.Row="0" Stretch="Fill" Name="SplashScreenProperty"/>
<Button Height="30" Grid.Row="1" Click="SetSplashScreen_Click" ToolTip="Use a 1400x900 png file for the best results">
<TextBlock Text="Change splash screen image" FontSize="15" FontWeight="Bold"/>
</Button>
</Grid>
<Grid Grid.Column="1" Grid.Row="0" Margin="0 5 0 5">
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="ModList Name" Name="ModlistNameProperty" ToolTip="Change the name of your ModList" MaxLength="50" AcceptsTab="False" FontSize="15" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBox Grid.Row="1" Text="Author" Name="ModlistAuthorProperty" ToolTip="Change the name of the Author" MaxLength="50" AcceptsTab="False" FontSize="15" Margin="0 0 0 5" />
<TextBox Grid.Row="2" Text="Description (700 characters max)" Name="ModlistDescriptionProperty" ToolTip="Change the description" MaxLength="700" AcceptsReturn="True" TextWrapping="Wrap" AcceptsTab="False" FontSize="15" Margin="0,0,0,0"/>
</Grid>
<TextBox Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Text="Website" Name="ModlistWebsiteProperty" ToolTip="Change the website" MaxLength="80" AcceptsReturn="False" AcceptsTab="False" FontSize="15"/>
<Grid Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Height="30" Margin="0,10,0,-10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Height="30" Grid.ColumnSpan="3" Text="Readme path" Name="ModlistReadmeProperty" AcceptsReturn="False" AcceptsTab="False" FontSize="15"/>
<Button Height="30" Grid.Column="2" Content="Choose" Click="ChooseReadme_Click"/>
</Grid>
<Button Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2" Click="SaveProperties_Click" Margin="0,160,0,-160" Height="30" VerticalAlignment="Bottom">
<TextBlock Text="Save" FontSize="15" FontWeight="Bold"/>
</Button>
</Grid>
</Window>

View File

@ -0,0 +1,79 @@
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Media.Imaging;
namespace Wabbajack
{
/// <summary>
/// Interaction logic for ModlistPropertiesWindow.xaml
/// </summary>
public partial class ModlistPropertiesWindow : Window
{
internal string newBannerFile;
internal readonly AppState state;
internal ModlistPropertiesWindow(AppState _state)
{
InitializeComponent();
var bannerImage = UIUtils.BitmapImageFromResource("Wabbajack.UI.banner.png");
SplashScreenProperty.Source = bannerImage;
newBannerFile = null;
state = _state;
}
private void Window_Closing(object sender, CancelEventArgs e)
{
//Hide();
}
private void SetSplashScreen_Click(object sender, RoutedEventArgs e)
{
var file = UIUtils.OpenFileDialog("Banner image|*.png");
if (file != null)
{
newBannerFile = file;
SplashScreenProperty.Source = new BitmapImage(new Uri(file));
}
}
private void SaveProperties_Click(object sender, RoutedEventArgs e)
{
if (state.UIReady)
{
if (newBannerFile != null)
{
BitmapImage splashScreen = new BitmapImage(new Uri(newBannerFile));
state.newImagePath = newBannerFile;
state.SplashScreenImage = splashScreen;
}
state.SplashScreenModName = ModlistNameProperty.Text;
state.SplashScreenSummary = ModlistDescriptionProperty.Text;
state.SplashScreenAuthorName = ModlistAuthorProperty.Text;
state._nexusSiteURL = ModlistWebsiteProperty.Text;
state.readmePath = ModlistReadmeProperty.Text;
state.ChangedProperties = true;
Hide();
}
}
public bool IsClosed { get; private set; }
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
IsClosed = true;
}
private void ChooseReadme_Click(object sender, RoutedEventArgs e)
{
var file = UIUtils.OpenFileDialog("Readme|*.txt");
if (file != null)
{
ModlistReadmeProperty.Text = file;
}
}
}
}

202
Wabbajack/UI/SlideShow.cs Normal file
View File

@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Wabbajack.Common;
using Wabbajack.NexusApi;
namespace Wabbajack.UI
{
public class Slide
{
public Slide(string modName, string modID, string modDescription, string modAuthor, bool isNSFW, string modUrl, string imageURL)
{
ModName = modName;
ModDescription = modDescription;
ModAuthor = modAuthor;
IsNSFW = isNSFW;
ModURL = modUrl;
ModID = modID;
ImageURL = imageURL;
}
public string ModName { get; }
public string ModDescription { get; }
public string ModAuthor { get; }
public bool IsNSFW { get; }
public string ModURL { get; }
public string ModID { get; }
public BitmapImage Image { get; set; }
public string ImageURL { get; }
}
internal class SlideShow
{
private readonly Random _random;
private Slide _lastSlide;
private const bool UseSync = false;
private const int MaxCacheSize = 10;
private readonly AppState _appState;
public SlideShow(AppState appState, bool checkCache)
{
SlideShowElements = new List<Slide>();
CachedSlides = new Dictionary<string, Slide>();
SlidesQueue = new Queue<Slide>();
_random = new Random();
_appState = appState;
if (!checkCache) return;
IEnumerable<Slide> files = NexusApiClient.CachedSlideShow;
IEnumerable<Slide> enumerable = files.ToList();
if (enumerable.Any())
{
SlideShowElements = enumerable.ToList();
}
}
public void PreloadSlideShow()
{
var turns = 0;
for (var i = 0; i < SlideShowElements.Count; i++)
{
if (turns >= 3)
break;
if (QueueRandomSlide(true, false))
turns++;
}
}
public void UpdateSlideShowItem()
{
if (!_appState.EnableSlideShow || !_appState.installing || SlidesQueue.Count==0) return;
var slide = SlidesQueue.Peek();
while (CachedSlides.Count >= MaxCacheSize)
{
var idx = _random.Next(0, SlideShowElements.Count);
var randomSlide = SlideShowElements[idx];
while (!CachedSlides.ContainsKey(randomSlide.ModID) || SlidesQueue.Contains(randomSlide))
{
idx = _random.Next(0, SlideShowElements.Count);
randomSlide = SlideShowElements[idx];
}
//if (SlidesQueue.Contains(randomSlide)) continue;
CachedSlides.Remove(randomSlide.ModID);
if (AppState.GcCollect)
GC.Collect();
}
if (!slide.IsNSFW || (slide.IsNSFW && ShowNSFW))
{
_appState.SplashScreenImage = _appState._noneImage;
if (slide.ImageURL != null && slide.Image != null)
{
_appState.dispatcher.Invoke(() =>
{
if (!CachedSlides.ContainsKey(slide.ModID)) return;
_appState.SplashScreenImage = slide.Image;
});
}
_appState.SplashScreenModName = slide.ModName;
_appState.SplashScreenAuthorName = slide.ModAuthor;
_appState.SplashScreenSummary = slide.ModDescription;
_appState._nexusSiteURL = slide.ModURL;
}
_appState.lastSlideShowUpdate = DateTime.Now;
SlidesQueue.Dequeue();
QueueRandomSlide(false, true);
}
private void CacheImage(Slide slide)
{
using (var ms = new MemoryStream())
{
if (UseSync)
{
_appState.dispatcher.Invoke(() =>
{
using (var stream = new HttpClient().GetStreamSync(slide.ImageURL))
stream.CopyTo(ms);
});
}
else
{
using (Task<Stream> stream = new HttpClient().GetStreamAsync(slide.ImageURL))
{
stream.Wait();
stream.Result.CopyTo(ms);
}
}
ms.Seek(0, SeekOrigin.Begin);
_appState.dispatcher.Invoke(() =>
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = ms;
image.EndInit();
image.Freeze();
slide.Image = image;
});
}
}
private bool QueueRandomSlide(bool init, bool checkLast)
{
var result = false;
var idx = _random.Next(0, SlideShowElements.Count);
var element = SlideShowElements[idx];
if (checkLast)
{
while (element == _lastSlide && (!element.IsNSFW || (element.IsNSFW && ShowNSFW)))
{
idx = _random.Next(0, SlideShowElements.Count);
element = SlideShowElements[idx];
}
}
if (element.ImageURL == null)
{
if (!init) SlidesQueue.Enqueue(element);
}
else
{
if (!CachedSlides.ContainsKey(element.ModID))
{
CacheImage(element);
CachedSlides.Add(element.ModID, element);
SlidesQueue.Enqueue(element);
result = true;
}
else
{
if(!init) SlidesQueue.Enqueue(element);
}
_lastSlide = element;
}
return result;
}
public bool ShowNSFW { get; set; }
public List<Slide> SlideShowElements { get; set; }
public Dictionary<string, Slide> CachedSlides { get; }
public Queue<Slide> SlidesQueue { get; }
}
}

View File

@ -0,0 +1,15 @@
<Window x:Class="Wabbajack.UI.TextViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Wabbajack.UI"
Style="{StaticResource {x:Type Window}}"
mc:Ignorable="d"
Icon="Icons/wabbajack.ico"
WindowStyle="ToolWindow"
Title="TextViewer" Height="450" Width="800">
<Grid>
<TextBlock FontSize="20" Name="TextBlock" TextWrapping="Wrap"/>
</Grid>
</Window>

View File

@ -0,0 +1,14 @@
using System.Windows;
namespace Wabbajack.UI
{
public partial class TextViewer : Window
{
public TextViewer(string text, string title)
{
InitializeComponent();
TextBlock.Text = text;
Title = title;
}
}
}

View File

@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.WindowsAPICodePack.Dialogs;
using System;
using System.Reflection;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Microsoft.WindowsAPICodePack.Dialogs;
using Wabbajack.Common;
namespace Wabbajack
{

View File

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 190 KiB

View File

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 178 KiB

BIN
Wabbajack/UI/none.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using Wabbajack.Common;
namespace Wabbajack.Updater

View File

@ -1,11 +1,4 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
using YamlDotNet.Serialization;
using System.Collections.Generic;
namespace Wabbajack.Validation
{

View File

@ -4,8 +4,6 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
@ -115,7 +113,7 @@ namespace Wabbajack.Validation
var nexus_mod_permissions = modlist.Archives
.OfType<NexusMod>()
.PMap(a => (a.Hash, FilePermissions(a), a))
.ToDictionary(a => a.Hash, a => new { permissions = a.Item2, archive = a.a});
.ToDictionary(a => a.Hash, a => new { permissions = a.Item2, archive = a.a });
modlist.Directives
.OfType<PatchedFromArchive>()

30
Wabbajack/ViewModel.cs Normal file
View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace Wabbajack
{
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
protected void RaiseAndSetIfChanged<T>(
ref T item,
T newItem,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(item, newItem)) return;
item = newItem;
this.RaisePropertyChanged(propertyName);
}
}
}

View File

@ -54,7 +54,7 @@
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>square_transparent_icon.ico</ApplicationIcon>
<ApplicationIcon>UI\Icons\wabbajack.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug %28no commandargs%29|AnyCPU'">
<DebugSymbols>true</DebugSymbols>
@ -186,20 +186,28 @@
</ApplicationDefinition>
<Compile Include="Data.cs" />
<Compile Include="LambdaCommand.cs" />
<Compile Include="UI\ModlistPropertiesWindow.xaml.cs">
<DependentUpon>ModlistPropertiesWindow.xaml</DependentUpon>
</Compile>
<Compile Include="NexusApi\Dtos.cs" />
<Compile Include="NexusApi\NexusApiUtils.cs" />
<Compile Include="ModeSelectionWindow.xaml.cs">
<Compile Include="UI\ModeSelectionWindow.xaml.cs">
<DependentUpon>ModeSelectionWindow.xaml</DependentUpon>
</Compile>
<Compile Include="ReportBuilder.cs" />
<Compile Include="Themes\LeftMarginMultiplierConverter.cs" />
<Compile Include="Themes\TreeViewItemExtensions.cs" />
<Compile Include="UIUtils.cs" />
<Compile Include="UI\SlideShow.cs" />
<Compile Include="UI\TextViewer.xaml.cs">
<DependentUpon>TextViewer.xaml</DependentUpon>
</Compile>
<Compile Include="UI\UIUtils.cs" />
<Compile Include="Updater\CheckForUpdates.cs" />
<Compile Include="Validation\DTOs.cs" />
<Compile Include="Validation\ValidateModlist.cs" />
<Compile Include="ViewModel.cs" />
<Compile Include="zEditIntegration.cs" />
<Page Include="MainWindow.xaml">
<Page Include="UI\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
@ -211,11 +219,15 @@
<Compile Include="AutoScrollBehavior.cs" />
<Compile Include="Compiler.cs" />
<Compile Include="Installer.cs" />
<Compile Include="MainWindow.xaml.cs">
<Compile Include="UI\MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Page Include="ModeSelectionWindow.xaml">
<Page Include="UI\ModeSelectionWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="UI\ModlistPropertiesWindow.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@ -223,6 +235,10 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="UI\TextViewer.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Compile Include="NexusApi\NexusApi.cs" />
@ -279,7 +295,7 @@
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<Resource Include="square_transparent_icon.ico" />
<Resource Include="UI\Icons\wabbajack.ico" />
</ItemGroup>
<ItemGroup>
<None Include="css.css" />
@ -288,17 +304,26 @@
<EmbeddedResource Include="css-min.css" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="banner.png" />
<EmbeddedResource Include="UI\banner.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="banner_small.png" />
<EmbeddedResource Include="UI\banner_small.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Icons\discord.png" />
<EmbeddedResource Include="Icons\github.png" />
<EmbeddedResource Include="Icons\patreon.png" />
<EmbeddedResource Include="UI\Icons\discord.png" />
<EmbeddedResource Include="UI\Icons\github.png" />
<EmbeddedResource Include="UI\Icons\patreon.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UI\Icons\next.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UI\none.jpg" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="UI\Icons\github_light.png" />
<EmbeddedResource Include="UI\Icons\patreon_light.png" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\packages\Fody.5.1.1\build\Fody.targets" Condition="Exists('..\packages\Fody.5.1.1\build\Fody.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">

View File

@ -1,12 +1,8 @@
using System;
using Alphaleonis.Win32.Filesystem;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
using VFS;
using Wabbajack.Common;
using Directory = Alphaleonis.Win32.Filesystem.Directory;
using File = Alphaleonis.Win32.Filesystem.File;
@ -49,7 +45,8 @@ namespace Wabbajack
.GroupBy(f => (f.name, f.filename));
merges.Where(m => m.Count() > 1)
.Do(m => {
.Do(m =>
{
Utils.Warning(
$"WARNING, you have two patches named {m.Key.name}\\{m.Key.filename} in your zEdit profiles. We'll pick one at random, this probably isn't what you want.");
});