diff --git a/LOMV2.sln b/LOMV2.sln
new file mode 100644
index 0000000..9444e73
--- /dev/null
+++ b/LOMV2.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.4.33213.308
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LOMV2", "LOMV2\LOMV2.csproj", "{FA3491CE-5EF8-40DE-8E19-218ECAEBB7A5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {FA3491CE-5EF8-40DE-8E19-218ECAEBB7A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FA3491CE-5EF8-40DE-8E19-218ECAEBB7A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FA3491CE-5EF8-40DE-8E19-218ECAEBB7A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FA3491CE-5EF8-40DE-8E19-218ECAEBB7A5}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {3B58DAEB-67E9-42DF-BA8A-0A1916C669C5}
+ EndGlobalSection
+EndGlobal
diff --git a/LOMV2/App.xaml b/LOMV2/App.xaml
new file mode 100644
index 0000000..e2fcc17
--- /dev/null
+++ b/LOMV2/App.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/LOMV2/App.xaml.cs b/LOMV2/App.xaml.cs
new file mode 100644
index 0000000..9f068fc
--- /dev/null
+++ b/LOMV2/App.xaml.cs
@@ -0,0 +1,38 @@
+using LOM.Services;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace LOM;
+
+///
+/// Interaction logic for App.xaml
+///
+public partial class App : Application
+{
+ private ServiceProvider serviceProvider;
+
+ public App()
+ {
+ IServiceCollection services = new ServiceCollection();
+ ConfigureServices(services);
+ serviceProvider = services.BuildServiceProvider();
+ }
+
+ private void ConfigureServices(IServiceCollection services)
+ {
+ services.AddSingleton();
+ services.AddScoped();
+ }
+
+ private void OnStartup(object sender, StartupEventArgs e)
+ {
+ var mainWindow = serviceProvider.GetService();
+ mainWindow.Show();
+ }
+}
diff --git a/LOMV2/AssemblyInfo.cs b/LOMV2/AssemblyInfo.cs
new file mode 100644
index 0000000..8b5504e
--- /dev/null
+++ b/LOMV2/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+using System.Windows;
+
+[assembly: ThemeInfo(
+ ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
+ //(used if a resource is not found in the page,
+ // or application resource dictionaries)
+ ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
+ //(used if a resource is not found in the page,
+ // app, or any theme specific resource dictionaries)
+)]
diff --git a/LOMV2/Converters/StringDoubleConverter.cs b/LOMV2/Converters/StringDoubleConverter.cs
new file mode 100644
index 0000000..53806ab
--- /dev/null
+++ b/LOMV2/Converters/StringDoubleConverter.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Data;
+using System.Windows;
+
+namespace LOM.Converters;
+
+[ValueConversion(typeof(String), typeof(Double))]
+public class StringBoolDataConverter : IValueConverter
+{
+ public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ double doValue = (double)value;
+ return doValue.ToString();
+ }
+
+ public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ {
+ string strValue = value as string;
+ if (double.TryParse(strValue, out double result))
+ {
+ return result;
+ }
+ return DependencyProperty.UnsetValue;
+ }
+}
diff --git a/LOMV2/InputDialog.xaml b/LOMV2/InputDialog.xaml
new file mode 100644
index 0000000..1f7fb31
--- /dev/null
+++ b/LOMV2/InputDialog.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Answer
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LOMV2/InputDialog.xaml.cs b/LOMV2/InputDialog.xaml.cs
new file mode 100644
index 0000000..56c0f6a
--- /dev/null
+++ b/LOMV2/InputDialog.xaml.cs
@@ -0,0 +1,45 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace LOM
+{
+ ///
+ /// Interaction logic for InputDialog.xaml
+ ///
+ public partial class InputDialog : Window
+ {
+ public InputDialog(string question, string defaultAnswer = "")
+ {
+ InitializeComponent();
+ lblQuestion.Content = question;
+ txtAnswer.Text = defaultAnswer;
+ }
+
+ private void btnDialogOk_Click(object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = true;
+ }
+
+ private void Window_ContentRendered(object sender, EventArgs e)
+ {
+ txtAnswer.SelectAll();
+ txtAnswer.Focus();
+ }
+
+ public string Answer
+ {
+ get { return txtAnswer.Text; }
+ }
+ }
+}
diff --git a/LOMV2/LOMV2.csproj b/LOMV2/LOMV2.csproj
new file mode 100644
index 0000000..47b5b07
--- /dev/null
+++ b/LOMV2/LOMV2.csproj
@@ -0,0 +1,32 @@
+
+
+
+ WinExe
+ net7.0-windows
+ enable
+ true
+ LOMV2
+ LOM
+ True
+ exeIcon2.ico
+ 0.8.0
+ exeIcon2.png
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ \
+
+
+
+
diff --git a/LOMV2/MainWindow.xaml b/LOMV2/MainWindow.xaml
new file mode 100644
index 0000000..d72f0a2
--- /dev/null
+++ b/LOMV2/MainWindow.xaml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LOMV2/MainWindow.xaml.cs b/LOMV2/MainWindow.xaml.cs
new file mode 100644
index 0000000..43df3af
--- /dev/null
+++ b/LOMV2/MainWindow.xaml.cs
@@ -0,0 +1,551 @@
+using LOM.Models;
+using LOM.ViewModels;
+using System.Windows.Forms;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Text.RegularExpressions;
+using LOM.Services;
+using MessageBox = System.Windows.MessageBox;
+using System.Diagnostics;
+using ListBox = System.Windows.Controls.ListBox;
+using System.IO;
+using System.Text.Json;
+using Microsoft.VisualBasic;
+
+
+namespace LOM;
+///
+/// Interaction logic for MainWindow.xaml
+///
+public partial class MainWindow : Window
+{
+ public MainWindowViewModel ViewModel { get; set; } = new();
+ private ISystemIO _systemIO { get; set; }
+
+ public MainWindow(ISystemIO systemIO)
+ {
+ _systemIO = systemIO;
+ InitializeComponent();
+
+ this.DataContext = ViewModel;
+
+ ModItemDataGrid.DataContext = ViewModel;
+
+ LoadSystemSettings();
+ }
+
+ private async void Refresh_Button_Click(object sender, RoutedEventArgs e)
+ {
+ RefreshMods();
+
+ Debug.WriteLine(String.Join("\n", ViewModel.ModInfos.Select(x => $"{x.DisplayName} | {x.DefaultLoadOrder}")));
+ }
+
+ private void Select_Main_Mods_Folder(object sender, RoutedEventArgs e)
+ {
+ string folderPath = PickFolder();
+ if (string.IsNullOrEmpty(folderPath))
+ return;
+
+ ViewModel.MainModsFolder = folderPath;
+
+ RefreshMods();
+
+ PersistSystemSettings();
+
+ Debug.WriteLine(String.Join("\n", ViewModel.ModInfos.Select(x => $"{x.DisplayName} | {x.DefaultLoadOrder}")));
+ }
+
+ private void Add_Secondary_Mods_Folder(object sender, RoutedEventArgs e)
+ {
+ string folderPath = PickFolder();
+ if (string.IsNullOrEmpty(folderPath))
+ return;
+
+ ViewModel.ModSources.Add(folderPath);
+
+ RefreshMods();
+
+ PersistSystemSettings();
+
+ Debug.WriteLine(String.Join("\n", ViewModel.ModInfos.Select(x => $"{x.DisplayName} | {x.DefaultLoadOrder}")));
+ }
+
+ private async void Upp_Button_Click(object sender, RoutedEventArgs e)
+ {
+ MoveSelectedItem(-1);
+
+ Debug.WriteLine(String.Join("\n", ViewModel.ModInfos.Select(x => $"{x.DisplayName} | {x.DefaultLoadOrder}")));
+
+ }
+
+ private async void Down_Button_Click(object sender, RoutedEventArgs e)
+ {
+ MoveSelectedItem(1);
+
+ Debug.WriteLine(String.Join("\n", ViewModel.ModInfos.Select(x => $"{x.DisplayName} | {x.DefaultLoadOrder}")));
+ }
+
+ private void MoveSelectedItem(int delta)
+ {
+ var selectedItem = ModItemDataGrid.SelectedItem as ModInfo;
+
+ if (selectedItem == null)
+ return;
+
+ var oldIndex = ViewModel.ModInfos.IndexOf(selectedItem);
+ var newIndex = oldIndex + delta;
+
+ if (newIndex < 0 || newIndex > ViewModel.ModInfos.Count() - 1)
+ return;
+
+ ViewModel.ModInfos.Move(oldIndex, newIndex);
+
+ var mods = SortAndUpdateMods(ViewModel.ModInfos.ToList());
+
+ UpdateGrid(mods);
+
+ ModItemDataGrid.SelectedIndex = newIndex;
+
+ GetOverridingData();
+ }
+
+ private void UpdateGrid(List mods)
+ {
+ ViewModel.ModInfos.Clear();
+ ViewModel.ModInfos = new ObservableCollection(mods);
+ ViewModel.OnPropertyChanged(nameof(ViewModel.ModInfos));
+ }
+
+ private List SortAndUpdateMods(List mods)
+ {
+ mods.OrderBy(x => x.DefaultLoadOrder).ToList();
+ for (int i = 0; i < mods.Count(); i++)
+ {
+ mods[i].DefaultLoadOrder = i + 1;
+ }
+ return mods;
+ }
+
+ private async void Apply(object sender, RoutedEventArgs e)
+ {
+ //Some checks:
+ if (string.IsNullOrEmpty(ViewModel.MainModsFolder))
+ {
+ MessageBox.Show("Main mods folder containing modlist.json was not selected.\nPlease select a valid main mods folder before trying to apply.", "Folder not set");
+ return;
+ }
+
+ _systemIO.WriteModListDotJson(ViewModel.ModInfos.ToList(), ViewModel.MainModsFolder);
+ _systemIO.WriteModsModDotJson(ViewModel.ModInfos.ToList());
+ }
+
+ private void RefreshMods()
+ {
+ var mods = GenerateModListFromMainPath(ViewModel.MainModsFolder);
+ ViewModel.ModSources.ToList().ForEach(async x =>
+ {
+ mods.AddRange(GenerateModListFromSecondaryPath(x));
+ });
+
+ mods = SortAndUpdateMods(mods);
+ UpdateGrid(mods);
+
+ ViewModel.OnPropertyChanged(nameof(ViewModel.ModSources));
+ ViewModel.OnPropertyChanged(nameof(ViewModel.ModSourcesNames));
+ ViewModel.OnPropertyChanged(nameof(ViewModel.MainModsFolder));
+
+ GetOverridingData();
+ }
+
+ private List GenerateModListFromMainPath(string folderPath)
+ {
+ var files = _systemIO.ScanFolder(folderPath);
+ var mods = _systemIO.GetModInfoFromFilesInfo(files);
+ var temp = ViewModel.ModInfos.ToList();
+
+ //Remove all mods that have the main mods folder as their source:
+ temp.Where(x => x.FolderName.Contains(ViewModel.MainModsFolder)).ToList().ForEach(x => temp.Remove(x));
+
+ //Update old to new main mods source:
+ ViewModel.MainModsFolder = folderPath;
+
+ //Add all found mods.
+ mods.ForEach(x => temp.Add(x));
+
+ return mods;
+ }
+
+ private List GenerateModListFromSecondaryPath(string folderPath)
+ {
+ var files = _systemIO.ScanFolder(folderPath);
+ var mods = _systemIO.GetModInfoFromFilesInfo(files);
+ var temp = ViewModel.ModInfos.ToList();
+
+ if (ViewModel.MainModsFolder == folderPath)
+ return new();
+
+ if (!ViewModel.ModSources.Contains(folderPath))
+ {
+ var duplicates = mods.Where(x => ViewModel.ModInfos.Any(y => y.DisplayName == x.DisplayName &&
+ y.Author == x.Author)).ToList();
+
+ if (duplicates.Count > 0)
+ {
+ string displayNames = "- " + string.Join("\n- ", duplicates.Select(x => x.DisplayName).ToList());
+ MessageBox.Show($"The following duplicates where detected while inporting mods from selected folder \n{displayNames}\n" +
+ $"Please remove these mods from either directory, refresh and try again.");
+ return new();
+ }
+ }
+
+ //Remove all mods that have the secondary mods folder as their source:
+ temp.Where(x => x.FolderName.Contains(folderPath)).ToList().ForEach(x => temp.Remove(x));
+
+ //Update old to new main mods source:
+ ViewModel.ModSources.Add(folderPath);
+
+ //Add all found mods.
+ mods.ForEach(x => temp.Add(x));
+
+ return mods;
+ }
+
+ public void GetOverridingData()
+ {
+ var modList = ViewModel.ModInfos.ToList();
+
+ modList.ForEach(x => {
+ x.OverridenByMods = new();
+ x.Overriding = new();
+ });
+
+ modList
+ .Where(mod => mod.Enabled)
+ .ToList()
+ .ForEach(mod =>
+ {
+ modList
+ .Where(mod2 => mod2.Enabled)
+ .Where(mod2 => mod2.DefaultLoadOrder < mod.DefaultLoadOrder)
+ .ToList()
+ .ForEach(mod2 =>
+ {
+ if (mod.DisplayName == mod2.DisplayName)
+ return;
+
+ List conflicting = mod.Manifest?.Where(x => mod2.Manifest?.Contains(x) ?? false)?.ToList() ?? new();
+ if (conflicting.Any())
+ {
+ mod.Overriding[mod2] = conflicting.ToList();
+ mod2.OverridenByMods[mod] = conflicting.ToList();
+ }
+ });
+ });
+ }
+
+ private string PickFolder()
+ {
+ using var dialog = new System.Windows.Forms.FolderBrowserDialog();
+ System.Windows.Forms.DialogResult result = dialog.ShowDialog();
+ if (result != System.Windows.Forms.DialogResult.OK)
+ return "";
+ return dialog.SelectedPath;
+ }
+
+ private void ModItemDataGrid_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ {
+ Regex regex = new Regex("[^0-9]+");
+ e.Handled = regex.IsMatch(e.Text);
+ e.Handled = !double.TryParse(e.Text, out var number);
+ }
+
+ private void ModItemDataGrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
+ {
+ ViewModel.OnPropertyChanged(nameof(ViewModel.SelectedModLabel));
+ ViewModel.OnPropertyChanged(nameof(ViewModel.SelectedMod));
+ }
+
+ private void Overriding_ListBox_Selected(object sender, SelectionChangedEventArgs e)
+ {
+ var listBox = sender as ListBox;
+ var selectedIndex = listBox?.SelectedIndex;
+
+ if (selectedIndex == null || selectedIndex < 0)
+ return;
+
+ var manifest = ViewModel?.SelectedMod?.Overriding.ElementAt(selectedIndex.Value).Value;
+
+ if (manifest == null)
+ return;
+
+ ManifestListBox.ItemsSource = manifest;
+ }
+
+ private void OverridenBy_ListBox_Selected(object sender, SelectionChangedEventArgs e)
+ {
+ var listBox = sender as ListBox;
+ var selectedIndex = listBox?.SelectedIndex;
+
+ if (selectedIndex == null || selectedIndex < 0)
+ return;
+
+ var manifest = ViewModel?.SelectedMod?.OverridenByMods.ElementAt(selectedIndex.Value).Value;
+
+ if (manifest == null)
+ return;
+
+ ManifestListBox.ItemsSource = manifest;
+ }
+
+ private void Enable_All_Button_Click(object sender, RoutedEventArgs e)
+ {
+ var mods = ViewModel.ModInfos.ToList();
+ mods.ForEach(x => x.Enabled = true);
+
+ UpdateGrid(mods);
+ }
+
+ private void Disable_All_Button_Click(object sender, RoutedEventArgs e)
+ {
+ var mods = ViewModel.ModInfos.ToList();
+ mods.ForEach(x => x.Enabled = false);
+
+ UpdateGrid(mods);
+ }
+
+ private void Remove_Button_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedMod = ModItemDataGrid.SelectedItem as ModInfo;
+
+ if (selectedMod == null)
+ return;
+
+ var result = MessageBox.Show($"Are you sure you want to PERMANENTLY remove {selectedMod.DisplayName} your disk?", "ARE YOU SURE", MessageBoxButton.YesNo);
+
+ if (result != MessageBoxResult.Yes)
+ return;
+
+ _systemIO.RemoveMod(selectedMod);
+
+ var mods = ViewModel.ModInfos.ToList();
+ mods.Remove(selectedMod);
+ SortAndUpdateMods(mods);
+ UpdateGrid(mods);
+ }
+
+ private void Window_Drop(object sender, System.Windows.DragEventArgs e)
+ {
+ if (ViewModel.MainModsFolder == null)
+ {
+ MessageBox.Show($"Main mods folder has not been selected.\nPlease select a mains mods folder first.", "No Folder Selected");
+ }
+
+ if (!e.Data.GetDataPresent(System.Windows.DataFormats.FileDrop))
+ return;
+
+ // Note that you can have more than one file.
+ string[] files = (string[])e.Data.GetData(System.Windows.DataFormats.FileDrop);
+
+ if (files.Length != 1)
+ {
+ MessageBox.Show($"More the one entry detected.\nPlease only drag-in one folder or a .zip/.rar file.");
+ return;
+ }
+
+ if (Directory.Exists(files[0]))
+ {
+ var path = files[0].Split("\\").Last();
+ if (!_systemIO.InsertDirectory(files[0], ViewModel.MainModsFolder + "\\" + path))
+ return;
+
+ AddSingleMod(ViewModel.MainModsFolder + "\\" + path);
+ return;
+ }
+ var fileInfo = new FileInfo(files[0]);
+
+ var supportedExtensions = new string[] { ".zip", ".rar" };
+
+ if (!supportedExtensions.Contains(fileInfo.Extension))
+ {
+ MessageBox.Show($"File extension not supported.\nPlease only drag-in one folder or a .zip/.rar file.");
+ return;
+ }
+
+ if (!_systemIO.UnzipAndInsertDirectory(files[0], ViewModel.MainModsFolder))
+ {
+ MessageBox.Show($"Failed to extract file.");
+ return;
+ }
+
+ var path2 = System.IO.Path.GetFileNameWithoutExtension(files[0]);
+ AddSingleMod(ViewModel.MainModsFolder + "\\" + path2);
+ }
+
+ private void AddSingleMod(string path)
+ {
+ var mod = _systemIO.GetSingleMod(path);
+ if (mod == null)
+ {
+ MessageBox.Show($"Failed to parse mod.json in {path}.\nThere may be residial files/folders leftover from failed operation!");
+ return;
+ }
+
+ var mods = ViewModel.ModInfos.ToList();
+ mods.Add(mod);
+
+ mods = SortAndUpdateMods(mods);
+ UpdateGrid(mods);
+ }
+
+ private void PersistSystemSettings()
+ {
+ var systemFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+
+ var systemSettings = new SystemSettingsDto() {
+ MainModsFolder = ViewModel.MainModsFolder,
+ ModSources = ViewModel.ModSources.ToList(),
+ Presets = ViewModel.Presets
+ };
+
+ if (!Directory.Exists($"{systemFolder}\\LOMV2"))
+ Directory.CreateDirectory($"{systemFolder}\\LOMV2");
+
+ File.WriteAllText($"{systemFolder}\\LOMV2\\settings.json", JsonSerializer.Serialize(systemSettings));
+ }
+
+ private void LoadSystemSettings()
+ {
+ var systemFolder = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
+
+ if (!File.Exists($"{systemFolder}\\LOMV2\\settings.json"))
+ return;
+
+ var systemSettingsJson = File.ReadAllText($"{systemFolder}\\LOMV2\\settings.json");
+ SystemSettingsDto systemSettings = JsonSerializer.Deserialize(systemSettingsJson);
+ ViewModel.ModSources = systemSettings.ModSources.ToHashSet();
+ ViewModel.MainModsFolder = systemSettings.MainModsFolder;
+ ViewModel.Presets = systemSettings.Presets;
+
+ RefreshMods();
+ }
+
+ private void SavePreset(string presetName)
+ {
+ if (ViewModel.Presets.ContainsKey(presetName))
+ if (MessageBox.Show($"Preset name {presetName} allready in use,\ndo you want to override?", "", MessageBoxButton.YesNo) == MessageBoxResult.No)
+ return;
+
+ ViewModel.Presets[presetName] = JsonSerializer.Serialize(ViewModel.ModInfos.ToArray());
+
+ ViewModel.OnPropertyChanged(nameof(ViewModel.Presets));
+ ViewModel.OnPropertyChanged(nameof(ViewModel.PresetNames));
+
+ PersistSystemSettings();
+ }
+
+ private void LoadPreset(string presetName)
+ {
+ if (!ViewModel.Presets.TryGetValue(presetName, out string presetJson))
+ return;
+
+ var presetMods = JsonSerializer.Deserialize>(presetJson);
+
+ if (presetMods == null)
+ return;
+
+ var mods = ViewModel.ModInfos.ToList();
+
+ mods.ForEach(mod => mod.Enabled = false);
+ mods.ForEach(mod =>
+ {
+ var match = presetMods.FirstOrDefault(presetMod => presetMod.DisplayName == mod.DisplayName &&
+ presetMod.Author == mod.Author &&
+ presetMod.FolderNameShort == mod.FolderNameShort);
+
+ if (match == null)
+ {
+ mod.DefaultLoadOrder = int.MaxValue;
+ return;
+ }
+
+ mod.Enabled = match.Enabled;
+ mod.DefaultLoadOrder = match.DefaultLoadOrder;
+ });
+
+ mods = SortAndUpdateMods(mods);
+ UpdateGrid(mods);
+ }
+
+ private void RemovePreset(string presetName)
+ {
+ ViewModel.Presets.Remove(presetName);
+ PersistSystemSettings();
+
+ ViewModel.OnPropertyChanged(nameof(ViewModel.Presets));
+ ViewModel.OnPropertyChanged(nameof(ViewModel.PresetNames));
+ }
+
+ public void Load_Preset_Button_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedPreset = PresetListBox.SelectedItem as string;
+ if (selectedPreset == null)
+ return;
+
+ LoadPreset(selectedPreset);
+ }
+
+ public void Save_Preset_Button_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedPreset = PresetListBox.SelectedItem as string ?? string.Empty;
+
+ InputDialog inputDialog = new("Preset Name:", selectedPreset);
+ if (inputDialog.ShowDialog() == false)
+ return;
+
+ string answer = inputDialog.Answer;
+ if (string.IsNullOrEmpty(answer))
+ return;
+
+ SavePreset(inputDialog.Answer);
+ }
+
+ public void Remove_Preset_Button_Click(object sender, RoutedEventArgs e)
+ {
+ var selectedPreset = PresetListBox.SelectedItem as string;
+ if (selectedPreset == null)
+ return;
+
+ RemovePreset(selectedPreset);
+ }
+
+ public void Remove_Secondary_Folder_Button_Click(Object sender, RoutedEventArgs e)
+ {
+ var selected = SecondaryFoldersListBox.SelectedItem as string;
+ if (selected == null)
+ return;
+
+ var mods = ViewModel.ModInfos.ToList();
+ mods.RemoveAll(x => x.FolderName.Contains(selected));
+ mods = SortAndUpdateMods(mods);
+ UpdateGrid(mods);
+
+ ViewModel.ModSources.RemoveWhere(x => x == selected);
+
+ PersistSystemSettings();
+ ViewModel.OnPropertyChanged(nameof(ViewModel.ModSources));
+ ViewModel.OnPropertyChanged(nameof(ViewModel.ModSourcesNames));
+ }
+
+ public void Enabled_CheckBox_Clicked(object sender, RoutedEventArgs e)
+ {
+ GetOverridingData();
+ var mods = ViewModel.ModInfos.ToList();
+ UpdateGrid(mods);
+ }
+}
diff --git a/LOMV2/Models/DirInfo.cs b/LOMV2/Models/DirInfo.cs
new file mode 100644
index 0000000..8ce143d
--- /dev/null
+++ b/LOMV2/Models/DirInfo.cs
@@ -0,0 +1,16 @@
+namespace LOM.Models;
+
+using System.Collections.Generic;
+public class DirInfoRoot{
+ public string Name {get; set;} = string.Empty;
+ public List Content {get; set;} = new();
+}
+
+public class DirInfo{
+ public string Name {get; set;} = string.Empty;
+ public string Path {get; set;} = string.Empty;
+ public string Type {get; set;} = string.Empty;
+ public string Content {get; set;} = string.Empty;
+
+
+}
\ No newline at end of file
diff --git a/LOMV2/Models/Mod.cs b/LOMV2/Models/Mod.cs
new file mode 100644
index 0000000..ea49328
--- /dev/null
+++ b/LOMV2/Models/Mod.cs
@@ -0,0 +1,19 @@
+namespace LOM.Models;
+using System.Collections.Generic;
+
+public class Mod
+{
+ public string displayName { get; set; }
+ public string version { get; set; }
+ public int? buildNumber { get; set; }
+ public string description { get; set; }
+ public string author { get; set; }
+ public string authorURL { get; set; }
+ public double? defaultLoadOrder { get; set; }
+ public string gameVersion { get; set; }
+ public List manifest { get; set; }
+ public long? steamPublishedFileId { get; set; }
+ public int? steamLastSubmittedBuildNumber { get; set; }
+ public string steamModVisibility { get; set; }
+ public bool bEnabled { get; set; } = false;
+}
diff --git a/LOMV2/Models/ModInfo.cs b/LOMV2/Models/ModInfo.cs
new file mode 100644
index 0000000..3e51f05
--- /dev/null
+++ b/LOMV2/Models/ModInfo.cs
@@ -0,0 +1,54 @@
+namespace LOM.Models;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json.Serialization;
+
+public class ModInfo
+{
+ public Mod? Mod {get ;set;} = new Mod();
+ public string? FolderName {get ;set;}
+
+ [JsonIgnore]
+ public string? FolderNameShort { get => FolderName.Split("\\").TakeLast(1).First(); }
+ [JsonIgnore]
+ public bool Enabled { get => Mod.bEnabled; set => Mod.bEnabled = value; }
+ [JsonIgnore]
+ public Dictionary> OverridenByMods { get; set; } = new();
+ [JsonIgnore]
+ public Dictionary> Overriding { get; set; } = new();
+ [JsonIgnore]
+ public bool IsOverriden => OverridenByMods.Any();
+ [JsonIgnore]
+ public bool IsOverriding => Overriding.Any();
+ [JsonIgnore]
+ public List OverridenByModsLabels => OverridenByMods.Select(x => x.Key.DisplayName).ToList();
+ [JsonIgnore]
+ public List OverridingLabls => Overriding.Select(x => x.Key.DisplayName).ToList();
+
+ #region acces stuff
+ [JsonIgnore]
+ public string? DisplayName {get => Mod?.displayName; set => Mod.displayName = value; }
+ [JsonIgnore]
+ public string? Version {get => Mod?.version; set => Mod.version = value; }
+ [JsonIgnore]
+ public int? BuildNumber {get => Mod?.buildNumber; set => Mod.buildNumber = value; }
+ [JsonIgnore]
+ public string? Description {get => Mod?.description; set => Mod.description = value; }
+ [JsonIgnore]
+ public string? Author {get => Mod?.author; set => Mod.author = value; }
+ [JsonIgnore]
+ public string? AuthorURL {get => Mod?.authorURL; set => Mod.authorURL = value; }
+ [JsonIgnore]
+ public double? DefaultLoadOrder {get => Mod?.defaultLoadOrder; set => Mod.defaultLoadOrder = value; }
+ [JsonIgnore]
+ public string? GameVersion {get => Mod?.gameVersion; set => Mod.gameVersion = value; }
+ [JsonIgnore]
+ public List? Manifest {get => Mod?.manifest; set => Mod.manifest = value; }
+ [JsonIgnore]
+ public long? SteamPublishedFileId {get => Mod?.steamPublishedFileId; set => Mod.steamPublishedFileId = value; }
+ [JsonIgnore]
+ public int? SteamLastSubmittedBuildNumber {get => Mod?.steamLastSubmittedBuildNumber; set => Mod.steamLastSubmittedBuildNumber = value; }
+ [JsonIgnore]
+ public string? SteamModVisibility {get => Mod?.steamModVisibility; set => Mod.steamModVisibility = value; }
+ #endregion
+}
\ No newline at end of file
diff --git a/LOMV2/Models/SystemSettingsDto.cs b/LOMV2/Models/SystemSettingsDto.cs
new file mode 100644
index 0000000..653760f
--- /dev/null
+++ b/LOMV2/Models/SystemSettingsDto.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Policy;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace LOM.Models
+{
+ public class SystemSettingsDto
+ {
+ public string MainModsFolder { get; set; } = string.Empty;
+ public List ModSources { get; set; } = new List();
+ public Dictionary Presets { get; set; } = new();
+ }
+}
diff --git a/LOMV2/Services/ISystemIO.cs b/LOMV2/Services/ISystemIO.cs
new file mode 100644
index 0000000..fbe43a7
--- /dev/null
+++ b/LOMV2/Services/ISystemIO.cs
@@ -0,0 +1,20 @@
+using LOM.Models;
+using System.Collections.Generic;
+using System.IO;
+
+namespace LOM.Services
+{
+ public interface ISystemIO
+ {
+ List GetModInfoFromFilesInfo(List files);
+ List ScanFolder(string path);
+ bool IsMainModsFolder (string path);
+ bool IsMainModsFolder (List files);
+ void WriteModListDotJson(List modInfos, string path);
+ void WriteModsModDotJson(List modInfos);
+ bool RemoveMod(ModInfo mod);
+ bool InsertDirectory(string source, string target);
+ bool UnzipAndInsertDirectory(string source, string target);
+ ModInfo? GetSingleMod(string path);
+ }
+}
\ No newline at end of file
diff --git a/LOMV2/Services/SystemIO.cs b/LOMV2/Services/SystemIO.cs
new file mode 100644
index 0000000..8222d82
--- /dev/null
+++ b/LOMV2/Services/SystemIO.cs
@@ -0,0 +1,203 @@
+using LOM.Models;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.IO.Compression;
+using SevenZipExtractor;
+
+namespace LOM.Services
+{
+ public class SystemIO : ISystemIO
+ {
+ public List ScanFolder(string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ return new();
+
+ return DirSearch(path).Select(x => new FileInfo(x)).ToList();
+ }
+
+ private List DirSearch(string sDir)
+ {
+ if(string.IsNullOrEmpty(sDir))
+ return new List();
+
+ List files = new List();
+ try
+ {
+ foreach (string f in Directory.GetFiles(sDir))
+ {
+ files.Add(f);
+ }
+ foreach (string d in Directory.GetDirectories(sDir))
+ {
+ files.AddRange(DirSearch(d));
+ }
+ }
+ catch (Exception ex)
+ {
+ return files;
+ }
+
+ return files;
+ }
+
+ public ModInfo? GetSingleMod(string path)
+ {
+ var files = ScanFolder(path);
+ var mods = GetModInfoFromFilesInfo(files);
+
+ if (mods.Count() > 1 || mods.Count() == 0)
+ return null;
+
+ return mods.First();
+ }
+
+ public void WriteModListDotJson(List modInfos, string path)
+ {
+ if (string.IsNullOrEmpty(path))
+ return;
+ if (modInfos == null)
+ return;
+
+ File.WriteAllText(path + "\\modlist.json", GenerateModListJson(modInfos));
+ }
+
+ public string GenerateModListJson(List modInfos)
+ {
+ if (modInfos == null)
+ return string.Empty;
+
+ string json = "{\"gameVersion\": \"1.1.328\",\"modStatus\":{";
+
+ modInfos.ForEach(x => {
+ json += $"\"{x.FolderName.Split("\\").TakeLast(1).ToArray()[0]}\":" + "{" + $"\"bEnabled\":{x.Enabled.ToString().ToLower()}" + "},";
+ });
+ json = json.Remove(json.Length - 1, 1);
+ json += "}}";
+
+ json = JsonPrettify(json);
+
+ return json;
+ }
+
+ public void WriteModsModDotJson(List modInfos)
+ {
+ if (modInfos == null)
+ return;
+
+ modInfos.ForEach(x => WriteModDotJson(x));
+ }
+
+ public void WriteModDotJson(ModInfo modInfo)
+ {
+ if (modInfo == null)
+ return;
+
+ var folderPath = modInfo.FolderName;
+ File.WriteAllText($"{modInfo.FolderName}\\mod.json", JsonSerializer.Serialize(modInfo.Mod, new JsonSerializerOptions { WriteIndented = true }));
+ }
+
+ ///
+ /// Will async loop over the files and generate the ModInfos
+ ///
+ ///
+ ///
+ public List GetModInfoFromFilesInfo(List files)
+ {
+ if (files == null)
+ return new();
+
+ return files
+ .Where(x => x.Name == "mod.json")
+ .Select(x =>
+ {
+ return new ModInfo()
+ {
+ Mod = JsonSerializer.Deserialize(File.ReadAllText(x.FullName)),
+ FolderName = x.DirectoryName,
+ };
+ })
+ .ToList();
+ }
+
+ public bool IsMainModsFolder(string path)
+ {
+ if(string.IsNullOrEmpty(path))
+ return false;
+
+ var files = ScanFolder(path);
+ return IsMainModsFolder(files);
+ }
+
+ public bool IsMainModsFolder(List files)
+ {
+ if (files == null)
+ return false;
+
+ return files.Count(x => x.Name == "modlist.json") == 1;
+ }
+
+ private static string JsonPrettify(string json)
+ {
+ using var jDoc = JsonDocument.Parse(json);
+ return JsonSerializer.Serialize(jDoc, new JsonSerializerOptions { WriteIndented = true });
+ }
+
+ public bool RemoveMod(ModInfo mod)
+ {
+ try
+ {
+ Directory.Delete(mod.FolderName, true);
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public bool InsertDirectory(string source, string target)
+ {
+ try
+ {
+ DeepCopy(new DirectoryInfo(source), target);
+ return true;
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ public bool UnzipAndInsertDirectory(string source, string target)
+ {
+ try
+ {
+ using ArchiveFile archiveFile = new(source);
+ archiveFile.Extract(target, true);
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ private void DeepCopy(DirectoryInfo directory, string destinationDir)
+ {
+ foreach (string dir in Directory.GetDirectories(directory.FullName, "*", SearchOption.AllDirectories))
+ {
+ string dirToCreate = dir.Replace(directory.FullName, destinationDir);
+ Directory.CreateDirectory(dirToCreate);
+ }
+
+ foreach (string newPath in Directory.GetFiles(directory.FullName, "*.*", SearchOption.AllDirectories))
+ {
+ File.Copy(newPath, newPath.Replace(directory.FullName, destinationDir), true);
+ }
+ }
+ }
+}
diff --git a/LOMV2/ViewModels/MainWindowViewModel.cs b/LOMV2/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..67860a0
--- /dev/null
+++ b/LOMV2/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,34 @@
+using LOM.Models;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace LOM.ViewModels
+{
+ public class MainWindowViewModel : INotifyPropertyChanged
+ {
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ public ObservableCollection ModInfos { get; set; } = new();
+ public Dictionary Presets { get; set; } = new();
+ public List PresetNames => Presets.Keys.ToList();
+ public HashSet ModSources { get; set; } = new();
+ public List ModSourcesNames => ModSources.ToList();
+ public string MainModsFolder { get; set; } = string.Empty;
+ public ModInfo? SelectedMod { get; set; }
+ public string SelectedModLabel => SelectedMod?.DisplayName ?? string.Empty;
+
+ // Create the OnPropertyChanged method to raise the event
+ // The calling member's name will be used as the parameter.
+ public void OnPropertyChanged([CallerMemberName] string name = null)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
+ }
+ }
+}
diff --git a/LOMV2/exeIcon.ico b/LOMV2/exeIcon.ico
new file mode 100644
index 0000000..fe4154e
Binary files /dev/null and b/LOMV2/exeIcon.ico differ
diff --git a/LOMV2/exeIcon2.ico b/LOMV2/exeIcon2.ico
new file mode 100644
index 0000000..ad592c3
Binary files /dev/null and b/LOMV2/exeIcon2.ico differ
diff --git a/LOMV2/exeIcon2.png b/LOMV2/exeIcon2.png
new file mode 100644
index 0000000..986cbfa
Binary files /dev/null and b/LOMV2/exeIcon2.png differ