The main wabbajack app compiles

This commit is contained in:
Timothy Baldridge 2020-03-28 14:04:22 -06:00
parent b605879d6a
commit c01ed4375c
18 changed files with 142 additions and 128 deletions

View File

@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@ -362,6 +363,16 @@ namespace Wabbajack.Common
{
return string.Compare(_path, other._path, StringComparison.Ordinal);
}
public string ReadAllText()
{
return File.ReadAllText(_path);
}
public FileStream OpenShared()
{
return File.Open(_path, FileMode.Open, FileAccess.Read);
}
}
public struct RelativePath : IPath, IEquatable<RelativePath>, IComparable<RelativePath>

View File

@ -937,15 +937,15 @@ namespace Wabbajack.Common
return ErrorResponse.Success;
}
public static IErrorResponse IsDirectoryPathValid(string path)
public static IErrorResponse IsDirectoryPathValid(AbsolutePath path)
{
if (string.IsNullOrWhiteSpace(path))
if (path == default)
{
return ErrorResponse.Fail("Path is empty");
}
try
{
var fi = new System.IO.DirectoryInfo(path);
var fi = new System.IO.DirectoryInfo((string)path);
}
catch (ArgumentException ex)
{

View File

@ -83,6 +83,16 @@ namespace Wabbajack.Lib.ModListRegistry
return metadata.OrderBy(m => (m.ValidationSummary?.HasFailures ?? false ? 1 : 0, m.Title)).ToList();
}
public bool NeedsDownload(AbsolutePath modlistPath)
{
if (!modlistPath.Exists) return true;
if (DownloadMetadata?.Hash == null)
{
return true;
}
return DownloadMetadata.Hash != modlistPath.FileHashCached(true);
}
}
public class DownloadMetadata

View File

@ -28,23 +28,22 @@ namespace Wabbajack
public static bool TryLoadTypicalSettings(out MainSettings settings)
{
if (!File.Exists(Consts.SettingsFile))
if (!Consts.SettingsFile.Exists)
{
settings = default;
return false;
}
// Version check
settings = JsonConvert.DeserializeObject<MainSettings>(File.ReadAllText(Consts.SettingsFile));
settings = JsonConvert.DeserializeObject<MainSettings>(Consts.SettingsFile.ReadAllText());
if (settings.Version == Consts.SettingsVersion)
return true;
var backup = Consts.SettingsFile + "-backup.json";
if(File.Exists(backup))
File.Delete(backup);
var backup = (AbsolutePath)(Consts.SettingsFile + "-backup.json");
backup.Delete();
File.Copy(Consts.SettingsFile, backup);
File.Delete(Consts.SettingsFile);
Consts.SettingsFile.CopyTo(backup);
Consts.SettingsFile.Delete();
settings = default;
return false;
@ -59,27 +58,27 @@ namespace Wabbajack
//settings._saveSignal.OnCompleted();
//await settings._saveSignal;
File.WriteAllText(Consts.SettingsFile, JsonConvert.SerializeObject(settings, Formatting.Indented));
Consts.SettingsFile.WriteAllText(JsonConvert.SerializeObject(settings, Formatting.Indented));
}
}
public class InstallerSettings
{
public string LastInstalledListLocation { get; set; }
public Dictionary<string, Mo2ModlistInstallationSettings> Mo2ModlistSettings { get; } = new Dictionary<string, Mo2ModlistInstallationSettings>();
public AbsolutePath LastInstalledListLocation { get; set; }
public Dictionary<AbsolutePath, Mo2ModlistInstallationSettings> Mo2ModlistSettings { get; } = new Dictionary<AbsolutePath, Mo2ModlistInstallationSettings>();
}
public class Mo2ModlistInstallationSettings
{
public string InstallationLocation { get; set; }
public string DownloadLocation { get; set; }
public AbsolutePath InstallationLocation { get; set; }
public AbsolutePath DownloadLocation { get; set; }
public bool AutomaticallyOverrideExistingInstall { get; set; }
}
public class CompilerSettings
{
public ModManager LastCompiledModManager { get; set; }
public string OutputLocation { get; set; }
public AbsolutePath OutputLocation { get; set; }
public MO2CompilationSettings MO2Compilation { get; } = new MO2CompilationSettings();
public VortexCompilationSettings VortexCompilation { get; } = new VortexCompilationSettings();
}
@ -118,14 +117,14 @@ namespace Wabbajack
public string Website { get; set; }
public bool ReadmeIsWebsite { get; set; }
public string Readme { get; set; }
public string SplashScreen { get; set; }
public AbsolutePath SplashScreen { get; set; }
}
public class MO2CompilationSettings
{
public string DownloadLocation { get; set; }
public string LastCompiledProfileLocation { get; set; }
public Dictionary<string, CompilationModlistSettings> ModlistSettings { get; } = new Dictionary<string, CompilationModlistSettings>();
public AbsolutePath DownloadLocation { get; set; }
public AbsolutePath LastCompiledProfileLocation { get; set; }
public Dictionary<AbsolutePath, CompilationModlistSettings> ModlistSettings { get; } = new Dictionary<AbsolutePath, CompilationModlistSettings>();
}
public class VortexCompilationSettings

