From d00ab6d185150ef6a20b9e6078f80749f705a745 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Sat, 24 Aug 2024 17:20:09 -0400 Subject: [PATCH] improve server selector --- EpinelPS/EpinelPS.csproj | 5 +- EpinelPS/Program.cs | 2 + ServerSelector.Desktop/Program.cs | 4 +- ServerSelector/App.axaml.cs | 12 +- ServerSelector/ServerSelector.csproj | 2 - ServerSelector/ServerSwitcher.cs | 166 ++++++++++++++++----- ServerSelector/ViewModels/MainViewModel.cs | 6 - ServerSelector/ViewModels/ViewModelBase.cs | 7 - ServerSelector/Views/MainView.axaml | 23 ++- ServerSelector/Views/MainView.axaml.cs | 124 ++++++++++++--- ServerSelector/Views/MainWindow.axaml | 4 +- ServerSelector/myCA.cer | 21 +++ 12 files changed, 269 insertions(+), 107 deletions(-) delete mode 100644 ServerSelector/ViewModels/MainViewModel.cs delete mode 100644 ServerSelector/ViewModels/ViewModelBase.cs create mode 100644 ServerSelector/myCA.cer diff --git a/EpinelPS/EpinelPS.csproj b/EpinelPS/EpinelPS.csproj index edb8123..c344cfe 100644 --- a/EpinelPS/EpinelPS.csproj +++ b/EpinelPS/EpinelPS.csproj @@ -16,9 +16,10 @@ - - + + + diff --git a/EpinelPS/Program.cs b/EpinelPS/Program.cs index 9984881..47794f5 100644 --- a/EpinelPS/Program.cs +++ b/EpinelPS/Program.cs @@ -13,6 +13,8 @@ using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Text; using EmbedIO.Files; +using Paseto; +using Paseto.Builder; namespace EpinelPS { diff --git a/ServerSelector.Desktop/Program.cs b/ServerSelector.Desktop/Program.cs index a151eff..35d8a79 100644 --- a/ServerSelector.Desktop/Program.cs +++ b/ServerSelector.Desktop/Program.cs @@ -1,5 +1,4 @@ using Avalonia; -using Avalonia.ReactiveUI; using System; namespace ServerSelector.Desktop; @@ -18,6 +17,5 @@ class Program => AppBuilder.Configure() .UsePlatformDetect() .WithInterFont() - .LogToTrace() - .UseReactiveUI(); + .LogToTrace(); } diff --git a/ServerSelector/App.axaml.cs b/ServerSelector/App.axaml.cs index 3e3f94a..f1c7133 100644 --- a/ServerSelector/App.axaml.cs +++ b/ServerSelector/App.axaml.cs @@ -1,8 +1,6 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; - -using ServerSelector.ViewModels; using ServerSelector.Views; namespace ServerSelector; @@ -18,17 +16,11 @@ public partial class App : Application { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { - desktop.MainWindow = new MainWindow - { - DataContext = new MainViewModel() - }; + desktop.MainWindow = new MainWindow(); } else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform) { - singleViewPlatform.MainView = new MainView - { - DataContext = new MainViewModel() - }; + singleViewPlatform.MainView = new MainView(); } base.OnFrameworkInitializationCompleted(); diff --git a/ServerSelector/ServerSelector.csproj b/ServerSelector/ServerSelector.csproj index 94a7b2a..4b8eae1 100644 --- a/ServerSelector/ServerSelector.csproj +++ b/ServerSelector/ServerSelector.csproj @@ -12,9 +12,7 @@ - - diff --git a/ServerSelector/ServerSwitcher.cs b/ServerSelector/ServerSwitcher.cs index 4f2e0d0..7b06588 100644 --- a/ServerSelector/ServerSwitcher.cs +++ b/ServerSelector/ServerSwitcher.cs @@ -10,23 +10,123 @@ namespace ServerSelector private static int GameAssemblySodiumIntegrityFuncHint = 0x5877FFB; private static byte[] GameAssemblySodiumIntegrityFuncOrg = [0xE8, 0xF0, 0x9D, 0x43, 0xFB]; private static byte[] GameAssemblySodiumIntegrityFuncPatch = [0xb0, 0x01, 0x90, 0x90, 0x90]; + private const string HostsStartMarker = "# begin ServerSelector entries"; + private const string HostsEndMarker = "# end ServerSelector entries"; public static bool IsUsingOfficalServer() { var hostsFile = File.ReadAllText("C:\\Windows\\System32\\drivers\\etc\\hosts"); - return !hostsFile.Contains("cloud.nikke-kr.com"); + return !hostsFile.Contains("global-lobby.nikke-kr.com"); } - public static string CheckIntegrity() + public static bool IsOffline() + { + var hostsFile = File.ReadAllText("C:\\Windows\\System32\\drivers\\etc\\hosts"); + return hostsFile.Contains("cloud.nikke-kr.com"); + } + + public static async Task CheckIntegrity(string gamePath, string launcherPath) { if (IsUsingOfficalServer()) return "Official server"; - // TODO + + if (!Directory.Exists(gamePath)) + { + return "Game path does not exist"; + } + + if (!Directory.Exists(launcherPath)) + { + return "Launcher path does not exist"; + } + + if (!File.Exists(Path.Combine(launcherPath, "nikke_launcher.exe"))) + { + return "Launcher path is invalid. Make sure that the game executable exists in the launcher folder"; + } + + string sodiumLib = AppDomain.CurrentDomain.BaseDirectory + "sodium.dll"; + string gameSodium = gamePath + "/nikke_Data/Plugins/x86_64/sodium.dll"; + string gameAssembly = gamePath + "/GameAssembly.dll"; + string sodiumBackup = gameSodium + ".bak"; + string hostsFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers/etc/hosts"); + var CAcert = await File.ReadAllTextAsync(AppDomain.CurrentDomain.BaseDirectory + "myCA.pem"); + + string launcherCertList = launcherPath + "/intl_service/cacert.pem"; + string gameCertList = gamePath + "/nikke_Data/Plugins/x86_64/cacert.pem"; + + var certList1 = await File.ReadAllTextAsync(launcherCertList); + + int goodSslIndex1 = certList1.IndexOf("Good SSL Ca"); + if (goodSslIndex1 == -1) + return "Patch missing"; + + var certList2 = await File.ReadAllTextAsync(gameCertList); + + int goodSslIndex2 = certList2.IndexOf("Good SSL Ca"); + if (goodSslIndex2 == -1) + return "Patch missing"; + + + // TODO: Check sodium lib + // TODO: Check if gameassembly was patched + return "OK"; } - public static async Task SaveCfg(bool useOffical, string gamePath, string launcherPath, string ip) + public static async Task RevertHostsFile() + { + string hostsFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers/etc/hosts"); + var txt = await File.ReadAllTextAsync(hostsFilePath); + + // remove stuff + try + { + + int startIdx = txt.IndexOf(HostsStartMarker); + int endIdx; + if (startIdx == -1) + { + startIdx = txt.IndexOf("cloud.nikke-kr.com"); + } + + string endIndexStr = HostsEndMarker; + if (!txt.Contains(endIndexStr)) + { + // old code, find new line character before start index + for (int i = startIdx - 1; i >= 0; i--) + { + var c = txt[i]; + if (c == '\n') + { + startIdx = i + 1; + break; + } + } + + endIndexStr = "y.io"; + endIdx = txt.IndexOf(endIndexStr) + endIndexStr.Length; + } + else + { + // add/subtract 2 to take into account newline + startIdx = txt.IndexOf(HostsStartMarker) - 2; + endIdx = txt.IndexOf(endIndexStr) + endIndexStr.Length; + } + + txt = txt.Substring(0, startIdx) + txt.Substring(endIdx); + + + await File.WriteAllTextAsync(hostsFilePath, txt); + } + catch + { + + } + } + + public static async Task SaveCfg(bool useOffical, string gamePath, string launcherPath, string ip, bool offlineMode) { string sodiumLib = AppDomain.CurrentDomain.BaseDirectory + "sodium.dll"; string gameSodium = gamePath + "/nikke_Data/Plugins/x86_64/sodium.dll"; @@ -42,40 +142,12 @@ namespace ServerSelector // TODO: allow changing ip address if (useOffical) { - var txt = await File.ReadAllTextAsync(hostsFilePath); - - // remove stuff - try - { - - int startIdx = txt.IndexOf("cloud.nikke-kr.com"); - - // find new line character before start index - for (int i = startIdx - 1; i >= 0; i--) - { - var c = txt[i]; - if (c == '\n') - { - startIdx = i + 1; - break; - } - } - - int endIdx = txt.IndexOf("y.io") + 4; - txt = txt.Substring(0, startIdx) + txt.Substring(endIdx); - - await File.WriteAllTextAsync(hostsFilePath, txt); - } - catch - { - - } - + await RevertHostsFile(); // remove cert X509Store store = new X509Store(StoreName.Root, StoreLocation.LocalMachine); store.Open(OpenFlags.ReadWrite); - store.Remove(new X509Certificate2(X509Certificate2.CreateFromCertFile(AppDomain.CurrentDomain.BaseDirectory + "myCA.pfx"))); + store.Remove(new X509Certificate2(X509Certificate.CreateFromCertFile(AppDomain.CurrentDomain.BaseDirectory + "myCA.pfx"))); store.Close(); // restore sodium @@ -117,17 +189,29 @@ namespace ServerSelector } var certList1 = await File.ReadAllTextAsync(launcherCertList); - await File.WriteAllTextAsync(launcherCertList, certList1.Substring(0, certList1.IndexOf("Good SSL Ca"))); + + int goodSslIndex1 = certList1.IndexOf("Good SSL Ca"); + if (goodSslIndex1 != -1) + await File.WriteAllTextAsync(launcherCertList, certList1.Substring(0, goodSslIndex1)); var certList2 = await File.ReadAllTextAsync(gameCertList); - await File.WriteAllTextAsync(gameCertList, certList2.Substring(0, certList2.IndexOf("Good SSL Ca"))); + + int goodSslIndex2 = certList2.IndexOf("Good SSL Ca"); + if (goodSslIndex2 != -1) + await File.WriteAllTextAsync(gameCertList, certList2.Substring(0, goodSslIndex2)); } else { // add to hosts file - string hosts = $@"{ip} cloud.nikke-kr.com + string hosts = $@"{HostsStartMarker} {ip} global-lobby.nikke-kr.com -{ip} jp-lobby.nikke-kr.com +"; + if (offlineMode) + { + hosts += $"{ip} cloud.nikke-kr.com" + Environment.NewLine; + } + + hosts += $@"{ip} jp-lobby.nikke-kr.com {ip} us-lobby.nikke-kr.com {ip} kr-lobby.nikke-kr.com {ip} sea-lobby.nikke-kr.com @@ -141,13 +225,15 @@ namespace ServerSelector 255.255.221.21 na.fleetlogd.com {ip} www.jupiterlauncher.com {ip} data-aws-na.intlgame.com -255.255.221.21 sentry.io"; +255.255.221.21 sentry.io +{HostsEndMarker}"; + await RevertHostsFile(); if (!(await File.ReadAllTextAsync(hostsFilePath)).Contains("global-lobby.nikke-kr.com")) { using StreamWriter w = File.AppendText(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers/etc/hosts")); w.WriteLine(); - w.WriteLine(hosts); + w.Write(hosts); } diff --git a/ServerSelector/ViewModels/MainViewModel.cs b/ServerSelector/ViewModels/MainViewModel.cs deleted file mode 100644 index 3ddb391..0000000 --- a/ServerSelector/ViewModels/MainViewModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace ServerSelector.ViewModels; - -public class MainViewModel : ViewModelBase -{ - public string Greeting => "Welcome to Avalonia!"; -} diff --git a/ServerSelector/ViewModels/ViewModelBase.cs b/ServerSelector/ViewModels/ViewModelBase.cs deleted file mode 100644 index c1afccd..0000000 --- a/ServerSelector/ViewModels/ViewModelBase.cs +++ /dev/null @@ -1,7 +0,0 @@ -using ReactiveUI; - -namespace ServerSelector.ViewModels; - -public class ViewModelBase : ReactiveObject -{ -} diff --git a/ServerSelector/Views/MainView.axaml b/ServerSelector/Views/MainView.axaml index d9aa0e9..6b6db36 100644 --- a/ServerSelector/Views/MainView.axaml +++ b/ServerSelector/Views/MainView.axaml @@ -3,17 +3,10 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:ServerSelector.ViewModels" - mc:Ignorable="d" d:DesignWidth="370" d:DesignHeight="250" + mc:Ignorable="d" d:DesignWidth="370" d:DesignHeight="300" x:Class="ServerSelector.Views.MainView" - x:DataType="vm:MainViewModel" xmlns:ui="using:FluentAvalonia.UI.Controls" > - - - - - @@ -34,15 +27,17 @@ + + Game path: - C:\NIKKE\NIKKE\game + C:\NIKKE\NIKKE\game Launcher path: - C:\NIKKE\Launcher + C:\NIKKE\Launcher @@ -55,11 +50,13 @@ - IP: + IP: 127.0.0.1 - Status: OK - + Enable offline mode + + Status: OK + diff --git a/ServerSelector/Views/MainView.axaml.cs b/ServerSelector/Views/MainView.axaml.cs index 3a6f648..606849c 100644 --- a/ServerSelector/Views/MainView.axaml.cs +++ b/ServerSelector/Views/MainView.axaml.cs @@ -1,4 +1,5 @@ using Avalonia.Controls; +using Avalonia.Media; using Avalonia.Platform.Storage; using FluentAvalonia.UI.Controls; using System; @@ -17,6 +18,7 @@ public partial class MainView : UserControl CmbServerSelection.SelectedIndex = ServerSwitcher.IsUsingOfficalServer() ? 0 : 1; TxtIpAddress.IsEnabled = !ServerSwitcher.IsUsingOfficalServer(); + ChkOffline.IsChecked = ServerSwitcher.IsOffline(); if (OperatingSystem.IsWindows() && !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { @@ -28,40 +30,108 @@ public partial class MainView : UserControl }; } - LblStatus.Text = "Status: " + ServerSwitcher.CheckIntegrity(); + UpdateIntegrityLabel(); } - private void Save_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + private void SetGamePathValid(bool isValid) { - if (string.IsNullOrEmpty(txtGamePath.Text) || string.IsNullOrEmpty(txtLauncherPath.Text)) + if (isValid) { - ShowWarningMsg("Game path / launcher path is empty", "Error"); - return; + txtGamePath.BorderBrush = null; + } + else + { + txtGamePath.BorderBrush = new SolidColorBrush(Color.FromRgb(255,0,0)); + } + } + private void SetLauncherPathValid(bool isValid) + { + if (isValid) + { + txtLauncherPath.BorderBrush = null; + } + else + { + txtLauncherPath.BorderBrush = new SolidColorBrush(Color.FromRgb(255, 0, 0)); + } + } + + private bool ValidatePaths(bool showMessage) + { + if (string.IsNullOrEmpty(txtGamePath.Text)) + { + SetGamePathValid(false); + if (showMessage) + ShowWarningMsg("Game path is blank", "Error"); + return false; + } + if (string.IsNullOrEmpty(txtLauncherPath.Text)) + { + SetLauncherPathValid(false); + if (showMessage) + ShowWarningMsg("Launcher path is blank", "Error"); + return false; } if (!Directory.Exists(txtGamePath.Text)) { - ShowWarningMsg("Game path folder does not exist", "Error"); - return; + SetGamePathValid(false); + if (showMessage) + ShowWarningMsg("Game path does not exist", "Error"); + return false; } if (!Directory.Exists(txtLauncherPath.Text)) { - ShowWarningMsg("Launcher folder does not exist", "Error"); - return; + SetLauncherPathValid(false); + if (showMessage) + ShowWarningMsg("Launcher path does not exist", "Error"); + return false; } if (!File.Exists(Path.Combine(txtLauncherPath.Text, "nikke_launcher.exe"))) { - ShowWarningMsg("Launcher path is invalid. Make sure that the game executable exists in the launcher folder", "Error"); - return; + SetGamePathValid(false); + if (showMessage) + ShowWarningMsg("Launcher path is invalid. Make sure that the game executable exists in the launcher folder", "Error"); + + return false; } - if (!File.Exists(Path.Combine(txtGamePath.Text, "nikke.exe"))) - { - ShowWarningMsg("Game path is invalid. Make sure that nikke.exe exists in the launcher folder", "Error"); + SetGamePathValid(true); + SetLauncherPathValid(true); + + return true; + } + + private async void UpdateIntegrityLabel() + { + if (!ValidatePaths(false) || txtGamePath.Text == null || txtLauncherPath.Text == null) return; + + SetLoadingScreenVisible(true); + LblStatus.Text = "Status: " + await ServerSwitcher.CheckIntegrity(txtGamePath.Text, txtLauncherPath.Text); + SetLoadingScreenVisible(false); + } + + private void SetLoadingScreenVisible(bool visible) + { + if (visible) + { + MainUI.IsVisible = false; + LoadingUI.IsVisible = true; } + else + { + LoadingUI.IsVisible = false; + MainUI.IsVisible = true; + } + } + + private async void Save_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + if (!ValidatePaths(true) || txtGamePath.Text == null || txtLauncherPath.Text == null) + return; if (CmbServerSelection.SelectedIndex == 1) { @@ -73,26 +143,27 @@ public partial class MainView : UserControl } if (TxtIpAddress.Text == null) TxtIpAddress.Text = ""; - MainUI.IsVisible = false; - LoadingUI.IsVisible = true; - + SetLoadingScreenVisible(true); try { - ServerSwitcher.SaveCfg(CmbServerSelection.SelectedIndex == 0, txtGamePath.Text, txtLauncherPath.Text, TxtIpAddress.Text); + await ServerSwitcher.SaveCfg(CmbServerSelection.SelectedIndex == 0, txtGamePath.Text, txtLauncherPath.Text, TxtIpAddress.Text, ChkOffline.IsChecked ?? false); } catch (Exception ex) { ShowWarningMsg("Failed to save configuration: " + ex.ToString(), "Error"); } - - LoadingUI.IsVisible = false; - MainUI.IsVisible = true; + UpdateIntegrityLabel(); + SetLoadingScreenVisible(false); } private void CmbServerSelection_SelectionChanged(object? sender, SelectionChangedEventArgs e) { if (CmbServerSelection != null) + { TxtIpAddress.IsEnabled = CmbServerSelection.SelectedIndex == 1; + ChkOffline.IsEnabled = CmbServerSelection.SelectedIndex == 1; + LblIp.IsEnabled = CmbServerSelection.SelectedIndex == 1; + } } public static void ShowWarningMsg(string text, string title) @@ -127,6 +198,7 @@ public partial class MainView : UserControl return; } } + UpdateIntegrityLabel(); } } } @@ -156,8 +228,16 @@ public partial class MainView : UserControl return; } } + UpdateIntegrityLabel(); } } } - + private void GamePath_TextChanged(object? sender, Avalonia.Controls.TextChangedEventArgs e) + { + UpdateIntegrityLabel(); + } + private void LauncherPath_TextChanged(object? sender, Avalonia.Controls.TextChangedEventArgs e) + { + UpdateIntegrityLabel(); + } } diff --git a/ServerSelector/Views/MainWindow.axaml b/ServerSelector/Views/MainWindow.axaml index 1643344..28f7b84 100644 --- a/ServerSelector/Views/MainWindow.axaml +++ b/ServerSelector/Views/MainWindow.axaml @@ -4,8 +4,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="clr-namespace:ServerSelector.Views" - mc:Ignorable="d" d:DesignWidth="370" d:DesignHeight="250" + mc:Ignorable="d" d:DesignWidth="370" d:DesignHeight="300" x:Class="ServerSelector.Views.MainWindow" - Title="Server Switcher" Width="370" Height="260" CanResize="False"> + Title="Server Switcher" Width="370" Height="300" CanResize="False"> diff --git a/ServerSelector/myCA.cer b/ServerSelector/myCA.cer new file mode 100644 index 0000000..103d5a5 --- /dev/null +++ b/ServerSelector/myCA.cer @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUcJP1SCl3PCMhEjSXcn1Ps1Wx+2kwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA3MDQxNTAwNDRaFw0yNjEw +MDcxNTAwNDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCl2jEFtcvfIyvmqz0+RgsaijaU6YsWT4N2Xm+UE5SK +k+C5EuGK/hhaFkIOjWHsxtxvVJQuXJcRZSjGhWLllOnvYtaIvKhvqQ/d1w6AhtoR +KdZw76T4v7p+gcV+3OpJOcYpZMHY9N4i4+gg3ZSXjOMqCbzgmpF5Fyv75nFq+HR/ +ZERt29OH6jLul1oAAcHGbfm5eZY9RS5HS3/uADZjhjnt7MURUes6P/xrWb75wcdD +yLaEwN+DzOZ+VvgbpJmS8hSWkm4e6h/vRsIjKTqnvw7dJIhrkeF7juIM8Z7QY96y +CYWME+hHWWEz0M6yg1GDWt7EuVHzzINpSgbZZhvizDgPAgMBAAGjUzBRMB0GA1Ud +DgQWBBTVvYY5ktXDZNPcsQjIoLMOhb88LTAfBgNVHSMEGDAWgBTVvYY5ktXDZNPc +sQjIoLMOhb88LTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBx +cXZMZwV8Q4Lfxm3z9cOlG8zp0b5JujeVqv6OV2p+CQ8Py8/R7/DD+IjpZ2QpKJ74 +UUSuqyyrL8zM21yiksgjYYZVxK4USjjL8kIB28Ml0+UmYtfkKmiXJNWs1gGPt8Q3 +aKLMNAj2aHx2vt1+Eu3iex8Pn9RZFsf/tUmxkara1jfVjEeNG/qjt1iM8yXbeyc0 +bx1y8LJKxWo8alux7ncNnDBF3td1ZchhoSg36IX+i6RTM8j/BNCTx3IiiY3hbfaH +hH3FPD4U/Do9+GjidDFVku8OK8MIggYdD02AqR56tG+Sw2RNP35Ky23HcqUyRK5/ +NkmdLtIH+gwww/0oBb4I +-----END CERTIFICATE-----