├── .gitignore ├── Screenshots └── BnsLauncher.png ├── Source ├── BnsLauncher │ ├── bns.ico │ ├── Fonts │ │ └── Password.ttf │ ├── Messages │ │ └── ReloadProfilesMessage.cs │ ├── FodyWeavers.xml │ ├── App.xaml.cs │ ├── Views │ │ ├── ShellView.xaml.cs │ │ ├── SideView.xaml.cs │ │ ├── AccountView.xaml.cs │ │ ├── AccountsView.xaml.cs │ │ ├── ProfilesView.xaml.cs │ │ ├── SettingsView.xaml.cs │ │ ├── SelectAccountView.xaml.cs │ │ ├── AboutView.xaml.cs │ │ ├── LogView.xaml.cs │ │ ├── ShellView.xaml │ │ ├── SideView.xaml │ │ ├── LogView.xaml │ │ ├── AboutView.xaml │ │ ├── SelectAccountView.xaml │ │ ├── AccountView.xaml │ │ ├── SettingsView.xaml │ │ ├── AccountsView.xaml │ │ └── ProfilesView.xaml │ ├── Logging │ │ ├── LogEntry.cs │ │ └── Logger.cs │ ├── ViewModels │ │ ├── LogViewModel.cs │ │ ├── SettingsViewModel.cs │ │ ├── AccountViewModel.cs │ │ ├── SelectAccountViewModel.cs │ │ ├── ShellViewModel.cs │ │ ├── AboutViewModel.cs │ │ ├── AccountsViewModel.cs │ │ ├── SideViewModel.cs │ │ └── ProfilesViewModel.cs │ ├── NavigationServices.cs │ ├── Constants.cs │ ├── Models │ │ └── NavigationItem.cs │ ├── Debouncer.cs │ ├── ValidationRules │ │ └── MinimumLengthRule.cs │ ├── App.xaml │ ├── MaterialDesignTheme.Overrides.xaml │ ├── Bootstrapper.cs │ ├── BnsLauncher.csproj │ └── FodyWeavers.xsd ├── BnsLauncher.Core │ ├── FodyWeavers.xml │ ├── Abstractions │ │ ├── ISampleProfileWriter.cs │ │ ├── ILogger.cs │ │ ├── IProfilesWatcher.cs │ │ ├── IGlobalConfigStorage.cs │ │ ├── IProfileLoader.cs │ │ ├── IGameStarter.cs │ │ └── IProfileAccountsGetter.cs │ ├── Models │ │ ├── ProcessInfo.cs │ │ ├── Account.cs │ │ ├── Profile.cs │ │ └── GlobalConfig.cs │ ├── StringExtensions.cs │ ├── Samples │ │ ├── LiveEU32 │ │ │ ├── binloader.xml │ │ │ ├── loginhelper.xml │ │ │ ├── profile.xml │ │ │ └── bnspatch.xml │ │ └── PrivateServer │ │ │ ├── binloader.xml │ │ │ ├── loginhelper.xml │ │ │ ├── profile.xml │ │ │ └── bnspatch.xml │ ├── PropertyChangedBase.cs │ ├── BnsLauncher.Core.csproj │ ├── Services │ │ ├── JsonGlobalConfigStorage.cs │ │ ├── ProfileAccountsGetter.cs │ │ ├── ProfilesWatcher.cs │ │ ├── NamedPipeToLog.cs │ │ ├── SampleProfileWriter.cs │ │ ├── ProfileLoader.cs │ │ └── GameStarter.cs │ ├── XmlExtensions.cs │ ├── FodyWeavers.xsd │ └── Helpers │ │ └── Encryption.cs ├── BnsLauncher.Tests │ ├── Mocks │ │ ├── NullSampleProfileWriter.cs │ │ └── MockLogger.cs │ ├── BnsLauncher.Tests.csproj │ ├── SampleProfileWriterTests.cs │ ├── Helpers │ │ └── ProfileSampleHelper.cs │ ├── ProfileAccountsGetterTests.cs │ └── ProfileLoaderTests.cs ├── Publish.bat └── BnsLauncher.sln ├── .gitattributes ├── LICENSE ├── .gitmodules ├── README.md ├── CHANGELOG.md └── THIRD-PARTY-NOTICES.txt /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | obj 3 | .idea 4 | .vs 5 | Publish -------------------------------------------------------------------------------- /Screenshots/BnsLauncher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notscuffed/BnsLauncher/HEAD/Screenshots/BnsLauncher.png -------------------------------------------------------------------------------- /Source/BnsLauncher/bns.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notscuffed/BnsLauncher/HEAD/Source/BnsLauncher/bns.ico -------------------------------------------------------------------------------- /Source/BnsLauncher/Fonts/Password.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notscuffed/BnsLauncher/HEAD/Source/BnsLauncher/Fonts/Password.ttf -------------------------------------------------------------------------------- /Source/BnsLauncher/Messages/ReloadProfilesMessage.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Messages 2 | { 3 | public class ReloadProfilesMessage 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Source/BnsLauncher/App.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher 2 | { 3 | /// 4 | /// Interaction logic for App.xaml 5 | /// 6 | public partial class App 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Abstractions/ISampleProfileWriter.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Core.Abstractions 2 | { 3 | public interface ISampleProfileWriter 4 | { 5 | void WriteSampleProfiles(string profilesDirectory); 6 | } 7 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/ShellView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Views 2 | { 3 | public partial class ShellView 4 | { 5 | public ShellView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/SideView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Views 2 | { 3 | public partial class SideView 4 | { 5 | public SideView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/AccountView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Views 2 | { 3 | public partial class AccountView 4 | { 5 | public AccountView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/AccountsView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Views 2 | { 3 | public partial class AccountsView 4 | { 5 | public AccountsView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/ProfilesView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Views 2 | { 3 | public partial class ProfilesView 4 | { 5 | public ProfilesView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/SettingsView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Views 2 | { 3 | public partial class SettingsView 4 | { 5 | public SettingsView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Abstractions/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BnsLauncher.Core.Abstractions 4 | { 5 | public interface ILogger 6 | { 7 | void Log(string text); 8 | void Log(Exception exception); 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/SelectAccountView.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BnsLauncher.Views 2 | { 3 | public partial class SelectAccountView 4 | { 5 | public SelectAccountView() 6 | { 7 | InitializeComponent(); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Abstractions/IProfilesWatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BnsLauncher.Core.Abstractions 4 | { 5 | public interface IProfilesWatcher 6 | { 7 | public event Action OnProfileChange; 8 | void WatchForChanges(); 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/AboutView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace BnsLauncher.Views 4 | { 5 | public partial class AboutView : Page 6 | { 7 | public AboutView() 8 | { 9 | InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Models/ProcessInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace BnsLauncher.Core.Models 4 | { 5 | public class ProcessInfo : PropertyChangedBase 6 | { 7 | public Process Process { get; set; } 8 | public Account Account { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Abstractions/IGlobalConfigStorage.cs: -------------------------------------------------------------------------------- 1 | using BnsLauncher.Core.Models; 2 | 3 | namespace BnsLauncher.Core.Abstractions 4 | { 5 | public interface IGlobalConfigStorage 6 | { 7 | void SaveConfig(GlobalConfig config); 8 | GlobalConfig LoadConfig(); 9 | } 10 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Logging/LogEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BnsLauncher.Logging 4 | { 5 | public class LogEntry 6 | { 7 | public string Text { get; set; } 8 | public string Color { get; set; } = "#AAA"; 9 | public DateTime DateTime { get; set; } = DateTime.Now; 10 | } 11 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Tests/Mocks/NullSampleProfileWriter.cs: -------------------------------------------------------------------------------- 1 | using BnsLauncher.Core.Abstractions; 2 | 3 | namespace BnsLauncher.Tests.Mocks 4 | { 5 | public class NullSampleProfileWriter : ISampleProfileWriter 6 | { 7 | public void WriteSampleProfiles(string profilesDirectory) 8 | { 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Abstractions/IProfileLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using BnsLauncher.Core.Models; 4 | 5 | namespace BnsLauncher.Core.Abstractions 6 | { 7 | public interface IProfileLoader 8 | { 9 | Task> LoadProfiles(string sourceDirectory); 10 | } 11 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/LogViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using BnsLauncher.Logging; 3 | using Caliburn.Micro; 4 | 5 | namespace BnsLauncher.ViewModels 6 | { 7 | public class LogViewModel : Screen 8 | { 9 | public ObservableCollection LogEntries { get; } = new ObservableCollection(); 10 | } 11 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Abstractions/IGameStarter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using BnsLauncher.Core.Models; 4 | 5 | namespace BnsLauncher.Core.Abstractions 6 | { 7 | public interface IGameStarter 8 | { 9 | void Start(Profile profile, GlobalConfig globalConfig, Account account); 10 | event Action OnProcessExit; 11 | } 12 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/SettingsViewModel.cs: -------------------------------------------------------------------------------- 1 | using BnsLauncher.Core.Models; 2 | using Caliburn.Micro; 3 | 4 | namespace BnsLauncher.ViewModels 5 | { 6 | public class SettingsViewModel : Screen 7 | { 8 | public SettingsViewModel(GlobalConfig globalConfig) 9 | { 10 | GlobalConfig = globalConfig; 11 | } 12 | 13 | public GlobalConfig GlobalConfig { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/NavigationServices.cs: -------------------------------------------------------------------------------- 1 | using Caliburn.Micro; 2 | 3 | namespace BnsLauncher 4 | { 5 | public class NavigationServices 6 | { 7 | public NavigationServices(INavigationService side, INavigationService main) 8 | { 9 | Side = side; 10 | Main = main; 11 | } 12 | 13 | public INavigationService Side { get; } 14 | public INavigationService Main { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace BnsLauncher.Core 4 | { 5 | public static class StringExtensions 6 | { 7 | public static bool WildMatch(this string input, string pattern) 8 | { 9 | return new Regex(Regex.Escape(pattern) 10 | .Replace("\\*", ".*") 11 | .Replace("\\?", ".")).IsMatch(input); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Tests/Mocks/MockLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BnsLauncher.Core.Abstractions; 3 | 4 | namespace BnsLauncher.Tests.Mocks 5 | { 6 | public class MockLogger : ILogger 7 | { 8 | public void Log(string text) 9 | { 10 | Console.WriteLine(text); 11 | } 12 | 13 | public void Log(Exception exception) 14 | { 15 | Console.WriteLine(exception); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Samples/LiveEU32/binloader.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Samples/PrivateServer/binloader.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Samples/LiveEU32/loginhelper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | 8 | true 9 | 10 | 11 | false 12 | -------------------------------------------------------------------------------- /Source/BnsLauncher/Constants.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace BnsLauncher 4 | { 5 | public static class Constants 6 | { 7 | public static readonly string AppDataPath = 8 | Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + 9 | "\\BnSLauncher"; 10 | 11 | public static readonly string ConfigPath = AppDataPath + "\\config.json"; 12 | public static readonly string ProfilesPath = AppDataPath + "\\Profiles"; 13 | } 14 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Samples/PrivateServer/loginhelper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | 8 | false 9 | 10 | 11 | false 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.csproj text=auto eol=crlf 3 | *.sln text=auto eol=crlf 4 | 5 | # Binary files 6 | *.gif -text diff 7 | *.gz -text diff 8 | *.ico -text diff 9 | *.jpeg -text diff 10 | *.jpg -text diff 11 | *.png -text diff 12 | *.phar -text diff 13 | *.pdf -text diff 14 | *.exe -text diff 15 | *.svgz -text diff 16 | *.eot -text diff 17 | *.ttf -text diff 18 | *.woff -text diff 19 | *.woff2 -text diff 20 | *.mp4 -text diff 21 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/PropertyChangedBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace BnsLauncher.Core 5 | { 6 | public class PropertyChangedBase : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | protected void OnPropertyChanged([CallerMemberName] string propertyName = null) 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/AccountViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BnsLauncher.Core.Models; 3 | using Caliburn.Micro; 4 | 5 | namespace BnsLauncher.ViewModels 6 | { 7 | public class AccountViewModel : Screen 8 | { 9 | public Account Account { get; set; } 10 | public string TitleText { get; set; } 11 | public string ActionButtonText { get; set; } 12 | public Action OnAction { get; set; } 13 | 14 | public void ExecuteAction() 15 | { 16 | OnAction?.Invoke(Account); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Models/NavigationItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using MaterialDesignThemes.Wpf; 4 | 5 | namespace BnsLauncher.Models 6 | { 7 | public class NavigationItem 8 | { 9 | public string Name { get; set; } 10 | public PackIconKind Icon { get; set; } 11 | public Func Action { get; set; } 12 | 13 | public NavigationItem(string name, PackIconKind icon, Func action) 14 | { 15 | Name = name; 16 | Icon = icon; 17 | Action = action; 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Samples/LiveEU32/profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Live EU 32-bit 5 | green 6 | white 7 | 8 | 9 | C:\Program Files (x86)\NCSOFT\BnS\bin\Client.exe 10 | /sesskey /launchbylauncher -lang:English -region:1 11 | 12 | 14 | 0 15 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Abstractions/IProfileAccountsGetter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using BnsLauncher.Core.Models; 3 | 4 | namespace BnsLauncher.Core.Abstractions 5 | { 6 | public interface IProfileAccountsGetter 7 | { 8 | /// 9 | /// Returns list of accounts for specific profile 10 | /// 11 | /// The profile to get accounts for 12 | /// Source of accounts 13 | /// 14 | Account[] GetAccountsForProfile(Profile profile, IEnumerable accountsSource); 15 | } 16 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Debouncer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace BnsLauncher 5 | { 6 | public static class Debouncer 7 | { 8 | public static Action Debounce(Action action, TimeSpan timeSpan) 9 | { 10 | Timer timer = null; 11 | 12 | return () => 13 | { 14 | if (timer != null) 15 | { 16 | timer.Dispose(); 17 | timer = null; 18 | } 19 | 20 | timer = new Timer(_ => action(), null, timeSpan, TimeSpan.FromMilliseconds(-1)); 21 | }; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/LogView.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Controls; 3 | 4 | namespace BnsLauncher.Views 5 | { 6 | public partial class LogView 7 | { 8 | public LogView() 9 | { 10 | InitializeComponent(); 11 | } 12 | 13 | private void ScrollViewer_OnScrollChanged(object sender, ScrollChangedEventArgs e) 14 | { 15 | if (!(e.Source is ScrollViewer scrollViewer)) 16 | return; 17 | 18 | if (Math.Abs(e.ExtentHeightChange) < 0.001) 19 | return; 20 | 21 | scrollViewer.ScrollToBottom(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Samples/PrivateServer/profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Private Server 5 | red 6 | white 7 | 8 | 9 | 11 | 12 | 14 | 0 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/BnsLauncher.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net472 5 | 8.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Tests/BnsLauncher.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | false 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/SelectAccountViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BnsLauncher.Core.Models; 3 | using Caliburn.Micro; 4 | 5 | namespace BnsLauncher.ViewModels 6 | { 7 | public class SelectAccountViewModel : Screen 8 | { 9 | private readonly NavigationServices _navigationServices; 10 | 11 | public SelectAccountViewModel(NavigationServices navigationServices) 12 | { 13 | _navigationServices = navigationServices; 14 | } 15 | 16 | public Account[] Accounts { get; set; } 17 | public Action OnAccountSelect { get; set; } 18 | 19 | public void SelectAccount(Account account) 20 | { 21 | OnAccountSelect?.Invoke(account); 22 | 23 | _navigationServices.Main.NavigateToViewModel(); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Source/Publish.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | SET out=%~dp0\Publish 4 | 5 | mkdir "%out%\temp" 6 | dotnet publish -c Release -o "%out%" BnsLauncher 7 | 8 | cd %out% 9 | move BnsLauncher.exe input.exe 10 | ilrepack /out:"BnsLauncher.exe" /wildcards /parallel input.exe BnsLauncher.Core.dll Caliburn.Micro.Core.dll Caliburn.Micro.Platform.dll MaterialDesignColors.dll System.Threading.Tasks.Extensions.dll System.IO.Abstractions.dll PropertyChanged.dll Unity.Abstractions.dll Unity.Container.dll System.Runtime.CompilerServices.Unsafe.dll Newtonsoft.Json.dll 11 | 12 | move BnsLauncher.exe .\temp\ 13 | move FluentWPF.dll .\temp\ 14 | move MaterialDesignThemes.Wpf.dll .\temp\ 15 | move Microsoft.Xaml.Behaviors.dll .\temp\ 16 | 17 | del /Q * 18 | 19 | cd temp 20 | move * ../ 21 | cd ../ 22 | rmdir temp 23 | 24 | copy "..\..\THIRD-PARTY-NOTICES.txt" "." 25 | copy "..\..\CHANGELOG.md" "." 26 | 27 | pause -------------------------------------------------------------------------------- /Source/BnsLauncher/ValidationRules/MinimumLengthRule.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Windows.Controls; 3 | 4 | namespace BnsLauncher.ValidationRules 5 | { 6 | public class MinimumLengthRule : ValidationRule 7 | { 8 | public override ValidationResult Validate(object value, CultureInfo cultureInfo) 9 | { 10 | if (!(value is string stringValue)) 11 | return new ValidationResult(false, "Required field"); 12 | 13 | if (string.IsNullOrWhiteSpace(stringValue)) 14 | return new ValidationResult(false, "Required field"); 15 | 16 | if (stringValue.Length < MinimumLength) 17 | return new ValidationResult(false, $"Minimum {MinimumLength} characters"); 18 | 19 | return new ValidationResult(true, null); 20 | } 21 | 22 | public int MinimumLength { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Services/JsonGlobalConfigStorage.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions; 2 | using BnsLauncher.Core.Abstractions; 3 | using BnsLauncher.Core.Models; 4 | using Newtonsoft.Json; 5 | 6 | namespace BnsLauncher.Core.Services 7 | { 8 | public class JsonGlobalConfigStorage : IGlobalConfigStorage 9 | { 10 | private readonly IFileSystem _fs; 11 | 12 | public JsonGlobalConfigStorage(IFileSystem fs) 13 | { 14 | _fs = fs; 15 | } 16 | 17 | public string ConfigPath { get; set; } 18 | 19 | public void SaveConfig(GlobalConfig config) 20 | { 21 | _fs.File.WriteAllText(ConfigPath, JsonConvert.SerializeObject(config)); 22 | } 23 | 24 | public GlobalConfig LoadConfig() 25 | { 26 | return _fs.File.Exists(ConfigPath) 27 | ? JsonConvert.DeserializeObject(_fs.File.ReadAllText(ConfigPath)) 28 | : new GlobalConfig(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/XmlExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Xml; 4 | 5 | namespace BnsLauncher.Core 6 | { 7 | public static class XmlExtensions 8 | { 9 | private static readonly string[] _trueValues = {"true", "1", "yes", "ok"}; 10 | 11 | public static string GetNodeText(this XmlNode node, string path, string defaultValue) 12 | { 13 | if (node == null) 14 | return defaultValue; 15 | 16 | var foundNode = node.SelectSingleNode(path); 17 | 18 | return foundNode == null ? defaultValue : foundNode.InnerText.Trim(); 19 | } 20 | 21 | public static bool GetNodeBoolean(this XmlNode node, string path, bool defaultValue) 22 | { 23 | if (node == null) 24 | return defaultValue; 25 | 26 | var foundNode = node.SelectSingleNode(path); 27 | 28 | return foundNode == null 29 | ? defaultValue 30 | : _trueValues.Contains(foundNode.InnerText.Trim(), StringComparer.OrdinalIgnoreCase); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 notscuffed 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Models/Account.cs: -------------------------------------------------------------------------------- 1 | using BnsLauncher.Core.Helpers; 2 | using Newtonsoft.Json; 3 | 4 | namespace BnsLauncher.Core.Models 5 | { 6 | public class Account : PropertyChangedBase 7 | { 8 | public string Username { get; set; } 9 | [JsonIgnore] public string Password { get; set; } 10 | [JsonIgnore] public string Pin { get; set; } 11 | public string ProfilePatterns { get; set; } 12 | public string CustomTitle { get; set; } 13 | 14 | [JsonIgnore] 15 | public string CustomTitleOrUsername => 16 | string.IsNullOrEmpty(CustomTitle) 17 | ? Username 18 | : CustomTitle; 19 | 20 | [JsonProperty("Password")] 21 | public string EncryptedPassword 22 | { 23 | get => Encryption.Encrypt(Password); 24 | set => Password = Encryption.Decrypt(value); 25 | } 26 | 27 | [JsonProperty("Pin")] 28 | public string EncryptedPin 29 | { 30 | get => Encryption.Encrypt(Pin); 31 | set => Pin = Encryption.Decrypt(value); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Logging/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows; 4 | using BnsLauncher.Core.Abstractions; 5 | 6 | namespace BnsLauncher.Logging 7 | { 8 | public class Logger : ILogger 9 | { 10 | private readonly ICollection _logEntriesCollection; 11 | 12 | public Logger(ICollection logEntriesCollection) 13 | { 14 | _logEntriesCollection = logEntriesCollection; 15 | } 16 | 17 | public void Log(string text) 18 | { 19 | Application.Current.Dispatcher.Invoke(() => _logEntriesCollection.Add( 20 | new LogEntry 21 | { 22 | Text = text 23 | } 24 | )); 25 | } 26 | 27 | public void Log(Exception exception) 28 | { 29 | Application.Current.Dispatcher.Invoke(() => _logEntriesCollection.Add( 30 | new LogEntry 31 | { 32 | Text = exception.ToString(), 33 | Color = "#B66" 34 | } 35 | )); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Tests/SampleProfileWriterTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions.TestingHelpers; 2 | using BnsLauncher.Core.Abstractions; 3 | using BnsLauncher.Core.Services; 4 | using BnsLauncher.Tests.Mocks; 5 | using NUnit.Framework; 6 | 7 | namespace BnsLauncher.Tests 8 | { 9 | [TestFixture] 10 | public class SampleProfileWriterTests 11 | { 12 | private ILogger _logger; 13 | 14 | [SetUp] 15 | public void SetUp() 16 | { 17 | _logger = new MockLogger(); 18 | } 19 | 20 | [Test] 21 | public void WriteSampleProfile() 22 | { 23 | var fs = new MockFileSystem(); 24 | var writer = new SampleProfileWriter(fs, _logger); 25 | writer.WriteSampleProfiles("C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles"); 26 | 27 | var profileFileCount = fs.Directory 28 | .GetFiles("C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles\\PrivateServer").Length; 29 | 30 | Assert.AreEqual(4, profileFileCount, "Private server profile folder must contain 4 files after writing"); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Tests/Helpers/ProfileSampleHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Abstractions.TestingHelpers; 4 | 5 | namespace BnsLauncher.Tests.Helpers 6 | { 7 | public static class ProfileSampleHelper 8 | { 9 | static ProfileSampleHelper() 10 | { 11 | SolutionDirectory = AppContext.BaseDirectory.Split( 12 | new[] {"BnsLauncher.Tests"}, 13 | StringSplitOptions.RemoveEmptyEntries)[0]; 14 | } 15 | 16 | public static string SolutionDirectory { get; } 17 | 18 | public static void WriteSampleProfile(this MockFileSystem fs, string testProfileRoot, string profile) 19 | { 20 | fs.Directory.CreateDirectory(testProfileRoot); 21 | 22 | var sampleDirectory = $"{SolutionDirectory}BnsLauncher.Core\\Samples\\{profile}"; 23 | 24 | foreach (var file in Directory.EnumerateFiles(sampleDirectory)) 25 | { 26 | var content = File.ReadAllBytes(file); 27 | fs.File.WriteAllBytes($"{testProfileRoot}\\{Path.GetFileName(file)}", content); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Services/ProfileAccountsGetter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using BnsLauncher.Core.Abstractions; 4 | using BnsLauncher.Core.Models; 5 | 6 | namespace BnsLauncher.Core.Services 7 | { 8 | public class ProfileAccountsGetter : IProfileAccountsGetter 9 | { 10 | public Account[] GetAccountsForProfile(Profile profile, IEnumerable accountsSource) 11 | { 12 | var matchedAccounts = new List(); 13 | var profileName = profile.Name; 14 | 15 | foreach (var account in accountsSource) 16 | { 17 | if (string.IsNullOrWhiteSpace(account.ProfilePatterns)) 18 | goto ADD; 19 | 20 | var patterns = account.ProfilePatterns 21 | .Split(new []{'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries); 22 | 23 | foreach (var pattern in patterns) 24 | { 25 | if (profileName.WildMatch(pattern)) 26 | goto ADD; 27 | } 28 | 29 | continue; 30 | 31 | ADD: 32 | matchedAccounts.Add(account); 33 | } 34 | 35 | return matchedAccounts.ToArray(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Services/ProfilesWatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Abstractions; 4 | using BnsLauncher.Core.Abstractions; 5 | 6 | namespace BnsLauncher.Core.Services 7 | { 8 | public class ProfilesWatcher : IProfilesWatcher 9 | { 10 | private readonly FileSystemWatcher _watcher; 11 | 12 | public ProfilesWatcher(string profileDirectory, IFileSystem fs) 13 | { 14 | profileDirectory = fs.Path.GetFullPath(profileDirectory); 15 | fs.Directory.CreateDirectory(profileDirectory); 16 | 17 | _watcher = new FileSystemWatcher 18 | { 19 | Path = profileDirectory, 20 | IncludeSubdirectories = true, 21 | }; 22 | 23 | _watcher.Changed += WatcherOnChanged; 24 | _watcher.Created += WatcherOnChanged; 25 | _watcher.Deleted += WatcherOnChanged; 26 | _watcher.Renamed += WatcherOnRenamed; 27 | } 28 | 29 | public event Action OnProfileChange; 30 | 31 | private void WatcherOnRenamed(object sender, RenamedEventArgs e) => OnProfileChange?.Invoke(); 32 | private void WatcherOnChanged(object sender, FileSystemEventArgs e) => OnProfileChange?.Invoke(); 33 | 34 | public void WatchForChanges() 35 | { 36 | _watcher.EnableRaisingEvents = true; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Dlls/dependencies/Detours"] 2 | path = Dlls/dependencies/Detours 3 | url = https://github.com/Microsoft/Detours.git 4 | [submodule "Dlls/dependencies/fmt"] 5 | path = Dlls/dependencies/fmt 6 | url = https://github.com/fmtlib/fmt.git 7 | [submodule "Dlls/dependencies/fnv1a_constexpr"] 8 | path = Dlls/dependencies/fnv1a_constexpr 9 | url = https://github.com/zeffy/fnv1a_constexpr.git 10 | [submodule "Dlls/dependencies/GSL"] 11 | path = Dlls/dependencies/GSL 12 | url = https://github.com/Microsoft/GSL.git 13 | [submodule "Dlls/dependencies/magic_enum"] 14 | path = Dlls/dependencies/magic_enum 15 | url = https://github.com/Neargye/magic_enum.git 16 | [submodule "Dlls/dependencies/ntapi"] 17 | path = Dlls/dependencies/ntapi 18 | url = https://github.com/zeffy/ntapi.git 19 | [submodule "Dlls/dependencies/pe"] 20 | path = Dlls/dependencies/pe 21 | url = https://github.com/zeffy/pe.git 22 | [submodule "Dlls/dependencies/processhacker"] 23 | path = Dlls/dependencies/processhacker 24 | url = https://github.com/processhacker/processhacker.git 25 | [submodule "Dlls/dependencies/pugixml"] 26 | path = Dlls/dependencies/pugixml 27 | url = https://github.com/zeux/pugixml.git 28 | [submodule "Dlls/dependencies/SafeInt"] 29 | path = Dlls/dependencies/SafeInt 30 | url = https://github.com/dcleblanc/SafeInt.git 31 | [submodule "Dlls/dependencies/wil"] 32 | path = Dlls/dependencies/wil 33 | url = https://github.com/Microsoft/wil.git 34 | -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/ShellViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Windows.Controls; 3 | using Caliburn.Micro; 4 | using Unity; 5 | 6 | namespace BnsLauncher.ViewModels 7 | { 8 | public class ShellViewModel : Screen 9 | { 10 | private readonly IUnityContainer _container; 11 | private Frame _main; 12 | private Frame _side; 13 | 14 | public ShellViewModel(IUnityContainer container) 15 | { 16 | _container = container; 17 | } 18 | 19 | public string Title { get; } = "BNS Launcher " + Assembly.GetExecutingAssembly().GetName().Version; 20 | 21 | public void RegisterSideFrame(Frame frame) 22 | { 23 | _side = frame; 24 | TryRegisterFrames(); 25 | } 26 | 27 | public void RegisterMainFrame(Frame frame) 28 | { 29 | _main = frame; 30 | TryRegisterFrames(); 31 | } 32 | 33 | private void TryRegisterFrames() 34 | { 35 | if (_main == null || _side == null) 36 | return; 37 | 38 | var navigationServices = new NavigationServices( 39 | new FrameAdapter(_side), 40 | new FrameAdapter(_main)); 41 | 42 | _container.RegisterInstance(navigationServices); 43 | 44 | navigationServices.Side.NavigateToViewModel(typeof(SideViewModel)); 45 | navigationServices.Main.NavigateToViewModel(typeof(ProfilesViewModel)); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/App.xaml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 13 | 15 | 17 | 19 | 21 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/ShellView.xaml: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 32 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/SideView.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 35 | BnS Launcher 36 | by notscuffed @ github 37 | 38 | 39 | -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/AboutViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using Caliburn.Micro; 4 | 5 | namespace BnsLauncher.ViewModels 6 | { 7 | public class AboutViewModel : Screen 8 | { 9 | public static IEnumerable Attributions { get; } = new[] 10 | { 11 | Add("bnspatch - by zeffy @ ", "https://github.com/bnsmodpolice/bnspatch"), 12 | Add("pluginloader - by zeffy @ ", "https://github.com/bnsmodpolice/pluginloader"), 13 | Add("UnityContainer @ ", "https://github.com/unitycontainer/unity"), 14 | Add("Fody.PropertyChanged @ ", "https://github.com/Fody/PropertyChanged"), 15 | Add("Newtonsoft.Json @ ", "https://github.com/JamesNK/Newtonsoft.Json"), 16 | Add("MaterialDesignInXAML @ ", "https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit"), 17 | Add("Caliburn.Micro @ ", "https://github.com/Caliburn-Micro/Caliburn.Micro"), 18 | Add("FluentWPF @ ", "https://github.com/sourcechord/FluentWPF"), 19 | }; 20 | 21 | public class Attribution 22 | { 23 | public string Text { get; set; } 24 | public string Link { get; set; } 25 | } 26 | 27 | public void OpenUri(object input) 28 | { 29 | switch (input) 30 | { 31 | case Attribution attribution: 32 | Process.Start(new ProcessStartInfo(attribution.Link)); 33 | break; 34 | 35 | case string link: 36 | Process.Start(new ProcessStartInfo(link)); 37 | break; 38 | } 39 | } 40 | 41 | private static Attribution Add(string text, string link) 42 | { 43 | return new Attribution {Text = text, Link = link}; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BnsLauncher", "BnsLauncher\BnsLauncher.csproj", "{79536CCB-D227-43F2-83AB-6CE8818C6CC6}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BnsLauncher.Core", "BnsLauncher.Core\BnsLauncher.Core.csproj", "{048570CC-0578-4E82-960E-261E6E38D3AE}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BnsLauncher.Tests", "BnsLauncher.Tests\BnsLauncher.Tests.csproj", "{A9125817-1969-4263-9531-17573ACA752D}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {79536CCB-D227-43F2-83AB-6CE8818C6CC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {79536CCB-D227-43F2-83AB-6CE8818C6CC6}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {79536CCB-D227-43F2-83AB-6CE8818C6CC6}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {79536CCB-D227-43F2-83AB-6CE8818C6CC6}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {048570CC-0578-4E82-960E-261E6E38D3AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {048570CC-0578-4E82-960E-261E6E38D3AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {048570CC-0578-4E82-960E-261E6E38D3AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {048570CC-0578-4E82-960E-261E6E38D3AE}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {A9125817-1969-4263-9531-17573ACA752D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {A9125817-1969-4263-9531-17573ACA752D}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {A9125817-1969-4263-9531-17573ACA752D}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {A9125817-1969-4263-9531-17573ACA752D}.Release|Any CPU.Build.0 = Release|Any CPU 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/LogView.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/AccountsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using BnsLauncher.Core.Models; 5 | using Caliburn.Micro; 6 | 7 | namespace BnsLauncher.ViewModels 8 | { 9 | public class AccountsViewModel : Screen 10 | { 11 | private readonly NavigationServices _navigationServices; 12 | private readonly GlobalConfig _globalConfig; 13 | 14 | public AccountsViewModel(NavigationServices navigationServices, GlobalConfig globalConfig) 15 | { 16 | _navigationServices = navigationServices; 17 | _globalConfig = globalConfig; 18 | } 19 | 20 | public ObservableCollection Accounts => _globalConfig.Accounts; 21 | 22 | public void EditAccount(Account account) 23 | { 24 | _navigationServices.Main.NavigateToViewModel(new Dictionary 25 | { 26 | [nameof(AccountViewModel.Account)] = account, 27 | [nameof(AccountViewModel.TitleText)] = "Editing account", 28 | [nameof(AccountViewModel.ActionButtonText)] = "Back", 29 | [nameof(AccountViewModel.OnAction)] = (Action) BackAction, 30 | }); 31 | } 32 | 33 | public void CreateAccount() 34 | { 35 | var account = new Account(); 36 | 37 | _navigationServices.Main.NavigateToViewModel(new Dictionary 38 | { 39 | [nameof(AccountViewModel.Account)] = account, 40 | [nameof(AccountViewModel.TitleText)] = "Adding account", 41 | [nameof(AccountViewModel.ActionButtonText)] = "Add", 42 | [nameof(AccountViewModel.OnAction)] = (Action) CreateAction, 43 | }); 44 | } 45 | 46 | public void RemoveAccount(Account account) 47 | { 48 | Accounts.Remove(account); 49 | } 50 | 51 | private void BackAction(Account account) 52 | { 53 | _navigationServices.Main.NavigateToViewModel(); 54 | } 55 | 56 | private void CreateAction(Account account) 57 | { 58 | Accounts.Add(account); 59 | _navigationServices.Main.NavigateToViewModel(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/MaterialDesignTheme.Overrides.xaml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 15 | 16 | 19 | 20 | 23 | 24 | 33 | 34 | 39 | 40 | 45 | 46 | 49 | 50 | 53 | 54 | 57 | 58 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Services/NamedPipeToLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Pipes; 3 | using System.Text; 4 | using BnsLauncher.Core.Abstractions; 5 | 6 | namespace BnsLauncher.Core.Services 7 | { 8 | public class NamedPipeToLog : IDisposable 9 | { 10 | private NamedPipeServerStream _pipeServer; 11 | private readonly ILogger _logger; 12 | private readonly byte[] _bytes = new byte[4096]; 13 | private readonly string _pipeName; 14 | 15 | public NamedPipeToLog(ILogger logger, string pipeName) 16 | { 17 | _logger = logger; 18 | _pipeName = pipeName; 19 | } 20 | 21 | public string LogPrefix { get; set; } = ""; 22 | 23 | public void StartLogging() 24 | { 25 | if (_pipeServer != null) 26 | throw new InvalidOperationException($"Do not use the same {nameof(NamedPipeToLog)} twice"); 27 | 28 | _pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.In, 2, PipeTransmissionMode.Message, 29 | PipeOptions.Asynchronous); 30 | 31 | _pipeServer.BeginWaitForConnection(ConnectionCallback, null); 32 | } 33 | 34 | public void Close() 35 | { 36 | _logger.Log("Pipe server closing: " + _pipeName); 37 | _pipeServer?.Close(); 38 | } 39 | 40 | public void Dispose() 41 | { 42 | Close(); 43 | } 44 | 45 | private void ConnectionCallback(IAsyncResult ar) 46 | { 47 | try 48 | { 49 | _pipeServer.EndWaitForConnection(ar); 50 | _pipeServer.BeginRead(_bytes, 0, _bytes.Length, Callback, null); 51 | } 52 | catch (ObjectDisposedException) 53 | { 54 | // ignore 55 | } 56 | catch (Exception exception) 57 | { 58 | _logger.Log(exception); 59 | } 60 | } 61 | 62 | private void Callback(IAsyncResult ar) 63 | { 64 | try 65 | { 66 | var read = _pipeServer.EndRead(ar); 67 | 68 | if (read == 0) 69 | return; 70 | 71 | _logger.Log(LogPrefix + Encoding.ASCII.GetString(_bytes, 0, read)); 72 | 73 | _pipeServer.BeginRead(_bytes, 0, _bytes.Length, Callback, null); 74 | } 75 | catch (ObjectDisposedException) 76 | { 77 | // ignore 78 | } 79 | catch (Exception exception) 80 | { 81 | _logger.Log(exception); 82 | } 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Models/Profile.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace BnsLauncher.Core.Models 5 | { 6 | public class Profile : PropertyChangedBase 7 | { 8 | private readonly object _processesLocker = new object(); 9 | private readonly Dictionary _processInfos = new Dictionary(); 10 | 11 | public string ProfilePath { get; set; } 12 | public string BnsPatchPath { get; set; } 13 | 14 | public string Name { get; set; } 15 | public char Initial => string.IsNullOrWhiteSpace(Name) ? ' ' : Name[0]; 16 | 17 | public string BackgroundImage { get; set; } 18 | public string Background { get; set; } 19 | public string Foreground { get; set; } 20 | public string ClientPath { get; set; } 21 | public string Arguments { get; set; } 22 | 23 | public string Ip { get; set; } 24 | public ushort Port { get; set; } 25 | 26 | public string IpPort => Port == 0 ? "" : $"{Ip}:{Port}"; 27 | 28 | public string BinPath { get; set; } 29 | public string LocalBinPath { get; set; } 30 | 31 | public int Priority { get; set; } 32 | 33 | public bool HasBins => 34 | !string.IsNullOrWhiteSpace(BinPath) && 35 | !string.IsNullOrWhiteSpace(LocalBinPath); 36 | 37 | public bool AllowAccounts { get; set; } 38 | public bool AllowPin { get; set; } 39 | public bool AutopinOnRelog { get; set; } 40 | 41 | public Dictionary CustomEnvironmentVariables { get; } = new Dictionary(); 42 | 43 | public ProcessInfo[] ProcessInfos 44 | { 45 | get 46 | { 47 | lock (_processesLocker) 48 | return _processInfos.Values.ToArray(); 49 | } 50 | } 51 | 52 | public void AddProcessInfo(ProcessInfo processInfo) 53 | { 54 | lock (_processesLocker) 55 | { 56 | _processInfos[processInfo.Process.Id] = processInfo; 57 | } 58 | 59 | OnPropertyChanged(nameof(ProcessInfos)); 60 | } 61 | 62 | public bool HasProcessWithId(int pid) 63 | { 64 | lock (_processesLocker) 65 | { 66 | return _processInfos.Keys.Contains(pid); 67 | } 68 | } 69 | 70 | public void RemoveProcessWithId(int pid) 71 | { 72 | lock (_processesLocker) 73 | { 74 | if (!_processInfos.Remove(pid)) 75 | return; 76 | } 77 | 78 | OnPropertyChanged(nameof(ProcessInfos)); 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/SideViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Diagnostics; 3 | using System.IO.Abstractions; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using BnsLauncher.Messages; 8 | using BnsLauncher.Models; 9 | using Caliburn.Micro; 10 | using MaterialDesignThemes.Wpf; 11 | 12 | namespace BnsLauncher.ViewModels 13 | { 14 | public class SideViewModel : Screen 15 | { 16 | private readonly NavigationServices _navigationServices; 17 | private readonly IEventAggregator _eventAggregator; 18 | 19 | public SideViewModel(NavigationServices navigationServices, IEventAggregator eventAggregator, IFileSystem fs) 20 | { 21 | _navigationServices = navigationServices; 22 | _eventAggregator = eventAggregator; 23 | 24 | NavigationItems = new List 25 | { 26 | new NavigationItem("Profiles", PackIconKind.Wrench, GoTo), 27 | new NavigationItem("Accounts", PackIconKind.Person, GoTo), 28 | new NavigationItem("Open profiles folder", PackIconKind.CodeTags, () => 29 | { 30 | Process.Start(new ProcessStartInfo 31 | { 32 | FileName = "explorer.exe", 33 | Arguments = fs.Path.GetFullPath(Constants.ProfilesPath) 34 | }); 35 | return Task.CompletedTask; 36 | }), 37 | new NavigationItem("Settings", PackIconKind.Cog, GoTo), 38 | new NavigationItem("Logs", PackIconKind.BugOutline, GoTo), 39 | new NavigationItem("About", PackIconKind.About, GoTo), 40 | new NavigationItem("Exit", PackIconKind.ExitRun, Exit), 41 | }; 42 | } 43 | 44 | public List NavigationItems { get; } 45 | 46 | public Task TabChanged(string name) 47 | { 48 | var item = NavigationItems.FirstOrDefault(x => x.Name == name); 49 | 50 | return item == null 51 | ? Task.CompletedTask 52 | : item.Action(); 53 | } 54 | 55 | private Task ReloadProfiles() 56 | { 57 | return _eventAggregator.PublishOnUIThreadAsync(new ReloadProfilesMessage()); 58 | } 59 | 60 | private Task Exit() 61 | { 62 | Application.Current.Shutdown(); 63 | return Task.CompletedTask; 64 | } 65 | 66 | private Task GoTo() 67 | { 68 | _navigationServices.Main.NavigateToViewModel(); 69 | return Task.CompletedTask; 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Models/GlobalConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Collections.Specialized; 5 | using System.ComponentModel; 6 | using System.Linq; 7 | 8 | namespace BnsLauncher.Core.Models 9 | { 10 | public class GlobalConfig : PropertyChangedBase 11 | { 12 | public string ClientPath { get; set; } 13 | public string Arguments { get; set; } 14 | public bool Unattended { get; set; } = true; 15 | public bool NoTextureStreaming { get; set; } = true; 16 | public ObservableCollection Accounts { get; set; } = new ObservableCollection(); 17 | 18 | public bool ShowPrivateServerIp { get; set; } = true; 19 | 20 | public GlobalConfig() 21 | { 22 | Accounts.CollectionChanged += AccountsOnCollectionChanged; 23 | } 24 | 25 | private void AccountsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 26 | { 27 | switch (e.Action) 28 | { 29 | case NotifyCollectionChangedAction.Add: 30 | Subscribe(e.NewItems.OfType()); 31 | break; 32 | 33 | case NotifyCollectionChangedAction.Remove: 34 | Unsubscribe(e.OldItems.OfType()); 35 | break; 36 | 37 | case NotifyCollectionChangedAction.Replace: 38 | Unsubscribe(e.OldItems.OfType()); 39 | Subscribe(e.NewItems.OfType()); 40 | break; 41 | 42 | case NotifyCollectionChangedAction.Move: 43 | // Do nothing 44 | break; 45 | 46 | case NotifyCollectionChangedAction.Reset: 47 | Unsubscribe(e.OldItems.OfType()); 48 | break; 49 | 50 | default: 51 | throw new ArgumentOutOfRangeException(); 52 | } 53 | } 54 | 55 | private void Unsubscribe(IEnumerable accounts) 56 | { 57 | foreach (var account in accounts) 58 | { 59 | account.PropertyChanged -= AccountOnPropertyChanged; 60 | } 61 | } 62 | 63 | private void Subscribe(IEnumerable accounts) 64 | { 65 | foreach (var account in accounts) 66 | { 67 | account.PropertyChanged += AccountOnPropertyChanged; 68 | } 69 | } 70 | 71 | private void AccountOnPropertyChanged(object sender, PropertyChangedEventArgs e) 72 | { 73 | OnPropertyChanged($"{nameof(Account)}In{nameof(Accounts)}"); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Tests/ProfileAccountsGetterTests.cs: -------------------------------------------------------------------------------- 1 | using BnsLauncher.Core.Abstractions; 2 | using BnsLauncher.Core.Models; 3 | using BnsLauncher.Core.Services; 4 | using NUnit.Framework; 5 | 6 | namespace BnsLauncher.Tests 7 | { 8 | [TestFixture] 9 | public class ProfileAccountsGetterTests 10 | { 11 | private IProfileAccountsGetter _profileAccountsGetter; 12 | 13 | [SetUp] 14 | public void SetUp() 15 | { 16 | _profileAccountsGetter = new ProfileAccountsGetter(); 17 | } 18 | 19 | [Test] 20 | public void MatchOneEndWildcard() 21 | { 22 | var accounts = new[] 23 | { 24 | new Account {ProfilePatterns = "Hello*"}, 25 | new Account {ProfilePatterns = "Dont match"}, 26 | }; 27 | 28 | var profile = new Profile {Name = "Hello world"}; 29 | 30 | var matchedAccounts = _profileAccountsGetter.GetAccountsForProfile(profile, accounts); 31 | 32 | Assert.AreEqual(1, matchedAccounts.Length, "Only one account should match"); 33 | Assert.AreEqual("Hello*", matchedAccounts[0].ProfilePatterns); 34 | } 35 | 36 | [Test] 37 | public void NoMatch() 38 | { 39 | var accounts = new[] 40 | { 41 | new Account {ProfilePatterns = "*Hello*"}, 42 | new Account {ProfilePatterns = "Dont match"}, 43 | }; 44 | 45 | var profile = new Profile {Name = "Wrong"}; 46 | 47 | var matchedAccounts = _profileAccountsGetter.GetAccountsForProfile(profile, accounts); 48 | 49 | Assert.IsEmpty(matchedAccounts, "No account should match"); 50 | } 51 | 52 | [Test] 53 | public void MatchAllFullWildcard() 54 | { 55 | var accounts = new[] 56 | { 57 | new Account {ProfilePatterns = "*"}, 58 | new Account {ProfilePatterns = "*"}, 59 | }; 60 | 61 | var profile = new Profile {Name = "Hello world"}; 62 | 63 | var matchedAccounts = _profileAccountsGetter.GetAccountsForProfile(profile, accounts); 64 | 65 | Assert.AreEqual(2, matchedAccounts.Length, "Two accounts must match"); 66 | } 67 | 68 | [Test] 69 | public void EmptyPatternMatchAll() 70 | { 71 | var accounts = new[] 72 | { 73 | new Account {ProfilePatterns = ""}, 74 | new Account {ProfilePatterns = " "}, 75 | }; 76 | 77 | var profile = new Profile {Name = "Hello world"}; 78 | 79 | var matchedAccounts = _profileAccountsGetter.GetAccountsForProfile(profile, accounts); 80 | 81 | Assert.AreEqual(2, matchedAccounts.Length, "All accounts must match"); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Services/SampleProfileWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Abstractions; 5 | using System.Linq; 6 | using System.Reflection; 7 | using BnsLauncher.Core.Abstractions; 8 | 9 | namespace BnsLauncher.Core.Services 10 | { 11 | public class SampleProfileWriter : ISampleProfileWriter 12 | { 13 | private const string SamplesNamespace = "BnsLauncher.Core.Samples."; 14 | private static readonly Assembly _assembly = typeof(ProfileLoader).Assembly; 15 | private readonly IFileSystem _fs; 16 | private readonly ILogger _logger; 17 | 18 | public SampleProfileWriter(IFileSystem fs, ILogger logger) 19 | { 20 | _fs = fs; 21 | _logger = logger; 22 | } 23 | 24 | public void WriteSampleProfiles(string profilesDirectory) 25 | { 26 | var profiles = _assembly.GetManifestResourceNames() 27 | .Where(x => x.StartsWith(SamplesNamespace)) 28 | .GroupBy(x => 29 | { 30 | var index = x.IndexOf('.', SamplesNamespace.Length); 31 | 32 | return index == -1 33 | ? null 34 | : x.Substring(SamplesNamespace.Length, index - SamplesNamespace.Length); 35 | }); 36 | 37 | foreach (var profile in profiles) 38 | { 39 | WriteProfile(profilesDirectory, profile.Key, profile); 40 | } 41 | } 42 | 43 | private void WriteProfile(string rootDirectory, string profileName, IEnumerable resources) 44 | { 45 | _logger.Log($"Writing sample profile: {profileName}"); 46 | 47 | var profileDirectory = _fs.Path.Combine(rootDirectory, profileName); 48 | _fs.Directory.CreateDirectory(profileDirectory); 49 | 50 | foreach (var resource in resources) 51 | { 52 | using var resourceStream = _assembly.GetManifestResourceStream(resource); 53 | 54 | if (resourceStream == null) 55 | { 56 | _logger.Log($"Failed to get resourceStream of '{resource}'"); 57 | return; 58 | } 59 | 60 | var fileName = resource.Substring(SamplesNamespace.Length + profileName.Length + 1); 61 | 62 | try 63 | { 64 | var filePath = _fs.Path.Combine(profileDirectory, fileName); 65 | using var outputStream = 66 | _fs.File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None); 67 | 68 | resourceStream.CopyTo(outputStream); 69 | } 70 | catch (Exception exception) 71 | { 72 | _logger.Log("Exception has occured while copying sample.xml profile"); 73 | _logger.Log(exception); 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/AboutView.xaml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | About 14 | 15 | 22 | 30 | 31 | Copyright © 2021 notscuffed 32 | 33 | 34 | Acknowledgements 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BNS Launcher 2 | 3 | [![Discord](https://discord.com/api/guilds/868873627066580992/embed.png)](https://discord.gg/3nV6gxkgyb) 4 | 5 | Custom Blade & Soul game launcher with profile support and more. 6 | Works with live game & private server client. 7 | 8 | Join my [discord server][0.0] for plugins & support: [https://discord.gg/3nV6gxkgyb][0.0] 9 | 10 | ## Features: 11 | - Account manager 12 | - Auto login - automatically log in to account in game (thanks to Hora from mod police discord) 13 | - Auto pin - automatically put pin in game (thanks to Hora from mod police discord) 14 | - Replace datafile.bin/localfile.bin at runtime (thanks to Hora from mod police discord) 15 | - Multiclient - run multiple clients at once (thanks to [pilao aka. zeffy][0.1]) 16 | - Profiles (thanks to [pilao aka. zeffy][0.1]) 17 | - Completly stops game guard from initializing in private server client 18 | - Profile icons (create 32x32 sized icon.png beside profile.xml) 19 | 20 | [0.0]: https://discord.gg/3nV6gxkgyb 21 | [0.1]: https://github.com/zeffy 22 | 23 | ## Screenshoot 24 | 25 | ![Alt text](/Screenshots/BnsLauncher.png?raw=true "Bns Launcher") 26 | 27 | ## Installation 28 | 29 | 1. Extract BnsLauncher.zip and put the launcher wherever you want 30 | 2. Copy bin/bin64 containing required plugin loader & plugins to your client folder 31 | 3. Run the launcher 32 | 4. Click open profiles folder 33 | 5. Edit profile.xml inside game profile you want to use 34 | 6. Change clientpath to your client path 35 | 7. Check other xml configs for more settings 36 | 8. Add accounts if you want to use autologin feature (make sure it's enabled in loginhelper.xml in your profile folder) 37 | 38 | ## Additional info 39 | 40 | - Profiles reload automatically on any file modification inside Profiles folder 41 | - To create more profiles just duplicate one of them, change name inside profile.xml to change name 42 | - Only profile.xml is required for profile to work you can delete other xmls you don't use 43 | - Clicking play when there's no accounts that match profile to be launched will just start the game without auto login, otherwise you will be asked to select account 44 | 45 | ## Private server info 46 | 47 | - If you get an error while starting the client make sure path to client doesn't contain non ASCII characters (chinese characters, other languages etc.) 48 | 49 | ## Acknowledgements: 50 | - [bnsmodpolice/**bnspatch**][1.0] (MIT License) 51 | - [bnsmodpolice/**pluginloader**][1.1] (MIT License) 52 | - Hora from mod police discord for binloader.dll & loginhelper.dll 53 | - [unitycontainer/**unity**][1.2] (Apache License 2.0) 54 | - [Fody/**PropertyChanged**][1.3] (MIT License) 55 | - [Newtonsoft/**Json**][1.4] (MIT License) 56 | - [MaterialDesignInXAML/**MaterialDesignInXamlToolkit**][1.5] (MIT License) 57 | - [Caliburn/**Micro**][1.6] (MIT License) 58 | - [sourcechord/**FluentWPF**][1.7] (MIT License) 59 | 60 | [1.0]: https://github.com/bnsmodpolice/bnspatch 61 | [1.1]: https://github.com/bnsmodpolice/pluginloader 62 | [1.2]: https://github.com/unitycontainer/unity 63 | [1.3]: https://github.com/Fody/PropertyChanged 64 | [1.4]: https://github.com/JamesNK/Newtonsoft.Json 65 | [1.5]: https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit 66 | [1.6]: https://github.com/Caliburn-Micro/Caliburn.Micro 67 | [1.7]: https://github.com/sourcechord/FluentWPF 68 | -------------------------------------------------------------------------------- /Source/BnsLauncher/Bootstrapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO.Abstractions; 3 | using System.Windows; 4 | using BnsLauncher.Core.Abstractions; 5 | using BnsLauncher.Core.Services; 6 | using BnsLauncher.Logging; 7 | using BnsLauncher.Messages; 8 | using BnsLauncher.ViewModels; 9 | using Caliburn.Micro; 10 | using Unity; 11 | 12 | namespace BnsLauncher 13 | { 14 | public class Bootstrapper : BootstrapperBase 15 | { 16 | private readonly IUnityContainer _container; 17 | private readonly IFileSystem _fs = new FileSystem(); 18 | private readonly IEventAggregator _eventAggregator = new EventAggregator(); 19 | 20 | public Bootstrapper() 21 | { 22 | _container = new UnityContainer(); 23 | 24 | Initialize(); 25 | } 26 | 27 | protected override void OnStartup(object sender, StartupEventArgs e) 28 | { 29 | DisplayRootViewFor(); 30 | 31 | _fs.Directory.CreateDirectory(Constants.ProfilesPath); 32 | } 33 | 34 | protected override void Configure() 35 | { 36 | _container.RegisterInstance(_fs); 37 | _container.RegisterSingleton(); 38 | _container.RegisterInstance(_eventAggregator); 39 | 40 | InitializeLogging(); 41 | 42 | // Services 43 | _container.RegisterSingleton(); 44 | _container.RegisterSingleton(); 45 | _container.RegisterSingleton(); 46 | _container.RegisterType(); 47 | 48 | // View models 49 | _container.RegisterSingleton(); 50 | _container.RegisterSingleton(); 51 | 52 | // Config 53 | var gameConfigStorage = new JsonGlobalConfigStorage(_fs) {ConfigPath = Constants.ConfigPath}; 54 | var config = gameConfigStorage.LoadConfig(); 55 | 56 | if (string.IsNullOrWhiteSpace(config.ClientPath)) 57 | config.ClientPath = "./bin/Client.exe"; 58 | 59 | config.PropertyChanged += (sender, args) => gameConfigStorage.SaveConfig(config); 60 | config.Accounts.CollectionChanged += (sender, args) => gameConfigStorage.SaveConfig(config); 61 | 62 | _container.RegisterInstance(gameConfigStorage); 63 | _container.RegisterInstance(config); 64 | 65 | // Profile watcher 66 | var profileWatcher = new ProfilesWatcher(Constants.ProfilesPath, _fs); 67 | var debouncedReload = Debouncer.Debounce( 68 | () => _eventAggregator.PublishOnUIThreadAsync(new ReloadProfilesMessage()), 69 | TimeSpan.FromMilliseconds(100)); 70 | profileWatcher.OnProfileChange += debouncedReload; 71 | profileWatcher.WatchForChanges(); 72 | _container.RegisterInstance(profileWatcher); 73 | } 74 | 75 | protected override object GetInstance(Type serviceType, string key) 76 | { 77 | return key == null 78 | ? _container.Resolve(serviceType) 79 | : _container.Resolve(serviceType, key); 80 | } 81 | 82 | private void InitializeLogging() 83 | { 84 | var logVM = new LogViewModel(); 85 | 86 | _container.RegisterInstance(logVM); 87 | _container.RegisterInstance(new Logger(logVM.LogEntries)); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/SelectAccountView.xaml: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | Select account 55 | 56 | 57 | 58 | 61 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Source/BnsLauncher/BnsLauncher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net472 6 | true 7 | 0.3.0 8 | bns.ico 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | ShellView.xaml 56 | 57 | 58 | SideView.xaml 59 | 60 | 61 | ProfilesView.xaml 62 | 63 | 64 | SettingsView.xaml 65 | 66 | 67 | AboutView.xaml 68 | 69 | 70 | LogView.xaml 71 | 72 | 73 | AccountsView.xaml 74 | 75 | 76 | AccountView.xaml 77 | 78 | 79 | SelectAccountView.xaml 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /Source/BnsLauncher/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Used to control if the On_PropertyName_Changed feature is enabled. 12 | 13 | 14 | 15 | 16 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. 17 | 18 | 19 | 20 | 21 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. 22 | 23 | 24 | 25 | 26 | Used to control if equality checks should use the Equals method resolved from the base class. 27 | 28 | 29 | 30 | 31 | Used to control if equality checks should use the static Equals method resolved from the base class. 32 | 33 | 34 | 35 | 36 | Used to turn off build warnings from this weaver. 37 | 38 | 39 | 40 | 41 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods. 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 50 | 51 | 52 | 53 | 54 | A comma-separated list of error codes that can be safely ignored in assembly verification. 55 | 56 | 57 | 58 | 59 | 'false' to turn off automatic generation of the XML Schema file. 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Used to control if the On_PropertyName_Changed feature is enabled. 12 | 13 | 14 | 15 | 16 | Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form. 17 | 18 | 19 | 20 | 21 | Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project. 22 | 23 | 24 | 25 | 26 | Used to control if equality checks should use the Equals method resolved from the base class. 27 | 28 | 29 | 30 | 31 | Used to control if equality checks should use the static Equals method resolved from the base class. 32 | 33 | 34 | 35 | 36 | Used to turn off build warnings from this weaver. 37 | 38 | 39 | 40 | 41 | Used to turn off build warnings about mismatched On_PropertyName_Changed methods. 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 50 | 51 | 52 | 53 | 54 | A comma-separated list of error codes that can be safely ignored in assembly verification. 55 | 56 | 57 | 58 | 59 | 'false' to turn off automatic generation of the XML Schema file. 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2021-09-05 [0.3.0] 2 | - Added profile icons (place 32x32 sized icon.png beside profile.xml) 3 | - Removed "Use all cores" setting which didn't do anything 4 | - Fixed crash when spamming kill process 5 | 6 | # 2021-07-25 [0.2.1] 7 | - Updated default profile.xml 8 | 9 | # 2021-07-25 [0.2.0] 10 | - Updated sample bnspatch.xml (Removes xml patch that was breaking F button) 11 | - Set client working directory when starting game 12 | - Ability to set custom environment variables in profile.xml 13 | 14 | # 2020-09-04 [0.1.1] 15 | - Added profile priority to allow profile reordering 16 | 17 | # 2020-08-09 [0.1.0] 18 | - Fixed launcher always showed username instead of display name 19 | 20 | # 2020-08-04 [0.0.9] 21 | - Fixed starting without account would still try to log in last logged in account (and probably other bugs releated to not clearing old environment variables) 22 | - Added ability to add custom titles for accounts 23 | - Added setting to hide private server ip in profiles 24 | 25 | # 2020-07-20 [0.0.8] 26 | - Added ability to not select any account when starting game 27 | 28 | # 2020-07-20 [0.0.7] 29 | - Added account system/support for loginhelper (automatically log in and put pin) 30 | - Added automatic profile reloading 31 | 32 | # 2020-07-13 [0.0.5] 33 | - Improved profile system 34 | - Added ability to specify client path/arguments in profile 35 | - Added support for binloader (you need to get the plugin yourself) 36 | 37 | # 2020-07-10 [0.0.4] 38 | - Added new GG bypass 39 | 40 | # 2020-06-07 [0.0.3] 41 | - Added WSAConnect ip rewrite option to bnspatch dll 42 | - Added logging tab and logging from named pipe 43 | - Enabled TOI by default in sample profile 44 | - Fixed clicking same button that didn't switch tab didn't do anything 45 | - Fixed clicking links in about tab didn't do anything 46 | - Fixed reloading profiles lost process list under profile 47 | 48 | # 2020-06-03 [0.0.2] 49 | - Fixed changing tab lost tracked processes 50 | - Fixed sample.xml was missing /@value at the end of the query for Lobby gate address, port, np-address and np-port causing it to not actually edit those xml values... 51 | 52 | # 2020-09-04 [0.1.1] 53 | - Added profile priority to allow profile reordering 54 | 55 | # 2020-08-09 [0.1.0] 56 | - Fixed launcher always showed username instead of display name 57 | 58 | # 2020-08-04 [0.0.9] 59 | - Fixed starting without account would still try to log in last logged in account (and probably other bugs releated to not clearing old environment variables) 60 | - Added ability to add custom titles for accounts 61 | - Added setting to hide private server ip in profiles 62 | 63 | # 2020-07-20 [0.0.8] 64 | - Added ability to not select any account when starting game 65 | 66 | # 2020-07-20 [0.0.7] 67 | - Added account system/support for loginhelper (automatically log in and put pin) 68 | - Added automatic profile reloading 69 | 70 | # 2020-07-13 [0.0.5] 71 | - Improved profile system 72 | - Added ability to specify client path/arguments in profile 73 | - Added support for binloader (you need to get the plugin yourself) 74 | 75 | # 2020-07-10 [0.0.4] 76 | - Added new GG bypass 77 | 78 | # 2020-06-07 [0.0.3] 79 | - Added WSAConnect ip rewrite option to bnspatch dll 80 | - Added logging tab and logging from named pipe 81 | - Enabled TOI by default in sample profile 82 | - Fixed clicking same button that didn't switch tab didn't do anything 83 | - Fixed clicking links in about tab didn't do anything 84 | - Fixed reloading profiles lost process list under profile 85 | 86 | # 2020-06-03 [0.0.2] 87 | - Fixed changing tab lost tracked processes 88 | - Fixed sample.xml was missing /@value at the end of the query for Lobby gate address, port, np-address and np-port causing it to not actually edit those xml values... -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/AccountView.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 26 | 27 | 48 | 49 | 50 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | Accounts 73 | 74 | 78 | 84 | 85 | 86 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Source/BnsLauncher/ViewModels/ProfilesViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using BnsLauncher.Core.Abstractions; 9 | using BnsLauncher.Core.Models; 10 | using BnsLauncher.Messages; 11 | using Caliburn.Micro; 12 | 13 | namespace BnsLauncher.ViewModels 14 | { 15 | public class ProfilesViewModel : Screen, IHandle 16 | { 17 | private readonly IProfileLoader _profileLoader; 18 | private readonly IGameStarter _gameStarter; 19 | private readonly IProfileAccountsGetter _profileAccountsGetter; 20 | private readonly NavigationServices _navigationServices; 21 | 22 | public ProfilesViewModel(IProfileLoader profileLoader, IGameStarter gameStarter, GlobalConfig globalConfig, 23 | IEventAggregator eventAggregator, IProfileAccountsGetter profileAccountsGetter, NavigationServices navigationServices) 24 | { 25 | _profileLoader = profileLoader; 26 | _gameStarter = gameStarter; 27 | GlobalConfig = globalConfig; 28 | _profileAccountsGetter = profileAccountsGetter; 29 | _navigationServices = navigationServices; 30 | 31 | gameStarter.OnProcessExit += GameStarterOnOnProcessExit; 32 | 33 | eventAggregator.SubscribeOnUIThread(this); 34 | } 35 | 36 | public ObservableCollection Profiles { get; set; } = new ObservableCollection(); 37 | public GlobalConfig GlobalConfig { get; set; } 38 | 39 | public void StartGame(Profile profile) 40 | { 41 | var matchedAccounts = _profileAccountsGetter.GetAccountsForProfile(profile, GlobalConfig.Accounts.ToArray()); 42 | 43 | // If no accounts then just start the game 44 | if (matchedAccounts.Length == 0) 45 | { 46 | _gameStarter.Start(profile, GlobalConfig, null); 47 | return; 48 | } 49 | 50 | // Otherwise select account 51 | _navigationServices.Main.NavigateToViewModel(new Dictionary 52 | { 53 | [nameof(SelectAccountViewModel.Accounts)] = matchedAccounts, 54 | [nameof(SelectAccountViewModel.OnAccountSelect)] = 55 | (Action) (account => _gameStarter.Start(profile, GlobalConfig, account)) 56 | }); 57 | } 58 | 59 | public void StopProcess(ProcessInfo processInfo) 60 | { 61 | if (processInfo != null) 62 | { 63 | try 64 | { 65 | if (!processInfo.Process.HasExited) 66 | processInfo.Process.Kill(); 67 | } 68 | catch 69 | { 70 | // ignore 71 | } 72 | } 73 | } 74 | 75 | protected override Task OnInitializeAsync(CancellationToken cancellationToken) => LoadProfiles(); 76 | public Task HandleAsync(ReloadProfilesMessage message, CancellationToken cancellationToken) => LoadProfiles(); 77 | 78 | private async Task LoadProfiles() 79 | { 80 | var oldProfiles = Profiles.ToDictionary(x => x.ProfilePath, x => x.ProcessInfos); 81 | var profiles = await _profileLoader.LoadProfiles(Constants.ProfilesPath); 82 | 83 | // Migrate old process infos 84 | foreach (var profile in profiles) 85 | { 86 | if (!oldProfiles.TryGetValue(profile.ProfilePath, out var oldProcessInfos)) 87 | continue; 88 | 89 | foreach (var processInfo in oldProcessInfos) 90 | { 91 | profile.AddProcessInfo(processInfo); 92 | } 93 | } 94 | 95 | Profiles.Clear(); 96 | 97 | foreach (var profile in profiles) 98 | { 99 | Profiles.Add(profile); 100 | } 101 | 102 | NotifyOfPropertyChange(nameof(Profiles)); 103 | } 104 | 105 | private void GameStarterOnOnProcessExit(Process process) 106 | { 107 | var pid = process.Id; 108 | var profile = Profiles.FirstOrDefault(x => x.HasProcessWithId(pid)); 109 | profile?.RemoveProcessWithId(pid); 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Tests/ProfileLoaderTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Abstractions.TestingHelpers; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using BnsLauncher.Core.Abstractions; 5 | using BnsLauncher.Core.Services; 6 | using BnsLauncher.Tests.Helpers; 7 | using BnsLauncher.Tests.Mocks; 8 | using NUnit.Framework; 9 | 10 | namespace BnsLauncher.Tests 11 | { 12 | [TestFixture] 13 | public class ProfileLoaderTests 14 | { 15 | private ILogger _logger; 16 | 17 | [SetUp] 18 | public void SetUp() 19 | { 20 | _logger = new MockLogger(); 21 | } 22 | 23 | [Test] 24 | public async Task SamplePrivateServerProfile() 25 | { 26 | var fs = new MockFileSystem(); 27 | fs.WriteSampleProfile( 28 | "C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles\\PrivateServer", 29 | "PrivateServer"); 30 | 31 | var profileLoader = new ProfileLoader(fs, _logger, new NullSampleProfileWriter()); 32 | var profiles = await profileLoader.LoadProfiles( 33 | "C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles"); 34 | 35 | Assert.AreEqual(1, profiles.Count, "Must only load 1 profile"); 36 | 37 | var profile = profiles.First(); 38 | 39 | Assert.AreEqual( 40 | "C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles\\PrivateServer", 41 | profile.ProfilePath, 42 | "Profile paths must be equal"); 43 | 44 | Assert.AreEqual( 45 | "C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles\\PrivateServer\\bnspatch.xml", 46 | profile.BnsPatchPath, 47 | "Bns patch paths must be equal"); 48 | 49 | Assert.AreEqual("Private Server", profile.Name, "Names must be equal"); 50 | Assert.AreEqual('P', profile.Initial, "Initials must be equal"); 51 | Assert.AreEqual("red", profile.Background, "Backgrounds must be equal"); 52 | Assert.AreEqual("white", profile.Foreground, "Foregrounds must be equal"); 53 | 54 | Assert.AreEqual("127.0.0.1", profile.Ip, "Ips must be equal"); 55 | Assert.AreEqual(10900, profile.Port, "Ports must be equal"); 56 | 57 | Assert.IsNull(profile.BinPath, "Bin path must be null"); 58 | Assert.IsNull(profile.LocalBinPath, "Local bin path must be null"); 59 | 60 | Assert.IsTrue(profile.AllowAccounts, "This profile must allow accounts"); 61 | Assert.IsFalse(profile.AllowPin, "This profile must not require pin"); 62 | Assert.IsFalse(profile.AutopinOnRelog, "This profile must not auto pin on relog"); 63 | } 64 | 65 | [Test] 66 | public async Task SampleLiveEU32Profile() 67 | { 68 | var fs = new MockFileSystem(); 69 | fs.WriteSampleProfile( 70 | "C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles\\LiveEU32", 71 | "LiveEU32"); 72 | 73 | var profileLoader = new ProfileLoader(fs, _logger, new NullSampleProfileWriter()); 74 | var profiles = await profileLoader.LoadProfiles( 75 | "C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles"); 76 | 77 | Assert.AreEqual(1, profiles.Count, "Must only load 1 profile"); 78 | 79 | var profile = profiles.First(); 80 | 81 | Assert.AreEqual( 82 | "C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles\\LiveEU32", 83 | profile.ProfilePath, 84 | "Profile paths must be equal"); 85 | 86 | Assert.AreEqual( 87 | "C:\\Users\\TestUser\\AppData\\Local\\BnSLauncher\\Profiles\\LiveEU32\\bnspatch.xml", 88 | profile.BnsPatchPath, 89 | "Bns patch paths must be equal"); 90 | 91 | Assert.AreEqual("Live EU 32-bit", profile.Name, "Names must be equal"); 92 | Assert.AreEqual('L', profile.Initial, "Initials must be equal"); 93 | Assert.AreEqual("green", profile.Background, "Backgrounds must be equal"); 94 | Assert.AreEqual("white", profile.Foreground, "Foregrounds must be equal"); 95 | Assert.AreEqual("C:\\Program Files (x86)\\NCSOFT\\BnS\\bin\\Client.exe", profile.ClientPath, "Client paths must be equal"); 96 | Assert.AreEqual("/sesskey /launchbylauncher -lang:English -region:1", profile.Arguments, "Arguments must be equal"); 97 | 98 | Assert.IsNull(profile.Ip, "Ip must be null"); 99 | Assert.AreEqual(0, profile.Port, "Ports must be 0"); 100 | 101 | Assert.IsNull(profile.BinPath, "Bin path must be null"); 102 | Assert.IsNull(profile.LocalBinPath, "Local bin path must be null"); 103 | 104 | Assert.IsTrue(profile.AllowAccounts, "This profile must allow accounts"); 105 | Assert.IsTrue(profile.AllowPin, "This profile must require pin"); 106 | Assert.IsFalse(profile.AutopinOnRelog, "This profile must not auto pin on relog"); 107 | } 108 | 109 | // TODO: Test profile that tests untested stuff from sample profiles 110 | // AutopinOnRelog 111 | } 112 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Samples/LiveEU32/bnspatch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 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 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /Source/BnsLauncher/Views/ProfilesView.xaml: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 55 | 59 | 60 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 74 | 75 | 76 | 85 | 86 | 87 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 119 | 123 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | Profiles 133 | 134 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Samples/PrivateServer/bnspatch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 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 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 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 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Services/ProfileLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO.Abstractions; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using System.Xml; 7 | using BnsLauncher.Core.Abstractions; 8 | using BnsLauncher.Core.Models; 9 | 10 | namespace BnsLauncher.Core.Services 11 | { 12 | public class ProfileLoader : IProfileLoader 13 | { 14 | private readonly IFileSystem _fs; 15 | private readonly ILogger _logger; 16 | private readonly ISampleProfileWriter _sampleProfileWriter; 17 | 18 | public ProfileLoader(IFileSystem fs, ILogger logger, ISampleProfileWriter sampleProfileWriter) 19 | { 20 | _fs = fs; 21 | _logger = logger; 22 | _sampleProfileWriter = sampleProfileWriter; 23 | } 24 | 25 | public Task> LoadProfiles(string sourceDirectory) 26 | { 27 | _logger.Log("Loading profiles.."); 28 | 29 | var directoryInfo = _fs.DirectoryInfo.FromDirectoryName(sourceDirectory); 30 | 31 | // Copy sample profiles if none found 32 | if (!directoryInfo.Exists || directoryInfo.GetDirectories().Length == 0) 33 | _sampleProfileWriter.WriteSampleProfiles(sourceDirectory); 34 | 35 | var profileList = _fs.Directory.EnumerateDirectories(directoryInfo.FullName) 36 | .Select(LoadProfile) 37 | .Where(profile => profile != null) 38 | .OrderByDescending(x => x.Priority) 39 | .ThenBy(x => x.Name) 40 | .ToList(); 41 | 42 | return Task.FromResult(profileList); 43 | } 44 | 45 | private Profile LoadProfile(string profileRoot) 46 | { 47 | var files = _fs.Directory 48 | .GetFiles(profileRoot, "*.xml") 49 | .ToDictionary(_fs.Path.GetFileName, _fs.File.ReadAllText, StringComparer.OrdinalIgnoreCase); 50 | 51 | if (!files.TryGetValue("profile.xml", out var profileXmlContent)) 52 | { 53 | _logger.Log($"profile.xml is missing from folder: '{profileRoot}'"); 54 | return null; 55 | } 56 | 57 | _logger.Log($"Loading xml profile: '{profileRoot}'"); 58 | 59 | Profile profile; 60 | 61 | try 62 | { 63 | var profileDocument = new XmlDocument(); 64 | profileDocument.LoadXml(profileXmlContent); 65 | 66 | profile = LoadProfile(profileDocument); 67 | profile.ProfilePath = profileRoot; 68 | 69 | LoadProfileImage(profile); 70 | } 71 | catch (Exception exception) 72 | { 73 | _logger.Log("Exception has occured while loading profile:"); 74 | _logger.Log(exception); 75 | return null; 76 | } 77 | 78 | if (files.TryGetValue("loginhelper.xml", out var loginhelperXmlContent)) 79 | { 80 | try 81 | { 82 | LoadLoginHelper(profile, loginhelperXmlContent); 83 | } 84 | catch (Exception exception) 85 | { 86 | _logger.Log("Exception has occured while reading loginhelper.xml"); 87 | _logger.Log(exception); 88 | } 89 | } 90 | 91 | if (files.TryGetValue("bnspatch.xml", out var bnspatchXmlContent)) 92 | { 93 | try 94 | { 95 | profile.BnsPatchPath = _fs.Path.Combine(profileRoot, "bnspatch.xml"); 96 | (profile.Ip, profile.Port) = LoadIpPortFromBnsPatch(bnspatchXmlContent); 97 | } 98 | catch (Exception exception) 99 | { 100 | _logger.Log("Exception has occured while reading bnspatch.xml"); 101 | _logger.Log(exception); 102 | } 103 | } 104 | 105 | if (files.TryGetValue("binloader.xml", out var binloaderXmlContent)) 106 | { 107 | try 108 | { 109 | (profile.BinPath, profile.LocalBinPath) = LoadBinsFromBinLoader(profileRoot, binloaderXmlContent); 110 | } 111 | catch (Exception exception) 112 | { 113 | _logger.Log("Exception has occured while reading binloader.xml"); 114 | _logger.Log(exception); 115 | } 116 | } 117 | 118 | return profile; 119 | } 120 | 121 | private void LoadLoginHelper(Profile profile, string loginhelperXmlContent) 122 | { 123 | var root = new XmlDocument(); 124 | root.LoadXml(loginhelperXmlContent); 125 | 126 | var loginhelper = root.SelectSingleNode("/loginhelper"); 127 | 128 | if (loginhelper == null) 129 | { 130 | _logger.Log("[LoadLoginHelper] Failed to find root node: 'loginhelper'"); 131 | return; 132 | } 133 | 134 | profile.AllowPin = loginhelper.GetNodeBoolean("./allow-pin", false); 135 | profile.AllowAccounts = loginhelper.GetNodeBoolean("./allow-accounts", false); 136 | profile.AutopinOnRelog = loginhelper.GetNodeBoolean("./autopin-on-relog", false); 137 | } 138 | 139 | private void LoadProfileImage(Profile profile) 140 | { 141 | var imagePath = _fs.Path.Combine(profile.ProfilePath, "icon.png"); 142 | 143 | if (!_fs.File.Exists(imagePath)) 144 | return; 145 | 146 | profile.BackgroundImage = imagePath; 147 | } 148 | 149 | private Profile LoadProfile(XmlNode root) 150 | { 151 | var profile = new Profile(); 152 | 153 | var profileRoot = root.SelectSingleNode("/profile"); 154 | 155 | if (profileRoot == null) 156 | { 157 | _logger.Log("[LoadProfileFromXml] Failed to find root node: 'profile'"); 158 | return null; 159 | } 160 | 161 | profile.Name = profileRoot.GetNodeText("./name", "No name"); 162 | profile.Background = profileRoot.GetNodeText("./background", "gray"); 163 | profile.Foreground = profileRoot.GetNodeText("./foreground", "white"); 164 | profile.ClientPath = profileRoot.GetNodeText("./clientpath", null); 165 | profile.Arguments = profileRoot.GetNodeText("./arguments", null); 166 | var priorityString = profileRoot.GetNodeText("./priority", "0"); 167 | 168 | if (int.TryParse(priorityString, out var priority)) 169 | { 170 | profile.Priority = priority; 171 | } 172 | else 173 | { 174 | _logger.Log($"[LoadProfileFromXml] Invalid priority string: '{priorityString}'"); 175 | } 176 | 177 | var setEnvNodes = profileRoot.SelectNodes("./set-env"); 178 | if (setEnvNodes != null) 179 | { 180 | foreach (XmlElement node in setEnvNodes) 181 | { 182 | var key = node.GetAttribute("key"); 183 | 184 | if (string.IsNullOrWhiteSpace(key)) 185 | continue; 186 | 187 | profile.CustomEnvironmentVariables[key] = node.InnerText; 188 | } 189 | } 190 | 191 | return profile; 192 | } 193 | 194 | private (string bin, string localbin) LoadBinsFromBinLoader(string profileRoot, string xml) 195 | { 196 | var root = new XmlDocument(); 197 | root.LoadXml(xml); 198 | 199 | var binloaderRoot = root.SelectSingleNode("/binloader"); 200 | 201 | if (binloaderRoot == null) 202 | { 203 | _logger.Log("[LoadBinsFromBinLoader] Failed to find root node: 'binloader'"); 204 | return (null, null); 205 | } 206 | 207 | var bin = binloaderRoot.GetNodeText("./bin", null); 208 | var localBin = binloaderRoot.GetNodeText("./localbin", null); 209 | 210 | if (string.IsNullOrWhiteSpace(bin) || string.IsNullOrWhiteSpace(localBin)) 211 | return (null, null); 212 | 213 | return ( 214 | _fs.Path.Combine(profileRoot, Environment.ExpandEnvironmentVariables(bin)), 215 | _fs.Path.Combine(profileRoot, Environment.ExpandEnvironmentVariables(localBin)) 216 | ); 217 | } 218 | 219 | private static (string ip, ushort port) LoadIpPortFromBnsPatch(string xml) 220 | { 221 | var root = new XmlDocument(); 222 | root.LoadXml(xml); 223 | 224 | var lobbyGateAddress = 225 | root.SelectSingleNode("//select-node[contains(@query, \"'lobby-gate-address'\")]/set-value"); 226 | var lobbyGatePort = 227 | root.SelectSingleNode("//select-node[contains(@query, \"'lobby-gate-port'\")]/set-value"); 228 | 229 | var address = lobbyGateAddress?.Attributes?["value"].Value; 230 | var portString = lobbyGatePort?.Attributes?["value"].Value; 231 | 232 | if (lobbyGateAddress == null || string.IsNullOrWhiteSpace(address)) 233 | return (null, 0); 234 | 235 | if (!ushort.TryParse(portString, out var port) 236 | || lobbyGatePort == null 237 | || string.IsNullOrWhiteSpace(portString)) 238 | return (null, 0); 239 | 240 | return (address, port); 241 | } 242 | } 243 | } -------------------------------------------------------------------------------- /Source/BnsLauncher.Core/Services/GameStarter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO.Abstractions; 5 | using System.Linq; 6 | using BnsLauncher.Core.Abstractions; 7 | using BnsLauncher.Core.Models; 8 | 9 | namespace BnsLauncher.Core.Services 10 | { 11 | public class GameStarter : IGameStarter 12 | { 13 | private readonly ILogger _logger; 14 | private readonly IFileSystem _fs; 15 | private readonly Dictionary _env = new Dictionary(); 16 | 17 | public GameStarter(ILogger logger, IFileSystem fs) 18 | { 19 | _logger = logger; 20 | _fs = fs; 21 | } 22 | 23 | public event Action OnProcessExit; 24 | 25 | public void Start(Profile profile, GlobalConfig globalConfig, Account account) 26 | { 27 | try 28 | { 29 | if (!GetClientPath(profile, globalConfig, out var clientPath)) 30 | return; 31 | 32 | if (!ClientExists(clientPath)) 33 | return; 34 | 35 | var arguments = GetArguments(profile, globalConfig); 36 | var logPipeName = GenerateLogPipeName(); 37 | 38 | SetEnvironmentVariables(profile, account); 39 | 40 | _logger.Log($"Starting game with arguments: '{arguments}'"); 41 | 42 | // Start client 43 | var processInfo = CreateProcess(clientPath, arguments, logPipeName); 44 | 45 | if (processInfo == null) 46 | return; 47 | 48 | processInfo.Account = account; 49 | 50 | profile.AddProcessInfo(processInfo); 51 | 52 | ClearEnvironmentVariables(); 53 | } 54 | catch (Exception exception) 55 | { 56 | _logger.Log("Exception occured while starting client"); 57 | _logger.Log(exception); 58 | } 59 | } 60 | 61 | private ProcessInfo CreateProcess(string clientPath, string arguments, string logPipeName) 62 | { 63 | var processStartInfo = new ProcessStartInfo 64 | { 65 | FileName = clientPath, 66 | Arguments = arguments, 67 | UseShellExecute = false, 68 | }; 69 | 70 | var clientBinDirectory = _fs.Path.GetDirectoryName(_fs.Path.GetFullPath(clientPath)); 71 | if (clientBinDirectory != null) 72 | { 73 | processStartInfo.WorkingDirectory = clientBinDirectory; 74 | _logger.Log($"Setting client working directory to: '{clientBinDirectory}'"); 75 | } 76 | else 77 | { 78 | _logger.Log($"Failed to get client working directory from path: '{clientPath}'"); 79 | } 80 | 81 | var process = new Process 82 | { 83 | StartInfo = processStartInfo, 84 | EnableRaisingEvents = true, 85 | }; 86 | 87 | foreach (var kp in _env) 88 | { 89 | processStartInfo.EnvironmentVariables[kp.Key] = kp.Value; 90 | } 91 | 92 | var namedPipeToLog = new NamedPipeToLog(_logger, logPipeName); 93 | 94 | process.Exited += (sender, eventArgs) => 95 | { 96 | OnProcessExit?.Invoke(process); 97 | 98 | namedPipeToLog.Close(); 99 | 100 | _logger.Log($"Process {process.Id} has exited"); 101 | }; 102 | 103 | if (!process.Start()) 104 | { 105 | _logger.Log("Failed to start process"); 106 | return null; 107 | } 108 | 109 | namedPipeToLog.LogPrefix = $"(Pid: {process.Id}) "; 110 | namedPipeToLog.StartLogging(); 111 | 112 | return new ProcessInfo {Process = process}; 113 | } 114 | 115 | private void ClearEnvironmentVariables() 116 | { 117 | _env.Clear(); 118 | } 119 | 120 | private void SetEnvironmentVariables(Profile profile, Account account) 121 | { 122 | ClearEnvironmentVariables(); 123 | 124 | // Make the game not required to run as admin 125 | SetEnvIfNotEmpty("__COMPAT_LAYER", "RUNASINVOKER"); 126 | 127 | SetEnvIfNotEmpty("BNS_PROFILE_XML", profile.BnsPatchPath); 128 | 129 | if (profile.HasBins) 130 | { 131 | if (!_fs.File.Exists(profile.BinPath)) 132 | { 133 | _logger.Log($"Bin doesn't exist at path: '{profile.BinPath}'"); 134 | goto SKIP; 135 | } 136 | 137 | if (!_fs.File.Exists(profile.LocalBinPath)) 138 | { 139 | _logger.Log($"Local bin doesn't exist at path: '{profile.LocalBinPath}'"); 140 | goto SKIP; 141 | } 142 | 143 | SetEnvIfNotEmpty("BNS_PROFILE_DATAFILE", profile.BinPath); 144 | SetEnvIfNotEmpty("BNS_PROFILE_LOCALFILE", profile.LocalBinPath); 145 | 146 | SKIP: ; 147 | } 148 | 149 | // Set custom environment variables 150 | foreach (var kp in profile.CustomEnvironmentVariables) 151 | { 152 | SetEnvIfNotEmpty(kp.Key, kp.Value); 153 | } 154 | 155 | // Account stuff 156 | if (account == null) 157 | return; 158 | 159 | if (!profile.AllowAccounts) 160 | { 161 | _logger.Log("Profile disallows automatic login"); 162 | return; 163 | } 164 | 165 | if (string.IsNullOrWhiteSpace(account.Username)) 166 | { 167 | _logger.Log("Account username is empty"); 168 | return; 169 | } 170 | 171 | if (string.IsNullOrWhiteSpace(account.Password)) 172 | { 173 | _logger.Log("Account password is empty"); 174 | return; 175 | } 176 | 177 | SetEnvIfNotEmpty("BNS_PROFILE_USERNAME", account.Username, true); 178 | SetEnvIfNotEmpty("BNS_PROFILE_PASSWORD", account.Password, true); 179 | 180 | if (!profile.AllowPin) 181 | { 182 | _logger.Log("Profile disallows automatic pin"); 183 | return; 184 | } 185 | 186 | var pin = account.Pin; 187 | 188 | if (string.IsNullOrEmpty(pin)) 189 | return; 190 | 191 | if (pin.Length != 6) 192 | { 193 | _logger.Log($"Invalid PIN length: {pin.Length}"); 194 | return; 195 | } 196 | 197 | if (!pin.All(char.IsDigit)) 198 | { 199 | _logger.Log("PIN must be a 6 digit number"); 200 | return; 201 | } 202 | 203 | SetEnvIfNotEmpty("BNS_PINCODE", account.Pin, true); 204 | } 205 | 206 | private string GenerateLogPipeName() 207 | { 208 | var pipeName = "bns_" + Guid.NewGuid().ToString().Replace("-", ""); 209 | 210 | _logger.Log($"Log pipe name: '{pipeName}'"); 211 | SetEnvIfNotEmpty("BNS_LOG", pipeName); 212 | 213 | return pipeName; 214 | } 215 | 216 | private bool GetClientPath(Profile profile, GlobalConfig globalConfig, out string clientPath) 217 | { 218 | if (!string.IsNullOrWhiteSpace(profile.ClientPath)) 219 | { 220 | _logger.Log($"Using profile client path : '{profile.ClientPath}'"); 221 | clientPath = profile.ClientPath; 222 | return true; 223 | } 224 | 225 | if (!string.IsNullOrWhiteSpace(globalConfig.ClientPath)) 226 | { 227 | _logger.Log($"Using global client path: '{globalConfig.ClientPath}'"); 228 | clientPath = globalConfig.ClientPath; 229 | return true; 230 | } 231 | 232 | _logger.Log("Client path is not set in either profile or global config"); 233 | clientPath = null; 234 | return false; 235 | } 236 | 237 | private bool ClientExists(string clientPath) 238 | { 239 | if (string.IsNullOrWhiteSpace(clientPath)) 240 | { 241 | _logger.Log("Client path is empty"); 242 | return false; 243 | } 244 | 245 | if (!_fs.File.Exists(clientPath)) 246 | { 247 | _logger.Log($"Client doesn't exist at path: '{clientPath}'"); 248 | return false; 249 | } 250 | 251 | return true; 252 | } 253 | 254 | private string GetArguments(Profile profile, GlobalConfig globalConfig) 255 | { 256 | var args = new List(); 257 | 258 | if (profile.Arguments != null) 259 | { 260 | _logger.Log($"Using profile arguments: '{profile.Arguments}'"); 261 | args.Add(profile.Arguments); 262 | } 263 | else if (globalConfig.Arguments != null) 264 | { 265 | _logger.Log($"Using global arguments: '{globalConfig.Arguments}'"); 266 | args.Add(globalConfig.Arguments); 267 | } 268 | else 269 | { 270 | _logger.Log("No arguments found in profile/global config"); 271 | } 272 | 273 | if (globalConfig.Unattended) 274 | args.Add("-UNATTENDED"); 275 | 276 | if (globalConfig.NoTextureStreaming) 277 | args.Add("-NOTEXTURESTREAMING"); 278 | 279 | return string.Join(" ", args); 280 | } 281 | 282 | private void SetEnvIfNotEmpty(string name, string value, bool censor = false) 283 | { 284 | if (string.IsNullOrWhiteSpace(value)) 285 | return; 286 | 287 | _env[name] = value; 288 | 289 | _logger.Log($"Set env. variable: {name}={(censor ? "***" : value)}"); 290 | } 291 | 292 | private void ClearEnv(string name) 293 | { 294 | _env.Remove(name); 295 | } 296 | } 297 | } -------------------------------------------------------------------------------- /THIRD-PARTY-NOTICES.txt: -------------------------------------------------------------------------------- 1 | ══════════════════ List of 3rd party libs/code used by BnsLauncher ══════════════════ 2 | 3 | * bnspatch & pluginloader - https://github.com/bnsmodpolice/bnspatch 4 | * UnityContainer - https://github.com/unitycontainer/unity 5 | * Fody.PropertyChanged - https://github.com/Fody/PropertyChanged 6 | * Newtonsoft.Json - https://github.com/JamesNK/Newtonsoft.Json 7 | * MaterialDesignInXAML - https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit 8 | * Caliburn.Micro - https://github.com/Caliburn-Micro/Caliburn.Micro 9 | * FluentWPF - https://github.com/sourcechord/FluentWPF 10 | 11 | ════════════════ Licenses for 3rd party libs/code used by BnsLauncher ════════════════ 12 | 13 | 14 | ═════ bnspatch & pluginloader 15 | 16 | The MIT License (MIT) 17 | 18 | Copyright (c) 2019-2020 zeffy 19 | 20 | Permission is hereby granted, free of charge, to any person obtaining a copy 21 | of this software and associated documentation files (the "Software"), to deal 22 | in the Software without restriction, including without limitation the rights 23 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 24 | copies of the Software, and to permit persons to whom the Software is 25 | furnished to do so, subject to the following conditions: 26 | 27 | The above copyright notice and this permission notice shall be included in all 28 | copies or substantial portions of the Software. 29 | 30 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 31 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 32 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 33 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 34 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 35 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 36 | SOFTWARE. 37 | 38 | 39 | 40 | ═════ Unity Container 41 | 42 | Apache License 43 | Version 2.0, January 2004 44 | http://www.apache.org/licenses/ 45 | 46 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 47 | 48 | 1. Definitions. 49 | 50 | "License" shall mean the terms and conditions for use, reproduction, 51 | and distribution as defined by Sections 1 through 9 of this document. 52 | 53 | "Licensor" shall mean the copyright owner or entity authorized by 54 | the copyright owner that is granting the License. 55 | 56 | "Legal Entity" shall mean the union of the acting entity and all 57 | other entities that control, are controlled by, or are under common 58 | control with that entity. For the purposes of this definition, 59 | "control" means (i) the power, direct or indirect, to cause the 60 | direction or management of such entity, whether by contract or 61 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 62 | outstanding shares, or (iii) beneficial ownership of such entity. 63 | 64 | "You" (or "Your") shall mean an individual or Legal Entity 65 | exercising permissions granted by this License. 66 | 67 | "Source" form shall mean the preferred form for making modifications, 68 | including but not limited to software source code, documentation 69 | source, and configuration files. 70 | 71 | "Object" form shall mean any form resulting from mechanical 72 | transformation or translation of a Source form, including but 73 | not limited to compiled object code, generated documentation, 74 | and conversions to other media types. 75 | 76 | "Work" shall mean the work of authorship, whether in Source or 77 | Object form, made available under the License, as indicated by a 78 | copyright notice that is included in or attached to the work 79 | (an example is provided in the Appendix below). 80 | 81 | "Derivative Works" shall mean any work, whether in Source or Object 82 | form, that is based on (or derived from) the Work and for which the 83 | editorial revisions, annotations, elaborations, or other modifications 84 | represent, as a whole, an original work of authorship. For the purposes 85 | of this License, Derivative Works shall not include works that remain 86 | separable from, or merely link (or bind by name) to the interfaces of, 87 | the Work and Derivative Works thereof. 88 | 89 | "Contribution" shall mean any work of authorship, including 90 | the original version of the Work and any modifications or additions 91 | to that Work or Derivative Works thereof, that is intentionally 92 | submitted to Licensor for inclusion in the Work by the copyright owner 93 | or by an individual or Legal Entity authorized to submit on behalf of 94 | the copyright owner. For the purposes of this definition, "submitted" 95 | means any form of electronic, verbal, or written communication sent 96 | to the Licensor or its representatives, including but not limited to 97 | communication on electronic mailing lists, source code control systems, 98 | and issue tracking systems that are managed by, or on behalf of, the 99 | Licensor for the purpose of discussing and improving the Work, but 100 | excluding communication that is conspicuously marked or otherwise 101 | designated in writing by the copyright owner as "Not a Contribution." 102 | 103 | "Contributor" shall mean Licensor and any individual or Legal Entity 104 | on behalf of whom a Contribution has been received by Licensor and 105 | subsequently incorporated within the Work. 106 | 107 | 2. Grant of Copyright License. Subject to the terms and conditions of 108 | this License, each Contributor hereby grants to You a perpetual, 109 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 110 | copyright license to reproduce, prepare Derivative Works of, 111 | publicly display, publicly perform, sublicense, and distribute the 112 | Work and such Derivative Works in Source or Object form. 113 | 114 | 3. Grant of Patent License. Subject to the terms and conditions of 115 | this License, each Contributor hereby grants to You a perpetual, 116 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 117 | (except as stated in this section) patent license to make, have made, 118 | use, offer to sell, sell, import, and otherwise transfer the Work, 119 | where such license applies only to those patent claims licensable 120 | by such Contributor that are necessarily infringed by their 121 | Contribution(s) alone or by combination of their Contribution(s) 122 | with the Work to which such Contribution(s) was submitted. If You 123 | institute patent litigation against any entity (including a 124 | cross-claim or counterclaim in a lawsuit) alleging that the Work 125 | or a Contribution incorporated within the Work constitutes direct 126 | or contributory patent infringement, then any patent licenses 127 | granted to You under this License for that Work shall terminate 128 | as of the date such litigation is filed. 129 | 130 | 4. Redistribution. You may reproduce and distribute copies of the 131 | Work or Derivative Works thereof in any medium, with or without 132 | modifications, and in Source or Object form, provided that You 133 | meet the following conditions: 134 | 135 | (a) You must give any other recipients of the Work or 136 | Derivative Works a copy of this License; and 137 | 138 | (b) You must cause any modified files to carry prominent notices 139 | stating that You changed the files; and 140 | 141 | (c) You must retain, in the Source form of any Derivative Works 142 | that You distribute, all copyright, patent, trademark, and 143 | attribution notices from the Source form of the Work, 144 | excluding those notices that do not pertain to any part of 145 | the Derivative Works; and 146 | 147 | (d) If the Work includes a "NOTICE" text file as part of its 148 | distribution, then any Derivative Works that You distribute must 149 | include a readable copy of the attribution notices contained 150 | within such NOTICE file, excluding those notices that do not 151 | pertain to any part of the Derivative Works, in at least one 152 | of the following places: within a NOTICE text file distributed 153 | as part of the Derivative Works; within the Source form or 154 | documentation, if provided along with the Derivative Works; or, 155 | within a display generated by the Derivative Works, if and 156 | wherever such third-party notices normally appear. The contents 157 | of the NOTICE file are for informational purposes only and 158 | do not modify the License. You may add Your own attribution 159 | notices within Derivative Works that You distribute, alongside 160 | or as an addendum to the NOTICE text from the Work, provided 161 | that such additional attribution notices cannot be construed 162 | as modifying the License. 163 | 164 | You may add Your own copyright statement to Your modifications and 165 | may provide additional or different license terms and conditions 166 | for use, reproduction, or distribution of Your modifications, or 167 | for any such Derivative Works as a whole, provided Your use, 168 | reproduction, and distribution of the Work otherwise complies with 169 | the conditions stated in this License. 170 | 171 | 5. Submission of Contributions. Unless You explicitly state otherwise, 172 | any Contribution intentionally submitted for inclusion in the Work 173 | by You to the Licensor shall be under the terms and conditions of 174 | this License, without any additional terms or conditions. 175 | Notwithstanding the above, nothing herein shall supersede or modify 176 | the terms of any separate license agreement you may have executed 177 | with Licensor regarding such Contributions. 178 | 179 | 6. Trademarks. This License does not grant permission to use the trade 180 | names, trademarks, service marks, or product names of the Licensor, 181 | except as required for reasonable and customary use in describing the 182 | origin of the Work and reproducing the content of the NOTICE file. 183 | 184 | 7. Disclaimer of Warranty. Unless required by applicable law or 185 | agreed to in writing, Licensor provides the Work (and each 186 | Contributor provides its Contributions) on an "AS IS" BASIS, 187 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 188 | implied, including, without limitation, any warranties or conditions 189 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 190 | PARTICULAR PURPOSE. You are solely responsible for determining the 191 | appropriateness of using or redistributing the Work and assume any 192 | risks associated with Your exercise of permissions under this License. 193 | 194 | 8. Limitation of Liability. In no event and under no legal theory, 195 | whether in tort (including negligence), contract, or otherwise, 196 | unless required by applicable law (such as deliberate and grossly 197 | negligent acts) or agreed to in writing, shall any Contributor be 198 | liable to You for damages, including any direct, indirect, special, 199 | incidental, or consequential damages of any character arising as a 200 | result of this License or out of the use or inability to use the 201 | Work (including but not limited to damages for loss of goodwill, 202 | work stoppage, computer failure or malfunction, or any and all 203 | other commercial damages or losses), even if such Contributor 204 | has been advised of the possibility of such damages. 205 | 206 | 9. Accepting Warranty or Additional Liability. While redistributing 207 | the Work or Derivative Works thereof, You may choose to offer, 208 | and charge a fee for, acceptance of support, warranty, indemnity, 209 | or other liability obligations and/or rights consistent with this 210 | License. However, in accepting such obligations, You may act only 211 | on Your own behalf and on Your sole responsibility, not on behalf 212 | of any other Contributor, and only if You agree to indemnify, 213 | defend, and hold each Contributor harmless for any liability 214 | incurred by, or claims asserted against, such Contributor by reason 215 | of your accepting any such warranty or additional liability. 216 | 217 | END OF TERMS AND CONDITIONS 218 | 219 | APPENDIX: How to apply the Apache License to your work. 220 | 221 | To apply the Apache License to your work, attach the following 222 | boilerplate notice, with the fields enclosed by brackets "{}" 223 | replaced with your own identifying information. (Don't include 224 | the brackets!) The text should be enclosed in the appropriate 225 | comment syntax for the file format. We also recommend that a 226 | file or class name and description of purpose be included on the 227 | same "printed page" as the copyright notice for easier 228 | identification within third-party archives. 229 | 230 | Copyright 2020 .NET Foundation and Contributors 231 | All Rights Reserved 232 | 233 | Licensed under the Apache License, Version 2.0 (the "License"); 234 | you may not use this file except in compliance with the License. 235 | You may obtain a copy of the License at 236 | 237 | http://www.apache.org/licenses/LICENSE-2.0 238 | 239 | Unless required by applicable law or agreed to in writing, software 240 | distributed under the License is distributed on an "AS IS" BASIS, 241 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 242 | See the License for the specific language governing permissions and 243 | limitations under the License. 244 | 245 | 246 | 247 | ═════ Fody.PropertyChanged 248 | 249 | The MIT License 250 | 251 | Copyright (c) 2012 Simon Cropp and contributors 252 | 253 | Permission is hereby granted, free of charge, to any person obtaining a copy 254 | of this software and associated documentation files (the "Software"), to deal 255 | in the Software without restriction, including without limitation the rights 256 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 257 | copies of the Software, and to permit persons to whom the Software is 258 | furnished to do so, subject to the following conditions: 259 | 260 | The above copyright notice and this permission notice shall be included in 261 | all copies or substantial portions of the Software. 262 | 263 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 264 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 265 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 266 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 267 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 268 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 269 | THE SOFTWARE. 270 | 271 | 272 | 273 | ═════ Newtonsoft.Json 274 | 275 | The MIT License (MIT) 276 | 277 | Copyright (c) 2007 James Newton-King 278 | 279 | Permission is hereby granted, free of charge, to any person obtaining a copy of 280 | this software and associated documentation files (the "Software"), to deal in 281 | the Software without restriction, including without limitation the rights to 282 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 283 | the Software, and to permit persons to whom the Software is furnished to do so, 284 | subject to the following conditions: 285 | 286 | The above copyright notice and this permission notice shall be included in all 287 | copies or substantial portions of the Software. 288 | 289 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 290 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 291 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 292 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 293 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 294 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 295 | 296 | 297 | 298 | ═════ MaterialDesignInXAML 299 | 300 | The MIT License (MIT) 301 | 302 | Copyright (c) James Willock, Mulholland Software and Contributors 303 | 304 | Permission is hereby granted, free of charge, to any person obtaining a copy 305 | of this software and associated documentation files (the "Software"), to deal 306 | in the Software without restriction, including without limitation the rights 307 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 308 | copies of the Software, and to permit persons to whom the Software is 309 | furnished to do so, subject to the following conditions: 310 | 311 | The above copyright notice and this permission notice shall be included in all 312 | copies or substantial portions of the Software. 313 | 314 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 315 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 316 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 317 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 318 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 319 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 320 | SOFTWARE. 321 | 322 | 323 | 324 | ═════ Caliburn.Micro 325 | 326 | The MIT License 327 | 328 | Copyright (c) 2010 Blue Spire Consulting, Inc. 329 | 330 | Permission is hereby granted, free of charge, to any person obtaining a copy 331 | of this software and associated documentation files (the "Software"), to deal 332 | in the Software without restriction, including without limitation the rights 333 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 334 | copies of the Software, and to permit persons to whom the Software is 335 | furnished to do so, subject to the following conditions: 336 | 337 | The above copyright notice and this permission notice shall be included in 338 | all copies or substantial portions of the Software. 339 | 340 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 341 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 342 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 343 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 344 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 345 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 346 | THE SOFTWARE. 347 | 348 | 349 | 350 | 351 | ═════ FluentWPF 352 | 353 | MIT License 354 | 355 | Copyright (c) 2016 minami_SC 356 | 357 | Permission is hereby granted, free of charge, to any person obtaining a copy 358 | of this software and associated documentation files (the "Software"), to deal 359 | in the Software without restriction, including without limitation the rights 360 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 361 | copies of the Software, and to permit persons to whom the Software is 362 | furnished to do so, subject to the following conditions: 363 | 364 | The above copyright notice and this permission notice shall be included in all 365 | copies or substantial portions of the Software. 366 | 367 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 368 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 369 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 370 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 371 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 372 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 373 | SOFTWARE. 374 | 375 | --------------------------------------------------------------------------------