View File

@ -8,6 +8,7 @@ using System.IO;
using System.Linq;
using System.Reactive.Linq;
using System.Windows.Input;
using Wabbajack.Common;
using Wabbajack.Lib;
namespace Wabbajack
@ -35,7 +36,7 @@ namespace Wabbajack
public ICommand SetTargetPathCommand { get; set; }
[Reactive]
public string TargetPath { get; set; }
public AbsolutePath TargetPath { get; set; }
[Reactive]
public string PromptTitle { get; set; }
@ -81,7 +82,7 @@ namespace Wabbajack
// Dont want to debounce the initial value, because we know it's null
.Skip(1)
.Debounce(TimeSpan.FromMilliseconds(200), RxApp.MainThreadScheduler)
.StartWith(default(string)),
.StartWith(default(AbsolutePath)),
resultSelector: (existsOption, type, path) => (ExistsOption: existsOption, Type: type, Path: path))
.StartWith((ExistsOption: ExistCheckOption, Type: PathType, Path: TargetPath))
.Replay(1)
@ -97,7 +98,7 @@ namespace Wabbajack
case CheckOptions.Off:
return false;
case CheckOptions.IfPathNotEmpty:
return !string.IsNullOrWhiteSpace(t.Path);
return t.Path != null;
case CheckOptions.On:
return true;
default:
@ -125,7 +126,7 @@ namespace Wabbajack
switch (t.ExistsOption)
{
case CheckOptions.IfPathNotEmpty:
if (string.IsNullOrWhiteSpace(t.Path)) return false;
if (t.Path == default) return false;
break;
case CheckOptions.On:
break;
@ -136,11 +137,11 @@ namespace Wabbajack
switch (t.Type)
{
case PathTypeOptions.Either:
return File.Exists(t.Path) || Directory.Exists(t.Path);
return t.Path.Exists;
case PathTypeOptions.File:
return File.Exists(t.Path);
return t.Path.IsFile;
case PathTypeOptions.Folder:
return Directory.Exists(t.Path);
return t.Path.IsDirectory;
case PathTypeOptions.Off:
default:
return false;
@ -171,7 +172,7 @@ namespace Wabbajack
case CheckOptions.Off:
return true;
case CheckOptions.IfPathNotEmpty:
if (string.IsNullOrWhiteSpace(target)) return true;
if (target == default) return true;
break;
case CheckOptions.On:
break;
@ -181,10 +182,7 @@ namespace Wabbajack
try
{
var extension = Path.GetExtension(target);
if (extension == null || !extension.StartsWith(".")) return false;
extension = extension.Substring(1);
if (!query.Any(filter => filter.Extensions.Any(ext => string.Equals(ext, extension)))) return false;
if (!query.Any(filter => filter.Extensions.Any(ext => new Extension(ext) == target.Extension))) return false;
}
catch (ArgumentException)
{
@ -250,23 +248,16 @@ namespace Wabbajack
return ReactiveCommand.Create(
execute: () =>
{
string dirPath;
if (File.Exists(TargetPath))
{
dirPath = Path.GetDirectoryName(TargetPath);
}
else
{
dirPath = TargetPath;
}
AbsolutePath dirPath;
dirPath = TargetPath.Exists ? TargetPath.Parent : TargetPath;
var dlg = new CommonOpenFileDialog
{
Title = PromptTitle,
IsFolderPicker = PathType == PathTypeOptions.Folder,
InitialDirectory = dirPath,
InitialDirectory = (string)dirPath,
AddToMostRecentlyUsedList = false,
AllowNonFileSystemItems = false,
DefaultDirectory = dirPath,
DefaultDirectory = (string)dirPath,
EnsureFileExists = true,
EnsurePathExists = true,
EnsureReadOnly = false,
@ -279,7 +270,7 @@ namespace Wabbajack
dlg.Filters.Add(filter);
}
if (dlg.ShowDialog() != CommonFileDialogResult.Ok) return;
TargetPath = dlg.FileName;
TargetPath = (AbsolutePath)dlg.FileName;
}, canExecute: canExecute);
}
}

View File

@ -30,16 +30,16 @@ namespace Wabbajack
return img;
}
public static bool TryGetBitmapImageFromFile(string path, out BitmapImage bitmapImage)
public static bool TryGetBitmapImageFromFile(AbsolutePath path, out BitmapImage bitmapImage)
{
try
{
if (!File.Exists(path))
if (!path.Exists)
{
bitmapImage = default;
return false;
}
bitmapImage = new BitmapImage(new Uri(path, UriKind.RelativeOrAbsolute));
bitmapImage = new BitmapImage(new Uri((string)path, UriKind.RelativeOrAbsolute));
return true;
}
catch (Exception)
@ -49,14 +49,14 @@ namespace Wabbajack
}
}
public static string OpenFileDialog(string filter, string initialDirectory = null)
public static AbsolutePath OpenFileDialog(string filter, string initialDirectory = null)
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = filter;
ofd.InitialDirectory = initialDirectory;
if (ofd.ShowDialog() == DialogResult.OK)
return ofd.FileName;
return null;
return (AbsolutePath)ofd.FileName;
return default;
}
public static IDisposable BindCpuStatus(IObservable<CPUStatus> status, ObservableCollectionExtended<CPUDisplayVM> list)

