├── Images ├── fantomebanner.png ├── createmoddialog.png ├── fantomedownload.png ├── leaguemodinstall.png └── leaguefolderselect.png ├── Fantome ├── LoLCustomSharp.dll ├── Resources │ ├── icon.ico │ └── fantome_logo.png ├── Utilities │ ├── MaterialDesign │ │ ├── ModifierCollection.cs │ │ ├── ModifierBase.cs │ │ ├── Modifier.cs │ │ └── TreeHelpers.cs │ ├── LeagueLocationValidator.cs │ ├── Logging.cs │ ├── Pathing.cs │ ├── WineDetector.cs │ ├── ThemeHelper.cs │ ├── UpdateHelper.cs │ ├── StartupGuard.cs │ ├── Config.cs │ └── DialogHelper.cs ├── ModManagement │ ├── WAD │ │ ├── WadContentRawFolder.cs │ │ ├── WadContentWadFile.cs │ │ ├── WadContentWadFolder.cs │ │ ├── WadContent.cs │ │ └── WadMerger.cs │ ├── IO │ │ └── ModInfo.cs │ ├── ModDatabase.cs │ ├── LeagueFileIndex.cs │ └── ModManager.cs ├── App.xaml.cs ├── MVVM │ ├── PropertyNotifier.cs │ ├── Converters │ │ └── IsHigherThanConverter.cs │ ├── ViewModels │ │ ├── CreateMod │ │ │ ├── CreateModWadItemViewModel.cs │ │ │ └── CreateModDialogViewModel.cs │ │ ├── InstallingModViewModel.cs │ │ ├── UninstallingModViewModel.cs │ │ ├── ModListCategoryViewModel.cs │ │ ├── LeagueLocationDialogViewModel.cs │ │ ├── ModListItemViewModel.cs │ │ ├── SettingsViewModel.cs │ │ ├── ModListViewModel.cs │ │ └── MainWindowViewModel.cs │ ├── ValidationRules │ │ ├── ModVersionValidationRule.cs │ │ ├── ModNameValidationRule.cs │ │ └── ModAuthorValidationRule.cs │ ├── ModelViews │ │ ├── Dialogs │ │ │ ├── MessageDialog.xaml.cs │ │ │ ├── AssetCollisionDialog.xaml.cs │ │ │ ├── InstallingModDialog.xaml │ │ │ ├── UninstallingModDialog.xaml │ │ │ ├── GeneratingWadFilesDialog.xaml │ │ │ ├── InstallingModDialog.xaml.cs │ │ │ ├── MessageDialog.xaml │ │ │ ├── ModContentValidationDialog.xaml.cs │ │ │ ├── ModContentValidationDialog.xaml │ │ │ ├── UninstallingModDialog.xaml.cs │ │ │ ├── GeneratingWadFilesDialog.xaml.cs │ │ │ ├── SettingsDialog.xaml.cs │ │ │ ├── LeagueLocationDialog.xaml.cs │ │ │ ├── AssetCollisionDialog.xaml │ │ │ ├── LeagueLocationDialog.xaml │ │ │ ├── CreateModDialog.xaml.cs │ │ │ ├── SettingsDialog.xaml │ │ │ └── CreateModDialog.xaml │ │ ├── ModCard.xaml.cs │ │ ├── ModListRow.xaml.cs │ │ ├── ModCard.xaml │ │ └── ModListRow.xaml │ └── Commands │ │ └── RelayCommand.cs ├── AssemblyInfo.cs ├── App.xaml ├── app.manifest ├── Fantome.csproj ├── Properties │ └── Resources.Designer.cs ├── MainWindow.xaml.cs └── MainWindow.xaml ├── Fantome.sln ├── README.md ├── .gitattributes └── .gitignore /Images/fantomebanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/Fantome/HEAD/Images/fantomebanner.png -------------------------------------------------------------------------------- /Fantome/LoLCustomSharp.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/Fantome/HEAD/Fantome/LoLCustomSharp.dll -------------------------------------------------------------------------------- /Fantome/Resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/Fantome/HEAD/Fantome/Resources/icon.ico -------------------------------------------------------------------------------- /Images/createmoddialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/Fantome/HEAD/Images/createmoddialog.png -------------------------------------------------------------------------------- /Images/fantomedownload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/Fantome/HEAD/Images/fantomedownload.png -------------------------------------------------------------------------------- /Images/leaguemodinstall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/Fantome/HEAD/Images/leaguemodinstall.png -------------------------------------------------------------------------------- /Images/leaguefolderselect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/Fantome/HEAD/Images/leaguefolderselect.png -------------------------------------------------------------------------------- /Fantome/Resources/fantome_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LeagueToolkit/Fantome/HEAD/Fantome/Resources/fantome_logo.png -------------------------------------------------------------------------------- /Fantome/Utilities/MaterialDesign/ModifierCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | 3 | namespace Fantome.Utilities.MaterialDesign 4 | { 5 | public class ModifierCollection : Collection 6 | { 7 | 8 | } 9 | } -------------------------------------------------------------------------------- /Fantome/Utilities/MaterialDesign/ModifierBase.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Fantome.Utilities.MaterialDesign 4 | { 5 | public abstract class ModifierBase 6 | { 7 | public abstract void Apply(DependencyObject target); 8 | } 9 | } -------------------------------------------------------------------------------- /Fantome/ModManagement/WAD/WadContentRawFolder.cs: -------------------------------------------------------------------------------- 1 | namespace Fantome.ModManagement.WAD 2 | { 3 | public sealed class WadContentRawFolder : WadContent 4 | { 5 | public WadContentRawFolder(string path) : base(WadContentType.RawFolder, path) { } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Fantome/Utilities/LeagueLocationValidator.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace Fantome.Utilities 4 | { 5 | public abstract class LeagueLocationValidator 6 | { 7 | public static bool Validate(string leagueLocation) 8 | { 9 | return File.Exists(string.Format(@"{0}\League of Legends.exe", leagueLocation)); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Fantome/ModManagement/WAD/WadContentWadFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Fantome.ModManagement.WAD 6 | { 7 | public sealed class WadContentWadFile : WadContent 8 | { 9 | public WadContentWadFile(string path) : base(WadContentType.WadFile, path) 10 | { 11 | 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Fantome/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | 9 | namespace Fantome 10 | { 11 | /// 12 | /// Interaction logic for App.xaml 13 | /// 14 | public partial class App : Application 15 | { 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Fantome/ModManagement/WAD/WadContentWadFolder.cs: -------------------------------------------------------------------------------- 1 | using PathIO = System.IO.Path; 2 | 3 | namespace Fantome.ModManagement.WAD 4 | { 5 | public sealed class WadContentWadFolder : WadContent 6 | { 7 | public WadContentWadFolder(string path) : base(WadContentType.WadFolder, path) { } 8 | 9 | public string GetWadName() 10 | { 11 | return PathIO.GetFileName(this.Path); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Fantome/MVVM/PropertyNotifier.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace Fantome.MVVM 5 | { 6 | public abstract class PropertyNotifier : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "") 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Fantome/ModManagement/WAD/WadContent.cs: -------------------------------------------------------------------------------- 1 | namespace Fantome.ModManagement.WAD 2 | { 3 | public abstract class WadContent 4 | { 5 | public WadContentType Type { get; private set; } 6 | public string Path { get; private set; } 7 | 8 | public WadContent(WadContentType type, string path) 9 | { 10 | this.Type = type; 11 | this.Path = path; 12 | } 13 | } 14 | 15 | public enum WadContentType 16 | { 17 | RawFolder, 18 | WadFolder, 19 | WadFile 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fantome/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /Fantome/Utilities/Logging.cs: -------------------------------------------------------------------------------- 1 | using Serilog; 2 | using System; 3 | 4 | namespace Fantome.Utilities 5 | { 6 | public static class Logging 7 | { 8 | public const string LOGS_FOLDER = "Logs"; 9 | 10 | public static void Initialize() 11 | { 12 | string logPath = string.Format(@"{0}\FantomeLog - {1}.txt", LOGS_FOLDER, DateTime.Now.ToString("dd.MM.yyyy - HH-mm-ss")); 13 | string loggingPattern = Config.Get("LoggingPattern"); 14 | 15 | Log.Logger = new LoggerConfiguration() 16 | .WriteTo.File(logPath, outputTemplate: loggingPattern) 17 | .CreateLogger(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Fantome/MVVM/Converters/IsHigherThanConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Text; 5 | using System.Windows.Data; 6 | 7 | namespace Fantome.MVVM.Converters 8 | { 9 | public class IsHigherThanConverter : IValueConverter 10 | { 11 | public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 12 | { 13 | return (double)value > (double)parameter; 14 | } 15 | 16 | public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 17 | { 18 | throw new NotImplementedException(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fantome/Utilities/Pathing.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace Fantome.Utilities 4 | { 5 | public static class Pathing 6 | { 7 | public static char GetPathSeparator(string path) 8 | { 9 | if (path.Contains('\\')) 10 | { 11 | return '\\'; 12 | } 13 | else 14 | { 15 | return '/'; 16 | } 17 | } 18 | public static char GetInvertedPathSeparator(char separator) 19 | { 20 | if (separator == '\\') 21 | { 22 | return '/'; 23 | } 24 | else 25 | { 26 | return '\\'; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/CreateMod/CreateModWadItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using PathIO = System.IO.Path; 6 | 7 | namespace Fantome.MVVM.ViewModels.CreateMod 8 | { 9 | public class CreateModWadItemViewModel : PropertyNotifier 10 | { 11 | public CreateModWadItemType Type { get; } 12 | public string Name => PathIO.GetFileName(this._path); 13 | public string Path => this._path; 14 | 15 | private string _path; 16 | 17 | public CreateModWadItemViewModel(CreateModWadItemType type, string path) 18 | { 19 | this.Type = type; 20 | this._path = path; 21 | } 22 | } 23 | 24 | public enum CreateModWadItemType 25 | { 26 | File, 27 | Folder 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/InstallingModViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Fantome.ModManagement; 7 | using Fantome.ModManagement.IO; 8 | 9 | namespace Fantome.MVVM.ViewModels 10 | { 11 | public class InstallingModViewModel 12 | { 13 | public ModFile Mod { get; private set; } 14 | public ModManager ModManager { get; private set; } 15 | 16 | public string InstallingString => "Installing " + this.Mod.GetID(); 17 | 18 | public InstallingModViewModel(ModFile mod, ModManager modManager) 19 | { 20 | this.Mod = mod; 21 | this.ModManager = modManager; 22 | } 23 | 24 | public void Install() 25 | { 26 | this.ModManager.InstallMod(this.Mod); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/UninstallingModViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Fantome.ModManagement; 7 | using Fantome.ModManagement.IO; 8 | 9 | namespace Fantome.MVVM.ViewModels 10 | { 11 | public class UninstallingModViewModel 12 | { 13 | public ModFile Mod { get; private set; } 14 | public ModManager ModManager { get; private set; } 15 | 16 | public string UninstallingString => "Uninstalling " + this.Mod.GetID(); 17 | 18 | public UninstallingModViewModel(ModFile mod, ModManager modManager) 19 | { 20 | this.Mod = mod; 21 | this.ModManager = modManager; 22 | } 23 | 24 | public void Uninstall() 25 | { 26 | this.ModManager.UninstallMod(this.Mod); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Fantome/Utilities/WineDetector.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Fantome.Utilities 5 | { 6 | public static class WineDetector 7 | { 8 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 9 | private static extern IntPtr GetModuleHandle(string lpModuleName); 10 | 11 | [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] 12 | private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 13 | 14 | public static bool IsRunningInWine() 15 | { 16 | IntPtr ntdllHandle = GetModuleHandle("ntdll.dll"); 17 | 18 | if (ntdllHandle != IntPtr.Zero) 19 | { 20 | IntPtr wineVersion = GetProcAddress(ntdllHandle, "wine_get_version"); 21 | 22 | return wineVersion != IntPtr.Zero; 23 | } 24 | 25 | return false; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Fantome/MVVM/ValidationRules/ModVersionValidationRule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Windows.Controls; 4 | 5 | namespace Fantome.MVVM.ValidationRules 6 | { 7 | public class ModVersionValidationRule : ValidationRule 8 | { 9 | public override ValidationResult Validate(object value, CultureInfo cultureInfo) 10 | { 11 | string versionString = value as string; 12 | 13 | if(string.IsNullOrEmpty(versionString)) 14 | { 15 | return new ValidationResult(false, "Version cannot be empty"); 16 | } 17 | else if(!Version.TryParse(versionString, out Version _)) 18 | { 19 | return new ValidationResult(false, "Not a valid version. Use 1.0 format"); 20 | } 21 | else 22 | { 23 | return new ValidationResult(true, ""); 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/MessageDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace Fantome.MVVM.ModelViews.Dialogs 17 | { 18 | /// 19 | /// Interaction logic for MessageDialog.xaml 20 | /// 21 | public partial class MessageDialog : UserControl 22 | { 23 | public string Message { get; private set; } 24 | 25 | public MessageDialog(string message) 26 | { 27 | this.Message = message; 28 | 29 | InitializeComponent(); 30 | 31 | this.DataContext = this; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/ModCard.xaml.cs: -------------------------------------------------------------------------------- 1 | using Fantome.MVVM.ViewModels; 2 | using System.Windows; 3 | using System.Windows.Controls; 4 | 5 | namespace Fantome.MVVM.ModelViews 6 | { 7 | /// 8 | /// Interaction logic for ModCard.xaml 9 | /// 10 | public partial class ModCard : UserControl 11 | { 12 | public ModListItemViewModel ViewModel { get => this.DataContext as ModListItemViewModel; } 13 | 14 | public ModCard() 15 | { 16 | InitializeComponent(); 17 | } 18 | 19 | private async void IsInstalledToggle_Checked(object sender, RoutedEventArgs e) 20 | { 21 | await this.ViewModel.Install(); 22 | } 23 | 24 | private async void IsInstalledToggle_Unchecked(object sender, RoutedEventArgs e) 25 | { 26 | await this.ViewModel.Uninstall(); 27 | } 28 | 29 | private void RemoveModButton_Click(object sender, RoutedEventArgs e) 30 | { 31 | this.ViewModel.Remove(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Fantome/MVVM/ValidationRules/ModNameValidationRule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using System.Windows.Controls; 5 | 6 | namespace Fantome.MVVM.ValidationRules 7 | { 8 | public class ModNameValidationRule : ValidationRule 9 | { 10 | private static readonly char[] INVALID_CHARACTERS = { '<', '>', ':', '"', '/', '\\', '|', '?', '*' }; 11 | 12 | public override ValidationResult Validate(object value, CultureInfo cultureInfo) 13 | { 14 | string modName = value as string; 15 | 16 | if (string.IsNullOrEmpty(modName)) 17 | { 18 | return new ValidationResult(false, "Name cannot be empty"); 19 | } 20 | else if (modName.Any(x => INVALID_CHARACTERS.Contains(x))) 21 | { 22 | return new ValidationResult(false, "Remove special characters from Name"); 23 | } 24 | else 25 | { 26 | return new ValidationResult(true, ""); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fantome/MVVM/ValidationRules/ModAuthorValidationRule.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using System.Windows.Controls; 5 | 6 | namespace Fantome.MVVM.ValidationRules 7 | { 8 | public class ModAuthorValidationRule : ValidationRule 9 | { 10 | private static readonly char[] INVALID_CHARACTERS = { '<', '>', ':', '"', '/', '\\', '|', '?', '*' }; 11 | 12 | public override ValidationResult Validate(object value, CultureInfo cultureInfo) 13 | { 14 | string modAuthor = value as string; 15 | 16 | if (string.IsNullOrEmpty(modAuthor)) 17 | { 18 | return new ValidationResult(false, "Author cannot be empty"); 19 | } 20 | else if (modAuthor.Any(x => INVALID_CHARACTERS.Contains(x))) 21 | { 22 | return new ValidationResult(false, "Remove special characters from Author"); 23 | } 24 | else 25 | { 26 | return new ValidationResult(true, ""); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/ModListRow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Fantome.MVVM.ViewModels; 2 | using System.Collections.Generic; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | 6 | namespace Fantome.MVVM.ModelViews 7 | { 8 | /// 9 | /// Interaction logic for ModListRow.xaml 10 | /// 11 | public partial class ModListRow : UserControl 12 | { 13 | public ModListItemViewModel ViewModel => this.DataContext as ModListItemViewModel; 14 | 15 | public ModListRow() 16 | { 17 | InitializeComponent(); 18 | } 19 | 20 | private async void IsInstalledToggle_Checked(object sender, RoutedEventArgs e) 21 | { 22 | await this.ViewModel.Install(); 23 | } 24 | 25 | private async void IsInstalledToggle_Unchecked(object sender, RoutedEventArgs e) 26 | { 27 | await this.ViewModel.Uninstall(); 28 | } 29 | 30 | private void RemoveModButton_Click(object sender, RoutedEventArgs e) 31 | { 32 | this.ViewModel.Remove(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/ModListCategoryViewModel.cs: -------------------------------------------------------------------------------- 1 | using Fantome.ModManagement.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Fantome.MVVM.ViewModels 10 | { 11 | public class ModListCategoryViewModel : PropertyNotifier 12 | { 13 | public ModCategory Category { get; private set; } 14 | public ObservableCollection Items 15 | { 16 | get => this._items; 17 | set 18 | { 19 | this._items = value; 20 | NotifyPropertyChanged(); 21 | } 22 | } 23 | 24 | private ObservableCollection _items = new ObservableCollection(); 25 | 26 | public ModListCategoryViewModel(ModCategory category, ObservableCollection items) 27 | { 28 | this.Category = category; 29 | this._items = items; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Fantome/Utilities/MaterialDesign/Modifier.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace Fantome.Utilities.MaterialDesign 5 | { 6 | public class Modifier : ModifierBase 7 | { 8 | public DependencyProperty Property { get; set; } 9 | public object Value { get; set; } 10 | public string TemplatePartName { get; set; } 11 | public override void Apply(DependencyObject target) 12 | { 13 | if (target is FrameworkElement element && Property is DependencyProperty property) 14 | { 15 | if (target.GetValue(Control.TemplateProperty) is ControlTemplate template && 16 | template.FindName(TemplatePartName, element) is DependencyObject templatePart) 17 | { 18 | templatePart.SetCurrentValue(property, Value); 19 | } 20 | else if (element.FindName(TemplatePartName) is DependencyObject childElement) 21 | { 22 | childElement.SetValue(property, Value); 23 | } 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/LeagueLocationDialogViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Microsoft.Win32; 9 | 10 | namespace Fantome.MVVM.ViewModels 11 | { 12 | public class LeagueLocationDialogViewModel : PropertyNotifier 13 | { 14 | public string LeagueLocation 15 | { 16 | get => this._leagueLocation; 17 | set 18 | { 19 | this._leagueLocation = value; 20 | this.IsLeagueSelected = true; 21 | NotifyPropertyChanged(); 22 | } 23 | } 24 | public bool IsLeagueSelected 25 | { 26 | get => this._isLeagueSelected; 27 | set 28 | { 29 | this._isLeagueSelected = value; 30 | NotifyPropertyChanged(); 31 | } 32 | } 33 | 34 | private string _leagueLocation; 35 | private bool _isLeagueSelected; 36 | 37 | public LeagueLocationDialogViewModel() 38 | { 39 | 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/AssetCollisionDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using Fantome.ModManagement.IO; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | 17 | namespace Fantome.MVVM.ModelViews.Dialogs 18 | { 19 | /// 20 | /// Interaction logic for AssetCollisionDialog.xaml 21 | /// 22 | public partial class AssetCollisionDialog : UserControl 23 | { 24 | public string Message { get; } 25 | public List Collisions { get; } 26 | 27 | public AssetCollisionDialog(ModFile mod, List collisions) 28 | { 29 | this.DataContext = this; 30 | this.Message = "Fantome has detected that the following mods have asset collisions with: " + mod.GetID() + '\n'; 31 | this.Message += "Do you want to uninstall these mods to install: " + mod.GetID() + '?'; 32 | this.Collisions = collisions; 33 | 34 | InitializeComponent(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Fantome/MVVM/Commands/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows.Input; 7 | 8 | namespace Fantome.MVVM.Commands 9 | { 10 | public class RelayCommand : ICommand 11 | { 12 | private readonly Predicate _canExecute; 13 | private readonly Action _execute; 14 | 15 | public RelayCommand(Action execute) : this(null, execute) 16 | { 17 | 18 | } 19 | 20 | public RelayCommand(Predicate canExecute, Action execute) 21 | { 22 | this._canExecute = canExecute; 23 | this._execute = execute; 24 | } 25 | 26 | public event EventHandler CanExecuteChanged 27 | { 28 | add => CommandManager.RequerySuggested += value; 29 | remove => CommandManager.RequerySuggested -= value; 30 | } 31 | 32 | public bool CanExecute(object parameter) 33 | { 34 | if(this._canExecute != null) 35 | { 36 | return this._canExecute(parameter); 37 | } 38 | 39 | return true; 40 | } 41 | 42 | public void Execute(object parameter) 43 | { 44 | this._execute(parameter); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Fantome/ModManagement/WAD/WadMerger.cs: -------------------------------------------------------------------------------- 1 | using LeagueToolkit.IO.WadFile; 2 | using System; 3 | using System.Linq; 4 | 5 | namespace Fantome.ModManagement.WAD 6 | { 7 | public static class WadMerger 8 | { 9 | public static WadBuilder Merge(WadBuilder wadBase, WadBuilder wadToMerge) 10 | { 11 | WadBuilder wadBuilder = new WadBuilder(); 12 | 13 | // First add new files and then modify changed ones 14 | foreach (var entryToMerge in wadToMerge.Entries) 15 | { 16 | // Add new entry 17 | if (wadBase.Entries.ContainsKey(entryToMerge.Key) is false) 18 | { 19 | wadBuilder.WithEntry(entryToMerge.Value); 20 | } 21 | // Modify existing entry 22 | else if (!entryToMerge.Value.Sha256Checksum.SequenceEqual(wadBase.Entries[entryToMerge.Key].Sha256Checksum)) 23 | { 24 | wadBuilder.WithEntry(entryToMerge.Value); 25 | } 26 | } 27 | 28 | // Copy over the rest 29 | foreach (var entry in wadBase.Entries.Where(x => wadToMerge.Entries.ContainsKey(x.Key) is false)) 30 | { 31 | wadBuilder.WithEntry(entry.Value); 32 | } 33 | 34 | return wadBuilder; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Fantome.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29728.190 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fantome", "Fantome\Fantome.csproj", "{CBE51029-2E6B-4FF1-BF87-5B53AE77C434}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | ReleasePortable|x64 = ReleasePortable|x64 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {CBE51029-2E6B-4FF1-BF87-5B53AE77C434}.Debug|x64.ActiveCfg = Debug|x64 16 | {CBE51029-2E6B-4FF1-BF87-5B53AE77C434}.Debug|x64.Build.0 = Debug|x64 17 | {CBE51029-2E6B-4FF1-BF87-5B53AE77C434}.Release|x64.ActiveCfg = Release|x64 18 | {CBE51029-2E6B-4FF1-BF87-5B53AE77C434}.Release|x64.Build.0 = Release|x64 19 | {CBE51029-2E6B-4FF1-BF87-5B53AE77C434}.ReleasePortable|x64.ActiveCfg = ReleasePortable|x64 20 | {CBE51029-2E6B-4FF1-BF87-5B53AE77C434}.ReleasePortable|x64.Build.0 = ReleasePortable|x64 21 | EndGlobalSection 22 | GlobalSection(SolutionProperties) = preSolution 23 | HideSolutionNode = FALSE 24 | EndGlobalSection 25 | GlobalSection(ExtensibilityGlobals) = postSolution 26 | SolutionGuid = {6DF1F266-1FFD-484A-A70B-3567568AF2D7} 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/InstallingModDialog.xaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/UninstallingModDialog.xaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/GeneratingWadFilesDialog.xaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Fantome](https://github.com/LoL-Fantome/Fantome/blob/master/Images/fantomebanner.png) 2 | 3 | 4 | 5 | 6 | 7 | 8 | ## Requirements 9 | * **8 GB RAM or more** is recommended due to some mod installations having high RAM usage (Map skins, DATA mods...) 10 | * **Windows 10** is required for the backgroud League patcher to work. If you don't have Windows 10 you won't be able to use any mods 11 | 12 | ![Foo](https://github.com/LoL-Fantome/Fantome/blob/master/Images/fantomedownload.png) 13 | 14 | ## [Introduction](https://github.com/LoL-Fantome/Fantome/wiki/Introduction) 15 | ## [Releases](https://github.com/LoL-Fantome/Fantome/releases) 16 | 17 | ## Donation 18 | [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://paypal.me/FilipQuitko?locale.x=en_US) 19 | 20 | ## Discord 21 | If you want to talk to the developers or other people in the community, join our discord server: 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 |

https://discord.gg/SUHpgaF

31 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/InstallingModDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Windows.Controls; 4 | using Fantome.MVVM.ViewModels; 5 | using Fantome.Utilities; 6 | using MaterialDesignThemes.Wpf; 7 | 8 | namespace Fantome.MVVM.ModelViews.Dialogs 9 | { 10 | /// 11 | /// Interaction logic for InstallingModDialog.xaml 12 | /// 13 | public partial class InstallingModDialog : UserControl 14 | { 15 | public InstallingModViewModel ViewModel => this.DataContext as InstallingModViewModel; 16 | 17 | public InstallingModDialog() 18 | { 19 | InitializeComponent(); 20 | } 21 | 22 | public void StartInstallation(object sender, EventArgs e) 23 | { 24 | BackgroundWorker worker = new BackgroundWorker(); 25 | worker.DoWork += InstallMod; 26 | worker.RunWorkerCompleted += CloseDialog; 27 | worker.WorkerSupportsCancellation = true; 28 | 29 | worker.RunWorkerAsync(new Tuple(worker, this.ViewModel)); 30 | } 31 | 32 | private void CloseDialog(object sender, RunWorkerCompletedEventArgs e) 33 | { 34 | DialogHelper.OperationDialog.IsOpen = false; 35 | } 36 | 37 | private void InstallMod(object sender, DoWorkEventArgs e) 38 | { 39 | Tuple argument = e.Argument as Tuple; 40 | 41 | argument.Item2.Install(); 42 | argument.Item1.CancelAsync(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Fantome/Utilities/ThemeHelper.cs: -------------------------------------------------------------------------------- 1 | using MaterialDesignColors; 2 | using MaterialDesignThemes.Wpf; 3 | using Serilog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows.Media; 10 | 11 | namespace Fantome.Utilities 12 | { 13 | public static class ThemeHelper 14 | { 15 | private static readonly PaletteHelper _paletteHelper = new PaletteHelper(); 16 | 17 | public static void LoadTheme() 18 | { 19 | Log.Information("Loading Theme from Config"); 20 | 21 | bool isDarkTheme = Config.Get("IsDarkTheme"); 22 | Color primaryColor = ConvertPrimaryColor(Config.Get("PrimaryColor")); 23 | Color secondaryColor = ConvertSecondaryColor(Config.Get("SecondaryColor")); 24 | Theme theme = Theme.Create(isDarkTheme ? Theme.Dark : Theme.Light, primaryColor, secondaryColor); 25 | 26 | _paletteHelper.SetTheme(theme); 27 | } 28 | 29 | public static void ChangeTheme(IBaseTheme theme, Color primaryColor, Color secondaryColor) 30 | { 31 | Log.Information("Changing Theme"); 32 | _paletteHelper.SetTheme(Theme.Create(theme, primaryColor, secondaryColor)); 33 | } 34 | 35 | public static Color ConvertPrimaryColor(PrimaryColor primaryColor) 36 | { 37 | return SwatchHelper.Lookup[(MaterialDesignColor)primaryColor]; 38 | } 39 | public static Color ConvertSecondaryColor(SecondaryColor secondaryColor) 40 | { 41 | return SwatchHelper.Lookup[(MaterialDesignColor)secondaryColor]; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/MessageDialog.xaml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/ModContentValidationDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Navigation; 14 | using System.Windows.Shapes; 15 | 16 | namespace Fantome.MVVM.ModelViews.Dialogs 17 | { 18 | /// 19 | /// Interaction logic for ModContentValidationDialog.xaml 20 | /// 21 | public partial class ModContentValidationDialog : UserControl 22 | { 23 | public string Message { get; private set; } = ""; 24 | 25 | public ModContentValidationDialog(string folderType) 26 | { 27 | if(folderType == "WAD") 28 | { 29 | this.Message += "The content of your selected WAD folder isn't valid.\n"; 30 | this.Message += "You cannot add anything other than .WAD.CLIENT files or folders into the WAD folder\n"; 31 | this.Message += "For more info on the Mod File format visit the Fantome wiki"; 32 | } 33 | else if(folderType == "RAW") 34 | { 35 | this.Message += "The content of your selected RAW folder isn't valid.\n"; 36 | this.Message += "You cannot add .WAD.CLIENT files or folders into the RAW folder\n"; 37 | this.Message += "For more info on the Mod File format visit the Fantome wiki"; 38 | } 39 | 40 | InitializeComponent(); 41 | 42 | this.DataContext = this; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/ModContentValidationDialog.xaml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Fantome/Utilities/UpdateHelper.cs: -------------------------------------------------------------------------------- 1 | using Octokit; 2 | using Serilog; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace Fantome.Utilities 11 | { 12 | public static class UpdateHelper 13 | { 14 | public static async Task CheckForUpdate() 15 | { 16 | try 17 | { 18 | GitHubClient gitClient = new GitHubClient(new ProductHeaderValue("Fantome")); 19 | 20 | IReadOnlyList releases = await gitClient.Repository.Release.GetAll("LoL-Fantome", "Fantome"); 21 | Release newestRelease = releases[0]; 22 | 23 | if (Version.TryParse(newestRelease.TagName, out Version newestVersion)) 24 | { 25 | if (!newestRelease.Prerelease && newestVersion > Assembly.GetExecutingAssembly().GetName().Version) 26 | { 27 | await DialogHelper.ShowMessageDialog("A new version of Fantome is available." + '\n' + @"Click the ""Update"" button to download it."); 28 | 29 | return new UpdateInfo(true); 30 | } 31 | } 32 | } 33 | catch (Exception exception) 34 | { 35 | Log.Information("Unable to check for updates " + exception); 36 | } 37 | 38 | return new UpdateInfo(false); 39 | } 40 | } 41 | 42 | public sealed class UpdateInfo 43 | { 44 | public bool IsUpdateAvailable { get; private set; } 45 | 46 | public UpdateInfo(bool isUpdateAvailable) 47 | { 48 | this.IsUpdateAvailable = isUpdateAvailable; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Fantome/Utilities/MaterialDesign/TreeHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Windows; 4 | 5 | namespace Fantome.Utilities.MaterialDesign 6 | { 7 | public class TreeHelpers 8 | { 9 | public static readonly DependencyProperty ModifiersProperty = DependencyProperty.RegisterAttached( 10 | "Modifiers", typeof(ModifierCollection), typeof(TreeHelpers), new PropertyMetadata(default(ModifierCollection), PropertyChangedCallback)); 11 | 12 | private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) 13 | { 14 | if (dependencyObject is FrameworkElement element && !element.IsLoaded) 15 | { 16 | element.Loaded += ElementOnLoaded; 17 | } 18 | else 19 | { 20 | ApplyModifiers(e.NewValue as IEnumerable); 21 | } 22 | 23 | void ApplyModifiers(IEnumerable modifiers) 24 | { 25 | foreach (var modifier in modifiers ?? Enumerable.Empty()) 26 | { 27 | modifier.Apply(dependencyObject); 28 | } 29 | } 30 | 31 | void ElementOnLoaded(object sender, RoutedEventArgs routedEventArgs) 32 | { 33 | ((FrameworkElement) sender).Loaded -= ElementOnLoaded; 34 | ApplyModifiers(GetModifiers((FrameworkElement) sender)); 35 | } 36 | } 37 | 38 | public static void SetModifiers(DependencyObject element, ModifierCollection value) 39 | { 40 | element.SetValue(ModifiersProperty, value); 41 | } 42 | 43 | public static ModifierCollection GetModifiers(DependencyObject element) 44 | { 45 | return (ModifierCollection) element.GetValue(ModifiersProperty); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Fantome/ModManagement/IO/ModInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Converters; 4 | 5 | namespace Fantome.ModManagement.IO 6 | { 7 | public class ModInfo : IEquatable 8 | { 9 | public string Name { get; private set; } 10 | public string Author { get; private set; } 11 | public string Version { get; private set; } 12 | public string Description { get; private set; } 13 | public ModCategory Category { get; private set; } 14 | 15 | public ModInfo(string name, string author, string version, string description, ModCategory category) 16 | { 17 | this.Name = name; 18 | this.Author = author; 19 | this.Version = version; 20 | this.Description = description; 21 | this.Category = category; 22 | } 23 | 24 | public string CreateID() 25 | { 26 | return string.Format("{0} - {1} (by {2})", this.Name, this.Version, this.Author); 27 | } 28 | 29 | public string Serialize() 30 | { 31 | return JsonConvert.SerializeObject(this, Formatting.Indented, new VersionConverter()); 32 | } 33 | public static ModInfo Deserialize(string json) 34 | { 35 | return JsonConvert.DeserializeObject(json, new VersionConverter()); 36 | } 37 | 38 | public bool Equals(ModInfo other) 39 | { 40 | return this == other; 41 | } 42 | public static bool operator ==(ModInfo info1, ModInfo info2) 43 | { 44 | return info1?.CreateID() == info2?.CreateID(); 45 | } 46 | public static bool operator !=(ModInfo info1, ModInfo info2) 47 | { 48 | return info1?.CreateID() != info2?.CreateID(); 49 | } 50 | } 51 | 52 | public enum ModCategory 53 | { 54 | Character, 55 | HUD, 56 | Map, 57 | Audio, 58 | Miscellaneous 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/UninstallingModDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | using Fantome.MVVM.ViewModels; 17 | using Fantome.Utilities; 18 | 19 | namespace Fantome.MVVM.ModelViews.Dialogs 20 | { 21 | /// 22 | /// Interaction logic for UninstallingModDialog.xaml 23 | /// 24 | public partial class UninstallingModDialog : UserControl 25 | { 26 | public UninstallingModViewModel ViewModel => this.DataContext as UninstallingModViewModel; 27 | 28 | public UninstallingModDialog() 29 | { 30 | InitializeComponent(); 31 | } 32 | 33 | public void StartUninstallation(object sender, EventArgs e) 34 | { 35 | BackgroundWorker worker = new BackgroundWorker(); 36 | worker.DoWork += InstallMod; 37 | worker.RunWorkerCompleted += CloseDialog; 38 | worker.WorkerSupportsCancellation = true; 39 | 40 | worker.RunWorkerAsync(new Tuple(worker, this.ViewModel)); 41 | } 42 | 43 | private void CloseDialog(object sender, RunWorkerCompletedEventArgs e) 44 | { 45 | DialogHelper.OperationDialog.IsOpen = false; 46 | } 47 | 48 | private void InstallMod(object sender, DoWorkEventArgs e) 49 | { 50 | Tuple argument = e.Argument as Tuple; 51 | 52 | argument.Item2.Uninstall(); 53 | argument.Item1.CancelAsync(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/GeneratingWadFilesDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using Fantome.ModManagement; 2 | using Fantome.ModManagement.IO; 3 | using Fantome.Utilities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Windows; 11 | using System.Windows.Controls; 12 | using System.Windows.Data; 13 | using System.Windows.Documents; 14 | using System.Windows.Input; 15 | using System.Windows.Media; 16 | using System.Windows.Media.Imaging; 17 | using System.Windows.Navigation; 18 | using System.Windows.Shapes; 19 | 20 | namespace Fantome.MVVM.ModelViews.Dialogs 21 | { 22 | /// 23 | /// Interaction logic for GeneratingWadFilesDialog.xaml 24 | /// 25 | public partial class GeneratingWadFilesDialog : UserControl 26 | { 27 | public string GeneratingString { get; } 28 | 29 | private ModFile _mod; 30 | private LeagueFileIndex _index; 31 | 32 | public GeneratingWadFilesDialog(ModFile mod, LeagueFileIndex index) 33 | { 34 | this.DataContext = this; 35 | this._mod = mod; 36 | this._index = index; 37 | 38 | this.GeneratingString = string.Format("Generating WAD files for {0}...", mod.GetID()); 39 | 40 | InitializeComponent(); 41 | } 42 | 43 | public void StartGeneration(object sender, EventArgs e) 44 | { 45 | BackgroundWorker worker = new BackgroundWorker(); 46 | worker.DoWork += GenerateWadFiles; 47 | worker.RunWorkerCompleted += CloseDialog; 48 | worker.WorkerSupportsCancellation = true; 49 | 50 | worker.RunWorkerAsync(); 51 | } 52 | 53 | private void CloseDialog(object sender, RunWorkerCompletedEventArgs e) 54 | { 55 | DialogHelper.OperationDialog.IsOpen = false; 56 | } 57 | 58 | private void GenerateWadFiles(object sender, DoWorkEventArgs e) 59 | { 60 | this._mod.GenerateWadFiles(this._index); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/SettingsDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | using Fantome.MVVM.ViewModels; 17 | using Fantome.Utilities; 18 | using Microsoft.WindowsAPICodePack.Dialogs; 19 | 20 | namespace Fantome.MVVM.ModelViews.Dialogs 21 | { 22 | /// 23 | /// Interaction logic for SettingsDialog.xaml 24 | /// 25 | public partial class SettingsDialog : UserControl 26 | { 27 | public SettingsViewModel ViewModel { get => this.DataContext as SettingsViewModel; } 28 | 29 | public SettingsDialog() 30 | { 31 | InitializeComponent(); 32 | } 33 | 34 | private async void SelectLeagueLocationButton_Click(object sender, RoutedEventArgs e) 35 | { 36 | CommonOpenFileDialog dialog = new CommonOpenFileDialog() 37 | { 38 | IsFolderPicker = true 39 | }; 40 | 41 | if (dialog.ShowDialog() == CommonFileDialogResult.Ok) 42 | { 43 | if (File.Exists(string.Format(@"{0}\League of Legends.exe", dialog.FileName))) 44 | { 45 | this.ViewModel.LeagueLocation = dialog.FileName; 46 | } 47 | else 48 | { 49 | await DialogHelper.ShowMessageDialog("You've selected an incorrect League of Legends game folder.\n" + 50 | @"Make sure it you're selecting the ""Game"" folder that contains the League of Legends.exe file" + '\n' + 51 | @"For official servers this is: C:\Riot Games\League of Legends\Game" + '\n' + 52 | @"For Garena: C:\Program Files (x86)\lol\{numbers}\Game"); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/LeagueLocationDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Controls; 9 | using System.Windows.Data; 10 | using System.Windows.Documents; 11 | using System.Windows.Input; 12 | using System.Windows.Media; 13 | using System.Windows.Media.Imaging; 14 | using System.Windows.Navigation; 15 | using System.Windows.Shapes; 16 | using Fantome.MVVM.ViewModels; 17 | using Fantome.Utilities; 18 | using Microsoft.WindowsAPICodePack.Dialogs; 19 | 20 | namespace Fantome.MVVM.ModelViews.Dialogs 21 | { 22 | /// 23 | /// Interaction logic for SelectLeagueLocationDialog.xaml 24 | /// 25 | public partial class LeagueLocationDialog : UserControl 26 | { 27 | public LeagueLocationDialogViewModel ViewModel { get => this.DataContext as LeagueLocationDialogViewModel; } 28 | 29 | public LeagueLocationDialog() 30 | { 31 | InitializeComponent(); 32 | } 33 | 34 | private async void SelectLeagueLocationButton_Click(object sender, RoutedEventArgs e) 35 | { 36 | CommonOpenFileDialog dialog = new CommonOpenFileDialog() 37 | { 38 | IsFolderPicker = true 39 | }; 40 | 41 | if (dialog.ShowDialog() == CommonFileDialogResult.Ok) 42 | { 43 | if (File.Exists(string.Format(@"{0}\League of Legends.exe", dialog.FileName))) 44 | { 45 | this.ViewModel.LeagueLocation = dialog.FileName; 46 | } 47 | else 48 | { 49 | await DialogHelper.ShowMessageDialog("You've selected an incorrect League of Legends game folder.\n" + 50 | @"Make sure it you're selecting the ""Game"" folder that contains the League of Legends.exe file" + '\n' + 51 | @"For official servers this is: C:\Riot Games\League of Legends\Game" + '\n' + 52 | @"For Garena: C:\Program Files (x86)\lol\{numbers}\Game"); 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/ModListItemViewModel.cs: -------------------------------------------------------------------------------- 1 | using Fantome.ModManagement; 2 | using Fantome.ModManagement.IO; 3 | using Fantome.Utilities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Drawing.Imaging; 8 | using System.IO; 9 | using System.Linq; 10 | using System.Runtime.CompilerServices; 11 | using System.Text; 12 | using System.Threading.Tasks; 13 | using System.Windows.Media.Imaging; 14 | 15 | namespace Fantome.MVVM.ViewModels 16 | { 17 | public class ModListItemViewModel : PropertyNotifier 18 | { 19 | public bool IsInstalled 20 | { 21 | get => this._isInstalled; 22 | set 23 | { 24 | this._isInstalled = value; 25 | NotifyPropertyChanged(); 26 | } 27 | } 28 | public string Name => this.Mod.Info.Name; 29 | public string Author => this.Mod.Info.Author; 30 | public string Version => this.Mod.Info.Version.ToString(); 31 | public BitmapImage Image => this._image; 32 | public ModFile Mod { get; private set; } 33 | 34 | private bool _isInstalled; 35 | private BitmapImage _image; 36 | 37 | private ModListViewModel _modList; 38 | 39 | public ModListItemViewModel(ModFile mod, ModListViewModel modList) 40 | { 41 | this.Mod = mod; 42 | this._modList = modList; 43 | 44 | if (mod.Image != null) 45 | { 46 | MemoryStream memoryStream = new MemoryStream(); 47 | BitmapImage bitmap = new BitmapImage(); 48 | 49 | mod.Image.Save(memoryStream, ImageFormat.Png); 50 | bitmap.BeginInit(); 51 | bitmap.StreamSource = memoryStream; 52 | bitmap.EndInit(); 53 | 54 | this._image = bitmap; 55 | } 56 | } 57 | 58 | public async Task Install(bool forceInstall = false) 59 | { 60 | await this._modList.InstallMod(this, forceInstall); 61 | } 62 | public async Task Uninstall(bool forceUninstall = false) 63 | { 64 | await this._modList.UninstallMod(this, forceUninstall); 65 | } 66 | public void Remove() 67 | { 68 | this._modList.RemoveMod(this); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /Fantome/App.xaml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Fantome/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 34 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/AssetCollisionDialog.xaml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Fantome/ModManagement/ModDatabase.cs: -------------------------------------------------------------------------------- 1 | using Fantome.ModManagement.IO; 2 | using Newtonsoft.Json; 3 | using Serilog; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | 8 | 9 | namespace Fantome.ModManagement 10 | { 11 | public class ModDatabase 12 | { 13 | public Dictionary Mods { get; set; } = new Dictionary(); 14 | 15 | private Dictionary _modFiles = new Dictionary(); 16 | 17 | public void AddMod(ModFile mod, bool isInstalled) 18 | { 19 | this.Mods.Add(mod.GetID(), isInstalled); 20 | this._modFiles.Add(mod.GetID(), mod); 21 | 22 | Write(); 23 | } 24 | public void RemoveMod(string id) 25 | { 26 | this.Mods.Remove(id); 27 | this._modFiles.Remove(id); 28 | 29 | Write(); 30 | } 31 | public void ChangeModState(string id, bool isInstalled) 32 | { 33 | this.Mods[id] = isInstalled; 34 | 35 | Write(); 36 | } 37 | public ModFile GetMod(string id) 38 | { 39 | ModFile mod = this._modFiles[id]; 40 | 41 | if(!mod.IsOpen) 42 | { 43 | mod.Reopen(); 44 | } 45 | 46 | return mod; 47 | } 48 | 49 | public void MountMods() 50 | { 51 | foreach (var mod in this.Mods) 52 | { 53 | string modPath = Path.Combine(ModManager.MOD_FOLDER, mod.Key + ".fantome"); 54 | if(File.Exists(modPath)) 55 | { 56 | try 57 | { 58 | this._modFiles.Add(mod.Key, new ModFile(modPath)); 59 | Log.Information("Mounted Mod: {0}", modPath); 60 | } 61 | catch(Exception excepton) 62 | { 63 | Log.Warning("Failed to mount Mod: {0}", modPath); 64 | Log.Warning("REASON: {0}", excepton); 65 | } 66 | } 67 | else 68 | { 69 | Log.Warning("Mod does not exist: {0}", modPath ); 70 | } 71 | } 72 | } 73 | 74 | public bool IsInstalled(string modID) 75 | { 76 | if (this.Mods.ContainsKey(modID)) 77 | { 78 | return this.Mods[modID]; 79 | } 80 | 81 | return false; 82 | } 83 | public bool ContainsMod(string modID) 84 | { 85 | return this.Mods.ContainsKey(modID); 86 | } 87 | 88 | public string Serialize() 89 | { 90 | return JsonConvert.SerializeObject(this, Formatting.Indented); 91 | } 92 | public static ModDatabase Deserialize(string json) 93 | { 94 | return JsonConvert.DeserializeObject(json); 95 | } 96 | public void Write(string fileLocation = ModManager.DATABASE_FILE) 97 | { 98 | File.WriteAllText(fileLocation, Serialize()); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/LeagueLocationDialog.xaml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /Fantome/Fantome.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net5.0-windows 6 | true 7 | 1.2 8 | Crauzer 9 | x64 10 | app.manifest 11 | Resources\icon.ico 12 | GPL-3.0-or-later 13 | https://github.com/LoL-Fantome/Fantome 14 | https://github.com/LoL-Fantome/Fantome 15 | Debug;Release;ReleasePortable 16 | 1.2.0.0 17 | 18 | 19 | 20 | 21 | false 22 | win-x64 23 | true 24 | en 25 | true 26 | 27 | 28 | 29 | 30 | true 31 | win-x64 32 | true 33 | en 34 | true 35 | true 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | LoLCustomSharp.dll 57 | 58 | 59 | 60 | 61 | 62 | Always 63 | 64 | 65 | Always 66 | 67 | 68 | 69 | 70 | 71 | True 72 | True 73 | Resources.resx 74 | 75 | 76 | 77 | 78 | 79 | PublicResXFileCodeGenerator 80 | Resources.Designer.cs 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /Fantome/Utilities/StartupGuard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.AccessControl; 6 | using System.Security.Principal; 7 | using System.Windows; 8 | 9 | namespace Fantome.Utilities 10 | { 11 | public static class StartupGuard 12 | { 13 | public static void CheckEnvironment() 14 | { 15 | //First we check if Fantome is running in Wine and if it isn't then we can check windows version 16 | if (!WineDetector.IsRunningInWine()) 17 | { // AS OF VERSION 1.1, FANTOME SUPPORTS ALL WINDOWS VERSIONS 18 | //OperatingSystem operatingSystem = Environment.OSVersion; 19 | //if (operatingSystem.Version.Major != 10) 20 | //{ 21 | // MessageBox.Show("You need to be running Windows 10 in order to properly use Fantome\n" 22 | // + @"By clicking the ""OK"" button you acknowledge that Fantome may not work correctly on your Windows version", 23 | // "", MessageBoxButton.OK, MessageBoxImage.Error); 24 | //} 25 | } 26 | } 27 | public static void CheckForExistingProcess() 28 | { 29 | foreach (Process process in Process.GetProcessesByName("Fantome").Where(x => x.Id != Process.GetCurrentProcess().Id)) 30 | { 31 | if (process.MainModule.ModuleName == "Fantome.exe") 32 | { 33 | if (MessageBox.Show("There is already a running instance of Fantome.\nPlease check your tray.", "", MessageBoxButton.OK, MessageBoxImage.Error) == MessageBoxResult.OK) 34 | { 35 | Application.Current.Shutdown(); 36 | } 37 | } 38 | } 39 | } 40 | public static void CheckForPathUnicodeCharacters() 41 | { 42 | string currentDirectory = Environment.CurrentDirectory; 43 | if (currentDirectory.Any(c => c > 255)) 44 | { 45 | string message = currentDirectory + '\n'; 46 | message += "The path to Fantome contains Unicode characters, please remove them from the path or move Fantome to a different directory\n"; 47 | message += "Unicode characters are letters from languages such as Russian, Chinese etc...."; 48 | 49 | if (MessageBox.Show(message, "", MessageBoxButton.OK, MessageBoxImage.Error) == MessageBoxResult.OK) 50 | { 51 | Application.Current.Shutdown(); 52 | } 53 | } 54 | } 55 | public static void CheckEnvironmentPrivilage() 56 | { 57 | try 58 | { 59 | // If any of these throw an exception then we cannot continue operating 60 | Directory.CreateDirectory("test"); 61 | Directory.Delete("test"); 62 | 63 | File.WriteAllBytes("test", new byte[0]); 64 | File.Delete("test"); 65 | } 66 | catch(Exception exception) 67 | { 68 | string messageFormat = "Fantome does not have the required access rights to work within its folder\n{0}"; 69 | if (MessageBox.Show(string.Format(messageFormat, exception), "", MessageBoxButton.OK, MessageBoxImage.Error) == MessageBoxResult.OK) 70 | { 71 | Application.Current.Shutdown(); 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /Fantome/Utilities/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using MaterialDesignColors; 8 | using MaterialDesignThemes.Wpf; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Converters; 11 | using Newtonsoft.Json.Linq; 12 | 13 | namespace Fantome.Utilities 14 | { 15 | public static class Config 16 | { 17 | public const string CONFIG_FILE = "CONFIG.json"; 18 | 19 | private static readonly Dictionary _defaultConfig = new Dictionary 20 | { 21 | { "LeagueLocation", "" }, 22 | { "LoggingPattern", "{Timestamp:dd-MM-yyyy HH:mm:ss.fff} | [{Level}] | {Message:lj}{NewLine}{Exception}" }, 23 | { "ModListType", 0 }, 24 | { "ParallelWadInstallation", false }, 25 | { "PackWadFolders", true }, 26 | { "InstallAddedMods", true }, 27 | { "IsDarkTheme", true }, 28 | { "PrimaryColor", PrimaryColor.Teal }, 29 | { "SecondaryColor", SecondaryColor.Lime }, 30 | { "MinimizeToTray", true } 31 | }; 32 | private static Dictionary _config = new Dictionary(); 33 | 34 | public static T Get(string key) 35 | { 36 | if (!_config.ContainsKey(key)) 37 | { 38 | return GetDefault(key); 39 | } 40 | 41 | if (typeof(T).BaseType == typeof(Enum)) 42 | { 43 | return (T)Enum.Parse(typeof(T), _config[key].ToString()); 44 | } 45 | else if (typeof(T).BaseType == typeof(Array)) 46 | { 47 | return (_config[key] as JArray).ToObject(); 48 | } 49 | else 50 | { 51 | return (T)Convert.ChangeType(_config[key], typeof(T)); 52 | } 53 | } 54 | public static T GetDefault(string key) 55 | { 56 | return (T)_config[key]; 57 | } 58 | public static void Set(string key, object value) 59 | { 60 | if (_config.ContainsKey(key)) 61 | { 62 | _config[key] = value; 63 | } 64 | else 65 | { 66 | _config.Add(key, value); 67 | } 68 | 69 | Write(); 70 | } 71 | 72 | public static void Load(string fileLocation = CONFIG_FILE) 73 | { 74 | if (File.Exists(CONFIG_FILE)) 75 | { 76 | Deserialize(File.ReadAllText(fileLocation)); 77 | 78 | //Check if config is outdated 79 | foreach (KeyValuePair configEntry in _defaultConfig) 80 | { 81 | if (!_config.ContainsKey(configEntry.Key)) 82 | { 83 | _config.Add(configEntry.Key, configEntry.Value); 84 | } 85 | } 86 | } 87 | else 88 | { 89 | _config = _defaultConfig; 90 | } 91 | } 92 | public static void Write(string fileLocation = CONFIG_FILE) 93 | { 94 | File.WriteAllText(fileLocation, Serialize()); 95 | } 96 | public static string Serialize() 97 | { 98 | return JsonConvert.SerializeObject(_config, Formatting.Indented); 99 | } 100 | public static void Deserialize(string json) 101 | { 102 | _config = JsonConvert.DeserializeObject>(json); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /Fantome/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Fantome.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | public class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | public static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Fantome.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | public static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Bitmap. 65 | /// 66 | public static System.Drawing.Bitmap fantome_logo { 67 | get { 68 | object obj = ResourceManager.GetObject("fantome_logo", resourceCulture); 69 | return ((System.Drawing.Bitmap)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 75 | /// 76 | public static System.Drawing.Icon icon { 77 | get { 78 | object obj = ResourceManager.GetObject("icon", resourceCulture); 79 | return ((System.Drawing.Icon)(obj)); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Fantome/Utilities/DialogHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Fantome.ModManagement; 7 | using Fantome.ModManagement.IO; 8 | using Fantome.MVVM.ViewModels; 9 | using Fantome.MVVM.ModelViews.Dialogs; 10 | using MaterialDesignThemes.Wpf; 11 | using Fantome.MVVM.ViewModels.CreateMod; 12 | 13 | namespace Fantome.Utilities 14 | { 15 | public static class DialogHelper 16 | { 17 | public static DialogHost MessageDialog { get; set; } 18 | public static DialogHost OperationDialog { get; set; } 19 | public static DialogHost RootDialog { get; set; } 20 | 21 | public static async void ShowModContentValidationDialog(string folderType) 22 | { 23 | ModContentValidationDialog dialog = new ModContentValidationDialog(folderType); 24 | 25 | await DialogHost.Show(dialog, "MessageDialog"); 26 | } 27 | 28 | public static async Task ShowAssetConflictDialog(ModFile mod, List collisions) 29 | { 30 | AssetCollisionDialog dialog = new AssetCollisionDialog(mod, collisions); 31 | 32 | return await DialogHost.Show(dialog, "MessageDialog"); 33 | } 34 | 35 | public static async Task ShowMessageDialog(string message) 36 | { 37 | MessageDialog dialog = new MessageDialog(message); 38 | 39 | await DialogHost.Show(dialog, "MessageDialog"); 40 | } 41 | 42 | public static async Task ShowGenerateWadFilesDialog(ModFile mod, LeagueFileIndex index) 43 | { 44 | GeneratingWadFilesDialog dialog = new GeneratingWadFilesDialog(mod, index); 45 | 46 | return await DialogHost.Show(dialog, "OperationDialog", dialog.StartGeneration, null); 47 | } 48 | 49 | public static async Task ShowInstallModDialog(ModFile mod, ModManager modManager) 50 | { 51 | InstallingModDialog dialog = new InstallingModDialog() 52 | { 53 | DataContext = new InstallingModViewModel(mod, modManager) 54 | }; 55 | 56 | return await DialogHost.Show(dialog, "OperationDialog", dialog.StartInstallation, null); 57 | } 58 | 59 | public static async Task ShowUninstallModDialog(ModFile mod, ModManager modManager) 60 | { 61 | UninstallingModDialog dialog = new UninstallingModDialog() 62 | { 63 | DataContext = new UninstallingModViewModel(mod, modManager) 64 | }; 65 | 66 | return await DialogHost.Show(dialog, "OperationDialog", dialog.StartUninstallation, null); 67 | } 68 | 69 | public static async Task ShowCreateModDialog(LeagueFileIndex index) 70 | { 71 | CreateModDialogViewModel dialogModel = new CreateModDialogViewModel(index); 72 | CreateModDialog dialog = new CreateModDialog(index, dialogModel); 73 | 74 | object result = await DialogHost.Show(dialog, "RootDialog", (dialog.DataContext as CreateModDialogViewModel).ClosingEventHandler); 75 | if ((bool)result) 76 | { 77 | return dialogModel.GetCreatedMod(); 78 | } 79 | else 80 | { 81 | return null; 82 | } 83 | } 84 | 85 | public static async Task ShowSettingsDialog() 86 | { 87 | SettingsDialog dialog = new SettingsDialog 88 | { 89 | DataContext = new SettingsViewModel() 90 | }; 91 | 92 | return await DialogHost.Show(dialog, "RootDialog", (dialog.DataContext as SettingsViewModel).OnSaveSettings); 93 | } 94 | 95 | public static async Task ShowLeagueLocationSelectionDialog() 96 | { 97 | LeagueLocationDialog dialog = new LeagueLocationDialog() 98 | { 99 | DataContext = new LeagueLocationDialogViewModel() 100 | }; 101 | 102 | object result = await DialogHost.Show(dialog, "RootDialog"); 103 | if ((bool)result == true) 104 | { 105 | return dialog.ViewModel.LeagueLocation; 106 | } 107 | 108 | return string.Empty; 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/ModCard.xaml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 87 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/ModListViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Fantome.ModManagement; 10 | using Fantome.ModManagement.IO; 11 | using Fantome.Utilities; 12 | using Serilog; 13 | 14 | namespace Fantome.MVVM.ViewModels 15 | { 16 | public class ModListViewModel : PropertyNotifier 17 | { 18 | public ObservableCollection Items 19 | { 20 | get => this._items; 21 | set 22 | { 23 | this._items = value; 24 | NotifyPropertyChanged(); 25 | } 26 | } 27 | public ObservableCollection Categories 28 | { 29 | get => this._categories; 30 | set 31 | { 32 | this._categories = value; 33 | NotifyPropertyChanged(); 34 | } 35 | } 36 | 37 | private ObservableCollection _items = new ObservableCollection(); 38 | private ObservableCollection _categories = new(); 39 | 40 | public ModManager ModManager { get; private set; } 41 | 42 | public ModListViewModel() 43 | { 44 | Log.Information("Creating a new ModListViewModel instance"); 45 | 46 | this.ModManager = new ModManager(); 47 | } 48 | 49 | public void Initialize(string leagueLocation) 50 | { 51 | Log.Information("Initializing ModListViewModel"); 52 | 53 | this.ModManager.Initialize(leagueLocation); 54 | 55 | SyncWithModManager(); 56 | } 57 | 58 | private void SyncWithModManager() 59 | { 60 | Log.Information("Syncing with Mod Manager"); 61 | 62 | //Remove non-existent mods 63 | List toRemove = new List(); 64 | foreach (ModListItemViewModel modItem in this.Items) 65 | { 66 | if (!this.ModManager.Database.ContainsMod(modItem.Mod.GetID())) 67 | { 68 | toRemove.Add(modItem); 69 | } 70 | } 71 | foreach (ModListItemViewModel modItem in toRemove) 72 | { 73 | RemoveMod(modItem); 74 | } 75 | 76 | //Check for new mods 77 | foreach (KeyValuePair modEntry in this.ModManager.Database.Mods) 78 | { 79 | this.Items.Add(new ModListItemViewModel(this.ModManager.Database.GetMod(modEntry.Key), this)); 80 | this.Items.Last().IsInstalled = modEntry.Value; 81 | } 82 | 83 | foreach(ModCategory category in Enum.GetValues(typeof(ModCategory)).Cast()) 84 | { 85 | this.Categories.Add(new ModListCategoryViewModel(category, new (this.Items.Where(x => x.Mod.Info.Category == category)))); 86 | } 87 | } 88 | 89 | public async Task AddMod(ModManager modManager, ModFile mod, bool install) 90 | { 91 | if (this.Items.Any(x => x.Mod == mod)) 92 | { 93 | await DialogHelper.ShowMessageDialog("A Mod with the same ID has already been added"); 94 | Log.Information("Cannot load Mod: {0} because it is already present in the database", mod.GetID()); 95 | } 96 | else 97 | { 98 | string validationError = mod.Validate(modManager.Index); 99 | if (!string.IsNullOrEmpty(validationError)) 100 | { 101 | await DialogHelper.ShowMessageDialog(validationError); 102 | } 103 | else 104 | { 105 | ModListItemViewModel modListItem = new ModListItemViewModel(mod, this); 106 | this.Items.Add(modListItem); 107 | this.Categories.First(x => x.Category == mod.Info.Category)?.Items.Add(modListItem); 108 | 109 | if (install) 110 | { 111 | await InstallMod(modListItem, true); 112 | } 113 | } 114 | } 115 | } 116 | public void RemoveMod(ModListItemViewModel mod) 117 | { 118 | this.ModManager.RemoveMod(mod.Mod); 119 | 120 | this.Items.Remove(mod); 121 | this.Categories.First(x => x.Category == mod.Mod.Info.Category).Items.Remove(mod); 122 | } 123 | 124 | public async Task InstallMod(ModListItemViewModel modItem, bool forceInstall = false) 125 | { 126 | if ((modItem.IsInstalled && !this.ModManager.Database.IsInstalled(modItem.Mod.GetID())) || forceInstall) 127 | { 128 | //Validate Mod before installation 129 | string validationError = modItem.Mod.Validate(this.ModManager.Index); 130 | if (!string.IsNullOrEmpty(validationError)) 131 | { 132 | await DialogHelper.ShowMessageDialog(validationError); 133 | modItem.IsInstalled = false; 134 | } 135 | else 136 | { 137 | //Generate WAD files for the Mod 138 | await DialogHelper.ShowGenerateWadFilesDialog(modItem.Mod, this.ModManager.Index); 139 | 140 | //Now we need to check for asset collisions 141 | List collisions = this.ModManager.Index.CheckForAssetCollisions(modItem.Mod.GetWadFiles(this.ModManager.Index)); 142 | if (collisions.Count != 0) 143 | { 144 | object uninstallCollisions = await DialogHelper.ShowAssetConflictDialog(modItem.Mod, collisions); 145 | if ((bool)uninstallCollisions == true) 146 | { 147 | foreach (string collision in collisions) 148 | { 149 | ModFile collisionMod = this.ModManager.Database.GetMod(collision); 150 | ModListItemViewModel collisionModViewModel = this.Items.FirstOrDefault(x => x.Mod == collisionMod); 151 | 152 | await UninstallMod(collisionModViewModel, true); 153 | } 154 | } 155 | else 156 | { 157 | modItem.IsInstalled = false; 158 | return; 159 | } 160 | } 161 | 162 | await DialogHelper.ShowInstallModDialog(modItem.Mod, this.ModManager); 163 | modItem.IsInstalled = true; 164 | } 165 | 166 | //Do this to release all memory allocated by the ZipArchive 167 | modItem.Mod.DisposeReopen(); 168 | } 169 | } 170 | 171 | public async Task UninstallMod(ModListItemViewModel modItem, bool forceUninstall = false) 172 | { 173 | if ((!modItem.IsInstalled && this.ModManager.Database.IsInstalled(modItem.Mod.GetID())) || forceUninstall) 174 | { 175 | await DialogHelper.ShowUninstallModDialog(modItem.Mod, this.ModManager); 176 | modItem.IsInstalled = false; 177 | 178 | //Do this to release all memory allocated by the ZipArchive 179 | modItem.Mod.DisposeReopen(); 180 | } 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/CreateMod/CreateModDialogViewModel.cs: -------------------------------------------------------------------------------- 1 | using Fantome.ModManagement; 2 | using Fantome.ModManagement.IO; 3 | using Fantome.MVVM.Commands; 4 | using Fantome.Utilities; 5 | using MaterialDesignThemes.Wpf; 6 | using Microsoft.WindowsAPICodePack.Dialogs; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Collections.ObjectModel; 10 | using System.IO; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | using System.Windows.Input; 14 | using System.Windows.Media.Imaging; 15 | using SImage = System.Drawing.Image; 16 | 17 | namespace Fantome.MVVM.ViewModels.CreateMod 18 | { 19 | public class CreateModDialogViewModel : PropertyNotifier 20 | { 21 | public string WadLocation 22 | { 23 | get => this._wadLocation; 24 | set 25 | { 26 | this._wadLocation = value; 27 | NotifyPropertyChanged(); 28 | } 29 | } 30 | public string RawLocation 31 | { 32 | get => this._rawLocation; 33 | set 34 | { 35 | this._rawLocation = value; 36 | NotifyPropertyChanged(); 37 | } 38 | } 39 | public string Name 40 | { 41 | get => this._name; 42 | set 43 | { 44 | this._name = value; 45 | NotifyPropertyChanged(); 46 | } 47 | } 48 | public string Author 49 | { 50 | get => this._author; 51 | set 52 | { 53 | this._author = value; 54 | NotifyPropertyChanged(); 55 | } 56 | } 57 | public string Version 58 | { 59 | get => this._version; 60 | set 61 | { 62 | this._version = value; 63 | NotifyPropertyChanged(); 64 | } 65 | } 66 | public List Categories => new (Enum.GetValues(typeof(ModCategory)).Cast()); 67 | public ModCategory Category 68 | { 69 | get => this._category; 70 | set 71 | { 72 | this._category = value; 73 | NotifyPropertyChanged(); 74 | } 75 | } 76 | public BitmapImage Image 77 | { 78 | get => this._image; 79 | set 80 | { 81 | this._image = value; 82 | NotifyPropertyChanged(); 83 | } 84 | } 85 | public ObservableCollection WadItems { get; set; } = new ObservableCollection(); 86 | 87 | private string _wadLocation; 88 | private string _rawLocation; 89 | private string _name; 90 | private string _author; 91 | private string _version = "1.0"; 92 | private ModCategory _category; 93 | private BitmapImage _image; 94 | private ModFile _createdMod; 95 | private LeagueFileIndex _index; 96 | 97 | public ICommand AddWadFilesCommand => new RelayCommand(AddWadFiles); 98 | public ICommand AddWadFoldersCommand => new RelayCommand(AddWadFolders); 99 | public ICommand RemoveWadFileCommand => new RelayCommand(RemoveWadItem); 100 | 101 | public CreateModDialogViewModel(LeagueFileIndex index) 102 | { 103 | this._index = index; 104 | } 105 | 106 | public async void AddWadFiles(object parameter) 107 | { 108 | CommonOpenFileDialog dialog = new CommonOpenFileDialog() 109 | { 110 | Multiselect = true 111 | }; 112 | List alreadyAddedWadFiles = new List(); 113 | List invalidWadFiles = new List(); 114 | 115 | dialog.Filters.Add(new CommonFileDialogFilter("wad.client Files", "wad.client")); 116 | 117 | if (dialog.ShowDialog() == CommonFileDialogResult.Ok) 118 | { 119 | //Adding and validation of WAD files 120 | foreach(string wadFilePath in dialog.FileNames) 121 | { 122 | string wadName = Path.GetFileName(wadFilePath); 123 | 124 | if (!ValidateWadItemPath(wadName)) 125 | { 126 | invalidWadFiles.Add(wadName); 127 | } 128 | else if (this.WadItems.Any(x => x.Name == wadName)) 129 | { 130 | alreadyAddedWadFiles.Add(wadName); 131 | } 132 | else 133 | { 134 | this.WadItems.Add(new CreateModWadItemViewModel(CreateModWadItemType.File, wadFilePath)); 135 | } 136 | } 137 | 138 | if(alreadyAddedWadFiles.Count != 0) 139 | { 140 | await ShowAlreadyAddedWadItems(alreadyAddedWadFiles); 141 | } 142 | if(invalidWadFiles.Count != 0) 143 | { 144 | await ShowInvalidWadItems(invalidWadFiles); 145 | } 146 | } 147 | } 148 | public async void AddWadFolders(object parameter) 149 | { 150 | CommonOpenFileDialog dialog = new CommonOpenFileDialog() 151 | { 152 | Multiselect = true, 153 | IsFolderPicker = true 154 | }; 155 | List alreadyAddedWadFiles = new List(); 156 | List invalidWadFolders = new List(); 157 | 158 | if (dialog.ShowDialog() == CommonFileDialogResult.Ok) 159 | { 160 | foreach (string wadFolderPath in dialog.FileNames) 161 | { 162 | string wadName = Path.GetFileName(wadFolderPath); 163 | 164 | if (!ValidateWadItemPath(wadName)) 165 | { 166 | invalidWadFolders.Add(wadName); 167 | } 168 | else if (this.WadItems.Any(x => x.Name == wadName)) 169 | { 170 | alreadyAddedWadFiles.Add(wadName); 171 | } 172 | else 173 | { 174 | this.WadItems.Add(new CreateModWadItemViewModel(CreateModWadItemType.Folder, wadFolderPath)); 175 | } 176 | } 177 | 178 | if (alreadyAddedWadFiles.Count != 0) 179 | { 180 | await ShowAlreadyAddedWadItems(alreadyAddedWadFiles); 181 | } 182 | if (invalidWadFolders.Count != 0) 183 | { 184 | await ShowInvalidWadItems(invalidWadFolders); 185 | } 186 | } 187 | } 188 | 189 | public bool ValidateWadItemPath(string path) 190 | { 191 | return !string.IsNullOrEmpty(this._index.FindWADPath(Path.GetFileName(path))); 192 | } 193 | 194 | public void RemoveWadItem(object parameter) 195 | { 196 | string wadName = parameter as string; 197 | if (!string.IsNullOrEmpty(wadName)) 198 | { 199 | this.WadItems.Remove(this.WadItems.FirstOrDefault(x => x.Name == wadName)); 200 | } 201 | } 202 | 203 | public async Task ShowInvalidWadItems(IEnumerable invalidWadNames) 204 | { 205 | string message = "The following WAD files are invalid:\n"; 206 | foreach (string invalidWadName in invalidWadNames) 207 | { 208 | message += invalidWadName + '\n'; 209 | } 210 | 211 | await DialogHelper.ShowMessageDialog(message); 212 | } 213 | public async Task ShowAlreadyAddedWadItems(IEnumerable alreadyAddedWadFiles) 214 | { 215 | string message = "The following WAD files were already added:\n"; 216 | foreach (string alreadyAddedWadFile in alreadyAddedWadFiles) 217 | { 218 | message += alreadyAddedWadFile + '\n'; 219 | } 220 | 221 | await DialogHelper.ShowMessageDialog(message); 222 | } 223 | 224 | public void ClosingEventHandler(object sender, DialogClosingEventArgs eventArgs) 225 | { 226 | if ((bool)eventArgs.Parameter == true) 227 | { 228 | IEnumerable wadFiles = this.WadItems 229 | .Where(x => x.Type == CreateModWadItemType.File) 230 | .Select(x => x.Path); 231 | 232 | IEnumerable wadFolders = this.WadItems 233 | .Where(x => x.Type == CreateModWadItemType.Folder) 234 | .Select(x => x.Path); 235 | 236 | ModInfo info = new ModInfo(this._name, this._author, this._version, "", this._category); 237 | SImage image = this._image == null ? null : SImage.FromStream(this._image.StreamSource); 238 | ModFile mod = new ModFile(this._index, wadFiles, wadFolders, info, image); 239 | 240 | this._createdMod = mod; 241 | } 242 | } 243 | 244 | public ModFile GetCreatedMod() 245 | { 246 | return this._createdMod; 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /Fantome/MVVM/ViewModels/MainWindowViewModel.cs: -------------------------------------------------------------------------------- 1 | using Fantome.ModManagement; 2 | using Fantome.ModManagement.IO; 3 | using Fantome.MVVM.Commands; 4 | using Fantome.MVVM.ModelViews.Dialogs; 5 | using Fantome.Utilities; 6 | using LoLCustomSharp; 7 | using MaterialDesignThemes.Wpf; 8 | using Microsoft.WindowsAPICodePack.Dialogs; 9 | using Serilog; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.ComponentModel; 13 | using System.Diagnostics; 14 | using System.IO; 15 | using System.IO.Compression; 16 | using System.Linq; 17 | using System.Runtime.CompilerServices; 18 | using System.Text; 19 | using System.Threading.Tasks; 20 | using System.Windows.Forms; 21 | using System.Windows.Input; 22 | using Newtonsoft.Json; 23 | 24 | namespace Fantome.MVVM.ViewModels 25 | { 26 | public class MainWindowViewModel: PropertyNotifier 27 | { 28 | // ---------- BINDINGS ----------- \\ 29 | public ModListViewModel ModList 30 | { 31 | get => this._modList; 32 | set 33 | { 34 | this._modList = value; 35 | NotifyPropertyChanged(); 36 | } 37 | } 38 | 39 | public bool IsModListTypeCard 40 | { 41 | get => Config.Get("ModListType") == 0; 42 | set 43 | { 44 | if (value) 45 | { 46 | Config.Set("ModListType", 0); 47 | } 48 | 49 | NotifyPropertyChanged(); 50 | } 51 | } 52 | public bool IsModListTypeRow 53 | { 54 | get => Config.Get("ModListType") == 1; 55 | set 56 | { 57 | if (value) 58 | { 59 | Config.Set("ModListType", 1); 60 | } 61 | 62 | NotifyPropertyChanged(); 63 | } 64 | } 65 | public bool IsUpdateAvailable 66 | { 67 | get => _isUpdateAvailable; 68 | set 69 | { 70 | this._isUpdateAvailable = value; 71 | NotifyPropertyChanged(); 72 | } 73 | } 74 | 75 | private bool _isUpdateAvailable; 76 | private ModListViewModel _modList; 77 | 78 | // ---------- CORE COMPONENTS ----------- \\ 79 | private OverlayPatcher _patcher; 80 | 81 | public NotifyIcon TrayIcon { get; private set; } 82 | 83 | // ---------- COMMANDS ----------- \\ 84 | public ICommand AddModCommand => new RelayCommand(AddMod); 85 | public ICommand RunSettingsDialogCommand => new RelayCommand(RunSettingsDialog); 86 | public ICommand RunCreateModDialogCommand => new RelayCommand(RunCreateModDialog); 87 | public ICommand OpenGithubCommand => new RelayCommand(OpenGithub); 88 | public ICommand OpenGithubReleasesCommand => new RelayCommand(OpenGithubReleases); 89 | 90 | public MainWindowViewModel() 91 | { 92 | Log.Information("Creating a new MainWindowViewModel instance"); 93 | } 94 | 95 | // ---------- INITIALIZATION ----------- \\ 96 | public async Task Initialize() 97 | { 98 | Log.Information("Initializing MainWindowViewModel"); 99 | 100 | InitializeTrayIcon(); 101 | 102 | string leagueLocation = Config.Get("LeagueLocation"); 103 | if (string.IsNullOrEmpty(leagueLocation) || !LeagueLocationValidator.Validate(leagueLocation)) 104 | { 105 | Log.Information("Detected empty LeagueLocation in config"); 106 | 107 | leagueLocation = await DialogHelper.ShowLeagueLocationSelectionDialog(); 108 | Config.Set("LeagueLocation", leagueLocation); 109 | } 110 | 111 | this._modList = new ModListViewModel(); 112 | 113 | this._modList.Initialize(leagueLocation); 114 | 115 | InitializePatcher(); 116 | } 117 | private void InitializeTrayIcon() 118 | { 119 | Log.Information("Initializing Tray Icon"); 120 | 121 | string[] arguments = Environment.GetCommandLineArgs(); 122 | ContextMenuStrip contextMenuStrip = new ContextMenuStrip(); 123 | 124 | //Add Close button 125 | contextMenuStrip.Items.Add(new ToolStripMenuItem("Close", null, delegate (object sender, EventArgs args) 126 | { 127 | System.Windows.Application.Current.Shutdown(); 128 | })); 129 | 130 | //Create Icon 131 | this.TrayIcon = new NotifyIcon() 132 | { 133 | Visible = false, 134 | Icon = Properties.Resources.icon, 135 | ContextMenuStrip = contextMenuStrip 136 | }; 137 | } 138 | private void InitializePatcher() 139 | { 140 | Log.Information("Initializing League Patcher"); 141 | 142 | string overlayDirectory = (Directory.GetCurrentDirectory() + @"\" + ModManager.OVERLAY_FOLDER + @"\").Replace('\\', '/'); 143 | this._patcher = new OverlayPatcher(); 144 | this._patcher.Start(overlayDirectory, OnPatcherMessage, OnPatcherError); 145 | } 146 | 147 | // ---------- MOD OPERATIONS ----------- \\ 148 | private async void AddMod(object o) => await AddMod(); 149 | public async Task AddMod() 150 | { 151 | CommonOpenFileDialog dialog = new CommonOpenFileDialog("Choose the ZIP file of your mod") 152 | { 153 | Multiselect = false 154 | }; 155 | 156 | dialog.Filters.Add(new CommonFileDialogFilter("Fantome mod Files", "*.fantome;*.zip")); 157 | 158 | if (dialog.ShowDialog() == CommonFileDialogResult.Ok) 159 | { 160 | await AddMod(dialog.FileName); 161 | } 162 | } 163 | public async Task AddMod(string modOriginalPath) 164 | { 165 | string modPath = ""; 166 | string currentModInfo; 167 | 168 | try 169 | { 170 | using (ModFile originalMod = new ModFile(modOriginalPath)) 171 | { 172 | modPath = string.Format(@"{0}\{1}.fantome", ModManager.MOD_FOLDER, originalMod.GetID()); 173 | currentModInfo = JsonConvert.SerializeObject(originalMod.Info); 174 | } 175 | } 176 | catch (Exception exception) 177 | { 178 | Log.Error(exception, "Failed to read mod file: \"{ModOriginalPath}\"", modOriginalPath); 179 | await DialogHelper.ShowMessageDialog($"Failed to read mod file: \"{modOriginalPath}\"\n\n{exception.Message}"); 180 | return; 181 | } 182 | 183 | //If mod is not in Mods folder then we copy it 184 | if (!File.Exists(modPath)) 185 | { 186 | Log.Information("Copying Mod: {0} to {1}", modOriginalPath, modPath); 187 | 188 | try 189 | { 190 | File.Copy(modOriginalPath, modPath, true); 191 | using ZipArchive modZip = ZipFile.Open(modPath, ZipArchiveMode.Update); 192 | ZipArchiveEntry infoJsonEntry = modZip.GetEntry(@"META\info.json") ?? modZip.GetEntry("META/info.json"); 193 | if (infoJsonEntry != null) 194 | { 195 | await using StreamWriter writer = new StreamWriter(infoJsonEntry.Open()); 196 | writer.BaseStream.SetLength(0); 197 | writer.Write(currentModInfo); 198 | } 199 | } 200 | catch (Exception exception) 201 | { 202 | Log.Error(exception, "Failed to copy mod from: {ModOriginalPath} to {ModPath}", modOriginalPath, modPath); 203 | await DialogHelper.ShowMessageDialog($"Failed to copy mod from: {modOriginalPath} to {modPath}\n\n{exception.Message}"); 204 | return; 205 | } 206 | } 207 | 208 | Log.Information("Loading Mod: {0}", modPath); 209 | 210 | bool installAddedMods = Config.Get("InstallAddedMods"); 211 | ModFile mod = new ModFile(modPath); 212 | await this.ModList.AddMod(this._modList.ModManager, mod, installAddedMods); 213 | } 214 | 215 | // ---------- DIALOGS ----------- \\ 216 | private async void RunSettingsDialog(object o) 217 | { 218 | await DialogHelper.ShowSettingsDialog(); 219 | } 220 | private async void RunCreateModDialog(object o) 221 | { 222 | ModFile createdMod = await DialogHelper.ShowCreateModDialog(this._modList.ModManager.Index); 223 | 224 | if (createdMod != null) 225 | { 226 | await this.ModList.AddMod(this._modList.ModManager, createdMod, false); 227 | } 228 | } 229 | 230 | // ---------- GITHUB ----------- \\ 231 | private void OpenGithub(object o) 232 | { 233 | Process.Start("cmd", "/C start https://github.com/LoL-Fantome/Fantome"); 234 | } 235 | private void OpenGithubReleases(object o) 236 | { 237 | Process.Start("cmd", "/C start https://github.com/LoL-Fantome/Fantome/releases"); 238 | } 239 | 240 | // ---------- PATCHER MESSAGE PIPELINE ----------- \\ 241 | private void OnPatcherError(Exception exception) 242 | { 243 | Log.Error("PATCHER: {0}", exception); 244 | } 245 | private void OnPatcherMessage(string message) 246 | { 247 | Log.Information("PATCHER: {0}", message); 248 | } 249 | 250 | // ---------- UPDATES ----------- \\ 251 | public async Task CheckForUpdate() 252 | { 253 | UpdateInfo updateInfo = await UpdateHelper.CheckForUpdate(); 254 | 255 | this.IsUpdateAvailable = updateInfo.IsUpdateAvailable; 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /Fantome/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 107 | 134 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /Fantome/ModManagement/LeagueFileIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Converters; 8 | using Fantome.ModManagement.IO; 9 | using Serilog; 10 | using Fantome.Utilities; 11 | using LeagueToolkit.Helpers.Exceptions; 12 | using LeagueToolkit.IO.WadFile; 13 | 14 | namespace Fantome.ModManagement 15 | { 16 | public class LeagueFileIndex 17 | { 18 | public Version Version { get; set; } = new Version(0, 0, 0, 0); 19 | [JsonIgnore] public Dictionary> Game => this._gameIndex; 20 | [JsonIgnore] public Dictionary> Mod => this._modIndex; 21 | [JsonIgnore] public Dictionary> ModEntryMap => this._modEntryMap; 22 | [JsonIgnore] public Dictionary EntryModMap => this._entryModMap; 23 | [JsonIgnore] public Dictionary> WadModMap => this._wadModMap; 24 | 25 | [JsonProperty] private Dictionary> _gameIndex = new Dictionary>(); 26 | [JsonProperty] private Dictionary> _modIndex = new Dictionary>(); 27 | [JsonProperty] private Dictionary> _modEntryMap = new Dictionary>(); 28 | [JsonProperty] private Dictionary _entryModMap = new Dictionary(); 29 | [JsonProperty] private Dictionary> _wadModMap = new Dictionary>(); 30 | 31 | private Dictionary> _newModIndex = new Dictionary>(); 32 | private Dictionary> _newModEntryMap = new Dictionary>(); 33 | private Dictionary _newEntryModMap = new Dictionary(); 34 | private Dictionary> _newWadModMap = new Dictionary>(); 35 | 36 | private bool _isEditing = true; 37 | 38 | public LeagueFileIndex() { } 39 | public LeagueFileIndex(string leagueFolder) 40 | { 41 | string wadRootPath = Path.Combine(leagueFolder, @"DATA\FINAL"); 42 | this.Version = new Version(FileVersionInfo.GetVersionInfo(Path.Combine(leagueFolder, "League of Legends.exe")).FileVersion); 43 | 44 | foreach (string wadFile in Directory.GetFiles(wadRootPath, "*.wad.client", SearchOption.AllDirectories)) 45 | { 46 | try 47 | { 48 | using (Wad wad = Wad.Mount(wadFile, false)) 49 | { 50 | List fileHashes = new List(); 51 | foreach (var entry in wad.Entries) 52 | { 53 | fileHashes.Add(entry.Key); 54 | 55 | string gameWadPath = wadFile.Replace(leagueFolder + Pathing.GetPathSeparator(leagueFolder), ""); 56 | 57 | if (this._gameIndex.ContainsKey(entry.Key)) 58 | { 59 | this._gameIndex[entry.Key].Add(gameWadPath); 60 | } 61 | else 62 | { 63 | this._gameIndex.Add(entry.Key, new List() { gameWadPath }); 64 | } 65 | } 66 | } 67 | } 68 | catch(InvalidFileSignatureException) 69 | { 70 | // Silent error, this can happen because the Riot patcher is broken 71 | Log.Error("Found a orrupted/invalid game WAD file: " + wadFile); 72 | } 73 | catch(Exception exception) 74 | { 75 | throw new Exception("Failed to index WAD file: " + wadFile, exception); 76 | } 77 | } 78 | 79 | Write(); 80 | } 81 | 82 | public void StartEdit() 83 | { 84 | Log.Information("Starting Index Edit"); 85 | this._isEditing = true; 86 | } 87 | public void EndEdit() 88 | { 89 | this._isEditing = false; 90 | 91 | foreach (KeyValuePair> newModEntry in this._newModIndex) 92 | { 93 | this._modIndex.Add(newModEntry.Key, newModEntry.Value); 94 | } 95 | foreach (KeyValuePair> newModEntry in this._newModEntryMap) 96 | { 97 | this._modEntryMap.Add(newModEntry.Key, newModEntry.Value); 98 | } 99 | foreach (KeyValuePair newEntryMod in this._newEntryModMap) 100 | { 101 | this._entryModMap.Add(newEntryMod.Key, newEntryMod.Value); 102 | } 103 | foreach (KeyValuePair> newWadMod in this._newWadModMap) 104 | { 105 | if(this._wadModMap.ContainsKey(newWadMod.Key)) 106 | { 107 | this._wadModMap[newWadMod.Key].AddRange(newWadMod.Value); 108 | } 109 | else 110 | { 111 | this._wadModMap.Add(newWadMod.Key, newWadMod.Value); 112 | } 113 | } 114 | 115 | Log.Information("Commited Index changes"); 116 | 117 | this._newModIndex = new Dictionary>(); 118 | this._newModEntryMap = new Dictionary>(); 119 | this._newEntryModMap = new Dictionary(); 120 | this._newWadModMap = new Dictionary>(); 121 | 122 | Write(); 123 | } 124 | public void CancelEdit() 125 | { 126 | this._isEditing = false; 127 | this._newModIndex = new Dictionary>(); 128 | this._newModEntryMap = new Dictionary>(); 129 | this._newEntryModMap = new Dictionary(); 130 | this._newWadModMap = new Dictionary>(); 131 | } 132 | 133 | public void AddModFile(ulong hash, string modId, List wads) 134 | { 135 | if (this._isEditing) 136 | { 137 | //Add to Entry - WAD map 138 | if (this._newModIndex.ContainsKey(hash)) 139 | { 140 | this._newModIndex[hash] = wads; 141 | } 142 | else 143 | { 144 | this._newModIndex.Add(hash, wads); 145 | } 146 | 147 | //Add to Mod - Entry map 148 | if (this._newModEntryMap.ContainsKey(modId)) 149 | { 150 | if(!this._newModEntryMap[modId].Contains(hash)) 151 | { 152 | this._newModEntryMap[modId].Add(hash); 153 | } 154 | } 155 | else 156 | { 157 | this._newModEntryMap.Add(modId, new List() { hash }); 158 | } 159 | 160 | //Add to Entry - Mod map 161 | if (!this._newEntryModMap.ContainsKey(hash)) 162 | { 163 | this._newEntryModMap.Add(hash, modId); 164 | } 165 | } 166 | } 167 | public void RemoveModdedEntry(ulong hash, string modId) 168 | { 169 | if (this._modIndex.ContainsKey(hash)) 170 | { 171 | this._modIndex.Remove(hash); 172 | } 173 | else 174 | { 175 | Log.Warning("Unable to remove Entry: {0} installed by {1} in the Mod Index", hash, modId); 176 | } 177 | 178 | if (this._modEntryMap.ContainsKey(modId)) 179 | { 180 | this._modEntryMap[modId].Remove(hash); 181 | } 182 | else 183 | { 184 | Log.Warning("Unable to remove Mod: {0} from Mod Entry Map", modId); 185 | } 186 | 187 | if (this._entryModMap.ContainsKey(hash)) 188 | { 189 | this._entryModMap.Remove(hash); 190 | } 191 | else 192 | { 193 | Log.Warning("Unable to remove Entry: {0} from Entry Mod Map", hash); 194 | } 195 | 196 | //Remove Mod entry since it's empty 197 | if (this._modEntryMap.ContainsKey(modId) && this._modEntryMap[modId].Count == 0) 198 | { 199 | this._modEntryMap.Remove(modId); 200 | } 201 | 202 | if (!this._isEditing) 203 | { 204 | Write(); 205 | } 206 | } 207 | public void AddMod(string modId, List wads) 208 | { 209 | if (this._isEditing) 210 | { 211 | foreach (string wadPath in wads) 212 | { 213 | if (this._newWadModMap.ContainsKey(wadPath)) 214 | { 215 | this._newWadModMap[wadPath].Add(modId); 216 | } 217 | else 218 | { 219 | this._newWadModMap.Add(wadPath, new List() { modId }); 220 | } 221 | 222 | Log.Information("Added Mod: {0} to WAD: {1}", modId, wadPath); 223 | } 224 | } 225 | } 226 | public void RemoveMod(string modId) 227 | { 228 | List wadsToRemove = new List(); 229 | 230 | foreach (KeyValuePair> wadFile in this._wadModMap) 231 | { 232 | wadFile.Value.RemoveAll(x => x == modId); 233 | Log.Information("Removed Mod: {0} from WAD: {1} in WAD Mod Map", modId, wadFile.Key); 234 | 235 | if(wadFile.Value.Count == 0) 236 | { 237 | wadsToRemove.Add(wadFile.Key); 238 | } 239 | } 240 | 241 | foreach(string wadToRemove in wadsToRemove) 242 | { 243 | this._wadModMap.Remove(wadToRemove); 244 | } 245 | } 246 | 247 | public List CheckForAssetCollisions(Dictionary modWadFiles) 248 | { 249 | List collidingMods = new List(); 250 | 251 | foreach (KeyValuePair modWadFile in modWadFiles) 252 | { 253 | foreach (var entry in modWadFile.Value.Entries) 254 | { 255 | //Check whether the entry is already modded, if it is we add the colliding mod to the list 256 | if (this._modIndex.ContainsKey(entry.Key)) 257 | { 258 | string collidingMod = this._entryModMap[entry.Key]; 259 | if (!collidingMods.Contains(collidingMod)) 260 | { 261 | collidingMods.Add(collidingMod); 262 | } 263 | } 264 | } 265 | } 266 | 267 | return collidingMods; 268 | } 269 | public string FindWADPath(string wadName) 270 | { 271 | foreach (KeyValuePair> file in this._gameIndex) 272 | { 273 | foreach (string wadFile in file.Value) 274 | { 275 | if (wadFile.Contains(wadName, StringComparison.OrdinalIgnoreCase)) 276 | { 277 | return wadFile; 278 | } 279 | } 280 | } 281 | 282 | return string.Empty; 283 | } 284 | public List GetModWadFiles(string modId) 285 | { 286 | List wadFiles = new List(); 287 | 288 | foreach (KeyValuePair> wadFile in this._wadModMap) 289 | { 290 | if (wadFile.Value.Any(x => x == modId)) 291 | { 292 | wadFiles.Add(wadFile.Key); 293 | } 294 | } 295 | 296 | return wadFiles; 297 | } 298 | 299 | public void CopyModData(LeagueFileIndex targetIndex) 300 | { 301 | targetIndex.SetModIndex(this.Mod); 302 | targetIndex.SetModEntryMap(this.ModEntryMap); 303 | targetIndex.SetEntryModMap(this.EntryModMap); 304 | targetIndex.SetWadModMap(this.WadModMap); 305 | } 306 | internal void SetModIndex(Dictionary> modIndex) 307 | { 308 | this._modIndex = modIndex; 309 | } 310 | internal void SetModEntryMap(Dictionary> modEntryMap) 311 | { 312 | this._modEntryMap = modEntryMap; 313 | } 314 | internal void SetEntryModMap(Dictionary entryModMap) 315 | { 316 | this._entryModMap = entryModMap; 317 | } 318 | internal void SetWadModMap(Dictionary> wadModMap) 319 | { 320 | this._wadModMap = wadModMap; 321 | } 322 | 323 | public static LeagueFileIndex Deserialize(string json) 324 | { 325 | return JsonConvert.DeserializeObject(json, new VersionConverter()); 326 | } 327 | public string Serialize() 328 | { 329 | return JsonConvert.SerializeObject(this, Formatting.Indented, new VersionConverter()); 330 | } 331 | public void Write(string fileLocation = ModManager.INDEX_FILE) 332 | { 333 | File.WriteAllText(fileLocation, Serialize()); 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /Fantome/MVVM/ModelViews/Dialogs/CreateModDialog.xaml: -------------------------------------------------------------------------------- 1 | 20 | 21 | 22 | 23 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 185 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 204 | 209 | 210 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /Fantome/ModManagement/ModManager.cs: -------------------------------------------------------------------------------- 1 | using Fantome.ModManagement.IO; 2 | using Fantome.ModManagement.WAD; 3 | using Fantome.Utilities; 4 | using LeagueToolkit.IO.WadFile; 5 | using Serilog; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Threading.Tasks; 12 | 13 | namespace Fantome.ModManagement 14 | { 15 | public class ModManager 16 | { 17 | public const string MOD_FOLDER = "Mods"; 18 | public const string OVERLAY_FOLDER = "Overlay"; 19 | public const string DATABASE_FILE = "MOD_DATABASE.json"; 20 | public const string INDEX_FILE = "FILE_INDEX.json"; 21 | 22 | public LeagueFileIndex Index { get; private set; } 23 | public string LeagueFolder { get; private set; } 24 | public ModDatabase Database { get; private set; } 25 | 26 | public ModManager() 27 | { 28 | Log.Information("Creating a new ModManager instance"); 29 | } 30 | 31 | public void Initialize(string leagueFolder) 32 | { 33 | this.LeagueFolder = leagueFolder; 34 | 35 | ProcessModDatabase(); 36 | LoadLeagueFileIndex(true); 37 | CheckIndexVersion(); 38 | 39 | if(!VerifyOverlayIntegrity()) 40 | { 41 | ForceReset(false); 42 | } 43 | } 44 | 45 | private async void LoadLeagueFileIndex(bool loadExisting) 46 | { 47 | if (File.Exists(INDEX_FILE) && loadExisting) 48 | { 49 | this.Index = LeagueFileIndex.Deserialize(File.ReadAllText(INDEX_FILE)); 50 | Log.Information("Loaded File Index from: " + INDEX_FILE); 51 | 52 | CheckIndexVersion(); 53 | } 54 | else 55 | { 56 | try 57 | { 58 | this.Index = new LeagueFileIndex(this.LeagueFolder); 59 | Log.Information("Created new Game Index from: " + this.LeagueFolder); 60 | } 61 | catch(Exception exception) 62 | { 63 | await DialogHelper.ShowMessageDialog("Failed to create League File Index\n" + exception); 64 | Log.Error("Failed to create League File Index"); 65 | Log.Error(exception.ToString()); 66 | } 67 | } 68 | } 69 | private void ProcessModDatabase() 70 | { 71 | if (File.Exists(DATABASE_FILE)) 72 | { 73 | this.Database = ModDatabase.Deserialize(File.ReadAllText(DATABASE_FILE)); 74 | 75 | SyncWithModFolder(); 76 | 77 | Log.Information("Loaded Mod Database: " + DATABASE_FILE); 78 | } 79 | else 80 | { 81 | this.Database = new ModDatabase(); 82 | 83 | SyncWithModFolder(); 84 | 85 | Log.Information("Created new Mod Database"); 86 | } 87 | 88 | this.Database.MountMods(); 89 | } 90 | 91 | private void CheckIndexVersion() 92 | { 93 | Version leagueVersion = GetLeagueVersion(); 94 | if (this.Index.Version != leagueVersion) 95 | { 96 | Log.Information("Index is out of date"); 97 | Log.Information("Current Index Version: {0}", this.Index.Version.ToString()); 98 | Log.Information("League Version: {0}", leagueVersion.ToString()); 99 | 100 | ForceReset(false); 101 | } 102 | } 103 | 104 | public void ForceReset(bool deleteMods) 105 | { 106 | Log.Information("Doing Force Reset"); 107 | Log.Information("DELETE_MODS = " + deleteMods); 108 | 109 | //Delete everything in overlay folder 110 | foreach(string directory in Directory.EnumerateDirectories(OVERLAY_FOLDER)) 111 | { 112 | Directory.Delete(directory, true); 113 | } 114 | 115 | if(deleteMods) 116 | { 117 | List modsToDelete = this.Database.Mods.Keys.ToList(); 118 | foreach (string modToDelete in modsToDelete) 119 | { 120 | this.Database.RemoveMod(modToDelete); 121 | } 122 | 123 | foreach(string file in Directory.EnumerateFiles(MOD_FOLDER)) 124 | { 125 | File.Delete(file); 126 | } 127 | } 128 | else 129 | { 130 | foreach (string databaseMod in this.Database.Mods.Keys.ToList()) 131 | { 132 | this.Database.ChangeModState(databaseMod, false); 133 | } 134 | } 135 | 136 | LoadLeagueFileIndex(false); 137 | } 138 | 139 | public async void AddMod(ModFile mod, bool install = false) 140 | { 141 | Log.Information("Adding Mod: {0} to Mod Manager", mod.GetID()); 142 | 143 | if (install) 144 | { 145 | await DialogHelper.ShowInstallModDialog(mod, this); 146 | } 147 | 148 | this.Database.AddMod(mod, install); 149 | } 150 | public void RemoveMod(ModFile mod) 151 | { 152 | string modId = mod.GetID(); 153 | 154 | if (this.Database.IsInstalled(mod.GetID())) 155 | { 156 | UninstallMod(mod); 157 | } 158 | 159 | mod.Content.Dispose(); 160 | this.Database.RemoveMod(modId); 161 | File.Delete(string.Format(@"{0}\{1}.fantome", MOD_FOLDER, modId)); 162 | } 163 | 164 | public void InstallMod(ModFile mod) 165 | { 166 | Log.Information("Installing Mod: {0}", mod.GetID()); 167 | 168 | this.Index.StartEdit(); 169 | 170 | //Update the Index with our new mod and also check for asset collisions 171 | UpdateIndex(mod); 172 | 173 | //Write all the created WAD files to our overlay directory 174 | WriteModWADFiles(mod); 175 | 176 | AddModToDatabase(mod); 177 | 178 | this.Index.EndEdit(); 179 | } 180 | private void UpdateIndex(ModFile mod) 181 | { 182 | var modWadFiles = mod.GetWadFiles(this.Index); 183 | this.Index.AddMod(mod.GetID(), modWadFiles.Keys.ToList()); 184 | 185 | foreach (KeyValuePair modWadFile in modWadFiles) 186 | { 187 | foreach (var entry in modWadFile.Value.Entries) 188 | { 189 | if (this.Index.Game.ContainsKey(entry.Key)) 190 | { 191 | this.Index.AddModFile(entry.Key, mod.GetID(), this.Index.Game[entry.Key]); 192 | } 193 | else 194 | { 195 | this.Index.AddModFile(entry.Key, mod.GetID(), new List() { modWadFile.Key }); 196 | } 197 | } 198 | } 199 | } 200 | private void WriteModWADFiles(ModFile mod) 201 | { 202 | Action> writeWadFileDelegate = new (WriteWadFile); 203 | 204 | if (Config.Get("ParallelWadInstallation")) 205 | { 206 | ParallelOptions parallelOptions = new ParallelOptions() 207 | { 208 | MaxDegreeOfParallelism = Environment.ProcessorCount 209 | }; 210 | 211 | Parallel.ForEach(mod.GetWadFiles(this.Index), parallelOptions, (modWadFile) => 212 | { 213 | writeWadFileDelegate.Invoke(modWadFile); 214 | }); 215 | } 216 | else 217 | { 218 | var modWadFiles = mod.GetWadFiles(this.Index); 219 | 220 | foreach (var modWadFile in modWadFiles) 221 | { 222 | writeWadFileDelegate.Invoke(modWadFile); 223 | } 224 | 225 | foreach(var modWadFile in modWadFiles) 226 | { 227 | modWadFile.Value.Dispose(); 228 | } 229 | } 230 | 231 | void WriteWadFile(KeyValuePair modWadFile) 232 | { 233 | string wadPath = this.Index.FindWADPath(modWadFile.Key); 234 | string overlayModWadPath = string.Format(@"{0}\{1}", OVERLAY_FOLDER, wadPath); 235 | string gameModWadPath = string.Format(@"{0}\{1}", this.LeagueFolder, wadPath); 236 | 237 | //Check if the WAD already exists, if it does, we need to merge the 2 WADs 238 | //if it doesnt, then we need to copy it from the game directory 239 | if (!File.Exists(overlayModWadPath)) 240 | { 241 | Directory.CreateDirectory(Path.GetDirectoryName(overlayModWadPath)); 242 | 243 | using (Wad baseWad = Wad.Mount(gameModWadPath, false)) 244 | { 245 | WadBuilder mergedWad = WadMerger.Merge(new WadBuilder(baseWad), modWadFile.Value); 246 | mergedWad.Build(overlayModWadPath); 247 | } 248 | } 249 | else 250 | { 251 | File.Move(overlayModWadPath, overlayModWadPath + ".temp"); 252 | 253 | using (Wad mergedWad = Wad.Mount(overlayModWadPath + ".temp", false)) 254 | { 255 | WadBuilder mergedWadBuilder = WadMerger.Merge(new WadBuilder(mergedWad), modWadFile.Value); 256 | mergedWadBuilder.Build(overlayModWadPath); 257 | } 258 | 259 | //Delete temp wad file 260 | File.Delete(overlayModWadPath + ".temp"); 261 | } 262 | } 263 | } 264 | 265 | public void UninstallMod(ModFile mod) 266 | { 267 | Log.Information("Uninstalling Mod: " + mod.GetID()); 268 | List moddedEntries = new List(); 269 | if (this.Index.ModEntryMap.TryGetValue(mod.GetID(), out List _moddedEntries)) 270 | { 271 | moddedEntries = new List(_moddedEntries); 272 | } 273 | Dictionary moddedWads = new Dictionary(); 274 | 275 | this.Index.StartEdit(); 276 | 277 | //In this loop we remove the installed WAD entries 278 | foreach (ulong moddedEntry in moddedEntries) 279 | { 280 | List moddedEntryWadFiles = this.Index.Mod[moddedEntry]; 281 | 282 | //Initialize WAD files for entry deletion 283 | foreach (string moddedEntryWadFile in moddedEntryWadFiles) 284 | { 285 | if (!moddedWads.ContainsKey(moddedEntryWadFile)) 286 | { 287 | using Wad moddedWad = Wad.Mount(string.Format(@"{0}\{1}", OVERLAY_FOLDER, moddedEntryWadFile), false); 288 | moddedWads.Add(moddedEntryWadFile, new WadBuilder(moddedWad)); 289 | } 290 | 291 | moddedWads[moddedEntryWadFile].RemoveEntry(moddedEntry); 292 | } 293 | 294 | this.Index.RemoveModdedEntry(moddedEntry, mod.GetID()); 295 | } 296 | 297 | //Now we need to either delete empty WAD files or fill the ones from which we removed the entries with original files 298 | //if the modified ones are the same as original then we need to delete those too 299 | foreach (KeyValuePair moddedWad in moddedWads) 300 | { 301 | //If the WAD isn't being used by any other mod or is empty we can delete it 302 | if (this.Index.WadModMap[moddedWad.Key].All(x => x == mod.GetID()) || 303 | moddedWad.Value.Entries.Count == 0) 304 | { 305 | File.Delete(string.Format(@"{0}\{1}", OVERLAY_FOLDER, moddedWad.Key)); 306 | } 307 | //If it's used by some other mods we need to merge it into the original WAD 308 | else 309 | { 310 | string gameWadPath = string.Format(@"{0}\{1}", this.LeagueFolder, moddedWad.Key); 311 | string overlayWadPath = string.Format(@"{0}\{1}", OVERLAY_FOLDER, moddedWad.Key); 312 | using Wad originalWad = Wad.Mount(gameWadPath, false); 313 | 314 | WadBuilder mergedWad = WadMerger.Merge(new WadBuilder(originalWad), moddedWad.Value); 315 | mergedWad.Build(overlayWadPath + ".temp"); 316 | 317 | File.Delete(overlayWadPath); 318 | File.Move(overlayWadPath + ".temp", overlayWadPath); 319 | } 320 | } 321 | 322 | this.Database.ChangeModState(mod.GetID(), false); 323 | this.Index.RemoveMod(mod.GetID()); 324 | this.Index.EndEdit(); 325 | } 326 | 327 | private void AddModToDatabase(ModFile mod) 328 | { 329 | string id = mod.GetID(); 330 | if (this.Database.Mods.ContainsKey(id)) 331 | { 332 | this.Database.ChangeModState(id, true); 333 | } 334 | else 335 | { 336 | this.Database.AddMod(mod, true); 337 | } 338 | } 339 | 340 | public void SyncWithModFolder() 341 | { 342 | foreach (KeyValuePair mod in this.Database.Mods) 343 | { 344 | //Remove mods which are not present in the Mods folder anymore 345 | string modPath = string.Format(@"{0}\{1}.fantome", MOD_FOLDER, mod.Key); 346 | if (!File.Exists(modPath)) 347 | { 348 | this.Database.RemoveMod(mod.Key); 349 | } 350 | } 351 | 352 | //Scan Mod folder for mods which were potentially added by the user 353 | foreach (string modFilePath in Directory.EnumerateFiles(MOD_FOLDER)) 354 | { 355 | string modFileName = Path.GetFileNameWithoutExtension(modFilePath); 356 | 357 | if (!this.Database.ContainsMod(modFileName)) 358 | { 359 | AddMod(new ModFile(modFilePath), false); 360 | } 361 | } 362 | } 363 | 364 | public bool VerifyOverlayIntegrity() 365 | { 366 | Log.Information("Verifying Overlay integrity..."); 367 | 368 | List installedModIds = this.Database.Mods.Where(x => x.Value).Select(x => x.Key).ToList(); 369 | 370 | foreach(string installedModId in installedModIds) 371 | { 372 | using ModFile mod = this.Database.GetMod(installedModId); 373 | Dictionary modWadFiles = mod.GetWadFiles(this.Index); 374 | 375 | // First verify that index is valid 376 | foreach(var modWadFile in modWadFiles) 377 | { 378 | if(this.Index.WadModMap.TryGetValue(modWadFile.Key, out List wadModIds)) 379 | { 380 | if(!wadModIds.Any(x => x == installedModId)) 381 | { 382 | Log.Warning("WAD: {0} is installed but Mod: {1} is not mapped to it in the index", modWadFile.Key, installedModId); 383 | return false; // Mod is installed but one of its WAD files isn't in the index 384 | } 385 | } 386 | else 387 | { 388 | Log.Warning("Mod: {0} is installed but its WAD: {1} is not mapped to it in the index", installedModId, modWadFile.Key); 389 | return false; // WAD is installed but isn't in index 390 | } 391 | 392 | if(this.Index.ModEntryMap.TryGetValue(installedModId, out List modEntries)) 393 | { 394 | foreach(var entry in modWadFile.Value.Entries) 395 | { 396 | if(!modEntries.Any(x => x == entry.Key)) 397 | { 398 | Log.Warning("Mod: {0} is installed but its WAD Entry: {1} is not mapped to it in the index", installedModId, entry.Key); 399 | return false; // An installed WAD Entry is not present in the index 400 | } 401 | } 402 | } 403 | else 404 | { 405 | Log.Warning("Mod: {0} is installed but isn't present in the Mod-Entry index map", installedModId); 406 | return false; // Mod is installed but doesn't have any entries registered 407 | } 408 | 409 | mod.DisposeReopen(); 410 | } 411 | } 412 | 413 | Log.Information("Overlay Integrity: Good"); 414 | return true; 415 | } 416 | 417 | private Version GetLeagueVersion() 418 | { 419 | return new Version(FileVersionInfo.GetVersionInfo(Path.Combine(this.LeagueFolder, "League of Legends.exe")).FileVersion); 420 | } 421 | } 422 | } 423 | --------------------------------------------------------------------------------