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-----