View File

@ -102,8 +102,10 @@ namespace Wabbajack
{
case ModManager.MO2:
return new MO2CompilerVM(this);
/*
case ModManager.Vortex:
return new VortexCompilerVM(this);
*/
default:
return null;
}
@ -128,12 +130,8 @@ namespace Wabbajack
.ObserveOnGuiThread()
.Select(path =>
{
if (string.IsNullOrWhiteSpace(path)) return UIUtils.BitmapImageFromResource("Resources/Wabba_Mouth_No_Text.png");
if (UIUtils.TryGetBitmapImageFromFile(path, out var image))
{
return image;
}
return null;
if (path == default) return UIUtils.BitmapImageFromResource("Resources/Wabba_Mouth_No_Text.png");
return UIUtils.TryGetBitmapImageFromFile(path, out var image) ? image : null;
})
.ToGuiProperty(this, nameof(Image));
@ -233,14 +231,14 @@ namespace Wabbajack
{
if (Completed?.Failed ?? false)
{
Process.Start("explorer.exe", Utils.LogFolder);
Process.Start("explorer.exe", (string)Utils.LogFolder);
}
else
{
Process.Start("explorer.exe",
string.IsNullOrWhiteSpace(OutputLocation.TargetPath)
? Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location)
: OutputLocation.TargetPath);
(string)(OutputLocation.TargetPath == default
? AbsolutePath.EntryPoint
: OutputLocation.TargetPath));
}
});

View File

@ -18,8 +18,8 @@ namespace Wabbajack
private readonly MO2CompilationSettings _settings;
private readonly ObservableAsPropertyHelper<string> _mo2Folder;
public string Mo2Folder => _mo2Folder.Value;
private readonly ObservableAsPropertyHelper<AbsolutePath> _mo2Folder;
public AbsolutePath Mo2Folder => _mo2Folder.Value;
private readonly ObservableAsPropertyHelper<string> _moProfile;
public string MOProfile => _moProfile.Value;
@ -60,12 +60,12 @@ namespace Wabbajack
{
try
{
var profileFolder = Path.GetDirectoryName(loc);
return Path.GetDirectoryName(Path.GetDirectoryName(profileFolder));
var profileFolder = loc.Parent;
return profileFolder.Parent.Parent;
}
catch (Exception)
{
return null;
return default;
}
})
.ToGuiProperty(this, nameof(Mo2Folder));
@ -74,8 +74,7 @@ namespace Wabbajack
{
try
{
var profileFolder = Path.GetDirectoryName(loc);
return Path.GetFileName(profileFolder);
return (string)loc.Parent.FileName;
}
catch (Exception)
{
@ -86,9 +85,9 @@ namespace Wabbajack
// Wire missing Mo2Folder to signal error state for ModList Location
ModListLocation.AdditionalError = this.WhenAny(x => x.Mo2Folder)
.Select<string, IErrorResponse>(moFolder =>
.Select<AbsolutePath, IErrorResponse>(moFolder =>
{
if (Directory.Exists(moFolder)) return ErrorResponse.Success;
if (moFolder.IsDirectory) return ErrorResponse.Success;
return ErrorResponse.Fail($"MO2 folder could not be located from the given ModList location.{Environment.NewLine}Make sure your ModList is inside a valid MO2 distribution.");
});
@ -132,7 +131,7 @@ namespace Wabbajack
// Load settings
_settings = parent.MWVM.Settings.Compiler.MO2Compilation;
ModListLocation.TargetPath = _settings.LastCompiledProfileLocation;
if (!string.IsNullOrWhiteSpace(_settings.DownloadLocation))
if (_settings.DownloadLocation != default)
{
DownloadLocation.TargetPath = _settings.DownloadLocation;
}
@ -143,7 +142,7 @@ namespace Wabbajack
// If Mo2 folder changes and download location is empty, set it for convenience
this.WhenAny(x => x.Mo2Folder)
.DelayInitial(TimeSpan.FromMilliseconds(100), RxApp.MainThreadScheduler)
.Where(x => Directory.Exists(x))
.Where(x => x.IsDirectory)
.FlowSwitch(
(this).WhenAny(x => x.DownloadLocation.Exists)
.Invert())
@ -165,14 +164,14 @@ namespace Wabbajack
public async Task<GetResponse<ModList>> Compile()
{
string outputFile;
if (string.IsNullOrWhiteSpace(Parent.OutputLocation.TargetPath))
AbsolutePath outputFile;
if (Parent.OutputLocation.TargetPath == default)
{
outputFile = MOProfile + Consts.ModListExtension;
outputFile = (MOProfile + Consts.ModListExtension).RelativeTo(AbsolutePath.EntryPoint);
}
else
{
outputFile = Path.Combine(Parent.OutputLocation.TargetPath, MOProfile + Consts.ModListExtension);
outputFile = Parent.OutputLocation.TargetPath.Combine(MOProfile + Consts.ModListExtension);
}
try
@ -187,7 +186,7 @@ namespace Wabbajack
ModListDescription = ModlistSettings.Description,
ModListImage = ModlistSettings.ImagePath.TargetPath,
ModListWebsite = ModlistSettings.Website,
ModListReadme = ModlistSettings.ReadmeIsWebsite ? ModlistSettings.ReadmeWebsite : ModlistSettings.ReadmeFilePath.TargetPath,
//ModListReadme = ModlistSettings.ReadmeIsWebsite ? ModlistSettings.ReadmeWebsite : ModlistSettings.ReadmeFilePath.TargetPath,
ReadmeIsWebsite = ModlistSettings.ReadmeIsWebsite,
MO2DownloadsFolder = DownloadLocation.TargetPath,
})

View File

@ -80,11 +80,13 @@ namespace Wabbajack
ReadmeIsWebsite = _settings.ReadmeIsWebsite;
if (ReadmeIsWebsite)
{
ReadmeWebsite = _settings.Readme;
// TODO README
// ReadmeWebsite = _settings.Readme;
}
else
{
ReadmeFilePath.TargetPath = _settings.Readme;
// TODO README
//ReadmeFilePath.TargetPath = _settings.Readme;
}
ImagePath.TargetPath = _settings.SplashScreen;
Website = _settings.Website;
@ -102,7 +104,8 @@ namespace Wabbajack
}
else
{
_settings.Readme = ReadmeFilePath.TargetPath;
// TODO README
//_settings.Readme = ReadmeFilePath.TargetPath;
}
_settings.SplashScreen = ImagePath.TargetPath;
_settings.Website = Website;

View File

@ -31,7 +31,7 @@ namespace Wabbajack
private readonly ObservableAsPropertyHelper<bool> _Exists;
public bool Exists => _Exists.Value;
public string Location { get; }
public AbsolutePath Location { get; }
[Reactive]
public Percent ProgressPercent { get; private set; }
@ -52,7 +52,7 @@ namespace Wabbajack
{
_parent = parent;
Metadata = metadata;
Location = Path.Combine(Consts.ModListDownloadFolder, Metadata.Links.MachineURL + Consts.ModListExtension);
Location = Consts.ModListDownloadFolder.RelativeTo(AbsolutePath.EntryPoint).Combine(Metadata.Links.MachineURL + Consts.ModListExtension);
IsBroken = metadata.ValidationSummary.HasFailures;
OpenWebsiteCommand = ReactiveCommand.Create(() => Utils.OpenWebsite(new Uri($"https://www.wabbajack.org/modlist/{Metadata.Links.MachineURL}")));
ExecuteCommand = ReactiveCommand.CreateFromObservable<Unit, Unit>(
@ -83,7 +83,7 @@ namespace Wabbajack
return false;
}
// Return an updated check on exists
return File.Exists(Location);
return Location.Exists;
}
return exists;
})
@ -92,7 +92,7 @@ namespace Wabbajack
.ObserveOnGuiThread()
.Select(_ =>
{
_parent.MWVM.OpenInstaller(Path.GetFullPath(Location));
_parent.MWVM.OpenInstaller(Location);
// Wait for modlist member to be filled, then open its readme
return _parent.MWVM.Installer.Value.WhenAny(x => x.ModList)

View File

@ -128,8 +128,8 @@ namespace Wabbajack
{
case ModManager.MO2:
return new MO2InstallerVM(this);
case ModManager.Vortex:
return new VortexInstallerVM(this);
/*case ModManager.Vortex:
return new VortexInstallerVM(this);*/
default:
return null;
}
@ -162,7 +162,7 @@ namespace Wabbajack
resultSelector: (path, active) => (path, active))
.Select(x =>
{
if (!x.active) return default(string);
if (!x.active) return default;
return x.path;
})
// Throttle slightly so changes happen more atomically
@ -175,8 +175,8 @@ namespace Wabbajack
// Convert from active path to modlist VM
.Select(modListPath =>
{
if (modListPath == null) return default(ModListVM);
if (!File.Exists(modListPath)) return default(ModListVM);
if (modListPath == default) return default;
if (!modListPath.Exists) return default;
return new ModListVM(modListPath);
})
.DisposeOld()

View File

@ -79,9 +79,9 @@ namespace Wabbajack
.Skip(1) // Don't do it initially
.Subscribe(installPath =>
{
if (string.IsNullOrWhiteSpace(DownloadLocation.TargetPath))
if (DownloadLocation.TargetPath == default)
{
DownloadLocation.TargetPath = Path.Combine(installPath, "downloads");
DownloadLocation.TargetPath = installPath.Combine("downloads");
}
})
.DisposeWith(CompositeDisposable);
@ -142,7 +142,7 @@ namespace Wabbajack
public void AfterInstallNavigation()
{
Process.Start("explorer.exe", Location.TargetPath);
Process.Start("explorer.exe", (string)Location.TargetPath);
}
public async Task<bool> Install()

View File

@ -154,7 +154,7 @@ namespace Wabbajack
Process.Start(process);
}
private static bool IsStartingFromModlist(out string modlistPath)
private static bool IsStartingFromModlist(out AbsolutePath modlistPath)
{
if (CLIArguments.InstallPath == null)
{
@ -162,14 +162,14 @@ namespace Wabbajack
return false;
}
modlistPath = CLIArguments.InstallPath;
modlistPath = (AbsolutePath)CLIArguments.InstallPath;
return true;
}
public void OpenInstaller(string path)
public void OpenInstaller(AbsolutePath path)
{
if (path == null) return;
if (path == default) return;
var installer = Installer.Value;
Settings.Installer.LastInstalledListLocation = path;
NavigateTo(installer);

View File

@ -15,7 +15,7 @@ namespace Wabbajack
{
public ModList SourceModList { get; private set; }
public Exception Error { get; }
public string ModListPath { get; }
public AbsolutePath ModListPath { get; }
public string Name => SourceModList?.Name;
public string Readme => SourceModList?.Readme;
public string Author => SourceModList?.Author;
@ -28,7 +28,7 @@ namespace Wabbajack
// and the cached image will automatically be released when the last interested party is gone.
public IObservable<BitmapImage> ImageObservable { get; }
public ModListVM(string modListPath)
public ModListVM(AbsolutePath modListPath)
{
ModListPath = modListPath;
try
@ -48,18 +48,16 @@ namespace Wabbajack
{
try
{
using (var fs = new FileStream(ModListPath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var ar = new ZipArchive(fs, ZipArchiveMode.Read))
using var fs = ModListPath.OpenShared();
using var ar = new ZipArchive(fs, ZipArchiveMode.Read);
var ms = new MemoryStream();
var entry = ar.GetEntry("modlist-image.png");
if (entry == null) return default(MemoryStream);
using (var e = entry.Open())
{
var ms = new MemoryStream();
var entry = ar.GetEntry("modlist-image.png");
if (entry == null) return default(MemoryStream);
using (var e = entry.Open())
{
e.CopyTo(ms);
}
return ms;
e.CopyTo(ms);
}
return ms;
}
catch (Exception ex)
{
@ -100,26 +98,24 @@ namespace Wabbajack
}
else
{
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())
using var fs = ModListPath.OpenShared();
using var ar = new ZipArchive(fs, ZipArchiveMode.Read);
using var ms = new MemoryStream();
var entry = ar.GetEntry(Readme);
if (entry == null)
{
var entry = ar.GetEntry(Readme);
if (entry == null)
{
Utils.Log($"Tried to open a non-existent readme: {Readme}");
return;
}
using (var e = entry.Open())
{
e.CopyTo(ms);
}
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms))
{
var viewer = new TextViewer(reader.ReadToEnd(), Name);
viewer.Show();
}
Utils.Log($"Tried to open a non-existent readme: {Readme}");
return;
}
using (var e = entry.Open())
{
e.CopyTo(ms);
}
ms.Seek(0, SeekOrigin.Begin);
using (var reader = new StreamReader(ms))
{
var viewer = new TextViewer(reader.ReadToEnd(), Name);
viewer.Show();
}
}
}

View File

@ -27,8 +27,7 @@ namespace Wabbajack
execute: () =>
{
var path = mainVM.Settings.Installer.LastInstalledListLocation;
if (string.IsNullOrWhiteSpace(path)
|| !File.Exists(path))
if (path == default || !path.Exists)
{
path = UIUtils.OpenFileDialog($"*{Consts.ModListExtension}|*{Consts.ModListExtension}");
}

View File

@ -272,9 +272,11 @@
<DataTemplate DataType="{x:Type local:MO2CompilerVM}">
<local:MO2CompilerConfigView />
</DataTemplate>
<!--
<DataTemplate DataType="{x:Type local:VortexCompilerVM}">
<local:VortexCompilerConfigView />
</DataTemplate>
-->
</ContentPresenter.Resources>
</ContentPresenter>
<local:BeginButton

View File

@ -358,9 +358,11 @@
<DataTemplate DataType="{x:Type local:MO2InstallerVM}">
<local:MO2InstallerConfigView />
</DataTemplate>
<!--
<DataTemplate DataType="{x:Type local:VortexInstallerVM}">
<local:VortexInstallerConfigView />
</DataTemplate>
-->
</ContentPresenter.Resources>
<ContentPresenter.Style>
<Style TargetType="ContentPresenter">

View File

@ -50,6 +50,10 @@
<None Remove="Resources\Wabba_Mouth.png" />
<None Remove="Resources\Wabba_Mouth_No_Text.png" />
<None Remove="Resources\Wabba_Mouth_Small.png" />
<Compile Remove="View Models\Compilers\VortexCompilerVM.cs" />
<None Include="View Models\Compilers\VortexCompilerVM.cs" />
<Compile Remove="View Models\Installers\VortexInstallerVM.cs" />
<None Include="View Models\Installers\VortexInstallerVM.cs" />
</ItemGroup>
<ItemGroup>